414 Commits

Author SHA1 Message Date
cat 0cf0e18e35 release: 0.4.2
Test / Create distribution (push) Successful in 38s
Test / ShareFS (push) Successful in 42s
Test / Sandbox (push) Successful in 48s
Test / Sandbox (race detector) (push) Successful in 49s
Test / Hakurei (push) Successful in 53s
Test / Hakurei (race detector) (push) Successful in 54s
Release / Create release (push) Successful in 1m10s
Test / Flake checks (push) Successful in 1m21s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-10 16:16:59 +09:00
cat ee5c0dd135 cmd/dist: optionally skip tests
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m43s
Test / ShareFS (push) Successful in 3m41s
Test / Hakurei (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 6m30s
Test / Flake checks (push) Successful in 1m21s
Works around incomplete syscall translation by qemu.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-10 04:15:07 +09:00
cat 92c48d82e2 internal/rosa/go: respect check flag
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m55s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 6m27s
Test / Hakurei (push) Successful in 2m36s
Test / Flake checks (push) Successful in 1m31s
These tests are also quite expensive, so optionally skip them.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-10 04:01:06 +09:00
cat c79a4fe7f8 internal/rosa/stage0: add riscv64 tarball
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m45s
Test / Sandbox (race detector) (push) Successful in 5m26s
Test / Hakurei (race detector) (push) Successful in 6m32s
Test / Flake checks (push) Successful in 1m20s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-09 10:51:19 +09:00
cat 0aeb2bccfb internal/rosa: libconfig artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m43s
Test / ShareFS (push) Successful in 3m37s
Test / Hakurei (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m29s
Test / Flake checks (push) Successful in 1m20s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-09 00:33:27 +09:00
cat 50e079b99f internal/rosa: xcb-util-keysyms artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m46s
Test / ShareFS (push) Successful in 3m40s
Test / Hakurei (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 5m25s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Flake checks (push) Successful in 1m20s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-09 00:16:06 +09:00
cat fb2cb5005a internal/rosa: libdisplay-info artifact
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m48s
Test / ShareFS (push) Successful in 3m38s
Test / Hakurei (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m20s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-09 00:07:43 +09:00
cat 6e73c28a92 internal/rosa: hwdata artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m45s
Test / ShareFS (push) Successful in 3m39s
Test / Hakurei (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m23s
Test / Flake checks (push) Successful in 1m31s
Required by libdisplay-info.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-09 00:05:40 +09:00
cat 2c08aa3674 internal/rosa/glslang: disable broken arm64 tests
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m44s
Test / ShareFS (push) Successful in 3m39s
Test / Hakurei (push) Successful in 3m58s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m31s
Test / Flake checks (push) Successful in 1m21s
These just fail on arm64, so disable them.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-08 23:56:19 +09:00
cat 1af73ae7b4 internal/rosa/go: 1.26.2 to 1.26.3
Test / Create distribution (push) Successful in 1m26s
Test / Sandbox (push) Successful in 3m19s
Test / ShareFS (push) Successful in 4m21s
Test / Hakurei (push) Successful in 4m38s
Test / Sandbox (race detector) (push) Successful in 5m51s
Test / Hakurei (race detector) (push) Successful in 7m10s
Test / Flake checks (push) Successful in 1m22s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-08 23:25:57 +09:00
cat c9aa5e04b1 internal/rosa/go: bootstrap 1.25.9 to 1.25.10
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m4s
Test / Hakurei (push) Successful in 4m18s
Test / ShareFS (push) Successful in 4m8s
Test / Sandbox (race detector) (push) Successful in 5m40s
Test / Hakurei (race detector) (push) Successful in 6m48s
Test / Flake checks (push) Successful in 1m22s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-08 23:20:39 +09:00
cat 70a38bd3b0 internal/rosa: libarchive artifact
Test / Create distribution (push) Successful in 1m26s
Test / Sandbox (push) Successful in 5m6s
Test / Hakurei (push) Successful in 7m25s
Test / Sandbox (race detector) (push) Successful in 8m6s
Test / ShareFS (push) Successful in 8m6s
Test / Hakurei (race detector) (push) Successful in 11m20s
Test / Flake checks (push) Successful in 14m59s
Required by mesa, also a cleaner implementation than GNU.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-08 23:16:33 +09:00
cat 533b15da89 internal/rosa/mksh: respect check flag
Test / Create distribution (push) Successful in 1m28s
Test / Sandbox (push) Successful in 5m13s
Test / Hakurei (push) Successful in 7m22s
Test / ShareFS (push) Successful in 8m3s
Test / Sandbox (race detector) (push) Successful in 8m12s
Test / Hakurei (race detector) (push) Successful in 11m10s
Test / Flake checks (push) Successful in 2m15s
This skips the test suite when OptSkipCheck is set.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-08 21:20:20 +09:00
cat a890e1d0e5 cmd/mbf: optionally override non-native flags
Test / Create distribution (push) Successful in 1m29s
Test / Sandbox (push) Successful in 5m5s
Test / Hakurei (push) Successful in 7m21s
Test / Sandbox (race detector) (push) Successful in 8m1s
Test / ShareFS (push) Successful in 8m1s
Test / Hakurei (race detector) (push) Successful in 11m19s
Test / Flake checks (push) Successful in 2m25s
This is a clean workaround for configuration differences to save time during development.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-08 13:45:36 +09:00
cat e3520835bb cmd/mbf: optionally register all targets
Test / Create distribution (push) Successful in 2m53s
Test / Sandbox (push) Successful in 7m1s
Test / Hakurei (push) Successful in 8m58s
Test / ShareFS (push) Successful in 9m25s
Test / Sandbox (race detector) (push) Successful in 9m30s
Test / Hakurei (race detector) (push) Successful in 12m26s
Test / Flake checks (push) Successful in 2m36s
This enables non-native cures from the daemon.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-08 13:29:58 +09:00
cat 0e56847754 cmd/mbf: add arm64 magic
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m49s
Test / ShareFS (push) Successful in 3m40s
Test / Hakurei (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 5m23s
Test / Hakurei (race detector) (push) Successful in 6m30s
Test / Flake checks (push) Successful in 1m23s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-08 00:23:09 +09:00
cat 145d03b366 cmd/mbf: optional emulated target architecture
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m48s
Test / ShareFS (push) Successful in 3m42s
Test / Hakurei (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Flake checks (push) Successful in 1m23s
This enables transparent cross-compilation without breaking purity.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-07 20:29:31 +09:00
cat 2886228d40 internal/rosa/qemu: build static binaries
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m46s
Test / ShareFS (push) Successful in 3m37s
Test / Hakurei (push) Successful in 3m45s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Flake checks (push) Successful in 1m30s
Dynamic linking here barely saves space, and this is required for binfmt.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-07 20:25:13 +09:00
cat e1e499b79e internal/rosa/git: disable more broken tests
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m40s
Test / ShareFS (push) Successful in 3m33s
Test / Hakurei (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 6m38s
Test / Flake checks (push) Successful in 1m47s
These are causing many spurious failures.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-07 20:06:11 +09:00
cat 65b7dd8b37 internal/rosa: configurable architecture
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m48s
Test / ShareFS (push) Successful in 3m40s
Test / Hakurei (push) Successful in 3m57s
Test / Sandbox (race detector) (push) Successful in 5m22s
Test / Hakurei (race detector) (push) Successful in 6m33s
Test / Flake checks (push) Successful in 1m29s
This enables curing via binfmt.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-07 20:01:44 +09:00
cat 8d72b9e5bd internal/pkg: optionally register binfmt
Test / Create distribution (push) Successful in 52s
Test / Sandbox (push) Successful in 2m0s
Test / ShareFS (push) Successful in 2m51s
Test / Hakurei (push) Successful in 3m5s
Test / Sandbox (race detector) (push) Successful in 6m5s
Test / Hakurei (race detector) (push) Successful in 7m8s
Test / Flake checks (push) Successful in 1m21s
This transparently supports curing foreign exec artifacts.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-07 19:43:06 +09:00
cat 8a3c3d145a internal/pkg: correctly generate cure expects
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m53s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m25s
Test / Flake checks (push) Successful in 1m22s
This needs to dereference the identifier symlink.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-07 15:57:45 +09:00
cat 575ef307ad container: binfmt registration
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m25s
Test / Flake checks (push) Successful in 1m22s
This arranges for binfmt entries to be registered for the container.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-07 15:55:19 +09:00
cat d4144fcf7f container: optionally map uid/gid 0 as init
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m45s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 6m31s
Test / Flake checks (push) Successful in 1m21s
Unfortunately required to work around flawed APIs like binfmt_misc.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-07 15:15:47 +09:00
cat bad66facbc container: improve capability handling
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 4m5s
Test / ShareFS (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 5m55s
Test / Hakurei (race detector) (push) Successful in 7m54s
Test / Flake checks (push) Successful in 1m35s
This cleans up preserving caps for expansion and correctly sets privileged caps.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-07 14:27:28 +09:00
cat 4aba014eac container: abandon response on termination
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m51s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m56s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m23s
This prevents blocking on early failure.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-07 00:58:02 +09:00
cat 779ba994ce container: check capability in test helper
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m51s
Test / ShareFS (push) Successful in 3m53s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m25s
Test / Flake checks (push) Successful in 1m22s
This makes corresponding nixos tests redundant.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-06 21:05:54 +09:00
cat 917be2de93 internal/pkg/exec: close early failure before wait
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m44s
Test / ShareFS (push) Successful in 3m53s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m22s
This avoids a deadlock on an early container failure.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-06 18:38:16 +09:00
cat 9aad98d409 internal/rosa: suppress init verbosity in tests
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m46s
Test / ShareFS (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 5m13s
Test / Hakurei (race detector) (push) Successful in 6m23s
Test / Hakurei (push) Successful in 2m28s
Test / Flake checks (push) Successful in 1m18s
This is generally the preferred option.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-06 06:54:20 +09:00
cat b0d06b67dc internal/pkg: centralise exec testdata checksums
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m53s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m19s
This significantly reduces maintenance burden.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-06 06:37:58 +09:00
cat 089100f29d internal/rosa/stage0: add arm64 tarball
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 6m23s
Test / Flake checks (push) Successful in 1m22s
This was bootstrapped from the old tarball, but with the new patchset.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-06 05:47:14 +09:00
cat dfd26abf6c internal/pkg: improve output measuring
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m52s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m21s
This significantly improves readability and maintainability.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-06 05:44:04 +09:00
cat 617ee21647 container/init: mount intermediate before early
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m45s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 5m22s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m22s
This is usable as scratch space during early.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-06 00:55:45 +09:00
cat 15cdb37ec2 cmd/mbf: optional init verbosity
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 3m51s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m13s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m21s
This output is generally not needed and only useful when debugging container machinery itself.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 23:56:16 +09:00
cat 1f0bdc7aca internal/rosa/meson: disable fallback
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 3m37s
Test / ShareFS (push) Successful in 4m39s
Test / Sandbox (race detector) (push) Successful in 6m2s
Test / Hakurei (race detector) (push) Successful in 7m9s
Test / Hakurei (push) Successful in 2m36s
Test / Flake checks (push) Successful in 1m24s
For some reason nodownload still allows fallback in some cases.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 21:32:19 +09:00
cat e3ffe85670 internal/rosa/python: pycparser artifact
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m54s
Test / ShareFS (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 6m25s
Test / Flake checks (push) Successful in 1m21s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 20:37:09 +09:00
cat f4403ba5cd internal/rosa: libpng artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m54s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m21s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 20:23:50 +09:00
cat 5a26895a22 internal/pkg: optionally suppress init verbosity
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m54s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Flake checks (push) Successful in 1m21s
This flag applies to every exec artifact cured by the cache. It has no effect on cure outcome.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 20:03:06 +09:00
cat 09d9f766a9 container: optionally suppress init verbosity
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m53s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m30s
This change also removes verbose output no longer considered useful.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 19:59:44 +09:00
cat 6558169666 internal/rosa/x: libXrandr artifact
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m50s
Test / ShareFS (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 5m26s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Flake checks (push) Successful in 1m21s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 19:39:19 +09:00
cat cccf970c57 internal/rosa/x: libXrender artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m44s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 6m31s
Test / Flake checks (push) Successful in 1m30s
Required by libXrandr.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 19:37:11 +09:00
cat 57ffb21690 internal/rosa/x: libXxf86vm artifact
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m54s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m23s
Test / Flake checks (push) Successful in 1m21s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 19:27:59 +09:00
cat 9c560b455a internal/rosa/stage0: replace amd64 tarball
Test / Create distribution (push) Successful in 4m1s
Test / Sandbox (push) Successful in 6m5s
Test / Hakurei (push) Successful in 7m32s
Test / ShareFS (push) Successful in 7m38s
Test / Sandbox (race detector) (push) Successful in 8m59s
Test / Hakurei (race detector) (push) Successful in 10m14s
Test / Flake checks (push) Successful in 1m25s
This toolchain is built with the new patchset.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 04:39:53 +09:00
cat 4c7c0fbfc6 internal/rosa/llvm: update configuration for early runtimes
Test / Sandbox (push) Successful in 2m2s
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (race detector) (push) Successful in 3m1s
Test / ShareFS (push) Successful in 3m18s
Test / Hakurei (race detector) (push) Successful in 10m2s
Test / Hakurei (push) Successful in 2m45s
Test / Flake checks (push) Successful in 1m56s
These were never updated when the bootstrap was moved to stage0-only.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 04:38:17 +09:00
cat 18b3b7904e internal/rosa/llvm: exclude benchmarks
Test / Create distribution (push) Successful in 1m30s
Test / Sandbox (push) Successful in 5m22s
Test / Hakurei (push) Successful in 7m16s
Test / Sandbox (race detector) (push) Successful in 7m59s
Test / ShareFS (push) Successful in 8m9s
Test / Hakurei (race detector) (push) Successful in 10m43s
Test / Flake checks (push) Successful in 2m2s
These are being built despite LLVM_BUILD_BENCHMARKS defaulting to off.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 03:11:26 +09:00
cat fefefdf734 internal/rosa/llvm: insert Rosa OS paths via musl ldso
Test / Create distribution (push) Successful in 2m47s
Test / Sandbox (push) Successful in 6m51s
Test / Hakurei (push) Successful in 8m55s
Test / Sandbox (race detector) (push) Successful in 9m24s
Test / ShareFS (push) Successful in 9m49s
Test / Hakurei (race detector) (push) Successful in 12m33s
Test / Flake checks (push) Successful in 2m45s
This is cleaner than unconditionally adding rpath, and avoids breaking rpath priority.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 02:44:26 +09:00
cat b84bb09a80 internal/rosa/hakurei: 0.4.0 to 0.4.1
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m52s
Test / ShareFS (push) Successful in 3m56s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m23s
Test / Flake checks (push) Successful in 1m20s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-04 05:28:14 +09:00
cat 337bf20f50 release: 0.4.1
Test / Create distribution (push) Successful in 38s
Test / ShareFS (push) Successful in 43s
Test / Sandbox (push) Successful in 48s
Test / Sandbox (race detector) (push) Successful in 48s
Test / Hakurei (race detector) (push) Successful in 55s
Release / Create release (push) Successful in 1m8s
Test / Hakurei (push) Successful in 2m14s
Test / Flake checks (push) Successful in 1m25s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-04 05:04:00 +09:00
cat 1cb792cf6e cmd/dist: increase gzip level
Test / Sandbox (push) Successful in 2m47s
Test / Create distribution (push) Successful in 34s
Test / Hakurei (push) Successful in 2m6s
Test / ShareFS (push) Successful in 3m59s
Test / Sandbox (race detector) (push) Successful in 3m30s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m22s
Performance does not matter in this case.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-04 04:04:18 +09:00
cat b2b40b07e8 cmd/dist: optional verbosity
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m54s
Test / Hakurei (push) Successful in 3m50s
Test / ShareFS (push) Successful in 3m59s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 6m29s
Test / Flake checks (push) Successful in 1m30s
This makes output less noisy. The build is fast enough not to require progress indication.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-04 04:02:02 +09:00
cat da11b26ec1 container/initoverlay: configure via fsconfig
Test / ShareFS (push) Successful in 40s
Test / Hakurei (push) Successful in 53s
Test / Hakurei (race detector) (push) Successful in 51s
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 1m48s
Test / Sandbox (race detector) (push) Successful in 2m37s
Test / Flake checks (push) Successful in 1m20s
This works around the page size limit at the cost of negligible performance regressions.

Closes #34.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-04 02:29:56 +09:00
cat 024489e800 ext: wrap file-descriptor-based mount facilities
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 3m50s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 5m12s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m20s
This only implements what is required by package container for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-04 01:54:35 +09:00
cat 0f795712b0 internal/rosa/llvm: enable LLVM_BUILD_TESTS
Test / Create distribution (push) Successful in 4m31s
Test / Sandbox (push) Successful in 8m7s
Test / Hakurei (push) Successful in 9m12s
Test / ShareFS (push) Successful in 9m15s
Test / Sandbox (race detector) (push) Successful in 10m40s
Test / Hakurei (race detector) (push) Successful in 12m15s
Test / Flake checks (push) Successful in 3m6s
This arranges for tests to be built early, and is more efficient towards the end of the build.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-03 20:05:30 +09:00
cat 7e2210ff71 internal/rosa/llvm: provide runtimes early in stage0
Test / Create distribution (push) Successful in 1m9s
Test / Sandbox (push) Successful in 2m58s
Test / Hakurei (push) Successful in 4m16s
Test / ShareFS (push) Successful in 4m11s
Test / Sandbox (race detector) (push) Successful in 6m13s
Test / Hakurei (race detector) (push) Successful in 7m59s
Test / Flake checks (push) Successful in 1m21s
The LLVM build system fails to handle a dynamically linked toolchain correctly, and leaks the system installation during builds.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-03 19:48:49 +09:00
cat a71a008f3c cmd/mbf: optionally build on early stages
Test / Create distribution (push) Successful in 1m8s
Test / Sandbox (push) Successful in 2m56s
Test / ShareFS (push) Successful in 3m57s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 6m32s
Test / Hakurei (push) Successful in 2m49s
Test / Flake checks (push) Successful in 1m36s
This makes debugging the bootstrap process much less cumbersome.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-03 18:46:47 +09:00
cat 162265b47e container: reject strings larger than a page
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m50s
Test / Hakurei (push) Successful in 3m50s
Test / ShareFS (push) Successful in 3m54s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 6m29s
Test / Flake checks (push) Successful in 1m22s
The vfs stores these values in a page obtained via GFP, and silently stops copying once the page is filled. This check prevents confusing behaviour in such cases.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-03 17:30:25 +09:00
cat 3fa7ac04e4 internal/rosa/x: combine with xcb
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 3m54s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m13s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m45s
Separating them no longer makes sense.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-03 04:38:00 +09:00
cat bf2867d653 internal/rosa/x: libxshmfence artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m55s
Test / Hakurei (push) Successful in 3m53s
Test / ShareFS (push) Successful in 3m53s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 6m23s
Test / Flake checks (push) Successful in 1m30s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-03 04:35:39 +09:00
cat ec0f0f6507 internal/rosa/x: libXext artifact
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 3m54s
Test / ShareFS (push) Successful in 3m58s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m30s
Test / Flake checks (push) Successful in 1m20s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-03 04:23:20 +09:00
cat a77a802955 internal/rosa/x: xlib artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m50s
Test / Hakurei (push) Successful in 3m54s
Test / ShareFS (push) Successful in 3m56s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 3m35s
Test / Flake checks (push) Successful in 1m24s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-03 04:15:21 +09:00
cat 4407e14dfc internal/rosa/x: migrate to xorgproto
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m50s
Test / Hakurei (push) Successful in 3m54s
Test / ShareFS (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 5m14s
Test / Hakurei (race detector) (push) Successful in 6m23s
Test / Flake checks (push) Successful in 1m31s
This is much cleaner than the many protocol packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-03 04:09:36 +09:00
cat e024d3184a internal/rosa/clang: install cpp symlink
Test / Create distribution (push) Successful in 1m13s
Test / Sandbox (push) Successful in 3m8s
Test / Hakurei (push) Successful in 4m19s
Test / ShareFS (push) Successful in 4m24s
Test / Sandbox (race detector) (push) Successful in 5m32s
Test / Hakurei (race detector) (push) Successful in 6m42s
Test / Flake checks (push) Successful in 1m31s
Required by some buggy autotools scripts.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-03 00:41:23 +09:00
cat 8e1bf00c2d internal/rosa/stage0: add arm64 tarball
Test / Create distribution (push) Successful in 1m37s
Test / Sandbox (push) Successful in 5m2s
Test / Hakurei (push) Successful in 8m21s
Test / ShareFS (push) Successful in 8m54s
Test / Hakurei (race detector) (push) Successful in 11m26s
Test / Sandbox (race detector) (push) Successful in 5m44s
Test / Flake checks (push) Successful in 10m12s
This replaces the previous, much larger stage0 distribution.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 23:53:08 +09:00
cat b111e22050 internal/rosa/x: libxtrans artifact
Test / Create distribution (push) Successful in 1m27s
Test / Sandbox (push) Successful in 4m26s
Test / Hakurei (push) Successful in 7m36s
Test / ShareFS (push) Successful in 8m22s
Test / Hakurei (race detector) (push) Successful in 1m24s
Test / Sandbox (race detector) (push) Successful in 3m48s
Test / Flake checks (push) Successful in 3m39s
Required by many X libraries.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 23:42:00 +09:00
cat 1fa458c0be internal/rosa/glslang: SPIRV-LLVM-Translator artifact
Test / Create distribution (push) Successful in 1m29s
Test / Sandbox (push) Successful in 4m36s
Test / Hakurei (push) Successful in 7m33s
Test / ShareFS (push) Successful in 8m18s
Test / Sandbox (race detector) (push) Successful in 8m31s
Test / Hakurei (race detector) (push) Successful in 10m58s
Test / Flake checks (push) Successful in 1m51s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 22:47:51 +09:00
cat 2c7ae67a67 internal/rosa/llvm: LIT args helper
Test / Create distribution (push) Successful in 1m28s
Test / Sandbox (push) Successful in 4m14s
Test / Hakurei (push) Successful in 7m20s
Test / ShareFS (push) Successful in 7m43s
Test / Sandbox (race detector) (push) Successful in 7m57s
Test / Hakurei (race detector) (push) Successful in 4m55s
Test / Flake checks (push) Successful in 2m23s
This is useful for other projects using LIT.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 22:17:57 +09:00
cat 3826621b21 internal/rosa/python: lit artifact
Test / Create distribution (push) Successful in 1m32s
Test / Sandbox (push) Successful in 5m49s
Test / Hakurei (push) Successful in 9m10s
Test / Sandbox (race detector) (push) Successful in 9m20s
Test / ShareFS (push) Successful in 9m52s
Test / Hakurei (race detector) (push) Successful in 12m10s
Test / Flake checks (push) Successful in 1m52s
Used by LLVM-related projects.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 22:15:37 +09:00
cat 041b505c2e internal/rosa/cmake: implicit CMAKE_BUILD_TYPE
Test / Create distribution (push) Successful in 1m33s
Test / Sandbox (push) Successful in 4m42s
Test / ShareFS (push) Successful in 8m24s
Test / Sandbox (race detector) (push) Successful in 8m35s
Test / Hakurei (race detector) (push) Successful in 11m41s
Test / Hakurei (push) Successful in 6m27s
Test / Flake checks (push) Successful in 3m35s
Lack of this behaviour is a holdover from when the helper was first split from the (now removed) LLVM helper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 21:53:38 +09:00
cat e6debce649 internal/rosa/llvm: make source independently available
Test / Create distribution (push) Successful in 1m29s
Test / Sandbox (push) Successful in 4m36s
Test / Hakurei (push) Successful in 7m41s
Test / ShareFS (push) Successful in 8m18s
Test / Sandbox (race detector) (push) Successful in 8m26s
Test / Hakurei (race detector) (push) Successful in 11m9s
Test / Flake checks (push) Successful in 2m31s
This is unfortunately still required, due to the monorepo nature of LLVM.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 21:47:01 +09:00
cat aa26b86fce internal/rosa/llvm: skip multiple-compile-threads-basic on arm64
Test / Create distribution (push) Successful in 1m28s
Test / Sandbox (push) Successful in 5m11s
Test / Hakurei (push) Successful in 7m16s
Test / Sandbox (race detector) (push) Successful in 7m49s
Test / ShareFS (push) Successful in 8m16s
Test / Hakurei (race detector) (push) Successful in 11m3s
Test / Flake checks (push) Successful in 2m24s
This intermittently crashes.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 12:39:46 +09:00
cat a57a8fd5d8 internal/rosa/llvm: skip unwind_leaffunction on arm64
Test / Create distribution (push) Successful in 1m29s
Test / Sandbox (push) Successful in 5m52s
Test / Sandbox (race detector) (push) Successful in 9m5s
Test / Hakurei (push) Successful in 9m11s
Test / ShareFS (push) Successful in 9m23s
Test / Hakurei (race detector) (push) Successful in 5m5s
Test / Flake checks (push) Successful in 1m53s
This unexpectedly passes.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 05:53:00 +09:00
maemachinebroke 1d5d063d6a cmd/mbf: package status dashboard
Test / Create distribution (push) Successful in 1m36s
Test / Sandbox (push) Successful in 5m44s
Test / Sandbox (race detector) (push) Successful in 8m14s
Test / ShareFS (push) Successful in 8m38s
Test / Hakurei (race detector) (push) Successful in 10m57s
Test / Hakurei (push) Successful in 6m38s
Test / Flake checks (push) Successful in 2m56s
This displays package metadata with optional status from a report.
2026-05-02 05:05:56 +09:00
cat e61628a34e cmd/mbf: test cure all via daemon
Test / Create distribution (push) Successful in 1m32s
Test / Sandbox (race detector) (push) Successful in 11m28s
Test / ShareFS (push) Successful in 13m35s
Test / Sandbox (push) Successful in 2m33s
Test / Hakurei (push) Successful in 6m54s
Test / Hakurei (race detector) (push) Successful in 8m27s
Test / Flake checks (push) Successful in 2m20s
This is the daemon equivalent of CureAll in internal/rosa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 02:39:12 +09:00
cat 5a18f14929 internal/rosa/gnu: bison disable broken test
Test / Create distribution (push) Successful in 2m4s
Test / ShareFS (push) Successful in 13m56s
Test / Hakurei (race detector) (push) Successful in 15m51s
Test / Sandbox (push) Successful in 4m53s
Test / Sandbox (race detector) (push) Successful in 5m46s
Test / Hakurei (push) Successful in 7m4s
Test / Flake checks (push) Successful in 2m57s
This is miscompiled by the current toolchain.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 02:23:51 +09:00
cat f12880688d internal/rosa/gnu: test skip helper
Test / Create distribution (push) Successful in 2m3s
Test / Sandbox (race detector) (push) Successful in 11m26s
Test / ShareFS (push) Successful in 13m58s
Test / Hakurei (race detector) (push) Successful in 15m42s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 6m19s
Test / Flake checks (push) Successful in 2m48s
The terribleness of GNU invites interesting helpers.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 05:19:54 +09:00
cat bb5bbfe16a internal/rosa/go: disable tsan test
Test / Hakurei (push) Successful in 2m7s
Test / Sandbox (push) Successful in 1m48s
Test / Hakurei (race detector) (push) Successful in 2m4s
Test / Create distribution (push) Successful in 1m22s
Test / ShareFS (push) Successful in 1m40s
Test / Sandbox (race detector) (push) Successful in 1m58s
Test / Flake checks (push) Successful in 4m30s
The newly enabled tsan does not play well with go126, so disable for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-02 00:12:41 +09:00
cat 427e1ca37c internal/rosa/go: bootstrap 1.25.7 to 1.25.9
Test / Create distribution (push) Successful in 1m27s
Test / Sandbox (push) Successful in 4m21s
Test / Hakurei (push) Successful in 7m38s
Test / ShareFS (push) Successful in 8m10s
Test / Sandbox (race detector) (push) Successful in 8m22s
Test / Hakurei (race detector) (push) Successful in 10m55s
Test / Flake checks (push) Successful in 1m49s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 23:24:07 +09:00
cat 96fdd9ecc5 internal/rosa: disable LTO in tests
Test / Create distribution (push) Successful in 1m27s
Test / Sandbox (push) Successful in 4m15s
Test / ShareFS (push) Successful in 7m44s
Test / Sandbox (race detector) (push) Successful in 8m2s
Test / Hakurei (race detector) (push) Successful in 10m25s
Test / Hakurei (push) Successful in 3m51s
Test / Flake checks (push) Successful in 1m46s
This is too expensive and not feasible for development.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 20:08:26 +09:00
cat 02771b655b internal/rosa/stage0: replace amd64 tarball
Test / Create distribution (push) Successful in 1m28s
Test / Sandbox (push) Successful in 4m17s
Test / Hakurei (push) Successful in 7m37s
Test / ShareFS (push) Successful in 8m10s
Test / Sandbox (race detector) (push) Successful in 8m19s
Test / Hakurei (race detector) (push) Successful in 10m51s
Test / Flake checks (push) Successful in 1m52s
This is a non-LTO distribution with the new layer configuration.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 18:57:28 +09:00
cat d1c8d2c39b internal/rosa/gnu: skip libtool tests in stage0
Test / Create distribution (push) Successful in 1m33s
Test / Sandbox (push) Successful in 5m19s
Test / Hakurei (push) Successful in 7m48s
Test / Sandbox (race detector) (push) Successful in 8m17s
Test / ShareFS (push) Successful in 8m43s
Test / Hakurei (race detector) (push) Successful in 11m45s
Test / Flake checks (push) Successful in 2m54s
This upsets the linker in stage0.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 05:26:40 +09:00
cat 0efd742e8a internal/rosa/llvm: enable libclc as a runtime
Test / Create distribution (push) Successful in 1m28s
Test / Sandbox (push) Successful in 5m20s
Test / Hakurei (push) Successful in 7m57s
Test / Sandbox (race detector) (push) Successful in 8m5s
Test / ShareFS (push) Successful in 8m39s
Test / Hakurei (race detector) (push) Successful in 11m53s
Test / Flake checks (push) Successful in 2m57s
Enabling this as a project is deprecated.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 05:17:02 +09:00
cat ae1fe638d5 internal/rosa/stage0: remove unused layers
Test / Create distribution (push) Successful in 1m29s
Test / Sandbox (push) Successful in 5m8s
Test / Hakurei (push) Successful in 7m30s
Test / Sandbox (race detector) (push) Successful in 8m7s
Test / ShareFS (push) Successful in 8m25s
Test / Hakurei (race detector) (push) Successful in 11m31s
Test / Flake checks (push) Successful in 2m51s
The stage0 toolchain no longer requires bundled dependencies other than the bare toolchain and environment itself.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 03:52:41 +09:00
cat 445d95023b internal/rosa: global preset flags
Test / Create distribution (push) Successful in 1m31s
Test / Sandbox (push) Successful in 5m8s
Test / Hakurei (push) Successful in 7m31s
Test / Sandbox (race detector) (push) Successful in 8m6s
Test / ShareFS (push) Successful in 8m28s
Test / Hakurei (race detector) (push) Successful in 11m8s
Test / Flake checks (push) Successful in 2m41s
These changes preset behaviour globally. Useful for ad hoc workarounds for development or bootstrapping on resource-constrained systems.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 03:42:48 +09:00
cat fc66f0bb47 internal/rosa/llvm: use llvm build system
Test / Create distribution (push) Successful in 1m32s
Test / Sandbox (push) Successful in 5m9s
Test / Hakurei (push) Successful in 7m32s
Test / Sandbox (race detector) (push) Successful in 8m0s
Test / ShareFS (push) Successful in 8m22s
Test / Hakurei (race detector) (push) Successful in 11m20s
Test / Flake checks (push) Successful in 2m48s
This removes the multistep bootstrap hack. Stage0 exceptions are also eliminated for a later change to bring the stage0 distribution down to just a bare toolchain, toybox and shell. This change also enables dynamic linking and ThinLTO.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 03:36:58 +09:00
cat 2cd6b35bee internal/rosa/cmake: run tests
Test / Create distribution (push) Successful in 1m27s
Test / Sandbox (push) Successful in 5m19s
Test / Hakurei (push) Successful in 7m24s
Test / Sandbox (race detector) (push) Successful in 8m4s
Test / ShareFS (push) Successful in 8m17s
Test / Hakurei (race detector) (push) Successful in 11m10s
Test / Flake checks (push) Successful in 2m43s
This uses the standard CMake test target.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 03:04:59 +09:00
cat 09a216c6ec internal/rosa/perl: make /system/bin writable
Test / Create distribution (push) Successful in 1m25s
Test / Sandbox (push) Successful in 5m3s
Test / Hakurei (push) Successful in 7m4s
Test / Sandbox (race detector) (push) Successful in 8m3s
Test / ShareFS (push) Successful in 8m10s
Test / Hakurei (race detector) (push) Successful in 10m41s
Test / Flake checks (push) Successful in 2m9s
This enables cure in stage0 where /system/bin is read-only.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-30 19:25:46 +09:00
cat 44d17325c2 internal/rosa: raise stage0 extra layers
Test / Create distribution (push) Successful in 1m26s
Test / Sandbox (push) Successful in 4m57s
Test / Hakurei (push) Successful in 7m4s
Test / Sandbox (race detector) (push) Successful in 7m49s
Test / ShareFS (push) Successful in 8m17s
Test / Hakurei (race detector) (push) Successful in 10m46s
Test / Flake checks (push) Successful in 1m55s
This enables extras to override stage0 tarball.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-30 18:58:42 +09:00
cat 544ce77cbc internal/rosa/make: do not attempt check
Test / Create distribution (push) Successful in 2m39s
Test / Sandbox (push) Successful in 6m27s
Test / Hakurei (push) Successful in 8m25s
Test / Sandbox (race detector) (push) Successful in 9m5s
Test / ShareFS (push) Successful in 9m23s
Test / Hakurei (race detector) (push) Successful in 11m58s
Test / Flake checks (push) Successful in 2m18s
This is circular during bootstrap, and tests are silently skipped without perl, so disable them explicitly.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-30 17:36:46 +09:00
cat 63c3c30b23 internal/rosa/zlib: compile with -fPIC
Test / Create distribution (push) Successful in 1m21s
Test / Sandbox (push) Successful in 4m49s
Test / Hakurei (push) Successful in 6m44s
Test / Sandbox (race detector) (push) Successful in 7m47s
Test / ShareFS (push) Successful in 7m54s
Test / Hakurei (race detector) (push) Successful in 10m42s
Test / Flake checks (push) Successful in 1m59s
For static linking into shared libraries. This was missed when migrating to CMake.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-30 15:55:46 +09:00
cat d23c4ecc7c internal/rosa/llvm: use correct triple for rpath
Test / Create distribution (push) Successful in 1m22s
Test / Sandbox (push) Successful in 4m54s
Test / Hakurei (push) Successful in 7m1s
Test / Sandbox (race detector) (push) Successful in 7m52s
Test / ShareFS (push) Successful in 8m10s
Test / Hakurei (race detector) (push) Successful in 10m43s
Test / Flake checks (push) Successful in 1m57s
MultiarchTriple produces a generic glibc triple string.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-30 00:39:13 +09:00
cat a46656dff8 internal/rosa/python: mako 1.3.11 to 1.3.12
Test / Create distribution (push) Successful in 1m39s
Test / Sandbox (push) Successful in 5m2s
Test / Hakurei (push) Successful in 6m52s
Test / ShareFS (push) Successful in 7m32s
Test / Sandbox (race detector) (push) Successful in 7m47s
Test / Hakurei (race detector) (push) Successful in 10m39s
Test / Flake checks (push) Successful in 1m56s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-29 14:25:26 +09:00
cat 77db153ff5 internal/rosa/python: trove-classifiers 2026.1.14.14 to 2026.4.28.13
Test / Create distribution (push) Successful in 1m34s
Test / Sandbox (push) Successful in 5m8s
Test / Hakurei (push) Successful in 6m42s
Test / ShareFS (push) Successful in 7m29s
Test / Sandbox (race detector) (push) Successful in 7m41s
Test / Hakurei (race detector) (push) Successful in 10m41s
Test / Flake checks (push) Successful in 1m55s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-29 14:25:07 +09:00
cat 520d95bc07 internal/rosa/libxslt: fetch source tarball
Test / Create distribution (push) Successful in 1m43s
Test / Sandbox (push) Successful in 5m8s
Test / Hakurei (push) Successful in 7m11s
Test / ShareFS (push) Successful in 7m38s
Test / Sandbox (race detector) (push) Successful in 8m1s
Test / Hakurei (race detector) (push) Successful in 10m58s
Test / Flake checks (push) Successful in 2m2s
This does not have submodules, so the overhead of git is unnecessary.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 18:31:44 +09:00
cat 451df3f4e7 internal/rosa/libxml2: fetch source tarball
Test / Create distribution (push) Successful in 1m28s
Test / Sandbox (push) Successful in 4m53s
Test / Hakurei (push) Successful in 6m54s
Test / ShareFS (push) Successful in 7m34s
Test / Sandbox (race detector) (push) Successful in 7m55s
Test / Hakurei (race detector) (push) Successful in 10m50s
Test / Flake checks (push) Successful in 2m6s
This does not have submodules, so the overhead of git is unnecessary.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 18:31:28 +09:00
cat 011fac15ed internal/rosa/git: 2.53.0 to 2.54.0
Test / Create distribution (push) Successful in 1m30s
Test / Sandbox (push) Successful in 5m0s
Test / Hakurei (push) Successful in 7m2s
Test / Sandbox (race detector) (push) Successful in 7m59s
Test / ShareFS (push) Successful in 8m17s
Test / Hakurei (race detector) (push) Successful in 10m45s
Test / Flake checks (push) Successful in 2m49s
This release broke httpd detection and job control on mksh.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 18:23:20 +09:00
cat 347682ad0b internal/rosa/kernel: 6.12.83 to 6.12.84
Test / Create distribution (push) Successful in 1m27s
Test / Sandbox (push) Successful in 4m57s
Test / Hakurei (push) Successful in 6m56s
Test / Sandbox (race detector) (push) Successful in 8m0s
Test / ShareFS (push) Successful in 8m10s
Test / Hakurei (race detector) (push) Successful in 10m50s
Test / Flake checks (push) Successful in 2m43s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 17:44:20 +09:00
cat 1a2b979add internal/rosa/rsync: 3.4.1 to 3.4.2
Test / Create distribution (push) Successful in 1m41s
Test / Hakurei (push) Successful in 10m25s
Test / ShareFS (push) Successful in 11m25s
Test / Sandbox (push) Successful in 2m52s
Test / Sandbox (race detector) (push) Successful in 3m51s
Test / Hakurei (race detector) (push) Successful in 7m21s
Test / Flake checks (push) Successful in 2m41s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 16:37:47 +09:00
cat b1c90cc380 internal/rosa/libexpat: 2.7.5 to 2.8.0
Test / Create distribution (push) Successful in 1m39s
Test / Sandbox (push) Successful in 6m17s
Test / Hakurei (push) Successful in 10m19s
Test / ShareFS (push) Successful in 11m21s
Test / Hakurei (race detector) (push) Successful in 14m4s
Test / Sandbox (race detector) (push) Successful in 3m47s
Test / Flake checks (push) Successful in 2m46s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 16:37:16 +09:00
cat 3a66b8143a internal/rosa/nss: 3.123 to 3.123.1
Test / Create distribution (push) Successful in 1m42s
Test / ShareFS (push) Successful in 11m22s
Test / Sandbox (push) Successful in 2m53s
Test / Sandbox (race detector) (push) Successful in 4m13s
Test / Hakurei (push) Successful in 6m43s
Test / Hakurei (race detector) (push) Successful in 9m24s
Test / Flake checks (push) Successful in 1m54s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 16:15:14 +09:00
cat 64bbd3aabd internal/rosa/mesa: libdrm 2.4.131 to 2.4.133
Test / Create distribution (push) Successful in 43s
Test / ShareFS (push) Successful in 7m51s
Test / Sandbox (race detector) (push) Successful in 8m20s
Test / Hakurei (race detector) (push) Successful in 11m53s
Test / Hakurei (push) Successful in 7m36s
Test / Sandbox (push) Successful in 2m49s
Test / Flake checks (push) Successful in 2m52s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 16:03:49 +09:00
cat 08799a13d0 internal/rosa/glslang: spirv-tools check stable versions
Test / Create distribution (push) Successful in 44s
Test / ShareFS (push) Successful in 8m21s
Test / Hakurei (push) Successful in 7m52s
Test / Hakurei (race detector) (push) Successful in 9m37s
Test / Sandbox (push) Successful in 2m52s
Test / Sandbox (race detector) (push) Successful in 3m54s
Test / Flake checks (push) Successful in 2m47s
This hides release candidates.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 16:03:29 +09:00
cat 1aef9c3bbb internal/rosa/python: pathspec 1.0.4 to 1.1.1
Test / Create distribution (push) Successful in 1m45s
Test / Sandbox (push) Successful in 5m18s
Test / Hakurei (push) Successful in 10m58s
Test / ShareFS (push) Successful in 12m18s
Test / Sandbox (race detector) (push) Successful in 5m26s
Test / Hakurei (race detector) (push) Successful in 10m54s
Test / Flake checks (push) Successful in 2m49s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 16:02:19 +09:00
cat 1f38303747 internal/rosa/python: packaging 26.1 to 26.2
Test / Create distribution (push) Successful in 59s
Test / Hakurei (push) Successful in 8m13s
Test / ShareFS (push) Successful in 8m55s
Test / Hakurei (race detector) (push) Successful in 10m57s
Test / Sandbox (push) Successful in 3m3s
Test / Sandbox (race detector) (push) Successful in 4m12s
Test / Flake checks (push) Successful in 2m41s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 16:01:56 +09:00
cat 640777b00c internal/rosa/gnu: parallel 20260322 to 20260422
Test / Create distribution (push) Successful in 1m30s
Test / Sandbox (push) Successful in 4m59s
Test / Hakurei (push) Successful in 7m3s
Test / ShareFS (push) Successful in 7m55s
Test / Sandbox (race detector) (push) Successful in 7m59s
Test / Hakurei (race detector) (push) Successful in 10m59s
Test / Flake checks (push) Successful in 2m56s
This pulls in bash with nonstandard hardcoded path.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 15:58:59 +09:00
cat 1d657193cf internal/rosa/kernel: disable md
Test / Create distribution (push) Successful in 2m31s
Test / Sandbox (push) Successful in 6m33s
Test / Hakurei (push) Successful in 8m44s
Test / Sandbox (race detector) (push) Successful in 9m22s
Test / ShareFS (push) Successful in 9m32s
Test / Hakurei (race detector) (push) Successful in 7m37s
Test / Flake checks (push) Successful in 3m22s
This is entirely unused and is a somewhat large attack surface, so disable it.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 15:48:20 +09:00
cat bab5406295 internal/rosa/go: require popcnt for x86
Test / Sandbox (push) Successful in 6m33s
Test / Hakurei (push) Successful in 8m16s
Test / ShareFS (push) Successful in 8m12s
Test / Sandbox (race detector) (push) Successful in 8m58s
Test / Hakurei (race detector) (push) Successful in 10m31s
Test / Flake checks (push) Successful in 1m51s
Test / Create distribution (push) Successful in 2m2s
This backports https://go.dev/cl/746640.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-28 14:36:59 +09:00
cat 725ae7d64d nix: remove all explicit timeouts
Test / ShareFS (push) Successful in 2m18s
Test / Create distribution (push) Successful in 2m43s
Test / Sandbox (push) Successful in 3m21s
Test / Sandbox (race detector) (push) Successful in 4m11s
Test / Hakurei (push) Successful in 4m26s
Test / Hakurei (race detector) (push) Successful in 5m14s
Test / Flake checks (push) Successful in 1m54s
These were useful during development because timing out is often the only indication of failure due to the terrible design of nixos vm test harness. This has become a nuisance however especially when the system is under load, so remove explicit values and fall back to the ludicrously high default.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 13:07:22 +09:00
cat 37a0c3967e internal/rosa/gnu: mpc fetch source tarball
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 3m3s
Test / Hakurei (push) Successful in 4m22s
Test / ShareFS (push) Successful in 4m27s
Test / Sandbox (race detector) (push) Successful in 6m7s
Test / Hakurei (race detector) (push) Successful in 3m35s
Test / Flake checks (push) Successful in 3m10s
This does not have submodules, so the overhead of git is unnecessary.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 12:57:11 +09:00
cat ea0692548f internal/rosa/gnu: coreutils 9.10 to 9.11
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m31s
Test / ShareFS (push) Successful in 5m13s
Test / Sandbox (race detector) (push) Successful in 6m16s
Test / Hakurei (race detector) (push) Successful in 8m20s
Test / Hakurei (push) Successful in 3m2s
Test / Flake checks (push) Successful in 1m34s
Test regression was fixed, dropping patch.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 12:30:46 +09:00
cat 48ea23e648 internal/rosa/gnu: sed 4.9 to 4.10
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m52s
Test / Hakurei (push) Successful in 4m33s
Test / ShareFS (push) Successful in 3m14s
Test / Sandbox (race detector) (push) Successful in 5m12s
Test / Hakurei (race detector) (push) Successful in 7m1s
Test / Flake checks (push) Successful in 1m26s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 12:30:06 +09:00
cat 40320e4920 internal/rosa/meson: 1.11.0 to 1.11.1
Test / Create distribution (push) Successful in 37s
Test / Sandbox (push) Successful in 3m10s
Test / ShareFS (push) Successful in 3m20s
Test / Sandbox (race detector) (push) Successful in 5m44s
Test / Hakurei (race detector) (push) Successful in 7m25s
Test / Hakurei (push) Successful in 2m40s
Test / Flake checks (push) Successful in 1m21s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 12:29:17 +09:00
cat 3ca0f61632 internal/rosa/llvm: 22.1.3 to 22.1.4
Test / Sandbox (push) Successful in 2m36s
Test / Create distribution (push) Successful in 1m32s
Test / Hakurei (push) Successful in 6m1s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m47s
Test / Hakurei (race detector) (push) Successful in 7m32s
Test / Flake checks (push) Successful in 1m37s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 12:28:55 +09:00
cat 6ffaac96e3 internal/rosa/cmake: 4.3.1 to 4.3.2
Test / Sandbox (push) Successful in 3m34s
Test / Hakurei (push) Successful in 5m22s
Test / ShareFS (push) Successful in 4m49s
Test / Sandbox (race detector) (push) Successful in 6m35s
Test / Hakurei (race detector) (push) Successful in 8m16s
Test / Flake checks (push) Successful in 1m45s
Test / Create distribution (push) Successful in 57s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 12:28:34 +09:00
cat 13c7713d0c internal/rosa/kernel: 6.12.82 to 6.12.83
Test / Create distribution (push) Successful in 1m21s
Test / Sandbox (push) Successful in 3m27s
Test / ShareFS (push) Successful in 5m19s
Test / Sandbox (race detector) (push) Successful in 6m46s
Test / Hakurei (race detector) (push) Successful in 8m17s
Test / Hakurei (push) Successful in 2m58s
Test / Flake checks (push) Successful in 1m28s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 12:28:14 +09:00
cat 42389f7ec5 internal/rosa/qemu: 10.2.2 to 11.0.0
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m49s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 4m3s
Test / Sandbox (race detector) (push) Successful in 5m31s
Test / Hakurei (race detector) (push) Successful in 6m28s
Test / Flake checks (push) Successful in 1m23s
This pulls in some python packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 01:15:13 +09:00
cat 30f130c691 internal/rosa/python: wheel artifact
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m32s
No idea why this ended up as a package.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 01:07:14 +09:00
cat ceb4d26087 internal/pkg: record cache variant on-disk
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m47s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hakurei (race detector) (push) Successful in 6m23s
Test / Hakurei (push) Successful in 2m39s
Test / Flake checks (push) Successful in 1m25s
This makes custom artifacts much less error-prone to use.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-23 00:53:21 +09:00
cat 852f3a9b3d internal/rosa/kernel: 6.12.81 to 6.12.82
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m53s
Test / ShareFS (push) Successful in 4m3s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m27s
Test / Hakurei (push) Successful in 2m37s
Test / Flake checks (push) Successful in 1m18s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-20 22:11:13 +09:00
cat 5e02dbdb0d internal/rosa/python: remove pypi helpers
Test / Create distribution (push) Successful in 1m20s
Test / Sandbox (push) Successful in 3m15s
Test / Hakurei (push) Successful in 4m22s
Test / ShareFS (push) Successful in 4m28s
Test / Sandbox (race detector) (push) Successful in 5m39s
Test / Hakurei (race detector) (push) Successful in 6m48s
Test / Flake checks (push) Successful in 1m25s
Pypi is disallowed by policy so these helpers are no longer useful.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-20 02:37:10 +09:00
cat 6a3248d472 internal/rosa/python: install pyyaml from source
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m57s
Test / ShareFS (push) Successful in 3m57s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 6m26s
Test / Hakurei (push) Successful in 3m12s
Test / Flake checks (push) Successful in 1m27s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-20 02:35:30 +09:00
cat 67404c98d9 internal/rosa/nss: install buildcatrust from source
Test / Create distribution (push) Successful in 2m0s
Test / Sandbox (push) Successful in 3m49s
Test / Hakurei (push) Successful in 4m56s
Test / ShareFS (push) Successful in 5m0s
Test / Sandbox (race detector) (push) Successful in 6m32s
Test / Hakurei (race detector) (push) Successful in 7m30s
Test / Flake checks (push) Successful in 2m55s
Dependencies are now available, so this no longer has to rely on the release.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-20 02:09:24 +09:00
cat b9bf69cfce internal/rosa/python: install mako from source
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 3m59s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m14s
Test / Hakurei (race detector) (push) Successful in 6m30s
Test / Flake checks (push) Successful in 2m28s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-20 01:55:23 +09:00
cat 4648f98272 internal/rosa/python: run tests via helper
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m52s
Test / ShareFS (push) Successful in 3m58s
Test / Sandbox (race detector) (push) Successful in 5m22s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Hakurei (push) Successful in 2m39s
Test / Flake checks (push) Successful in 1m25s
Despite the lack of standards, pytest seems somewhat widely agreed upon.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-20 01:50:57 +09:00
cat 11d99439ac internal/rosa/python: install markupsafe from source
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m9s
Test / ShareFS (push) Successful in 2m52s
Test / Hakurei (race detector) (push) Successful in 3m51s
Test / Flake checks (push) Successful in 1m24s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-20 01:26:11 +09:00
cat 39e4c5b8ac internal/rosa/python: optionally install before check
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m52s
Test / ShareFS (push) Successful in 3m27s
Test / Sandbox (race detector) (push) Successful in 4m50s
Test / Hakurei (race detector) (push) Successful in 6m25s
Test / Hakurei (push) Successful in 2m43s
Test / Flake checks (push) Successful in 1m23s
Some test suites require package to be installed globally.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-20 01:25:43 +09:00
cat e8f6db38b6 internal/rosa/python: install pytest from source
Test / Create distribution (push) Successful in 1m2s
Test / ShareFS (push) Successful in 5m1s
Test / Hakurei (push) Successful in 5m5s
Test / Hakurei (race detector) (push) Successful in 10m55s
Test / Sandbox (push) Successful in 3m29s
Test / Sandbox (race detector) (push) Successful in 4m35s
Test / Flake checks (push) Successful in 3m1s
Used by many python packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 23:17:38 +09:00
cat 20d5b71575 internal/rosa/python: install iniconfig from source
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 3m48s
Test / Hakurei (race detector) (push) Successful in 6m23s
Test / Sandbox (race detector) (push) Successful in 2m40s
Test / ShareFS (push) Successful in 2m50s
Test / Flake checks (push) Successful in 1m21s
This also required the setuptools-scm hack.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 22:53:32 +09:00
cat e903e7f542 internal/rosa/python: install pygments from source
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m51s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m59s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m26s
Test / Flake checks (push) Successful in 1m22s
This finally has its dependencies.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 22:40:43 +09:00
cat 1caa051f4d internal/rosa/python: hatchling artifact
Test / Sandbox (push) Successful in 2m48s
Test / ShareFS (push) Successful in 3m53s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Create distribution (push) Successful in 33s
Test / Hakurei (push) Successful in 2m37s
Test / Flake checks (push) Successful in 1m21s
Required by many python packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 22:35:18 +09:00
cat dcdc6f7f6d internal/rosa/python: trove-classifiers artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 4m55s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hakurei (race detector) (push) Successful in 6m27s
Test / Flake checks (push) Successful in 1m31s
Required by hatchling.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 22:32:12 +09:00
cat 5ad6f26b46 internal/rosa/python: install packaging from source
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m50s
Test / Hakurei (push) Successful in 3m50s
Test / ShareFS (push) Successful in 3m54s
Test / Sandbox (race detector) (push) Successful in 5m13s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m22s
This is required by many packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 22:12:49 +09:00
cat 7ba75a79f4 internal/rosa/python: install pluggy from source
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 3m44s
Test / ShareFS (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 5m13s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m23s
This finally has all its dependencies at this point.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 21:55:55 +09:00
cat 9ef84d3904 internal/rosa/python: setuptools-scm artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m54s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m20s
Test / Flake checks (push) Successful in 1m21s
Awful hack required by many packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 21:38:44 +09:00
cat 3b7b6e51fb internal/rosa/python: pass build dependencies separately
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m53s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m22s
This is cleaner with less duplicate code.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 20:26:41 +09:00
cat b1b4debb82 internal/rosa/python: pathspec artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m52s
Test / Hakurei (push) Successful in 3m53s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 5m14s
Test / Hakurei (race detector) (push) Successful in 6m28s
Test / Flake checks (push) Successful in 1m22s
Required by hatchling, which is required by many python packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 20:13:26 +09:00
cat 021cbbc2a8 cmd/mbf: default daemon socket in cache
Test / Create distribution (push) Successful in 1m7s
Test / Sandbox (push) Successful in 2m52s
Test / Hakurei (push) Successful in 3m53s
Test / ShareFS (push) Successful in 4m1s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 6m26s
Test / Flake checks (push) Successful in 1m23s
This location makes more sense than the current directory.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 19:50:54 +09:00
cat a4a54a4a4d cmd/mbf: remove pointless recover
Test / Create distribution (push) Successful in 37s
Test / Sandbox (push) Successful in 1m43s
Test / Hakurei (push) Successful in 2m46s
Test / ShareFS (push) Successful in 2m44s
Test / Sandbox (race detector) (push) Successful in 5m25s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m20s
This used to scrub the cache, and was not fully removed when that became nonviable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 19:49:01 +09:00
cat 04a344aac6 internal/rosa/python: flirt_core artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m42s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m26s
Test / Flake checks (push) Successful in 1m23s
A build system required by a dependency of another build system, which is required by yet another build system.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 19:25:04 +09:00
cat 6b98156a3d internal/rosa/python: change insane strict_timestamps default
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hakurei (race detector) (push) Successful in 6m25s
Test / Flake checks (push) Successful in 1m20s
There is no scenario where this is useful, and it breaks builds.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 18:56:22 +09:00
cat 753432cf09 cmd/mbf: optionally wait for cancel
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m57s
Test / ShareFS (push) Successful in 3m57s
Test / Sandbox (race detector) (push) Successful in 5m36s
Test / Hakurei (race detector) (push) Successful in 9m26s
Test / Flake checks (push) Successful in 2m12s
Synchronisation is not needed here during interactive use.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 18:24:11 +09:00
cat f8902e3679 internal/rosa/python: append to source path
Test / Create distribution (push) Successful in 1m10s
Test / Sandbox (push) Successful in 2m55s
Test / Hakurei (push) Successful in 4m1s
Test / ShareFS (push) Successful in 4m5s
Test / Sandbox (race detector) (push) Successful in 5m26s
Test / Hakurei (race detector) (push) Successful in 6m34s
Test / Flake checks (push) Successful in 1m22s
This gets around messy projects with multiple packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 17:51:00 +09:00
cat 8ee53a5164 internal/rosa: use builtin for checksum warning
Test / Create distribution (push) Successful in 40s
Test / Sandbox (push) Successful in 2m49s
Test / Hakurei (push) Successful in 3m57s
Test / ShareFS (push) Successful in 3m59s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m30s
Test / Flake checks (push) Successful in 1m21s
This avoids having to configure the logger early.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 17:50:12 +09:00
cat 3981d44757 internal/rosa/python: migrate setuptools to wrapper
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 3m56s
Test / ShareFS (push) Successful in 4m4s
Test / Sandbox (race detector) (push) Successful in 6m2s
Test / Hakurei (race detector) (push) Successful in 7m54s
Test / Flake checks (push) Successful in 2m9s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 15:36:43 +09:00
cat 9fd67e47b4 internal/rosa/python: wrap python package
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 5m13s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Flake checks (push) Successful in 1m22s
Metadata for this is somewhat boilerplate-heavy, so wrap it to create metadata in one call.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 15:22:18 +09:00
cat 4dcec40156 cmd/mbf: close on cancel completion
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m55s
Test / Hakurei (push) Successful in 3m52s
Test / ShareFS (push) Successful in 3m57s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 6m29s
Test / Flake checks (push) Successful in 1m21s
Like the previous change, this enables synchronisation on the client side via epoll.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 15:03:52 +09:00
cat 9a274c78a3 cmd/mbf: close on abort completion
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m49s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Flake checks (push) Successful in 1m19s
This enables synchronisation on the client side via epoll.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 14:53:28 +09:00
cat 5647c3a91f internal/rosa/meson: run meson test suite
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 3m35s
Test / Hakurei (push) Successful in 4m38s
Test / ShareFS (push) Successful in 4m49s
Test / Sandbox (race detector) (push) Successful in 5m53s
Test / Hakurei (race detector) (push) Successful in 6m56s
Test / Flake checks (push) Successful in 1m16s
Tests requiring internet access or unreasonable dependencies are removed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 01:07:20 +09:00
cat 992139c75d internal/rosa/python: extra script after install
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m49s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Flake checks (push) Successful in 1m21s
This is generally for test suite, due to the lack of standard or widely agreed upon convention.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 00:35:24 +09:00
cat 57c69b533e internal/rosa/meson: migrate to helper
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m44s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 6m25s
Test / Hakurei (push) Successful in 2m51s
Test / Flake checks (push) Successful in 1m21s
This also migrates to source from the Microsoft Github release.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 00:16:22 +09:00
cat 6f0c2a80f2 internal/rosa/python: migrate setuptools to helper
Test / Create distribution (push) Successful in 1m33s
Test / Sandbox (push) Successful in 3m18s
Test / Hakurei (push) Successful in 4m25s
Test / ShareFS (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 5m46s
Test / Hakurei (race detector) (push) Successful in 6m54s
Test / Flake checks (push) Successful in 1m24s
This is much cleaner, and should be functionally equivalent.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 00:04:19 +09:00
cat 08dfefb28d internal/rosa/python: pip helper
Test / Create distribution (push) Successful in 1m29s
Test / Sandbox (push) Successful in 3m12s
Test / ShareFS (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 5m44s
Test / Hakurei (race detector) (push) Successful in 6m55s
Test / Hakurei (push) Successful in 2m39s
Test / Flake checks (push) Successful in 1m23s
Binary pip releases are not considered acceptable, this more generic helper is required for building from source.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-19 00:03:36 +09:00
cat b081629662 internal/rosa/libxml2: 2.15.2 to 2.15.3
Test / Create distribution (push) Successful in 1m11s
Test / Sandbox (push) Successful in 3m3s
Test / ShareFS (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m46s
Test / Hakurei (race detector) (push) Successful in 6m58s
Test / Hakurei (push) Successful in 2m27s
Test / Flake checks (push) Successful in 1m15s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-18 09:05:49 +09:00
cat fba541f301 internal/rosa/nss: 3.122.1 to 3.123
Test / Sandbox (push) Successful in 2m1s
Test / Create distribution (push) Successful in 1m1s
Test / Hakurei (push) Successful in 4m36s
Test / ShareFS (push) Successful in 3m3s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m43s
Test / Flake checks (push) Successful in 1m22s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-18 09:05:23 +09:00
cat 5f0da3d5c2 internal/rosa/gnu: mpc 1.4.0 to 1.4.1
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 3m5s
Test / Hakurei (push) Successful in 4m22s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m46s
Test / Hakurei (race detector) (push) Successful in 7m2s
Test / Flake checks (push) Successful in 1m24s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-18 09:04:33 +09:00
cat 4d5841dd62 internal/rosa: elfutils 0.194 to 0.195
Test / Create distribution (push) Successful in 44s
Test / Sandbox (push) Successful in 3m4s
Test / ShareFS (push) Successful in 4m32s
Test / Sandbox (race detector) (push) Successful in 5m41s
Test / Hakurei (race detector) (push) Successful in 6m53s
Test / Hakurei (push) Successful in 2m31s
Test / Flake checks (push) Successful in 1m20s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-18 09:03:49 +09:00
cat 9e752b588a internal/pkg: drop cached error on cancel
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m54s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m23s
This avoids disabling the artifact when using the individual cancel method. Unfortunately this makes the method blocking.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-18 03:24:48 +09:00
cat 27b1aaae38 internal/pkg: pending error alongside done channel
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m44s
Test / ShareFS (push) Successful in 3m48s
Test / Hakurei (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 6m28s
Test / Flake checks (push) Successful in 1m20s
This significantly simplifies synchronisation of access to identErr.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-18 03:10:37 +09:00
cat 9e18de1dc2 internal/pkg: flush cached errors on abort
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 5m14s
Test / Hakurei (race detector) (push) Successful in 6m17s
Test / Flake checks (push) Successful in 1m17s
This avoids disabling the artifact until cache is reopened. The same has to be implemented for Cancel in a future change.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-18 02:59:44 +09:00
cat b80ea91a42 cmd/mbf: abort remote cures
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m15s
This command arranges for all pending cures to be aborted. It does not wait for cures to complete.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 22:47:02 +09:00
cat 30a9dfa4b8 internal/pkg: abort all pending cures
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m53s
Test / Sandbox (race detector) (push) Successful in 5m27s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m31s
This cancels all current pending cures without closing the cache.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 22:40:35 +09:00
cat 8d657b6fdf cmd/mbf: cancel remote cure
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 6m19s
Test / Flake checks (push) Successful in 1m22s
This exposes the new fine-grained cancel API in cmd/mbf.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 22:00:04 +09:00
cat ae9b9adfd2 internal/rosa: retry in SIGSEGV test
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 5m10s
Test / Hakurei (race detector) (push) Successful in 6m19s
Test / Flake checks (push) Successful in 1m21s
Munmap is not always immediate.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 20:45:19 +09:00
cat dd6a480a21 cmd/mbf: handle flags in serve
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m47s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 5m13s
Test / Hakurei (race detector) (push) Successful in 6m27s
Test / Hakurei (push) Successful in 2m38s
Test / Flake checks (push) Successful in 1m21s
This enables easier expansion of the protocol.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 20:14:09 +09:00
cat 3942272c30 internal/pkg: fine-grained cancellation
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hakurei (race detector) (push) Successful in 6m34s
Test / Flake checks (push) Successful in 1m21s
This enables a specific artifact to be targeted for cancellation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 19:33:21 +09:00
cat 9036986156 cmd/mbf: optionally ignore reply
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m57s
Test / Hakurei (push) Successful in 3m52s
Test / ShareFS (push) Successful in 4m58s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m29s
Test / Flake checks (push) Successful in 1m47s
An acknowledgement is not always required in this use case. This change also adds 64 bits of connection configuration for future expansion.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 16:46:49 +09:00
cat a394971dd7 cmd/mbf: do not abort cache acquisition during testing
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 2m59s
Test / Hakurei (push) Successful in 3m58s
Test / ShareFS (push) Successful in 4m7s
Test / Sandbox (race detector) (push) Successful in 5m29s
Test / Hakurei (race detector) (push) Successful in 6m32s
Test / Flake checks (push) Successful in 1m22s
This can sometimes fire during testing due to how short the test is.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 02:06:51 +09:00
cat 9daba60809 cmd/mbf: daemon command
Test / Create distribution (push) Successful in 1m9s
Test / Sandbox (push) Successful in 2m49s
Test / ShareFS (push) Successful in 4m0s
Test / Sandbox (race detector) (push) Successful in 5m25s
Test / Hakurei (race detector) (push) Successful in 6m27s
Test / Hakurei (push) Successful in 3m53s
Test / Flake checks (push) Successful in 1m25s
This services internal/pkg artifact IR with Rosa OS extensions originating from another process.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 02:05:59 +09:00
cat bcd79a22ff cmd/mbf: do not open cache for IR encoding
Test / Create distribution (push) Successful in 1m7s
Test / Sandbox (push) Successful in 2m52s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 3m56s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 6m28s
Test / Flake checks (push) Successful in 1m24s
This can now be allocated independently.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 01:04:39 +09:00
cat 0ff7ab915b internal/pkg: move IR primitives out of cache
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 51s
Test / Hakurei (push) Successful in 2m43s
Test / ShareFS (push) Successful in 2m39s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m24s
These are memory management and caching primitives. Having them as part of Cache is cumbersome and requires a temporary directory that is never used. This change isolates them from Cache to enable independent use.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-17 01:02:13 +09:00
cat 823575acac cmd/mbf: move info command
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m44s
Test / Sandbox (race detector) (push) Successful in 5m14s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m19s
This is cleaner with less shared state.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-16 17:43:52 +09:00
cat 136bc0917b cmd/mbf: optionally open cache
Test / Sandbox (push) Successful in 3m13s
Test / Create distribution (push) Successful in 1m1s
Test / ShareFS (push) Successful in 2m42s
Test / Hakurei (push) Successful in 2m49s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m35s
Test / Flake checks (push) Successful in 1m27s
Some commands do not require the cache. This change also makes acquisition of locked cache cancelable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-16 15:59:34 +09:00
cat d6b082dd0b internal/rosa/ninja: bootstrap with verbose output
Test / ShareFS (push) Successful in 41s
Test / Sandbox (push) Successful in 47s
Test / Hakurei (push) Successful in 51s
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (race detector) (push) Successful in 2m33s
Test / Hakurei (race detector) (push) Successful in 3m45s
Test / Flake checks (push) Successful in 2m28s
This otherwise outputs nothing, and appears to hang until the (fully single-threaded) bootstrap completes.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 22:19:05 +09:00
cat 89d6d9576b internal/rosa/make: optionally format value as is
Test / Create distribution (push) Successful in 1m22s
Test / ShareFS (push) Successful in 4m37s
Test / Sandbox (race detector) (push) Successful in 6m27s
Test / Hakurei (race detector) (push) Successful in 11m35s
Test / Sandbox (push) Successful in 3m51s
Test / Hakurei (push) Successful in 6m59s
Test / Flake checks (push) Successful in 3m5s
This enables correct formatting for awkward configure scripts.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 22:17:58 +09:00
cat fafce04a5d internal/rosa/kernel: firmware 20260309 to 20260410
Test / Create distribution (push) Successful in 1m42s
Test / Sandbox (push) Successful in 4m43s
Test / Hakurei (push) Successful in 9m0s
Test / ShareFS (push) Successful in 6m37s
Test / Sandbox (race detector) (push) Successful in 7m41s
Test / Hakurei (race detector) (push) Successful in 11m49s
Test / Flake checks (push) Successful in 3m23s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 22:16:47 +09:00
cat 5d760a1db9 internal/rosa/kernel: 6.12.80 to 6.12.81
Test / Create distribution (push) Successful in 3m56s
Test / Sandbox (push) Successful in 6m45s
Test / Sandbox (race detector) (push) Successful in 8m8s
Test / Hakurei (push) Successful in 9m17s
Test / ShareFS (push) Successful in 7m49s
Test / Hakurei (race detector) (push) Successful in 11m30s
Test / Flake checks (push) Successful in 2m26s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 22:16:30 +09:00
cat d197e40b2a internal/rosa/python: mako 1.3.10 to 1.3.11
Test / Create distribution (push) Successful in 1m9s
Test / ShareFS (push) Successful in 8m17s
Test / Sandbox (race detector) (push) Successful in 8m23s
Test / Hakurei (race detector) (push) Successful in 11m2s
Test / Sandbox (push) Successful in 3m54s
Test / Hakurei (push) Successful in 6m57s
Test / Flake checks (push) Successful in 3m15s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 22:21:54 +09:00
cat 2008902247 internal/rosa/python: packaging 26.0 to 26.1
Test / Create distribution (push) Successful in 1m7s
Test / Sandbox (push) Successful in 2m58s
Test / Hakurei (push) Successful in 5m25s
Test / ShareFS (push) Successful in 6m57s
Test / Sandbox (race detector) (push) Successful in 3m28s
Test / Hakurei (race detector) (push) Successful in 6m50s
Test / Flake checks (push) Successful in 3m20s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 22:15:18 +09:00
cat 30ac985fd2 internal/rosa/meson: 1.10.2 to 1.11.0
Test / Create distribution (push) Successful in 1m7s
Test / Sandbox (push) Successful in 3m0s
Test / Hakurei (push) Successful in 4m52s
Test / ShareFS (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 11m4s
Test / Flake checks (push) Successful in 2m25s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 22:14:52 +09:00
cat e9fec368f8 internal/rosa/nss: 3.122 to 3.122.1
Test / Create distribution (push) Successful in 37s
Test / Sandbox (push) Successful in 2m55s
Test / ShareFS (push) Successful in 3m58s
Test / Sandbox (race detector) (push) Successful in 5m14s
Test / Hakurei (race detector) (push) Successful in 7m5s
Test / Hakurei (push) Successful in 4m35s
Test / Flake checks (push) Successful in 3m29s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 22:13:45 +09:00
cat 46add42f58 internal/rosa/openssl: disable building docs
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 3m8s
Test / Hakurei (push) Successful in 4m28s
Test / ShareFS (push) Successful in 4m48s
Test / Sandbox (race detector) (push) Successful in 5m58s
Test / Hakurei (race detector) (push) Successful in 7m12s
Test / Flake checks (push) Successful in 2m25s
These take very long and are never used in the Rosa OS environment.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 22:13:18 +09:00
cat 377b61e342 internal/rosa/openssl: do not double test job count
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 4m16s
Test / ShareFS (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 5m40s
Test / Hakurei (race detector) (push) Successful in 7m3s
Test / Flake checks (push) Successful in 2m23s
The test suite is racy, this reduces flakiness.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 22:12:36 +09:00
cat 520c36db6d internal/rosa: respect preferred job count
Test / Create distribution (push) Successful in 3m52s
Test / Sandbox (push) Successful in 9m15s
Test / ShareFS (push) Successful in 14m28s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (push) Successful in 8m11s
Test / Hakurei (race detector) (push) Successful in 9m23s
Test / Flake checks (push) Successful in 3m23s
This discontinues use of nproc, and also overrides detection behaviour in ninja.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 18:49:36 +09:00
cat 3352bb975b internal/pkg: job count in container environment
Test / Create distribution (push) Successful in 2m6s
Test / Sandbox (push) Successful in 4m51s
Test / Hakurei (push) Successful in 6m5s
Test / ShareFS (push) Successful in 6m16s
Test / Sandbox (race detector) (push) Successful in 7m15s
Test / Hakurei (race detector) (push) Successful in 8m19s
Test / Flake checks (push) Successful in 2m7s
This exposes preferred job count to the container initial process.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 15:49:21 +09:00
cat f7f48d57e9 internal/pkg: pass impure job count
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m58s
Test / Hakurei (push) Successful in 3m56s
Test / ShareFS (push) Successful in 4m6s
Test / Sandbox (race detector) (push) Successful in 5m24s
Test / Hakurei (race detector) (push) Successful in 6m49s
Test / Flake checks (push) Successful in 1m27s
This is cleaner than checking cpu count during cure, it is impossible to avoid impurity in both situations but this is configurable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-15 15:36:44 +09:00
cat 5c2345128e internal/rosa/llvm: autodetect stage0 target
Test / Create distribution (push) Successful in 1m34s
Test / Sandbox (push) Successful in 4m11s
Test / Hakurei (push) Successful in 5m26s
Test / ShareFS (push) Successful in 5m35s
Test / Sandbox (race detector) (push) Successful in 6m37s
Test / Hakurei (race detector) (push) Successful in 7m49s
Test / Flake checks (push) Successful in 2m26s
This is fine, now that stages beyond stage0 have explicit target.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-14 03:10:26 +09:00
cat 78f9676b1f internal/rosa/llvm: centralise llvm source
Test / Create distribution (push) Successful in 2m25s
Test / Sandbox (push) Successful in 4m36s
Test / ShareFS (push) Successful in 6m11s
Test / Sandbox (race detector) (push) Successful in 7m20s
Test / Hakurei (race detector) (push) Successful in 11m45s
Test / Hakurei (push) Successful in 4m5s
Test / Flake checks (push) Successful in 1m41s
This avoids having to sidestep the NewPackage name formatting machinery to take the cache fast path.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-14 03:03:06 +09:00
cat 5b5b676132 internal/rosa/cmake: remove variant
Test / Create distribution (push) Successful in 1m21s
Test / Sandbox (race detector) (push) Successful in 9m30s
Test / ShareFS (push) Successful in 9m35s
Test / Sandbox (push) Successful in 3m6s
Test / Hakurei (push) Successful in 4m24s
Test / Hakurei (race detector) (push) Successful in 5m23s
Test / Flake checks (push) Successful in 1m35s
This has no effect outside formatting of name and is a remnant of the old llvm helpers.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-14 02:57:47 +09:00
cat 78383fb6e8 internal/rosa/llvm: migrate libclc
Test / Create distribution (push) Successful in 1m19s
Test / Sandbox (push) Successful in 3m20s
Test / Hakurei (push) Successful in 4m32s
Test / ShareFS (push) Successful in 4m42s
Test / Sandbox (race detector) (push) Successful in 5m55s
Test / Hakurei (race detector) (push) Successful in 7m3s
Test / Flake checks (push) Successful in 1m27s
This eliminates newLLVMVariant.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-14 02:40:13 +09:00
cat e97f6a393f internal/rosa/llvm: migrate runtimes and clang
Test / Create distribution (push) Successful in 1m56s
Test / ShareFS (push) Successful in 8m1s
Test / Sandbox (race detector) (push) Successful in 8m31s
Test / Hakurei (race detector) (push) Successful in 9m57s
Test / Sandbox (push) Successful in 2m13s
Test / Hakurei (push) Successful in 3m17s
Test / Flake checks (push) Successful in 1m38s
This eliminates most newLLVM family of functions.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-14 02:07:13 +09:00
cat eeffefd22b internal/rosa/llvm: migrate compiler-rt helper
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m23s
Test / Hakurei (push) Successful in 4m29s
Test / ShareFS (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m51s
Test / Hakurei (race detector) (push) Successful in 6m58s
Test / Flake checks (push) Successful in 1m26s
This also removes unused dependencies.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-14 01:12:56 +09:00
cat ac825640ab internal/rosa/llvm: migrate musl
Test / Create distribution (push) Successful in 2m48s
Test / Sandbox (push) Successful in 6m4s
Test / Hakurei (push) Successful in 7m14s
Test / ShareFS (push) Successful in 7m23s
Test / Sandbox (race detector) (push) Successful in 9m34s
Test / Hakurei (race detector) (push) Successful in 4m2s
Test / Flake checks (push) Successful in 1m33s
This removes the pointless special treatment given to musl.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-14 00:35:42 +09:00
cat a7f7ce1795 internal/rosa/llvm: migrate compiler-rt
Test / Create distribution (push) Successful in 1m24s
Test / Sandbox (push) Successful in 3m33s
Test / Hakurei (push) Successful in 6m17s
Test / ShareFS (push) Successful in 6m14s
Test / Hakurei (race detector) (push) Successful in 8m34s
Test / Sandbox (race detector) (push) Successful in 5m39s
Test / Flake checks (push) Successful in 1m40s
The newLLVM family of functions predate the package system. This change migrates compiler-rt without changing any resulting artifacts.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-14 00:19:33 +09:00
cat 38c639e35c internal/rosa/llvm: remove project/runtime helper
Test / Create distribution (push) Successful in 2m48s
Test / Sandbox (push) Successful in 7m21s
Test / ShareFS (push) Successful in 8m34s
Test / Sandbox (race detector) (push) Successful in 9m6s
Test / Hakurei (race detector) (push) Successful in 10m14s
Test / Hakurei (push) Successful in 3m30s
Test / Flake checks (push) Successful in 2m54s
More remnants from early days, these are not reusable at all but that was not known at the time.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-14 00:03:23 +09:00
cat b2cb13e94c internal/rosa/llvm: centralise patches
Test / Create distribution (push) Successful in 1m18s
Test / Sandbox (push) Successful in 3m19s
Test / Hakurei (push) Successful in 4m19s
Test / ShareFS (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 5m48s
Test / Hakurei (race detector) (push) Successful in 8m20s
Test / Flake checks (push) Successful in 4m32s
This enables easier reuse of the patchset.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 23:52:44 +09:00
cat 46f98d12d6 internal/rosa/llvm: remove conditional flags in helper
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m13s
Test / Hakurei (push) Successful in 4m17s
Test / ShareFS (push) Successful in 4m27s
Test / Sandbox (race detector) (push) Successful in 5m41s
Test / Hakurei (race detector) (push) Successful in 6m50s
Test / Flake checks (push) Successful in 1m27s
The llvm helper is a remnant from very early days, and ended up not being very useful, but was never removed. This change begins its removal, without changing the resulting artifacts for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 23:38:11 +09:00
cat 503c7f953c internal/rosa/x: libpciaccess artifact
Test / Create distribution (push) Successful in 1m17s
Test / Sandbox (push) Successful in 3m19s
Test / Hakurei (push) Successful in 7m51s
Test / ShareFS (push) Successful in 8m40s
Test / Sandbox (race detector) (push) Successful in 4m32s
Test / Hakurei (race detector) (push) Successful in 4m14s
Test / Flake checks (push) Successful in 1m34s
Required by userspace gpu drivers.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 19:04:38 +09:00
cat 15c9f6545d internal/rosa/perl: populate anitya identifiers
Test / Create distribution (push) Successful in 1m23s
Test / Sandbox (push) Successful in 3m27s
Test / Hakurei (push) Successful in 4m29s
Test / ShareFS (push) Successful in 4m37s
Test / Sandbox (race detector) (push) Successful in 5m59s
Test / Hakurei (race detector) (push) Successful in 7m3s
Test / Flake checks (push) Successful in 1m28s
These are also tracked by Anitya.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 18:44:43 +09:00
cat 83b0e32c55 internal/rosa: helpers for common url formats
Test / Create distribution (push) Successful in 1m20s
Test / Sandbox (push) Successful in 3m16s
Test / ShareFS (push) Successful in 4m39s
Test / Sandbox (race detector) (push) Successful in 5m49s
Test / Hakurei (race detector) (push) Successful in 6m59s
Test / Hakurei (push) Successful in 3m13s
Test / Flake checks (push) Successful in 1m37s
This cleans up call site of NewPackage.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 18:02:57 +09:00
cat eeaf26e7a2 internal/rosa: wrapper around git helper
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m43s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m23s
This results in much cleaner call site for the majority of use cases.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 15:20:51 +09:00
cat b587caf2e8 internal/rosa: assume file source is xz-compressed
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m51s
Test / Hakurei (push) Successful in 3m54s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 6m20s
Test / Flake checks (push) Successful in 1m22s
XZ happens to be the only widely-used format that is awful to deal with, everything else is natively supported.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 15:07:30 +09:00
cat f1c2ca4928 internal/rosa/mesa: libdrm artifact
Test / Create distribution (push) Successful in 1m23s
Test / Sandbox (push) Successful in 3m21s
Test / Hakurei (push) Successful in 4m34s
Test / ShareFS (push) Successful in 4m41s
Test / Sandbox (race detector) (push) Successful in 6m0s
Test / Hakurei (race detector) (push) Successful in 7m3s
Test / Flake checks (push) Successful in 1m29s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 03:27:09 +09:00
cat 0ca301219f internal/rosa/python: pyyaml artifact
Test / Create distribution (push) Successful in 1m39s
Test / Sandbox (push) Successful in 4m9s
Test / Hakurei (push) Successful in 5m59s
Test / ShareFS (push) Successful in 6m2s
Test / Sandbox (race detector) (push) Successful in 7m0s
Test / Hakurei (race detector) (push) Successful in 8m19s
Test / Flake checks (push) Successful in 1m54s
Mesa unfortunately requires this horrible format.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 03:18:47 +09:00
cat e2199e1276 internal/rosa/python: mako artifact
Test / Create distribution (push) Successful in 1m42s
Test / Sandbox (push) Successful in 4m3s
Test / Hakurei (push) Successful in 5m56s
Test / ShareFS (push) Successful in 6m2s
Test / Sandbox (race detector) (push) Successful in 6m59s
Test / Hakurei (race detector) (push) Successful in 8m18s
Test / Flake checks (push) Successful in 1m53s
This unfortunately pulls in platform-specific package.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 03:11:38 +09:00
cat 86eacb3208 cmd/mbf: checksum command
Test / Create distribution (push) Successful in 1m19s
Test / Sandbox (push) Successful in 3m16s
Test / Hakurei (push) Successful in 4m36s
Test / ShareFS (push) Successful in 4m43s
Test / Sandbox (race detector) (push) Successful in 5m55s
Test / Hakurei (race detector) (push) Successful in 7m11s
Test / Flake checks (push) Successful in 1m31s
This computes and encodes sha384 checksum of data streamed from standard input.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 03:09:21 +09:00
cat 8541bdd858 internal/rosa: wrap per-arch values
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m12s
Test / Hakurei (push) Successful in 4m35s
Test / ShareFS (push) Successful in 4m38s
Test / Sandbox (race detector) (push) Successful in 5m52s
Test / Hakurei (race detector) (push) Successful in 7m9s
Test / Flake checks (push) Successful in 1m31s
This is cleaner syntax in some specific cases.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 02:59:55 +09:00
cat 46be0b0dc8 internal/rosa/nss: buildcatrust 0.4.0 to 0.5.1
Test / Create distribution (push) Successful in 1m51s
Test / Sandbox (push) Successful in 7m29s
Test / ShareFS (push) Successful in 9m35s
Test / Sandbox (race detector) (push) Successful in 9m52s
Test / Hakurei (race detector) (push) Successful in 4m13s
Test / Hakurei (push) Successful in 2m53s
Test / Flake checks (push) Successful in 1m31s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 02:18:21 +09:00
cat cbe37e87e7 internal/rosa/python: pytest 9.0.2 to 9.0.3
Test / Create distribution (push) Successful in 1m27s
Test / ShareFS (push) Successful in 7m24s
Test / Sandbox (race detector) (push) Successful in 8m9s
Test / Hakurei (race detector) (push) Successful in 11m13s
Test / Sandbox (push) Successful in 2m57s
Test / Hakurei (push) Successful in 4m20s
Test / Flake checks (push) Successful in 1m36s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 02:18:02 +09:00
cat 66d741fb07 internal/rosa/python: pygments 2.19.2 to 2.20.0
Test / Create distribution (push) Successful in 2m13s
Test / ShareFS (push) Successful in 9m37s
Test / Sandbox (race detector) (push) Successful in 9m41s
Test / Hakurei (race detector) (push) Successful in 11m21s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 4m2s
Test / Flake checks (push) Successful in 1m37s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 02:13:04 +09:00
cat 0d449011f6 internal/rosa/python: use predictable URLs
Test / Create distribution (push) Successful in 1m18s
Test / Sandbox (push) Successful in 3m25s
Test / Hakurei (push) Successful in 5m54s
Test / ShareFS (push) Successful in 5m53s
Test / Sandbox (race detector) (push) Successful in 7m30s
Test / Hakurei (race detector) (push) Successful in 9m6s
Test / Flake checks (push) Successful in 2m53s
This is much cleaner and more maintainable than specifying URL prefix manually. This change also populates Anitya project identifiers.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 02:08:22 +09:00
cat 46428ed85d internal/rosa/python: url pip wheel helper
Test / Create distribution (push) Successful in 1m36s
Test / Sandbox (push) Successful in 3m34s
Test / Hakurei (push) Successful in 4m54s
Test / ShareFS (push) Successful in 4m55s
Test / Sandbox (race detector) (push) Successful in 6m9s
Test / Hakurei (race detector) (push) Successful in 4m44s
Test / Flake checks (push) Successful in 3m0s
This enables a cleaner higher-level helper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-13 01:59:28 +09:00
cat 081d6b463c internal/rosa/llvm: libclc artifact
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m51s
Test / ShareFS (push) Successful in 3m54s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m24s
This is built independently of llvm build system to avoid having to build llvm again.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-12 22:57:04 +09:00
cat 11b3171180 internal/rosa/glslang: glslang artifact
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m56s
Test / ShareFS (push) Successful in 3m50s
Test / Hakurei (push) Successful in 4m3s
Test / Sandbox (race detector) (push) Successful in 5m12s
Test / Hakurei (race detector) (push) Successful in 6m20s
Test / Flake checks (push) Successful in 1m21s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-12 22:34:17 +09:00
cat adbb84c3dd internal/rosa/glslang: spirv-tools artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Flake checks (push) Successful in 1m31s
Required by glslang.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-12 22:27:49 +09:00
cat 1084e31d95 internal/rosa/glslang: spirv-headers artifact
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m45s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m25s
Test / Flake checks (push) Successful in 1m21s
Required by spirv-tools.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-12 22:16:29 +09:00
cat 27a1b8fe0a internal/rosa/mesa: libglvnd artifact
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m51s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 5m14s
Test / Hakurei (race detector) (push) Successful in 6m18s
Test / Flake checks (push) Successful in 1m22s
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-12 21:27:30 +09:00
cat b2141a41d7 internal/rosa/dbus: xdg-dbus-proxy artifact
Test / Create distribution (push) Successful in 1m13s
Test / Sandbox (push) Successful in 3m0s
Test / Hakurei (push) Successful in 4m8s
Test / ShareFS (push) Successful in 4m19s
Test / Sandbox (race detector) (push) Successful in 5m31s
Test / Hakurei (race detector) (push) Successful in 6m35s
Test / Flake checks (push) Successful in 1m22s
This is currently a hakurei runtime dependency, but will eventually be removed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-12 19:41:49 +09:00
cat c0dff5bc87 internal/rosa/gnu: gcc set with-multilib-list as needed
Test / Create distribution (push) Successful in 1m22s
Test / Sandbox (push) Successful in 3m19s
Test / Hakurei (push) Successful in 6m57s
Test / ShareFS (push) Successful in 6m54s
Test / Sandbox (race detector) (push) Successful in 7m38s
Test / Hakurei (race detector) (push) Successful in 8m50s
Test / Flake checks (push) Successful in 2m8s
This breaks riscv64.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-12 18:03:45 +09:00
cat 04513c0510 internal/rosa/gnu: gmp explicit CC
Test / Create distribution (push) Successful in 1m25s
Test / Sandbox (push) Successful in 3m20s
Test / Hakurei (push) Successful in 4m26s
Test / ShareFS (push) Successful in 4m23s
Test / Sandbox (race detector) (push) Successful in 5m50s
Test / Hakurei (race detector) (push) Successful in 6m51s
Test / Flake checks (push) Successful in 1m54s
The configure script is hard coded to use gcc without fallback on riscv64.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-12 17:25:15 +09:00
cat 28ebf973d6 nix: add sharefs supplementary group
Test / Sandbox (push) Successful in 1m1s
Test / Hakurei (push) Successful in 1m5s
Test / Sandbox (race detector) (push) Successful in 1m2s
Test / Hakurei (race detector) (push) Successful in 1m10s
Test / Create distribution (push) Successful in 1m19s
Test / ShareFS (push) Successful in 3m6s
Test / Flake checks (push) Successful in 1m33s
This works around vfs inode file attribute race.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-11 23:28:18 +09:00
cat 41aeb404ec internal/rosa/hakurei: 0.3.7 to 0.4.0
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m9s
Test / Hakurei (push) Successful in 4m27s
Test / ShareFS (push) Successful in 4m32s
Test / Sandbox (race detector) (push) Successful in 5m37s
Test / Hakurei (race detector) (push) Successful in 6m49s
Test / Flake checks (push) Successful in 1m28s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-11 10:53:29 +09:00
cat 0b1009786f release: 0.4.0
Test / Create distribution (push) Successful in 37s
Release / Create release (push) Successful in 45s
Test / ShareFS (push) Successful in 43s
Test / Sandbox (push) Successful in 48s
Test / Hakurei (push) Successful in 52s
Test / Sandbox (race detector) (push) Successful in 48s
Test / Hakurei (race detector) (push) Successful in 55s
Test / Flake checks (push) Successful in 1m28s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-11 10:49:43 +09:00
cat b390640376 internal/landlock: relocate from package container
Test / Create distribution (push) Successful in 35s
Test / ShareFS (push) Successful in 39s
Test / Sandbox (push) Successful in 45s
Test / Hakurei (push) Successful in 50s
Test / Sandbox (race detector) (push) Successful in 45s
Test / Hakurei (race detector) (push) Successful in 50s
Test / Flake checks (push) Successful in 1m21s
This is not possible to use directly, so remove it from the public API.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 23:56:45 +09:00
cat ad2c9f36cd container: unexport PR_SET_NO_NEW_PRIVS wrapper
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m52s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m16s
Test / Flake checks (push) Successful in 1m23s
This is subtle to use correctly. It also does not make sense as part of the container API.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 23:45:51 +09:00
cat 67db3fbb8d check: use encoding interfaces
Test / Create distribution (push) Successful in 1m13s
Test / Sandbox (push) Successful in 3m5s
Test / Hakurei (push) Successful in 4m15s
Test / ShareFS (push) Successful in 4m16s
Test / Sandbox (race detector) (push) Successful in 5m34s
Test / Hakurei (race detector) (push) Successful in 6m41s
Test / Flake checks (push) Successful in 1m25s
This turned out not to require specific treatment, so the shared interfaces are cleaner.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 22:11:53 +09:00
cat 560cb626a1 hst: remove enablement json adapter
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 5m11s
Test / Hakurei (race detector) (push) Successful in 6m20s
Test / Flake checks (push) Successful in 1m23s
The go116 behaviour of built-in new function makes this cleaner.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 20:47:30 +09:00
cat c33a6a5b7e hst: optionally reject insecure options
Test / Create distribution (push) Successful in 1m31s
Test / Sandbox (push) Successful in 3m35s
Test / Hakurei (push) Successful in 4m43s
Test / ShareFS (push) Successful in 4m49s
Test / Sandbox (race detector) (push) Successful in 5m59s
Test / Hakurei (race detector) (push) Successful in 7m13s
Test / Flake checks (push) Successful in 1m30s
This prevents inadvertent use of insecure compatibility features.

Closes #30.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 19:34:02 +09:00
cat 952082bd9b internal/rosa/python: 3.14.3 to 3.14.4
Test / Create distribution (push) Successful in 1m31s
Test / Sandbox (push) Successful in 5m0s
Test / ShareFS (push) Successful in 13m54s
Test / Hakurei (race detector) (push) Successful in 21m43s
Test / Sandbox (race detector) (push) Successful in 8m37s
Test / Hakurei (push) Successful in 6m30s
Test / Flake checks (push) Successful in 1m29s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 02:38:22 +09:00
cat 24a9b24823 internal/rosa/openssl: 3.6.1 to 3.6.2
Test / Sandbox (push) Successful in 5m7s
Test / Create distribution (push) Successful in 2m36s
Test / Sandbox (race detector) (push) Successful in 4m48s
Test / ShareFS (push) Successful in 7m20s
Test / Hakurei (race detector) (push) Successful in 9m30s
Test / Hakurei (push) Successful in 5m34s
Test / Flake checks (push) Successful in 1m32s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 02:38:02 +09:00
cat c2e61e7987 internal/rosa/libcap: 2.77 to 2.78
Test / Create distribution (push) Successful in 7m4s
Test / Sandbox (push) Successful in 11m33s
Test / ShareFS (push) Successful in 15m46s
Test / Sandbox (race detector) (push) Successful in 4m12s
Test / Hakurei (push) Successful in 1m5s
Test / Hakurei (race detector) (push) Successful in 1m4s
Test / Flake checks (push) Successful in 5m25s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 02:37:04 +09:00
cat 86787b3bc5 internal/rosa/tamago: 1.26.1 to 1.26.2
Test / Create distribution (push) Successful in 7m2s
Test / Sandbox (push) Successful in 12m41s
Test / Sandbox (race detector) (push) Successful in 15m28s
Test / ShareFS (push) Successful in 16m13s
Test / Hakurei (race detector) (push) Successful in 1m6s
Test / Hakurei (push) Successful in 5m57s
Test / Flake checks (push) Successful in 1m35s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 02:31:57 +09:00
cat cdfcfe6ce0 internal/rosa/go: 1.26.1 to 1.26.2
Test / Create distribution (push) Successful in 1m21s
Test / Sandbox (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 7m33s
Test / ShareFS (push) Successful in 7m32s
Test / Hakurei (race detector) (push) Successful in 11m11s
Test / Hakurei (push) Successful in 9m13s
Test / Flake checks (push) Successful in 5m39s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 02:18:27 +09:00
cat 68a2f0c240 internal/rosa/llvm: remove unused field
Test / Create distribution (push) Successful in 1m20s
Test / Sandbox (push) Successful in 4m18s
Test / Sandbox (race detector) (push) Successful in 6m53s
Test / ShareFS (push) Successful in 7m0s
Test / Hakurei (race detector) (push) Successful in 10m52s
Test / Hakurei (push) Successful in 6m57s
Test / Flake checks (push) Successful in 1m34s
This change also renames confusingly named flags field and corrects its doc comment.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 02:13:26 +09:00
cat 7319c7adf9 internal/rosa/llvm: use latest version on arm64
Test / Create distribution (push) Successful in 2m33s
Test / Sandbox (race detector) (push) Successful in 10m0s
Test / ShareFS (push) Successful in 11m16s
Test / Sandbox (push) Successful in 2m19s
Test / Hakurei (push) Successful in 6m19s
Test / Hakurei (race detector) (push) Successful in 7m9s
Test / Flake checks (push) Successful in 1m28s
This also removes arch-specific patches because they were not useful.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 01:07:25 +09:00
cat e9c890cbb2 internal/rosa/llvm: enable cross compilation
Test / Create distribution (push) Successful in 2m16s
Test / ShareFS (push) Successful in 14m13s
Test / Sandbox (push) Successful in 4m24s
Test / Sandbox (race detector) (push) Successful in 5m48s
Test / Hakurei (push) Successful in 10m2s
Test / Hakurei (race detector) (push) Successful in 7m42s
Test / Flake checks (push) Successful in 1m44s
This now passes the test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 00:59:14 +09:00
cat 6f924336fc internal/rosa/llvm: increase stack size
Test / Create distribution (push) Successful in 1m56s
Test / Sandbox (push) Successful in 9m57s
Test / Sandbox (race detector) (push) Successful in 12m34s
Test / ShareFS (push) Successful in 2m0s
Test / Hakurei (race detector) (push) Successful in 11m33s
Test / Hakurei (push) Successful in 6m3s
Test / Flake checks (push) Successful in 1m40s
Some aarch64 regression tests fail intermittently on the default size.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-10 00:56:51 +09:00
cat bd88f10524 internal/rosa/llvm: 22.1.2 to 22.1.3
Test / Create distribution (push) Successful in 1m7s
Test / ShareFS (push) Successful in 1m24s
Test / Sandbox (push) Successful in 4m46s
Test / Sandbox (race detector) (push) Successful in 6m30s
Test / Hakurei (race detector) (push) Successful in 14m5s
Test / Hakurei (push) Successful in 9m59s
Test / Flake checks (push) Successful in 1m59s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-09 17:36:23 +09:00
cat e34e3b917e internal/kobject: process uevent message
Test / Create distribution (push) Successful in 1m19s
Test / Sandbox (push) Successful in 3m21s
Test / Hakurei (push) Successful in 4m37s
Test / ShareFS (push) Successful in 4m36s
Test / Sandbox (race detector) (push) Successful in 5m54s
Test / Hakurei (race detector) (push) Successful in 7m7s
Test / Flake checks (push) Successful in 1m29s
This deals with environment variables generally present in every message.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-08 18:00:04 +09:00
cat b0ba165107 cmd/sharefs: group-accessible permission bits
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m17s
Test / Hakurei (push) Successful in 4m21s
Test / ShareFS (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 5m43s
Test / Hakurei (race detector) (push) Successful in 6m48s
Test / Flake checks (push) Successful in 1m28s
This works around the race in vfs via supplementary group.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-08 16:14:47 +09:00
cat 351d6c5a35 cmd/sharefs: reproduce vfs inode file attribute race
Test / ShareFS (push) Successful in 46s
Test / Sandbox (race detector) (push) Successful in 53s
Test / Sandbox (push) Successful in 55s
Test / Hakurei (push) Successful in 1m1s
Test / Hakurei (race detector) (push) Successful in 1m0s
Test / Create distribution (push) Successful in 1m11s
Test / Flake checks (push) Successful in 1m28s
This happens in the vfs permissions check only and stale data appears to never reach userspace.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-08 15:51:36 +09:00
cat f23f73701c cmd/mbf: optional host abstract
Test / Create distribution (push) Successful in 1m30s
Test / Sandbox (push) Successful in 3m25s
Test / Hakurei (push) Successful in 4m28s
Test / ShareFS (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 5m47s
Test / Hakurei (race detector) (push) Successful in 6m54s
Test / Flake checks (push) Successful in 1m23s
This works around kernels with Landlock LSM disabled. Does not affect cure outcome.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 18:15:49 +09:00
cat 876917229a internal/rosa/go: enable riscv64 bootstrap path
Test / Create distribution (push) Successful in 44s
Test / Sandbox (push) Successful in 1m7s
Test / ShareFS (push) Successful in 3m7s
Test / Hakurei (push) Successful in 3m21s
Test / Sandbox (race detector) (push) Successful in 5m34s
Test / Hakurei (race detector) (push) Successful in 6m43s
Test / Flake checks (push) Successful in 1m24s
This is quite expensive, but no other option, unfortunately.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 18:11:42 +09:00
cat 0558032c2d container: do not set static deadline
Test / Create distribution (push) Successful in 1m19s
Test / Sandbox (push) Successful in 3m25s
Test / Hakurei (push) Successful in 5m0s
Test / ShareFS (push) Successful in 4m53s
Test / Sandbox (race detector) (push) Successful in 6m1s
Test / Hakurei (race detector) (push) Successful in 3m37s
Test / Flake checks (push) Successful in 1m27s
This usually ends up in the buffer, or completes well before the deadline, however this can still timeout on a very slow system.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 17:00:20 +09:00
cat c61cdc505f internal/params: relocate from package container
Test / Create distribution (push) Successful in 1m19s
Test / Sandbox (push) Successful in 3m20s
Test / Hakurei (push) Successful in 4m26s
Test / ShareFS (push) Successful in 4m28s
Test / Sandbox (race detector) (push) Successful in 5m42s
Test / Hakurei (race detector) (push) Successful in 6m57s
Test / Flake checks (push) Successful in 1m27s
This does not make sense as part of the public API, so make it internal.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 16:37:44 +09:00
cat 062edb3487 container: remove setup pipe helper
Test / Create distribution (push) Successful in 1m20s
Test / Sandbox (push) Successful in 3m10s
Test / Hakurei (push) Successful in 4m24s
Test / ShareFS (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m46s
Test / Hakurei (race detector) (push) Successful in 6m53s
Test / Flake checks (push) Successful in 1m30s
The API forces use of finalizer to close the read end of the setup pipe, which is no longer considered acceptable. Exporting this as part of package container also imposes unnecessary maintenance burden.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 16:05:33 +09:00
cat e4355279a1 all: optionally forbid degrading in tests
Test / Create distribution (push) Successful in 1m28s
Test / Sandbox (push) Successful in 3m27s
Test / Hakurei (push) Successful in 4m41s
Test / ShareFS (push) Successful in 4m47s
Test / Sandbox (race detector) (push) Successful in 5m57s
Test / Hakurei (race detector) (push) Successful in 7m3s
Test / Flake checks (push) Successful in 1m29s
This enables transparently degradable tests to be forced on in environments known to support them.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 15:22:52 +09:00
cat 289fdebead container: transparently degrade landlock in tests
Test / Create distribution (push) Successful in 1m20s
Test / Sandbox (push) Successful in 3m15s
Test / Hakurei (push) Successful in 4m31s
Test / ShareFS (push) Successful in 4m37s
Test / Sandbox (race detector) (push) Successful in 5m53s
Test / Hakurei (race detector) (push) Successful in 6m56s
Test / Flake checks (push) Successful in 1m28s
Explicitly requiring landlock in tests will be supported in a future change.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 15:03:48 +09:00
cat 9c9e190db9 ldd: remove timeout
Test / Create distribution (push) Successful in 1m19s
Test / Sandbox (push) Successful in 3m16s
Test / Hakurei (push) Successful in 4m19s
Test / ShareFS (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 5m49s
Test / Hakurei (race detector) (push) Successful in 6m53s
Test / Flake checks (push) Successful in 1m28s
The program generally never blocks, and it is more flexible to leave it up to the caller to set a timeout.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 14:49:20 +09:00
cat d7d42c69a1 internal/pkg: transparently degrade landlock in tests
Test / Create distribution (push) Successful in 1m23s
Test / Sandbox (push) Successful in 3m26s
Test / Hakurei (push) Successful in 4m52s
Test / ShareFS (push) Successful in 4m57s
Test / Sandbox (race detector) (push) Successful in 5m56s
Test / Hakurei (race detector) (push) Successful in 7m8s
Test / Flake checks (push) Successful in 1m29s
This does not test package container, so should transparently cope with Landlock LSM being unavailable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 14:44:34 +09:00
cat c758e762bd container: skip landlock on hostnet
Test / Create distribution (push) Successful in 1m34s
Test / Sandbox (push) Successful in 3m46s
Test / ShareFS (push) Successful in 5m0s
Test / Sandbox (race detector) (push) Successful in 6m10s
Test / Hakurei (race detector) (push) Successful in 7m29s
Test / Hakurei (push) Successful in 3m21s
Test / Flake checks (push) Successful in 1m43s
This overlaps with net namespace, so can be skipped without degrading security.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 14:36:44 +09:00
cat 10f8b1c221 internal/pkg: optional landlock LSM
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m8s
Test / Hakurei (push) Successful in 4m17s
Test / ShareFS (push) Successful in 4m22s
Test / Sandbox (race detector) (push) Successful in 5m43s
Test / Hakurei (race detector) (push) Successful in 6m51s
Test / Flake checks (push) Successful in 1m29s
The alpine linux riscv64 kernel does not enable Landlock LSM, and kernel compilation is not yet feasible.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-07 12:44:07 +09:00
cat 6907700d67 cmd/dist: set hsu tar header mode bits
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m1s
Test / Hakurei (push) Successful in 4m5s
Test / ShareFS (push) Successful in 4m14s
Test / Sandbox (race detector) (push) Successful in 5m27s
Test / Hakurei (race detector) (push) Successful in 6m30s
Test / Flake checks (push) Successful in 1m20s
This has no effect, but is nice to have.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-06 23:37:38 +09:00
cat 0243f3ffbd internal/rosa/stage0: add riscv64 tarball
Test / Create distribution (push) Successful in 39s
Test / ShareFS (push) Successful in 3m44s
Test / Sandbox (race detector) (push) Successful in 49s
Test / Sandbox (push) Successful in 49s
Test / Hakurei (push) Successful in 54s
Test / Hakurei (race detector) (push) Successful in 1m48s
Test / Flake checks (push) Successful in 1m29s
This had not yet passed all test suites because emulator is prohibitively slow.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-06 13:57:43 +09:00
cat cd0beeaf8e internal/uevent: optionally pass UUID during coldboot
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 5m12s
Test / Hakurei (race detector) (push) Successful in 6m20s
Test / Flake checks (push) Successful in 1m20s
This enables rejection of non-coldboot synthetic events.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-06 12:42:47 +09:00
cat a69273ab2a cmd/dist: replace dist/release.sh
Test / Create distribution (push) Successful in 1m19s
Test / Sandbox (push) Successful in 3m12s
Test / Hakurei (push) Successful in 4m26s
Test / ShareFS (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 5m46s
Test / Hakurei (race detector) (push) Successful in 7m1s
Test / Flake checks (push) Successful in 1m29s
This is much more robust than a shell script.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 23:58:08 +09:00
cat 4cd0f57e48 dist: remove redundant cleanup
Test / Create distribution (push) Successful in 1m10s
Test / Sandbox (push) Successful in 2m57s
Test / Hakurei (push) Successful in 4m20s
Test / ShareFS (push) Successful in 4m17s
Test / Sandbox (race detector) (push) Successful in 5m47s
Test / Hakurei (race detector) (push) Successful in 6m47s
Test / Flake checks (push) Successful in 1m27s
This breaks on shells that do not evaluate pathnames.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 16:16:37 +09:00
cat 33a0e6c01b hst: conditionally skip root remount
Test / Create distribution (push) Successful in 43s
Test / Sandbox (push) Successful in 56s
Test / Hakurei (push) Successful in 4m25s
Test / ShareFS (push) Successful in 2m19s
Test / Sandbox (race detector) (push) Successful in 3m22s
Test / Hakurei (race detector) (push) Successful in 6m55s
Test / Flake checks (push) Successful in 1m31s
This enables the writable root overlay use case.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 14:04:21 +09:00
cat d58f5c7590 dist: destroy workdir on exist
Test / Create distribution (push) Successful in 1m21s
Test / Sandbox (push) Successful in 3m22s
Test / Hakurei (push) Successful in 4m27s
Test / ShareFS (push) Successful in 4m32s
Test / Sandbox (race detector) (push) Successful in 5m47s
Test / Hakurei (race detector) (push) Successful in 6m55s
Test / Flake checks (push) Successful in 1m26s
This no longer relies on the hermetic build system to clean up.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 13:32:16 +09:00
cat 1da992e342 dist: prefix from environment
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 5m11s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m27s
These are baked in, so make them configurable for the build.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 13:11:44 +09:00
cat 9641805ec2 container/init: ignore finished process
Test / Create distribution (push) Successful in 1m25s
Test / Sandbox (push) Successful in 4m19s
Test / Hakurei (push) Successful in 5m36s
Test / ShareFS (push) Successful in 5m38s
Test / Sandbox (race detector) (push) Successful in 6m25s
Test / Hakurei (race detector) (push) Successful in 7m38s
Test / Flake checks (push) Successful in 1m35s
This is not considered an error, if the process finishes while the signal is being delivered.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 00:38:33 +09:00
cat 0738f4889a internal/rosa/gnu: fetch mpc source via git
Test / Create distribution (push) Successful in 1m34s
Test / Sandbox (push) Successful in 3m38s
Test / Hakurei (push) Successful in 4m55s
Test / ShareFS (push) Successful in 4m57s
Test / Sandbox (race detector) (push) Successful in 6m13s
Test / Hakurei (race detector) (push) Successful in 7m19s
Test / Flake checks (push) Successful in 2m12s
Eliminates the xz dependency.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 00:35:15 +09:00
cat 7de3cfe221 internal/rosa/netfilter: fetch iptables source via git
Test / Create distribution (push) Successful in 1m43s
Test / Sandbox (push) Successful in 3m54s
Test / ShareFS (push) Successful in 5m41s
Test / Hakurei (push) Successful in 5m46s
Test / Sandbox (race detector) (push) Successful in 6m46s
Test / Hakurei (race detector) (push) Successful in 8m3s
Test / Flake checks (push) Successful in 1m32s
Eliminates the xz dependency.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 00:19:40 +09:00
cat 8b0648dd5d internal/rosa/netfilter: fetch libnftnl source via git
Test / Create distribution (push) Successful in 1m43s
Test / Sandbox (push) Successful in 3m49s
Test / Hakurei (push) Successful in 5m38s
Test / ShareFS (push) Successful in 5m36s
Test / Sandbox (race detector) (push) Successful in 6m44s
Test / Hakurei (race detector) (push) Successful in 8m0s
Test / Flake checks (push) Successful in 1m32s
Eliminates the xz dependency.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 00:14:45 +09:00
cat 4667fac76c internal/rosa/libbsd: fetch source via git
Test / Create distribution (push) Successful in 1m21s
Test / Sandbox (push) Successful in 3m16s
Test / Hakurei (push) Successful in 4m28s
Test / ShareFS (push) Successful in 4m38s
Test / Sandbox (race detector) (push) Successful in 5m46s
Test / Hakurei (race detector) (push) Successful in 7m7s
Test / Flake checks (push) Successful in 2m20s
Eliminates the xz dependency.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 00:05:56 +09:00
cat 52e5443b0e internal/rosa/libbsd: fetch libmd source via git
Test / Create distribution (push) Successful in 1m28s
Test / Sandbox (push) Successful in 3m26s
Test / Hakurei (push) Successful in 4m46s
Test / ShareFS (push) Successful in 4m50s
Test / Sandbox (race detector) (push) Successful in 6m4s
Test / Hakurei (race detector) (push) Successful in 7m10s
Test / Flake checks (push) Successful in 1m50s
Eliminates the xz dependency.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-05 00:02:56 +09:00
cat 130e470b60 internal/rosa/libxslt: fetch source via git
Test / Create distribution (push) Successful in 49s
Test / Sandbox (push) Successful in 4m14s
Test / Hakurei (push) Successful in 7m3s
Test / ShareFS (push) Successful in 7m16s
Test / Hakurei (race detector) (push) Successful in 9m54s
Test / Sandbox (race detector) (push) Successful in 2m35s
Test / Flake checks (push) Successful in 1m30s
Eliminates the xz dependency.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-04 23:05:10 +09:00
cat ba5ee8e3ee internal/rosa/libxml2: fetch source via git
Test / Create distribution (push) Successful in 1m41s
Test / Sandbox (push) Successful in 4m29s
Test / Hakurei (push) Successful in 0s
Test / ShareFS (push) Successful in 7m20s
Test / Sandbox (race detector) (push) Successful in 8m9s
Test / Hakurei (race detector) (push) Successful in 10m5s
Test / Flake checks (push) Successful in 1m33s
Eliminates the xz dependency. This also switches to meson to avoid pulling in autotools.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-04 22:58:29 +09:00
cat d1cef30877 internal/rosa/gtk: fetch glib source via git
Test / Create distribution (push) Successful in 1m53s
Test / Sandbox (push) Successful in 4m28s
Test / ShareFS (push) Successful in 5m30s
Test / Hakurei (push) Successful in 7m13s
Test / Sandbox (race detector) (push) Successful in 6m48s
Test / Hakurei (race detector) (push) Successful in 4m17s
Test / Flake checks (push) Successful in 1m36s
This eliminates xz dependency.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-04 22:47:10 +09:00
cat 0188a3f0c7 internal/rosa/gnu: gnutls disable arm64 hardware acceleration
Test / Create distribution (push) Successful in 1m36s
Test / ShareFS (push) Successful in 7m39s
Test / Sandbox (race detector) (push) Successful in 8m11s
Test / Hakurei (race detector) (push) Successful in 9m55s
Test / Sandbox (push) Successful in 1m43s
Test / Hakurei (push) Successful in 2m57s
Test / Flake checks (push) Successful in 1m29s
Hardware on arm64 is quite messy, this miscompiles.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-04 23:37:18 +09:00
cat 04fe3b24ce internal/rosa/gnu: gnutls configure trust store
Test / Create distribution (push) Successful in 1m21s
Test / Sandbox (push) Successful in 3m18s
Test / Hakurei (push) Successful in 4m31s
Test / ShareFS (push) Successful in 4m45s
Test / Sandbox (race detector) (push) Successful in 5m49s
Test / Hakurei (race detector) (push) Successful in 6m55s
Test / Flake checks (push) Successful in 1m53s
The test suite is somehow happy on amd64 but fails on arm64.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-04 22:33:51 +09:00
cat 93ad551054 internal/rosa/git: shallow clone
Test / Create distribution (push) Successful in 1m24s
Test / Sandbox (push) Successful in 3m19s
Test / Hakurei (push) Successful in 4m32s
Test / ShareFS (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m58s
Test / Hakurei (race detector) (push) Successful in 7m2s
Test / Flake checks (push) Successful in 1m29s
The .git directory is destroyed anyway, so no point fetching more than the bare minimum.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-04 20:14:40 +09:00
cat 3d54d1f176 internal/rosa: drop caches
Test / Create distribution (push) Successful in 1m21s
Test / Sandbox (push) Successful in 3m14s
Test / Hakurei (push) Successful in 4m27s
Test / ShareFS (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 5m55s
Test / Hakurei (race detector) (push) Successful in 6m55s
Test / Flake checks (push) Successful in 1m30s
This enables accurate benchmarking of the toolchain abstraction.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 20:58:23 +09:00
cat 9feac7738f internal/pkg: optionally suppress via assumed checksum
Test / Create distribution (push) Successful in 1m20s
Test / Sandbox (push) Successful in 3m24s
Test / Hakurei (push) Successful in 4m31s
Test / ShareFS (push) Successful in 4m41s
Test / Sandbox (race detector) (push) Successful in 5m53s
Test / Hakurei (race detector) (push) Successful in 6m58s
Test / Flake checks (push) Successful in 1m28s
This is quite error-prone and causes cache inconsistency similar to the store inconsistency seen on nix when a similar condition happens. Keep this behind a flag in case it is ever beneficial.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 20:33:59 +09:00
cat 591a60bac9 internal/pkg: per-cache SCHED_IDLE
Test / Create distribution (push) Successful in 1m19s
Test / Sandbox (push) Successful in 3m11s
Test / Hakurei (push) Successful in 4m22s
Test / ShareFS (push) Successful in 4m27s
Test / Sandbox (race detector) (push) Successful in 5m49s
Test / Hakurei (race detector) (push) Successful in 6m48s
Test / Flake checks (push) Successful in 1m27s
This is cleaner than setting it globally, and is impossible to race.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 16:49:34 +09:00
cat 5093a06026 internal/pkg: cache flags
Test / Create distribution (push) Successful in 1m19s
Test / Sandbox (push) Successful in 3m11s
Test / Hakurei (push) Successful in 4m25s
Test / ShareFS (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 5m48s
Test / Hakurei (race detector) (push) Successful in 6m56s
Test / Flake checks (push) Successful in 1m29s
This is cleaner for extending the API.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 16:16:55 +09:00
cat 50c1d7f880 internal/rosa/kernel: 6.12.78 to 6.12.80
Test / ShareFS (push) Successful in 47s
Test / Sandbox (push) Successful in 54s
Test / Hakurei (push) Successful in 59s
Test / Hakurei (race detector) (push) Successful in 57s
Test / Sandbox (race detector) (push) Successful in 52s
Test / Create distribution (push) Successful in 1m10s
Test / Flake checks (push) Successful in 1m27s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 16:14:16 +09:00
cat 9e63633fbc container: remove test timeouts
Test / Create distribution (push) Successful in 3m2s
Test / Sandbox (push) Successful in 5m25s
Test / Hakurei (push) Successful in 7m45s
Test / ShareFS (push) Successful in 7m54s
Test / Sandbox (race detector) (push) Successful in 8m47s
Test / Hakurei (race detector) (push) Successful in 10m38s
Test / Flake checks (push) Successful in 1m49s
These timeouts are no longer useful, and causes spurious test failures under load.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 10:51:37 +09:00
cat 61f981a34a internal/rosa/perl: 5.42.1 to 5.42.2
Test / Create distribution (push) Successful in 1m25s
Test / Sandbox (push) Successful in 2m36s
Test / Hakurei (push) Successful in 4m11s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 6m1s
Test / Hakurei (race detector) (push) Successful in 4m24s
Test / Flake checks (push) Successful in 4m19s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 10:45:25 +09:00
cat d717c41bbe internal/rosa/cmake: 4.3.0 to 4.3.1
Test / Sandbox (push) Successful in 2m37s
Test / Create distribution (push) Successful in 1m14s
Test / Hakurei (push) Successful in 3m50s
Test / ShareFS (push) Successful in 3m19s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 4m14s
Test / Flake checks (push) Successful in 3m46s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 10:45:07 +09:00
cat b896eec9b7 internal/rosa/gnu: parallel 20260222 to 20260322
Test / Create distribution (push) Successful in 39s
Test / Sandbox (push) Successful in 2m6s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 6m32s
Test / Hakurei (race detector) (push) Successful in 9m45s
Test / Hakurei (push) Successful in 2m52s
Test / Flake checks (push) Successful in 2m32s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 10:44:48 +09:00
cat 8ab99e5e40 internal/rosa/util-linux: 2.41.3 to 2.42
Test / Sandbox (push) Successful in 3m25s
Test / Create distribution (push) Successful in 1m14s
Test / ShareFS (push) Successful in 4m35s
Test / Sandbox (race detector) (push) Successful in 6m50s
Test / Hakurei (race detector) (push) Successful in 4m12s
Test / Hakurei (push) Successful in 3m47s
Test / Flake checks (push) Successful in 3m13s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 10:44:25 +09:00
cat 2b6160ef7d internal/rosa/wayland: wayland-protocols 1.47 to 1.48
Test / Create distribution (push) Successful in 1m29s
Test / Sandbox (push) Successful in 5m36s
Test / Hakurei (push) Successful in 7m50s
Test / ShareFS (push) Successful in 5m41s
Test / Sandbox (race detector) (push) Successful in 8m49s
Test / Hakurei (race detector) (push) Successful in 3m41s
Test / Flake checks (push) Successful in 3m51s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 10:44:04 +09:00
cat 4dcac7f133 internal/rosa/xz: 5.8.2 to 5.8.3
Test / Sandbox (push) Successful in 5m33s
Test / ShareFS (push) Successful in 7m47s
Test / Sandbox (race detector) (push) Successful in 8m53s
Test / Hakurei (race detector) (push) Successful in 10m40s
Test / Hakurei (push) Successful in 2m45s
Test / Create distribution (push) Successful in 1m51s
Test / Flake checks (push) Successful in 3m51s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 10:43:37 +09:00
cat 966fd4df9e internal/rosa: connman artifact
Test / Create distribution (push) Successful in 1m20s
Test / Sandbox (push) Successful in 3m12s
Test / Hakurei (push) Successful in 4m28s
Test / ShareFS (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 5m42s
Test / Hakurei (race detector) (push) Successful in 6m51s
Test / Flake checks (push) Successful in 1m26s
Will be gradually replaced with a native implementation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 01:17:10 +09:00
cat a2cf59b989 internal/rosa/ncurses: also build dynamic library
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m3s
Test / Hakurei (push) Successful in 4m22s
Test / ShareFS (push) Successful in 4m26s
Test / Sandbox (race detector) (push) Successful in 5m40s
Test / Hakurei (race detector) (push) Successful in 6m45s
Test / Flake checks (push) Successful in 1m47s
GNU readline breaks without this.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 01:13:04 +09:00
cat e87f59c4e4 internal/rosa/gnu: readline artifact
Test / Sandbox (push) Successful in 3m48s
Test / Hakurei (push) Successful in 5m30s
Test / ShareFS (push) Successful in 5m24s
Test / Sandbox (race detector) (push) Successful in 6m30s
Test / Hakurei (race detector) (push) Successful in 7m40s
Test / Flake checks (push) Successful in 1m30s
Test / Create distribution (push) Successful in 35s
Nice to have library for command line programs.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 00:42:59 +09:00
cat 3b221c3e77 internal/rosa/gnu: gnutls artifact
Test / Create distribution (push) Successful in 1m17s
Test / Sandbox (push) Successful in 4m11s
Test / Hakurei (push) Successful in 5m37s
Test / ShareFS (push) Successful in 5m2s
Test / Sandbox (race detector) (push) Successful in 6m27s
Test / Hakurei (race detector) (push) Successful in 7m40s
Test / Flake checks (push) Successful in 1m28s
Incredibly ugly and expensive package, but unfortunately required by some packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 00:36:28 +09:00
cat ff3b385b12 internal/rosa: libunistring artifact
Test / Create distribution (push) Successful in 1m29s
Test / Sandbox (push) Successful in 3m44s
Test / Hakurei (push) Successful in 5m22s
Test / ShareFS (push) Successful in 5m26s
Test / Sandbox (race detector) (push) Successful in 6m36s
Test / Hakurei (race detector) (push) Successful in 7m44s
Test / Flake checks (push) Successful in 1m28s
Required by GnuTLS.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-03 00:00:13 +09:00
cat c6920e6ab7 cmd/mbf: pick up $TERM
Test / Create distribution (push) Successful in 45s
Test / Sandbox (push) Successful in 3m35s
Test / Hakurei (push) Successful in 5m15s
Test / ShareFS (push) Successful in 5m25s
Test / Sandbox (race detector) (push) Successful in 6m34s
Test / Hakurei (race detector) (push) Successful in 7m47s
Test / Flake checks (push) Successful in 1m27s
This improves behaviour of some programs.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-02 23:59:15 +09:00
cat 59b25d45fe internal/pkg: pick up $TERM if attaching stdin
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m11s
Test / Hakurei (push) Successful in 4m29s
Test / ShareFS (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 5m42s
Test / Hakurei (race detector) (push) Successful in 6m57s
Test / Flake checks (push) Successful in 1m26s
This improves behaviour of some programs.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-02 23:50:07 +09:00
cat 9b99650eb1 internal/rosa: libev artifact
Test / Create distribution (push) Successful in 1m17s
Test / Sandbox (push) Successful in 3m14s
Test / Hakurei (push) Successful in 4m20s
Test / ShareFS (push) Successful in 4m28s
Test / Sandbox (race detector) (push) Successful in 5m43s
Test / Hakurei (race detector) (push) Successful in 3m43s
Test / Flake checks (push) Successful in 1m27s
Required by gnutls.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-02 23:35:01 +09:00
cat 15bff9e1a6 internal/rosa/git: determine reporting name from url
Test / Create distribution (push) Successful in 1m17s
Test / Sandbox (push) Successful in 3m16s
Test / Hakurei (push) Successful in 4m17s
Test / ShareFS (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 5m47s
Test / Hakurei (race detector) (push) Successful in 6m48s
Test / Flake checks (push) Successful in 1m26s
This is generally correct, and is a lot cleaner to call.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-02 22:42:16 +09:00
cat b948525c07 internal/rosa: nettle3 artifact
Test / Create distribution (push) Successful in 1m17s
Test / Sandbox (push) Successful in 3m9s
Test / Hakurei (push) Successful in 4m23s
Test / ShareFS (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 5m48s
Test / Hakurei (race detector) (push) Successful in 6m45s
Test / Flake checks (push) Successful in 1m27s
Removed after all packages upgrade for nettle 4.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-02 22:12:39 +09:00
cat 9acbd16e9a internal/rosa/p11: explicitly enable libffi
Test / Create distribution (push) Successful in 1m19s
Test / Sandbox (push) Successful in 3m12s
Test / Hakurei (push) Successful in 4m19s
Test / ShareFS (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 5m43s
Test / Hakurei (race detector) (push) Successful in 6m49s
Test / Flake checks (push) Successful in 1m47s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-02 22:05:07 +09:00
cat 64e5a1068b internal/rosa: libtasn1 artifact
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m44s
Test / Sandbox (race detector) (push) Successful in 5m10s
Test / Hakurei (race detector) (push) Successful in 6m13s
Test / Flake checks (push) Successful in 1m23s
Optional dependency of p11-kit.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-01 17:20:12 +09:00
cat b6cbd49d8c internal/rosa: p11-kit artifact
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m42s
Test / ShareFS (push) Successful in 3m45s
Test / Hakurei (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 5m10s
Test / Hakurei (race detector) (push) Successful in 6m16s
Test / Flake checks (push) Successful in 1m22s
Another package distributed in xz only. This is fetched from the git remote directly to avoid XZ Utils.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-01 16:34:30 +09:00
cat 6913b9224a internal/rosa/git: recursively clone submodules
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m45s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m21s
There is generally no reason to disable this, so it was not made optional.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-01 16:21:46 +09:00
cat 9584958ecc internal/rosa/pkg-config: generate build system
Test / Create distribution (push) Successful in 1m3s
Test / ShareFS (push) Successful in 8m9s
Test / Sandbox (race detector) (push) Successful in 8m15s
Test / Hakurei (race detector) (push) Successful in 3m42s
Test / Sandbox (push) Successful in 1m36s
Test / Hakurei (push) Successful in 2m48s
Test / Flake checks (push) Successful in 1m24s
This unfortunately pulls automake, libtool and their dependencies into stage2.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-04-01 15:31:24 +09:00
cat 389844b1ea internal/rosa/gnu: mpc 1.3.1 to 1.4.0
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 3m54s
Test / Sandbox (race detector) (push) Successful in 5m13s
Test / Hakurei (race detector) (push) Successful in 6m20s
Test / Flake checks (push) Successful in 1m23s
This package now unfortunately switched to xz as well.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-31 23:56:20 +09:00
cat 5b7ab35633 internal/rosa: iptables artifact
Test / Create distribution (push) Successful in 1m11s
Test / Sandbox (push) Successful in 3m19s
Test / Hakurei (push) Successful in 4m27s
Test / ShareFS (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m33s
Test / Hakurei (race detector) (push) Successful in 6m42s
Test / Flake checks (push) Successful in 1m21s
This also pulls in netlink libraries from netfilter project.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-31 23:47:16 +09:00
cat 52b1a5a725 internal/rosa: use type P in helper interface
Test / Create distribution (push) Successful in 42s
Test / Sandbox (push) Successful in 1m50s
Test / ShareFS (push) Successful in 3m12s
Test / Sandbox (race detector) (push) Successful in 5m32s
Test / Hakurei (race detector) (push) Successful in 6m36s
Test / Hakurei (push) Successful in 2m40s
Test / Flake checks (push) Successful in 1m26s
This is easier to type and serialises correctly.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-31 23:45:01 +09:00
cat 6b78df8714 internal/rosa: libmd and libbsd artifacts
Test / Create distribution (push) Successful in 1m12s
Test / Sandbox (push) Successful in 3m5s
Test / Hakurei (push) Successful in 4m13s
Test / ShareFS (push) Successful in 4m18s
Test / Sandbox (race detector) (push) Successful in 5m26s
Test / Hakurei (race detector) (push) Successful in 6m33s
Test / Flake checks (push) Successful in 1m27s
These provide headers that are provided by glibc but not musl.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-31 22:57:08 +09:00
cat dadf170a46 internal/rosa: dbus artifact
Test / Create distribution (push) Successful in 1m8s
Test / Sandbox (push) Successful in 2m54s
Test / ShareFS (push) Successful in 3m49s
Test / Hakurei (push) Successful in 4m1s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m24s
Test / Flake checks (push) Successful in 2m55s
Unfortunate ugly indirect dependency we cannot yet get rid of.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-31 21:47:05 +09:00
cat 9594832302 internal/rosa/meson: disallow download
Test / Create distribution (push) Successful in 1m7s
Test / Sandbox (push) Successful in 2m49s
Test / ShareFS (push) Successful in 4m4s
Test / Sandbox (race detector) (push) Successful in 5m23s
Test / Hakurei (race detector) (push) Successful in 6m33s
Test / Hakurei (push) Successful in 2m47s
Test / Flake checks (push) Successful in 1m27s
This will fail and waste time on KindExec, and cause nondeterminism in KindExecNet.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-31 21:38:09 +09:00
cat 91a2d4d6e1 internal/uevent: integrate error handling in event loop
Test / Create distribution (push) Successful in 1m17s
Test / Sandbox (push) Successful in 3m13s
Test / Hakurei (push) Successful in 4m18s
Test / ShareFS (push) Successful in 4m24s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 6m42s
Test / Flake checks (push) Successful in 1m25s
There are many subtleties when recovering from errors in the event loop, and coldboot requires internals to drain the receive buffer as synthetic uevents are being arranged.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-31 00:10:14 +09:00
cat a854719b9f internal/netlink: optional recvmsg without netpoll
Test / Create distribution (push) Successful in 1m14s
Test / Sandbox (push) Successful in 3m7s
Test / Hakurei (push) Successful in 4m10s
Test / ShareFS (push) Successful in 4m22s
Test / Sandbox (race detector) (push) Successful in 5m34s
Test / Hakurei (race detector) (push) Successful in 6m39s
Test / Flake checks (push) Successful in 1m25s
For draining the socket receive buffer.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-30 23:18:43 +09:00
cat f03c0fb249 internal/uevent: synthetic events for coldboot
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m5s
Test / Hakurei (push) Successful in 4m12s
Test / ShareFS (push) Successful in 4m16s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 6m39s
Test / Flake checks (push) Successful in 1m24s
This causes the kernel to regenerate events that happened before earlyinit started.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-30 23:01:08 +09:00
cat a6600be34a all: use filepath
Test / Create distribution (push) Successful in 1m17s
Test / Sandbox (push) Successful in 3m5s
Test / Hakurei (push) Successful in 4m12s
Test / ShareFS (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 5m39s
Test / Hakurei (race detector) (push) Successful in 6m44s
Test / Flake checks (push) Successful in 1m24s
This makes package check portable, and removes nonportable behaviour from package pkg, pipewire, and system. All other packages remain nonportable due to their nature. No latency increase was observed due to this change on amd64 and arm64 linux.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-30 18:24:53 +09:00
cat b5592633f5 internal/uevent: separate recvmsg helper
Test / Create distribution (push) Successful in 1m13s
Test / Sandbox (push) Successful in 3m3s
Test / Hakurei (push) Successful in 4m13s
Test / ShareFS (push) Successful in 4m16s
Test / Sandbox (race detector) (push) Successful in 5m37s
Test / Hakurei (race detector) (push) Successful in 6m46s
Test / Flake checks (push) Successful in 1m23s
This enables messages to be received separately.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-30 02:53:26 +09:00
cat 584e302168 internal/netlink: set receive buffer size
Test / Create distribution (push) Successful in 1m14s
Test / Sandbox (push) Successful in 3m2s
Test / Hakurei (push) Successful in 4m15s
Test / ShareFS (push) Successful in 4m15s
Test / Sandbox (race detector) (push) Successful in 5m32s
Test / Hakurei (race detector) (push) Successful in 6m44s
Test / Flake checks (push) Successful in 1m24s
This is done by both systemd sd-device and AOSP ueventd to improve robustness. Rosa OS will still handle ENOBUFS via coldboot but a big buffer should mitigate this as well.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-30 02:14:47 +09:00
cat 141958656f internal/uevent: handle state divergence
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m9s
Test / Hakurei (push) Successful in 4m13s
Test / ShareFS (push) Successful in 4m15s
Test / Sandbox (race detector) (push) Successful in 5m37s
Test / Hakurei (race detector) (push) Successful in 6m47s
Test / Flake checks (push) Successful in 1m25s
This requires the caller to arrange for a coldboot to happen, some time after this error is encountered, and to resume event processing.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-30 01:50:09 +09:00
cat 648079f42c internal/netlink: switch to recvmsg/sendmsg
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m6s
Test / Hakurei (push) Successful in 4m13s
Test / ShareFS (push) Successful in 4m20s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 6m40s
Test / Flake checks (push) Successful in 1m25s
These are more flexible than recvfrom/sendto.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-29 23:36:00 +09:00
cat 19c76e0831 cmd: document Rosa OS programs
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m6s
Test / Hakurei (push) Successful in 4m12s
Test / ShareFS (push) Successful in 4m17s
Test / Sandbox (race detector) (push) Successful in 5m43s
Test / Hakurei (race detector) (push) Successful in 6m39s
Test / Flake checks (push) Successful in 1m24s
The earlyinit and mbf program are not covered by the compatibility promise, so specify that here.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-28 22:43:25 +09:00
cat 71fcc972ba cmd/hsu: alternative hsurc path for Rosa OS
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m15s
Test / Hakurei (push) Successful in 4m25s
Test / ShareFS (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 5m40s
Test / Hakurei (race detector) (push) Successful in 6m52s
Test / Flake checks (push) Successful in 1m26s
Rosa OS does not have /etc.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-28 18:22:55 +09:00
cat 62002efd08 cmd/hsu: document hsurc format and internals
Test / Create distribution (push) Successful in 1m11s
Test / Sandbox (push) Successful in 2m3s
Test / Sandbox (race detector) (push) Successful in 3m7s
Test / ShareFS (push) Successful in 3m18s
Test / Hakurei (race detector) (push) Successful in 4m14s
Test / Hakurei (push) Successful in 3m9s
Test / Flake checks (push) Successful in 1m36s
This was previously only documented via an unexported function.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-28 18:17:31 +09:00
cat e33294db9c cmd/hakurei: document stable behaviour
Test / Create distribution (push) Successful in 1m14s
Test / Sandbox (push) Successful in 2m59s
Test / Hakurei (push) Successful in 4m10s
Test / ShareFS (push) Successful in 4m12s
Test / Sandbox (race detector) (push) Successful in 5m33s
Test / Hakurei (race detector) (push) Successful in 6m40s
Test / Flake checks (push) Successful in 1m25s
These are undocumented anywhere else and is required by tools invoking hakurei.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-28 17:31:46 +09:00
cat b1ea3b4acf cmd/hakurei: rename app to run
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m7s
Test / Hakurei (push) Successful in 4m21s
Test / ShareFS (push) Successful in 4m20s
Test / Sandbox (race detector) (push) Successful in 5m39s
Test / Hakurei (race detector) (push) Successful in 6m36s
Test / Flake checks (push) Successful in 1m24s
The run command was a legacy holdover from very early days and is only useful for testing and demonstration these days. This change also renames it to exec.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-28 16:48:26 +09:00
cat 2c254c70b8 cmd/hakurei: remove linkname directive
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m4s
Test / Hakurei (push) Successful in 4m25s
Test / ShareFS (push) Successful in 4m21s
Test / Sandbox (race detector) (push) Successful in 5m43s
Test / Hakurei (race detector) (push) Successful in 6m41s
Test / Flake checks (push) Successful in 1m25s
This used to be a function that did much more, and was later relocated to another package and exported.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-28 16:20:02 +09:00
cat ea014d6af2 internal/uevent: consume kernel-originated events
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m5s
Test / Hakurei (push) Successful in 4m18s
Test / ShareFS (push) Successful in 4m20s
Test / Sandbox (race detector) (push) Successful in 5m42s
Test / Hakurei (race detector) (push) Successful in 6m46s
Test / Flake checks (push) Successful in 1m25s
These are not possible to cover outside integration vm. Extreme care is required when dealing with this method, so keep it simple.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-28 15:39:16 +09:00
cat 1b48484c16 internal/uevent: exclusive socket access
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m2s
Test / Hakurei (push) Successful in 4m15s
Test / ShareFS (push) Successful in 4m16s
Test / Sandbox (race detector) (push) Successful in 5m34s
Test / Hakurei (race detector) (push) Successful in 6m39s
Test / Flake checks (push) Successful in 1m45s
This is a much simplified mutex, since blocking is not required.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-28 01:01:06 +09:00
cat 713bff3eb0 internal/uevent: decode uevent messages
Test / Create distribution (push) Successful in 1m14s
Test / Sandbox (push) Successful in 3m3s
Test / Hakurei (push) Successful in 4m13s
Test / ShareFS (push) Successful in 4m17s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 6m38s
Test / Flake checks (push) Successful in 1m24s
The wire format and behaviour is entirely undocumented. This is implemented by reading lib/kobject_uevent.c, with testdata collected from the internal/rosa kernel.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-28 00:49:34 +09:00
cat 30f459e690 internal/uevent: nontrivial errors
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m11s
Test / Hakurei (push) Successful in 4m20s
Test / ShareFS (push) Successful in 4m18s
Test / Sandbox (race detector) (push) Successful in 5m32s
Test / Hakurei (race detector) (push) Successful in 6m39s
Test / Flake checks (push) Successful in 1m25s
These errors are best represented as JSON.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-28 00:07:56 +09:00
cat 8766fddcb3 internal/uevent: recoverable errors
Test / Create distribution (push) Successful in 1m13s
Test / Sandbox (push) Successful in 3m1s
Test / Hakurei (push) Successful in 4m9s
Test / ShareFS (push) Successful in 4m17s
Test / Sandbox (race detector) (push) Successful in 5m36s
Test / Hakurei (race detector) (push) Successful in 6m42s
Test / Flake checks (push) Successful in 1m26s
This runs in the Rosa OS init, so recover as much as possible, as otherwise it is likely to require a full system reboot to resume event processing. The caller is responsible for reporting the error.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-27 22:58:16 +09:00
cat 2745602be3 internal/uevent: wrap netlink socket
Test / Create distribution (push) Successful in 1m18s
Test / Sandbox (push) Successful in 3m13s
Test / Hakurei (push) Successful in 4m14s
Test / ShareFS (push) Successful in 4m21s
Test / Sandbox (race detector) (push) Successful in 5m37s
Test / Hakurei (race detector) (push) Successful in 6m41s
Test / Flake checks (push) Successful in 1m26s
Unfortunately these messages do not have the same format as rtnetlink.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-27 22:46:18 +09:00
cat ee22847dde internal/uevent: kobject_action lookup
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m12s
Test / Hakurei (push) Successful in 4m13s
Test / ShareFS (push) Successful in 4m22s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 6m46s
Test / Flake checks (push) Successful in 1m47s
This is encoded as part of kobject uevent message headers.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-27 22:39:43 +09:00
cat c61188649b internal/netlink: export generic connection
Test / Create distribution (push) Successful in 1m14s
Test / Sandbox (push) Successful in 3m2s
Test / Hakurei (push) Successful in 4m18s
Test / ShareFS (push) Successful in 4m17s
Test / Sandbox (race detector) (push) Successful in 5m50s
Test / Hakurei (race detector) (push) Successful in 6m38s
Test / Flake checks (push) Successful in 1m25s
This enables abstractions around some families to be implemented in a separate package.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-27 19:08:48 +09:00
cat 6a87a96838 internal/rosa/kernel: 6.12.77 to 6.12.78
Test / Create distribution (push) Successful in 3m10s
Test / Sandbox (push) Successful in 6m21s
Test / ShareFS (push) Successful in 8m35s
Test / Sandbox (race detector) (push) Successful in 9m16s
Test / Hakurei (push) Successful in 3m23s
Test / Hakurei (race detector) (push) Successful in 4m16s
Test / Flake checks (push) Successful in 1m30s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-27 10:40:27 +09:00
cat 2548a681e9 internal/rosa: key-value type
Test / Create distribution (push) Successful in 1m34s
Test / Sandbox (push) Successful in 4m52s
Test / Hakurei (push) Successful in 5m53s
Test / ShareFS (push) Successful in 5m56s
Test / Sandbox (race detector) (push) Successful in 6m52s
Test / Hakurei (race detector) (push) Successful in 8m8s
Test / Flake checks (push) Successful in 1m29s
This type is used very frequently. The new type is much easier to type and can receive helper methods eventually if needed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 16:23:15 +09:00
cat d514d0679f internal/rosa: set PYTHONUNBUFFERED=1
Test / Create distribution (push) Successful in 2m53s
Test / Sandbox (push) Successful in 5m48s
Test / Hakurei (push) Successful in 7m43s
Test / ShareFS (push) Successful in 7m41s
Test / Sandbox (race detector) (push) Successful in 8m14s
Test / Hakurei (race detector) (push) Successful in 9m16s
Test / Flake checks (push) Successful in 1m30s
Some python tools try to be clever and buffers output. This makes the build process appear to hang and is quite frustrating. Instead of trying to address this on a case-by-case basis, this is turned off globally for the interpreter.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 15:29:29 +09:00
cat 4407892632 cmd/mbf: optionally enter cure container
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 5m10s
Test / Hakurei (race detector) (push) Successful in 6m16s
Test / Flake checks (push) Successful in 1m22s
This is very useful for troubleshooting failing tests and such. The ephemeral state is cleaned up by internal/pkg.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 15:10:11 +09:00
cat e661260607 internal/pkg: enter exec container
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m38s
Test / ShareFS (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 6m20s
Test / Flake checks (push) Successful in 1m22s
This enables much easier troubleshooting of failing cures.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 15:05:04 +09:00
cat 044490e0a5 cmd/mbf: retain session by default
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m43s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 6m14s
Test / Flake checks (push) Successful in 1m30s
This almost never make sense to be turned off.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 14:59:17 +09:00
cat af038c89ff internal/pkg: collection helper-artifact
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m45s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 3m27s
Test / Flake checks (push) Successful in 1m20s
This was moved from internal/rosa because it is considered generally useful.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 14:11:10 +09:00
cat d2f30173cd internal/pkg: isolate container params
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m44s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 5m12s
Test / Hakurei (race detector) (push) Successful in 6m11s
Test / Flake checks (push) Successful in 1m20s
This enables exporting container params for interactive troubleshooting within the cure container.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 14:02:58 +09:00
cat 5319ea994c internal/rosa/libseccomp: fix upstream out-of-bounds read
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m40s
Test / Sandbox (race detector) (push) Successful in 5m5s
Test / Hakurei (race detector) (push) Successful in 6m9s
Test / Flake checks (push) Successful in 1m15s
This was revealed by optimisation changes in the latest toolchain.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 10:43:11 +09:00
cat bbe178be3e internal/rosa/llvm: 22.1.1 to 22.1.2
Test / Create distribution (push) Successful in 1m14s
Test / Sandbox (push) Successful in 3m7s
Test / Hakurei (push) Successful in 4m30s
Test / ShareFS (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m47s
Test / Hakurei (race detector) (push) Successful in 6m58s
Test / Flake checks (push) Successful in 1m24s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 09:56:34 +09:00
cat ca28e9936b internal/rosa/musl: 1.2.5 to 1.2.6
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m30s
Test / Hakurei (push) Successful in 4m25s
Test / ShareFS (push) Successful in 2m53s
Test / Sandbox (race detector) (push) Successful in 5m22s
Test / Hakurei (race detector) (push) Successful in 6m41s
Test / Flake checks (push) Successful in 1m22s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 09:56:06 +09:00
cat f61c6ade56 internal/rosa/nss: 3.121 to 3.122
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 3m5s
Test / ShareFS (push) Successful in 3m41s
Test / Sandbox (race detector) (push) Successful in 5m49s
Test / Hakurei (race detector) (push) Successful in 6m55s
Test / Hakurei (push) Successful in 2m27s
Test / Flake checks (push) Successful in 1m18s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 09:55:27 +09:00
cat fce3d63823 internal/rosa/gnu: autoconf 2.72 to 2.73
Test / Create distribution (push) Successful in 1m10s
Test / Sandbox (push) Successful in 3m8s
Test / Hakurei (push) Successful in 4m25s
Test / ShareFS (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 5m53s
Test / Hakurei (race detector) (push) Successful in 6m48s
Test / Flake checks (push) Successful in 1m24s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-26 09:54:44 +09:00
cat 722c3cc54f internal/netlink: optional check header as reply
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m41s
Test / Sandbox (race detector) (push) Successful in 5m10s
Test / Hakurei (race detector) (push) Successful in 6m18s
Test / Flake checks (push) Successful in 1m19s
Not every received message is a reply.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-25 19:33:01 +09:00
cat 372d509e5c internal/netlink: expose multicast groups
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m14s
Test / Hakurei (push) Successful in 4m19s
Test / ShareFS (push) Successful in 4m19s
Test / Sandbox (race detector) (push) Successful in 5m36s
Test / Hakurei (race detector) (push) Successful in 6m43s
Test / Flake checks (push) Successful in 1m25s
This also gets rid of the cached pid value for port since that prevents multiple sockets from being open at once.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-25 17:55:35 +09:00
cat d62516ed1e internal/netlink: enlarge recvfrom buffer
Test / Create distribution (push) Successful in 1m13s
Test / Sandbox (push) Successful in 3m3s
Test / Hakurei (push) Successful in 4m19s
Test / ShareFS (push) Successful in 4m16s
Test / Sandbox (race detector) (push) Successful in 5m32s
Test / Hakurei (race detector) (push) Successful in 6m44s
Test / Flake checks (push) Successful in 1m22s
This also uses an array type for the buffer since its size now uses the hardcoded value found in the kernel.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-25 17:18:56 +09:00
cat d2b635eb55 cmd/mbf: correctly describe --with-toolchain
Test / Create distribution (push) Successful in 1m12s
Test / Sandbox (push) Successful in 2m58s
Test / ShareFS (push) Successful in 4m11s
Test / Hakurei (push) Successful in 4m23s
Test / Sandbox (race detector) (push) Successful in 5m33s
Test / Hakurei (race detector) (push) Successful in 6m34s
Test / Flake checks (push) Successful in 1m23s
The behaviour of this was changed to include the stage2 toolchain instead, but the help text was never updated.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-25 15:41:28 +09:00
cat 50403e9d60 internal/netlink: wrap netpoll via context
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 6m9s
Test / Flake checks (push) Successful in 1m18s
This removes netpoll boilerplate for the most common use case.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-25 15:39:29 +09:00
cat b98c5f2e21 internal/netlink: nonblocking socket I/O
Test / Create distribution (push) Successful in 1m13s
Test / Sandbox (push) Successful in 2m59s
Test / Hakurei (push) Successful in 4m0s
Test / ShareFS (push) Successful in 4m1s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 3m21s
Test / Flake checks (push) Successful in 1m18s
This enables use with blocking calls like when used with NETLINK_KOBJECT_UEVENT.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-25 14:06:59 +09:00
cat d972cffe5a internal/netlink: make full response available
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m53s
Test / ShareFS (push) Successful in 4m43s
Test / Sandbox (race detector) (push) Successful in 5m31s
Test / Hakurei (push) Successful in 5m39s
Test / Hakurei (race detector) (push) Successful in 7m40s
Test / Flake checks (push) Successful in 1m20s
The previous API makes it impossible to retrieve remaining messages in the current iteration.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-23 16:39:25 +09:00
cat d8648304bb internal/netlink: isolate receive method
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m59s
Test / ShareFS (push) Successful in 4m49s
Test / Hakurei (push) Successful in 5m36s
Test / Sandbox (race detector) (push) Successful in 5m33s
Test / Hakurei (race detector) (push) Successful in 8m6s
Test / Flake checks (push) Successful in 1m23s
This enables use with epoll for receiving events only.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-23 15:03:15 +09:00
cat f7bfa9a6c2 internal/rosa/go: disable go1.25.7 smtp test
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 1m55s
Test / Sandbox (race detector) (push) Successful in 2m41s
Test / ShareFS (push) Successful in 3m35s
Test / Hakurei (push) Successful in 4m38s
Test / Hakurei (race detector) (push) Successful in 5m5s
Test / Flake checks (push) Successful in 1m21s
This uses certs that had just expired.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-20 17:52:54 +09:00
cat 7035b4b598 internal/rosa/cmake: 4.2.3 to 4.3.0
Test / Create distribution (push) Successful in 1m26s
Test / Sandbox (push) Successful in 3m42s
Test / ShareFS (push) Successful in 5m56s
Test / Sandbox (race detector) (push) Successful in 6m23s
Test / Hakurei (race detector) (push) Successful in 5m6s
Test / Hakurei (push) Successful in 5m25s
Test / Flake checks (push) Successful in 1m22s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-20 16:39:57 +09:00
cat 094b8400dd internal/rosa/qemu: 10.2.1 to 10.2.2
Test / Create distribution (push) Successful in 1m14s
Test / Sandbox (push) Successful in 3m34s
Test / Sandbox (race detector) (push) Successful in 6m56s
Test / ShareFS (push) Successful in 7m1s
Test / Hakurei (race detector) (push) Successful in 10m13s
Test / Hakurei (push) Successful in 4m28s
Test / Flake checks (push) Successful in 2m56s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-20 16:13:51 +09:00
cat 4652d921d8 internal/rosa/wayland: 1.24.91 to 1.25.0
Test / Create distribution (push) Successful in 1m17s
Test / Sandbox (push) Successful in 3m37s
Test / Sandbox (race detector) (push) Successful in 7m2s
Test / ShareFS (push) Successful in 7m13s
Test / Hakurei (push) Successful in 8m11s
Test / Hakurei (race detector) (push) Successful in 10m22s
Test / Flake checks (push) Successful in 1m22s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-20 16:13:28 +09:00
cat 066213c245 internal/rosa/libexpat: 2.7.4 to 2.7.5
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 6m57s
Test / ShareFS (push) Successful in 6m5s
Test / Hakurei (race detector) (push) Successful in 10m9s
Test / Hakurei (push) Successful in 5m29s
Test / Flake checks (push) Successful in 1m58s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-20 16:00:50 +09:00
cat 98832c21ee internal/rosa/fuse: 3.18.1 to 3.18.2
Test / Create distribution (push) Successful in 1m11s
Test / Sandbox (push) Successful in 3m12s
Test / ShareFS (push) Successful in 5m5s
Test / Sandbox (race detector) (push) Successful in 5m38s
Test / Hakurei (push) Successful in 5m46s
Test / Hakurei (race detector) (push) Successful in 8m18s
Test / Flake checks (push) Successful in 1m43s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-20 15:57:49 +09:00
cat 6cdb6a652b internal/rosa/gtk: glib 2.87.5 to 2.88.0
Test / Create distribution (push) Successful in 36s
Test / ShareFS (push) Successful in 41s
Test / Sandbox (push) Successful in 48s
Test / Sandbox (race detector) (push) Successful in 47s
Test / Hakurei (push) Successful in 52s
Test / Hakurei (race detector) (push) Successful in 2m0s
Test / Flake checks (push) Successful in 1m21s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 20:58:39 +09:00
cat 7c932cbceb internal/rosa: strace artifact
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 3m1s
Test / ShareFS (push) Successful in 4m49s
Test / Hakurei (push) Successful in 5m37s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 8m36s
Test / Flake checks (push) Successful in 1m21s
This is not part of the system, but a useful development tool. The test suite is quite broken but that is considered acceptable for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 20:40:17 +09:00
cat 20ebddd9bf internal/rosa: export source kind
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 3m3s
Test / ShareFS (push) Successful in 4m53s
Test / Hakurei (push) Successful in 5m30s
Test / Sandbox (race detector) (push) Successful in 5m28s
Test / Hakurei (race detector) (push) Successful in 7m57s
Test / Flake checks (push) Successful in 1m22s
This is set for an exported field, so export the constants as well.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 20:09:44 +09:00
cat 420c721c7d all: raise timeout defaults
Test / Create distribution (push) Successful in 1m47s
Test / Sandbox (push) Successful in 12m48s
Test / Sandbox (race detector) (push) Successful in 14m31s
Test / Hakurei (push) Successful in 15m26s
Test / Hakurei (race detector) (push) Successful in 16m51s
Test / ShareFS (push) Successful in 38s
Test / Flake checks (push) Successful in 1m21s
This avoids timing out on systems running very slowly.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 17:04:06 +09:00
cat bac583f89e internal/stub: move from container
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m39s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 6m8s
Test / Flake checks (push) Successful in 1m19s
This package solves a very specific stubbing use case, in a less than elegant manner.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 16:09:14 +09:00
cat 722989c682 fhs: move from container
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m41s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 5m10s
Test / Hakurei (race detector) (push) Successful in 6m10s
Test / Flake checks (push) Successful in 1m23s
This package is not container-specific.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 15:56:36 +09:00
cat b852402f67 ext: move syscall wrappers from container
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m36s
Test / Hakurei (push) Successful in 3m42s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 6m11s
Test / Flake checks (push) Successful in 1m30s
These are generally useful, and none of them are container-specific. Syscalls subtle to use and requiring container-specific setup remains in container.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 15:48:40 +09:00
cat 6d015a949e check: move from container
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 5m5s
Test / Hakurei (race detector) (push) Successful in 6m11s
Test / Flake checks (push) Successful in 1m22s
This package is not container specific, and widely used across the project.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 15:39:03 +09:00
cat e9a72490db vfs: move from container
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m43s
Test / ShareFS (push) Successful in 3m44s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 6m7s
Test / Flake checks (push) Successful in 1m21s
This package is not container-specific.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 15:30:30 +09:00
cat 0a12d456ce container: set CLOEXEC via close_range
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m42s
Test / ShareFS (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 6m7s
Test / Flake checks (push) Successful in 1m23s
This is guarded behind the close_range build tag for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 14:19:00 +09:00
cat d1fc1a3db7 ext: wrap close_range syscall
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m38s
Test / ShareFS (push) Successful in 3m41s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m10s
Test / Flake checks (push) Successful in 1m17s
This is useful for container when called with CLOSE_RANGE_CLOEXEC.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 14:15:16 +09:00
cat 1c2d5f6b57 ext: integer limit values
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m36s
Test / ShareFS (push) Successful in 3m44s
Test / Sandbox (race detector) (push) Successful in 4m58s
Test / Hakurei (race detector) (push) Successful in 54s
Test / Flake checks (push) Successful in 1m23s
For portably using C integers without cgo.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 14:09:38 +09:00
cat faea1f4bd6 all: remove deprecated packages
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m19s
Test / Hakurei (push) Successful in 3m15s
Test / ShareFS (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 5m8s
Test / Hakurei (race detector) (push) Successful in 5m44s
Test / Flake checks (push) Successful in 1m23s
Closes #24.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 13:54:56 +09:00
cat 0cb1007daa ldd: remove deprecated API
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m42s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 6m12s
Test / Flake checks (push) Successful in 1m35s
Closes #25.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 13:53:14 +09:00
cat e292031624 ext: move lookup test
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 6m13s
Test / Flake checks (push) Successful in 1m31s
This was kept in-place to reduce patch size in the previous patch.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 13:44:59 +09:00
cat cd5959fe5a ext: isolate from container/std
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m39s
Test / ShareFS (push) Successful in 3m42s
Test / Hakurei (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 6m12s
Test / Flake checks (push) Successful in 1m30s
These are too general to belong in the container package. This targets the v0.4 release to reduce the wrapper maintenance burden.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-17 13:39:26 +09:00
cat 08c35ca24f container: use new netlink implementation
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m44s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 6m9s
Test / Flake checks (push) Successful in 1m23s
This is adapted from the container netlink implementation and is much more reusable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-16 23:33:52 +09:00
cat 72bd3fb05e internal/netlink: generalise implementation from container
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m41s
Test / ShareFS (push) Successful in 3m42s
Test / Hakurei (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 6m8s
Test / Flake checks (push) Successful in 1m23s
This is useful for uevent implementation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-16 23:07:51 +09:00
cat 59c66747df internal/rosa/kernel: 6.12.76 to 6.12.77
Test / Create distribution (push) Successful in 1m8s
Test / Sandbox (push) Successful in 3m18s
Test / Hakurei (push) Successful in 4m56s
Test / ShareFS (push) Successful in 5m27s
Test / Hakurei (race detector) (push) Successful in 3m32s
Test / Sandbox (race detector) (push) Successful in 2m29s
Test / Flake checks (push) Successful in 1m22s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-16 15:21:33 +09:00
cat 9e6fe8db4b internal/rosa/meson: 1.10.1 to 1.10.2
Test / Create distribution (push) Successful in 42s
Test / ShareFS (push) Successful in 48s
Test / Sandbox (push) Successful in 55s
Test / Sandbox (race detector) (push) Successful in 55s
Test / Hakurei (push) Successful in 58s
Test / Hakurei (race detector) (push) Successful in 58s
Test / Flake checks (push) Successful in 1m51s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-16 13:41:06 +09:00
cat 5168ee3e13 internal/rosa/python: remove pre_commit
Test / Create distribution (push) Successful in 1m48s
Test / Sandbox (push) Successful in 3m50s
Test / Hakurei (push) Successful in 5m44s
Test / ShareFS (push) Successful in 5m54s
Test / Sandbox (race detector) (push) Successful in 7m8s
Test / Hakurei (race detector) (push) Successful in 8m45s
Test / Flake checks (push) Successful in 1m58s
This is unused and introduces many dependencies.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-16 13:39:56 +09:00
cat c8313c2dc4 internal/rosa/tamago: disable cgo
Test / Create distribution (push) Successful in 2m8s
Test / Sandbox (push) Successful in 5m15s
Test / Hakurei (push) Successful in 6m44s
Test / ShareFS (push) Successful in 6m45s
Test / Sandbox (race detector) (push) Successful in 7m28s
Test / Hakurei (race detector) (push) Successful in 8m34s
Test / Flake checks (push) Successful in 1m25s
This toolchain does not support cgo for the new target, anyway, and disabling it altogether avoids adding a dependency on arm64.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-16 13:22:10 +09:00
cat 3fcdadb669 internal/rosa/curl: remove broken test
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m41s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 6m10s
Test / Flake checks (push) Successful in 1m16s
Upstream testdata is not broken on the arm64 builder, but breaks reproducibly on amd64.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-16 12:54:03 +09:00
cat 3966bc5152 internal/rosa/hakurei: 0.3.6 to 0.3.7
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m36s
Test / ShareFS (push) Successful in 3m41s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m8s
Test / Flake checks (push) Successful in 1m20s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-15 21:43:55 +09:00
cat b208af8b85 release: 0.3.7
Release / Create release (push) Successful in 53s
Test / Create distribution (push) Successful in 44s
Test / ShareFS (push) Successful in 50s
Test / Sandbox (push) Successful in 56s
Test / Hakurei (push) Successful in 1m2s
Test / Sandbox (race detector) (push) Successful in 58s
Test / Hakurei (race detector) (push) Successful in 1m2s
Test / Flake checks (push) Successful in 1m30s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-15 21:04:55 +09:00
cat 8d650c0c8f all: migrate to rosa/hakurei
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m40s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m2s
Test / Flake checks (push) Successful in 1m20s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-15 20:12:51 +09:00
cat a720efc32d internal/rosa/llvm: arch-specific versions
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 6m7s
Test / Flake checks (push) Successful in 1m20s
This enables temporarily avoiding a broken release on specific targets.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-15 15:06:36 +09:00
cat 400540cd41 internal/rosa/llvm: arch-specific patches
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m37s
Test / Sandbox (race detector) (push) Successful in 4m58s
Test / Hakurei (race detector) (push) Successful in 6m5s
Test / Flake checks (push) Successful in 1m18s
Broken aarch64 tests in LLVM seem unlikely to be fixed soon.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-15 11:37:24 +09:00
cat 1113efa5c2 internal/rosa/kernel: enable arm64 block drivers
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m41s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m39s
Test / Sandbox (race detector) (push) Successful in 5m2s
Test / Hakurei (race detector) (push) Successful in 6m10s
Test / Flake checks (push) Successful in 1m18s
These are added separately to the amd64 patch due to the arm64 toolchain not being available at that time.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-15 00:22:05 +09:00
cat 8b875f865c cmd/earlyinit: remount root and set firmware path
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m44s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 6m10s
Test / Flake checks (push) Successful in 1m20s
The default search paths cannot be configured, configuring them here is most sound for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-14 19:50:04 +09:00
cat 8905d653ba cmd/earlyinit: mount pseudo-filesystems
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m42s
Test / ShareFS (push) Successful in 3m40s
Test / Hakurei (push) Successful in 3m45s
Test / Sandbox (race detector) (push) Successful in 5m2s
Test / Hakurei (race detector) (push) Successful in 6m5s
Test / Flake checks (push) Successful in 1m30s
The proposal for merging both init programs was unanimously accepted, so this is set up here alongside devtmpfs.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-14 19:43:42 +09:00
cat 9c2fb6246f internal/rosa/kernel: enable FW_LOADER
Test / Create distribution (push) Successful in 2m8s
Test / Sandbox (push) Successful in 4m13s
Test / Hakurei (push) Successful in 5m18s
Test / ShareFS (push) Successful in 5m15s
Test / Sandbox (race detector) (push) Successful in 6m30s
Test / Hakurei (race detector) (push) Successful in 7m35s
Test / Flake checks (push) Successful in 1m21s
This wants to be loaded early, so having it as a dlkm is not helpful as it will always be loaded anyway.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-14 19:32:14 +09:00
cat 9c116acec6 internal/rosa/kernel: enable amd64 block drivers
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m37s
Test / ShareFS (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 5m9s
Test / Hakurei (race detector) (push) Successful in 6m50s
Test / Flake checks (push) Successful in 3m5s
These have to be built into initramfs, anyway, so build them into the kernel instead. The arm64 toolchain is not yet ready, so will be updated in a later patch.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-14 19:22:56 +09:00
cat 988239a2bc internal/rosa: basic system image
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m46s
Test / ShareFS (push) Successful in 3m42s
Test / Hakurei (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m7s
Test / Flake checks (push) Successful in 1m23s
This is a simple image for debugging and is not yet set up for dm-verity.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-14 15:54:13 +09:00
cat bc03118142 cmd/earlyinit: handle args from cmdline
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m41s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m41s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m11s
Test / Flake checks (push) Successful in 1m22s
These are set by the bootloader.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-14 15:13:52 +09:00
cat 74c213264a internal/rosa/git: install libexec symlinks
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m41s
Test / ShareFS (push) Successful in 3m42s
Test / Hakurei (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 5m11s
Test / Hakurei (race detector) (push) Successful in 3m22s
Test / Flake checks (push) Successful in 1m16s
This is less clumsy to represent.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 20:43:23 +09:00
cat 345cffddc2 cmd/mbf: optionally export output
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m41s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 6m6s
Test / Flake checks (push) Successful in 1m21s
This is for debugging for now, as no program consumes this format yet.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 19:53:55 +09:00
cat 49163758c8 internal/rosa/llvm: 22.1.0 to 22.1.1
Test / Create distribution (push) Successful in 1m22s
Test / Sandbox (push) Successful in 3m14s
Test / Hakurei (push) Successful in 4m28s
Test / ShareFS (push) Successful in 4m37s
Test / Sandbox (race detector) (push) Successful in 5m41s
Test / Hakurei (race detector) (push) Successful in 6m52s
Test / Flake checks (push) Successful in 1m24s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 16:08:49 +09:00
cat ad22c15fb1 internal/rosa/perl: 5.42.0 to 5.42.1
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 3m19s
Test / ShareFS (push) Successful in 3m25s
Test / Sandbox (race detector) (push) Successful in 5m45s
Test / Hakurei (race detector) (push) Successful in 6m52s
Test / Hakurei (push) Successful in 4m27s
Test / Flake checks (push) Successful in 2m7s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 16:08:24 +09:00
cat 9c774f7e0a internal/rosa/python: setuptools 82.0.0 to 82.0.1
Test / Create distribution (push) Successful in 1m21s
Test / Sandbox (push) Successful in 3m6s
Test / Hakurei (push) Successful in 4m33s
Test / ShareFS (push) Successful in 4m33s
Test / Sandbox (race detector) (push) Successful in 5m55s
Test / Hakurei (race detector) (push) Successful in 7m1s
Test / Flake checks (push) Successful in 1m22s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 15:32:00 +09:00
cat 707f0a349f internal/rosa/gtk: glib 2.87.3 to 2.87.5
Test / Create distribution (push) Successful in 3m43s
Test / Sandbox (push) Successful in 6m11s
Test / Hakurei (push) Successful in 7m18s
Test / ShareFS (push) Successful in 7m18s
Test / Sandbox (race detector) (push) Successful in 8m28s
Test / Hakurei (race detector) (push) Successful in 9m33s
Test / Flake checks (push) Successful in 3m8s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 15:26:42 +09:00
cat 7c35be066a internal/rosa/tamago: 1.26.0 to 1.26.1
Test / Create distribution (push) Successful in 1m9s
Test / Sandbox (push) Successful in 2m50s
Test / Hakurei (push) Successful in 4m34s
Test / ShareFS (push) Successful in 4m50s
Test / Sandbox (race detector) (push) Successful in 5m53s
Test / Hakurei (race detector) (push) Successful in 6m58s
Test / Flake checks (push) Successful in 4m41s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 15:23:29 +09:00
cat f91d55fa5e internal/rosa/curl: 8.18.0 to 8.19.0
Test / Create distribution (push) Successful in 1m9s
Test / Sandbox (push) Successful in 2m54s
Test / ShareFS (push) Successful in 4m48s
Test / Sandbox (race detector) (push) Successful in 43s
Test / Hakurei (push) Successful in 6m19s
Test / Hakurei (race detector) (push) Successful in 7m8s
Test / Flake checks (push) Successful in 1m24s
The test suite now depends on python to run mock servers. SMB is disabled because it is completely unused, and pulls in a python dependency for tests. A broken test is fixed and the patch hopefully upstreamed before next release.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 15:23:07 +09:00
cat 5862cc1966 internal/rosa/kernel: firmware 20260221 to 20260309
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m41s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m11s
Test / Flake checks (push) Successful in 1m17s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 14:06:21 +09:00
cat b3f0360a05 internal/rosa: populate runtime dependencies
Test / Create distribution (push) Successful in 3m36s
Test / ShareFS (push) Successful in 8m53s
Test / Sandbox (race detector) (push) Successful in 9m28s
Test / Hakurei (race detector) (push) Successful in 10m25s
Test / Sandbox (push) Successful in 1m37s
Test / Hakurei (push) Successful in 2m27s
Test / Flake checks (push) Successful in 1m19s
This also removes manually resolved indirect dependencies.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 13:23:30 +09:00
cat 8938994036 cmd/mbf: display runtime dependency info
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m41s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m10s
Test / Flake checks (push) Successful in 1m14s
This only presents top-level dependencies, resolving indirect dependencies can be misleading in this context.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 10:46:37 +09:00
cat 96d382f805 cmd/mbf: resolve runtime dependencies
Test / Create distribution (push) Successful in 1m9s
Test / Sandbox (push) Successful in 2m55s
Test / Hakurei (push) Successful in 3m52s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m10s
Test / Hakurei (race detector) (push) Successful in 6m12s
Test / Flake checks (push) Successful in 1m30s
This also adds the collection meta-artifact for concurrent curing.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 10:41:22 +09:00
cat 5c785c135c internal/rosa: collection meta-artifact
Test / Create distribution (push) Successful in 41s
Test / Sandbox (push) Successful in 1m51s
Test / Hakurei (push) Successful in 2m54s
Test / ShareFS (push) Successful in 3m2s
Test / Sandbox (race detector) (push) Successful in 5m5s
Test / Hakurei (race detector) (push) Successful in 6m14s
Test / Flake checks (push) Successful in 1m30s
This is a stub FloodArtifact for concurrently curing multiple artifacts.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 10:34:45 +09:00
cat 0130f8ea6d internal/rosa: represent runtime dependencies
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m37s
Test / ShareFS (push) Successful in 3m41s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 6m10s
Test / Flake checks (push) Successful in 1m21s
This also resolves indirect dependencies, reducing noise.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-13 10:31:14 +09:00
cat faac5c4a83 internal/rosa: store artifact results in struct
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m43s
Test / ShareFS (push) Successful in 3m40s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m8s
Test / Flake checks (push) Successful in 1m21s
This is cleaner and makes adding additional values easier.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-12 18:08:41 +09:00
cat 620062cca9 hst: expose scheduling priority
Test / ShareFS (push) Successful in 43s
Test / Hakurei (push) Successful in 53s
Test / Sandbox (push) Successful in 49s
Test / Sandbox (race detector) (push) Successful in 48s
Test / Hakurei (race detector) (push) Successful in 53s
Test / Create distribution (push) Successful in 35s
Test / Flake checks (push) Successful in 1m18s
This is useful when limits are configured to allow it.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-12 02:15:14 +09:00
cat 196b200d0f container: expose priority and SCHED_OTHER policy
Test / Create distribution (push) Successful in 35s
Test / ShareFS (push) Successful in 40s
Test / Sandbox (push) Successful in 46s
Test / Sandbox (race detector) (push) Successful in 45s
Test / Hakurei (push) Successful in 52s
Test / Hakurei (race detector) (push) Successful in 50s
Test / Flake checks (push) Successful in 1m14s
The more explicit API removes the arbitrary limit preventing use of SCHED_OTHER (referred to as SCHED_NORMAL in the kernel). This change also exposes priority value to set.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-12 01:14:03 +09:00
cat 04e6bc3c5c hst: expose scheduling policy
Test / ShareFS (push) Successful in 39s
Test / Sandbox (push) Successful in 45s
Test / Hakurei (push) Successful in 50s
Test / Sandbox (race detector) (push) Successful in 45s
Test / Hakurei (race detector) (push) Successful in 49s
Test / Create distribution (push) Successful in 59s
Test / Flake checks (push) Successful in 1m19s
This is primarily useful for poorly written music players for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-12 00:52:18 +09:00
cat 5c540f90aa internal/outcome: improve doc comments
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 4m21s
Test / ShareFS (push) Successful in 4m49s
Test / Hakurei (race detector) (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 2m24s
Test / Flake checks (push) Successful in 1m17s
This improves readability on smaller displays.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-11 21:04:02 +09:00
cat 1e8ac5f68e container: use policy name in log message
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m49s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m4s
Test / Flake checks (push) Successful in 1m18s
This is more helpful than having the user resolve the integer.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-11 20:20:34 +09:00
cat fd515badff container: move scheduler policy constants to std
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m39s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m57s
Test / Flake checks (push) Successful in 2m9s
This avoids depending on cgo.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-11 20:03:08 +09:00
cat 330a344845 hst: improve doc comments
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m26s
These now read a lot better both in source and on pkgsite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-11 19:21:55 +09:00
cat 48cdf8bf85 go: 1.26
Test / Sandbox (push) Successful in 3m1s
Test / Hakurei (push) Successful in 3m58s
Test / ShareFS (push) Successful in 4m10s
Test / Sandbox (race detector) (push) Successful in 5m25s
Test / Hakurei (race detector) (push) Successful in 6m48s
Test / Create distribution (push) Successful in 1m3s
Test / Flake checks (push) Successful in 2m24s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-10 03:29:19 +09:00
cat 7fb42ba49d internal/rosa/llvm: set LLVM_LIT_ARGS
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m11s
Test / Hakurei (push) Successful in 4m23s
Test / ShareFS (push) Successful in 4m24s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m33s
Test / Flake checks (push) Successful in 1m30s
This replaces the progress bar, which was worse than useless.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-10 02:05:11 +09:00
cat 19a2737148 container: sched policy string representation
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 4m58s
Test / Hakurei (race detector) (push) Successful in 5m55s
Test / Flake checks (push) Successful in 1m31s
This also uses priority obtained via sched_get_priority_min, and improves bounds checking.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-09 18:38:31 +09:00
cat baf2def9cc internal/rosa/kmod: prefix moduledir
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 4m58s
Test / Hakurei (race detector) (push) Successful in 5m56s
Test / Flake checks (push) Successful in 1m32s
This change also works around the kernel build system being unaware of this option.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-09 16:40:55 +09:00
cat 242e042cb9 internal/rosa/nss: rename from ssl
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m38s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 4m55s
Test / Hakurei (race detector) (push) Successful in 5m52s
Test / Flake checks (push) Successful in 1m32s
The SSL name came from earlier on and is counterintuitive.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-09 14:58:31 +09:00
400 changed files with 29716 additions and 9198 deletions
+6 -28
View File
@@ -1,37 +1,15 @@
# Binaries for programs and plugins # produced by tools and text editors
*.exe *.qcow2
*.exe~
*.dll
*.so
*.dylib
*.pkg
/hakurei
# Test binary, built with `go test -c`
*.test *.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# env file
.env
.idea .idea
.vscode .vscode
# go generate # go generate
/cmd/hakurei/LICENSE /cmd/hakurei/LICENSE
/internal/pkg/testdata/testtool /cmd/mbf/internal/pkgserver/ui/static
/internal/pkg/internal/testtool/testtool
/internal/rosa/hakurei_current.tar.gz /internal/rosa/hakurei_current.tar.gz
# release # cmd/dist default destination
/dist/hakurei-* /dist
# interactive nixos vm
nixos.qcow2
+4 -4
View File
@@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<a href="https://git.gensokyo.uk/security/hakurei"> <a href="https://git.gensokyo.uk/rosa/hakurei">
<picture> <picture>
<img src="https://basement.gensokyo.uk/images/yukari1.png" width="200px" alt="Yukari"> <img src="https://basement.gensokyo.uk/images/yukari1.png" width="200px" alt="Yukari">
</picture> </picture>
@@ -8,16 +8,16 @@
<p align="center"> <p align="center">
<a href="https://pkg.go.dev/hakurei.app"><img src="https://pkg.go.dev/badge/hakurei.app.svg" alt="Go Reference" /></a> <a href="https://pkg.go.dev/hakurei.app"><img src="https://pkg.go.dev/badge/hakurei.app.svg" alt="Go Reference" /></a>
<a href="https://git.gensokyo.uk/security/hakurei/actions"><img src="https://git.gensokyo.uk/security/hakurei/actions/workflows/test.yml/badge.svg?branch=staging&style=flat-square" alt="Gitea Workflow Status" /></a> <a href="https://git.gensokyo.uk/rosa/hakurei/actions"><img src="https://git.gensokyo.uk/rosa/hakurei/actions/workflows/test.yml/badge.svg?branch=staging&style=flat-square" alt="Gitea Workflow Status" /></a>
<br/> <br/>
<a href="https://git.gensokyo.uk/security/hakurei/releases"><img src="https://img.shields.io/gitea/v/release/security/hakurei?gitea_url=https%3A%2F%2Fgit.gensokyo.uk&color=purple" alt="Release" /></a> <a href="https://git.gensokyo.uk/rosa/hakurei/releases"><img src="https://img.shields.io/gitea/v/release/rosa/hakurei?gitea_url=https%3A%2F%2Fgit.gensokyo.uk&color=purple" alt="Release" /></a>
<a href="https://goreportcard.com/report/hakurei.app"><img src="https://goreportcard.com/badge/hakurei.app" alt="Go Report Card" /></a> <a href="https://goreportcard.com/report/hakurei.app"><img src="https://goreportcard.com/badge/hakurei.app" alt="Go Report Card" /></a>
<a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a> <a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
</p> </p>
Hakurei is a tool for running sandboxed desktop applications as dedicated Hakurei is a tool for running sandboxed desktop applications as dedicated
subordinate users on the Linux kernel. It implements the application container subordinate users on the Linux kernel. It implements the application container
of [planterette (WIP)](https://git.gensokyo.uk/security/planterette), a of [planterette (WIP)](https://git.gensokyo.uk/rosa/planterette), a
self-contained Android-like package manager with modern security features. self-contained Android-like package manager with modern security features.
Interaction with hakurei happens entirely through structures described by Interaction with hakurei happens entirely through structures described by
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh -e
HAKUREI_DIST_MAKE='' exec "$(dirname -- "$0")/cmd/dist/dist.sh"
@@ -2,10 +2,10 @@
package check package check
import ( import (
"encoding/json" "encoding"
"errors" "errors"
"fmt" "fmt"
"path" "path/filepath"
"slices" "slices"
"strings" "strings"
"syscall" "syscall"
@@ -30,6 +30,16 @@ func (e AbsoluteError) Is(target error) bool {
// Absolute holds a pathname checked to be absolute. // Absolute holds a pathname checked to be absolute.
type Absolute struct{ pathname unique.Handle[string] } type Absolute struct{ pathname unique.Handle[string] }
var (
_ encoding.TextAppender = new(Absolute)
_ encoding.TextMarshaler = new(Absolute)
_ encoding.TextUnmarshaler = new(Absolute)
_ encoding.BinaryAppender = new(Absolute)
_ encoding.BinaryMarshaler = new(Absolute)
_ encoding.BinaryUnmarshaler = new(Absolute)
)
// ok returns whether [Absolute] is not the zero value. // ok returns whether [Absolute] is not the zero value.
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) } func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
@@ -61,7 +71,7 @@ func (a *Absolute) Is(v *Absolute) bool {
// NewAbs checks pathname and returns a new [Absolute] if pathname is absolute. // NewAbs checks pathname and returns a new [Absolute] if pathname is absolute.
func NewAbs(pathname string) (*Absolute, error) { func NewAbs(pathname string) (*Absolute, error) {
if !path.IsAbs(pathname) { if !filepath.IsAbs(pathname) {
return nil, AbsoluteError(pathname) return nil, AbsoluteError(pathname)
} }
return unsafeAbs(pathname), nil return unsafeAbs(pathname), nil
@@ -76,46 +86,35 @@ func MustAbs(pathname string) *Absolute {
} }
} }
// Append calls [path.Join] with [Absolute] as the first element. // Append calls [filepath.Join] with [Absolute] as the first element.
func (a *Absolute) Append(elem ...string) *Absolute { func (a *Absolute) Append(elem ...string) *Absolute {
return unsafeAbs(path.Join(append([]string{a.String()}, elem...)...)) return unsafeAbs(filepath.Join(append([]string{a.String()}, elem...)...))
} }
// Dir calls [path.Dir] with [Absolute] as its argument. // Dir calls [filepath.Dir] with [Absolute] as its argument.
func (a *Absolute) Dir() *Absolute { return unsafeAbs(path.Dir(a.String())) } func (a *Absolute) Dir() *Absolute { return unsafeAbs(filepath.Dir(a.String())) }
// GobEncode returns the checked pathname. // AppendText appends the checked pathname.
func (a *Absolute) GobEncode() ([]byte, error) { func (a *Absolute) AppendText(data []byte) ([]byte, error) {
return []byte(a.String()), nil return append(data, a.String()...), nil
} }
// GobDecode stores data if it represents an absolute pathname. // MarshalText returns the checked pathname.
func (a *Absolute) GobDecode(data []byte) error { func (a *Absolute) MarshalText() ([]byte, error) { return a.AppendText(nil) }
// UnmarshalText stores data if it represents an absolute pathname.
func (a *Absolute) UnmarshalText(data []byte) error {
pathname := string(data) pathname := string(data)
if !path.IsAbs(pathname) { if !filepath.IsAbs(pathname) {
return AbsoluteError(pathname) return AbsoluteError(pathname)
} }
a.pathname = unique.Make(pathname) a.pathname = unique.Make(pathname)
return nil return nil
} }
// MarshalJSON returns a JSON representation of the checked pathname. func (a *Absolute) AppendBinary(data []byte) ([]byte, error) { return a.AppendText(data) }
func (a *Absolute) MarshalJSON() ([]byte, error) { func (a *Absolute) MarshalBinary() ([]byte, error) { return a.MarshalText() }
return json.Marshal(a.String()) func (a *Absolute) UnmarshalBinary(data []byte) error { return a.UnmarshalText(data) }
}
// UnmarshalJSON stores data if it represents an absolute pathname.
func (a *Absolute) UnmarshalJSON(data []byte) error {
var pathname string
if err := json.Unmarshal(data, &pathname); err != nil {
return err
}
if !path.IsAbs(pathname) {
return AbsoluteError(pathname)
}
a.pathname = unique.Make(pathname)
return nil
}
// SortAbs calls [slices.SortFunc] for a slice of [Absolute]. // SortAbs calls [slices.SortFunc] for a slice of [Absolute].
func SortAbs(x []*Absolute) { func SortAbs(x []*Absolute) {
@@ -11,12 +11,12 @@ import (
"testing" "testing"
_ "unsafe" // for go:linkname _ "unsafe" // for go:linkname
. "hakurei.app/container/check" . "hakurei.app/check"
) )
// unsafeAbs returns check.Absolute on any string value. // unsafeAbs returns check.Absolute on any string value.
// //
//go:linkname unsafeAbs hakurei.app/container/check.unsafeAbs //go:linkname unsafeAbs hakurei.app/check.unsafeAbs
func unsafeAbs(pathname string) *Absolute func unsafeAbs(pathname string) *Absolute
func TestAbsoluteError(t *testing.T) { func TestAbsoluteError(t *testing.T) {
@@ -170,20 +170,20 @@ func TestCodecAbsolute(t *testing.T) {
{"good", MustAbs("/etc"), {"good", MustAbs("/etc"),
nil, nil,
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\b\xff\x80\x00\x04/etc", "\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\b\xff\x80\x00\x04/etc",
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x04/etc\x01\xfc\xc0\xed\x00\x00\x00", ",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x04/etc\x01\xfc\xc0\xed\x00\x00\x00",
`"/etc"`, `{"val":"/etc","magic":3236757504}`}, `"/etc"`, `{"val":"/etc","magic":3236757504}`},
{"not absolute", nil, {"not absolute", nil,
AbsoluteError("etc"), AbsoluteError("etc"),
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc", "\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc",
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00", ",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00",
`"etc"`, `{"val":"etc","magic":3236757504}`}, `"etc"`, `{"val":"etc","magic":3236757504}`},
{"zero", nil, {"zero", nil,
new(AbsoluteError), new(AbsoluteError),
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x04\xff\x80\x00\x00", "\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x04\xff\x80\x00\x00",
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\f\xff\x84\x01\x00\x01\xfb\x01\x81\xda\x00\x00\x00", ",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\f\xff\x84\x01\x00\x01\xfb\x01\x81\xda\x00\x00\x00",
`""`, `{"val":"","magic":3236757504}`}, `""`, `{"val":"","magic":3236757504}`},
} }
@@ -347,15 +347,6 @@ func TestCodecAbsolute(t *testing.T) {
}) })
}) })
} }
t.Run("json passthrough", func(t *testing.T) {
t.Parallel()
wantErr := "invalid character ':' looking for beginning of value"
if err := new(Absolute).UnmarshalJSON([]byte(":3")); err == nil || err.Error() != wantErr {
t.Errorf("UnmarshalJSON: error = %v, want %s", err, wantErr)
}
})
} }
func TestAbsoluteWrap(t *testing.T) { func TestAbsoluteWrap(t *testing.T) {
@@ -4,15 +4,23 @@ import "strings"
const ( const (
// SpecialOverlayEscape is the escape string for overlay mount options. // SpecialOverlayEscape is the escape string for overlay mount options.
//
// Deprecated: This is no longer used and will be removed in 0.5.
SpecialOverlayEscape = `\` SpecialOverlayEscape = `\`
// SpecialOverlayOption is the separator string between overlay mount options. // SpecialOverlayOption is the separator string between overlay mount options.
//
// Deprecated: This is no longer used and will be removed in 0.5.
SpecialOverlayOption = "," SpecialOverlayOption = ","
// SpecialOverlayPath is the separator string between overlay paths. // SpecialOverlayPath is the separator string between overlay paths.
//
// Deprecated: This is no longer used and will be removed in 0.5.
SpecialOverlayPath = ":" SpecialOverlayPath = ":"
) )
// EscapeOverlayDataSegment escapes a string for formatting into the data // EscapeOverlayDataSegment escapes a string for formatting into the data
// argument of an overlay mount system call. // argument of an overlay mount system call.
//
// Deprecated: This is no longer used and will be removed in 0.5.
func EscapeOverlayDataSegment(s string) string { func EscapeOverlayDataSegment(s string) string {
if s == "" { if s == "" {
return "" return ""
@@ -3,7 +3,7 @@ package check_test
import ( import (
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
) )
func TestEscapeOverlayDataSegment(t *testing.T) { func TestEscapeOverlayDataSegment(t *testing.T) {
+5 -5
View File
@@ -1,11 +1,11 @@
#compdef hakurei #compdef hakurei
_hakurei_app() { _hakurei_run() {
__hakurei_files __hakurei_files
return $? return $?
} }
_hakurei_run() { _hakurei_exec() {
_arguments \ _arguments \
'--id[Reverse-DNS style Application identifier, leave empty to inherit instance identifier]:id' \ '--id[Reverse-DNS style Application identifier, leave empty to inherit instance identifier]:id' \
'-a[Application identity]: :_numbers' \ '-a[Application identity]: :_numbers' \
@@ -57,9 +57,9 @@ __hakurei_instances() {
{ {
local -a _hakurei_cmds local -a _hakurei_cmds
_hakurei_cmds=( _hakurei_cmds=(
"app:Load and start container from configuration file" "run:Load and start container from configuration file"
"run:Configure and start a permissive container" "exec:Configure and start a permissive container"
"show:Show live or local app configuration" "show:Show live or local instance configuration"
"ps:List active instances" "ps:List active instances"
"version:Display version information" "version:Display version information"
"license:Show full license text" "license:Show full license text"
Vendored Executable
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh -e
TOOLCHAIN_VERSION="$(go version)"
cd "$(dirname -- "$0")/../.."
echo "Building cmd/dist using ${TOOLCHAIN_VERSION}."
FLAGS=''
if test -n "$VERBOSE"; then
FLAGS="$FLAGS -v"
fi
go run $FLAGS --tags=dist ./cmd/dist
+249
View File
@@ -0,0 +1,249 @@
//go:build dist
package main
import (
"archive/tar"
"compress/gzip"
"context"
"crypto/sha512"
_ "embed"
"encoding/hex"
"fmt"
"io"
"io/fs"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
)
// getenv looks up an environment variable, and returns fallback if it is unset.
func getenv(key, fallback string) string {
if v, ok := os.LookupEnv(key); ok {
return v
}
return fallback
}
// mustRun runs a command with the current process's environment and panics
// on error or non-zero exit code.
func mustRun(ctx context.Context, name string, arg ...string) {
cmd := exec.CommandContext(ctx, name, arg...)
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
panic(err)
}
}
//go:embed comp/_hakurei
var comp []byte
func main() {
log.SetFlags(0)
log.SetPrefix("")
verbose := os.Getenv("VERBOSE") != ""
runTests := os.Getenv("HAKUREI_DIST_MAKE") == ""
version := getenv("HAKUREI_VERSION", "untagged")
prefix := getenv("PREFIX", "/usr")
destdir := getenv("DESTDIR", "dist")
if verbose {
log.Println()
}
if err := os.MkdirAll(destdir, 0755); err != nil {
log.Fatal(err)
}
s, err := os.MkdirTemp(destdir, ".dist.*")
if err != nil {
log.Fatal(err)
}
defer func() {
var code int
if err = os.RemoveAll(s); err != nil {
code = 1
log.Println(err)
}
if r := recover(); r != nil {
code = 1
log.Println(r)
}
os.Exit(code)
}()
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
verboseFlag := "-v"
if !verbose {
verboseFlag = "-buildvcs=false"
}
log.Printf("Building hakurei for %s/%s.", runtime.GOOS, runtime.GOARCH)
mustRun(ctx, "go", "generate", "./...")
mustRun(
ctx, "go", "build",
"-trimpath",
verboseFlag, "-o", s,
"-ldflags=-s -w "+
"-buildid= -linkmode external -extldflags=-static "+
"-X hakurei.app/internal/info.buildVersion="+version+" "+
"-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+
"-X hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu "+
"-X main.hakureiPath="+prefix+"/bin/hakurei",
"./...",
)
log.Println()
if runTests {
log.Println("##### Testing Hakurei.")
mustRun(
ctx, "go", "test",
"-ldflags=-buildid= -linkmode external -extldflags=-static",
"./...",
)
log.Println()
}
log.Println("##### Creating distribution.")
const suffix = ".tar.gz"
distName := "hakurei-" + version + "-" + runtime.GOARCH
var f *os.File
if f, err = os.OpenFile(
filepath.Join(s, distName+suffix),
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
0644,
); err != nil {
panic(err)
}
defer func() {
if f == nil {
return
}
if err = f.Close(); err != nil {
log.Println(err)
}
}()
h := sha512.New()
gw, _ := gzip.NewWriterLevel(io.MultiWriter(f, h), gzip.BestCompression)
tw := tar.NewWriter(gw)
mustWriteHeader := func(name string, size int64, mode os.FileMode) {
header := tar.Header{
Name: filepath.Join(distName, name),
Size: size,
Mode: int64(mode),
Uname: "root",
Gname: "root",
}
if mode&os.ModeDir != 0 {
header.Typeflag = tar.TypeDir
fmt.Printf("%s %s\n", mode, name)
} else {
header.Typeflag = tar.TypeReg
fmt.Printf("%s %s (%d bytes)\n", mode, name, size)
}
if err = tw.WriteHeader(&header); err != nil {
panic(err)
}
}
mustWriteFile := func(name string, data []byte, mode os.FileMode) {
mustWriteHeader(name, int64(len(data)), mode)
if mode&os.ModeDir != 0 {
return
}
if _, err = tw.Write(data); err != nil {
panic(err)
}
}
mustWriteFromPath := func(dst, src string, mode os.FileMode) {
var r *os.File
if r, err = os.Open(src); err != nil {
panic(err)
}
var fi os.FileInfo
if fi, err = r.Stat(); err != nil {
_ = r.Close()
panic(err)
}
if mode == 0 {
mode = fi.Mode()
}
mustWriteHeader(dst, fi.Size(), mode)
if _, err = io.Copy(tw, r); err != nil {
_ = r.Close()
panic(err)
} else if err = r.Close(); err != nil {
panic(err)
}
}
mustWriteFile(".", nil, fs.ModeDir|0755)
mustWriteFile("comp/", nil, os.ModeDir|0755)
mustWriteFile("comp/_hakurei", comp, 0644)
mustWriteFile("install.sh", []byte(`#!/bin/sh -e
cd "$(dirname -- "$0")" || exit 1
install -vDm0755 "bin/hakurei" "${DESTDIR}`+prefix+`/bin/hakurei"
install -vDm0755 "bin/sharefs" "${DESTDIR}`+prefix+`/bin/sharefs"
install -vDm4511 "bin/hsu" "${DESTDIR}`+prefix+`/bin/hsu"
if [ ! -f "${DESTDIR}/etc/hsurc" ]; then
install -vDm0400 "hsurc.default" "${DESTDIR}/etc/hsurc"
fi
install -vDm0644 "comp/_hakurei" "${DESTDIR}`+prefix+`/share/zsh/site-functions/_hakurei"
`), 0755)
mustWriteFromPath("README.md", "README.md", 0)
mustWriteFile("hsurc.default", []byte("1000 0"), 0400)
mustWriteFromPath("bin/hsu", filepath.Join(s, "hsu"), 04511)
for _, name := range []string{
"hakurei",
"sharefs",
} {
mustWriteFromPath(
filepath.Join("bin", name),
filepath.Join(s, name),
0,
)
}
if err = tw.Close(); err != nil {
panic(err)
} else if err = gw.Close(); err != nil {
panic(err)
} else if err = f.Close(); err != nil {
panic(err)
}
f = nil
if err = os.WriteFile(
filepath.Join(destdir, distName+suffix+".sha512"),
append(hex.AppendEncode(nil, h.Sum(nil)), " "+distName+suffix+"\n"...),
0644,
); err != nil {
panic(err)
}
if err = os.Rename(
filepath.Join(s, distName+suffix),
filepath.Join(destdir, distName+suffix),
); err != nil {
panic(err)
}
}
+73
View File
@@ -1,9 +1,14 @@
// The earlyinit is part of the Rosa OS initramfs and serves as the system init.
//
// This program is an internal detail of Rosa OS and is not usable on its own.
// It is not covered by the compatibility promise.
package main package main
import ( import (
"log" "log"
"os" "os"
"runtime" "runtime"
"strings"
. "syscall" . "syscall"
) )
@@ -12,6 +17,22 @@ func main() {
log.SetFlags(0) log.SetFlags(0)
log.SetPrefix("earlyinit: ") log.SetPrefix("earlyinit: ")
var (
option map[string]string
flags []string
)
if len(os.Args) > 1 {
option = make(map[string]string)
for _, s := range os.Args[1:] {
key, value, ok := strings.Cut(s, "=")
if !ok {
flags = append(flags, s)
continue
}
option[key] = value
}
}
if err := Mount( if err := Mount(
"devtmpfs", "devtmpfs",
"/dev/", "/dev/",
@@ -55,4 +76,56 @@ func main() {
} }
} }
// staying in rootfs, these are no longer used
must(os.Remove("/root"))
must(os.Remove("/init"))
must(os.Mkdir("/proc", 0))
mustSyscall("mount proc", Mount(
"proc",
"/proc",
"proc",
MS_NOSUID|MS_NOEXEC|MS_NODEV,
"hidepid=1",
))
must(os.Mkdir("/sys", 0))
mustSyscall("mount sysfs", Mount(
"sysfs",
"/sys",
"sysfs",
0,
"",
))
// after top level has been set up
mustSyscall("remount root", Mount(
"",
"/",
"",
MS_REMOUNT|MS_BIND|
MS_RDONLY|MS_NODEV|MS_NOSUID|MS_NOEXEC,
"",
))
must(os.WriteFile(
"/sys/module/firmware_class/parameters/path",
[]byte("/system/lib/firmware"),
0,
))
}
// mustSyscall calls [log.Fatalln] if err is non-nil.
func mustSyscall(action string, err error) {
if err != nil {
log.Fatalln("cannot "+action+":", err)
}
}
// must calls [log.Fatal] with err if it is non-nil.
func must(err error) {
if err != nil {
log.Fatal(err)
}
} }
+48 -21
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -11,11 +12,11 @@ import (
"strconv" "strconv"
"sync" "sync"
"time" "time"
_ "unsafe" // for go:linkname
"hakurei.app/check"
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container/check" "hakurei.app/ext"
"hakurei.app/container/fhs" "hakurei.app/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/dbus" "hakurei.app/internal/dbus"
"hakurei.app/internal/env" "hakurei.app/internal/env"
@@ -26,13 +27,19 @@ import (
// optionalErrorUnwrap calls [errors.Unwrap] and returns the resulting value // optionalErrorUnwrap calls [errors.Unwrap] and returns the resulting value
// if it is not nil, or the original value if it is. // if it is not nil, or the original value if it is.
// func optionalErrorUnwrap(err error) error {
//go:linkname optionalErrorUnwrap hakurei.app/container.optionalErrorUnwrap if underlyingErr := errors.Unwrap(err); underlyingErr != nil {
func optionalErrorUnwrap(err error) error return underlyingErr
}
return err
}
var errSuccess = errors.New("success")
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command { func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
var ( var (
flagVerbose bool flagVerbose bool
flagInsecure bool
flagJSON bool flagJSON bool
) )
c := command.New(out, log.Printf, "hakurei", func([]string) error { c := command.New(out, log.Printf, "hakurei", func([]string) error {
@@ -51,6 +58,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
return nil return nil
}). }).
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity"). Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
Flag(&flagInsecure, "insecure", command.BoolFlag(false), "Allow use of insecure compatibility options").
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable") Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
c.Command("shim", command.UsageInternal, func([]string) error { outcome.Shim(msg); return errSuccess }) c.Command("shim", command.UsageInternal, func([]string) error { outcome.Shim(msg); return errSuccess })
@@ -59,9 +67,9 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
var ( var (
flagIdentifierFile int flagIdentifierFile int
) )
c.NewCommand("app", "Load and start container from configuration file", func(args []string) error { c.NewCommand("run", "Load and start container from configuration file", func(args []string) error {
if len(args) < 1 { if len(args) < 1 {
log.Fatal("app requires at least 1 argument") log.Fatal("run requires at least 1 argument")
} }
config := tryPath(msg, args[0]) config := tryPath(msg, args[0])
@@ -69,7 +77,12 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
config.Container.Args = append(config.Container.Args, args[1:]...) config.Container.Args = append(config.Container.Args, args[1:]...)
} }
outcome.Main(ctx, msg, config, flagIdentifierFile) var flags int
if flagInsecure {
flags |= hst.VAllowInsecure
}
outcome.Main(ctx, msg, config, flags, flagIdentifierFile)
panic("unreachable") panic("unreachable")
}). }).
Flag(&flagIdentifierFile, "identifier-fd", command.IntFlag(-1), Flag(&flagIdentifierFile, "identifier-fd", command.IntFlag(-1),
@@ -89,12 +102,15 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
flagHomeDir string flagHomeDir string
flagUserName string flagUserName string
flagSchedPolicy string
flagSchedPriority int
flagPrivateRuntime, flagPrivateTmpdir bool flagPrivateRuntime, flagPrivateTmpdir bool
flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool
) )
c.NewCommand("run", "Configure and start a permissive container", func(args []string) error { c.NewCommand("exec", "Configure and start a permissive container", func(args []string) error {
if flagIdentity < hst.IdentityStart || flagIdentity > hst.IdentityEnd { if flagIdentity < hst.IdentityStart || flagIdentity > hst.IdentityEnd {
log.Fatalf("identity %d out of range", flagIdentity) log.Fatalf("identity %d out of range", flagIdentity)
} }
@@ -131,12 +147,12 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
log.Fatal(optionalErrorUnwrap(err)) log.Fatal(optionalErrorUnwrap(err))
return err return err
} else if progPath, err = check.NewAbs(p); err != nil { } else if progPath, err = check.NewAbs(p); err != nil {
log.Fatal(err.Error()) log.Fatal(err)
return err return err
} }
} }
var et hst.Enablement var et hst.Enablements
if flagWayland { if flagWayland {
et |= hst.EWayland et |= hst.EWayland
} }
@@ -150,11 +166,11 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
et |= hst.EPipeWire et |= hst.EPipeWire
} }
config := &hst.Config{ config := hst.Config{
ID: flagID, ID: flagID,
Identity: flagIdentity, Identity: flagIdentity,
Groups: flagGroups, Groups: flagGroups,
Enablements: hst.NewEnablements(et), Enablements: &et,
Container: &hst.ContainerConfig{ Container: &hst.ContainerConfig{
Filesystem: []hst.FilesystemConfigJSON{ Filesystem: []hst.FilesystemConfigJSON{
@@ -177,6 +193,13 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
}, },
} }
if err := config.SchedPolicy.UnmarshalText(
[]byte(flagSchedPolicy),
); err != nil {
log.Fatal(err)
}
config.SchedPriority = ext.Int(flagSchedPriority)
// bind GPU stuff // bind GPU stuff
if et&(hst.EX11|hst.EWayland) != 0 { if et&(hst.EX11|hst.EWayland) != 0 {
config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{ config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
@@ -214,7 +237,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
homeDir = passwd.HomeDir homeDir = passwd.HomeDir
} }
if a, err := check.NewAbs(homeDir); err != nil { if a, err := check.NewAbs(homeDir); err != nil {
log.Fatal(err.Error()) log.Fatal(err)
return err return err
} else { } else {
config.Container.Home = a config.Container.Home = a
@@ -234,11 +257,11 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
config.SessionBus = dbus.NewConfig(flagID, true, flagDBusMpris) config.SessionBus = dbus.NewConfig(flagID, true, flagDBusMpris)
} else { } else {
if f, err := os.Open(flagDBusConfigSession); err != nil { if f, err := os.Open(flagDBusConfigSession); err != nil {
log.Fatal(err.Error()) log.Fatal(err)
} else { } else {
decodeJSON(log.Fatal, "load session bus proxy config", f, &config.SessionBus) decodeJSON(log.Fatal, "load session bus proxy config", f, &config.SessionBus)
if err = f.Close(); err != nil { if err = f.Close(); err != nil {
log.Fatal(err.Error()) log.Fatal(err)
} }
} }
} }
@@ -246,11 +269,11 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
// system bus proxy is optional // system bus proxy is optional
if flagDBusConfigSystem != "nil" { if flagDBusConfigSystem != "nil" {
if f, err := os.Open(flagDBusConfigSystem); err != nil { if f, err := os.Open(flagDBusConfigSystem); err != nil {
log.Fatal(err.Error()) log.Fatal(err)
} else { } else {
decodeJSON(log.Fatal, "load system bus proxy config", f, &config.SystemBus) decodeJSON(log.Fatal, "load system bus proxy config", f, &config.SystemBus)
if err = f.Close(); err != nil { if err = f.Close(); err != nil {
log.Fatal(err.Error()) log.Fatal(err)
} }
} }
} }
@@ -266,7 +289,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
} }
} }
outcome.Main(ctx, msg, config, -1) outcome.Main(ctx, msg, &config, 0, -1)
panic("unreachable") panic("unreachable")
}). }).
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"), Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
@@ -287,6 +310,10 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
"Container home directory"). "Container home directory").
Flag(&flagUserName, "u", command.StringFlag("chronos"), Flag(&flagUserName, "u", command.StringFlag("chronos"),
"Passwd user name within sandbox"). "Passwd user name within sandbox").
Flag(&flagSchedPolicy, "policy", command.StringFlag(""),
"Scheduling policy to set for the container").
Flag(&flagSchedPriority, "priority", command.IntFlag(0),
"Scheduling priority to set for the container").
Flag(&flagPrivateRuntime, "private-runtime", command.BoolFlag(false), Flag(&flagPrivateRuntime, "private-runtime", command.BoolFlag(false),
"Do not share XDG_RUNTIME_DIR between containers under the same identity"). "Do not share XDG_RUNTIME_DIR between containers under the same identity").
Flag(&flagPrivateTmpdir, "private-tmpdir", command.BoolFlag(false), Flag(&flagPrivateTmpdir, "private-tmpdir", command.BoolFlag(false),
@@ -308,7 +335,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
flagShort bool flagShort bool
flagNoStore bool flagNoStore bool
) )
c.NewCommand("show", "Show live or local app configuration", func(args []string) error { c.NewCommand("show", "Show live or local instance configuration", func(args []string) error {
switch len(args) { switch len(args) {
case 0: // system case 0: // system
printShowSystem(os.Stdout, flagShort, flagJSON) printShowSystem(os.Stdout, flagShort, flagJSON)
+10 -6
View File
@@ -20,12 +20,12 @@ func TestHelp(t *testing.T) {
}{ }{
{ {
"main", []string{}, ` "main", []string{}, `
Usage: hakurei [-h | --help] [-v] [--json] COMMAND [OPTIONS] Usage: hakurei [-h | --help] [-v] [--insecure] [--json] COMMAND [OPTIONS]
Commands: Commands:
app Load and start container from configuration file run Load and start container from configuration file
run Configure and start a permissive container exec Configure and start a permissive container
show Show live or local app configuration show Show live or local instance configuration
ps List active instances ps List active instances
version Display version information version Display version information
license Show full license text license Show full license text
@@ -35,8 +35,8 @@ Commands:
`, `,
}, },
{ {
"run", []string{"run", "-h"}, ` "exec", []string{"exec", "-h"}, `
Usage: hakurei run [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS] Usage: hakurei exec [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--policy <value>] [--priority <int>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS]
Flags: Flags:
-X Enable direct connection to X11 -X Enable direct connection to X11
@@ -60,6 +60,10 @@ Flags:
Allow owning MPRIS D-Bus path, has no effect if custom config is available Allow owning MPRIS D-Bus path, has no effect if custom config is available
-pipewire -pipewire
Enable connection to PipeWire via SecurityContext Enable connection to PipeWire via SecurityContext
-policy string
Scheduling policy to set for the container
-priority int
Scheduling priority to set for the container
-private-runtime -private-runtime
Do not share XDG_RUNTIME_DIR between containers under the same identity Do not share XDG_RUNTIME_DIR between containers under the same identity
-private-tmpdir -private-tmpdir
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"strings" "strings"
"testing" "testing"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestDecodeJSON(t *testing.T) { func TestDecodeJSON(t *testing.T) {
+43 -11
View File
@@ -1,8 +1,42 @@
// Hakurei runs user-specified containers as subordinate users.
//
// This program is generally invoked by another, higher level program, which
// creates container configuration via package [hst] or an implementation of it.
//
// The parent may leave files open and specify their file descriptor for various
// uses. In these cases, standard streams and netpoll files are treated as
// invalid file descriptors and rejected. All string representations must be in
// decimal.
//
// When specifying a [hst.Config] JSON stream or file to the run subcommand, the
// argument "-" is equivalent to stdin. Otherwise, file descriptor rules
// described above applies. Invalid file descriptors are treated as file names
// in their string representation, with the exception that if a netpoll file
// descriptor is attempted, the program fails.
//
// The flag --identifier-fd can be optionally specified to the run subcommand to
// receive the identifier of the newly started instance. File descriptor rules
// described above applies, and the file must be writable. This is sent after
// its state is made available, so the client must not attempt to poll for it.
// This uses the internal binary format of [hst.ID].
//
// For the show and ps subcommands, the flag --json can be applied to the main
// hakurei command to serialise output in JSON when applicable. Additionally,
// the flag --short targeting each subcommand is used to omit some information
// in both JSON and user-facing output. Only JSON-encoded output is covered
// under the compatibility promise.
//
// A template for [hst.Config] demonstrating all available configuration fields
// is returned by [hst.Template]. The JSON-encoded equivalent of this can be
// obtained via the template subcommand. Fields left unpopulated in the template
// (the direct_* family of fields, which are insecure under any configuration if
// enabled) are unsupported.
//
// For simple (but insecure) testing scenarios, the exec subcommand can be used
// to generate a simple, permissive configuration in-memory. See its help
// message for all available options.
package main package main
// this works around go:embed '..' limitation
//go:generate cp ../../LICENSE .
import ( import (
"context" "context"
_ "embed" _ "embed"
@@ -13,15 +47,13 @@ import (
"syscall" "syscall"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/ext"
"hakurei.app/message" "hakurei.app/message"
) )
var ( //go:generate cp ../../LICENSE .
errSuccess = errors.New("success")
//go:embed LICENSE //go:embed LICENSE
license string var license string
)
// earlyHardeningErrs are errors collected while setting up early hardening feature. // earlyHardeningErrs are errors collected while setting up early hardening feature.
type earlyHardeningErrs struct{ yamaLSM, dumpable error } type earlyHardeningErrs struct{ yamaLSM, dumpable error }
@@ -30,13 +62,13 @@ func main() {
// early init path, skips root check and duplicate PR_SET_DUMPABLE // early init path, skips root check and duplicate PR_SET_DUMPABLE
container.TryArgv0(nil) container.TryArgv0(nil)
log.SetPrefix("hakurei: ")
log.SetFlags(0) log.SetFlags(0)
log.SetPrefix("hakurei: ")
msg := message.New(log.Default()) msg := message.New(log.Default())
early := earlyHardeningErrs{ early := earlyHardeningErrs{
yamaLSM: container.SetPtracer(0), yamaLSM: ext.SetPtracer(0),
dumpable: container.SetDumpable(container.SUID_DUMP_DISABLE), dumpable: ext.SetDumpable(ext.SUID_DUMP_DISABLE),
} }
if os.Geteuid() == 0 { if os.Geteuid() == 0 {
+19 -8
View File
@@ -17,8 +17,9 @@ import (
) )
// tryPath attempts to read [hst.Config] from multiple sources. // tryPath attempts to read [hst.Config] from multiple sources.
// tryPath reads from [os.Stdin] if name has value "-". //
// Otherwise, name is passed to tryFd, and if that returns nil, name is passed to [os.Open]. // tryPath reads from [os.Stdin] if name has value "-". Otherwise, name is
// passed to tryFd, and if that returns nil, name is passed to [os.Open].
func tryPath(msg message.Msg, name string) (config *hst.Config) { func tryPath(msg message.Msg, name string) (config *hst.Config) {
var r io.ReadCloser var r io.ReadCloser
config = new(hst.Config) config = new(hst.Config)
@@ -46,7 +47,8 @@ func tryPath(msg message.Msg, name string) (config *hst.Config) {
return return
} }
// tryFd returns a [io.ReadCloser] if name represents an integer corresponding to a valid file descriptor. // tryFd returns a [io.ReadCloser] if name represents an integer corresponding
// to a valid file descriptor.
func tryFd(msg message.Msg, name string) io.ReadCloser { func tryFd(msg message.Msg, name string) io.ReadCloser {
if v, err := strconv.Atoi(name); err != nil { if v, err := strconv.Atoi(name); err != nil {
if !errors.Is(err, strconv.ErrSyntax) { if !errors.Is(err, strconv.ErrSyntax) {
@@ -60,7 +62,12 @@ func tryFd(msg message.Msg, name string) io.ReadCloser {
msg.Verbosef("trying config stream from %d", v) msg.Verbosef("trying config stream from %d", v)
fd := uintptr(v) fd := uintptr(v)
if _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_GETFD, 0); errno != 0 { if _, _, errno := syscall.Syscall(
syscall.SYS_FCNTL,
fd,
syscall.F_GETFD,
0,
); errno != 0 {
if errors.Is(errno, syscall.EBADF) { // reject bad fd if errors.Is(errno, syscall.EBADF) { // reject bad fd
return nil return nil
} }
@@ -75,10 +82,12 @@ func tryFd(msg message.Msg, name string) io.ReadCloser {
} }
} }
// shortLengthMin is the minimum length a short form identifier can have and still be interpreted as an identifier. // shortLengthMin is the minimum length a short form identifier can have and
// still be interpreted as an identifier.
const shortLengthMin = 1 << 3 const shortLengthMin = 1 << 3
// shortIdentifier returns an eight character short representation of [hst.ID] from its random bytes. // shortIdentifier returns an eight character short representation of [hst.ID]
// from its random bytes.
func shortIdentifier(id *hst.ID) string { func shortIdentifier(id *hst.ID) string {
return shortIdentifierString(id.String()) return shortIdentifierString(id.String())
} }
@@ -88,7 +97,8 @@ func shortIdentifierString(s string) string {
return s[len(hst.ID{}) : len(hst.ID{})+shortLengthMin] return s[len(hst.ID{}) : len(hst.ID{})+shortLengthMin]
} }
// tryIdentifier attempts to match [hst.State] from a [hex] representation of [hst.ID] or a prefix of its lower half. // tryIdentifier attempts to match [hst.State] from a [hex] representation of
// [hst.ID] or a prefix of its lower half.
func tryIdentifier(msg message.Msg, name string, s *store.Store) *hst.State { func tryIdentifier(msg message.Msg, name string, s *store.Store) *hst.State {
const ( const (
likeShort = 1 << iota likeShort = 1 << iota
@@ -96,7 +106,8 @@ func tryIdentifier(msg message.Msg, name string, s *store.Store) *hst.State {
) )
var likely uintptr var likely uintptr
if len(name) >= shortLengthMin && len(name) <= len(hst.ID{}) { // half the hex representation // half the hex representation
if len(name) >= shortLengthMin && len(name) <= len(hst.ID{}) {
// cannot safely decode here due to unknown alignment // cannot safely decode here due to unknown alignment
for _, c := range name { for _, c := range name {
if c >= '0' && c <= '9' { if c >= '0' && c <= '9' {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"testing" "testing"
"time" "time"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/store" "hakurei.app/internal/store"
"hakurei.app/message" "hakurei.app/message"
+1 -1
View File
@@ -56,7 +56,7 @@ func printShowInstance(
t := newPrinter(output) t := newPrinter(output)
defer t.MustFlush() defer t.MustFlush()
if err := config.Validate(); err != nil { if err := config.Validate(hst.VAllowInsecure); err != nil {
valid = false valid = false
if m, ok := message.GetMessage(err); ok { if m, ok := message.GetMessage(err); ok {
mustPrint(output, "Error: "+m+"!\n\n") mustPrint(output, "Error: "+m+"!\n\n")
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"testing" "testing"
"time" "time"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/store" "hakurei.app/internal/store"
"hakurei.app/message" "hakurei.app/message"
@@ -32,7 +32,7 @@ var (
PID: 0xbeef, PID: 0xbeef,
ShimPID: 0xcafe, ShimPID: 0xcafe,
Config: &hst.Config{ Config: &hst.Config{
Enablements: hst.NewEnablements(hst.EWayland | hst.EPipeWire), Enablements: new(hst.EWayland | hst.EPipeWire),
Identity: 1, Identity: 1,
Container: &hst.ContainerConfig{ Container: &hst.ContainerConfig{
Shell: check.MustAbs("/bin/sh"), Shell: check.MustAbs("/bin/sh"),
+7
View File
@@ -0,0 +1,7 @@
//go:build !rosa
package main
// hsuConfPath is an absolute pathname to the hsu configuration file. Its
// contents are interpreted by parseConfig.
const hsuConfPath = "/etc/hsurc"
+7
View File
@@ -0,0 +1,7 @@
//go:build rosa
package main
// hsuConfPath is the pathname to the hsu configuration file, specific to
// Rosa OS. Its contents are interpreted by parseConfig.
const hsuConfPath = "/system/etc/hsurc"
+1 -1
View File
@@ -1,6 +1,6 @@
package main package main
/* copied from hst and must never be changed */ /* keep in sync with hst */
const ( const (
userOffset = 100000 userOffset = 100000
+75 -14
View File
@@ -1,13 +1,64 @@
// hsu starts the hakurei shim as the target subordinate user.
//
// The hsu program must be installed with the setuid and setgid bit set, and
// owned by root. A configuration file must be installed at /etc/hsurc with
// permission bits 0400, and owned by root. Each line of the file specifies a
// hakurei userid to kernel uid mapping. A line consists of the decimal string
// representation of the uid of the user wishing to start hakurei containers,
// followed by a space, followed by the decimal string representation of its
// userid. Duplicate uid entries are ignored, with the first occurrence taking
// effect.
//
// For example, to map the kernel uid 1000 to the hakurei user id 0:
//
// 1000 0
//
// # Internals
//
// Hakurei and hsu holds pathnames pointing to each other set at link time. For
// this reason, a distribution of hakurei has fixed installation prefix. Since
// this program is never invoked by the user, behaviour described in the
// following paragraphs are considered an internal detail and not covered by the
// compatibility promise.
//
// After checking credentials, hsu checks via /proc/ the absolute pathname of
// its parent process, and fails if it does not match the hakurei pathname set
// at link time. This is not a security feature: the priv-side is considered
// trusted, and this feature makes no attempt to address the racy nature of
// querying /proc/, or debuggers attached to the parent process. Instead, this
// aims to discourage misuse and reduce confusion if the user accidentally
// stumbles upon this program. It also prevents accidental use of the incorrect
// installation of hsu in some environments.
//
// Since target container environment variables are set up in shim via the
// [container] infrastructure, the environment is used for parameters from the
// parent process.
//
// HAKUREI_SHIM specifies a single byte between '3' and '9' representing the
// setup pipe file descriptor. It is passed as is to the shim process and is the
// only value in the environment of the shim process. Since hsurc is not
// accessible to the parent process, leaving this unset causes hsu to print the
// corresponding hakurei user id of the parent and terminate.
//
// HAKUREI_IDENTITY specifies the identity of the instance being started and is
// used to produce the kernel uid alongside hakurei user id looked up from hsurc.
//
// HAKUREI_GROUPS specifies supplementary groups to inherit from the credentials
// of the parent process in a ' ' separated list of decimal string
// representations of gid. This has the unfortunate consequence of allowing
// users mapped via hsurc to effectively drop group membership, so special care
// must be taken to ensure this does not lead to an increase in access. This is
// not applicable to Rosa OS since unsigned code execution is not permitted
// outside hakurei containers, and is generally nonapplicable to the security
// model of hakurei, where all untrusted code runs within containers.
package main package main
// minimise imports to avoid inadvertently calling init or global variable functions
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log" "log"
"os" "os"
"path" "path/filepath"
"runtime" "runtime"
"slices" "slices"
"strconv" "strconv"
@@ -16,10 +67,13 @@ import (
) )
const ( const (
// envIdentity is the name of the environment variable holding a // envShim is the name of the environment variable holding a single byte
// single byte representing the shim setup pipe file descriptor. // representing the shim setup pipe file descriptor.
envShim = "HAKUREI_SHIM" envShim = "HAKUREI_SHIM"
// envGroups holds a ' ' separated list of string representations of // envIdentity is the name of the environment variable holding a decimal
// string representation of the current application identity.
envIdentity = "HAKUREI_IDENTITY"
// envGroups holds a ' ' separated list of decimal string representations of
// supplementary group gid. Membership requirements are enforced. // supplementary group gid. Membership requirements are enforced.
envGroups = "HAKUREI_GROUPS" envGroups = "HAKUREI_GROUPS"
) )
@@ -35,7 +89,6 @@ func main() {
log.SetFlags(0) log.SetFlags(0)
log.SetPrefix("hsu: ") log.SetPrefix("hsu: ")
log.SetOutput(os.Stderr)
if os.Geteuid() != 0 { if os.Geteuid() != 0 {
log.Fatal("this program must be owned by uid 0 and have the setuid bit set") log.Fatal("this program must be owned by uid 0 and have the setuid bit set")
@@ -49,13 +102,13 @@ func main() {
log.Fatal("this program must not be started by root") log.Fatal("this program must not be started by root")
} }
if !path.IsAbs(hakureiPath) { if !filepath.IsAbs(hakureiPath) {
log.Fatal("this program is compiled incorrectly") log.Fatal("this program is compiled incorrectly")
return return
} }
var toolPath string var toolPath string
pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe") pexe := filepath.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
if p, err := os.Readlink(pexe); err != nil { if p, err := os.Readlink(pexe); err != nil {
log.Fatalf("cannot read parent executable path: %v", err) log.Fatalf("cannot read parent executable path: %v", err)
} else if strings.HasSuffix(p, " (deleted)") { } else if strings.HasSuffix(p, " (deleted)") {
@@ -99,8 +152,6 @@ func main() {
// last possible uid outcome // last possible uid outcome
uidEnd = 999919999 uidEnd = 999919999
) )
// cast to int for use with library functions
uid := int(toUser(userid, identity)) uid := int(toUser(userid, identity))
// final bounds check to catch any bugs // final bounds check to catch any bugs
@@ -136,7 +187,6 @@ func main() {
} }
// careful! users in the allowlist is effectively allowed to drop groups via hsu // careful! users in the allowlist is effectively allowed to drop groups via hsu
if err := syscall.Setresgid(uid, uid, uid); err != nil { if err := syscall.Setresgid(uid, uid, uid); err != nil {
log.Fatalf("cannot set gid: %v", err) log.Fatalf("cannot set gid: %v", err)
} }
@@ -146,10 +196,21 @@ func main() {
if err := syscall.Setresuid(uid, uid, uid); err != nil { if err := syscall.Setresuid(uid, uid, uid); err != nil {
log.Fatalf("cannot set uid: %v", err) log.Fatalf("cannot set uid: %v", err)
} }
if _, _, errno := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0); errno != 0 {
if _, _, errno := syscall.AllThreadsSyscall(
syscall.SYS_PRCTL,
PR_SET_NO_NEW_PRIVS, 1,
0,
); errno != 0 {
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error()) log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
} }
if err := syscall.Exec(toolPath, []string{"hakurei", "shim"}, []string{envShim + "=" + shimSetupFd}); err != nil {
if err := syscall.Exec(toolPath, []string{
"hakurei",
"shim",
}, []string{
envShim + "=" + shimSetupFd,
}); err != nil {
log.Fatalf("cannot start shim: %v", err) log.Fatalf("cannot start shim: %v", err)
} }
+10 -15
View File
@@ -18,8 +18,9 @@ const (
useridEnd = useridStart + rangeSize - 1 useridEnd = useridStart + rangeSize - 1
) )
// parseUint32Fast parses a string representation of an unsigned 32-bit integer value // parseUint32Fast parses a string representation of an unsigned 32-bit integer
// using the fast path only. This limits the range of values it is defined in. // value using the fast path only. This limits the range of values it is defined
// in but is perfectly adequate for this use case.
func parseUint32Fast(s string) (uint32, error) { func parseUint32Fast(s string) (uint32, error) {
sLen := len(s) sLen := len(s)
if sLen < 1 { if sLen < 1 {
@@ -40,12 +41,14 @@ func parseUint32Fast(s string) (uint32, error) {
return n, nil return n, nil
} }
// parseConfig reads a list of allowed users from r until it encounters puid or [io.EOF]. // parseConfig reads a list of allowed users from r until it encounters puid or
// [io.EOF].
// //
// Each line of the file specifies a hakurei userid to kernel uid mapping. A line consists // Each line of the file specifies a hakurei userid to kernel uid mapping. A
// of the string representation of the uid of the user wishing to start hakurei containers, // line consists of the string representation of the uid of the user wishing to
// followed by a space, followed by the string representation of its userid. Duplicate uid // start hakurei containers, followed by a space, followed by the string
// entries are ignored, with the first occurrence taking effect. // representation of its userid. Duplicate uid entries are ignored, with the
// first occurrence taking effect.
// //
// All string representations are parsed by calling parseUint32Fast. // All string representations are parsed by calling parseUint32Fast.
func parseConfig(r io.Reader, puid uint32) (userid uint32, ok bool, err error) { func parseConfig(r io.Reader, puid uint32) (userid uint32, ok bool, err error) {
@@ -81,10 +84,6 @@ func parseConfig(r io.Reader, puid uint32) (userid uint32, ok bool, err error) {
return useridEnd + 1, false, s.Err() return useridEnd + 1, false, s.Err()
} }
// hsuConfPath is an absolute pathname to the hsu configuration file.
// Its contents are interpreted by parseConfig.
const hsuConfPath = "/etc/hsurc"
// mustParseConfig calls parseConfig to interpret the contents of hsuConfPath, // mustParseConfig calls parseConfig to interpret the contents of hsuConfPath,
// terminating the program if an error is encountered, the syntax is incorrect, // terminating the program if an error is encountered, the syntax is incorrect,
// or the current user is not authorised to use hsu because its uid is missing. // or the current user is not authorised to use hsu because its uid is missing.
@@ -112,10 +111,6 @@ func mustParseConfig(puid int) (userid uint32) {
return return
} }
// envIdentity is the name of the environment variable holding a
// string representation of the current application identity.
var envIdentity = "HAKUREI_IDENTITY"
// mustReadIdentity calls parseUint32Fast to interpret the value stored in envIdentity, // mustReadIdentity calls parseUint32Fast to interpret the value stored in envIdentity,
// terminating the program if the value is not set, malformed, or out of bounds. // terminating the program if the value is not set, malformed, or out of bounds.
func mustReadIdentity() uint32 { func mustReadIdentity() uint32 {
+135
View File
@@ -0,0 +1,135 @@
package main
import (
"context"
"os"
"path/filepath"
"testing"
"hakurei.app/check"
"hakurei.app/container"
"hakurei.app/internal/pkg"
"hakurei.app/message"
)
// cache refers to an instance of [pkg.Cache] that might be open.
type cache struct {
ctx context.Context
msg message.Msg
// Should generally not be used directly.
c *pkg.Cache
cures, jobs int
// Primarily to work around missing landlock LSM.
hostAbstract bool
// Set SCHED_IDLE.
idle bool
// Unset [pkg.CSuppressInit].
verboseInit bool
// Loaded artifact of [rosa.QEMU].
qemu pkg.Artifact
base string
}
// open opens the underlying [pkg.Cache].
func (cache *cache) open() (err error) {
if cache.c != nil {
return os.ErrInvalid
}
var base *check.Absolute
if cache.base, err = filepath.Abs(cache.base); err != nil {
return
} else if base, err = check.NewAbs(cache.base); err != nil {
return
}
var flags int
if cache.idle {
flags |= pkg.CSchedIdle
}
if cache.hostAbstract {
flags |= pkg.CHostAbstract
}
if !cache.verboseInit {
flags |= pkg.CSuppressInit
}
done := make(chan struct{})
defer close(done)
go func() {
select {
case <-cache.ctx.Done():
if testing.Testing() {
return
}
os.Exit(2)
case <-done:
return
}
}()
cache.msg.Verbosef("opening cache at %s", base)
cache.c, err = pkg.Open(
cache.ctx,
cache.msg,
flags,
cache.cures,
cache.jobs,
base,
)
if err != nil {
return
}
done <- struct{}{}
if cache.qemu != nil {
var pathname *check.Absolute
pathname, _, err = cache.c.Cure(cache.qemu)
if err != nil {
cache.c.Close()
return
}
pkg.RegisterArch("riscv64", container.BinfmtEntry{
Offset: 0,
Magic: "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00",
Mask: "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
Interpreter: pathname.Append(
"system/bin",
"qemu-riscv64",
),
})
pkg.RegisterArch("arm64", container.BinfmtEntry{
Offset: 0,
Magic: "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00",
Mask: "\xff\xff\xff\xff\xff\xff\xff\xfc\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
Interpreter: pathname.Append(
"system/bin",
"qemu-aarch64",
),
})
}
return
}
// Close closes the underlying [pkg.Cache] if it is open.
func (cache *cache) Close() {
if cache.c != nil {
cache.c.Close()
}
}
// Do calls f on the underlying cache and returns its error value.
func (cache *cache) Do(f func(cache *pkg.Cache) error) error {
if cache.c == nil {
if err := cache.open(); err != nil {
return err
}
}
return f(cache.c)
}
+37
View File
@@ -0,0 +1,37 @@
package main
import (
"log"
"os"
"testing"
"hakurei.app/internal/pkg"
"hakurei.app/message"
)
func TestCache(t *testing.T) {
t.Parallel()
cm := cache{
ctx: t.Context(),
msg: message.New(log.New(os.Stderr, "check: ", 0)),
base: t.TempDir(),
hostAbstract: true, idle: true,
}
defer cm.Close()
cm.Close()
if err := cm.open(); err != nil {
t.Fatalf("open: error = %v", err)
}
if err := cm.open(); err != os.ErrInvalid {
t.Errorf("(duplicate) open: error = %v", err)
}
if err := cm.Do(func(cache *pkg.Cache) error {
return cache.Scrub(0)
}); err != nil {
t.Errorf("Scrub: error = %v", err)
}
}
+354
View File
@@ -0,0 +1,354 @@
package main
import (
"context"
"encoding/binary"
"errors"
"io"
"log"
"math"
"net"
"os"
"sync"
"syscall"
"testing"
"time"
"unique"
"hakurei.app/check"
"hakurei.app/internal/pkg"
)
// daemonTimeout is the maximum amount of time cureFromIR will wait on I/O.
const daemonTimeout = 30 * time.Second
// daemonDeadline returns the deadline corresponding to daemonTimeout, or the
// zero value when running in a test.
func daemonDeadline() time.Time {
if testing.Testing() {
return time.Time{}
}
return time.Now().Add(daemonTimeout)
}
const (
// remoteNoReply notifies that the client will not receive a cure reply.
remoteNoReply = 1 << iota
)
// cureFromIR services an IR curing request.
func cureFromIR(
cache *pkg.Cache,
conn net.Conn,
flags uint64,
) (pkg.Artifact, error) {
a, decodeErr := cache.NewDecoder(conn).Decode()
if decodeErr != nil {
_, err := conn.Write([]byte("\x00" + decodeErr.Error()))
return nil, errors.Join(decodeErr, err, conn.Close())
}
pathname, _, cureErr := cache.Cure(a)
if flags&remoteNoReply != 0 {
return a, errors.Join(cureErr, conn.Close())
}
if err := conn.SetWriteDeadline(daemonDeadline()); err != nil {
return a, errors.Join(cureErr, err, conn.Close())
}
if cureErr != nil {
_, err := conn.Write([]byte("\x00" + cureErr.Error()))
return a, errors.Join(cureErr, err, conn.Close())
}
_, err := conn.Write([]byte(pathname.String()))
if testing.Testing() && errors.Is(err, io.ErrClosedPipe) {
return a, nil
}
return a, errors.Join(err, conn.Close())
}
const (
// specialCancel is a message consisting of a single identifier referring
// to a curing artifact to be cancelled.
specialCancel = iota
// specialAbort requests for all pending cures to be aborted. It has no
// message body.
specialAbort
// remoteSpecial denotes a special message with custom layout.
remoteSpecial = math.MaxUint64
)
// writeSpecialHeader writes the header of a remoteSpecial message.
func writeSpecialHeader(conn net.Conn, kind uint64) error {
var sh [16]byte
binary.LittleEndian.PutUint64(sh[:], remoteSpecial)
binary.LittleEndian.PutUint64(sh[8:], kind)
if n, err := conn.Write(sh[:]); err != nil {
return err
} else if n != len(sh) {
return io.ErrShortWrite
}
return nil
}
// cancelIdent reads an identifier from conn and cancels the corresponding cure.
func cancelIdent(
cache *pkg.Cache,
conn net.Conn,
) (*pkg.ID, bool, error) {
var ident pkg.ID
if _, err := io.ReadFull(conn, ident[:]); err != nil {
return nil, false, errors.Join(err, conn.Close())
}
ok := cache.Cancel(unique.Make(ident))
return &ident, ok, conn.Close()
}
// serve services connections from a [net.UnixListener].
func serve(
ctx context.Context,
log *log.Logger,
cm *cache,
ul *net.UnixListener,
) error {
ul.SetUnlinkOnClose(true)
if cm.c == nil {
if err := cm.open(); err != nil {
return errors.Join(err, ul.Close())
}
}
var wg sync.WaitGroup
defer wg.Wait()
wg.Go(func() {
for {
if ctx.Err() != nil {
break
}
conn, err := ul.AcceptUnix()
if err != nil {
if !errors.Is(err, os.ErrDeadlineExceeded) {
log.Println(err)
}
continue
}
wg.Go(func() {
done := make(chan struct{})
defer close(done)
go func() {
select {
case <-ctx.Done():
_ = conn.SetDeadline(time.Now())
case <-done:
return
}
}()
if _err := conn.SetReadDeadline(daemonDeadline()); _err != nil {
log.Println(_err)
if _err = conn.Close(); _err != nil {
log.Println(_err)
}
return
}
var word [8]byte
if _, _err := io.ReadFull(conn, word[:]); _err != nil {
log.Println(_err)
if _err = conn.Close(); _err != nil {
log.Println(_err)
}
return
}
flags := binary.LittleEndian.Uint64(word[:])
if flags == remoteSpecial {
if _, _err := io.ReadFull(conn, word[:]); _err != nil {
log.Println(_err)
if _err = conn.Close(); _err != nil {
log.Println(_err)
}
return
}
switch special := binary.LittleEndian.Uint64(word[:]); special {
default:
log.Printf("invalid special %d", special)
case specialCancel:
if id, ok, _err := cancelIdent(cm.c, conn); _err != nil {
log.Println(_err)
} else if !ok {
log.Println(
"attempting to cancel invalid artifact",
pkg.Encode(*id),
)
} else {
log.Println(
"cancelled artifact",
pkg.Encode(*id),
)
}
case specialAbort:
log.Println("aborting all pending cures")
cm.c.Abort()
if _err := conn.Close(); _err != nil {
log.Println(_err)
}
}
return
}
if a, _err := cureFromIR(cm.c, conn, flags); _err != nil {
log.Println(_err)
} else {
log.Printf(
"fulfilled artifact %s",
pkg.Encode(cm.c.Ident(a).Value()),
)
}
})
}
})
<-ctx.Done()
if err := ul.SetDeadline(time.Now()); err != nil {
return errors.Join(err, ul.Close())
}
wg.Wait()
return ul.Close()
}
// dial wraps [net.DialUnix] with a context.
func dial(ctx context.Context, addr *net.UnixAddr) (
done chan<- struct{},
conn *net.UnixConn,
err error,
) {
conn, err = net.DialUnix("unix", nil, addr)
if err != nil {
return
}
d := make(chan struct{})
done = d
go func() {
select {
case <-ctx.Done():
_ = conn.SetDeadline(time.Now())
case <-d:
return
}
}()
return
}
// cureRemote cures a [pkg.Artifact] on a daemon.
func cureRemote(
ctx context.Context,
addr *net.UnixAddr,
a pkg.Artifact,
flags uint64,
) (*check.Absolute, error) {
if flags == remoteSpecial {
return nil, syscall.EINVAL
}
done, conn, err := dial(ctx, addr)
if err != nil {
return nil, err
}
defer close(done)
if n, flagErr := conn.Write(binary.LittleEndian.AppendUint64(nil, flags)); flagErr != nil {
return nil, errors.Join(flagErr, conn.Close())
} else if n != 8 {
return nil, errors.Join(io.ErrShortWrite, conn.Close())
}
if err = pkg.NewIR().EncodeAll(conn, a); err != nil {
return nil, errors.Join(err, conn.Close())
} else if err = conn.CloseWrite(); err != nil {
return nil, errors.Join(err, conn.Close())
}
if flags&remoteNoReply != 0 {
return nil, conn.Close()
}
payload, recvErr := io.ReadAll(conn)
if err = errors.Join(recvErr, conn.Close()); err != nil {
if errors.Is(err, os.ErrDeadlineExceeded) {
if cancelErr := ctx.Err(); cancelErr != nil {
err = cancelErr
}
}
return nil, err
}
if len(payload) > 0 && payload[0] == 0 {
return nil, errors.New(string(payload[1:]))
}
var p *check.Absolute
p, err = check.NewAbs(string(payload))
return p, err
}
// cancelRemote cancels a [pkg.Artifact] curing on a daemon.
func cancelRemote(
ctx context.Context,
addr *net.UnixAddr,
a pkg.Artifact,
wait bool,
) error {
done, conn, err := dial(ctx, addr)
if err != nil {
return err
}
defer close(done)
if err = writeSpecialHeader(conn, specialCancel); err != nil {
return errors.Join(err, conn.Close())
}
var n int
id := pkg.NewIR().Ident(a).Value()
if n, err = conn.Write(id[:]); err != nil {
return errors.Join(err, conn.Close())
} else if n != len(id) {
return errors.Join(io.ErrShortWrite, conn.Close())
}
if wait {
if _, err = conn.Read(make([]byte, 1)); err == io.EOF {
err = nil
}
}
return errors.Join(err, conn.Close())
}
// abortRemote aborts all [pkg.Artifact] curing on a daemon.
func abortRemote(
ctx context.Context,
addr *net.UnixAddr,
wait bool,
) error {
done, conn, err := dial(ctx, addr)
if err != nil {
return err
}
defer close(done)
err = writeSpecialHeader(conn, specialAbort)
if wait && err == nil {
if _, err = conn.Read(make([]byte, 1)); err == io.EOF {
err = nil
}
}
return errors.Join(err, conn.Close())
}
+146
View File
@@ -0,0 +1,146 @@
package main
import (
"bytes"
"context"
"errors"
"io"
"log"
"net"
"os"
"path/filepath"
"slices"
"strings"
"testing"
"time"
"hakurei.app/check"
"hakurei.app/internal/pkg"
"hakurei.app/message"
)
func TestNoReply(t *testing.T) {
t.Parallel()
if !daemonDeadline().IsZero() {
t.Fatal("daemonDeadline did not return the zero value")
}
c, err := pkg.Open(
t.Context(),
message.New(log.New(os.Stderr, "cir: ", 0)),
0, 0, 0,
check.MustAbs(t.TempDir()),
)
if err != nil {
t.Fatalf("Open: error = %v", err)
}
defer c.Close()
client, server := net.Pipe()
done := make(chan struct{})
go func() {
defer close(done)
go func() {
<-t.Context().Done()
if _err := client.SetDeadline(time.Now()); _err != nil && !errors.Is(_err, io.ErrClosedPipe) {
panic(_err)
}
}()
if _err := c.EncodeAll(
client,
pkg.NewFile("check", []byte{0}),
); _err != nil {
panic(_err)
} else if _err = client.Close(); _err != nil {
panic(_err)
}
}()
a, cureErr := cureFromIR(c, server, remoteNoReply)
if cureErr != nil {
t.Fatalf("cureFromIR: error = %v", cureErr)
}
<-done
wantIdent := pkg.MustDecode("fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG")
if gotIdent := c.Ident(a).Value(); gotIdent != wantIdent {
t.Errorf(
"cureFromIR: %s, want %s",
pkg.Encode(gotIdent), pkg.Encode(wantIdent),
)
}
}
func TestDaemon(t *testing.T) {
t.Parallel()
var buf bytes.Buffer
logger := log.New(&buf, "daemon: ", 0)
addr := net.UnixAddr{
Name: filepath.Join(t.TempDir(), "daemon"),
Net: "unix",
}
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
cm := cache{
ctx: ctx,
msg: message.New(logger),
base: t.TempDir(),
}
defer cm.Close()
ul, err := net.ListenUnix("unix", &addr)
if err != nil {
t.Fatalf("ListenUnix: error = %v", err)
}
done := make(chan struct{})
go func() {
defer close(done)
if _err := serve(ctx, logger, &cm, ul); _err != nil {
panic(_err)
}
}()
if err = cancelRemote(ctx, &addr, pkg.NewFile("nonexistent", nil), true); err != nil {
t.Fatalf("cancelRemote: error = %v", err)
}
if err = abortRemote(ctx, &addr, true); err != nil {
t.Fatalf("abortRemote: error = %v", err)
}
// keep this last for synchronisation
var p *check.Absolute
p, err = cureRemote(ctx, &addr, pkg.NewFile("check", []byte{0}), 0)
if err != nil {
t.Fatalf("cureRemote: error = %v", err)
}
cancel()
<-done
const want = "fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG"
if got := filepath.Base(p.String()); got != want {
t.Errorf("cureRemote: %s, want %s", got, want)
}
wantLog := []string{
"",
"daemon: aborting all pending cures",
"daemon: attempting to cancel invalid artifact kQm9fmnCmXST1-MMmxzcau2oKZCXXrlZydo4PkeV5hO_2PKfeC8t98hrbV_ZZx_j",
"daemon: fulfilled artifact fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG",
}
gotLog := strings.Split(buf.String(), "\n")
slices.Sort(gotLog)
if !slices.Equal(gotLog, wantLog) {
t.Errorf(
"serve: logged\n%s\nwant\n%s",
strings.Join(gotLog, "\n"), strings.Join(wantLog, "\n"),
)
}
}
+114
View File
@@ -0,0 +1,114 @@
package main
import (
"errors"
"fmt"
"io"
"os"
"strings"
"hakurei.app/internal/pkg"
"hakurei.app/internal/rosa"
)
// commandInfo implements the info subcommand.
func commandInfo(
cm *cache,
args []string,
w io.Writer,
writeStatus bool,
r *rosa.Report,
) (err error) {
if len(args) == 0 {
return errors.New("info requires at least 1 argument")
}
// recovered by HandleAccess
mustPrintln := func(a ...any) {
if _, _err := fmt.Fprintln(w, a...); _err != nil {
panic(_err)
}
}
mustPrint := func(a ...any) {
if _, _err := fmt.Fprint(w, a...); _err != nil {
panic(_err)
}
}
for i, name := range args {
if p, ok := rosa.ResolveName(name); !ok {
return fmt.Errorf("unknown artifact %q", name)
} else {
var suffix string
if version := rosa.Std.Version(p); version != rosa.Unversioned {
suffix += "-" + version
}
mustPrintln("name : " + name + suffix)
meta := rosa.GetMetadata(p)
mustPrintln("description : " + meta.Description)
if meta.Website != "" {
mustPrintln("website : " +
strings.TrimSuffix(meta.Website, "/"))
}
if len(meta.Dependencies) > 0 {
mustPrint("depends on :")
for _, d := range meta.Dependencies {
s := rosa.GetMetadata(d).Name
if version := rosa.Std.Version(d); version != rosa.Unversioned {
s += "-" + version
}
mustPrint(" " + s)
}
mustPrintln()
}
const statusPrefix = "status : "
if writeStatus {
if r == nil {
var f io.ReadSeekCloser
err = cm.Do(func(cache *pkg.Cache) (err error) {
f, err = cache.OpenStatus(rosa.Std.Load(p))
return
})
if err != nil {
if errors.Is(err, os.ErrNotExist) {
mustPrintln(
statusPrefix + "not yet cured",
)
} else {
return
}
} else {
mustPrint(statusPrefix)
_, err = io.Copy(w, f)
if err = errors.Join(err, f.Close()); err != nil {
return
}
}
} else if err = cm.Do(func(cache *pkg.Cache) (err error) {
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
if status == nil {
mustPrintln(
statusPrefix + "not in report",
)
} else {
mustPrintln("size :", n)
mustPrint(statusPrefix)
if _, err = w.Write(status); err != nil {
return
}
}
return
}); err != nil {
return
}
}
if i != len(args)-1 {
mustPrintln()
}
}
}
return nil
}
+181
View File
@@ -0,0 +1,181 @@
package main
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"reflect"
"strings"
"syscall"
"testing"
"unsafe"
"hakurei.app/internal/pkg"
"hakurei.app/internal/rosa"
"hakurei.app/message"
)
func TestInfo(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
args []string
status map[string]string
report string
want string
wantErr any
}{
{"qemu", []string{"qemu"}, nil, "", `
name : qemu-` + rosa.Std.Version(rosa.QEMU) + `
description : a generic and open source machine emulator and virtualizer
website : https://www.qemu.org
depends on : glib-` + rosa.Std.Version(rosa.GLib) + ` zstd-` + rosa.Std.Version(rosa.Zstd) + `
`, nil},
{"multi", []string{"hakurei", "hakurei-dist"}, nil, "", `
name : hakurei-` + rosa.Std.Version(rosa.Hakurei) + `
description : low-level userspace tooling for Rosa OS
website : https://hakurei.app
name : hakurei-dist-` + rosa.Std.Version(rosa.HakureiDist) + `
description : low-level userspace tooling for Rosa OS (distribution tarball)
website : https://hakurei.app
`, nil},
{"nonexistent", []string{"zlib", "\x00"}, nil, "", `
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
description : lossless data-compression library
website : https://zlib.net
`, fmt.Errorf("unknown artifact %q", "\x00")},
{"status cache", []string{"zlib", "zstd"}, map[string]string{
"zstd": "internal/pkg (amd64) on satori\n",
"hakurei": "internal/pkg (amd64) on satori\n\n",
}, "", `
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
description : lossless data-compression library
website : https://zlib.net
status : not yet cured
name : zstd-` + rosa.Std.Version(rosa.Zstd) + `
description : a fast compression algorithm
website : https://facebook.github.io/zstd
status : internal/pkg (amd64) on satori
`, nil},
{"status cache perm", []string{"zlib"}, map[string]string{
"zlib": "\x00",
}, "", `
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
description : lossless data-compression library
website : https://zlib.net
`, func(cm *cache) error {
return &os.PathError{
Op: "open",
Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(rosa.Std.Load(rosa.Zlib)).Value())),
Err: syscall.EACCES,
}
}},
{"status report", []string{"zlib"}, nil, strings.Repeat("\x00", len(pkg.Checksum{})+8), `
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
description : lossless data-compression library
website : https://zlib.net
status : not in report
`, nil},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var (
cm *cache
buf strings.Builder
r *rosa.Report
)
if tc.status != nil || tc.report != "" {
cm = &cache{
ctx: context.Background(),
msg: message.New(log.New(os.Stderr, "info: ", 0)),
base: t.TempDir(),
}
defer cm.Close()
}
if tc.report != "" {
pathname := filepath.Join(t.TempDir(), "report")
err := os.WriteFile(
pathname,
unsafe.Slice(unsafe.StringData(tc.report), len(tc.report)),
0400,
)
if err != nil {
t.Fatal(err)
}
r, err = rosa.OpenReport(pathname)
if err != nil {
t.Fatal(err)
}
defer func() {
if err = r.Close(); err != nil {
t.Fatal(err)
}
}()
}
if tc.status != nil {
for name, status := range tc.status {
p, ok := rosa.ResolveName(name)
if !ok {
t.Fatalf("invalid name %q", name)
}
perm := os.FileMode(0400)
if status == "\x00" {
perm = 0
}
if err := cm.Do(func(cache *pkg.Cache) error {
return os.WriteFile(filepath.Join(
cm.base,
"status",
pkg.Encode(cache.Ident(rosa.Std.Load(p)).Value()),
), unsafe.Slice(unsafe.StringData(status), len(status)), perm)
}); err != nil {
t.Fatalf("Do: error = %v", err)
}
}
}
var wantErr error
switch c := tc.wantErr.(type) {
case error:
wantErr = c
case func(cm *cache) error:
wantErr = c(cm)
default:
if tc.wantErr != nil {
t.Fatalf("invalid wantErr %#v", tc.wantErr)
}
}
if err := commandInfo(
cm,
tc.args,
&buf,
cm != nil,
r,
); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("commandInfo: error = %v, want %v", err, wantErr)
}
if got := buf.String(); got != strings.TrimPrefix(tc.want, "\n") {
t.Errorf("commandInfo:\n%s\nwant\n%s", got, tc.want)
}
})
}
}
+202
View File
@@ -0,0 +1,202 @@
// Package pkgserver implements the package metadata service backend.
package pkgserver
import (
"context"
"encoding/json"
"log"
"net/http"
"net/url"
"path"
"strconv"
"sync"
"time"
"hakurei.app/internal/info"
"hakurei.app/internal/rosa"
)
// for lazy initialisation of serveInfo
var (
infoPayload struct {
// Current package count.
Count int `json:"count"`
// Hakurei version, set at link time.
HakureiVersion string `json:"hakurei_version"`
}
infoPayloadOnce sync.Once
)
// handleInfo writes constant system information.
func handleInfo(w http.ResponseWriter, _ *http.Request) {
infoPayloadOnce.Do(func() {
infoPayload.Count = int(rosa.PresetUnexportedStart)
infoPayload.HakureiVersion = info.Version()
})
// TODO(mae): cache entire response if no additional fields are planned
writeAPIPayload(w, infoPayload)
}
// newStatusHandler returns a [http.HandlerFunc] that offers status files for
// viewing or download, if available.
func (index *packageIndex) newStatusHandler(disposition bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
m, ok := index.names[path.Base(r.URL.Path)]
if !ok || !m.HasReport {
http.NotFound(w, r)
return
}
contentType := "text/plain; charset=utf-8"
if disposition {
contentType = "application/octet-stream"
// quoting like this is unsound, but okay, because metadata is hardcoded
contentDisposition := `attachment; filename="`
contentDisposition += m.Name + "-"
if m.Version != "" {
contentDisposition += m.Version + "-"
}
contentDisposition += m.ids + `.log"`
w.Header().Set("Content-Disposition", contentDisposition)
}
w.Header().Set("Content-Type", contentType)
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
if err := func() (err error) {
defer index.handleAccess(&err)()
_, err = w.Write(m.status)
return
}(); err != nil {
log.Println(err)
http.Error(
w, "cannot deliver status, contact maintainers",
http.StatusInternalServerError,
)
}
}
}
// handleGet writes a slice of metadata with specified order.
func (index *packageIndex) handleGet(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
limit, err := strconv.Atoi(q.Get("limit"))
if err != nil || limit > 100 || limit < 1 {
http.Error(
w, "limit must be an integer between 1 and 100",
http.StatusBadRequest,
)
return
}
i, err := strconv.Atoi(q.Get("index"))
if err != nil || i >= len(index.sorts[0]) || i < 0 {
http.Error(
w, "index must be an integer between 0 and "+
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
http.StatusBadRequest,
)
return
}
sort, err := strconv.Atoi(q.Get("sort"))
if err != nil || sort >= len(index.sorts) || sort < 0 {
http.Error(
w, "sort must be an integer between 0 and "+
strconv.Itoa(sortOrderEnd),
http.StatusBadRequest,
)
return
}
values := index.sorts[sort][i:min(i+limit, len(index.sorts[sort]))]
writeAPIPayload(w, &struct {
Values []*metadata `json:"values"`
}{values})
}
func (index *packageIndex) handleSearch(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
limit, err := strconv.Atoi(q.Get("limit"))
if err != nil || limit > 100 || limit < 1 {
http.Error(
w, "limit must be an integer between 1 and 100",
http.StatusBadRequest,
)
return
}
i, err := strconv.Atoi(q.Get("index"))
if err != nil || i >= len(index.sorts[0]) || i < 0 {
http.Error(
w, "index must be an integer between 0 and "+
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
http.StatusBadRequest,
)
return
}
search, err := url.QueryUnescape(q.Get("search"))
if len(search) > 100 || err != nil {
http.Error(
w, "search must be a string between 0 and 100 characters long",
http.StatusBadRequest,
)
return
}
desc := q.Get("desc") == "true"
n, res, err := index.performSearchQuery(limit, i, search, desc)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
writeAPIPayload(w, &struct {
Count int `json:"count"`
Values []searchResult `json:"values"`
}{n, res})
}
// apiVersion is the name of the current API revision, as part of the pattern.
const apiVersion = "v1"
// registerAPI registers API handler functions.
func (index *packageIndex) registerAPI(mux *http.ServeMux) {
mux.HandleFunc("GET /api/"+apiVersion+"/info", handleInfo)
mux.HandleFunc("GET /api/"+apiVersion+"/get", index.handleGet)
mux.HandleFunc("GET /api/"+apiVersion+"/search", index.handleSearch)
mux.HandleFunc("GET /api/"+apiVersion+"/status/", index.newStatusHandler(false))
mux.HandleFunc("GET /status/", index.newStatusHandler(true))
}
// Register arranges for mux to service API requests.
func Register(ctx context.Context, mux *http.ServeMux, report *rosa.Report) error {
var index packageIndex
index.search = make(searchCache)
if err := index.populate(report); err != nil {
return err
}
ticker := time.NewTicker(1 * time.Minute)
go func() {
for {
select {
case <-ctx.Done():
ticker.Stop()
return
case <-ticker.C:
index.search.clean()
}
}
}()
index.registerAPI(mux)
return nil
}
// writeAPIPayload sets headers common to API responses and encodes payload as
// JSON for the response body.
func writeAPIPayload(w http.ResponseWriter, payload any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
if err := json.NewEncoder(w).Encode(payload); err != nil {
log.Println(err)
http.Error(
w, "cannot encode payload, contact maintainers",
http.StatusInternalServerError,
)
}
}
+181
View File
@@ -0,0 +1,181 @@
package pkgserver
import (
"net/http"
"net/http/httptest"
"slices"
"strconv"
"testing"
"hakurei.app/internal/info"
"hakurei.app/internal/rosa"
)
// prefix is prepended to every API path.
const prefix = "/api/" + apiVersion + "/"
func TestAPIInfo(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
handleInfo(w, httptest.NewRequestWithContext(
t.Context(),
http.MethodGet,
prefix+"info",
nil,
))
resp := w.Result()
checkStatus(t, resp, http.StatusOK)
checkAPIHeader(t, w.Header())
checkPayload(t, resp, struct {
Count int `json:"count"`
HakureiVersion string `json:"hakurei_version"`
}{int(rosa.PresetUnexportedStart), info.Version()})
}
func TestAPIGet(t *testing.T) {
t.Parallel()
const target = prefix + "get"
index := newIndex(t)
newRequest := func(suffix string) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
index.handleGet(w, httptest.NewRequestWithContext(
t.Context(),
http.MethodGet,
target+suffix,
nil,
))
return w
}
checkValidate := func(t *testing.T, suffix string, vmin, vmax int, wantErr string) {
t.Run("invalid", func(t *testing.T) {
t.Parallel()
w := newRequest("?" + suffix + "=invalid")
resp := w.Result()
checkError(t, resp, wantErr, http.StatusBadRequest)
})
t.Run("min", func(t *testing.T) {
t.Parallel()
w := newRequest("?" + suffix + "=" + strconv.Itoa(vmin-1))
resp := w.Result()
checkError(t, resp, wantErr, http.StatusBadRequest)
w = newRequest("?" + suffix + "=" + strconv.Itoa(vmin))
resp = w.Result()
checkStatus(t, resp, http.StatusOK)
})
t.Run("max", func(t *testing.T) {
t.Parallel()
w := newRequest("?" + suffix + "=" + strconv.Itoa(vmax+1))
resp := w.Result()
checkError(t, resp, wantErr, http.StatusBadRequest)
w = newRequest("?" + suffix + "=" + strconv.Itoa(vmax))
resp = w.Result()
checkStatus(t, resp, http.StatusOK)
})
}
t.Run("limit", func(t *testing.T) {
t.Parallel()
checkValidate(
t, "index=0&sort=0&limit", 1, 100,
"limit must be an integer between 1 and 100",
)
})
t.Run("index", func(t *testing.T) {
t.Parallel()
checkValidate(
t, "limit=1&sort=0&index", 0, int(rosa.PresetUnexportedStart-1),
"index must be an integer between 0 and "+strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
)
})
t.Run("sort", func(t *testing.T) {
t.Parallel()
checkValidate(
t, "index=0&limit=1&sort", 0, int(sortOrderEnd),
"sort must be an integer between 0 and "+strconv.Itoa(int(sortOrderEnd)),
)
})
checkWithSuffix := func(name, suffix string, want []*metadata) {
t.Run(name, func(t *testing.T) {
t.Parallel()
w := newRequest(suffix)
resp := w.Result()
checkStatus(t, resp, http.StatusOK)
checkAPIHeader(t, w.Header())
checkPayloadFunc(t, resp, func(got *struct {
Values []*metadata `json:"values"`
}) bool {
return slices.EqualFunc(got.Values, want, func(a, b *metadata) bool {
return (a.Version == b.Version ||
a.Version == rosa.Unversioned ||
b.Version == rosa.Unversioned) &&
a.HasReport == b.HasReport &&
a.Name == b.Name &&
a.Description == b.Description &&
a.Website == b.Website
})
})
})
}
checkWithSuffix("declarationAscending", "?limit=2&index=1&sort=0", []*metadata{
{
Metadata: rosa.GetMetadata(1),
Version: rosa.Std.Version(1),
},
{
Metadata: rosa.GetMetadata(2),
Version: rosa.Std.Version(2),
},
})
checkWithSuffix("declarationAscending offset", "?limit=3&index=5&sort=0", []*metadata{
{
Metadata: rosa.GetMetadata(5),
Version: rosa.Std.Version(5),
},
{
Metadata: rosa.GetMetadata(6),
Version: rosa.Std.Version(6),
},
{
Metadata: rosa.GetMetadata(7),
Version: rosa.Std.Version(7),
},
})
checkWithSuffix("declarationDescending", "?limit=3&index=0&sort=1", []*metadata{
{
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 1),
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 1),
},
{
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 2),
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 2),
},
{
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 3),
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 3),
},
})
checkWithSuffix("declarationDescending offset", "?limit=1&index=37&sort=1", []*metadata{
{
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 38),
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 38),
},
})
}
+106
View File
@@ -0,0 +1,106 @@
package pkgserver
import (
"cmp"
"errors"
"slices"
"strings"
"hakurei.app/internal/pkg"
"hakurei.app/internal/rosa"
)
const (
declarationAscending = iota
declarationDescending
nameAscending
nameDescending
sizeAscending
sizeDescending
sortOrderEnd = iota - 1
)
// packageIndex refers to metadata by name and various sort orders.
type packageIndex struct {
sorts [sortOrderEnd + 1][rosa.PresetUnexportedStart]*metadata
names map[string]*metadata
search searchCache
// Taken from [rosa.Report] if available.
handleAccess func(*error) func()
}
// metadata holds [rosa.Metadata] extended with additional information.
type metadata struct {
p rosa.PArtifact
*rosa.Metadata
// Populated via [rosa.Toolchain.Version], [rosa.Unversioned] is equivalent
// to the zero value. Otherwise, the zero value is invalid.
Version string `json:"version,omitempty"`
// Output data size, available if present in report.
Size int64 `json:"size,omitempty"`
// Whether the underlying [pkg.Artifact] is present in the report.
HasReport bool `json:"report"`
// Ident string encoded ahead of time.
ids string
// Backed by [rosa.Report], access must be prepared by HandleAccess.
status []byte
}
// populate deterministically populates packageIndex, optionally with a report.
func (index *packageIndex) populate(report *rosa.Report) (err error) {
if report != nil {
defer report.HandleAccess(&err)()
index.handleAccess = report.HandleAccess
}
var work [rosa.PresetUnexportedStart]*metadata
index.names = make(map[string]*metadata)
ir := pkg.NewIR()
for p := range rosa.PresetUnexportedStart {
m := metadata{
p: p,
Metadata: rosa.GetMetadata(p),
Version: rosa.Std.Version(p),
}
if m.Version == "" {
return errors.New("invalid version from " + m.Name)
}
if m.Version == rosa.Unversioned {
m.Version = ""
}
if report != nil {
id := ir.Ident(rosa.Std.Load(p))
m.ids = pkg.Encode(id.Value())
m.status, m.Size = report.ArtifactOf(id)
m.HasReport = m.Size >= 0
}
work[p] = &m
index.names[m.Name] = &m
}
index.sorts[declarationAscending] = work
index.sorts[declarationDescending] = work
slices.Reverse(index.sorts[declarationDescending][:])
index.sorts[nameAscending] = work
slices.SortFunc(index.sorts[nameAscending][:], func(a, b *metadata) int {
return strings.Compare(a.Name, b.Name)
})
index.sorts[nameDescending] = index.sorts[nameAscending]
slices.Reverse(index.sorts[nameDescending][:])
index.sorts[sizeAscending] = work
slices.SortFunc(index.sorts[sizeAscending][:], func(a, b *metadata) int {
return cmp.Compare(a.Size, b.Size)
})
index.sorts[sizeDescending] = index.sorts[sizeAscending]
slices.Reverse(index.sorts[sizeDescending][:])
return
}
+96
View File
@@ -0,0 +1,96 @@
package pkgserver
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"reflect"
"testing"
)
// newIndex returns the address of a newly populated packageIndex.
func newIndex(t *testing.T) *packageIndex {
t.Helper()
var index packageIndex
if err := index.populate(nil); err != nil {
t.Fatalf("populate: error = %v", err)
}
return &index
}
// checkStatus checks response status code.
func checkStatus(t *testing.T, resp *http.Response, want int) {
t.Helper()
if resp.StatusCode != want {
t.Errorf(
"StatusCode: %s, want %s",
http.StatusText(resp.StatusCode),
http.StatusText(want),
)
}
}
// checkHeader checks the value of a header entry.
func checkHeader(t *testing.T, h http.Header, key, want string) {
t.Helper()
if got := h.Get(key); got != want {
t.Errorf("%s: %q, want %q", key, got, want)
}
}
// checkAPIHeader checks common entries set for API endpoints.
func checkAPIHeader(t *testing.T, h http.Header) {
t.Helper()
checkHeader(t, h, "Content-Type", "application/json; charset=utf-8")
checkHeader(t, h, "Cache-Control", "no-cache, no-store, must-revalidate")
checkHeader(t, h, "Pragma", "no-cache")
checkHeader(t, h, "Expires", "0")
}
// checkPayloadFunc checks the JSON response of an API endpoint by passing it to f.
func checkPayloadFunc[T any](
t *testing.T,
resp *http.Response,
f func(got *T) bool,
) {
t.Helper()
var got T
r := io.Reader(resp.Body)
if testing.Verbose() {
var buf bytes.Buffer
r = io.TeeReader(r, &buf)
defer func() { t.Helper(); t.Log(buf.String()) }()
}
if err := json.NewDecoder(r).Decode(&got); err != nil {
t.Fatalf("Decode: error = %v", err)
}
if !f(&got) {
t.Errorf("Body: %#v", got)
}
}
// checkPayload checks the JSON response of an API endpoint.
func checkPayload[T any](t *testing.T, resp *http.Response, want T) {
t.Helper()
checkPayloadFunc(t, resp, func(got *T) bool {
return reflect.DeepEqual(got, &want)
})
}
func checkError(t *testing.T, resp *http.Response, error string, code int) {
t.Helper()
checkStatus(t, resp, code)
if got, _ := io.ReadAll(resp.Body); string(got) != fmt.Sprintln(error) {
t.Errorf("Body: %q, want %q", string(got), error)
}
}
+81
View File
@@ -0,0 +1,81 @@
package pkgserver
import (
"cmp"
"maps"
"regexp"
"slices"
"time"
)
type searchCache map[string]searchCacheEntry
type searchResult struct {
NameIndices [][]int `json:"name_matches"`
DescIndices [][]int `json:"desc_matches,omitempty"`
Score float64 `json:"score"`
*metadata
}
type searchCacheEntry struct {
query string
results []searchResult
expiry time.Time
}
func (index *packageIndex) performSearchQuery(limit int, i int, search string, desc bool) (int, []searchResult, error) {
query := search
if desc {
query += ";withDesc"
}
entry, ok := index.search[query]
if ok && len(entry.results) > 0 {
return len(entry.results), entry.results[min(i, len(entry.results)-1):min(i+limit, len(entry.results))], nil
}
regex, err := regexp.Compile(search)
if err != nil {
return 0, make([]searchResult, 0), err
}
res := make([]searchResult, 0)
for p := range maps.Values(index.names) {
nameIndices := regex.FindAllIndex([]byte(p.Name), -1)
var descIndices [][]int = nil
if desc {
descIndices = regex.FindAllIndex([]byte(p.Description), -1)
}
if nameIndices == nil && descIndices == nil {
continue
}
score := float64(indexsum(nameIndices)) / (float64(len(nameIndices)) + 1)
if desc {
score += float64(indexsum(descIndices)) / (float64(len(descIndices)) + 1) / 10.0
}
res = append(res, searchResult{
NameIndices: nameIndices,
DescIndices: descIndices,
Score: score,
metadata: p,
})
}
slices.SortFunc(res[:], func(a, b searchResult) int { return -cmp.Compare(a.Score, b.Score) })
expiry := time.Now().Add(1 * time.Minute)
entry = searchCacheEntry{
query: search,
results: res,
expiry: expiry,
}
index.search[query] = entry
return len(res), res[i:min(i+limit, len(entry.results))], nil
}
func (s *searchCache) clean() {
maps.DeleteFunc(*s, func(_ string, v searchCacheEntry) bool {
return v.expiry.Before(time.Now())
})
}
func indexsum(in [][]int) int {
sum := 0
for i := 0; i < len(in); i++ {
sum += in[i][1] - in[i][0]
}
return sum
}
+57
View File
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Hakurei PkgServer</title>
<script src="index.js"></script>
</head>
<body>
<h1>Hakurei PkgServer</h1>
<div class="top-controls" id="top-controls-regular">
<p>Showing entries <span id="entry-counter"></span>.</p>
<span id="search-bar">
<label for="search">Search: </label>
<input type="text" name="search" id="search"/>
<button onclick="doSearch()">Find</button>
<label for="include-desc">Include descriptions: </label>
<input type="checkbox" name="include-desc" id="include-desc" checked/>
</span>
<div><label for="count">Entries per page: </label><select name="count" id="count">
<option value="10">10</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="50">50</option>
</select></div>
<div><label for="sort">Sort by: </label><select name="sort" id="sort">
<option value="0">Definition (ascending)</option>
<option value="1">Definition (descending)</option>
<option value="2">Name (ascending)</option>
<option value="3">Name (descending)</option>
<option value="4">Size (ascending)</option>
<option value="5">Size (descending)</option>
</select></div>
</div>
<div class="top-controls" id="search-top-controls" hidden>
<p>Showing search results <span id="search-entry-counter"></span> for query "<span id="search-query"></span>".</p>
<button onclick="exitSearch()">Back</button>
<div><label for="search-count">Entries per page: </label><select name="search-count" id="search-count">
<option value="10">10</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="50">50</option>
</select></div>
<p>Sorted by best match</p>
</div>
<div class="page-controls"><a href="javascript:prevPage()">&laquo; Previous</a> <input type="text" class="page-number" value="1"/> <a href="javascript:nextPage()">Next &raquo;</a></div>
<table id="pkg-list">
<tr><td>Loading...</td></tr>
</table>
<div class="page-controls"><a href="javascript:prevPage()">&laquo; Previous</a> <input type="text" class="page-number" value="1"/> <a href="javascript:nextPage()">Next &raquo;</a></div>
<footer>
<p>&copy;<a href="https://hakurei.app/">Hakurei</a> (<span id="hakurei-version">unknown</span>). Licensed under the MIT license.</p>
</footer>
<script>main();</script>
</body>
</html>
+331
View File
@@ -0,0 +1,331 @@
interface PackageIndexEntry {
name: string
size?: number
description?: string
website?: string
version?: string
report?: boolean
}
function entryToHTML(entry: PackageIndexEntry | SearchResult): HTMLTableRowElement {
let v = entry.version != null ? `<span>${escapeHtml(entry.version)}</span>` : ""
let s = entry.size != null && entry.size > 0 ? `<p>Size: ${toByteSizeString(entry.size)} (${entry.size})</p>` : ""
let n: string
let d: string
if ('name_matches' in entry) {
n = `<h2>${nameMatches(entry as SearchResult)} ${v}</h2>`
} else {
n = `<h2>${escapeHtml(entry.name)} ${v}</h2>`
}
if ('desc_matches' in entry && STATE.getIncludeDescriptions()) {
d = descMatches(entry as SearchResult)
} else {
d = (entry as PackageIndexEntry).description != null ? `<p>${escapeHtml((entry as PackageIndexEntry).description)}</p>` : ""
}
let w = entry.website != null ? `<a href="${encodeURI(entry.website)}">Website</a>` : ""
let r = entry.report ? `Log (<a href=\"${encodeURI('/api/v1/status/' + entry.name)}\">View</a> | <a href=\"${encodeURI('/status/' + entry.name)}\">Download</a>)` : ""
let row = <HTMLTableRowElement>(document.createElement('tr'))
row.innerHTML = `<td>
${n}
${d}
${s}
${w}
${r}
</td>`
return row
}
function nameMatches(sr: SearchResult): string {
return markMatches(sr.name, sr.name_matches)
}
function descMatches(sr: SearchResult): string {
return markMatches(sr.description!, sr.desc_matches)
}
function markMatches(str: string, indices: [number, number][]): string {
if (indices == null) {
return str
}
let out: string = ""
let j = 0
for (let i = 0; i < str.length; i++) {
if (j < indices.length) {
if (i === indices[j][0]) {
out += `<mark>${escapeHtmlChar(str[i])}`
continue
}
if (i === indices[j][1]) {
out += `</mark>${escapeHtmlChar(str[i])}`
j++
continue
}
}
out += escapeHtmlChar(str[i])
}
if (indices[j] !== undefined) {
out += "</mark>"
}
return out
}
function toByteSizeString(bytes: number): string {
if (bytes == null) return `unspecified`
if (bytes < 1024) return `${bytes}B`
if (bytes < Math.pow(1024, 2)) return `${(bytes / 1024).toFixed(2)}kiB`
if (bytes < Math.pow(1024, 3)) return `${(bytes / Math.pow(1024, 2)).toFixed(2)}MiB`
if (bytes < Math.pow(1024, 4)) return `${(bytes / Math.pow(1024, 3)).toFixed(2)}GiB`
if (bytes < Math.pow(1024, 5)) return `${(bytes / Math.pow(1024, 4)).toFixed(2)}TiB`
return "not only is it big, it's large"
}
const API_VERSION = 1
const ENDPOINT = `/api/v${API_VERSION}`
interface InfoPayload {
count?: number
hakurei_version?: string
}
async function infoRequest(): Promise<InfoPayload> {
const res = await fetch(`${ENDPOINT}/info`)
const payload = await res.json()
return payload as InfoPayload
}
interface GetPayload {
values?: PackageIndexEntry[]
}
enum SortOrders {
DeclarationAscending,
DeclarationDescending,
NameAscending,
NameDescending
}
async function getRequest(limit: number, index: number, sort: SortOrders): Promise<GetPayload> {
const res = await fetch(`${ENDPOINT}/get?limit=${limit}&index=${index}&sort=${sort.valueOf()}`)
const payload = await res.json()
return payload as GetPayload
}
interface SearchResult extends PackageIndexEntry {
name_matches: [number, number][]
desc_matches: [number, number][]
score: number
}
interface SearchPayload {
count?: number
values?: SearchResult[]
}
async function searchRequest(limit: number, index: number, search: string, desc: boolean): Promise<SearchPayload> {
const res = await fetch(`${ENDPOINT}/search?limit=${limit}&index=${index}&search=${encodeURIComponent(search)}&desc=${desc}`)
if (!res.ok) {
exitSearch()
alert("invalid search query!")
return Promise.reject(res.statusText)
}
const payload = await res.json()
return payload as SearchPayload
}
class State {
entriesPerPage: number = 10
entryIndex: number = 0
maxTotal: number = 0
maxEntries: number = 0
sort: SortOrders = SortOrders.DeclarationAscending
search: boolean = false
getEntriesPerPage(): number {
return this.entriesPerPage
}
setEntriesPerPage(entriesPerPage: number) {
this.entriesPerPage = entriesPerPage
this.setEntryIndex(Math.floor(this.getEntryIndex() / entriesPerPage) * entriesPerPage)
}
getEntryIndex(): number {
return this.entryIndex
}
setEntryIndex(entryIndex: number) {
this.entryIndex = entryIndex
this.updatePage()
this.updateRange()
this.updateListings()
}
getMaxTotal(): number {
return this.maxTotal
}
setMaxTotal(max: number) {
this.maxTotal = max
}
getSortOrder(): SortOrders {
return this.sort
}
setSortOrder(sortOrder: SortOrders) {
this.sort = sortOrder
this.setEntryIndex(0)
}
updatePage() {
let page = Math.ceil(((this.getEntryIndex() + this.getEntriesPerPage()) - 1) / this.getEntriesPerPage())
for (let e of document.getElementsByClassName("page-number")) {
(e as HTMLInputElement).value = String(page)
}
}
updateRange() {
let max = Math.min(this.getEntryIndex() + this.getEntriesPerPage(), this.getMaxTotal())
document.getElementById("entry-counter")!.textContent = `${this.getEntryIndex() + 1}-${max} of ${this.getMaxTotal()}`
if (this.search) {
document.getElementById("search-entry-counter")!.textContent = `${this.getEntryIndex() + 1}-${max} of ${this.maxTotal}/${this.maxEntries}`
document.getElementById("search-query")!.innerHTML = `<code>${escapeHtml(this.getSearchQuery())}</code>`
}
}
getSearchQuery(): string {
let queryString = document.getElementById("search")!;
return (queryString as HTMLInputElement).value
}
getIncludeDescriptions(): boolean {
let includeDesc = document.getElementById("include-desc")!;
return (includeDesc as HTMLInputElement).checked
}
updateListings() {
if (this.search) {
searchRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSearchQuery(), this.getIncludeDescriptions())
.then(res => {
let table = document.getElementById("pkg-list")!
table.innerHTML = ''
for (let row of res.values!) {
table.appendChild(entryToHTML(row))
}
STATE.maxTotal = res.count!
STATE.updateRange()
if(res.count! < 1) {
exitSearch()
alert("no results found!")
}
})
} else {
getRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSortOrder())
.then(res => {
let table = document.getElementById("pkg-list")!
table.innerHTML = ''
for (let row of res.values!) {
table.appendChild(entryToHTML(row))
}
})
}
}
}
let STATE: State
function lastPageIndex(): number {
return Math.floor(STATE.getMaxTotal() / STATE.getEntriesPerPage()) * STATE.getEntriesPerPage()
}
function setPage(page: number) {
STATE.setEntryIndex(Math.max(0, Math.min(STATE.getEntriesPerPage() * (page - 1), lastPageIndex())))
}
function escapeHtml(str?: string): string {
let out: string = ''
if (str == undefined) return ""
for (let i = 0; i < str.length; i++) {
out += escapeHtmlChar(str[i])
}
return out
}
function escapeHtmlChar(char: string): string {
if (char.length != 1) return char
switch (char[0]) {
case '&':
return "&amp;"
case '<':
return "&lt;"
case '>':
return "&gt;"
case '"':
return "&quot;"
case "'":
return "&apos;"
default:
return char
}
}
function firstPage() {
STATE.setEntryIndex(0)
}
function prevPage() {
let index = STATE.getEntryIndex()
STATE.setEntryIndex(Math.max(0, index - STATE.getEntriesPerPage()))
}
function lastPage() {
STATE.setEntryIndex(lastPageIndex())
}
function nextPage() {
let index = STATE.getEntryIndex()
STATE.setEntryIndex(Math.min(lastPageIndex(), index + STATE.getEntriesPerPage()))
}
function doSearch() {
document.getElementById("top-controls-regular")!.toggleAttribute("hidden");
document.getElementById("search-top-controls")!.toggleAttribute("hidden");
STATE.search = true;
STATE.setEntryIndex(0);
}
function exitSearch() {
document.getElementById("top-controls-regular")!.toggleAttribute("hidden");
document.getElementById("search-top-controls")!.toggleAttribute("hidden");
STATE.search = false;
STATE.setMaxTotal(STATE.maxEntries)
STATE.setEntryIndex(0)
}
function main() {
STATE = new State()
infoRequest()
.then(res => {
STATE.maxEntries = res.count!
STATE.setMaxTotal(STATE.maxEntries)
document.getElementById("hakurei-version")!.textContent = res.hakurei_version!
STATE.updateRange()
STATE.updateListings()
})
for (let e of document.getElementsByClassName("page-number")) {
e.addEventListener("change", (_) => {
setPage(parseInt((e as HTMLInputElement).value))
})
}
document.getElementById("count")?.addEventListener("change", (event) => {
STATE.setEntriesPerPage(parseInt((event.target as HTMLSelectElement).value))
})
document.getElementById("sort")?.addEventListener("change", (event) => {
STATE.setSortOrder(parseInt((event.target as HTMLSelectElement).value))
})
document.getElementById("search")?.addEventListener("keyup", (event) => {
if (event.key === 'Enter') doSearch()
})
}
+21
View File
@@ -0,0 +1,21 @@
.page-number {
width: 2em;
text-align: center;
}
.page-number {
width: 2em;
text-align: center;
}
@media (prefers-color-scheme: dark) {
html {
background-color: #2c2c2c;
color: ghostwhite;
}
}
@media (prefers-color-scheme: light) {
html {
background-color: #d3d3d3;
color: black;
}
}
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "ES2024",
"strict": true,
"alwaysStrict": true,
"outDir": "static"
}
}
+9
View File
@@ -0,0 +1,9 @@
// Package ui holds the static web UI.
package ui
import "net/http"
// Register arranges for mux to serve the embedded frontend.
func Register(mux *http.ServeMux) {
mux.Handle("GET /", http.FileServer(http.FS(static)))
}
+21
View File
@@ -0,0 +1,21 @@
//go:build frontend
package ui
import (
"embed"
"io/fs"
)
//go:generate tsc
//go:generate cp index.html style.css static
//go:embed static
var _static embed.FS
var static = func() fs.FS {
if f, err := fs.Sub(_static, "static"); err != nil {
panic(err)
} else {
return f
}
}()
+7
View File
@@ -0,0 +1,7 @@
//go:build !frontend
package ui
import "testing/fstest"
var static fstest.MapFS
+368 -160
View File
@@ -1,32 +1,50 @@
// The mbf program is a frontend for [hakurei.app/internal/rosa].
//
// This program is not covered by the compatibility promise. The command line
// interface, available packages and their behaviour, and even the on-disk
// format, may change at any time.
//
// # Name
//
// The name mbf stands for maiden's best friend, as a tribute to the DOOM source
// port of [the same name]. This name is a placeholder and is subject to change.
//
// [the same name]: https://www.doomwiki.org/wiki/MBF
package main package main
import ( import (
"context" "context"
"crypto/sha512"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log" "log"
"net"
"net/http"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
"time" "time"
"unique" "unique"
"hakurei.app/check"
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/ext"
"hakurei.app/fhs"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
"hakurei.app/internal/rosa" "hakurei.app/internal/rosa"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/cmd/mbf/internal/pkgserver"
"hakurei.app/cmd/mbf/internal/pkgserver/ui"
) )
func main() { func main() {
@@ -40,77 +58,141 @@ func main() {
log.Fatal("this program must not run as root") log.Fatal("this program must not run as root")
} }
var cache *pkg.Cache
ctx, stop := signal.NotifyContext(context.Background(), ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
defer stop() defer stop()
defer func() {
if cache != nil {
cache.Close()
}
if r := recover(); r != nil { var cm cache
fmt.Println(r) defer func() { cm.Close() }()
log.Fatal("consider scrubbing the on-disk cache")
}
}()
var ( var (
flagQuiet bool flagQuiet bool
flagCures int flagQEMU bool
flagBase string flagArch string
flagTShift int flagCheck bool
flagIdle bool flagLTO bool
flagCrossOverride int
addr net.UnixAddr
) )
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) { c := command.New(os.Stderr, log.Printf, "mbf", func([]string) error {
msg.SwapVerbose(!flagQuiet) msg.SwapVerbose(!flagQuiet)
cm.ctx, cm.msg = ctx, msg
flagBase = os.ExpandEnv(flagBase) cm.base = os.ExpandEnv(cm.base)
if flagBase == "" { if cm.base == "" {
flagBase = "cache" cm.base = "cache"
} }
var base *check.Absolute addr.Net = "unix"
if flagBase, err = filepath.Abs(flagBase); err != nil { addr.Name = os.ExpandEnv(addr.Name)
return if addr.Name == "" {
} else if base, err = check.NewAbs(flagBase); err != nil { addr.Name = filepath.Join(cm.base, "daemon")
return
} }
if cache, err = pkg.Open(ctx, msg, flagCures, base); err == nil {
if flagTShift < 0 { var flags int
cache.SetThreshold(0) if !flagCheck {
} else if flagTShift > 31 { flags |= rosa.OptSkipCheck
cache.SetThreshold(1 << 31) }
} else { if !flagLTO {
cache.SetThreshold(1 << flagTShift) flags |= rosa.OptLLVMNoLTO
}
rosa.DropCaches("", flags)
cross := flagArch != "" && flagArch != runtime.GOARCH
if flagQEMU || cross {
cm.qemu = rosa.Std.Load(rosa.QEMU)
}
if cross {
if flagCrossOverride != -1 {
flags = flagCrossOverride
}
rosa.DropCaches(flagArch, flags)
if !rosa.HasStage0() {
return pkg.UnsupportedArchError(flagArch)
} }
} }
if flagIdle { return nil
pkg.SchedPolicy = container.SCHED_IDLE
}
return
}).Flag( }).Flag(
&flagQuiet, &flagQuiet,
"q", command.BoolFlag(false), "q", command.BoolFlag(false),
"Do not print cure messages", "Do not print cure messages",
).Flag( ).Flag(
&flagCures, &flagQEMU,
"register", command.BoolFlag(false),
"Enable additional target architectures",
).Flag(
&flagArch,
"arch", command.StringFlag(runtime.GOARCH),
"Target architecture",
).Flag(
&flagLTO,
"lto", command.BoolFlag(false),
"Enable LTO in stage2 and stage3 LLVM toolchains",
).Flag(
&flagCheck,
"check", command.BoolFlag(true),
"Run test suites",
).Flag(
&flagCrossOverride,
"cross-flags", command.IntFlag(-1),
"Override non-native target preset flags",
).Flag(
&cm.verboseInit,
"v", command.BoolFlag(false),
"Do not suppress verbose output from init",
).Flag(
&cm.cures,
"cures", command.IntFlag(0), "cures", command.IntFlag(0),
"Maximum number of dependencies to cure at any given time", "Maximum number of dependencies to cure at any given time",
).Flag( ).Flag(
&flagBase, &cm.jobs,
"jobs", command.IntFlag(0),
"Preferred number of jobs to run, when applicable",
).Flag(
&cm.base,
"d", command.StringFlag("$MBF_CACHE_DIR"), "d", command.StringFlag("$MBF_CACHE_DIR"),
"Directory to store cured artifacts", "Directory to store cured artifacts",
).Flag( ).Flag(
&flagTShift, &cm.idle,
"tshift", command.IntFlag(-1),
"Dependency graph size exponent, to the power of 2",
).Flag(
&flagIdle,
"sched-idle", command.BoolFlag(false), "sched-idle", command.BoolFlag(false),
"Set SCHED_IDLE scheduling policy", "Set SCHED_IDLE scheduling policy",
).Flag(
&cm.hostAbstract,
"host-abstract", command.BoolFlag(
os.Getenv("MBF_HOST_ABSTRACT") != "",
),
"Do not restrict networked cure containers from connecting to host "+
"abstract UNIX sockets",
).Flag(
&addr.Name,
"socket", command.StringFlag("$MBF_DAEMON_SOCKET"),
"Pathname of socket to bind to",
)
c.NewCommand(
"checksum", "Compute checksum of data read from standard input",
func([]string) error {
done := make(chan struct{})
defer close(done)
go func() {
select {
case <-ctx.Done():
os.Exit(1)
case <-done:
return
}
}()
h := sha512.New384()
if _, err := io.Copy(h, os.Stdin); err != nil {
return err
}
log.Println(pkg.Encode(pkg.Checksum(h.Sum(nil))))
return nil
},
) )
{ {
@@ -124,7 +206,9 @@ func main() {
if flagShifts < 0 || flagShifts > 31 { if flagShifts < 0 || flagShifts > 31 {
flagShifts = 12 flagShifts = 12
} }
return cm.Do(func(cache *pkg.Cache) error {
return cache.Scrub(runtime.NumCPU() << flagShifts) return cache.Scrub(runtime.NumCPU() << flagShifts)
})
}, },
).Flag( ).Flag(
&flagShifts, &flagShifts,
@@ -135,6 +219,7 @@ func main() {
{ {
var ( var (
flagBind string
flagStatus bool flagStatus bool
flagReport string flagReport string
) )
@@ -142,9 +227,7 @@ func main() {
"info", "info",
"Display out-of-band metadata of an artifact", "Display out-of-band metadata of an artifact",
func(args []string) (err error) { func(args []string) (err error) {
if len(args) == 0 { const shutdownTimeout = 15 * time.Second
return errors.New("info requires at least 1 argument")
}
var r *rosa.Report var r *rosa.Report
if flagReport != "" { if flagReport != "" {
@@ -159,73 +242,42 @@ func main() {
defer r.HandleAccess(&err)() defer r.HandleAccess(&err)()
} }
for i, name := range args { if flagBind == "" {
if p, ok := rosa.ResolveName(name); !ok { return commandInfo(&cm, args, os.Stdout, flagStatus, r)
return fmt.Errorf("unknown artifact %q", name)
} else {
var suffix string
if version := rosa.Std.Version(p); version != rosa.Unversioned {
suffix += "-" + version
}
fmt.Println("name : " + name + suffix)
meta := rosa.GetMetadata(p)
fmt.Println("description : " + meta.Description)
if meta.Website != "" {
fmt.Println("website : " +
strings.TrimSuffix(meta.Website, "/"))
} }
const statusPrefix = "status : " var mux http.ServeMux
if flagStatus { ui.Register(&mux)
if r == nil { if err = pkgserver.Register(ctx, &mux, r); err != nil {
var f io.ReadSeekCloser
f, err = cache.OpenStatus(rosa.Std.Load(p))
if err != nil {
if errors.Is(err, os.ErrNotExist) {
fmt.Println(
statusPrefix + "not yet cured",
)
} else {
return return
} }
} else {
fmt.Print(statusPrefix)
_, err = io.Copy(os.Stdout, f)
if err = errors.Join(err, f.Close()); err != nil {
return
}
}
} else {
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
if status == nil {
fmt.Println(
statusPrefix + "not in report",
)
} else {
fmt.Println("size :", n)
fmt.Print(statusPrefix)
if _, err = os.Stdout.Write(status); err != nil {
return
}
}
}
}
if i != len(args)-1 { server := http.Server{Addr: flagBind, Handler: &mux}
fmt.Println() go func() {
<-ctx.Done()
cc, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancel()
if _err := server.Shutdown(cc); _err != nil {
log.Fatal(_err)
} }
}()
msg.Verbosef("listening on %q", flagBind)
err = server.ListenAndServe()
if errors.Is(err, http.ErrServerClosed) {
err = nil
} }
} return
return nil
}, },
). ).Flag(
Flag( &flagBind,
"bind", command.StringFlag(""),
"TCP address for the server to listen on",
).Flag(
&flagStatus, &flagStatus,
"status", command.BoolFlag(false), "status", command.BoolFlag(false),
"Display cure status if available", "Display cure status if available",
). ).Flag(
Flag(
&flagReport, &flagReport,
"report", command.StringFlag(""), "report", command.StringFlag(""),
"Load cure status from this report file instead of cache", "Load cure status from this report file instead of cache",
@@ -260,10 +312,12 @@ func main() {
return errors.New("report requires 1 argument") return errors.New("report requires 1 argument")
} }
if container.Isatty(int(w.Fd())) { if ext.Isatty(int(w.Fd())) {
return errors.New("output appears to be a terminal") return errors.New("output appears to be a terminal")
} }
return cm.Do(func(cache *pkg.Cache) error {
return rosa.WriteReport(msg, w, cache) return rosa.WriteReport(msg, w, cache)
})
}, },
) )
@@ -326,14 +380,26 @@ func main() {
" package(s) are out of date")) " package(s) are out of date"))
} }
return errors.Join(errs...) return errors.Join(errs...)
}). }).Flag(
Flag(
&flagJobs, &flagJobs,
"j", command.IntFlag(32), "j", command.IntFlag(32),
"Maximum number of simultaneous connections", "Maximum number of simultaneous connections",
) )
} }
c.NewCommand(
"daemon",
"Service artifact IR with Rosa OS extensions",
func(args []string) error {
ul, err := net.ListenUnix("unix", &addr)
if err != nil {
return err
}
log.Printf("listening on pathname socket at %s", addr.Name)
return serve(ctx, log.Default(), &cm, ul)
},
)
{ {
var ( var (
flagGentoo string flagGentoo string
@@ -358,25 +424,37 @@ func main() {
rosa.SetGentooStage3(flagGentoo, checksum) rosa.SetGentooStage3(flagGentoo, checksum)
} }
_, _, _, stage1 := (t - 2).NewLLVM()
_, _, _, stage2 := (t - 1).NewLLVM()
_, _, _, stage3 := t.NewLLVM()
var ( var (
pathname *check.Absolute pathname *check.Absolute
checksum [2]unique.Handle[pkg.Checksum] checksum [2]unique.Handle[pkg.Checksum]
) )
if pathname, _, err = cache.Cure(stage1); err != nil { if err = cm.Do(func(cache *pkg.Cache) (err error) {
return err pathname, _, err = cache.Cure(
(t - 2).Load(rosa.LLVM),
)
return
}); err != nil {
return
} }
log.Println("stage1:", pathname) log.Println("stage1:", pathname)
if pathname, checksum[0], err = cache.Cure(stage2); err != nil { if err = cm.Do(func(cache *pkg.Cache) (err error) {
return err pathname, checksum[0], err = cache.Cure(
(t - 1).Load(rosa.LLVM),
)
return
}); err != nil {
return
} }
log.Println("stage2:", pathname) log.Println("stage2:", pathname)
if pathname, checksum[1], err = cache.Cure(stage3); err != nil { if err = cm.Do(func(cache *pkg.Cache) (err error) {
return err pathname, checksum[1], err = cache.Cure(
t.Load(rosa.LLVM),
)
return
}); err != nil {
return
} }
log.Println("stage3:", pathname) log.Println("stage3:", pathname)
@@ -393,28 +471,28 @@ func main() {
} }
if flagStage0 { if flagStage0 {
if pathname, _, err = cache.Cure( if err = cm.Do(func(cache *pkg.Cache) (err error) {
pathname, _, err = cache.Cure(
t.Load(rosa.Stage0), t.Load(rosa.Stage0),
); err != nil { )
return err return
}); err != nil {
return
} }
log.Println(pathname) log.Println(pathname)
} }
return return
}, },
). ).Flag(
Flag(
&flagGentoo, &flagGentoo,
"gentoo", command.StringFlag(""), "gentoo", command.StringFlag(""),
"Bootstrap from a Gentoo stage3 tarball", "Bootstrap from a Gentoo stage3 tarball",
). ).Flag(
Flag(
&flagChecksum, &flagChecksum,
"checksum", command.StringFlag(""), "checksum", command.StringFlag(""),
"Checksum of Gentoo stage3 tarball", "Checksum of Gentoo stage3 tarball",
). ).Flag(
Flag(
&flagStage0, &flagStage0,
"stage0", command.BoolFlag(false), "stage0", command.BoolFlag(false),
"Create bootstrap stage0 tarball", "Create bootstrap stage0 tarball",
@@ -424,6 +502,13 @@ func main() {
{ {
var ( var (
flagDump string flagDump string
flagEnter bool
flagExport string
flagRemote bool
flagNoReply bool
flagBoot bool
flagStd bool
) )
c.NewCommand( c.NewCommand(
"cure", "cure",
@@ -432,15 +517,55 @@ func main() {
if len(args) != 1 { if len(args) != 1 {
return errors.New("cure requires 1 argument") return errors.New("cure requires 1 argument")
} }
if p, ok := rosa.ResolveName(args[0]); !ok { p, ok := rosa.ResolveName(args[0])
if !ok {
return fmt.Errorf("unknown artifact %q", args[0]) return fmt.Errorf("unknown artifact %q", args[0])
} else if flagDump == "" {
pathname, _, err := cache.Cure(rosa.Std.Load(p))
if err == nil {
log.Println(pathname)
} }
t := rosa.Std
if flagBoot {
t -= 2
} else if flagStd {
t -= 1
}
switch {
default:
var pathname *check.Absolute
err := cm.Do(func(cache *pkg.Cache) (err error) {
pathname, _, err = cache.Cure(t.Load(p))
return
})
if err != nil {
return err return err
} else { }
log.Println(pathname)
if flagExport != "" {
msg.Verbosef("exporting %s to %s...", args[0], flagExport)
var f *os.File
if f, err = os.OpenFile(
flagExport,
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
0400,
); err != nil {
return err
} else if _, err = pkg.Flatten(
os.DirFS(pathname.String()),
".",
f,
); err != nil {
_ = f.Close()
return err
} else if err = f.Close(); err != nil {
return err
}
}
return nil
case flagDump != "":
f, err := os.OpenFile( f, err := os.OpenFile(
flagDump, flagDump,
os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.O_WRONLY|os.O_CREATE|os.O_EXCL,
@@ -450,22 +575,84 @@ func main() {
return err return err
} }
if err = cache.EncodeAll(f, rosa.Std.Load(p)); err != nil { if err = pkg.NewIR().EncodeAll(f, rosa.Std.Load(p)); err != nil {
_ = f.Close() _ = f.Close()
return err return err
} }
return f.Close() return f.Close()
case flagEnter:
return cm.Do(func(cache *pkg.Cache) error {
return cache.EnterExec(
ctx,
t.Load(p),
true, os.Stdin, os.Stdout, os.Stderr,
rosa.AbsSystem.Append("bin", "mksh"),
"sh",
)
})
case flagRemote:
var flags uint64
if flagNoReply {
flags |= remoteNoReply
}
a := t.Load(p)
pathname, err := cureRemote(ctx, &addr, a, flags)
if !flagNoReply && err == nil {
log.Println(pathname)
}
if errors.Is(err, context.Canceled) {
cc, cancel := context.WithDeadline(context.Background(), daemonDeadline())
defer cancel()
if _err := cancelRemote(cc, &addr, a, false); _err != nil {
log.Println(err)
}
}
return err
} }
}, },
). ).Flag(
Flag(
&flagDump, &flagDump,
"dump", command.StringFlag(""), "dump", command.StringFlag(""),
"Write IR to specified pathname and terminate", "Write IR to specified pathname and terminate",
).Flag(
&flagExport,
"export", command.StringFlag(""),
"Export cured artifact to specified pathname",
).Flag(
&flagEnter,
"enter", command.BoolFlag(false),
"Enter cure container with an interactive shell",
).Flag(
&flagRemote,
"daemon", command.BoolFlag(false),
"Cure artifact on the daemon",
).Flag(
&flagNoReply,
"no-reply", command.BoolFlag(false),
"Do not receive a reply from the daemon",
).Flag(
&flagBoot,
"boot", command.BoolFlag(false),
"Build on the stage0 toolchain",
).Flag(
&flagStd,
"std", command.BoolFlag(false),
"Build on the intermediate toolchain",
) )
} }
c.NewCommand(
"abort",
"Abort all pending cures on the daemon",
func([]string) error { return abortRemote(ctx, &addr, false) },
)
{ {
var ( var (
flagNet bool flagNet bool
@@ -477,37 +664,59 @@ func main() {
"shell", "shell",
"Interactive shell in the specified Rosa OS environment", "Interactive shell in the specified Rosa OS environment",
func(args []string) error { func(args []string) error {
root := make([]pkg.Artifact, 0, 6+len(args)) presets := make([]rosa.PArtifact, len(args)+3)
for _, arg := range args { for i, arg := range args {
p, ok := rosa.ResolveName(arg) p, ok := rosa.ResolveName(arg)
if !ok { if !ok {
return fmt.Errorf("unknown artifact %q", arg) return fmt.Errorf("unknown artifact %q", arg)
} }
root = append(root, rosa.Std.Load(p)) presets[i] = p
} }
if flagWithToolchain { base := rosa.LLVM
musl, compilerRT, runtimes, clang := rosa.Std.NewLLVM() if !flagWithToolchain {
root = append(root, musl, compilerRT, runtimes, clang) base = rosa.Musl
} else {
root = append(root, rosa.Std.Load(rosa.Musl))
} }
root = append(root, presets = append(presets,
rosa.Std.Load(rosa.Mksh), base,
rosa.Std.Load(rosa.Toybox), rosa.Mksh,
rosa.Toybox,
) )
root := make(pkg.Collect, 0, 6+len(args))
root = rosa.Std.AppendPresets(root, presets...)
if err := cm.Do(func(cache *pkg.Cache) error {
_, _, err := cache.Cure(&root)
return err
}); err == nil {
return errors.New("unreachable")
} else if !pkg.IsCollected(err) {
return err
}
type cureRes struct { type cureRes struct {
pathname *check.Absolute pathname *check.Absolute
checksum unique.Handle[pkg.Checksum] checksum unique.Handle[pkg.Checksum]
} }
cured := make(map[pkg.Artifact]cureRes) cured := make(map[pkg.Artifact]cureRes)
for _, a := range root { for _, a := range root {
if err := cm.Do(func(cache *pkg.Cache) error {
pathname, checksum, err := cache.Cure(a) pathname, checksum, err := cache.Cure(a)
if err != nil { if err == nil {
cured[a] = cureRes{pathname, checksum}
}
return err
}); err != nil {
return err
}
}
// explicitly open for direct error-free use from this point
if cm.c == nil {
if err := cm.open(); err != nil {
return err return err
} }
cured[a] = cureRes{pathname, checksum}
} }
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) ( layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
@@ -517,7 +726,7 @@ func main() {
res := cured[a] res := cured[a]
return res.pathname, res.checksum return res.pathname, res.checksum
}, func(i int, d pkg.Artifact) { }, func(i int, d pkg.Artifact) {
r := pkg.Encode(cache.Ident(d).Value()) r := pkg.Encode(cm.c.Ident(d).Value())
if s, ok := d.(fmt.Stringer); ok { if s, ok := d.(fmt.Stringer); ok {
if name := s.String(); name != "" { if name := s.String(); name != "" {
r += "-" + name r += "-" + name
@@ -536,6 +745,10 @@ func main() {
z.Hostname = "localhost" z.Hostname = "localhost"
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1 z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
z.Quiet = !cm.verboseInit
if s, ok := os.LookupEnv("TERM"); ok {
z.Env = append(z.Env, "TERM="+s)
}
var tempdir *check.Absolute var tempdir *check.Absolute
if s, err := filepath.Abs(os.TempDir()); err != nil { if s, err := filepath.Abs(os.TempDir()); err != nil {
@@ -578,21 +791,18 @@ func main() {
} }
return z.Wait() return z.Wait()
}, },
). ).Flag(
Flag(
&flagNet, &flagNet,
"net", command.BoolFlag(false), "net", command.BoolFlag(false),
"Share host net namespace", "Share host net namespace",
). ).Flag(
Flag(
&flagSession, &flagSession,
"session", command.BoolFlag(false), "session", command.BoolFlag(true),
"Retain session", "Retain session",
). ).Flag(
Flag(
&flagWithToolchain, &flagWithToolchain,
"with-toolchain", command.BoolFlag(false), "with-toolchain", command.BoolFlag(false),
"Include the stage3 LLVM toolchain", "Include the stage2 LLVM toolchain",
) )
} }
@@ -604,9 +814,7 @@ func main() {
) )
c.MustParse(os.Args[1:], func(err error) { c.MustParse(os.Args[1:], func(err error) {
if cache != nil { cm.Close()
cache.Close()
}
if w, ok := err.(interface{ Unwrap() []error }); !ok { if w, ok := err.(interface{ Unwrap() []error }); !ok {
log.Fatal(err) log.Fatal(err)
} else { } else {
+47
View File
@@ -0,0 +1,47 @@
package main
import (
"net"
"os"
"testing"
"hakurei.app/internal/rosa"
)
func TestMain(m *testing.M) {
rosa.DropCaches("", rosa.OptLLVMNoLTO)
os.Exit(m.Run())
}
func TestCureAll(t *testing.T) {
t.Parallel()
const env = "ROSA_TEST_DAEMON"
if !testing.Verbose() {
t.Skip("verbose flag not set")
}
pathname, ok := os.LookupEnv(env)
if !ok {
t.Skip(env + " not set")
}
addr := net.UnixAddr{Net: "unix", Name: pathname}
t.Cleanup(func() {
if t.Failed() {
if err := abortRemote(t.Context(), &addr, false); err != nil {
t.Fatal(err)
}
}
})
for i := range rosa.PresetEnd {
p := rosa.PArtifact(i)
t.Run(rosa.GetMetadata(p).Name, func(t *testing.T) {
_, err := cureRemote(t.Context(), &addr, rosa.Std.Load(p), 0)
if err != nil {
t.Error(err)
}
})
}
}
+2 -2
View File
@@ -7,8 +7,8 @@
#endif #endif
#define SHAREFS_MEDIA_RW_ID (1 << 10) - 1 /* owning gid presented to userspace */ #define SHAREFS_MEDIA_RW_ID (1 << 10) - 1 /* owning gid presented to userspace */
#define SHAREFS_PERM_DIR 0700 /* permission bits for directories presented to userspace */ #define SHAREFS_PERM_DIR 0770 /* permission bits for directories presented to userspace */
#define SHAREFS_PERM_REG 0600 /* permission bits for regular files presented to userspace */ #define SHAREFS_PERM_REG 0660 /* permission bits for regular files presented to userspace */
#define SHAREFS_FORBIDDEN_FLAGS O_DIRECT /* these open flags are cleared unconditionally */ #define SHAREFS_FORBIDDEN_FLAGS O_DIRECT /* these open flags are cleared unconditionally */
/* sharefs_private is populated by sharefs_init and contains process-wide context */ /* sharefs_private is populated by sharefs_init and contains process-wide context */
+38 -20
View File
@@ -19,22 +19,21 @@ import (
"encoding/gob" "encoding/gob"
"errors" "errors"
"fmt" "fmt"
"io"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"path" "path/filepath"
"runtime" "runtime"
"runtime/cgo" "runtime/cgo"
"strconv" "strconv"
"syscall" "syscall"
"unsafe" "unsafe"
"hakurei.app/check"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/helper/proc" "hakurei.app/internal/helper/proc"
"hakurei.app/internal/info" "hakurei.app/internal/info"
@@ -85,7 +84,10 @@ func destroySetup(private_data unsafe.Pointer) (ok bool) {
} }
//export sharefs_init //export sharefs_init
func sharefs_init(_ *C.struct_fuse_conn_info, cfg *C.struct_fuse_config) unsafe.Pointer { func sharefs_init(
_ *C.struct_fuse_conn_info,
cfg *C.struct_fuse_config,
) unsafe.Pointer {
ctx := C.fuse_get_context() ctx := C.fuse_get_context()
priv := (*C.struct_sharefs_private)(ctx.private_data) priv := (*C.struct_sharefs_private)(ctx.private_data)
setup := cgo.Handle(priv.setup).Value().(*setupState) setup := cgo.Handle(priv.setup).Value().(*setupState)
@@ -103,7 +105,11 @@ func sharefs_init(_ *C.struct_fuse_conn_info, cfg *C.struct_fuse_config) unsafe.
cfg.negative_timeout = 0 cfg.negative_timeout = 0
// all future filesystem operations happen through this dirfd // all future filesystem operations happen through this dirfd
if fd, err := syscall.Open(setup.Source.String(), syscall.O_DIRECTORY|syscall.O_RDONLY|syscall.O_CLOEXEC, 0); err != nil { if fd, err := syscall.Open(
setup.Source.String(),
syscall.O_DIRECTORY|syscall.O_RDONLY|syscall.O_CLOEXEC,
0,
); err != nil {
log.Printf("cannot open %q: %v", setup.Source, err) log.Printf("cannot open %q: %v", setup.Source, err)
goto fail goto fail
} else if err = syscall.Fchdir(fd); err != nil { } else if err = syscall.Fchdir(fd); err != nil {
@@ -138,9 +144,9 @@ func sharefs_destroy(private_data unsafe.Pointer) {
func showHelp(args *fuseArgs) { func showHelp(args *fuseArgs) {
executableName := sharefsName executableName := sharefsName
if args.argc > 0 { if args.argc > 0 {
executableName = path.Base(C.GoString(*args.argv)) executableName = filepath.Base(C.GoString(*args.argv))
} else if name, err := os.Executable(); err == nil { } else if name, err := os.Executable(); err == nil {
executableName = path.Base(name) executableName = filepath.Base(name)
} }
fmt.Printf("usage: %s [options] <mountpoint>\n\n", executableName) fmt.Printf("usage: %s [options] <mountpoint>\n\n", executableName)
@@ -169,8 +175,11 @@ func parseOpts(args *fuseArgs, setup *setupState, log *log.Logger) (ok bool) {
// Decimal string representation of gid to set when running as root. // Decimal string representation of gid to set when running as root.
setgid *C.char setgid *C.char
// Decimal string representation of open file descriptor to read setupState from. // Decimal string representation of open file descriptor to read
// This is an internal detail for containerisation and must not be specified directly. // setupState from.
//
// This is an internal detail for containerisation and must not be
// specified directly.
setup *C.char setup *C.char
} }
@@ -253,7 +262,8 @@ func parseOpts(args *fuseArgs, setup *setupState, log *log.Logger) (ok bool) {
return true return true
} }
// copyArgs returns a heap allocated copy of an argument slice in fuse_args representation. // copyArgs returns a heap allocated copy of an argument slice in fuse_args
// representation.
func copyArgs(s ...string) fuseArgs { func copyArgs(s ...string) fuseArgs {
if len(s) == 0 { if len(s) == 0 {
return fuseArgs{argc: 0, argv: nil, allocated: 0} return fuseArgs{argc: 0, argv: nil, allocated: 0}
@@ -269,6 +279,7 @@ func copyArgs(s ...string) fuseArgs {
func freeArgs(args *fuseArgs) { C.fuse_opt_free_args(args) } func freeArgs(args *fuseArgs) { C.fuse_opt_free_args(args) }
// unsafeAddArgument adds an argument to fuseArgs via fuse_opt_add_arg. // unsafeAddArgument adds an argument to fuseArgs via fuse_opt_add_arg.
//
// The last byte of arg must be 0. // The last byte of arg must be 0.
func unsafeAddArgument(args *fuseArgs, arg string) { func unsafeAddArgument(args *fuseArgs, arg string) {
C.fuse_opt_add_arg(args, (*C.char)(unsafe.Pointer(unsafe.StringData(arg)))) C.fuse_opt_add_arg(args, (*C.char)(unsafe.Pointer(unsafe.StringData(arg))))
@@ -288,8 +299,8 @@ func _main(s ...string) (exitCode int) {
args := copyArgs(s...) args := copyArgs(s...)
defer freeArgs(&args) defer freeArgs(&args)
// this causes the kernel to enforce access control based on // this causes the kernel to enforce access control based on struct stat
// struct stat populated by sharefs_getattr // populated by sharefs_getattr
unsafeAddArgument(&args, "-odefault_permissions\x00") unsafeAddArgument(&args, "-odefault_permissions\x00")
var priv C.struct_sharefs_private var priv C.struct_sharefs_private
@@ -453,15 +464,19 @@ func _main(s ...string) (exitCode int) {
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
} }
z.Bind(z.Path, z.Path, 0) z.Bind(z.Path, z.Path, 0)
setup.Fuse = int(proc.ExtraFileSlice(&z.ExtraFiles, os.NewFile(uintptr(C.fuse_session_fd(se)), "fuse"))) setup.Fuse = int(proc.ExtraFileSlice(
&z.ExtraFiles,
os.NewFile(uintptr(C.fuse_session_fd(se)), "fuse"),
))
var setupWriter io.WriteCloser var setupPipe [2]*os.File
if fd, w, err := container.Setup(&z.ExtraFiles); err != nil { if r, w, err := os.Pipe(); err != nil {
log.Println(err) log.Println(err)
return 5 return 5
} else { } else {
z.Args = append(z.Args, "-osetup="+strconv.Itoa(fd)) z.Args = append(z.Args, "-osetup="+strconv.Itoa(3+len(z.ExtraFiles)))
setupWriter = w z.ExtraFiles = append(z.ExtraFiles, r)
setupPipe[0], setupPipe[1] = r, w
} }
if err := z.Start(); err != nil { if err := z.Start(); err != nil {
@@ -472,6 +487,9 @@ func _main(s ...string) (exitCode int) {
} }
return 5 return 5
} }
if err := setupPipe[0].Close(); err != nil {
log.Println(err)
}
if err := z.Serve(); err != nil { if err := z.Serve(); err != nil {
if m, ok := message.GetMessage(err); ok { if m, ok := message.GetMessage(err); ok {
log.Println(m) log.Println(m)
@@ -481,10 +499,10 @@ func _main(s ...string) (exitCode int) {
return 5 return 5
} }
if err := gob.NewEncoder(setupWriter).Encode(&setup); err != nil { if err := gob.NewEncoder(setupPipe[1]).Encode(&setup); err != nil {
log.Println(err) log.Println(err)
return 5 return 5
} else if err = setupWriter.Close(); err != nil { } else if err = setupPipe[1].Close(); err != nil {
log.Println(err) log.Println(err)
} }
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
) )
func TestParseOpts(t *testing.T) { func TestParseOpts(t *testing.T) {
+7
View File
@@ -1,3 +1,10 @@
// The sharefs FUSE filesystem is a permissionless shared filesystem.
//
// This filesystem is the primary means of file sharing between hakurei
// application containers. It serves the same purpose in Rosa OS as /sdcard
// does in AOSP.
//
// See help message for all available options.
package main package main
import ( import (
+4 -1
View File
@@ -20,11 +20,14 @@
}; };
virtualisation = { virtualisation = {
# Hopefully reduces spurious test failures:
memorySize = if pkgs.stdenv.hostPlatform.is32bit then 2046 else 8192;
diskSize = 6 * 1024; diskSize = 6 * 1024;
qemu.options = [ qemu.options = [
# Increase test performance: # Increase test performance:
"-smp 8" "-smp 16"
]; ];
}; };
+1 -1
View File
@@ -28,7 +28,7 @@ testers.nixosTest {
# For go tests: # For go tests:
(pkgs.writeShellScriptBin "sharefs-workload-hakurei-tests" '' (pkgs.writeShellScriptBin "sharefs-workload-hakurei-tests" ''
cp -r "${self.packages.${system}.hakurei.src}" "/sdcard/hakurei" && cd "/sdcard/hakurei" cp -r "${self.packages.${system}.hakurei.src}" "/sdcard/hakurei" && cd "/sdcard/hakurei"
${fhs}/bin/hakurei-fhs -c 'CC="clang -O3 -Werror" go test ./...' ${fhs}/bin/hakurei-fhs -c 'ROSA_SKIP_BINFMT=1 CC="clang -O3 -Werror" go test ./...'
'') '')
]; ];
+122
View File
@@ -0,0 +1,122 @@
//go:build raceattr
// The raceattr program reproduces vfs inode file attribute race.
//
// Even though libfuse high-level API presents the address of a struct stat
// alongside struct fuse_context, file attributes are actually inherent to the
// inode, instead of the specific call from userspace. The kernel implementation
// in fs/fuse/xattr.c appears to make stale data in the inode (set by a previous
// call) impossible or very unlikely to reach userspace via the stat family of
// syscalls. However, when using default_permissions to have the VFS check
// permissions, this race still happens, despite the resulting struct stat being
// correct when overriding the check via capabilities otherwise.
//
// This program reproduces the failure, but because of its continuous nature, it
// is provided independent of the vm integration test suite.
package main
import (
"context"
"flag"
"log"
"os"
"os/signal"
"runtime"
"sync"
"sync/atomic"
"syscall"
)
func newStatAs(
ctx context.Context, cancel context.CancelFunc,
n *atomic.Uint64, ok *atomic.Bool,
uid uint32, pathname string,
continuous bool,
) func() {
return func() {
runtime.LockOSThread()
defer cancel()
if _, _, errno := syscall.Syscall(
syscall.SYS_SETUID, uintptr(uid),
0, 0,
); errno != 0 {
cancel()
log.Printf("cannot set uid to %d: %s", uid, errno)
}
var stat syscall.Stat_t
for {
if ctx.Err() != nil {
return
}
if err := syscall.Lstat(pathname, &stat); err != nil {
// SHAREFS_PERM_DIR not world executable, or
// SHAREFS_PERM_REG not world readable
if !continuous {
cancel()
}
ok.Store(true)
log.Printf("uid %d: %v", uid, err)
} else if stat.Uid != uid {
// appears to be unreachable
if !continuous {
cancel()
}
ok.Store(true)
log.Printf("got uid %d instead of %d", stat.Uid, uid)
}
n.Add(1)
}
}
}
func main() {
log.SetFlags(0)
log.SetPrefix("raceattr: ")
p := flag.String("target", "/sdcard/raceattr", "pathname of test file")
u0 := flag.Int("uid0", 1<<10-1, "first uid")
u1 := flag.Int("uid1", 1<<10-2, "second uid")
count := flag.Int("count", 1, "threads per uid")
continuous := flag.Bool("continuous", false, "keep running even after reproduce")
flag.Parse()
if os.Geteuid() != 0 {
log.Fatal("this program must run as root")
}
ctx, cancel := signal.NotifyContext(
context.Background(),
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGHUP,
)
if err := os.WriteFile(*p, nil, 0); err != nil {
log.Fatal(err)
}
var (
wg sync.WaitGroup
n atomic.Uint64
ok atomic.Bool
)
if *count < 1 {
*count = 1
}
for range *count {
wg.Go(newStatAs(ctx, cancel, &n, &ok, uint32(*u0), *p, *continuous))
if *u1 >= 0 {
wg.Go(newStatAs(ctx, cancel, &n, &ok, uint32(*u1), *p, *continuous))
}
}
wg.Wait()
if !*continuous && ok.Load() {
log.Printf("reproduced after %d calls", n.Load())
}
}
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/fhs" "hakurei.app/fhs"
) )
func init() { gob.Register(new(AutoEtcOp)) } func init() { gob.Register(new(AutoEtcOp)) }
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"os" "os"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestAutoEtcOp(t *testing.T) { func TestAutoEtcOp(t *testing.T) {
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/fhs" "hakurei.app/fhs"
"hakurei.app/message" "hakurei.app/message"
) )
+2 -2
View File
@@ -5,9 +5,9 @@ import (
"os" "os"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
"hakurei.app/message" "hakurei.app/message"
) )
+46
View File
@@ -0,0 +1,46 @@
package container
import (
"strings"
"unsafe"
"hakurei.app/check"
)
// escapeBinfmt escapes magic/mask sequences in a [BinfmtEntry].
func escapeBinfmt(buf *strings.Builder, s string) string {
const lowerhex = "0123456789abcdef"
buf.Reset()
for _, c := range unsafe.Slice(unsafe.StringData(s), len(s)) {
switch c {
case 0, '\\', ':':
buf.WriteString(`\x`)
buf.WriteByte(lowerhex[c>>4])
buf.WriteByte(lowerhex[c&0xf])
default:
buf.WriteByte(c)
}
}
return buf.String()
}
// BinfmtEntry is an entry to be registered by the init process.
type BinfmtEntry struct {
// The offset of the magic/mask in the file, counted in bytes.
Offset byte
// The byte sequence binfmt_misc is matching for.
Magic string
// An (optional, defaults to all 0xff) mask.
Mask string
// The program that should be invoked with the binary as first argument.
Interpreter *check.Absolute
}
// Valid returns whether e can be registered into the kernel.
func (e *BinfmtEntry) Valid() bool {
return e != nil &&
int(e.Offset)+max(len(e.Magic), len(e.Mask)) < 128 &&
e.Interpreter != nil && len(e.Interpreter.String()) < 128
}
+62
View File
@@ -0,0 +1,62 @@
package container
import (
"strings"
"testing"
"hakurei.app/fhs"
)
func TestEscapeBinfmt(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
magic string
want string
}{
{"packed DOS applications", "\x0eDEX", "\x0eDEX"},
{"riscv64 magic",
"\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00",
"\x7fELF\x02\x01\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\x02\\x00\xf3\\x00"},
{"riscv64 mask",
"\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
"\xff\xff\xff\xff\xff\xff\xff\\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := escapeBinfmt(new(strings.Builder), tc.magic)
if got != tc.want {
t.Errorf("escapeBinfmt: %q, want %q", got, tc.want)
}
})
}
}
func TestBinfmtEntry(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
e BinfmtEntry
valid bool
}{
{"zero", BinfmtEntry{}, false},
{"large offset", BinfmtEntry{Offset: 128}, false},
{"long magic", BinfmtEntry{Magic: strings.Repeat("\x00", 128)}, false},
{"long mask", BinfmtEntry{Mask: strings.Repeat("\x00", 128)}, false},
{"valid", BinfmtEntry{Interpreter: fhs.AbsRoot}, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if tc.e.Valid() != tc.valid {
t.Errorf("Valid: %v", !tc.valid)
}
})
}
}
+6 -3
View File
@@ -3,6 +3,8 @@ package container
import ( import (
"syscall" "syscall"
"unsafe" "unsafe"
"hakurei.app/ext"
) )
const ( const (
@@ -16,6 +18,7 @@ const (
CAP_SETPCAP = 0x8 CAP_SETPCAP = 0x8
CAP_NET_ADMIN = 0xc CAP_NET_ADMIN = 0xc
CAP_DAC_OVERRIDE = 0x1 CAP_DAC_OVERRIDE = 0x1
CAP_SETFCAP = 0x1f
) )
type ( type (
@@ -51,15 +54,15 @@ func capset(hdrp *capHeader, datap *[2]capData) error {
// capBoundingSetDrop drops a capability from the calling thread's capability bounding set. // capBoundingSetDrop drops a capability from the calling thread's capability bounding set.
func capBoundingSetDrop(cap uintptr) error { func capBoundingSetDrop(cap uintptr) error {
return Prctl(syscall.PR_CAPBSET_DROP, cap, 0) return ext.Prctl(syscall.PR_CAPBSET_DROP, cap, 0)
} }
// capAmbientClearAll clears the ambient capability set of the calling thread. // capAmbientClearAll clears the ambient capability set of the calling thread.
func capAmbientClearAll() error { func capAmbientClearAll() error {
return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0) return ext.Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0)
} }
// capAmbientRaise adds to the ambient capability set of the calling thread. // capAmbientRaise adds to the ambient capability set of the calling thread.
func capAmbientRaise(cap uintptr) error { func capAmbientRaise(cap uintptr) error {
return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap) return ext.Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap)
} }
+121 -60
View File
@@ -16,10 +16,12 @@ import (
. "syscall" . "syscall"
"time" "time"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/fhs"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/ext"
"hakurei.app/fhs"
"hakurei.app/internal/landlock"
"hakurei.app/message" "hakurei.app/message"
) )
@@ -27,9 +29,6 @@ const (
// CancelSignal is the signal expected by container init on context cancel. // CancelSignal is the signal expected by container init on context cancel.
// A custom [Container.Cancel] function must eventually deliver this signal. // A custom [Container.Cancel] function must eventually deliver this signal.
CancelSignal = SIGUSR2 CancelSignal = SIGUSR2
// Timeout for writing initParams to Container.setup.
initSetupTimeout = 5 * time.Second
) )
type ( type (
@@ -38,9 +37,13 @@ type (
Container struct { Container struct {
// Whether the container init should stay alive after its parent terminates. // Whether the container init should stay alive after its parent terminates.
AllowOrphan bool AllowOrphan bool
// Scheduling policy to set via sched_setscheduler(2). The zero value // Whether to set SchedPolicy and SchedPriority via sched_setscheduler(2).
// skips this call. Supported policies are [SCHED_BATCH], [SCHED_IDLE]. SetScheduler bool
SchedPolicy int // Scheduling policy to set via sched_setscheduler(2).
SchedPolicy ext.SchedPolicy
// Scheduling priority to set via sched_setscheduler(2). The zero value
// implies the minimum value supported by the current SchedPolicy.
SchedPriority ext.Int
// Cgroup fd, nil to disable. // Cgroup fd, nil to disable.
Cgroup *int Cgroup *int
// ExtraFiles passed through to initial process in the container, with // ExtraFiles passed through to initial process in the container, with
@@ -48,7 +51,7 @@ type (
ExtraFiles []*os.File ExtraFiles []*os.File
// Write end of a pipe connected to the init to deliver [Params]. // Write end of a pipe connected to the init to deliver [Params].
setup *os.File setup [2]*os.File
// Cancels the context passed to the underlying cmd. // Cancels the context passed to the underlying cmd.
cancel context.CancelFunc cancel context.CancelFunc
// Closed after Wait returns. Keeps the spawning thread alive. // Closed after Wait returns. Keeps the spawning thread alive.
@@ -64,6 +67,9 @@ type (
// Copied to the underlying [exec.Cmd]. // Copied to the underlying [exec.Cmd].
WaitDelay time.Duration WaitDelay time.Duration
// Suppress verbose output of init.
Quiet bool
cmd *exec.Cmd cmd *exec.Cmd
ctx context.Context ctx context.Context
msg message.Msg msg message.Msg
@@ -85,12 +91,20 @@ type (
// Time to wait for processes lingering after the initial process terminates. // Time to wait for processes lingering after the initial process terminates.
AdoptWaitDelay time.Duration AdoptWaitDelay time.Duration
// Map uid/gid 0 in the init process. Requires [FstypeProc] attached to
// [fhs.Proc] in the container filesystem.
InitAsRoot bool
// Mapped Uid in user namespace. // Mapped Uid in user namespace.
Uid int Uid int
// Mapped Gid in user namespace. // Mapped Gid in user namespace.
Gid int Gid int
// Hostname value in UTS namespace. // Hostname value in UTS namespace.
Hostname string Hostname string
// Register binfmt_misc entries.
Binfmt []BinfmtEntry
// Alternative pathname to attach binfmt_misc filesystem. The zero value
// requires [FstypeProc] to be made available at [fhs.Proc].
BinfmtPath *check.Absolute
// Sequential container setup ops. // Sequential container setup ops.
*Ops *Ops
@@ -181,31 +195,24 @@ var (
closeOnExecErr error closeOnExecErr error
) )
// ensureCloseOnExec ensures all currently open file descriptors have the syscall.FD_CLOEXEC flag set. // ensureCloseOnExec ensures all currently open file descriptors have the
// This is only ran once as it is intended to handle files left open by the parent, and any file opened // syscall.FD_CLOEXEC flag set.
// on this side should already have syscall.FD_CLOEXEC set. //
// This is only ran once as it is intended to handle files left open by the
// parent, and any file opened on this side should already have
// syscall.FD_CLOEXEC set.
func ensureCloseOnExec() error { func ensureCloseOnExec() error {
closeOnExecOnce.Do(func() { closeOnExecOnce.Do(func() { closeOnExecErr = doCloseOnExec() })
const fdPrefixPath = "/proc/self/fd/"
var entries []os.DirEntry
if entries, closeOnExecErr = os.ReadDir(fdPrefixPath); closeOnExecErr != nil {
return
}
var fd int
for _, ent := range entries {
if fd, closeOnExecErr = strconv.Atoi(ent.Name()); closeOnExecErr != nil {
break // not reached
}
CloseOnExec(fd)
}
})
if closeOnExecErr == nil { if closeOnExecErr == nil {
return nil return nil
} }
return &StartError{Fatal: true, Step: "set FD_CLOEXEC on all open files", Err: closeOnExecErr, Passthrough: true} return &StartError{
Fatal: true,
Step: "set FD_CLOEXEC on all open files",
Err: closeOnExecErr,
Passthrough: true,
}
} }
// Start starts the container init. The init process blocks until Serve is called. // Start starts the container init. The init process blocks until Serve is called.
@@ -217,6 +224,9 @@ func (p *Container) Start() error {
if p.cmd.Process != nil { if p.cmd.Process != nil {
return errors.New("container: already started") return errors.New("container: already started")
} }
if !p.InitAsRoot && len(p.Binfmt) > 0 {
return errors.New("container: init as root required, but not enabled")
}
if err := ensureCloseOnExec(); err != nil { if err := ensureCloseOnExec(); err != nil {
return err return err
@@ -287,16 +297,30 @@ func (p *Container) Start() error {
if !p.HostNet { if !p.HostNet {
p.cmd.SysProcAttr.Cloneflags |= CLONE_NEWNET p.cmd.SysProcAttr.Cloneflags |= CLONE_NEWNET
} }
if p.InitAsRoot {
p.cmd.SysProcAttr.AmbientCaps = append(p.cmd.SysProcAttr.AmbientCaps,
// mappings during init as root
CAP_SETFCAP,
)
if !p.SeccompDisable &&
len(p.SeccompRules) == 0 &&
p.SeccompPresets&std.PresetDenyNS != 0 {
return errors.New("container: as root requires late namespace creation")
}
}
// place setup pipe before user supplied extra files, this is later restored by init // place setup pipe before user supplied extra files, this is later restored by init
if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil { if r, w, err := os.Pipe(); err != nil {
return &StartError{ return &StartError{
Fatal: true, Fatal: true,
Step: "set up params stream", Step: "set up params stream",
Err: err, Err: err,
} }
} else { } else {
p.setup = f fd := 3 + len(p.cmd.ExtraFiles)
p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, r)
p.setup[0], p.setup[1] = r, w
p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)} p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)}
} }
p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, p.ExtraFiles...) p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, p.ExtraFiles...)
@@ -310,7 +334,7 @@ func (p *Container) Start() error {
done <- func() error { done <- func() error {
// PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes // PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes
// created from the calling thread // created from the calling thread
if err := SetNoNewPrivs(); err != nil { if err := setNoNewPrivs(); err != nil {
return &StartError{ return &StartError{
Fatal: true, Fatal: true,
Step: "prctl(PR_SET_NO_NEW_PRIVS)", Step: "prctl(PR_SET_NO_NEW_PRIVS)",
@@ -320,15 +344,17 @@ func (p *Container) Start() error {
// landlock: depends on per-thread state but acts on a process group // landlock: depends on per-thread state but acts on a process group
{ {
rulesetAttr := &RulesetAttr{Scoped: LANDLOCK_SCOPE_SIGNAL} rulesetAttr := &landlock.RulesetAttr{
Scoped: landlock.LANDLOCK_SCOPE_SIGNAL,
}
if !p.HostAbstract { if !p.HostAbstract {
rulesetAttr.Scoped |= LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET rulesetAttr.Scoped |= landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
} }
if abi, err := LandlockGetABI(); err != nil { if abi, err := landlock.GetABI(); err != nil {
if p.HostAbstract { if p.HostAbstract || !p.HostNet {
// landlock can be skipped here as it restricts access // landlock can be skipped here as it restricts access
// to resources already covered by namespaces (pid) // to resources already covered by namespaces (pid, net)
goto landlockOut goto landlockOut
} }
return &StartError{Step: "get landlock ABI", Err: err} return &StartError{Step: "get landlock ABI", Err: err}
@@ -342,8 +368,6 @@ func (p *Container) Start() error {
Err: ENOSYS, Err: ENOSYS,
Origin: true, Origin: true,
} }
} else {
p.msg.Verbosef("landlock abi version %d", abi)
} }
if rulesetFd, err := rulesetAttr.Create(0); err != nil { if rulesetFd, err := rulesetAttr.Create(0); err != nil {
@@ -353,8 +377,7 @@ func (p *Container) Start() error {
Err: err, Err: err,
} }
} else { } else {
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr) if err = landlock.RestrictSelf(rulesetFd, 0); err != nil {
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
_ = Close(rulesetFd) _ = Close(rulesetFd)
return &StartError{ return &StartError{
Fatal: true, Fatal: true,
@@ -373,22 +396,43 @@ func (p *Container) Start() error {
// sched_setscheduler: thread-directed but acts on all processes // sched_setscheduler: thread-directed but acts on all processes
// created from the calling thread // created from the calling thread
if p.SchedPolicy > 0 { if p.SetScheduler {
p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy) if p.SchedPolicy < 0 || p.SchedPolicy > ext.SCHED_LAST {
return &StartError{
Fatal: false,
Step: "set scheduling policy",
Err: EINVAL,
}
}
var param schedParam
if priority, err := p.SchedPolicy.GetPriorityMin(); err != nil {
return &StartError{
Fatal: true,
Step: "get minimum priority",
Err: err,
}
} else {
param.priority = max(priority, p.SchedPriority)
}
p.msg.Verbosef(
"setting scheduling policy %s priority %d",
p.SchedPolicy, param.priority,
)
if err := schedSetscheduler( if err := schedSetscheduler(
0, // calling thread 0, // calling thread
p.SchedPolicy, p.SchedPolicy,
&schedParam{0}, &param,
); err != nil { ); err != nil {
return &StartError{ return &StartError{
Fatal: true, Fatal: true,
Step: "enforce landlock ruleset", Step: "set scheduling policy",
Err: err, Err: err,
} }
} }
} }
p.msg.Verbose("starting container init")
if err := p.cmd.Start(); err != nil { if err := p.cmd.Start(); err != nil {
return &StartError{ return &StartError{
Step: "start container init", Step: "start container init",
@@ -408,24 +452,33 @@ func (p *Container) Start() error {
// Serve serves [Container.Params] to the container init. // Serve serves [Container.Params] to the container init.
// //
// Serve must only be called once. // Serve must only be called once.
func (p *Container) Serve() error { func (p *Container) Serve() (err error) {
if p.setup == nil { if p.setup[0] == nil || p.setup[1] == nil {
panic("invalid serve") panic("invalid serve")
} }
setup := p.setup done := make(chan struct{})
p.setup = nil defer func() {
if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil { if closeErr := p.setup[1].Close(); err == nil {
err = closeErr
}
if err != nil {
p.cancel()
}
close(done)
p.setup[0], p.setup[1] = nil, nil
}()
if err = p.setup[0].Close(); err != nil {
return &StartError{ return &StartError{
Fatal: true, Fatal: true,
Step: "set init pipe deadline", Step: "close read end of init pipe",
Err: err, Err: err,
Passthrough: true, Passthrough: true,
} }
} }
if p.Path == nil { if p.Path == nil {
p.cancel()
return &StartError{ return &StartError{
Step: "invalid executable pathname", Step: "invalid executable pathname",
Err: EINVAL, Err: EINVAL,
@@ -441,18 +494,26 @@ func (p *Container) Serve() error {
p.SeccompRules = make([]std.NativeRule, 0) p.SeccompRules = make([]std.NativeRule, 0)
} }
err := gob.NewEncoder(setup).Encode(&initParams{ t := time.Now().UTC()
go func(f *os.File) {
select {
case <-p.ctx.Done():
if cancelErr := f.SetWriteDeadline(t); cancelErr != nil {
p.msg.Verbose(err)
}
case <-done:
return
}
}(p.setup[1])
return gob.NewEncoder(p.setup[1]).Encode(&initParams{
p.Params, p.Params,
Getuid(), Getuid(),
Getgid(), Getgid(),
len(p.ExtraFiles), len(p.ExtraFiles),
p.msg.IsVerbose(), p.msg.IsVerbose() && !p.Quiet,
}) })
_ = setup.Close()
if err != nil {
p.cancel()
}
return err
} }
// Wait blocks until the container init process to exit and releases any // Wait blocks until the container init process to exit and releases any
+195 -67
View File
@@ -17,17 +17,22 @@ import (
"syscall" "syscall"
"testing" "testing"
"time" "time"
"unsafe"
"hakurei.app/check"
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/container/vfs" "hakurei.app/ext"
"hakurei.app/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/info"
"hakurei.app/internal/landlock"
"hakurei.app/internal/params"
"hakurei.app/ldd" "hakurei.app/ldd"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/vfs"
) )
// Note: this package requires cgo, which is unavailable in the Go playground. // Note: this package requires cgo, which is unavailable in the Go playground.
@@ -83,9 +88,9 @@ func TestStartError(t *testing.T) {
{"params env", &container.StartError{ {"params env", &container.StartError{
Fatal: true, Fatal: true,
Step: "set up params stream", Step: "set up params stream",
Err: container.ErrReceiveEnv, Err: params.ErrReceiveEnv,
}, "set up params stream: environment variable not set", }, "set up params stream: environment variable not set",
container.ErrReceiveEnv, syscall.EBADF, params.ErrReceiveEnv, syscall.EBADF,
"cannot set up params stream: environment variable not set"}, "cannot set up params stream: environment variable not set"},
{"params", &container.StartError{ {"params", &container.StartError{
@@ -230,6 +235,9 @@ func earlyMnt(mnt ...*vfs.MountInfoEntry) func(*testing.T, context.Context) []*v
return func(*testing.T, context.Context) []*vfs.MountInfoEntry { return mnt } return func(*testing.T, context.Context) []*vfs.MountInfoEntry { return mnt }
} }
//go:linkname toHost hakurei.app/container.toHost
func toHost(name string) string
var containerTestCases = []struct { var containerTestCases = []struct {
name string name string
filter bool filter bool
@@ -258,7 +266,7 @@ var containerTestCases = []struct {
1000, 100, nil, 0, std.PresetExt}, 1000, 100, nil, 0, std.PresetExt},
{"custom rules", true, true, true, false, {"custom rules", true, true, true, false,
emptyOps, emptyMnt, emptyOps, emptyMnt,
1, 31, []std.NativeRule{{Syscall: std.ScmpSyscall(syscall.SYS_SETUID), Errno: std.ScmpErrno(syscall.EPERM)}}, 0, std.PresetExt}, 1, 31, []std.NativeRule{{Syscall: ext.SyscallNum(syscall.SYS_SETUID), Errno: std.ScmpErrno(syscall.EPERM)}}, 0, std.PresetExt},
{"tmpfs", true, false, false, true, {"tmpfs", true, false, false, true,
earlyOps(new(container.Ops). earlyOps(new(container.Ops).
@@ -329,13 +337,15 @@ var containerTestCases = []struct {
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry { func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
return []*vfs.MountInfoEntry{ return []*vfs.MountInfoEntry{
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay", ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
"rw,lowerdir="+ "rw"+
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+ ",lowerdir+="+
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+ toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+
",lowerdir+="+
toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
",upperdir="+ ",upperdir="+
container.InternalToHostOvlEscape(ctx.Value(testVal("upper")).(*check.Absolute).String())+ toHost(ctx.Value(testVal("upper")).(*check.Absolute).String())+
",workdir="+ ",workdir="+
container.InternalToHostOvlEscape(ctx.Value(testVal("work")).(*check.Absolute).String())+ toHost(ctx.Value(testVal("work")).(*check.Absolute).String())+
",redirect_dir=nofollow,uuid=on,userxattr"), ",redirect_dir=nofollow,uuid=on,userxattr"),
} }
}, },
@@ -385,9 +395,11 @@ var containerTestCases = []struct {
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry { func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
return []*vfs.MountInfoEntry{ return []*vfs.MountInfoEntry{
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay", ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
"ro,lowerdir="+ "ro"+
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+ ",lowerdir+="+
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+ toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+
",lowerdir+="+
toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
",redirect_dir=nofollow,userxattr"), ",redirect_dir=nofollow,userxattr"),
} }
}, },
@@ -397,49 +409,18 @@ var containerTestCases = []struct {
func TestContainer(t *testing.T) { func TestContainer(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) { var suffix string
wantErr := context.Canceled runTests:
wantExitCode := 0
if err := c.Wait(); !reflect.DeepEqual(err, wantErr) {
if m, ok := container.InternalMessageFromError(err); ok {
t.Error(m)
}
t.Errorf("Wait: error = %#v, want %#v", err, wantErr)
}
if ps := c.ProcessState(); ps == nil {
t.Errorf("ProcessState unexpectedly returned nil")
} else if code := ps.ExitCode(); code != wantExitCode {
t.Errorf("ExitCode: %d, want %d", code, wantExitCode)
}
}))
t.Run("forward", testContainerCancel(func(c *container.Container) {
c.ForwardCancel = true
}, func(t *testing.T, c *container.Container) {
var exitError *exec.ExitError
if err := c.Wait(); !errors.As(err, &exitError) {
if m, ok := container.InternalMessageFromError(err); ok {
t.Error(m)
}
t.Errorf("Wait: error = %v", err)
}
if code := exitError.ExitCode(); code != blockExitCodeInterrupt {
t.Errorf("ExitCode: %d, want %d", code, blockExitCodeInterrupt)
}
}))
for i, tc := range containerTestCases { for i, tc := range containerTestCases {
t.Run(tc.name, func(t *testing.T) { _suffix := suffix
t.Run(tc.name+_suffix, func(t *testing.T) {
t.Parallel() t.Parallel()
wantOps, wantOpsCtx := tc.ops(t) wantOps, wantOpsCtx := tc.ops(t)
wantMnt := tc.mnt(t, wantOpsCtx) wantMnt := tc.mnt(t, wantOpsCtx)
ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
defer cancel()
var libPaths []*check.Absolute var libPaths []*check.Absolute
c := helperNewContainerLibPaths(ctx, &libPaths, "container", strconv.Itoa(i)) c := helperNewContainerLibPaths(t.Context(), &libPaths, "container", strconv.Itoa(i))
c.Uid = tc.uid c.Uid = tc.uid
c.Gid = tc.gid c.Gid = tc.gid
c.Hostname = hostnameFromTestCase(tc.name) c.Hostname = hostnameFromTestCase(tc.name)
@@ -449,7 +430,6 @@ func TestContainer(t *testing.T) {
} else { } else {
c.Stdout, c.Stderr = os.Stdout, os.Stderr c.Stdout, c.Stderr = os.Stdout, os.Stderr
} }
c.WaitDelay = helperDefaultTimeout
*c.Ops = append(*c.Ops, *wantOps...) *c.Ops = append(*c.Ops, *wantOps...)
c.SeccompRules = tc.rules c.SeccompRules = tc.rules
c.SeccompFlags = tc.flags | seccomp.AllowMultiarch c.SeccompFlags = tc.flags | seccomp.AllowMultiarch
@@ -457,6 +437,20 @@ func TestContainer(t *testing.T) {
c.SeccompDisable = !tc.filter c.SeccompDisable = !tc.filter
c.RetainSession = tc.session c.RetainSession = tc.session
c.HostNet = tc.net c.HostNet = tc.net
c.InitAsRoot = _suffix != ""
c.Env = append(c.Env, "HAKUREI_TEST_SUFFIX="+_suffix)
if info.CanDegrade {
if _, err := landlock.GetABI(); err != nil {
if !errors.Is(err, syscall.ENOSYS) {
t.Fatalf("LandlockGetABI: error = %v", err)
}
c.HostAbstract = true
t.Log("Landlock LSM is unavailable, enabling HostAbstract")
}
}
if c.InitAsRoot {
c.SeccompPresets &= ^std.PresetDenyNS
}
c. c.
Readonly(check.MustAbs(pathReadonly), 0755). Readonly(check.MustAbs(pathReadonly), 0755).
@@ -525,6 +519,11 @@ func TestContainer(t *testing.T) {
} }
}) })
} }
if suffix == "" {
suffix = " as root"
goto runTests
}
} }
func ent(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoEntry { func ent(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoEntry {
@@ -547,40 +546,49 @@ func hostnameFromTestCase(name string) string {
} }
func testContainerCancel( func testContainerCancel(
t *testing.T,
containerExtra func(c *container.Container), containerExtra func(c *container.Container),
waitCheck func(t *testing.T, c *container.Container), waitCheck func(ps *os.ProcessState, waitErr error),
) func(t *testing.T) { ) {
return func(t *testing.T) { ctx, cancel := context.WithCancel(t.Context())
t.Parallel()
ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
c := helperNewContainer(ctx, "block") c := helperNewContainer(ctx, "block")
c.Stdout, c.Stderr = os.Stdout, os.Stderr c.Stdout, c.Stderr = os.Stdout, os.Stderr
c.WaitDelay = helperDefaultTimeout
if containerExtra != nil { if containerExtra != nil {
containerExtra(c) containerExtra(c)
} }
ready := make(chan struct{}) ready := make(chan struct{})
if r, w, err := os.Pipe(); err != nil { var waitErr error
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("cannot pipe: %v", err) t.Fatalf("cannot pipe: %v", err)
} else { }
c.ExtraFiles = append(c.ExtraFiles, w) c.ExtraFiles = append(c.ExtraFiles, w)
go func() { go func() {
defer close(ready) defer close(ready)
if _, err = r.Read(make([]byte, 1)); err != nil { if _, _err := r.Read(make([]byte, 1)); _err != nil {
panic(err.Error()) panic(_err)
} }
}() }()
}
if err := c.Start(); err != nil { if err = c.Start(); err != nil {
if m, ok := container.InternalMessageFromError(err); ok { if m, ok := container.InternalMessageFromError(err); ok {
t.Fatal(m) t.Fatal(m)
} else { } else {
t.Fatalf("cannot start container: %v", err) t.Fatalf("cannot start container: %v", err)
} }
} else if err = c.Serve(); err != nil { }
done := make(chan struct{})
go func() {
defer close(done)
waitErr = c.Wait()
_ = r.SetReadDeadline(time.Now())
}()
if err = c.Serve(); err != nil {
if m, ok := container.InternalMessageFromError(err); ok { if m, ok := container.InternalMessageFromError(err); ok {
t.Error(m) t.Error(m)
} else { } else {
@@ -589,8 +597,67 @@ func testContainerCancel(
} }
<-ready <-ready
cancel() cancel()
waitCheck(t, c) <-done
waitCheck(c.ProcessState(), waitErr)
} }
func TestForward(t *testing.T) {
t.Parallel()
f := func(ps *os.ProcessState, waitErr error) {
var exitError *exec.ExitError
if !errors.As(waitErr, &exitError) {
if m, ok := container.InternalMessageFromError(waitErr); ok {
t.Error(m)
}
t.Errorf("Wait: error = %v", waitErr)
}
if code := exitError.ExitCode(); code != blockExitCodeInterrupt {
t.Errorf("ExitCode: %d, want %d", code, blockExitCodeInterrupt)
}
}
t.Run("direct", func(t *testing.T) {
t.Parallel()
testContainerCancel(t, func(c *container.Container) {
c.ForwardCancel = true
}, f)
})
t.Run("as root", func(t *testing.T) {
testContainerCancel(t, func(c *container.Container) {
c.ForwardCancel = true
c.InitAsRoot = true
c.Proc(fhs.AbsProc)
}, f)
})
}
func TestCancel(t *testing.T) {
t.Parallel()
f := func(ps *os.ProcessState, waitErr error) {
wantErr := context.Canceled
if !reflect.DeepEqual(waitErr, wantErr) {
if m, ok := container.InternalMessageFromError(waitErr); ok {
t.Error(m)
}
t.Errorf("Wait: error = %#v, want %#v", waitErr, wantErr)
}
if ps == nil {
t.Errorf("ProcessState unexpectedly returned nil")
} else if code := ps.ExitCode(); code != 0 {
t.Errorf("ExitCode: %d, want %d", code, 0)
}
}
t.Run("direct", func(t *testing.T) {
t.Parallel()
testContainerCancel(t, nil, f)
})
t.Run("as root", func(t *testing.T) {
testContainerCancel(t, func(c *container.Container) {
c.InitAsRoot = true
c.Proc(fhs.AbsProc)
}, f)
})
} }
func TestContainerString(t *testing.T) { func TestContainerString(t *testing.T) {
@@ -626,6 +693,8 @@ func init() {
}) })
c.Command("container", command.UsageInternal, func(args []string) error { c.Command("container", command.UsageInternal, func(args []string) error {
asRoot := os.Getenv("HAKUREI_TEST_SUFFIX") == " as root"
if len(args) != 1 { if len(args) != 1 {
return syscall.EINVAL return syscall.EINVAL
} }
@@ -643,6 +712,66 @@ func init() {
return fmt.Errorf("gid: %d, want %d", gid, tc.gid) return fmt.Errorf("gid: %d, want %d", gid, tc.gid)
} }
// no attack surface increase during as root due to no_new_privs
var wantBounding uintptr = 1
asRootNot := " not"
if !asRoot {
wantBounding = 0
asRootNot = ""
}
const (
PR_CAP_AMBIENT = 0x2f
PR_CAP_AMBIENT_IS_SET = 0x1
)
for i := range container.LastCap(nil) + 1 {
r, _, errno := syscall.Syscall(
syscall.SYS_PRCTL,
PR_CAP_AMBIENT,
PR_CAP_AMBIENT_IS_SET,
i,
)
if errno != 0 {
return os.NewSyscallError("prctl", errno)
}
if r != 0 {
return fmt.Errorf("capability %d in ambient set", i)
}
r, _, errno = syscall.Syscall(
syscall.SYS_PRCTL,
syscall.PR_CAPBSET_READ,
i,
0,
)
if errno != 0 {
return os.NewSyscallError("prctl", errno)
}
if r != wantBounding {
return fmt.Errorf("capability %d%s in bounding set", i, asRootNot)
}
}
const _LINUX_CAPABILITY_VERSION_3 = 0x20080522
var capData struct {
effective uint32
permitted uint32
inheritable uint32
}
if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&struct {
version uint32
pid int32
}{_LINUX_CAPABILITY_VERSION_3, 0})), uintptr(unsafe.Pointer(&capData)), 0); errno != 0 {
return os.NewSyscallError("capget", errno)
}
if max(capData.effective, capData.permitted, capData.inheritable) != 0 {
return fmt.Errorf(
"effective = %d, permitted = %d, inheritable = %d",
capData.effective, capData.permitted, capData.inheritable,
)
}
wantHost := hostnameFromTestCase(tc.name) wantHost := hostnameFromTestCase(tc.name)
if host, err := os.Hostname(); err != nil { if host, err := os.Hostname(); err != nil {
return fmt.Errorf("cannot get hostname: %v", err) return fmt.Errorf("cannot get hostname: %v", err)
@@ -737,7 +866,6 @@ func init() {
const ( const (
envDoCheck = "HAKUREI_TEST_DO_CHECK" envDoCheck = "HAKUREI_TEST_DO_CHECK"
helperDefaultTimeout = 5 * time.Second
helperInnerPath = "/usr/bin/helper" helperInnerPath = "/usr/bin/helper"
) )
@@ -761,7 +889,7 @@ func TestMain(m *testing.M) {
} }
c.MustParse(os.Args[1:], func(err error) { c.MustParse(os.Args[1:], func(err error) {
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err)
} }
}) })
return return
+62 -9
View File
@@ -1,8 +1,10 @@
package container package container
import ( import (
"context"
"io" "io"
"io/fs" "io/fs"
"net"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
@@ -12,6 +14,9 @@ import (
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/ext"
"hakurei.app/internal/netlink"
"hakurei.app/internal/params"
"hakurei.app/message" "hakurei.app/message"
) )
@@ -52,7 +57,7 @@ type syscallDispatcher interface {
// isatty provides [Isatty]. // isatty provides [Isatty].
isatty(fd int) bool isatty(fd int) bool
// receive provides [Receive]. // receive provides [Receive].
receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error) receive(key string, e any, fdp *int) (closeFunc func() error, err error)
// bindMount provides procPaths.bindMount. // bindMount provides procPaths.bindMount.
bindMount(msg message.Msg, source, target string, flags uintptr) error bindMount(msg message.Msg, source, target string, flags uintptr) error
@@ -60,10 +65,12 @@ type syscallDispatcher interface {
remount(msg message.Msg, target string, flags uintptr) error remount(msg message.Msg, target string, flags uintptr) error
// mountTmpfs provides mountTmpfs. // mountTmpfs provides mountTmpfs.
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
// mountOverlay provides mountOverlay.
mountOverlay(target string, options [][2]string) error
// ensureFile provides ensureFile. // ensureFile provides ensureFile.
ensureFile(name string, perm, pperm os.FileMode) error ensureFile(name string, perm, pperm os.FileMode) error
// mustLoopback provides mustLoopback. // mustLoopback provides mustLoopback.
mustLoopback(msg message.Msg) mustLoopback(ctx context.Context, msg message.Msg)
// seccompLoad provides [seccomp.Load]. // seccompLoad provides [seccomp.Load].
seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error
@@ -141,18 +148,18 @@ func (k direct) new(f func(k syscallDispatcher)) { go f(k) }
func (direct) lockOSThread() { runtime.LockOSThread() } func (direct) lockOSThread() { runtime.LockOSThread() }
func (direct) setPtracer(pid uintptr) error { return SetPtracer(pid) } func (direct) setPtracer(pid uintptr) error { return ext.SetPtracer(pid) }
func (direct) setDumpable(dumpable uintptr) error { return SetDumpable(dumpable) } func (direct) setDumpable(dumpable uintptr) error { return ext.SetDumpable(dumpable) }
func (direct) setNoNewPrivs() error { return SetNoNewPrivs() } func (direct) setNoNewPrivs() error { return setNoNewPrivs() }
func (direct) lastcap(msg message.Msg) uintptr { return LastCap(msg) } func (direct) lastcap(msg message.Msg) uintptr { return LastCap(msg) }
func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) } func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) }
func (direct) capBoundingSetDrop(cap uintptr) error { return capBoundingSetDrop(cap) } func (direct) capBoundingSetDrop(cap uintptr) error { return capBoundingSetDrop(cap) }
func (direct) capAmbientClearAll() error { return capAmbientClearAll() } func (direct) capAmbientClearAll() error { return capAmbientClearAll() }
func (direct) capAmbientRaise(cap uintptr) error { return capAmbientRaise(cap) } func (direct) capAmbientRaise(cap uintptr) error { return capAmbientRaise(cap) }
func (direct) isatty(fd int) bool { return Isatty(fd) } func (direct) isatty(fd int) bool { return ext.Isatty(fd) }
func (direct) receive(key string, e any, fdp *uintptr) (func() error, error) { func (direct) receive(key string, e any, fdp *int) (func() error, error) {
return Receive(key, e, fdp) return params.Receive(key, e, fdp)
} }
func (direct) bindMount(msg message.Msg, source, target string, flags uintptr) error { func (direct) bindMount(msg message.Msg, source, target string, flags uintptr) error {
@@ -164,10 +171,56 @@ func (direct) remount(msg message.Msg, target string, flags uintptr) error {
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error { func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
return mountTmpfs(k, fsname, target, flags, size, perm) return mountTmpfs(k, fsname, target, flags, size, perm)
} }
func (k direct) mountOverlay(target string, options [][2]string) error {
return mountOverlay(target, options)
}
func (direct) ensureFile(name string, perm, pperm os.FileMode) error { func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
return ensureFile(name, perm, pperm) return ensureFile(name, perm, pperm)
} }
func (direct) mustLoopback(msg message.Msg) { mustLoopback(msg) } func (direct) mustLoopback(ctx context.Context, msg message.Msg) {
var lo int
if ifi, err := net.InterfaceByName("lo"); err != nil {
msg.GetLogger().Fatalln(err)
} else {
lo = ifi.Index
}
c, err := netlink.DialRoute(0)
if err != nil {
msg.GetLogger().Fatalln(err)
}
must := func(err error) {
if err == nil {
return
}
if closeErr := c.Close(); closeErr != nil {
msg.Verbosef("cannot close RTNETLINK: %v", closeErr)
}
switch err.(type) {
case *os.SyscallError:
msg.GetLogger().Fatalf("cannot %v", err)
case syscall.Errno:
msg.GetLogger().Fatalf("RTNETLINK answers: %v", err)
default:
if err == context.DeadlineExceeded || err == context.Canceled {
msg.GetLogger().Fatalf("interrupted RTNETLINK operation")
}
msg.GetLogger().Fatal("RTNETLINK answers with malformed message")
}
}
must(c.SendNewaddrLo(ctx, uint32(lo)))
must(c.SendIfInfomsg(ctx, syscall.RTM_NEWLINK, 0, &syscall.IfInfomsg{
Family: syscall.AF_UNSPEC,
Index: int32(lo),
Flags: syscall.IFF_UP,
Change: syscall.IFF_UP,
}))
must(c.Close())
}
func (direct) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error { func (direct) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error {
return seccomp.Load(rules, flags) return seccomp.Load(rules, flags)
+21 -5
View File
@@ -2,6 +2,7 @@ package container
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@@ -18,7 +19,7 @@ import (
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
"hakurei.app/message" "hakurei.app/message"
) )
@@ -389,7 +390,7 @@ func (k *kstub) isatty(fd int) bool {
return expect.Ret.(bool) return expect.Ret.(bool)
} }
func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error) { func (k *kstub) receive(key string, e any, fdp *int) (closeFunc func() error, err error) {
k.Helper() k.Helper()
expect := k.Expects("receive") expect := k.Expects("receive")
@@ -407,10 +408,17 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
} }
return nil return nil
} }
// avoid changing test cases
var fdpComp *uintptr
if fdp != nil {
fdpComp = new(uintptr(*fdp))
}
err = expect.Error( err = expect.Error(
stub.CheckArg(k.Stub, "key", key, 0), stub.CheckArg(k.Stub, "key", key, 0),
stub.CheckArgReflect(k.Stub, "e", e, 1), stub.CheckArgReflect(k.Stub, "e", e, 1),
stub.CheckArgReflect(k.Stub, "fdp", fdp, 2)) stub.CheckArgReflect(k.Stub, "fdp", fdpComp, 2))
// 3 is unused so stores params // 3 is unused so stores params
if expect.Args[3] != nil { if expect.Args[3] != nil {
@@ -425,7 +433,7 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
if expect.Args[4] != nil { if expect.Args[4] != nil {
if v, ok := expect.Args[4].(uintptr); ok && v >= 3 { if v, ok := expect.Args[4].(uintptr); ok && v >= 3 {
if fdp != nil { if fdp != nil {
*fdp = v *fdp = int(v)
} }
} }
} }
@@ -460,6 +468,14 @@ func (k *kstub) mountTmpfs(fsname, target string, flags uintptr, size int, perm
stub.CheckArg(k.Stub, "perm", perm, 4)) stub.CheckArg(k.Stub, "perm", perm, 4))
} }
func (k *kstub) mountOverlay(target string, options [][2]string) error {
k.Helper()
return k.Expects("mountOverlay").Error(
stub.CheckArg(k.Stub, "target", target, 0),
stub.CheckArgReflect(k.Stub, "options", options, 1),
)
}
func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error { func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
k.Helper() k.Helper()
return k.Expects("ensureFile").Error( return k.Expects("ensureFile").Error(
@@ -468,7 +484,7 @@ func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
stub.CheckArg(k.Stub, "pperm", pperm, 2)) stub.CheckArg(k.Stub, "pperm", pperm, 2))
} }
func (*kstub) mustLoopback(message.Msg) { /* noop */ } func (*kstub) mustLoopback(context.Context, message.Msg) { /* noop */ }
func (k *kstub) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error { func (k *kstub) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error {
k.Helper() k.Helper()
+6 -2
View File
@@ -5,9 +5,9 @@ import (
"os" "os"
"syscall" "syscall"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/vfs"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/vfs"
) )
// messageFromError returns a printable error message for a supported concrete type. // messageFromError returns a printable error message for a supported concrete type.
@@ -118,6 +118,10 @@ func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
// mount wraps syscall.Mount for error handling. // mount wraps syscall.Mount for error handling.
func mount(source, target, fstype string, flags uintptr, data string) error { func mount(source, target, fstype string, flags uintptr, data string) error {
if max(len(source), len(target), len(data))+1 > os.Getpagesize() {
return &MountError{source, target, fstype, flags, data, syscall.ENOMEM}
}
err := syscall.Mount(source, target, fstype, flags, data) err := syscall.Mount(source, target, fstype, flags, data)
if err == nil { if err == nil {
return nil return nil
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"syscall" "syscall"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
"hakurei.app/container/vfs" "hakurei.app/vfs"
) )
func TestMessageFromError(t *testing.T) { func TestMessageFromError(t *testing.T) {
-37
View File
@@ -1,37 +0,0 @@
package container
import (
"fmt"
"log"
"os"
"sync"
"hakurei.app/message"
)
var (
executable string
executableOnce sync.Once
)
func copyExecutable(msg message.Msg) {
if name, err := os.Executable(); err != nil {
m := fmt.Sprintf("cannot read executable path: %v", err)
if msg != nil {
msg.BeforeExit()
msg.GetLogger().Fatal(m)
} else {
log.Fatal(m)
}
} else {
executable = name
}
}
// MustExecutable calls [os.Executable] and terminates the process on error.
//
// Deprecated: This is no longer used and will be removed in 0.4.
func MustExecutable(msg message.Msg) string {
executableOnce.Do(func() { copyExecutable(msg) })
return executable
}
-18
View File
@@ -1,18 +0,0 @@
package container_test
import (
"os"
"testing"
"hakurei.app/container"
"hakurei.app/message"
)
func TestExecutable(t *testing.T) {
t.Parallel()
for i := 0; i < 16; i++ {
if got := container.MustExecutable(message.New(nil)); got != os.Args[0] {
t.Errorf("MustExecutable: %q, want %q", got, os.Args[0])
}
}
}
+151 -63
View File
@@ -7,16 +7,21 @@ import (
"log" "log"
"os" "os"
"os/exec" "os/exec"
"path" "os/signal"
"path/filepath"
"slices" "slices"
"strconv" "strconv"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
. "syscall" . "syscall"
"time" "time"
"hakurei.app/container/fhs" "hakurei.app/check"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/ext"
"hakurei.app/fhs"
"hakurei.app/internal/params"
"hakurei.app/message" "hakurei.app/message"
) )
@@ -145,64 +150,76 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
} }
var ( var (
params initParams param initParams
closeSetup func() error closeSetup func() error
setupFd uintptr setupFd int
offsetSetup int
) )
if f, err := k.receive(setupEnv, &params, &setupFd); err != nil { if f, err := k.receive(setupEnv, &param, &setupFd); err != nil {
if errors.Is(err, EBADF) { if errors.Is(err, EBADF) {
k.fatal(msg, "invalid setup descriptor") k.fatal(msg, "invalid setup descriptor")
} }
if errors.Is(err, ErrReceiveEnv) { if errors.Is(err, params.ErrReceiveEnv) {
k.fatal(msg, setupEnv+" not set") k.fatal(msg, setupEnv+" not set")
} }
k.fatalf(msg, "cannot decode init setup payload: %v", err) k.fatalf(msg, "cannot decode init setup payload: %v", err)
} else { } else {
if params.Ops == nil { if param.Ops == nil {
k.fatal(msg, "invalid setup parameters") k.fatal(msg, "invalid setup parameters")
} }
if params.ParentPerm == 0 { if param.ParentPerm == 0 {
params.ParentPerm = 0755 param.ParentPerm = 0755
} }
msg.SwapVerbose(params.Verbose) msg.SwapVerbose(param.Verbose)
msg.Verbose("received setup parameters") msg.Verbose("received setup parameters")
closeSetup = f closeSetup = f
offsetSetup = int(setupFd + 1)
} }
if !params.HostNet { if !param.HostNet {
k.mustLoopback(msg) ctx, cancel := signal.NotifyContext(context.Background(), CancelSignal,
os.Interrupt, SIGTERM, SIGQUIT)
defer cancel() // for panics
k.mustLoopback(ctx, msg)
cancel()
}
uid, gid := param.Uid, param.Gid
if param.InitAsRoot {
uid, gid = 0, 0
} }
// write uid/gid map here so parent does not need to set dumpable // write uid/gid map here so parent does not need to set dumpable
if err := k.setDumpable(SUID_DUMP_USER); err != nil { if err := k.setDumpable(ext.SUID_DUMP_USER); err != nil {
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err) k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
} }
if err := k.writeFile(fhs.Proc+"self/uid_map", if err := k.writeFile(
append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...), fhs.Proc+"self/uid_map",
0); err != nil { []byte(strconv.Itoa(uid)+" "+strconv.Itoa(param.HostUid)+" 1\n"),
0,
); err != nil {
k.fatalf(msg, "%v", err) k.fatalf(msg, "%v", err)
} }
if err := k.writeFile(fhs.Proc+"self/setgroups", if err := k.writeFile(
fhs.Proc+"self/setgroups",
[]byte("deny\n"), []byte("deny\n"),
0); err != nil && !os.IsNotExist(err) { 0,
); err != nil && !os.IsNotExist(err) {
k.fatalf(msg, "%v", err) k.fatalf(msg, "%v", err)
} }
if err := k.writeFile(fhs.Proc+"self/gid_map", if err := k.writeFile(fhs.Proc+"self/gid_map",
append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...), []byte(strconv.Itoa(gid)+" "+strconv.Itoa(param.HostGid)+" 1\n"),
0); err != nil { 0,
); err != nil {
k.fatalf(msg, "%v", err) k.fatalf(msg, "%v", err)
} }
if err := k.setDumpable(SUID_DUMP_DISABLE); err != nil { if err := k.setDumpable(ext.SUID_DUMP_DISABLE); err != nil {
k.fatalf(msg, "cannot set SUID_DUMP_DISABLE: %v", err) k.fatalf(msg, "cannot set SUID_DUMP_DISABLE: %v", err)
} }
oldmask := k.umask(0) oldmask := k.umask(0)
if params.Hostname != "" { if param.Hostname != "" {
if err := k.sethostname([]byte(params.Hostname)); err != nil { if err := k.sethostname([]byte(param.Hostname)); err != nil {
k.fatalf(msg, "cannot set hostname: %v", err) k.fatalf(msg, "cannot set hostname: %v", err)
} }
} }
@@ -215,15 +232,32 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
state := &setupState{process: make(map[int]WaitStatus), Params: &params.Params, Msg: msg, Context: ctx} state := &setupState{process: make(map[int]WaitStatus), Params: &param.Params, Msg: msg, Context: ctx}
defer cancel() defer cancel()
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
k.fatalf(msg, "cannot mount intermediate root: %v", optionalErrorUnwrap(err))
}
if err := k.chdir(intermediateHostPath); err != nil {
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
}
if len(param.Binfmt) > 0 {
for i, e := range param.Binfmt {
if pathname, err := k.evalSymlinks(e.Interpreter.String()); err != nil {
k.fatal(msg, err)
} else if param.Binfmt[i].Interpreter, err = check.NewAbs(pathname); err != nil {
k.fatal(msg, err)
}
}
}
/* early is called right before pivot_root into intermediate root; /* early is called right before pivot_root into intermediate root;
this step is mostly for gathering information that would otherwise be this step is mostly for gathering information that would otherwise be
difficult to obtain via library functions after pivot_root, and difficult to obtain via library functions after pivot_root, and
implementations are expected to avoid changing the state of the mount implementations are expected to avoid changing the state of the mount
namespace */ namespace */
for i, op := range *params.Ops { for i, op := range *param.Ops {
if op == nil || !op.Valid() { if op == nil || !op.Valid() {
k.fatalf(msg, "invalid op at index %d", i) k.fatalf(msg, "invalid op at index %d", i)
} }
@@ -237,13 +271,6 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
} }
} }
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
k.fatalf(msg, "cannot mount intermediate root: %v", optionalErrorUnwrap(err))
}
if err := k.chdir(intermediateHostPath); err != nil {
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
}
if err := k.mkdir(sysrootDir, 0755); err != nil { if err := k.mkdir(sysrootDir, 0755); err != nil {
k.fatalf(msg, "%v", err) k.fatalf(msg, "%v", err)
} }
@@ -266,7 +293,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
step sets up the container filesystem, and implementations are expected to step sets up the container filesystem, and implementations are expected to
keep the host root and sysroot mount points intact but otherwise can do keep the host root and sysroot mount points intact but otherwise can do
whatever they need to. Calling chdir is allowed but discouraged. */ whatever they need to. Calling chdir is allowed but discouraged. */
for i, op := range *params.Ops { for i, op := range *param.Ops {
// ops already checked during early setup // ops already checked during early setup
if prefix, ok := op.prefix(); ok { if prefix, ok := op.prefix(); ok {
msg.Verbosef("%s %s", prefix, op) msg.Verbosef("%s %s", prefix, op)
@@ -280,6 +307,48 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
} }
} }
if len(param.Binfmt) > 0 {
const interpreter = "/interpreter"
if param.BinfmtPath == nil {
param.BinfmtPath = fhs.AbsProcSys.Append("fs/binfmt_misc")
}
binfmt := sysrootPath + param.BinfmtPath.String()
if err := k.mkdirAll(binfmt, 0); err != nil {
k.fatal(msg, err)
}
if err := k.mount(
SourceBinfmtMisc,
binfmt,
FstypeBinfmtMisc,
MS_NOSUID|MS_NOEXEC|MS_NODEV,
zeroString,
); err != nil {
k.fatal(msg, err)
}
var buf strings.Builder
buf.Grow(1920)
register := binfmt + "/register"
for i, e := range param.Binfmt {
if err := k.symlink(hostPath+e.Interpreter.String(), interpreter); err != nil {
k.fatal(msg, err)
} else if err = k.writeFile(register, []byte(":"+
strconv.Itoa(i)+":"+
"M:"+
strconv.Itoa(int(e.Offset))+":"+
escapeBinfmt(&buf, e.Magic)+":"+
escapeBinfmt(&buf, e.Mask)+":"+
interpreter+":"+
"F"), 0); err != nil {
k.fatal(msg, err)
} else if err = k.remove(interpreter); err != nil {
k.fatal(msg, err)
}
}
}
// setup requiring host root complete at this point // setup requiring host root complete at this point
if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil { if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil {
k.fatalf(msg, "cannot make host root rprivate: %v", optionalErrorUnwrap(err)) k.fatalf(msg, "cannot make host root rprivate: %v", optionalErrorUnwrap(err))
@@ -290,7 +359,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
{ {
var fd int var fd int
if err := IgnoringEINTR(func() (err error) { if err := ext.IgnoringEINTR(func() (err error) {
fd, err = k.open(fhs.Root, O_DIRECTORY|O_RDONLY, 0) fd, err = k.open(fhs.Root, O_DIRECTORY|O_RDONLY, 0)
return return
}); err != nil { }); err != nil {
@@ -318,11 +387,19 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
} }
} }
var keepCaps []uintptr
if param.Privileged {
keepCaps = append(keepCaps, CAP_SYS_ADMIN, CAP_SETPCAP)
}
if param.InitAsRoot {
keepCaps = append(keepCaps, CAP_SETFCAP)
}
if err := k.capAmbientClearAll(); err != nil { if err := k.capAmbientClearAll(); err != nil {
k.fatalf(msg, "cannot clear the ambient capability set: %v", err) k.fatalf(msg, "cannot clear the ambient capability set: %v", err)
} }
for i := uintptr(0); i <= lastcap; i++ { for i := range lastcap + 1 {
if params.Privileged && i == CAP_SYS_ADMIN { if slices.Contains(keepCaps, i) {
continue continue
} }
if err := k.capBoundingSetDrop(i); err != nil { if err := k.capBoundingSetDrop(i); err != nil {
@@ -331,27 +408,30 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
} }
var keep [2]uint32 var keep [2]uint32
if params.Privileged { for _, c := range keepCaps {
keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN) keep[capToIndex(c)] |= capToMask(c)
}
if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil {
k.fatalf(msg, "cannot raise CAP_SYS_ADMIN: %v", err)
}
}
if err := k.capset( if err := k.capset(
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
&[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}}, &[2]capData{{keep[0], keep[0], keep[0]}, {keep[1], keep[1], keep[1]}},
); err != nil { ); err != nil {
k.fatalf(msg, "cannot capset: %v", err) k.fatalf(msg, "cannot capset: %v", err)
} }
if !params.SeccompDisable { for _, c := range keepCaps {
rules := params.SeccompRules if err := k.capAmbientRaise(c); err != nil {
if len(rules) == 0 { // non-empty rules slice always overrides presets k.fatalf(msg, "cannot raise %#x: %v", c, err)
msg.Verbosef("resolving presets %#x", params.SeccompPresets)
rules = seccomp.Preset(params.SeccompPresets, params.SeccompFlags)
} }
if err := k.seccompLoad(rules, params.SeccompFlags); err != nil { }
if !param.SeccompDisable {
rules := param.SeccompRules
if len(rules) == 0 { // non-empty rules slice always overrides presets
msg.Verbosef("resolving presets %#x", param.SeccompPresets)
rules = seccomp.Preset(param.SeccompPresets, param.SeccompFlags)
}
if err := k.seccompLoad(rules, param.SeccompFlags); err != nil {
// this also indirectly asserts PR_SET_NO_NEW_PRIVS // this also indirectly asserts PR_SET_NO_NEW_PRIVS
k.fatalf(msg, "cannot load syscall filter: %v", err) k.fatalf(msg, "cannot load syscall filter: %v", err)
} }
@@ -360,10 +440,10 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
msg.Verbose("syscall filter not configured") msg.Verbose("syscall filter not configured")
} }
extraFiles := make([]*os.File, params.Count) extraFiles := make([]*os.File, param.Count)
for i := range extraFiles { for i := range extraFiles {
// setup fd is placed before all extra files // setup fd is placed before all extra files
extraFiles[i] = k.newFile(uintptr(offsetSetup+i), "extra file "+strconv.Itoa(i)) extraFiles[i] = k.newFile(uintptr(setupFd+1+i), "extra file "+strconv.Itoa(i))
} }
k.umask(oldmask) k.umask(oldmask)
@@ -441,7 +521,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
// called right before startup of initial process, all state changes to the // called right before startup of initial process, all state changes to the
// current process is prohibited during late // current process is prohibited during late
for i, op := range *params.Ops { for i, op := range *param.Ops {
// ops already checked during early setup // ops already checked during early setup
if err := op.late(state, k); err != nil { if err := op.late(state, k); err != nil {
if m, ok := messageFromError(err); ok { if m, ok := messageFromError(err); ok {
@@ -462,14 +542,22 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
k.fatalf(msg, "cannot close setup pipe: %v", err) k.fatalf(msg, "cannot close setup pipe: %v", err)
} }
cmd := exec.Command(params.Path.String()) cmd := exec.Command(param.Path.String())
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.Args = params.Args cmd.Args = param.Args
cmd.Env = params.Env cmd.Env = param.Env
cmd.ExtraFiles = extraFiles cmd.ExtraFiles = extraFiles
cmd.Dir = params.Dir.String() cmd.Dir = param.Dir.String()
msg.Verbosef("starting initial process %s", params.Path) if param.InitAsRoot {
cmd.SysProcAttr = &SysProcAttr{
Cloneflags: CLONE_NEWUSER,
UidMappings: []SysProcIDMap{{ContainerID: param.Uid, HostID: 0, Size: 1}},
GidMappings: []SysProcIDMap{{ContainerID: param.Gid, HostID: 0, Size: 1}},
}
}
msg.Verbosef("starting initial process %s", param.Path)
if err := k.start(cmd); err != nil { if err := k.start(cmd); err != nil {
k.fatalf(msg, "%v", err) k.fatalf(msg, "%v", err)
} }
@@ -487,9 +575,9 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
for { for {
select { select {
case s := <-sig: case s := <-sig:
if s == CancelSignal && params.ForwardCancel && cmd.Process != nil { if s == CancelSignal && param.ForwardCancel && cmd.Process != nil {
msg.Verbose("forwarding context cancellation") msg.Verbose("forwarding context cancellation")
if err := k.signal(cmd, os.Interrupt); err != nil { if err := k.signal(cmd, os.Interrupt); err != nil && !errors.Is(err, os.ErrProcessDone) {
k.printf(msg, "cannot forward cancellation: %v", err) k.printf(msg, "cannot forward cancellation: %v", err)
} }
continue continue
@@ -519,7 +607,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
cancel() cancel()
// start timeout early // start timeout early
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }() go func() { time.Sleep(param.AdoptWaitDelay); close(timeout) }()
// close initial process files; this also keeps them alive // close initial process files; this also keeps them alive
for _, f := range extraFiles { for _, f := range extraFiles {
@@ -563,7 +651,7 @@ func TryArgv0(msg message.Msg) {
msg = message.New(log.Default()) msg = message.New(log.Default())
} }
if len(os.Args) > 0 && path.Base(os.Args[0]) == initName { if len(os.Args) > 0 && filepath.Base(os.Args[0]) == initName {
Init(msg) Init(msg)
msg.BeforeExit() msg.BeforeExit()
os.Exit(0) os.Exit(0)
+77 -76
View File
@@ -7,10 +7,11 @@ import (
"testing" "testing"
"time" "time"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/container/stub" "hakurei.app/internal/params"
"hakurei.app/internal/stub"
) )
func TestInitEntrypoint(t *testing.T) { func TestInitEntrypoint(t *testing.T) {
@@ -40,7 +41,7 @@ func TestInitEntrypoint(t *testing.T) {
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
call("setPtracer", stub.ExpectArgs{uintptr(0)}, nil, nil), call("setPtracer", stub.ExpectArgs{uintptr(0)}, nil, nil),
call("receive", stub.ExpectArgs{"HAKUREI_SETUP", new(initParams), new(uintptr)}, nil, ErrReceiveEnv), call("receive", stub.ExpectArgs{"HAKUREI_SETUP", new(initParams), new(uintptr)}, nil, params.ErrReceiveEnv),
call("fatal", stub.ExpectArgs{[]any{"HAKUREI_SETUP not set"}}, nil, nil), call("fatal", stub.ExpectArgs{[]any{"HAKUREI_SETUP not set"}}, nil, nil),
}, },
}, nil}, }, nil},
@@ -331,6 +332,8 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil), call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
/* end early */ /* end early */
@@ -369,6 +372,8 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil), call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
/* end early */ /* end early */
@@ -407,6 +412,8 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", stub.UniqueError(61)), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", stub.UniqueError(61)),
call("fatalf", stub.ExpectArgs{"cannot prepare op at index %d: %v", []any{0, stub.UniqueError(61)}}, nil, nil), call("fatalf", stub.ExpectArgs{"cannot prepare op at index %d: %v", []any{0, stub.UniqueError(61)}}, nil, nil),
@@ -446,6 +453,8 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", &os.PathError{Op: "readlink", Path: "/", Err: stub.UniqueError(60)}), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", &os.PathError{Op: "readlink", Path: "/", Err: stub.UniqueError(60)}),
call("fatal", stub.ExpectArgs{[]any{"cannot readlink /: unique error 60 injected by the test suite"}}, nil, nil), call("fatal", stub.ExpectArgs{[]any{"cannot readlink /: unique error 60 injected by the test suite"}}, nil, nil),
@@ -485,9 +494,6 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
/* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, stub.UniqueError(58)), call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, stub.UniqueError(58)),
call("fatalf", stub.ExpectArgs{"cannot mount intermediate root: %v", []any{stub.UniqueError(58)}}, nil, nil), call("fatalf", stub.ExpectArgs{"cannot mount intermediate root: %v", []any{stub.UniqueError(58)}}, nil, nil),
}, },
@@ -525,9 +531,6 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
/* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil), call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, stub.UniqueError(56)), call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, stub.UniqueError(56)),
call("fatalf", stub.ExpectArgs{"cannot enter intermediate host path: %v", []any{stub.UniqueError(56)}}, nil, nil), call("fatalf", stub.ExpectArgs{"cannot enter intermediate host path: %v", []any{stub.UniqueError(56)}}, nil, nil),
@@ -566,11 +569,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, stub.UniqueError(54)), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, stub.UniqueError(54)),
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(54)}}, nil, nil), call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(54)}}, nil, nil),
}, },
@@ -608,11 +611,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, stub.UniqueError(52)), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, stub.UniqueError(52)),
call("fatalf", stub.ExpectArgs{"cannot bind sysroot: %v", []any{stub.UniqueError(52)}}, nil, nil), call("fatalf", stub.ExpectArgs{"cannot bind sysroot: %v", []any{stub.UniqueError(52)}}, nil, nil),
@@ -651,11 +654,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, stub.UniqueError(50)), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, stub.UniqueError(50)),
@@ -695,11 +698,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -740,11 +743,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -786,11 +789,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -841,11 +844,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -896,11 +899,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -952,11 +955,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1009,11 +1012,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1068,11 +1071,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1128,11 +1131,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1189,11 +1192,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1251,11 +1254,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1314,11 +1317,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1378,11 +1381,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1443,11 +1446,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1509,11 +1512,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1583,11 +1586,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1621,7 +1624,6 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
@@ -1653,8 +1655,9 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, nil),
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, stub.UniqueError(19)), call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, stub.UniqueError(19)),
call("fatalf", stub.ExpectArgs{"cannot raise CAP_SYS_ADMIN: %v", []any{stub.UniqueError(19)}}, nil, nil), call("fatalf", stub.ExpectArgs{"cannot raise %#x: %v", []any{uintptr(0x15), stub.UniqueError(19)}}, nil, nil),
}, },
}, nil}, }, nil},
@@ -1690,11 +1693,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1728,7 +1731,6 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
@@ -1760,8 +1762,7 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil), call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, stub.UniqueError(17)),
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0, 0x200000, 0x200000}, {0, 0, 0}}}, nil, stub.UniqueError(17)),
call("fatalf", stub.ExpectArgs{"cannot capset: %v", []any{stub.UniqueError(17)}}, nil, nil), call("fatalf", stub.ExpectArgs{"cannot capset: %v", []any{stub.UniqueError(17)}}, nil, nil),
}, },
}, nil}, }, nil},
@@ -1798,11 +1799,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1836,7 +1837,6 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
@@ -1868,8 +1868,9 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, nil),
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil), call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0, 0x200000, 0x200000}, {0, 0, 0}}}, nil, nil), call("capAmbientRaise", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil), call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil),
call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, stub.UniqueError(15)), call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, stub.UniqueError(15)),
call("fatalf", stub.ExpectArgs{"cannot load syscall filter: %v", []any{stub.UniqueError(15)}}, nil, nil), call("fatalf", stub.ExpectArgs{"cannot load syscall filter: %v", []any{stub.UniqueError(15)}}, nil, nil),
@@ -1907,11 +1908,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2031,11 +2032,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil), call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2131,11 +2132,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil), call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2231,11 +2232,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil), call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2322,11 +2323,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil), call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2417,11 +2418,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil), call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2519,11 +2520,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2658,11 +2659,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil), call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
/* begin early */ /* begin early */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* end early */ /* end early */
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2696,7 +2697,6 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
@@ -2728,8 +2728,9 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil), call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, nil),
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil), call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0, 0x200000, 0x200000}, {0, 0, 0}}}, nil, nil), call("capAmbientRaise", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil), call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil),
call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, nil), call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, nil),
call("verbosef", stub.ExpectArgs{"%d filter rules loaded", []any{73}}, nil, nil), call("verbosef", stub.ExpectArgs{"%d filter rules loaded", []any{73}}, nil, nil),
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"os" "os"
"syscall" "syscall"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/std" "hakurei.app/container/std"
) )
+2 -2
View File
@@ -6,9 +6,9 @@ import (
"syscall" "syscall"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestBindMountOp(t *testing.T) { func TestBindMountOp(t *testing.T) {
+2 -2
View File
@@ -12,8 +12,8 @@ import (
"syscall" "syscall"
"time" "time"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/fhs" "hakurei.app/fhs"
) )
func init() { gob.Register(new(DaemonOp)) } func init() { gob.Register(new(DaemonOp)) }
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"os" "os"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
"hakurei.app/message" "hakurei.app/message"
) )
+10 -10
View File
@@ -3,11 +3,11 @@ package container
import ( import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"path" "path/filepath"
. "syscall" . "syscall"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/fhs" "hakurei.app/fhs"
) )
func init() { gob.Register(new(MountDevOp)) } func init() { gob.Register(new(MountDevOp)) }
@@ -46,7 +46,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
} }
for _, name := range []string{"null", "zero", "full", "random", "urandom", "tty"} { for _, name := range []string{"null", "zero", "full", "random", "urandom", "tty"} {
targetPath := path.Join(target, name) targetPath := filepath.Join(target, name)
if err := k.ensureFile(targetPath, 0444, state.ParentPerm); err != nil { if err := k.ensureFile(targetPath, 0444, state.ParentPerm); err != nil {
return err return err
} }
@@ -62,7 +62,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
for i, name := range []string{"stdin", "stdout", "stderr"} { for i, name := range []string{"stdin", "stdout", "stderr"} {
if err := k.symlink( if err := k.symlink(
fhs.Proc+"self/fd/"+string(rune(i+'0')), fhs.Proc+"self/fd/"+string(rune(i+'0')),
path.Join(target, name), filepath.Join(target, name),
); err != nil { ); err != nil {
return err return err
} }
@@ -72,13 +72,13 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
{fhs.Proc + "kcore", "core"}, {fhs.Proc + "kcore", "core"},
{"pts/ptmx", "ptmx"}, {"pts/ptmx", "ptmx"},
} { } {
if err := k.symlink(pair[0], path.Join(target, pair[1])); err != nil { if err := k.symlink(pair[0], filepath.Join(target, pair[1])); err != nil {
return err return err
} }
} }
devShmPath := path.Join(target, "shm") devShmPath := filepath.Join(target, "shm")
devPtsPath := path.Join(target, "pts") devPtsPath := filepath.Join(target, "pts")
for _, name := range []string{devShmPath, devPtsPath} { for _, name := range []string{devShmPath, devPtsPath} {
if err := k.mkdir(name, state.ParentPerm); err != nil { if err := k.mkdir(name, state.ParentPerm); err != nil {
return err return err
@@ -92,7 +92,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
if state.RetainSession { if state.RetainSession {
if k.isatty(Stdout) { if k.isatty(Stdout) {
consolePath := path.Join(target, "console") consolePath := filepath.Join(target, "console")
if err := k.ensureFile(consolePath, 0444, state.ParentPerm); err != nil { if err := k.ensureFile(consolePath, 0444, state.ParentPerm); err != nil {
return err return err
} }
@@ -110,7 +110,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
} }
if d.Mqueue { if d.Mqueue {
mqueueTarget := path.Join(target, "mqueue") mqueueTarget := filepath.Join(target, "mqueue")
if err := k.mkdir(mqueueTarget, state.ParentPerm); err != nil { if err := k.mkdir(mqueueTarget, state.ParentPerm); err != nil {
return err return err
} }
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"os" "os"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestMountDevOp(t *testing.T) { func TestMountDevOp(t *testing.T) {
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"hakurei.app/container/check" "hakurei.app/check"
) )
func init() { gob.Register(new(MkdirOp)) } func init() { gob.Register(new(MkdirOp)) }
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"os" "os"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestMkdirOp(t *testing.T) { func TestMkdirOp(t *testing.T) {
+42 -14
View File
@@ -4,10 +4,10 @@ import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"slices" "slices"
"strings"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/fhs" "hakurei.app/ext"
"hakurei.app/fhs"
) )
const ( const (
@@ -150,7 +150,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
if v, err := k.evalSymlinks(o.Upper.String()); err != nil { if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
return err return err
} else { } else {
o.upper = check.EscapeOverlayDataSegment(toHost(v)) o.upper = toHost(v)
} }
} }
@@ -158,7 +158,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
if v, err := k.evalSymlinks(o.Work.String()); err != nil { if v, err := k.evalSymlinks(o.Work.String()); err != nil {
return err return err
} else { } else {
o.work = check.EscapeOverlayDataSegment(toHost(v)) o.work = toHost(v)
} }
} }
} }
@@ -168,12 +168,39 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
if v, err := k.evalSymlinks(a.String()); err != nil { if v, err := k.evalSymlinks(a.String()); err != nil {
return err return err
} else { } else {
o.lower[i] = check.EscapeOverlayDataSegment(toHost(v)) o.lower[i] = toHost(v)
} }
} }
return nil return nil
} }
// mountOverlay sets up an overlay mount via [ext.FS].
func mountOverlay(target string, options [][2]string) error {
fs, err := ext.OpenFS(SourceOverlay, 0)
if err != nil {
return err
}
if err = fs.SetString("source", SourceOverlay); err != nil {
_ = fs.Close()
return err
}
for _, option := range options {
if err = fs.SetString(option[0], option[1]); err != nil {
_ = fs.Close()
return err
}
}
if err = fs.SetFlag(OptionOverlayUserxattr); err != nil {
_ = fs.Close()
return err
}
if err = fs.Mount(target, 0); err != nil {
_ = fs.Close()
return err
}
return fs.Close()
}
func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error { func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
target := o.Target.String() target := o.Target.String()
if !o.noPrefix { if !o.noPrefix {
@@ -194,7 +221,7 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
} }
} }
options := make([]string, 0, 4) options := make([][2]string, 0, 2+len(o.lower))
if o.upper == zeroString && o.work == zeroString { // readonly if o.upper == zeroString && o.work == zeroString { // readonly
if len(o.Lower) < 2 { if len(o.Lower) < 2 {
@@ -205,15 +232,16 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
if len(o.Lower) == 0 { if len(o.Lower) == 0 {
return &OverlayArgumentError{OverlayEmptyLower, zeroString} return &OverlayArgumentError{OverlayEmptyLower, zeroString}
} }
options = append(options, options = append(options, [][2]string{
OptionOverlayUpperdir+"="+o.upper, {OptionOverlayUpperdir, o.upper},
OptionOverlayWorkdir+"="+o.work) {OptionOverlayWorkdir, o.work},
}...)
}
for _, lower := range o.lower {
options = append(options, [2]string{OptionOverlayLowerdir + "+", lower})
} }
options = append(options,
OptionOverlayLowerdir+"="+strings.Join(o.lower, check.SpecialOverlayPath),
OptionOverlayUserxattr)
return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, check.SpecialOverlayOption)) return k.mountOverlay(target, options)
} }
func (o *MountOverlayOp) late(*setupState, syscallDispatcher) error { return nil } func (o *MountOverlayOp) late(*setupState, syscallDispatcher) error { return nil }
+35 -35
View File
@@ -5,8 +5,8 @@ import (
"os" "os"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestMountOverlayOp(t *testing.T) { func TestMountOverlayOp(t *testing.T) {
@@ -97,13 +97,12 @@ func TestMountOverlayOp(t *testing.T) {
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil),
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil), call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil),
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil), call("mkdirTemp", stub.ExpectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil),
call("mount", stub.ExpectArgs{"overlay", "/sysroot", "overlay", uintptr(0), "" + call("mountOverlay", stub.ExpectArgs{"/sysroot", [][2]string{
"upperdir=overlay.upper.32768," + {"upperdir", "overlay.upper.32768"},
"workdir=overlay.work.32768," + {"workdir", "overlay.work.32768"},
"lowerdir=" + {"lowerdir+", `/host/var/lib/planterette/base/debian:f92c9052`},
`/host/var/lib/planterette/base/debian\:f92c9052:` + {"lowerdir+", `/host/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052`},
`/host/var/lib/planterette/app/org.chromium.Chromium@debian\:f92c9052,` + }}, nil, nil),
"userxattr"}, nil, nil),
}, nil}, }, nil},
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{ {"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
@@ -129,11 +128,10 @@ func TestMountOverlayOp(t *testing.T) {
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil), call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("mkdirAll", stub.ExpectArgs{"/nix/store", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/nix/store", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"overlay", "/nix/store", "overlay", uintptr(0), "" + call("mountOverlay", stub.ExpectArgs{"/nix/store", [][2]string{
"lowerdir=" + {"lowerdir+", "/host/mnt-root/nix/.ro-store"},
"/host/mnt-root/nix/.ro-store:" + {"lowerdir+", "/host/mnt-root/nix/.ro-store0"},
"/host/mnt-root/nix/.ro-store0," + }}, nil, nil),
"userxattr"}, nil, nil),
}, nil}, }, nil},
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{ {"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
@@ -147,11 +145,10 @@ func TestMountOverlayOp(t *testing.T) {
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil), call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" + call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
"lowerdir=" + {"lowerdir+", "/host/mnt-root/nix/.ro-store"},
"/host/mnt-root/nix/.ro-store:" + {"lowerdir+", "/host/mnt-root/nix/.ro-store0"},
"/host/mnt-root/nix/.ro-store0," + }}, nil, nil),
"userxattr"}, nil, nil),
}, nil}, }, nil},
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{ {"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
@@ -219,7 +216,11 @@ func TestMountOverlayOp(t *testing.T) {
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil), call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "upperdir=/host/mnt-root/nix/.rw-store/.upper,workdir=/host/mnt-root/nix/.rw-store/.work,lowerdir=/host/mnt-root/nix/ro-store,userxattr"}, nil, stub.UniqueError(0)), call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
{"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
{"workdir", "/host/mnt-root/nix/.rw-store/.work"},
{"lowerdir+", "/host/mnt-root/nix/ro-store"},
}}, nil, stub.UniqueError(0)),
}, stub.UniqueError(0)}, }, stub.UniqueError(0)},
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{ {"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
@@ -233,11 +234,11 @@ func TestMountOverlayOp(t *testing.T) {
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil), call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" + call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
"upperdir=/host/mnt-root/nix/.rw-store/.upper," + {"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
"workdir=/host/mnt-root/nix/.rw-store/.work," + {"workdir", "/host/mnt-root/nix/.rw-store/.work"},
"lowerdir=/host/mnt-root/nix/ro-store," + {"lowerdir+", "/host/mnt-root/nix/ro-store"},
"userxattr"}, nil, nil), }}, nil, nil),
}, nil}, }, nil},
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{ {"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
@@ -261,16 +262,15 @@ func TestMountOverlayOp(t *testing.T) {
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil), call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" + call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
"upperdir=/host/mnt-root/nix/.rw-store/.upper," + {"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
"workdir=/host/mnt-root/nix/.rw-store/.work," + {"workdir", "/host/mnt-root/nix/.rw-store/.work"},
"lowerdir=" + {"lowerdir+", "/host/mnt-root/nix/ro-store"},
"/host/mnt-root/nix/ro-store:" + {"lowerdir+", "/host/mnt-root/nix/ro-store0"},
"/host/mnt-root/nix/ro-store0:" + {"lowerdir+", "/host/mnt-root/nix/ro-store1"},
"/host/mnt-root/nix/ro-store1:" + {"lowerdir+", "/host/mnt-root/nix/ro-store2"},
"/host/mnt-root/nix/ro-store2:" + {"lowerdir+", "/host/mnt-root/nix/ro-store3"},
"/host/mnt-root/nix/ro-store3," + }}, nil, nil),
"userxattr"}, nil, nil),
}, nil}, }, nil},
}) })
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"syscall" "syscall"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/fhs" "hakurei.app/fhs"
) )
const ( const (
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"os" "os"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestTmpfileOp(t *testing.T) { func TestTmpfileOp(t *testing.T) {
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
. "syscall" . "syscall"
"hakurei.app/container/check" "hakurei.app/check"
) )
func init() { gob.Register(new(MountProcOp)) } func init() { gob.Register(new(MountProcOp)) }
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"os" "os"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestMountProcOp(t *testing.T) { func TestMountProcOp(t *testing.T) {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"hakurei.app/container/check" "hakurei.app/check"
) )
func init() { gob.Register(new(RemountOp)) } func init() { gob.Register(new(RemountOp)) }
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"syscall" "syscall"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestRemountOp(t *testing.T) { func TestRemountOp(t *testing.T) {
+4 -4
View File
@@ -3,9 +3,9 @@ package container
import ( import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"path" "path/filepath"
"hakurei.app/container/check" "hakurei.app/check"
) )
func init() { gob.Register(new(SymlinkOp)) } func init() { gob.Register(new(SymlinkOp)) }
@@ -30,7 +30,7 @@ func (l *SymlinkOp) Valid() bool { return l != nil && l.Target != nil && l.LinkN
func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error { func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
if l.Dereference { if l.Dereference {
if !path.IsAbs(l.LinkName) { if !filepath.IsAbs(l.LinkName) {
return check.AbsoluteError(l.LinkName) return check.AbsoluteError(l.LinkName)
} }
if name, err := k.readlink(l.LinkName); err != nil { if name, err := k.readlink(l.LinkName); err != nil {
@@ -44,7 +44,7 @@ func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
func (l *SymlinkOp) apply(state *setupState, k syscallDispatcher) error { func (l *SymlinkOp) apply(state *setupState, k syscallDispatcher) error {
target := toSysroot(l.Target.String()) target := toSysroot(l.Target.String())
if err := k.mkdirAll(path.Dir(target), state.ParentPerm); err != nil { if err := k.mkdirAll(filepath.Dir(target), state.ParentPerm); err != nil {
return err return err
} }
return k.symlink(l.LinkName, target) return k.symlink(l.LinkName, target)
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"os" "os"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestSymlinkOp(t *testing.T) { func TestSymlinkOp(t *testing.T) {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"strconv" "strconv"
. "syscall" . "syscall"
"hakurei.app/container/check" "hakurei.app/check"
) )
func init() { gob.Register(new(MountTmpfsOp)) } func init() { gob.Register(new(MountTmpfsOp)) }
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"syscall" "syscall"
"testing" "testing"
"hakurei.app/container/check" "hakurei.app/check"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
) )
func TestMountTmpfsOp(t *testing.T) { func TestMountTmpfsOp(t *testing.T) {
-65
View File
@@ -1,65 +0,0 @@
package container_test
import (
"testing"
"unsafe"
"hakurei.app/container"
)
func TestLandlockString(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
rulesetAttr *container.RulesetAttr
want string
}{
{"nil", nil, "NULL"},
{"zero", new(container.RulesetAttr), "0"},
{"some", &container.RulesetAttr{Scoped: container.LANDLOCK_SCOPE_SIGNAL}, "scoped: signal"},
{"set", &container.RulesetAttr{
HandledAccessFS: container.LANDLOCK_ACCESS_FS_MAKE_SYM | container.LANDLOCK_ACCESS_FS_IOCTL_DEV | container.LANDLOCK_ACCESS_FS_WRITE_FILE,
HandledAccessNet: container.LANDLOCK_ACCESS_NET_BIND_TCP,
Scoped: container.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | container.LANDLOCK_SCOPE_SIGNAL,
}, "fs: write_file make_sym fs_ioctl_dev, net: bind_tcp, scoped: abstract_unix_socket signal"},
{"all", &container.RulesetAttr{
HandledAccessFS: container.LANDLOCK_ACCESS_FS_EXECUTE |
container.LANDLOCK_ACCESS_FS_WRITE_FILE |
container.LANDLOCK_ACCESS_FS_READ_FILE |
container.LANDLOCK_ACCESS_FS_READ_DIR |
container.LANDLOCK_ACCESS_FS_REMOVE_DIR |
container.LANDLOCK_ACCESS_FS_REMOVE_FILE |
container.LANDLOCK_ACCESS_FS_MAKE_CHAR |
container.LANDLOCK_ACCESS_FS_MAKE_DIR |
container.LANDLOCK_ACCESS_FS_MAKE_REG |
container.LANDLOCK_ACCESS_FS_MAKE_SOCK |
container.LANDLOCK_ACCESS_FS_MAKE_FIFO |
container.LANDLOCK_ACCESS_FS_MAKE_BLOCK |
container.LANDLOCK_ACCESS_FS_MAKE_SYM |
container.LANDLOCK_ACCESS_FS_REFER |
container.LANDLOCK_ACCESS_FS_TRUNCATE |
container.LANDLOCK_ACCESS_FS_IOCTL_DEV,
HandledAccessNet: container.LANDLOCK_ACCESS_NET_BIND_TCP |
container.LANDLOCK_ACCESS_NET_CONNECT_TCP,
Scoped: container.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
container.LANDLOCK_SCOPE_SIGNAL,
}, "fs: execute write_file read_file read_dir remove_dir remove_file make_char make_dir make_reg make_sock make_fifo make_block make_sym fs_refer fs_truncate fs_ioctl_dev, net: bind_tcp connect_tcp, scoped: abstract_unix_socket signal"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := tc.rulesetAttr.String(); got != tc.want {
t.Errorf("String: %s, want %s", got, tc.want)
}
})
}
}
func TestLandlockAttrSize(t *testing.T) {
t.Parallel()
want := 24
if got := unsafe.Sizeof(container.RulesetAttr{}); got != uintptr(want) {
t.Errorf("Sizeof: %d, want %d", got, want)
}
}
+9 -2
View File
@@ -6,8 +6,9 @@ import (
"os" "os"
. "syscall" . "syscall"
"hakurei.app/container/vfs" "hakurei.app/ext"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/vfs"
) )
/* /*
@@ -39,6 +40,9 @@ const (
// SourceMqueue is used when mounting mqueue. // SourceMqueue is used when mounting mqueue.
// Note that any source value is allowed when fstype is [FstypeMqueue]. // Note that any source value is allowed when fstype is [FstypeMqueue].
SourceMqueue = "mqueue" SourceMqueue = "mqueue"
// SourceBinfmtMisc is used when mounting binfmt_misc.
// Note that any source value is allowed when fstype is [SourceBinfmtMisc].
SourceBinfmtMisc = "binfmt_misc"
// SourceOverlay is used when mounting overlay. // SourceOverlay is used when mounting overlay.
// Note that any source value is allowed when fstype is [FstypeOverlay]. // Note that any source value is allowed when fstype is [FstypeOverlay].
SourceOverlay = "overlay" SourceOverlay = "overlay"
@@ -69,6 +73,9 @@ const (
// FstypeMqueue represents the mqueue pseudo-filesystem. // FstypeMqueue represents the mqueue pseudo-filesystem.
// This filesystem type is usually mounted on /dev/mqueue. // This filesystem type is usually mounted on /dev/mqueue.
FstypeMqueue = "mqueue" FstypeMqueue = "mqueue"
// FstypeBinfmtMisc represents the binfmt_misc pseudo-filesystem.
// This filesystem type is usually mounted on /proc/sys/fs/binfmt_misc.
FstypeBinfmtMisc = "binfmt_misc"
// FstypeOverlay represents the overlay pseudo-filesystem. // FstypeOverlay represents the overlay pseudo-filesystem.
// This filesystem type can be mounted anywhere in the container filesystem. // This filesystem type can be mounted anywhere in the container filesystem.
FstypeOverlay = "overlay" FstypeOverlay = "overlay"
@@ -115,7 +122,7 @@ func (p *procPaths) remount(msg message.Msg, target string, flags uintptr) error
var targetKFinal string var targetKFinal string
{ {
var destFd int var destFd int
if err := IgnoringEINTR(func() (err error) { if err := ext.IgnoringEINTR(func() (err error) {
destFd, err = p.k.open(targetFinal, O_PATH|O_CLOEXEC, 0) destFd, err = p.k.open(targetFinal, O_PATH|O_CLOEXEC, 0)
return return
}); err != nil { }); err != nil {
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"syscall" "syscall"
"testing" "testing"
"hakurei.app/container/stub" "hakurei.app/internal/stub"
"hakurei.app/container/vfs" "hakurei.app/vfs"
) )
func TestBindMount(t *testing.T) { func TestBindMount(t *testing.T) {
-269
View File
@@ -1,269 +0,0 @@
package container
import (
"encoding/binary"
"errors"
"net"
"os"
. "syscall"
"unsafe"
"hakurei.app/container/std"
"hakurei.app/message"
)
// rtnetlink represents a NETLINK_ROUTE socket.
type rtnetlink struct {
// Sent as part of rtnetlink messages.
pid uint32
// AF_NETLINK socket.
fd int
// Whether the socket is open.
ok bool
// Message sequence number.
seq uint32
}
// open creates the underlying NETLINK_ROUTE socket.
func (s *rtnetlink) open() (err error) {
if s.ok || s.fd < 0 {
return os.ErrInvalid
}
s.pid = uint32(Getpid())
if s.fd, err = Socket(
AF_NETLINK,
SOCK_RAW|SOCK_CLOEXEC,
NETLINK_ROUTE,
); err != nil {
return os.NewSyscallError("socket", err)
} else if err = Bind(s.fd, &SockaddrNetlink{
Family: AF_NETLINK,
Pid: s.pid,
}); err != nil {
_ = s.close()
return os.NewSyscallError("bind", err)
} else {
s.ok = true
return nil
}
}
// close closes the underlying NETLINK_ROUTE socket.
func (s *rtnetlink) close() error {
if !s.ok {
return os.ErrInvalid
}
s.ok = false
err := Close(s.fd)
s.fd = -1
return err
}
// roundtrip sends a netlink message and handles the reply.
func (s *rtnetlink) roundtrip(data []byte) error {
if !s.ok {
return os.ErrInvalid
}
defer func() { s.seq++ }()
if err := Sendto(s.fd, data, 0, &SockaddrNetlink{
Family: AF_NETLINK,
}); err != nil {
return os.NewSyscallError("sendto", err)
}
buf := make([]byte, Getpagesize())
done:
for {
p := buf
if n, _, err := Recvfrom(s.fd, p, 0); err != nil {
return os.NewSyscallError("recvfrom", err)
} else if n < NLMSG_HDRLEN {
return errors.ErrUnsupported
} else {
p = p[:n]
}
if msgs, err := ParseNetlinkMessage(p); err != nil {
return err
} else {
for _, m := range msgs {
if m.Header.Seq != s.seq || m.Header.Pid != s.pid {
return errors.ErrUnsupported
}
if m.Header.Type == NLMSG_DONE {
break done
}
if m.Header.Type == NLMSG_ERROR {
if len(m.Data) >= 4 {
errno := Errno(-std.Int(binary.NativeEndian.Uint32(m.Data)))
if errno == 0 {
return nil
}
return errno
}
return errors.ErrUnsupported
}
}
}
}
return nil
}
// mustRoundtrip calls roundtrip and terminates via msg for a non-nil error.
func (s *rtnetlink) mustRoundtrip(msg message.Msg, data []byte) {
err := s.roundtrip(data)
if err == nil {
return
}
if closeErr := Close(s.fd); closeErr != nil {
msg.Verbosef("cannot close: %v", err)
}
switch err.(type) {
case *os.SyscallError:
msg.GetLogger().Fatalf("cannot %v", err)
case Errno:
msg.GetLogger().Fatalf("RTNETLINK answers: %v", err)
default:
msg.GetLogger().Fatalln("RTNETLINK answers with unexpected message")
}
}
// newaddrLo represents a RTM_NEWADDR message with two addresses.
type newaddrLo struct {
header NlMsghdr
data IfAddrmsg
r0 RtAttr
a0 [4]byte // in_addr
r1 RtAttr
a1 [4]byte // in_addr
}
// sizeofNewaddrLo is the expected size of newaddrLo.
const sizeofNewaddrLo = NLMSG_HDRLEN + SizeofIfAddrmsg + (SizeofRtAttr+4)*2
// newaddrLo returns the address of a populated newaddrLo.
func (s *rtnetlink) newaddrLo(lo int) *newaddrLo {
return &newaddrLo{NlMsghdr{
Len: sizeofNewaddrLo,
Type: RTM_NEWADDR,
Flags: NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
Seq: s.seq,
Pid: s.pid,
}, IfAddrmsg{
Family: AF_INET,
Prefixlen: 8,
Flags: IFA_F_PERMANENT,
Scope: RT_SCOPE_HOST,
Index: uint32(lo),
}, RtAttr{
Len: uint16(SizeofRtAttr + len(newaddrLo{}.a0)),
Type: IFA_LOCAL,
}, [4]byte{127, 0, 0, 1}, RtAttr{
Len: uint16(SizeofRtAttr + len(newaddrLo{}.a1)),
Type: IFA_ADDRESS,
}, [4]byte{127, 0, 0, 1}}
}
func (msg *newaddrLo) toWireFormat() []byte {
var buf [sizeofNewaddrLo]byte
*(*uint32)(unsafe.Pointer(&buf[0:4][0])) = msg.header.Len
*(*uint16)(unsafe.Pointer(&buf[4:6][0])) = msg.header.Type
*(*uint16)(unsafe.Pointer(&buf[6:8][0])) = msg.header.Flags
*(*uint32)(unsafe.Pointer(&buf[8:12][0])) = msg.header.Seq
*(*uint32)(unsafe.Pointer(&buf[12:16][0])) = msg.header.Pid
buf[16] = msg.data.Family
buf[17] = msg.data.Prefixlen
buf[18] = msg.data.Flags
buf[19] = msg.data.Scope
*(*uint32)(unsafe.Pointer(&buf[20:24][0])) = msg.data.Index
*(*uint16)(unsafe.Pointer(&buf[24:26][0])) = msg.r0.Len
*(*uint16)(unsafe.Pointer(&buf[26:28][0])) = msg.r0.Type
copy(buf[28:32], msg.a0[:])
*(*uint16)(unsafe.Pointer(&buf[32:34][0])) = msg.r1.Len
*(*uint16)(unsafe.Pointer(&buf[34:36][0])) = msg.r1.Type
copy(buf[36:40], msg.a1[:])
return buf[:]
}
// newlinkLo represents a RTM_NEWLINK message.
type newlinkLo struct {
header NlMsghdr
data IfInfomsg
}
// sizeofNewlinkLo is the expected size of newlinkLo.
const sizeofNewlinkLo = NLMSG_HDRLEN + SizeofIfInfomsg
// newlinkLo returns the address of a populated newlinkLo.
func (s *rtnetlink) newlinkLo(lo int) *newlinkLo {
return &newlinkLo{NlMsghdr{
Len: sizeofNewlinkLo,
Type: RTM_NEWLINK,
Flags: NLM_F_REQUEST | NLM_F_ACK,
Seq: s.seq,
Pid: s.pid,
}, IfInfomsg{
Family: AF_UNSPEC,
Index: int32(lo),
Flags: IFF_UP,
Change: IFF_UP,
}}
}
func (msg *newlinkLo) toWireFormat() []byte {
var buf [sizeofNewlinkLo]byte
*(*uint32)(unsafe.Pointer(&buf[0:4][0])) = msg.header.Len
*(*uint16)(unsafe.Pointer(&buf[4:6][0])) = msg.header.Type
*(*uint16)(unsafe.Pointer(&buf[6:8][0])) = msg.header.Flags
*(*uint32)(unsafe.Pointer(&buf[8:12][0])) = msg.header.Seq
*(*uint32)(unsafe.Pointer(&buf[12:16][0])) = msg.header.Pid
buf[16] = msg.data.Family
*(*uint16)(unsafe.Pointer(&buf[18:20][0])) = msg.data.Type
*(*int32)(unsafe.Pointer(&buf[20:24][0])) = msg.data.Index
*(*uint32)(unsafe.Pointer(&buf[24:28][0])) = msg.data.Flags
*(*uint32)(unsafe.Pointer(&buf[28:32][0])) = msg.data.Change
return buf[:]
}
// mustLoopback creates the loopback address and brings the lo interface up.
// mustLoopback calls a fatal method of the underlying [log.Logger] of m with a
// user-facing error message if RTNETLINK behaves unexpectedly.
func mustLoopback(msg message.Msg) {
log := msg.GetLogger()
var lo int
if ifi, err := net.InterfaceByName("lo"); err != nil {
log.Fatalln(err)
} else {
lo = ifi.Index
}
var s rtnetlink
if err := s.open(); err != nil {
log.Fatalln(err)
}
defer func() {
if err := s.close(); err != nil {
msg.Verbosef("cannot close netlink: %v", err)
}
}()
s.mustRoundtrip(msg, s.newaddrLo(lo).toWireFormat())
s.mustRoundtrip(msg, s.newlinkLo(lo).toWireFormat())
}
-72
View File
@@ -1,72 +0,0 @@
package container
import (
"testing"
"unsafe"
)
func TestSizeof(t *testing.T) {
if got := unsafe.Sizeof(newaddrLo{}); got != sizeofNewaddrLo {
t.Fatalf("newaddrLo: sizeof = %#x, want %#x", got, sizeofNewaddrLo)
}
if got := unsafe.Sizeof(newlinkLo{}); got != sizeofNewlinkLo {
t.Fatalf("newlinkLo: sizeof = %#x, want %#x", got, sizeofNewlinkLo)
}
}
func TestRtnetlinkMessage(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
msg interface{ toWireFormat() []byte }
want []byte
}{
{"newaddrLo", (&rtnetlink{pid: 1, seq: 0}).newaddrLo(1), []byte{
/* Len */ 0x28, 0, 0, 0,
/* Type */ 0x14, 0,
/* Flags */ 5, 6,
/* Seq */ 0, 0, 0, 0,
/* Pid */ 1, 0, 0, 0,
/* Family */ 2,
/* Prefixlen */ 8,
/* Flags */ 0x80,
/* Scope */ 0xfe,
/* Index */ 1, 0, 0, 0,
/* Len */ 8, 0,
/* Type */ 2, 0,
/* in_addr */ 127, 0, 0, 1,
/* Len */ 8, 0,
/* Type */ 1, 0,
/* in_addr */ 127, 0, 0, 1,
}},
{"newlinkLo", (&rtnetlink{pid: 1, seq: 1}).newlinkLo(1), []byte{
/* Len */ 0x20, 0, 0, 0,
/* Type */ 0x10, 0,
/* Flags */ 5, 0,
/* Seq */ 1, 0, 0, 0,
/* Pid */ 1, 0, 0, 0,
/* Family */ 0,
/* pad */ 0,
/* Type */ 0, 0,
/* Index */ 1, 0, 0, 0,
/* Flags */ 1, 0, 0, 0,
/* Change */ 1, 0, 0, 0,
}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := tc.msg.toWireFormat(); string(got) != string(tc.want) {
t.Fatalf("toWireFormat: %#v, want %#v", got, tc.want)
}
})
}
}
-47
View File
@@ -1,47 +0,0 @@
package container
import (
"encoding/gob"
"errors"
"os"
"strconv"
"syscall"
)
// Setup appends the read end of a pipe for setup params transmission and returns its fd.
func Setup(extraFiles *[]*os.File) (int, *os.File, error) {
if r, w, err := os.Pipe(); err != nil {
return -1, nil, err
} else {
fd := 3 + len(*extraFiles)
*extraFiles = append(*extraFiles, r)
return fd, w, nil
}
}
var (
ErrReceiveEnv = errors.New("environment variable not set")
)
// Receive retrieves setup fd from the environment and receives params.
func Receive(key string, e any, fdp *uintptr) (func() error, error) {
var setup *os.File
if s, ok := os.LookupEnv(key); !ok {
return nil, ErrReceiveEnv
} else {
if fd, err := strconv.Atoi(s); err != nil {
return nil, optionalErrorUnwrap(err)
} else {
setup = os.NewFile(uintptr(fd), "setup")
if setup == nil {
return nil, syscall.EDOM
}
if fdp != nil {
*fdp = setup.Fd()
}
}
}
return setup.Close, gob.NewDecoder(setup).Decode(e)
}
+6 -6
View File
@@ -4,13 +4,13 @@ import (
"errors" "errors"
"io/fs" "io/fs"
"os" "os"
"path" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"hakurei.app/container/fhs" "hakurei.app/fhs"
"hakurei.app/container/vfs" "hakurei.app/vfs"
) )
const ( const (
@@ -29,16 +29,16 @@ const (
func toSysroot(name string) string { func toSysroot(name string) string {
name = strings.TrimLeftFunc(name, func(r rune) bool { return r == '/' }) name = strings.TrimLeftFunc(name, func(r rune) bool { return r == '/' })
return path.Join(sysrootPath, name) return filepath.Join(sysrootPath, name)
} }
func toHost(name string) string { func toHost(name string) string {
name = strings.TrimLeftFunc(name, func(r rune) bool { return r == '/' }) name = strings.TrimLeftFunc(name, func(r rune) bool { return r == '/' })
return path.Join(hostPath, name) return filepath.Join(hostPath, name)
} }
func createFile(name string, perm, pperm os.FileMode, content []byte) error { func createFile(name string, perm, pperm os.FileMode, content []byte) error {
if err := os.MkdirAll(path.Dir(name), pperm); err != nil { if err := os.MkdirAll(filepath.Dir(name), pperm); err != nil {
return err return err
} }
f, err := os.OpenFile(name, syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, perm) f, err := os.OpenFile(name, syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, perm)
+13 -17
View File
@@ -4,14 +4,13 @@ import (
"io" "io"
"math" "math"
"os" "os"
"path" "path/filepath"
"reflect" "reflect"
"syscall" "syscall"
"testing" "testing"
"unsafe" "unsafe"
"hakurei.app/container/check" "hakurei.app/vfs"
"hakurei.app/container/vfs"
) )
func TestToSysroot(t *testing.T) { func TestToSysroot(t *testing.T) {
@@ -50,9 +49,6 @@ func TestToHost(t *testing.T) {
} }
} }
// InternalToHostOvlEscape exports toHost passed to [check.EscapeOverlayDataSegment].
func InternalToHostOvlEscape(s string) string { return check.EscapeOverlayDataSegment(toHost(s)) }
func TestCreateFile(t *testing.T) { func TestCreateFile(t *testing.T) {
t.Run("nonexistent", func(t *testing.T) { t.Run("nonexistent", func(t *testing.T) {
t.Run("mkdir", func(t *testing.T) { t.Run("mkdir", func(t *testing.T) {
@@ -61,7 +57,7 @@ func TestCreateFile(t *testing.T) {
Path: "/proc/nonexistent", Path: "/proc/nonexistent",
Err: syscall.ENOENT, Err: syscall.ENOENT,
} }
if err := createFile(path.Join(Nonexistent, ":3"), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) { if err := createFile(filepath.Join(Nonexistent, ":3"), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
t.Errorf("createFile: error = %#v, want %#v", err, wantErr) t.Errorf("createFile: error = %#v, want %#v", err, wantErr)
} }
}) })
@@ -72,7 +68,7 @@ func TestCreateFile(t *testing.T) {
Path: "/proc/nonexistent", Path: "/proc/nonexistent",
Err: syscall.ENOENT, Err: syscall.ENOENT,
} }
if err := createFile(path.Join(Nonexistent), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) { if err := createFile(filepath.Join(Nonexistent), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
t.Errorf("createFile: error = %#v, want %#v", err, wantErr) t.Errorf("createFile: error = %#v, want %#v", err, wantErr)
} }
}) })
@@ -80,7 +76,7 @@ func TestCreateFile(t *testing.T) {
t.Run("touch", func(t *testing.T) { t.Run("touch", func(t *testing.T) {
tempDir := t.TempDir() tempDir := t.TempDir()
pathname := path.Join(tempDir, "empty") pathname := filepath.Join(tempDir, "empty")
if err := createFile(pathname, 0644, 0755, nil); err != nil { if err := createFile(pathname, 0644, 0755, nil); err != nil {
t.Fatalf("createFile: error = %v", err) t.Fatalf("createFile: error = %v", err)
} }
@@ -93,7 +89,7 @@ func TestCreateFile(t *testing.T) {
t.Run("write", func(t *testing.T) { t.Run("write", func(t *testing.T) {
tempDir := t.TempDir() tempDir := t.TempDir()
pathname := path.Join(tempDir, "zero") pathname := filepath.Join(tempDir, "zero")
if err := createFile(pathname, 0644, 0755, []byte{0}); err != nil { if err := createFile(pathname, 0644, 0755, []byte{0}); err != nil {
t.Fatalf("createFile: error = %v", err) t.Fatalf("createFile: error = %v", err)
} }
@@ -107,7 +103,7 @@ func TestCreateFile(t *testing.T) {
func TestEnsureFile(t *testing.T) { func TestEnsureFile(t *testing.T) {
t.Run("create", func(t *testing.T) { t.Run("create", func(t *testing.T) {
if err := ensureFile(path.Join(t.TempDir(), "ensure"), 0644, 0755); err != nil { if err := ensureFile(filepath.Join(t.TempDir(), "ensure"), 0644, 0755); err != nil {
t.Errorf("ensureFile: error = %v", err) t.Errorf("ensureFile: error = %v", err)
} }
}) })
@@ -115,7 +111,7 @@ func TestEnsureFile(t *testing.T) {
t.Run("stat", func(t *testing.T) { t.Run("stat", func(t *testing.T) {
t.Run("inaccessible", func(t *testing.T) { t.Run("inaccessible", func(t *testing.T) {
tempDir := t.TempDir() tempDir := t.TempDir()
pathname := path.Join(tempDir, "inaccessible") pathname := filepath.Join(tempDir, "inaccessible")
if f, err := os.Create(pathname); err != nil { if f, err := os.Create(pathname); err != nil {
t.Fatalf("Create: error = %v", err) t.Fatalf("Create: error = %v", err)
} else { } else {
@@ -150,7 +146,7 @@ func TestEnsureFile(t *testing.T) {
t.Run("ensure", func(t *testing.T) { t.Run("ensure", func(t *testing.T) {
tempDir := t.TempDir() tempDir := t.TempDir()
pathname := path.Join(tempDir, "ensure") pathname := filepath.Join(tempDir, "ensure")
if f, err := os.Create(pathname); err != nil { if f, err := os.Create(pathname); err != nil {
t.Fatalf("Create: error = %v", err) t.Fatalf("Create: error = %v", err)
} else { } else {
@@ -195,12 +191,12 @@ func TestProcPaths(t *testing.T) {
t.Run("sample", func(t *testing.T) { t.Run("sample", func(t *testing.T) {
tempDir := t.TempDir() tempDir := t.TempDir()
if err := os.MkdirAll(path.Join(tempDir, "proc/self"), 0755); err != nil { if err := os.MkdirAll(filepath.Join(tempDir, "proc/self"), 0755); err != nil {
t.Fatalf("MkdirAll: error = %v", err) t.Fatalf("MkdirAll: error = %v", err)
} }
t.Run("clean", func(t *testing.T) { t.Run("clean", func(t *testing.T) {
if err := os.WriteFile(path.Join(tempDir, "proc/self/mountinfo"), []byte(`15 20 0:3 / /proc rw,relatime - proc /proc rw if err := os.WriteFile(filepath.Join(tempDir, "proc/self/mountinfo"), []byte(`15 20 0:3 / /proc rw,relatime - proc /proc rw
16 20 0:15 / /sys rw,relatime - sysfs /sys rw 16 20 0:15 / /sys rw,relatime - sysfs /sys rw
17 20 0:5 / /dev rw,relatime - devtmpfs udev rw,size=1983516k,nr_inodes=495879,mode=755`), 0644); err != nil { 17 20 0:5 / /dev rw,relatime - devtmpfs udev rw,size=1983516k,nr_inodes=495879,mode=755`), 0644); err != nil {
t.Fatalf("WriteFile: error = %v", err) t.Fatalf("WriteFile: error = %v", err)
@@ -243,8 +239,8 @@ func TestProcPaths(t *testing.T) {
}) })
t.Run("malformed", func(t *testing.T) { t.Run("malformed", func(t *testing.T) {
path.Join(tempDir, "proc/self/mountinfo") filepath.Join(tempDir, "proc/self/mountinfo")
if err := os.WriteFile(path.Join(tempDir, "proc/self/mountinfo"), []byte{0}, 0644); err != nil { if err := os.WriteFile(filepath.Join(tempDir, "proc/self/mountinfo"), []byte{0}, 0644); err != nil {
t.Fatalf("WriteFile: error = %v", err) t.Fatalf("WriteFile: error = %v", err)
} }
+3 -2
View File
@@ -16,6 +16,7 @@ import (
"unsafe" "unsafe"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/ext"
) )
// ErrInvalidRules is returned for a zero-length rules slice. // ErrInvalidRules is returned for a zero-length rules slice.
@@ -219,9 +220,9 @@ const (
// syscallResolveName resolves a syscall number by name via seccomp_syscall_resolve_name. // syscallResolveName resolves a syscall number by name via seccomp_syscall_resolve_name.
// This function is only for testing the lookup tables and included here for convenience. // This function is only for testing the lookup tables and included here for convenience.
func syscallResolveName(s string) (num std.ScmpSyscall, ok bool) { func syscallResolveName(s string) (num ext.SyscallNum, ok bool) {
v := C.CString(s) v := C.CString(s)
num = std.ScmpSyscall(C.seccomp_syscall_resolve_name(v)) num = ext.SyscallNum(C.seccomp_syscall_resolve_name(v))
C.free(unsafe.Pointer(v)) C.free(unsafe.Pointer(v))
ok = num != C.__NR_SCMP_ERROR ok = num != C.__NR_SCMP_ERROR
return return
+1
View File
@@ -6,6 +6,7 @@ import (
. "syscall" . "syscall"
. "hakurei.app/container/std" . "hakurei.app/container/std"
. "hakurei.app/ext"
) )
func Preset(presets FilterPreset, flags ExportFlag) (rules []NativeRule) { func Preset(presets FilterPreset, flags ExportFlag) (rules []NativeRule) {
+17 -3
View File
@@ -6,12 +6,13 @@ import (
"unsafe" "unsafe"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/ext"
) )
func TestSyscallResolveName(t *testing.T) { func TestSyscallResolveName(t *testing.T) {
t.Parallel() t.Parallel()
for name, want := range std.Syscalls() { for name, want := range ext.Syscalls() {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
@@ -24,8 +25,10 @@ func TestSyscallResolveName(t *testing.T) {
} }
func TestRuleType(t *testing.T) { func TestRuleType(t *testing.T) {
assertKind[std.Uint, scmpUint](t) assertKind[ext.Uint, scmpUint](t)
assertKind[std.Int, scmpInt](t) assertOverflow(t, ext.Uint(ext.MaxUint))
assertKind[ext.Int, scmpInt](t)
assertOverflow(t, ext.Int(ext.MaxInt))
assertSize[std.NativeRule, syscallRule](t) assertSize[std.NativeRule, syscallRule](t)
assertKind[std.ScmpDatum, scmpDatum](t) assertKind[std.ScmpDatum, scmpDatum](t)
@@ -61,3 +64,14 @@ func assertKind[native, equivalent any](t *testing.T) {
t.Fatalf("%s: %s, want %s", nativeType.Name(), nativeType.Kind(), equivalentType.Kind()) t.Fatalf("%s: %s, want %s", nativeType.Name(), nativeType.Kind(), equivalentType.Kind())
} }
} }
// assertOverflow asserts that incrementing m overflows.
func assertOverflow[T ~int32 | ~uint32](t *testing.T, m T) {
t.Helper()
old := m
m++
if m > old {
t.Fatalf("unexpected value %#x", m)
}
}
+6 -52
View File
@@ -1,34 +1,20 @@
package std package std
import ( import "hakurei.app/ext"
"encoding/json"
"strconv"
)
type ( type (
// ScmpUint is equivalent to C.uint.
//
// Deprecated: This type has been renamed to Uint and will be removed in 0.4.
ScmpUint = Uint
// ScmpInt is equivalent to C.int.
//
// Deprecated: This type has been renamed to Int and will be removed in 0.4.
ScmpInt = Int
// ScmpSyscall represents a syscall number passed to libseccomp via [NativeRule.Syscall].
ScmpSyscall Int
// ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno]. // ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno].
ScmpErrno Int ScmpErrno = ext.Int
// ScmpCompare is equivalent to enum scmp_compare; // ScmpCompare is equivalent to enum scmp_compare;
ScmpCompare Uint ScmpCompare = ext.Uint
// ScmpDatum is equivalent to scmp_datum_t. // ScmpDatum is equivalent to scmp_datum_t.
ScmpDatum uint64 ScmpDatum = uint64
// ScmpArgCmp is equivalent to struct scmp_arg_cmp. // ScmpArgCmp is equivalent to struct scmp_arg_cmp.
ScmpArgCmp struct { ScmpArgCmp struct {
// argument number, starting at 0 // argument number, starting at 0
Arg Uint `json:"arg"` Arg ext.Uint `json:"arg"`
// the comparison op, e.g. SCMP_CMP_* // the comparison op, e.g. SCMP_CMP_*
Op ScmpCompare `json:"op"` Op ScmpCompare `json:"op"`
@@ -39,42 +25,10 @@ type (
// A NativeRule specifies an arch-specific action taken by seccomp under certain conditions. // A NativeRule specifies an arch-specific action taken by seccomp under certain conditions.
NativeRule struct { NativeRule struct {
// Syscall is the arch-dependent syscall number to act against. // Syscall is the arch-dependent syscall number to act against.
Syscall ScmpSyscall `json:"syscall"` Syscall ext.SyscallNum `json:"syscall"`
// Errno is the errno value to return when the condition is satisfied. // Errno is the errno value to return when the condition is satisfied.
Errno ScmpErrno `json:"errno"` Errno ScmpErrno `json:"errno"`
// Arg is the optional struct scmp_arg_cmp passed to libseccomp. // Arg is the optional struct scmp_arg_cmp passed to libseccomp.
Arg *ScmpArgCmp `json:"arg,omitempty"` Arg *ScmpArgCmp `json:"arg,omitempty"`
} }
) )
// MarshalJSON resolves the name of [ScmpSyscall] and encodes it as a [json] string.
// If such a name does not exist, the syscall number is encoded instead.
func (num *ScmpSyscall) MarshalJSON() ([]byte, error) {
n := *num
for name, cur := range Syscalls() {
if cur == n {
return json.Marshal(name)
}
}
return json.Marshal(n)
}
// SyscallNameError is returned when trying to unmarshal an invalid syscall name into [ScmpSyscall].
type SyscallNameError string
func (e SyscallNameError) Error() string { return "invalid syscall name " + strconv.Quote(string(e)) }
// UnmarshalJSON looks up the syscall number corresponding to name encoded in data
// by calling [SyscallResolveName].
func (num *ScmpSyscall) UnmarshalJSON(data []byte) error {
var name string
if err := json.Unmarshal(data, &name); err != nil {
return err
}
if n, ok := SyscallResolveName(name); !ok {
return SyscallNameError(name)
} else {
*num = n
return nil
}
}

Some files were not shown because too many files have changed in this diff Show More