7/19
This commit is contained in:
1 parent
0d8859b063
commit
7b22e3746a
29 files changed
+3949
-193
No files matched your search
@@ -0,0 +1,261 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
Context for Claude Code sessions on this repo (libglacier-ng, the package
|
||||||
|
manager for the Everest/Glacier Linux distribution). This file exists
|
||||||
|
because there's no built-in way to hand off a claude.ai planning
|
||||||
|
conversation into Claude Code — this is that handoff, written down.
|
||||||
|
|
||||||
|
For the symlink-routing / package-scope design specifically, see
|
||||||
|
`PACKAGE_SCOPES.txt` at the repo root — that doc is the source of truth
|
||||||
|
for that topic and isn't duplicated here.
|
||||||
|
|
||||||
|
## What this project is
|
||||||
|
|
||||||
|
A from-scratch Linux distribution (Everest, built on the Glacier
|
||||||
|
package-management model) with its own package manager
|
||||||
|
(`libglacier-ng` + `gpkg`/`gstore` CLI tools) and its own build
|
||||||
|
tooling (`gbuild`/`gworld`) for bootstrapping a set of packages up to
|
||||||
|
a minimal bootable system.
|
||||||
|
|
||||||
|
## Core architecture
|
||||||
|
|
||||||
|
**`gl_context_t`** (transaction.h) is the central abstraction: `uid`,
|
||||||
|
`root_path`, `index_path`, `store_path`, `links_path`, `mode`
|
||||||
|
(`GL_CTX_LIVE` / `GL_CTX_STAGE`), `scope` (`GL_SCOPE_USR` /
|
||||||
|
`GL_SCOPE_SYS`), `lock_fd`.
|
||||||
|
|
||||||
|
**Transaction lifecycle**: `gl_init_live_context` / `gl_init_stage_context`
|
||||||
|
→ `gl_commit_transaction` or `gl_abort_transaction`. Staged installs
|
||||||
|
hardlink-seed `index`/`store` from live at stage-init time (copy-on-write,
|
||||||
|
so removing a live package inside a transaction doesn't require special
|
||||||
|
casing). The `links` tree is deliberately **not** seeded — see below.
|
||||||
|
|
||||||
|
**Commit** does atomic rename-based promotion (stage → live, with the
|
||||||
|
previous live moved to `old/` as a rollback point), fsyncs the parent
|
||||||
|
dirs, then calls `gl_relink_store` to regenerate symlinks and
|
||||||
|
`gl_rebuild_index` to regenerate the index — both from a fresh
|
||||||
|
`GL_CTX_LIVE` context, not the just-committed stage context.
|
||||||
|
|
||||||
|
**Scopes**:
|
||||||
|
- `GL_SCOPE_USR`: per-uid, `/glacier/usr/{index,store,links}/<uid>`.
|
||||||
|
- `GL_SCOPE_SYS`: system-wide, `/glacier/sys/{index,store}`. Symlink
|
||||||
|
destination depends on the package's **repo** — see `PACKAGE_SCOPES.txt`.
|
||||||
|
Short version: `repo == "base"` → `/usr`; anything else →
|
||||||
|
`/glacier/sys/links`. This is decided per-package (in `gl_link_pkg` /
|
||||||
|
`gl_relink_store`), not baked into the context.
|
||||||
|
|
||||||
|
**Repos**: `base` (minimal working system, boot-critical by definition —
|
||||||
|
this is a repo-level classification, not a per-package flag), `extra`
|
||||||
|
(important but not required), `community` (everything else). `extra`
|
||||||
|
and `community` currently behave identically — see open items.
|
||||||
|
|
||||||
|
## Safety mechanisms (read before touching links/symlink code)
|
||||||
|
|
||||||
|
`ctx->links_path` is **not always glacier-exclusive territory** —
|
||||||
|
for system-scope `base` packages it's literally `/usr`, which holds
|
||||||
|
plenty of content glacier has no business touching. Everything that
|
||||||
|
reads or writes a links destination has to treat it as "possibly
|
||||||
|
shared":
|
||||||
|
|
||||||
|
- `is_glacier_symlink(path, store_prefix)` (istoreutils.c) is the one
|
||||||
|
source of truth for "did glacier create this." True only if `path`
|
||||||
|
is a symlink whose target lives under `store_prefix`. Never touch a
|
||||||
|
path this returns false for.
|
||||||
|
- `gl_link_pkg` will not overwrite an existing path unless
|
||||||
|
`is_glacier_symlink` says it's safe to replace.
|
||||||
|
- `gl_relink_store` prunes stale symlinks (`prune_stale_links`) rather
|
||||||
|
than wiping and rebuilding the whole links directory — a full
|
||||||
|
`rm -rf` of `ctx->links_path` would be catastrophic once that path
|
||||||
|
can be `/usr`. It prunes **both** possible system-scope destinations
|
||||||
|
(`/glacier/sys/links` and `/usr`) since a single pass can't know in
|
||||||
|
advance whether any base-repo packages are involved.
|
||||||
|
- The stage links tree is never hardlink-seeded — it's genuinely
|
||||||
|
unused (`gl_link_pkg` only ever runs against `GL_CTX_LIVE`;
|
||||||
|
`gl_relink_store` always builds its own fresh live context). Seeding
|
||||||
|
it would mean hardlinking all of `/usr` on every staged system
|
||||||
|
transaction, and risking `EXDEV` if `/usr` and the stage area are on
|
||||||
|
different filesystems.
|
||||||
|
|
||||||
|
## Bug history (so it doesn't get relitigated or reintroduced)
|
||||||
|
|
||||||
|
Roughly chronological, all already fixed in the current tree:
|
||||||
|
|
||||||
|
1. **`gl_abort_transaction` never actually released the lock file** — a
|
||||||
|
copy-paste bug wrote into the wrong variable. Fixed by moving off
|
||||||
|
`O_EXCL`-based locking entirely: `gl_begin_transaction` now just
|
||||||
|
`open(O_CREAT)` + `flock()`, and lock files are never `unlink()`'d
|
||||||
|
(the kernel releases `flock()` automatically on process death, even
|
||||||
|
`SIGKILL`; explicit unlinking has its own TOCTOU race with a second
|
||||||
|
process creating a new inode at the same path).
|
||||||
|
2. **`EXDEV` wasn't checked before renaming** — `gl_commit_transaction`
|
||||||
|
now `stat()`s both sides and fails cleanly with `GL_TXN_ERR_XDEV`
|
||||||
|
before attempting any renames if stage and live aren't on the same
|
||||||
|
filesystem.
|
||||||
|
3. **Segfault on install**, root cause: the Makefile's `transaction`
|
||||||
|
target didn't link `libglacier_transaction.so` against
|
||||||
|
`libglacier_istoreutils.so`/`libglacier_log.so`, even though
|
||||||
|
`transaction.c` calls `gl_relink_store()` and `lg_printf()`. Fixed
|
||||||
|
by adding those `-l` flags and an explicit `transaction: log
|
||||||
|
istoreutils` prerequisite (build order matters now).
|
||||||
|
4. **Symlinks going stale after commit** — `gl_link_pkg` used to run
|
||||||
|
during staged installs too, embedding the *stage* path as the
|
||||||
|
symlink target. Fixed: `gl_link_pkg` only runs for `GL_CTX_LIVE`;
|
||||||
|
links are fully regenerated post-commit by `gl_relink_store`.
|
||||||
|
5. **Dangling symlinks never cleaned up on removal** — see "Safety
|
||||||
|
mechanisms" above; this is what `prune_stale_links` fixes.
|
||||||
|
6. **`GL_TXN_ERR_RENAME` on every commit after the first** — leftover
|
||||||
|
`old/` directory from a previous commit made the next commit's
|
||||||
|
`rename()` fail with `ENOTEMPTY`. Fixed: `gl_commit_transaction`
|
||||||
|
wipes `old_base` if present before using it.
|
||||||
|
7. **`-Wstringop-truncation` errors** (real bug, not noise) — four
|
||||||
|
`strncpy(dst, ctx->foo_path, PATH_MAX - 1)` calls that could leave
|
||||||
|
`dst` unterminated. Fixed by switching to `snprintf(dst,
|
||||||
|
sizeof(dst), "%s", ...)`, which always terminates.
|
||||||
|
(`-Wformat-truncation`, separately, is suppressed via
|
||||||
|
`-Wno-error=format-truncation` in `config.mk` — those warnings are
|
||||||
|
about genuinely-safe `snprintf` truncation, not a real bug.)
|
||||||
|
8. **`gbuild` build-system autodetection** checked for a bare
|
||||||
|
`Makefile` before `configure`/`configure.ac`. Broke musl (and any
|
||||||
|
project with a hand-rolled, non-autotools build system that ships
|
||||||
|
both) since its `Makefile` is non-functional without `configure`
|
||||||
|
generating `config.mak` first. Fixed by reordering the checks.
|
||||||
|
9. **Index only stored `repo::pkgname`, no version.** Now
|
||||||
|
`repo::pkgname::version` (`gl_rebuild_index` looks up the
|
||||||
|
`<pkgname>-<version>` store subdirectory). `struct gpkg_entry` in
|
||||||
|
`istoreutils.h` needs a `char *ver;` field — this was a manual
|
||||||
|
header edit, double check it's actually present.
|
||||||
|
10. **`gworld` recipe parser silently dropped `SYS_PROFILE`** — the
|
||||||
|
key-matching regex only matched uppercase letters, not
|
||||||
|
underscores. Fixed to `^([%u_][%u%d_]*)=(.*)$`. Caught by an
|
||||||
|
actual precedence test, not code review — worth remembering that
|
||||||
|
class of bug (a field silently never parsing, falling back to a
|
||||||
|
default that happens to look right) is easy to miss by inspection
|
||||||
|
alone.
|
||||||
|
|
||||||
|
## CLI additions
|
||||||
|
|
||||||
|
- `gpkg -s` / `--system`: operate against `GL_SCOPE_SYS` instead of a
|
||||||
|
uid's tree. Requires root (checked via `geteuid()`). Must appear
|
||||||
|
*before* `-l`/`-x` in a grouped flag string (e.g. `-sl`, not `-ls`) —
|
||||||
|
flags are handled in parse order, same constraint `-V`/`-S` already
|
||||||
|
had.
|
||||||
|
- `gstore -N` / `--new-system`: calls `gl_init_sys()`. Requires root.
|
||||||
|
- `gbuild -P` / `--profile PROFILE`: system profile, `ARCH-LIBC[-FEATURE...]`
|
||||||
|
(e.g. `x86_64-musl`, `x86_64-glibc-multilib`). Derives a cross-compile
|
||||||
|
prefix (translating `glibc`→`gnu` per real GNU triplet convention),
|
||||||
|
detects native-vs-cross against the build host, and only pre-fills
|
||||||
|
`-t`/`-x` defaults for whichever the user didn't already set
|
||||||
|
explicitly. Features export as `GL_FEATURE_<NAME>=1` env vars for
|
||||||
|
custom `-b`/`-i` commands to branch on — gbuild itself doesn't
|
||||||
|
hardcode per-feature behavior, that's intentional.
|
||||||
|
|
||||||
|
## Build system
|
||||||
|
|
||||||
|
`Makefile` builds separate `.so`s per module
|
||||||
|
(`libglacier_log.so`, `libglacier_pkg.so`, `libglacier_istoreutils.so`,
|
||||||
|
`libglacier_transaction.so`, `libglacier_dag.so`) — **this split is
|
||||||
|
deliberate Unix-philosophy design**, not an accident: a third-party
|
||||||
|
glacier-compatible program should be able to link only
|
||||||
|
`libglacier_log.so` without pulling in everything else. `gpkg`/`gstore`
|
||||||
|
load the specific per-module `.so`s they need directly, matching this.
|
||||||
|
|
||||||
|
There's also a `libglacier-ng` Makefile target producing a **combined**
|
||||||
|
`.so` — this exists *solely* for the Lua FFI test harness's
|
||||||
|
convenience (one thing to `ffi.load()` instead of juggling rpath
|
||||||
|
chains in a throwaway test process). It is **not** in `all` and **not**
|
||||||
|
installed. Don't reach for it outside `tests/` — that's a sign
|
||||||
|
something should be split-loaded instead.
|
||||||
|
|
||||||
|
`config.mk` holds `CFLAGS`, including `-Wno-error=format-truncation`
|
||||||
|
(see bug #7 above for why that specific warning is suppressed and
|
||||||
|
others aren't).
|
||||||
|
|
||||||
|
## Lua tooling
|
||||||
|
|
||||||
|
- `gpkg`, `gstore`: LuaJIT + FFI, calling directly into the `.so`s.
|
||||||
|
- `lib/glacier_cdef.lua`: single shared source of truth for every
|
||||||
|
`ffi.cdef()` declaration, used by `gpkg`, `gstore`, and the test
|
||||||
|
suite. This used to be duplicated three ways, which is exactly what
|
||||||
|
let a stale signature drift silently into `gpkg` at one point — if
|
||||||
|
you're editing a C function signature, this file needs the matching
|
||||||
|
update, and it's the *only* place that needs it now.
|
||||||
|
- `tests/*.lua`: LuaJIT FFI test suite exercising real filesystem/process
|
||||||
|
behavior (`fork`+`SIGKILL` for lock crash-recovery, real `mount()`
|
||||||
|
for the `EXDEV` check, etc.) — not mocked. Run via:
|
||||||
|
```sh
|
||||||
|
cd tests
|
||||||
|
make -C .. libglacier-ng # builds the test-only combined .so
|
||||||
|
GLACIER_TEST_CONFIRM=yes LIBGLACIER_SO=$(pwd)/../build/lib/shared/libglacier-ng.so luajit run_all.lua
|
||||||
|
```
|
||||||
|
`test_exdev.lua` needs `--cap-add=SYS_ADMIN` in Docker to actually
|
||||||
|
exercise the mount path; it skips cleanly (not a failure) without it.
|
||||||
|
|
||||||
|
## Build automation (bootstrap toolchain)
|
||||||
|
|
||||||
|
- `gbuild` (bash): single-package builder. Autodetects build system
|
||||||
|
(cmake/meson/cargo/go/autotools/make), supports cross-compilation
|
||||||
|
(`-t`/`-x`, now also `-P` for profiles).
|
||||||
|
- `gworld` (Lua): batch orchestrator. Reads `recipes/*.recipe`,
|
||||||
|
topologically sorts by `DEPS`, invokes `gbuild` once per package in
|
||||||
|
order. `-s SYSROOT` gives every package in the batch a shared
|
||||||
|
staging dir — relies on `gbuild -p` never wiping its target, so
|
||||||
|
headers/libs accumulate across the batch for free.
|
||||||
|
- Recipe format: `NAME VER REPO URL/REF-or-SRC DEPS BUILD INSTALL
|
||||||
|
FLAGS SYS_PROFILE` (plain `key=value`, see `recipes/musl.recipe` for
|
||||||
|
a real example).
|
||||||
|
- Real multi-pass toolchain bootstraps (LFS-style — gcc built twice,
|
||||||
|
etc.) should use separate recipes per pass (`gcc-pass1`,
|
||||||
|
`gcc-pass2`), publishing only the final pass under the real
|
||||||
|
name/repo. The DAG models build steps, not abstract packages.
|
||||||
|
- **`grootstrap` needs a rebuild, not yet done.** The old design (raw
|
||||||
|
`tar` extraction into a target dir + chroot + register everything
|
||||||
|
under an arbitrary bootstrap uid) predates the `GL_SCOPE_SYS` +
|
||||||
|
base-repo design and is now more complicated than necessary. Once
|
||||||
|
`gpkg`/`gstore` themselves exist on the target (still an unsolved
|
||||||
|
bootstrapping-the-bootstrapper problem — how do they get there
|
||||||
|
before a working package manager exists to install them with),
|
||||||
|
every package including `base` ones can go through the same `gpkg
|
||||||
|
-s -l` path; the repo alone routes symlinks correctly. No more
|
||||||
|
special-casing base packages during bootstrap.
|
||||||
|
|
||||||
|
## Known open items / stubs
|
||||||
|
|
||||||
|
- `gpkg -f` / `-u` (merge/update) are unimplemented stubs. `-f`
|
||||||
|
references an undefined `uid` variable (should be `uidn`) — will
|
||||||
|
nil-concatenation-error the moment it's actually implemented.
|
||||||
|
- `gpkg -x`'s confirmation summary shows placeholder `unknown::pkg
|
||||||
|
0.0.0 (unknown)` since it has no local manifest to read version/repo
|
||||||
|
from for a bare package name. Would need `resolve_repo` called
|
||||||
|
before building the summary, not just before the removal itself.
|
||||||
|
- `gpkg -x` doesn't print a completion message on success (`-l` does:
|
||||||
|
"Completed with no errors."). Cosmetic inconsistency.
|
||||||
|
- Reinstalling an up-to-date package double-logs ("Installed X" +
|
||||||
|
"staged from X" for the same event).
|
||||||
|
- `extra` vs `community` repos don't currently diverge in behavior —
|
||||||
|
both just mean "not base" for symlink-routing purposes.
|
||||||
|
- `gl_relink_store`/`gl_rebuild_index` are full rescans of the store on
|
||||||
|
every commit — fine at current scale, worth revisiting if large
|
||||||
|
transactions become common (a dirty-set of touched packages instead
|
||||||
|
of full rescans).
|
||||||
|
- Package manifests don't record which `SYS_PROFILE` they were built
|
||||||
|
under — deliberately not added yet (would need a `pkg.c` /
|
||||||
|
`gl_gpm2gpkg` change), flagged as a future option, not a gap that
|
||||||
|
needs fixing now.
|
||||||
|
- A stray nested `lib/glacier/lib/glacier/` directory was spotted once
|
||||||
|
in a `tree` listing — looked like a leftover `make install`
|
||||||
|
artifact, not in any load path, harmless but worth a `rm -rf`
|
||||||
|
eventually.
|
||||||
|
|
||||||
|
## Conventions worth knowing before editing
|
||||||
|
|
||||||
|
- C source generally uses tabs for indentation, though the codebase is
|
||||||
|
inconsistent about it in places (some blocks use spaces). ALWAYS USE TABS WHEN POSSIBLE.
|
||||||
|
- `PATH_MAX`-sized stack buffers + `snprintf` is the standard pattern
|
||||||
|
for path construction throughout; prefer it over `strncpy` for
|
||||||
|
anything copying into a fixed buffer (see bug #7).
|
||||||
|
- Don't add convenience wrappers that silently touch a broader
|
||||||
|
filesystem scope than what's asked — this codebase has already been
|
||||||
|
bitten twice by exactly that class of mistake (`gl_relink_store`'s
|
||||||
|
wipe, `gl_link_pkg`'s blind `unlink`), both only becoming dangerous
|
||||||
|
once a path assumption (`links_path` is glacier-exclusive) quietly
|
||||||
|
stopped holding.
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
include config.mk
|
include config.mk
|
||||||
|
|
||||||
.PHONY: all prepare log log_test rt pkg istoreutils make_conf dag install clean static libglacier.a install_static
|
.PHONY: all prepare log log_test rt pkg istoreutils make_conf dag install clean static libglacier.a install_static libglacier-ng sha256 gsha256sum libglacier_verify
|
||||||
|
|
||||||
all: prepare log log_test rt pkg istoreutils make_conf dag
|
all: prepare log log_test rt pkg transaction istoreutils make_conf dag sha256 libglacier_verify gsha256sum
|
||||||
@echo "libglacier-ng has finished building"
|
@echo "libglacier-ng has finished building"
|
||||||
|
|
||||||
static: prepare log_static pkg_static istoreutils_static libglacier.a
|
static: prepare log_static pkg_static istoreutils_static libglacier.a
|
||||||
@@ -29,6 +29,7 @@ prepare:
|
|||||||
mkdir -p build/lib/tmp
|
mkdir -p build/lib/tmp
|
||||||
mkdir -p build/lib/shared
|
mkdir -p build/lib/shared
|
||||||
mkdir -p build/include/glacier
|
mkdir -p build/include/glacier
|
||||||
|
mkdir -p build/bin
|
||||||
|
|
||||||
log:
|
log:
|
||||||
$(CC) $(CFLAGS) src/log.c -c -o build/lib/tmp/log.o
|
$(CC) $(CFLAGS) src/log.c -c -o build/lib/tmp/log.o
|
||||||
@@ -40,6 +41,22 @@ log_test:
|
|||||||
rt:
|
rt:
|
||||||
$(CC) $(CFLAGS) src/runtime.c -c -o build/lib/tmp/runtime.o
|
$(CC) $(CFLAGS) src/runtime.c -c -o build/lib/tmp/runtime.o
|
||||||
|
|
||||||
|
sha256:
|
||||||
|
$(CC) $(CFLAGS) src/sha256.c -c -o build/lib/tmp/sha256.o
|
||||||
|
|
||||||
|
libglacier_verify:
|
||||||
|
$(CC) $(CFLAGS) -shared -fPIC src/sha256.c \
|
||||||
|
-o build/lib/shared/libglacier_verify.so
|
||||||
|
|
||||||
|
gsha256sum: prepare
|
||||||
|
$(CC) $(CFLAGS) src/gsha256sum.c src/sha256.c -o build/bin/gsha256sum
|
||||||
|
|
||||||
|
transaction: log istoreutils
|
||||||
|
$(CC) $(CFLAGS) -shared -fPIC src/transaction.c \
|
||||||
|
-Lbuild/lib/shared -lglacier_istoreutils -lglacier_log \
|
||||||
|
-Wl,-rpath,'$$ORIGIN' \
|
||||||
|
-o build/lib/shared/libglacier_transaction.so
|
||||||
|
|
||||||
istoreutils: pkg
|
istoreutils: pkg
|
||||||
$(CC) $(CFLAGS) -shared -fPIC src/istoreutils.c \
|
$(CC) $(CFLAGS) -shared -fPIC src/istoreutils.c \
|
||||||
-Lbuild/lib/shared -lglacier_log -lglacier_pkg -larchive \
|
-Lbuild/lib/shared -lglacier_log -lglacier_pkg -larchive \
|
||||||
@@ -55,6 +72,12 @@ pkg:
|
|||||||
make_conf:
|
make_conf:
|
||||||
$(CC) $(CFLAGS) src/make_conf.c -c -o build/lib/tmp/make_conf.o
|
$(CC) $(CFLAGS) src/make_conf.c -c -o build/lib/tmp/make_conf.o
|
||||||
|
|
||||||
|
libglacier-ng: prepare
|
||||||
|
$(CC) $(CFLAGS) -shared -fPIC \
|
||||||
|
src/log.c src/pkg.c src/transaction.c src/istoreutils.c \
|
||||||
|
-larchive -lconfig \
|
||||||
|
-o build/lib/shared/libglacier-ng.so
|
||||||
|
|
||||||
dag:
|
dag:
|
||||||
$(CC) $(CFLAGS) -shared -fPIC src/dag.c \
|
$(CC) $(CFLAGS) -shared -fPIC src/dag.c \
|
||||||
-Lbuild/lib/shared -lglacier_log \
|
-Lbuild/lib/shared -lglacier_log \
|
||||||
@@ -65,8 +88,10 @@ install:
|
|||||||
mkdir -p $(PREFIX)/lib/glacier/
|
mkdir -p $(PREFIX)/lib/glacier/
|
||||||
install build/lib/shared/libglacier_log.so $(PREFIX)/lib/glacier/ -m 755
|
install build/lib/shared/libglacier_log.so $(PREFIX)/lib/glacier/ -m 755
|
||||||
install build/lib/shared/libglacier_istoreutils.so $(PREFIX)/lib/glacier/ -m 755
|
install build/lib/shared/libglacier_istoreutils.so $(PREFIX)/lib/glacier/ -m 755
|
||||||
|
install build/lib/shared/libglacier_transaction.so $(PREFIX)/lib/glacier/ -m 755
|
||||||
install build/lib/shared/libglacier_pkg.so $(PREFIX)/lib/glacier -m 755
|
install build/lib/shared/libglacier_pkg.so $(PREFIX)/lib/glacier -m 755
|
||||||
install build/lib/shared/libglacier_dag.so $(PREFIX)/lib/glacier -m 755
|
install build/lib/shared/libglacier_verify.so $(PREFIX)/lib/glacier -m 755
|
||||||
|
install build/bin/gsha256sum $(PREFIX)/bin/ -m 755
|
||||||
|
|
||||||
install_static:
|
install_static:
|
||||||
mkdir -p $(PREFIX)/lib/glacier
|
mkdir -p $(PREFIX)/lib/glacier
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
+------------------------------------+
|
||||||
|
| Package scopes and symlink routing |
|
||||||
|
+------------------------------------+
|
||||||
|
|
||||||
|
This document records the design decisions behind how glacier packages
|
||||||
|
are scoped, how their repo determines where their symlinks land, and
|
||||||
|
what "boot-critical" means in this system. It exists because these
|
||||||
|
rules aren't obvious from reading the code alone, and getting them
|
||||||
|
wrong (particularly the symlink-placement rule) has real consequences
|
||||||
|
up to and including destroying a live /usr.
|
||||||
|
|
||||||
|
|
||||||
|
+------------------------------------+
|
||||||
|
| 1. The two scopes |
|
||||||
|
+------------------------------------+
|
||||||
|
|
||||||
|
USR (GL_SCOPE_USR)
|
||||||
|
Per-uid packages. Index/store/links all live under a specific
|
||||||
|
uid's tree:
|
||||||
|
/glacier/usr/index/<uid>
|
||||||
|
/glacier/usr/store/<uid>
|
||||||
|
/glacier/usr/links/<uid>
|
||||||
|
Symlinks always land under /glacier/usr/links/<uid>/{bin,...}.
|
||||||
|
There is no base-repo exception for USR scope. Nothing installed
|
||||||
|
under a per-user scope is ever considered part of the base system,
|
||||||
|
so nothing here ever touches /usr.
|
||||||
|
|
||||||
|
SYS (GL_SCOPE_SYS)
|
||||||
|
System-wide packages, not owned by any particular uid:
|
||||||
|
/glacier/sys/index
|
||||||
|
/glacier/sys/store
|
||||||
|
Symlink destination depends on the package's repo (see section 3).
|
||||||
|
Requires root. gpkg enforces this via geteuid() before it will
|
||||||
|
even attempt a system-scope operation.
|
||||||
|
|
||||||
|
|
||||||
|
+------------------------------------+
|
||||||
|
| 2. Repo semantics |
|
||||||
|
+------------------------------------+
|
||||||
|
|
||||||
|
A package's repo (PKG_REPO in its manifest) is not just a label for
|
||||||
|
organizing the store — under system scope, it is the one thing that
|
||||||
|
decides where that package's activation symlinks physically land.
|
||||||
|
|
||||||
|
base
|
||||||
|
Packages that make up a minimal working system. ALL packages
|
||||||
|
in this repo are considered boot-critical BY DEFINITION — this
|
||||||
|
is a repo-level classification, not a per-package flag. A
|
||||||
|
package doesn't have to be individually essential for booting
|
||||||
|
to count; if it's filed under base, it's treated as part of
|
||||||
|
the base system, full stop.
|
||||||
|
|
||||||
|
extra
|
||||||
|
Software that may be important but is not necessary to run
|
||||||
|
Everest, nor to install it.
|
||||||
|
|
||||||
|
community
|
||||||
|
Everything else.
|
||||||
|
|
||||||
|
NOTE: extra and community are currently NOT distinguished by the
|
||||||
|
library itself — both simply mean "not base," and both route to
|
||||||
|
the same default system-scope links location. If they need to
|
||||||
|
diverge further later (different confirmation prompts, different
|
||||||
|
trust handling, whatever), that is a deliberate future change, not
|
||||||
|
something already implemented.
|
||||||
|
|
||||||
|
|
||||||
|
+------------------------------------+
|
||||||
|
| 3. Symlink placement rule |
|
||||||
|
+------------------------------------+
|
||||||
|
|
||||||
|
scope == USR -> /glacier/usr/links/<uid>
|
||||||
|
scope == SYS && repo == "base" -> /usr (*)
|
||||||
|
scope == SYS && repo != "base" -> /glacier/sys/links
|
||||||
|
|
||||||
|
(*) This is the base-system exception. Packages that are part of the
|
||||||
|
minimal working system get their symlinks placed directly into
|
||||||
|
/usr/bin, /usr/lib, etc. — real FHS locations — so that anything
|
||||||
|
with a hardcoded path (init scripts, systemd units, shebang lines)
|
||||||
|
works without needing glacier-aware PATH setup.
|
||||||
|
|
||||||
|
Everything an operator installs under system scope that ISN'T
|
||||||
|
part of the base system stays isolated under /glacier/sys/links,
|
||||||
|
same as it always has. The base system is the exception to the
|
||||||
|
norm, not the other way around.
|
||||||
|
|
||||||
|
This decision is made per-package, at the point where a package's repo
|
||||||
|
is actually known (inside gl_link_pkg / gl_unlink_pkg / the walk loop
|
||||||
|
in gl_relink_store) — NOT baked into the context at creation time.
|
||||||
|
gl_context_t's links_path field always holds the DEFAULT destination
|
||||||
|
for that scope; the /usr override is computed separately per package.
|
||||||
|
|
||||||
|
|
||||||
|
+------------------------------------+
|
||||||
|
| 4. Why /usr as a target is safe |
|
||||||
|
+------------------------------------+
|
||||||
|
|
||||||
|
/usr is not glacier-exclusive territory the way /glacier/sys/links is.
|
||||||
|
It holds plenty of content glacier has no business touching. Every
|
||||||
|
place that reads or writes into a links destination therefore treats
|
||||||
|
that destination as "possibly shared, not owned":
|
||||||
|
|
||||||
|
- is_glacier_symlink(path, store_prefix) is the single source of
|
||||||
|
truth for "did glacier itself create this." It reports true only
|
||||||
|
if `path` is a symlink whose target lives under the relevant
|
||||||
|
store path. Anything else — a real file, a directory, a foreign
|
||||||
|
symlink pointing somewhere else entirely — is never touched.
|
||||||
|
|
||||||
|
- gl_link_pkg refuses to overwrite an existing path unless
|
||||||
|
is_glacier_symlink says it's safe to replace. It will not clobber
|
||||||
|
a foreign file that happens to occupy the same path.
|
||||||
|
|
||||||
|
- gl_relink_store no longer wipes and rebuilds its links directory
|
||||||
|
wholesale (that was safe only when the target was glacier-only
|
||||||
|
territory). It prunes ONLY stale symlinks glacier itself created
|
||||||
|
(identified via is_glacier_symlink, removed only if their store
|
||||||
|
target no longer exists) and leaves everything else alone.
|
||||||
|
|
||||||
|
- A single system-scope relink pass prunes BOTH possible
|
||||||
|
destinations (/glacier/sys/links and /usr), since it can't know in
|
||||||
|
advance whether any base-repo packages are involved without
|
||||||
|
checking both.
|
||||||
|
|
||||||
|
- The stage tree's links directory is no longer hardlink-seeded at
|
||||||
|
all. It was always unused (gl_link_pkg only ever runs against a
|
||||||
|
LIVE context; gl_relink_store always builds its own fresh live
|
||||||
|
context rather than touching a staged one) — and now that live
|
||||||
|
links_path can be /usr, seeding it would mean hardlinking the
|
||||||
|
entire /usr tree on every staged system transaction, and risking
|
||||||
|
EXDEV outright if /usr and the stage area are on different
|
||||||
|
filesystems.
|
||||||
|
|
||||||
|
|
||||||
|
+------------------------------------+
|
||||||
|
| 5. Updating base-system packages |
|
||||||
|
+------------------------------------+
|
||||||
|
|
||||||
|
Base-repo packages are tracked in the system index like anything else,
|
||||||
|
and updating them works exactly the same way updating any other
|
||||||
|
package does — same staged transaction, same commit/rollback. No
|
||||||
|
separate mechanism was built for this, because none was needed:
|
||||||
|
|
||||||
|
gpkg -s newmusl.gpkg
|
||||||
|
|
||||||
|
...updates musl in place under system scope, symlinks and all,
|
||||||
|
whether it was originally installed by a bootstrap tool or by a normal
|
||||||
|
`gpkg -s` call later. There is no meaningful distinction between
|
||||||
|
"how it originally got there" and "how you update it."
|
||||||
|
|
||||||
|
Replacing something as fundamental as libc on a LIVE running system is
|
||||||
|
safe under this model specifically because of POSIX unlink semantics:
|
||||||
|
a process that already has a shared object open keeps working off the
|
||||||
|
old inode even after the symlink swap happens. Only newly-spawned
|
||||||
|
processes after the swap see the new version. This falls directly out
|
||||||
|
of the atomic-rename commit design already in place — nothing extra
|
||||||
|
was added to make it work.
|
||||||
|
|
||||||
|
|
||||||
|
+------------------------------------+
|
||||||
|
| 6. Open items |
|
||||||
|
+------------------------------------+
|
||||||
|
|
||||||
|
- grootstrap has not been rebuilt against this design yet. The
|
||||||
|
simplification this design enables: once gpkg/gstore themselves
|
||||||
|
exist on the target (still a bootstrapping problem that needs
|
||||||
|
solving separately), EVERY package — base included — can go
|
||||||
|
through the same `gpkg -s -l` path. There is no longer a need for
|
||||||
|
a separate raw-extraction special-case for base packages
|
||||||
|
specifically; the repo alone routes them to /usr correctly.
|
||||||
|
|
||||||
|
- extra vs community currently behave identically (see section 2).
|
||||||
|
Whether they should diverge, and how, is undecided.
|
||||||
|
|
||||||
|
- The bootstrapping-a-bootstrapper problem (getting gpkg/gstore onto
|
||||||
|
a target filesystem before there's a working package manager to
|
||||||
|
install them with) is still unsolved and orthogonal to everything
|
||||||
|
in this document.
|
||||||
@@ -0,0 +1,379 @@
|
|||||||
|
+--------------------------------------------+
|
||||||
|
| Package integrity & scope security |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
This document records design decisions made in one planning session,
|
||||||
|
covering package integrity verification and the hard separation
|
||||||
|
between per-user and system-scope package tools. NONE OF THIS IS
|
||||||
|
IMPLEMENTED YET. It exists so implementation can start from a settled
|
||||||
|
plan instead of re-deriving it.
|
||||||
|
|
||||||
|
Companion document: PACKAGE_SCOPES.txt (repo/scope symlink routing —
|
||||||
|
referenced but not repeated here).
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 1. Scope of this phase |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
Two different guarantees are in play, and they are NOT the same thing:
|
||||||
|
|
||||||
|
integrity - has this file been corrupted or altered
|
||||||
|
authenticity - did this actually come from who it claims to be
|
||||||
|
|
||||||
|
A hash alone only ever proves integrity. If the hash travels with the
|
||||||
|
file from the same untrusted source, tampering both together defeats
|
||||||
|
it. This phase is INTEGRITY ONLY. Authenticity (signing, a trusted
|
||||||
|
keyring) is a deliberate future phase, tied to remote-repo support —
|
||||||
|
not being designed in detail now, but a few choices below are made to
|
||||||
|
stay cheap to extend into later.
|
||||||
|
|
||||||
|
Everest is a security-focused distro (this is also *why* per-user
|
||||||
|
package isolation exists in the first place). Two sides of the CIA
|
||||||
|
triad — confidentiality is out of scope here, but integrity and
|
||||||
|
(eventually) authenticity are both treated as first-class, not
|
||||||
|
best-effort.
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 2. Hash algorithm: SHA-256 |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
Decision: SHA-256, not SHA-512.
|
||||||
|
|
||||||
|
Rationale (SHA-512 was seriously considered, not dismissed by default):
|
||||||
|
- SHA-512's usual advantage (faster on 64-bit CPUs, due to 64-bit
|
||||||
|
word operations) does not hold across this project's actual
|
||||||
|
target set. Everest explicitly supports multiple architectures
|
||||||
|
via SYS_PROFILE, including 32-bit ones — on a 32-bit target,
|
||||||
|
SHA-512 is SLOWER than SHA-256 (64-bit math has to be emulated).
|
||||||
|
- SHA-256's 128-bit collision resistance is not a meaningful
|
||||||
|
weakness for this use case. There is no realistic attack path
|
||||||
|
where 128 bits is the bottleneck for "did this archive get
|
||||||
|
corrupted or tampered with."
|
||||||
|
- SHA-256 is what every comparable tool already defaults to (apk,
|
||||||
|
pacman, dpkg), and is the simpler implementation to vendor and
|
||||||
|
audit (smaller constant tables, no 64-bit rotates).
|
||||||
|
|
||||||
|
Escape hatch, explicitly kept open: hash values are namespaced as
|
||||||
|
"sha256:<hex>", never a bare hex string, specifically so a future
|
||||||
|
algorithm change is additive, not a format migration. "sha256 can
|
||||||
|
always be added to base if needed" — i.e. if the vendored
|
||||||
|
implementation is ever found lacking, the fallback is depending on a
|
||||||
|
real sha256sum binary once one exists in the base repo, not a redesign
|
||||||
|
of the sidecar format.
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 3. What gets hashed, and how |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
WHAT: whole-archive only, for now. Per-file hashing (and the
|
||||||
|
eventual `gpkg --verify`-style installed-file audit it would enable)
|
||||||
|
is explicitly deferred, not rejected.
|
||||||
|
|
||||||
|
WHERE: a sidecar file next to the .gpkg —
|
||||||
|
|
||||||
|
musl-1.2.5.gpkg
|
||||||
|
musl-1.2.5.gpkg.sha256 <- contains exactly: sha256:<hex>
|
||||||
|
|
||||||
|
Sidecar was chosen over embedding in the manifest specifically because
|
||||||
|
a whole-archive hash cannot cleanly be embedded inside the file it is
|
||||||
|
hashing without excluding the hash field itself from the computation.
|
||||||
|
This also generalizes naturally to a future remote repo index that
|
||||||
|
just lists hashes for everything it serves.
|
||||||
|
|
||||||
|
IMPLEMENTATION SHAPE: one shared vendored implementation, compiled
|
||||||
|
two different ways, so there is exactly one place the algorithm
|
||||||
|
itself is ever implemented:
|
||||||
|
|
||||||
|
- src/sha256.c / src/sha256.h
|
||||||
|
Streaming API (init/update/final), not just "hash this file" —
|
||||||
|
chosen now specifically so per-file hashing later reuses the
|
||||||
|
same primitive without a rewrite.
|
||||||
|
|
||||||
|
- gpkg / syspkg side:
|
||||||
|
Compiled into a PRIVATE .so (no public header, not part of the
|
||||||
|
split-library public API surface). Loaded directly via FFI from
|
||||||
|
gpkg_common.lua (see section 6). Private for now because this is
|
||||||
|
new, unaudited code; promoting it to a real public
|
||||||
|
libglacier_crypto.so is the natural path once per-file hashing
|
||||||
|
is built and the API has proven itself.
|
||||||
|
|
||||||
|
- gbuild side:
|
||||||
|
Compiled into a tiny standalone CLI helper, `gsha256sum FILE`,
|
||||||
|
printing `sha256:<hex>` to stdout. gbuild shells out to it the
|
||||||
|
same way it already shells out to git/tar/make. This keeps
|
||||||
|
gbuild from depending on the host having sha256sum or openssl
|
||||||
|
installed, while still not needing gbuild to link against the
|
||||||
|
private .so directly (gbuild is a bash script, not something
|
||||||
|
that can drive FFI).
|
||||||
|
|
||||||
|
gbuild change: after producing the .gpkg archive, hash it with
|
||||||
|
gsha256sum and write the sidecar alongside it. This is not optional
|
||||||
|
groundwork — the moment verification is turned on, EVERYTHING
|
||||||
|
becomes a "missing sidecar" failure until this exists, so the two
|
||||||
|
changes (gbuild writes sidecars / gpkg+syspkg check them) ship as
|
||||||
|
ONE atomic change, not sequential phases.
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 4. Refusal policy |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
Missing sidecar and hash mismatch are treated IDENTICALLY. There is no
|
||||||
|
soft "unverified, proceed anyway by default" tier — absence of proof
|
||||||
|
is treated the same as proof of tampering.
|
||||||
|
|
||||||
|
repo == "base" -> ALWAYS refuse. No flag, no
|
||||||
|
scope, no exception. Ever.
|
||||||
|
scope == SYS (via syspkg) -> ALWAYS refuse, for ANY repo,
|
||||||
|
including extra/community.
|
||||||
|
No flag overrides this.
|
||||||
|
scope == USR && repo != base -> refuse by default. The ONLY
|
||||||
|
place a bypass flag could ever
|
||||||
|
apply, once one exists.
|
||||||
|
|
||||||
|
No bypass flag exists yet. One may be added later, but ONLY for the
|
||||||
|
USR-scope/non-base case above — never for base, never for system
|
||||||
|
scope, regardless of how the flag is invoked.
|
||||||
|
|
||||||
|
Net effect, stated as a real security property: there is no flag
|
||||||
|
combination, present or future, that can install a corrupted base
|
||||||
|
package or a corrupted system-scope package.
|
||||||
|
|
||||||
|
HOOK POINT: gpkg's read_manifest_fields already resolves a package's
|
||||||
|
repo before any transaction machinery runs (it's needed for the
|
||||||
|
existing confirmation summary). Verification belongs right there,
|
||||||
|
before gl_init_stage_context is ever called. A failure aborts that
|
||||||
|
package the same way any other install error already does today — no
|
||||||
|
new failure-handling pattern needed.
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 5. Tool separation: gpkg / gstore / syspkg |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
gpkg and gstore are BOTH strictly per-user tools. Neither has, or will
|
||||||
|
ever have, any code path into GL_SCOPE_SYS. This is a reversal of
|
||||||
|
earlier work in this project (gpkg briefly gained a -s/--system flag
|
||||||
|
and gstore briefly gained -N/--new-system) — both need to come back
|
||||||
|
out. Recorded here explicitly so it isn't missed during
|
||||||
|
implementation: removing capability that already exists in the
|
||||||
|
current tree, not just "don't add it."
|
||||||
|
|
||||||
|
syspkg is a new, separate binary — the SOLE tool with any system-scope
|
||||||
|
capability at all: install, remove, AND store creation/init (what
|
||||||
|
gstore -N used to do). One tool to secure, one tool to audit, one
|
||||||
|
place the privilege boundary has to be enforced.
|
||||||
|
|
||||||
|
Why store creation belongs in syspkg and not "the installer calling
|
||||||
|
gl_init_sys() directly": that would create a second, independent,
|
||||||
|
unaudited entry point into system-scope mutation — the exact problem
|
||||||
|
already solved for install/remove. syspkg owning ALL system-scope
|
||||||
|
mutation, with nothing else able to touch it, is the actual point.
|
||||||
|
|
||||||
|
gstore's existing UID bounds-check behavior (hard-refuses < 1000, even
|
||||||
|
under doas, even as UID 0) is confirmed already working correctly and
|
||||||
|
is being preserved exactly as-is — gstore stays permanently
|
||||||
|
user-scope-only; the fix is removing -N, not touching the bounds
|
||||||
|
check.
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 6. syspkg privilege model |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
syspkg is always run as root. It is invoked via doas (or sudo) —
|
||||||
|
NEVER made setuid. This was an explicit rejected alternative, not an
|
||||||
|
unconsidered default:
|
||||||
|
|
||||||
|
A setuid LuaJIT interpreter is a real anti-pattern. It would inherit
|
||||||
|
environment variables, library search paths, and require() resolution
|
||||||
|
from whatever untrusted shell invoked it — all of which become
|
||||||
|
privilege-escalation surface the instant the process is running as
|
||||||
|
root. This is the same class of problem that has made setuid
|
||||||
|
scripts (shell, Perl, anything with a runtime) a long-standing
|
||||||
|
security no-go.
|
||||||
|
|
||||||
|
syspkg's own responsibility is limited to checking geteuid() == 0 and
|
||||||
|
refusing otherwise. The actual privilege boundary is enforced by doas
|
||||||
|
— a hardened, audited, purpose-built tool for exactly this — not
|
||||||
|
reimplemented inside syspkg itself.
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 7. Shared code: gpkg_common.lua |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
gpkg and syspkg need nearly identical machinery: manifest reading, the
|
||||||
|
summary/confirmation prompt, staged-transaction handling
|
||||||
|
(gl_init_stage_context -> install/remove loop -> commit/abort), and
|
||||||
|
the hash-verification check from section 4. All of that lives in
|
||||||
|
lib/gpkg_common.lua, required by both.
|
||||||
|
|
||||||
|
What stays DISTINCT per-binary:
|
||||||
|
- Which scope each one is even capable of requesting. gpkg's code
|
||||||
|
simply never constructs a GL_SCOPE_SYS context — not "doesn't
|
||||||
|
expose a flag for it," does not have the capability in its code
|
||||||
|
path at all.
|
||||||
|
- The privilege model (only syspkg is ever invoked with elevation).
|
||||||
|
|
||||||
|
This split means the refusal matrix in section 4 lives ONCE in the
|
||||||
|
shared module — it does not need to be written twice or kept in sync
|
||||||
|
across two files. syspkg is simply the only caller that can ever
|
||||||
|
reach the scope == SYS branch of it.
|
||||||
|
|
||||||
|
This decision exists partly because of history in this project: a
|
||||||
|
previously-duplicated ffi.cdef block across three files caused a real,
|
||||||
|
hard-to-diagnose bug. Not repeating that shape here on purpose.
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 8. Base repo cannot install to USR |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
A package in the base repo can NEVER be installed under GL_SCOPE_USR
|
||||||
|
(per-user scope). This is very unlikely to be attempted in practice,
|
||||||
|
but is being hardcoded as a real invariant anyway, not left as an
|
||||||
|
assumption.
|
||||||
|
|
||||||
|
ENFORCEMENT LEVEL: the library itself (gl_install_pkg), not any CLI.
|
||||||
|
scope == GL_SCOPE_USR && repo == GL_BASE_REPO refuses unconditionally,
|
||||||
|
regardless of which caller asked for it.
|
||||||
|
|
||||||
|
Rationale: the whole point of the split-.so architecture is that a
|
||||||
|
third-party tool can be built directly against libglacier-ng without
|
||||||
|
going through gpkg/syspkg at all. A rule that only one CLI happens to
|
||||||
|
respect is not a real guarantee. This must be a property of the
|
||||||
|
package system itself.
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 9. syspkg self-update |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
syspkg will itself ship as a base-repo package (it has to, to be part
|
||||||
|
of the minimal system) and therefore has to be able to update itself
|
||||||
|
while running. Resolved the same way the earlier libc-live-update
|
||||||
|
question was resolved (see PACKAGE_SCOPES.txt section 5): POSIX
|
||||||
|
unlink() semantics already make this safe — a running process keeps
|
||||||
|
its old inode open even after the symlink swap, only newly-spawned
|
||||||
|
processes see the new version. No special self-replace mechanism
|
||||||
|
needed; this falls directly out of the existing atomic-rename commit
|
||||||
|
design.
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 10. Open: the installer / grootstrap |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
STATUS: genuinely undecided, not just unimplemented. The actual
|
||||||
|
process of installing Everest end-to-end is, in the author's own
|
||||||
|
words, "kind of elusive" right now. grootstrap's rework is explicitly
|
||||||
|
BLOCKED on this getting its own dedicated design pass — not something
|
||||||
|
to force an answer on tonight.
|
||||||
|
|
||||||
|
What IS clear:
|
||||||
|
- grootstrap (or whatever the installer turns out to be) cannot be
|
||||||
|
correctly rebuilt until syspkg, gpkg_common.lua, and hash
|
||||||
|
verification all exist — every package a bootstrap installs is
|
||||||
|
base-repo, the strictest tier, so the installer is the FIRST real
|
||||||
|
consumer of all three at once.
|
||||||
|
- Whatever places syspkg itself onto a target filesystem, before
|
||||||
|
syspkg exists there to enforce anything, is definitionally outside
|
||||||
|
the "corrupted base package can never install" guarantee. This is
|
||||||
|
an honest edge, not a flaw — it only closes once the installer's
|
||||||
|
actual shape is designed. Live/install media is presumably the
|
||||||
|
root of trust at that point (if you trust what you booted from,
|
||||||
|
you trust what it lays down) — but this has not been decided, only
|
||||||
|
named as the shape of the problem.
|
||||||
|
|
||||||
|
REFERENCE MATERIAL (from an old installation guide at
|
||||||
|
everestlinux.org/install, read only through its "Syncing the Build
|
||||||
|
Environment" section — earlier material only, deliberately not
|
||||||
|
consulting anything past that point. Describes a previous,
|
||||||
|
possibly-outdated vision of the install process; treat as INPUT to the
|
||||||
|
future design pass, not as decisions already made. No system image
|
||||||
|
tarball concept appears anywhere in this range — that idea is
|
||||||
|
discarded entirely, not merely unmentioned):
|
||||||
|
|
||||||
|
- "system mountpoint" — a target directory (example used:
|
||||||
|
/mnt/everest, with a SYS_MNT env var pointing at it) that the new
|
||||||
|
system's root gets built at, kept separate from the host doing the
|
||||||
|
building. Conceptually the same thing grootstrap's TARGET_DIR
|
||||||
|
already is.
|
||||||
|
|
||||||
|
- Partition layout described around that mountpoint: an EFI system
|
||||||
|
partition at .../boot, an OPTIONAL dedicated partition for
|
||||||
|
/glacier itself (suggested minimum 15 GB), an optional /home
|
||||||
|
partition, and the remainder as root. The optional standalone
|
||||||
|
glacier partition is worth noting specifically — it implies
|
||||||
|
/glacier was originally conceived as something that could be its
|
||||||
|
own filesystem, separate from root, not necessarily just a
|
||||||
|
directory tree living on the root filesystem. Not a decision,
|
||||||
|
just a detail worth not losing.
|
||||||
|
|
||||||
|
- "template index" — a named, downloadable list of packages
|
||||||
|
defining what a given install should contain (the guide's own
|
||||||
|
words: "provides a list of packages which can be merged into a
|
||||||
|
build environment, thereby creating a semi-functional system").
|
||||||
|
The guide frames CHOOSING a template index around a specific set
|
||||||
|
of questions: does the install need 32-bit libraries, will it run
|
||||||
|
proprietary software that can't be recompiled, does it need a
|
||||||
|
specific C library for the hardware, is SELinux wanted, is a
|
||||||
|
non-default init system wanted. Those are essentially the exact
|
||||||
|
axes SYS_PROFILE already encodes (ARCH-LIBC[-FEATURE...]) — this
|
||||||
|
is a real correspondence, not a coincidence to ignore. Worth
|
||||||
|
deciding whether a template index simply IS a SYS_PROFILE plus a
|
||||||
|
recipes/ set under current design, or a separate downstream
|
||||||
|
concept (e.g. a pre-resolved list of already-built package
|
||||||
|
references to fetch, rather than a list of things to build from
|
||||||
|
source). The guide also warns that switching template
|
||||||
|
indexes/profiles after install requires rebuilding most of the
|
||||||
|
system — consistent with SYS_PROFILE being a foundational,
|
||||||
|
not-meant-to-change-casually choice in the current design too.
|
||||||
|
|
||||||
|
- "glacier-bootstrap PATH_TO_TEMPLATE_INDEX /mnt/everest" — a
|
||||||
|
referenced (possibly not-yet-existing-in-current-form) tool
|
||||||
|
described as taking "the specified template index, and
|
||||||
|
bootstrap[ping] a system at the specified directory, using the
|
||||||
|
packages listed within the template index as a guide." This is
|
||||||
|
package-list-driven bootstrapping, not image-based — it lines up
|
||||||
|
with what grootstrap is already trying to be (extract/install a
|
||||||
|
defined set of packages into a target directory), not with
|
||||||
|
anything tarball-shaped.
|
||||||
|
|
||||||
|
None of the above is a decision. It's what exists to react to when the
|
||||||
|
installer gets its own design session — scoped deliberately to only
|
||||||
|
the early, environment-setup half of the old guide for now.
|
||||||
|
|
||||||
|
|
||||||
|
+--------------------------------------------+
|
||||||
|
| 11. Build order for tomorrow |
|
||||||
|
+--------------------------------------------+
|
||||||
|
|
||||||
|
Dependency order, not necessarily literal implementation order within
|
||||||
|
a day, but grootstrap specifically cannot start until the first three
|
||||||
|
exist:
|
||||||
|
|
||||||
|
1. src/sha256.c / src/sha256.h (no dependencies)
|
||||||
|
2. gsha256sum CLI (needs 1)
|
||||||
|
3. private hash .so for gpkg/syspkg (needs 1)
|
||||||
|
4. gbuild: write sidecar on build (needs 2)
|
||||||
|
5. lib/gpkg_common.lua (shared logic extraction)
|
||||||
|
6. gl_install_pkg: base->USR refusal (library-level, needs nothing
|
||||||
|
above, can happen any time)
|
||||||
|
7. gpkg: remove -s/--system entirely (needs 5, to not duplicate
|
||||||
|
logic while trimming it)
|
||||||
|
8. gstore: remove -N/--new-system entirely
|
||||||
|
9. syspkg: new binary — install/remove/init under GL_SCOPE_SYS,
|
||||||
|
doas-only, no setuid (needs 1, 3, 5)
|
||||||
|
10. grootstrap rework BLOCKED — needs 4, 9, AND a
|
||||||
|
separate installer design
|
||||||
|
pass (section 10). Do not
|
||||||
|
start until that exists.
|
||||||
|
|
||||||
|
Verify-downloads-from-remote-repos (authenticity, signing, keyring)
|
||||||
|
is the explicitly agreed NEXT major item after everything above is
|
||||||
|
implemented — not part of this phase.
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# Security Review — libglacier-ng (dev branch, working tree)
|
||||||
|
|
||||||
|
Date: 2026-07-16
|
||||||
|
Scope: uncommitted working-tree changes (Makefile, config.mk, src/common.h,
|
||||||
|
src/istoreutils.{c,h}, new src/transaction.{c,h}). The `dev` branch had no
|
||||||
|
new commits vs. `origin/dev`, so the actual diff under review is the
|
||||||
|
unstaged/untracked working-tree state.
|
||||||
|
|
||||||
|
All three findings below share one root cause: package manifests
|
||||||
|
(`PKG_NAME` / `PKG_REPO`, read from `manifest.gpm.cfg` inside the `.gpkg`
|
||||||
|
archive being installed) are fully attacker-controlled and are never
|
||||||
|
validated anywhere in the codebase. A full-repo search for
|
||||||
|
`signature|verify|trusted|checksum|sha256|gpg|allowlist` returned zero
|
||||||
|
hits — there is no package authenticity or provenance check at all.
|
||||||
|
|
||||||
|
This PR's changes (`GL_SCOPE_SYS` / `gl_init_sys`, and the new
|
||||||
|
`GL_BASE_REPO` → `/usr` symlink routing) newly plug that untrusted data
|
||||||
|
into root-privileged, system-wide install paths, which is what escalates
|
||||||
|
these from "attacker writes into their own store" to "attacker writes as
|
||||||
|
root, or shadows real `/usr` binaries."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Finding 1 — Authorization bypass via self-declared "base" repo
|
||||||
|
|
||||||
|
* **File:** `src/istoreutils.c:742` (also `:859`, `:1404`)
|
||||||
|
* **Severity:** High
|
||||||
|
* **Category:** `authorization-bypass`
|
||||||
|
* **Confidence:** High
|
||||||
|
|
||||||
|
**Description:** System-scope symlink placement into `/usr` is gated only
|
||||||
|
by a package's manifest self-declaring `PKG_REPO="base"`. There is no
|
||||||
|
check that the package actually originated from a trusted/vetted base
|
||||||
|
repository — the classification is just a string the package author
|
||||||
|
wrote into their own manifest.
|
||||||
|
|
||||||
|
**Exploit scenario:** An operator runs `gpkg -s malicious.gpkg` (a
|
||||||
|
normal, documented system-scope install per `PACKAGE_SCOPES.txt`) on a
|
||||||
|
package whose `manifest.gpm.cfg` the attacker fully controls. Setting
|
||||||
|
`PKG_REPO="base"` causes `gl_link_pkg` (istoreutils.c:742) and
|
||||||
|
`gl_relink_store` (istoreutils.c:1404) to route the package's symlinks
|
||||||
|
into `GL_BASE_LINKS_DEST` (`/usr`) instead of the isolated
|
||||||
|
`/glacier/sys/links` tree — the same privileged, real-FHS locations init
|
||||||
|
scripts, systemd units, and shebang lines trust. Combined with Finding 3
|
||||||
|
(unsanitized `PKG_NAME`), the attacker can target specific paths such as
|
||||||
|
`/usr/bin/sudo`.
|
||||||
|
|
||||||
|
**Recommendation:** Don't trust the manifest's self-declared repo for
|
||||||
|
privilege routing. Either (a) determine repo/trust from the source the
|
||||||
|
package was fetched from (signed repo metadata, not embedded manifest
|
||||||
|
fields), or (b) require a separate signature/checksum check before a
|
||||||
|
package is allowed to claim `base` classification and land in `/usr`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Finding 2 — Tar-slip path traversal during archive extraction
|
||||||
|
|
||||||
|
* **File:** `src/istoreutils.c:1242` (loop starting ~1234, `dest_path` built at 1249)
|
||||||
|
* **Severity:** High
|
||||||
|
* **Category:** `path-traversal`
|
||||||
|
* **Confidence:** High
|
||||||
|
|
||||||
|
**Description:** `gl_install_pkg` extracts the package tar with a
|
||||||
|
hand-rolled `open()`/`mkdir()` loop instead of libarchive's
|
||||||
|
`archive_write_disk` (which offers `ARCHIVE_EXTRACT_SECURE_NODOTDOT` /
|
||||||
|
`SECURE_SYMLINKS` protections). The per-entry relative path (`rel_path`,
|
||||||
|
istoreutils.c:1242) is taken directly from the tar entry name after
|
||||||
|
stripping a known prefix, and is concatenated into `dest_path` via
|
||||||
|
`snprintf` with no rejection of `..` segments or absolute paths.
|
||||||
|
|
||||||
|
**Exploit scenario:** Because the strip prefix is
|
||||||
|
`"<pkg_name>-<ver>/files/"` and `pkg_name` is itself attacker-controlled
|
||||||
|
(pulled verbatim from the manifest, no validation), the attacker fully
|
||||||
|
controls the prefix match and can name a tar entry
|
||||||
|
`"<pkg_name>-<ver>/files/../../../../etc/cron.d/pwn"`. After prefix
|
||||||
|
stripping this becomes `../../../../etc/cron.d/pwn`, written wherever the
|
||||||
|
installing process can write. This code path is reachable for both
|
||||||
|
per-uid and (newly, via this PR) root-privileged system-scope installs,
|
||||||
|
so the same bug now yields root-level arbitrary file write.
|
||||||
|
|
||||||
|
**Recommendation:** Reject any entry path containing `..` components or a
|
||||||
|
leading `/` before extraction, or switch to `archive_write_disk` with
|
||||||
|
`ARCHIVE_EXTRACT_SECURE_NODOTDOT | ARCHIVE_EXTRACT_SECURE_SYMLINKS |
|
||||||
|
ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS` set.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Finding 3 — Path traversal via unsanitized PKG_NAME / PKG_REPO in store paths
|
||||||
|
|
||||||
|
* **File:** `src/istoreutils.c:1196` (manifest parsing: `src/pkg.c:149,153`)
|
||||||
|
* **Severity:** Medium–High
|
||||||
|
* **Category:** `path-traversal`
|
||||||
|
* **Confidence:** High
|
||||||
|
|
||||||
|
**Description:** `gl_gpm2gpkg` (`pkg.c:149,153`) reads `PKG_NAME` and
|
||||||
|
`PKG_REPO` straight out of the archive's manifest config with
|
||||||
|
`config_lookup_string` + `strdup`, no validation. `gl_install_pkg` then
|
||||||
|
builds the package's store directory as `"%s/%s/%s"` from
|
||||||
|
`ctx->store_path`, `pkg.pkg_repo`, `pkg.pkg_name` (istoreutils.c:1196)
|
||||||
|
and `mkdir -p`s it.
|
||||||
|
|
||||||
|
**Exploit scenario:** A manifest with `PKG_REPO="../../../../tmp"` (or a
|
||||||
|
`PKG_NAME` containing `../` segments) causes the package directory —
|
||||||
|
and, via the extraction loop right after (Finding 2), arbitrary files —
|
||||||
|
to be created outside `ctx->store_path` entirely. Previously this only
|
||||||
|
affected a single user's own per-uid store; with this PR's new
|
||||||
|
`GL_SCOPE_SYS` (`ctx->store_path == "/glacier/sys/store"`, root-owned),
|
||||||
|
the same bug now lets an untrusted package write anywhere root can
|
||||||
|
write.
|
||||||
|
|
||||||
|
**Recommendation:** Validate `PKG_NAME` and `PKG_REPO` against an
|
||||||
|
allowlist pattern (e.g. `^[A-Za-z0-9._-]+$`) immediately after parsing
|
||||||
|
in `gl_gpm2gpkg`, rejecting the manifest outright if either field
|
||||||
|
contains `/`, `..`, or is empty.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Note — unrelated working-tree cleanup
|
||||||
|
|
||||||
|
The working tree also contains what look like leftover failed-patch
|
||||||
|
artifacts, not part of the reviewed logic but worth cleaning up before
|
||||||
|
committing:
|
||||||
|
|
||||||
|
```
|
||||||
|
istoreutils.c.diff
|
||||||
|
istoreutils.h.diff
|
||||||
|
src/istoreutils.c.back
|
||||||
|
src/istoreutils.c.rej
|
||||||
|
src/istoreutils.h.back
|
||||||
|
```
|
||||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
PREFIX ?= /usr
|
PREFIX ?= /usr
|
||||||
|
|
||||||
CFLAGS = -std=c11 -pedantic -O2 -flto -Wall -Wextra -Wshadow -Wformat=2 -Wconversion -Wpedantic -Werror
|
CFLAGS = -std=c11 -pedantic -O2 -Wall -Wextra -Wshadow -Wformat=2 -Wconversion -Wpedantic -Werror -Wno-format-truncation
|
||||||
|
|
||||||
TEST_DIR = /home/lw/Projects/glacier-ng/lib
|
TEST_DIR = /home/lw/Projects/glacier-ng/lib
|
||||||
+60
-7
@@ -1,17 +1,17 @@
|
|||||||
#ifndef COMMON_H_
|
#ifndef COMMON_H_
|
||||||
#define COMMON_H_
|
#define COMMON_H_
|
||||||
|
|
||||||
#ifndef _POSIX_C_SOURCE
|
|
||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
#endif
|
|
||||||
#ifndef _DEFAULT_SOURCE
|
|
||||||
#define _DEFAULT_SOURCE
|
|
||||||
#endif
|
|
||||||
#ifndef _XOPEN_SOURCE
|
|
||||||
#define _XOPEN_SOURCE 700
|
#define _XOPEN_SOURCE 700
|
||||||
#endif
|
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <linux/limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
@@ -20,4 +20,57 @@ typedef struct
|
|||||||
}
|
}
|
||||||
dir_t;
|
dir_t;
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
gl_mkdirp(const char *path, mode_t mode)
|
||||||
|
{
|
||||||
|
char tmp[PATH_MAX];
|
||||||
|
strncpy(tmp, path, sizeof(tmp));
|
||||||
|
tmp[sizeof(tmp) - 1] = '\0';
|
||||||
|
|
||||||
|
for (char *p = tmp + 1; *p; p++) {
|
||||||
|
if (*p == '/') {
|
||||||
|
*p = '\0';
|
||||||
|
if (mkdir(tmp, mode) == -1 && errno != EEXIST) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mkdir(tmp, mode) == -1 && errno != EEXIST) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
gl_rmdir_recursive(const char *path)
|
||||||
|
{
|
||||||
|
DIR *dir = opendir(path);
|
||||||
|
if (!dir) { return -1; }
|
||||||
|
|
||||||
|
struct dirent *ent;
|
||||||
|
while ((ent = readdir(dir))) {
|
||||||
|
if (ent->d_name[0] == '.') { continue; }
|
||||||
|
|
||||||
|
char full[PATH_MAX];
|
||||||
|
snprintf(full, sizeof(full), "%s/%s", path, ent->d_name);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (lstat(full, &st) != 0) { continue; }
|
||||||
|
|
||||||
|
if (S_ISDIR(st.st_mode)) {
|
||||||
|
gl_rmdir_recursive(full);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
unlink(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
return rmdir(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "sha256.h"
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
fprintf(stderr, "usage: %s FILE\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char hex[GL_SHA256_HEX_SIZE];
|
||||||
|
if (gl_sha256_file(argv[1], hex) != 0) {
|
||||||
|
perror(argv[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("sha256:%s\n", hex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+401
-178
@@ -21,6 +21,7 @@
|
|||||||
#include "istoreutils.h"
|
#include "istoreutils.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "pkg.h"
|
#include "pkg.h"
|
||||||
|
#include "transaction.h"
|
||||||
|
|
||||||
static const dir_t usr_index = { "/glacier/usr/index", 0700 };
|
static const dir_t usr_index = { "/glacier/usr/index", 0700 };
|
||||||
static const dir_t sys_index = { "/glacier/sys/index", 0700 };
|
static const dir_t sys_index = { "/glacier/sys/index", 0700 };
|
||||||
@@ -29,6 +30,17 @@ static const dir_t usr_store = { "/glacier/usr/store", 0700 };
|
|||||||
static const dir_t sys_store = { "/glacier/sys/store", 0700 };
|
static const dir_t sys_store = { "/glacier/sys/store", 0700 };
|
||||||
|
|
||||||
static const dir_t usr_links = { "/glacier/usr/links", 0700 };
|
static const dir_t usr_links = { "/glacier/usr/links", 0700 };
|
||||||
|
static const dir_t sys_links = { "/glacier/sys/links", 0700 };
|
||||||
|
|
||||||
|
/* Base-system exception: packages in the "base" repo under system
|
||||||
|
* scope get their symlinks placed directly in /usr rather than the
|
||||||
|
* default isolated /glacier/sys/links, so boot/init/scripts that
|
||||||
|
* hardcode /usr/bin, /usr/lib, etc. work without glacier-aware PATH
|
||||||
|
* setup. Everything else installed under system scope (an operator
|
||||||
|
* running `gpkg -s` on something that isn't part of the base system)
|
||||||
|
* stays under /glacier/sys/links. See gl_link_pkg / gl_relink_store. */
|
||||||
|
#define GL_BASE_REPO "base"
|
||||||
|
#define GL_BASE_LINKS_DEST "/usr"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
gl_usr_istore_exists(index_store_t index_or_store, int uid)
|
gl_usr_istore_exists(index_store_t index_or_store, int uid)
|
||||||
@@ -115,6 +127,36 @@ create_index(const char *ind_path, uid_t uid)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_dir(const char *d)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
return stat(d, &st) == 0 && S_ISDIR(st.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* is_glacier_symlink
|
||||||
|
* Returns true only if `path` is a symlink whose target lives under
|
||||||
|
* `store_prefix` — i.e. a symlink glacier itself created, as opposed
|
||||||
|
* to some unrelated file, directory, or foreign symlink that happens
|
||||||
|
* to occupy that path. This distinction only matters once a scope's
|
||||||
|
* links_path can alias a directory glacier doesn't exclusively own
|
||||||
|
* (e.g. /usr for system-scope packages) rather than always being an
|
||||||
|
* isolated glacier-only tree. Everywhere this is used, the rule is the
|
||||||
|
* same: if this returns false, never touch the path — not overwrite
|
||||||
|
* it, not prune it, nothing.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
is_glacier_symlink(const char *path, const char *store_prefix)
|
||||||
|
{
|
||||||
|
char target[PATH_MAX];
|
||||||
|
ssize_t n = readlink(path, target, sizeof(target) - 1);
|
||||||
|
if (n < 0) { return false; } /* not a symlink, or doesn't exist */
|
||||||
|
target[n] = '\0';
|
||||||
|
return strncmp(target, store_prefix, strlen(store_prefix)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
gl_init_user(uid_t uid, bool isVerbose)
|
gl_init_user(uid_t uid, bool isVerbose)
|
||||||
{
|
{
|
||||||
@@ -253,6 +295,46 @@ gl_init_user(uid_t uid, bool isVerbose)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
gl_init_sys(bool isVerbose)
|
||||||
|
{
|
||||||
|
const char *paths[] = {
|
||||||
|
sys_index.path,
|
||||||
|
sys_store.path,
|
||||||
|
sys_links.path,
|
||||||
|
};
|
||||||
|
mode_t modes[] = {
|
||||||
|
sys_index.mode,
|
||||||
|
sys_store.mode,
|
||||||
|
sys_links.mode,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (isVerbose) { lg_printf(0, "Creating %s", paths[i]); }
|
||||||
|
|
||||||
|
if (mkdir(paths[i], modes[i]) == -1) {
|
||||||
|
switch (errno) {
|
||||||
|
case EEXIST:
|
||||||
|
break;
|
||||||
|
case EACCES:
|
||||||
|
lg_printf(2, "Cannot create %s: permission denied", paths[i]);
|
||||||
|
return 1;
|
||||||
|
case ENOSPC:
|
||||||
|
lg_printf(2, "Cannot create %s: no space left on device", paths[i]);
|
||||||
|
return 1;
|
||||||
|
case EROFS:
|
||||||
|
lg_printf(2, "Cannot create %s: read-only filesystem", paths[i]);
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
lg_printf(2, "Cannot create %s: mkdir failed", paths[i]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
trim(char *s)
|
trim(char *s)
|
||||||
{
|
{
|
||||||
@@ -313,17 +395,34 @@ gl_parse_index(struct gindex *idx, FILE *f)
|
|||||||
|
|
||||||
char *first = strstr(line, "::");
|
char *first = strstr(line, "::");
|
||||||
if (!first) { continue; }
|
if (!first) { continue; }
|
||||||
|
|
||||||
*first = 0;
|
*first = 0;
|
||||||
|
|
||||||
char *repo = line;
|
char *repo = line;
|
||||||
char *pkg = first + 2;
|
char *rest = first + 2;
|
||||||
|
|
||||||
|
char *second = strstr(rest, "::");
|
||||||
|
char *pkg;
|
||||||
|
char *ver;
|
||||||
|
if (second) {
|
||||||
|
*second = 0;
|
||||||
|
pkg = rest;
|
||||||
|
ver = second + 2;
|
||||||
|
} else {
|
||||||
|
/* Older index files may only have repo::pkg with no
|
||||||
|
* version field — tolerate that rather than reject it. */
|
||||||
|
pkg = rest;
|
||||||
|
ver = "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
trim(repo);
|
trim(repo);
|
||||||
trim(pkg);
|
trim(pkg);
|
||||||
|
trim(ver);
|
||||||
|
|
||||||
struct gpkg_entry ent;
|
struct gpkg_entry ent;
|
||||||
ent.repo = dupstr(repo);
|
ent.repo = dupstr(repo);
|
||||||
ent.pkg = dupstr(pkg);
|
ent.pkg = dupstr(pkg);
|
||||||
if (!ent.repo || !ent.pkg) { return -1; }
|
ent.ver = dupstr(ver);
|
||||||
|
if (!ent.repo || !ent.pkg || !ent.ver) { return -1; }
|
||||||
|
|
||||||
struct gpkg_entry *tmp =
|
struct gpkg_entry *tmp =
|
||||||
realloc(idx->entries, sizeof(*idx->entries) * (idx->count + 1));
|
realloc(idx->entries, sizeof(*idx->entries) * (idx->count + 1));
|
||||||
@@ -341,80 +440,19 @@ gl_free_index(struct gindex *idx)
|
|||||||
for (size_t i = 0; i < idx->count; i++) {
|
for (size_t i = 0; i < idx->count; i++) {
|
||||||
free(idx->entries[i].repo);
|
free(idx->entries[i].repo);
|
||||||
free(idx->entries[i].pkg);
|
free(idx->entries[i].pkg);
|
||||||
|
free(idx->entries[i].ver);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(idx->entries);
|
free(idx->entries);
|
||||||
idx->entries = NULL;
|
idx->entries = NULL;
|
||||||
idx->count = 0;
|
idx->count = 0;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
int
|
int
|
||||||
gl_rebuild_index(uid_t uid, const char *out_dir)
|
gl_rebuild_index(gl_context_t *ctx)
|
||||||
{
|
{
|
||||||
char store_path[PATH_MAX];
|
if (!ctx) { return -1; }
|
||||||
snprintf(store_path, sizeof(store_path), "%s/%d", usr_store.path, uid);
|
|
||||||
|
|
||||||
DIR *store_dir = opendir(store_path);
|
DIR *store_dir = opendir(ctx->store_path);
|
||||||
if (!store_dir) return -1;
|
|
||||||
|
|
||||||
FILE *out = fopen(out_dir, "w");
|
|
||||||
if (!out) return -1;
|
|
||||||
|
|
||||||
size_t listed = 0;
|
|
||||||
|
|
||||||
struct dirent *repo_ent;
|
|
||||||
while ((repo_ent = readdir(store_dir))) {
|
|
||||||
if (repo_ent->d_name[0] == '.') { continue; }
|
|
||||||
|
|
||||||
char repo_path[PATH_MAX];
|
|
||||||
snprintf(repo_path, sizeof(repo_path), "%s/%s",
|
|
||||||
store_path, repo_ent->d_name);
|
|
||||||
|
|
||||||
struct stat rst;
|
|
||||||
if (stat(repo_path, &rst) != 0 || !S_ISDIR(rst.st_mode)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
DIR *repo_dir = opendir(repo_path);
|
|
||||||
if (!repo_dir) continue;
|
|
||||||
|
|
||||||
struct dirent *pkg_ent;
|
|
||||||
while ((pkg_ent = readdir(repo_dir))) {
|
|
||||||
if (pkg_ent->d_name[0] == '.') { continue; }
|
|
||||||
|
|
||||||
char pkg_path[PATH_MAX];
|
|
||||||
snprintf(pkg_path, sizeof(pkg_path), "%s/%s",
|
|
||||||
repo_path, pkg_ent->d_name);
|
|
||||||
|
|
||||||
struct stat pst;
|
|
||||||
if (stat(pkg_path, &pst) != 0 || !S_ISDIR(pst.st_mode)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(out, "%s::%s\n",
|
|
||||||
repo_ent->d_name,
|
|
||||||
pkg_ent->d_name);
|
|
||||||
listed++;
|
|
||||||
}
|
|
||||||
closedir(repo_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
rewind(out);
|
|
||||||
fprintf(out, "uid = %d\n", uid);
|
|
||||||
|
|
||||||
closedir(store_dir);
|
|
||||||
fclose(out);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
int
|
|
||||||
gl_rebuild_index(uid_t uid, const char *out_dir)
|
|
||||||
{
|
|
||||||
char store_path[PATH_MAX];
|
|
||||||
snprintf(store_path, sizeof(store_path), "%s/%d", usr_store.path, uid);
|
|
||||||
|
|
||||||
DIR *store_dir = opendir(store_path);
|
|
||||||
if (!store_dir) return -1;
|
if (!store_dir) return -1;
|
||||||
|
|
||||||
/* Write to a new timestamped index file */
|
/* Write to a new timestamped index file */
|
||||||
@@ -422,7 +460,8 @@ gl_rebuild_index(uid_t uid, const char *out_dir)
|
|||||||
make_index_name(fname, sizeof(fname));
|
make_index_name(fname, sizeof(fname));
|
||||||
|
|
||||||
char new_index_path[PATH_MAX];
|
char new_index_path[PATH_MAX];
|
||||||
snprintf(new_index_path, sizeof(new_index_path), "%s/%s", out_dir, fname);
|
snprintf(new_index_path, sizeof(new_index_path), "%s/%s",
|
||||||
|
ctx->index_path, fname);
|
||||||
|
|
||||||
FILE *out = fopen(new_index_path, "w");
|
FILE *out = fopen(new_index_path, "w");
|
||||||
if (!out) {
|
if (!out) {
|
||||||
@@ -438,7 +477,7 @@ gl_rebuild_index(uid_t uid, const char *out_dir)
|
|||||||
|
|
||||||
char repo_path[PATH_MAX];
|
char repo_path[PATH_MAX];
|
||||||
snprintf(repo_path, sizeof(repo_path), "%s/%s",
|
snprintf(repo_path, sizeof(repo_path), "%s/%s",
|
||||||
store_path, repo_ent->d_name);
|
ctx->store_path, repo_ent->d_name);
|
||||||
|
|
||||||
struct stat rst;
|
struct stat rst;
|
||||||
if (stat(repo_path, &rst) != 0 || !S_ISDIR(rst.st_mode)) {
|
if (stat(repo_path, &rst) != 0 || !S_ISDIR(rst.st_mode)) {
|
||||||
@@ -461,9 +500,33 @@ gl_rebuild_index(uid_t uid, const char *out_dir)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(out, "%s::%s\n",
|
/* One version dir per package name, named
|
||||||
|
* "<pkg_name>-<version>" (see gl_cat_dir_and_ver
|
||||||
|
* in pkg.c, which is what created it). Strip the
|
||||||
|
* known "<pkg_name>-" prefix to recover just the
|
||||||
|
* version part — safe even if pkg_name itself
|
||||||
|
* contains dashes, since we already know its
|
||||||
|
* exact length from pkg_ent->d_name. */
|
||||||
|
char pkg_ver[64] = "unknown";
|
||||||
|
DIR *ver_dir = opendir(pkg_path);
|
||||||
|
if (ver_dir) {
|
||||||
|
struct dirent *ver_ent;
|
||||||
|
while ((ver_ent = readdir(ver_dir))) {
|
||||||
|
if (ver_ent->d_name[0] == '.') { continue; }
|
||||||
|
size_t prefix_len = strlen(pkg_ent->d_name) + 1;
|
||||||
|
if (strlen(ver_ent->d_name) > prefix_len) {
|
||||||
|
snprintf(pkg_ver, sizeof(pkg_ver), "%s",
|
||||||
|
ver_ent->d_name + prefix_len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
closedir(ver_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(out, "%s::%s::%s\n",
|
||||||
repo_ent->d_name,
|
repo_ent->d_name,
|
||||||
pkg_ent->d_name);
|
pkg_ent->d_name,
|
||||||
|
pkg_ver);
|
||||||
listed++;
|
listed++;
|
||||||
}
|
}
|
||||||
closedir(repo_dir);
|
closedir(repo_dir);
|
||||||
@@ -476,14 +539,15 @@ gl_rebuild_index(uid_t uid, const char *out_dir)
|
|||||||
if (!tmp_in) return -1;
|
if (!tmp_in) return -1;
|
||||||
|
|
||||||
char tmp_path[PATH_MAX];
|
char tmp_path[PATH_MAX];
|
||||||
snprintf(tmp_path, sizeof(tmp_path), "%s/.index_tmp_XXXXXX", out_dir);
|
snprintf(tmp_path, sizeof(tmp_path), "%s/.index_tmp_XXXXXX",
|
||||||
|
ctx->index_path);
|
||||||
int tmp_fd = mkstemp(tmp_path);
|
int tmp_fd = mkstemp(tmp_path);
|
||||||
if (tmp_fd < 0) { fclose(tmp_in); return -1; }
|
if (tmp_fd < 0) { fclose(tmp_in); return -1; }
|
||||||
|
|
||||||
FILE *tmp_out = fdopen(tmp_fd, "w");
|
FILE *tmp_out = fdopen(tmp_fd, "w");
|
||||||
if (!tmp_out) { close(tmp_fd); fclose(tmp_in); return -1; }
|
if (!tmp_out) { close(tmp_fd); fclose(tmp_in); return -1; }
|
||||||
|
|
||||||
fprintf(tmp_out, "uid = %d\n", uid);
|
fprintf(tmp_out, "uid = %d\n", ctx->uid);
|
||||||
fprintf(tmp_out, "listed = %zu\n\n", listed);
|
fprintf(tmp_out, "listed = %zu\n\n", listed);
|
||||||
|
|
||||||
char line[512];
|
char line[512];
|
||||||
@@ -494,15 +558,15 @@ gl_rebuild_index(uid_t uid, const char *out_dir)
|
|||||||
fclose(tmp_out);
|
fclose(tmp_out);
|
||||||
|
|
||||||
rename(tmp_path, new_index_path);
|
rename(tmp_path, new_index_path);
|
||||||
chown(new_index_path, uid, (gid_t)-1);
|
chown(new_index_path, ctx->uid, (gid_t)-1);
|
||||||
|
|
||||||
/* Update the "current" symlink to point at the new index */
|
/* Update the "current" symlink to point at the new index */
|
||||||
char link_path[PATH_MAX];
|
char link_path[PATH_MAX];
|
||||||
snprintf(link_path, sizeof(link_path), "%s/current", out_dir);
|
snprintf(link_path, sizeof(link_path), "%s/current", ctx->index_path);
|
||||||
unlink(link_path);
|
unlink(link_path);
|
||||||
|
|
||||||
if (symlink(fname, link_path) != 0) { return -1; }
|
if (symlink(fname, link_path) != 0) { return -1; }
|
||||||
lchown(link_path, uid, (gid_t)-1);
|
lchown(link_path, ctx->uid, (gid_t)-1);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -672,62 +736,14 @@ gl_backup_istore(const char *tar_path,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
gl_mkdirp(const char *path, mode_t mode)
|
gl_unlink_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo)
|
||||||
{
|
|
||||||
char tmp[PATH_MAX];
|
|
||||||
strncpy(tmp, path, sizeof(tmp));
|
|
||||||
tmp[sizeof(tmp) - 1] = '\0';
|
|
||||||
|
|
||||||
for (char *p = tmp + 1; *p; p++) {
|
|
||||||
if (*p == '/') {
|
|
||||||
*p = '\0';
|
|
||||||
if (mkdir(tmp, mode) == -1 && errno != EEXIST) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
*p = '/';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mkdir(tmp, mode) == -1 && errno != EEXIST) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
gl_rmdir_recursive(const char *path)
|
|
||||||
{
|
|
||||||
DIR *dir = opendir(path);
|
|
||||||
if (!dir) { return -1; }
|
|
||||||
|
|
||||||
struct dirent *ent;
|
|
||||||
while ((ent = readdir(dir))) {
|
|
||||||
if (ent->d_name[0] == '.') { continue; }
|
|
||||||
|
|
||||||
char full[PATH_MAX];
|
|
||||||
snprintf(full, sizeof(full), "%s/%s", path, ent->d_name);
|
|
||||||
|
|
||||||
struct stat st;
|
|
||||||
if (lstat(full, &st) != 0) { continue; }
|
|
||||||
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
|
||||||
gl_rmdir_recursive(full);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
unlink(full);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closedir(dir);
|
|
||||||
return rmdir(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
gl_unlink_pkg(const char *pkg_store_final, uid_t uid)
|
|
||||||
{
|
{
|
||||||
char links_base[PATH_MAX];
|
char links_base[PATH_MAX];
|
||||||
snprintf(links_base, sizeof(links_base), "%s/%d", usr_links.path, uid);
|
if (ctx->scope == GL_SCOPE_SYS && strcmp(pkg_repo, GL_BASE_REPO) == 0) {
|
||||||
|
snprintf(links_base, sizeof(links_base), "%s", GL_BASE_LINKS_DEST);
|
||||||
|
} else {
|
||||||
|
snprintf(links_base, sizeof(links_base), "%s", ctx->links_path);
|
||||||
|
}
|
||||||
|
|
||||||
size_t strip_len = strlen(pkg_store_final);
|
size_t strip_len = strlen(pkg_store_final);
|
||||||
|
|
||||||
@@ -781,12 +797,14 @@ gl_unlink_pkg(const char *pkg_store_final, uid_t uid)
|
|||||||
}
|
}
|
||||||
|
|
||||||
gl_remove_status_t
|
gl_remove_status_t
|
||||||
gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid)
|
gl_remove_pkg(gl_context_t *ctx, const char *pkg_name, const char *pkg_repo)
|
||||||
{
|
{
|
||||||
|
if (!ctx) { return GL_REMOVE_ERR_NOT_FOUND; }
|
||||||
|
|
||||||
/* find the package base directory in the store */
|
/* find the package base directory in the store */
|
||||||
char pkg_store_base[PATH_MAX];
|
char pkg_store_base[PATH_MAX];
|
||||||
snprintf(pkg_store_base, sizeof(pkg_store_base),
|
snprintf(pkg_store_base, sizeof(pkg_store_base),
|
||||||
"%s/%d/%s/%s", usr_store.path, uid, pkg_repo, pkg_name);
|
"%s/%s/%s", ctx->store_path, pkg_repo, pkg_name);
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(pkg_store_base, &st) != 0 || !S_ISDIR(st.st_mode)) {
|
if (stat(pkg_store_base, &st) != 0 || !S_ISDIR(st.st_mode)) {
|
||||||
@@ -811,9 +829,14 @@ gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid)
|
|||||||
return GL_REMOVE_ERR_NOT_FOUND;
|
return GL_REMOVE_ERR_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* remove symlinks from links tree */
|
/* remove symlinks from links tree. GL_CTX_STAGE: skip — the
|
||||||
if (gl_unlink_pkg(pkg_store_final, uid) != 0) {
|
* stage links tree isn't seeded (links are fully derived from
|
||||||
return GL_REMOVE_ERR_UNLINK;
|
* the store post-commit by relink_store), so there's nothing
|
||||||
|
* there to unlink. */
|
||||||
|
if (ctx->mode == GL_CTX_LIVE) {
|
||||||
|
if (gl_unlink_pkg(ctx, pkg_store_final, pkg_repo) != 0) {
|
||||||
|
return GL_REMOVE_ERR_UNLINK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* remove package from store */
|
/* remove package from store */
|
||||||
@@ -822,10 +845,7 @@ gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* rebuild index */
|
/* rebuild index */
|
||||||
char index_dir[PATH_MAX];
|
if (gl_rebuild_index(ctx) != 0) {
|
||||||
snprintf(index_dir, sizeof(index_dir), "%s/%d", usr_index.path, uid);
|
|
||||||
|
|
||||||
if (gl_rebuild_index(uid, index_dir) != 0) {
|
|
||||||
return GL_REMOVE_ERR_INDEX;
|
return GL_REMOVE_ERR_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -833,10 +853,14 @@ gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
gl_link_pkg(const char *pkg_store_final, uid_t uid)
|
gl_link_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo)
|
||||||
{
|
{
|
||||||
char links_base[PATH_MAX];
|
char links_base[PATH_MAX];
|
||||||
snprintf(links_base, sizeof(links_base), "%s/%d", usr_links.path, uid);
|
if (ctx->scope == GL_SCOPE_SYS && strcmp(pkg_repo, GL_BASE_REPO) == 0) {
|
||||||
|
snprintf(links_base, sizeof(links_base), "%s", GL_BASE_LINKS_DEST);
|
||||||
|
} else {
|
||||||
|
snprintf(links_base, sizeof(links_base), "%s", ctx->links_path);
|
||||||
|
}
|
||||||
|
|
||||||
size_t strip_len = strlen(pkg_store_final);
|
size_t strip_len = strlen(pkg_store_final);
|
||||||
|
|
||||||
@@ -895,18 +919,29 @@ gl_link_pkg(const char *pkg_store_final, uid_t uid)
|
|||||||
gl_mkdirp(parent, 0700);
|
gl_mkdirp(parent, 0700);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* remove existing symlink if present */
|
/* Only ever replace an existing entry if it's a
|
||||||
unlink(link_path);
|
* symlink we created ourselves. See
|
||||||
|
* is_glacier_symlink's comment for why this
|
||||||
|
* check exists at all. */
|
||||||
|
struct stat existing_st;
|
||||||
|
if (lstat(link_path, &existing_st) == 0 &&
|
||||||
|
!is_glacier_symlink(link_path, ctx->store_path)) {
|
||||||
|
lg_printf(1,
|
||||||
|
"Refusing to overwrite non-glacier path: %s",
|
||||||
|
link_path);
|
||||||
|
} else {
|
||||||
|
unlink(link_path);
|
||||||
|
|
||||||
/* symlink store path -> links tree */
|
/* symlink store path -> links tree */
|
||||||
if (symlink(full_path, link_path) != 0) {
|
if (symlink(full_path, link_path) != 0) {
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
free(current);
|
free(current);
|
||||||
while (top > 0) { free(stack[--top]); }
|
while (top > 0) { free(stack[--top]); }
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lchown(link_path, ctx->uid, (gid_t)-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
lchown(link_path, uid, (gid_t)-1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -949,6 +984,78 @@ gl_delete_user(uid_t uid, bool isVerbose)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char
|
||||||
|
*gl_find_pkg_repo(gl_context_t *ctx, const char *pkg_name)
|
||||||
|
{
|
||||||
|
char store_path[PATH_MAX];
|
||||||
|
snprintf(store_path, sizeof(store_path), "%s", ctx->store_path);
|
||||||
|
|
||||||
|
DIR *uid_dir = opendir(store_path);
|
||||||
|
if (!uid_dir) { return NULL; }
|
||||||
|
|
||||||
|
struct dirent *repo_entry;
|
||||||
|
while ((repo_entry = readdir(uid_dir)) != NULL) {
|
||||||
|
if (repo_entry->d_name[0] == '.') { continue; }
|
||||||
|
|
||||||
|
char pkg_path[PATH_MAX];
|
||||||
|
snprintf(pkg_path, sizeof(pkg_path), "%s/%s/%s", store_path, repo_entry->d_name, pkg_name);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(pkg_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
|
char *repo = strdup(repo_entry->d_name);
|
||||||
|
closedir(uid_dir);
|
||||||
|
return repo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(uid_dir);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_repo_list_t
|
||||||
|
gl_find_pkg_repos(gl_context_t *ctx, const char *pkg_name)
|
||||||
|
{
|
||||||
|
gl_repo_list_t result = { NULL, 0 };
|
||||||
|
|
||||||
|
char store_path[PATH_MAX];
|
||||||
|
snprintf(store_path, sizeof(store_path), "%s", ctx->store_path);
|
||||||
|
|
||||||
|
DIR *uid_dir = opendir(store_path);
|
||||||
|
if (!uid_dir) { return result; }
|
||||||
|
|
||||||
|
struct dirent *repo_entry;
|
||||||
|
while ((repo_entry = readdir(uid_dir)) != NULL) {
|
||||||
|
if (repo_entry->d_name[0] == '.') { continue; }
|
||||||
|
|
||||||
|
char pkg_path[PATH_MAX];
|
||||||
|
snprintf(pkg_path, sizeof(pkg_path),
|
||||||
|
"%s/%s/%s", store_path, repo_entry->d_name, pkg_name);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(pkg_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
|
char **tmp = realloc(result.repos,
|
||||||
|
(result.count + 1) * sizeof(char *));
|
||||||
|
if (!tmp) { break; }
|
||||||
|
result.repos = tmp;
|
||||||
|
result.repos[result.count] = strdup(repo_entry->d_name);
|
||||||
|
result.count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(uid_dir);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
gl_free_repo_list(gl_repo_list_t *list)
|
||||||
|
{
|
||||||
|
if (!list) { return; }
|
||||||
|
for (size_t i = 0; i < list->count; i++) { free(list->repos[i]); }
|
||||||
|
free(list->repos);
|
||||||
|
list->repos = NULL;
|
||||||
|
list->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
gl_restore_status_t
|
gl_restore_status_t
|
||||||
gl_restore_istore(const char *tar_path, const char *glacier_root, const char *uid)
|
gl_restore_istore(const char *tar_path, const char *glacier_root, const char *uid)
|
||||||
{
|
{
|
||||||
@@ -1011,8 +1118,9 @@ gl_restore_istore(const char *tar_path, const char *glacier_root, const char *ui
|
|||||||
}
|
}
|
||||||
|
|
||||||
gl_install_status_t
|
gl_install_status_t
|
||||||
gl_install_pkg(const char *gpkg_path, uid_t uid)
|
gl_install_pkg(gl_context_t *ctx, const char *gpkg_path)
|
||||||
{
|
{
|
||||||
|
if (!ctx) { return GL_INSTALL_ERR_OPEN; }
|
||||||
char original_cwd[PATH_MAX];
|
char original_cwd[PATH_MAX];
|
||||||
if (getcwd(original_cwd, sizeof(original_cwd)) == NULL) {
|
if (getcwd(original_cwd, sizeof(original_cwd)) == NULL) {
|
||||||
return GL_INSTALL_ERR_OPEN;
|
return GL_INSTALL_ERR_OPEN;
|
||||||
@@ -1086,7 +1194,7 @@ gl_install_pkg(const char *gpkg_path, uid_t uid)
|
|||||||
|
|
||||||
/* construct the directory for the package in the store */
|
/* construct the directory for the package in the store */
|
||||||
snprintf(pkg_store_base, sizeof(pkg_store_base),
|
snprintf(pkg_store_base, sizeof(pkg_store_base),
|
||||||
"%s/%d/%s/%s", usr_store.path, uid, pkg.pkg_repo, pkg.pkg_name);
|
"%s/%s/%s", ctx->store_path, pkg.pkg_repo, pkg.pkg_name);
|
||||||
|
|
||||||
snprintf(pkg_store_final, sizeof(pkg_store_final),
|
snprintf(pkg_store_final, sizeof(pkg_store_final),
|
||||||
"%s/%s-%d.%d.%d", pkg_store_base, pkg.pkg_name,
|
"%s/%s-%d.%d.%d", pkg_store_base, pkg.pkg_name,
|
||||||
@@ -1186,15 +1294,13 @@ gl_install_pkg(const char *gpkg_path, uid_t uid)
|
|||||||
archive_read_free(a2);
|
archive_read_free(a2);
|
||||||
|
|
||||||
/* now the index will be updated */
|
/* now the index will be updated */
|
||||||
|
/* Resolve "current" symlink to confirm an index exists to rebuild
|
||||||
/* now the index will be updated */
|
* against. The actual write happens in gl_rebuild_index below —
|
||||||
char index_dir[PATH_MAX];
|
* anything written here would just be overwritten by that call,
|
||||||
snprintf(index_dir, sizeof(index_dir),
|
* since it creates a fresh timestamped index file and repoints
|
||||||
"%s/%d", usr_index.path, uid);
|
* "current" at it rather than editing this one in place. */
|
||||||
|
|
||||||
/* Resolve "current" symlink to find the actual index file to append to */
|
|
||||||
char current_link[PATH_MAX];
|
char current_link[PATH_MAX];
|
||||||
snprintf(current_link, sizeof(current_link), "%s/current", index_dir);
|
snprintf(current_link, sizeof(current_link), "%s/current", ctx->index_path);
|
||||||
|
|
||||||
char current_target[PATH_MAX];
|
char current_target[PATH_MAX];
|
||||||
ssize_t len = readlink(current_link, current_target, sizeof(current_target) - 1);
|
ssize_t len = readlink(current_link, current_target, sizeof(current_target) - 1);
|
||||||
@@ -1204,31 +1310,148 @@ gl_install_pkg(const char *gpkg_path, uid_t uid)
|
|||||||
}
|
}
|
||||||
current_target[len] = '\0';
|
current_target[len] = '\0';
|
||||||
|
|
||||||
char index_path[PATH_MAX];
|
if (gl_rebuild_index(ctx) != 0) {
|
||||||
snprintf(index_path, sizeof(index_path), "%s/%s", index_dir, current_target);
|
|
||||||
|
|
||||||
FILE *idx = fopen(index_path, "a");
|
|
||||||
if (!idx) {
|
|
||||||
gl_free_pkg(&pkg);
|
gl_free_pkg(&pkg);
|
||||||
return GL_INSTALL_ERR_INDEX;
|
return GL_INSTALL_ERR_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(idx, "%s::%s\n", pkg.pkg_repo, pkg.pkg_name);
|
if (ctx->mode == GL_CTX_LIVE) {
|
||||||
fclose(idx);
|
if (gl_link_pkg(ctx, pkg_store_final, pkg.pkg_repo) != 0) {
|
||||||
|
gl_free_pkg(&pkg);
|
||||||
if (gl_rebuild_index(uid, index_dir) != 0) {
|
return GL_INSTALL_ERR_EXTRACT;
|
||||||
gl_free_pkg(&pkg);
|
}
|
||||||
return GL_INSTALL_ERR_INDEX;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gl_link_pkg(pkg_store_final, uid) != 0) {
|
|
||||||
gl_free_pkg(&pkg);
|
|
||||||
return GL_INSTALL_ERR_EXTRACT;
|
|
||||||
}
|
}
|
||||||
|
/* GL_CTX_STAGE: skip. A symlink written here would target
|
||||||
|
* ctx->store_path, which is the stage path — it goes stale the
|
||||||
|
* moment gl_commit_transaction renames the store into its live
|
||||||
|
* location. The links tree is fully derived from the store, so
|
||||||
|
* it's regenerated wholesale post-commit instead of tracked
|
||||||
|
* incrementally here (see relink_store in transaction.c). */
|
||||||
|
|
||||||
gl_free_pkg(&pkg);
|
gl_free_pkg(&pkg);
|
||||||
chdir(original_cwd);
|
chdir(original_cwd);
|
||||||
return GL_INSTALL_OK;
|
return GL_INSTALL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* prune_stale_links
|
||||||
|
* Recursively removes only the symlinks glacier itself created (per
|
||||||
|
* is_glacier_symlink) whose store target no longer exists — i.e. links
|
||||||
|
* left behind by a package that's since been removed. Directories,
|
||||||
|
* regular files, and symlinks glacier didn't create are never touched,
|
||||||
|
* recursed into for directories aside. This must stay this
|
||||||
|
* conservative because links_path is not always glacier-exclusive
|
||||||
|
* territory: for system-scope packages it can be /usr itself, which
|
||||||
|
* holds plenty of content glacier has no business deleting.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
prune_stale_links(const char *dir_path, const char *store_prefix)
|
||||||
|
{
|
||||||
|
DIR *dir = opendir(dir_path);
|
||||||
|
if (!dir) { return; }
|
||||||
|
|
||||||
|
struct dirent *ent;
|
||||||
|
while ((ent = readdir(dir))) {
|
||||||
|
if (ent->d_name[0] == '.') { continue; }
|
||||||
|
|
||||||
|
char full[PATH_MAX];
|
||||||
|
snprintf(full, sizeof(full), "%s/%s", dir_path, ent->d_name);
|
||||||
|
|
||||||
|
struct stat lst;
|
||||||
|
if (lstat(full, &lst) != 0) { continue; }
|
||||||
|
|
||||||
|
if (S_ISLNK(lst.st_mode)) {
|
||||||
|
if (!is_glacier_symlink(full, store_prefix)) {
|
||||||
|
continue; /* not ours - never touch */
|
||||||
|
}
|
||||||
|
struct stat target_st;
|
||||||
|
if (stat(full, &target_st) != 0 && errno == ENOENT) {
|
||||||
|
/* our symlink, but its target is gone */
|
||||||
|
unlink(full);
|
||||||
|
}
|
||||||
|
} else if (S_ISDIR(lst.st_mode)) {
|
||||||
|
prune_stale_links(full, store_prefix);
|
||||||
|
}
|
||||||
|
/* regular files: never ours, never touched */
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
gl_relink_store(gl_context_t *ctx)
|
||||||
|
{
|
||||||
|
/* This function's contract is "derive the links tree entirely
|
||||||
|
* from the store" (same as gl_rebuild_index does for the index) -
|
||||||
|
* but "derive fully" now means prune-then-refresh, not
|
||||||
|
* wipe-then-rebuild. links_path may be a directory glacier
|
||||||
|
* doesn't exclusively own (e.g. /usr for system scope), so a
|
||||||
|
* recursive delete of the whole tree is off the table; only
|
||||||
|
* glacier's own stale symlinks get removed. gl_mkdirp is
|
||||||
|
* non-destructive (mkdir tolerates EEXIST) so this is still safe
|
||||||
|
* to call even if links_path already exists. */
|
||||||
|
if (gl_mkdirp(ctx->links_path, 0700) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
prune_stale_links(ctx->links_path, ctx->store_path);
|
||||||
|
|
||||||
|
/* Base-repo packages under system scope route to /usr instead of
|
||||||
|
* ctx->links_path (see GL_BASE_REPO) — prune there too, since a
|
||||||
|
* package that WAS in the base repo and got removed/moved leaves
|
||||||
|
* its stale symlink in /usr, not in ctx->links_path. Only relevant
|
||||||
|
* for system scope; /usr is never a valid destination for
|
||||||
|
* per-uid packages. */
|
||||||
|
if (ctx->scope == GL_SCOPE_SYS) {
|
||||||
|
prune_stale_links(GL_BASE_LINKS_DEST, ctx->store_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR *repo_dir = opendir(ctx->store_path);
|
||||||
|
if (!repo_dir) return (errno == ENOENT) ? 0 : -1;
|
||||||
|
|
||||||
|
struct dirent *repo_ent;
|
||||||
|
while ((repo_ent = readdir(repo_dir))) {
|
||||||
|
if (repo_ent->d_name[0] == '.') continue;
|
||||||
|
|
||||||
|
char repo_path[PATH_MAX];
|
||||||
|
snprintf(repo_path, sizeof(repo_path), "%s/%s",
|
||||||
|
ctx->store_path, repo_ent->d_name);
|
||||||
|
if (!is_dir(repo_path)) continue;
|
||||||
|
|
||||||
|
DIR *name_dir = opendir(repo_path);
|
||||||
|
if (!name_dir) continue;
|
||||||
|
|
||||||
|
struct dirent *name_ent;
|
||||||
|
while ((name_ent = readdir(name_dir))) {
|
||||||
|
if (name_ent->d_name[0] == '.') continue;
|
||||||
|
|
||||||
|
char name_path[PATH_MAX];
|
||||||
|
snprintf(name_path, sizeof(name_path), "%s/%s",
|
||||||
|
repo_path, name_ent->d_name);
|
||||||
|
if (!is_dir(name_path)) continue;
|
||||||
|
|
||||||
|
/* one version dir per package name today */
|
||||||
|
DIR *ver_dir = opendir(name_path);
|
||||||
|
if (!ver_dir) continue;
|
||||||
|
|
||||||
|
struct dirent *ver_ent;
|
||||||
|
while ((ver_ent = readdir(ver_dir))) {
|
||||||
|
if (ver_ent->d_name[0] == '.') continue;
|
||||||
|
|
||||||
|
char ver_path[PATH_MAX];
|
||||||
|
snprintf(ver_path, sizeof(ver_path), "%s/%s",
|
||||||
|
name_path, ver_ent->d_name);
|
||||||
|
if (!is_dir(ver_path)) continue;
|
||||||
|
|
||||||
|
if (gl_link_pkg(ctx, ver_path, repo_ent->d_name) != 0) {
|
||||||
|
closedir(ver_dir); closedir(name_dir);
|
||||||
|
closedir(repo_dir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(ver_dir);
|
||||||
|
}
|
||||||
|
closedir(name_dir);
|
||||||
|
}
|
||||||
|
closedir(repo_dir);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,302 @@
|
|||||||
|
--- istoreutils.c.orig 2026-06-01 19:43:00.493773073 +0000
|
||||||
|
+++ istoreutils.c.new 2026-06-01 19:43:00.573469330 +0000
|
||||||
|
@@ -21,6 +21,7 @@
|
||||||
|
#include "istoreutils.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "pkg.h"
|
||||||
|
+#include "transaction.h"
|
||||||
|
|
||||||
|
static const dir_t usr_index = { "/glacier/usr/index", 0700 };
|
||||||
|
static const dir_t sys_index = { "/glacier/sys/index", 0700 };
|
||||||
|
@@ -29,6 +30,7 @@
|
||||||
|
static const dir_t sys_store = { "/glacier/sys/store", 0700 };
|
||||||
|
|
||||||
|
static const dir_t usr_links = { "/glacier/usr/links", 0700 };
|
||||||
|
+static const dir_t sys_links = { "/glacier/sys/links", 0700 };
|
||||||
|
|
||||||
|
bool
|
||||||
|
gl_usr_istore_exists(index_store_t index_or_store, int uid)
|
||||||
|
@@ -253,6 +255,46 @@
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
+int
|
||||||
|
+gl_init_sys(bool isVerbose)
|
||||||
|
+{
|
||||||
|
+ const char *paths[] = {
|
||||||
|
+ sys_index.path,
|
||||||
|
+ sys_store.path,
|
||||||
|
+ sys_links.path,
|
||||||
|
+ };
|
||||||
|
+ mode_t modes[] = {
|
||||||
|
+ sys_index.mode,
|
||||||
|
+ sys_store.mode,
|
||||||
|
+ sys_links.mode,
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ for (int i = 0; i < 3; i++) {
|
||||||
|
+ if (isVerbose) { lg_printf(0, "Creating %s", paths[i]); }
|
||||||
|
+
|
||||||
|
+ if (mkdir(paths[i], modes[i]) == -1) {
|
||||||
|
+ switch (errno) {
|
||||||
|
+ case EEXIST:
|
||||||
|
+ break;
|
||||||
|
+ case EACCES:
|
||||||
|
+ lg_printf(2, "Cannot create %s: permission denied", paths[i]);
|
||||||
|
+ return 1;
|
||||||
|
+ case ENOSPC:
|
||||||
|
+ lg_printf(2, "Cannot create %s: no space left on device", paths[i]);
|
||||||
|
+ return 1;
|
||||||
|
+ case EROFS:
|
||||||
|
+ lg_printf(2, "Cannot create %s: read-only filesystem", paths[i]);
|
||||||
|
+ return 1;
|
||||||
|
+ default:
|
||||||
|
+ lg_printf(2, "Cannot create %s: mkdir failed", paths[i]);
|
||||||
|
+ return -1;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
static void
|
||||||
|
trim(char *s)
|
||||||
|
{
|
||||||
|
@@ -409,12 +451,11 @@
|
||||||
|
*/
|
||||||
|
|
||||||
|
int
|
||||||
|
-gl_rebuild_index(uid_t uid, const char *out_dir)
|
||||||
|
+gl_rebuild_index(gl_context_t *ctx)
|
||||||
|
{
|
||||||
|
- char store_path[PATH_MAX];
|
||||||
|
- snprintf(store_path, sizeof(store_path), "%s/%d", usr_store.path, uid);
|
||||||
|
+ if (!ctx) { return -1; }
|
||||||
|
|
||||||
|
- DIR *store_dir = opendir(store_path);
|
||||||
|
+ DIR *store_dir = opendir(ctx->store_path);
|
||||||
|
if (!store_dir) return -1;
|
||||||
|
|
||||||
|
/* Write to a new timestamped index file */
|
||||||
|
@@ -422,7 +463,8 @@
|
||||||
|
make_index_name(fname, sizeof(fname));
|
||||||
|
|
||||||
|
char new_index_path[PATH_MAX];
|
||||||
|
- snprintf(new_index_path, sizeof(new_index_path), "%s/%s", out_dir, fname);
|
||||||
|
+ snprintf(new_index_path, sizeof(new_index_path), "%s/%s",
|
||||||
|
+ ctx->index_path, fname);
|
||||||
|
|
||||||
|
FILE *out = fopen(new_index_path, "w");
|
||||||
|
if (!out) {
|
||||||
|
@@ -438,7 +480,7 @@
|
||||||
|
|
||||||
|
char repo_path[PATH_MAX];
|
||||||
|
snprintf(repo_path, sizeof(repo_path), "%s/%s",
|
||||||
|
- store_path, repo_ent->d_name);
|
||||||
|
+ ctx->store_path, repo_ent->d_name);
|
||||||
|
|
||||||
|
struct stat rst;
|
||||||
|
if (stat(repo_path, &rst) != 0 || !S_ISDIR(rst.st_mode)) {
|
||||||
|
@@ -476,14 +518,15 @@
|
||||||
|
if (!tmp_in) return -1;
|
||||||
|
|
||||||
|
char tmp_path[PATH_MAX];
|
||||||
|
- snprintf(tmp_path, sizeof(tmp_path), "%s/.index_tmp_XXXXXX", out_dir);
|
||||||
|
+ snprintf(tmp_path, sizeof(tmp_path), "%s/.index_tmp_XXXXXX",
|
||||||
|
+ ctx->index_path);
|
||||||
|
int tmp_fd = mkstemp(tmp_path);
|
||||||
|
if (tmp_fd < 0) { fclose(tmp_in); return -1; }
|
||||||
|
|
||||||
|
FILE *tmp_out = fdopen(tmp_fd, "w");
|
||||||
|
if (!tmp_out) { close(tmp_fd); fclose(tmp_in); return -1; }
|
||||||
|
|
||||||
|
- fprintf(tmp_out, "uid = %d\n", uid);
|
||||||
|
+ fprintf(tmp_out, "uid = %d\n", ctx->uid);
|
||||||
|
fprintf(tmp_out, "listed = %zu\n\n", listed);
|
||||||
|
|
||||||
|
char line[512];
|
||||||
|
@@ -494,15 +537,15 @@
|
||||||
|
fclose(tmp_out);
|
||||||
|
|
||||||
|
rename(tmp_path, new_index_path);
|
||||||
|
- chown(new_index_path, uid, (gid_t)-1);
|
||||||
|
+ chown(new_index_path, ctx->uid, (gid_t)-1);
|
||||||
|
|
||||||
|
/* Update the "current" symlink to point at the new index */
|
||||||
|
char link_path[PATH_MAX];
|
||||||
|
- snprintf(link_path, sizeof(link_path), "%s/current", out_dir);
|
||||||
|
+ snprintf(link_path, sizeof(link_path), "%s/current", ctx->index_path);
|
||||||
|
unlink(link_path);
|
||||||
|
|
||||||
|
if (symlink(fname, link_path) != 0) { return -1; }
|
||||||
|
- lchown(link_path, uid, (gid_t)-1);
|
||||||
|
+ lchown(link_path, ctx->uid, (gid_t)-1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@@ -672,10 +715,10 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
-gl_unlink_pkg(const char *pkg_store_final, uid_t uid)
|
||||||
|
+gl_unlink_pkg(gl_context_t *ctx, const char *pkg_store_final)
|
||||||
|
{
|
||||||
|
char links_base[PATH_MAX];
|
||||||
|
- snprintf(links_base, sizeof(links_base), "%s/%d", usr_links.path, uid);
|
||||||
|
+ strncpy(links_base, ctx->links_path, PATH_MAX - 1);
|
||||||
|
|
||||||
|
size_t strip_len = strlen(pkg_store_final);
|
||||||
|
|
||||||
|
@@ -729,12 +772,14 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_remove_status_t
|
||||||
|
-gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid)
|
||||||
|
+gl_remove_pkg(gl_context_t *ctx, const char *pkg_name, const char *pkg_repo)
|
||||||
|
{
|
||||||
|
+ if (!ctx) { return GL_REMOVE_ERR_NOT_FOUND; }
|
||||||
|
+
|
||||||
|
/* find the package base directory in the store */
|
||||||
|
char pkg_store_base[PATH_MAX];
|
||||||
|
snprintf(pkg_store_base, sizeof(pkg_store_base),
|
||||||
|
- "%s/%d/%s/%s", usr_store.path, uid, pkg_repo, pkg_name);
|
||||||
|
+ "%s/%s/%s", ctx->store_path, pkg_repo, pkg_name);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(pkg_store_base, &st) != 0 || !S_ISDIR(st.st_mode)) {
|
||||||
|
@@ -760,7 +805,7 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove symlinks from links tree */
|
||||||
|
- if (gl_unlink_pkg(pkg_store_final, uid) != 0) {
|
||||||
|
+ if (gl_unlink_pkg(ctx, pkg_store_final) != 0) {
|
||||||
|
return GL_REMOVE_ERR_UNLINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -770,10 +815,7 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rebuild index */
|
||||||
|
- char index_dir[PATH_MAX];
|
||||||
|
- snprintf(index_dir, sizeof(index_dir), "%s/%d", usr_index.path, uid);
|
||||||
|
-
|
||||||
|
- if (gl_rebuild_index(uid, index_dir) != 0) {
|
||||||
|
+ if (gl_rebuild_index(ctx) != 0) {
|
||||||
|
return GL_REMOVE_ERR_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -781,10 +823,10 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
-gl_link_pkg(const char *pkg_store_final, uid_t uid)
|
||||||
|
+gl_link_pkg(gl_context_t *ctx, const char *pkg_store_final)
|
||||||
|
{
|
||||||
|
char links_base[PATH_MAX];
|
||||||
|
- snprintf(links_base, sizeof(links_base), "%s/%d", usr_links.path, uid);
|
||||||
|
+ strncpy(links_base, ctx->links_path, PATH_MAX - 1);
|
||||||
|
|
||||||
|
size_t strip_len = strlen(pkg_store_final);
|
||||||
|
|
||||||
|
@@ -854,7 +896,7 @@
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
- lchown(link_path, uid, (gid_t)-1);
|
||||||
|
+ lchown(link_path, ctx->uid, (gid_t)-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -898,11 +940,10 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
char
|
||||||
|
-*gl_find_pkg_repo(const char *pkg_name, uid_t uid)
|
||||||
|
+*gl_find_pkg_repo(gl_context_t *ctx, const char *pkg_name)
|
||||||
|
{
|
||||||
|
char store_path[PATH_MAX];
|
||||||
|
- snprintf(store_path, sizeof(store_path),
|
||||||
|
-"/glacier/usr/store/%u", uid);
|
||||||
|
+ strncpy(store_path, ctx->store_path, PATH_MAX - 1);
|
||||||
|
|
||||||
|
DIR *uid_dir = opendir(store_path);
|
||||||
|
if (!uid_dir) { return NULL; }
|
||||||
|
@@ -927,13 +968,12 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_repo_list_t
|
||||||
|
-gl_find_pkg_repos(const char *pkg_name, uid_t uid)
|
||||||
|
+gl_find_pkg_repos(gl_context_t *ctx, const char *pkg_name)
|
||||||
|
{
|
||||||
|
gl_repo_list_t result = { NULL, 0 };
|
||||||
|
|
||||||
|
char store_path[PATH_MAX];
|
||||||
|
- snprintf(store_path, sizeof(store_path),
|
||||||
|
- "/glacier/usr/store/%u", uid);
|
||||||
|
+ strncpy(store_path, ctx->store_path, PATH_MAX - 1);
|
||||||
|
|
||||||
|
DIR *uid_dir = opendir(store_path);
|
||||||
|
if (!uid_dir) { return result; }
|
||||||
|
@@ -1033,8 +1073,9 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_install_status_t
|
||||||
|
-gl_install_pkg(const char *gpkg_path, uid_t uid)
|
||||||
|
+gl_install_pkg(gl_context_t *ctx, const char *gpkg_path)
|
||||||
|
{
|
||||||
|
+ if (!ctx) { return GL_INSTALL_ERR_OPEN; }
|
||||||
|
char original_cwd[PATH_MAX];
|
||||||
|
if (getcwd(original_cwd, sizeof(original_cwd)) == NULL) {
|
||||||
|
return GL_INSTALL_ERR_OPEN;
|
||||||
|
@@ -1108,7 +1149,7 @@
|
||||||
|
|
||||||
|
/* construct the directory for the package in the store */
|
||||||
|
snprintf(pkg_store_base, sizeof(pkg_store_base),
|
||||||
|
- "%s/%d/%s/%s", usr_store.path, uid, pkg.pkg_repo, pkg.pkg_name);
|
||||||
|
+ "%s/%s/%s", ctx->store_path, pkg.pkg_repo, pkg.pkg_name);
|
||||||
|
|
||||||
|
snprintf(pkg_store_final, sizeof(pkg_store_final),
|
||||||
|
"%s/%s-%d.%d.%d", pkg_store_base, pkg.pkg_name,
|
||||||
|
@@ -1208,15 +1249,9 @@
|
||||||
|
archive_read_free(a2);
|
||||||
|
|
||||||
|
/* now the index will be updated */
|
||||||
|
-
|
||||||
|
- /* now the index will be updated */
|
||||||
|
- char index_dir[PATH_MAX];
|
||||||
|
- snprintf(index_dir, sizeof(index_dir),
|
||||||
|
- "%s/%d", usr_index.path, uid);
|
||||||
|
-
|
||||||
|
/* Resolve "current" symlink to find the actual index file to append to */
|
||||||
|
char current_link[PATH_MAX];
|
||||||
|
- snprintf(current_link, sizeof(current_link), "%s/current", index_dir);
|
||||||
|
+ snprintf(current_link, sizeof(current_link), "%s/current", ctx->index_path);
|
||||||
|
|
||||||
|
char current_target[PATH_MAX];
|
||||||
|
ssize_t len = readlink(current_link, current_target, sizeof(current_target) - 1);
|
||||||
|
@@ -1227,7 +1262,8 @@
|
||||||
|
current_target[len] = '\0';
|
||||||
|
|
||||||
|
char index_path[PATH_MAX];
|
||||||
|
- snprintf(index_path, sizeof(index_path), "%s/%s", index_dir, current_target);
|
||||||
|
+ snprintf(index_path, sizeof(index_path), "%s/%s",
|
||||||
|
+ ctx->index_path, current_target);
|
||||||
|
|
||||||
|
FILE *idx = fopen(index_path, "a");
|
||||||
|
if (!idx) {
|
||||||
|
@@ -1238,12 +1274,12 @@
|
||||||
|
fprintf(idx, "%s::%s\n", pkg.pkg_repo, pkg.pkg_name);
|
||||||
|
fclose(idx);
|
||||||
|
|
||||||
|
- if (gl_rebuild_index(uid, index_dir) != 0) {
|
||||||
|
+ if (gl_rebuild_index(ctx) != 0) {
|
||||||
|
gl_free_pkg(&pkg);
|
||||||
|
return GL_INSTALL_ERR_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
- if (gl_link_pkg(pkg_store_final, uid) != 0) {
|
||||||
|
+ if (gl_link_pkg(ctx, pkg_store_final) != 0) {
|
||||||
|
gl_free_pkg(&pkg);
|
||||||
|
return GL_INSTALL_ERR_EXTRACT;
|
||||||
|
}
|
||||||
+19
-4
@@ -5,6 +5,8 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "transaction.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
INDEX,
|
INDEX,
|
||||||
STORE
|
STORE
|
||||||
@@ -58,6 +60,7 @@ typedef enum {
|
|||||||
struct gpkg_entry {
|
struct gpkg_entry {
|
||||||
char *repo;
|
char *repo;
|
||||||
char *pkg;
|
char *pkg;
|
||||||
|
char *ver;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct gindex {
|
struct gindex {
|
||||||
@@ -71,21 +74,33 @@ bool gl_usr_istore_exists(index_store_t index_or_store, int uid);
|
|||||||
bool gl_sys_istore_exists(index_store_t index_or_store);
|
bool gl_sys_istore_exists(index_store_t index_or_store);
|
||||||
|
|
||||||
int gl_init_user(uid_t uid, bool isVerbose);
|
int gl_init_user(uid_t uid, bool isVerbose);
|
||||||
|
int gl_init_sys(bool isVerbose);
|
||||||
int gl_delete_user(uid_t uid, bool isVerbose);
|
int gl_delete_user(uid_t uid, bool isVerbose);
|
||||||
|
|
||||||
int gl_parse_index(struct gindex *idx, FILE *f);
|
int gl_parse_index(struct gindex *idx, FILE *f);
|
||||||
void gl_free_index(struct gindex *idx);
|
void gl_free_index(struct gindex *idx);
|
||||||
|
|
||||||
int gl_rebuild_index(uid_t uid, const char *out_dir);
|
int gl_rebuild_index(gl_context_t *ctx);
|
||||||
|
|
||||||
char *gl_find_pkg_repo(const char *pkg_name, uid_t uid);
|
char *gl_find_pkg_repo(gl_context_t *ctx, const char *pkg_name);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char **repos;
|
||||||
|
size_t count;
|
||||||
|
}
|
||||||
|
gl_repo_list_t;
|
||||||
|
|
||||||
|
gl_repo_list_t gl_find_pkg_repos(gl_context_t *ctx, const char *pkg_name);
|
||||||
|
void gl_free_repo_list(gl_repo_list_t *list);
|
||||||
|
|
||||||
gl_backup_status_t gl_backup_istore(const char *tar_path, const char *glacier_root,
|
gl_backup_status_t gl_backup_istore(const char *tar_path, const char *glacier_root,
|
||||||
const char *uid);
|
const char *uid);
|
||||||
gl_restore_status_t gl_restore_istore(const char *tar_path, const char *glacier_root, const char *uid);
|
gl_restore_status_t gl_restore_istore(const char *tar_path, const char *glacier_root, const char *uid);
|
||||||
|
|
||||||
gl_install_status_t gl_install_pkg(const char *gpkg_path, uid_t uid);
|
gl_install_status_t gl_install_pkg(gl_context_t *ctx, const char *gpkg_path);
|
||||||
gl_remove_status_t gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid);
|
gl_remove_status_t gl_remove_pkg(gl_context_t *ctx, const char *pkg_name, const char *pkg_repo);
|
||||||
|
|
||||||
|
int gl_relink_store(gl_context_t *ctx);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#ifndef ISTOREUTILS_H_
|
||||||
|
#define ISTOREUTILS_H_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
INDEX,
|
||||||
|
STORE
|
||||||
|
}
|
||||||
|
index_store_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GL_BACKUP_OK = 0,
|
||||||
|
GL_BACKUP_ERR_ALLOC = 1,
|
||||||
|
GL_BACKUP_ERR_OPEN = 2,
|
||||||
|
GL_BACKUP_ERR_ADD_INDEX = 3,
|
||||||
|
GL_BACKUP_ERR_ADD_STORE = 4,
|
||||||
|
GL_BACKUP_ERR_ADD_LINKS = 5,
|
||||||
|
GL_BACKUP_ERR_CLOSE = 6
|
||||||
|
}
|
||||||
|
gl_backup_status_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GL_RESTORE_OK = 0,
|
||||||
|
GL_RESTORE_ERR_OPEN = 1,
|
||||||
|
GL_RESTORE_ERR_EXTRACT = 2
|
||||||
|
} gl_restore_status_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GL_INSTALL_OK: installation successful
|
||||||
|
* GL_INSTALL_ERR_OPEN: unable to open .gpkg file
|
||||||
|
* GL_INSTALL_ERR_MANIFEST: manifest is missing or unparseable
|
||||||
|
* GL_INSTALL_ERR_EXTRACT: unable to extract files
|
||||||
|
* GL_INSTALL_ERR_INDEX: unable to update index
|
||||||
|
* GL_INSTALL_ERR_ALREADY_INSTALLED: package already installed
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GL_INSTALL_OK = 0,
|
||||||
|
GL_INSTALL_ERR_OPEN = 1,
|
||||||
|
GL_INSTALL_ERR_MANIFEST = 2,
|
||||||
|
GL_INSTALL_ERR_EXTRACT = 3,
|
||||||
|
GL_INSTALL_ERR_INDEX = 4,
|
||||||
|
GL_INSTALL_ERR_ALREADY_INSTALLED = 5,
|
||||||
|
GL_INSTALL_SKIPPED = 6,
|
||||||
|
}
|
||||||
|
gl_install_status_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GL_REMOVE_OK = 0,
|
||||||
|
GL_REMOVE_ERR_NOT_FOUND = 1,
|
||||||
|
GL_REMOVE_ERR_UNLINK = 2,
|
||||||
|
GL_REMOVE_ERR_INDEX = 3,
|
||||||
|
} gl_remove_status_t;
|
||||||
|
|
||||||
|
struct gpkg_entry {
|
||||||
|
char *repo;
|
||||||
|
char *pkg;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gindex {
|
||||||
|
int uid;
|
||||||
|
int listed;
|
||||||
|
struct gpkg_entry *entries;
|
||||||
|
size_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool gl_usr_istore_exists(index_store_t index_or_store, int uid);
|
||||||
|
bool gl_sys_istore_exists(index_store_t index_or_store);
|
||||||
|
|
||||||
|
int gl_init_user(uid_t uid, bool isVerbose);
|
||||||
|
int gl_delete_user(uid_t uid, bool isVerbose);
|
||||||
|
|
||||||
|
int gl_parse_index(struct gindex *idx, FILE *f);
|
||||||
|
void gl_free_index(struct gindex *idx);
|
||||||
|
|
||||||
|
int gl_rebuild_index(uid_t uid, const char *out_dir);
|
||||||
|
|
||||||
|
char *gl_find_pkg_repo(const char *pkg_name, uid_t uid);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char **repos;
|
||||||
|
size_t count;
|
||||||
|
}
|
||||||
|
gl_repo_list_t;
|
||||||
|
|
||||||
|
gl_repo_list_t gl_find_pkg_repos(const char *pkg_name, uid_t uid);
|
||||||
|
void gl_free_repo_list(gl_repo_list_t *list);
|
||||||
|
|
||||||
|
gl_backup_status_t gl_backup_istore(const char *tar_path, const char *glacier_root,
|
||||||
|
const char *uid);
|
||||||
|
gl_restore_status_t gl_restore_istore(const char *tar_path, const char *glacier_root, const char *uid);
|
||||||
|
|
||||||
|
gl_install_status_t gl_install_pkg(const char *gpkg_path, uid_t uid);
|
||||||
|
gl_remove_status_t gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
+203
@@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
* gl_sha256 — vendored SHA-256 (FIPS 180-4).
|
||||||
|
* See sha256.h for API rationale.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "sha256.h"
|
||||||
|
|
||||||
|
#define ROTRIGHT(a, b) (((a) >> (b)) | ((a) << (32 - (b))))
|
||||||
|
|
||||||
|
#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
|
||||||
|
#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
|
||||||
|
#define EP0(x) (ROTRIGHT(x, 2) ^ ROTRIGHT(x, 13) ^ ROTRIGHT(x, 22))
|
||||||
|
#define EP1(x) (ROTRIGHT(x, 6) ^ ROTRIGHT(x, 11) ^ ROTRIGHT(x, 25))
|
||||||
|
#define SIG0(x) (ROTRIGHT(x, 7) ^ ROTRIGHT(x, 18) ^ ((x) >> 3))
|
||||||
|
#define SIG1(x) (ROTRIGHT(x, 17) ^ ROTRIGHT(x, 19) ^ ((x) >> 10))
|
||||||
|
|
||||||
|
static const uint32_t k[64] = {
|
||||||
|
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||||
|
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||||
|
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||||
|
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||||
|
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||||||
|
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||||
|
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||||
|
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||||
|
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||||
|
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||||
|
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||||||
|
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||||
|
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||||||
|
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||||
|
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||||
|
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
sha256_transform(gl_sha256_ctx_t *ctx, const uint8_t data[64])
|
||||||
|
{
|
||||||
|
uint32_t m[64];
|
||||||
|
uint32_t i, j;
|
||||||
|
|
||||||
|
for (i = 0, j = 0; i < 16; ++i, j += 4) {
|
||||||
|
m[i] = ((uint32_t)data[j] << 24) |
|
||||||
|
((uint32_t)data[j + 1] << 16) |
|
||||||
|
((uint32_t)data[j + 2] << 8) |
|
||||||
|
((uint32_t)data[j + 3]);
|
||||||
|
}
|
||||||
|
for (; i < 64; ++i) {
|
||||||
|
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t a = ctx->state[0];
|
||||||
|
uint32_t b = ctx->state[1];
|
||||||
|
uint32_t c = ctx->state[2];
|
||||||
|
uint32_t d = ctx->state[3];
|
||||||
|
uint32_t e = ctx->state[4];
|
||||||
|
uint32_t f = ctx->state[5];
|
||||||
|
uint32_t g = ctx->state[6];
|
||||||
|
uint32_t h = ctx->state[7];
|
||||||
|
|
||||||
|
for (i = 0; i < 64; ++i) {
|
||||||
|
uint32_t t1 = h + EP1(e) + CH(e, f, g) + k[i] + m[i];
|
||||||
|
uint32_t t2 = EP0(a) + MAJ(a, b, c);
|
||||||
|
h = g;
|
||||||
|
g = f;
|
||||||
|
f = e;
|
||||||
|
e = d + t1;
|
||||||
|
d = c;
|
||||||
|
c = b;
|
||||||
|
b = a;
|
||||||
|
a = t1 + t2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->state[0] += a;
|
||||||
|
ctx->state[1] += b;
|
||||||
|
ctx->state[2] += c;
|
||||||
|
ctx->state[3] += d;
|
||||||
|
ctx->state[4] += e;
|
||||||
|
ctx->state[5] += f;
|
||||||
|
ctx->state[6] += g;
|
||||||
|
ctx->state[7] += h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
gl_sha256_init(gl_sha256_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
ctx->datalen = 0;
|
||||||
|
ctx->bitlen = 0;
|
||||||
|
ctx->state[0] = 0x6a09e667;
|
||||||
|
ctx->state[1] = 0xbb67ae85;
|
||||||
|
ctx->state[2] = 0x3c6ef372;
|
||||||
|
ctx->state[3] = 0xa54ff53a;
|
||||||
|
ctx->state[4] = 0x510e527f;
|
||||||
|
ctx->state[5] = 0x9b05688c;
|
||||||
|
ctx->state[6] = 0x1f83d9ab;
|
||||||
|
ctx->state[7] = 0x5be0cd19;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
gl_sha256_update(gl_sha256_ctx_t *ctx, const uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < len; ++i) {
|
||||||
|
ctx->data[ctx->datalen] = data[i];
|
||||||
|
ctx->datalen++;
|
||||||
|
if (ctx->datalen == 64) {
|
||||||
|
sha256_transform(ctx, ctx->data);
|
||||||
|
ctx->bitlen += 512;
|
||||||
|
ctx->datalen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
gl_sha256_final(gl_sha256_ctx_t *ctx, uint8_t digest[GL_SHA256_DIGEST_SIZE])
|
||||||
|
{
|
||||||
|
size_t i = ctx->datalen;
|
||||||
|
|
||||||
|
if (ctx->datalen < 56) {
|
||||||
|
ctx->data[i++] = 0x80;
|
||||||
|
while (i < 56) {
|
||||||
|
ctx->data[i++] = 0x00;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx->data[i++] = 0x80;
|
||||||
|
while (i < 64) {
|
||||||
|
ctx->data[i++] = 0x00;
|
||||||
|
}
|
||||||
|
sha256_transform(ctx, ctx->data);
|
||||||
|
memset(ctx->data, 0, 56);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->bitlen += (uint64_t)ctx->datalen * 8;
|
||||||
|
ctx->data[63] = (uint8_t)(ctx->bitlen);
|
||||||
|
ctx->data[62] = (uint8_t)(ctx->bitlen >> 8);
|
||||||
|
ctx->data[61] = (uint8_t)(ctx->bitlen >> 16);
|
||||||
|
ctx->data[60] = (uint8_t)(ctx->bitlen >> 24);
|
||||||
|
ctx->data[59] = (uint8_t)(ctx->bitlen >> 32);
|
||||||
|
ctx->data[58] = (uint8_t)(ctx->bitlen >> 40);
|
||||||
|
ctx->data[57] = (uint8_t)(ctx->bitlen >> 48);
|
||||||
|
ctx->data[56] = (uint8_t)(ctx->bitlen >> 56);
|
||||||
|
sha256_transform(ctx, ctx->data);
|
||||||
|
|
||||||
|
for (i = 0; i < 4; ++i) {
|
||||||
|
uint32_t shift = 24 - (uint32_t)i * 8;
|
||||||
|
digest[i] = (uint8_t)((ctx->state[0] >> shift) & 0xffU);
|
||||||
|
digest[i + 4] = (uint8_t)((ctx->state[1] >> shift) & 0xffU);
|
||||||
|
digest[i + 8] = (uint8_t)((ctx->state[2] >> shift) & 0xffU);
|
||||||
|
digest[i + 12] = (uint8_t)((ctx->state[3] >> shift) & 0xffU);
|
||||||
|
digest[i + 16] = (uint8_t)((ctx->state[4] >> shift) & 0xffU);
|
||||||
|
digest[i + 20] = (uint8_t)((ctx->state[5] >> shift) & 0xffU);
|
||||||
|
digest[i + 24] = (uint8_t)((ctx->state[6] >> shift) & 0xffU);
|
||||||
|
digest[i + 28] = (uint8_t)((ctx->state[7] >> shift) & 0xffU);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
gl_sha256_to_hex(const uint8_t digest[GL_SHA256_DIGEST_SIZE], char *out)
|
||||||
|
{
|
||||||
|
static const char hexchars[] = "0123456789abcdef";
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < GL_SHA256_DIGEST_SIZE; i++) {
|
||||||
|
out[i * 2] = hexchars[(digest[i] >> 4) & 0x0fU];
|
||||||
|
out[i * 2 + 1] = hexchars[digest[i] & 0x0fU];
|
||||||
|
}
|
||||||
|
out[GL_SHA256_DIGEST_SIZE * 2] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
gl_sha256_file(const char *path, char *hex_out)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_sha256_ctx_t ctx;
|
||||||
|
gl_sha256_init(&ctx);
|
||||||
|
|
||||||
|
uint8_t buf[65536];
|
||||||
|
size_t n;
|
||||||
|
while ((n = fread(buf, 1, sizeof(buf), f)) > 0) {
|
||||||
|
gl_sha256_update(&ctx, buf, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ferror(f)) {
|
||||||
|
fclose(f);
|
||||||
|
errno = EIO;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
uint8_t digest[GL_SHA256_DIGEST_SIZE];
|
||||||
|
gl_sha256_final(&ctx, digest);
|
||||||
|
gl_sha256_to_hex(digest, hex_out);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#ifndef GL_SHA256_H_
|
||||||
|
#define GL_SHA256_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define GL_SHA256_DIGEST_SIZE 32
|
||||||
|
#define GL_SHA256_HEX_SIZE (GL_SHA256_DIGEST_SIZE * 2 + 1)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t state[8];
|
||||||
|
uint64_t bitlen;
|
||||||
|
uint8_t data[64];
|
||||||
|
size_t datalen;
|
||||||
|
}
|
||||||
|
gl_sha256_ctx_t;
|
||||||
|
|
||||||
|
void gl_sha256_init(gl_sha256_ctx_t *ctx);
|
||||||
|
void gl_sha256_update(gl_sha256_ctx_t *ctx, const uint8_t *data, size_t len);
|
||||||
|
void gl_sha256_final(gl_sha256_ctx_t *ctx, uint8_t digest[GL_SHA256_DIGEST_SIZE]);
|
||||||
|
|
||||||
|
void gl_sha256_to_hex(const uint8_t digest[GL_SHA256_DIGEST_SIZE], char *out);
|
||||||
|
|
||||||
|
int gl_sha256_file(const char *path, char *hex_out);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,420 @@
|
|||||||
|
#include <linux/limits.h>
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "istoreutils.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "transaction.h"
|
||||||
|
|
||||||
|
#define GL_STAGE_BASE "/glacier/usr/stage"
|
||||||
|
#define GL_LOCK_BASE "/glacier/usr/lock"
|
||||||
|
#define GL_OLD_BASE "/glacier/usr/old"
|
||||||
|
|
||||||
|
#define GL_USR_INDEX "/glacier/usr/index"
|
||||||
|
#define GL_USR_STORE "/glacier/usr/store"
|
||||||
|
#define GL_USR_LINKS "/glacier/usr/links"
|
||||||
|
|
||||||
|
#define GL_SYS_INDEX "/glacier/sys/index"
|
||||||
|
#define GL_SYS_STORE "/glacier/sys/store"
|
||||||
|
#define GL_SYS_LINKS "/glacier/sys/links"
|
||||||
|
|
||||||
|
static int
|
||||||
|
fsync_dir(const char *path)
|
||||||
|
{
|
||||||
|
int fd = open(path, O_RDONLY | O_DIRECTORY);
|
||||||
|
if (fd < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int r = fsync(fd);
|
||||||
|
close(fd);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
atomic_swap(const char *live, const char *stage, const char *backup)
|
||||||
|
{
|
||||||
|
if (rename(live, backup) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rename(stage, live) != 0) {
|
||||||
|
rename(backup, live);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
derive_base_paths(gl_ctx_scope_t scope, uid_t uid, char *index_out,
|
||||||
|
char *store_out, char *links_out)
|
||||||
|
{
|
||||||
|
switch (scope) {
|
||||||
|
case GL_SCOPE_USR:
|
||||||
|
snprintf(index_out, PATH_MAX, "/glacier/usr/index/%u", uid);
|
||||||
|
snprintf(store_out, PATH_MAX, "/glacier/usr/store/%u", uid);
|
||||||
|
snprintf(links_out, PATH_MAX, "/glacier/usr/links/%u", uid);
|
||||||
|
break;
|
||||||
|
case GL_SCOPE_SYS:
|
||||||
|
strncpy(index_out, "/glacier/sys/index", PATH_MAX - 1);
|
||||||
|
strncpy(store_out, "/glacier/sys/store", PATH_MAX - 1);
|
||||||
|
/* This is the DEFAULT system-scope links location — for
|
||||||
|
* packages the operator explicitly installs under system
|
||||||
|
* scope that aren't part of the base system. Base-repo
|
||||||
|
* packages get routed to /usr instead, but that's a
|
||||||
|
* per-package decision made in gl_link_pkg/gl_relink_store
|
||||||
|
* (which know a package's repo), not something the context
|
||||||
|
* can decide up front. See GL_BASE_REPO in istoreutils.c. */
|
||||||
|
strncpy(links_out, "/glacier/sys/links", PATH_MAX - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
check_same_filesystem(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
struct stat sa, sb;
|
||||||
|
if (stat(a, &sa) != 0 || stat(b, &sb) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return sa.st_dev == sb.st_dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
hardlink_tree(const char *src, const char *dest)
|
||||||
|
{
|
||||||
|
DIR *dir = opendir(src);
|
||||||
|
if (!dir) {
|
||||||
|
return (errno == ENOENT) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *ent;
|
||||||
|
while ((ent = readdir(dir))) {
|
||||||
|
if (ent->d_name[0] == '.') { continue; }
|
||||||
|
|
||||||
|
char s[PATH_MAX], d[PATH_MAX];
|
||||||
|
snprintf(s, sizeof(s), "%s/%s", src, ent->d_name);
|
||||||
|
snprintf(d, sizeof(d), "%s/%s", dest, ent->d_name);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if(lstat(s, &st) != 0) { continue; }
|
||||||
|
|
||||||
|
if (S_ISDIR(st.st_mode)) {
|
||||||
|
if (mkdir(d, st.st_mode & 0777) != 0 && errno != EEXIST) {
|
||||||
|
closedir(dir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (hardlink_tree(s, d) != 0) {
|
||||||
|
closedir(dir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (S_ISLNK(st.st_mode)) {
|
||||||
|
char target[PATH_MAX];
|
||||||
|
ssize_t n = readlink(s, target, sizeof(target) - 1);
|
||||||
|
if (n < 0) { continue; }
|
||||||
|
target[n] = '\0';
|
||||||
|
symlink(target, d);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (link(s, d) != 0 && errno != EEXIST) {
|
||||||
|
closedir(dir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
gl_init_live_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid)
|
||||||
|
{
|
||||||
|
if (!ctx) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->uid = uid;
|
||||||
|
ctx->mode = GL_CTX_LIVE;
|
||||||
|
ctx->scope = scope;
|
||||||
|
ctx->lock_fd = -1;
|
||||||
|
|
||||||
|
strncpy(ctx->root_path, "/glacier", PATH_MAX - 1);
|
||||||
|
derive_base_paths(scope, uid, ctx->index_path, ctx->store_path,
|
||||||
|
ctx->links_path);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_txn_status_t
|
||||||
|
gl_init_stage_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid)
|
||||||
|
{
|
||||||
|
if (!ctx) {
|
||||||
|
return GL_TXN_ERR_NO_TXN;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->uid = uid;
|
||||||
|
ctx->mode = GL_CTX_STAGE;
|
||||||
|
ctx->scope = scope;
|
||||||
|
ctx->lock_fd = -1;
|
||||||
|
|
||||||
|
strncpy(ctx->root_path, "/glacier", PATH_MAX - 1);
|
||||||
|
|
||||||
|
char stage_base[PATH_MAX - 7];
|
||||||
|
|
||||||
|
switch (scope) {
|
||||||
|
case GL_SCOPE_USR:
|
||||||
|
snprintf(stage_base, sizeof(stage_base), "/glacier/usr/stage/%u", uid);
|
||||||
|
break;
|
||||||
|
case GL_SCOPE_SYS:
|
||||||
|
strncpy(stage_base, "/glacier/sys/stage", sizeof(stage_base));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(ctx->index_path, PATH_MAX, "%s/index", stage_base);
|
||||||
|
snprintf(ctx->store_path, PATH_MAX, "%s/store", stage_base);
|
||||||
|
snprintf(ctx->links_path, PATH_MAX, "%s/links", stage_base);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(stage_base, &st) == 0) {
|
||||||
|
if (gl_rmdir_recursive(stage_base) != 0) {
|
||||||
|
return GL_TXN_ERR_MKDIR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gl_mkdirp(GL_LOCK_BASE, 0700) != 0 && errno != EEXIST) {
|
||||||
|
return GL_TXN_ERR_MKDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_txn_status_t lock_status = gl_begin_transaction(ctx);
|
||||||
|
if (lock_status != GL_TXN_OK) {
|
||||||
|
return lock_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gl_mkdirp(ctx->index_path, 0700) != 0) {
|
||||||
|
gl_abort_transaction(ctx);
|
||||||
|
return GL_TXN_ERR_MKDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gl_mkdirp(ctx->store_path, 0700) != 0) {
|
||||||
|
gl_abort_transaction(ctx);
|
||||||
|
return GL_TXN_ERR_MKDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gl_mkdirp(ctx->links_path, 0700) != 0) {
|
||||||
|
gl_abort_transaction(ctx);
|
||||||
|
return GL_TXN_ERR_MKDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chown(stage_base, uid, (gid_t)-1) != 0) {
|
||||||
|
gl_abort_transaction(ctx);
|
||||||
|
return GL_TXN_ERR_PERM;
|
||||||
|
}
|
||||||
|
|
||||||
|
char live_index[PATH_MAX], live_store[PATH_MAX], live_links[PATH_MAX];
|
||||||
|
derive_base_paths(scope, uid, live_index, live_store, live_links);
|
||||||
|
/* live_links is populated (derive_base_paths always fills all
|
||||||
|
* three) but intentionally unused below — see the comment on the
|
||||||
|
* hardlink_tree call. */
|
||||||
|
|
||||||
|
/* Only index and store need copy-on-write seeding — the staged
|
||||||
|
* links tree is never actually read from or written to (gl_link_pkg
|
||||||
|
* skips GL_CTX_STAGE entirely; gl_relink_store always builds its
|
||||||
|
* own fresh live context rather than touching ctx->links_path of a
|
||||||
|
* stage context). Seeding it was always wasted work; now that
|
||||||
|
* live_links can be /usr for system scope, it would also be
|
||||||
|
* actively dangerous — hardlinking the entire /usr tree on every
|
||||||
|
* transaction, and risking EXDEV outright if /usr and the stage
|
||||||
|
* area are on different filesystems. */
|
||||||
|
if (hardlink_tree(live_index, ctx->index_path) != 0 ||
|
||||||
|
hardlink_tree(live_store, ctx->store_path) != 0) {
|
||||||
|
gl_abort_transaction(ctx);
|
||||||
|
return GL_TXN_ERR_MKDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GL_TXN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_txn_status_t
|
||||||
|
gl_begin_transaction(gl_context_t *ctx)
|
||||||
|
{
|
||||||
|
if (!ctx) {
|
||||||
|
return GL_TXN_ERR_NO_TXN;
|
||||||
|
}
|
||||||
|
|
||||||
|
char lock_path[PATH_MAX];
|
||||||
|
switch (ctx->scope) {
|
||||||
|
case GL_SCOPE_USR:
|
||||||
|
snprintf(lock_path, PATH_MAX, "%s/%u.lock", GL_LOCK_BASE,
|
||||||
|
ctx->uid);
|
||||||
|
break;
|
||||||
|
case GL_SCOPE_SYS:
|
||||||
|
snprintf(lock_path, PATH_MAX, "%s/sys.lock", GL_LOCK_BASE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd = open(lock_path, O_WRONLY | O_CREAT, 0600);
|
||||||
|
if (fd < 0) {
|
||||||
|
return GL_TXN_ERR_LOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flock(fd, LOCK_EX | LOCK_NB) != 0) {
|
||||||
|
close(fd);
|
||||||
|
return GL_TXN_ERR_LOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->lock_fd = fd;
|
||||||
|
return GL_TXN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_txn_status_t
|
||||||
|
gl_commit_transaction(gl_context_t *ctx)
|
||||||
|
{
|
||||||
|
if (!ctx || ctx->lock_fd < 0) {
|
||||||
|
return GL_TXN_ERR_NO_TXN;
|
||||||
|
}
|
||||||
|
if (ctx->mode != GL_CTX_STAGE) {
|
||||||
|
return GL_TXN_ERR_NO_TXN;
|
||||||
|
}
|
||||||
|
|
||||||
|
char stage_base[PATH_MAX];
|
||||||
|
switch (ctx->scope) {
|
||||||
|
case GL_SCOPE_USR:
|
||||||
|
snprintf(stage_base, PATH_MAX, "/glacier/usr/stage/%u",
|
||||||
|
ctx->uid);
|
||||||
|
break;
|
||||||
|
case GL_SCOPE_SYS:
|
||||||
|
strncpy(stage_base, "/glacier/sys/stage", PATH_MAX - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int same = check_same_filesystem(ctx->root_path, stage_base);
|
||||||
|
if (same < 0) {
|
||||||
|
return GL_TXN_ERR_RENAME; /* failed to stat either side */
|
||||||
|
}
|
||||||
|
if (!same) {
|
||||||
|
return GL_TXN_ERR_XDEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Derive live paths the same way gl_init_live_context would */
|
||||||
|
char live_index[PATH_MAX], live_store[PATH_MAX], live_links[PATH_MAX];
|
||||||
|
derive_base_paths(ctx->scope, ctx->uid, live_index, live_store,
|
||||||
|
live_links);
|
||||||
|
|
||||||
|
/* Derive old/backup paths */
|
||||||
|
char old_base[PATH_MAX];
|
||||||
|
switch (ctx->scope) {
|
||||||
|
case GL_SCOPE_USR:
|
||||||
|
snprintf(old_base, PATH_MAX, "/glacier/usr/old/%u", ctx->uid);
|
||||||
|
break;
|
||||||
|
case GL_SCOPE_SYS:
|
||||||
|
strncpy(old_base, "/glacier/sys/old", PATH_MAX - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char old_index[PATH_MAX], old_store[PATH_MAX], old_links[PATH_MAX];
|
||||||
|
snprintf(old_index, PATH_MAX, "%s/index", old_base);
|
||||||
|
snprintf(old_store, PATH_MAX, "%s/store", old_base);
|
||||||
|
snprintf(old_links, PATH_MAX, "%s/links", old_base);
|
||||||
|
|
||||||
|
struct stat old_st;
|
||||||
|
if (stat(old_base, &old_st) == 0) {
|
||||||
|
if (gl_rmdir_recursive(old_base) != 0) {
|
||||||
|
return GL_TXN_ERR_RENAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gl_mkdirp(old_base, 0700) != 0) {
|
||||||
|
return GL_TXN_ERR_RENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atomic_swap(live_index, ctx->index_path, old_index) != 0) {
|
||||||
|
return GL_TXN_ERR_RENAME;
|
||||||
|
}
|
||||||
|
if (atomic_swap(live_store, ctx->store_path, old_store) != 0) {
|
||||||
|
rename(live_index, ctx->index_path);
|
||||||
|
rename(old_index, live_index);
|
||||||
|
return GL_TXN_ERR_RENAME;
|
||||||
|
}
|
||||||
|
if (atomic_swap(live_links, ctx->links_path, old_links) != 0) {
|
||||||
|
rename(live_store, ctx->store_path);
|
||||||
|
rename(old_store, live_store);
|
||||||
|
rename(live_index, ctx->index_path);
|
||||||
|
rename(old_index, live_index);
|
||||||
|
return GL_TXN_ERR_RENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fsync the parent directories, not the uid subdirs */
|
||||||
|
switch (ctx->scope) {
|
||||||
|
case GL_SCOPE_USR:
|
||||||
|
fsync_dir("/glacier/usr/index");
|
||||||
|
fsync_dir("/glacier/usr/store");
|
||||||
|
fsync_dir("/glacier/usr/links");
|
||||||
|
break;
|
||||||
|
case GL_SCOPE_SYS:
|
||||||
|
fsync_dir("/glacier/sys/index");
|
||||||
|
fsync_dir("/glacier/sys/store");
|
||||||
|
fsync_dir("/glacier/sys/links");
|
||||||
|
fsync_dir("/usr"); /* base-repo packages land here instead */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_context_t live_ctx;
|
||||||
|
gl_init_live_context(&live_ctx, ctx->scope, ctx->uid);
|
||||||
|
|
||||||
|
if (gl_relink_store(&live_ctx) != 0) {
|
||||||
|
lg_printf(2, "Post-commit relink incomplete for uid %u", ctx->uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_rmdir_recursive(old_base);
|
||||||
|
|
||||||
|
/* Remove stage base */
|
||||||
|
gl_rmdir_recursive(stage_base);
|
||||||
|
|
||||||
|
close(ctx->lock_fd);
|
||||||
|
ctx->lock_fd = -1;
|
||||||
|
|
||||||
|
return GL_TXN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_txn_status_t
|
||||||
|
gl_abort_transaction(gl_context_t *ctx)
|
||||||
|
{
|
||||||
|
if (!ctx) {
|
||||||
|
return GL_TXN_ERR_NO_TXN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove stage area — live is untouched */
|
||||||
|
char stage_uid[PATH_MAX];
|
||||||
|
switch (ctx->scope) {
|
||||||
|
case GL_SCOPE_USR:
|
||||||
|
snprintf(stage_uid, PATH_MAX, "/glacier/usr/stage/%u",
|
||||||
|
ctx->uid);
|
||||||
|
break;
|
||||||
|
case GL_SCOPE_SYS:
|
||||||
|
strncpy(stage_uid, "/glacier/sys/stage", PATH_MAX - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_rmdir_recursive(stage_uid); /* best-effort; ignore errors */
|
||||||
|
|
||||||
|
/* Release lock */
|
||||||
|
|
||||||
|
if (ctx->lock_fd >= 0) {
|
||||||
|
close(ctx->lock_fd);
|
||||||
|
ctx->lock_fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GL_TXN_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef TRANSACTION_H_
|
||||||
|
#define TRANSACTION_H_
|
||||||
|
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#define GL_UID_NONE ((uid_t)0)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GL_CTX_LIVE,
|
||||||
|
GL_CTX_STAGE
|
||||||
|
} gl_ctx_mode_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GL_SCOPE_USR,
|
||||||
|
GL_SCOPE_SYS
|
||||||
|
} gl_ctx_scope_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uid_t uid;
|
||||||
|
|
||||||
|
char root_path[PATH_MAX];
|
||||||
|
char index_path[PATH_MAX];
|
||||||
|
char store_path[PATH_MAX];
|
||||||
|
char links_path[PATH_MAX];
|
||||||
|
|
||||||
|
gl_ctx_mode_t mode;
|
||||||
|
gl_ctx_scope_t scope;
|
||||||
|
|
||||||
|
int lock_fd;
|
||||||
|
} gl_context_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GL_TXN_OK = 0, /* success */
|
||||||
|
GL_TXN_ERR_LOCK = 1, /* unable to acquire lock */
|
||||||
|
GL_TXN_ERR_MKDIR = 2, /* unable to create txn stage */
|
||||||
|
GL_TXN_ERR_PERM = 3, /* permission error */
|
||||||
|
GL_TXN_ERR_RENAME = 4, /* atomic swap failed */
|
||||||
|
GL_TXN_ERR_SYNC = 5, /* fsync failed */
|
||||||
|
GL_TXN_ERR_CLEANUP = 6, /* cleanup failed */
|
||||||
|
GL_TXN_ERR_NO_TXN = 7, /* aborted without beginning */
|
||||||
|
GL_TXN_ERR_XDEV = 8 /* stage and live on different filesystems */
|
||||||
|
} gl_txn_status_t;
|
||||||
|
|
||||||
|
int gl_init_live_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid);
|
||||||
|
|
||||||
|
gl_txn_status_t gl_init_stage_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid);
|
||||||
|
gl_txn_status_t gl_begin_transaction(gl_context_t *ctx);
|
||||||
|
gl_txn_status_t gl_commit_transaction(gl_context_t *ctx);
|
||||||
|
gl_txn_status_t gl_abort_transaction(gl_context_t *ctx);
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in new issue
Block a user