From 7b22e3746a8966964a7210a142c3b8a2605bd7c4 Mon Sep 17 00:00:00 2001 From: Liam Waldron Date: Sun, 19 Jul 2026 17:49:23 -0400 Subject: [PATCH] 7/19 --- CLAUDE.md | 261 ++++ Makefile | 31 +- PACKAGE_SCOPES.txt | 177 +++ SECURITY_DESIGN.txt | 379 ++++++ SECURITY_REVIEW_2026-07-16.md | 130 ++ build/bin/gsha256sum | Bin 0 -> 16784 bytes build/lib/shared/libglacier-ng.so | Bin 0 -> 68328 bytes build/lib/shared/libglacier_dag.so | Bin 15944 -> 15952 bytes build/lib/shared/libglacier_istoreutils.so | Bin 32176 -> 44792 bytes build/lib/shared/libglacier_log.so | Bin 16600 -> 16608 bytes build/lib/shared/libglacier_pkg.so | Bin 21976 -> 21992 bytes build/lib/shared/libglacier_transaction.so | Bin 0 -> 20936 bytes build/lib/shared/libglacier_verify.so | Bin 0 -> 15968 bytes build/lib/tmp/log.o | Bin 21800 -> 11984 bytes build/lib/tmp/make_conf.o | Bin 12344 -> 3680 bytes build/lib/tmp/runtime.o | Bin 5440 -> 2008 bytes build/lib/tmp/sha256.o | Bin 0 -> 5744 bytes config.mk | 2 +- src/common.h | 67 +- src/gsha256sum.c | 21 + src/istoreutils.c | 579 ++++++--- src/istoreutils.c.back | 1365 ++++++++++++++++++++ src/istoreutils.c.rej | 302 +++++ src/istoreutils.h | 23 +- src/istoreutils.h.back | 101 ++ src/sha256.c | 203 +++ src/sha256.h | 26 + src/transaction.c | 420 ++++++ src/transaction.h | 55 + 29 files changed, 3949 insertions(+), 193 deletions(-) create mode 100644 CLAUDE.md create mode 100644 PACKAGE_SCOPES.txt create mode 100644 SECURITY_DESIGN.txt create mode 100644 SECURITY_REVIEW_2026-07-16.md create mode 100755 build/bin/gsha256sum create mode 100755 build/lib/shared/libglacier-ng.so create mode 100755 build/lib/shared/libglacier_transaction.so create mode 100755 build/lib/shared/libglacier_verify.so create mode 100644 build/lib/tmp/sha256.o create mode 100644 src/gsha256sum.c create mode 100644 src/istoreutils.c.back create mode 100644 src/istoreutils.c.rej create mode 100644 src/istoreutils.h.back create mode 100644 src/sha256.c create mode 100644 src/sha256.h create mode 100644 src/transaction.c create mode 100644 src/transaction.h diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f2a944b --- /dev/null +++ b/CLAUDE.md @@ -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}/`. +- `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 + `-` 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_=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. diff --git a/Makefile b/Makefile index c459893..3b627c5 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ 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" static: prepare log_static pkg_static istoreutils_static libglacier.a @@ -29,6 +29,7 @@ prepare: mkdir -p build/lib/tmp mkdir -p build/lib/shared mkdir -p build/include/glacier + mkdir -p build/bin log: $(CC) $(CFLAGS) src/log.c -c -o build/lib/tmp/log.o @@ -40,6 +41,22 @@ log_test: rt: $(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 $(CC) $(CFLAGS) -shared -fPIC src/istoreutils.c \ -Lbuild/lib/shared -lglacier_log -lglacier_pkg -larchive \ @@ -55,6 +72,12 @@ pkg: make_conf: $(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: $(CC) $(CFLAGS) -shared -fPIC src/dag.c \ -Lbuild/lib/shared -lglacier_log \ @@ -65,8 +88,10 @@ install: mkdir -p $(PREFIX)/lib/glacier/ 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_transaction.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: mkdir -p $(PREFIX)/lib/glacier diff --git a/PACKAGE_SCOPES.txt b/PACKAGE_SCOPES.txt new file mode 100644 index 0000000..00547f5 --- /dev/null +++ b/PACKAGE_SCOPES.txt @@ -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/ + /glacier/usr/store/ + /glacier/usr/links/ + Symlinks always land under /glacier/usr/links//{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/ + 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. diff --git a/SECURITY_DESIGN.txt b/SECURITY_DESIGN.txt new file mode 100644 index 0000000..882db5c --- /dev/null +++ b/SECURITY_DESIGN.txt @@ -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:", 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: + +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:` 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. diff --git a/SECURITY_REVIEW_2026-07-16.md b/SECURITY_REVIEW_2026-07-16.md new file mode 100644 index 0000000..dec0589 --- /dev/null +++ b/SECURITY_REVIEW_2026-07-16.md @@ -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 +`"-/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 +`"-/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 +``` diff --git a/build/bin/gsha256sum b/build/bin/gsha256sum new file mode 100755 index 0000000000000000000000000000000000000000..cbab32208aa2e6fa81c265aa2c6881e8dc4963bc GIT binary patch literal 16784 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 delta 2052 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 literal 32176 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 diff --git a/build/lib/shared/libglacier_log.so b/build/lib/shared/libglacier_log.so index 7fde15552183396abbb1133831c4ac333da3a5a1..e6395d152e16108e42463c3dad45441797da9f21 100755 GIT binary patch delta 2404 zcmZXWdu&rx7{JfDYsb1l+p))OQCWpM);S#Q*ybcuS}5U)gog~XI3P|Z1VbFkCYk_c zu7lonH*_BUkU->-si6F!mKZ=B#eq}8ALtZ$g+Ua&_*xg3yfoMEoYT9od6Ij+$M1Wb z^R>I(xn_K&8OPlCN=I=+=3pjbYcR88Z>gnb6t`+R&L7Km7ja`qlB>ADx%gURy$QgO z*N6i8Q|_XTho-$bY034-@{5I^m$uEk(A702ep)ynIAac8?LHT1JVt0N%0e;HVV;A} zkn`pRcvrkQy*dl`#W!Xb;zciyL8#kS5M~LoYJPr%J%5BfYlJ;*ggu4a8vCK8^`fl$ z-XDz+>C6c&r~{?tep*wDtn|og#j9XyP5b=_9rhXkQ< zqHS{(n2-yh577wKtY?web5HUPmMrMt&s#hHI~XwSu$6bX45oJN9_#Q0=&ZxLps)@r zhj$1Lb%@dk)!aaEeloNqp!_Q~qmv%F`P5Ok%ol=6yPU#Gj2c#=Z9+Z+y9+eQ(te2zZg>G=jN~2T*6ME8u zLP$Gu7cQW5%dH7p_%G1Cx;BTp)b&z^tOTVDZQmVT@5-f#;PcCBp2b3o9M>8kPU+Wb zSa>x>6^Nlepx%Vm4~eo`E!pQO*JY(oR>P7X4$>2p>@IeWU`TSvYEXIsT)7_Zl3bb$ z1&_g$pvh$|N{c9cS~tkq;XfOf8)cM!THDQP2PKbD$4yH^y~#$sDlLN^5K16gDjJk( z>g5s>8%3yToZNhZPP}$mU}<1k_lg(|Ja=2ye?O~T1rb{;D>vn6C44nLg1f=3E`nE% zP6fxe+;IvV?6}$*iqyP}3_7cd91z|F#N~scb#j)=*(A``bC(&f!u6 zuAqsx2HEFX2Tmxu&R}yXJI7fM=)f+3U?klkj~7{#uLP#Ay}|Vvpx5451qQkq zzdLS}nJg6S@3=OQZ7w@m>k{Lgt_aRspVakscy_{b0G^}poQ?-36r|zRq^__ePrj+^ z5%}WDw{*P|WCUb_>?!OGO@}`K`XXqAlwCm3OsicLWUHe+EogOZHqElSTg-E;2_dZk zXIk9>t1Do2K+H^Q#w>W%A^LW=L*BvPbUhn%1febF&8GG=RiJD#utNj7&R+iktGz`K z2CZ-@%4CcLw3FZ>(OeH<3Gzsh7_TZiluj>6l8;wmEnZw26|9w|-cqlt%u`nBsVu7? z0bx8@XflysJh@I7GZe`}YgluHQUc|Ws~*u=kfJfe4K`hml7LrqmV-uf;rD3RnoC{- z`W*N$pgX={5N#lPfGUh;qvQy>kM^~E#;6TRWWZbGOn@bz(MTHy$^+|b;w=-Mz2Lzp z2cf4PK%ibCbwK+W%}4wE!|8p}0W=%NCeYWYeK-}#@iGyYusuKq%0%-rh|Gq`<=~Ul z>s$ji{Tbd(-_@|1!KSN~ESf6ftt@jd+Y=;EE;_$V#V7N59Q&?-dV*Cuo1!Cs?0;r- z(4bD9YmCf9E8}Bq7fDBjh$B?@P7?Up=uqhDNS)1-@}Ei75mWvdtirQwe}(Ov2!?85 zrf+Ba$87IndoSC6r5(*U4WS7zmt6DehLxo+#S;}Zrg;9ey#iiG4o{zAF+MYm3{3CA eo5*3`6wA=RK=A?J76Cs@-ts?zFOg6EMgIW|f92@_ delta 2436 zcmZXWe@qld6vtJI*-n`Fy z-Ci{HGDM7QudNQD+}K^@5oKpCnJ{L5mMbtddf>p z03%T&3-q_dRaXlaUF^QuGL8IfJ@7}*+5Puct&VhxO`^LoSyvm<4VfemGM-pTBMqAu z3SIPud9iRZk{MlW6?!7O9ce;Qa2z4+_S9-#AcxHFj+w6-GtU__pE71Xi9Q;C!Xk(A zPQ($i=O#Ut5L~6xE)E`{tSDwqt6aUgh%4L7CU-@kt=t}#u5f!y>f*LZ>gVc!twY@eE44yrEv6YU1jtEJ^$p0KN4Jo0ORZrd%5C)=?1JjYtBQPo zx7BA)pj8`?%1Y&_K@)GsHc76$z?xpN#=qLX#{W+H`mB-Die)dMW6{cZ{tUQ!! zXW?Xgjz-`Cf8=^I5ICy`ni1eTSJyHWat`9Bh_*zbEk2yWyl6P;KE{F}r-?Nyl#^t@ zJ|wF<^ujO%!7K**iLuo>i_z;+4E8l+`8redx&(vmXH3!=3qtc1&VtxY1%okw`kudf z%=VnO9TMtyx?c;wr1x7)?5P1l{@($XR?9lj1ue9s8G+#r=n99p!d9;EpZba*UNG`& zM+6S(fr=3U4;OHriRQa*hv6-}2uCf}*#(_lGuW4miPlA%33`glak(*k?xc@^Jku2^nhm^-Ren+9P@l4IGg%4tp#@{?mFCuaqo=yC#OaU z<#b)zROgC^nzj!|cf})3I|X|Xwv!%7`z451Mc7}3a+I6|A@gnV^`dM`4n-}ndFo67 zTSkr9Z}V0~FSK0|qqYdKHm~27;kS9_+mewx-xl*G`=m_7os9aXK23ALC&;dft})k{ zLQ(Z17(%(=Ee|w}e-iw*_$pDHGGN1lF<1=P__wCzv0@IlGsM{@unW|Y?utn1Ezz`p zTCOk{$;_%1Z8=%lSw2s;H+z;h+xr^zi<4=$H<1>aOimFWn@xNxgbW)pj^6RQ+-}G+ z+F*p)lDH;Nzt82)g2yPnR->)}-3(fYm;f}$_xU1WJ3Rtg#i@hzkD*7|x|SwR?ZitT z_|o0Yn298g45qwLVO}fsWxL#$;Rz&xkk?1kYjhoGFQ-Z5i~P}afQCV>XqQOvYl_VLmof|4f(r z^l*NElAgy;5ZrTI?TVo`=3jx|IlXUC_BwDGEql)O&oX_%LBlyN;bZ3eJUB&cZ>%Zo z3J>pX%5c1ESjROS$7z5<0Ut~GSV0A-?dCq^<8eNo;p1gK-eD`6hoCtbeMyXTs2^rtmw%SF@%8}hIdMCA+Bq>9(OU^1Cs1*1Cfj85`W9$~sEFC^Ud&k(PzA1PrIL`#Bd6^E_1ip~g zo9)z=Y7QQoqeHg@B?%JarF6+7ybw`K7i1kD5_U2K|50I~i`J)R%S-8=RQuF}f))`T zb_vg&0zX7gq-I+hX6w^MbTGBlYZLYiy}iO-bf(U=PRP~>dp=raw#$`tqq$W6IUO8h zr%|(6Zti*8e87~y{-K7(ozlbGH*a3EZheD3Si9xn4blUf*KFIljdk0%ZDrloEn6CU zBI!$Hc?ErMtlhE6tJ|p)2{Q|`yQw_R?uZDgkxqkphmlag39ZuG$(*;+6`uA|*YU+P|t#n+85>c0iJ7 zG}oC@OgCg<4MfNPDUeFTh|&p6BA9kOijE@!DV7U+`W#0>bAs!X6F4@fdWNixYy03kob|PRVcqv@$f{aM zebqGp7Vj4%tYWtkY*|9oWS2F&6|f|!SJ)9OXM)&KKJ2LRW+baW;PNpt8xTHF`-AFP z?I(;elIaSmH^4dxtlEz_)CAeKc%xZ+k^2DVE2QO&9PGW65=z?U3##7=vm_He$sgV@ zlI*u;lkKv#j)wvJkWh-#D{M3hzX1M8W``0_|AaC zcSL+I6BH5Wr2dbHUt)|S5zhjv_6&zcB7TGWkPtCJ#G?Yfd_8`z*jE2P;@Jp<70q)< zdDZiPBAB$(?wOOm zc@zK9D4LTXh3aG$)zQDYuXDqOd}A~CYf)Z50H?pEmZ-O@iFys?vY1+Cxx&5N#CkY% zS*zvaJk)7st%6xoFO!Z{?j&s?o5+1cy`Wv-_!)Mtr(pGh>fkcAJcF=t5;lnX2^{(! zW5OYGvl6aXa0Lz)6IEm78xCqWzQk!t&u&K4QnmWNW~Q&r%FZ6(+TTKzB~S7*JSvXZ z*TeMMtQn?14AaSZ3k#keW;PyoY90E(t+na{kM@{8DAu;JLC51}EjCQs^0Mh?d6ScT z+AP|aH+7tu(bNm1rO&A~(W`lO(`VOc+U$aG9f_p+TCb0)+^bwGi25~AFR_DT)>w&R zhpp`dd?%5Tv|Oy*IBu%R?j!!9x+>eF7}YD=~xF;P~vaNL|IhY)v@NDWHcmqOgBy&*<6t(%RI{}unv|96o8 zI&XAkAGViXB;fgd@JOo>V#4$Jcew7>U0-0hZg|auh%LsE)`}p{x9l8y_#7&lwj4m@ z+qOvcSZ_444b`dS6oVIWr?xpU@5LP3WTuX|fkbu0t|K1y|6B4VlS*Mo%VR#N@qR$8 zVrpCe&Y~+rSu1%R^9UImsYJE$1R@)JHmH4{(OVuPQe!Dt-e4j=T?B=yq!sg(qhy#! ztc%e*N_H{cOhbHoFQ6wGdrZ>l>{U26e6?p+{=M=mo}f%OdneO_UMp?&P8=r{Jg~Wb z?Z)+63-E?XpYcwo_j@h$tao{MGQ<+ z{W0_ni2o+TamEAi#e=%kgnkG;%Z3fr^8<#XA42a#pN!qojD9luhOgpj@f8>x!~ba$ zJv_G}?16c_IY}P4#9!DKE4Fr1xstUv$*(wDk}H&BPSfr*#Tiht{EBU{V)mzJfqw^n zx30wFndl|(v?T9MYG*EBdH}y>=;Jhuo%kKWuN@fwB!Qw9Q$T5!P0L?OQ)~gIlnI{4 z&y5^7(6fgrxQJgNFgJQBpxD+bW^Uvrrk!wLBEuCM4`M8GQ%v8!yR?=7tYI00>X3tl4`1GPchv--=LV60K>TJd?i}nz9iF;cTI$MOU~D|r*tImOL_t!6+*lV zx*BPHVL^E@W&@ZVMa^PoUP8h;OhF~9oh58rNQ(Oa$KgEm!?Zjzysi!jSa&;^_H@gFuju2ri!s3nbz?wABaCVc7^iXi!h0_LV(P}$-$YNeoJ%Aa|(ez83m z#F$Zy1PP+F1_K$(9j($45s$Qqky}ANS*}hub3y6eonPYU5I0b zgS~>Aq_NV`d_#xce@v^&><*3dW=h8rG+m{8Kuv}_GOiK~?+gt#+70S%P)!ov%bf_SJilRW z@MEDf@yda124<_vrOOz_;fBByuOd?DBk(hewcfk2*O_eMWlE{Q6R#DPa@j{%>sT## z60b(KaLw$WgK2JvS?pV<(EpskyAtptLXR&&&uhTPXFMWA(c<-iQPejlyTo;?5#?=pV;NKtevt|XmIB0qQ=!!Gw>Yb zH#4PS(*yZydxprTDLn)CbeZIKy0falVw|p{w7aU`bjw6{Ezh@%+~oCiFYlM-DBW38 lU@^{8ly=vY0=tU#BK6jmS|nB^@EoK&Yv;=cX?N|6e*yb}`_BLX delta 5143 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 LabyW(k=p+P|5;of literal 0 HcmV?d00001 diff --git a/build/lib/shared/libglacier_verify.so b/build/lib/shared/libglacier_verify.so new file mode 100755 index 0000000000000000000000000000000000000000..d7146ba363efaf93c1829cc67664da96ffc24f01 GIT binary patch literal 15968 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 VL+fiM`yF@_2YJj&GFQ9(zW@>S1aJTV literal 21800 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 F{NDsx!wUca diff --git a/build/lib/tmp/make_conf.o b/build/lib/tmp/make_conf.o index 5ea55e9d4336233f8808a30dc92463a72c5a1c7b..14f6d644edd8ae7958abf9ab1f098600bc868e5a 100644 GIT binary patch literal 3680 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 F{{TCe-YWnA diff --git a/build/lib/tmp/runtime.o b/build/lib/tmp/runtime.o index 0df3025614ddb81b6be049e2a81d59a60c5d11fe..db544533cd5976cf6cbcf5ed76d96f77c6cccaf1 100644 GIT binary patch literal 2008 zcmbtU&1(};5T8v`yS8;3rAo9)bBG#Ix7mQjs>qh6W`)vHwcrP`Y?I9g%}3qcK&uB2 zQK6JV5B>ul1pfq&fqL>Jc=0ZRP!D?XB-EMR_mbBoz39MXW`6HCGw;25o9DB0w<3}R zTqL*&-H4+A_oH23_gx(nI08oNhy0!5mu+uIYa6W(^3KS(4h$Q49YDpA@fUv9(p}6* z6^za3*%SiCMm8BYw)$z!*!XH}Rg*DeBbw|$*`#c2Ue|u}9^=?!^IkGuFfNYz6`E(T zXX?@89c+=sBCQ?SnA&ZAd{3v?{p{yW*j|33RJHOI+u>HtH|j;$w^FlK>;Q(`%*?e! zDmTA$A)!sGnyMu->C9w$GIND?ibB_sK&71{4I&`53FCJsh2l|MBaxoo0ckBJjSUaT z+eki*7%9nl`pGJ>g+WE09hz5U3@@SHARMJhKhsL=Rb*O;xB9Y*9$6ez;)qNua&CxL z4;0#06n^dXBJ_23-CYf;`E%)l=*9e~A5QH<0ghvQOsM0Z1+fHbkb{mOv`+jq!~CVMNnjtGJ#ERz7dLE|{k4 zS@{(+UtTebR;7w^Y6xdVO& zwVGW=O1Uf&b?)*yobZ^KT12=a9{Cf(W7?h94j{fGWOKO1a92 z;eGM@Cl1LIRHBCS%PhZ4BtgRY^da|}|2sAi-k;`A-x<&6^Y7K0+I?dEe^P)ljM|SH j&ZnB;*&_sPcA!NH1Yu=6$Wioq<$qz|vko(G!ufvzXD{I0 literal 5440 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 literal 0 HcmV?d00001 diff --git a/config.mk b/config.mk index 6658c60..d57e28c 100644 --- a/config.mk +++ b/config.mk @@ -4,6 +4,6 @@ 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 diff --git a/src/common.h b/src/common.h index 963dc3d..96c29ef 100644 --- a/src/common.h +++ b/src/common.h @@ -1,17 +1,17 @@ #ifndef COMMON_H_ #define COMMON_H_ -#ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L -#endif -#ifndef _DEFAULT_SOURCE -#define _DEFAULT_SOURCE -#endif -#ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 -#endif +#include +#include +#include +#include +#include +#include #include +#include typedef struct { @@ -20,4 +20,57 @@ typedef struct } 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 diff --git a/src/gsha256sum.c b/src/gsha256sum.c new file mode 100644 index 0000000..1f26b5b --- /dev/null +++ b/src/gsha256sum.c @@ -0,0 +1,21 @@ +#include + +#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; +} diff --git a/src/istoreutils.c b/src/istoreutils.c index deaf3f2..3cb2409 100644 --- a/src/istoreutils.c +++ b/src/istoreutils.c @@ -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,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 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 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; } +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 gl_init_user(uid_t uid, bool isVerbose) { @@ -253,6 +295,46 @@ gl_init_user(uid_t uid, bool isVerbose) 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) { @@ -313,17 +395,34 @@ gl_parse_index(struct gindex *idx, FILE *f) char *first = strstr(line, "::"); if (!first) { continue; } - *first = 0; + 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(pkg); + trim(ver); struct gpkg_entry ent; ent.repo = dupstr(repo); 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 = 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++) { free(idx->entries[i].repo); free(idx->entries[i].pkg); + free(idx->entries[i].ver); } free(idx->entries); idx->entries = NULL; idx->count = 0; } -/* 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); - 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); + DIR *store_dir = opendir(ctx->store_path); if (!store_dir) return -1; /* 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)); 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 +477,7 @@ gl_rebuild_index(uid_t uid, const char *out_dir) 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)) { @@ -461,9 +500,33 @@ gl_rebuild_index(uid_t uid, const char *out_dir) continue; } - fprintf(out, "%s::%s\n", + /* One version dir per package name, named + * "-" (see gl_cat_dir_and_ver + * in pkg.c, which is what created it). Strip the + * known "-" 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, - pkg_ent->d_name); + pkg_ent->d_name, + pkg_ver); listed++; } closedir(repo_dir); @@ -476,14 +539,15 @@ gl_rebuild_index(uid_t uid, const char *out_dir) 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 +558,15 @@ gl_rebuild_index(uid_t uid, const char *out_dir) 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,62 +736,14 @@ gl_backup_istore(const char *tar_path, } static 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; -} - -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) +gl_unlink_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo) { 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); @@ -781,12 +797,14 @@ gl_unlink_pkg(const char *pkg_store_final, uid_t uid) } 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)) { @@ -811,9 +829,14 @@ gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid) return GL_REMOVE_ERR_NOT_FOUND; } - /* remove symlinks from links tree */ - if (gl_unlink_pkg(pkg_store_final, uid) != 0) { - return GL_REMOVE_ERR_UNLINK; + /* remove symlinks from links tree. GL_CTX_STAGE: skip — the + * stage links tree isn't seeded (links are fully derived from + * 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 */ @@ -822,10 +845,7 @@ gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid) } /* 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; } @@ -833,10 +853,14 @@ gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid) } 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]; - 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); @@ -895,18 +919,29 @@ gl_link_pkg(const char *pkg_store_final, uid_t uid) gl_mkdirp(parent, 0700); } - /* remove existing symlink if present */ - unlink(link_path); + /* Only ever replace an existing entry if it's a + * 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 */ - if (symlink(full_path, link_path) != 0) { - closedir(dir); - free(current); - while (top > 0) { free(stack[--top]); } - return -1; + /* symlink store path -> links tree */ + if (symlink(full_path, link_path) != 0) { + closedir(dir); + free(current); + while (top > 0) { free(stack[--top]); } + 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; } +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_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_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; @@ -1086,7 +1194,7 @@ gl_install_pkg(const char *gpkg_path, uid_t uid) /* 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, @@ -1186,15 +1294,13 @@ gl_install_pkg(const char *gpkg_path, uid_t uid) 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 */ + /* Resolve "current" symlink to confirm an index exists to rebuild + * against. The actual write happens in gl_rebuild_index below — + * anything written here would just be overwritten by that call, + * since it creates a fresh timestamped index file and repoints + * "current" at it rather than editing this one in place. */ 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); @@ -1204,31 +1310,148 @@ gl_install_pkg(const char *gpkg_path, uid_t uid) } current_target[len] = '\0'; - char index_path[PATH_MAX]; - snprintf(index_path, sizeof(index_path), "%s/%s", index_dir, current_target); - - FILE *idx = fopen(index_path, "a"); - if (!idx) { + if (gl_rebuild_index(ctx) != 0) { gl_free_pkg(&pkg); return GL_INSTALL_ERR_INDEX; } - fprintf(idx, "%s::%s\n", pkg.pkg_repo, pkg.pkg_name); - fclose(idx); - - if (gl_rebuild_index(uid, index_dir) != 0) { - 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; + if (ctx->mode == GL_CTX_LIVE) { + if (gl_link_pkg(ctx, pkg_store_final, pkg.pkg_repo) != 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); chdir(original_cwd); 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; +} \ No newline at end of file diff --git a/src/istoreutils.c.back b/src/istoreutils.c.back new file mode 100644 index 0000000..5d52e76 --- /dev/null +++ b/src/istoreutils.c.back @@ -0,0 +1,1365 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#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 }; + +static const dir_t usr_store = { "/glacier/usr/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 sys_links = { "/glacier/sys/links", 0700 }; + +bool +gl_usr_istore_exists(index_store_t index_or_store, int uid) +{ + char path[PATH_MAX]; + struct stat st; + + switch (index_or_store) { + case INDEX: + snprintf(path, sizeof(path), "%s/%d", usr_index.path, uid); + break; + case STORE: + snprintf(path, sizeof(path), "%s/%d", usr_store.path, uid); + break; + default: + printf("libglacier: argument index_or_store of function gl_usr_istore_exists must be INDEX or STORE\n"); + return false; + } + + if (stat(path, &st) != 0) { + return false; + } + + return S_ISDIR(st.st_mode); +} + +bool +gl_sys_istore_exists(index_store_t index_or_store) +{ + char path[PATH_MAX]; + struct stat st; + + switch (index_or_store) { + case INDEX: + snprintf(path, sizeof(path), "%s", sys_index.path); + break; + case STORE: + snprintf(path, sizeof(path), "%s", sys_store.path); + break; + default: + printf("libglacier: argument index_or_store of function gl_sys_istore_exists must be INDEX or STORE\n"); + return false; + } + + if (stat(path, &st) != 0) { + return false; + } + + return S_ISDIR(st.st_mode); +} + +static void +make_index_name(char *buffer, size_t buffersz) +{ + time_t now = time(NULL); + struct tm *tm = localtime(&now); + strftime(buffer, buffersz, "index-%m_%d_%Y@%H:%M", tm); +} + +static int +create_index(const char *ind_path, uid_t uid) +{ + char fname[64]; + make_index_name(fname, sizeof(fname)); + + char full_path[PATH_MAX]; + snprintf(full_path, sizeof(full_path), "%s/%s", ind_path, fname); + + FILE *f = fopen(full_path, "w"); + if (!f) { return -1; } + fprintf(f, "uid = %d\n", uid); + fprintf(f, "listed = 0\n\n"); + fclose(f); + + if (chown(full_path, uid, (gid_t)-1) != 0) { return -1; } + + char link_path[PATH_MAX]; + snprintf(link_path, sizeof(link_path), "%s/current", ind_path); + unlink(link_path); + + if (symlink(fname, link_path) != 0) { return -1; } + if (lchown(link_path, uid, (gid_t)-1) != 0) { return -1; } + + return 0; +} + +static bool +is_dir(const char *d) +{ + struct stat st; + return stat(d, &st) == 0 && S_ISDIR(st.st_mode); +} + + +int +gl_init_user(uid_t uid, bool isVerbose) +{ + if (isVerbose) { lg_printf(0, "Checking if UID is within acceptable bounds..."); } + if (uid < 1000) { + if (isVerbose) { + lg_printf(2, "Attempted to create index and store for UID < 1000. Quitting..."); + } + return 2; + } + + if (isVerbose) { lg_printf(0, "Constructing paths for index and store..."); } + + char ind_path[PATH_MAX]; + char sto_path[PATH_MAX]; + char lnk_path[PATH_MAX]; + + snprintf(ind_path, sizeof(ind_path), "%s/%d", usr_index.path, uid); + snprintf(sto_path, sizeof(sto_path), "%s/%d", usr_store.path, uid); + snprintf(lnk_path, sizeof(lnk_path), "%s/%d", usr_links.path, uid); + + if (isVerbose) { lg_printf(0, "Paths constructed:\n%s\n%s", ind_path, sto_path); } + + if (isVerbose) { lg_printf(0, "Attempting to create index directory at %s", ind_path); } + if (mkdir(ind_path, 0700) == -1) { + switch (errno) { + case EACCES: + lg_printf(2, "Cannot create index: permission denied"); + return 1; + break; + case EEXIST: + lg_printf(2, "Cannot create index: directory already exists"); + return 1; + break; + case ENOENT: + /* should never happen, but here for completeness */ + lg_printf(2, "Cannot create index: parent directory does not exist"); + return 1; + break; + case ENOSPC: + lg_printf(2, "Cannot create index: no space left on device"); + return 1; + break; + case EROFS: + lg_printf(2, "Cannot create index: read only filesystem"); + return 1; + break; + default: + printf("libglacier: mkdir error in function gl_init_user\n"); + return -1; + break; + } + } + + if (isVerbose) { lg_printf(0, "Attempting to create store directory at %s", sto_path); } + if (mkdir(sto_path, 0700) == -1) { + switch (errno) { + case EACCES: + lg_printf(2, "Cannot create store: permission denied"); + return 1; + break; + case EEXIST: + lg_printf(2, "Cannot create store: directory already exists"); + return 1; + break; + case ENOENT: + /* should never happen, but here for completeness */ + lg_printf(2, "Cannot create store: parent directory does not exist"); + return 1; + break; + case ENOSPC: + lg_printf(2, "Cannot create store: no space left on device"); + return 1; + break; + case EROFS: + lg_printf(2, "Cannot create store: read only filesystem"); + return 1; + break; + default: + printf("libglacier: mkdir error in function gl_init_user\n"); + return -1; + break; + } + } + + if (isVerbose) { lg_printf(0, "Attempting to create links directory at %s", lnk_path); } + if (mkdir(lnk_path, 0700) == -1) { + switch (errno) { + case EACCES: + lg_printf(2, "Cannot create store: permission denied"); + return 1; + break; + case EEXIST: + lg_printf(2, "Cannot create store: directory already exists"); + return 1; + break; + case ENOENT: + /* should never happen, but here for completeness */ + lg_printf(2, "Cannot create store: parent directory does not exist"); + return 1; + break; + case ENOSPC: + lg_printf(2, "Cannot create store: no space left on device"); + return 1; + break; + case EROFS: + lg_printf(2, "Cannot create store: read only filesystem"); + return 1; + break; + default: + printf("libglacier: mkdir error in function gl_init_user\n"); + return -1; + break; + } + } + + if (isVerbose) { lg_printf(0, "Changing ownership of index and store to UID %i", uid); } + + if (chown(sto_path, uid, (gid_t)-1) != 0) { + return 1; + } + + if (chown(ind_path, uid, (gid_t)-1) != 0) { + return 1; + } + + if (chown(lnk_path, uid, (gid_t)-1) != 0) { + return 1; + } + + if (create_index(ind_path, uid) != 0) { + /* No switch for errno. chown() shouldn't fail if mkdir() didn't */ + lg_printf(2, "Cannot create initial index file"); + return 1; + } + 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) +{ + char *p = s; + while (*p && isspace((unsigned char)*p)) { + p++; + } + + if (p != s) { + memmove(s, p, strlen(p) + 1); + } + + size_t len = strlen(s); + while (len && isspace((unsigned char)s[len - 1])) { + s[--len] = 0; + } +} + +static char +*dupstr(const char *s) +{ + size_t n = strlen(s); + char *p = malloc(n + 1); + if (!p) { + return NULL; + } + + memcpy(p, s, n + 1); + return p; +} + +int +gl_parse_index(struct gindex *idx, FILE *f) +{ + idx->uid = -1; + idx->listed = -1; + idx->entries = NULL; + idx->count = 0; + + char line[512]; + + while (fgets(line, sizeof(line), f)) { + trim(line); + if (!line[0] || line[0] == '#') { continue; } + + char *eq = strchr(line, '='); + if (eq) { + *eq = 0; + char *key = line; + char *val = eq + 1; + trim(key); + trim(val); + + if (strcmp(key, "uid") == 0) { idx->uid = atoi(val); } + else if (strcmp(key, "listed") == 0) { idx->listed=atoi(val); } + continue; + } + + char *first = strstr(line, "::"); + if (!first) { continue; } + + *first = 0; + char *repo = line; + char *pkg = first + 2; + trim(repo); + trim(pkg); + + struct gpkg_entry ent; + ent.repo = dupstr(repo); + ent.pkg = dupstr(pkg); + if (!ent.repo || !ent.pkg) { return -1; } + + struct gpkg_entry *tmp = + realloc(idx->entries, sizeof(*idx->entries) * (idx->count + 1)); + if (!tmp) { return -1; } + idx->entries = tmp; + idx->entries[idx->count++] = ent; + } + + return 0; +} + +void +gl_free_index(struct gindex *idx) +{ + for (size_t i = 0; i < idx->count; i++) { + free(idx->entries[i].repo); + free(idx->entries[i].pkg); + } + + free(idx->entries); + idx->entries = NULL; + idx->count = 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; + + 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(gl_context_t *ctx) +{ + if (!ctx) { return -1; } + + DIR *store_dir = opendir(ctx->store_path); + if (!store_dir) return -1; + + /* Write to a new timestamped index file */ + char fname[64]; + make_index_name(fname, sizeof(fname)); + + char new_index_path[PATH_MAX]; + snprintf(new_index_path, sizeof(new_index_path), "%s/%s", + ctx->index_path, fname); + + FILE *out = fopen(new_index_path, "w"); + if (!out) { + closedir(store_dir); + 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", + ctx->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); + } + closedir(store_dir); + fclose(out); + + /* Prepend header by copying through a temp file then renaming over */ + FILE *tmp_in = fopen(new_index_path, "r"); + if (!tmp_in) return -1; + + char tmp_path[PATH_MAX]; + 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", ctx->uid); + fprintf(tmp_out, "listed = %zu\n\n", listed); + + char line[512]; + while (fgets(line, sizeof(line), tmp_in)) { + fputs(line, tmp_out); + } + fclose(tmp_in); + fclose(tmp_out); + + rename(tmp_path, new_index_path); + 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", ctx->index_path); + unlink(link_path); + + if (symlink(fname, link_path) != 0) { return -1; } + lchown(link_path, ctx->uid, (gid_t)-1); + + return 0; +} + +static int +add_to_archive(struct archive *a, const char *real_path, const char *archive_path) +{ + struct stat st; + + if (lstat(real_path, &st) != 0) { + return -1; + } + + if (S_ISDIR(st.st_mode)) { + struct archive_entry *entry = archive_entry_new(); + archive_entry_set_pathname(entry, archive_path); + archive_entry_set_filetype(entry, AE_IFDIR); + archive_entry_set_perm(entry, st.st_mode & 0777); + + if (archive_write_header(a, entry) != ARCHIVE_OK) { + archive_entry_free(entry); + return -1; + } + + archive_entry_free(entry); + + DIR *dir = opendir(real_path); /* NEW */ + if (!dir) { + return -1; + } + + struct dirent *ent; + while ((ent = readdir(dir))) { /* NEW */ + if (strcmp(ent->d_name, ".") == 0 || + strcmp(ent->d_name, "..") == 0) { + continue; + } + + char child_real[PATH_MAX]; /* NEW */ + char child_arch[PATH_MAX]; /* NEW */ + + snprintf(child_real, sizeof(child_real), + "%s/%s", real_path, ent->d_name); + + snprintf(child_arch, sizeof(child_arch), + "%s/%s", archive_path, ent->d_name); + + if (add_to_archive(a, child_real, child_arch) != 0) { + closedir(dir); + return -1; + } + } + + closedir(dir); + return 0; + } + + /* existing file handling logic continues below */ + + if (S_ISREG(st.st_mode)) { + + int fd = open(real_path, O_RDONLY); + if (fd < 0) { + return -1; + } + + struct archive_entry *entry = archive_entry_new(); + archive_entry_set_pathname(entry, archive_path); + archive_entry_set_size(entry, st.st_size); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, st.st_mode & 0777); + + if (archive_write_header(a, entry) != ARCHIVE_OK) { + archive_entry_free(entry); + close(fd); + return -1; + } + + char buf[8192]; + ssize_t n; + + while ((n = read(fd, buf, sizeof(buf))) > 0) { + if (archive_write_data(a, buf, (size_t)n) < 0) { + archive_entry_free(entry); + close(fd); + return -1; + } + } + + archive_entry_free(entry); + close(fd); + + if (n < 0) { + return -1; + } + + return 0; + } + + return 0; /* NEW: ignore other file types */ +} + +gl_backup_status_t +gl_backup_istore(const char *tar_path, + const char *glacier_root, + const char *uid) +{ + struct archive *a = archive_write_new(); + if (!a) { return GL_BACKUP_ERR_ALLOC; } + + if (archive_write_add_filter_gzip(a) != ARCHIVE_OK) { + archive_write_free(a); + return GL_BACKUP_ERR_OPEN; + } + + if (archive_write_set_format_pax_restricted(a) != ARCHIVE_OK) { + archive_write_free(a); + return GL_BACKUP_ERR_OPEN; + } + + if (archive_write_open_filename(a, tar_path) != ARCHIVE_OK) { + archive_write_free(a); + return GL_BACKUP_ERR_OPEN; + } + + char index_path[1024]; + char store_path[1024]; + char links_path[1024]; + + char arch_index[512]; + char arch_store[512]; + char arch_links[512]; + + snprintf(index_path, sizeof(index_path), "%s/index/%s", glacier_root, uid); + snprintf(store_path, sizeof(store_path), "%s/store/%s", glacier_root, uid); + snprintf(links_path, sizeof(links_path), "%s/links/%s", glacier_root, uid); + + snprintf(arch_index, sizeof(arch_index), "%s/index", uid); + snprintf(arch_store, sizeof(arch_store), "%s/store", uid); + snprintf(arch_links, sizeof(arch_links), "%s/links", uid); + + if (add_to_archive(a, index_path, arch_index) != 0) { + archive_write_close(a); + archive_write_free(a); + return GL_BACKUP_ERR_ADD_INDEX; + } + + if (add_to_archive(a, store_path, arch_store) != 0) { + archive_write_close(a); + archive_write_free(a); + return GL_BACKUP_ERR_ADD_STORE; + } + + if (add_to_archive(a, links_path, arch_links) != 0) { + archive_write_close(a); + archive_write_free(a); + return GL_BACKUP_ERR_ADD_LINKS; + } + + if (archive_write_close(a) != ARCHIVE_OK) { + archive_write_free(a); + return GL_BACKUP_ERR_CLOSE; + } + + archive_write_free(a); + return GL_BACKUP_OK; +} + +static int +gl_unlink_pkg(gl_context_t *ctx, const char *pkg_store_final) +{ + char links_base[PATH_MAX]; + strncpy(links_base, ctx->links_path, PATH_MAX - 1); + + size_t strip_len = strlen(pkg_store_final); + + char *stack[4096]; + int top = 0; + stack[top++] = strdup(pkg_store_final); + + while (top > 0) { + char * current = stack[--top]; + + DIR *dir = opendir(current); + if (!dir) { + free(current); + continue; + } + + struct dirent *ent; + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') { continue; } + + char full_path[PATH_MAX]; + snprintf(full_path, sizeof(full_path), "%s/%s", + current, ent->d_name); + + struct stat st; + if (lstat(full_path, &st) != 0) { continue; } + const char *rel = full_path + strip_len; + if (rel[0] == '/') { rel++; } + + if (strncmp(rel, "usr/", 4) == 0) { rel += 4; } + + char link_path[PATH_MAX]; + snprintf(link_path, sizeof(link_path), "%s/%s", + links_base, rel); + + if (S_ISDIR(st.st_mode)) { + if (top < 4096) { + stack[top++] = strdup(full_path); + } + } + else if (S_ISREG(st.st_mode)) { + unlink(link_path); + } + } + + closedir(dir); + free(current); + } + + return 0; +} + +gl_remove_status_t +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/%s/%s", ctx->store_path, pkg_repo, pkg_name); + + struct stat st; + if (stat(pkg_store_base, &st) != 0 || !S_ISDIR(st.st_mode)) { + return GL_REMOVE_ERR_NOT_FOUND; + } + + /* find the versioned subdirectory */ + DIR *dir = opendir(pkg_store_base); + if (!dir) { return GL_REMOVE_ERR_NOT_FOUND; } + + char pkg_store_final[PATH_MAX] = {0}; + struct dirent *ent; + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') { continue; } + snprintf(pkg_store_final, sizeof(pkg_store_final), + "%s/%s", pkg_store_base, ent->d_name); + break; + } + closedir(dir); + + if (pkg_store_final[0] == '\0') { + return GL_REMOVE_ERR_NOT_FOUND; + } + + /* remove symlinks from links tree. GL_CTX_STAGE: skip — the + * stage links tree isn't seeded (links are fully derived from + * 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) != 0) { + return GL_REMOVE_ERR_UNLINK; + } + } + + /* remove package from store */ + if (gl_rmdir_recursive(pkg_store_base) != 0) { + return GL_REMOVE_ERR_UNLINK; + } + + /* rebuild index */ + if (gl_rebuild_index(ctx) != 0) { + return GL_REMOVE_ERR_INDEX; + } + + return GL_REMOVE_OK; +} + +static int +gl_link_pkg(gl_context_t *ctx, const char *pkg_store_final) +{ + char links_base[PATH_MAX]; + strncpy(links_base, ctx->links_path, PATH_MAX - 1); + + size_t strip_len = strlen(pkg_store_final); + + /* use a stack-based queue to walk the tree without recursion */ + char *stack[4096]; + int top = 0; + + stack[top++] = strdup(pkg_store_final); + + while (top > 0) { + char *current = stack[--top]; + + DIR *dir = opendir(current); + if (!dir) { + free(current); + continue; + } + + struct dirent *ent; + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') { continue; } + + char full_path[PATH_MAX]; + snprintf(full_path, sizeof(full_path), "%s/%s", + current, ent->d_name); + + struct stat st; + if (lstat(full_path, &st) != 0) { continue; } + + /* relative path inside the package, e.g. usr/bin/kilo */ + const char *rel = full_path + strip_len; + if (rel[0] == '/') { rel++; } + + /* strip leading usr/ so bin/kilo lands at links/1000/bin/kilo */ + if (strncmp(rel, "usr/", 4) == 0) { rel += 4; } + + char link_path[PATH_MAX]; + snprintf(link_path, sizeof(link_path), "%s/%s", + links_base, rel); + + if (S_ISDIR(st.st_mode)) { + /* create the directory in the links tree */ + gl_mkdirp(link_path, 0700); + + /* push onto stack to recurse into */ + if (top < 4096) { + stack[top++] = strdup(full_path); + } + } else if (S_ISREG(st.st_mode)) { + /* create parent dirs in links tree */ + char parent[PATH_MAX]; + strncpy(parent, link_path, sizeof(parent)); + char *slash = strrchr(parent, '/'); + if (slash) { + *slash = '\0'; + gl_mkdirp(parent, 0700); + } + + /* remove existing symlink if present */ + unlink(link_path); + + /* symlink store path -> links tree */ + if (symlink(full_path, link_path) != 0) { + closedir(dir); + free(current); + while (top > 0) { free(stack[--top]); } + return -1; + } + + lchown(link_path, ctx->uid, (gid_t)-1); + } + } + + closedir(dir); + free(current); + } + + return 0; +} + +int +gl_delete_user(uid_t uid, bool isVerbose) +{ + char ind_path[PATH_MAX]; + char sto_path[PATH_MAX]; + char lnk_path[PATH_MAX]; + + snprintf(ind_path, sizeof(ind_path), "%s/%d", usr_index.path, uid); + snprintf(sto_path, sizeof(sto_path), "%s/%d", usr_store.path, uid); + snprintf(lnk_path, sizeof(lnk_path), "%s/%d", usr_links.path, uid); + + if (isVerbose) { lg_printf(0, "Removing Glacier directories for UID %d...", uid); } + + if (gl_rmdir_recursive(ind_path) != 0) { + lg_printf(2, "Failed to remove index for UID %d", uid); + return 1; + } + + if (gl_rmdir_recursive(sto_path) != 0) { + lg_printf(2, "Failed to remove store for UID %d", uid); + return 1; + } + + if (gl_rmdir_recursive(lnk_path) != 0) { + lg_printf(2, "Failed to remove links for UID %d", uid); + return 1; + } + + lg_printf(3, "Successfully removed Glacier directories for UID %d.", uid); + return 0; +} + +char +*gl_find_pkg_repo(gl_context_t *ctx, const char *pkg_name) +{ + char store_path[PATH_MAX]; + strncpy(store_path, ctx->store_path, PATH_MAX - 1); + + 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]; + strncpy(store_path, ctx->store_path, PATH_MAX - 1); + + 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_istore(const char *tar_path, const char *glacier_root, const char *uid) +{ + struct archive *a = archive_read_new(); + archive_read_support_filter_gzip(a); + archive_read_support_format_tar(a); + + if (archive_read_open_filename(a, tar_path, 10240) != ARCHIVE_OK) { + archive_read_free(a); + return GL_RESTORE_ERR_OPEN; + } + + struct archive_entry *entry; + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + const char *rel = archive_entry_pathname(entry); + + char dest[PATH_MAX]; + snprintf(dest, sizeof(dest), "%s/%s/%s", + glacier_root, uid, rel); + + archive_entry_set_pathname(entry, dest); + + if (archive_entry_filetype(entry) == AE_IFDIR) { + gl_mkdirp(dest, archive_entry_perm(entry)); + archive_read_data_skip(a); + continue; + } + + /* create parent dirs */ + char parent[PATH_MAX]; + strncpy(parent, dest, sizeof(parent)); + char *slash = strrchr(parent, '/'); + if (slash) { + *slash = '\0'; + gl_mkdirp(parent, 0700); + } + + int fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, + archive_entry_perm(entry)); + if (fd < 0) { + archive_read_free(a); + return GL_RESTORE_ERR_EXTRACT; + } + + char buf[8192]; + ssize_t n; + while ((n = archive_read_data(a, buf, sizeof(buf))) > 0) { + ssize_t written = write(fd, buf, (size_t)n); + if (written < 0 || written != n) { + close(fd); + archive_read_free(a); + return GL_RESTORE_ERR_EXTRACT; + } + } + close(fd); + } + + archive_read_free(a); + return GL_RESTORE_OK; +} + +gl_install_status_t +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; + } + + /* The archive is opened and the manifest is parsed here */ + struct archive *a = archive_read_new(); + archive_read_support_filter_gzip(a); + archive_read_support_format_tar(a); + + if (archive_read_open_filename(a, gpkg_path, 10240) != ARCHIVE_OK) { + archive_read_free(a); + return GL_INSTALL_ERR_OPEN; + } + + struct archive_entry *entry; + gl_pkg_t pkg = {0}; + bool manifest_found = false; + + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + const char *path = archive_entry_pathname(entry); + + if (strstr(path, "manifest.gpm.cfg")) { + char tmp_manifest[PATH_MAX]; + strncpy(tmp_manifest, "/tmp/gpkg_manifest_XXXXXX", + sizeof(tmp_manifest)); + + int fd = mkstemp(tmp_manifest); + if (fd < 0) { + archive_read_free(a); + return GL_INSTALL_ERR_MANIFEST; + } + + char buffer[8192]; + ssize_t n; + + while ((n = archive_read_data(a, buffer, sizeof(buffer))) > 0) { + ssize_t written = write(fd, buffer, (size_t)n); + if (written < 0 || written != n) { + close(fd); + unlink(tmp_manifest); + archive_read_free(a); + return GL_INSTALL_ERR_MANIFEST; + } + } + + close(fd); + + if (gl_gpm2gpkg(tmp_manifest, &pkg) != 0) { + unlink(tmp_manifest); + archive_read_free(a); + return GL_INSTALL_ERR_MANIFEST; + } + + unlink(tmp_manifest); + manifest_found = true; + break; + } + + archive_read_data_skip(a); + } + + archive_read_free(a); + + if (!manifest_found) { + return GL_INSTALL_ERR_MANIFEST; + } + + char pkg_store_base[PATH_MAX]; + char pkg_store_final[PATH_MAX]; + + /* construct the directory for the package in the store */ + snprintf(pkg_store_base, sizeof(pkg_store_base), + "%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, + pkg.pkg_ver.maj, pkg.pkg_ver.min, pkg.pkg_ver.pat); + + if (gl_mkdirp(pkg_store_base, 0700) == -1) { + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_EXTRACT; + } + + if (mkdir(pkg_store_final, 0700) == -1) { + if (errno == EEXIST) { + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_ALREADY_INSTALLED; + } + + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_EXTRACT; + } + + /* now the archive must be reopened in order to extract package files */ + struct archive *a2 = archive_read_new(); + archive_read_support_filter_gzip(a2); + archive_read_support_format_tar(a2); + + if (archive_read_open_filename(a2, gpkg_path, 10240) != ARCHIVE_OK) { + archive_read_free(a2); + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_EXTRACT; + } + + char strip_prefix[PATH_MAX]; + snprintf(strip_prefix, sizeof(strip_prefix), + "%s-%d.%d.%d/files/", pkg.pkg_name, + pkg.pkg_ver.maj, pkg.pkg_ver.min, pkg.pkg_ver.pat); + + while (archive_read_next_header(a2, &entry) == ARCHIVE_OK) { + const char *entry_path = archive_entry_pathname(entry); + + if (strncmp(entry_path, strip_prefix, strlen(strip_prefix)) != 0) { + archive_read_data_skip(a2); + continue; + } + + const char *rel_path = entry_path + strlen(strip_prefix); + if (rel_path[0] == '\0') { + archive_read_data_skip(a2); + continue; + } + + char dest_path[PATH_MAX]; + snprintf(dest_path, sizeof(dest_path), "%s/%s", + pkg_store_final, rel_path); + + if (archive_entry_filetype(entry) == AE_IFDIR) { + if (mkdir(dest_path, archive_entry_perm(entry)) == -1 + && errno != EEXIST) { + archive_read_free(a2); + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_EXTRACT; + } + } + else if (archive_entry_filetype(entry) == AE_IFREG) { + char parent[PATH_MAX]; + strncpy(parent, dest_path, sizeof(parent)); + char *slash = strrchr(parent, '/'); + if (slash) { + *slash = '\0'; + gl_mkdirp(parent, 0700); + } + + int fd = open(dest_path, O_WRONLY | O_CREAT | O_TRUNC, + archive_entry_perm(entry)); + + if (fd < 0) { + archive_read_free(a2); + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_EXTRACT; + } + + char buffer[8192]; + ssize_t n; + while ((n = archive_read_data(a2, buffer, sizeof(buffer))) > 0) { + ssize_t written = write(fd, buffer, (size_t)n); + if (written < 0 || written != n) { + close(fd); + archive_read_free(a2); + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_EXTRACT; + } + } + + close(fd); + } + } + + archive_read_free(a2); + + /* now the index will be updated */ + /* 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", ctx->index_path); + + char current_target[PATH_MAX]; + ssize_t len = readlink(current_link, current_target, sizeof(current_target) - 1); + if (len < 0) { + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_INDEX; + } + current_target[len] = '\0'; + + char index_path[PATH_MAX]; + snprintf(index_path, sizeof(index_path), "%s/%s", + ctx->index_path, current_target); + + FILE *idx = fopen(index_path, "a"); + if (!idx) { + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_INDEX; + } + + fprintf(idx, "%s::%s\n", pkg.pkg_repo, pkg.pkg_name); + fclose(idx); + + if (gl_rebuild_index(ctx) != 0) { + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_INDEX; + } + + if (ctx->mode == GL_CTX_LIVE) { + if (gl_link_pkg(ctx, pkg_store_final) != 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); + chdir(original_cwd); + return GL_INSTALL_OK; +} + +int +gl_relink_store(gl_context_t *ctx) +{ + 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) != 0) { + closedir(ver_dir); closedir(name_dir); + closedir(repo_dir); + return -1; + } + } + closedir(ver_dir); + } + closedir(name_dir); + } + closedir(repo_dir); + return 0; +} \ No newline at end of file diff --git a/src/istoreutils.c.rej b/src/istoreutils.c.rej new file mode 100644 index 0000000..617ab7f --- /dev/null +++ b/src/istoreutils.c.rej @@ -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; + } diff --git a/src/istoreutils.h b/src/istoreutils.h index 77dd546..db50d4a 100644 --- a/src/istoreutils.h +++ b/src/istoreutils.h @@ -5,6 +5,8 @@ #include #include +#include "transaction.h" + typedef enum { INDEX, STORE @@ -58,6 +60,7 @@ typedef enum { struct gpkg_entry { char *repo; char *pkg; + char *ver; }; 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); 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_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); +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, 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); +gl_install_status_t gl_install_pkg(gl_context_t *ctx, const char *gpkg_path); +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); diff --git a/src/istoreutils.h.back b/src/istoreutils.h.back new file mode 100644 index 0000000..a3ee8e3 --- /dev/null +++ b/src/istoreutils.h.back @@ -0,0 +1,101 @@ +#ifndef ISTOREUTILS_H_ +#define ISTOREUTILS_H_ + +#include +#include +#include + +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 diff --git a/src/sha256.c b/src/sha256.c new file mode 100644 index 0000000..c1a2153 --- /dev/null +++ b/src/sha256.c @@ -0,0 +1,203 @@ +/* + * gl_sha256 — vendored SHA-256 (FIPS 180-4). + * See sha256.h for API rationale. + */ + +#include +#include +#include + +#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; +} diff --git a/src/sha256.h b/src/sha256.h new file mode 100644 index 0000000..18f1267 --- /dev/null +++ b/src/sha256.h @@ -0,0 +1,26 @@ +#ifndef GL_SHA256_H_ +#define GL_SHA256_H_ + +#include +#include + +#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 diff --git a/src/transaction.c b/src/transaction.c new file mode 100644 index 0000000..78ae174 --- /dev/null +++ b/src/transaction.c @@ -0,0 +1,420 @@ +#include +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} \ No newline at end of file diff --git a/src/transaction.h b/src/transaction.h new file mode 100644 index 0000000..b925b69 --- /dev/null +++ b/src/transaction.h @@ -0,0 +1,55 @@ +#ifndef TRANSACTION_H_ +#define TRANSACTION_H_ + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include + +#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