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