Compare commits

..

350 Commits

Author SHA1 Message Date
cat 79342e3053 release: 0.4.4
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-17 21:07:22 +09:00
cat 825bf24731 nix: read version from cmd/dist
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-17 21:06:03 +09:00
cat 08112f0b90 hst: optionally cover /run/ early
This works around awkward root permissions.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-17 02:59:48 +09:00
cat e1a1e1e399 cmd/app: use ephemeral overlay root
This replaces autoroot.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-17 02:32:46 +09:00
cat 92b61889a6 hst: support ephemeral overlay mounts
This is useful for reusing a readonly template without autoroot.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-17 02:16:57 +09:00
cat 28e133c298 cmd/app: start application from configuration
This is currently not very usable due to hakurei immutable overlay mount limitations.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-17 01:54:29 +09:00
cat 323dcb2820 cmd/app: high-level app configuration syntax
This replaces the nixos module.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-17 01:50:53 +09:00
cat f46a0370a7 cmd/app: initial container template command
This program is an experimental frontend for cmd/hakurei. This serves as a short-term replacement of the nixos module and a testing environment to implement API useful to planterette.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-16 23:27:50 +09:00
cat 5ee66a86ee internal/rosa/python: respect toolchain opts
This was missed when migrating the pip helper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-16 22:06:34 +09:00
cat d75b8b6e32 internal/rosa/package/tamago: 1.26.3 to 1.26.4
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-16 21:49:04 +09:00
cat 7c9481d38d internal/rosa/package/strace: 6.19 to 7.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-16 21:48:45 +09:00
cat 983ab4378f internal/rosa/package/nss: 3.124 to 3.125
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-16 21:48:26 +09:00
cat 5b5c096d31 internal/rosa/package/iproute2: 7.0.0 to 7.1.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-16 21:48:04 +09:00
cat 5f49127120 internal/rosa/package/python: pytest 9.0.3 to 9.1.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-16 21:47:40 +09:00
cat 164f8c873d internal/rosa/package/gcc: binutils 2.46.0 to 2.46.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-16 21:47:16 +09:00
cat 9274b642ec internal/rosa/package/kernel: 6.12.92 to 6.12.93
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-16 21:37:42 +09:00
cat 5992be3dd3 internal/rosa/package/python: add kernel-headers
Missing kernel headers implicitly disables many useful things. This change also enables PGO and installs symlink, since a rebuild is unavoidable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-10 16:30:52 +09:00
cat c661a3b63a cmd/mbf: migrate shell to enter
This reduces duplicate code. This change also adds resolv.conf to the container.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-10 13:34:44 +09:00
cat df0bb877db internal/rosa: export etc native artifact
This is useful for external container tooling.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-10 13:26:38 +09:00
cat f333b8fbd6 cmd/mbf: register binfmt entry for shell
This fixes --arch for shell.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-10 12:51:09 +09:00
cat 928a9f61e9 cmd/hsu: remove parent check
This check serves no real purpose and only makes it more difficult to start containers.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-09 18:08:17 +09:00
cat ce06539eca internal/rosa: expose supported architectures
This information is useful to external tooling and makes a lot more sense in this package than cmd/mbf. This change also fixes non-native artifact resolution during clean.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-09 16:05:04 +09:00
cat 69908e5a41 internal/rosa: remove external toolchain reference
This should have used toolchain passed as the argument and was mistakenly never changed when migrating to late evaluation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-09 15:58:02 +09:00
cat b5445573a8 internal/rosa/package/ninja: work around test suite bug
The test suite hard codes /bin/echo.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-08 18:43:42 +09:00
cat f869ff95a1 all: apply modernisers
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-08 14:58:24 +09:00
cat 725f2e0ef3 internal/rosa/package/wayland: wayland-protocols 1.48 to 1.49
This finally eliminates the backport patch.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-08 13:45:36 +09:00
cat 4ffa20cd3f cmd/mbf: custom azalea path
This enables out-of-tree packaging.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-08 13:42:06 +09:00
cat 38450db74a internal/rosa: access backing storage through fs
This is more versatile than hardcoding the os.Root implementation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-07 22:08:57 +09:00
cat 9344f694c7 internal/pkg: defer directory permissions
This allows creation of directory structures with awkward permission bits.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-07 20:26:55 +09:00
cat e8bb5a622d internal/rosa: mirror service via external cache
This provides an authenticated implementation of the external cache.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-07 19:25:11 +09:00
cat 22e508fe17 internal/pkg: expose measured reader to extern status
This is always required by the implementation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-07 16:14:54 +09:00
cat b18ecf5832 nix: work around systemd nondeterminism
NixOS switched to systemd initramfs by default.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-07 14:10:06 +09:00
cat ec29e755fb internal/pkg: replace outcomes from external cache
This is primarily useful for implementing a mirror service. This change also works around the zero-dependency FloodArtifact edge case.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-07 13:52:26 +09:00
cat 9aaf160ff4 internal/pkg: do not hold up cures during status link
This is a bunch of string operation and a single syscall. Do not hold up queue here.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-07 02:28:45 +09:00
cat e192fca762 internal/pkg: check for unclean shutdown
This avoids running into nasty surprises opening a cache that suffered unclean shutdown due to power loss. All other parts of the cache are not prone to inconsistent state.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-07 01:21:00 +09:00
cat 7eafc7b1e4 nix: update flake lock
Was unfortunately not able to implement vm test suite before this release. Hopefully the last nixos update we have to follow.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-06 13:14:09 +09:00
cat 282462c2f0 internal/rosa/package/mesa: 26.1.1 to 26.1.2
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:23:40 +09:00
cat d9f522d648 internal/rosa/package/libinput: 1.31.2 to 1.31.3
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:23:11 +09:00
cat a0b0c7ecc9 internal/rosa/package/libdrm: 2.4.133 to 2.4.134
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:22:50 +09:00
cat 2b8809da7a internal/rosa/package/hwdata: 0.407 to 0.408
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:22:28 +09:00
cat 84cda19d63 internal/rosa/package/harfbuzz: 14.2.0 to 14.2.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:22:08 +09:00
cat 154ab953c1 internal/rosa/package/fontconfig: 2.18.0 to 2.18.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:21:43 +09:00
cat 34e11dd312 internal/rosa/package/python: hatchling 1.16.5 to 1.17.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:21:10 +09:00
cat 140cb3cc47 internal/rosa/package/python: trove-classifiers 2026.5.22.10 to 2026.6.1.19
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:20:42 +09:00
cat 82c974d656 internal/rosa/package/xkbcommon: 1.13.1 to 1.13.2
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:20:12 +09:00
cat 8c17f201e5 internal/rosa/package/x: xwayland 24.1.11 to 24.1.12
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:19:46 +09:00
cat 63e0457538 internal/rosa/package/x: xserver 21.1.22 to 21.1.23
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:19:22 +09:00
cat ca93264c1f internal/rosa/package/dtc: 1.8.0 to 1.8.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-05 09:18:53 +09:00
cat 94173eafff internal/rosa/llvm: 22.1.6 to 22.1.7
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-04 20:13:26 +09:00
cat e28c4aa3c0 internal/rosa/package/kernel: 6.12.91 to 6.12.92
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-04 20:10:27 +09:00
cat 8e8410ce38 internal/pkg: archive unpack artifact
This unpacks an internal/pkg archive stream used in the upcoming mirror service.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-04 19:46:36 +09:00
cat 8fb6fdaa80 internal/pkg: prefix reporting names
This reads better than suffixes.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-04 19:05:36 +09:00
cat db69dcf0be internal/pkg: remove tar built-in decompressor
This is replaced by decompressArtifact and is no longer necessary.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-04 18:54:43 +09:00
cat 76c1fb84c8 internal/pkg: stream decompress artifact
The tarArtifact predates FileArtifact pipelining. This migrates decompression and buffering into a standalone artifact implementation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-04 18:33:04 +09:00
cat 729be19af3 internal/pkg: rename archive checksum helpers
These names are more consistent with other helper names, so rename them while the API is still internal.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-04 14:13:20 +09:00
cat 4d04f86f4a internal/rosa/go: 1.26.3 to 1.26.4
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-03 16:18:04 +09:00
cat 42cea1e7c6 internal/pkg: streaming archive reader/writer
This is much more robust and efficient than the simple buffering implementation for larger files. Allocations happen almost exclusively in WalkDir.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-03 16:00:36 +09:00
cat 83498b5a8a cmd/mbf: keep non-native entries alive
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-02 16:06:04 +09:00
cat 9e824452bd internal/pkg: expose snapshot of binfmt entries
This is otherwise not externally accessible. The resulting map can be safely mutated.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-02 16:05:09 +09:00
cat 56937ac396 internal/rosa: populate opts of cloned S
This was missed when migrating opts into S.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-02 16:00:51 +09:00
cat 6c2f7089b6 internal/pkg: destroy unreachable status entries
These must be destroyed alongside their corresponding identifier or substitute entries to avoid inconsistent state.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-02 15:50:30 +09:00
cat 74c18390b4 internal/pkg: correctly scrub substitute status
Scrubbing for status predates substitutes. This change fixes scrub handling of substitute status entries.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-02 15:37:55 +09:00
cat 1490b32387 cmd/mbf: garbage collection commands
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-02 13:37:46 +09:00
cat fbd2329d50 internal/pkg: garbage collection
This destroys cache entries not referred to by user-specified artifacts and optionally their inputs.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-02 13:28:24 +09:00
cat f398f71fa9 internal/pkg: input iterator via IR cache
Primarily useful for garbage collection.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-31 14:19:27 +09:00
cat 4d017b1309 internal/rosa/package/rsync: annotate blocked update
Despite rsync being practically feature complete, with minimal changes in many years, it recently had a large number of poor quality AI-generated changes, with the first release including them apparently being 3.4.2. Release 3.4.3 then introduced a dependency on kernel-headers, creating a dependency loop that prevented directly upgrading to it. This change annotates the blockage since it is not worth working around the dependency loop for AI-generated garbage.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-30 23:15:04 +09:00
cat cd1d447664 internal/rosa/package/rsync: 3.4.2 to 3.4.1
This appears to be the last release not to contain AI slop.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-30 19:10:40 +09:00
cat 8b87e0eb76 internal/rosa: annotate blocked updates
These situations, while unfortunate, are inevitable at a larger scale. This change enables annotation and optional hiding of blocked updates.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-30 18:56:55 +09:00
cat f4215ddda5 internal/rosa/package/qemu: 11.0.0 to 11.0.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-28 12:16:55 +09:00
cat 652b4c2ba0 internal/rosa/package/gnu: parallel 20260422 to 20260522
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-28 12:16:05 +09:00
cat 75715f4590 cmd/earlyinit: mount /dev/shm
Required by many programs.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 23:59:48 +09:00
cat 87c3b3663d cmd/earlyinit: improve change message formatting
This is significantly more readable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 23:51:46 +09:00
cat 6d792023b2 internal/rosa/package: seatd
Required by wlroots sessions.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 23:17:33 +09:00
cat 3e62cf379f internal/rosa/package: libliftoff
Wanted by wlroots.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 23:07:53 +09:00
cat 6d991f2644 cmd/earlyinit: downgrade target error severity
This happens during coldboot, before reporting gains persistent state.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 18:58:30 +09:00
cat a2cc28f53c cmd/earlyinit: load device drivers
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 18:54:05 +09:00
cat 598c7aa30f internal/report: strict-exempt severity
For reportable but inconsequential errors like modprobe by MODALIAS failures.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 18:53:15 +09:00
cat b18f40d974 internal/kobject: pass action kind for range
Useful for handling most uevents.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 17:58:01 +09:00
cat 12a6061051 internal/rosa/package: sway
Required by vm test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 17:10:18 +09:00
cat 02c3823ed7 internal/rosa/package: libinput
Required by sway.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 17:07:49 +09:00
cat cfbd8bd6f1 internal/rosa/package: libudev-zero
Avoids systemd, required by many projects.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 17:07:35 +09:00
cat fc7a339ed2 internal/rosa/package/kernel: 6.12.90 to 6.12.91
This unfortunately updates headers linux/mii.h and linux/virtio_net.h.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 15:47:53 +09:00
cat 97fdd5db8b internal/rosa/package/dtc: 1.7.2 to 1.8.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 15:46:58 +09:00
cat a5d9f76f50 internal/rosa/cmake: use DESTDIR instead of --prefix
Turns out --prefix is deeply broken, and DESTDIR works even when using ninja.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 15:45:58 +09:00
cat b313dfefb0 internal/rosa/package: libevdev
Required by sway.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 13:07:10 +09:00
cat 5b32297178 internal/rosa/package: check
Required by libevdev.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 13:05:24 +09:00
cat 32cc72821e internal/rosa/package: pango
Required by sway.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 12:33:10 +09:00
cat 958473e6f4 internal/rosa/package: harfbuzz
Required by pango.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 12:32:59 +09:00
cat cc52bb3035 internal/rosa/package: fribidi
Required by pango.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 12:13:31 +09:00
cat 7816f8b523 internal/rosa/package: cairo
Required by sway, which is required by vm test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 12:00:13 +09:00
cat 91e4229e32 internal/rosa/package: json-c
Required by sway, which is required by vm test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 11:20:28 +09:00
cat c346cb4c57 internal/rosa/package: wlroots
Required by sway, which is required by vm test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 10:59:09 +09:00
cat 0e0d5e8def internal/rosa/package/x: xwayland
Generally useful for wayland display servers.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 10:45:03 +09:00
cat ea875416d4 internal/rosa/package: enable libglvnd in libepoxy
This is no longer circular because xserver does not require libepoxy.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 10:38:22 +09:00
cat 8c7109a2d5 cmd/earlyinit: mount system device
This currently relies on a trusted bootloader to determine the boot device.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-27 10:37:45 +09:00
cat caf3e0db4b internal/kobject: improve error JSON representation
This is now usable by internal/report.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-26 18:39:09 +09:00
cat 12b9f51128 internal/report: report errors with persistent backing
This is useful for reporting errors from processes that must never terminate.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-26 14:56:16 +09:00
cat d15f965d0c internal/kobject: range over objects
For matching devices based on some criteria.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-25 23:17:14 +09:00
cat fadd1b14f7 internal/kobject: process uevent
This tracks kernel state by merging a stream of uevent. Inconsistencies are reported and recovered from gracefully.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-25 21:31:59 +09:00
cat 3458806685 internal/rosa: resolve runtime for overlay extras
This is generally for system image creation, so this behaviour makes more sense.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-25 10:37:21 +09:00
cat 8ca70550ab internal/rosa/package: db
For iproute2.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-25 10:31:45 +09:00
cat 7eebf49b98 internal/rosa/package: libbpf
For eBPF support in iproute2.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-25 09:51:26 +09:00
cat 8c84883af6 internal/rosa/package: iproute2
Useful for debugging for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-25 09:33:32 +09:00
cat 4e1359aa95 internal/rosa/package: ignore local directory
For testing packages noneligible for upstream.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-24 16:47:24 +09:00
cat a3867ad65f internal/rosa/azalea: replace binding token
This replaces the '*' placeholder with a less confusing '#'.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-24 16:20:26 +09:00
cat 689f972976 internal/rosa/package: migrate stage0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 17:53:05 +09:00
cat 3f33b62dfd internal/rosa/package: migrate system image
The overlay argument also enables migration of stage0.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 17:44:38 +09:00
cat ac5488eef6 internal/rosa/package: migrate initramfs image
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 17:30:39 +09:00
cat 77a15130c7 internal/rosa/package: foot
Used by vm test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 16:51:32 +09:00
cat 4c1e823908 internal/rosa/package: fcft
Required by foot.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 16:45:42 +09:00
cat 5f5a398a5b internal/rosa/package: utf8proc
Required by foot.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 16:35:38 +09:00
cat d5e4a2e6a7 internal/rosa/package: tllist
Required by foot.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 16:35:24 +09:00
cat 57c6b84b60 internal/rosa/package: fontconfig
Required by foot.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 16:23:06 +09:00
cat 4269627b4b internal/rosa/package: xkbcommon
Required by foot.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 16:22:51 +09:00
cat d50e3c3d5b internal/rosa/package/glib: 2.88.1 to 2.89.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 14:33:37 +09:00
cat eae2890d98 internal/rosa/package/python: trove-classifiers 2026.5.7.17 to 2026.5.22.10
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 14:33:20 +09:00
cat f8ebfd71a7 internal/rosa/package/spirv: spirv-headers 1.4.341.0 to 1.4.350.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 14:32:52 +09:00
cat dce1a05f6c internal/rosa/package/firmware: 20260410 to 20260519
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 14:32:16 +09:00
cat eff265837c internal/rosa/package/mesa: 26.1.0 to 26.1.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 14:32:01 +09:00
cat 7d809eb15f internal/rosa/package/nss: 3.123.1 to 3.124
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 14:31:36 +09:00
cat 9accc2f961 internal/rosa/package/nss: nspr 4.38.2 to 4.39
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 14:31:19 +09:00
cat e5a4094298 internal/rosa: remove unused helpers
These are no longer needed after migration.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 14:00:18 +09:00
cat 410c4f8bb0 internal/rosa/package/cmake: 4.3.2 to 4.3.3
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 13:02:23 +09:00
cat 2e23c6d367 internal/rosa/package/kernel: 6.12.87 to 6.12.90
Unfortunately this causes rebuilds due to a single io_uring API change.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 13:01:44 +09:00
cat f5e9a0c04e internal/pkg: destroy new substitution status on fault
This avoids leaving behind the substitution status path of a faulted cure.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 01:31:13 +09:00
cat 3bd4ef616c internal/pkg: report errors exiting cure
This makes ongoing errors more obvious when multiple failures occur.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 01:25:42 +09:00
cat 41402fd578 internal/rosa/package/util-linux: 2.42 to 2.42.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-23 01:00:27 +09:00
cat b47fa1a214 internal/rosa: IR-curable source override
This creates a tarball in-memory for overriding hakurei-source.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-22 22:24:16 +09:00
cat 9e363cb2c9 internal/rosa/go: runtime dependencies for alterative path
The GCC toolchain is not dependency-free, so append them here.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-22 13:42:02 +09:00
cat 1389c77022 internal/rosa/package/hakurei: 0.4.2 to 0.4.3
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-22 03:16:56 +09:00
cat e231341e48 release: 0.4.3
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-22 02:18:10 +09:00
cat 70f977627d internal/pkg: arch-specific expected offline substituted
IR includes the target architecture name.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-22 01:51:15 +09:00
cat f3a6f7ddf9 internal/rosa/package: migrate hakurei
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-22 00:07:25 +09:00
cat 2a9aa3b400 cmd/dist: include version in release
This makes HAKUREI_VERSION optional during build.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 23:37:08 +09:00
cat 68a91523b9 internal/rosa/package: migrate bison
This is the final remaining trivial legacy artifact.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 23:06:34 +09:00
cat 5647321622 internal/rosa: move azalea builtins
This improves readability.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 22:51:50 +09:00
cat cdd31dd27b internal/rosa/azalea: integer arrays
This is useful for some helper functions. Performance is unaffected.

Before:
BenchmarkStage3-128     	    8308	   1960687 ns/op	 1023794 B/op	   14755 allocs/op
BenchmarkAll-128        	    3331	   5518571 ns/op	 2902320 B/op	   37993 allocs/op

After:
BenchmarkStage3-128     	    8330	   1946273 ns/op	 1023046 B/op	   14750 allocs/op
BenchmarkAll-128        	    3296	   5585805 ns/op	 2901746 B/op	   37991 allocs/op

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 22:47:41 +09:00
cat 0615899e56 internal/rosa: do not register stage0
Nothing can depend on this, so remove it from the namespace.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 22:20:01 +09:00
cat 0914569e62 internal/rosa/go: migrate to generic helper
The go toolchain predates all abstractions currently available. This migration causes rebuilds due to internal cleanups affecting the final build script.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 19:55:38 +09:00
cat 25d9edfc64 internal/rosa/package: migrate tamago
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 19:40:16 +09:00
cat af4c3bbff2 internal/rosa/package: migrate toybox
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 18:10:02 +09:00
cat 54aae9d72a internal/rosa/package: migrate llvm patches
LLVM itself is unlikely to ever be migrated due to complexity of the bootstrap, so migrate patches instead.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 17:51:23 +09:00
cat 58646f8ea5 internal/rosa/package/googletest: 1.16.0 to 1.17.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 17:02:40 +09:00
cat 9d7a27d8ac internal/rosa/package: migrate ninja
The ninja package predates all abstractions currently available. This migration causes rebuilds due to the old package being nonreproducible.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 16:48:10 +09:00
cat 6546ddc64b internal/rosa: expose in-place behaviour in generic helper
This change also combines the createDir and wantsDir methods, and replaces the non-inplace target of the generic helper with a deterministic path.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 16:11:38 +09:00
cat cbf18b302d internal/rosa/package: migrate nss
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 15:36:10 +09:00
cat 1acb5b0105 internal/rosa: extra inputs in alternative path
This works around particularly unwieldy build systems.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 15:05:12 +09:00
cat 40b33f9fc7 internal/rosa: enforce exclusions
This restores unexported artifact behaviour.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 14:48:16 +09:00
cat 443a7a30f6 internal/rosa: use string pair for files
This is a much cleaner representation than the separator syntax.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 14:07:20 +09:00
cat 497e4a5642 internal/rosa/package/git: disable flaky test
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 13:35:21 +09:00
cat c0e3841ddb internal/rosa/llvm: 22.1.5 to 22.1.6
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-21 10:39:42 +09:00
cat 9ce2c325db internal/rosa/package/kernel: populate riscv64 checksum
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 21:25:15 +09:00
cat 9836030c59 internal/rosa: reinitialise frame alongside cache
The cached frame can contain information made outdated by the DropCaches call.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 09:01:40 +09:00
cat b482fd4abf internal/rosa: remove global handles
These no longer serve any purpose.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 08:15:23 +09:00
cat 2e502ede6c internal/rosa/package: migrate X packages
This also improves naming consistency.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 07:44:17 +09:00
cat 4bec0b890c internal/rosa/package: migrate unzip
This is now possible via the generic helper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 06:21:49 +09:00
cat 7770ccf0aa internal/rosa/package: migrate wayland
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 06:12:19 +09:00
cat 656059278d internal/rosa/package: migrate remaining trivial packages
The rest are migrated individually.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 05:57:26 +09:00
cat 1a9974ffdc internal/rosa/package: migrate qemu
This has many dependencies.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 05:10:41 +09:00
cat 1a2699b486 internal/rosa/package: migrate multiple packages
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 05:01:06 +09:00
cat 1d3d621e2f internal/rosa/package: migrate perl Module::Build
This is now possible via the generic helper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 04:48:03 +09:00
cat 47f4e287fc internal/rosa/package: migrate multiple libraries
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 04:37:58 +09:00
cat 2e710328a4 internal/rosa/package: migrate musl
This removes some legacy cruft, causing 2 rebuilds per stage.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 04:13:10 +09:00
cat 2e7b52d701 internal/rosa/package: migrate mesa
This has many dependencies.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 04:02:02 +09:00
cat d728607505 internal/rosa/package: migrate mesa dependencies
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 03:51:48 +09:00
cat ef414ab01a internal/rosa/package: migrate many libraries
This also adds more string helpers.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 03:33:15 +09:00
cat 96abf266dd internal/rosa/package: migrate hwdata, kmod, libarchive
This removes a blank line in CTestCustom.cmake, causing a libarchive rebuild. Resulting IR is identical otherwise.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 02:25:41 +09:00
cat fcba32e9c4 internal/rosa/package: migrate glib
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 02:09:12 +09:00
cat a7f5a5802d internal/rosa/package: migrate spirv-llvm-translator
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-20 00:01:03 +09:00
cat bb230378e0 internal/rosa/package: migrate glslang
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 23:48:39 +09:00
cat f638c73933 internal/rosa: bind anitya functions
This is far more scalable than individual fields.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 23:34:21 +09:00
cat 98d915af3d internal/rosa/package: migrate argp-standalone, dtc, elfutils, flex, freetype, fuse
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 23:26:34 +09:00
cat c0593e8325 internal/rosa/package: migrate dbus
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 22:51:08 +09:00
cat 608d8303ec internal/rosa/package: migrate git
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 22:37:24 +09:00
cat 1c6f30379e internal/rosa/package: migrate bzip2, curl, connman
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 22:26:40 +09:00
cat 009a4e0d58 internal/rosa/hakurei: migrate to helper
This predates the helper infrastructure, so migrate it.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 22:10:39 +09:00
cat e7c8656691 internal/rosa: remove fakeroot
This is unused and broken, so remove it.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 21:54:40 +09:00
cat d6be116ff8 internal/rosa/package: migrate firmware
This does not depend on the kernel.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 21:46:40 +09:00
cat 962b02cf25 internal/rosa/package: migrate kernel
This introduces bindings for extra paths and KnownChecksum, and exposes a passthrough special case.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 21:40:15 +09:00
cat 6fd6d971ed internal/rosa/package: migrate mksh
This benefits greatly from the new generic helper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 19:47:57 +09:00
cat 548c96c7ec internal/rosa/package: migrate make
This also introduces the generic helper for unusual build scripts.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 19:28:18 +09:00
cat 6e8bfa6c4c internal/rosa/package: migrate cmake
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 18:42:54 +09:00
cat a770d62b9b internal/rosa/package: migrate meson
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 18:24:07 +09:00
cat ff44060763 internal/rosa/package: migrate python packages
This also migrates LLVM LIT via the newly implemented special case.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 18:15:04 +09:00
cat 3010a209b5 internal/rosa/azalea: pass through source ident
For source handle special case.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 18:10:11 +09:00
cat e65a3b435c internal/rosa/package/gnutls: 3.8.12 to 3.8.13
The new release came with new broken tests, but at least nettle3 can be removed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 04:36:32 +09:00
cat 23515f67c8 internal/rosa/package: migrate perl packages
Most of these are currently unused.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 04:08:22 +09:00
cat 4389df60ae internal/rosa/perl: remove obsolete helper
This method predates the helper infrastructure.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 03:40:31 +09:00
cat 8092492018 internal/rosa/perl: Makefile.PL helper
This can be invoked from azalea.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 03:24:27 +09:00
cat a7877844bf internal/rosa/package: migrate perl interpreter
Packages will be migrated separtely.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 02:49:11 +09:00
cat 1ed027846d internal/rosa/package: migrate python interpreter
Packages will take quite some work.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 02:06:17 +09:00
cat 2f376d4813 internal/rosa/package: rename buildcatrust
This causes a single rebuild due to substitution.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 01:50:03 +09:00
cat dc3810b530 internal/rosa/python: remove unnecessary input
This is added by the helper. Removing it has no effect since it is promoted by Append.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 01:48:24 +09:00
cat 6e9e8c74f3 internal/rosa: migrate buildcatrust
Other nss-related packages are unlikely to be migrated any time soon.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 01:44:23 +09:00
cat 4d60fa5632 internal/rosa: evaluate packages late
This also enables concurrent evaluation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 01:26:21 +09:00
cat 8807cbc730 internal/rosa: create metadata alongside artifact
This enables deferring evaluation of azalea-based packages and fixes the longstanding quirk of version being disjoint from other metadata.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-19 00:44:24 +09:00
cat 0e95573f18 internal/rosa/package: migrate acl
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-18 22:42:43 +09:00
cat eb2b53307a internal/rosa/package: migrate gcc
The azalea implementation used an adaptation of this as testdata.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-18 22:29:53 +09:00
cat 682b3a2ce5 internal/rosa: track evaluation time
Useful to track performance regressions over migrations.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-18 22:18:09 +09:00
cat 594221eb78 internal/rosa/package: migrate gnutls
This is the first nontrivial package to be migrated to azalea. Validated to generate identical IR.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-18 22:06:00 +09:00
cat 34822925e1 internal/rosa: migrate GNU software
These are quite trivial, so migrate them in one pass.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-18 21:23:19 +09:00
cat 37df040d85 internal/rosa: evaluate packages from fs
This migrates GNU sed to azalea, and resulting IR matches.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-18 17:54:01 +09:00
cat 0360e779f3 internal/rosa: initial azalea bindings
Supported fields are still rather minimal, but evaluation works, and resulting artifacts cure correctly.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-18 02:56:38 +09:00
cat 3e236333a7 internal/rosa: panic error for invalid handle
This enables recovery and better error handling for errors originating from external azalea files.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-18 00:07:39 +09:00
cat f24ae21af1 internal/rosa/azalea: package special case
This is more efficient for the inputs array and packages in general.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-17 23:49:19 +09:00
cat 99b324fb17 cmd/mbf: update pkgserver title text
This makes more sense for its purpose.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-17 18:57:26 +09:00
kat 6f50811dc9 cmd/mbf: bring back pkgserver's favicon!
It existed in mae's #33, but ozy was not satisfied with including
a binary file identical to https://hakurei.app's favicon, and hence
removed it. However, it's possible to explicitly specify the favicon
with a link tag [1]; provided a content security policy that isn't too
strong, this should work fine.

[1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel#icon
2026-05-17 19:47:42 +10:00
cat 6b87bac401 cmd/mbf: clone pkgserver order slices
These are no longer arrays, so must be cloned for sorting.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-17 18:30:57 +09:00
cat a967aa3b6e internal/rosa/kernel: arch-specific headers checksum
These headers differ by target architecture.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-17 18:01:11 +09:00
cat 38bc2c7508 internal/rosa: pass stage alongside state
This cleans up many function signatures.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-17 17:50:30 +09:00
cat 30eb0d6a61 internal/rosa: key metadata by string
For upcoming azalea integration. The API is quite ugly right now to ease migration.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-17 15:56:53 +09:00
cat c2ff9c9fa5 internal/rosa/azalea: evaluator
Performance is sufficient for the use case, despite the fact that I could not even think of a lower-effort way to do this:

BenchmarkParse-128        	   55100	     21494 ns/op
BenchmarkEvaluate-128     	  131670	      9248 ns/op

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-17 12:44:34 +09:00
cat d38d306147 internal/rosa/azalea: ast and parser
This syntax is not final, but acts as a stopgap solution and a proof of concept.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-16 14:29:28 +09:00
cat c32c06b2e8 internal/rosa: mesa artifact
This has many dependencies.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 05:12:35 +09:00
cat 61199f734c internal/rosa/glslang: remove headers prefix
Maintainers tried to be clever with this and breaks cmake paths.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 04:57:38 +09:00
cat 87cf0d4e6b internal/rosa/mesa: libva artifact
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 04:40:04 +09:00
cat cf0dffa0f5 internal/rosa/mesa: libglvnd enable glx
Required to break circular dependency.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 04:35:50 +09:00
cat 686d7ec63a internal/rosa/x: xserver artifact
Required by libglvnd test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 04:15:48 +09:00
cat 4c653b1151 internal/rosa/x: xkeyboard-config artifact
Required by xserver test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 03:59:22 +09:00
cat 0b0a63d151 internal/rosa/x: libxcb-util-wm artifact
Required by xserver xephyr.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 03:46:04 +09:00
cat 6231cfe2aa internal/rosa/x: libxcb-util-image artifact
Required by xserver xephyr.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 03:36:45 +09:00
cat 712e80890b internal/rosa/x: libxcb-util artifact
Required by xserver xephyr.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 03:25:24 +09:00
cat 3fe7d48014 internal/rosa/x: libxcb-render-util artifact
Required by xserver.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 03:09:37 +09:00
cat 16f9d39427 internal/rosa: libepoxy artifact
Required by xserver.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 02:16:55 +09:00
cat c1cd5ba07b internal/rosa: libtirpc artifact
Required by xserver.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 02:07:25 +09:00
cat 7b0cd2e472 internal/rosa/x: libXdmcp artifact
Required by xserver.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 01:44:37 +09:00
cat e580307528 internal/rosa/x: libxcvt artifact
Required by xserver.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 01:24:00 +09:00
cat ee1dffb676 internal/rosa/x: libXfont2 artifact
Required by xserver.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 01:17:27 +09:00
cat f095fcf181 internal/rosa/x: font-util and libfontenc artifact
Required by libXfont2.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 01:14:12 +09:00
cat ca8a130130 internal/rosa: freetype artifact
Required by libXfont.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-14 00:54:42 +09:00
cat 0ad6b00e41 internal/rosa/x: xkbcomp artifact
Required by xserver.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 22:24:08 +09:00
cat ad0f1cf36b internal/rosa/x: libxkbfile artifact
Required by xkbcomp.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 22:22:02 +09:00
cat b12d924fa2 internal/rosa: pixman artifact
Required by xserver.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 22:07:53 +09:00
cat c31d8ae41a internal/rosa/x: libXfixes artifact
Required by libva.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 21:36:47 +09:00
cat 6dbbf15c0e internal/rosa: lm_sensors artifact
Generally useful, and an optional dependency of mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 20:11:37 +09:00
cat be7de68a42 internal/rosa/perl: Test::Cmd artifact
Required by lm_sensors test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 20:05:43 +09:00
cat a759cf3666 internal/pkg: check exec substitution
This relies on the testtool having ident as relevant input to assert successful substitution.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 19:43:53 +09:00
cat 8c2dd3e984 internal/pkg: verify status kind
While it is still impossible to reliably determine the expected contents of these status files, this checks their nature for expected substitution behaviour.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 19:27:58 +09:00
cat 67038d5af4 internal/pkg: log fault in tests when available
This would otherwise only be available in verbose output, interleaved with everything else.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 18:58:18 +09:00
cat 53d8d12e7f internal/rosa/git: disable flaky test
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 18:51:11 +09:00
cat 7997d79e56 cmd/mbf: display and destroy fault entries
This change extends cmd/mbf commands for working with fault entries.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 19:06:09 +09:00
cat f2f1726190 internal/pkg: record cure faults
These are useful for troubleshooting. This change records them in a separate directory.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 17:58:18 +09:00
cat f63203cb0a internal/pkg: populate substitute status
These are not created when taking the fast path, but should be inherited from the alternative.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 16:16:37 +09:00
cat 19555c7670 internal/rosa/gtk: glib 2.88.0 to 2.88.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 00:48:38 +09:00
cat a3beab8959 internal/rosa/libucontext: 1.5 to 1.5.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 00:48:15 +09:00
cat 2ea786d6a9 internal/rosa/libbsd: libmd 1.1.0 to 1.2.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 00:47:57 +09:00
cat 747d4ec4b0 internal/rosa/libexpat: 2.8.0 to 2.8.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 00:47:32 +09:00
cat b76e6f6519 internal/rosa/tamago: 1.26.2 to 1.26.3
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 00:47:05 +09:00
cat 840d8f68bf internal/rosa/git: disable flaky test
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 00:38:59 +09:00
cat 4bede7ecdd internal/pkg: discontinue DCE resolution on signal
This serves as a stopgap measure to skip long-running DCE resolutions.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 00:29:01 +09:00
cat 487a03b5a3 internal/pkg: deduplicate DCE by ident
This eliminates edge cases where target artifacts do not compare equal.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-13 00:18:27 +09:00
cat 8f3c22896a internal/pkg: DCE benchmark unwrap only
This eliminates noise at lower depths.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 19:56:59 +09:00
cat a167c1aba5 internal/pkg: hold artifact in DCE
This is significantly slower but enables much better error reporting.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 19:45:25 +09:00
cat a6008ef68b internal/pkg: benchmark early DCE
This error has never had decent performance, now is a good time to improve that.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 18:59:25 +09:00
cat 5228b27362 internal/rosa/glslang: 16.2.0 to 16.3.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 17:53:35 +09:00
cat f00d3a07ad internal/rosa/python: trove-classifiers 2026.4.28.13 to 2026.5.7.17
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 17:53:17 +09:00
cat f9538bc21b internal/rosa/python: 3.14.4 to 3.14.5
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 17:52:53 +09:00
cat 6ae5efec56 internal/rosa/gnu: gcc 15.2.0 to 16.1.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 17:52:31 +09:00
cat 14f4c59c8c internal/rosa/llvm: 22.1.4 to 22.1.5
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 17:52:13 +09:00
cat 688d43417b internal/pkg: rename measured exec type
This type is no longer exclusive to KindExecNet.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 15:23:33 +09:00
cat 9f8fafa39b internal/rosa: measure kernel headers
This makes version bumps robust and much less tedious.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 15:19:57 +09:00
cat 6643cfbeee internal/pkg: optionally measure exec artifact
Useful for verifying deterministic output without enabling network access.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 15:11:17 +09:00
cat dcde38f2e9 internal/rosa/llvm: set exclusive bit
This was missed when improving bootstrap.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 15:08:09 +09:00
cat deebbf6b1a internal/rosa/git: disable more flaky tests
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 04:13:02 +09:00
cat 0c557798bc internal/rosa/curl: 8.19.0 to 8.20.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 04:12:40 +09:00
cat 327e6ed5a2 internal/rosa/kernel: 6.12.84 to 6.12.87
This change also pins header version constants to the same values, to be updated manually on a real API change. This eliminates rebuilds on bumping kernel version.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 04:05:30 +09:00
cat 76c7a423a9 internal/rosa/git: disable more flaky tests
Again, causing too many spurious failures.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 03:18:12 +09:00
cat 6e113b8836 internal/pkg: content-based dependency substitution
This change introduces a new fast path for FloodArtifact. It is taken when a curing artifact has identical-by-content controlled relevant inputs and are otherwise identical to an already-cured artifact.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-12 00:19:42 +09:00
cat ce9f4b5f71 internal/rosa: vim artifact
Very useful for troubleshooting.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-10 21:45:56 +09:00
cat 8f727273ef internal/pkg: add riscv64 sums
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-10 17:12:30 +09:00
cat d0a63b942e internal/pkg: add arm64 sums
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-10 16:42:42 +09:00
cat 7f2126df32 internal/rosa/hakurei: 0.4.1 to 0.4.2
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-10 16:30:12 +09:00
cat 0cf0e18e35 release: 0.4.2
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-10 16:16:59 +09:00
cat ee5c0dd135 cmd/dist: optionally skip tests
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
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
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-09 10:51:19 +09:00
cat 0aeb2bccfb internal/rosa: libconfig artifact
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
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
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-09 00:07:43 +09:00
cat 6e73c28a92 internal/rosa: hwdata artifact
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
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
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
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-08 23:20:39 +09:00
cat 70a38bd3b0 internal/rosa: libarchive artifact
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
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
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
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
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-08 00:23:09 +09:00
cat 145d03b366 cmd/mbf: optional emulated target architecture
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Required by mesa.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-05 20:37:09 +09:00
cat f4403ba5cd internal/rosa: libpng artifact
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
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
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
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
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
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
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
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
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
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
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-04 05:28:14 +09:00
cat 337bf20f50 release: 0.4.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-04 05:04:00 +09:00
cat 1cb792cf6e cmd/dist: increase gzip level
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 23:24:07 +09:00
cat 96fdd9ecc5 internal/rosa: disable LTO in tests
This is too expensive and not feasible for development.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-05-01 20:08:26 +09:00
375 changed files with 21861 additions and 10315 deletions
+5 -2
View File
@@ -7,9 +7,12 @@
# go generate
/cmd/hakurei/LICENSE
/cmd/pkgserver/ui/static/*.js
/internal/pkg/testdata/testtool
/cmd/mbf/internal/pkgserver/ui/static
/internal/pkg/internal/testtool/testtool
/internal/rosa/hakurei_current.tar.gz
# cmd/dist default destination
/dist
# local packages
/internal/rosa/package/local
+1 -4
View File
@@ -1,6 +1,3 @@
#!/bin/sh -e
TOOLCHAIN_VERSION="$(go version)"
cd "$(dirname -- "$0")/"
echo "# Building cmd/dist using ${TOOLCHAIN_VERSION}."
go run -v --tags=dist ./cmd/dist
HAKUREI_DIST_MAKE='' exec "$(dirname -- "$0")/cmd/dist/dist.sh"
+8 -2
View File
@@ -20,8 +20,8 @@ func (e AbsoluteError) Error() string {
}
func (e AbsoluteError) Is(target error) bool {
var ce AbsoluteError
if !errors.As(target, &ce) {
ce, ok := errors.AsType[AbsoluteError](target)
if !ok {
return errors.Is(target, syscall.EINVAL)
}
return e == ce
@@ -31,6 +31,8 @@ func (e AbsoluteError) Is(target error) bool {
type Absolute struct{ pathname unique.Handle[string] }
var (
_ fmt.GoStringer = new(Absolute)
_ encoding.TextAppender = new(Absolute)
_ encoding.TextMarshaler = new(Absolute)
_ encoding.TextUnmarshaler = new(Absolute)
@@ -40,6 +42,10 @@ var (
_ encoding.BinaryUnmarshaler = new(Absolute)
)
func (a *Absolute) GoString() string {
return fmt.Sprintf("check.MustAbs(%q)", a.String())
}
// ok returns whether [Absolute] is not the zero value.
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
+8
View File
@@ -4,15 +4,23 @@ import "strings"
const (
// SpecialOverlayEscape is the escape string for overlay mount options.
//
// Deprecated: This is no longer used and will be removed in 0.5.
SpecialOverlayEscape = `\`
// SpecialOverlayOption is the separator string between overlay mount options.
//
// Deprecated: This is no longer used and will be removed in 0.5.
SpecialOverlayOption = ","
// SpecialOverlayPath is the separator string between overlay paths.
//
// Deprecated: This is no longer used and will be removed in 0.5.
SpecialOverlayPath = ":"
)
// EscapeOverlayDataSegment escapes a string for formatting into the data
// 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 {
if s == "" {
return ""
+264
View File
@@ -0,0 +1,264 @@
package main
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
"hakurei.app/check"
"hakurei.app/fhs"
"hakurei.app/hst"
)
// parsePair parses a NUL-delimited quoted paths pair.
func parsePair(s string) (source, target *check.Absolute, err error) {
var p string
if p, err = strconv.Unquote(s); err != nil {
return
}
_source, _target, ok := strings.Cut(p, "\x00")
if source, err = check.NewAbs(_source); err != nil {
return
}
if !ok {
return
}
target, err = check.NewAbs(_target)
return
}
// parse decodes a high-level configuration stream and returns its
// corresponding [hst.Config].
func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) {
shell := fhs.AbsRoot.Append("bin", "zsh")
home := hst.AbsPrivateTmp.Append("home")
c := hst.Config{
ID: id,
Enablements: new(hst.Enablements),
SessionBus: &hst.BusConfig{
Own: []string{
id + ".*",
"org.mpris.MediaPlayer2." + id + ".*",
},
Filter: true,
},
SystemBus: &hst.BusConfig{Filter: true},
Container: &hst.ContainerConfig{
Env: make(map[string]string),
Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSOverlay{
Target: fhs.AbsRoot,
Lower: []*check.Absolute{
base.Append("template", "initial"),
},
Upper: base.Append("template", "upper"),
}},
{FilesystemConfig: &hst.FSBind{
Target: home,
Source: base.Append("state", id),
Write: true,
Ensure: true,
}},
{FilesystemConfig: &hst.FSEphemeral{
Target: fhs.AbsVar.Append("tmp"),
Write: true,
Perm: 01777,
}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block")}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus")}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class")}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev")}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices")}},
},
Username: "chronos",
Shell: shell,
Home: home,
Path: shell,
Args: []string{"zsh", "-c"},
Flags: hst.FCoverRun,
},
}
s := bufio.NewScanner(r)
scanOnce := func() error {
if s.Scan() {
return nil
}
if err := s.Err(); err != nil {
return err
}
return io.ErrUnexpectedEOF
}
if err := scanOnce(); err != nil {
return nil, err
}
if v, err := strconv.Atoi(s.Text()); err != nil {
return nil, err
} else {
c.Identity = v
}
if err := scanOnce(); err != nil {
return nil, err
}
c.Container.Args = append(c.Container.Args, s.Text())
var flagGPU, flagSystemBus bool
flags := map[string]*bool{
"gpu": &flagGPU,
"system_bus": &flagSystemBus,
}
for s.Scan() {
key, value, ok := strings.Cut(s.Text(), " ")
if key != "" && key[0] == ';' {
continue
}
if !ok {
if key == "" {
continue
}
var p *bool
if p, ok = flags[key]; ok {
*p = true
continue
}
switch key {
case "wayland":
*c.Enablements |= hst.EWayland
case "x11":
*c.Enablements |= hst.EX11
case "dbus":
*c.Enablements |= hst.EDBus
case "pipewire":
*c.Enablements |= hst.EPipeWire
case "multiarch":
c.Container.Flags |= hst.FMultiarch
case "devel":
c.Container.Flags |= hst.FDevel
case "userns":
c.Container.Flags |= hst.FUserns
case "net":
c.Container.Flags |= hst.FHostNet
case "abstract":
c.Container.Flags |= hst.FHostAbstract
case "tty":
c.Container.Flags |= hst.FTty
case "mapuid":
c.Container.Flags |= hst.FMapRealUID
case "device":
c.Container.Flags |= hst.FDevice
case "share_runtime":
c.Container.Flags |= hst.FShareRuntime
case "share_tmpdir":
c.Container.Flags |= hst.FShareTmpdir
default:
return nil, fmt.Errorf("invalid flag %q", key)
}
continue
}
switch key {
case "group":
c.Groups = append(c.Groups, value)
continue
case "env":
if key, value, ok = strings.Cut(value, "="); !ok {
return nil, fmt.Errorf("invalid environment %q", key)
}
c.Container.Env[key] = value
continue
case "ro":
source, target, err := parsePair(value)
if err != nil {
return nil, err
}
c.Container.Filesystem = append(c.Container.Filesystem,
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
Target: target,
Source: source,
}},
)
continue
case "rw":
source, target, err := parsePair(value)
if err != nil {
return nil, err
}
c.Container.Filesystem = append(c.Container.Filesystem,
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
Target: target,
Source: source,
Write: true,
}},
)
continue
case "own":
c.SessionBus.Own = append(c.SessionBus.Own, value)
continue
case "own_system":
c.SystemBus.Own = append(c.SystemBus.Own, value)
continue
case "talk":
c.SessionBus.Talk = append(c.SessionBus.Talk, value)
continue
case "talk_system":
c.SystemBus.Talk = append(c.SystemBus.Talk, value)
continue
default:
return nil, fmt.Errorf("invalid key %q", key)
}
}
if err := s.Err(); err != nil {
return nil, err
}
if flagGPU {
c.Container.Filesystem = append(c.Container.Filesystem, []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSBind{
Source: fhs.AbsDev.Append("dri"),
Device: true,
Optional: true,
}},
}...)
}
if !flagSystemBus {
c.SystemBus = nil
}
if c.Container.Flags&hst.FShareTmpdir == 0 {
c.Container.Filesystem = append(c.Container.Filesystem,
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{
Target: fhs.AbsTmp,
Write: true,
Perm: 01777,
}},
)
}
return &c, nil
}
+152
View File
@@ -0,0 +1,152 @@
package main
import (
"reflect"
"strings"
"testing"
"hakurei.app/check"
"hakurei.app/fhs"
"hakurei.app/hst"
)
func TestParse(t *testing.T) {
t.Parallel()
base := fhs.AbsProc.Append("nonexistent")
testCases := []struct {
name string
data string
want *hst.Config
err error
}{
{"com.discordapp.Discord", `8
exec Discord --ozone-platform-hint=wayland
gpu
wayland
dbus
system_bus
pipewire
userns
net
mapuid
share_runtime
share_tmpdir
group media_rw
env ELECTRON_TRASH=gio
rw "/sdcard"
; remove before reusing
ro "/bin\x00/.hakurei/bin"
talk org.kde.StatusNotifierWatcher
talk com.canonical.AppMenu.Registrar
talk com.canonical.indicator.application
talk com.canonical.Unity
`, &hst.Config{
Identity: 8,
ID: "com.discordapp.Discord",
Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire),
Groups: []string{"media_rw"},
SessionBus: &hst.BusConfig{
Talk: []string{
"org.kde.StatusNotifierWatcher",
"com.canonical.AppMenu.Registrar",
"com.canonical.indicator.application",
"com.canonical.Unity",
},
Own: []string{
"com.discordapp.Discord.*",
"org.mpris.MediaPlayer2.com.discordapp.Discord.*",
},
Filter: true,
},
SystemBus: &hst.BusConfig{Filter: true},
Container: &hst.ContainerConfig{
Env: map[string]string{
"ELECTRON_TRASH": "gio",
},
Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSOverlay{
Target: fhs.AbsRoot,
Lower: []*check.Absolute{
base.Append("template", "initial"),
},
Upper: base.Append("template", "upper"),
}},
{FilesystemConfig: &hst.FSBind{
Target: hst.AbsPrivateTmp.Append("home"),
Source: base.Append("state", "com.discordapp.Discord"),
Write: true,
Ensure: true,
}},
{FilesystemConfig: &hst.FSEphemeral{
Target: fhs.AbsVar.Append("tmp"),
Write: true,
Perm: 01777,
}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block")}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus")}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class")}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev")}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices")}},
{FilesystemConfig: &hst.FSBind{
Source: check.MustAbs("/sdcard"),
Write: true,
}},
{FilesystemConfig: &hst.FSBind{
Target: check.MustAbs("/.hakurei/bin"),
Source: check.MustAbs("/bin"),
}},
{FilesystemConfig: &hst.FSBind{
Source: fhs.AbsDev.Append("dri"),
Device: true,
Optional: true,
}},
},
Username: "chronos",
Shell: fhs.AbsRoot.Append("bin", "zsh"),
Home: hst.AbsPrivateTmp.Append("home"),
Path: fhs.AbsRoot.Append("bin", "zsh"),
Args: []string{
"zsh", "-c",
"exec Discord --ozone-platform-hint=wayland",
},
Flags: hst.FCoverRun | hst.FUserns | hst.FHostNet | hst.FMapRealUID |
hst.FShareRuntime | hst.FShareTmpdir,
},
}, nil},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got, err := parse(
tc.name,
base,
strings.NewReader(tc.data),
)
if !reflect.DeepEqual(err, tc.err) {
t.Errorf("parse: error = %v, want %v", err, tc.err)
}
if err != nil {
return
}
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("parse: %#v, want %#v", got, tc.want)
}
})
}
}
+170
View File
@@ -0,0 +1,170 @@
// The app program is a proof-of-concept frontend for cmd/hakurei.
//
// This program is not covered by the compatibility promise. The command line
// interface and configuration syntax may change at any time.
package main
import (
"context"
"errors"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"hakurei.app/check"
"hakurei.app/command"
"hakurei.app/fhs"
"hakurei.app/hst"
"hakurei.app/message"
)
func main() {
log.SetFlags(0)
log.SetPrefix("app: ")
msg := message.New(log.Default())
ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
defer stop()
var (
flagVerbose bool
flagBase string
base, template, initial, upper, work *check.Absolute
)
c := command.New(os.Stderr, log.Printf, "app", func([]string) (err error) {
msg.SwapVerbose(flagVerbose)
flagBase = os.ExpandEnv(flagBase)
if flagBase == "" {
flagBase = "state"
}
if flagBase, err = filepath.Abs(flagBase); err != nil {
return
} else if base, err = check.NewAbs(flagBase); err != nil {
return
}
template = base.Append("template")
initial = template.Append("initial")
upper = template.Append("upper")
work = template.Append("work")
return
}).Flag(
&flagVerbose,
"v", command.BoolFlag(false),
"Increase log verbosity",
).Flag(
&flagBase,
"d", command.StringFlag("$HAKUREI_APP_PATH"),
"Configuration and state directory",
)
{
var (
flagShell string
flagHome string
)
c.NewCommand(
"enter", "Enter mutable state template",
func([]string) error {
config := hst.Config{
ID: "app.hakurei.mutable",
Container: &hst.ContainerConfig{
Hostname: "mutable",
Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSOverlay{
Target: fhs.AbsRoot,
Lower: []*check.Absolute{initial},
Upper: upper,
Work: work,
}},
{FilesystemConfig: &hst.FSEphemeral{
Target: fhs.AbsTmp,
Write: true,
Perm: 0755,
}},
},
Username: "chronos",
Flags: hst.FMultiarch |
hst.FDevel |
hst.FUserns |
hst.FHostNet |
hst.FTty,
},
}
if a, err := check.NewAbs(flagShell); err != nil {
return err
} else {
config.Container.Shell = a
config.Container.Path = a
config.Container.Args = []string{
"-" + filepath.Base(flagShell),
}
}
if a, err := check.NewAbs(flagHome); err != nil {
return err
} else {
config.Container.Home = a
}
return run(ctx, msg, &config)
},
).Flag(
&flagShell,
"shell", command.StringFlag("/bin/zsh"),
"Shell program within container",
).Flag(
&flagHome,
"home", command.StringFlag("/home/chronos"),
"Home directory within container",
)
}
c.NewCommand(
"run", "Start the named application",
func(args []string) error {
if len(args) != 1 {
return errors.New("run requires 1 argument")
}
var config *hst.Config
f, err := os.Open(base.Append("app", args[0]).String())
if err != nil {
return err
}
config, err = parse(args[0], base, f)
if closeErr := f.Close(); err == nil {
err = closeErr
}
if err != nil {
return err
}
return run(ctx, msg, config)
},
)
c.MustParse(os.Args[1:], func(err error) {
if e, ok := errors.AsType[*exec.ExitError](err); ok && e != nil {
os.Exit(e.ExitCode())
}
if w, ok := err.(interface{ Unwrap() []error }); !ok {
log.Fatal(err)
} else {
errs := w.Unwrap()
for i, e := range errs {
if i == len(errs)-1 {
log.Fatal(e)
}
log.Println(e)
}
}
})
}
+51
View File
@@ -0,0 +1,51 @@
package main
import (
"context"
"encoding/json"
"os"
"os/exec"
"syscall"
"hakurei.app/hst"
"hakurei.app/message"
)
// run starts a container via cmd/hakurei and returns after it terminates.
func run(ctx context.Context, msg message.Msg, config *hst.Config) error {
c, cancel := context.WithCancel(ctx)
defer cancel()
cmd := exec.CommandContext(c, "hakurei")
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.Cancel = func() error {
return cmd.Process.Signal(syscall.SIGINT)
}
if msg.IsVerbose() {
cmd.Args = append(cmd.Args, "-v")
}
cmd.Args = append(cmd.Args, "run", "3")
r, w, err := os.Pipe()
if err != nil {
return err
}
cmd.ExtraFiles = append(cmd.ExtraFiles, r)
if err = cmd.Start(); err != nil {
_, _ = r.Close(), w.Close()
return err
}
if err = r.Close(); err != nil {
_ = w.Close()
return err
} else if err = json.NewEncoder(w).Encode(&config); err != nil {
_ = w.Close()
return err
} else if err = w.Close(); err != nil {
return err
}
return cmd.Wait()
}
+1
View File
@@ -0,0 +1 @@
v0.4.4
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
+32 -15
View File
@@ -18,8 +18,13 @@ import (
"os/signal"
"path/filepath"
"runtime"
"strings"
)
//go:generate sh -c "git describe --tags > VERSION"
//go:embed VERSION
var version string
// 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 {
@@ -42,14 +47,19 @@ func mustRun(ctx context.Context, name string, arg ...string) {
var comp []byte
func main() {
fmt.Println()
log.SetFlags(0)
log.SetPrefix("# ")
log.SetPrefix("")
version := getenv("HAKUREI_VERSION", "untagged")
verbose := os.Getenv("VERBOSE") != ""
runTests := os.Getenv("HAKUREI_DIST_MAKE") == ""
version = getenv("HAKUREI_VERSION", strings.TrimSpace(version))
prefix := getenv("PREFIX", "/usr")
destdir := getenv("DESTDIR", "dist")
if verbose {
log.Println()
}
if err := os.MkdirAll(destdir, 0755); err != nil {
log.Fatal(err)
}
@@ -76,12 +86,17 @@ func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
log.Println("Building hakurei.")
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",
"-v", "-o", s,
verboseFlag, "-o", s,
"-ldflags=-s -w "+
"-buildid= -linkmode external -extldflags=-static "+
"-X hakurei.app/internal/info.buildVersion="+version+" "+
@@ -90,17 +105,19 @@ func main() {
"-X main.hakureiPath="+prefix+"/bin/hakurei",
"./...",
)
fmt.Println()
log.Println()
log.Println("Testing Hakurei.")
mustRun(
ctx, "go", "test",
"-ldflags=-buildid= -linkmode external -extldflags=-static",
"./...",
)
fmt.Println()
if runTests {
log.Println("##### Testing Hakurei.")
mustRun(
ctx, "go", "test",
"-ldflags=-buildid= -linkmode external -extldflags=-static",
"./...",
)
log.Println()
}
log.Println("Creating distribution.")
log.Println("##### Creating distribution.")
const suffix = ".tar.gz"
distName := "hakurei-" + version + "-" + runtime.GOARCH
var f *os.File
@@ -121,7 +138,7 @@ func main() {
}()
h := sha512.New()
gw := gzip.NewWriter(io.MultiWriter(f, h))
gw, _ := gzip.NewWriterLevel(io.MultiWriter(f, h), gzip.BestCompression)
tw := tar.NewWriter(gw)
mustWriteHeader := func(name string, size int64, mode os.FileMode) {
+153 -20
View File
@@ -5,17 +5,91 @@
package main
import (
"context"
"crypto/rand"
"log"
"os"
"os/signal"
"runtime"
"runtime/pprof"
"slices"
"strings"
. "syscall"
"hakurei.app/internal/kobject"
"hakurei.app/internal/report"
"hakurei.app/internal/uevent"
"hakurei.app/message"
)
var r report.Reporter
func init() {
log.SetFlags(0)
log.SetPrefix("earlyinit: ")
r.SetOutput(log.Default())
// this handles SIGQUIT to provide useful debugging information without
// terminating, and prevents the runtime from throwing on the must family
// of early error reporting functions, DO NOT REMOVE
c := make(chan os.Signal, 1)
signal.Notify(c, SIGQUIT)
go func() {
for {
<-c
if p := pprof.Lookup("goroutine"); p == nil {
log.Println("initial built-in goroutine profile does not exist")
} else if err := p.WriteTo(os.Stderr, 2); err != nil {
log.Println(err)
}
}
}()
}
// fatal calls [log.Println] with v and blocks forever. Must be called from
// main. Must not be used after error reporting is set up.
func fatal(v ...any) {
log.Println(v...)
log.Println("unable to continue, please reboot and resolve the problem manually")
select {}
}
// must calls fatal with err if it is non-nil.
func must(err error) {
if err != nil {
log.Println(err)
select {}
}
}
// mustSyscall is like must, but with an additional action name.
func mustSyscall(action string, err error) {
if err != nil {
fatal("cannot "+action+":", err)
select {}
}
}
// must1 is like must, but with an additional passed through value.
func must1[T any](v T, err error) T {
must(err)
return v
}
const (
// optionSystem specifies devpath of the system device.
optionSystem = "system"
// flagVerbose increases output verbosity.
flagVerbose = "verbose"
// flagStrict sets [report.DStrict] on r.
flagStrict = "strict"
// flagNoRecover sets [report.DNoRecover] on r.
flagNoRecover = "no_recover"
)
func main() {
runtime.LockOSThread()
log.SetFlags(0)
log.SetPrefix("earlyinit: ")
var (
option map[string]string
@@ -33,15 +107,44 @@ func main() {
}
}
if err := Mount(
{
var flag uint64
if slices.Contains(flags, flagStrict) {
flag |= report.DStrict
}
if slices.Contains(flags, flagNoRecover) {
flag |= report.DNoRecover
}
log.Printf("reporting flags %x", flag)
r.SetFlags(flag)
}
msg := message.New(log.Default())
msg.SwapVerbose(slices.Contains(flags, flagVerbose))
mustSyscall("mount devtmpfs", Mount(
"devtmpfs",
"/dev/",
"devtmpfs",
MS_NOSUID|MS_NOEXEC,
"",
); err != nil {
log.Fatalf("cannot mount devtmpfs: %v", err)
}
))
must(os.Mkdir("/dev/pts/", 0))
mustSyscall("mount devpts", Mount(
"devpts",
"/dev/pts/",
"devpts",
MS_NOSUID|MS_NOEXEC,
"mode=620,ptmxmode=666",
))
must(os.Mkdir("/dev/shm/", 0))
mustSyscall("mount shm", Mount(
"shm",
"/dev/shm/",
"tmpfs",
MS_NOSUID|MS_NODEV,
"",
))
// The kernel might be unable to set up the console. When that happens,
// printk is called with "Warning: unable to open an initial console."
@@ -98,6 +201,49 @@ func main() {
"",
))
conn := must1(uevent.Dial(-128 * 1024 * 1024))
events := make(chan *uevent.Message, 1<<10)
var uuid uevent.UUID
must1(rand.Read(uuid[:]))
ctx, cancel := context.WithCancel(context.Background())
go consume(ctx, msg, &r, conn, uuid, events)
s := kobject.New(uuid, func(o *kobject.Object, env map[string]string) {
p := make([]string, 0, len(env))
for k, v := range env {
p = append(p, k+"="+v)
}
slices.Sort(p)
log.Printf("change %s: %s", o.DevPath, strings.Join(p, ", "))
}, func(err error) {
severity := report.Inconsistent
if e, ok := err.(kobject.EventError); ok && e.Kind == kobject.EBadTarget {
severity = report.Trivial
}
r.Dispatch(
severity,
"processed inconsistent uevent",
err,
)
})
go func() {
s.Consume(ctx, events)
log.Println("closing NETLINK_KOBJECT_UEVENT socket")
cancel()
if err := conn.Close(); err != nil {
log.Fatal(err) // not reached
}
}()
must(os.Mkdir("/system", 0))
if devpath := option[optionSystem]; devpath == "" {
fatal("system must be nonempty")
} else {
log.Printf("waiting for devpath pattern %q", devpath)
mustMountSystem(ctx, s, devpath)
}
// after top level has been set up
mustSyscall("remount root", Mount(
"",
@@ -113,19 +259,6 @@ func main() {
[]byte("/system/lib/firmware"),
0,
))
go dispatchModprobe(ctx, s)
}
// 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)
}
}
+73
View File
@@ -0,0 +1,73 @@
package main
import (
"context"
"errors"
"fmt"
"log"
"os/exec"
"strings"
"hakurei.app/internal/kobject"
"hakurei.app/internal/report"
"hakurei.app/internal/uevent"
)
// ModprobeError describes an unsuccessful modprobe invocation.
type ModprobeError struct {
ModAlias string `json:"modalias"`
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
ExitCode int `json:"exit_code"`
}
var _ report.RepresentableError = ModprobeError{}
func (ModprobeError) Representable() {}
func (e ModprobeError) Error() string {
return fmt.Sprintf(
"modprobe exit status %d: %s",
e.ExitCode, strings.TrimSpace(e.Stderr),
)
}
// dispatchModprobe invokes modprobe for [uevent.KOBJ_ADD] events raising new
// MODALIAS strings.
func dispatchModprobe(
ctx context.Context,
s *kobject.State,
) {
aliases := make(chan string, 1<<8)
go func() {
defer close(aliases)
s.Range(ctx, func(o *kobject.Object, act uevent.KobjectAction) bool {
if act == uevent.KOBJ_ADD && o.Driver == "" && o.ModAlias != "" {
aliases <- o.ModAlias
}
return true
})
}()
for alias := range aliases {
stdout, err := exec.Command("/system/sbin/modprobe", alias).Output()
if err == nil {
if len(stdout) > 0 {
log.Println(string(stdout))
}
continue
}
exitError, ok := errors.AsType[*exec.ExitError](err)
if !ok || exitError == nil {
r.Dispatch(report.Degraded, "invoke modprobe", err)
continue
}
r.Dispatch(report.Trivial, "load device driver", ModprobeError{
ModAlias: alias,
Stdout: string(stdout),
Stderr: string(exitError.Stderr),
ExitCode: exitError.ExitCode(),
})
}
}
+71
View File
@@ -0,0 +1,71 @@
package main
import (
"context"
"errors"
"os"
"path/filepath"
"strconv"
"syscall"
"time"
"hakurei.app/check"
"hakurei.app/fhs"
"hakurei.app/internal/kobject"
"hakurei.app/internal/uevent"
)
// mustMountSystem waits for and mounts a system device matching pattern.
func mustMountSystem(
ctx context.Context,
s *kobject.State,
pattern string,
) {
c, stop := context.WithTimeout(ctx, 30*time.Second)
defer stop()
for {
var matchErr error
var systemPath *check.Absolute
s.Range(c, func(o *kobject.Object, act uevent.KobjectAction) bool {
if (act != uevent.KOBJ_ADD && act != uevent.KOBJ_CHANGE) ||
o.Subsystem != "block" ||
o.Env["DEVTYPE"] != "disk" {
return true
}
if ok, err := filepath.Match(pattern, o.DevPath); err != nil {
matchErr = err
return false
} else if !ok {
return true
}
name, ok := o.Env["DEVNAME"]
if !ok {
return true
}
systemPath = fhs.AbsDev.Append(name)
return false
})
if c.Err() != nil {
fatal("devpath", strconv.Quote(pattern), "never appeared")
}
if matchErr != nil {
fatal("cannot match system devpath:", matchErr)
}
err := syscall.Mount(
systemPath.String(),
"/system/",
"squashfs",
0,
"threads=multi",
)
if err == nil {
break
}
if !errors.Is(err, os.ErrNotExist) {
fatal("cannot mount system:", err)
}
}
}
+104
View File
@@ -0,0 +1,104 @@
package main
import (
"context"
"time"
"hakurei.app/fhs"
"hakurei.app/internal/report"
"hakurei.app/internal/uevent"
"hakurei.app/message"
)
// newRejectColdboot returns a function to be called on every subsequent pending
// coldboot, and returns whether coldboot should proceed. Rejection is sticky.
func newRejectColdboot() func() bool {
// one coldboot per five minutes, two consecutive coldboot
const (
coldbootInterval = 5 * time.Minute
coldbootBurst = 2
)
done := make(chan struct{})
s := make(chan struct{}, coldbootBurst)
s <- struct{}{} // for early fault before reporting is ready
go func() {
t := time.NewTicker(coldbootInterval)
for {
select {
case <-done:
return
case <-t.C:
select {
case s <- struct{}{}:
default:
}
}
}
}()
return func() bool {
select {
case <-s:
return true
case <-done:
return false
default:
close(done)
return false
}
}
}
// consume continuously consumes events from conn with retries.
func consume(
ctx context.Context,
msg message.Msg,
r *report.Reporter,
conn *uevent.Conn,
uuid uevent.UUID,
events chan<- *uevent.Message,
) {
defer close(events)
nextColdboot := newRejectColdboot()
coldboot := true
retry:
if dispatchErr := conn.Consume(ctx, fhs.Sys, &uuid, events, coldboot, func(path string) {
msg.Verbose("coldboot visited", path)
}, func(err error) bool {
if _, ok := err.(uevent.NeedsColdboot); ok && !nextColdboot() {
r.Dispatch(
report.Degraded,
"rejecting coldboot loop",
err,
)
return false
}
r.Dispatch(
report.Inconsistent,
"consumed invalid message",
err,
)
return true
}, nil); dispatchErr != nil {
if _, ok := dispatchErr.(uevent.Recoverable); !ok {
r.Dispatch(
report.Fatal,
"discontinuing uevent processing due to nonrecoverable error",
dispatchErr,
)
return
}
if _, ok := dispatchErr.(uevent.NeedsColdboot); ok {
// coldboot loop rejected by handler
coldboot = false
}
goto retry
}
}
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"testing"
"testing/synctest"
"time"
)
func TestRejectColdboot(t *testing.T) {
t.Parallel()
synctest.Test(t, func(t *testing.T) {
nextColdboot := newRejectColdboot()
want := func(want bool) {
if got := nextColdboot(); got != want {
t.Fatalf("nextColdboot: %v, want %v", got, want)
}
}
synctest.Wait()
want(true)
time.Sleep(time.Hour)
synctest.Wait()
want(true)
want(true)
time.Sleep(5 * time.Minute)
synctest.Wait()
want(true)
want(false)
time.Sleep(time.Hour)
synctest.Wait()
want(false)
want(false)
})
}
+6 -5
View File
@@ -7,7 +7,8 @@ import (
"strconv"
)
// decodeJSON decodes json from r and stores it in v. A non-nil error results in a call to fatal.
// decodeJSON decodes json from r and stores it in v. A non-nil error results in
// a call to fatal.
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any) {
err := json.NewDecoder(r).Decode(v)
if err == nil {
@@ -47,14 +48,14 @@ func encodeJSON(fatal func(v ...any), output io.Writer, short bool, v any) {
}
if err := encoder.Encode(v); err != nil {
var marshalerError *json.MarshalerError
if errors.As(err, &marshalerError) && marshalerError != nil {
if e, ok := errors.AsType[*json.MarshalerError](err); ok && e != nil {
// this likely indicates an implementation error in hst
fatal("cannot encode json for " + marshalerError.Type.String() + ": " + marshalerError.Err.Error())
fatal("cannot encode json for " + e.Type.String() + ": " + e.Err.Error())
return
}
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does not need to be handled
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does
// not need to be handled
fatal("cannot write json: " + err.Error())
}
}
+5 -2
View File
@@ -64,7 +64,7 @@ func TestPrintShowInstance(t *testing.T) {
Identity: 9 (org.chromium.Chromium)
Enablements: wayland, dbus, pipewire
Groups: video, dialout, plugdev
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir
Home: /data/data/org.chromium.Chromium
Hostname: localhost
Path: /run/current-system/sw/bin/chromium
@@ -161,7 +161,7 @@ App
Identity: 9 (org.chromium.Chromium)
Enablements: wayland, dbus, pipewire
Groups: video, dialout, plugdev
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir
Home: /data/data/org.chromium.Chromium
Hostname: localhost
Path: /run/current-system/sw/bin/chromium
@@ -355,6 +355,7 @@ App
"multiarch": true,
"map_real_uid": true,
"device": true,
"cover_run": true,
"share_runtime": true,
"share_tmpdir": true
},
@@ -506,6 +507,7 @@ App
"multiarch": true,
"map_real_uid": true,
"device": true,
"cover_run": true,
"share_runtime": true,
"share_tmpdir": true
}
@@ -704,6 +706,7 @@ func TestPrintPs(t *testing.T) {
"multiarch": true,
"map_real_uid": true,
"device": true,
"cover_run": true,
"share_runtime": true,
"share_tmpdir": true
},
+1 -23
View File
@@ -21,15 +21,6 @@
// 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.
@@ -62,7 +53,6 @@ import (
"runtime"
"slices"
"strconv"
"strings"
"syscall"
)
@@ -107,18 +97,6 @@ func main() {
return
}
var toolPath string
pexe := filepath.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
if p, err := os.Readlink(pexe); err != nil {
log.Fatalf("cannot read parent executable path: %v", err)
} else if strings.HasSuffix(p, " (deleted)") {
log.Fatal("hakurei executable has been deleted")
} else if p != hakureiPath {
log.Fatal("this program must be started by hakurei")
} else {
toolPath = p
}
// refuse to run if hsurc is not protected correctly
if s, err := os.Stat(hsuConfPath); err != nil {
log.Fatal(err)
@@ -205,7 +183,7 @@ func main() {
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
}
if err := syscall.Exec(toolPath, []string{
if err := syscall.Exec(hakureiPath, []string{
"hakurei",
"shim",
}, []string{
+48 -3
View File
@@ -2,12 +2,14 @@ package main
import (
"context"
"net/http"
"os"
"path/filepath"
"testing"
"hakurei.app/check"
"hakurei.app/internal/pkg"
"hakurei.app/internal/rosa"
"hakurei.app/message"
)
@@ -19,10 +21,17 @@ type cache struct {
// Should generally not be used directly.
c *pkg.Cache
cures, jobs int
hostAbstract, idle bool
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
base, mirror string
}
// open opens the underlying [pkg.Cache].
@@ -45,6 +54,9 @@ func (cache *cache) open() (err error) {
if cache.hostAbstract {
flags |= pkg.CHostAbstract
}
if !cache.verboseInit {
flags |= pkg.CSuppressInit
}
done := make(chan struct{})
defer close(done)
@@ -70,6 +82,39 @@ func (cache *cache) open() (err error) {
cache.jobs,
base,
)
if err != nil {
return
}
done <- struct{}{}
if cache.mirror != "" {
var pub []byte
pub, err = os.ReadFile(base.Append("ed25519.pub").String())
if err != nil {
cache.c.Close()
return
}
var r rosa.Remote
if r, err = rosa.NewRemote(cache.mirror, pub, http.DefaultClient); err != nil {
cache.c.Close()
return err
}
cache.c.SetExternal(r)
}
if cache.qemu != nil {
var pathname *check.Absolute
pathname, _, err = cache.c.Cure(cache.qemu)
if err != nil {
cache.c.Close()
return
}
for arch, entry := range rosa.Arches(pathname) {
pkg.RegisterArch(arch, entry)
}
}
return
}
+14 -23
View File
@@ -6,6 +6,7 @@ import (
"io"
"os"
"strings"
"unique"
"hakurei.app/internal/pkg"
"hakurei.app/internal/rosa"
@@ -17,25 +18,12 @@ func commandInfo(
args []string,
w io.Writer,
writeStatus bool,
reportPath string,
r *rosa.Report,
) (err error) {
if len(args) == 0 {
return errors.New("info requires at least 1 argument")
}
var r *rosa.Report
if reportPath != "" {
if r, err = rosa.OpenReport(reportPath); err != nil {
return err
}
defer func() {
if closeErr := r.Close(); err == nil {
err = closeErr
}
}()
defer r.HandleAccess(&err)()
}
// recovered by HandleAccess
mustPrintln := func(a ...any) {
if _, _err := fmt.Fprintln(w, a...); _err != nil {
@@ -48,17 +36,19 @@ func commandInfo(
}
}
t := rosa.Native().Std()
for i, name := range args {
if p, ok := rosa.ResolveName(name); !ok {
handle := rosa.ArtifactH(unique.Make(name))
if meta, a := t.Load(handle); meta == nil {
return fmt.Errorf("unknown artifact %q", name)
} else {
var suffix string
if version := rosa.Std.Version(p); version != rosa.Unversioned {
suffix += "-" + version
if meta.Version != rosa.Unversioned {
suffix += "-" + meta.Version
}
mustPrintln("name : " + name + suffix)
meta := rosa.GetMetadata(p)
mustPrintln("description : " + meta.Description)
if meta.Website != "" {
mustPrintln("website : " +
@@ -67,9 +57,10 @@ func commandInfo(
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
_meta, _ := rosa.Native().Std().MustLoad(d)
s := _meta.Name
if _meta.Version != rosa.Unversioned {
s += "-" + _meta.Version
}
mustPrint(" " + s)
}
@@ -81,7 +72,7 @@ func commandInfo(
if r == nil {
var f io.ReadSeekCloser
err = cm.Do(func(cache *pkg.Cache) (err error) {
f, err = cache.OpenStatus(rosa.Std.Load(p))
f, err = cache.OpenStatus(a)
return
})
if err != nil {
@@ -100,7 +91,7 @@ func commandInfo(
}
}
} else if err = cm.Do(func(cache *pkg.Cache) (err error) {
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
status, n := r.ArtifactOf(cache.Ident(a))
if status == nil {
mustPrintln(
statusPrefix + "not in report",
+39 -19
View File
@@ -10,6 +10,7 @@ import (
"strings"
"syscall"
"testing"
"unique"
"unsafe"
"hakurei.app/internal/pkg"
@@ -20,6 +21,14 @@ import (
func TestInfo(t *testing.T) {
t.Parallel()
_t := rosa.Native().Std()
qemuMeta, _ := _t.Load(rosa.H("qemu"))
glibMeta, _ := _t.Load(rosa.H("glib"))
zlibMeta, zlib := _t.Load(rosa.H("zlib"))
zstdMeta, _ := _t.Load(rosa.H("zstd"))
hakureiMeta, _ := _t.Load(rosa.H("hakurei"))
hakureiDistMeta, _ := _t.Load(rosa.H("hakurei-dist"))
testCases := []struct {
name string
args []string
@@ -29,24 +38,24 @@ func TestInfo(t *testing.T) {
wantErr any
}{
{"qemu", []string{"qemu"}, nil, "", `
name : qemu-` + rosa.Std.Version(rosa.QEMU) + `
name : qemu-` + qemuMeta.Version + `
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) + `
depends on : glib-` + glibMeta.Version + ` zstd-` + zstdMeta.Version + `
`, nil},
{"multi", []string{"hakurei", "hakurei-dist"}, nil, "", `
name : hakurei-` + rosa.Std.Version(rosa.Hakurei) + `
name : hakurei-` + hakureiMeta.Version + `
description : low-level userspace tooling for Rosa OS
website : https://hakurei.app
name : hakurei-dist-` + rosa.Std.Version(rosa.HakureiDist) + `
name : hakurei-dist-` + hakureiDistMeta.Version + `
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) + `
name : zlib-` + zlibMeta.Version + `
description : lossless data-compression library
website : https://zlib.net
@@ -56,12 +65,12 @@ website : https://zlib.net
"zstd": "internal/pkg (amd64) on satori\n",
"hakurei": "internal/pkg (amd64) on satori\n\n",
}, "", `
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
name : zlib-` + zlibMeta.Version + `
description : lossless data-compression library
website : https://zlib.net
status : not yet cured
name : zstd-` + rosa.Std.Version(rosa.Zstd) + `
name : zstd-` + zstdMeta.Version + `
description : a fast compression algorithm
website : https://facebook.github.io/zstd
status : internal/pkg (amd64) on satori
@@ -70,19 +79,19 @@ status : internal/pkg (amd64) on satori
{"status cache perm", []string{"zlib"}, map[string]string{
"zlib": "\x00",
}, "", `
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
name : zlib-` + zlibMeta.Version + `
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())),
Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(zlib).Value())),
Err: syscall.EACCES,
}
}},
{"status report", []string{"zlib"}, nil, strings.Repeat("\x00", len(pkg.Checksum{})+8), `
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
name : zlib-` + zlibMeta.Version + `
description : lossless data-compression library
website : https://zlib.net
status : not in report
@@ -95,7 +104,7 @@ status : not in report
var (
cm *cache
buf strings.Builder
rp string
r *rosa.Report
)
if tc.status != nil || tc.report != "" {
@@ -108,20 +117,31 @@ status : not in report
}
if tc.report != "" {
rp = filepath.Join(t.TempDir(), "report")
if err := os.WriteFile(
rp,
pathname := filepath.Join(t.TempDir(), "report")
err := os.WriteFile(
pathname,
unsafe.Slice(unsafe.StringData(tc.report), len(tc.report)),
0400,
); err != nil {
)
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 {
_, a := _t.Load(rosa.ArtifactH(unique.Make(name)))
if a == nil {
t.Fatalf("invalid name %q", name)
}
perm := os.FileMode(0400)
@@ -132,7 +152,7 @@ status : not in report
return os.WriteFile(filepath.Join(
cm.base,
"status",
pkg.Encode(cache.Ident(rosa.Std.Load(p)).Value()),
pkg.Encode(cache.Ident(a).Value()),
), unsafe.Slice(unsafe.StringData(status), len(status)), perm)
}); err != nil {
t.Fatalf("Do: error = %v", err)
@@ -157,7 +177,7 @@ status : not in report
tc.args,
&buf,
cm != nil,
rp,
r,
); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("commandInfo: error = %v, want %v", err, wantErr)
}
@@ -1,6 +1,8 @@
package main
// Package pkgserver implements the package metadata service backend.
package pkgserver
import (
"context"
"encoding/json"
"log"
"net/http"
@@ -8,6 +10,7 @@ import (
"path"
"strconv"
"sync"
"time"
"hakurei.app/internal/info"
"hakurei.app/internal/rosa"
@@ -27,7 +30,7 @@ var (
// handleInfo writes constant system information.
func handleInfo(w http.ResponseWriter, _ *http.Request) {
infoPayloadOnce.Do(func() {
infoPayload.Count = int(rosa.PresetUnexportedStart)
infoPayload.Count = len(rosa.Native().Collect())
infoPayload.HakureiVersion = info.Version()
})
// TODO(mae): cache entire response if no additional fields are planned
@@ -88,7 +91,7 @@ func (index *packageIndex) handleGet(w http.ResponseWriter, r *http.Request) {
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)),
strconv.Itoa(len(index.sorts[0])-1),
http.StatusBadRequest,
)
return
@@ -122,7 +125,7 @@ func (index *packageIndex) handleSearch(w http.ResponseWriter, r *http.Request)
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)),
strconv.Itoa(len(index.sorts[0])-1),
http.StatusBadRequest,
)
return
@@ -158,6 +161,29 @@ func (index *packageIndex) registerAPI(mux *http.ServeMux) {
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) {
@@ -1,9 +1,8 @@
package main
package pkgserver
import (
"net/http"
"net/http/httptest"
"slices"
"strconv"
"testing"
@@ -32,7 +31,7 @@ func TestAPIInfo(t *testing.T) {
checkPayload(t, resp, struct {
Count int `json:"count"`
HakureiVersion string `json:"hakurei_version"`
}{int(rosa.PresetUnexportedStart), info.Version()})
}{len(rosa.Native().Collect()), info.Version()})
}
func TestAPIGet(t *testing.T) {
@@ -93,11 +92,12 @@ func TestAPIGet(t *testing.T) {
)
})
count := len(rosa.Native().Collect())
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, "limit=1&sort=0&index", 0, count-1,
"index must be an integer between 0 and "+strconv.Itoa(count-1),
)
})
@@ -108,76 +108,4 @@ func TestAPIGet(t *testing.T) {
"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 {
Count int `json:"count"`
Values []*metadata `json:"values"`
}) bool {
return got.Count == len(want) &&
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=0&sort=0", []*metadata{
{
Metadata: rosa.GetMetadata(0),
Version: rosa.Std.Version(0),
},
{
Metadata: rosa.GetMetadata(1),
Version: rosa.Std.Version(1),
},
})
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),
},
})
}
@@ -1,4 +1,4 @@
package main
package pkgserver
import (
"cmp"
@@ -23,7 +23,7 @@ const (
// packageIndex refers to metadata by name and various sort orders.
type packageIndex struct {
sorts [sortOrderEnd + 1][rosa.PresetUnexportedStart]*metadata
sorts [sortOrderEnd + 1][]*metadata
names map[string]*metadata
search searchCache
// Taken from [rosa.Report] if available.
@@ -32,11 +32,11 @@ type packageIndex struct {
// metadata holds [rosa.Metadata] extended with additional information.
type metadata struct {
p rosa.PArtifact
handle rosa.ArtifactH
*rosa.Metadata
// Populated via [rosa.Toolchain.Version], [rosa.Unversioned] is equivalent
// to the zero value. Otherwise, the zero value is invalid.
// Copied from [rosa.Metadata], [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"`
@@ -50,20 +50,23 @@ type metadata struct {
}
// populate deterministically populates packageIndex, optionally with a report.
func (index *packageIndex) populate(cache *pkg.Cache, report *rosa.Report) (err error) {
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
handles := rosa.Native().Collect()
work := make([]*metadata, len(handles))
index.names = make(map[string]*metadata)
for p := range rosa.PresetUnexportedStart {
ir := pkg.NewIR()
for i, handle := range handles {
meta, a := rosa.Native().Std().MustLoad(handle)
m := metadata{
p: p,
handle: handle,
Metadata: rosa.GetMetadata(p),
Version: rosa.Std.Version(p),
Metadata: meta,
Version: meta.Version,
}
if m.Version == "" {
return errors.New("invalid version from " + m.Name)
@@ -72,33 +75,33 @@ func (index *packageIndex) populate(cache *pkg.Cache, report *rosa.Report) (err
m.Version = ""
}
if cache != nil && report != nil {
id := cache.Ident(rosa.Std.Load(p))
if report != nil {
id := ir.Ident(a)
m.ids = pkg.Encode(id.Value())
m.status, m.Size = report.ArtifactOf(id)
m.HasReport = m.Size >= 0
}
work[p] = &m
work[i] = &m
index.names[m.Name] = &m
}
index.sorts[declarationAscending] = work
index.sorts[declarationDescending] = work
index.sorts[declarationDescending] = slices.Clone(work)
slices.Reverse(index.sorts[declarationDescending][:])
index.sorts[nameAscending] = work
index.sorts[nameAscending] = slices.Clone(work)
slices.SortFunc(index.sorts[nameAscending][:], func(a, b *metadata) int {
return strings.Compare(a.Name, b.Name)
})
index.sorts[nameDescending] = index.sorts[nameAscending]
index.sorts[nameDescending] = slices.Clone(index.sorts[nameAscending])
slices.Reverse(index.sorts[nameDescending][:])
index.sorts[sizeAscending] = work
index.sorts[sizeAscending] = slices.Clone(work)
slices.SortFunc(index.sorts[sizeAscending][:], func(a, b *metadata) int {
return cmp.Compare(a.Size, b.Size)
})
index.sorts[sizeDescending] = index.sorts[sizeAscending]
index.sorts[sizeDescending] = slices.Clone(index.sorts[sizeAscending])
slices.Reverse(index.sorts[sizeDescending][:])
return
@@ -1,4 +1,4 @@
package main
package pkgserver
import (
"bytes"
@@ -15,7 +15,7 @@ func newIndex(t *testing.T) *packageIndex {
t.Helper()
var index packageIndex
if err := index.populate(nil, nil); err != nil {
if err := index.populate(nil); err != nil {
t.Fatalf("populate: error = %v", err)
}
return &index
@@ -1,4 +1,4 @@
package main
package pkgserver
import (
"cmp"
@@ -74,7 +74,7 @@ func (s *searchCache) clean() {
}
func indexsum(in [][]int) int {
sum := 0
for i := 0; i < len(in); i++ {
for i := range in {
sum += in[i][1] - in[i][0]
}
return sum
@@ -3,12 +3,13 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="static/style.css">
<title>Hakurei PkgServer</title>
<script src="static/index.js"></script>
<link rel="stylesheet" href="style.css">
<link rel="icon" href="https://hakurei.app/favicon.ico"/>
<title>Rosa OS Packages</title>
<script src="index.js"></script>
</head>
<body>
<h1>Hakurei PkgServer</h1>
<h1>Rosa OS Packages</h1>
<div class="top-controls" id="top-controls-regular">
<p>Showing entries <span id="entry-counter"></span>.</p>
<span id="search-bar">
@@ -54,4 +55,4 @@
</footer>
<script>main();</script>
</body>
</html>
</html>
+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
}
}()
@@ -1,7 +1,7 @@
//go:build !frontend
package main
package ui
import "testing/fstest"
var content fstest.MapFS
var static fstest.MapFS
+491 -170
View File
@@ -14,12 +14,14 @@ package main
import (
"context"
"crypto/ed25519"
"crypto/sha512"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
@@ -30,19 +32,34 @@ import (
"syscall"
"time"
"unique"
"unsafe"
"hakurei.app/check"
"hakurei.app/command"
"hakurei.app/container"
"hakurei.app/container/seccomp"
"hakurei.app/container/std"
"hakurei.app/ext"
"hakurei.app/fhs"
"hakurei.app/internal/pkg"
"hakurei.app/internal/rosa"
"hakurei.app/message"
"hakurei.app/cmd/mbf/internal/pkgserver"
"hakurei.app/cmd/mbf/internal/pkgserver/ui"
)
// writeFileExcl is like [os.WriteFile], but sets [os.O_EXCL] instead.
func writeFileExcl(name string, data []byte, perm os.FileMode) error {
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
if err != nil {
return err
}
_, err = f.Write(data)
if err1 := f.Close(); err1 != nil && err == nil {
err = err1
}
return err
}
func main() {
container.TryArgv0(nil)
@@ -54,6 +71,20 @@ func main() {
log.Fatal("this program must not run as root")
}
defer func() {
r := recover()
if r == nil {
return
}
switch r.(type) {
case rosa.LoadError, pkg.IRStringError:
log.Fatal(r)
default:
panic(r)
}
}()
ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
defer stop()
@@ -63,18 +94,36 @@ func main() {
var (
flagQuiet bool
flagQEMU bool
flagArch string
flagCheck bool
flagLTO bool
flagPT bool
flagDry bool
flagPath string
flagSourcePath string
flagCrossOverride int
addr net.UnixAddr
)
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) error {
if !rosa.Native().HasStageEarly() {
return pkg.UnsupportedArchError(runtime.GOARCH)
}
if flagPT {
log.Println("parsed in", rosa.ParseTime())
}
msg.SwapVerbose(!flagQuiet)
cm.ctx, cm.msg = ctx, msg
cm.base = os.ExpandEnv(cm.base)
if cm.base == "" {
cm.base = "cache"
}
cm.mirror = os.ExpandEnv(cm.mirror)
azaleaPath := os.ExpandEnv(flagPath)
addr.Net = "unix"
addr.Name = os.ExpandEnv(addr.Name)
@@ -89,13 +138,53 @@ func main() {
if !flagLTO {
flags |= rosa.OptLLVMNoLTO
}
rosa.DropCaches(flags)
rosa.Native().DropCaches("", flags)
cross := flagArch != "" && flagArch != runtime.GOARCH
if flagQEMU || cross {
_, cm.qemu = rosa.Native().Std().MustLoad(rosa.H("qemu"))
}
if cross {
if flagCrossOverride != -1 {
flags = flagCrossOverride
}
rosa.Native().DropCaches(flagArch, flags)
if !rosa.Native().HasStageEarly() {
return pkg.UnsupportedArchError(flagArch)
}
}
if flagSourcePath != "" {
if err := rosa.Native().SetSource(os.DirFS(flagSourcePath)); err != nil {
return err
}
}
if azaleaPath != "" {
var root *os.Root
if a, err := check.NewAbs(azaleaPath); err != nil {
return err
} else if root, err = os.OpenRoot(a.String()); err != nil {
return err
} else if err = rosa.Native().RegisterFS(root.FS()); err != nil {
return err
}
}
return nil
}).Flag(
&flagQuiet,
"q", command.BoolFlag(false),
"Do not print cure messages",
).Flag(
&flagQEMU,
"register", command.BoolFlag(false),
"Enable additional target architectures",
).Flag(
&flagArch,
"arch", command.StringFlag(runtime.GOARCH),
"Target architecture",
).Flag(
&flagLTO,
"lto", command.BoolFlag(false),
@@ -104,6 +193,14 @@ func main() {
&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),
@@ -116,6 +213,10 @@ func main() {
&cm.base,
"d", command.StringFlag("$MBF_CACHE_DIR"),
"Directory to store cured artifacts",
).Flag(
&cm.mirror,
"r", command.StringFlag("$MBF_REMOTE"),
"URL of mirror service",
).Flag(
&cm.idle,
"sched-idle", command.BoolFlag(false),
@@ -131,12 +232,38 @@ func main() {
&addr.Name,
"socket", command.StringFlag("$MBF_DAEMON_SOCKET"),
"Pathname of socket to bind to",
).Flag(
&flagPT,
"parse-time", command.BoolFlag(false),
"Print duration of the initial azalea parse",
).Flag(
&flagDry,
"dry", command.BoolFlag(false),
"Do not destroy cache entries",
).Flag(
&flagSourcePath,
"source", command.StringFlag(""),
"Override hakurei source tree",
).Flag(
&flagPath,
"p", command.StringFlag("$AZALEA_PATH"),
"Load additional azalea files",
)
c.NewCommand(
"checksum", "Compute checksum of data read from standard input",
func([]string) error {
go func() { <-ctx.Done(); os.Exit(1) }()
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
@@ -170,6 +297,7 @@ func main() {
{
var (
flagBind string
flagStatus bool
flagReport string
)
@@ -177,8 +305,52 @@ func main() {
"info",
"Display out-of-band metadata of an artifact",
func(args []string) (err error) {
return commandInfo(&cm, args, os.Stdout, flagStatus, flagReport)
const shutdownTimeout = 15 * time.Second
var r *rosa.Report
if flagReport != "" {
if r, err = rosa.OpenReport(flagReport); err != nil {
return err
}
defer func() {
if closeErr := r.Close(); err == nil {
err = closeErr
}
}()
defer r.HandleAccess(&err)()
}
if flagBind == "" {
return commandInfo(&cm, args, os.Stdout, flagStatus, r)
}
var mux http.ServeMux
ui.Register(&mux)
if err = pkgserver.Register(ctx, &mux, r); err != nil {
return
}
server := http.Server{Addr: flagBind, Handler: &mux}
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
},
).Flag(
&flagBind,
"bind", command.StringFlag(""),
"TCP address for the server to listen on",
).Flag(
&flagStatus,
"status", command.BoolFlag(false),
@@ -228,7 +400,10 @@ func main() {
)
{
var flagJobs int
var (
flagJobs int
flagNoBlock bool
)
c.NewCommand("updates", command.UsageInternal, func([]string) error {
var (
errsMu sync.Mutex
@@ -237,16 +412,21 @@ func main() {
n atomic.Uint64
)
w := make(chan rosa.PArtifact)
w := make(chan rosa.ArtifactH)
var wg sync.WaitGroup
for range max(flagJobs, 1) {
wg.Go(func() {
for p := range w {
meta := rosa.GetMetadata(p)
meta, _ := rosa.Native().Std().MustLoad(p)
if meta.ID == 0 {
continue
}
if !flagNoBlock && meta.Blocked != "" {
msg.Verbosef("%s is blocked: %s", meta.Name, meta.Blocked)
continue
}
v, err := meta.GetVersions(ctx)
if err != nil {
errsMu.Lock()
@@ -255,12 +435,9 @@ func main() {
continue
}
if current, latest :=
rosa.Std.Version(p),
meta.GetLatest(v); current != latest {
if latest := meta.GetLatest(v); meta.Version != latest {
n.Add(1)
log.Printf("%s %s < %s", meta.Name, current, latest)
log.Printf("%s %s < %s", meta.Name, meta.Version, latest)
continue
}
@@ -270,9 +447,9 @@ func main() {
}
done:
for i := range rosa.PresetEnd {
for _, p := range rosa.Native().CollectAll() {
select {
case w <- rosa.PArtifact(i):
case w <- p:
break
case <-ctx.Done():
break done
@@ -290,9 +467,23 @@ func main() {
&flagJobs,
"j", command.IntFlag(32),
"Maximum number of simultaneous connections",
).Flag(
&flagNoBlock,
"ignore-block", command.BoolFlag(false),
"Inhibit update blocking",
)
}
c.NewCommand("blocked", command.UsageInternal, func([]string) error {
for _, p := range rosa.Native().CollectAll() {
meta, _ := rosa.Native().Std().Load(p)
if meta.Blocked != "" {
fmt.Printf("%s: %s\n", meta.Name, meta.Blocked)
}
}
return nil
})
c.NewCommand(
"daemon",
"Service artifact IR with Rosa OS extensions",
@@ -306,20 +497,82 @@ func main() {
},
)
c.NewCommand(
"keygen",
"Create keypair for local cache",
func([]string) error {
pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
return err
}
return errors.Join(writeFileExcl(filepath.Join(
cm.base,
"ed25519.pub",
), pub, 0444), writeFileExcl(filepath.Join(
cm.base,
"ed25519",
), priv, 0400))
},
)
c.NewCommand(
"serve",
"Export local cache as mirror",
func(args []string) error {
const shutdownTimeout = 15 * time.Second
if len(args) != 1 {
return errors.New("serve requires 1 argument")
}
var key ed25519.PrivateKey
if p, err := os.ReadFile(filepath.Join(cm.base, "ed25519")); err != nil {
return err
} else if len(p) != ed25519.PrivateKeySize {
return errors.New("invalid private key")
} else {
key = p
}
var h http.Handler
if base, err := os.OpenRoot(cm.base); err != nil {
return err
} else {
h = rosa.NewMirror(msg, base.FS(), key)
}
server := http.Server{Addr: args[0], Handler: h}
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", args[0])
err := server.ListenAndServe()
if errors.Is(err, http.ErrServerClosed) {
err = nil
}
return err
},
)
{
var (
flagGentoo string
flagChecksum string
flagStage0 bool
)
c.NewCommand(
"stage3",
"Check for toolchain 3-stage non-determinism",
func(args []string) (err error) {
t := rosa.Std
s := rosa.Std
if flagGentoo != "" {
t -= 3 // magic number to discourage misuse
s -= 3 // magic number to discourage misuse
var checksum pkg.Checksum
if len(flagChecksum) != 0 {
@@ -327,7 +580,7 @@ func main() {
return
}
}
rosa.SetGentooStage3(flagGentoo, checksum)
rosa.Native().SetGentooStage3(flagGentoo, checksum)
}
var (
@@ -335,10 +588,10 @@ func main() {
checksum [2]unique.Handle[pkg.Checksum]
)
_llvm := rosa.H("llvm")
if err = cm.Do(func(cache *pkg.Cache) (err error) {
pathname, _, err = cache.Cure(
(t - 2).Load(rosa.LLVM),
)
_, llvm := rosa.Native().New(s - 2).Load(_llvm)
pathname, _, err = cache.Cure(llvm)
return
}); err != nil {
return
@@ -346,18 +599,16 @@ func main() {
log.Println("stage1:", pathname)
if err = cm.Do(func(cache *pkg.Cache) (err error) {
pathname, checksum[0], err = cache.Cure(
(t - 1).Load(rosa.LLVM),
)
_, llvm := rosa.Native().New(s - 1).Load(_llvm)
pathname, checksum[0], err = cache.Cure(llvm)
return
}); err != nil {
return
}
log.Println("stage2:", pathname)
if err = cm.Do(func(cache *pkg.Cache) (err error) {
pathname, checksum[1], err = cache.Cure(
t.Load(rosa.LLVM),
)
_, llvm := rosa.Native().New(s).Load(_llvm)
pathname, checksum[1], err = cache.Cure(llvm)
return
}); err != nil {
return
@@ -375,19 +626,6 @@ func main() {
"("+pkg.Encode(checksum[0].Value())+")",
)
}
if flagStage0 {
if err = cm.Do(func(cache *pkg.Cache) (err error) {
pathname, _, err = cache.Cure(
t.Load(rosa.Stage0),
)
return
}); err != nil {
return
}
log.Println(pathname)
}
return
},
).Flag(
@@ -398,10 +636,6 @@ func main() {
&flagChecksum,
"checksum", command.StringFlag(""),
"Checksum of Gentoo stage3 tarball",
).Flag(
&flagStage0,
"stage0", command.BoolFlag(false),
"Create bootstrap stage0 tarball",
)
}
@@ -412,6 +646,11 @@ func main() {
flagExport string
flagRemote bool
flagNoReply bool
flagFaults bool
flagPop bool
flagBoot bool
flagStd bool
)
c.NewCommand(
"cure",
@@ -420,8 +659,16 @@ func main() {
if len(args) != 1 {
return errors.New("cure requires 1 argument")
}
p, ok := rosa.ResolveName(args[0])
if !ok {
t := rosa.Std
if flagBoot {
t -= 2
} else if flagStd {
t -= 1
}
_, a := rosa.Native().New(t).Load(rosa.ArtifactH(unique.Make(args[0])))
if a == nil {
return fmt.Errorf("unknown artifact %q", args[0])
}
@@ -429,7 +676,7 @@ func main() {
default:
var pathname *check.Absolute
err := cm.Do(func(cache *pkg.Cache) (err error) {
pathname, _, err = cache.Cure(rosa.Std.Load(p))
pathname, _, err = cache.Cure(a)
return
})
if err != nil {
@@ -447,7 +694,7 @@ func main() {
0400,
); err != nil {
return err
} else if _, err = pkg.Flatten(
} else if err = pkg.Write(
os.DirFS(pathname.String()),
".",
f,
@@ -471,7 +718,7 @@ func main() {
return err
}
if err = pkg.NewIR().EncodeAll(f, rosa.Std.Load(p)); err != nil {
if err = pkg.NewIR().EncodeAll(f, a); err != nil {
_ = f.Close()
return err
}
@@ -482,8 +729,8 @@ func main() {
return cm.Do(func(cache *pkg.Cache) error {
return cache.EnterExec(
ctx,
rosa.Std.Load(p),
true, os.Stdin, os.Stdout, os.Stderr,
a,
"", true, os.Stdin, os.Stdout, os.Stderr,
rosa.AbsSystem.Append("bin", "mksh"),
"sh",
)
@@ -494,7 +741,6 @@ func main() {
if flagNoReply {
flags |= remoteNoReply
}
a := rosa.Std.Load(p)
pathname, err := cureRemote(ctx, &addr, a, flags)
if !flagNoReply && err == nil {
log.Println(pathname)
@@ -510,6 +756,49 @@ func main() {
}
return err
case flagFaults:
var faults []pkg.Fault
if err := cm.Do(func(cache *pkg.Cache) (err error) {
faults, err = cache.ReadFaults(a)
return
}); err != nil {
return err
}
for _, fault := range faults {
log.Printf("%s: %s ago", fault.String(), time.Since(fault.Time()))
}
return nil
case flagPop:
var faults []pkg.Fault
if err := cm.Do(func(cache *pkg.Cache) (err error) {
faults, err = cache.ReadFaults(a)
return
}); err != nil {
return err
}
if len(faults) == 0 {
return errors.New("no fault entries found")
}
fault := faults[len(faults)-1]
r, err := fault.Open()
if err != nil {
return err
}
if _, err = io.Copy(os.Stdout, r); err != nil {
_ = r.Close()
return err
}
fmt.Println()
if err = r.Close(); err != nil {
return err
}
log.Printf("faulting cure terminated %s ago", time.Since(fault.Time()))
return fault.Destroy()
}
},
).Flag(
@@ -532,6 +821,98 @@ func main() {
&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",
).Flag(
&flagFaults,
"faults", command.BoolFlag(false),
"Display fault entries of the specified artifact",
).Flag(
&flagPop,
"pop", command.BoolFlag(false),
"Display and destroy the most recent fault entry",
)
}
cleanC := c.New("clean", "Remove unused entries from the cache")
cleanC.NewCommand(
"fault",
"Remove all fault entries from the cache",
func([]string) error {
return cm.Do(func(*pkg.Cache) error {
pathname := filepath.Join(cm.base, "fault")
dents, err := os.ReadDir(pathname)
if err != nil {
return err
}
for _, dent := range dents {
msg.Verbosef("destroying entry %s", dent.Name())
if err = os.Remove(filepath.Join(pathname, dent.Name())); err != nil {
return err
}
}
log.Printf("destroyed %d fault entries", len(dents))
return nil
})
},
)
cleanC.NewCommand(
"checksum",
"Remove unreachable checksum entries",
func([]string) error {
return cm.Do(func(cache *pkg.Cache) error {
_, checksums, err := cache.Clean(flagDry, false)
log.Printf("destroyed %d entries", len(checksums))
return err
})
},
)
{
var flagDeep bool
cleanC.NewCommand(
"all",
"Remove identifiers not reachable by loaded packages",
func([]string) error {
return cm.Do(func(cache *pkg.Cache) error {
t := rosa.Native().Clone().Std()
handles := t.CollectAll()
flags := t.Flags()
a := t.Append(nil, handles...)
for arch := range rosa.Arches(nil) {
if arch == runtime.GOARCH {
continue
}
t.DropCaches(arch, rosa.OptLLVMNoLTO|rosa.OptSkipCheck)
a = t.Append(a, handles...)
t.DropCaches(arch, flags)
a = t.Append(a, handles...)
}
ids, checksums, err := cache.Clean(
flagDry,
!flagDeep,
a...,
)
log.Printf(
"destroyed %d identifier and %d checksum entries",
len(ids), len(checksums),
)
return err
})
},
).Flag(
&flagDeep,
"deep", command.BoolFlag(false),
"Include transitive inputs",
)
}
@@ -552,131 +933,72 @@ func main() {
"shell",
"Interactive shell in the specified Rosa OS environment",
func(args []string) error {
presets := make([]rosa.PArtifact, len(args)+3)
for i, arg := range args {
p, ok := rosa.ResolveName(arg)
if !ok {
return fmt.Errorf("unknown artifact %q", arg)
resolvconf := "nameserver 1.1.1.1\nnameserver 1.0.0.1\n"
if p, err := os.ReadFile(fhs.AbsEtc.Append(
"resolv.conf",
).String()); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return err
}
presets[i] = p
} else {
resolvconf = unsafe.String(unsafe.SliceData(p), len(p))
}
base := rosa.LLVM
if !flagWithToolchain {
base = rosa.Musl
handles := make([]rosa.ArtifactH, len(args), len(args)+3)
for i, arg := range args {
handles[i] = rosa.ArtifactH(unique.Make(arg))
if meta, _ := rosa.Native().Std().Load(handles[i]); meta == nil {
return fmt.Errorf("unknown artifact %q", arg)
}
}
presets = append(presets,
base := rosa.H("llvm")
if !flagWithToolchain {
base = rosa.H("musl")
}
handles = append(handles,
base,
rosa.Mksh,
rosa.Toybox,
rosa.H("mksh"),
rosa.H("toybox"),
)
root := make(pkg.Collect, 0, 6+len(args))
root = rosa.Std.AppendPresets(root, presets...)
root = append(root, rosa.NewEtc(false))
root = rosa.Native().Std().Append(root, handles...)
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 {
pathname *check.Absolute
checksum unique.Handle[pkg.Checksum]
}
cured := make(map[pkg.Artifact]cureRes)
for _, a := range root {
if err := cm.Do(func(cache *pkg.Cache) error {
pathname, checksum, err := cache.Cure(a)
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
}
}
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
*check.Absolute,
unique.Handle[pkg.Checksum],
) {
res := cured[a]
return res.pathname, res.checksum
}, func(i int, d pkg.Artifact) {
r := pkg.Encode(cm.c.Ident(d).Value())
if s, ok := d.(fmt.Stringer); ok {
if name := s.String(); name != "" {
r += "-" + name
}
}
msg.Verbosef("promoted layer %d as %s", i, r)
return cm.Do(func(cache *pkg.Cache) error {
return cache.EnterExec(
ctx,
pkg.NewExec(
"",
rosa.Native().Arch(),
new(pkg.Checksum),
1,
flagNet,
false,
fhs.AbsRoot,
[]string{
"SHELL=/system/bin/mksh",
"PATH=/system/bin",
"HOME=/",
},
fhs.AbsProc.Append("nonexistent"),
nil,
pkg.Path(fhs.AbsRoot, true, root...),
pkg.Path(
fhs.AbsEtc.Append("resolv.conf"), false,
pkg.NewFile(
"resolv.conf",
unsafe.Slice(unsafe.StringData(resolvconf), len(resolvconf)),
),
),
),
"localhost",
flagSession, os.Stdin, os.Stdout, os.Stderr,
rosa.AbsSystem.Append("bin", "mksh"),
"sh",
)
})
z := container.New(ctx, msg)
z.WaitDelay = 3 * time.Second
z.SeccompPresets = pkg.SeccompPresets
z.SeccompFlags |= seccomp.AllowMultiarch
z.ParentPerm = 0700
z.HostNet = flagNet
z.RetainSession = flagSession
z.Hostname = "localhost"
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
if s, ok := os.LookupEnv("TERM"); ok {
z.Env = append(z.Env, "TERM="+s)
}
var tempdir *check.Absolute
if s, err := filepath.Abs(os.TempDir()); err != nil {
return err
} else if tempdir, err = check.NewAbs(s); err != nil {
return err
}
z.Dir = fhs.AbsRoot
z.Env = []string{
"SHELL=/system/bin/mksh",
"PATH=/system/bin",
"HOME=/",
}
z.Path = rosa.AbsSystem.Append("bin", "mksh")
z.Args = []string{"mksh"}
z.
OverlayEphemeral(fhs.AbsRoot, layers...).
Place(
fhs.AbsEtc.Append("hosts"),
[]byte("127.0.0.1 localhost\n"),
).
Place(
fhs.AbsEtc.Append("passwd"),
[]byte("media_rw:x:1023:1023::/:/system/bin/sh\n"+
"nobody:x:65534:65534::/proc/nonexistent:/system/bin/false\n"),
).
Place(
fhs.AbsEtc.Append("group"),
[]byte("media_rw:x:1023:\nnobody:x:65534:\n"),
).
Bind(tempdir, fhs.AbsTmp, std.BindWritable).
Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
if err := z.Start(); err != nil {
return err
}
if err := z.Serve(); err != nil {
return err
}
return z.Wait()
},
).Flag(
&flagNet,
@@ -691,7 +1013,6 @@ func main() {
"with-toolchain", command.BoolFlag(false),
"Include the stage2 LLVM toolchain",
)
}
c.Command(
+47
View File
@@ -0,0 +1,47 @@
package main
import (
"net"
"os"
"testing"
"hakurei.app/internal/rosa"
)
func TestMain(m *testing.M) {
rosa.Native().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 _, handle := range rosa.Native().Collect() {
_, a := rosa.Native().Std().MustLoad(handle)
t.Run(handle.String(), func(t *testing.T) {
_, err := cureRemote(t.Context(), &addr, a, 0)
if err != nil {
t.Error(err)
}
})
}
}
-114
View File
@@ -1,114 +0,0 @@
package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"hakurei.app/check"
"hakurei.app/command"
"hakurei.app/internal/pkg"
"hakurei.app/internal/rosa"
"hakurei.app/message"
)
const shutdownTimeout = 15 * time.Second
func main() {
log.SetFlags(0)
log.SetPrefix("pkgserver: ")
var (
flagBaseDir string
flagAddr string
)
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
defer stop()
msg := message.New(log.Default())
c := command.New(os.Stderr, log.Printf, "pkgserver", func(args []string) error {
var (
cache *pkg.Cache
report *rosa.Report
)
switch len(args) {
case 0:
break
case 1:
baseDir, err := check.NewAbs(flagBaseDir)
if err != nil {
return err
}
cache, err = pkg.Open(ctx, msg, 0, 0, 0, baseDir)
if err != nil {
return err
}
defer cache.Close()
report, err = rosa.OpenReport(args[0])
if err != nil {
return err
}
default:
return errors.New("pkgserver requires 1 argument")
}
var index packageIndex
index.search = make(searchCache)
if err := index.populate(cache, 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()
}
}
}()
var mux http.ServeMux
uiRoutes(&mux)
index.registerAPI(&mux)
server := http.Server{
Addr: flagAddr,
Handler: &mux,
}
go func() {
<-ctx.Done()
c, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancel()
if err := server.Shutdown(c); err != nil {
log.Fatal(err)
}
}()
return server.ListenAndServe()
}).Flag(
&flagBaseDir,
"b", command.StringFlag(""),
"base directory for cache",
).Flag(
&flagAddr,
"addr", command.StringFlag(":8067"),
"TCP network address to listen on",
)
c.MustParse(os.Args[1:], func(err error) {
if errors.Is(err, http.ErrServerClosed) {
os.Exit(0)
}
log.Fatal(err)
})
}
-33
View File
@@ -1,33 +0,0 @@
package main
import "net/http"
func serveWebUI(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-XSS-Protection", "1")
w.Header().Set("X-Frame-Options", "DENY")
http.ServeFileFS(w, r, content, "ui/index.html")
}
func serveStaticContent(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/static/style.css":
http.ServeFileFS(w, r, content, "ui/static/style.css")
case "/favicon.ico":
http.ServeFileFS(w, r, content, "ui/static/favicon.ico")
case "/static/index.js":
http.ServeFileFS(w, r, content, "ui/static/index.js")
default:
http.NotFound(w, r)
}
}
func uiRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /{$}", serveWebUI)
mux.HandleFunc("GET /favicon.ico", serveStaticContent)
mux.HandleFunc("GET /static/", serveStaticContent)
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

-9
View File
@@ -1,9 +0,0 @@
//go:build frontend
package main
import "embed"
//go:generate tsc -p ui
//go:embed ui/*
var content embed.FS
+2 -2
View File
@@ -508,8 +508,8 @@ func _main(s ...string) (exitCode int) {
if !z.AllowOrphan {
if err := z.Wait(); err != nil {
var exitError *exec.ExitError
if !errors.As(err, &exitError) || exitError == nil {
exitError, ok := errors.AsType[*exec.ExitError](err)
if !ok || exitError == nil {
log.Println(err)
return 5
}
+4 -1
View File
@@ -20,11 +20,14 @@
};
virtualisation = {
# Hopefully reduces spurious test failures:
memorySize = if pkgs.stdenv.hostPlatform.is32bit then 2046 else 8192;
diskSize = 6 * 1024;
qemu.options = [
# Increase test performance:
"-smp 8"
"-smp 16"
];
};
+1 -1
View File
@@ -28,7 +28,7 @@ testers.nixosTest {
# For go tests:
(pkgs.writeShellScriptBin "sharefs-workload-hakurei-tests" ''
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 ./...'
'')
];
+2 -2
View File
@@ -91,8 +91,8 @@ func (n *node) MustParse(arguments []string, handleError func(error)) {
case ErrEmptyTree:
os.Exit(1)
default:
var flagError FlagError
if !errors.As(err, &flagError) { // returned by HandlerFunc
flagError, ok := errors.AsType[FlagError](err)
if !ok { // returned by HandlerFunc
handleError(err)
os.Exit(1)
}
+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)
}
})
}
}
+1
View File
@@ -18,6 +18,7 @@ const (
CAP_SETPCAP = 0x8
CAP_NET_ADMIN = 0xc
CAP_DAC_OVERRIDE = 0x1
CAP_SETFCAP = 0x1f
)
type (
+29 -11
View File
@@ -67,6 +67,9 @@ type (
// Copied to the underlying [exec.Cmd].
WaitDelay time.Duration
// Suppress verbose output of init.
Quiet bool
cmd *exec.Cmd
ctx context.Context
msg message.Msg
@@ -88,12 +91,20 @@ type (
// Time to wait for processes lingering after the initial process terminates.
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.
Uid int
// Mapped Gid in user namespace.
Gid int
// Hostname value in UTS namespace.
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.
*Ops
@@ -143,11 +154,8 @@ func (e *StartError) Error() string {
return e.Step
}
{
var syscallError *os.SyscallError
if errors.As(e.Err, &syscallError) && syscallError != nil {
return e.Step + " " + syscallError.Error()
}
if se, ok := errors.AsType[*os.SyscallError](e.Err); ok && se != nil {
return e.Step + " " + se.Error()
}
return e.Step + ": " + e.Err.Error()
@@ -213,6 +221,9 @@ func (p *Container) Start() error {
if p.cmd.Process != nil {
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 {
return err
@@ -283,6 +294,18 @@ func (p *Container) Start() error {
if !p.HostNet {
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
if r, w, err := os.Pipe(); err != nil {
@@ -342,8 +365,6 @@ func (p *Container) Start() error {
Err: ENOSYS,
Origin: true,
}
} else {
p.msg.Verbosef("landlock abi version %d", abi)
}
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
@@ -353,7 +374,6 @@ func (p *Container) Start() error {
Err: err,
}
} else {
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
if err = landlock.RestrictSelf(rulesetFd, 0); err != nil {
_ = Close(rulesetFd)
return &StartError{
@@ -410,7 +430,6 @@ func (p *Container) Start() error {
}
}
p.msg.Verbose("starting container init")
if err := p.cmd.Start(); err != nil {
return &StartError{
Step: "start container init",
@@ -481,7 +500,6 @@ func (p *Container) Serve() (err error) {
}
case <-done:
p.msg.Verbose("setup payload took", time.Since(t))
return
}
}(p.setup[1])
@@ -491,7 +509,7 @@ func (p *Container) Serve() (err error) {
Getuid(),
Getgid(),
len(p.ExtraFiles),
p.msg.IsVerbose(),
p.msg.IsVerbose() && !p.Quiet,
})
}
+203 -81
View File
@@ -16,6 +16,8 @@ import (
"strings"
"syscall"
"testing"
"time"
"unsafe"
"hakurei.app/check"
"hakurei.app/command"
@@ -233,6 +235,9 @@ func earlyMnt(mnt ...*vfs.MountInfoEntry) func(*testing.T, context.Context) []*v
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 {
name string
filter bool
@@ -332,13 +337,15 @@ var containerTestCases = []struct {
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
return []*vfs.MountInfoEntry{
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
"rw,lowerdir="+
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
"rw"+
",lowerdir+="+
toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+
",lowerdir+="+
toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
",upperdir="+
container.InternalToHostOvlEscape(ctx.Value(testVal("upper")).(*check.Absolute).String())+
toHost(ctx.Value(testVal("upper")).(*check.Absolute).String())+
",workdir="+
container.InternalToHostOvlEscape(ctx.Value(testVal("work")).(*check.Absolute).String())+
toHost(ctx.Value(testVal("work")).(*check.Absolute).String())+
",redirect_dir=nofollow,uuid=on,userxattr"),
}
},
@@ -388,9 +395,11 @@ var containerTestCases = []struct {
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
return []*vfs.MountInfoEntry{
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
"ro,lowerdir="+
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
"ro"+
",lowerdir+="+
toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+
",lowerdir+="+
toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
",redirect_dir=nofollow,userxattr"),
}
},
@@ -400,39 +409,11 @@ var containerTestCases = []struct {
func TestContainer(t *testing.T) {
t.Parallel()
t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) {
wantErr := context.Canceled
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)
}
}))
var suffix string
runTests:
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()
wantOps, wantOpsCtx := tc.ops(t)
@@ -456,6 +437,8 @@ func TestContainer(t *testing.T) {
c.SeccompDisable = !tc.filter
c.RetainSession = tc.session
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) {
@@ -465,6 +448,9 @@ func TestContainer(t *testing.T) {
t.Log("Landlock LSM is unavailable, enabling HostAbstract")
}
}
if c.InitAsRoot {
c.SeccompPresets &= ^std.PresetDenyNS
}
c.
Readonly(check.MustAbs(pathReadonly), 0755).
@@ -533,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 {
@@ -555,49 +546,118 @@ func hostnameFromTestCase(name string) string {
}
func testContainerCancel(
t *testing.T,
containerExtra func(c *container.Container),
waitCheck func(t *testing.T, c *container.Container),
) func(t *testing.T) {
return func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(t.Context())
waitCheck func(ps *os.ProcessState, waitErr error),
) {
ctx, cancel := context.WithCancel(t.Context())
c := helperNewContainer(ctx, "block")
c.Stdout, c.Stderr = os.Stdout, os.Stderr
if containerExtra != nil {
containerExtra(c)
}
ready := make(chan struct{})
if r, w, err := os.Pipe(); err != nil {
t.Fatalf("cannot pipe: %v", err)
} else {
c.ExtraFiles = append(c.ExtraFiles, w)
go func() {
defer close(ready)
if _, err = r.Read(make([]byte, 1)); err != nil {
panic(err.Error())
}
}()
}
if err := c.Start(); err != nil {
if m, ok := container.InternalMessageFromError(err); ok {
t.Fatal(m)
} else {
t.Fatalf("cannot start container: %v", err)
}
} else if err = c.Serve(); err != nil {
if m, ok := container.InternalMessageFromError(err); ok {
t.Error(m)
} else {
t.Errorf("cannot serve setup params: %v", err)
}
}
<-ready
cancel()
waitCheck(t, c)
c := helperNewContainer(ctx, "block")
c.Stdout, c.Stderr = os.Stdout, os.Stderr
if containerExtra != nil {
containerExtra(c)
}
ready := make(chan struct{})
var waitErr error
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("cannot pipe: %v", err)
}
c.ExtraFiles = append(c.ExtraFiles, w)
go func() {
defer close(ready)
if _, _err := r.Read(make([]byte, 1)); _err != nil {
panic(_err)
}
}()
if err = c.Start(); err != nil {
if m, ok := container.InternalMessageFromError(err); ok {
t.Fatal(m)
} else {
t.Fatalf("cannot start container: %v", err)
}
}
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 {
t.Error(m)
} else {
t.Errorf("cannot serve setup params: %v", err)
}
}
<-ready
cancel()
<-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) {
@@ -633,6 +693,8 @@ func init() {
})
c.Command("container", command.UsageInternal, func(args []string) error {
asRoot := os.Getenv("HAKUREI_TEST_SUFFIX") == " as root"
if len(args) != 1 {
return syscall.EINVAL
}
@@ -650,6 +712,66 @@ func init() {
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)
if host, err := os.Hostname(); err != nil {
return fmt.Errorf("cannot get hostname: %v", err)
@@ -767,7 +889,7 @@ func TestMain(m *testing.M) {
}
c.MustParse(os.Args[1:], func(err error) {
if err != nil {
log.Fatal(err.Error())
log.Fatal(err)
}
})
return
+5
View File
@@ -65,6 +65,8 @@ type syscallDispatcher interface {
remount(msg message.Msg, target string, flags uintptr) error
// mountTmpfs provides mountTmpfs.
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(name string, perm, pperm os.FileMode) error
// mustLoopback provides mustLoopback.
@@ -169,6 +171,9 @@ 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 {
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 {
return ensureFile(name, perm, pperm)
}
+8 -2
View File
@@ -235,8 +235,6 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
})
}
func sliceAddr[S any](s []S) *[]S { return &s }
func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
// check happens in Close, and cleanup is not guaranteed to run, so relying
@@ -468,6 +466,14 @@ func (k *kstub) mountTmpfs(fsname, target string, flags uintptr, size int, perm
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 {
k.Helper()
return k.Expects("ensureFile").Error(
+10 -8
View File
@@ -46,9 +46,8 @@ func messageFromError(err error) (m string, ok bool) {
// While this is usable for pointer errors, such use should be avoided as nil
// check is omitted.
func messagePrefix[T error](prefix string, err error) (string, bool) {
var targetError T
if errors.As(err, &targetError) {
return prefix + targetError.Error(), true
if e, ok := errors.AsType[T](err); ok {
return prefix + e.Error(), true
}
return zeroString, false
}
@@ -58,9 +57,8 @@ func messagePrefixP[V any, T interface {
*V
error
}](prefix string, err error) (string, bool) {
var targetError T
if errors.As(err, &targetError) && targetError != nil {
return prefix + targetError.Error(), true
if e, ok := errors.AsType[T](err); ok && e != nil {
return prefix + e.Error(), true
}
return zeroString, false
}
@@ -109,8 +107,8 @@ func optionalErrorUnwrap(err error) error {
// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
var errno syscall.Errno
if !errors.As(err, &errno) {
errno, ok := errors.AsType[syscall.Errno](err)
if !ok {
return 0, &os.PathError{Op: op, Path: path, Err: err}
}
return errno, nil
@@ -118,6 +116,10 @@ func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
// mount wraps syscall.Mount for error handling.
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)
if err == nil {
return nil
+106 -23
View File
@@ -11,11 +11,13 @@ import (
"path/filepath"
"slices"
"strconv"
"strings"
"sync"
"sync/atomic"
. "syscall"
"time"
"hakurei.app/check"
"hakurei.app/container/seccomp"
"hakurei.app/ext"
"hakurei.app/fhs"
@@ -182,23 +184,33 @@ func initEntrypoint(k syscallDispatcher, msg message.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
if err := k.setDumpable(ext.SUID_DUMP_USER); err != nil {
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
}
if err := k.writeFile(fhs.Proc+"self/uid_map",
append([]byte{}, strconv.Itoa(param.Uid)+" "+strconv.Itoa(param.HostUid)+" 1\n"...),
0); err != nil {
if err := k.writeFile(
fhs.Proc+"self/uid_map",
[]byte(strconv.Itoa(uid)+" "+strconv.Itoa(param.HostUid)+" 1\n"),
0,
); err != nil {
k.fatalf(msg, "%v", err)
}
if err := k.writeFile(fhs.Proc+"self/setgroups",
if err := k.writeFile(
fhs.Proc+"self/setgroups",
[]byte("deny\n"),
0); err != nil && !os.IsNotExist(err) {
0,
); err != nil && !os.IsNotExist(err) {
k.fatalf(msg, "%v", err)
}
if err := k.writeFile(fhs.Proc+"self/gid_map",
append([]byte{}, strconv.Itoa(param.Gid)+" "+strconv.Itoa(param.HostGid)+" 1\n"...),
0); err != nil {
[]byte(strconv.Itoa(gid)+" "+strconv.Itoa(param.HostGid)+" 1\n"),
0,
); err != nil {
k.fatalf(msg, "%v", err)
}
if err := k.setDumpable(ext.SUID_DUMP_DISABLE); err != nil {
@@ -223,6 +235,23 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
state := &setupState{process: make(map[int]WaitStatus), Params: &param.Params, Msg: msg, Context: ctx}
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;
this step is mostly for gathering information that would otherwise be
difficult to obtain via library functions after pivot_root, and
@@ -242,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 {
k.fatalf(msg, "%v", err)
}
@@ -285,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
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))
@@ -323,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 {
k.fatalf(msg, "cannot clear the ambient capability set: %v", err)
}
for i := uintptr(0); i <= lastcap; i++ {
if param.Privileged && i == CAP_SYS_ADMIN {
for i := range lastcap + 1 {
if slices.Contains(keepCaps, i) {
continue
}
if err := k.capBoundingSetDrop(i); err != nil {
@@ -336,20 +408,23 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
}
var keep [2]uint32
if param.Privileged {
keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN)
if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil {
k.fatalf(msg, "cannot raise CAP_SYS_ADMIN: %v", err)
}
for _, c := range keepCaps {
keep[capToIndex(c)] |= capToMask(c)
}
if err := k.capset(
&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 {
k.fatalf(msg, "cannot capset: %v", err)
}
for _, c := range keepCaps {
if err := k.capAmbientRaise(c); err != nil {
k.fatalf(msg, "cannot raise %#x: %v", c, err)
}
}
if !param.SeccompDisable {
rules := param.SeccompRules
if len(rules) == 0 { // non-empty rules slice always overrides presets
@@ -474,6 +549,14 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
cmd.ExtraFiles = extraFiles
cmd.Dir = param.Dir.String()
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 {
k.fatalf(msg, "%v", err)
+81 -81
View File
@@ -95,7 +95,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16,
Gid: 1 << 15,
Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict,
RetainSession: true,
@@ -123,7 +123,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16,
Gid: 1 << 15,
Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict,
RetainSession: true,
@@ -152,7 +152,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16,
Gid: 1 << 15,
Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict,
RetainSession: true,
@@ -182,7 +182,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16,
Gid: 1 << 15,
Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict,
RetainSession: true,
@@ -213,7 +213,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16,
Gid: 1 << 15,
Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict,
RetainSession: true,
@@ -245,7 +245,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16,
Gid: 1 << 15,
Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict,
RetainSession: true,
@@ -279,7 +279,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16,
Gid: 1 << 15,
Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict,
RetainSession: true,
@@ -315,7 +315,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16,
Gid: 1 << 15,
Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict,
RetainSession: true,
@@ -332,6 +332,8 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
/* end early */
@@ -370,6 +372,8 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
/* end early */
@@ -408,6 +412,8 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
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),
@@ -447,6 +453,8 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
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),
@@ -486,9 +494,6 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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("fatalf", stub.ExpectArgs{"cannot mount intermediate root: %v", []any{stub.UniqueError(58)}}, nil, nil),
},
@@ -526,9 +531,6 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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("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),
@@ -567,11 +569,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(54)}}, nil, nil),
},
@@ -609,11 +611,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("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),
@@ -652,11 +654,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, stub.UniqueError(50)),
@@ -696,11 +698,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -741,11 +743,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -787,11 +789,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -842,11 +844,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -897,11 +899,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -953,11 +955,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1010,11 +1012,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1069,11 +1071,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1129,11 +1131,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1190,11 +1192,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1252,11 +1254,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1315,11 +1317,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1379,11 +1381,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1444,11 +1446,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1510,11 +1512,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1584,11 +1586,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1622,7 +1624,6 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, 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(0xa)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
@@ -1654,8 +1655,9 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, 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("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},
@@ -1691,11 +1693,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1729,7 +1731,6 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, 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(0xa)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
@@ -1761,8 +1762,7 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, 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{{0, 0x200000, 0x200000}, {0, 0, 0}}}, nil, stub.UniqueError(17)),
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, stub.UniqueError(17)),
call("fatalf", stub.ExpectArgs{"cannot capset: %v", []any{stub.UniqueError(17)}}, nil, nil),
},
}, nil},
@@ -1799,11 +1799,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -1837,7 +1837,6 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, 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(0xa)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
@@ -1869,8 +1868,9 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, 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("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("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),
@@ -1908,11 +1908,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2032,11 +2032,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(4), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2132,11 +2132,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(4), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2232,11 +2232,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(4), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2323,11 +2323,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(4), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2418,11 +2418,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(4), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2520,11 +2520,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2659,11 +2659,11 @@ func TestInitEntrypoint(t *testing.T) {
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
call("lastcap", stub.ExpectArgs{}, uintptr(40), 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 */
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
/* 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("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
@@ -2697,7 +2697,6 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, 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(0xa)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
@@ -2729,8 +2728,9 @@ func TestInitEntrypoint(t *testing.T) {
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, 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("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("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, nil),
call("verbosef", stub.ExpectArgs{"%d filter rules loaded", []any{73}}, nil, nil),
+40 -12
View File
@@ -4,9 +4,9 @@ import (
"encoding/gob"
"fmt"
"slices"
"strings"
"hakurei.app/check"
"hakurei.app/ext"
"hakurei.app/fhs"
)
@@ -150,7 +150,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
return err
} 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 {
return err
} 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 {
return err
} else {
o.lower[i] = check.EscapeOverlayDataSegment(toHost(v))
o.lower[i] = toHost(v)
}
}
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 {
target := o.Target.String()
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 len(o.Lower) < 2 {
@@ -205,15 +232,16 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
if len(o.Lower) == 0 {
return &OverlayArgumentError{OverlayEmptyLower, zeroString}
}
options = append(options,
OptionOverlayUpperdir+"="+o.upper,
OptionOverlayWorkdir+"="+o.work)
options = append(options, [][2]string{
{OptionOverlayUpperdir, o.upper},
{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 }
+33 -33
View File
@@ -97,13 +97,12 @@ func TestMountOverlayOp(t *testing.T) {
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.work.*"}, "overlay.work.32768", nil),
call("mount", stub.ExpectArgs{"overlay", "/sysroot", "overlay", uintptr(0), "" +
"upperdir=overlay.upper.32768," +
"workdir=overlay.work.32768," +
"lowerdir=" +
`/host/var/lib/planterette/base/debian\:f92c9052:` +
`/host/var/lib/planterette/app/org.chromium.Chromium@debian\:f92c9052,` +
"userxattr"}, nil, nil),
call("mountOverlay", stub.ExpectArgs{"/sysroot", [][2]string{
{"upperdir", "overlay.upper.32768"},
{"workdir", "overlay.work.32768"},
{"lowerdir+", `/host/var/lib/planterette/base/debian:f92c9052`},
{"lowerdir+", `/host/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052`},
}}, nil, nil),
}, nil},
{"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),
}, nil, []stub.Call{
call("mkdirAll", stub.ExpectArgs{"/nix/store", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"overlay", "/nix/store", "overlay", uintptr(0), "" +
"lowerdir=" +
"/host/mnt-root/nix/.ro-store:" +
"/host/mnt-root/nix/.ro-store0," +
"userxattr"}, nil, nil),
call("mountOverlay", stub.ExpectArgs{"/nix/store", [][2]string{
{"lowerdir+", "/host/mnt-root/nix/.ro-store"},
{"lowerdir+", "/host/mnt-root/nix/.ro-store0"},
}}, nil, nil),
}, nil},
{"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),
}, nil, []stub.Call{
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil),
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
"lowerdir=" +
"/host/mnt-root/nix/.ro-store:" +
"/host/mnt-root/nix/.ro-store0," +
"userxattr"}, nil, nil),
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
{"lowerdir+", "/host/mnt-root/nix/.ro-store"},
{"lowerdir+", "/host/mnt-root/nix/.ro-store0"},
}}, nil, nil),
}, nil},
{"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),
}, nil, []stub.Call{
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)},
{"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),
}, nil, []stub.Call{
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, nil),
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, nil),
}, nil},
{"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),
}, nil, []stub.Call{
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:" +
"/host/mnt-root/nix/ro-store0:" +
"/host/mnt-root/nix/ro-store1:" +
"/host/mnt-root/nix/ro-store2:" +
"/host/mnt-root/nix/ro-store3," +
"userxattr"}, nil, nil),
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"},
{"lowerdir+", "/host/mnt-root/nix/ro-store0"},
{"lowerdir+", "/host/mnt-root/nix/ro-store1"},
{"lowerdir+", "/host/mnt-root/nix/ro-store2"},
{"lowerdir+", "/host/mnt-root/nix/ro-store3"},
}}, nil, nil),
}, nil},
})
+6
View File
@@ -40,6 +40,9 @@ const (
// SourceMqueue is used when mounting mqueue.
// Note that any source value is allowed when fstype is [FstypeMqueue].
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.
// Note that any source value is allowed when fstype is [FstypeOverlay].
SourceOverlay = "overlay"
@@ -70,6 +73,9 @@ const (
// FstypeMqueue represents the mqueue pseudo-filesystem.
// This filesystem type is usually mounted on /dev/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.
// This filesystem type can be mounted anywhere in the container filesystem.
FstypeOverlay = "overlay"
-4
View File
@@ -10,7 +10,6 @@ import (
"testing"
"unsafe"
"hakurei.app/check"
"hakurei.app/vfs"
)
@@ -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) {
t.Run("nonexistent", func(t *testing.T) {
t.Run("mkdir", func(t *testing.T) {
+1 -1
View File
@@ -39,7 +39,7 @@ func TestSyscall(t *testing.T) {
t.Errorf("Unmarshal: %v, want %v", got, tc.want)
}
})
if errors.As(tc.err, new(ext.SyscallNameError)) {
if _, ok := errors.AsType[ext.SyscallNameError](tc.err); ok {
return
}
+267
View File
@@ -0,0 +1,267 @@
package ext
import (
"os"
"runtime"
"syscall"
"unsafe"
)
// include/uapi/linux/mount.h
/*
* move_mount() flags.
*/
const (
MOVE_MOUNT_F_SYMLINKS = 1 << iota /* Follow symlinks on from path */
MOVE_MOUNT_F_AUTOMOUNTS /* Follow automounts on from path */
MOVE_MOUNT_F_EMPTY_PATH /* Empty from path permitted */
_
MOVE_MOUNT_T_SYMLINKS /* Follow symlinks on to path */
MOVE_MOUNT_T_AUTOMOUNTS /* Follow automounts on to path */
MOVE_MOUNT_T_EMPTY_PATH /* Empty to path permitted */
_
MOVE_MOUNT_SET_GROUP /* Set sharing group instead */
MOVE_MOUNT_BENEATH /* Mount beneath top mount */
)
/*
* fsopen() flags.
*/
const (
FSOPEN_CLOEXEC = 1 << iota
)
/*
* fspick() flags.
*/
const (
FSPICK_CLOEXEC = 1 << iota
FSPICK_SYMLINK_NOFOLLOW
FSPICK_NO_AUTOMOUNT
FSPICK_EMPTY_PATH
)
/*
* The type of fsconfig() call made.
*/
const (
FSCONFIG_SET_FLAG = iota /* Set parameter, supplying no value */
FSCONFIG_SET_STRING /* Set parameter, supplying a string value */
FSCONFIG_SET_BINARY /* Set parameter, supplying a binary blob value */
FSCONFIG_SET_PATH /* Set parameter, supplying an object by path */
FSCONFIG_SET_PATH_EMPTY /* Set parameter, supplying an object by (empty) path */
FSCONFIG_SET_FD /* Set parameter, supplying an object by fd */
FSCONFIG_CMD_CREATE /* Create new or reuse existing superblock */
FSCONFIG_CMD_RECONFIGURE /* Invoke superblock reconfiguration */
FSCONFIG_CMD_CREATE_EXCL /* Create new superblock, fail if reusing existing superblock */
)
/*
* fsmount() flags.
*/
const (
FSMOUNT_CLOEXEC = 1 << iota
)
/*
* Mount attributes.
*/
const (
MOUNT_ATTR_RDONLY = 0x00000001 /* Mount read-only */
MOUNT_ATTR_NOSUID = 0x00000002 /* Ignore suid and sgid bits */
MOUNT_ATTR_NODEV = 0x00000004 /* Disallow access to device special files */
MOUNT_ATTR_NOEXEC = 0x00000008 /* Disallow program execution */
MOUNT_ATTR__ATIME = 0x00000070 /* Setting on how atime should be updated */
MOUNT_ATTR_RELATIME = 0x00000000 /* - Update atime relative to mtime/ctime. */
MOUNT_ATTR_NOATIME = 0x00000010 /* - Do not update access times. */
MOUNT_ATTR_STRICTATIME = 0x00000020 /* - Always perform atime updates */
MOUNT_ATTR_NODIRATIME = 0x00000080 /* Do not update directory access times */
MOUNT_ATTR_IDMAP = 0x00100000 /* Idmap mount to @userns_fd in struct mount_attr. */
MOUNT_ATTR_NOSYMFOLLOW = 0x00200000 /* Do not follow symlinks */
)
// FS provides low-level wrappers around the suite of file-descriptor-based
// mount facilities in Linux.
type FS struct {
fd uintptr
c runtime.Cleanup
}
// newFS allocates a new [FS] for the specified fd.
func newFS(fd uintptr) *FS {
fs := FS{fd: fd}
fs.c = runtime.AddCleanup(&fs, func(fd uintptr) {
_ = syscall.Close(int(fd))
}, fd)
return &fs
}
// Close closes the underlying filesystem context.
func (fs *FS) Close() error {
if fs == nil {
return syscall.EINVAL
}
err := syscall.Close(int(fs.fd))
fs.c.Stop()
return err
}
// OpenFS creates a new filesystem context.
func OpenFS(fsname string, flags int) (fs *FS, err error) {
var s *byte
s, err = syscall.BytePtrFromString(fsname)
if err != nil {
return
}
fd, _, errno := syscall.Syscall(
SYS_FSOPEN,
uintptr(unsafe.Pointer(s)),
uintptr(flags|FSOPEN_CLOEXEC),
0,
)
if errno != 0 {
err = os.NewSyscallError("fsopen", errno)
} else {
fs = newFS(fd)
}
return
}
// PickFS selects filesystem for reconfiguration.
func PickFS(dirfd int, pathname string, flags int) (fs *FS, err error) {
var s *byte
s, err = syscall.BytePtrFromString(pathname)
if err != nil {
return
}
fd, _, errno := syscall.Syscall(
SYS_FSPICK,
uintptr(dirfd),
uintptr(unsafe.Pointer(s)),
uintptr(flags|FSPICK_CLOEXEC),
)
if errno != 0 {
err = os.NewSyscallError("fspick", errno)
} else {
fs = newFS(fd)
}
return
}
// config configures new or existing filesystem context.
func (fs *FS) config(cmd uint, key *byte, value unsafe.Pointer, aux int) (err error) {
_, _, errno := syscall.Syscall6(
SYS_FSCONFIG,
fs.fd,
uintptr(cmd),
uintptr(unsafe.Pointer(key)),
uintptr(value),
uintptr(aux),
0,
)
if errno != 0 {
err = os.NewSyscallError("fsconfig", errno)
}
return
}
// SetFlag sets the flag parameter named by key. ([FSCONFIG_SET_FLAG])
func (fs *FS) SetFlag(key string) (err error) {
var s *byte
s, err = syscall.BytePtrFromString(key)
if err != nil {
return
}
return fs.config(FSCONFIG_SET_FLAG, s, nil, 0)
}
// SetString sets the string parameter named by key to the value specified by
// value. ([FSCONFIG_SET_STRING])
func (fs *FS) SetString(key, value string) (err error) {
var s0 *byte
s0, err = syscall.BytePtrFromString(key)
if err != nil {
return
}
var s1 *byte
s1, err = syscall.BytePtrFromString(value)
if err != nil {
return
}
return fs.config(FSCONFIG_SET_STRING, s0, unsafe.Pointer(s1), 0)
}
// mount instantiates mount object from filesystem context.
func (fs *FS) mount(flags, attrFlags int) (fsfd int, err error) {
r, _, errno := syscall.Syscall(
SYS_FSMOUNT,
fs.fd,
uintptr(flags|FSMOUNT_CLOEXEC),
uintptr(attrFlags),
)
fsfd = int(r)
if errno != 0 {
err = os.NewSyscallError("fsmount", errno)
}
return
}
// MoveMount moves or attaches mount object to filesystem.
func MoveMount(
fromDirfd int,
fromPathname string,
toDirfd int,
toPathname string,
flags int,
) (err error) {
var s0 *byte
s0, err = syscall.BytePtrFromString(fromPathname)
if err != nil {
return
}
var s1 *byte
s1, err = syscall.BytePtrFromString(toPathname)
if err != nil {
return
}
_, _, errno := syscall.Syscall6(
SYS_MOVE_MOUNT,
uintptr(fromDirfd),
uintptr(unsafe.Pointer(s0)),
uintptr(toDirfd),
uintptr(unsafe.Pointer(s1)),
uintptr(flags),
0,
)
if errno != 0 {
err = os.NewSyscallError("move_mount", errno)
}
return
}
// Mount attaches the underlying filesystem context to the specified pathname.
func (fs *FS) Mount(pathname string, attrFlags int) error {
if err := fs.config(FSCONFIG_CMD_CREATE_EXCL, nil, nil, 0); err != nil {
return err
}
fd, err := fs.mount(0, attrFlags)
if err != nil {
return err
}
err = MoveMount(
fd, "",
-1, pathname,
MOVE_MOUNT_F_EMPTY_PATH,
)
closeErr := syscall.Close(fd)
if err == nil {
err = closeErr
}
return err
}
+2
View File
@@ -42,6 +42,8 @@ var (
AbsDevShm = unsafeAbs(DevShm)
// AbsProc is [Proc] as [check.Absolute].
AbsProc = unsafeAbs(Proc)
// AbsProcSys is [ProcSys] as [check.Absolute].
AbsProcSys = unsafeAbs(ProcSys)
// AbsProcSelfExe is [ProcSelfExe] as [check.Absolute].
AbsProcSelfExe = unsafeAbs(ProcSelfExe)
// AbsSys is [Sys] as [check.Absolute].
Generated
+8 -8
View File
@@ -7,32 +7,32 @@
]
},
"locked": {
"lastModified": 1772985280,
"narHash": "sha256-FdrNykOoY9VStevU4zjSUdvsL9SzJTcXt4omdEDZDLk=",
"lastModified": 1780361225,
"narHash": "sha256-wnV9ttf4fPWNonBIQmvlrSlNpQYgx5HgWWd007mwIFA=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "8f736f007139d7f70752657dff6a401a585d6cbc",
"rev": "e28654b71096e08c019d4861ca26acb646f583d8",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-25.11",
"ref": "release-26.05",
"repo": "home-manager",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1772822230,
"narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=",
"lastModified": 1780453794,
"narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "71caefce12ba78d84fe618cf61644dce01cf3a96",
"rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"ref": "nixos-26.05",
"repo": "nixpkgs",
"type": "github"
}
+4 -5
View File
@@ -2,10 +2,10 @@
description = "hakurei container tool and nixos module";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
home-manager = {
url = "github:nix-community/home-manager/release-25.11";
url = "github:nix-community/home-manager/release-26.05";
inputs.nixpkgs.follows = "nixpkgs";
};
};
@@ -37,7 +37,7 @@
inherit (pkgs)
runCommandLocal
callPackage
nixfmt-rfc-style
nixfmt
deadnix
statix
;
@@ -57,7 +57,7 @@
sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt ]; } ''
cd ${./.}
echo "running nixfmt..."
@@ -139,7 +139,6 @@
GOCACHE="$(mktemp -d)" \
PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
DESTDIR="$out" \
HAKUREI_VERSION="v${hakurei.version}" \
./all.sh
'';
}
+15
View File
@@ -2,6 +2,7 @@ package hst
import (
"encoding/json"
"fmt"
"strings"
"syscall"
"time"
@@ -68,6 +69,8 @@ const (
// FDevice mount /dev/ from the init mount namespace as is in the container
// mount namespace.
FDevice
// FCoverRun covers /run/ in the container mount namespace early.
FCoverRun
// FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity.
FShareRuntime
@@ -100,6 +103,8 @@ func (flags Flags) String() string {
return "mapuid"
case FDevice:
return "device"
case FCoverRun:
return "cover_run"
case FShareRuntime:
return "runtime"
case FShareTmpdir:
@@ -161,6 +166,10 @@ type ContainerConfig struct {
Flags Flags `json:"-"`
}
func (c *ContainerConfig) GoString() string {
return fmt.Sprintf("&%#v", *c)
}
// ContainerConfigF is [ContainerConfig] stripped of its methods.
//
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
@@ -191,6 +200,8 @@ type containerConfigJSON = struct {
// Corresponds to [FDevice].
Device bool `json:"device,omitempty"`
// Corresponds to [FCoverRun].
CoverRun bool `json:"cover_run,omitempty"`
// Corresponds to [FShareRuntime].
ShareRuntime bool `json:"share_runtime,omitempty"`
@@ -214,6 +225,7 @@ func (c *ContainerConfig) MarshalJSON() ([]byte, error) {
Multiarch: c.Flags&FMultiarch != 0,
MapRealUID: c.Flags&FMapRealUID != 0,
Device: c.Flags&FDevice != 0,
CoverRun: c.Flags&FCoverRun != 0,
ShareRuntime: c.Flags&FShareRuntime != 0,
ShareTmpdir: c.Flags&FShareTmpdir != 0,
})
@@ -257,6 +269,9 @@ func (c *ContainerConfig) UnmarshalJSON(data []byte) error {
if v.Device {
c.Flags |= FDevice
}
if v.CoverRun {
c.Flags |= FCoverRun
}
if v.ShareRuntime {
c.Flags |= FShareRuntime
}
+3 -3
View File
@@ -21,8 +21,8 @@ func TestFlagsString(t *testing.T) {
}{
{"none", 0, "none"},
{"none high", hst.FAll + 1, "none"},
{"all", hst.FAll, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir"},
{"all high", math.MaxUint, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir"},
{"all", hst.FAll, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir"},
{"all high", math.MaxUint, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@@ -53,7 +53,7 @@ func TestContainerConfig(t *testing.T) {
{"hostnet hostabstract mapuid", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID},
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":true}`},
{"all", &hst.ContainerConfig{Flags: hst.FAll},
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"device":true,"share_runtime":true,"share_tmpdir":true}`},
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"device":true,"cover_run":true,"share_runtime":true,"share_tmpdir":true}`},
}
for _, tc := range testCases {
+5
View File
@@ -1,6 +1,7 @@
package hst
import (
"fmt"
"strconv"
"strings"
)
@@ -61,6 +62,10 @@ type BusConfig struct {
Filter bool `json:"filter"`
}
func (c *BusConfig) GoString() string {
return fmt.Sprintf("&%#v", *c)
}
// Interfaces iterates over all interface strings specified in [BusConfig].
func (c *BusConfig) Interfaces(yield func(string) bool) {
if c == nil {
+9 -6
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"reflect"
"strings"
"hakurei.app/check"
)
@@ -36,6 +37,8 @@ type Ops interface {
Bind(source, target *check.Absolute, flags int) Ops
// Overlay appends an op that mounts the overlay pseudo filesystem.
Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) Ops
// OverlayEphemeral appends a MountOverlayOp with an ephemeral upperdir and workdir.
OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) Ops
// OverlayReadonly appends an op that mounts the overlay pseudo filesystem readonly.
OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) Ops
@@ -78,17 +81,17 @@ type FSImplError struct{ Value FilesystemConfig }
func (f FSImplError) Error() string {
implType := reflect.TypeOf(f.Value)
var name string
for implType != nil && implType.Kind() == reflect.Ptr {
name += "*"
var buf strings.Builder
for implType != nil && implType.Kind() == reflect.Pointer {
buf.WriteByte('*')
implType = implType.Elem()
}
if implType != nil {
name += implType.Name()
buf.WriteString(implType.Name())
} else {
name += "nil"
buf.WriteString("nil")
}
return fmt.Sprintf("implementation %s not supported", name)
return "implementation " + buf.String() + " not supported"
}
// FilesystemConfigJSON is the [json] adapter for [FilesystemConfig].
+9 -4
View File
@@ -3,6 +3,7 @@ package hst_test
import (
"encoding/json"
"errors"
"fmt"
"os"
"reflect"
"strings"
@@ -103,7 +104,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
t.Run("marshal", func(t *testing.T) {
t.Parallel()
wantErr := tc.wantErr
if errors.As(wantErr, new(hst.FSTypeError)) {
if _, ok := errors.AsType[hst.FSTypeError](wantErr); ok {
// for unsupported implementation tc
wantErr = hst.FSImplError{Value: stubFS{"cat"}}
}
@@ -139,7 +140,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
t.Run("unmarshal", func(t *testing.T) {
t.Parallel()
if tc.data == "\x00" && tc.sData == "\x00" {
if errors.As(tc.wantErr, new(hst.FSImplError)) {
if _, ok := errors.AsType[hst.FSImplError](tc.wantErr); ok {
// this error is only returned on marshal
return
}
@@ -283,11 +284,11 @@ func checkFs(t *testing.T, testCases []fsTestCase) {
if !reflect.DeepEqual(ops, &tc.ops) {
gotString := new(strings.Builder)
for _, op := range *ops {
gotString.WriteString("\n" + op.String())
gotString.WriteString("\n" + fmt.Sprintf("%#v", op))
}
wantString := new(strings.Builder)
for _, op := range tc.ops {
wantString.WriteString("\n" + op.String())
wantString.WriteString("\n" + fmt.Sprintf("%#v", op))
}
t.Errorf("Apply: %s, want %s", gotString, wantString)
}
@@ -339,6 +340,10 @@ func (p opsAdapter) Overlay(target, state, work *check.Absolute, layers ...*chec
return opsAdapter{p.Ops.Overlay(target, state, work, layers...)}
}
func (p opsAdapter) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
return opsAdapter{p.Ops.OverlayEphemeral(target, layers...)}
}
func (p opsAdapter) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
return opsAdapter{p.Ops.OverlayReadonly(target, layers...)}
}
+1 -6
View File
@@ -43,18 +43,13 @@ func (e *FSEphemeral) Apply(z *ApplyState) {
return
}
size := e.Size
if size < 0 {
size = 0
}
perm := e.Perm
if perm == 0 {
perm = fsEphemeralDefaultPerm
}
if e.Write {
z.Tmpfs(e.Target, size, perm)
z.Tmpfs(e.Target, max(e.Size, 0), perm)
} else {
z.Readonly(e.Target, perm)
}
+27 -9
View File
@@ -2,6 +2,7 @@ package hst
import (
"encoding/gob"
"slices"
"strings"
"hakurei.app/check"
@@ -40,7 +41,7 @@ func (o *FSOverlay) Valid() bool {
}
if o.Upper != nil { // rw
return o.Work != nil && len(o.Lower) > 0
return o.Work != nil || len(o.Lower) > 0
} else { // ro
return len(o.Lower) >= 2
}
@@ -58,8 +59,11 @@ func (o *FSOverlay) Host() []*check.Absolute {
return nil
}
p := make([]*check.Absolute, 0, 2+len(o.Lower))
if o.Upper != nil && o.Work != nil {
p = append(p, o.Upper, o.Work)
if o.Upper != nil {
p = append(p, o.Upper)
if o.Work != nil {
p = append(p, o.Work)
}
}
p = append(p, o.Lower...)
return p
@@ -70,11 +74,18 @@ func (o *FSOverlay) Apply(z *ApplyState) {
return
}
if o.Upper != nil && o.Work != nil {
z.Overlay(o.Target, o.Upper, o.Work, o.Lower...)
if o.Upper != nil {
if o.Target.Is(fhs.AbsRoot) {
z.NoRemountRoot = true
}
if o.Work != nil {
z.Overlay(o.Target, o.Upper, o.Work, o.Lower...)
} else {
z.OverlayEphemeral(o.Target, slices.Concat(
o.Lower,
[]*check.Absolute{o.Upper})...,
)
}
} else {
z.OverlayReadonly(o.Target, o.Lower...)
}
@@ -90,12 +101,19 @@ func (o *FSOverlay) String() string {
lower[i] = check.EscapeOverlayDataSegment(a.String())
}
if o.Upper != nil && o.Work != nil {
return "w*" + strings.Join(append([]string{
if o.Upper != nil {
if o.Work != nil {
return "w*" + strings.Join(append([]string{
check.EscapeOverlayDataSegment(o.Target.String()),
check.EscapeOverlayDataSegment(o.Upper.String()),
check.EscapeOverlayDataSegment(o.Work.String())},
lower...), check.SpecialOverlayPath)
}
return "e*" + strings.Join(append([]string{
check.EscapeOverlayDataSegment(o.Target.String()),
check.EscapeOverlayDataSegment(o.Upper.String()),
check.EscapeOverlayDataSegment(o.Work.String())},
check.EscapeOverlayDataSegment(o.Upper.String())},
lower...), check.SpecialOverlayPath)
} else {
return "*" + strings.Join(append([]string{
check.EscapeOverlayDataSegment(o.Target.String())},
+13 -1
View File
@@ -5,6 +5,7 @@ import (
"hakurei.app/check"
"hakurei.app/container"
"hakurei.app/fhs"
"hakurei.app/hst"
)
@@ -14,7 +15,7 @@ func TestFSOverlay(t *testing.T) {
checkFs(t, []fsTestCase{
{"nil", (*hst.FSOverlay)(nil), false, nil, nil, nil, "<invalid>"},
{"nil lower", &hst.FSOverlay{Target: m("/etc"), Lower: []*check.Absolute{nil}}, false, nil, nil, nil, "<invalid>"},
{"zero lower", &hst.FSOverlay{Target: m("/etc"), Upper: m("/"), Work: m("/")}, false, nil, nil, nil, "<invalid>"},
{"zero lower", &hst.FSOverlay{Target: m("/etc"), Work: m("/")}, false, nil, nil, nil, "<invalid>"},
{"zero lower ro", &hst.FSOverlay{Target: m("/etc")}, false, nil, nil, nil, "<invalid>"},
{"short lower", &hst.FSOverlay{Target: m("/etc"), Lower: ms("/etc")}, false, nil, nil, nil, "<invalid>"},
@@ -62,5 +63,16 @@ func TestFSOverlay(t *testing.T) {
Work: m("/tmp/work"),
}}, m("/"), ms("/tmp/upper", "/tmp/work", "/tmp/.src0", "/tmp/.src1"),
"w*/:/tmp/upper:/tmp/work:/tmp/.src0:/tmp/.src1"},
{"ephemeral", &hst.FSOverlay{
Target: m("/"),
Lower: ms("/tmp/.src0", "/tmp/.src1"),
Upper: m("/tmp/upper"),
}, true, container.Ops{&container.MountOverlayOp{
Target: m("/"),
Lower: ms("/tmp/.src0", "/tmp/.src1", "/tmp/upper"),
Upper: fhs.AbsRoot,
}}, m("/"), ms("/tmp/upper", "/tmp/.src0", "/tmp/.src1"),
"e*/:/tmp/upper:/tmp/.src0:/tmp/.src1"},
})
}
+1
View File
@@ -245,6 +245,7 @@ func TestTemplate(t *testing.T) {
"multiarch": true,
"map_real_uid": true,
"device": true,
"cover_run": true,
"share_runtime": true,
"share_tmpdir": true
}
+2 -2
View File
@@ -80,7 +80,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
continue
}
if ib := bytes.IndexByte([]byte("-_/.\\*"), b); ib != -1 { // - // _/.\*
if found := bytes.Contains([]byte("-_/.\\*"), []byte{b}); found { // - // _/.\*
goto opt
} else if b >= '0' && b <= '9' { // 0-9
goto opt
@@ -101,7 +101,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
break
}
if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
if errors.As(err, new(hex.InvalidByteError)) {
if _, ok := errors.AsType[hex.InvalidByteError](err); ok {
errno = ErrBadValHexByte
break
}
+17
View File
@@ -2,6 +2,7 @@ package kobject
import (
"errors"
"maps"
"strconv"
"strings"
"unsafe"
@@ -28,6 +29,22 @@ type Event struct {
Subsystem string `json:"subsystem"`
}
// Clone returns a copy of e.
func (e *Event) Clone() Event {
v := *e
v.Env = maps.Clone(e.Env)
return v
}
// makeColdboot allocates a new [Object] from e in [StateColdboot].
func (e *Event) makeColdboot() *Object {
return &Object{
State: StateColdboot,
DevPath: e.DevPath,
Subsystem: e.Subsystem,
}
}
// Populate populates e with the contents of a [uevent.Message].
//
// The ACTION and DEVPATH environment variables are ignored and assumed to be
+491
View File
@@ -0,0 +1,491 @@
// Package kobject interprets uevent messages from a NETLINK_KOBJECT_UEVENT socket.
package kobject
import (
"context"
"fmt"
"maps"
"slices"
"strconv"
"sync"
"hakurei.app/internal/report"
"hakurei.app/internal/uevent"
)
const (
// StateColdboot denotes an [Object] populated by a coldboot event. It is
// eligible for all event actions.
StateColdboot = iota
// StateNew denotes an [Object] previously populated by a [uevent.KOBJ_ADD]
// event, but has not yet been targeted by a [uevent.KOBJ_BIND] event, or
// has been targeted by a [uevent.KOBJ_UNBIND] event.
StateNew
// StateBound denotes an [Object] that has been targeted by a
// [uevent.KOBJ_BIND] and has not been targeted by a [uevent.KOBJ_UNBIND]
// after that.
StateBound
)
// Object represents a kernel object.
type Object struct {
// Origin of the object.
State int `json:"state,omitempty"`
// Set by [uevent.KOBJ_OFFLINE] and [uevent.KOBJ_ONLINE].
Offline bool `json:"offline,omitempty"`
// alloc_uevent_skb: devpath
DevPath string `json:"devpath"`
// registered per-driver (optional)
ModAlias string `json:"modalias,omitempty"`
// dev_driver_uevent: drv->name (optional)
Driver string `json:"driver,omitempty"`
// SUBSYSTEM value set by the kernel.
Subsystem string `json:"subsystem"`
// Uninterpreted environment variable pairs. An entry missing a separator
// gains the value "\x00".
Env map[string]string `json:"env"`
}
// Clone returns the address of a copy of o.
func (o *Object) Clone() *Object {
v := *o
v.Env = maps.Clone(o.Env)
return &v
}
// GoString returns compound literal for the underlying value.
func (o *Object) GoString() string {
return fmt.Sprintf("&%#v", *o)
}
// merge merges uninterpreted environment variable pairs from an [Event].
func (o *Object) merge(env map[string]string) {
for k, v := range env {
if v == "\x00" {
continue
}
switch k {
case "MODALIAS":
o.ModAlias = v
continue
case "DRIVER":
o.Driver = v
continue
default:
if o.Env == nil {
o.Env = make(map[string]string)
}
o.Env[k] = v
}
}
}
// update updates o with pairs from env, optionally stripping visited pairs.
func (o *Object) update(env map[string]string, strip bool) {
for k := range o.Env {
if v, ok := env[k]; ok {
if strip {
delete(env, k)
}
o.Env[k] = v
}
}
}
// A pendingIterator is a callback currently iterating through objects targeted
// by ongoing events.
type pendingIterator struct {
f func(o *Object, act uevent.KobjectAction) bool
done chan<- struct{}
}
// State processes a stream of [Event] populated from [uevent.Message] received
// from a NETLINK_KOBJECT_UEVENT socket and presents an efficient representation
// of kernel state.
type State struct {
// Next expected SEQNUM.
seq uint64
// DevPath to environment variables.
uevent map[string]*Object
// Synchronises access to uevent and its objects.
ueventMu sync.RWMutex
// Alive iterators.
iter []*pendingIterator
// Synchronises access to iter.
iterMu sync.Mutex
// UUID for synthetic [uevent.Coldboot] events.
coldboot uevent.UUID
// Called on [uevent.KOBJ_CHANGE] with stripped environment variables.
handleChange func(o *Object, env map[string]string)
// Reports errors populating [Event] from [uevent.Message]. A user-supplied
// nil value is replaced with a noop.
reportErr func(error)
}
// New returns the address of a new [State].
func New(
coldboot uevent.UUID,
handleChange func(o *Object, env map[string]string),
reportErr func(error),
) *State {
return &State{
uevent: make(map[string]*Object),
coldboot: coldboot,
handleChange: handleChange,
reportErr: reportErr,
}
}
// deleteIter removes an iterator from s. Must be called after acquiring iterMu.
func (s *State) deleteIter(p *pendingIterator) {
s.iter = slices.DeleteFunc(s.iter, func(v *pendingIterator) bool {
return p == v
})
}
// dispatchIter broadcasts an [Object] to all alive iterators.
func (s *State) dispatchIter(o *Object, act uevent.KobjectAction) {
s.iterMu.Lock()
defer s.iterMu.Unlock()
for _, p := range s.iter {
if !p.f(o, act) {
s.deleteIter(p)
close(p.done)
}
}
}
// Range calls f on all current and upcoming [Object] values tracked by s until
// f returns false or the context is cancelled. f must not retain o or modify
// the value it points to.
func (s *State) Range(
ctx context.Context,
f func(o *Object, act uevent.KobjectAction) bool,
) {
done := make(chan struct{})
p := pendingIterator{f, done}
s.iterMu.Lock()
s.ueventMu.RLock()
for _, o := range s.uevent {
if !f(o, uevent.KOBJ_ADD) {
s.ueventMu.RUnlock()
s.iterMu.Unlock()
return
}
}
s.ueventMu.RUnlock()
s.iter = append(s.iter, &p)
s.iterMu.Unlock()
select {
case <-ctx.Done():
s.iterMu.Lock()
s.deleteIter(&p)
s.iterMu.Unlock()
return
case <-done:
// deregistered by dispatchIter
return
}
}
// An EventError describes a malformed or inconsistent [Event].
type EventError struct {
Kind int `json:"fault"`
E Event `json:"event"`
O *Object `json:"object,omitempty"`
}
var _ report.RepresentableError = EventError{}
func (EventError) Representable() {}
const (
// EUnexpectedColdboot is reported for a coldboot event with action other
// than the expected [uevent.KOBJ_ADD].
EUnexpectedColdboot = iota
// EDuplicateAdd is reported for a [uevent.KOBJ_ADD] event on a
// still-existing entry that was not the result of a coldboot.
EDuplicateAdd
// EBadTarget is reported for an event on a nonexistent [Object]. This is
// generally only possible before coldboot completes.
EBadTarget
// ERemoveState is reported for a [uevent.KOBJ_REMOVE] event targeting an
// entry in a state other than [StateColdboot] and [StateNew].
ERemoveState
// EUnexpectedOffline is reported for a [uevent.KOBJ_OFFLINE] or
// [uevent.KOBJ_ONLINE] event targeting an already offline or online object.
EUnexpectedOffline
// EBindState is reported for a [uevent.KOBJ_BIND] event targeting an entry
// in a state other than [StateColdboot] and [StateNew].
EBindState
// EUnbindState is reported for a [uevent.KOBJ_UNBIND] event targeting an
// entry in a state other than [StateBound].
EUnbindState
// EMalformedMove is reported for a [uevent.KOBJ_MOVE] event missing the
// DEVPATH_OLD environment variable.
EMalformedMove
)
func (e EventError) Error() string {
switch e.Kind {
case EUnexpectedColdboot:
return "unexpected " + e.E.Action.String() + " coldboot event"
case EDuplicateAdd:
return "duplicate add event on devpath " + strconv.Quote(e.E.DevPath)
case EBadTarget:
return "unexpected " + e.E.Action.String() + " event on devpath " +
strconv.Quote(e.E.DevPath)
case ERemoveState:
if e.O == nil {
return "invalid remove event error"
}
return "remove event targeting devpath " + strconv.Quote(e.E.DevPath) +
" in state " + strconv.Itoa(e.O.State)
case EUnexpectedOffline:
if e.O == nil {
return "invalid unexpected offline error"
}
if e.O.Offline {
return "offline event targeting devpath " + strconv.Quote(e.E.DevPath)
}
return "online event targeting devpath " + strconv.Quote(e.E.DevPath)
case EBindState:
if e.O == nil {
return "invalid bind state error"
}
return "bind event targeting devpath " + strconv.Quote(e.E.DevPath) +
" in state " + strconv.Itoa(e.O.State)
case EUnbindState:
if e.O == nil {
return "invalid unbind state error"
}
return "unbind event targeting devpath " + strconv.Quote(e.E.DevPath) +
" in state " + strconv.Itoa(e.O.State)
case EMalformedMove:
return "move event targeting devpath " + strconv.Quote(e.E.DevPath) +
" missing DEVPATH_OLD"
default:
return "invalid event error kind " + strconv.Itoa(e.Kind)
}
}
// NewError returns a new [EventError] for e and o.
func (e *Event) NewError(kind int, o *Object) error {
if o != nil {
o = o.Clone()
}
return EventError{kind, e.Clone(), o}
}
// processEvent merges an event into s.
func (s *State) processEvent(e *Event) {
s.ueventMu.Lock()
defer s.ueventMu.Unlock()
coldboot := e.Synth != nil
if e.Action != uevent.KOBJ_ADD && coldboot {
s.reportErr(e.NewError(EUnexpectedColdboot, nil))
return
}
switch act := e.Action; act {
case uevent.KOBJ_ADD:
if e.Synth == nil {
if o, ok := s.uevent[e.DevPath]; ok {
s.reportErr(e.NewError(EDuplicateAdd, o))
o.merge(e.Env)
s.dispatchIter(o, act)
return
}
}
o := e.makeColdboot()
if !coldboot {
o.State = StateNew
}
o.merge(e.Env)
s.uevent[e.DevPath] = o
s.dispatchIter(o, act)
return
case uevent.KOBJ_REMOVE:
if o, ok := s.uevent[e.DevPath]; !ok {
s.reportErr(e.NewError(EBadTarget, nil))
return
} else if o.State != StateColdboot && o.State != StateNew {
s.reportErr(e.NewError(ERemoveState, o))
}
delete(s.uevent, e.DevPath)
return
case uevent.KOBJ_CHANGE:
o, ok := s.uevent[e.DevPath]
if !ok {
s.reportErr(e.NewError(EBadTarget, nil))
// this suffers from the coldboot race window similar to KOBJ_MOVE,
// however this action combines driver-specific and change-specific
// environment variables and combines them with environment
// variables meant to convey state of the kobject, and it is not
// possible to reliably separate them, so this fallback avoids the
// race at the cost of including some garbage in tracked state
o = e.makeColdboot()
o.merge(e.Env)
s.uevent[e.DevPath] = o
s.dispatchIter(o, act)
return
}
o.update(e.Env, true)
if s.handleChange != nil {
s.handleChange(o, e.Env)
}
s.dispatchIter(o, act)
return
case uevent.KOBJ_MOVE:
var o *Object
if old, ok := e.Env["DEVPATH_OLD"]; !ok {
s.reportErr(e.NewError(EMalformedMove, nil))
// not reached
o = e.makeColdboot()
} else if o, ok = s.uevent[old]; !ok {
s.reportErr(e.NewError(EBadTarget, nil))
// this generally happens during coldboot, dropping the event here
// may cause inconsistent state if the coldboot event for this
// object was generated before the bind event
delete(e.Env, "DEVPATH_OLD")
o = e.makeColdboot()
} else {
delete(s.uevent, old)
delete(e.Env, "DEVPATH_OLD")
}
o.merge(e.Env)
s.uevent[e.DevPath] = o
o.DevPath = e.DevPath
s.dispatchIter(o, act)
return
case uevent.KOBJ_ONLINE:
o, ok := s.uevent[e.DevPath]
if !ok {
s.reportErr(e.NewError(EBadTarget, nil))
// coldboot race window similar to an unexpected KOBJ_MOVE
o = e.makeColdboot()
s.uevent[e.DevPath] = o
o.merge(e.Env)
}
if !o.Offline {
s.reportErr(e.NewError(EUnexpectedOffline, o))
}
o.Offline = false
s.dispatchIter(o, act)
return
case uevent.KOBJ_OFFLINE:
o, ok := s.uevent[e.DevPath]
if !ok {
s.reportErr(e.NewError(EBadTarget, nil))
// coldboot race window similar to an unexpected KOBJ_MOVE
o = e.makeColdboot()
s.uevent[e.DevPath] = o
o.merge(e.Env)
}
if o.Offline {
s.reportErr(e.NewError(EUnexpectedOffline, o))
}
o.Offline = true
s.dispatchIter(o, act)
return
case uevent.KOBJ_BIND:
o, ok := s.uevent[e.DevPath]
if !ok {
s.reportErr(e.NewError(EBadTarget, nil))
// coldboot race window similar to an unexpected KOBJ_MOVE
o = e.makeColdboot()
s.uevent[e.DevPath] = o
}
if o.State != StateColdboot && o.State != StateNew {
s.reportErr(e.NewError(EBindState, o))
}
o.State = StateBound
o.merge(e.Env)
s.dispatchIter(o, act)
return
case uevent.KOBJ_UNBIND:
o, ok := s.uevent[e.DevPath]
if !ok {
s.reportErr(e.NewError(EBadTarget, nil))
// coldboot race window similar to an unexpected KOBJ_MOVE, but does
// not result in inconsistent state if dropped
return
}
if o.State != StateBound {
s.reportErr(e.NewError(EUnbindState, o))
}
o.State = StateNew
o.Driver = ""
s.dispatchIter(o, act)
return
default: // not reached
s.reportErr(fmt.Errorf("invalid action %d", e.Action))
return
}
}
// BadSequenceError is reported for an unexpected SEQNUM.
type BadSequenceError struct{ Got, Want uint64 }
func (e BadSequenceError) Error() string {
return "SEQNUM=" + strconv.FormatUint(e.Got, 10) +
", want " + strconv.FormatUint(e.Want, 10)
}
// Consume receives uevent messages and updates s to reflect state of kernel.
func (s *State) Consume(ctx context.Context, events <-chan *uevent.Message) {
if s.uevent == nil {
s.uevent = make(map[string]*Object)
}
if s.reportErr == nil {
s.reportErr = func(error) {}
}
var e Event
for {
select {
case <-ctx.Done():
return
case m, ok := <-events:
if !ok {
return
}
e.Populate(s.reportErr, m)
// skip external synthetic event
if e.Synth != nil && *e.Synth != s.coldboot {
continue
}
if s.seq == 0 {
s.seq = e.Sequence
}
if s.seq != e.Sequence {
s.reportErr(BadSequenceError{e.Sequence, s.seq})
}
s.seq++
s.processEvent(&e)
}
}
}
File diff suppressed because it is too large Load Diff
+266
View File
@@ -0,0 +1,266 @@
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXPWRBN:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXPWRBN:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXPWRBN:","SEQNUM=777"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXPWRBN:00/wakeup/wakeup7","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXPWRBN:00/wakeup/wakeup7","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=778"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00/LNXCPU:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00/LNXCPU:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXCPU:","SEQNUM=779"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:ACPI0010:PNP0A05:","SEQNUM=780"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0103:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0103:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0103:","SEQNUM=781"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=782"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=783"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=784"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/QEMU0002:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/QEMU0002:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:QEMU0002:","SEQNUM=785"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=786"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00/wakeup/wakeup0","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00/wakeup/wakeup0","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=787"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0303:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0303:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0303:","SEQNUM=788"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0400:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0400:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0400:","SEQNUM=789"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0501:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0501:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0501:","SEQNUM=790"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00/device:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00/device:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=791"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0700:","SEQNUM=792"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0B00:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0B00:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0B00:","SEQNUM=793"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0F13:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0F13:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0F13:","SEQNUM=794"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=795"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/wakeup/wakeup1","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/wakeup/wakeup1","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=796"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=797"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03/wakeup/wakeup2","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03/wakeup/wakeup2","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=798"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=799"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04/wakeup/wakeup3","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04/wakeup/wakeup3","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=800"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=801"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05/wakeup/wakeup4","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05/wakeup/wakeup4","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=802"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=803"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06/wakeup/wakeup5","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06/wakeup/wakeup5","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=804"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=805"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=806"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:09","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:09","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=807"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0a","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0a","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=808"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0b","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0b","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=809"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0c","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0c","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=810"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0d","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0d","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=811"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0e","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0e","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=812"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0f","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0f","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=813"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:10","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:10","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=814"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:11","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:11","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=815"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:12","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:12","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=816"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:13","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:13","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=817"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:14","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:14","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=818"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:15","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:15","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=819"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:16","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:16","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=820"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:17","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:17","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=821"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:18","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:18","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=822"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:19","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:19","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=823"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1a","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1a","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=824"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1b","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1b","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=825"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1c","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1c","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=826"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1d","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1d","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=827"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1e","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1e","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=828"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1f","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1f","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=829"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:20","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:20","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=830"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:21","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:21","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=831"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:22","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:22","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=832"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A03:","SEQNUM=833"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/wakeup/wakeup6","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/wakeup/wakeup6","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=834"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=835"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=836"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=837"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:03","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:03","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=838"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:04","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:04","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=839"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYBUS:","SEQNUM=840"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYBUS:","SEQNUM=841"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYSTM:","SEQNUM=842"]}
{"action":"add","devpath":"/devices/breakpoint","env":["ACTION=add","DEVPATH=/devices/breakpoint","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=843"]}
{"action":"add","devpath":"/devices/cpu","env":["ACTION=add","DEVPATH=/devices/cpu","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=844"]}
{"action":"add","devpath":"/devices/kprobe","env":["ACTION=add","DEVPATH=/devices/kprobe","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=845"]}
{"action":"add","devpath":"/devices/msr","env":["ACTION=add","DEVPATH=/devices/msr","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=846"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:00.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:00.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=60000","PCI_ID=8086:1237","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:00.0","MODALIAS=pci:v00008086d00001237sv00001AF4sd00001100bc06sc00i00","SEQNUM=847"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=60100","PCI_ID=8086:7000","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.0","MODALIAS=pci:v00008086d00007000sv00001AF4sd00001100bc06sc01i00","SEQNUM=848"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/ata_port/ata1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/ata_port/ata1","SUBSYSTEM=ata_port","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=849"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/host0/scsi_host/host0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/host0/scsi_host/host0","SUBSYSTEM=scsi_host","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=850"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/host0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/host0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_host","SEQNUM=851"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/ata_link/link1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/ata_link/link1","SUBSYSTEM=ata_link","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=852"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.0/ata_device/dev1.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.0/ata_device/dev1.0","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=853"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.1/ata_device/dev1.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.1/ata_device/dev1.1","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=854"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/ata_port/ata2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/ata_port/ata2","SUBSYSTEM=ata_port","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=855"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/scsi_host/host1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/scsi_host/host1","SUBSYSTEM=scsi_host","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=856"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/bsg/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/bsg/1:0:0:0","SUBSYSTEM=bsg","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=251","MINOR=0","DEVNAME=bsg/1:0:0:0","SEQNUM=857"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/scsi_device/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/scsi_device/1:0:0:0","SUBSYSTEM=scsi_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=858"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_device","MODALIAS=scsi:t-0x05","SEQNUM=859"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_target","SEQNUM=860"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_host","SEQNUM=861"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/ata_link/link2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/ata_link/link2","SUBSYSTEM=ata_link","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=862"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.0/ata_device/dev2.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.0/ata_device/dev2.0","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=863"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.1/ata_device/dev2.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.1/ata_device/dev2.1","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=864"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=ata_piix","PCI_CLASS=10180","PCI_ID=8086:7010","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.1","MODALIAS=pci:v00008086d00007010sv00001AF4sd00001100bc01sc01i80","SEQNUM=865"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.3","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.3","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=68000","PCI_ID=8086:7113","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.3","MODALIAS=pci:v00008086d00007113sv00001AF4sd00001100bc06sc80i00","SEQNUM=866"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:02.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:02.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=20000","PCI_ID=8086:100E","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:02.0","MODALIAS=pci:v00008086d0000100Esv00001AF4sd00001100bc02sc00i00","SEQNUM=867"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=virtio-pci","PCI_CLASS=10000","PCI_ID=1AF4:1001","PCI_SUBSYS_ID=1AF4:0002","PCI_SLOT_NAME=0000:00:03.0","MODALIAS=pci:v00001AF4d00001001sv00001AF4sd00000002bc01sc00i00","SEQNUM=868"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0/virtio0/block/vda","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0/virtio0/block/vda","SUBSYSTEM=block","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=254","MINOR=0","DEVNAME=vda","DEVTYPE=disk","DISKSEQ=1","SEQNUM=869"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0/virtio0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0/virtio0","SUBSYSTEM=virtio","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=virtio_blk","MODALIAS=virtio:d00000002v00001AF4","SEQNUM=870"]}
{"action":"add","devpath":"/devices/pci0000:00/QEMU0002:00","env":["ACTION=add","DEVPATH=/devices/pci0000:00/QEMU0002:00","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:QEMU0002:","SEQNUM=871"]}
{"action":"add","devpath":"/devices/pci0000:00/pci_bus/0000:00","env":["ACTION=add","DEVPATH=/devices/pci0000:00/pci_bus/0000:00","SUBSYSTEM=pci_bus","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=872"]}
{"action":"add","devpath":"/devices/platform/PNP0103:00","env":["ACTION=add","DEVPATH=/devices/platform/PNP0103:00","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0103:","SEQNUM=873"]}
{"action":"add","devpath":"/devices/platform/pcspkr","env":["ACTION=add","DEVPATH=/devices/platform/pcspkr","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=platform:pcspkr","SEQNUM=874"]}
{"action":"add","devpath":"/devices/platform/reg-dummy/regulator/regulator.0","env":["ACTION=add","DEVPATH=/devices/platform/reg-dummy/regulator/regulator.0","SUBSYSTEM=regulator","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=875"]}
{"action":"add","devpath":"/devices/platform/reg-dummy","env":["ACTION=add","DEVPATH=/devices/platform/reg-dummy","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=reg-dummy","MODALIAS=platform:reg-dummy","SEQNUM=876"]}
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.1/tty/ttyS1","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.1/tty/ttyS1","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=65","DEVNAME=ttyS1","SEQNUM=877"]}
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.1","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.1","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=878"]}
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.2/tty/ttyS2","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.2/tty/ttyS2","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=66","DEVNAME=ttyS2","SEQNUM=879"]}
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.2","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.2","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=880"]}
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.3/tty/ttyS3","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.3/tty/ttyS3","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=67","DEVNAME=ttyS3","SEQNUM=881"]}
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.3","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.3","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=882"]}
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=ctrl","DRIVER=ctrl","SEQNUM=883"]}
{"action":"add","devpath":"/devices/platform/serial8250","env":["ACTION=add","DEVPATH=/devices/platform/serial8250","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=serial8250","MODALIAS=platform:serial8250","SEQNUM=884"]}
{"action":"add","devpath":"/devices/pnp0/00:00","env":["ACTION=add","DEVPATH=/devices/pnp0/00:00","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=885"]}
{"action":"add","devpath":"/devices/pnp0/00:01","env":["ACTION=add","DEVPATH=/devices/pnp0/00:01","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=886"]}
{"action":"add","devpath":"/devices/pnp0/00:02","env":["ACTION=add","DEVPATH=/devices/pnp0/00:02","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=887"]}
{"action":"add","devpath":"/devices/pnp0/00:03","env":["ACTION=add","DEVPATH=/devices/pnp0/00:03","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=888"]}
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0/00:04:0.0/tty/ttyS0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0/00:04:0.0/tty/ttyS0","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=64","DEVNAME=ttyS0","SEQNUM=889"]}
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0/00:04:0.0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0/00:04:0.0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=890"]}
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=ctrl","DRIVER=ctrl","SEQNUM=891"]}
{"action":"add","devpath":"/devices/pnp0/00:04","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=serial","SEQNUM=892"]}
{"action":"add","devpath":"/devices/pnp0/00:05","env":["ACTION=add","DEVPATH=/devices/pnp0/00:05","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=893"]}
{"action":"add","devpath":"/devices/software","env":["ACTION=add","DEVPATH=/devices/software","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=894"]}
{"action":"add","devpath":"/devices/system/clockevents/broadcast","env":["ACTION=add","DEVPATH=/devices/system/clockevents/broadcast","SUBSYSTEM=clockevents","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=895"]}
{"action":"add","devpath":"/devices/system/clockevents/clockevent0","env":["ACTION=add","DEVPATH=/devices/system/clockevents/clockevent0","SUBSYSTEM=clockevents","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=896"]}
{"action":"add","devpath":"/devices/system/clocksource/clocksource0","env":["ACTION=add","DEVPATH=/devices/system/clocksource/clocksource0","SUBSYSTEM=clocksource","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=897"]}
{"action":"add","devpath":"/devices/system/container/PNP0A06:00","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:00","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=898"]}
{"action":"add","devpath":"/devices/system/container/PNP0A06:01","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:01","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=899"]}
{"action":"add","devpath":"/devices/system/container/PNP0A06:02","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:02","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=900"]}
{"action":"add","devpath":"/devices/system/cpu/cpu0","env":["ACTION=add","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=901"]}
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=902"]}
{"action":"add","devpath":"/devices/system/memory/memory0","env":["ACTION=add","DEVPATH=/devices/system/memory/memory0","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=903"]}
{"action":"add","devpath":"/devices/system/memory/memory1","env":["ACTION=add","DEVPATH=/devices/system/memory/memory1","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=904"]}
{"action":"add","devpath":"/devices/system/memory/memory2","env":["ACTION=add","DEVPATH=/devices/system/memory/memory2","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=905"]}
{"action":"add","devpath":"/devices/system/memory/memory3","env":["ACTION=add","DEVPATH=/devices/system/memory/memory3","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=906"]}
{"action":"add","devpath":"/devices/system/memory/memory4","env":["ACTION=add","DEVPATH=/devices/system/memory/memory4","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=907"]}
{"action":"add","devpath":"/devices/system/memory/memory5","env":["ACTION=add","DEVPATH=/devices/system/memory/memory5","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=908"]}
{"action":"add","devpath":"/devices/system/memory/memory6","env":["ACTION=add","DEVPATH=/devices/system/memory/memory6","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=909"]}
{"action":"add","devpath":"/devices/system/memory/memory7","env":["ACTION=add","DEVPATH=/devices/system/memory/memory7","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=910"]}
{"action":"add","devpath":"/devices/system/node/node0","env":["ACTION=add","DEVPATH=/devices/system/node/node0","SUBSYSTEM=node","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=911"]}
{"action":"add","devpath":"/devices/tracepoint","env":["ACTION=add","DEVPATH=/devices/tracepoint","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=912"]}
{"action":"add","devpath":"/devices/uprobe","env":["ACTION=add","DEVPATH=/devices/uprobe","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=913"]}
{"action":"add","devpath":"/devices/virtual/bdi/254:0","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/254:0","SUBSYSTEM=bdi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=914"]}
{"action":"add","devpath":"/devices/virtual/devlink/:ata2--scsi:1:0:0:0","env":["ACTION=add","DEVPATH=/devices/virtual/devlink/:ata2--scsi:1:0:0:0","SUBSYSTEM=devlink","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=915"]}
{"action":"add","devpath":"/devices/virtual/dmi/id","env":["ACTION=add","DEVPATH=/devices/virtual/dmi/id","SUBSYSTEM=dmi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=dmi:bvnSeaBIOS:bvrrel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org:bd04/01/2014:br0.0:svnQEMU:pnStandardPC(i440FX+PIIX,1996):pvrpc-i440fx-10.1:cvnQEMU:ct1:cvrpc-i440fx-10.1:sku:","SEQNUM=916"]}
{"action":"add","devpath":"/devices/virtual/mem/full","env":["ACTION=add","DEVPATH=/devices/virtual/mem/full","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=7","DEVNAME=full","DEVMODE=0666","SEQNUM=917"]}
{"action":"add","devpath":"/devices/virtual/mem/kmsg","env":["ACTION=add","DEVPATH=/devices/virtual/mem/kmsg","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=11","DEVNAME=kmsg","DEVMODE=0644","SEQNUM=918"]}
{"action":"add","devpath":"/devices/virtual/mem/mem","env":["ACTION=add","DEVPATH=/devices/virtual/mem/mem","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=1","DEVNAME=mem","SEQNUM=919"]}
{"action":"add","devpath":"/devices/virtual/mem/null","env":["ACTION=add","DEVPATH=/devices/virtual/mem/null","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=3","DEVNAME=null","DEVMODE=0666","SEQNUM=920"]}
{"action":"add","devpath":"/devices/virtual/mem/port","env":["ACTION=add","DEVPATH=/devices/virtual/mem/port","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=4","DEVNAME=port","SEQNUM=921"]}
{"action":"add","devpath":"/devices/virtual/mem/random","env":["ACTION=add","DEVPATH=/devices/virtual/mem/random","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=8","DEVNAME=random","DEVMODE=0666","SEQNUM=922"]}
{"action":"add","devpath":"/devices/virtual/mem/urandom","env":["ACTION=add","DEVPATH=/devices/virtual/mem/urandom","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=9","DEVNAME=urandom","DEVMODE=0666","SEQNUM=923"]}
{"action":"add","devpath":"/devices/virtual/mem/zero","env":["ACTION=add","DEVPATH=/devices/virtual/mem/zero","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=5","DEVNAME=zero","DEVMODE=0666","SEQNUM=924"]}
{"action":"add","devpath":"/devices/virtual/memory_tiering/memory_tier4","env":["ACTION=add","DEVPATH=/devices/virtual/memory_tiering/memory_tier4","SUBSYSTEM=memory_tiering","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=925"]}
{"action":"add","devpath":"/devices/virtual/misc/cpu_dma_latency","env":["ACTION=add","DEVPATH=/devices/virtual/misc/cpu_dma_latency","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=259","DEVNAME=cpu_dma_latency","SEQNUM=926"]}
{"action":"add","devpath":"/devices/virtual/misc/hpet","env":["ACTION=add","DEVPATH=/devices/virtual/misc/hpet","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=228","DEVNAME=hpet","SEQNUM=927"]}
{"action":"add","devpath":"/devices/virtual/misc/snapshot","env":["ACTION=add","DEVPATH=/devices/virtual/misc/snapshot","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=231","DEVNAME=snapshot","SEQNUM=928"]}
{"action":"add","devpath":"/devices/virtual/misc/udmabuf","env":["ACTION=add","DEVPATH=/devices/virtual/misc/udmabuf","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=258","DEVNAME=udmabuf","SEQNUM=929"]}
{"action":"add","devpath":"/devices/virtual/misc/userfaultfd","env":["ACTION=add","DEVPATH=/devices/virtual/misc/userfaultfd","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=257","DEVNAME=userfaultfd","SEQNUM=930"]}
{"action":"add","devpath":"/devices/virtual/misc/vga_arbiter","env":["ACTION=add","DEVPATH=/devices/virtual/misc/vga_arbiter","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=256","DEVNAME=vga_arbiter","SEQNUM=931"]}
{"action":"add","devpath":"/devices/virtual/net/lo","env":["ACTION=add","DEVPATH=/devices/virtual/net/lo","SUBSYSTEM=net","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","INTERFACE=lo","IFINDEX=1","SEQNUM=932"]}
{"action":"add","devpath":"/devices/virtual/thermal/cooling_device0","env":["ACTION=add","DEVPATH=/devices/virtual/thermal/cooling_device0","SUBSYSTEM=thermal","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=933"]}
{"action":"add","devpath":"/devices/virtual/tty/console","env":["ACTION=add","DEVPATH=/devices/virtual/tty/console","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=1","DEVNAME=console","SEQNUM=934"]}
{"action":"add","devpath":"/devices/virtual/tty/ptmx","env":["ACTION=add","DEVPATH=/devices/virtual/tty/ptmx","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=2","DEVNAME=ptmx","DEVMODE=0666","SEQNUM=935"]}
{"action":"add","devpath":"/devices/virtual/tty/tty","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=0","DEVNAME=tty","DEVMODE=0666","SEQNUM=936"]}
{"action":"add","devpath":"/devices/virtual/tty/tty0","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty0","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=0","DEVNAME=tty0","SEQNUM=937"]}
{"action":"add","devpath":"/devices/virtual/tty/tty1","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty1","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=1","DEVNAME=tty1","SEQNUM=938"]}
{"action":"add","devpath":"/devices/virtual/tty/tty10","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty10","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=10","DEVNAME=tty10","SEQNUM=939"]}
{"action":"add","devpath":"/devices/virtual/tty/tty11","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty11","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=11","DEVNAME=tty11","SEQNUM=940"]}
{"action":"add","devpath":"/devices/virtual/tty/tty12","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty12","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=12","DEVNAME=tty12","SEQNUM=941"]}
{"action":"add","devpath":"/devices/virtual/tty/tty13","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty13","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=13","DEVNAME=tty13","SEQNUM=942"]}
{"action":"add","devpath":"/devices/virtual/tty/tty14","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty14","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=14","DEVNAME=tty14","SEQNUM=943"]}
{"action":"add","devpath":"/devices/virtual/tty/tty15","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty15","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=15","DEVNAME=tty15","SEQNUM=944"]}
{"action":"add","devpath":"/devices/virtual/tty/tty16","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty16","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=16","DEVNAME=tty16","SEQNUM=945"]}
{"action":"add","devpath":"/devices/virtual/tty/tty17","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty17","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=17","DEVNAME=tty17","SEQNUM=946"]}
{"action":"add","devpath":"/devices/virtual/tty/tty18","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty18","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=18","DEVNAME=tty18","SEQNUM=947"]}
{"action":"add","devpath":"/devices/virtual/tty/tty19","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty19","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=19","DEVNAME=tty19","SEQNUM=948"]}
{"action":"add","devpath":"/devices/virtual/tty/tty2","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty2","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=2","DEVNAME=tty2","SEQNUM=949"]}
{"action":"add","devpath":"/devices/virtual/tty/tty20","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty20","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=20","DEVNAME=tty20","SEQNUM=950"]}
{"action":"add","devpath":"/devices/virtual/tty/tty21","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty21","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=21","DEVNAME=tty21","SEQNUM=951"]}
{"action":"add","devpath":"/devices/virtual/tty/tty22","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty22","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=22","DEVNAME=tty22","SEQNUM=952"]}
{"action":"add","devpath":"/devices/virtual/tty/tty23","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty23","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=23","DEVNAME=tty23","SEQNUM=953"]}
{"action":"add","devpath":"/devices/virtual/tty/tty24","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty24","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=24","DEVNAME=tty24","SEQNUM=954"]}
{"action":"add","devpath":"/devices/virtual/tty/tty25","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty25","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=25","DEVNAME=tty25","SEQNUM=955"]}
{"action":"add","devpath":"/devices/virtual/tty/tty26","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty26","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=26","DEVNAME=tty26","SEQNUM=956"]}
{"action":"add","devpath":"/devices/virtual/tty/tty27","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty27","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=27","DEVNAME=tty27","SEQNUM=957"]}
{"action":"add","devpath":"/devices/virtual/tty/tty28","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty28","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=28","DEVNAME=tty28","SEQNUM=958"]}
{"action":"add","devpath":"/devices/virtual/tty/tty29","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty29","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=29","DEVNAME=tty29","SEQNUM=959"]}
{"action":"add","devpath":"/devices/virtual/tty/tty3","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty3","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=3","DEVNAME=tty3","SEQNUM=960"]}
{"action":"add","devpath":"/devices/virtual/tty/tty30","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty30","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=30","DEVNAME=tty30","SEQNUM=961"]}
{"action":"add","devpath":"/devices/virtual/tty/tty31","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty31","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=31","DEVNAME=tty31","SEQNUM=962"]}
{"action":"add","devpath":"/devices/virtual/tty/tty32","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty32","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=32","DEVNAME=tty32","SEQNUM=963"]}
{"action":"add","devpath":"/devices/virtual/tty/tty33","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty33","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=33","DEVNAME=tty33","SEQNUM=964"]}
{"action":"add","devpath":"/devices/virtual/tty/tty34","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty34","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=34","DEVNAME=tty34","SEQNUM=965"]}
{"action":"add","devpath":"/devices/virtual/tty/tty35","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty35","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=35","DEVNAME=tty35","SEQNUM=966"]}
{"action":"add","devpath":"/devices/virtual/tty/tty36","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty36","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=36","DEVNAME=tty36","SEQNUM=967"]}
{"action":"add","devpath":"/devices/virtual/tty/tty37","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty37","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=37","DEVNAME=tty37","SEQNUM=968"]}
{"action":"add","devpath":"/devices/virtual/tty/tty38","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty38","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=38","DEVNAME=tty38","SEQNUM=969"]}
{"action":"add","devpath":"/devices/virtual/tty/tty39","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty39","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=39","DEVNAME=tty39","SEQNUM=970"]}
{"action":"add","devpath":"/devices/virtual/tty/tty4","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty4","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=4","DEVNAME=tty4","SEQNUM=971"]}
{"action":"add","devpath":"/devices/virtual/tty/tty40","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty40","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=40","DEVNAME=tty40","SEQNUM=972"]}
{"action":"add","devpath":"/devices/virtual/tty/tty41","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty41","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=41","DEVNAME=tty41","SEQNUM=973"]}
{"action":"add","devpath":"/devices/virtual/tty/tty42","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty42","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=42","DEVNAME=tty42","SEQNUM=974"]}
{"action":"add","devpath":"/devices/virtual/tty/tty43","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty43","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=43","DEVNAME=tty43","SEQNUM=975"]}
{"action":"add","devpath":"/devices/virtual/tty/tty44","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty44","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=44","DEVNAME=tty44","SEQNUM=976"]}
{"action":"add","devpath":"/devices/virtual/tty/tty45","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty45","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=45","DEVNAME=tty45","SEQNUM=977"]}
{"action":"add","devpath":"/devices/virtual/tty/tty46","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty46","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=46","DEVNAME=tty46","SEQNUM=978"]}
{"action":"add","devpath":"/devices/virtual/tty/tty47","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty47","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=47","DEVNAME=tty47","SEQNUM=979"]}
{"action":"add","devpath":"/devices/virtual/tty/tty48","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty48","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=48","DEVNAME=tty48","SEQNUM=980"]}
{"action":"add","devpath":"/devices/virtual/tty/tty49","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty49","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=49","DEVNAME=tty49","SEQNUM=981"]}
{"action":"add","devpath":"/devices/virtual/tty/tty5","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty5","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=5","DEVNAME=tty5","SEQNUM=982"]}
{"action":"add","devpath":"/devices/virtual/tty/tty50","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty50","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=50","DEVNAME=tty50","SEQNUM=983"]}
{"action":"add","devpath":"/devices/virtual/tty/tty51","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty51","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=51","DEVNAME=tty51","SEQNUM=984"]}
{"action":"add","devpath":"/devices/virtual/tty/tty52","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty52","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=52","DEVNAME=tty52","SEQNUM=985"]}
{"action":"add","devpath":"/devices/virtual/tty/tty53","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty53","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=53","DEVNAME=tty53","SEQNUM=986"]}
{"action":"add","devpath":"/devices/virtual/tty/tty54","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty54","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=54","DEVNAME=tty54","SEQNUM=987"]}
{"action":"add","devpath":"/devices/virtual/tty/tty55","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty55","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=55","DEVNAME=tty55","SEQNUM=988"]}
{"action":"add","devpath":"/devices/virtual/tty/tty56","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty56","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=56","DEVNAME=tty56","SEQNUM=989"]}
{"action":"add","devpath":"/devices/virtual/tty/tty57","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty57","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=57","DEVNAME=tty57","SEQNUM=990"]}
{"action":"add","devpath":"/devices/virtual/tty/tty58","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty58","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=58","DEVNAME=tty58","SEQNUM=991"]}
{"action":"add","devpath":"/devices/virtual/tty/tty59","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty59","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=59","DEVNAME=tty59","SEQNUM=992"]}
{"action":"add","devpath":"/devices/virtual/tty/tty6","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty6","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=6","DEVNAME=tty6","SEQNUM=993"]}
{"action":"add","devpath":"/devices/virtual/tty/tty60","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty60","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=60","DEVNAME=tty60","SEQNUM=994"]}
{"action":"add","devpath":"/devices/virtual/tty/tty61","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty61","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=61","DEVNAME=tty61","SEQNUM=995"]}
{"action":"add","devpath":"/devices/virtual/tty/tty62","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty62","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=62","DEVNAME=tty62","SEQNUM=996"]}
{"action":"add","devpath":"/devices/virtual/tty/tty63","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty63","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=63","DEVNAME=tty63","SEQNUM=997"]}
{"action":"add","devpath":"/devices/virtual/tty/tty7","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty7","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=7","DEVNAME=tty7","SEQNUM=998"]}
{"action":"add","devpath":"/devices/virtual/tty/tty8","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty8","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=8","DEVNAME=tty8","SEQNUM=999"]}
{"action":"add","devpath":"/devices/virtual/tty/tty9","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty9","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=9","DEVNAME=tty9","SEQNUM=1000"]}
{"action":"add","devpath":"/devices/virtual/vc/vcs","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcs","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=0","DEVNAME=vcs","SEQNUM=1001"]}
{"action":"add","devpath":"/devices/virtual/vc/vcs1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcs1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=1","DEVNAME=vcs1","SEQNUM=1002"]}
{"action":"add","devpath":"/devices/virtual/vc/vcsa","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsa","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=128","DEVNAME=vcsa","SEQNUM=1003"]}
{"action":"add","devpath":"/devices/virtual/vc/vcsa1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsa1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=129","DEVNAME=vcsa1","SEQNUM=1004"]}
{"action":"add","devpath":"/devices/virtual/vc/vcsu","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsu","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=64","DEVNAME=vcsu","SEQNUM=1005"]}
{"action":"add","devpath":"/devices/virtual/vc/vcsu1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsu1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=65","DEVNAME=vcsu1","SEQNUM=1006"]}
{"action":"add","devpath":"/devices/virtual/vtconsole/vtcon0","env":["ACTION=add","DEVPATH=/devices/virtual/vtconsole/vtcon0","SUBSYSTEM=vtconsole","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1007"]}
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-auth-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-auth-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1008"]}
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-delete-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-delete-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1009"]}
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-reset-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-reset-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1010"]}
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1011"]}
{"action":"add","devpath":"/devices/virtual/workqueue/scsi_tmf_0","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/scsi_tmf_0","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1012"]}
{"action":"add","devpath":"/devices/virtual/workqueue/scsi_tmf_1","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/scsi_tmf_1","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1013"]}
{"action":"add","devpath":"/devices/virtual/workqueue/writeback","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/writeback","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1014"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1015"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1016"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1017"]}
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1018"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1019"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1020"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1021"]}
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1022"]}
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1023"]}
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","SEQNUM=1024"]}
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1025"]}
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1026"]}
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1027"]}
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","SEQNUM=1028"]}
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1029"]}
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1030"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1031"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1032"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1033"]}
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1034"]}
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1035"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1036"]}
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1037"]}
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1038"]}
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1039"]}
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","SEQNUM=1040"]}
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1041"]}
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1042"]}
+1
View File
@@ -0,0 +1 @@
{"action":"move","devpath":"/devices/virtual/net/_lo","env":["ACTION=move","DEVPATH=/devices/virtual/net/_lo","SUBSYSTEM=net","DEVPATH_OLD=/devices/virtual/net/lo","INTERFACE=_lo","IFINDEX=1","SEQNUM=1043"]}
+8
View File
@@ -0,0 +1,8 @@
{"action":"remove","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=remove","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1044"]}
{"action":"offline","devpath":"/devices/system/cpu/cpu0","env":["ACTION=offline","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1045"]}
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1046"]}
{"action":"online","devpath":"/devices/system/cpu/cpu0","env":["ACTION=online","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1047"]}
{"action":"remove","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=remove","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1048"]}
{"action":"offline","devpath":"/devices/system/cpu/cpu0","env":["ACTION=offline","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1049"]}
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1050"]}
{"action":"online","devpath":"/devices/system/cpu/cpu0","env":["ACTION=online","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1051"]}
+19
View File
@@ -0,0 +1,19 @@
{"action":"add","devpath":"/devices/virtual/misc/loop-control","env":["ACTION=add","DEVPATH=/devices/virtual/misc/loop-control","SUBSYSTEM=misc","MAJOR=10","MINOR=237","DEVNAME=loop-control","SEQNUM=1052"]}
{"action":"add","devpath":"/devices/virtual/bdi/7:0","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:0","SUBSYSTEM=bdi","SEQNUM=1053"]}
{"action":"add","devpath":"/devices/virtual/block/loop0","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=2","SEQNUM=1054"]}
{"action":"add","devpath":"/devices/virtual/bdi/7:1","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:1","SUBSYSTEM=bdi","SEQNUM=1055"]}
{"action":"add","devpath":"/devices/virtual/block/loop1","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop1","SUBSYSTEM=block","MAJOR=7","MINOR=1","DEVNAME=loop1","DEVTYPE=disk","DISKSEQ=3","SEQNUM=1056"]}
{"action":"add","devpath":"/devices/virtual/bdi/7:2","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:2","SUBSYSTEM=bdi","SEQNUM=1057"]}
{"action":"add","devpath":"/devices/virtual/block/loop2","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop2","SUBSYSTEM=block","MAJOR=7","MINOR=2","DEVNAME=loop2","DEVTYPE=disk","DISKSEQ=4","SEQNUM=1058"]}
{"action":"add","devpath":"/devices/virtual/bdi/7:3","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:3","SUBSYSTEM=bdi","SEQNUM=1059"]}
{"action":"add","devpath":"/devices/virtual/block/loop3","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop3","SUBSYSTEM=block","MAJOR=7","MINOR=3","DEVNAME=loop3","DEVTYPE=disk","DISKSEQ=5","SEQNUM=1060"]}
{"action":"add","devpath":"/devices/virtual/bdi/7:4","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:4","SUBSYSTEM=bdi","SEQNUM=1061"]}
{"action":"add","devpath":"/devices/virtual/block/loop4","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop4","SUBSYSTEM=block","MAJOR=7","MINOR=4","DEVNAME=loop4","DEVTYPE=disk","DISKSEQ=6","SEQNUM=1062"]}
{"action":"add","devpath":"/devices/virtual/bdi/7:5","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:5","SUBSYSTEM=bdi","SEQNUM=1063"]}
{"action":"add","devpath":"/devices/virtual/block/loop5","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop5","SUBSYSTEM=block","MAJOR=7","MINOR=5","DEVNAME=loop5","DEVTYPE=disk","DISKSEQ=7","SEQNUM=1064"]}
{"action":"add","devpath":"/devices/virtual/bdi/7:6","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:6","SUBSYSTEM=bdi","SEQNUM=1065"]}
{"action":"add","devpath":"/devices/virtual/block/loop6","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop6","SUBSYSTEM=block","MAJOR=7","MINOR=6","DEVNAME=loop6","DEVTYPE=disk","DISKSEQ=8","SEQNUM=1066"]}
{"action":"add","devpath":"/devices/virtual/bdi/7:7","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:7","SUBSYSTEM=bdi","SEQNUM=1067"]}
{"action":"add","devpath":"/devices/virtual/block/loop7","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop7","SUBSYSTEM=block","MAJOR=7","MINOR=7","DEVNAME=loop7","DEVTYPE=disk","DISKSEQ=9","SEQNUM=1068"]}
{"action":"add","devpath":"/module/loop","env":["ACTION=add","DEVPATH=/module/loop","SUBSYSTEM=module","SEQNUM=1069"]}
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1070"]}
+2
View File
@@ -0,0 +1,2 @@
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1071"]}
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","DISK_MEDIA_CHANGE=1","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1072"]}
+18
View File
@@ -0,0 +1,18 @@
{"action":"remove","devpath":"/devices/virtual/misc/loop-control","env":["ACTION=remove","DEVPATH=/devices/virtual/misc/loop-control","SUBSYSTEM=misc","MAJOR=10","MINOR=237","DEVNAME=loop-control","SEQNUM=1073"]}
{"action":"remove","devpath":"/devices/virtual/bdi/7:0","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:0","SUBSYSTEM=bdi","SEQNUM=1074"]}
{"action":"remove","devpath":"/devices/virtual/block/loop0","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=11","SEQNUM=1075"]}
{"action":"remove","devpath":"/devices/virtual/bdi/7:1","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:1","SUBSYSTEM=bdi","SEQNUM=1076"]}
{"action":"remove","devpath":"/devices/virtual/block/loop1","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop1","SUBSYSTEM=block","MAJOR=7","MINOR=1","DEVNAME=loop1","DEVTYPE=disk","DISKSEQ=3","SEQNUM=1077"]}
{"action":"remove","devpath":"/devices/virtual/bdi/7:2","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:2","SUBSYSTEM=bdi","SEQNUM=1078"]}
{"action":"remove","devpath":"/devices/virtual/block/loop2","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop2","SUBSYSTEM=block","MAJOR=7","MINOR=2","DEVNAME=loop2","DEVTYPE=disk","DISKSEQ=4","SEQNUM=1079"]}
{"action":"remove","devpath":"/devices/virtual/bdi/7:3","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:3","SUBSYSTEM=bdi","SEQNUM=1080"]}
{"action":"remove","devpath":"/devices/virtual/block/loop3","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop3","SUBSYSTEM=block","MAJOR=7","MINOR=3","DEVNAME=loop3","DEVTYPE=disk","DISKSEQ=5","SEQNUM=1081"]}
{"action":"remove","devpath":"/devices/virtual/bdi/7:4","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:4","SUBSYSTEM=bdi","SEQNUM=1082"]}
{"action":"remove","devpath":"/devices/virtual/block/loop4","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop4","SUBSYSTEM=block","MAJOR=7","MINOR=4","DEVNAME=loop4","DEVTYPE=disk","DISKSEQ=6","SEQNUM=1083"]}
{"action":"remove","devpath":"/devices/virtual/bdi/7:5","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:5","SUBSYSTEM=bdi","SEQNUM=1084"]}
{"action":"remove","devpath":"/devices/virtual/block/loop5","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop5","SUBSYSTEM=block","MAJOR=7","MINOR=5","DEVNAME=loop5","DEVTYPE=disk","DISKSEQ=7","SEQNUM=1085"]}
{"action":"remove","devpath":"/devices/virtual/bdi/7:6","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:6","SUBSYSTEM=bdi","SEQNUM=1086"]}
{"action":"remove","devpath":"/devices/virtual/block/loop6","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop6","SUBSYSTEM=block","MAJOR=7","MINOR=6","DEVNAME=loop6","DEVTYPE=disk","DISKSEQ=8","SEQNUM=1087"]}
{"action":"remove","devpath":"/devices/virtual/bdi/7:7","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:7","SUBSYSTEM=bdi","SEQNUM=1088"]}
{"action":"remove","devpath":"/devices/virtual/block/loop7","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop7","SUBSYSTEM=block","MAJOR=7","MINOR=7","DEVNAME=loop7","DEVTYPE=disk","DISKSEQ=9","SEQNUM=1089"]}
{"action":"remove","devpath":"/module/loop","env":["ACTION=remove","DEVPATH=/module/loop","SUBSYSTEM=module","SEQNUM=1090"]}
+1 -1
View File
@@ -40,7 +40,7 @@ func TestTransform(t *testing.T) {
const maxChunkWords = 8 << 10
buf := make([]byte, 2*maxChunkWords*8)
for i := uint64(0); i < 2*maxChunkWords; i++ {
for i := range uint64(2 * maxChunkWords) {
binary.LittleEndian.PutUint64(buf[i*8:], i)
}
if err := lockedfile.Write(path, bytes.NewReader(buf[:8]), 0666); err != nil {
+1 -2
View File
@@ -58,8 +58,7 @@ func (k *outcome) finalise(
supp := make([]string, len(config.Groups))
for i, name := range config.Groups {
if gid, err := k.lookupGroupId(name); err != nil {
var unknownGroupError user.UnknownGroupError
if errors.As(err, &unknownGroupError) {
if unknownGroupError, ok := errors.AsType[user.UnknownGroupError](err); ok {
return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError)
} else {
return &hst.AppError{Step: "look up group by name", Err: err, Msg: err.Error()}
+4 -6
View File
@@ -51,18 +51,16 @@ func (h *Hsu) ID() (int, error) {
cmd.Stderr = os.Stderr // pass through fatal messages
cmd.Env = make([]string, 0)
cmd.Dir = fhs.Root
var (
p []byte
exitError *exec.ExitError
)
var p []byte
const step = "obtain uid from hsu"
if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil {
h.id, h.idErr = strconv.Atoi(string(p))
if h.idErr != nil {
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
}
} else if errors.As(h.idErr, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
} else if exitError, ok := errors.AsType[*exec.ExitError](h.idErr); ok &&
exitError != nil &&
exitError.ExitCode() == 1 {
// hsu prints an error message in this case
h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess}
} else if errors.Is(h.idErr, os.ErrNotExist) {
+3 -3
View File
@@ -328,11 +328,11 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
}
if err := k.sys.Revert((*system.Criteria)(&ec)); err != nil {
var joinError interface {
joinError, ok := errors.AsType[interface {
Unwrap() []error
error
}
if !errors.As(err, &joinError) || joinError == nil {
}](err)
if !ok || joinError == nil {
perror(err, "revert system setup")
} else {
for _, v := range joinError.Unwrap() {
+1
View File
@@ -136,6 +136,7 @@ func TestOutcomeRun(t *testing.T) {
Tmpfs(fhs.AbsDevShm, 0, 01777).
// spRuntimeOp
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/1971"), std.BindWritable).
+2 -2
View File
@@ -390,8 +390,8 @@ func shimEntrypoint(k syscallDispatcher) {
if err := k.containerWait(z); err != nil {
sp.destroy()
var exitError *exec.ExitError
if !errors.As(err, &exitError) {
exitError, ok := errors.AsType[*exec.ExitError](err)
if !ok {
if errors.Is(err, context.Canceled) {
k.exit(hst.ExitCancel)
}
+1
View File
@@ -71,6 +71,7 @@ func TestShimEntrypoint(t *testing.T) {
Tmpfs(fhs.AbsDevShm, 0, 01777).
// spRuntimeOp
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
Bind(m("/tmp/hakurei.10/runtime/9999"), m("/run/user/1000"), std.BindWritable).
+4
View File
@@ -382,6 +382,10 @@ func (p opsAdapter) Overlay(target, state, work *check.Absolute, layers ...*chec
return opsAdapter{p.Ops.Overlay(target, state, work, layers...)}
}
func (p opsAdapter) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
return opsAdapter{p.Ops.OverlayEphemeral(target, layers...)}
}
func (p opsAdapter) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
return opsAdapter{p.Ops.OverlayReadonly(target, layers...)}
}
+3
View File
@@ -113,6 +113,9 @@ func (s *spRuntimeOp) toContainer(state *outcomeStateParams) error {
}
if state.Container.Flags&hst.FCoverRun != 0 {
state.params.Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755)
}
state.params.Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755)
if state.Container.Flags&hst.FShareRuntime != 0 {
_, runtimeDirInst := s.commonPaths(state.outcomeState)
+4
View File
@@ -40,6 +40,7 @@ func TestSpRuntimeOp(t *testing.T) {
// this op configures the container state and does not make calls during toContainer
}, &container.Params{
Ops: new(container.Ops).
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
}, paramsWantEnv(config, map[string]string{
@@ -67,6 +68,7 @@ func TestSpRuntimeOp(t *testing.T) {
// this op configures the container state and does not make calls during toContainer
}, &container.Params{
Ops: new(container.Ops).
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
}, paramsWantEnv(config, map[string]string{
@@ -94,6 +96,7 @@ func TestSpRuntimeOp(t *testing.T) {
// this op configures the container state and does not make calls during toContainer
}, &container.Params{
Ops: new(container.Ops).
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
}, paramsWantEnv(config, map[string]string{
@@ -117,6 +120,7 @@ func TestSpRuntimeOp(t *testing.T) {
// this op configures the container state and does not make calls during toContainer
}, &container.Params{
Ops: new(container.Ops).
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
}, paramsWantEnv(config, map[string]string{
+4 -4
View File
@@ -176,8 +176,8 @@ func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
case reflect.Struct:
data = SPA_TYPE_Struct.append(data)
var err error
for i := 0; i < v.NumField(); i++ {
data, err = marshalValueAppend(data, v.Field(i))
for _, field := range v.Fields() {
data, err = marshalValueAppend(data, field)
if err != nil {
return data, err
}
@@ -370,8 +370,8 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
}
var fieldWireSize Word
for i := 0; i < v.NumField(); i++ {
if err := unmarshalValue(data, v.Field(i), &fieldWireSize); err != nil {
for _, field := range v.Fields() {
if err := unmarshalValue(data, field, &fieldWireSize); err != nil {
return err
}
// bounds check completed in successful call to unmarshalValue
+405
View File
@@ -0,0 +1,405 @@
package pkg
import (
"crypto/sha512"
"encoding/binary"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"unsafe"
"hakurei.app/check"
)
/*
| mode uint32 | path_sz uint32 |
| data_sz uint64 |
| path string |
| data []byte |
*/
// An ArchiveHeader represents a single header in an archive.
type ArchiveHeader struct {
Mode fs.FileMode // file mode bits
Path string // pathname of the file
Size uint64 // size of data segment
}
// Writer implements sequential writing of an archive. [Writer.WriteHeader]
// begins a new file with the provided [ArchiveHeader], and then Writer can be
// treated as an [io.Writer] to supply that file's data.
//
// It is the caller's responsibility to write entries in lexical order.
type Writer struct {
// Underlying writer.
w io.Writer
// Current header.
h ArchiveHeader
// Fixed-size header segment.
buf [wordSize * 2]byte
// Current position in data segment.
n uint64
}
// NewWriter returns the address of a new [Writer] writing to w.
func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
var zero [wordSize]byte
// padSize returns the padding size for aligning sz.
func padSize[T int | uint64](sz T) T {
return (wordSize - (sz)%wordSize) % wordSize
}
// flush concludes writing to the current file and writes padding.
func (aw *Writer) flush() error {
if aw.h.Size > aw.n {
return fmt.Errorf("missed writing %d bytes", aw.h.Size-aw.n)
} else if aw.h.Size < aw.n {
return fmt.Errorf("wrote %d bytes beyond end of file", aw.n-aw.h.Size)
}
if psz := padSize(aw.h.Size); psz != 0 {
if _, err := aw.w.Write(zero[:psz]); err != nil {
return err
}
}
aw.n = 0
return nil
}
// WriteHeader writes h and begins accepting its corresponding file.
func (aw *Writer) WriteHeader(h *ArchiveHeader) error {
if err := aw.flush(); err != nil {
return err
}
aw.h = *h
binary.LittleEndian.PutUint32(aw.buf[:], uint32(aw.h.Mode))
binary.LittleEndian.PutUint32(aw.buf[wordSize/2:], uint32(len(aw.h.Path)))
binary.LittleEndian.PutUint64(aw.buf[wordSize:], aw.h.Size)
if _, err := aw.w.Write(aw.buf[:]); err != nil {
return err
} else if _, err = aw.w.Write(
unsafe.Slice(unsafe.StringData(aw.h.Path), len(aw.h.Path)),
); err != nil {
return err
} else if psz := padSize(len(aw.h.Path)); psz != 0 {
if _, err = aw.w.Write(zero[:psz]); err != nil {
return err
}
}
return nil
}
// Write writes p to the underlying writer and records the new position. Invalid
// positions are reported by WriteHeader and Close.
func (aw *Writer) Write(p []byte) (n int, err error) {
n, err = aw.w.Write(p)
aw.n += uint64(n)
return
}
// Close concludes writing to the archive stream.
func (aw *Writer) Close() (err error) {
err = aw.flush()
aw.w = nil
return
}
// ErrInsecurePath is returned by [FlatEntry.Decode] if validation is requested
// and a nonlocal path is encountered in the stream.
var ErrInsecurePath = errors.New("insecure file path")
// Reader implements sequential reading of an archive. [Reader.Next] advances to
// the next file in the archive (including the first), and then Reader can be
// treated as an [io.Reader] to access the file's data.
type Reader struct {
// Underlying reader.
r io.Reader
// Fixed-size header segment.
buf [wordSize * 2]byte
// Remaining bytes in current data segment.
n, pad uint64
}
// NewReader returns the address of a new [Reader] reading from r.
func NewReader(r io.Reader) *Reader { return &Reader{r: r} }
// Next advances ar to the next entry. Remaining bytes of the current data
// segment are discarded. Advancing beyond the final entry returns [io.EOF].
func (ar *Reader) Next() (*ArchiveHeader, error) {
if dsz := int64(ar.n + ar.pad); dsz > 0 {
if n, err := io.CopyN(io.Discard, ar.r, dsz); err != nil {
if errors.Is(err, io.EOF) && n != dsz {
err = io.ErrUnexpectedEOF
}
return nil, err
}
}
if _, err := io.ReadFull(ar.r, ar.buf[:]); err != nil {
return nil, err
}
h := ArchiveHeader{
Mode: fs.FileMode(binary.LittleEndian.Uint32(ar.buf[:])),
Size: binary.LittleEndian.Uint64(ar.buf[wordSize:]),
}
pathSize := int(binary.LittleEndian.Uint32(ar.buf[wordSize/2:]))
pPathSize := alignSize(pathSize)
buf := make([]byte, pPathSize)
if _, err := io.ReadFull(ar.r, buf); err != nil {
if errors.Is(err, io.EOF) {
err = io.ErrUnexpectedEOF
}
return nil, err
}
h.Path = unsafe.String(unsafe.SliceData(buf), pathSize)
if !filepath.IsLocal(h.Path) {
return &h, ErrInsecurePath
}
ar.n = h.Size
ar.pad = padSize(h.Size)
return &h, nil
}
// Read implements [io.Reader] for the data segment of the current entry.
func (ar *Reader) Read(p []byte) (n int, err error) {
if uint64(len(p)) > ar.n {
p = p[:ar.n]
}
if len(p) > 0 {
n, err = ar.r.Read(p)
ar.n -= uint64(n)
}
switch err {
case io.EOF:
if ar.n > 0 {
return n, io.ErrUnexpectedEOF
}
case nil:
if ar.n == 0 {
return n, io.EOF
}
}
return
}
// Write writes a deterministic representation of the contents of fsys to w.
// The resulting data can be hashed to produce a deterministic checksum for the
// directory.
func Write(fsys fs.FS, root string, w io.Writer) error {
aw := NewWriter(w)
if err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
var fi fs.FileInfo
fi, err = d.Info()
if err != nil {
return err
}
h := ArchiveHeader{
Path: path,
Mode: fi.Mode(),
}
if h.Mode.IsRegular() {
h.Size = uint64(fi.Size())
if err = aw.WriteHeader(&h); err != nil {
return err
}
var r fs.File
r, err = fsys.Open(path)
if err != nil {
return err
}
_, err = io.Copy(aw, r)
if _err := r.Close(); err == nil {
err = _err
}
return err
} else if h.Mode&fs.ModeSymlink != 0 {
var newpath string
if newpath, err = fs.ReadLink(fsys, path); err != nil {
return err
}
h.Size = uint64(len(newpath))
if err = aw.WriteHeader(&h); err != nil {
return err
}
_, err = aw.Write(unsafe.Slice(unsafe.StringData(newpath), len(newpath)))
return err
} else if !h.Mode.IsDir() {
return InvalidFileModeError(h.Mode)
}
return aw.WriteHeader(&h)
}); err != nil {
return err
}
return aw.Close()
}
// SumFS saves checksum of the archive of fsys to the value pointed to by buf.
func SumFS(buf *Checksum, fsys fs.FS, root string) error {
h := sha512.New384()
if err := Write(fsys, root, h); err != nil {
return err
}
h.Sum(buf[:0])
return nil
}
// SumDir saves checksum of the archive of directory at pathname to the value
// pointed to by buf.
func SumDir(buf *Checksum, pathname *check.Absolute) error {
return SumFS(buf, os.DirFS(pathname.String()), ".")
}
// archiveArtifact is an [Artifact] unpacking an archive supported by [Reader]
// backed by a [FileArtifact].
type archiveArtifact struct {
// Caller-supplied backing archive.
f Artifact
}
// NewArchive returns a new [Artifact] backed by the supplied [Artifact]. The
// source [Artifact] must be a [FileArtifact] and produce a stream compatible
// with [Reader].
func NewArchive(a Artifact) Artifact {
return archiveArtifact{a}
}
// Kind returns the hardcoded [Kind] constant.
func (archiveArtifact) Kind() Kind { return KindArchive }
// Params is a noop.
func (archiveArtifact) Params(*IContext) {}
func init() {
register(KindArchive, func(r *IRReader) Artifact {
a := NewArchive(r.Next())
if _, ok := r.Finalise(); ok {
panic(ErrUnexpectedChecksum)
}
return a
})
}
// Dependencies returns a slice containing the backing file.
func (a archiveArtifact) Dependencies() []Artifact {
return []Artifact{a.f}
}
// IsExclusive returns false: [Reader] is fully sequential.
func (archiveArtifact) IsExclusive() bool { return false }
// Cure cures the [Artifact], producing a directory located at work.
func (a archiveArtifact) Cure(t *TContext) (err error) {
var r io.ReadCloser
if r, err = t.Open(a.f); err != nil {
return
}
defer func() {
closeErr := r.Close()
if err == nil {
err = closeErr
}
}()
type dirTargetPerm struct {
path string
mode fs.FileMode
}
var madeDirectories []dirTargetPerm
if err = os.MkdirAll(t.GetWorkDir().String(), 0700); err != nil {
return
}
var root *os.Root
if root, err = os.OpenRoot(t.GetWorkDir().String()); err != nil {
return
}
defer func() {
closeErr := root.Close()
if err == nil {
err = closeErr
}
}()
var header *ArchiveHeader
ar := NewReader(r)
for header, err = ar.Next(); err == nil; header, err = ar.Next() {
if header.Mode.IsRegular() {
var f *os.File
if f, err = root.OpenFile(
header.Path,
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
header.Mode.Perm(),
); err != nil {
return
}
if _, err = io.Copy(f, ar); err != nil {
_ = f.Close()
return
} else if err = f.Close(); err != nil {
return
}
} else if header.Mode&fs.ModeSymlink != 0 {
var p []byte
if p, err = io.ReadAll(ar); err != nil {
return
}
if err = root.Symlink(
unsafe.String(unsafe.SliceData(p), len(p)),
header.Path,
); err != nil {
return
}
} else if header.Mode.IsDir() {
if header.Path == "." {
continue
}
madeDirectories = append(madeDirectories, dirTargetPerm{
path: header.Path,
mode: header.Mode,
})
if err = root.Mkdir(header.Path, 0700); err != nil {
return
}
} else {
return InvalidFileModeError(header.Mode)
}
}
if errors.Is(err, io.EOF) {
err = nil
}
if err == nil {
for _, e := range madeDirectories {
if err = root.Chmod(e.path, e.mode.Perm()); err != nil {
return
}
}
} else {
return
}
return
}
+240
View File
@@ -0,0 +1,240 @@
package pkg_test
import (
"bytes"
"io"
"io/fs"
"maps"
"reflect"
"testing"
"testing/fstest"
"unsafe"
"hakurei.app/check"
"hakurei.app/internal/pkg"
)
func TestArchive(t *testing.T) {
t.Parallel()
type entry struct {
path string
mode fs.FileMode
data string
}
testCases := []struct {
name string
fsys fs.FS
entries []entry
sum pkg.Checksum
err error
}{
{"bad type", fstest.MapFS{
".": {Mode: fs.ModeDir | 0700},
"invalid": {Mode: fs.ModeCharDevice | 0400},
}, nil, pkg.Checksum{}, pkg.InvalidFileModeError(
fs.ModeCharDevice | 0400,
)},
{"coldboot", fstest.MapFS{
".": {Mode: fs.ModeDir | 0700},
"devices": {Mode: fs.ModeDir | 0700},
"devices/uevent": {Mode: 0600, Data: []byte("add")},
"devices/empty": {Mode: fs.ModeDir | 0700},
"devices/sub": {Mode: fs.ModeDir | 0700},
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
"block": {Mode: fs.ModeDir | 0700},
"block/uevent": {Mode: 0600},
}, []entry{
{".", fs.ModeDir | 0700, ""},
{"block", fs.ModeDir | 0700, ""},
{"block/uevent", 0600, ""},
{"devices", fs.ModeDir | 0700, ""},
{"devices/empty", fs.ModeDir | 0700, ""},
{"devices/sub", fs.ModeDir | 0700, ""},
{"devices/sub/uevent", 0600, "add"},
{"devices/uevent", 0600, "add"},
}, pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8"), nil},
{"empty", fstest.MapFS{
".": {Mode: fs.ModeDir | 0700},
"checksum": {Mode: fs.ModeDir | 0700},
"identifier": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
}, []entry{
{".", fs.ModeDir | 0700, ""},
{"checksum", fs.ModeDir | 0700, ""},
{"identifier", fs.ModeDir | 0700, ""},
{"work", fs.ModeDir | 0700, ""},
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C"), nil},
{"sample directory step garbage", fstest.MapFS{
".": {Mode: fs.ModeDir | 0500},
"lib": {Mode: fs.ModeDir | 0500},
"lib/check": {Mode: 0400},
"lib/pkgconfig": {Mode: fs.ModeDir | 0500},
}, []entry{
{".", fs.ModeDir | 0500, ""},
{"lib", fs.ModeDir | 0500, ""},
{"lib/check", 0400, ""},
{"lib/pkgconfig", fs.ModeDir | 0500, ""},
}, pkg.MustDecode("CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT"), nil},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
t.Run("roundtrip", func(t *testing.T) {
t.Parallel()
var buf bytes.Buffer
if err := pkg.Write(
tc.fsys,
".",
&buf,
); !reflect.DeepEqual(err, tc.err) {
t.Fatalf("Flatten: error = %v, want %v", err, tc.err)
} else if tc.err != nil {
return
}
r := pkg.NewReader(bytes.NewReader(buf.Bytes()))
var got []entry
for {
h, err := r.Next()
if err != nil {
if err == io.EOF {
break
}
t.Fatalf("Next: error = %v", err)
}
var data []byte
if data, err = io.ReadAll(r); err != nil {
t.Fatalf("Read: error = %v", err)
}
got = append(got, entry{
path: h.Path,
mode: h.Mode,
data: unsafe.String(unsafe.SliceData(data), len(data)),
})
}
if !reflect.DeepEqual(got, tc.entries) {
t.Fatalf("Reader: %#v, want %#v", got, tc.entries)
}
})
if tc.err != nil {
return
}
t.Run("hash", func(t *testing.T) {
t.Parallel()
var got pkg.Checksum
if err := pkg.SumFS(&got, tc.fsys, "."); err != nil {
t.Fatalf("SumFS: error = %v", err)
} else if got != tc.sum {
t.Fatalf("SumFS: %v", &pkg.ChecksumMismatchError{
Got: got,
Want: tc.sum,
})
}
})
})
}
}
var archiveTestdata = fstest.MapFS{
".": {Mode: fs.ModeDir | 0700},
"devices": {Mode: fs.ModeDir | 0700},
"devices/uevent": {Mode: 0600, Data: []byte("add")},
"devices/empty": {Mode: fs.ModeDir | 0700},
"devices/sub": {Mode: fs.ModeDir | 0700},
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
"block": {Mode: fs.ModeDir | 0700},
"block/uevent": {Mode: 0600},
}
func TestArchiveArtifact(t *testing.T) {
t.Parallel()
want := maps.Clone(archiveTestdata)
want["."].Mode = fs.ModeDir | 0500
checkWithCache(t, []cacheTestCase{
{"unpack", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
var buf bytes.Buffer
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
t.Fatal(err)
}
cureMany(t, c, []cureStep{
{"sample", pkg.NewArchive(
pkg.NewFile("", buf.Bytes()),
), ignorePathname, expectsFS(want), nil},
})
}, expectsFS{
".": {Mode: fs.ModeDir | 0700},
"checksum": {Mode: fs.ModeDir | 0700},
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F": {Mode: fs.ModeDir | 0500},
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block": {Mode: fs.ModeDir | 0700},
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block/uevent": {Mode: 0600},
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices": {Mode: fs.ModeDir | 0700},
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/empty": {Mode: fs.ModeDir | 0700},
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub": {Mode: fs.ModeDir | 0700},
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/uevent": {Mode: 0600, Data: []byte("add")},
"identifier": {Mode: fs.ModeDir | 0700},
"identifier/3oYyAbRJ_we7AgWo1BRcRcnxXFk3mAQ0Qui2nGQMi8GIJNJQtvUC6P2IeoA5mbjD": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F")},
"substitute": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
}},
})
}
func BenchmarkArchiveRead(b *testing.B) {
var buf bytes.Buffer
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
b.Fatal(err)
}
testdata := buf.Bytes()
for b.Loop() {
r := pkg.NewReader(bytes.NewReader(testdata))
for {
_, err := r.Next()
if err != nil {
if err == io.EOF {
break
}
b.Fatal(err)
}
}
}
}
func BenchmarkArchiveWrite(b *testing.B) {
for b.Loop() {
if err := pkg.Write(archiveTestdata, ".", io.Discard); err != nil {
b.Fatal(err)
}
}
}
+152
View File
@@ -0,0 +1,152 @@
package pkg
import (
"errors"
"os"
"unique"
)
// Clean destroys checksum backing entries without any identifier or substitute
// entry referring to it. If at least one keep [Artifact] is specified,
// identifier and substitute entries not kept alive by them are destroyed first.
func (c *Cache) Clean(dry, inputs bool, keep ...Artifact) (
[]unique.Handle[ID],
[]unique.Handle[Checksum],
error,
) {
c.identMu.Lock()
defer c.identMu.Unlock()
c.checksumMu.Lock()
defer c.checksumMu.Unlock()
dents, err := os.ReadDir(c.base.Append(dirChecksum).String())
if err != nil {
return nil, nil, err
}
checksums := make(map[unique.Handle[Checksum]]string, len(dents))
var buf Checksum
for _, dent := range dents {
name := dent.Name()
if err = Decode(&buf, name); err != nil {
return nil, nil, err
}
checksums[unique.Make(buf)] = name
}
type identPair struct {
id unique.Handle[ID]
name string
}
dents, err = os.ReadDir(c.base.Append(dirIdentifier).String())
if err != nil {
return nil, nil, err
}
keepIdents := make(map[unique.Handle[ID]]struct{})
if inputs {
for _, id := range Inputs((*Collect)(&keep)) {
keepIdents[id] = struct{}{}
}
} else {
for _, a := range keep {
keepIdents[c.Ident(a)] = struct{}{}
}
}
idents := make([]identPair, 0, len(dents))
for _, dent := range dents {
name := dent.Name()
if err = Decode(&buf, name); err != nil {
return nil, nil, err
}
id := unique.Make(ID(buf))
if _, ok := keepIdents[id]; len(keep) == 0 || ok {
if err = readlinkChecksum(c.base.Append(
dirIdentifier,
name,
), &buf); err != nil {
return nil, nil, err
}
delete(checksums, unique.Make(buf))
continue
}
c.msg.Verbosef("arranging for destruction of %s...", name)
idents = append(idents, identPair{id, name})
}
destroyedIdents := make([]unique.Handle[ID], 0, len(idents))
for _, pair := range idents {
if !dry {
if err = os.Remove(c.base.Append(
dirStatus,
pair.name,
).String()); err != nil && !errors.Is(err, os.ErrNotExist) {
return destroyedIdents, nil, err
}
if err = os.Remove(c.base.Append(
dirIdentifier,
pair.name,
).String()); err != nil {
return destroyedIdents, nil, err
}
}
destroyedIdents = append(destroyedIdents, pair.id)
}
destroyedChecksums := make([]unique.Handle[Checksum], 0, len(checksums))
for checksum, name := range checksums {
if err = c.parent.Err(); err != nil {
return destroyedIdents, destroyedChecksums, err
}
c.msg.Verbosef("destroying checksum %s...", name)
if !dry {
if err = errors.Join(removeAll(c.base.Append(
dirChecksum,
name,
))); err != nil {
return destroyedIdents, destroyedChecksums, err
}
}
destroyedChecksums = append(destroyedChecksums, checksum)
}
dents, err = os.ReadDir(c.base.Append(dirSubstitute).String())
if err != nil {
return destroyedIdents, destroyedChecksums, err
}
for _, dent := range dents {
name := dent.Name()
if err = readlinkChecksum(c.base.Append(
dirSubstitute,
name,
), &buf); err != nil {
return destroyedIdents, destroyedChecksums, err
}
if _, ok := checksums[unique.Make(buf)]; !ok {
continue
}
c.msg.Verbosef("destroying substitute %s...", name)
if !dry {
if err = os.Remove(c.base.Append(
dirStatus,
name,
).String()); err != nil && !errors.Is(err, os.ErrNotExist) {
return destroyedIdents, nil, err
}
if err = os.Remove(c.base.Append(
dirSubstitute,
name,
).String()); err != nil {
return destroyedIdents, destroyedChecksums, err
}
}
}
return destroyedIdents, destroyedChecksums, nil
}
+293
View File
@@ -0,0 +1,293 @@
package pkg_test
import (
"bytes"
"crypto/sha512"
"io/fs"
"log"
"os"
"slices"
"strings"
"testing"
"unique"
"hakurei.app/internal/pkg"
"hakurei.app/message"
)
// formatHandles returns a user-facing string representing h.
func formatHandles[T pkg.ID | pkg.Checksum](handles ...unique.Handle[T]) string {
var buf strings.Builder
for _, h := range handles {
buf.WriteString(pkg.Encode(pkg.Checksum(h.Value())))
buf.WriteString(", ")
}
return strings.TrimSuffix(buf.String(), ", ")
}
func TestClean(t *testing.T) {
t.Parallel()
ic := pkg.NewIR()
testCases := []struct {
name string
a []pkg.Artifact
keep []pkg.Artifact
inputs bool
want expectsFS
wantIdents []unique.Handle[pkg.ID]
wantChecksums []unique.Handle[pkg.Checksum]
}{
{"simple", []pkg.Artifact{
pkg.NewFile("file", nil),
}, nil, false, expectsFS{
".": {Mode: fs.ModeDir | 0700},
"checksum": {Mode: fs.ModeDir | 0700},
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400},
"identifier": {Mode: fs.ModeDir | 0700},
"identifier/pPRjw2XYgjB5k8dYedwxTBMgHh4_v2JM_G2Vd-skQbAGOOgPsl3CGSUbEF7om_MO": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
"lock": {Mode: 0644},
"variant": {Mode: 0400},
"status": {Mode: fs.ModeDir | 0700},
"substitute": {Mode: fs.ModeDir | 0700},
"fault": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
}, nil, nil},
{"keep", []pkg.Artifact{
pkg.NewFile("removed-file", []byte("removed file")),
}, []pkg.Artifact{
pkg.NewFile("file", []byte("\xfd")),
}, false, expectsFS{
".": {Mode: fs.ModeDir | 0700},
"checksum": {Mode: fs.ModeDir | 0700},
"checksum/KgZ-FjbGuU-XP2QEHInpgv-2Zn0cTH5NqFMgTU0XrSdKmSwyC-3baVs1BMCP5spk": {Mode: 0400, Data: []byte("\xfd")},
"identifier": {Mode: fs.ModeDir | 0700},
"identifier/FMwSBYw22KqM8jZryfY2ChHXpLuVDdWYyNOYdHvIVYk8ujY6UnGRm5brr2sTTfpD": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/KgZ-FjbGuU-XP2QEHInpgv-2Zn0cTH5NqFMgTU0XrSdKmSwyC-3baVs1BMCP5spk")},
"lock": {Mode: 0644},
"variant": {Mode: 0400},
"status": {Mode: fs.ModeDir | 0700},
"substitute": {Mode: fs.ModeDir | 0700},
"fault": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
}, []unique.Handle[pkg.ID]{
ic.Ident(pkg.NewFile("removed-file", []byte("removed file"))),
}, []unique.Handle[pkg.Checksum]{
unique.Make(sha512.Sum384([]byte("removed file"))),
}},
{"inputs anchored substitute", []pkg.Artifact{
&stubArtifactF{
kind: pkg.KindExec,
params: []byte("destroyed"),
deps: []pkg.Artifact{
pkg.NewFile("destroyed-input", []byte("destroyed")),
},
cure: func(f *pkg.FContext) error {
p := f.GetWorkDir()
if err := os.MkdirAll(p.String(), 0755); err != nil {
return err
}
return os.WriteFile(p.Append("result").String(), nil, 0444)
},
},
}, []pkg.Artifact{
&stubArtifactF{
kind: pkg.KindExec,
params: []byte("kept"),
deps: []pkg.Artifact{
pkg.NewFile("kept-input", []byte("kept")),
},
cure: func(f *pkg.FContext) error {
p := f.GetWorkDir()
if err := os.MkdirAll(p.String(), 0755); err != nil {
return err
}
return os.WriteFile(p.Append("result").String(), nil, 0444)
},
},
}, true, expectsFS{
".": {Mode: fs.ModeDir | 0700},
"checksum": {Mode: fs.ModeDir | 0700},
"checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv": {Mode: 0400, Data: []byte("kept")},
"checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE": {Mode: fs.ModeDir | 0500},
"checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE/result": {Mode: 0444},
"identifier": {Mode: fs.ModeDir | 0700},
"identifier/Ef8KX6s_rS_nLgze0rj90zKyrGAvOnzyU0DL7nrYQWEG_f4a9pmUKI6HBEMD8AE8": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv")},
"identifier/xoIGLemzLF227e-w_AJcf_1Sgqh2gs3KFgqvOIWUQE-9P_y2vHBMBytL4GRGQqTb": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
"lock": {Mode: 0644},
"variant": {Mode: 0400},
"status": {Mode: fs.ModeDir | 0700},
"substitute": {Mode: fs.ModeDir | 0700},
"substitute/4bjS-QjGcSV4nth-W6Vg3-wolKmKgiq4Ld2oRIWcOfy6Wi41XXLAWPoo8FcDx6BH": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
"substitute/dzO8FEY9lu4hwRT6BfRZOX-uYGsC_5XH4jEJ7sJyThcmG9J_w1ArOAaUCGfL8wAM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
"fault": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
}, []unique.Handle[pkg.ID]{
ic.Ident(pkg.NewFile("destroyed-input", []byte("destroyed"))),
ic.Ident(&stubArtifactF{
kind: pkg.KindExec,
params: []byte("destroyed"),
deps: []pkg.Artifact{
pkg.NewFile("destroyed-input", []byte("destroyed")),
},
}),
}, []unique.Handle[pkg.Checksum]{
unique.Make(sha512.Sum384([]byte("destroyed"))),
}},
{"inputs", []pkg.Artifact{
&stubArtifactF{
kind: pkg.KindExec,
params: []byte("destroyed"),
deps: []pkg.Artifact{
pkg.NewFile("destroyed-input", []byte("destroyed")),
},
cure: func(f *pkg.FContext) error {
if w, err := f.GetStatusWriter(); err != nil {
return err
} else if _, err = w.Write([]byte("destroyed")); err != nil {
return err
}
p := f.GetWorkDir()
if err := os.MkdirAll(p.String(), 0755); err != nil {
return err
}
return os.WriteFile(p.Append("result").String(), nil, 0444)
},
},
}, []pkg.Artifact{
&stubArtifactF{
kind: pkg.KindExec,
params: []byte("kept"),
deps: []pkg.Artifact{
pkg.NewFile("kept-input", []byte("kept")),
},
cure: func(f *pkg.FContext) error {
if w, err := f.GetStatusWriter(); err != nil {
return err
} else if _, err = w.Write([]byte("kept")); err != nil {
return err
}
p := f.GetWorkDir()
if err := os.MkdirAll(p.String(), 0755); err != nil {
return err
}
return os.WriteFile(p.Append("result").String(), []byte{0}, 0444)
},
},
}, true, expectsFS{
".": {Mode: fs.ModeDir | 0700},
"checksum": {Mode: fs.ModeDir | 0700},
"checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W": {Mode: fs.ModeDir | 0500},
"checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W/result": {Mode: 0444, Data: []byte("\x00")},
"checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv": {Mode: 0400, Data: []byte("kept")},
"identifier": {Mode: fs.ModeDir | 0700},
"identifier/Ef8KX6s_rS_nLgze0rj90zKyrGAvOnzyU0DL7nrYQWEG_f4a9pmUKI6HBEMD8AE8": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv")},
"identifier/xoIGLemzLF227e-w_AJcf_1Sgqh2gs3KFgqvOIWUQE-9P_y2vHBMBytL4GRGQqTb": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")},
"lock": {Mode: 0644},
"variant": {Mode: 0400},
"status": {Mode: fs.ModeDir | 0700},
"status/4bjS-QjGcSV4nth-W6Vg3-wolKmKgiq4Ld2oRIWcOfy6Wi41XXLAWPoo8FcDx6BH": {Mode: 0400, Data: []byte(statusHeader + "kept")},
"status/xoIGLemzLF227e-w_AJcf_1Sgqh2gs3KFgqvOIWUQE-9P_y2vHBMBytL4GRGQqTb": {Mode: 0400, Data: []byte(statusHeader + "kept")},
"substitute": {Mode: fs.ModeDir | 0700},
"substitute/4bjS-QjGcSV4nth-W6Vg3-wolKmKgiq4Ld2oRIWcOfy6Wi41XXLAWPoo8FcDx6BH": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")},
"fault": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
}, []unique.Handle[pkg.ID]{
ic.Ident(pkg.NewFile("destroyed-input", []byte("destroyed"))),
ic.Ident(&stubArtifactF{
kind: pkg.KindExec,
params: []byte("destroyed"),
deps: []pkg.Artifact{
pkg.NewFile("destroyed-input", []byte("destroyed")),
},
}),
}, []unique.Handle[pkg.Checksum]{
unique.Make(expectsFS{
".": {Mode: fs.ModeDir | 0500},
"result": {Mode: 0444},
}.hash()),
unique.Make(sha512.Sum384([]byte("destroyed"))),
}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
base := makeBase(t)
msg := message.New(log.New(os.Stderr, "clean: ", 0))
msg.SwapVerbose(testing.Verbose())
c, err := pkg.Open(t.Context(), msg, 0, 0, 0, base)
if err != nil {
t.Fatal(err)
}
t.Cleanup(c.Close)
all := pkg.Collect(slices.Concat(tc.a, tc.keep))
if _, _, err = c.Cure(&all); !pkg.IsCollected(err) {
t.Fatal(err)
}
var (
idents []unique.Handle[pkg.ID]
checksums []unique.Handle[pkg.Checksum]
)
idents, checksums, err = c.Clean(false, tc.inputs, tc.keep...)
if err != nil {
t.Fatalf("Clean: error = %v", err)
}
var buf [2]pkg.Checksum
slices.SortFunc(idents, func(a, b unique.Handle[pkg.ID]) int {
buf[0], buf[1] = a.Value(), b.Value()
return bytes.Compare(buf[0][:], buf[1][:])
})
slices.SortFunc(checksums, func(a, b unique.Handle[pkg.Checksum]) int {
buf[0], buf[1] = a.Value(), b.Value()
return bytes.Compare(buf[0][:], buf[1][:])
})
if !slices.Equal(idents, tc.wantIdents) {
t.Errorf(
"Clean: idents = %s, want %s",
formatHandles(idents...), formatHandles(tc.wantIdents...),
)
}
if !slices.Equal(checksums, tc.wantChecksums) {
t.Errorf(
"Clean: checksums = %s, want %s",
formatHandles(checksums...), formatHandles(tc.wantChecksums...),
)
}
want := tc.want.hash()
var checksum pkg.Checksum
if err = pkg.SumDir(&checksum, base); err != nil {
t.Fatalf("SumDir: error = %v", err)
} else if checksum != want {
t.Error(expectsFrom(base.String()))
}
})
}
}
+119
View File
@@ -0,0 +1,119 @@
package pkg
import (
"compress/bzip2"
"compress/gzip"
"fmt"
"io"
"os"
)
const (
// Gzip denotes a stream compressed via [gzip].
Gzip = iota
// Bzip2 denotes a stream compressed via [bzip2].
Bzip2
)
// A decompressArtifact is a [FileArtifact] decompressing a backing
// [FileArtifact] stream.
type decompressArtifact struct {
// Caller-supplied backing stream.
f Artifact
// Compression on top of the stream.
compress uint32
}
var _ FileArtifact = new(decompressArtifact)
// decompressArtifactNamed embeds decompressArtifact for a [fmt.Stringer] stream.
type decompressArtifactNamed struct {
decompressArtifact
// Copied from decompressArtifact.f.
name string
}
var _ fmt.Stringer = new(decompressArtifactNamed)
// NewDecompress returns a [FileArtifact] decompressing the supplied [Artifact].
func NewDecompress(a Artifact, compress uint32) FileArtifact {
da := decompressArtifact{a, compress}
if s, ok := a.(fmt.Stringer); ok {
if name := s.String(); name != "" {
return &decompressArtifactNamed{da, name}
}
}
return &da
}
// String returns the name of the underlying [Artifact] prefixed with decompress.
func (a *decompressArtifactNamed) String() string { return "decompress-" + a.name }
// Kind returns the hardcoded [Kind] constant.
func (a *decompressArtifact) Kind() Kind { return KindDecompress }
// Params writes value of compression enum.
func (a *decompressArtifact) Params(ctx *IContext) { ctx.WriteUint32(a.compress) }
func init() {
register(KindDecompress, func(r *IRReader) Artifact {
a := NewDecompress(r.Next(), r.ReadUint32())
if _, ok := r.Finalise(); ok {
panic(ErrUnexpectedChecksum)
}
return a
})
}
// Dependencies returns a slice containing the backing file.
func (a *decompressArtifact) Dependencies() []Artifact {
return []Artifact{a.f}
}
// IsExclusive returns false: decompressor is fully sequential.
func (a *decompressArtifact) IsExclusive() bool { return false }
// compoundCloser is an [io.ReadCloser] with an additional [io.Closer] attached.
type compoundCloser struct {
io.ReadCloser
c io.Closer
}
// Close closes [io.ReadCloser] and the additional [io.Closer]. It returns the
// non-nil error returned by the underlying [io.ReadCloser], otherwise it
// returns the error returned by the additional [io.Closer].
func (c compoundCloser) Close() error {
err := c.ReadCloser.Close()
if _err := c.c.Close(); err == nil {
err = _err
}
return err
}
// Cure returns a decompressor [io.ReadCloser].
func (a *decompressArtifact) Cure(r *RContext) (io.ReadCloser, error) {
sr, err := r.Open(a.f)
if err != nil {
return nil, err
}
br := r.cache.getReaderRC(sr)
var dr io.ReadCloser
switch a.compress {
case Gzip:
if dr, err = gzip.NewReader(br); err != nil {
_ = br.Close()
return nil, err
}
return compoundCloser{dr, br}, nil
case Bzip2:
return struct {
io.Reader
io.Closer
}{bzip2.NewReader(br), br}, nil
default:
return nil, os.ErrInvalid
}
}
+70
View File
@@ -0,0 +1,70 @@
package pkg_test
import (
"bytes"
"compress/gzip"
"crypto/sha512"
"io/fs"
"net/http"
"testing"
"testing/fstest"
"hakurei.app/check"
"hakurei.app/internal/pkg"
)
func TestDecompress(t *testing.T) {
t.Parallel()
var buf bytes.Buffer
gw := gzip.NewWriter(&buf)
if _, err := gw.Write([]byte{0}); err != nil {
t.Fatal(err)
} else if err = gw.Close(); err != nil {
t.Fatal(err)
}
testdata := buf.String()
var transport http.Transport
client := http.Client{Transport: &transport}
transport.RegisterProtocol("file", http.NewFileTransportFS(fstest.MapFS{
"testdata": {Data: []byte(testdata), Mode: 0400},
}))
testdataChecksum := func() pkg.Checksum {
h := sha512.New384()
h.Write([]byte(testdata))
return (pkg.Checksum)(h.Sum(nil))
}()
checkWithCache(t, []cacheTestCase{
{"decompress", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
cureMany(t, c, []cureStep{
{"close", pkg.NewDecompress(pkg.NewHTTPGet(
&client,
"file:///testdata",
pkg.Checksum{0xfd},
), pkg.Gzip), nil, nil, &pkg.ChecksumMismatchError{
Got: testdataChecksum,
Want: pkg.Checksum{0xfd},
}},
{"gzip", pkg.NewDecompress(pkg.NewHTTPGet(
&client,
"file:///testdata",
testdataChecksum,
), pkg.Gzip), ignorePathname, expectsChecksum(sha512.Sum384([]byte{0})), nil},
})
}, expectsFS{
".": {Mode: fs.ModeDir | 0700},
"checksum": {Mode: fs.ModeDir | 0700},
"checksum/" + pkg.Encode(sha512.Sum384([]byte{0})): {Mode: 0400, Data: []byte{0}},
"identifier": {Mode: fs.ModeDir | 0700},
"identifier/QpjkahDrz7pz-tv0eAGNXR6x9NAtTjWCK5Hr7G1cIZj9rT7bLYJWUQeLD4wamAlF": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
"substitute": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
}},
})
}
-203
View File
@@ -1,203 +0,0 @@
package pkg
import (
"crypto/sha512"
"encoding/binary"
"errors"
"io"
"io/fs"
"math"
"os"
"path/filepath"
"syscall"
"hakurei.app/check"
)
// FlatEntry is a directory entry to be encoded for [Flatten].
type FlatEntry struct {
Mode fs.FileMode // file mode bits
Path string // pathname of the file
Data []byte // file content or symlink destination
}
/*
| mode uint32 | path_sz uint32 |
| data_sz uint64 |
| path string |
| data []byte |
*/
// Encode encodes the entry for transmission or hashing.
func (ent *FlatEntry) Encode(w io.Writer) (n int, err error) {
pPathSize := alignSize(len(ent.Path))
if pPathSize > math.MaxUint32 {
return 0, syscall.E2BIG
}
pDataSize := alignSize(len(ent.Data))
payload := make([]byte, wordSize*2+pPathSize+pDataSize)
binary.LittleEndian.PutUint32(payload, uint32(ent.Mode))
binary.LittleEndian.PutUint32(payload[wordSize/2:], uint32(len(ent.Path)))
binary.LittleEndian.PutUint64(payload[wordSize:], uint64(len(ent.Data)))
copy(payload[wordSize*2:], ent.Path)
copy(payload[wordSize*2+pPathSize:], ent.Data)
return w.Write(payload)
}
// ErrInsecurePath is returned by [FlatEntry.Decode] if validation is requested
// and a nonlocal path is encountered in the stream.
var ErrInsecurePath = errors.New("insecure file path")
// Decode decodes the entry from its representation produced by Encode.
func (ent *FlatEntry) Decode(r io.Reader, validate bool) (n int, err error) {
var nr int
header := make([]byte, wordSize*2)
nr, err = r.Read(header)
n += nr
if err != nil {
if errors.Is(err, io.EOF) && n != 0 {
err = io.ErrUnexpectedEOF
}
return
}
ent.Mode = fs.FileMode(binary.LittleEndian.Uint32(header))
pathSize := int(binary.LittleEndian.Uint32(header[wordSize/2:]))
pPathSize := alignSize(pathSize)
dataSize := int(binary.LittleEndian.Uint64(header[wordSize:]))
pDataSize := alignSize(dataSize)
buf := make([]byte, pPathSize+pDataSize)
nr, err = r.Read(buf)
n += nr
if err != nil {
if errors.Is(err, io.EOF) {
if nr != len(buf) {
err = io.ErrUnexpectedEOF
return
}
} else {
return
}
}
ent.Path = string(buf[:pathSize])
if ent.Mode.IsDir() {
ent.Data = nil
} else {
ent.Data = buf[pPathSize : pPathSize+dataSize]
}
if validate && !filepath.IsLocal(ent.Path) {
err = ErrInsecurePath
}
return
}
// DirScanner provides an efficient interface for reading a stream of encoded
// [FlatEntry]. Successive calls to the Scan method will step through the
// entries in the stream.
type DirScanner struct {
// Underlying reader to scan [FlatEntry] representations from.
r io.Reader
// First non-EOF I/O error, returned by the Err method.
err error
// Entry to store results in. Its address is returned by the Entry method
// and is updated on every call to Scan.
ent FlatEntry
// Validate pathnames during decoding.
validate bool
}
// NewDirScanner returns the address of a new instance of [DirScanner] reading
// from r. The caller must no longer read from r after this function returns.
func NewDirScanner(r io.Reader, validate bool) *DirScanner {
return &DirScanner{r: r, validate: validate}
}
// Err returns the first non-EOF I/O error.
func (s *DirScanner) Err() error {
if errors.Is(s.err, io.EOF) {
return nil
}
return s.err
}
// Entry returns the address to the [FlatEntry] value storing the last result.
func (s *DirScanner) Entry() *FlatEntry { return &s.ent }
// Scan advances to the next [FlatEntry].
func (s *DirScanner) Scan() bool {
if s.err != nil {
return false
}
var n int
n, s.err = s.ent.Decode(s.r, s.validate)
if errors.Is(s.err, io.EOF) {
return n != 0
}
return s.err == nil
}
// Flatten writes a deterministic representation of the contents of fsys to w.
// The resulting data can be hashed to produce a deterministic checksum for the
// directory.
func Flatten(fsys fs.FS, root string, w io.Writer) (n int, err error) {
var nr int
err = fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
var fi fs.FileInfo
fi, err = d.Info()
if err != nil {
return err
}
ent := FlatEntry{
Path: path,
Mode: fi.Mode(),
}
if ent.Mode.IsRegular() {
if ent.Data, err = fs.ReadFile(fsys, path); err != nil {
return err
}
} else if ent.Mode&fs.ModeSymlink != 0 {
var newpath string
if newpath, err = fs.ReadLink(fsys, path); err != nil {
return err
}
ent.Data = []byte(newpath)
} else if !ent.Mode.IsDir() {
return InvalidFileModeError(ent.Mode)
}
nr, err = ent.Encode(w)
n += nr
return err
})
return
}
// HashFS returns a checksum produced by hashing the result of [Flatten].
func HashFS(buf *Checksum, fsys fs.FS, root string) error {
h := sha512.New384()
if _, err := Flatten(fsys, root, h); err != nil {
return err
}
h.Sum(buf[:0])
return nil
}
// HashDir returns a checksum produced by hashing the result of [Flatten].
func HashDir(buf *Checksum, pathname *check.Absolute) error {
return HashFS(buf, os.DirFS(pathname.String()), ".")
}

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