112 Commits

Author SHA1 Message Date
04d9984da0 internal/rosa/meson: migrate to helper interface
This change also removes some unused options.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 14:07:16 +09:00
145ccd1c92 remove .github
This is no longer needed after discontinuation of the Microsoft GitHub mirror.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 13:46:24 +09:00
c5089cad78 cmd: remove hpkg
This proof-of-concept was abandoned long ago. Its test suite is flaky, heavy on I/O and does not increase test coverage. This change fully removes hpkg and supporting code.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 13:42:49 +09:00
c83905f311 internal/rosa/cmake: enable check
CMake was packaged very early, before the current infrastructure existed to support this. This change patches out broken tests and enables the test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 13:12:20 +09:00
b7cc14f296 internal/rosa/cmake: 4.2.1 to 4.2.3
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 13:09:21 +09:00
57e1e5141d internal/rosa/ninja: remove cmake dependency
This does not actually depend on cmake. This is left over from very early on.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 13:08:18 +09:00
1440195c3f internal/rosa/llvm: pass patches via helper
This was missed while migrating LLVM to the new interface.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 01:30:14 +09:00
cc60e0d15d internal/rosa/make: migrate to helper interface
This also updates all affected artifacts to use new behaviour.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 01:24:50 +09:00
9deaf853f0 internal/rosa/cmake: migrate to helper interface
This change also removes some unused options.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 23:50:22 +09:00
2baa9df133 internal/rosa: general helper abstraction
This greatly increases code sharing and makes implementations far simpler.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 23:39:43 +09:00
51d3df2419 internal/rosa/make: split build and check
Doing these together breaks far too many buggy makefiles.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 14:41:34 +09:00
1d0fcf3a75 internal/rosa/perl: migrate to make helper
This uses the new configure helper behaviour.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 10:45:25 +09:00
e92971e0c2 internal/rosa/make: alternative configure script
This enables using the configure helper with non-autotools configure scripts.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 10:42:20 +09:00
6159c74e96 internal/rosa/toybox: migrate to make helper
A previous change caused world rebuild, so it is a good time to do this.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 10:02:36 +09:00
2a34a269d0 internal/rosa: stricter cure-script options
This change also moves .cure-script out of /system/bin.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 23:29:22 +09:00
ef130adb27 internal/rosa/kernel: early serial
Having serial driver before initramfs is helpful.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 23:22:18 +09:00
5694e528e6 cmd/mbf: use standalone musl in container
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 23:12:13 +09:00
b4e82e68a7 internal/rosa/images: initramfs via gen_init_cpio
This is much cleaner than hacking around the cpio tool.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 23:04:58 +09:00
d041fee791 internal/rosa: export musl
This can be useful externally.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 22:49:54 +09:00
cefd02e960 internal/rosa: gen_init_cpio artifact
This works much better than hacking around the toybox cpio implementation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 21:19:03 +09:00
ad8f799703 container/std: rename seccomp types
Aliases will be kept until 0.4.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 20:48:30 +09:00
c74c269b66 container: use /proc/self/exe directly
This is a more reliable form of pathname to self and also cheaper than os.Executable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 20:44:44 +09:00
4b0cce4db5 ldd: treat nil pathname as self
This is a helpful shortcut for examining a test program's ldd output.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 20:37:01 +09:00
cd9b534d6b container: improve documentation
This change removes inconsistencies collected over time in this package.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 20:18:30 +09:00
84e6922f30 cmd/mbf: optionally set SCHED_IDLE
None of the other supported policies are applicable here.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-26 16:50:35 +09:00
c16725a679 internal/pkg: set container scheduling policy
This is not as necessary as it was for nix, since internal/pkg only unblocks exclusive artifacts one at a time. Still, this is useful when running alongside an unprivileged music player which cannot set itself to a higher priority.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-26 16:35:23 +09:00
a6160cd410 container: set scheduling policy
This is thread-directed so cannot be done externally. The glibc wrapper exposes this behaviour so most multithreaded programs using this is straight up incorrect.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-26 16:29:47 +09:00
826347fe1f internal/rosa: expose standalone musl
This is useful in the system image and might also be used elsewhere.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-26 14:21:32 +09:00
085eaed7ba cmd/earlyinit: early /dev/ and io setup
This establishes an environment where devtmpfs is mounted, and if the kernel fails to set up console, 1 and 2 is pointed at /dev/kmsg.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-25 16:37:42 +09:00
37d368a7f9 internal/rosa: initramfs artifact
This constructs a single-program initramfs and populates /dev/null so the runtime does not throw if the kernel fails to set up console.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-25 16:31:52 +09:00
2aeac7f582 internal/rosa: fakeroot artifact
XSLT is untamable and extremely unpleasant to work with. This patches out the broken docs for now in the interest of getting some work done.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-25 01:20:31 +09:00
2b93631f52 cmd/mbf: use stage2 musl when possible
This avoids pulling in the stage3 toolchain when it is not requested.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-25 01:05:49 +09:00
b3749aaf0b internal/rosa/kernel: arm64 configuration
These new dependencies do not apply to amd64, but adding them anyway in case they are needed some day.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 23:57:28 +09:00
c8bb88cced internal/rosa: libxslt artifact
For building documentation that cannot be turned off.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 23:41:54 +09:00
f7f80f95b9 internal/rosa/perl: various perl module artifacts
This change includes helpers for both Makefile.PL and Build.PL as well as various modules.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 23:09:55 +09:00
6ea6c794fb internal/rosa/gnu: build single-binary coreutils
This enables more fine-grained toybox replacements.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 16:15:15 +09:00
6c2da4c4b2 internal/rosa: libcap artifact
Required by fakeroot. Quite refreshing to package a non-autotools project.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 16:02:54 +09:00
90f915a708 internal/rosa/kernel: disable DEBUG_STACK_USAGE
This is no longer needed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 14:10:37 +09:00
a5fea4686e internal/rosa: make toolchain optional
The final Rosa OS image does not need the toolchain.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-23 18:07:38 +09:00
ae8c365c0f internal/rosa/hakurei: optionally use embedded source
This builds hakurei in Rosa OS between releases.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-23 17:56:16 +09:00
485db515f7 internal/pkg/ir: raise string limit to 16 MiB
A string holds "current" hakurei source code. For now the compressed tarball is 4.9 MiB long.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-23 15:12:19 +09:00
ec7ee0789e internal/rosa/fuse: fix init script path
The default value is quite misleading.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-23 14:39:36 +09:00
42c93a57a4 internal/rosa: fix patches
Turns out alacritty clobbers output. It turns tabs into spaces and also removes whitespace-only lines for some reason.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-23 02:12:09 +09:00
b1b14810ac internal/rosa/kernel: increase audio powersave timeout
This feature is incredibly annoying as some amplifiers take time to wake up, and causes a non-insignificant amount of audio to be dropped.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-22 21:28:39 +09:00
de117ef365 internal/rosa: ncurses artifact
For running menuconfig.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-22 18:07:24 +09:00
5e4bf23e0c internal/rosa/musl: migrate to make helper
This is much cleaner and eliminates the early ugliness.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-22 15:53:41 +09:00
d4519e2075 internal/rosa/make: expose --host
This should be set alongside --build.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-22 15:14:09 +09:00
7f1e4cf43c internal/rosa: kernel artifact
The configuration still wants some cleanup, but this works fine as a generic kernel for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 23:19:16 +09:00
d021621fba internal/rosa: install kernel headers out-of-tree
This is somewhat cheaper than the implementation with extra artifact and is more friendly to the make helper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 19:16:10 +09:00
56567307ec internal/rosa: gnu tar artifact
Initially, libarchive was going to be used, but its test suite simply does not want to work under musl, not even with libiconv. The ticket last discussing this ceased any activity in 2020.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 18:09:51 +09:00
0264a1ef09 internal/rosa: libiconv artifact
For software that assumes glibc.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 14:35:32 +09:00
0123bbee3d internal/rosa: bc artifact
Required by the kernel.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 00:30:40 +09:00
771adad603 internal/rosa: texinfo artifact
Yet another wheel reinvented by GNU. Required to shut some GNU programs up.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 00:28:21 +09:00
178305cb22 internal/rosa: elfutils artifact
Required by the kernel.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-20 21:51:07 +09:00
c2456e252c internal/rosa: musl-obstack artifact
Yet another nonstandard glibc extension used by elfutils.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-20 21:34:59 +09:00
273068b90c internal/rosa: musl-fts artifact
Another nonstandard glibc extension used by elfutils.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-20 21:29:09 +09:00
16b20e1d34 internal/rosa: argp-standalone artifact
Nonstandard glibc extension used by elfutils.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-20 21:19:34 +09:00
b983917a6e internal/rosa: expose kernel source
This also removes the unused kernel helper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-20 18:45:42 +09:00
e1b8f40add cmd/mbf: cache dir via environment
This is much less cumbersome than dragging the flag around all the time.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 15:32:25 +09:00
6df0d37c5a cmd/mbf: Rosa OS container helper
This sets up a Rosa OS container with its shell as the initial process.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 15:12:16 +09:00
1619b06541 internal/pkg: export layer promotion
This is a useful helper for external tooling.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 15:06:10 +09:00
e335d99c6b internal/pkg: export seccomp presets
This is useful for external tooling providing an execArtifact-like environment.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 14:30:31 +09:00
d888d09b6d cmd/mbf: explicit help command
Not having this command is counterintuitive.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 13:55:05 +09:00
54176e7315 internal/rosa: use LTS kernel
For out-of-tree modules.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 01:22:08 +09:00
3bfe99d3d8 internal/lockedfile: keep objects alive while stopping cleanups
Fixes https://go.dev/issues/74780.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-18 20:00:32 +09:00
149dfbb6af internal/rosa: tamago toolchain artifact
Currently used by the (wip) bootloader, might not make it into the final OS.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-18 19:26:42 +09:00
58801b44d4 internal/rosa: util-linux artifact
This stuff will likely be implemented natively in the final system. For now, it is useful for debugging.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-18 14:21:59 +09:00
e065bbf792 internal/rosa: procps artifact
Generally pretty useful, and required by util-linux test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-18 14:07:36 +09:00
a883e57e7d internal/rosa: qemu artifact
This is still a quite minimal build. More features will be enabled as dependencies become available. The powerpc failure will be investigated if it is ever needed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 17:34:25 +09:00
ef9bd8ecbf internal/rosa/go: 1.25.7 to 1.26.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 14:40:24 +09:00
a40527dcb2 internal/pkg/ir: document reason for avoiding ident cache
This got brought up earlier today as a potential optimisation. This change documents why it is not viable, and hopefully clears up some performance implications of using IRDecoder, namely that its decoding costs do not amortise.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 14:38:42 +09:00
88d9a6163e container/initplace: return nil for createTemp error injection
This matches os package behaviour, and avoids adding the cleanup.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 14:16:54 +09:00
47860b0387 internal/rosa/python: enable bzip2 and xz
This is required by qemu test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 02:39:11 +09:00
50c9da8b6d internal/rosa/python: enable openssl
This is required by qemu test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 02:01:57 +09:00
16966043c7 internal/rosa: dtc artifact
Required by qemu.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 00:54:34 +09:00
a3515a6ef5 internal/rosa: bison artifact
Required by dtc, which is required by qemu.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 00:32:04 +09:00
7f05baab28 internal/rosa: flex artifact
Required by dtc, which is required by qemu.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 23:55:36 +09:00
d4d5e631ae internal/rosa: glib artifact
Unfortunately required by many programs, even non-gtk ones.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 19:01:02 +09:00
1df3bcc3b9 nix: mount tmpfs on /tmp
This hopefully eliminates spurious test failures caused by /tmp running out of space.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 18:22:29 +09:00
1809b53e52 internal/rosa/wayland: build-only tests patch
This patch last had any discussion eight months ago and is still not merged.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 13:55:34 +09:00
67b2914c94 internal/rosa: meson helper
This is used by quite a few projects.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 13:49:13 +09:00
74dee11822 internal/rosa/cmake: optional variant string
This improves consistency with other helpers and removes the usually unnecessary variant suffix.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 12:44:40 +09:00
a58c9258cc internal/rosa/pcre2: downgrade to 10.43
Latest release breaks assumptions made by GLib.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 01:51:38 +09:00
710b164c91 internal/pkg: allow devel syscalls
This is required by the GLib test suite, and possibly others.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 01:46:28 +09:00
93911d6015 internal/rosa: pcre2 artifact
Required by GLib.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 01:20:52 +09:00
bb097536d4 internal/rosa: remove libcxxabi hack
This was caused by stack overflow which was resolved many commits ago.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-15 21:59:09 +09:00
49b6526a38 internal/rosa: remove redundant meson flags
These have no effect.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-15 20:18:34 +09:00
f9c31df94d internal/rosa: fixed-size toolchain enum
This fits in an inlined uint32 IR value.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-15 00:58:14 +09:00
4f570cc5c9 internal/pkg: expose extra methods to file
This is useful for FileArtifact processing another stream.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-15 00:45:37 +09:00
5828631e79 internal/pkg: split off context common
For making these methods available to RContext.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-15 00:39:24 +09:00
4f9f4875d7 internal/rosa/openssl: scale jobs based on cpu count
The hardcoded value of 256 causes test failures due to excessive load on some machines. Twice the cpu count appears to almost saturate all cpus without causing spurious failures.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-13 03:34:08 +09:00
d49e654482 internal/rosa: kmod artifact
Required by the kernel.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-13 03:15:27 +09:00
b746e352e5 internal/rosa/zstd: fix libdir
CMake implicitly changes it to lib64 which is not supported.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-13 03:14:30 +09:00
c620d88dce update README document
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-13 01:38:50 +09:00
7cd14b8865 internal/rosa: squashfs-tools artifact
The Makefile is very poorly written, so had to be configured through the environment.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-12 02:33:33 +09:00
3e18a4b397 internal/rosa: zstd artifact
Optional dependency of many programs, and generally useful to have around.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-12 01:48:21 +09:00
1791b604b5 internal/rosa/make: configurable configure and install
This makes the helper useful for non-autotools build systems.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-12 01:19:40 +09:00
59ff6db7ec internal/rosa: toolchain type methods
This improves readability for toolchain-specific checks.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 18:18:11 +09:00
430e099556 internal/rosa/stage0: add arm64 tarball
This took far longer to complete because the aarch64 development machine is much slower.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 17:57:54 +09:00
17b64bb42c internal/pkg: skip resolved cure errors
This significantly improves error resolution performance.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 17:50:18 +09:00
dbb89dfb0f internal/pkg: buffer tar reader
This significantly improves performance and is a good assumption since the primary use case of FileArtifact is over the network.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 02:08:44 +09:00
de06ea2be4 internal/pkg: read buffer free list
Reader has a non-insignificant buffer that is worth saving as well.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 02:02:46 +09:00
1ef7bedfb5 internal/rosa/toybox: do not assume bash location
For compatibility with Gentoo stage3 as bootstrap seed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 01:36:41 +09:00
05a828c474 internal/pkg: validate tar pathnames
TContext no longer validates FileArtifact ahead of time, validation outcome is instead determined after consuming the reader to EOF. All data must therefore be treated as untrusted input until the reader is closed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 00:40:54 +09:00
0061d11f93 internal/rosa: use self-hosted stage0
This removes the bootstrap dependency on Gentoo stage3 tarball.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-10 22:31:08 +09:00
fb101a02f2 internal/rosa: self-host stage0 tarball
This replaces gentoo stage3 tarballs.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-10 21:22:46 +09:00
3dbd67d113 internal/rosa: consistent stage0 paths
This makes using the gentoo stage3 as our stage0 compatible with Rosa OS stage0 tarballs.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-10 19:28:47 +09:00
f511f0a9e9 internal/rosa: bzip2 artifact
For creating the stage0 tarball. Might be replaced by a custom artifact at some point.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-09 02:26:52 +09:00
47995137b3 internal/rosa/perl: skip installing manpages
Perl manpages ignore prefix and gets installed to /. This change does not use the configure script because it is completely broken and specifying either "none" or a single space character (undocumented) has no effect.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-09 01:50:52 +09:00
e1b8607101 internal/rosa: rename stage0 toolchain
This is stage0 relative to Rosa OS, and stage3 relative to the toolchain it is compiled on (Gentoo in this case). Referring to the toolchain itself as stage3 is counterintuitive and misleading.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-09 00:55:07 +09:00
3d3bd45b95 internal/rosa/hakurei: 0.3.4 to 0.3.5
This removes all backport patches.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-09 00:27:45 +09:00
9fb0b2452e release: 0.3.5
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-09 00:11:20 +09:00
132 changed files with 30365 additions and 3094 deletions

View File

@@ -89,23 +89,6 @@ jobs:
path: result/* path: result/*
retention-days: 1 retention-days: 1
hpkg:
name: Hpkg
runs-on: nix
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run NixOS test
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.hpkg
- name: Upload test output
uses: actions/upload-artifact@v3
with:
name: "hpkg-vm-output"
path: result/*
retention-days: 1
check: check:
name: Flake checks name: Flake checks
needs: needs:
@@ -114,7 +97,6 @@ jobs:
- sandbox - sandbox
- sandbox-race - sandbox-race
- sharefs - sharefs
- hpkg
runs-on: nix runs-on: nix
steps: steps:
- name: Checkout - name: Checkout

View File

@@ -1,5 +0,0 @@
DO NOT ADD NEW ACTIONS HERE
This port is solely for releasing to the github mirror and serves no purpose during development.
All development happens at https://git.gensokyo.uk/security/hakurei. If you wish to contribute,
request for an account on git.gensokyo.uk.

View File

@@ -1,46 +0,0 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
name: Create release
runs-on: ubuntu-latest
permissions:
packages: write
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v32
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v6
with:
primary-key: build-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: build-${{ runner.os }}-
gc-max-store-size-linux: 1G
purge: true
purge-prefixes: build-${{ runner.os }}-
purge-created: 60
purge-primary-key: never
- name: Build for release
run: nix build --print-out-paths --print-build-logs .#dist
- name: Release
uses: softprops/action-gh-release@v2
with:
files: |-
result/hakurei-**

View File

@@ -1,48 +0,0 @@
name: Test
on:
- push
jobs:
dist:
name: Create distribution
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v32
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v6
with:
primary-key: build-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: build-${{ runner.os }}-
gc-max-store-size-linux: 1G
purge: true
purge-prefixes: build-${{ runner.os }}-
purge-created: 60
purge-primary-key: never
- name: Build for test
id: build-test
run: >-
export HAKUREI_REV="$(git rev-parse --short HEAD)" &&
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' package.nix &&
nix build --print-out-paths --print-build-logs .#dist &&
mv package.nix.old package.nix &&
echo "rev=$HAKUREI_REV" >> $GITHUB_OUTPUT
- name: Upload test build
uses: actions/upload-artifact@v4
with:
name: "hakurei-${{ steps.build-test.outputs.rev }}"
path: result/*
retention-days: 1

1
.gitignore vendored
View File

@@ -28,6 +28,7 @@ go.work.sum
# go generate # go generate
/cmd/hakurei/LICENSE /cmd/hakurei/LICENSE
/internal/pkg/testdata/testtool /internal/pkg/testdata/testtool
/internal/rosa/hakurei_current.tar.gz
# release # release
/dist/hakurei-* /dist/hakurei-*

181
README.md
View File

@@ -15,164 +15,51 @@
<a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a> <a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
</p> </p>
Hakurei is a tool for running sandboxed graphical applications as dedicated subordinate users on the Linux kernel. Hakurei is a tool for running sandboxed desktop applications as dedicated
It implements the application container of [planterette (WIP)](https://git.gensokyo.uk/security/planterette), subordinate users on the Linux kernel. It implements the application container
a self-contained Android-like package manager with modern security features. of [planterette (WIP)](https://git.gensokyo.uk/security/planterette), a
self-contained Android-like package manager with modern security features.
## NixOS Module usage Interaction with hakurei happens entirely through structures described by
package [hst](https://pkg.go.dev/hakurei.app/hst). No native API is available
due to internal details of uid isolation.
The NixOS module currently requires home-manager to configure subordinate users. Full module documentation can be found [here](options.md). ## Notable Packages
To use the module, import it into your configuration with Package [container](https://pkg.go.dev/hakurei.app/container) is general purpose
container tooling. It is used by the hakurei shim process running as the target
subordinate user to set up the application container. It has a single dependency,
[libseccomp](https://github.com/seccomp/libseccomp), to create BPF programs
for the [system call filter](https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html).
```nix Package [internal/pkg](https://pkg.go.dev/hakurei.app/internal/pkg) provides
{ infrastructure for hermetic builds. This replaces the legacy nix-based testing
inputs = { framework and serves as the build system of Rosa OS, currently developed under
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; package [internal/rosa](https://pkg.go.dev/hakurei.app/internal/rosa).
hakurei = { ## Dependencies
url = "git+https://git.gensokyo.uk/security/hakurei";
# Optional but recommended to limit the size of your system closure. `container` depends on:
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, hakurei, ... }: - [libseccomp](https://github.com/seccomp/libseccomp) to generate BPF programs.
{
nixosConfigurations.hakurei = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
hakurei.nixosModules.hakurei
];
};
};
}
```
This adds the `environment.hakurei` option: `cmd/hakurei` depends on:
```nix - [acl](https://savannah.nongnu.org/projects/acl/) to export sockets to
{ pkgs, ... }: subordinate users.
- [wayland](https://gitlab.freedesktop.org/wayland/wayland) to set up
[security-context-v1](https://wayland.app/protocols/security-context-v1).
- [xcb](https://xcb.freedesktop.org/) to grant and revoke subordinate users
access to the X server.
{ `cmd/sharefs` depends on:
environment.hakurei = {
enable = true;
stateDir = "/var/lib/hakurei";
users = {
alice = 0;
nixos = 10;
};
commonPaths = [ - [fuse](https://github.com/libfuse/libfuse) to implement the filesystem.
{
src = "/sdcard";
write = true;
}
];
extraHomeConfig = { New dependencies will generally not be added. Patches adding new dependencies
home.stateVersion = "23.05"; are very likely to be rejected.
};
apps = { ## NixOS Module (deprecated)
"org.chromium.Chromium" = {
name = "chromium";
identity = 1;
packages = [ pkgs.chromium ];
userns = true;
mapRealUid = true;
dbus = {
system = {
filter = true;
talk = [
"org.bluez"
"org.freedesktop.Avahi"
"org.freedesktop.UPower"
];
};
session =
f:
f {
talk = [
"org.freedesktop.FileManager1"
"org.freedesktop.Notifications"
"org.freedesktop.ScreenSaver"
"org.freedesktop.secrets"
"org.kde.kwalletd5"
"org.kde.kwalletd6"
];
own = [
"org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.chromium.*"
];
call = { };
broadcast = { };
};
};
};
"org.claws_mail.Claws-Mail" = { The NixOS module is in maintenance mode and will be removed once planterette is
name = "claws-mail"; feature-complete. Full module documentation can be found [here](options.md).
identity = 2;
packages = [ pkgs.claws-mail ];
gpu = false;
capability.pulse = false;
};
"org.weechat" = {
name = "weechat";
identity = 3;
shareUid = true;
packages = [ pkgs.weechat ];
capability = {
wayland = false;
x11 = false;
dbus = true;
pulse = false;
};
};
"dev.vencord.Vesktop" = {
name = "discord";
identity = 3;
shareUid = true;
packages = [ pkgs.vesktop ];
share = pkgs.vesktop;
command = "vesktop --ozone-platform-hint=wayland";
userns = true;
mapRealUid = true;
capability.x11 = true;
dbus = {
session =
f:
f {
talk = [ "org.kde.StatusNotifierWatcher" ];
own = [ ];
call = { };
broadcast = { };
};
system.filter = true;
};
};
"io.looking-glass" = {
name = "looking-glass-client";
identity = 4;
useCommonPaths = false;
groups = [ "plugdev" ];
extraPaths = [
{
src = "/dev/shm/looking-glass";
write = true;
}
];
extraConfig = {
programs.looking-glass-client.enable = true;
};
};
};
};
}
```

58
cmd/earlyinit/main.go Normal file
View File

@@ -0,0 +1,58 @@
package main
import (
"log"
"os"
"runtime"
. "syscall"
)
func main() {
runtime.LockOSThread()
log.SetFlags(0)
log.SetPrefix("earlyinit: ")
if err := Mount(
"devtmpfs",
"/dev/",
"devtmpfs",
MS_NOSUID|MS_NOEXEC,
"",
); err != nil {
log.Fatalf("cannot mount devtmpfs: %v", err)
}
// The kernel might be unable to set up the console. When that happens,
// printk is called with "Warning: unable to open an initial console."
// and the init runs with no files. The checkfds runtime function
// populates 0-2 by opening /dev/null for them.
//
// This check replaces 1 and 2 with /dev/kmsg to improve the chance
// of output being visible to the user.
if fi, err := os.Stdout.Stat(); err == nil {
if stat, ok := fi.Sys().(*Stat_t); ok {
if stat.Rdev == 0x103 {
var fd int
if fd, err = Open(
"/dev/kmsg",
O_WRONLY|O_CLOEXEC,
0,
); err != nil {
log.Fatalf("cannot open kmsg: %v", err)
}
if err = Dup3(fd, Stdout, 0); err != nil {
log.Fatalf("cannot open stdout: %v", err)
}
if err = Dup3(fd, Stderr, 0); err != nil {
log.Fatalf("cannot open stderr: %v", err)
}
if err = Close(fd); err != nil {
log.Printf("cannot close kmsg: %v", err)
}
}
}
}
}

View File

@@ -1,7 +0,0 @@
This program is a proof of concept and is now deprecated. It is only kept
around for API demonstration purposes and to make the most out of the test
suite.
This program is replaced by planterette, which can be found at
https://git.gensokyo.uk/security/planterette. Development effort should be
focused there instead.

View File

@@ -1,173 +0,0 @@
package main
import (
"encoding/json"
"log"
"os"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/hst"
)
type appInfo struct {
Name string `json:"name"`
Version string `json:"version"`
// passed through to [hst.Config]
ID string `json:"id"`
// passed through to [hst.Config]
Identity int `json:"identity"`
// passed through to [hst.Config]
Groups []string `json:"groups,omitempty"`
// passed through to [hst.Config]
Devel bool `json:"devel,omitempty"`
// passed through to [hst.Config]
Userns bool `json:"userns,omitempty"`
// passed through to [hst.Config]
HostNet bool `json:"net,omitempty"`
// passed through to [hst.Config]
HostAbstract bool `json:"abstract,omitempty"`
// passed through to [hst.Config]
Device bool `json:"dev,omitempty"`
// passed through to [hst.Config]
Tty bool `json:"tty,omitempty"`
// passed through to [hst.Config]
MapRealUID bool `json:"map_real_uid,omitempty"`
// passed through to [hst.Config]
DirectWayland bool `json:"direct_wayland,omitempty"`
// passed through to [hst.Config]
SystemBus *hst.BusConfig `json:"system_bus,omitempty"`
// passed through to [hst.Config]
SessionBus *hst.BusConfig `json:"session_bus,omitempty"`
// passed through to [hst.Config]
Enablements *hst.Enablements `json:"enablements,omitempty"`
// passed through to [hst.Config]
Multiarch bool `json:"multiarch,omitempty"`
// passed through to [hst.Config]
Bluetooth bool `json:"bluetooth,omitempty"`
// allow gpu access within sandbox
GPU bool `json:"gpu"`
// store path to nixGL mesa wrappers
Mesa string `json:"mesa,omitempty"`
// store path to nixGL source
NixGL string `json:"nix_gl,omitempty"`
// store path to activate-and-exec script
Launcher *check.Absolute `json:"launcher"`
// store path to /run/current-system
CurrentSystem *check.Absolute `json:"current_system"`
// store path to home-manager activation package
ActivationPackage string `json:"activation_package"`
}
func (app *appInfo) toHst(pathSet *appPathSet, pathname *check.Absolute, argv []string, flagDropShell bool) *hst.Config {
config := &hst.Config{
ID: app.ID,
Enablements: app.Enablements,
SystemBus: app.SystemBus,
SessionBus: app.SessionBus,
DirectWayland: app.DirectWayland,
Identity: app.Identity,
Groups: app.Groups,
Container: &hst.ContainerConfig{
Hostname: formatHostname(app.Name),
Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append("store"), Target: pathNixStore}},
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSBind{Source: pathSet.metaPath, Target: hst.AbsPrivateTmp.Append("app")}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
},
Username: "hakurei",
Shell: pathShell,
Home: pathDataData.Append(app.ID),
Path: pathname,
Args: argv,
},
ExtraPerms: []hst.ExtraPermConfig{
{Path: dataHome, Execute: true},
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
},
}
if app.Devel {
config.Container.Flags |= hst.FDevel
}
if app.Userns {
config.Container.Flags |= hst.FUserns
}
if app.HostNet {
config.Container.Flags |= hst.FHostNet
}
if app.HostAbstract {
config.Container.Flags |= hst.FHostAbstract
}
if app.Device {
config.Container.Flags |= hst.FDevice
}
if app.Tty || flagDropShell {
config.Container.Flags |= hst.FTty
}
if app.MapRealUID {
config.Container.Flags |= hst.FMapRealUID
}
if app.Multiarch {
config.Container.Flags |= hst.FMultiarch
}
config.Container.Flags |= hst.FShareRuntime | hst.FShareTmpdir
return config
}
func loadAppInfo(name string, beforeFail func()) *appInfo {
bundle := new(appInfo)
if f, err := os.Open(name); err != nil {
beforeFail()
log.Fatalf("cannot open bundle: %v", err)
} else if err = json.NewDecoder(f).Decode(&bundle); err != nil {
beforeFail()
log.Fatalf("cannot parse bundle metadata: %v", err)
} else if err = f.Close(); err != nil {
log.Printf("cannot close bundle metadata: %v", err)
// not fatal
}
if bundle.ID == "" {
beforeFail()
log.Fatal("application identifier must not be empty")
}
if bundle.Launcher == nil {
beforeFail()
log.Fatal("launcher must not be empty")
}
if bundle.CurrentSystem == nil {
beforeFail()
log.Fatal("current-system must not be empty")
}
return bundle
}
func formatHostname(name string) string {
if h, err := os.Hostname(); err != nil {
log.Printf("cannot get hostname: %v", err)
return "hakurei-" + name
} else {
return h + "-" + name
}
}

View File

@@ -1,256 +0,0 @@
{
nixpkgsFor,
system,
nixpkgs,
home-manager,
}:
{
lib,
stdenv,
closureInfo,
writeScript,
runtimeShell,
writeText,
symlinkJoin,
vmTools,
runCommand,
fetchFromGitHub,
zstd,
nix,
sqlite,
name ? throw "name is required",
version ? throw "version is required",
pname ? "${name}-${version}",
modules ? [ ],
nixosModules ? [ ],
script ? ''
exec "$SHELL" "$@"
'',
id ? name,
identity ? throw "identity is required",
groups ? [ ],
userns ? false,
net ? true,
dev ? false,
no_new_session ? false,
map_real_uid ? false,
direct_wayland ? false,
system_bus ? null,
session_bus ? null,
allow_wayland ? true,
allow_x11 ? false,
allow_dbus ? true,
allow_audio ? true,
gpu ? allow_wayland || allow_x11,
}:
let
inherit (lib) optionals;
homeManagerConfiguration = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgsFor.${system};
modules = modules ++ [
{
home = {
username = "hakurei";
homeDirectory = "/data/data/${id}";
stateVersion = "22.11";
};
}
];
};
launcher = writeScript "hakurei-${pname}" ''
#!${runtimeShell} -el
${script}
'';
extraNixOSConfig =
{ pkgs, ... }:
{
environment = {
etc.nixpkgs.source = nixpkgs.outPath;
systemPackages = [ pkgs.nix ];
};
imports = nixosModules;
};
nixos = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
extraNixOSConfig
{ nix.settings.experimental-features = [ "flakes" ]; }
{ nix.settings.experimental-features = [ "nix-command" ]; }
{ boot.isContainer = true; }
{ system.stateVersion = "22.11"; }
];
};
etc = vmTools.runInLinuxVM (
runCommand "etc" { } ''
mkdir -p /etc
${nixos.config.system.build.etcActivationCommands}
# remove unused files
rm -rf /etc/sudoers
mkdir -p $out
tar -C /etc -cf "$out/etc.tar" .
''
);
extendSessionDefault = id: ext: {
filter = true;
talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
own =
(optionals (id != null) [
"${id}.*"
"org.mpris.MediaPlayer2.${id}.*"
])
++ ext.own;
inherit (ext) call broadcast;
};
nixGL = fetchFromGitHub {
owner = "nix-community";
repo = "nixGL";
rev = "310f8e49a149e4c9ea52f1adf70cdc768ec53f8a";
hash = "sha256-lnzZQYG0+EXl/6NkGpyIz+FEOc/DSEG57AP1VsdeNrM=";
};
mesaWrappers =
let
isIntelX86Platform = system == "x86_64-linux";
nixGLPackages = import (nixGL + "/default.nix") {
pkgs = nixpkgs.legacyPackages.${system};
enable32bits = isIntelX86Platform;
enableIntelX86Extensions = isIntelX86Platform;
};
in
symlinkJoin {
name = "nixGL-mesa";
paths = with nixGLPackages; [
nixGLIntel
nixVulkanIntel
];
};
info = builtins.toJSON {
inherit
name
version
id
identity
launcher
groups
userns
net
dev
no_new_session
map_real_uid
direct_wayland
system_bus
gpu
;
session_bus =
if session_bus != null then
(session_bus (extendSessionDefault id))
else
(extendSessionDefault id {
talk = [ ];
own = [ ];
call = { };
broadcast = { };
});
enablements = {
wayland = allow_wayland;
x11 = allow_x11;
dbus = allow_dbus;
pipewire = allow_audio;
};
mesa = if gpu then mesaWrappers else null;
nix_gl = if gpu then nixGL else null;
current_system = nixos.config.system.build.toplevel;
activation_package = homeManagerConfiguration.activationPackage;
};
in
stdenv.mkDerivation {
name = "${pname}.pkg";
inherit version;
__structuredAttrs = true;
nativeBuildInputs = [
zstd
nix
sqlite
];
buildCommand = ''
NIX_ROOT="$(mktemp -d)"
export USER="nobody"
# create bootstrap store
bootstrapClosureInfo="${
closureInfo {
rootPaths = [
nix
nixos.config.system.build.toplevel
];
}
}"
echo "copying bootstrap store paths..."
mkdir -p "$NIX_ROOT/nix/store"
xargs -n 1 -a "$bootstrapClosureInfo/store-paths" cp -at "$NIX_ROOT/nix/store/"
NIX_REMOTE="local?root=$NIX_ROOT" nix-store --load-db < "$bootstrapClosureInfo/registration"
NIX_REMOTE="local?root=$NIX_ROOT" nix-store --optimise
sqlite3 "$NIX_ROOT/nix/var/nix/db/db.sqlite" "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
chmod -R +r "$NIX_ROOT/nix/var"
# create binary cache
closureInfo="${
closureInfo {
rootPaths = [
homeManagerConfiguration.activationPackage
launcher
]
++ optionals gpu [
mesaWrappers
nixGL
];
}
}"
echo "copying application paths..."
TMP_STORE="$(mktemp -d)"
mkdir -p "$TMP_STORE/nix/store"
xargs -n 1 -a "$closureInfo/store-paths" cp -at "$TMP_STORE/nix/store/"
NIX_REMOTE="local?root=$TMP_STORE" nix-store --load-db < "$closureInfo/registration"
sqlite3 "$TMP_STORE/nix/var/nix/db/db.sqlite" "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
NIX_REMOTE="local?root=$TMP_STORE" nix --offline --extra-experimental-features nix-command \
--verbose --log-format raw-with-logs \
copy --all --no-check-sigs --to \
"file://$NIX_ROOT/res?compression=zstd&compression-level=19&parallel-compression=true"
# package /etc
mkdir -p "$NIX_ROOT/etc"
tar -C "$NIX_ROOT/etc" -xf "${etc}/etc.tar"
# write metadata
cp "${writeText "bundle.json" info}" "$NIX_ROOT/bundle.json"
# create an intermediate file to improve zstd performance
INTER="$(mktemp)"
tar -C "$NIX_ROOT" -cf "$INTER" .
zstd -T0 -19 -fo "$out" "$INTER"
'';
}

View File

@@ -1,335 +0,0 @@
package main
import (
"context"
"encoding/json"
"errors"
"log"
"os"
"os/signal"
"path"
"syscall"
"hakurei.app/command"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/hst"
"hakurei.app/message"
)
var (
errSuccess = errors.New("success")
)
func main() {
log.SetPrefix("hpkg: ")
log.SetFlags(0)
msg := message.New(log.Default())
if err := os.Setenv("SHELL", pathShell.String()); err != nil {
log.Fatalf("cannot set $SHELL: %v", err)
}
if os.Geteuid() == 0 {
log.Fatal("this program must not run as root")
}
ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer stop() // unreachable
var (
flagVerbose bool
flagDropShell bool
)
c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { msg.SwapVerbose(flagVerbose); return nil }).
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
{
var (
flagDropShellActivate bool
)
c.NewCommand("install", "Install an application from its package", func(args []string) error {
if len(args) != 1 {
log.Println("invalid argument")
return syscall.EINVAL
}
pkgPath := args[0]
if !path.IsAbs(pkgPath) {
if dir, err := os.Getwd(); err != nil {
log.Printf("cannot get current directory: %v", err)
return err
} else {
pkgPath = path.Join(dir, pkgPath)
}
}
/*
Look up paths to programs started by hpkg.
This is done here to ease error handling as cleanup is not yet required.
*/
var (
_ = lookPath("zstd")
tar = lookPath("tar")
chmod = lookPath("chmod")
rm = lookPath("rm")
)
/*
Extract package and set up for cleanup.
*/
var workDir *check.Absolute
if p, err := os.MkdirTemp("", "hpkg.*"); err != nil {
log.Printf("cannot create temporary directory: %v", err)
return err
} else if workDir, err = check.NewAbs(p); err != nil {
log.Printf("invalid temporary directory: %v", err)
return err
}
cleanup := func() {
// should be faster than a native implementation
mustRun(msg, chmod, "-R", "+w", workDir.String())
mustRun(msg, rm, "-rf", workDir.String())
}
beforeRunFail.Store(&cleanup)
mustRun(msg, tar, "-C", workDir.String(), "-xf", pkgPath)
/*
Parse bundle and app metadata, do pre-install checks.
*/
bundle := loadAppInfo(path.Join(workDir.String(), "bundle.json"), cleanup)
pathSet := pathSetByApp(bundle.ID)
a := bundle
if s, err := os.Stat(pathSet.metaPath.String()); err != nil {
if !os.IsNotExist(err) {
cleanup()
log.Printf("cannot access %q: %v", pathSet.metaPath, err)
return err
}
// did not modify app, clean installation condition met later
} else if s.IsDir() {
cleanup()
log.Printf("metadata path %q is not a file", pathSet.metaPath)
return syscall.EBADMSG
} else {
a = loadAppInfo(pathSet.metaPath.String(), cleanup)
if a.ID != bundle.ID {
cleanup()
log.Printf("app %q claims to have identifier %q",
bundle.ID, a.ID)
return syscall.EBADE
}
// sec: should verify credentials
}
if a != bundle {
// do not try to re-install
if a.NixGL == bundle.NixGL &&
a.CurrentSystem == bundle.CurrentSystem &&
a.Launcher == bundle.Launcher &&
a.ActivationPackage == bundle.ActivationPackage {
cleanup()
log.Printf("package %q is identical to local application %q",
pkgPath, a.ID)
return errSuccess
}
// identity determines uid
if a.Identity != bundle.Identity {
cleanup()
log.Printf("package %q identity %d differs from installed %d",
pkgPath, bundle.Identity, a.Identity)
return syscall.EBADE
}
// sec: should compare version string
msg.Verbosef("installing application %q version %q over local %q",
bundle.ID, bundle.Version, a.Version)
} else {
msg.Verbosef("application %q clean installation", bundle.ID)
// sec: should install credentials
}
/*
Setup steps for files owned by the target user.
*/
withCacheDir(ctx, msg, "install", []string{
// export inner bundle path in the environment
"export BUNDLE=" + hst.PrivateTmp + "/bundle",
// replace inner /etc
"mkdir -p etc",
"chmod -R +w etc",
"rm -rf etc",
"cp -dRf $BUNDLE/etc etc",
// replace inner /nix
"mkdir -p nix",
"chmod -R +w nix",
"rm -rf nix",
"cp -dRf /nix nix",
// copy from binary cache
"nix copy --offline --no-check-sigs --all --from file://$BUNDLE/res --to $PWD",
// deduplicate nix store
"nix store --offline --store $PWD optimise",
// make cache directory world-readable for autoetc
"chmod 0755 .",
}, workDir, bundle, pathSet, flagDropShell, cleanup)
if bundle.GPU {
withCacheDir(ctx, msg, "mesa-wrappers", []string{
// link nixGL mesa wrappers
"mkdir -p nix/.nixGL",
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
"ln -s " + bundle.Mesa + "/bin/nixVulkanIntel nix/.nixGL/nixVulkan",
}, workDir, bundle, pathSet, false, cleanup)
}
/*
Activate home-manager generation.
*/
withNixDaemon(ctx, msg, "activate", []string{
// clean up broken links
"mkdir -p .local/state/{nix,home-manager}",
"chmod -R +w .local/state/{nix,home-manager}",
"rm -rf .local/state/{nix,home-manager}",
// run activation script
bundle.ActivationPackage + "/activate",
}, false, func(config *hst.Config) *hst.Config { return config },
bundle, pathSet, flagDropShellActivate, cleanup)
/*
Installation complete. Write metadata to block re-installs or downgrades.
*/
// serialise metadata to ensure consistency
if f, err := os.OpenFile(pathSet.metaPath.String()+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
cleanup()
log.Printf("cannot create metadata file: %v", err)
return err
} else if err = json.NewEncoder(f).Encode(bundle); err != nil {
cleanup()
log.Printf("cannot write metadata: %v", err)
return err
} else if err = f.Close(); err != nil {
log.Printf("cannot close metadata file: %v", err)
// not fatal
}
if err := os.Rename(pathSet.metaPath.String()+"~", pathSet.metaPath.String()); err != nil {
cleanup()
log.Printf("cannot rename metadata file: %v", err)
return err
}
cleanup()
return errSuccess
}).
Flag(&flagDropShellActivate, "s", command.BoolFlag(false), "Drop to a shell on activation")
}
{
var (
flagDropShellNixGL bool
flagAutoDrivers bool
)
c.NewCommand("start", "Start an application", func(args []string) error {
if len(args) < 1 {
log.Println("invalid argument")
return syscall.EINVAL
}
/*
Parse app metadata.
*/
id := args[0]
pathSet := pathSetByApp(id)
a := loadAppInfo(pathSet.metaPath.String(), func() {})
if a.ID != id {
log.Printf("app %q claims to have identifier %q", id, a.ID)
return syscall.EBADE
}
/*
Prepare nixGL.
*/
if a.GPU && flagAutoDrivers {
withNixDaemon(ctx, msg, "nix-gl", []string{
"mkdir -p /nix/.nixGL/auto",
"rm -rf /nix/.nixGL/auto",
"export NIXPKGS_ALLOW_UNFREE=1",
"nix build --impure " +
"--out-link /nix/.nixGL/auto/opengl " +
"--override-input nixpkgs path:/etc/nixpkgs " +
"path:" + a.NixGL,
"nix build --impure " +
"--out-link /nix/.nixGL/auto/vulkan " +
"--override-input nixpkgs path:/etc/nixpkgs " +
"path:" + a.NixGL + "#nixVulkanNvidia",
}, true, func(config *hst.Config) *hst.Config {
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices"), Optional: true}},
}...)
appendGPUFilesystem(config)
return config
}, a, pathSet, flagDropShellNixGL, func() {})
}
/*
Create app configuration.
*/
pathname := a.Launcher
argv := make([]string, 1, len(args))
if flagDropShell {
pathname = pathShell
argv[0] = bash
} else {
argv[0] = a.Launcher.String()
}
argv = append(argv, args[1:]...)
config := a.toHst(pathSet, pathname, argv, flagDropShell)
/*
Expose GPU devices.
*/
if a.GPU {
config.Container.Filesystem = append(config.Container.Filesystem,
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append(".nixGL"), Target: hst.AbsPrivateTmp.Append("nixGL")}})
appendGPUFilesystem(config)
}
/*
Spawn app.
*/
mustRunApp(ctx, msg, config, func() {})
return errSuccess
}).
Flag(&flagDropShellNixGL, "s", command.BoolFlag(false), "Drop to a shell on nixGL build").
Flag(&flagAutoDrivers, "auto-drivers", command.BoolFlag(false), "Attempt automatic opengl driver detection")
}
c.MustParse(os.Args[1:], func(err error) {
msg.Verbosef("command returned %v", err)
if errors.Is(err, errSuccess) {
msg.BeforeExit()
os.Exit(0)
}
})
log.Fatal("unreachable")
}

View File

@@ -1,117 +0,0 @@
package main
import (
"log"
"os"
"os/exec"
"strconv"
"sync/atomic"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/hst"
"hakurei.app/message"
)
const bash = "bash"
var (
dataHome *check.Absolute
)
func init() {
// dataHome
if a, err := check.NewAbs(os.Getenv("HAKUREI_DATA_HOME")); err == nil {
dataHome = a
} else {
dataHome = fhs.AbsVarLib.Append("hakurei/" + strconv.Itoa(os.Getuid()))
}
}
var (
pathBin = fhs.AbsRoot.Append("bin")
pathNix = check.MustAbs("/nix/")
pathNixStore = pathNix.Append("store/")
pathCurrentSystem = fhs.AbsRun.Append("current-system")
pathSwBin = pathCurrentSystem.Append("sw/bin/")
pathShell = pathSwBin.Append(bash)
pathData = check.MustAbs("/data")
pathDataData = pathData.Append("data")
)
func lookPath(file string) string {
if p, err := exec.LookPath(file); err != nil {
log.Fatalf("%s: command not found", file)
return ""
} else {
return p
}
}
var beforeRunFail = new(atomic.Pointer[func()])
func mustRun(msg message.Msg, name string, arg ...string) {
msg.Verbosef("spawning process: %q %q", name, arg)
cmd := exec.Command(name, arg...)
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
if f := beforeRunFail.Swap(nil); f != nil {
(*f)()
}
log.Fatalf("%s: %v", name, err)
}
}
type appPathSet struct {
// ${dataHome}/${id}
baseDir *check.Absolute
// ${baseDir}/app
metaPath *check.Absolute
// ${baseDir}/files
homeDir *check.Absolute
// ${baseDir}/cache
cacheDir *check.Absolute
// ${baseDir}/cache/nix
nixPath *check.Absolute
}
func pathSetByApp(id string) *appPathSet {
pathSet := new(appPathSet)
pathSet.baseDir = dataHome.Append(id)
pathSet.metaPath = pathSet.baseDir.Append("app")
pathSet.homeDir = pathSet.baseDir.Append("files")
pathSet.cacheDir = pathSet.baseDir.Append("cache")
pathSet.nixPath = pathSet.cacheDir.Append("nix")
return pathSet
}
func appendGPUFilesystem(config *hst.Config) {
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
// flatpak commit 763a686d874dd668f0236f911de00b80766ffe79
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("dri"), Device: true, Optional: true}},
// mali
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("mali"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("mali0"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("umplock"), Device: true, Optional: true}},
// nvidia
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidiactl"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-modeset"), Device: true, Optional: true}},
// nvidia OpenCL/CUDA
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-uvm"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-uvm-tools"), Device: true, Optional: true}},
// flatpak commit d2dff2875bb3b7e2cd92d8204088d743fd07f3ff
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia0"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia1"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia2"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia3"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia4"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia5"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia6"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia7"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia8"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia9"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia10"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia11"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia12"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia13"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia14"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia15"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia16"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia17"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia18"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia19"), Device: true, Optional: true}},
}...)
}

View File

@@ -1,61 +0,0 @@
package main
import (
"context"
"encoding/json"
"errors"
"io"
"log"
"os"
"os/exec"
"hakurei.app/hst"
"hakurei.app/internal/info"
"hakurei.app/message"
)
var hakureiPathVal = info.MustHakureiPath().String()
func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, beforeFail func()) {
var (
cmd *exec.Cmd
st io.WriteCloser
)
if r, w, err := os.Pipe(); err != nil {
beforeFail()
log.Fatalf("cannot pipe: %v", err)
} else {
if msg.IsVerbose() {
cmd = exec.CommandContext(ctx, hakureiPathVal, "-v", "app", "3")
} else {
cmd = exec.CommandContext(ctx, hakureiPathVal, "app", "3")
}
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.ExtraFiles = []*os.File{r}
st = w
}
go func() {
if err := json.NewEncoder(st).Encode(config); err != nil {
beforeFail()
log.Fatalf("cannot send configuration: %v", err)
}
}()
if err := cmd.Start(); err != nil {
beforeFail()
log.Fatalf("cannot start hakurei: %v", err)
}
if err := cmd.Wait(); err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
beforeFail()
msg.BeforeExit()
os.Exit(exitError.ExitCode())
} else {
beforeFail()
log.Fatalf("cannot wait: %v", err)
}
}
}

View File

@@ -1,62 +0,0 @@
{ pkgs, ... }:
{
users.users = {
alice = {
isNormalUser = true;
description = "Alice Foobar";
password = "foobar";
uid = 1000;
};
};
home-manager.users.alice.home.stateVersion = "24.11";
# Automatically login on tty1 as a normal user:
services.getty.autologinUser = "alice";
environment = {
variables = {
SWAYSOCK = "/tmp/sway-ipc.sock";
WLR_RENDERER = "pixman";
};
};
# Automatically configure and start Sway when logging in on tty1:
programs.bash.loginShellInit = ''
if [ "$(tty)" = "/dev/tty1" ]; then
set -e
mkdir -p ~/.config/sway
(sed s/Mod4/Mod1/ /etc/sway/config &&
echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' &&
echo 'output Virtual-1 res 1680x1050') > ~/.config/sway/config
sway --validate
systemd-cat --identifier=session sway && touch /tmp/sway-exit-ok
fi
'';
programs.sway.enable = true;
virtualisation = {
diskSize = 6 * 1024;
qemu.options = [
# Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
"-vga none -device virtio-gpu-pci"
# Increase zstd performance:
"-smp 8"
];
};
environment.hakurei = {
enable = true;
stateDir = "/var/lib/hakurei";
users.alice = 0;
extraHomeConfig = {
home.stateVersion = "23.05";
};
};
}

View File

@@ -1,34 +0,0 @@
{
testers,
callPackage,
system,
self,
}:
let
buildPackage = self.buildPackage.${system};
in
testers.nixosTest {
name = "hpkg";
nodes.machine = {
environment.etc = {
"foot.pkg".source = callPackage ./foot.nix { inherit buildPackage; };
};
imports = [
./configuration.nix
self.nixosModules.hakurei
self.inputs.home-manager.nixosModules.home-manager
];
};
# adapted from nixos sway integration tests
# testScriptWithTypes:49: error: Cannot call function of unknown type
# (machine.succeed if succeed else machine.execute)(
# ^
# Found 1 error in 1 file (checked 1 source file)
skipTypeCheck = true;
testScript = builtins.readFile ./test.py;
}

View File

@@ -1,48 +0,0 @@
{
lib,
buildPackage,
foot,
wayland-utils,
inconsolata,
}:
buildPackage {
name = "foot";
inherit (foot) version;
identity = 2;
id = "org.codeberg.dnkl.foot";
modules = [
{
home.packages = [
foot
# For wayland-info:
wayland-utils
];
}
];
nixosModules = [
{
# To help with OCR:
environment.etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
main = {
font = "inconsolata:size=14";
};
colors = rec {
foreground = "000000";
background = "ffffff";
regular2 = foreground;
};
};
fonts.packages = [ inconsolata ];
}
];
script = ''
exec foot "$@"
'';
}

View File

@@ -1,110 +0,0 @@
import json
import shlex
q = shlex.quote
NODE_GROUPS = ["nodes", "floating_nodes"]
def swaymsg(command: str = "", succeed=True, type="command"):
assert command != "" or type != "command", "Must specify command or type"
shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
with machine.nested(
f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
):
ret = (machine.succeed if succeed else machine.execute)(
f"su - alice -c {shell}"
)
# execute also returns a status code, but disregard.
if not succeed:
_, ret = ret
if not succeed and not ret:
return None
parsed = json.loads(ret)
return parsed
def walk(tree):
yield tree
for group in NODE_GROUPS:
for node in tree.get(group, []):
yield from walk(node)
def wait_for_window(pattern):
def func(last_chance):
nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
if last_chance:
nodes = list(nodes)
machine.log(f"Last call! Current list of windows: {nodes}")
return any(pattern in name for name in nodes)
retry(func)
def collect_state_ui(name):
swaymsg(f"exec hakurei ps > '/tmp/{name}.ps'")
machine.copy_from_vm(f"/tmp/{name}.ps", "")
swaymsg(f"exec hakurei --json ps > '/tmp/{name}.json'")
machine.copy_from_vm(f"/tmp/{name}.json", "")
machine.screenshot(name)
def check_state(name, enablements):
instances = json.loads(machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei --json ps"))
if len(instances) != 1:
raise Exception(f"unexpected state length {len(instances)}")
instance = instances[0]
if len(instance['container']['args']) != 1 or not (instance['container']['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (instance['container']['args'][0]):
raise Exception(f"unexpected args {instance['container']['args']}")
if instance['enablements'] != enablements:
raise Exception(f"unexpected enablements {instance['enablements']}")
start_all()
machine.wait_for_unit("multi-user.target")
# To check hakurei's version:
print(machine.succeed("sudo -u alice -i hakurei version"))
# Wait for Sway to complete startup:
machine.wait_for_file("/run/user/1000/wayland-1")
machine.wait_for_file("/tmp/sway-ipc.sock")
# Prepare hpkg directory:
machine.succeed("install -dm 0700 -o alice -g users /var/lib/hakurei/1000")
# Install hpkg app:
swaymsg("exec hpkg -v install /etc/foot.pkg && touch /tmp/hpkg-install-ok")
machine.wait_for_file("/tmp/hpkg-install-ok")
# Start app (foot) with Wayland enablement:
swaymsg("exec hpkg -v start org.codeberg.dnkl.foot")
wait_for_window("hakurei@machine-foot")
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
machine.wait_for_file("/tmp/hakurei.0/tmpdir/2/success-client")
collect_state_ui("app_wayland")
check_state("foot", {"wayland": True, "dbus": True, "pipewire": True})
# Verify acl on XDG_RUNTIME_DIR:
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002"))
machine.send_chars("exit\n")
machine.wait_until_fails("pgrep foot")
# Verify acl cleanup on XDG_RUNTIME_DIR:
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002")
# Exit Sway and verify process exit status 0:
swaymsg("exit", succeed=False)
machine.wait_for_file("/tmp/sway-exit-ok")
# Print hakurei share and rundir contents:
print(machine.succeed("find /tmp/hakurei.0 "
+ "-path '/tmp/hakurei.0/runtime/*/*' -prune -o "
+ "-path '/tmp/hakurei.0/tmpdir/*/*' -prune -o "
+ "-print"))
print(machine.fail("ls /run/user/1000/hakurei"))

View File

@@ -1,130 +0,0 @@
package main
import (
"context"
"os"
"strings"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/hst"
"hakurei.app/message"
)
func withNixDaemon(
ctx context.Context,
msg message.Msg,
action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config,
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
) {
flags := hst.FMultiarch | hst.FUserns // nix sandbox requires userns
if net {
flags |= hst.FHostNet
}
if dropShell {
flags |= hst.FTty
}
mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{
ID: app.ID,
ExtraPerms: []hst.ExtraPermConfig{
{Path: dataHome, Execute: true},
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
},
Identity: app.Identity,
Container: &hst.ContainerConfig{
Hostname: formatHostname(app.Name) + "-" + action,
Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath, Target: pathNix, Write: true}},
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
},
Username: "hakurei",
Shell: pathShell,
Home: pathDataData.Append(app.ID),
Path: pathShell,
Args: []string{bash, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
// start nix-daemon
"nix-daemon --store / & " +
// wait for socket to appear
"(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " +
// create directory so nix stops complaining
"mkdir -p /nix/var/nix/profiles/per-user/root/channels && " +
strings.Join(command, " && ") +
// terminate nix-daemon
" && pkill nix-daemon",
},
Flags: flags,
},
}), dropShell, beforeFail)
}
func withCacheDir(
ctx context.Context,
msg message.Msg,
action string, command []string, workDir *check.Absolute,
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
) {
flags := hst.FMultiarch
if dropShell {
flags |= hst.FTty
}
mustRunAppDropShell(ctx, msg, &hst.Config{
ID: app.ID,
ExtraPerms: []hst.ExtraPermConfig{
{Path: dataHome, Execute: true},
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
{Path: workDir, Execute: true},
},
Identity: app.Identity,
Container: &hst.ContainerConfig{
Hostname: formatHostname(app.Name) + "-" + action,
Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: workDir.Append(fhs.Etc), Special: true}},
{FilesystemConfig: &hst.FSBind{Source: workDir.Append("nix"), Target: pathNix}},
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSBind{Source: workDir, Target: hst.AbsPrivateTmp.Append("bundle")}},
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID, "cache"), Source: pathSet.cacheDir, Write: true, Ensure: true}},
},
Username: "nixos",
Shell: pathShell,
Home: pathDataData.Append(app.ID, "cache"),
Path: pathShell,
Args: []string{bash, "-lc", strings.Join(command, " && ")},
Flags: flags,
},
}, dropShell, beforeFail)
}
func mustRunAppDropShell(ctx context.Context, msg message.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
if dropShell {
if config.Container != nil {
config.Container.Args = []string{bash, "-l"}
}
mustRunApp(ctx, msg, config, beforeFail)
beforeFail()
msg.BeforeExit()
os.Exit(0)
}
mustRunApp(ctx, msg, config, beforeFail)
}

View File

@@ -10,11 +10,15 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"syscall" "syscall"
"time"
"unique" "unique"
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/container/seccomp"
"hakurei.app/container/std"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
"hakurei.app/internal/rosa" "hakurei.app/internal/rosa"
"hakurei.app/message" "hakurei.app/message"
@@ -51,10 +55,16 @@ func main() {
flagCures int flagCures int
flagBase string flagBase string
flagTShift int flagTShift int
flagIdle bool
) )
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) { c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) {
msg.SwapVerbose(!flagQuiet) msg.SwapVerbose(!flagQuiet)
flagBase = os.ExpandEnv(flagBase)
if flagBase == "" {
flagBase = "cache"
}
var base *check.Absolute var base *check.Absolute
if flagBase, err = filepath.Abs(flagBase); err != nil { if flagBase, err = filepath.Abs(flagBase); err != nil {
return return
@@ -70,6 +80,11 @@ func main() {
cache.SetThreshold(1 << flagTShift) cache.SetThreshold(1 << flagTShift)
} }
} }
if flagIdle {
pkg.SchedPolicy = container.SCHED_IDLE
}
return return
}).Flag( }).Flag(
&flagQuiet, &flagQuiet,
@@ -81,12 +96,16 @@ func main() {
"Maximum number of dependencies to cure at any given time", "Maximum number of dependencies to cure at any given time",
).Flag( ).Flag(
&flagBase, &flagBase,
"d", command.StringFlag("cache"), "d", command.StringFlag("$MBF_CACHE_DIR"),
"Directory to store cured artifacts", "Directory to store cured artifacts",
).Flag( ).Flag(
&flagTShift, &flagTShift,
"tshift", command.IntFlag(-1), "tshift", command.IntFlag(-1),
"Dependency graph size exponent, to the power of 2", "Dependency graph size exponent, to the power of 2",
).Flag(
&flagIdle,
"sched-idle", command.BoolFlag(false),
"Set SCHED_IDLE scheduling policy",
) )
{ {
@@ -109,46 +128,92 @@ func main() {
) )
} }
c.NewCommand( {
"stage3", var (
"Check for toolchain 3-stage non-determinism", flagGentoo string
func(args []string) (err error) { flagChecksum string
_, _, _, stage1 := (rosa.Std - 2).NewLLVM()
_, _, _, stage2 := (rosa.Std - 1).NewLLVM()
_, _, _, stage3 := rosa.Std.NewLLVM()
var (
pathname *check.Absolute
checksum [2]unique.Handle[pkg.Checksum]
)
if pathname, _, err = cache.Cure(stage1); err != nil { flagStage0 bool
return err )
} c.NewCommand(
log.Println("stage1:", pathname) "stage3",
"Check for toolchain 3-stage non-determinism",
func(args []string) (err error) {
t := rosa.Std
if flagGentoo != "" {
t -= 3 // magic number to discourage misuse
if pathname, checksum[0], err = cache.Cure(stage2); err != nil { var checksum pkg.Checksum
return err if len(flagChecksum) != 0 {
} if err = pkg.Decode(&checksum, flagChecksum); err != nil {
log.Println("stage2:", pathname) return
if pathname, checksum[1], err = cache.Cure(stage3); err != nil { }
return err }
} rosa.SetGentooStage3(flagGentoo, checksum)
log.Println("stage3:", pathname)
if checksum[0] != checksum[1] {
err = &pkg.ChecksumMismatchError{
Got: checksum[0].Value(),
Want: checksum[1].Value(),
} }
} else {
log.Println( _, _, _, stage1 := (t - 2).NewLLVM()
"stage2 is identical to stage3", _, _, _, stage2 := (t - 1).NewLLVM()
"("+pkg.Encode(checksum[0].Value())+")", _, _, _, stage3 := t.NewLLVM()
var (
pathname *check.Absolute
checksum [2]unique.Handle[pkg.Checksum]
) )
}
return if pathname, _, err = cache.Cure(stage1); err != nil {
}, return err
) }
log.Println("stage1:", pathname)
if pathname, checksum[0], err = cache.Cure(stage2); err != nil {
return err
}
log.Println("stage2:", pathname)
if pathname, checksum[1], err = cache.Cure(stage3); err != nil {
return err
}
log.Println("stage3:", pathname)
if checksum[0] != checksum[1] {
err = &pkg.ChecksumMismatchError{
Got: checksum[0].Value(),
Want: checksum[1].Value(),
}
} else {
log.Println(
"stage2 is identical to stage3",
"("+pkg.Encode(checksum[0].Value())+")",
)
}
if flagStage0 {
if pathname, _, err = cache.Cure(
t.Load(rosa.Stage0),
); err != nil {
return err
}
log.Println(pathname)
}
return
},
).
Flag(
&flagGentoo,
"gentoo", command.StringFlag(""),
"Bootstrap from a Gentoo stage3 tarball",
).
Flag(
&flagChecksum,
"checksum", command.StringFlag(""),
"Checksum of Gentoo stage3 tarball",
).
Flag(
&flagStage0,
"stage0", command.BoolFlag(false),
"Create bootstrap stage0 tarball",
)
}
{ {
var ( var (
@@ -162,7 +227,7 @@ func main() {
return errors.New("cure requires 1 argument") return errors.New("cure requires 1 argument")
} }
if p, ok := rosa.ResolveName(args[0]); !ok { if p, ok := rosa.ResolveName(args[0]); !ok {
return fmt.Errorf("unsupported artifact %q", args[0]) return fmt.Errorf("unknown artifact %q", args[0])
} else if flagDump == "" { } else if flagDump == "" {
pathname, _, err := cache.Cure(rosa.Std.Load(p)) pathname, _, err := cache.Cure(rosa.Std.Load(p))
if err == nil { if err == nil {
@@ -195,6 +260,143 @@ func main() {
) )
} }
{
var (
flagNet bool
flagSession bool
flagWithToolchain bool
)
c.NewCommand(
"shell",
"Interactive shell in the specified Rosa OS environment",
func(args []string) error {
root := make([]pkg.Artifact, 0, 6+len(args))
for _, arg := range args {
p, ok := rosa.ResolveName(arg)
if !ok {
return fmt.Errorf("unknown artifact %q", arg)
}
root = append(root, rosa.Std.Load(p))
}
if flagWithToolchain {
musl, compilerRT, runtimes, clang := rosa.Std.NewLLVM()
root = append(root, musl, compilerRT, runtimes, clang)
} else {
root = append(root, rosa.Std.Load(rosa.Musl))
}
root = append(root,
rosa.Std.Load(rosa.Mksh),
rosa.Std.Load(rosa.Toybox),
)
type cureRes struct {
pathname *check.Absolute
checksum unique.Handle[pkg.Checksum]
}
cured := make(map[pkg.Artifact]cureRes)
for _, a := range root {
pathname, checksum, err := cache.Cure(a)
if err != nil {
return err
}
cured[a] = cureRes{pathname, checksum}
}
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(cache.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)
})
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
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,
"net", command.BoolFlag(false),
"Share host net namespace",
).
Flag(
&flagSession,
"session", command.BoolFlag(false),
"Retain session",
).
Flag(
&flagWithToolchain,
"with-toolchain", command.BoolFlag(false),
"Include the stage3 LLVM toolchain",
)
}
c.Command(
"help",
"Show this help message",
func([]string) error { c.PrintHelp(); return nil },
)
c.MustParse(os.Args[1:], func(err error) { c.MustParse(os.Args[1:], func(err error) {
if cache != nil { if cache != nil {
cache.Close() cache.Close()

View File

@@ -33,6 +33,7 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/helper/proc" "hakurei.app/internal/helper/proc"
@@ -441,12 +442,7 @@ func _main(s ...string) (exitCode int) {
// keep fuse_parse_cmdline happy in the container // keep fuse_parse_cmdline happy in the container
z.Tmpfs(check.MustAbs(container.Nonexistent), 1<<10, 0755) z.Tmpfs(check.MustAbs(container.Nonexistent), 1<<10, 0755)
if a, err := check.NewAbs(container.MustExecutable(msg)); err != nil { z.Path = fhs.AbsProcSelfExe
log.Println(err)
return 5
} else {
z.Path = a
}
z.Args = s z.Args = s
z.ForwardCancel = true z.ForwardCancel = true
z.SeccompPresets |= std.PresetStrict z.SeccompPresets |= std.PresetStrict

View File

@@ -10,8 +10,7 @@ import (
func init() { gob.Register(new(AutoEtcOp)) } func init() { gob.Register(new(AutoEtcOp)) }
// Etc appends an [Op] that expands host /etc into a toplevel symlink mirror with /etc semantics. // Etc is a helper for appending [AutoEtcOp] to [Ops].
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
func (f *Ops) Etc(host *check.Absolute, prefix string) *Ops { func (f *Ops) Etc(host *check.Absolute, prefix string) *Ops {
e := &AutoEtcOp{prefix} e := &AutoEtcOp{prefix}
f.Mkdir(fhs.AbsEtc, 0755) f.Mkdir(fhs.AbsEtc, 0755)
@@ -20,6 +19,9 @@ func (f *Ops) Etc(host *check.Absolute, prefix string) *Ops {
return f return f
} }
// AutoEtcOp expands host /etc into a toplevel symlink mirror with /etc semantics.
//
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
type AutoEtcOp struct{ Prefix string } type AutoEtcOp struct{ Prefix string }
func (e *AutoEtcOp) Valid() bool { return e != nil } func (e *AutoEtcOp) Valid() bool { return e != nil }

View File

@@ -11,13 +11,15 @@ import (
func init() { gob.Register(new(AutoRootOp)) } func init() { gob.Register(new(AutoRootOp)) }
// Root appends an [Op] that expands a directory into a toplevel bind mount mirror on container root. // Root is a helper for appending [AutoRootOp] to [Ops].
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
func (f *Ops) Root(host *check.Absolute, flags int) *Ops { func (f *Ops) Root(host *check.Absolute, flags int) *Ops {
*f = append(*f, &AutoRootOp{host, flags, nil}) *f = append(*f, &AutoRootOp{host, flags, nil})
return f return f
} }
// AutoRootOp expands a directory into a toplevel bind mount mirror on container root.
//
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
type AutoRootOp struct { type AutoRootOp struct {
Host *check.Absolute Host *check.Absolute
// passed through to bindMount // passed through to bindMount

View File

@@ -50,10 +50,16 @@ func capset(hdrp *capHeader, datap *[2]capData) error {
} }
// capBoundingSetDrop drops a capability from the calling thread's capability bounding set. // capBoundingSetDrop drops a capability from the calling thread's capability bounding set.
func capBoundingSetDrop(cap uintptr) error { return Prctl(syscall.PR_CAPBSET_DROP, cap, 0) } func capBoundingSetDrop(cap uintptr) error {
return Prctl(syscall.PR_CAPBSET_DROP, cap, 0)
}
// capAmbientClearAll clears the ambient capability set of the calling thread. // capAmbientClearAll clears the ambient capability set of the calling thread.
func capAmbientClearAll() error { return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0) } func capAmbientClearAll() error {
return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0)
}
// capAmbientRaise adds to the ambient capability set of the calling thread. // capAmbientRaise adds to the ambient capability set of the calling thread.
func capAmbientRaise(cap uintptr) error { return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap) } func capAmbientRaise(cap uintptr) error {
return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap)
}

View File

@@ -11,7 +11,8 @@ const (
SpecialOverlayPath = ":" SpecialOverlayPath = ":"
) )
// EscapeOverlayDataSegment escapes a string for formatting into the data argument of an overlay mount call. // EscapeOverlayDataSegment escapes a string for formatting into the data
// argument of an overlay mount system call.
func EscapeOverlayDataSegment(s string) string { func EscapeOverlayDataSegment(s string) string {
if s == "" { if s == "" {
return "" return ""

View File

@@ -1,4 +1,5 @@
// Package container implements unprivileged Linux containers with built-in support for syscall filtering. // Package container implements unprivileged Linux containers with built-in
// support for syscall filtering.
package container package container
import ( import (
@@ -37,24 +38,30 @@ type (
Container struct { Container struct {
// Whether the container init should stay alive after its parent terminates. // Whether the container init should stay alive after its parent terminates.
AllowOrphan bool AllowOrphan bool
// Scheduling policy to set via sched_setscheduler(2). The zero value
// skips this call. Supported policies are [SCHED_BATCH], [SCHED_IDLE].
SchedPolicy int
// Cgroup fd, nil to disable. // Cgroup fd, nil to disable.
Cgroup *int Cgroup *int
// ExtraFiles passed through to initial process in the container, // ExtraFiles passed through to initial process in the container, with
// with behaviour identical to its [exec.Cmd] counterpart. // behaviour identical to its [exec.Cmd] counterpart.
ExtraFiles []*os.File ExtraFiles []*os.File
// param pipe for shim and init // Write end of a pipe connected to the init to deliver [Params].
setup *os.File setup *os.File
// cancels cmd // Cancels the context passed to the underlying cmd.
cancel context.CancelFunc cancel context.CancelFunc
// closed after Wait returns // Closed after Wait returns. Keeps the spawning thread alive.
wait chan struct{} wait chan struct{}
Stdin io.Reader Stdin io.Reader
Stdout io.Writer Stdout io.Writer
Stderr io.Writer Stderr io.Writer
Cancel func(cmd *exec.Cmd) error // Custom cancellation behaviour for the underlying [exec.Cmd]. Must
// deliver [CancelSignal] before returning.
Cancel func(cmd *exec.Cmd) error
// Copied to the underlying [exec.Cmd].
WaitDelay time.Duration WaitDelay time.Duration
cmd *exec.Cmd cmd *exec.Cmd
@@ -283,7 +290,11 @@ func (p *Container) Start() error {
// place setup pipe before user supplied extra files, this is later restored by init // place setup pipe before user supplied extra files, this is later restored by init
if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil { if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil {
return &StartError{true, "set up params stream", err, false, false} return &StartError{
Fatal: true,
Step: "set up params stream",
Err: err,
}
} else { } else {
p.setup = f p.setup = f
p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)} p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)}
@@ -295,10 +306,16 @@ func (p *Container) Start() error {
runtime.LockOSThread() runtime.LockOSThread()
p.wait = make(chan struct{}) p.wait = make(chan struct{})
done <- func() error { // setup depending on per-thread state must happen here // setup depending on per-thread state must happen here
// PR_SET_NO_NEW_PRIVS: depends on per-thread state but acts on all processes created from that thread done <- func() error {
// PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes
// created from the calling thread
if err := SetNoNewPrivs(); err != nil { if err := SetNoNewPrivs(); err != nil {
return &StartError{true, "prctl(PR_SET_NO_NEW_PRIVS)", err, false, false} return &StartError{
Fatal: true,
Step: "prctl(PR_SET_NO_NEW_PRIVS)",
Err: err,
}
} }
// landlock: depends on per-thread state but acts on a process group // landlock: depends on per-thread state but acts on a process group
@@ -310,28 +327,40 @@ func (p *Container) Start() error {
if abi, err := LandlockGetABI(); err != nil { if abi, err := LandlockGetABI(); err != nil {
if p.HostAbstract { if p.HostAbstract {
// landlock can be skipped here as it restricts access to resources // landlock can be skipped here as it restricts access
// already covered by namespaces (pid) // to resources already covered by namespaces (pid)
goto landlockOut goto landlockOut
} }
return &StartError{false, "get landlock ABI", err, false, false} return &StartError{Step: "get landlock ABI", Err: err}
} else if abi < 6 { } else if abi < 6 {
if p.HostAbstract { if p.HostAbstract {
// see above comment // see above comment
goto landlockOut goto landlockOut
} }
return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false} return &StartError{
Step: "kernel too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET",
Err: ENOSYS,
Origin: true,
}
} else { } else {
p.msg.Verbosef("landlock abi version %d", abi) p.msg.Verbosef("landlock abi version %d", abi)
} }
if rulesetFd, err := rulesetAttr.Create(0); err != nil { if rulesetFd, err := rulesetAttr.Create(0); err != nil {
return &StartError{true, "create landlock ruleset", err, false, false} return &StartError{
Fatal: true,
Step: "create landlock ruleset",
Err: err,
}
} else { } else {
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr) p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil { if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
_ = Close(rulesetFd) _ = Close(rulesetFd)
return &StartError{true, "enforce landlock ruleset", err, false, false} return &StartError{
Fatal: true,
Step: "enforce landlock ruleset",
Err: err,
}
} }
if err = Close(rulesetFd); err != nil { if err = Close(rulesetFd); err != nil {
p.msg.Verbosef("cannot close landlock ruleset: %v", err) p.msg.Verbosef("cannot close landlock ruleset: %v", err)
@@ -342,9 +371,30 @@ func (p *Container) Start() error {
landlockOut: landlockOut:
} }
// sched_setscheduler: thread-directed but acts on all processes
// created from the calling thread
if p.SchedPolicy > 0 {
p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy)
if err := schedSetscheduler(
0, // calling thread
p.SchedPolicy,
&schedParam{0},
); err != nil {
return &StartError{
Fatal: true,
Step: "enforce landlock ruleset",
Err: err,
}
}
}
p.msg.Verbose("starting container init") p.msg.Verbose("starting container init")
if err := p.cmd.Start(); err != nil { if err := p.cmd.Start(); err != nil {
return &StartError{false, "start container init", err, false, true} return &StartError{
Step: "start container init",
Err: err,
Passthrough: true,
}
} }
return nil return nil
}() }()
@@ -356,6 +406,7 @@ func (p *Container) Start() error {
} }
// Serve serves [Container.Params] to the container init. // Serve serves [Container.Params] to the container init.
//
// Serve must only be called once. // Serve must only be called once.
func (p *Container) Serve() error { func (p *Container) Serve() error {
if p.setup == nil { if p.setup == nil {
@@ -365,12 +416,21 @@ func (p *Container) Serve() error {
setup := p.setup setup := p.setup
p.setup = nil p.setup = nil
if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil { if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil {
return &StartError{true, "set init pipe deadline", err, false, true} return &StartError{
Fatal: true,
Step: "set init pipe deadline",
Err: err,
Passthrough: true,
}
} }
if p.Path == nil { if p.Path == nil {
p.cancel() p.cancel()
return &StartError{false, "invalid executable pathname", EINVAL, true, false} return &StartError{
Step: "invalid executable pathname",
Err: EINVAL,
Origin: true,
}
} }
// do not transmit nil // do not transmit nil
@@ -395,7 +455,8 @@ func (p *Container) Serve() error {
return err return err
} }
// Wait waits for the container init process to exit and releases any resources associated with the [Container]. // Wait blocks until the container init process to exit and releases any
// resources associated with the [Container].
func (p *Container) Wait() error { func (p *Container) Wait() error {
if p.cmd == nil || p.cmd.Process == nil { if p.cmd == nil || p.cmd.Process == nil {
return EINVAL return EINVAL
@@ -440,11 +501,13 @@ func (p *Container) StderrPipe() (r io.ReadCloser, err error) {
} }
func (p *Container) String() string { func (p *Container) String() string {
return fmt.Sprintf("argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x", return fmt.Sprintf(
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets)) "argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x",
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets),
)
} }
// ProcessState returns the address to os.ProcessState held by the underlying [exec.Cmd]. // ProcessState returns the address of os.ProcessState held by the underlying [exec.Cmd].
func (p *Container) ProcessState() *os.ProcessState { func (p *Container) ProcessState() *os.ProcessState {
if p.cmd == nil { if p.cmd == nil {
return nil return nil
@@ -452,7 +515,8 @@ func (p *Container) ProcessState() *os.ProcessState {
return p.cmd.ProcessState return p.cmd.ProcessState
} }
// New returns the address to a new instance of [Container] that requires further initialisation before use. // New returns the address to a new instance of [Container]. This value requires
// further initialisation before use.
func New(ctx context.Context, msg message.Msg) *Container { func New(ctx context.Context, msg message.Msg) *Container {
if msg == nil { if msg == nil {
msg = message.New(nil) msg = message.New(nil)
@@ -461,12 +525,18 @@ func New(ctx context.Context, msg message.Msg) *Container {
p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}} p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}}
c, cancel := context.WithCancel(ctx) c, cancel := context.WithCancel(ctx)
p.cancel = cancel p.cancel = cancel
p.cmd = exec.CommandContext(c, MustExecutable(msg)) p.cmd = exec.CommandContext(c, fhs.ProcSelfExe)
return p return p
} }
// NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields. // NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields.
func NewCommand(ctx context.Context, msg message.Msg, pathname *check.Absolute, name string, args ...string) *Container { func NewCommand(
ctx context.Context,
msg message.Msg,
pathname *check.Absolute,
name string,
args ...string,
) *Container {
z := New(ctx, msg) z := New(ctx, msg)
z.Path = pathname z.Path = pathname
z.Args = append([]string{name}, args...) z.Args = append([]string{name}, args...)

View File

@@ -773,14 +773,13 @@ func TestMain(m *testing.M) {
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) { func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) {
msg := message.New(nil) msg := message.New(nil)
msg.SwapVerbose(testing.Verbose()) msg.SwapVerbose(testing.Verbose())
executable := check.MustAbs(container.MustExecutable(msg))
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...) c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
c.Env = append(c.Env, envDoCheck+"=1") c.Env = append(c.Env, envDoCheck+"=1")
c.Bind(executable, absHelperInnerPath, 0) c.Bind(fhs.AbsProcSelfExe, absHelperInnerPath, 0)
// in case test has cgo enabled // in case test has cgo enabled
if entries, err := ldd.Resolve(ctx, msg, executable); err != nil { if entries, err := ldd.Resolve(ctx, msg, nil); err != nil {
log.Fatalf("ldd: %v", err) log.Fatalf("ldd: %v", err)
} else { } else {
*libPaths = ldd.Path(entries) *libPaths = ldd.Path(entries)

View File

@@ -21,7 +21,8 @@ type osFile interface {
fs.File fs.File
} }
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour. // syscallDispatcher provides methods that make state-dependent system calls as
// part of their behaviour.
type syscallDispatcher interface { type syscallDispatcher interface {
// new starts a goroutine with a new instance of syscallDispatcher. // new starts a goroutine with a new instance of syscallDispatcher.
// A syscallDispatcher must never be used in any goroutine other than the one owning it, // A syscallDispatcher must never be used in any goroutine other than the one owning it,

View File

@@ -238,8 +238,11 @@ func sliceAddr[S any](s []S) *[]S { return &s }
func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile { func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr} f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
// check happens in Close, and cleanup is not guaranteed to run, so relying on it for sloppy implementations will cause sporadic test results // check happens in Close, and cleanup is not guaranteed to run, so relying
f.cleanup = runtime.AddCleanup(f, func(name string) { f.t.Fatalf("checkedOsFile %s became unreachable without a call to Close", name) }, f.name) // on it for sloppy implementations will cause sporadic test results
f.cleanup = runtime.AddCleanup(f, func(name string) {
panic("checkedOsFile " + name + " became unreachable without a call to Close")
}, name)
return f return f
} }

View File

@@ -43,7 +43,8 @@ func messageFromError(err error) (m string, ok bool) {
} }
// messagePrefix checks and prefixes the error message of a non-pointer error. // messagePrefix checks and prefixes the error message of a non-pointer error.
// While this is usable for pointer errors, such use should be avoided as nil check is omitted. // 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) { func messagePrefix[T error](prefix string, err error) (string, bool) {
var targetError T var targetError T
if errors.As(err, &targetError) { if errors.As(err, &targetError) {

View File

@@ -28,6 +28,9 @@ func copyExecutable(msg message.Msg) {
} }
} }
// MustExecutable calls [os.Executable] and terminates the process on error.
//
// Deprecated: This is no longer used and will be removed in 0.4.
func MustExecutable(msg message.Msg) string { func MustExecutable(msg message.Msg) string {
executableOnce.Do(func() { copyExecutable(msg) }) executableOnce.Do(func() { copyExecutable(msg) })
return executable return executable

View File

@@ -42,6 +42,8 @@ var (
AbsDevShm = unsafeAbs(DevShm) AbsDevShm = unsafeAbs(DevShm)
// AbsProc is [Proc] as [check.Absolute]. // AbsProc is [Proc] as [check.Absolute].
AbsProc = unsafeAbs(Proc) AbsProc = unsafeAbs(Proc)
// AbsProcSelfExe is [ProcSelfExe] as [check.Absolute].
AbsProcSelfExe = unsafeAbs(ProcSelfExe)
// AbsSys is [Sys] as [check.Absolute]. // AbsSys is [Sys] as [check.Absolute].
AbsSys = unsafeAbs(Sys) AbsSys = unsafeAbs(Sys)
) )

View File

@@ -9,7 +9,8 @@ const (
// Tmp points to the place for small temporary files. // Tmp points to the place for small temporary files.
Tmp = "/tmp/" Tmp = "/tmp/"
// Run points to a "tmpfs" file system for system packages to place runtime data, socket files, and similar. // Run points to a "tmpfs" file system for system packages to place runtime
// data, socket files, and similar.
Run = "/run/" Run = "/run/"
// RunUser points to a directory containing per-user runtime directories, // RunUser points to a directory containing per-user runtime directories,
// each usually individually mounted "tmpfs" instances. // each usually individually mounted "tmpfs" instances.
@@ -17,10 +18,12 @@ const (
// Usr points to vendor-supplied operating system resources. // Usr points to vendor-supplied operating system resources.
Usr = "/usr/" Usr = "/usr/"
// UsrBin points to binaries and executables for user commands that shall appear in the $PATH search path. // UsrBin points to binaries and executables for user commands that shall
// appear in the $PATH search path.
UsrBin = Usr + "bin/" UsrBin = Usr + "bin/"
// Var points to persistent, variable system data. Writable during normal system operation. // Var points to persistent, variable system data. Writable during normal
// system operation.
Var = "/var/" Var = "/var/"
// VarLib points to persistent system data. // VarLib points to persistent system data.
VarLib = Var + "lib/" VarLib = Var + "lib/"
@@ -29,12 +32,20 @@ const (
// Dev points to the root directory for device nodes. // Dev points to the root directory for device nodes.
Dev = "/dev/" Dev = "/dev/"
// DevShm is the place for POSIX shared memory segments, as created via shm_open(3). // DevShm is the place for POSIX shared memory segments, as created via
// shm_open(3).
DevShm = "/dev/shm/" DevShm = "/dev/shm/"
// Proc points to a virtual kernel file system exposing the process list and other functionality. // Proc points to a virtual kernel file system exposing the process list and
// other functionality.
Proc = "/proc/" Proc = "/proc/"
// ProcSys points to a hierarchy below /proc/ that exposes a number of kernel tunables. // ProcSys points to a hierarchy below /proc/ that exposes a number of
// kernel tunables.
ProcSys = Proc + "sys/" ProcSys = Proc + "sys/"
// Sys points to a virtual kernel file system exposing discovered devices and other functionality. // ProcSelf resolves to the process's own /proc/pid directory.
ProcSelf = Proc + "self/"
// ProcSelfExe is a symbolic link to program pathname.
ProcSelfExe = ProcSelf + "exe"
// Sys points to a virtual kernel file system exposing discovered devices
// and other functionality.
Sys = "/sys/" Sys = "/sys/"
) )

View File

@@ -33,12 +33,12 @@ const (
- This path is only accessible by init and root: - This path is only accessible by init and root:
The container init sets SUID_DUMP_DISABLE and terminates if that fails. The container init sets SUID_DUMP_DISABLE and terminates if that fails.
It should be noted that none of this should become relevant at any point since the resulting It should be noted that none of this should become relevant at any point
intermediate root tmpfs should be effectively anonymous. */ since the resulting intermediate root tmpfs should be effectively anonymous. */
intermediateHostPath = fhs.Proc + "self/fd" intermediateHostPath = fhs.Proc + "self/fd"
// setupEnv is the name of the environment variable holding the string representation of // setupEnv is the name of the environment variable holding the string
// the read end file descriptor of the setup params pipe. // representation of the read end file descriptor of the setup params pipe.
setupEnv = "HAKUREI_SETUP" setupEnv = "HAKUREI_SETUP"
// exitUnexpectedWait4 is the exit code if wait4 returns an unexpected errno. // exitUnexpectedWait4 is the exit code if wait4 returns an unexpected errno.
@@ -59,7 +59,8 @@ type (
// late is called right before starting the initial process. // late is called right before starting the initial process.
late(state *setupState, k syscallDispatcher) error late(state *setupState, k syscallDispatcher) error
// prefix returns a log message prefix, and whether this Op prints no identifying message on its own. // prefix returns a log message prefix, and whether this Op prints no
// identifying message on its own.
prefix() (string, bool) prefix() (string, bool)
Is(op Op) bool Is(op Op) bool
@@ -71,9 +72,11 @@ type (
setupState struct { setupState struct {
nonrepeatable uintptr nonrepeatable uintptr
// Whether early reaping has concluded. Must only be accessed in the wait4 loop. // Whether early reaping has concluded. Must only be accessed in the
// wait4 loop.
processConcluded bool processConcluded bool
// Process to syscall.WaitStatus populated in the wait4 loop. Freed after early reaping concludes. // Process to syscall.WaitStatus populated in the wait4 loop. Freed
// after early reaping concludes.
process map[int]WaitStatus process map[int]WaitStatus
// Synchronises access to process. // Synchronises access to process.
processMu sync.RWMutex processMu sync.RWMutex
@@ -216,9 +219,10 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
defer cancel() defer cancel()
/* early is called right before pivot_root into intermediate root; /* early is called right before pivot_root into intermediate root;
this step is mostly for gathering information that would otherwise be difficult to obtain this step is mostly for gathering information that would otherwise be
via library functions after pivot_root, and implementations are expected to avoid changing difficult to obtain via library functions after pivot_root, and
the state of the mount namespace */ implementations are expected to avoid changing the state of the mount
namespace */
for i, op := range *params.Ops { for i, op := range *params.Ops {
if op == nil || !op.Valid() { if op == nil || !op.Valid() {
k.fatalf(msg, "invalid op at index %d", i) k.fatalf(msg, "invalid op at index %d", i)
@@ -258,10 +262,10 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
k.fatalf(msg, "cannot enter intermediate root: %v", err) k.fatalf(msg, "cannot enter intermediate root: %v", err)
} }
/* apply is called right after pivot_root and entering the new root; /* apply is called right after pivot_root and entering the new root. This
this step sets up the container filesystem, and implementations are expected to keep the host root step sets up the container filesystem, and implementations are expected to
and sysroot mount points intact but otherwise can do whatever they need to; keep the host root and sysroot mount points intact but otherwise can do
chdir is allowed but discouraged */ whatever they need to. Calling chdir is allowed but discouraged. */
for i, op := range *params.Ops { for i, op := range *params.Ops {
// ops already checked during early setup // ops already checked during early setup
if prefix, ok := op.prefix(); ok { if prefix, ok := op.prefix(); ok {

View File

@@ -12,14 +12,16 @@ import (
func init() { gob.Register(new(BindMountOp)) } func init() { gob.Register(new(BindMountOp)) }
// Bind appends an [Op] that bind mounts host path [BindMountOp.Source] on container path [BindMountOp.Target]. // Bind is a helper for appending [BindMountOp] to [Ops].
func (f *Ops) Bind(source, target *check.Absolute, flags int) *Ops { func (f *Ops) Bind(source, target *check.Absolute, flags int) *Ops {
*f = append(*f, &BindMountOp{nil, source, target, flags}) *f = append(*f, &BindMountOp{nil, source, target, flags})
return f return f
} }
// BindMountOp bind mounts host path Source on container path Target. // BindMountOp creates a bind mount from host path Source to container path Target.
// Note that Flags uses bits declared in this package and should not be set with constants in [syscall]. //
// Note that Flags uses bits declared in the [std] package and should not be set
// with constants in [syscall].
type BindMountOp struct { type BindMountOp struct {
sourceFinal, Source, Target *check.Absolute sourceFinal, Source, Target *check.Absolute

View File

@@ -24,8 +24,7 @@ const (
daemonTimeout = 5 * time.Second daemonTimeout = 5 * time.Second
) )
// Daemon appends an [Op] that starts a daemon in the container and blocks until // Daemon is a helper for appending [DaemonOp] to [Ops].
// [DaemonOp.Target] appears.
func (f *Ops) Daemon(target, path *check.Absolute, args ...string) *Ops { func (f *Ops) Daemon(target, path *check.Absolute, args ...string) *Ops {
*f = append(*f, &DaemonOp{target, path, args}) *f = append(*f, &DaemonOp{target, path, args})
return f return f

View File

@@ -19,7 +19,9 @@ func (f *Ops) Dev(target *check.Absolute, mqueue bool) *Ops {
} }
// DevWritable appends an [Op] that mounts a writable subset of host /dev. // DevWritable appends an [Op] that mounts a writable subset of host /dev.
// There is usually no good reason to write to /dev, so this should always be followed by a [RemountOp]. //
// There is usually no good reason to write to /dev, so this should always be
// followed by a [RemountOp].
func (f *Ops) DevWritable(target *check.Absolute, mqueue bool) *Ops { func (f *Ops) DevWritable(target *check.Absolute, mqueue bool) *Ops {
*f = append(*f, &MountDevOp{target, mqueue, true}) *f = append(*f, &MountDevOp{target, mqueue, true})
return f return f

View File

@@ -10,7 +10,7 @@ import (
func init() { gob.Register(new(MkdirOp)) } func init() { gob.Register(new(MkdirOp)) }
// Mkdir appends an [Op] that creates a directory in the container filesystem. // Mkdir is a helper for appending [MkdirOp] to [Ops].
func (f *Ops) Mkdir(name *check.Absolute, perm os.FileMode) *Ops { func (f *Ops) Mkdir(name *check.Absolute, perm os.FileMode) *Ops {
*f = append(*f, &MkdirOp{name, perm}) *f = append(*f, &MkdirOp{name, perm})
return f return f

View File

@@ -54,8 +54,11 @@ func (e *OverlayArgumentError) Error() string {
} }
} }
// Overlay appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target]. // Overlay is a helper for appending [MountOverlayOp] to [Ops].
func (f *Ops) Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) *Ops { func (f *Ops) Overlay(
target, state, work *check.Absolute,
layers ...*check.Absolute,
) *Ops {
*f = append(*f, &MountOverlayOp{ *f = append(*f, &MountOverlayOp{
Target: target, Target: target,
Lower: layers, Lower: layers,
@@ -65,13 +68,12 @@ func (f *Ops) Overlay(target, state, work *check.Absolute, layers ...*check.Abso
return f return f
} }
// OverlayEphemeral appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target] // OverlayEphemeral appends a [MountOverlayOp] with an ephemeral upperdir and workdir.
// with an ephemeral upperdir and workdir.
func (f *Ops) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) *Ops { func (f *Ops) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) *Ops {
return f.Overlay(target, fhs.AbsRoot, nil, layers...) return f.Overlay(target, fhs.AbsRoot, nil, layers...)
} }
// OverlayReadonly appends an [Op] that mounts the overlay pseudo filesystem readonly on [MountOverlayOp.Target] // OverlayReadonly appends a readonly [MountOverlayOp].
func (f *Ops) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) *Ops { func (f *Ops) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) *Ops {
return f.Overlay(target, nil, nil, layers...) return f.Overlay(target, nil, nil, layers...)
} }
@@ -82,25 +84,34 @@ type MountOverlayOp struct {
// Any filesystem, does not need to be on a writable filesystem. // Any filesystem, does not need to be on a writable filesystem.
Lower []*check.Absolute Lower []*check.Absolute
// formatted for [OptionOverlayLowerdir], resolved, prefixed and escaped during early // Formatted for [OptionOverlayLowerdir].
//
// Resolved, prefixed and escaped during early.
lower []string lower []string
// The upperdir is normally on a writable filesystem. // The upperdir is normally on a writable filesystem.
// //
// If Work is nil and Upper holds the special value [fhs.AbsRoot], // If Work is nil and Upper holds the special value [fhs.AbsRoot], an
// an ephemeral upperdir and workdir will be set up. // ephemeral upperdir and workdir will be set up.
// //
// If both Work and Upper are nil, upperdir and workdir is omitted and the overlay is mounted readonly. // If both Work and Upper are nil, upperdir and workdir is omitted and the
// overlay is mounted readonly.
Upper *check.Absolute Upper *check.Absolute
// formatted for [OptionOverlayUpperdir], resolved, prefixed and escaped during early // Formatted for [OptionOverlayUpperdir].
//
// Resolved, prefixed and escaped during early.
upper string upper string
// The workdir needs to be an empty directory on the same filesystem as upperdir. // The workdir needs to be an empty directory on the same filesystem as upperdir.
Work *check.Absolute Work *check.Absolute
// formatted for [OptionOverlayWorkdir], resolved, prefixed and escaped during early // Formatted for [OptionOverlayWorkdir].
//
// Resolved, prefixed and escaped during early.
work string work string
ephemeral bool ephemeral bool
// used internally for mounting to the intermediate root // Used internally for mounting to the intermediate root.
noPrefix bool noPrefix bool
} }

View File

@@ -16,7 +16,7 @@ const (
func init() { gob.Register(new(TmpfileOp)) } func init() { gob.Register(new(TmpfileOp)) }
// Place appends an [Op] that places a file in container path [TmpfileOp.Path] containing [TmpfileOp.Data]. // Place is a helper for appending [TmpfileOp] to [Ops].
func (f *Ops) Place(name *check.Absolute, data []byte) *Ops { func (f *Ops) Place(name *check.Absolute, data []byte) *Ops {
*f = append(*f, &TmpfileOp{name, data}) *f = append(*f, &TmpfileOp{name, data})
return f return f

View File

@@ -21,7 +21,7 @@ func TestTmpfileOp(t *testing.T) {
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), stub.UniqueError(5)), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, (*checkedOsFile)(nil), stub.UniqueError(5)),
}, stub.UniqueError(5)}, }, stub.UniqueError(5)},
{"Write", &Params{ParentPerm: 0700}, &TmpfileOp{ {"Write", &Params{ParentPerm: 0700}, &TmpfileOp{
@@ -35,14 +35,14 @@ func TestTmpfileOp(t *testing.T) {
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, stub.UniqueError(3)), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.Close", sampleDataString, stub.UniqueError(3)), nil),
}, stub.UniqueError(3)}, }, stub.UniqueError(3)},
{"ensureFile", &Params{ParentPerm: 0700}, &TmpfileOp{ {"ensureFile", &Params{ParentPerm: 0700}, &TmpfileOp{
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.ensureFile", sampleDataString, nil), nil),
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, stub.UniqueError(2)), call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, stub.UniqueError(2)),
}, stub.UniqueError(2)}, }, stub.UniqueError(2)},
@@ -50,29 +50,29 @@ func TestTmpfileOp(t *testing.T) {
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.bindMount", sampleDataString, nil), nil),
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil), call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, stub.UniqueError(1)), call("bindMount", stub.ExpectArgs{"tmp.bindMount", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, stub.UniqueError(1)),
}, stub.UniqueError(1)}, }, stub.UniqueError(1)},
{"remove", &Params{ParentPerm: 0700}, &TmpfileOp{ {"remove", &Params{ParentPerm: 0700}, &TmpfileOp{
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.remove", sampleDataString, nil), nil),
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil), call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil), call("bindMount", stub.ExpectArgs{"tmp.remove", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
call("remove", stub.ExpectArgs{"tmp.32768"}, nil, stub.UniqueError(0)), call("remove", stub.ExpectArgs{"tmp.remove"}, nil, stub.UniqueError(0)),
}, stub.UniqueError(0)}, }, stub.UniqueError(0)},
{"success", &Params{ParentPerm: 0700}, &TmpfileOp{ {"success", &Params{ParentPerm: 0700}, &TmpfileOp{
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.success", sampleDataString, nil), nil),
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil), call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil), call("bindMount", stub.ExpectArgs{"tmp.success", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
call("remove", stub.ExpectArgs{"tmp.32768"}, nil, nil), call("remove", stub.ExpectArgs{"tmp.success"}, nil, nil),
}, nil}, }, nil},
}) })

View File

@@ -10,7 +10,7 @@ import (
func init() { gob.Register(new(MountProcOp)) } func init() { gob.Register(new(MountProcOp)) }
// Proc appends an [Op] that mounts a private instance of proc. // Proc is a helper for appending [MountProcOp] to [Ops].
func (f *Ops) Proc(target *check.Absolute) *Ops { func (f *Ops) Proc(target *check.Absolute) *Ops {
*f = append(*f, &MountProcOp{target}) *f = append(*f, &MountProcOp{target})
return f return f

View File

@@ -9,7 +9,7 @@ import (
func init() { gob.Register(new(RemountOp)) } func init() { gob.Register(new(RemountOp)) }
// Remount appends an [Op] that applies [RemountOp.Flags] on container path [RemountOp.Target]. // Remount is a helper for appending [RemountOp] to [Ops].
func (f *Ops) Remount(target *check.Absolute, flags uintptr) *Ops { func (f *Ops) Remount(target *check.Absolute, flags uintptr) *Ops {
*f = append(*f, &RemountOp{target, flags}) *f = append(*f, &RemountOp{target, flags})
return f return f

View File

@@ -38,6 +38,7 @@ const (
_LANDLOCK_ACCESS_FS_DELIM _LANDLOCK_ACCESS_FS_DELIM
) )
// String returns a space-separated string of [LandlockAccessFS] flags.
func (f LandlockAccessFS) String() string { func (f LandlockAccessFS) String() string {
switch f { switch f {
case LANDLOCK_ACCESS_FS_EXECUTE: case LANDLOCK_ACCESS_FS_EXECUTE:
@@ -116,6 +117,7 @@ const (
_LANDLOCK_ACCESS_NET_DELIM _LANDLOCK_ACCESS_NET_DELIM
) )
// String returns a space-separated string of [LandlockAccessNet] flags.
func (f LandlockAccessNet) String() string { func (f LandlockAccessNet) String() string {
switch f { switch f {
case LANDLOCK_ACCESS_NET_BIND_TCP: case LANDLOCK_ACCESS_NET_BIND_TCP:
@@ -152,6 +154,7 @@ const (
_LANDLOCK_SCOPE_DELIM _LANDLOCK_SCOPE_DELIM
) )
// String returns a space-separated string of [LandlockScope] flags.
func (f LandlockScope) String() string { func (f LandlockScope) String() string {
switch f { switch f {
case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET:
@@ -184,10 +187,12 @@ type RulesetAttr struct {
HandledAccessFS LandlockAccessFS HandledAccessFS LandlockAccessFS
// Bitmask of handled network actions. // Bitmask of handled network actions.
HandledAccessNet LandlockAccessNet HandledAccessNet LandlockAccessNet
// Bitmask of scopes restricting a Landlock domain from accessing outside resources (e.g. IPCs). // Bitmask of scopes restricting a Landlock domain from accessing outside
// resources (e.g. IPCs).
Scoped LandlockScope Scoped LandlockScope
} }
// String returns a user-facing description of [RulesetAttr].
func (rulesetAttr *RulesetAttr) String() string { func (rulesetAttr *RulesetAttr) String() string {
if rulesetAttr == nil { if rulesetAttr == nil {
return "NULL" return "NULL"
@@ -208,6 +213,7 @@ func (rulesetAttr *RulesetAttr) String() string {
return strings.Join(elems, ", ") return strings.Join(elems, ", ")
} }
// Create loads the ruleset into the kernel.
func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) { func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
var pointer, size uintptr var pointer, size uintptr
// NULL needed for abi version // NULL needed for abi version
@@ -216,10 +222,13 @@ func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
size = unsafe.Sizeof(*rulesetAttr) size = unsafe.Sizeof(*rulesetAttr)
} }
rulesetFd, _, errno := syscall.Syscall(std.SYS_LANDLOCK_CREATE_RULESET, pointer, size, flags) rulesetFd, _, errno := syscall.Syscall(
std.SYS_LANDLOCK_CREATE_RULESET,
pointer, size,
flags,
)
fd = int(rulesetFd) fd = int(rulesetFd)
err = errno err = errno
if fd < 0 { if fd < 0 {
return return
} }
@@ -230,12 +239,19 @@ func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
return fd, nil return fd, nil
} }
// LandlockGetABI returns the ABI version supported by the kernel.
func LandlockGetABI() (int, error) { func LandlockGetABI() (int, error) {
return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION) return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION)
} }
// LandlockRestrictSelf applies a loaded ruleset to the calling thread.
func LandlockRestrictSelf(rulesetFd int, flags uintptr) error { func LandlockRestrictSelf(rulesetFd int, flags uintptr) error {
r, _, errno := syscall.Syscall(std.SYS_LANDLOCK_RESTRICT_SELF, uintptr(rulesetFd), flags, 0) r, _, errno := syscall.Syscall(
std.SYS_LANDLOCK_RESTRICT_SELF,
uintptr(rulesetFd),
flags,
0,
)
if r != 0 { if r != 0 {
return errno return errno
} }

View File

@@ -99,7 +99,7 @@ done:
} }
if m.Header.Type == NLMSG_ERROR { if m.Header.Type == NLMSG_ERROR {
if len(m.Data) >= 4 { if len(m.Data) >= 4 {
errno := Errno(-std.ScmpInt(binary.NativeEndian.Uint32(m.Data))) errno := Errno(-std.Int(binary.NativeEndian.Uint32(m.Data)))
if errno == 0 { if errno == 0 {
return nil return nil
} }

View File

@@ -15,7 +15,10 @@ import (
const ( const (
// Nonexistent is a path that cannot exist. // Nonexistent is a path that cannot exist.
// /proc is chosen because a system with covered /proc is unsupported by this package. //
// This path can never be presented by the kernel if proc is mounted on
// /proc/. This can only exist if parts of /proc/ is covered, or proc is not
// mounted at all. Neither configuration is supported by this package.
Nonexistent = fhs.Proc + "nonexistent" Nonexistent = fhs.Proc + "nonexistent"
hostPath = fhs.Root + hostDir hostPath = fhs.Root + hostDir

View File

@@ -88,18 +88,22 @@ var resPrefix = [...]string{
7: "seccomp_load failed", 7: "seccomp_load failed",
} }
// cbAllocateBuffer is the function signature for the function handle passed to hakurei_export_filter // cbAllocateBuffer is the function signature for the function handle passed to
// which allocates the buffer that the resulting bpf program is copied into, and writes its slice header // hakurei_scmp_make_filter which allocates the buffer that the resulting bpf
// to a value held by the caller. // program is copied into, and writes its slice header to a value held by the caller.
type cbAllocateBuffer = func(len C.size_t) (buf unsafe.Pointer) type cbAllocateBuffer = func(len C.size_t) (buf unsafe.Pointer)
// hakurei_scmp_allocate allocates a buffer of specified size known to the
// runtime through a callback passed in a [cgo.Handle].
//
//export hakurei_scmp_allocate //export hakurei_scmp_allocate
func hakurei_scmp_allocate(f C.uintptr_t, len C.size_t) (buf unsafe.Pointer) { func hakurei_scmp_allocate(f C.uintptr_t, len C.size_t) (buf unsafe.Pointer) {
return cgo.Handle(f).Value().(cbAllocateBuffer)(len) return cgo.Handle(f).Value().(cbAllocateBuffer)(len)
} }
// makeFilter generates a bpf program from a slice of [std.NativeRule] and writes the resulting byte slice to p. // makeFilter generates a bpf program from a slice of [std.NativeRule] and
// The filter is installed to the current process if p is nil. // writes the resulting byte slice to p. The filter is installed to the current
// process if p is nil.
func makeFilter(rules []std.NativeRule, flags ExportFlag, p *[]byte) error { func makeFilter(rules []std.NativeRule, flags ExportFlag, p *[]byte) error {
if len(rules) == 0 { if len(rules) == 0 {
return ErrInvalidRules return ErrInvalidRules
@@ -170,8 +174,8 @@ func Export(rules []std.NativeRule, flags ExportFlag) (data []byte, err error) {
return return
} }
// Load generates a bpf program from a slice of [std.NativeRule] and enforces it on the current process. // Load generates a bpf program from a slice of [std.NativeRule] and enforces it
// Errors returned by libseccomp is wrapped in [LibraryError]. // on the current process. Errors returned by libseccomp is wrapped in [LibraryError].
func Load(rules []std.NativeRule, flags ExportFlag) error { return makeFilter(rules, flags, nil) } func Load(rules []std.NativeRule, flags ExportFlag) error { return makeFilter(rules, flags, nil) }
type ( type (

View File

@@ -24,8 +24,8 @@ func TestSyscallResolveName(t *testing.T) {
} }
func TestRuleType(t *testing.T) { func TestRuleType(t *testing.T) {
assertKind[std.ScmpUint, scmpUint](t) assertKind[std.Uint, scmpUint](t)
assertKind[std.ScmpInt, scmpInt](t) assertKind[std.Int, scmpInt](t)
assertSize[std.NativeRule, syscallRule](t) assertSize[std.NativeRule, syscallRule](t)
assertKind[std.ScmpDatum, scmpDatum](t) assertKind[std.ScmpDatum, scmpDatum](t)

View File

@@ -7,24 +7,28 @@ import (
type ( type (
// ScmpUint is equivalent to C.uint. // ScmpUint is equivalent to C.uint.
ScmpUint uint32 //
// Deprecated: This type has been renamed to Uint and will be removed in 0.4.
ScmpUint = Uint
// ScmpInt is equivalent to C.int. // ScmpInt is equivalent to C.int.
ScmpInt int32 //
// Deprecated: This type has been renamed to Int and will be removed in 0.4.
ScmpInt = Int
// ScmpSyscall represents a syscall number passed to libseccomp via [NativeRule.Syscall]. // ScmpSyscall represents a syscall number passed to libseccomp via [NativeRule.Syscall].
ScmpSyscall ScmpInt ScmpSyscall Int
// ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno]. // ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno].
ScmpErrno ScmpInt ScmpErrno Int
// ScmpCompare is equivalent to enum scmp_compare; // ScmpCompare is equivalent to enum scmp_compare;
ScmpCompare ScmpUint ScmpCompare Uint
// ScmpDatum is equivalent to scmp_datum_t. // ScmpDatum is equivalent to scmp_datum_t.
ScmpDatum uint64 ScmpDatum uint64
// ScmpArgCmp is equivalent to struct scmp_arg_cmp. // ScmpArgCmp is equivalent to struct scmp_arg_cmp.
ScmpArgCmp struct { ScmpArgCmp struct {
// argument number, starting at 0 // argument number, starting at 0
Arg ScmpUint `json:"arg"` Arg Uint `json:"arg"`
// the comparison op, e.g. SCMP_CMP_* // the comparison op, e.g. SCMP_CMP_*
Op ScmpCompare `json:"op"` Op ScmpCompare `json:"op"`

8
container/std/types.go Normal file
View File

@@ -0,0 +1,8 @@
package std
type (
// Uint is equivalent to C.uint.
Uint uint32
// Int is equivalent to C.int.
Int int32
)

View File

@@ -3,6 +3,8 @@ package container
import ( import (
. "syscall" . "syscall"
"unsafe" "unsafe"
"hakurei.app/container/std"
) )
// Prctl manipulates various aspects of the behavior of the calling thread or process. // Prctl manipulates various aspects of the behavior of the calling thread or process.
@@ -41,6 +43,49 @@ func Isatty(fd int) bool {
return r == 0 return r == 0
} }
// include/uapi/linux/sched.h
const (
SCHED_NORMAL = iota
SCHED_FIFO
SCHED_RR
SCHED_BATCH
_ // SCHED_ISO: reserved but not implemented yet
SCHED_IDLE
SCHED_DEADLINE
SCHED_EXT
)
// schedParam is equivalent to struct sched_param from include/linux/sched.h.
type schedParam struct {
// sched_priority
priority std.Int
}
// schedSetscheduler sets both the scheduling policy and parameters for the
// thread whose ID is specified in tid. If tid equals zero, the scheduling
// policy and parameters of the calling thread will be set.
//
// This function is unexported because it is [very subtle to use correctly]. The
// function signature in libc is misleading: pid actually refers to a thread ID.
// The glibc wrapper for this system call ignores this semantic and exposes
// this counterintuitive behaviour.
//
// This function is only called from the container setup thread. Do not reuse
// this if you do not have something similar in place!
//
// [very subtle to use correctly]: https://www.openwall.com/lists/musl/2016/03/01/4
func schedSetscheduler(tid, policy int, param *schedParam) error {
if r, _, errno := Syscall(
SYS_SCHED_SETSCHEDULER,
uintptr(tid),
uintptr(policy),
uintptr(unsafe.Pointer(param)),
); r < 0 {
return errno
}
return nil
}
// IgnoringEINTR makes a function call and repeats it if it returns an // IgnoringEINTR makes a function call and repeats it if it returns an
// EINTR error. This appears to be required even though we install all // EINTR error. This appears to be required even though we install all
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846. // signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.

View File

@@ -2,6 +2,8 @@ package vfs
import "strings" import "strings"
// Unmangle reverses mangling of strings done by the kernel. Its behaviour is
// consistent with the equivalent function in util-linux.
func Unmangle(s string) string { func Unmangle(s string) string {
if !strings.ContainsRune(s, '\\') { if !strings.ContainsRune(s, '\\') {
return s return s

View File

@@ -24,6 +24,7 @@ var (
ErrMountInfoSep = errors.New("bad optional fields separator") ErrMountInfoSep = errors.New("bad optional fields separator")
) )
// A DecoderError describes a nonrecoverable error decoding a mountinfo stream.
type DecoderError struct { type DecoderError struct {
Op string Op string
Line int Line int
@@ -51,7 +52,8 @@ func (e *DecoderError) Error() string {
} }
type ( type (
// A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from an input stream. // A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from
// an input stream.
MountInfoDecoder struct { MountInfoDecoder struct {
s *bufio.Scanner s *bufio.Scanner
m *MountInfo m *MountInfo
@@ -72,13 +74,16 @@ type (
MountInfoEntry struct { MountInfoEntry struct {
// mount ID: a unique ID for the mount (may be reused after umount(2)). // mount ID: a unique ID for the mount (may be reused after umount(2)).
ID int `json:"id"` ID int `json:"id"`
// parent ID: the ID of the parent mount (or of self for the root of this mount namespace's mount tree). // parent ID: the ID of the parent mount (or of self for the root of
// this mount namespace's mount tree).
Parent int `json:"parent"` Parent int `json:"parent"`
// major:minor: the value of st_dev for files on this filesystem (see stat(2)). // major:minor: the value of st_dev for files on this filesystem (see stat(2)).
Devno DevT `json:"devno"` Devno DevT `json:"devno"`
// root: the pathname of the directory in the filesystem which forms the root of this mount. // root: the pathname of the directory in the filesystem which forms the
// root of this mount.
Root string `json:"root"` Root string `json:"root"`
// mount point: the pathname of the mount point relative to the process's root directory. // mount point: the pathname of the mount point relative to the
// process's root directory.
Target string `json:"target"` Target string `json:"target"`
// mount options: per-mount options (see mount(2)). // mount options: per-mount options (see mount(2)).
VfsOptstr string `json:"vfs_optstr"` VfsOptstr string `json:"vfs_optstr"`
@@ -126,7 +131,8 @@ func (e *MountInfoEntry) Flags() (flags uintptr, unmatched []string) {
// NewMountInfoDecoder returns a new decoder that reads from r. // NewMountInfoDecoder returns a new decoder that reads from r.
// //
// The decoder introduces its own buffering and may read data from r beyond the mountinfo entries requested. // The decoder introduces its own buffering and may read data from r beyond the
// mountinfo entries requested.
func NewMountInfoDecoder(r io.Reader) *MountInfoDecoder { func NewMountInfoDecoder(r io.Reader) *MountInfoDecoder {
return &MountInfoDecoder{s: bufio.NewScanner(r)} return &MountInfoDecoder{s: bufio.NewScanner(r)}
} }
@@ -271,6 +277,8 @@ func parseMountInfoLine(s string, ent *MountInfoEntry) error {
return nil return nil
} }
// EqualWithIgnore compares to [MountInfoEntry] values, ignoring fields that
// compare equal to ignore.
func (e *MountInfoEntry) EqualWithIgnore(want *MountInfoEntry, ignore string) bool { func (e *MountInfoEntry) EqualWithIgnore(want *MountInfoEntry, ignore string) bool {
return (e.ID == want.ID || want.ID == -1) && return (e.ID == want.ID || want.ID == -1) &&
(e.Parent == want.Parent || want.Parent == -1) && (e.Parent == want.Parent || want.Parent == -1) &&
@@ -284,6 +292,8 @@ func (e *MountInfoEntry) EqualWithIgnore(want *MountInfoEntry, ignore string) bo
(e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore) (e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore)
} }
// String returns a user-facing representation of a [MountInfoEntry]. It fits
// roughly into the mountinfo format, but without mangling.
func (e *MountInfoEntry) String() string { func (e *MountInfoEntry) String() string {
return fmt.Sprintf("%d %d %d:%d %s %s %s %s %s %s %s", return fmt.Sprintf("%d %d %d:%d %s %s %s %s %s %s %s",
e.ID, e.Parent, e.Devno[0], e.Devno[1], e.Root, e.Target, e.VfsOptstr, e.ID, e.Parent, e.Devno[0], e.Devno[1], e.Root, e.Target, e.VfsOptstr,

View File

@@ -6,6 +6,7 @@ import (
"strings" "strings"
) )
// UnfoldTargetError is a pathname that never appeared in a mount hierarchy.
type UnfoldTargetError string type UnfoldTargetError string
func (e UnfoldTargetError) Error() string { func (e UnfoldTargetError) Error() string {
@@ -27,6 +28,7 @@ func (n *MountInfoNode) Collective() iter.Seq[*MountInfoNode] {
return func(yield func(*MountInfoNode) bool) { n.visit(yield) } return func(yield func(*MountInfoNode) bool) { n.visit(yield) }
} }
// visit recursively visits all visible mountinfo nodes.
func (n *MountInfoNode) visit(yield func(*MountInfoNode) bool) bool { func (n *MountInfoNode) visit(yield func(*MountInfoNode) bool) bool {
if !n.Covered && !yield(n) { if !n.Covered && !yield(n) {
return false return false

4
dist/release.sh vendored
View File

@@ -13,7 +13,7 @@ echo
echo '# Building hakurei.' echo '# Building hakurei.'
go generate ./... go generate ./...
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
-buildid= -extldflags '-static' -buildid= -linkmode external -extldflags=-static
-X hakurei.app/internal/info.buildVersion=${VERSION} -X hakurei.app/internal/info.buildVersion=${VERSION}
-X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei -X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu -X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
@@ -21,7 +21,7 @@ go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
echo echo
echo '# Testing hakurei.' echo '# Testing hakurei.'
go test -ldflags='-buildid= -extldflags=-static' ./... go test -ldflags='-buildid= -linkmode external -extldflags=-static' ./...
echo echo
echo '# Creating distribution.' echo '# Creating distribution.'

View File

@@ -29,20 +29,6 @@
{ {
nixosModules.hakurei = import ./nixos.nix self.packages; nixosModules.hakurei = import ./nixos.nix self.packages;
buildPackage = forAllSystems (
system:
nixpkgsFor.${system}.callPackage (
import ./cmd/hpkg/build.nix {
inherit
nixpkgsFor
system
nixpkgs
home-manager
;
}
)
);
checks = forAllSystems ( checks = forAllSystems (
system: system:
let let
@@ -71,8 +57,6 @@
sharefs = callPackage ./cmd/sharefs/test { inherit system self; }; sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
hpkg = callPackage ./cmd/hpkg/test { inherit system self; };
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } '' formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
cd ${./.} cd ${./.}
@@ -127,11 +111,6 @@
glibc glibc
xdg-dbus-proxy xdg-dbus-proxy
# hpkg
zstd
gnutar
coreutils
# for check # for check
util-linux util-linux
nettools nettools
@@ -219,7 +198,7 @@
./test/interactive/trace.nix ./test/interactive/trace.nix
self.nixosModules.hakurei self.nixosModules.hakurei
self.inputs.home-manager.nixosModules.home-manager home-manager.nixosModules.home-manager
]; ];
}; };
in in

View File

@@ -8,7 +8,6 @@
package filelock package filelock
import ( import (
"errors"
"io/fs" "io/fs"
) )
@@ -74,10 +73,3 @@ func (lt lockType) String() string {
return "Unlock" return "Unlock"
} }
} }
// IsNotSupported returns a boolean indicating whether the error is known to
// report that a function is not supported (possibly for a specific input).
// It is satisfied by errors.ErrUnsupported as well as some syscall errors.
func IsNotSupported(err error) bool {
return errors.Is(err, errors.ErrUnsupported)
}

View File

@@ -14,7 +14,7 @@ import (
"testing" "testing"
"time" "time"
"hakurei.app/container" "hakurei.app/container/fhs"
"hakurei.app/internal/lockedfile/internal/filelock" "hakurei.app/internal/lockedfile/internal/filelock"
"hakurei.app/internal/lockedfile/internal/testexec" "hakurei.app/internal/lockedfile/internal/testexec"
) )
@@ -197,7 +197,7 @@ func TestLockNotDroppedByExecCommand(t *testing.T) {
// Some kinds of file locks are dropped when a duplicated or forked file // Some kinds of file locks are dropped when a duplicated or forked file
// descriptor is unlocked. Double-check that the approach used by os/exec does // descriptor is unlocked. Double-check that the approach used by os/exec does
// not accidentally drop locks. // not accidentally drop locks.
cmd := testexec.CommandContext(t, t.Context(), container.MustExecutable(nil), "-test.run=^$") cmd := testexec.CommandContext(t, t.Context(), fhs.ProcSelfExe, "-test.run=^$")
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
t.Fatalf("exec failed: %v", err) t.Fatalf("exec failed: %v", err)
} }

View File

@@ -94,6 +94,11 @@ func (f *File) Close() error {
err := closeFile(f.osFile.File) err := closeFile(f.osFile.File)
f.cleanup.Stop() f.cleanup.Stop()
// f may be dead at the moment after we access f.cleanup,
// so the cleanup can fire before Stop completes. Keep f
// alive while we call Stop. See the documentation for
// runtime.Cleanup.Stop.
runtime.KeepAlive(f)
return err return err
} }

View File

@@ -15,7 +15,7 @@ import (
"testing" "testing"
"time" "time"
"hakurei.app/container" "hakurei.app/container/fhs"
"hakurei.app/internal/lockedfile" "hakurei.app/internal/lockedfile"
"hakurei.app/internal/lockedfile/internal/testexec" "hakurei.app/internal/lockedfile/internal/testexec"
) )
@@ -215,7 +215,7 @@ func TestSpuriousEDEADLK(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
cmd := testexec.CommandContext(t, t.Context(), container.MustExecutable(nil), "-test.run=^"+t.Name()+"$") cmd := testexec.CommandContext(t, t.Context(), fhs.ProcSelfExe, "-test.run=^"+t.Name()+"$")
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", dirVar, dir)) cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", dirVar, dir))
qDone := make(chan struct{}) qDone := make(chan struct{})

View File

@@ -23,7 +23,7 @@ import (
"hakurei.app/message" "hakurei.app/message"
) )
// AbsWork is the container pathname [CureContext.GetWorkDir] is mounted on. // AbsWork is the container pathname [TContext.GetWorkDir] is mounted on.
var AbsWork = fhs.AbsRoot.Append("work/") var AbsWork = fhs.AbsRoot.Append("work/")
// ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make // ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make
@@ -39,22 +39,23 @@ type ExecPath struct {
W bool W bool
} }
// layers returns pathnames collected from A deduplicated by checksum. // SchedPolicy is the [container] scheduling policy.
func (p *ExecPath) layers(f *FContext) []*check.Absolute { var SchedPolicy int
msg := f.GetMessage()
layers := make([]*check.Absolute, 0, len(p.A)) // PromoteLayers returns artifacts with identical-by-content layers promoted to
checksums := make(map[unique.Handle[Checksum]]struct{}, len(p.A)) // the highest priority instance, as if mounted via [ExecPath].
for i := range p.A { func PromoteLayers(
d := p.A[len(p.A)-1-i] artifacts []Artifact,
pathname, checksum := f.GetArtifact(d) getArtifact func(Artifact) (*check.Absolute, unique.Handle[Checksum]),
report func(i int, d Artifact),
) []*check.Absolute {
layers := make([]*check.Absolute, 0, len(artifacts))
checksums := make(map[unique.Handle[Checksum]]struct{}, len(artifacts))
for i := range artifacts {
d := artifacts[len(artifacts)-1-i]
pathname, checksum := getArtifact(d)
if _, ok := checksums[checksum]; ok { if _, ok := checksums[checksum]; ok {
if msg.IsVerbose() { report(len(artifacts)-1-i, d)
msg.Verbosef(
"promoted layer %d as %s",
len(p.A)-1-i, reportName(d, f.cache.Ident(d)),
)
}
continue continue
} }
checksums[checksum] = struct{}{} checksums[checksum] = struct{}{}
@@ -64,6 +65,19 @@ func (p *ExecPath) layers(f *FContext) []*check.Absolute {
return layers return layers
} }
// layers returns pathnames collected from A deduplicated via [PromoteLayers].
func (p *ExecPath) layers(f *FContext) []*check.Absolute {
msg := f.GetMessage()
return PromoteLayers(p.A, f.GetArtifact, func(i int, d Artifact) {
if msg.IsVerbose() {
msg.Verbosef(
"promoted layer %d as %s",
i, reportName(d, f.cache.Ident(d)),
)
}
})
}
// Path returns a populated [ExecPath]. // Path returns a populated [ExecPath].
func Path(pathname *check.Absolute, writable bool, a ...Artifact) ExecPath { func Path(pathname *check.Absolute, writable bool, a ...Artifact) ExecPath {
return ExecPath{pathname, a, writable} return ExecPath{pathname, a, writable}
@@ -365,6 +379,10 @@ func scanVerbose(
} }
} }
// SeccompPresets is the [seccomp] presets used by exec artifacts.
const SeccompPresets = std.PresetStrict &
^(std.PresetDenyNS | std.PresetDenyDevel)
// cure is like Cure but allows optional host net namespace. This is used for // cure is like Cure but allows optional host net namespace. This is used for
// the [KnownChecksum] variant where networking is allowed. // the [KnownChecksum] variant where networking is allowed.
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
@@ -388,11 +406,12 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
z := container.New(ctx, f.GetMessage()) z := container.New(ctx, f.GetMessage())
z.WaitDelay = execWaitDelay z.WaitDelay = execWaitDelay
z.SeccompPresets |= std.PresetStrict & ^std.PresetDenyNS z.SeccompPresets = SeccompPresets
z.SeccompFlags |= seccomp.AllowMultiarch z.SeccompFlags |= seccomp.AllowMultiarch
z.ParentPerm = 0700 z.ParentPerm = 0700
z.HostNet = hostNet z.HostNet = hostNet
z.Hostname = "cure" z.Hostname = "cure"
z.SchedPolicy = SchedPolicy
if z.HostNet { if z.HostNet {
z.Hostname = "cure-net" z.Hostname = "cure-net"
} }

View File

@@ -150,7 +150,7 @@ func (i *IContext) WriteUint32(v uint32) {
} }
// irMaxStringLength is the maximum acceptable wire size of [IRKindString]. // irMaxStringLength is the maximum acceptable wire size of [IRKindString].
const irMaxStringLength = 1 << 20 const irMaxStringLength = 1 << 24
// IRStringError is a string value too big to encode in IR. // IRStringError is a string value too big to encode in IR.
type IRStringError string type IRStringError string
@@ -310,6 +310,13 @@ type (
// verbose logging is enabled. Artifacts may only depend on artifacts // verbose logging is enabled. Artifacts may only depend on artifacts
// previously described in the IR stream. // previously described in the IR stream.
// //
// IRDecoder rejects an IR stream on the first decoding error, it does not
// check against nonzero reserved ancillary data or incorrectly ordered or
// redundant unstructured dependencies. An invalid IR stream as such will
// yield [Artifact] values with identifiers disagreeing with those computed
// by IRDecoder. For this reason, IRDecoder does not access the ident cache
// to avoid putting [Cache] into an inconsistent state.
//
// Methods of IRDecoder are not safe for concurrent use. // Methods of IRDecoder are not safe for concurrent use.
IRDecoder struct { IRDecoder struct {
// Address of underlying [Cache], must not be exposed directly. // Address of underlying [Cache], must not be exposed directly.

View File

@@ -65,18 +65,23 @@ func MustDecode(s string) (checksum Checksum) {
return return
} }
// common holds elements and receives methods shared between different contexts.
type common struct {
// Address of underlying [Cache], should be zeroed or made unusable after
// Cure returns and must not be exposed directly.
cache *Cache
}
// TContext is passed to [TrivialArtifact.Cure] and provides information and // TContext is passed to [TrivialArtifact.Cure] and provides information and
// methods required for curing the [TrivialArtifact]. // methods required for curing the [TrivialArtifact].
// //
// Methods of TContext are safe for concurrent use. TContext is valid // Methods of TContext are safe for concurrent use. TContext is valid
// until [TrivialArtifact.Cure] returns. // until [TrivialArtifact.Cure] returns.
type TContext struct { type TContext struct {
// Address of underlying [Cache], should be zeroed or made unusable after
// [TrivialArtifact.Cure] returns and must not be exposed directly.
cache *Cache
// Populated during [Cache.Cure]. // Populated during [Cache.Cure].
work, temp *check.Absolute work, temp *check.Absolute
common
} }
// destroy destroys the temporary directory and joins its errors with the error // destroy destroys the temporary directory and joins its errors with the error
@@ -104,10 +109,10 @@ func (t *TContext) destroy(errP *error) {
} }
// Unwrap returns the underlying [context.Context]. // Unwrap returns the underlying [context.Context].
func (t *TContext) Unwrap() context.Context { return t.cache.ctx } func (c *common) Unwrap() context.Context { return c.cache.ctx }
// GetMessage returns [message.Msg] held by the underlying [Cache]. // GetMessage returns [message.Msg] held by the underlying [Cache].
func (t *TContext) GetMessage() message.Msg { return t.cache.msg } func (c *common) GetMessage() message.Msg { return c.cache.msg }
// GetWorkDir returns a pathname to a directory which [Artifact] is expected to // GetWorkDir returns a pathname to a directory which [Artifact] is expected to
// write its output to. This is not the final resting place of the [Artifact] // write its output to. This is not the final resting place of the [Artifact]
@@ -126,13 +131,13 @@ func (t *TContext) GetTempDir() *check.Absolute { return t.temp }
// If err is nil, the caller must close the resulting [io.ReadCloser] and return // If err is nil, the caller must close the resulting [io.ReadCloser] and return
// its error, if any. Failure to read r to EOF may result in a spurious // its error, if any. Failure to read r to EOF may result in a spurious
// [ChecksumMismatchError], or the underlying implementation may block on Close. // [ChecksumMismatchError], or the underlying implementation may block on Close.
func (t *TContext) Open(a Artifact) (r io.ReadCloser, err error) { func (c *common) Open(a Artifact) (r io.ReadCloser, err error) {
if f, ok := a.(FileArtifact); ok { if f, ok := a.(FileArtifact); ok {
return t.cache.openFile(f) return c.cache.openFile(f)
} }
var pathname *check.Absolute var pathname *check.Absolute
if pathname, _, err = t.cache.Cure(a); err != nil { if pathname, _, err = c.cache.Cure(a); err != nil {
return return
} }
@@ -191,14 +196,7 @@ func (f *FContext) GetArtifact(a Artifact) (
// //
// Methods of RContext are safe for concurrent use. RContext is valid // Methods of RContext are safe for concurrent use. RContext is valid
// until [FileArtifact.Cure] returns. // until [FileArtifact.Cure] returns.
type RContext struct { type RContext struct{ common }
// Address of underlying [Cache], should be zeroed or made unusable after
// [FileArtifact.Cure] returns and must not be exposed directly.
cache *Cache
}
// Unwrap returns the underlying [context.Context].
func (r *RContext) Unwrap() context.Context { return r.cache.ctx }
// An Artifact is a read-only reference to a piece of data that may be created // An Artifact is a read-only reference to a piece of data that may be created
// deterministically but might not currently be available in memory or on the // deterministically but might not currently be available in memory or on the
@@ -466,7 +464,7 @@ type Cache struct {
// Synchronises entry into exclusive artifacts for the cure method. // Synchronises entry into exclusive artifacts for the cure method.
exclMu sync.Mutex exclMu sync.Mutex
// Buffered I/O free list, must not be accessed directly. // Buffered I/O free list, must not be accessed directly.
bufioPool sync.Pool brPool, bwPool sync.Pool
// Unlocks the on-filesystem cache. Must only be called from Close. // Unlocks the on-filesystem cache. Must only be called from Close.
unlock func() unlock func()
@@ -548,6 +546,26 @@ func (c *Cache) unsafeIdent(a Artifact, encodeKind bool) (
return return
} }
// getReader is like [bufio.NewReader] but for brPool.
func (c *Cache) getReader(r io.Reader) *bufio.Reader {
br := c.brPool.Get().(*bufio.Reader)
br.Reset(r)
return br
}
// putReader adds br to brPool.
func (c *Cache) putReader(br *bufio.Reader) { c.brPool.Put(br) }
// getWriter is like [bufio.NewWriter] but for bwPool.
func (c *Cache) getWriter(w io.Writer) *bufio.Writer {
bw := c.bwPool.Get().(*bufio.Writer)
bw.Reset(w)
return bw
}
// putWriter adds bw to bwPool.
func (c *Cache) putWriter(bw *bufio.Writer) { c.bwPool.Put(bw) }
// A ChecksumMismatchError describes an [Artifact] with unexpected content. // A ChecksumMismatchError describes an [Artifact] with unexpected content.
type ChecksumMismatchError struct { type ChecksumMismatchError struct {
// Actual and expected checksums. // Actual and expected checksums.
@@ -927,14 +945,14 @@ func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
} }
if c.msg.IsVerbose() { if c.msg.IsVerbose() {
rn := reportName(f, c.Ident(f)) rn := reportName(f, c.Ident(f))
c.msg.Verbosef("curing %s to memory...", rn) c.msg.Verbosef("curing %s in memory...", rn)
defer func() { defer func() {
if err == nil { if err == nil {
c.msg.Verbosef("cured %s to memory", rn) c.msg.Verbosef("opened %s for reading", rn)
} }
}() }()
} }
return f.Cure(&RContext{c}) return f.Cure(&RContext{common{c}})
} }
return return
} }
@@ -1131,6 +1149,9 @@ type DependencyCureError []*CureError
// unwrapM recursively expands underlying errors into a caller-supplied map. // unwrapM recursively expands underlying errors into a caller-supplied map.
func (e *DependencyCureError) unwrapM(me map[unique.Handle[ID]]*CureError) { func (e *DependencyCureError) unwrapM(me map[unique.Handle[ID]]*CureError) {
for _, err := range *e { for _, err := range *e {
if _, ok := me[err.Ident]; ok {
continue
}
if _e, ok := err.Err.(*DependencyCureError); ok { if _e, ok := err.Err.(*DependencyCureError); ok {
_e.unwrapM(me) _e.unwrapM(me)
continue continue
@@ -1214,13 +1235,6 @@ func (c *Cache) exitCure(a Artifact, curesExempt bool) {
<-c.cures <-c.cures
} }
// getWriter is like [bufio.NewWriter] but for bufioPool.
func (c *Cache) getWriter(w io.Writer) *bufio.Writer {
bw := c.bufioPool.Get().(*bufio.Writer)
bw.Reset(w)
return bw
}
// measuredReader implements [io.ReadCloser] and measures the checksum during // measuredReader implements [io.ReadCloser] and measures the checksum during
// Close. If the underlying reader is not read to EOF, Close blocks until all // Close. If the underlying reader is not read to EOF, Close blocks until all
// remaining data is consumed and validated. // remaining data is consumed and validated.
@@ -1303,9 +1317,6 @@ func (r *RContext) NewMeasuredReader(
return r.cache.newMeasuredReader(rc, checksum) return r.cache.newMeasuredReader(rc, checksum)
} }
// putWriter adds bw to bufioPool.
func (c *Cache) putWriter(bw *bufio.Writer) { c.bufioPool.Put(bw) }
// cure implements Cure without checking the full dependency graph. // cure implements Cure without checking the full dependency graph.
func (c *Cache) cure(a Artifact, curesExempt bool) ( func (c *Cache) cure(a Artifact, curesExempt bool) (
pathname *check.Absolute, pathname *check.Absolute,
@@ -1437,7 +1448,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
if err = c.enterCure(a, curesExempt); err != nil { if err = c.enterCure(a, curesExempt); err != nil {
return return
} }
r, err = f.Cure(&RContext{c}) r, err = f.Cure(&RContext{common{c}})
if err == nil { if err == nil {
if checksumPathname == nil || c.IsStrict() { if checksumPathname == nil || c.IsStrict() {
h := sha512.New384() h := sha512.New384()
@@ -1513,7 +1524,11 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
return return
} }
t := TContext{c, c.base.Append(dirWork, ids), c.base.Append(dirTemp, ids)} t := TContext{
c.base.Append(dirWork, ids),
c.base.Append(dirTemp, ids),
common{c},
}
switch ca := a.(type) { switch ca := a.(type) {
case TrivialArtifact: case TrivialArtifact:
defer t.destroy(&err) defer t.destroy(&err)
@@ -1713,13 +1728,16 @@ func open(
msg: msg, msg: msg,
base: base, base: base,
identPool: sync.Pool{New: func() any { return new(extIdent) }},
ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]), ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]),
identErr: make(map[unique.Handle[ID]]error), identErr: make(map[unique.Handle[ID]]error),
identPending: make(map[unique.Handle[ID]]<-chan struct{}), identPending: make(map[unique.Handle[ID]]<-chan struct{}),
brPool: sync.Pool{New: func() any { return new(bufio.Reader) }},
bwPool: sync.Pool{New: func() any { return new(bufio.Writer) }},
} }
c.ctx, c.cancel = context.WithCancel(ctx) c.ctx, c.cancel = context.WithCancel(ctx)
c.identPool.New = func() any { return new(extIdent) }
c.bufioPool.New = func() any { return new(bufio.Writer) }
if lock || !testing.Testing() { if lock || !testing.Testing() {
if unlock, err := lockedfile.MutexAt( if unlock, err := lockedfile.MutexAt(

View File

@@ -10,8 +10,7 @@ import (
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
"path"
"hakurei.app/container/check"
) )
const ( const (
@@ -100,7 +99,6 @@ func (e DisallowedTypeflagError) Error() string {
// Cure cures the [Artifact], producing a directory located at work. // Cure cures the [Artifact], producing a directory located at work.
func (a *tarArtifact) Cure(t *TContext) (err error) { func (a *tarArtifact) Cure(t *TContext) (err error) {
temp := t.GetTempDir()
var tr io.ReadCloser var tr io.ReadCloser
if tr, err = t.Open(a.f); err != nil { if tr, err = t.Open(a.f); err != nil {
return return
@@ -116,7 +114,9 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
err = closeErr err = closeErr
} }
}(tr) }(tr)
tr = io.NopCloser(tr) br := t.cache.getReader(tr)
defer t.cache.putReader(br)
tr = io.NopCloser(br)
switch a.compression { switch a.compression {
case TarUncompressed: case TarUncompressed:
@@ -137,14 +137,24 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
} }
type dirTargetPerm struct { type dirTargetPerm struct {
path *check.Absolute path string
mode fs.FileMode mode fs.FileMode
} }
var madeDirectories []dirTargetPerm var madeDirectories []dirTargetPerm
if err = os.MkdirAll(temp.String(), 0700); err != nil { if err = os.MkdirAll(t.GetTempDir().String(), 0700); err != nil {
return return
} }
var root *os.Root
if root, err = os.OpenRoot(t.GetTempDir().String()); err != nil {
return
}
defer func() {
closeErr := root.Close()
if err == nil {
err = closeErr
}
}()
var header *tar.Header var header *tar.Header
r := tar.NewReader(tr) r := tar.NewReader(tr)
@@ -158,9 +168,8 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
} }
} }
pathname := temp.Append(header.Name)
if typeflag >= '0' && typeflag <= '9' && typeflag != tar.TypeDir { if typeflag >= '0' && typeflag <= '9' && typeflag != tar.TypeDir {
if err = os.MkdirAll(pathname.Dir().String(), 0700); err != nil { if err = root.MkdirAll(path.Dir(header.Name), 0700); err != nil {
return return
} }
} }
@@ -168,8 +177,8 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
switch typeflag { switch typeflag {
case tar.TypeReg: case tar.TypeReg:
var f *os.File var f *os.File
if f, err = os.OpenFile( if f, err = root.OpenFile(
pathname.String(), header.Name,
os.O_CREATE|os.O_EXCL|os.O_WRONLY, os.O_CREATE|os.O_EXCL|os.O_WRONLY,
header.FileInfo().Mode()&0500, header.FileInfo().Mode()&0500,
); err != nil { ); err != nil {
@@ -184,26 +193,29 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
break break
case tar.TypeLink: case tar.TypeLink:
if err = os.Link( if err = root.Link(
temp.Append(header.Linkname).String(), header.Linkname,
pathname.String(), header.Name,
); err != nil { ); err != nil {
return return
} }
break break
case tar.TypeSymlink: case tar.TypeSymlink:
if err = os.Symlink(header.Linkname, pathname.String()); err != nil { if err = root.Symlink(
header.Linkname,
header.Name,
); err != nil {
return return
} }
break break
case tar.TypeDir: case tar.TypeDir:
madeDirectories = append(madeDirectories, dirTargetPerm{ madeDirectories = append(madeDirectories, dirTargetPerm{
path: pathname, path: header.Name,
mode: header.FileInfo().Mode(), mode: header.FileInfo().Mode(),
}) })
if err = os.MkdirAll(pathname.String(), 0700); err != nil { if err = root.MkdirAll(header.Name, 0700); err != nil {
return return
} }
break break
@@ -220,7 +232,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
} }
if err == nil { if err == nil {
for _, e := range madeDirectories { for _, e := range madeDirectories {
if err = os.Chmod(e.path.String(), e.mode&0500); err != nil { if err = root.Chmod(e.path, e.mode&0500); err != nil {
return return
} }
} }
@@ -228,6 +240,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
return return
} }
temp := t.GetTempDir()
if err = os.Chmod(temp.String(), 0700); err != nil { if err = os.Chmod(temp.String(), 0700); err != nil {
return return
} }

View File

@@ -7,13 +7,14 @@ func (t Toolchain) newAttr() pkg.Artifact {
version = "2.5.2" version = "2.5.2"
checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l" checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l"
) )
return t.NewViaMake("attr", version, t.NewPatchedSource( return t.NewPackage("attr", version, pkg.NewHTTPGetTar(
"attr", version, pkg.NewHTTPGetTar( nil, "https://download.savannah.nongnu.org/releases/attr/"+
nil, "https://download.savannah.nongnu.org/releases/attr/"+ "attr-"+version+".tar.gz",
"attr-"+version+".tar.gz", mustDecode(checksum),
mustDecode(checksum), pkg.TarGzip,
pkg.TarGzip, ), &PackageAttr{
), true, [2]string{"libgen-basename", `From 8a80d895dfd779373363c3a4b62ecce5a549efb2 Mon Sep 17 00:00:00 2001 Patches: [][2]string{
{"libgen-basename", `From 8a80d895dfd779373363c3a4b62ecce5a549efb2 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me> From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Sat, 30 Mar 2024 10:17:10 +0100 Date: Sat, 30 Mar 2024 10:17:10 +0100
Subject: tools/attr.c: Add missing libgen.h include for basename(3) Subject: tools/attr.c: Add missing libgen.h include for basename(3)
@@ -38,29 +39,29 @@ index f12e4af..6a3c1e9 100644
#include <attr/attributes.h> #include <attr/attributes.h>
-- --
cgit v1.1`}, [2]string{"musl-errno", `diff --git a/test/attr.test b/test/attr.test cgit v1.1`},
{"musl-errno", `diff --git a/test/attr.test b/test/attr.test
index 6ce2f9b..e9bde92 100644 index 6ce2f9b..e9bde92 100644
--- a/test/attr.test --- a/test/attr.test
+++ b/test/attr.test +++ b/test/attr.test
@@ -11,7 +11,7 @@ Try various valid and invalid names @@ -11,7 +11,7 @@ Try various valid and invalid names
$ touch f $ touch f
$ setfattr -n user -v value f $ setfattr -n user -v value f
- > setfattr: f: Operation not supported - > setfattr: f: Operation not supported
+ > setfattr: f: Not supported + > setfattr: f: Not supported
$ setfattr -n user. -v value f $ setfattr -n user. -v value f
> setfattr: f: Invalid argument > setfattr: f: Invalid argument
`}, `},
), &MakeAttr{ },
ScriptEarly: ` ScriptEarly: `
ln -s ../../system/bin/perl /usr/bin ln -s ../../system/bin/perl /usr/bin
`, `,
Configure: [][2]string{ }, (*MakeHelper)(nil),
{"enable-static"}, Perl,
},
},
t.Load(Perl),
) )
} }
func init() { artifactsF[Attr] = Toolchain.newAttr } func init() { artifactsF[Attr] = Toolchain.newAttr }
@@ -70,21 +71,16 @@ func (t Toolchain) newACL() pkg.Artifact {
version = "2.3.2" version = "2.3.2"
checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P" checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P"
) )
return t.NewViaMake("acl", version, pkg.NewHTTPGetTar( return t.NewPackage("acl", version, pkg.NewHTTPGetTar(
nil, nil, "https://download.savannah.nongnu.org/releases/acl/"+
"https://download.savannah.nongnu.org/releases/acl/"+
"acl-"+version+".tar.gz", "acl-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), nil, &MakeHelper{
Configure: [][2]string{
{"enable-static"},
},
// makes assumptions about uid_map/gid_map // makes assumptions about uid_map/gid_map
SkipCheck: true, SkipCheck: true,
}, },
t.Load(Attr), Attr,
) )
} }
func init() { artifactsF[ACL] = Toolchain.newACL } func init() { artifactsF[ACL] = Toolchain.newACL }

View File

@@ -10,20 +10,40 @@ import (
type PArtifact int type PArtifact int
const ( const (
ACL PArtifact = iota // ImageInitramfs is the Rosa OS initramfs archive.
ImageInitramfs PArtifact = iota
// Kernel is the generic Rosa OS Linux kernel.
Kernel
// KernelHeaders is an installation of kernel headers for [Kernel].
KernelHeaders
// KernelSource is a writable kernel source tree installed to [AbsUsrSrc].
KernelSource
ACL
ArgpStandalone
Attr Attr
Autoconf Autoconf
Automake Automake
BC
Bash Bash
Binutils Binutils
Bison
Bzip2
CMake CMake
Coreutils Coreutils
Curl Curl
DTC
Diffutils Diffutils
Elfutils
Fakeroot
Findutils Findutils
Flex
Fuse Fuse
Gawk
GMP GMP
GLib
Gawk
GenInitCPIO
Gettext Gettext
Git Git
Go Go
@@ -33,9 +53,11 @@ const (
Hakurei Hakurei
HakureiDist HakureiDist
IniConfig IniConfig
KernelHeaders Kmod
LibXau LibXau
Libcap
Libexpat Libexpat
Libiconv
Libpsl Libpsl
Libffi Libffi
Libgd Libgd
@@ -43,31 +65,52 @@ const (
Libseccomp Libseccomp
Libucontext Libucontext
Libxml2 Libxml2
Libxslt
M4 M4
MPC MPC
MPFR MPFR
Make Make
Meson Meson
Mksh Mksh
MuslFts
MuslObstack
NSS NSS
NSSCACert NSSCACert
Ncurses
Ninja Ninja
OpenSSL OpenSSL
PCRE2
Packaging Packaging
Patch Patch
Perl Perl
PerlLocaleGettext
PerlMIMECharset
PerlModuleBuild
PerlPodParser
PerlSGMLS
PerlTermReadKey
PerlTextCharWidth
PerlTextWrapI18N
PerlUnicodeGCString
PerlYAMLTiny
PkgConfig PkgConfig
Pluggy Pluggy
Procps
PyTest PyTest
Pygments Pygments
Python Python
QEMU
Rsync Rsync
Sed Sed
Setuptools Setuptools
SquashfsTools
TamaGo
Tar
Texinfo
Toybox Toybox
toyboxEarly toyboxEarly
Unzip Unzip
utilMacros UtilLinux
Wayland Wayland
WaylandProtocols WaylandProtocols
XCB XCB
@@ -75,13 +118,22 @@ const (
Xproto Xproto
XZ XZ
Zlib Zlib
Zstd
buildcatrust buildcatrust
utilMacros
// Musl is a standalone libc that does not depend on the toolchain.
Musl
// gcc is a hacked-to-pieces GCC toolchain meant for use in intermediate // gcc is a hacked-to-pieces GCC toolchain meant for use in intermediate
// stages only. This preset and its direct output must never be exposed. // stages only. This preset and its direct output must never be exposed.
gcc gcc
// Stage0 is a tarball containing all compile-time dependencies of artifacts
// part of the [Std] toolchain.
Stage0
// _presetEnd is the total number of presets and does not denote a preset. // _presetEnd is the total number of presets and does not denote a preset.
_presetEnd _presetEnd
) )
@@ -107,20 +159,36 @@ func (t Toolchain) Load(p PArtifact) pkg.Artifact {
// ResolveName returns a [PArtifact] by name. // ResolveName returns a [PArtifact] by name.
func ResolveName(name string) (p PArtifact, ok bool) { func ResolveName(name string) (p PArtifact, ok bool) {
p, ok = map[string]PArtifact{ p, ok = map[string]PArtifact{
"initramfs-image": ImageInitramfs,
"kernel": Kernel,
"kernel-headers": KernelHeaders,
"kernel-source": KernelSource,
"acl": ACL, "acl": ACL,
"argp-standalone": ArgpStandalone,
"attr": Attr, "attr": Attr,
"autoconf": Autoconf, "autoconf": Autoconf,
"automake": Automake, "automake": Automake,
"bc": BC,
"bash": Bash, "bash": Bash,
"binutils": Binutils, "binutils": Binutils,
"bison": Bison,
"bzip2": Bzip2,
"cmake": CMake, "cmake": CMake,
"coreutils": Coreutils, "coreutils": Coreutils,
"curl": Curl, "curl": Curl,
"dtc": DTC,
"diffutils": Diffutils, "diffutils": Diffutils,
"elfutils": Elfutils,
"fakeroot": Fakeroot,
"findutils": Findutils, "findutils": Findutils,
"flex": Flex,
"fuse": Fuse, "fuse": Fuse,
"gawk": Gawk,
"gmp": GMP, "gmp": GMP,
"glib": GLib,
"gawk": Gawk,
"gen_init_cpio": GenInitCPIO,
"gettext": Gettext, "gettext": Gettext,
"git": Git, "git": Git,
"go": Go, "go": Go,
@@ -130,13 +198,16 @@ func ResolveName(name string) (p PArtifact, ok bool) {
"hakurei": Hakurei, "hakurei": Hakurei,
"hakurei-dist": HakureiDist, "hakurei-dist": HakureiDist,
"iniconfig": IniConfig, "iniconfig": IniConfig,
"kernel-headers": KernelHeaders, "kmod": Kmod,
"libXau": LibXau, "libXau": LibXau,
"libcap": Libcap,
"libexpat": Libexpat, "libexpat": Libexpat,
"libiconv": Libiconv,
"libpsl": Libpsl, "libpsl": Libpsl,
"libseccomp": Libseccomp, "libseccomp": Libseccomp,
"libucontext": Libucontext, "libucontext": Libucontext,
"libxml2": Libxml2, "libxml2": Libxml2,
"libxslt": Libxslt,
"libffi": Libffi, "libffi": Libffi,
"libgd": Libgd, "libgd": Libgd,
"libtool": Libtool, "libtool": Libtool,
@@ -146,23 +217,44 @@ func ResolveName(name string) (p PArtifact, ok bool) {
"make": Make, "make": Make,
"meson": Meson, "meson": Meson,
"mksh": Mksh, "mksh": Mksh,
"musl-fts": MuslFts,
"musl-obstack": MuslObstack,
"nss": NSS, "nss": NSS,
"nss-cacert": NSSCACert, "nss-cacert": NSSCACert,
"ncurses": Ncurses,
"ninja": Ninja, "ninja": Ninja,
"openssl": OpenSSL, "openssl": OpenSSL,
"pcre2": PCRE2,
"packaging": Packaging, "packaging": Packaging,
"patch": Patch, "patch": Patch,
"perl": Perl, "perl": Perl,
"Locale::gettext": PerlLocaleGettext,
"MIME::Charset": PerlMIMECharset,
"Module::Build": PerlModuleBuild,
"Pod::Parser": PerlPodParser,
"SGMLS": PerlSGMLS,
"Term::ReadKey": PerlTermReadKey,
"Text::CharWidth": PerlTextCharWidth,
"Text::WrapI18N": PerlTextWrapI18N,
"Unicode::GCString": PerlUnicodeGCString,
"YAML::Tiny": PerlYAMLTiny,
"pkg-config": PkgConfig, "pkg-config": PkgConfig,
"pluggy": Pluggy, "pluggy": Pluggy,
"procps": Procps,
"pytest": PyTest, "pytest": PyTest,
"pygments": Pygments, "pygments": Pygments,
"python": Python, "python": Python,
"qemu": QEMU,
"rsync": Rsync, "rsync": Rsync,
"sed": Sed, "sed": Sed,
"setuptools": Setuptools, "setuptools": Setuptools,
"squashfs-tools": SquashfsTools,
"tamago": TamaGo,
"tar": Tar,
"texinfo": Texinfo,
"toybox": Toybox, "toybox": Toybox,
"unzip": Unzip, "unzip": Unzip,
"util-linux": UtilLinux,
"wayland": Wayland, "wayland": Wayland,
"wayland-protocols": WaylandProtocols, "wayland-protocols": WaylandProtocols,
"xcb": XCB, "xcb": XCB,
@@ -170,6 +262,7 @@ func ResolveName(name string) (p PArtifact, ok bool) {
"xproto": Xproto, "xproto": Xproto,
"xz": XZ, "xz": XZ,
"zlib": Zlib, "zlib": Zlib,
"zstd": Zstd,
}[name] }[name]
return return
} }

View File

@@ -0,0 +1,28 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newArgpStandalone() pkg.Artifact {
const (
version = "1.3"
checksum = "vtW0VyO2pJ-hPyYmDI2zwSLS8QL0sPAUKC1t3zNYbwN2TmsaE-fADhaVtNd3eNFl"
)
return t.NewPackage("argp-standalone", version, pkg.NewHTTPGetTar(
nil, "http://www.lysator.liu.se/~nisse/misc/"+
"argp-standalone-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), &PackageAttr{
Env: []string{
"CC=cc -std=gnu89 -fPIC",
},
}, &MakeHelper{
Install: `
install -D -m644 /usr/src/argp-standalone/argp.h /work/system/include/argp.h
install -D -m755 libargp.a /work/system/lib/libargp.a
`,
},
Diffutils,
)
}
func init() { artifactsF[ArgpStandalone] = Toolchain.newArgpStandalone }

28
internal/rosa/bzip2.go Normal file
View File

@@ -0,0 +1,28 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newBzip2() pkg.Artifact {
const (
version = "1.0.8"
checksum = "cTLykcco7boom-s05H1JVsQi1AtChYL84nXkg_92Dm1Xt94Ob_qlMg_-NSguIK-c"
)
return t.NewPackage("bzip2", version, pkg.NewHTTPGetTar(
nil, "https://sourceware.org/pub/bzip2/bzip2-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), &PackageAttr{
Writable: true,
EnterSource: true,
}, &MakeHelper{
// uses source tree as scratch space
SkipConfigure: true,
SkipCheck: true,
InPlace: true,
Make: []string{
"CC=cc",
},
Install: "make PREFIX=/work/system install",
})
}
func init() { artifactsF[Bzip2] = Toolchain.newBzip2 }

View File

@@ -1,82 +1,159 @@
package rosa package rosa
import ( import (
"path"
"slices" "slices"
"strings" "strings"
"hakurei.app/container/check"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
) )
func (t Toolchain) newCMake() pkg.Artifact { func (t Toolchain) newCMake() pkg.Artifact {
const ( const (
version = "4.2.1" version = "4.2.3"
checksum = "Y3OdbMsob6Xk2y1DCME6z4Fryb5_TkFD7knRT8dTNIRtSqbiCJyyDN9AxggN_I75" checksum = "Y4uYGnLrDQX78UdzH7fMzfok46Nt_1taDIHSmqgboU1yFi6f0iAXBDegMCu4eS-J"
) )
return t.New("cmake-"+version, 0, []pkg.Artifact{ return t.NewPackage("cmake", version, pkg.NewHTTPGetTar(
t.Load(Make), nil, "https://github.com/Kitware/CMake/releases/download/"+
t.Load(KernelHeaders), "v"+version+"/cmake-"+version+".tar.gz",
}, nil, nil, ` mustDecode(checksum),
cd "$(mktemp -d)" pkg.TarGzip,
/usr/src/cmake/bootstrap \ ), &PackageAttr{
--prefix=/system \ // test suite expects writable source tree
--parallel="$(nproc)" \ Writable: true,
-- \
-DCMAKE_USE_OPENSSL=OFF
make "-j$(nproc)"
make DESTDIR=/work install
`, pkg.Path(AbsUsrSrc.Append("cmake"), true, t.NewPatchedSource(
// expected to be writable in the copy made during bootstrap // expected to be writable in the copy made during bootstrap
"cmake", version, pkg.NewHTTPGetTar( Chmod: true,
nil, "https://github.com/Kitware/CMake/releases/download/"+
"v"+version+"/cmake-"+version+".tar.gz", Patches: [][2]string{
mustDecode(checksum), {"bootstrap-test-no-openssl", `diff --git a/Tests/BootstrapTest.cmake b/Tests/BootstrapTest.cmake
pkg.TarGzip, index 137de78bc1..b4da52e664 100644
), false, --- a/Tests/BootstrapTest.cmake
))) +++ b/Tests/BootstrapTest.cmake
@@ -9,7 +9,7 @@ if(NOT nproc EQUAL 0)
endif()
message(STATUS "running bootstrap: ${bootstrap} ${ninja_arg} ${parallel_arg}")
execute_process(
- COMMAND ${bootstrap} ${ninja_arg} ${parallel_arg}
+ COMMAND ${bootstrap} ${ninja_arg} ${parallel_arg} -- -DCMAKE_USE_OPENSSL=OFF
WORKING_DIRECTORY "${bin_dir}"
RESULT_VARIABLE result
)
`},
{"disable-broken-tests-musl", `diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index 2ead810437..f85cbb8b1c 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -384,7 +384,6 @@ if(BUILD_TESTING)
add_subdirectory(CMakeLib)
endif()
add_subdirectory(CMakeOnly)
- add_subdirectory(RunCMake)
add_subdirectory(FindPackageModeMakefileTest)
@@ -528,9 +527,6 @@ if(BUILD_TESTING)
-DCMake_TEST_CUDA:BOOL=${CMake_TEST_CUDA}
-DCMake_INSTALL_NAME_TOOL_BUG:BOOL=${CMake_INSTALL_NAME_TOOL_BUG}
)
- ADD_TEST_MACRO(ExportImport ExportImport)
- set_property(TEST ExportImport APPEND
- PROPERTY LABELS "CUDA")
ADD_TEST_MACRO(Unset Unset)
ADD_TEST_MACRO(PolicyScope PolicyScope)
ADD_TEST_MACRO(EmptyLibrary EmptyLibrary)
@@ -624,7 +620,6 @@ if(BUILD_TESTING)
# run test for BundleUtilities on supported platforms/compilers
if((MSVC OR
MINGW OR
- CMAKE_SYSTEM_NAME MATCHES "Linux" OR
CMAKE_SYSTEM_NAME MATCHES "Darwin")
AND NOT CMAKE_GENERATOR STREQUAL "Watcom WMake")
@@ -3095,10 +3090,6 @@ if(BUILD_TESTING)
"${CMake_SOURCE_DIR}/Tests/CTestTestFdSetSize/test.cmake.in"
"${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/test.cmake"
@ONLY ESCAPE_QUOTES)
- add_test(CTestTestFdSetSize ${CMAKE_CTEST_COMMAND}
- -S "${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/test.cmake" -j20 -V --timeout 120
- --output-log "${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/testOutput.log"
- )
if(CMAKE_TESTS_CDASH_SERVER)
set(regex "^([^:]+)://([^/]+)(.*)$")
`},
},
}, &MakeHelper{
OmitDefaults: true,
ConfigureName: "/usr/src/cmake/bootstrap",
Configure: [][2]string{
{"prefix", "/system"},
{"parallel", `"$(nproc)"`},
{"--"},
{"-DCMAKE_USE_OPENSSL", "OFF"},
{"-DCMake_TEST_NO_NETWORK", "ON"},
},
Check: []string{
"CTEST_OUTPUT_ON_FAILURE=1",
"CTEST_PARALLEL_LEVEL=128",
"test",
},
},
KernelHeaders,
)
} }
func init() { artifactsF[CMake] = Toolchain.newCMake } func init() { artifactsF[CMake] = Toolchain.newCMake }
// CMakeAttr holds the project-specific attributes that will be applied to a new // CMakeHelper is the [CMake] build system helper.
// [pkg.Artifact] compiled via [CMake]. type CMakeHelper struct {
type CMakeAttr struct { // Joined with name with a dash if non-empty.
Variant string
// Path elements joined with source. // Path elements joined with source.
Append []string Append []string
// Use source tree as scratch space.
Writable bool
// CMake CACHE entries. // CMake CACHE entries.
Cache [][2]string Cache [][2]string
// Additional environment variables.
Env []string
// Runs before cmake.
ScriptEarly string
// Runs after cmake, replaces default.
ScriptConfigured string
// Runs after install. // Runs after install.
Script string Script string
// Override the default installation prefix [AbsSystem].
Prefix *check.Absolute
// Passed through to [Toolchain.New].
Paths []pkg.ExecPath
// Passed through to [Toolchain.New].
Flag int
} }
// NewViaCMake returns a [pkg.Artifact] for compiling and installing via [CMake]. var _ Helper = new(CMakeHelper)
func (t Toolchain) NewViaCMake(
name, version, variant string, // name returns its arguments and an optional variant string joined with '-'.
source pkg.Artifact, func (attr *CMakeHelper) name(name, version string) string {
attr *CMakeAttr, if attr != nil && attr.Variant != "" {
extra ...pkg.Artifact, name += "-" + attr.Variant
) pkg.Artifact {
if name == "" || version == "" || variant == "" {
panic("names must be non-empty")
} }
return name + "-" + version
}
// extra returns a hardcoded slice of [CMake] and [Ninja].
func (*CMakeHelper) extra(int) []PArtifact {
return []PArtifact{CMake, Ninja}
}
// wantsChmod returns false.
func (*CMakeHelper) wantsChmod() bool { return false }
// wantsWrite returns false.
func (*CMakeHelper) wantsWrite() bool { return false }
// scriptEarly returns the zero value.
func (*CMakeHelper) scriptEarly() string { return "" }
// createDir returns true.
func (*CMakeHelper) createDir() bool { return true }
// wantsDir returns a hardcoded, deterministic pathname.
func (*CMakeHelper) wantsDir() string { return "/cure/" }
// script generates the cure script.
func (attr *CMakeHelper) script(name string) string {
if attr == nil { if attr == nil {
attr = &CMakeAttr{ attr = &CMakeHelper{
Cache: [][2]string{ Cache: [][2]string{
{"CMAKE_BUILD_TYPE", "Release"}, {"CMAKE_BUILD_TYPE", "Release"},
}, },
@@ -86,40 +163,21 @@ func (t Toolchain) NewViaCMake(
panic("CACHE must be non-empty") panic("CACHE must be non-empty")
} }
scriptConfigured := "cmake --build .\ncmake --install .\n" return `
if attr.ScriptConfigured != "" {
scriptConfigured = attr.ScriptConfigured
}
prefix := attr.Prefix
if prefix == nil {
prefix = AbsSystem
}
sourcePath := AbsUsrSrc.Append(name)
return t.New(name+"-"+variant+"-"+version, attr.Flag, stage3Concat(t, extra,
t.Load(CMake),
t.Load(Ninja),
), nil, slices.Concat([]string{
"ROSA_SOURCE=" + sourcePath.String(),
"ROSA_CMAKE_SOURCE=" + sourcePath.Append(attr.Append...).String(),
"ROSA_INSTALL_PREFIX=/work" + prefix.String(),
}, attr.Env), attr.ScriptEarly+`
mkdir /cure && cd /cure
cmake -G Ninja \ cmake -G Ninja \
-DCMAKE_C_COMPILER_TARGET="${ROSA_TRIPLE}" \ -DCMAKE_C_COMPILER_TARGET="${ROSA_TRIPLE}" \
-DCMAKE_CXX_COMPILER_TARGET="${ROSA_TRIPLE}" \ -DCMAKE_CXX_COMPILER_TARGET="${ROSA_TRIPLE}" \
-DCMAKE_ASM_COMPILER_TARGET="${ROSA_TRIPLE}" \ -DCMAKE_ASM_COMPILER_TARGET="${ROSA_TRIPLE}" \
`+strings.Join(slices.Collect(func(yield func(string) bool) { ` + strings.Join(slices.Collect(func(yield func(string) bool) {
for _, v := range attr.Cache { for _, v := range attr.Cache {
if !yield("-D" + v[0] + "=" + v[1]) { if !yield("-D" + v[0] + "=" + v[1]) {
return return
} }
} }
}), " \\\n\t")+` \ }), " \\\n\t") + ` \
-DCMAKE_INSTALL_PREFIX="${ROSA_INSTALL_PREFIX}" \ -DCMAKE_INSTALL_PREFIX=/work/system \
"${ROSA_CMAKE_SOURCE}" '/usr/src/` + name + `/` + path.Join(attr.Append...) + `'
`+scriptConfigured+attr.Script, slices.Concat([]pkg.ExecPath{ cmake --build .
pkg.Path(sourcePath, attr.Writable, source), cmake --install .
}, attr.Paths)...) ` + attr.Script
} }

View File

@@ -7,26 +7,24 @@ func (t Toolchain) newCurl() pkg.Artifact {
version = "8.18.0" version = "8.18.0"
checksum = "YpOolP_sx1DIrCEJ3elgVAu0wTLDS-EZMZFvOP0eha7FaLueZUlEpuMwDzJNyi7i" checksum = "YpOolP_sx1DIrCEJ3elgVAu0wTLDS-EZMZFvOP0eha7FaLueZUlEpuMwDzJNyi7i"
) )
return t.NewViaMake("curl", version, pkg.NewHTTPGetTar( return t.NewPackage("curl", version, pkg.NewHTTPGetTar(
nil, "https://curl.se/download/curl-"+version+".tar.bz2", nil, "https://curl.se/download/curl-"+version+".tar.bz2",
mustDecode(checksum), mustDecode(checksum),
pkg.TarBzip2, pkg.TarBzip2,
), &MakeAttr{ ), nil, &MakeHelper{
Env: []string{
"TFLAGS=-j256",
},
Configure: [][2]string{ Configure: [][2]string{
{"with-openssl"}, {"with-openssl"},
{"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"}, {"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"},
}, },
ScriptConfigured: ` Check: []string{
make "-j$(nproc)" "TFLAGS=-j256",
`, "check",
},
}, },
t.Load(Perl), Perl,
t.Load(Libpsl), Libpsl,
t.Load(OpenSSL), OpenSSL,
) )
} }
func init() { artifactsF[Curl] = Toolchain.newCurl } func init() { artifactsF[Curl] = Toolchain.newCurl }

33
internal/rosa/dtc.go Normal file
View File

@@ -0,0 +1,33 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newDTC() pkg.Artifact {
const (
version = "1.7.2"
checksum = "vUoiRynPyYRexTpS6USweT5p4SVHvvVJs8uqFkkVD-YnFjwf6v3elQ0-Etrh00Dt"
)
return t.NewPackage("dtc", version, pkg.NewHTTPGetTar(
nil, "https://git.kernel.org/pub/scm/utils/dtc/dtc.git/snapshot/"+
"dtc-v"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), &PackageAttr{
// works around buggy test:
// fdtdump-runtest.sh /usr/src/dtc/tests/fdtdump.dts
Writable: true,
Chmod: true,
}, &MesonHelper{
Setup: [][2]string{
{"Dyaml", "disabled"},
{"Dstatic-build", "true"},
},
},
Flex,
Bison,
M4,
Coreutils,
Diffutils,
)
}
func init() { artifactsF[DTC] = Toolchain.newDTC }

41
internal/rosa/elfutils.go Normal file
View File

@@ -0,0 +1,41 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newElfutils() pkg.Artifact {
const (
version = "0.194"
checksum = "Q3XUygUPv9vR1TkWucwUsQ8Kb1_F6gzk-KMPELr3cC_4AcTrprhVPMvN0CKkiYRa"
)
return t.NewPackage("elfutils", version, pkg.NewHTTPGetTar(
nil, "https://sourceware.org/elfutils/ftp/"+
version+"/elfutils-"+version+".tar.bz2",
mustDecode(checksum),
pkg.TarBzip2,
), &PackageAttr{
Env: []string{
"CC=cc" +
// nonstandard glibc extension
" -DFNM_EXTMATCH=0",
},
}, &MakeHelper{
// nonstandard glibc extension
SkipCheck: true,
Configure: [][2]string{
{"enable-deterministic-archives"},
},
},
M4,
PkgConfig,
Zlib,
Bzip2,
Zstd,
ArgpStandalone,
MuslFts,
MuslObstack,
KernelHeaders,
)
}
func init() { artifactsF[Elfutils] = Toolchain.newElfutils }

51
internal/rosa/fakeroot.go Normal file
View File

@@ -0,0 +1,51 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newFakeroot() pkg.Artifact {
const (
version = "1.37.2"
checksum = "4ve-eDqVspzQ6VWDhPS0NjW3aSenBJcPAJq_BFT7OOFgUdrQzoTBxZWipDAGWxF8"
)
return t.NewPackage("fakeroot", version, pkg.NewHTTPGetTar(
nil, "https://salsa.debian.org/clint/fakeroot/-/archive/upstream/"+
version+"/fakeroot-upstream-"+version+".tar.bz2",
mustDecode(checksum),
pkg.TarBzip2,
), &PackageAttr{
Patches: [][2]string{
{"remove-broken-docs", `diff --git a/doc/Makefile.am b/doc/Makefile.am
index f135ad9..85c784c 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,5 +1,4 @@
AUTOMAKE_OPTIONS=foreign
-SUBDIRS = de es fr nl pt ro sv
man_MANS = faked.1 fakeroot.1
`},
},
Env: []string{
"CONFIG_SHELL=/bin/sh",
},
}, &MakeHelper{
Generate: "./bootstrap",
// makes assumptions about /etc/passwd
SkipCheck: true,
},
M4,
Perl,
Autoconf,
Automake,
Libtool,
PkgConfig,
Attr,
Libcap,
KernelHeaders,
)
}
func init() { artifactsF[Fakeroot] = Toolchain.newFakeroot }

21
internal/rosa/flex.go Normal file
View File

@@ -0,0 +1,21 @@
package rosa
import (
"hakurei.app/internal/pkg"
)
func (t Toolchain) newFlex() pkg.Artifact {
const (
version = "2.6.4"
checksum = "p9POjQU7VhgOf3x5iFro8fjhy0NOanvA7CTeuWS_veSNgCixIJshTrWVkc5XLZkB"
)
return t.NewPackage("flex", version, pkg.NewHTTPGetTar(
nil, "https://github.com/westes/flex/releases/download/"+
"v"+version+"/flex-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), nil, (*MakeHelper)(nil),
M4,
)
}
func init() { artifactsF[Flex] = Toolchain.newFlex }

View File

@@ -7,39 +7,30 @@ func (t Toolchain) newFuse() pkg.Artifact {
version = "3.18.1" version = "3.18.1"
checksum = "COb-BgJRWXLbt9XUkNeuiroQizpMifXqxgieE1SlkMXhs_WGSyJStrmyewAw2hd6" checksum = "COb-BgJRWXLbt9XUkNeuiroQizpMifXqxgieE1SlkMXhs_WGSyJStrmyewAw2hd6"
) )
return t.New("fuse-"+version, 0, []pkg.Artifact{ return t.NewPackage("fuse", version, pkg.NewHTTPGetTar(
t.Load(Python),
t.Load(Meson),
t.Load(Ninja),
t.Load(IniConfig),
t.Load(Packaging),
t.Load(Pluggy),
t.Load(Pygments),
t.Load(PyTest),
t.Load(KernelHeaders),
}, nil, nil, `
cd "$(mktemp -d)"
meson setup \
--reconfigure \
--buildtype=release \
--prefix=/system \
--prefer-static \
-Dtests=true \
-Duseroot=false \
-Dinitscriptdir=/system/init.d \
-Ddefault_library=both \
. /usr/src/fuse
meson compile
python3 -m pytest test/
meson install \
--destdir=/work
`, pkg.Path(AbsUsrSrc.Append("fuse"), false, pkg.NewHTTPGetTar(
nil, "https://github.com/libfuse/libfuse/releases/download/"+ nil, "https://github.com/libfuse/libfuse/releases/download/"+
"fuse-"+version+"/fuse-"+version+".tar.gz", "fuse-"+version+"/fuse-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
))) ), nil, &MesonHelper{
Setup: [][2]string{
{"Ddefault_library", "both"},
{"Dtests", "true"},
{"Duseroot", "false"},
{"Dinitscriptdir", "/system/etc"},
},
ScriptCompiled: "python3 -m pytest test/",
// this project uses pytest
SkipTest: true,
},
IniConfig,
Packaging,
Pluggy,
Pygments,
PyTest,
KernelHeaders,
)
} }
func init() { artifactsF[Fuse] = Toolchain.newFuse } func init() { artifactsF[Fuse] = Toolchain.newFuse }

View File

@@ -1,40 +1,30 @@
package rosa package rosa
import ( import "hakurei.app/internal/pkg"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newGit() pkg.Artifact { func (t Toolchain) newGit() pkg.Artifact {
const ( const (
version = "2.52.0" version = "2.52.0"
checksum = "uH3J1HAN_c6PfGNJd2OBwW4zo36n71wmkdvityYnrh8Ak0D1IifiAvEWz9Vi9DmS" checksum = "uH3J1HAN_c6PfGNJd2OBwW4zo36n71wmkdvityYnrh8Ak0D1IifiAvEWz9Vi9DmS"
) )
return t.NewViaMake("git", version, t.NewPatchedSource( return t.NewPackage("git", version, pkg.NewHTTPGetTar(
"git", version, pkg.NewHTTPGetTar( nil, "https://www.kernel.org/pub/software/scm/git/"+
nil, "https://www.kernel.org/pub/software/scm/git/"+ "git-"+version+".tar.gz",
"git-"+version+".tar.gz", mustDecode(checksum),
mustDecode(checksum), pkg.TarGzip,
pkg.TarGzip, ), &PackageAttr{
), false,
), &MakeAttr{
// uses source tree as scratch space
Writable: true,
InPlace: true,
// test suite in subdirectory
SkipCheck: true,
Make: []string{"all"},
ScriptEarly: ` ScriptEarly: `
cd /usr/src/git
make configure
`,
Script: `
ln -s ../../system/bin/perl /usr/bin/ || true ln -s ../../system/bin/perl /usr/bin/ || true
`,
// uses source tree as scratch space
EnterSource: true,
}, &MakeHelper{
InPlace: true,
Generate: "make configure",
ScriptMakeEarly: `
function disable_test { function disable_test {
local test=$1 pattern=$2 local test=$1 pattern=${2:-''}
if [ $# -eq 1 ]; then if [ $# -eq 1 ]; then
rm "t/${test}.sh" rm "t/${test}.sh"
else else
@@ -56,23 +46,23 @@ disable_test t9300-fast-import
disable_test t0211-trace2-perf disable_test t0211-trace2-perf
disable_test t1517-outside-repo disable_test t1517-outside-repo
disable_test t2200-add-update disable_test t2200-add-update
make \
-C t \
GIT_PROVE_OPTS="--jobs 32 --failures" \
prove
`, `,
Check: []string{
"-C t",
`GIT_PROVE_OPTS="--jobs 32 --failures"`,
"prove",
},
}, },
t.Load(Perl), Perl,
t.Load(Diffutils), Diffutils,
t.Load(M4), M4,
t.Load(Autoconf), Autoconf,
t.Load(Gettext), Gettext,
t.Load(Zlib), Zlib,
t.Load(Curl), Curl,
t.Load(OpenSSL), OpenSSL,
t.Load(Libexpat), Libexpat,
) )
} }
func init() { artifactsF[Git] = Toolchain.newGit } func init() { artifactsF[Git] = Toolchain.newGit }

View File

@@ -7,33 +7,49 @@ func (t Toolchain) newM4() pkg.Artifact {
version = "1.4.20" version = "1.4.20"
checksum = "RT0_L3m4Co86bVBY3lCFAEs040yI1WdeNmRylFpah8IZovTm6O4wI7qiHJN3qsW9" checksum = "RT0_L3m4Co86bVBY3lCFAEs040yI1WdeNmRylFpah8IZovTm6O4wI7qiHJN3qsW9"
) )
return t.NewViaMake("m4", version, pkg.NewHTTPGetTar( return t.NewPackage("m4", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/m4/m4-"+version+".tar.bz2", nil, "https://ftpmirror.gnu.org/gnu/m4/m4-"+version+".tar.bz2",
mustDecode(checksum), mustDecode(checksum),
pkg.TarBzip2, pkg.TarBzip2,
), &MakeAttr{ ), &PackageAttr{
Writable: true, Writable: true,
ScriptEarly: ` ScriptEarly: `
cd /usr/src/m4
chmod +w tests/test-c32ispunct.sh && echo '#!/bin/sh' > tests/test-c32ispunct.sh chmod +w tests/test-c32ispunct.sh && echo '#!/bin/sh' > tests/test-c32ispunct.sh
`, `,
}, }, (*MakeHelper)(nil),
t.Load(Diffutils), Diffutils,
) )
} }
func init() { artifactsF[M4] = Toolchain.newM4 } func init() { artifactsF[M4] = Toolchain.newM4 }
func (t Toolchain) newBison() pkg.Artifact {
const (
version = "3.8.2"
checksum = "BhRM6K7URj1LNOkIDCFDctSErLS-Xo5d9ba9seg10o6ACrgC1uNhED7CQPgIY29Y"
)
return t.NewPackage("bison", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/bison/bison-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), nil, (*MakeHelper)(nil),
M4,
Diffutils,
Sed,
)
}
func init() { artifactsF[Bison] = Toolchain.newBison }
func (t Toolchain) newSed() pkg.Artifact { func (t Toolchain) newSed() pkg.Artifact {
const ( const (
version = "4.9" version = "4.9"
checksum = "pe7HWH4PHNYrazOTlUoE1fXmhn2GOPFN_xE62i0llOr3kYGrH1g2_orDz0UtZ9Nt" checksum = "pe7HWH4PHNYrazOTlUoE1fXmhn2GOPFN_xE62i0llOr3kYGrH1g2_orDz0UtZ9Nt"
) )
return t.NewViaMake("sed", version, pkg.NewHTTPGetTar( return t.NewPackage("sed", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/sed/sed-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/sed/sed-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), nil, ), nil, (*MakeHelper)(nil),
t.Load(Diffutils), Diffutils,
) )
} }
func init() { artifactsF[Sed] = Toolchain.newSed } func init() { artifactsF[Sed] = Toolchain.newSed }
@@ -43,20 +59,22 @@ func (t Toolchain) newAutoconf() pkg.Artifact {
version = "2.72" version = "2.72"
checksum = "-c5blYkC-xLDer3TWEqJTyh1RLbOd1c5dnRLKsDnIrg_wWNOLBpaqMY8FvmUFJ33" checksum = "-c5blYkC-xLDer3TWEqJTyh1RLbOd1c5dnRLKsDnIrg_wWNOLBpaqMY8FvmUFJ33"
) )
return t.NewViaMake("autoconf", version, pkg.NewHTTPGetTar( return t.NewPackage("autoconf", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/autoconf/autoconf-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/autoconf/autoconf-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Make: []string{
`TESTSUITEFLAGS="-j$(nproc)"`,
},
Flag: TExclusive, Flag: TExclusive,
}, &MakeHelper{
Check: []string{
`TESTSUITEFLAGS="-j$(nproc)"`,
"check",
},
}, },
t.Load(M4), M4,
t.Load(Perl), Perl,
t.Load(Bash), Bash,
t.Load(Diffutils), Diffutils,
) )
} }
func init() { artifactsF[Autoconf] = Toolchain.newAutoconf } func init() { artifactsF[Autoconf] = Toolchain.newAutoconf }
@@ -66,15 +84,13 @@ func (t Toolchain) newAutomake() pkg.Artifact {
version = "1.18.1" version = "1.18.1"
checksum = "FjvLG_GdQP7cThTZJLDMxYpRcKdpAVG-YDs1Fj1yaHlSdh_Kx6nRGN14E0r_BjcG" checksum = "FjvLG_GdQP7cThTZJLDMxYpRcKdpAVG-YDs1Fj1yaHlSdh_Kx6nRGN14E0r_BjcG"
) )
return t.NewViaMake("automake", version, pkg.NewHTTPGetTar( return t.NewPackage("automake", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/automake/automake-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/automake/automake-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Writable: true, Writable: true,
ScriptEarly: ` ScriptEarly: `
cd /usr/src/automake
test_disable() { chmod +w "$2" && echo "$1" > "$2"; } test_disable() { chmod +w "$2" && echo "$1" > "$2"; }
test_disable '#!/bin/sh' t/objcxx-minidemo.sh test_disable '#!/bin/sh' t/objcxx-minidemo.sh
@@ -84,13 +100,13 @@ test_disable '#!/bin/sh' t/dist-no-built-sources.sh
test_disable '#!/bin/sh' t/distname.sh test_disable '#!/bin/sh' t/distname.sh
test_disable '#!/bin/sh' t/pr9.sh test_disable '#!/bin/sh' t/pr9.sh
`, `,
}, }, (*MakeHelper)(nil),
t.Load(M4), M4,
t.Load(Perl), Perl,
t.Load(Grep), Grep,
t.Load(Gzip), Gzip,
t.Load(Autoconf), Autoconf,
t.Load(Diffutils), Diffutils,
) )
} }
func init() { artifactsF[Automake] = Toolchain.newAutomake } func init() { artifactsF[Automake] = Toolchain.newAutomake }
@@ -100,17 +116,18 @@ func (t Toolchain) newLibtool() pkg.Artifact {
version = "2.5.4" version = "2.5.4"
checksum = "pa6LSrQggh8mSJHQfwGjysAApmZlGJt8wif2cCLzqAAa2jpsTY0jZ-6stS3BWZ2Q" checksum = "pa6LSrQggh8mSJHQfwGjysAApmZlGJt8wif2cCLzqAAa2jpsTY0jZ-6stS3BWZ2Q"
) )
return t.NewViaMake("libtool", version, pkg.NewHTTPGetTar( return t.NewPackage("libtool", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/libtool/libtool-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/libtool/libtool-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), nil, &MakeHelper{
Make: []string{ Check: []string{
`TESTSUITEFLAGS=32`, `TESTSUITEFLAGS="-j$(nproc)"`,
"check",
}, },
}, },
t.Load(M4), M4,
t.Load(Diffutils), Diffutils,
) )
} }
func init() { artifactsF[Libtool] = Toolchain.newLibtool } func init() { artifactsF[Libtool] = Toolchain.newLibtool }
@@ -120,11 +137,11 @@ func (t Toolchain) newGzip() pkg.Artifact {
version = "1.14" version = "1.14"
checksum = "NWhjUavnNfTDFkZJyAUonL9aCOak8GVajWX2OMlzpFnuI0ErpBFyj88mz2xSjz0q" checksum = "NWhjUavnNfTDFkZJyAUonL9aCOak8GVajWX2OMlzpFnuI0ErpBFyj88mz2xSjz0q"
) )
return t.NewViaMake("gzip", version, pkg.NewHTTPGetTar( return t.NewPackage("gzip", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/gzip/gzip-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/gzip/gzip-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), nil, &MakeHelper{
// dependency loop // dependency loop
SkipCheck: true, SkipCheck: true,
}) })
@@ -136,14 +153,13 @@ func (t Toolchain) newGettext() pkg.Artifact {
version = "1.0" version = "1.0"
checksum = "3MasKeEdPeFEgWgzsBKk7JqWqql1wEMbgPmzAfs-mluyokoW0N8oQVxPQoOnSdgC" checksum = "3MasKeEdPeFEgWgzsBKk7JqWqql1wEMbgPmzAfs-mluyokoW0N8oQVxPQoOnSdgC"
) )
return t.NewViaMake("gettext", version, pkg.NewHTTPGetTar( return t.NewPackage("gettext", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/gettext/gettext-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/gettext/gettext-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Writable: true, Writable: true,
ScriptEarly: ` ScriptEarly: `
cd /usr/src/gettext
test_disable() { chmod +w "$2" && echo "$1" > "$2"; } test_disable() { chmod +w "$2" && echo "$1" > "$2"; }
test_disable '#!/bin/sh' gettext-tools/tests/msgcat-22 test_disable '#!/bin/sh' gettext-tools/tests/msgcat-22
@@ -158,12 +174,12 @@ test_disable 'int main(){return 0;}' gettext-tools/gnulib-tests/test-stdcountof-
touch gettext-tools/autotools/archive.dir.tar touch gettext-tools/autotools/archive.dir.tar
`, `,
}, }, (*MakeHelper)(nil),
t.Load(Diffutils), Diffutils,
t.Load(Gzip), Gzip,
t.Load(Sed), Sed,
t.Load(KernelHeaders), KernelHeaders,
) )
} }
func init() { artifactsF[Gettext] = Toolchain.newGettext } func init() { artifactsF[Gettext] = Toolchain.newGettext }
@@ -173,14 +189,13 @@ func (t Toolchain) newDiffutils() pkg.Artifact {
version = "3.12" version = "3.12"
checksum = "9J5VAq5oA7eqwzS1Yvw-l3G5o-TccUrNQR3PvyB_lgdryOFAfxtvQfKfhdpquE44" checksum = "9J5VAq5oA7eqwzS1Yvw-l3G5o-TccUrNQR3PvyB_lgdryOFAfxtvQfKfhdpquE44"
) )
return t.NewViaMake("diffutils", version, pkg.NewHTTPGetTar( return t.NewPackage("diffutils", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/diffutils/diffutils-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/diffutils/diffutils-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Writable: true, Writable: true,
ScriptEarly: ` ScriptEarly: `
cd /usr/src/diffutils
test_disable() { chmod +w "$2" && echo "$1" > "$2"; } test_disable() { chmod +w "$2" && echo "$1" > "$2"; }
test_disable '#!/bin/sh' gnulib-tests/test-c32ispunct.sh test_disable '#!/bin/sh' gnulib-tests/test-c32ispunct.sh
@@ -188,7 +203,7 @@ test_disable 'int main(){return 0;}' gnulib-tests/test-c32ispunct.c
test_disable '#!/bin/sh' tests/cmp test_disable '#!/bin/sh' tests/cmp
`, `,
Flag: TEarly, Flag: TEarly,
}) }, (*MakeHelper)(nil))
} }
func init() { artifactsF[Diffutils] = Toolchain.newDiffutils } func init() { artifactsF[Diffutils] = Toolchain.newDiffutils }
@@ -197,21 +212,20 @@ func (t Toolchain) newPatch() pkg.Artifact {
version = "2.8" version = "2.8"
checksum = "MA0BQc662i8QYBD-DdGgyyfTwaeALZ1K0yusV9rAmNiIsQdX-69YC4t9JEGXZkeR" checksum = "MA0BQc662i8QYBD-DdGgyyfTwaeALZ1K0yusV9rAmNiIsQdX-69YC4t9JEGXZkeR"
) )
return t.NewViaMake("patch", version, pkg.NewHTTPGetTar( return t.NewPackage("patch", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/patch/patch-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/patch/patch-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Writable: true, Writable: true,
ScriptEarly: ` ScriptEarly: `
cd /usr/src/patch
test_disable() { chmod +w "$2" && echo "$1" > "$2"; } test_disable() { chmod +w "$2" && echo "$1" > "$2"; }
test_disable '#!/bin/sh' tests/ed-style test_disable '#!/bin/sh' tests/ed-style
test_disable '#!/bin/sh' tests/need-filename test_disable '#!/bin/sh' tests/need-filename
`, `,
Flag: TEarly, Flag: TEarly,
}) }, (*MakeHelper)(nil))
} }
func init() { artifactsF[Patch] = Toolchain.newPatch } func init() { artifactsF[Patch] = Toolchain.newPatch }
@@ -220,16 +234,17 @@ func (t Toolchain) newBash() pkg.Artifact {
version = "5.3" version = "5.3"
checksum = "4LQ_GRoB_ko-Ih8QPf_xRKA02xAm_TOxQgcJLmFDT6udUPxTAWrsj-ZNeuTusyDq" checksum = "4LQ_GRoB_ko-Ih8QPf_xRKA02xAm_TOxQgcJLmFDT6udUPxTAWrsj-ZNeuTusyDq"
) )
return t.NewViaMake("bash", version, pkg.NewHTTPGetTar( return t.NewPackage("bash", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/bash/bash-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/bash/bash-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Flag: TEarly,
}, &MakeHelper{
Script: "ln -s bash /work/system/bin/sh\n", Script: "ln -s bash /work/system/bin/sh\n",
Configure: [][2]string{ Configure: [][2]string{
{"without-bash-malloc"}, {"without-bash-malloc"},
}, },
Flag: TEarly,
}) })
} }
func init() { artifactsF[Bash] = Toolchain.newBash } func init() { artifactsF[Bash] = Toolchain.newBash }
@@ -239,44 +254,66 @@ func (t Toolchain) newCoreutils() pkg.Artifact {
version = "9.9" version = "9.9"
checksum = "B1_TaXj1j5aiVIcazLWu8Ix03wDV54uo2_iBry4qHG6Y-9bjDpUPlkNLmU_3Nvw6" checksum = "B1_TaXj1j5aiVIcazLWu8Ix03wDV54uo2_iBry4qHG6Y-9bjDpUPlkNLmU_3Nvw6"
) )
return t.NewViaMake("coreutils", version, pkg.NewHTTPGetTar( return t.NewPackage("coreutils", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/coreutils/coreutils-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/coreutils/coreutils-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Writable: true, Writable: true,
ScriptEarly: ` ScriptEarly: `
cd /usr/src/coreutils
test_disable() { chmod +w "$2" && echo "$1" > "$2"; } test_disable() { chmod +w "$2" && echo "$1" > "$2"; }
test_disable '#!/bin/sh' gnulib-tests/test-c32ispunct.sh test_disable '#!/bin/sh' gnulib-tests/test-c32ispunct.sh
test_disable '#!/bin/sh' tests/split/line-bytes.sh test_disable '#!/bin/sh' tests/split/line-bytes.sh
test_disable '#!/bin/sh' tests/dd/no-allocate.sh test_disable '#!/bin/sh' tests/dd/no-allocate.sh
test_disable '#!/bin/sh' tests/env/env.sh
test_disable 'int main(){return 0;}' gnulib-tests/test-chown.c test_disable 'int main(){return 0;}' gnulib-tests/test-chown.c
test_disable 'int main(){return 0;}' gnulib-tests/test-fchownat.c test_disable 'int main(){return 0;}' gnulib-tests/test-fchownat.c
test_disable 'int main(){return 0;}' gnulib-tests/test-lchown.c test_disable 'int main(){return 0;}' gnulib-tests/test-lchown.c
`, `,
Flag: TEarly, Flag: TEarly,
}, &MakeHelper{
Configure: [][2]string{
{"enable-single-binary", "symlinks"},
},
}, },
t.Load(Perl), Perl,
t.Load(Bash), Bash,
t.Load(KernelHeaders), KernelHeaders,
) )
} }
func init() { artifactsF[Coreutils] = Toolchain.newCoreutils } func init() { artifactsF[Coreutils] = Toolchain.newCoreutils }
func (t Toolchain) newTexinfo() pkg.Artifact {
const (
version = "7.2"
checksum = "9EelM5b7QGMAY5DKrAm_El8lofBGuFWlaBPSBhh7l_VQE8054MBmC0KBvGrABqjv"
)
return t.NewPackage("texinfo", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/texinfo/texinfo-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), nil, &MakeHelper{
// nonstandard glibc extension
SkipCheck: true,
},
Perl,
)
}
func init() { artifactsF[Texinfo] = Toolchain.newTexinfo }
func (t Toolchain) newGperf() pkg.Artifact { func (t Toolchain) newGperf() pkg.Artifact {
const ( const (
version = "3.3" version = "3.3"
checksum = "RtIy9pPb_Bb8-31J2Nw-rRGso2JlS-lDlVhuNYhqR7Nt4xM_nObznxAlBMnarJv7" checksum = "RtIy9pPb_Bb8-31J2Nw-rRGso2JlS-lDlVhuNYhqR7Nt4xM_nObznxAlBMnarJv7"
) )
return t.NewViaMake("gperf", version, pkg.NewHTTPGetTar( return t.NewPackage("gperf", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gperf/gperf-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gperf/gperf-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), nil, ), nil, (*MakeHelper)(nil),
t.Load(Diffutils), Diffutils,
) )
} }
func init() { artifactsF[Gperf] = Toolchain.newGperf } func init() { artifactsF[Gperf] = Toolchain.newGperf }
@@ -286,13 +323,13 @@ func (t Toolchain) newGawk() pkg.Artifact {
version = "5.3.2" version = "5.3.2"
checksum = "uIs0d14h_d2DgMGYwrPtegGNyt_bxzG3D6Fe-MmExx_pVoVkQaHzrtmiXVr6NHKk" checksum = "uIs0d14h_d2DgMGYwrPtegGNyt_bxzG3D6Fe-MmExx_pVoVkQaHzrtmiXVr6NHKk"
) )
return t.NewViaMake("gawk", version, pkg.NewHTTPGetTar( return t.NewPackage("gawk", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/gawk/gawk-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/gawk/gawk-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Flag: TEarly, Flag: TEarly,
}, &MakeHelper{
// dependency loop // dependency loop
SkipCheck: true, SkipCheck: true,
}) })
@@ -304,22 +341,20 @@ func (t Toolchain) newGrep() pkg.Artifact {
version = "3.12" version = "3.12"
checksum = "qMB4RjaPNRRYsxix6YOrjE8gyAT1zVSTy4nW4wKW9fqa0CHYAuWgPwDTirENzm_1" checksum = "qMB4RjaPNRRYsxix6YOrjE8gyAT1zVSTy4nW4wKW9fqa0CHYAuWgPwDTirENzm_1"
) )
return t.NewViaMake("grep", version, pkg.NewHTTPGetTar( return t.NewPackage("grep", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/grep/grep-"+version+".tar.gz", nil, "https://ftpmirror.gnu.org/gnu/grep/grep-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Writable: true, Writable: true,
ScriptEarly: ` ScriptEarly: `
cd /usr/src/grep
test_disable() { chmod +w "$2" && echo "$1" > "$2"; } test_disable() { chmod +w "$2" && echo "$1" > "$2"; }
test_disable '#!/bin/sh' gnulib-tests/test-c32ispunct.sh test_disable '#!/bin/sh' gnulib-tests/test-c32ispunct.sh
test_disable 'int main(){return 0;}' gnulib-tests/test-c32ispunct.c test_disable 'int main(){return 0;}' gnulib-tests/test-c32ispunct.c
`, `,
}, }, (*MakeHelper)(nil),
t.Load(Diffutils), Diffutils,
) )
} }
func init() { artifactsF[Grep] = Toolchain.newGrep } func init() { artifactsF[Grep] = Toolchain.newGrep }
@@ -329,43 +364,99 @@ func (t Toolchain) newFindutils() pkg.Artifact {
version = "4.10.0" version = "4.10.0"
checksum = "ZXABdNBQXL7QjTygynRRTdXYWxQKZ0Wn5eMd3NUnxR0xaS0u0VfcKoTlbo50zxv6" checksum = "ZXABdNBQXL7QjTygynRRTdXYWxQKZ0Wn5eMd3NUnxR0xaS0u0VfcKoTlbo50zxv6"
) )
return t.NewViaMake("findutils", version, pkg.NewHTTPGet( return t.NewPackage("findutils", version, pkg.NewHTTPGet(
nil, "https://ftpmirror.gnu.org/gnu/findutils/findutils-"+version+".tar.xz", nil, "https://ftpmirror.gnu.org/gnu/findutils/findutils-"+version+".tar.xz",
mustDecode(checksum), mustDecode(checksum),
), &MakeAttr{ ), &PackageAttr{
SourceSuffix: ".tar.xz", SourceKind: sourceTarXZ,
ScriptEarly: ` ScriptEarly: `
cd /usr/src/
tar xf findutils.tar.xz
mv findutils-` + version + ` findutils
cd findutils
echo '#!/bin/sh' > gnulib-tests/test-c32ispunct.sh echo '#!/bin/sh' > gnulib-tests/test-c32ispunct.sh
echo 'int main(){return 0;}' > tests/xargs/test-sigusr.c echo 'int main(){return 0;}' > tests/xargs/test-sigusr.c
`, `,
}, }, (*MakeHelper)(nil),
t.Load(Diffutils), Diffutils,
t.Load(XZ), XZ,
t.Load(Sed), Sed,
) )
} }
func init() { artifactsF[Findutils] = Toolchain.newFindutils } func init() { artifactsF[Findutils] = Toolchain.newFindutils }
func (t Toolchain) newBC() pkg.Artifact {
const (
version = "1.08.2"
checksum = "8h6f3hjV80XiFs6v9HOPF2KEyg1kuOgn5eeFdVspV05ODBVQss-ey5glc8AmneLy"
)
return t.NewPackage("bc", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/bc/bc-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), &PackageAttr{
// source expected to be writable
Writable: true,
Chmod: true,
}, (*MakeHelper)(nil),
Perl,
Texinfo,
)
}
func init() { artifactsF[BC] = Toolchain.newBC }
func (t Toolchain) newLibiconv() pkg.Artifact {
const (
version = "1.18"
checksum = "iV5q3VxP5VPdJ-X7O5OQI4fGm8VjeYb5viLd1L3eAHg26bbHb2_Qn63XPF3ucVZr"
)
return t.NewPackage("libiconv", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/libiconv/libiconv-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), nil, (*MakeHelper)(nil))
}
func init() { artifactsF[Libiconv] = Toolchain.newLibiconv }
func (t Toolchain) newTar() pkg.Artifact {
const (
version = "1.35"
checksum = "zSaoSlVUDW0dSfm4sbL4FrXLFR8U40Fh3zY5DWhR5NCIJ6GjU6Kc4VZo2-ZqpBRA"
)
return t.NewPackage("tar", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/tar/tar-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), nil, &MakeHelper{
Configure: [][2]string{
{"disable-acl"},
{"without-posix-acls"},
{"without-xattrs"},
},
Check: []string{
// very expensive
"TARTEST_SKIP_LARGE_FILES=1",
`TESTSUITEFLAGS="-j$(nproc)"`,
"check",
},
},
Diffutils,
Gzip,
Bzip2,
Zstd,
)
}
func init() { artifactsF[Tar] = Toolchain.newTar }
func (t Toolchain) newBinutils() pkg.Artifact { func (t Toolchain) newBinutils() pkg.Artifact {
const ( const (
version = "2.45" version = "2.45"
checksum = "hlLtqqHDmzAT2OQVHaKEd_io2DGFvJkaeS-igBuK8bRRir7LUKGHgHYNkDVKaHTT" checksum = "hlLtqqHDmzAT2OQVHaKEd_io2DGFvJkaeS-igBuK8bRRir7LUKGHgHYNkDVKaHTT"
) )
return t.NewViaMake("binutils", version, pkg.NewHTTPGetTar( return t.NewPackage("binutils", version, pkg.NewHTTPGetTar(
nil, "https://ftpmirror.gnu.org/gnu/binutils/binutils-"+version+".tar.bz2", nil, "https://ftpmirror.gnu.org/gnu/binutils/binutils-"+version+".tar.bz2",
mustDecode(checksum), mustDecode(checksum),
pkg.TarBzip2, pkg.TarBzip2,
), &MakeAttr{ ), nil, (*MakeHelper)(nil),
ScriptConfigured: ` Bash,
make "-j$(nproc)"
`,
},
t.Load(Bash),
) )
} }
func init() { artifactsF[Binutils] = Toolchain.newBinutils } func init() { artifactsF[Binutils] = Toolchain.newBinutils }
@@ -375,17 +466,13 @@ func (t Toolchain) newGMP() pkg.Artifact {
version = "6.3.0" version = "6.3.0"
checksum = "yrgbgEDWKDdMWVHh7gPbVl56-sRtVVhfvv0M_LX7xMUUk_mvZ1QOJEAnt7g4i3k5" checksum = "yrgbgEDWKDdMWVHh7gPbVl56-sRtVVhfvv0M_LX7xMUUk_mvZ1QOJEAnt7g4i3k5"
) )
return t.NewViaMake("gmp", version, pkg.NewHTTPGetTar( return t.NewPackage("gmp", version, pkg.NewHTTPGetTar(
nil, "https://gcc.gnu.org/pub/gcc/infrastructure/"+ nil, "https://gcc.gnu.org/pub/gcc/infrastructure/"+
"gmp-"+version+".tar.bz2", "gmp-"+version+".tar.bz2",
mustDecode(checksum), mustDecode(checksum),
pkg.TarBzip2, pkg.TarBzip2,
), &MakeAttr{ ), nil, (*MakeHelper)(nil),
ScriptConfigured: ` M4,
make "-j$(nproc)"
`,
},
t.Load(M4),
) )
} }
func init() { artifactsF[GMP] = Toolchain.newGMP } func init() { artifactsF[GMP] = Toolchain.newGMP }
@@ -395,13 +482,13 @@ func (t Toolchain) newMPFR() pkg.Artifact {
version = "4.2.2" version = "4.2.2"
checksum = "wN3gx0zfIuCn9r3VAn_9bmfvAYILwrRfgBjYSD1IjLqyLrLojNN5vKyQuTE9kA-B" checksum = "wN3gx0zfIuCn9r3VAn_9bmfvAYILwrRfgBjYSD1IjLqyLrLojNN5vKyQuTE9kA-B"
) )
return t.NewViaMake("mpfr", version, pkg.NewHTTPGetTar( return t.NewPackage("mpfr", version, pkg.NewHTTPGetTar(
nil, "https://gcc.gnu.org/pub/gcc/infrastructure/"+ nil, "https://gcc.gnu.org/pub/gcc/infrastructure/"+
"mpfr-"+version+".tar.bz2", "mpfr-"+version+".tar.bz2",
mustDecode(checksum), mustDecode(checksum),
pkg.TarBzip2, pkg.TarBzip2,
), nil, ), nil, (*MakeHelper)(nil),
t.Load(GMP), GMP,
) )
} }
func init() { artifactsF[MPFR] = Toolchain.newMPFR } func init() { artifactsF[MPFR] = Toolchain.newMPFR }
@@ -411,14 +498,14 @@ func (t Toolchain) newMPC() pkg.Artifact {
version = "1.3.1" version = "1.3.1"
checksum = "o8r8K9R4x7PuRx0-JE3-bC5jZQrtxGV2nkB773aqJ3uaxOiBDCID1gKjPaaDxX4V" checksum = "o8r8K9R4x7PuRx0-JE3-bC5jZQrtxGV2nkB773aqJ3uaxOiBDCID1gKjPaaDxX4V"
) )
return t.NewViaMake("mpc", version, pkg.NewHTTPGetTar( return t.NewPackage("mpc", version, pkg.NewHTTPGetTar(
nil, "https://gcc.gnu.org/pub/gcc/infrastructure/"+ nil, "https://gcc.gnu.org/pub/gcc/infrastructure/"+
"mpc-"+version+".tar.gz", "mpc-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), nil, ), nil, (*MakeHelper)(nil),
t.Load(GMP), GMP,
t.Load(MPFR), MPFR,
) )
} }
func init() { artifactsF[MPC] = Toolchain.newMPC } func init() { artifactsF[MPC] = Toolchain.newMPC }
@@ -428,14 +515,14 @@ func (t Toolchain) newGCC() pkg.Artifact {
version = "15.2.0" version = "15.2.0"
checksum = "TXJ5WrbXlGLzy1swghQTr4qxgDCyIZFgJry51XEPTBZ8QYbVmFeB4lZbSMtPJ-a1" checksum = "TXJ5WrbXlGLzy1swghQTr4qxgDCyIZFgJry51XEPTBZ8QYbVmFeB4lZbSMtPJ-a1"
) )
return t.NewViaMake("gcc", version, t.NewPatchedSource( return t.NewPackage("gcc", version, pkg.NewHTTPGetTar(
"gcc", version, nil, "https://ftp.tsukuba.wide.ad.jp/software/gcc/releases/"+
pkg.NewHTTPGetTar( "gcc-"+version+"/gcc-"+version+".tar.gz",
nil, "https://ftp.tsukuba.wide.ad.jp/software/gcc/releases/"+ mustDecode(checksum),
"gcc-"+version+"/gcc-"+version+".tar.gz", pkg.TarGzip,
mustDecode(checksum), ), &PackageAttr{
pkg.TarGzip, Patches: [][2]string{
), true, [2]string{"musl-off64_t-loff_t", `diff --git a/libgo/sysinfo.c b/libgo/sysinfo.c {"musl-off64_t-loff_t", `diff --git a/libgo/sysinfo.c b/libgo/sysinfo.c
index 180f5c31d74..44d7ea73f7d 100644 index 180f5c31d74..44d7ea73f7d 100644
--- a/libgo/sysinfo.c --- a/libgo/sysinfo.c
+++ b/libgo/sysinfo.c +++ b/libgo/sysinfo.c
@@ -451,7 +538,9 @@ index 180f5c31d74..44d7ea73f7d 100644
// The following section introduces explicit references to types and // The following section introduces explicit references to types and
// constants of interest to support bootstrapping libgo using a // constants of interest to support bootstrapping libgo using a
`}, [2]string{"musl-legacy-lfs", `diff --git a/libgo/go/internal/syscall/unix/at_largefile.go b/libgo/go/internal/syscall/unix/at_largefile.go `},
{"musl-legacy-lfs", `diff --git a/libgo/go/internal/syscall/unix/at_largefile.go b/libgo/go/internal/syscall/unix/at_largefile.go
index 82e0dcfd074..16151ecad1b 100644 index 82e0dcfd074..16151ecad1b 100644
--- a/libgo/go/internal/syscall/unix/at_largefile.go --- a/libgo/go/internal/syscall/unix/at_largefile.go
+++ b/libgo/go/internal/syscall/unix/at_largefile.go +++ b/libgo/go/internal/syscall/unix/at_largefile.go
@@ -579,11 +668,18 @@ index f84860891e6..7efc9615985 100644
} }
#endif #endif
`}), &MakeAttr{ `},
},
ScriptEarly: ` ScriptEarly: `
ln -s system/lib / ln -s system/lib /
ln -s system/lib /work/ ln -s system/lib /work/
`, `,
// GCC spends most of its time in its many configure scripts, however
// it also saturates the CPU for a consequential amount of time.
Flag: TExclusive,
}, &MakeHelper{
Configure: [][2]string{ Configure: [][2]string{
{"disable-multilib"}, {"disable-multilib"},
{"with-multilib-list", `""`}, {"with-multilib-list", `""`},
@@ -604,20 +700,16 @@ ln -s system/lib /work/
// well in its current state. That does not matter as long as the // well in its current state. That does not matter as long as the
// toolchain it produces passes its own test suite. // toolchain it produces passes its own test suite.
SkipCheck: true, SkipCheck: true,
// GCC spends most of its time in its many configure scripts, however
// it also saturates the CPU for a consequential amount of time.
Flag: TExclusive,
}, },
t.Load(Binutils), Binutils,
t.Load(GMP), GMP,
t.Load(MPFR), MPFR,
t.Load(MPC), MPC,
t.Load(Zlib), Zlib,
t.Load(Libucontext), Libucontext,
t.Load(KernelHeaders), KernelHeaders,
) )
} }
func init() { artifactsF[gcc] = Toolchain.newGCC } func init() { artifactsF[gcc] = Toolchain.newGCC }

View File

@@ -143,7 +143,7 @@ sed -i \
go125 := t.newGo( go125 := t.newGo(
"1.25.7", "1.25.7",
"fyylHdBVRUobnBjYj3NKBaYPUw3kGmo2mEELiZonOYurPfbarNU1x77B99Fjut7Q", "fyylHdBVRUobnBjYj3NKBaYPUw3kGmo2mEELiZonOYurPfbarNU1x77B99Fjut7Q",
finalEnv, ` []string{"CGO_ENABLED=0"}, `
sed -i \ sed -i \
's,/lib/ld-musl-`+linuxArch()+`.so.1,/system/bin/linker,' \ 's,/lib/ld-musl-`+linuxArch()+`.so.1,/system/bin/linker,' \
cmd/link/internal/`+runtime.GOARCH+`/obj.go cmd/link/internal/`+runtime.GOARCH+`/obj.go
@@ -153,6 +153,17 @@ rm \
`, go123, `, go123,
) )
return go125 return t.newGo(
"1.26.0",
"uHLcrgBc0NMcyTMDLRNAZIcOx0RyQlyekSl9xbWSwj3esEFWJysYLfLa3S8p39Nh",
finalEnv, `
sed -i \
's,/lib/ld-musl-`+linuxArch()+`.so.1,/system/bin/linker,' \
cmd/link/internal/`+runtime.GOARCH+`/obj.go
rm \
os/root_unix_test.go
`, go125,
)
} }
func init() { artifactsF[Go] = Toolchain.newGoLatest } func init() { artifactsF[Go] = Toolchain.newGoLatest }

51
internal/rosa/gtk.go Normal file
View File

@@ -0,0 +1,51 @@
package rosa
import (
"strings"
"hakurei.app/container/fhs"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newGLib() pkg.Artifact {
const (
version = "2.86.4"
checksum = "AfTjBrrxtXXPL6dFa1LfTe40PyPSth62CoIkM5m_VJTUngGLOFHw6I4XE7RGQE8G"
)
return t.NewPackage("glib", version, pkg.NewHTTPGet(
nil, "https://download.gnome.org/sources/glib/"+
strings.Join(strings.SplitN(version, ".", 3)[:2], ".")+
"/glib-"+version+".tar.xz",
mustDecode(checksum),
), &PackageAttr{
SourceKind: sourceTarXZ,
Paths: []pkg.ExecPath{
pkg.Path(fhs.AbsEtc.Append(
"machine-id",
), false, pkg.NewFile(
"glib-machine-id",
[]byte("ffffffffffffffffffffffffffffffff\n"),
)),
pkg.Path(AbsSystem.Append(
"var/lib/dbus/machine-id",
), false, pkg.NewFile(
"glib-machine-id",
[]byte("fefefefefefefefefefefefefefefefe\n"),
)),
},
}, &MesonHelper{
Setup: [][2]string{
{"Ddefault_library", "both"},
},
},
XZ,
Packaging,
Bash,
PCRE2,
Libffi,
Zlib,
)
}
func init() { artifactsF[GLib] = Toolchain.newGLib }

View File

@@ -1,15 +1,9 @@
package rosa package rosa
import ( import "hakurei.app/internal/pkg"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newHakurei(suffix, script string) pkg.Artifact { func (t Toolchain) newHakurei(suffix, script string) pkg.Artifact {
const ( return t.New("hakurei"+suffix+"-"+hakureiVersion, 0, []pkg.Artifact{
version = "0.3.4"
checksum = "wVwSLo75a2OnH5tgxNWXR_YhiOJUFnYM_9-sJtxAEOKhcPE0BJafs6PU8o5JzyCT"
)
return t.New("hakurei"+suffix+"-"+version, 0, []pkg.Artifact{
t.Load(Go), t.Load(Go),
t.Load(Gzip), t.Load(Gzip),
@@ -43,214 +37,10 @@ echo
chmod -R +w /usr/src/hakurei chmod -R +w /usr/src/hakurei
cd /usr/src/hakurei cd /usr/src/hakurei
HAKUREI_VERSION='v`+version+`' HAKUREI_VERSION='v`+hakureiVersion+`'
`+script, pkg.Path(AbsUsrSrc.Append("hakurei"), true, t.NewPatchedSource("hakurei", version, pkg.NewHTTPGetTar( `+script, pkg.Path(AbsUsrSrc.Append("hakurei"), true, t.NewPatchedSource(
nil, "https://git.gensokyo.uk/security/hakurei/archive/"+ "hakurei", hakureiVersion, hakureiSource, true, hakureiPatches...,
"v"+version+".tar.gz", )), pkg.Path(AbsUsrSrc.Append("hostname", "main.go"), false, pkg.NewFile(
mustDecode(checksum),
pkg.TarGzip,
), true, [2]string{"dist-00-tests", `From 67e453f5c4de915de23ecbe5980e595758f0f2fb Mon Sep 17 00:00:00 2001
From: Ophestra <cat@gensokyo.uk>
Date: Tue, 27 Jan 2026 06:49:48 +0900
Subject: [PATCH] dist: run tests
This used to be impossible due to nix jank which has been addressed.
Signed-off-by: Ophestra <cat@gensokyo.uk>
---
dist/release.sh | 21 ++++++++++++++++-----
flake.nix | 32 ++++++++++++++++++++------------
internal/acl/acl_test.go | 2 +-
package.nix | 2 +-
4 files changed, 38 insertions(+), 19 deletions(-)
diff --git a/dist/release.sh b/dist/release.sh
index 4dcb278..0ba9104 100755
--- a/dist/release.sh
+++ b/dist/release.sh
@@ -2,19 +2,30 @@
cd "$(dirname -- "$0")/.."
VERSION="${HAKUREI_VERSION:-untagged}"
pname="hakurei-${VERSION}"
-out="dist/${pname}"
+out="${DESTDIR:-dist}/${pname}"
+echo '# Preparing distribution files.'
mkdir -p "${out}"
cp -v "README.md" "dist/hsurc.default" "dist/install.sh" "${out}"
cp -rv "dist/comp" "${out}"
+echo
+echo '# Building hakurei.'
go generate ./...
-go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static'
+go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
+ -buildid= -extldflags '-static'
-X hakurei.app/internal/info.buildVersion=${VERSION}
-X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
-X main.hakureiPath=/usr/bin/hakurei" ./...
+echo
-rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}"
-rm -rf "./${out}"
-(cd dist && sha512sum "${pname}.tar.gz" > "${pname}.tar.gz.sha512")
+echo '# Testing hakurei.'
+go test -ldflags='-buildid= -extldflags=-static' ./...
+echo
+
+echo '# Creating distribution.'
+rm -f "${out}.tar.gz" && tar -C "${out}/.." -vczf "${out}.tar.gz" "${pname}"
+rm -rf "${out}"
+(cd "${out}/.." && sha512sum "${pname}.tar.gz" > "${pname}.tar.gz.sha512")
+echo
diff --git a/flake.nix b/flake.nix
index 9e09c61..2340b92 100644
--- a/flake.nix
+++ b/flake.nix
@@ -143,19 +143,27 @@
"bin/mount.fuse.sharefs" = "${hakurei}/libexec/sharefs";
};
- dist = pkgs.runCommand "${hakurei.name}-dist" { buildInputs = hakurei.targetPkgs ++ [ pkgs.pkgsStatic.musl ]; } ''
- # go requires XDG_CACHE_HOME for the build cache
- export XDG_CACHE_HOME="$(mktemp -d)"
+ dist =
+ pkgs.runCommand "${hakurei.name}-dist"
+ {
+ buildInputs = hakurei.targetPkgs ++ [
+ pkgs.pkgsStatic.musl
+ ];
+ }
+ ''
+ cd $(mktemp -d) \
+ && cp -r ${hakurei.src}/. . \
+ && chmod +w cmd && cp -r ${hsu.src}/. cmd/hsu/ \
+ && chmod -R +w .
- # get a different workdir as go does not like /build
- cd $(mktemp -d) \
- && cp -r ${hakurei.src}/. . \
- && chmod +w cmd && cp -r ${hsu.src}/. cmd/hsu/ \
- && chmod -R +w .
-
- export HAKUREI_VERSION="v${hakurei.version}"
- CC="clang -O3 -Werror" ./dist/release.sh && mkdir $out && cp -v "dist/hakurei-$HAKUREI_VERSION.tar.gz"* $out
- '';
+ CC="musl-clang -O3 -Werror -Qunused-arguments" \
+ GOCACHE="$(mktemp -d)" \
+ HAKUREI_TEST_SKIP_ACL=1 \
+ PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
+ DESTDIR="$out" \
+ HAKUREI_VERSION="v${hakurei.version}" \
+ ./dist/release.sh
+ '';
}
);
diff --git a/internal/acl/acl_test.go b/internal/acl/acl_test.go
index af6da55..19ce45a 100644
--- a/internal/acl/acl_test.go
+++ b/internal/acl/acl_test.go
@@ -24,7 +24,7 @@ var (
)
func TestUpdate(t *testing.T) {
- if os.Getenv("GO_TEST_SKIP_ACL") == "1" {
+ if os.Getenv("HAKUREI_TEST_SKIP_ACL") == "1" {
t.Skip("acl test skipped")
}
diff --git a/package.nix b/package.nix
index 00c4401..2eaa2ec 100644
--- a/package.nix
+++ b/package.nix
@@ -89,7 +89,7 @@ buildGoModule rec {
CC = "clang -O3 -Werror";
# nix build environment does not allow acls
- GO_TEST_SKIP_ACL = 1;
+ HAKUREI_TEST_SKIP_ACL = 1;
};
buildInputs = [`}, [2]string{"container-tests", `From bf14a412e47344fff2681f4b24d1ecc7415bfcb0 Mon Sep 17 00:00:00 2001
From: Ophestra <cat@gensokyo.uk>
Date: Sat, 31 Jan 2026 10:59:56 +0900
Subject: [PATCH] container: fix host-dependent test cases
These are not fully controlled by hakurei and may change depending on host configuration.
Signed-off-by: Ophestra <cat@gensokyo.uk>
---
container/container_test.go | 27 +++++++++++++++------------
1 file changed, 15 insertions(+), 12 deletions(-)
diff --git a/container/container_test.go b/container/container_test.go
index d737a18..98713cb 100644
--- a/container/container_test.go
+++ b/container/container_test.go
@@ -275,12 +275,12 @@ var containerTestCases = []struct {
),
earlyMnt(
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
- ent("/null", "/dev/null", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/zero", "/dev/zero", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/full", "/dev/full", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/random", "/dev/random", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
+ ent("/null", "/dev/null", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/zero", "/dev/zero", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/full", "/dev/full", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/random", "/dev/random", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/urandom", "/dev/urandom", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/tty", "/dev/tty", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
ent("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
@@ -293,12 +293,12 @@ var containerTestCases = []struct {
),
earlyMnt(
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
- ent("/null", "/dev/null", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/zero", "/dev/zero", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/full", "/dev/full", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/random", "/dev/random", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
+ ent("/null", "/dev/null", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/zero", "/dev/zero", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/full", "/dev/full", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/random", "/dev/random", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/urandom", "/dev/urandom", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/tty", "/dev/tty", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
),
@@ -696,6 +696,9 @@ func init() {
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",relatime")
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",noatime")
+ cur.FsOptstr = strings.Replace(cur.FsOptstr, ",seclabel", "", 1)
+ mnt[i].FsOptstr = strings.Replace(mnt[i].FsOptstr, ",seclabel", "", 1)
+
if !cur.EqualWithIgnore(mnt[i], "\x00") {
fail = true
log.Printf("[FAIL] %s", cur)`}, [2]string{"dist-01-tarball-name", `diff --git a/dist/release.sh b/dist/release.sh
index 0ba9104..2990ee1 100755
--- a/dist/release.sh
+++ b/dist/release.sh
@@ -1,7 +1,7 @@
#!/bin/sh -e
cd "$(dirname -- "$0")/.."
VERSION="${HAKUREI_VERSION:-untagged}"
-pname="hakurei-${VERSION}"
+pname="hakurei-${VERSION}-$(go env GOARCH)"
out="${DESTDIR:-dist}/${pname}"
echo '# Preparing distribution files.'
`}),
), pkg.Path(AbsUsrSrc.Append("hostname", "main.go"), false, pkg.NewFile(
"hostname.go", "hostname.go",
[]byte(` []byte(`
package main package main
@@ -276,6 +66,7 @@ echo '# Building hakurei.'
go generate -v ./... go generate -v ./...
go build -trimpath -v -o /work/system/libexec/hakurei -ldflags="-s -w go build -trimpath -v -o /work/system/libexec/hakurei -ldflags="-s -w
-buildid= -buildid=
-linkmode external
-extldflags=-static -extldflags=-static
-X hakurei.app/internal/info.buildVersion="$HAKUREI_VERSION" -X hakurei.app/internal/info.buildVersion="$HAKUREI_VERSION"
-X hakurei.app/internal/info.hakureiPath=/system/bin/hakurei -X hakurei.app/internal/info.hakureiPath=/system/bin/hakurei
@@ -284,7 +75,7 @@ go build -trimpath -v -o /work/system/libexec/hakurei -ldflags="-s -w
echo echo
echo '# Testing hakurei.' echo '# Testing hakurei.'
go test -ldflags='-buildid= -extldflags=-static' ./... go test -ldflags='-buildid= -linkmode external -extldflags=-static' ./...
echo echo
mkdir -p /work/system/bin/ mkdir -p /work/system/bin/

View File

@@ -0,0 +1,26 @@
//go:build current
package rosa
import (
_ "embed"
"hakurei.app/internal/pkg"
)
const hakureiVersion = "1.0-current"
// hakureiSourceTarball is a compressed tarball of the hakurei source code.
//
//go:generate tar -zc -C ../.. --exclude .git --exclude *.tar.gz -f hakurei_current.tar.gz .
//go:embed hakurei_current.tar.gz
var hakureiSourceTarball []byte
// hakureiSource is the source code at the time this package is compiled.
var hakureiSource = pkg.NewTar(pkg.NewFile(
"hakurei-current.tar.gz",
hakureiSourceTarball,
), pkg.TarGzip)
// hakureiPatches are patches applied against the compile-time source tree.
var hakureiPatches [][2]string

View File

@@ -0,0 +1,51 @@
//go:build !current
package rosa
import "hakurei.app/internal/pkg"
const hakureiVersion = "0.3.5"
// hakureiSource is the source code of a hakurei release.
var hakureiSource = pkg.NewHTTPGetTar(
nil, "https://git.gensokyo.uk/security/hakurei/archive/"+
"v"+hakureiVersion+".tar.gz",
mustDecode("6Tn38NLezRD2d3aGdFg5qFfqn8_KvC6HwMKwJMPvaHmVw8xRgxn8B0PObswl2mOk"),
pkg.TarGzip,
)
// hakureiPatches are patches applied against a hakurei release.
var hakureiPatches = [][2]string{
{"createTemp-error-injection", `diff --git a/container/dispatcher_test.go b/container/dispatcher_test.go
index 5de37fc..fe0c4db 100644
--- a/container/dispatcher_test.go
+++ b/container/dispatcher_test.go
@@ -238,8 +238,11 @@ 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 on it for sloppy implementations will cause sporadic test results
- f.cleanup = runtime.AddCleanup(f, func(name string) { f.t.Fatalf("checkedOsFile %s became unreachable without a call to Close", name) }, f.name)
+ // check happens in Close, and cleanup is not guaranteed to run, so relying
+ // on it for sloppy implementations will cause sporadic test results
+ f.cleanup = runtime.AddCleanup(f, func(name string) {
+ panic("checkedOsFile " + name + " became unreachable without a call to Close")
+ }, name)
return f
}
diff --git a/container/initplace_test.go b/container/initplace_test.go
index afeddbe..1c2f20b 100644
--- a/container/initplace_test.go
+++ b/container/initplace_test.go
@@ -21,7 +21,7 @@ func TestTmpfileOp(t *testing.T) {
Path: samplePath,
Data: sampleData,
}, nil, nil, []stub.Call{
- call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), stub.UniqueError(5)),
+ call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, (*checkedOsFile)(nil), stub.UniqueError(5)),
}, stub.UniqueError(5)},
{"Write", &Params{ParentPerm: 0700}, &TmpfileOp{
`},
}

19
internal/rosa/images.go Normal file
View File

@@ -0,0 +1,19 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newImageInitramfs() pkg.Artifact {
return t.New("initramfs", TNoToolchain, []pkg.Artifact{
t.Load(Zstd),
t.Load(Hakurei),
t.Load(GenInitCPIO),
}, nil, nil, `
gen_init_cpio -t 4294967295 -c /usr/src/initramfs | zstd > /work/initramfs.zst
`, pkg.Path(AbsUsrSrc.Append("initramfs"), false, pkg.NewFile("initramfs", []byte(`
dir /dev 0755 0 0
nod /dev/null 0666 0 0 c 1 3
nod /dev/console 0600 0 0 c 5 1
file /init /system/libexec/hakurei/earlyinit 0555 0 0
`))))
}
func init() { artifactsF[ImageInitramfs] = Toolchain.newImageInitramfs }

View File

@@ -1,44 +1,136 @@
package rosa package rosa
import ( import "hakurei.app/internal/pkg"
"slices"
"hakurei.app/internal/pkg" const kernelVersion = "6.12.73"
var kernelSource = pkg.NewHTTPGetTar(
nil, "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/"+
"snapshot/linux-"+kernelVersion+".tar.gz",
mustDecode("29oUBJKF1ULIv1-XQLpEUUc3LgjUSmyvOSskG37MYUcBlBjMk7RcbCTLrD7UfSM6"),
pkg.TarGzip,
) )
// newKernel is a helper for interacting with Kbuild. func (t Toolchain) newKernelSource() pkg.Artifact {
func (t Toolchain) newKernel( return t.New("kernel-"+kernelVersion+"-src", 0, nil, nil, nil, `
flag int, mkdir -p /work/usr/src/
patches [][2]string, cp -r /usr/src/linux /work/usr/src/
script string, chmod -R +w /work/usr/src/linux/
extra ...pkg.Artifact, `, pkg.Path(AbsUsrSrc.Append("linux"), false, kernelSource))
) pkg.Artifact {
const (
version = "6.18.5"
checksum = "-V1e1WWl7HuePkmm84sSKF7nLuHfUs494uNMzMqXEyxcNE_PUE0FICL0oGWn44mM"
)
return t.New("kernel-"+version, flag, slices.Concat([]pkg.Artifact{
t.Load(Make),
}, extra), nil, nil, `
export LLVM=1
export HOSTLDFLAGS="${LDFLAGS}"
cd /usr/src/linux
`+script, pkg.Path(AbsUsrSrc.Append("linux"), true, t.NewPatchedSource(
"kernel", version, pkg.NewHTTPGetTar(
nil,
"https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/"+
"snapshot/linux-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), false, patches...,
)))
} }
func init() { artifactsF[KernelSource] = Toolchain.newKernelSource }
func (t Toolchain) newKernelHeaders() pkg.Artifact { func (t Toolchain) newKernelHeaders() pkg.Artifact {
return t.newKernel(TEarly, nil, ` return t.NewPackage("kernel-headers", kernelVersion, kernelSource, &PackageAttr{
make "-j$(nproc)" \ Flag: TEarly,
INSTALL_HDR_PATH=/work/system \ }, &MakeHelper{
headers_install SkipConfigure: true,
`, t.Load(Rsync))
SkipCheck: true,
Make: []string{
"-f /usr/src/kernel-headers/Makefile",
"O=/tmp/kbuild",
"LLVM=1",
`HOSTLDFLAGS="${LDFLAGS:-''}"`,
"INSTALL_HDR_PATH=/work/system",
"headers_install",
},
Install: "# headers installed during make",
},
Rsync,
)
} }
func init() { artifactsF[KernelHeaders] = Toolchain.newKernelHeaders } func init() { artifactsF[KernelHeaders] = Toolchain.newKernelHeaders }
func (t Toolchain) newKernel() pkg.Artifact {
return t.NewPackage("kernel", kernelVersion, kernelSource, &PackageAttr{
Env: []string{
"PATH=/system/sbin",
},
ScriptEarly: `
install -Dm0400 \
/usr/src/.config \
/tmp/kbuild/.config
install -Dm0500 \
/usr/src/.installkernel \
/sbin/installkernel
`,
Paths: []pkg.ExecPath{
pkg.Path(AbsUsrSrc.Append(
".config",
), false, pkg.NewFile(".config", kernelConfig)),
pkg.Path(AbsUsrSrc.Append(
".installkernel",
), false, pkg.NewFile("installkernel", []byte(`#!/bin/sh -e
echo "Installing linux $1..."
cp -av "$2" "$4"
cp -av "$3" "$4"
`))),
},
Flag: TExclusive,
}, &MakeHelper{
OmitDefaults: true,
SkipConfigure: true,
// Build, install, and boot kernel before running kselftest on it.
SkipCheck: true,
Make: []string{
"-f /usr/src/kernel/Makefile",
"O=/tmp/kbuild",
"LLVM=1",
"KBUILD_BUILD_VERSION='1-Rosa'",
"KBUILD_BUILD_TIMESTAMP='2106-02-07 06:28:15 UTC'",
"KBUILD_BUILD_USER=kbuild",
"KBUILD_BUILD_HOST=localhost",
"all",
},
Install: `
make \
"-j$(nproc)" \
-f /usr/src/kernel/Makefile \
O=/tmp/kbuild \
LLVM=1 \
INSTALL_PATH=/work \
install \
INSTALL_MOD_PATH=/work \
modules_install
rm -v /work/lib/modules/` + kernelVersion + `/build
`,
},
Flex,
Bison,
M4,
Tar,
Perl,
BC,
Sed,
Gawk,
Coreutils,
Diffutils,
Python,
XZ,
Zlib,
Gzip,
Bzip2,
Zstd,
Kmod,
Elfutils,
OpenSSL,
UtilLinux,
KernelHeaders,
)
}
func init() { artifactsF[Kernel] = Toolchain.newKernel }
func (t Toolchain) newGenInitCPIO() pkg.Artifact {
return t.New("gen_init_cpio-"+kernelVersion, 0, nil, nil, nil, `
mkdir -p /work/system/bin/
cc -o /work/system/bin/gen_init_cpio /usr/src/linux/usr/gen_init_cpio.c
`, pkg.Path(AbsUsrSrc.Append("linux"), false, kernelSource))
}
func init() { artifactsF[GenInitCPIO] = Toolchain.newGenInitCPIO }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
package rosa
import _ "embed"
//go:embed kernel_amd64.config
var kernelConfig []byte
const kernelName = "bzImage"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
package rosa
import _ "embed"
//go:embed kernel_arm64.config
var kernelConfig []byte
const kernelName = "vmlinuz.efi"

33
internal/rosa/kmod.go Normal file
View File

@@ -0,0 +1,33 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newKmod() pkg.Artifact {
const (
version = "34.2"
checksum = "0K7POeTKxMhExsaTsnKAC6LUNsRSfe6sSZxWONPbOu-GI_pXOw3toU_BIoqfBhJV"
)
return t.NewPackage("kmod", version, pkg.NewHTTPGetTar(
nil, "https://www.kernel.org/pub/linux/utils/kernel/"+
"kmod/kmod-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), nil, &MesonHelper{
Setup: [][2]string{
{"Dsysconfdir", "/system/etc"},
{"Dbashcompletiondir", "no"},
{"Dfishcompletiondir", "no"},
{"Dxz", "disabled"},
{"Dmanpages", "false"},
},
// makes assumptions about the running kernel
SkipTest: true,
},
Zlib,
Zstd,
OpenSSL,
KernelHeaders,
)
}
func init() { artifactsF[Kmod] = Toolchain.newKmod }

44
internal/rosa/libcap.go Normal file
View File

@@ -0,0 +1,44 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newLibcap() pkg.Artifact {
const (
version = "2.77"
checksum = "2GOTFU4cl2QoS7Dv5wh0c9-hxsQwIzMB9Y_gfAo5xKHqcM13fiHt1RbPkfemzjmB"
)
return t.NewPackage("libcap", version, pkg.NewHTTPGetTar(
nil, "https://git.kernel.org/pub/scm/libs/libcap/libcap.git/"+
"snapshot/libcap-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), &PackageAttr{
// uses source tree as scratch space
Writable: true,
Chmod: true,
Env: []string{
"prefix=/system",
"lib=lib",
},
ScriptEarly: `
ln -s ../system/bin/bash /bin/
`,
}, &MakeHelper{
SkipConfigure: true,
InPlace: true,
Make: []string{
"CC=cc",
"all",
},
Check: []string{
"CC=cc",
"test",
},
},
Bash,
Diffutils,
)
}
func init() { artifactsF[Libcap] = Toolchain.newLibcap }

View File

@@ -11,18 +11,14 @@ func (t Toolchain) newLibexpat() pkg.Artifact {
version = "2.7.3" version = "2.7.3"
checksum = "GmkoD23nRi9cMT0cgG1XRMrZWD82UcOMzkkvP1gkwSFWCBgeSXMuoLpa8-v8kxW-" checksum = "GmkoD23nRi9cMT0cgG1XRMrZWD82UcOMzkkvP1gkwSFWCBgeSXMuoLpa8-v8kxW-"
) )
return t.NewViaMake("libexpat", version, pkg.NewHTTPGetTar( return t.NewPackage("libexpat", version, pkg.NewHTTPGetTar(
nil, "https://github.com/libexpat/libexpat/releases/download/"+ nil, "https://github.com/libexpat/libexpat/releases/download/"+
"R_"+strings.ReplaceAll(version, ".", "_")+"/"+ "R_"+strings.ReplaceAll(version, ".", "_")+"/"+
"expat-"+version+".tar.bz2", "expat-"+version+".tar.bz2",
mustDecode(checksum), mustDecode(checksum),
pkg.TarBzip2, pkg.TarBzip2,
), &MakeAttr{ ), nil, (*MakeHelper)(nil),
Configure: [][2]string{ Bash,
{"enable-static"},
},
},
t.Load(Bash),
) )
} }
func init() { artifactsF[Libexpat] = Toolchain.newLibexpat } func init() { artifactsF[Libexpat] = Toolchain.newLibexpat }

View File

@@ -7,17 +7,13 @@ func (t Toolchain) newLibffi() pkg.Artifact {
version = "3.4.5" version = "3.4.5"
checksum = "apIJzypF4rDudeRoI_n3K7N-zCeBLTbQlHRn9NSAZqdLAWA80mR0gXPTpHsL7oMl" checksum = "apIJzypF4rDudeRoI_n3K7N-zCeBLTbQlHRn9NSAZqdLAWA80mR0gXPTpHsL7oMl"
) )
return t.NewViaMake("libffi", version, pkg.NewHTTPGetTar( return t.NewPackage("libffi", version, pkg.NewHTTPGetTar(
nil, "https://github.com/libffi/libffi/releases/download/"+ nil, "https://github.com/libffi/libffi/releases/download/"+
"v"+version+"/libffi-"+version+".tar.gz", "v"+version+"/libffi-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), nil, (*MakeHelper)(nil),
Configure: [][2]string{ KernelHeaders,
{"enable-static"},
},
},
t.Load(KernelHeaders),
) )
} }
func init() { artifactsF[Libffi] = Toolchain.newLibffi } func init() { artifactsF[Libffi] = Toolchain.newLibffi }

View File

@@ -7,24 +7,20 @@ func (t Toolchain) newLibgd() pkg.Artifact {
version = "2.3.3" version = "2.3.3"
checksum = "8T-sh1_FJT9K9aajgxzh8ot6vWIF-xxjcKAHvTak9MgGUcsFfzP8cAvvv44u2r36" checksum = "8T-sh1_FJT9K9aajgxzh8ot6vWIF-xxjcKAHvTak9MgGUcsFfzP8cAvvv44u2r36"
) )
return t.NewViaMake("libgd", version, pkg.NewHTTPGetTar( return t.NewPackage("libgd", version, pkg.NewHTTPGetTar(
nil, "https://github.com/libgd/libgd/releases/download/"+ nil, "https://github.com/libgd/libgd/releases/download/"+
"gd-"+version+"/libgd-"+version+".tar.gz", "gd-"+version+"/libgd-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
OmitDefaults: true,
Env: []string{ Env: []string{
"TMPDIR=/dev/shm/gd", "TMPDIR=/dev/shm/gd",
}, },
ScriptEarly: ` ScriptEarly: `
mkdir /dev/shm/gd mkdir /dev/shm/gd
`, `,
Configure: [][2]string{ }, (*MakeHelper)(nil),
{"enable-static"}, Zlib,
},
},
t.Load(Zlib),
) )
} }
func init() { artifactsF[Libgd] = Toolchain.newLibgd } func init() { artifactsF[Libgd] = Toolchain.newLibgd }

View File

@@ -7,22 +7,20 @@ func (t Toolchain) newLibpsl() pkg.Artifact {
version = "0.21.5" version = "0.21.5"
checksum = "XjfxSzh7peG2Vg4vJlL8z4JZJLcXqbuP6pLWkrGCmRxlnYUFTKNBqWGHCxEOlCad" checksum = "XjfxSzh7peG2Vg4vJlL8z4JZJLcXqbuP6pLWkrGCmRxlnYUFTKNBqWGHCxEOlCad"
) )
return t.NewViaMake("libpsl", version, pkg.NewHTTPGetTar( return t.NewPackage("libpsl", version, pkg.NewHTTPGetTar(
nil, "https://github.com/rockdaboot/libpsl/releases/download/"+ nil, "https://github.com/rockdaboot/libpsl/releases/download/"+
version+"/libpsl-"+version+".tar.gz", version+"/libpsl-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Writable: true, Writable: true,
ScriptEarly: ` ScriptEarly: `
cd /usr/src/libpsl
test_disable() { chmod +w "$2" && echo "$1" > "$2"; } test_disable() { chmod +w "$2" && echo "$1" > "$2"; }
test_disable 'int main(){return 0;}' tests/test-is-public-builtin.c test_disable 'int main(){return 0;}' tests/test-is-public-builtin.c
`, `,
}, }, (*MakeHelper)(nil),
t.Load(Python), Python,
) )
} }
func init() { artifactsF[Libpsl] = Toolchain.newLibpsl } func init() { artifactsF[Libpsl] = Toolchain.newLibpsl }

View File

@@ -9,25 +9,21 @@ func (t Toolchain) newLibseccomp() pkg.Artifact {
version = "2.6.0" version = "2.6.0"
checksum = "mMu-iR71guPjFbb31u-YexBaanKE_nYPjPux-vuBiPfS_0kbwJdfCGlkofaUm-EY" checksum = "mMu-iR71guPjFbb31u-YexBaanKE_nYPjPux-vuBiPfS_0kbwJdfCGlkofaUm-EY"
) )
return t.NewViaMake("libseccomp", version, pkg.NewHTTPGetTar( return t.NewPackage("libseccomp", version, pkg.NewHTTPGetTar(
nil, nil, "https://github.com/seccomp/libseccomp/releases/download/"+
"https://github.com/seccomp/libseccomp/releases/download/"+
"v"+version+"/libseccomp-"+version+".tar.gz", "v"+version+"/libseccomp-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
ScriptEarly: ` ScriptEarly: `
ln -s ../system/bin/bash /bin/ ln -s ../system/bin/bash /bin/
`, `,
Configure: [][2]string{ }, (*MakeHelper)(nil),
{"enable-static"}, Bash,
}, Diffutils,
}, Gperf,
t.Load(Bash),
t.Load(Diffutils),
t.Load(Gperf),
t.Load(KernelHeaders), KernelHeaders,
) )
} }
func init() { artifactsF[Libseccomp] = Toolchain.newLibseccomp } func init() { artifactsF[Libseccomp] = Toolchain.newLibseccomp }

View File

@@ -7,34 +7,24 @@ func (t Toolchain) newLibucontext() pkg.Artifact {
version = "1.5" version = "1.5"
checksum = "Ggk7FMmDNBdCx1Z9PcNWWW6LSpjGYssn2vU0GK5BLXJYw7ZxZbA2m_eSgT9TFnIG" checksum = "Ggk7FMmDNBdCx1Z9PcNWWW6LSpjGYssn2vU0GK5BLXJYw7ZxZbA2m_eSgT9TFnIG"
) )
return t.New("libucontext", 0, []pkg.Artifact{ return t.NewPackage("libucontext", version, pkg.NewHTTPGetTar(
t.Load(Make), nil, "https://github.com/kaniini/libucontext/archive/refs/tags/"+
}, nil, []string{ "libucontext-"+version+".tar.gz",
"ARCH=" + linuxArch(), mustDecode(checksum),
}, ` pkg.TarGzip,
cd /usr/src/libucontext ), &PackageAttr{
make check // uses source tree as scratch space
make DESTDIR=/work install Writable: true,
`, pkg.Path(AbsUsrSrc.Append("libucontext"), true, Chmod: true,
t.NewPatchedSource("libucontext", version, pkg.NewHTTPGetTar( EnterSource: true,
nil, "https://github.com/kaniini/libucontext/archive/refs/tags/"+ }, &MakeHelper{
"libucontext-"+version+".tar.gz", OmitDefaults: true,
mustDecode(checksum), SkipConfigure: true,
pkg.TarGzip, InPlace: true,
), true, [2]string{"rosa-prefix", `diff --git a/Makefile b/Makefile Make: []string{
index c80e574..4a8c1d3 100644 "ARCH=" + linuxArch(),
--- a/Makefile },
+++ b/Makefile Install: "make prefix=/system DESTDIR=/work install",
@@ -17,7 +17,7 @@ ifeq ($(ARCH),$(filter $(ARCH),arm64)) })
override ARCH = aarch64
endif
-prefix = /usr
+prefix = /system
libdir = ${prefix}/lib
shared_libdir = ${libdir}
static_libdir = ${libdir}
`}),
))
} }
func init() { artifactsF[Libucontext] = Toolchain.newLibucontext } func init() { artifactsF[Libucontext] = Toolchain.newLibucontext }

View File

@@ -11,24 +11,16 @@ func (t Toolchain) newLibxml2() pkg.Artifact {
version = "2.15.1" version = "2.15.1"
checksum = "pYzAR3cNrEHezhEMirgiq7jbboLzwMj5GD7SQp0jhSIMdgoU4G9oU9Gxun3zzUIU" checksum = "pYzAR3cNrEHezhEMirgiq7jbboLzwMj5GD7SQp0jhSIMdgoU4G9oU9Gxun3zzUIU"
) )
return t.NewViaMake("libxml2", version, pkg.NewHTTPGet( return t.NewPackage("libxml2", version, pkg.NewHTTPGet(
nil, "https://download.gnome.org/sources/libxml2/"+ nil, "https://download.gnome.org/sources/libxml2/"+
strings.Join(strings.Split(version, ".")[:2], ".")+ strings.Join(strings.Split(version, ".")[:2], ".")+
"/libxml2-"+version+".tar.xz", "/libxml2-"+version+".tar.xz",
mustDecode(checksum), mustDecode(checksum),
), &MakeAttr{ ), &PackageAttr{
ScriptEarly: ` SourceKind: sourceTarXZ,
cd /usr/src/ }, (*MakeHelper)(nil),
tar xf libxml2.tar.xz Diffutils,
mv libxml2-` + version + ` libxml2 XZ,
`,
Configure: [][2]string{
{"enable-static"},
},
SourceSuffix: ".tar.xz",
},
t.Load(Diffutils),
t.Load(XZ),
) )
} }
func init() { artifactsF[Libxml2] = Toolchain.newLibxml2 } func init() { artifactsF[Libxml2] = Toolchain.newLibxml2 }

32
internal/rosa/libxslt.go Normal file
View File

@@ -0,0 +1,32 @@
package rosa
import (
"strings"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newLibxslt() pkg.Artifact {
const (
version = "1.1.45"
checksum = "vw72UbREQnA3YDYuZ9-93hDr9BYCaKV6oh_U4Kt4n1Js_na4E-nFj-ksZnZ0kvEK"
)
return t.NewPackage("libxslt", version, pkg.NewHTTPGet(
nil, "https://download.gnome.org/sources/libxslt/"+
strings.Join(strings.Split(version, ".")[:2], ".")+
"/libxslt-"+version+".tar.xz",
mustDecode(checksum),
), &PackageAttr{
SourceKind: sourceTarXZ,
}, &MakeHelper{
// python libxml2 cyclic dependency
SkipCheck: true,
},
XZ,
Python,
PkgConfig,
Libxml2,
)
}
func init() { artifactsF[Libxslt] = Toolchain.newLibxslt }

View File

@@ -7,33 +7,27 @@ import (
"strings" "strings"
"sync" "sync"
"hakurei.app/container/check"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
) )
// llvmAttr holds the attributes that will be applied to a new [pkg.Artifact] // llvmAttr holds the attributes that will be applied to a new [pkg.Artifact]
// containing a LLVM variant. // containing a LLVM variant.
type llvmAttr struct { type llvmAttr struct {
// Passed through to PackageAttr.Flag.
flags int flags int
// Concatenated with default environment for CMakeAttr.Env. // Concatenated with default environment for PackageAttr.Env.
env []string env []string
// Concatenated with generated entries for CMakeAttr.Cache. // Concatenated with generated entries for CMakeHelper.Cache.
cmake [][2]string cmake [][2]string
// Override CMakeAttr.Append. // Override CMakeHelper.Append.
append []string append []string
// Concatenated with default dependencies for Toolchain.NewViaCMake. // Passed through to PackageAttr.NonStage0.
extra []pkg.Artifact nonStage0 []pkg.Artifact
// Passed through to CMakeAttr.Paths. // Passed through to PackageAttr.Paths.
paths []pkg.ExecPath paths []pkg.ExecPath
// Passed through to CMakeAttr.ScriptConfigured. // Concatenated with default fixup for CMakeHelper.Script.
scriptConfigured string
// Concatenated with default fixup for CMakeAttr.Script.
script string script string
// Passed through to CMakeAttr.Prefix.
prefix *check.Absolute
// Passed through to CMakeAttr.Writable.
writable bool
// Patch name and body pairs. // Patch name and body pairs.
patches [][2]string patches [][2]string
@@ -101,7 +95,7 @@ func (t Toolchain) newLLVMVariant(variant string, attr *llvmAttr) pkg.Artifact {
} }
} }
var script, scriptEarly string var script string
cache := [][2]string{ cache := [][2]string{
{"CMAKE_BUILD_TYPE", "Release"}, {"CMAKE_BUILD_TYPE", "Release"},
@@ -159,15 +153,6 @@ ln -s ld.lld /work/system/bin/ld
[2]string{"LIBCXX_HAS_MUSL_LIBC", "ON"}, [2]string{"LIBCXX_HAS_MUSL_LIBC", "ON"},
[2]string{"LIBCXX_USE_COMPILER_RT", "ON"}, [2]string{"LIBCXX_USE_COMPILER_RT", "ON"},
) )
if t > toolchainStage3 {
// libcxxabi fails to compile if c++ headers not prefixed in /usr
// is found by the compiler, and doing this is easier than
// overriding CXXFLAGS; not using mv here to avoid chown failures
scriptEarly += `
cp -r /system/include /usr/include && rm -rf /system/include
`
}
} }
if attr.flags&llvmRuntimeLibcxxABI != 0 { if attr.flags&llvmRuntimeLibcxxABI != 0 {
cache = append(cache, cache = append(cache,
@@ -176,41 +161,40 @@ cp -r /system/include /usr/include && rm -rf /system/include
) )
} }
return t.NewViaCMake("llvm", version, variant, t.NewPatchedSource( return t.NewPackage("llvm", version, pkg.NewHTTPGetTar(
"llvmorg", version, pkg.NewHTTPGetTar( nil, "https://github.com/llvm/llvm-project/archive/refs/tags/"+
nil, "https://github.com/llvm/llvm-project/archive/refs/tags/"+ "llvmorg-"+version+".tar.gz",
"llvmorg-"+version+".tar.gz", mustDecode(checksum),
mustDecode(checksum), pkg.TarGzip,
pkg.TarGzip, ), &PackageAttr{
), true, attr.patches..., Patches: attr.patches,
), &CMakeAttr{ NonStage0: attr.nonStage0,
Cache: slices.Concat(cache, attr.cmake),
Append: cmakeAppend,
Prefix: attr.prefix,
Env: slices.Concat([]string{ Env: slices.Concat([]string{
"ROSA_LLVM_PROJECTS=" + strings.Join(projects, ";"), "ROSA_LLVM_PROJECTS=" + strings.Join(projects, ";"),
"ROSA_LLVM_RUNTIMES=" + strings.Join(runtimes, ";"), "ROSA_LLVM_RUNTIMES=" + strings.Join(runtimes, ";"),
}, attr.env), }, attr.env),
ScriptEarly: scriptEarly,
ScriptConfigured: attr.scriptConfigured,
Script: script + attr.script,
Writable: attr.writable,
Paths: attr.paths, Paths: attr.paths,
Flag: TExclusive, Flag: TExclusive,
}, stage3Concat(t, attr.extra, }, &CMakeHelper{
t.Load(Libffi), Variant: variant,
t.Load(Python),
t.Load(Perl),
t.Load(Diffutils),
t.Load(Bash),
t.Load(Gawk),
t.Load(Coreutils),
t.Load(Findutils),
t.Load(KernelHeaders), Cache: slices.Concat(cache, attr.cmake),
)...) Append: cmakeAppend,
Script: script + attr.script,
},
Libffi,
Python,
Perl,
Diffutils,
Bash,
Gawk,
Coreutils,
Findutils,
KernelHeaders,
)
} }
// newLLVM returns LLVM toolchain across multiple [pkg.Artifact]. // newLLVM returns LLVM toolchain across multiple [pkg.Artifact].
@@ -233,7 +217,7 @@ func (t Toolchain) newLLVM() (musl, compilerRT, runtimes, clang pkg.Artifact) {
} }
compilerRT = t.newLLVMVariant("compiler-rt", &llvmAttr{ compilerRT = t.newLLVMVariant("compiler-rt", &llvmAttr{
env: stage3ExclConcat(t, []string{}, env: stage0ExclConcat(t, []string{},
"LDFLAGS="+earlyLDFLAGS(false), "LDFLAGS="+earlyLDFLAGS(false),
), ),
cmake: [][2]string{ cmake: [][2]string{
@@ -253,42 +237,36 @@ func (t Toolchain) newLLVM() (musl, compilerRT, runtimes, clang pkg.Artifact) {
{"COMPILER_RT_BUILD_XRAY", "OFF"}, {"COMPILER_RT_BUILD_XRAY", "OFF"},
}, },
append: []string{"compiler-rt"}, append: []string{"compiler-rt"},
extra: []pkg.Artifact{t.NewMusl(&MuslAttr{ nonStage0: []pkg.Artifact{t.newMusl(true, []string{
Headers: true, "CC=clang",
Env: []string{
"CC=clang",
},
})}, })},
script: ` script: `
mkdir -p "${ROSA_INSTALL_PREFIX}/lib/clang/21/lib/" mkdir -p "/work/system/lib/clang/21/lib/"
ln -s \ ln -s \
"../../../${ROSA_TRIPLE}" \ "../../../${ROSA_TRIPLE}" \
"${ROSA_INSTALL_PREFIX}/lib/clang/21/lib/" "/work/system/lib/clang/21/lib/"
ln -s \ ln -s \
"clang_rt.crtbegin-` + linuxArch() + `.o" \ "clang_rt.crtbegin-` + linuxArch() + `.o" \
"${ROSA_INSTALL_PREFIX}/lib/${ROSA_TRIPLE}/crtbeginS.o" "/work/system/lib/${ROSA_TRIPLE}/crtbeginS.o"
ln -s \ ln -s \
"clang_rt.crtend-` + linuxArch() + `.o" \ "clang_rt.crtend-` + linuxArch() + `.o" \
"${ROSA_INSTALL_PREFIX}/lib/${ROSA_TRIPLE}/crtendS.o" "/work/system/lib/${ROSA_TRIPLE}/crtendS.o"
`, `,
}) })
musl = t.NewMusl(&MuslAttr{ musl = t.newMusl(false, stage0ExclConcat(t, []string{
Extra: []pkg.Artifact{compilerRT}, "CC=clang",
Env: stage3ExclConcat(t, []string{ "LIBCC=/system/lib/clang/21/lib/" +
"CC=clang", triplet() + "/libclang_rt.builtins.a",
"LIBCC=/system/lib/clang/21/lib/" + "AR=ar",
triplet() + "/libclang_rt.builtins.a", "RANLIB=ranlib",
"AR=ar", },
"RANLIB=ranlib", "LDFLAGS="+earlyLDFLAGS(false),
}, ), compilerRT)
"LDFLAGS="+earlyLDFLAGS(false),
),
})
runtimes = t.newLLVMVariant("runtimes", &llvmAttr{ runtimes = t.newLLVMVariant("runtimes", &llvmAttr{
env: stage3ExclConcat(t, []string{}, env: stage0ExclConcat(t, []string{},
"LDFLAGS="+earlyLDFLAGS(false), "LDFLAGS="+earlyLDFLAGS(false),
), ),
flags: llvmRuntimeLibunwind | llvmRuntimeLibcxx | llvmRuntimeLibcxxABI, flags: llvmRuntimeLibunwind | llvmRuntimeLibcxx | llvmRuntimeLibcxxABI,
@@ -300,7 +278,7 @@ ln -s \
{"LIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL", "OFF"}, {"LIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL", "OFF"},
}, minimalDeps), }, minimalDeps),
append: []string{"runtimes"}, append: []string{"runtimes"},
extra: []pkg.Artifact{ nonStage0: []pkg.Artifact{
compilerRT, compilerRT,
musl, musl,
}, },
@@ -308,7 +286,7 @@ ln -s \
clang = t.newLLVMVariant("clang", &llvmAttr{ clang = t.newLLVMVariant("clang", &llvmAttr{
flags: llvmProjectClang | llvmProjectLld, flags: llvmProjectClang | llvmProjectLld,
env: stage3ExclConcat(t, []string{}, env: stage0ExclConcat(t, []string{},
"CFLAGS="+earlyCFLAGS, "CFLAGS="+earlyCFLAGS,
"CXXFLAGS="+earlyCXXFLAGS(), "CXXFLAGS="+earlyCXXFLAGS(),
"LDFLAGS="+earlyLDFLAGS(false), "LDFLAGS="+earlyLDFLAGS(false),
@@ -318,7 +296,7 @@ ln -s \
{"CMAKE_CROSSCOMPILING", "OFF"}, {"CMAKE_CROSSCOMPILING", "OFF"},
{"CXX_SUPPORTS_CUSTOM_LINKER", "ON"}, {"CXX_SUPPORTS_CUSTOM_LINKER", "ON"},
}, minimalDeps), }, minimalDeps),
extra: []pkg.Artifact{ nonStage0: []pkg.Artifact{
musl, musl,
compilerRT, compilerRT,
runtimes, runtimes,
@@ -336,7 +314,7 @@ index 657f4230379e..12c305756184 100644
--- a/llvm/include/llvm/TargetParser/Triple.h --- a/llvm/include/llvm/TargetParser/Triple.h
+++ b/llvm/include/llvm/TargetParser/Triple.h +++ b/llvm/include/llvm/TargetParser/Triple.h
@@ -185,6 +185,7 @@ public: @@ -185,6 +185,7 @@ public:
Apple, Apple,
PC, PC,
+ Rosa, + Rosa,
@@ -362,7 +340,7 @@ index 0584c941d2e6..e4d6ef963cc7 100644
+ .Case("rosa", Triple::Rosa) + .Case("rosa", Triple::Rosa)
.Default(Triple::UnknownVendor); .Default(Triple::UnknownVendor);
} }
`}, `},
{"xfail-broken-tests", `diff --git a/clang/test/Modules/timestamps.c b/clang/test/Modules/timestamps.c {"xfail-broken-tests", `diff --git a/clang/test/Modules/timestamps.c b/clang/test/Modules/timestamps.c
@@ -374,14 +352,14 @@ index 50fdce630255..4b4465a75617 100644
+ +
/// Verify timestamps that gets embedded in the module /// Verify timestamps that gets embedded in the module
#include <c-header.h> #include <c-header.h>
`}, `},
{"path-system-include", `diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp {"path-system-include", `diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index cdbf21fb9026..dd052858700d 100644 index 8ac8d4eb9181..e46b04a898ca 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp --- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp +++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -773,6 +773,12 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs, @@ -671,6 +671,12 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
addExternCSystemInclude( addExternCSystemInclude(
DriverArgs, CC1Args, DriverArgs, CC1Args,
concat(SysRoot, "/usr/include", MultiarchIncludeDir)); concat(SysRoot, "/usr/include", MultiarchIncludeDir));
@@ -391,15 +369,15 @@ index cdbf21fb9026..dd052858700d 100644
+ DriverArgs, CC1Args, + DriverArgs, CC1Args,
+ concat(SysRoot, "/system/include", MultiarchIncludeDir)); + concat(SysRoot, "/system/include", MultiarchIncludeDir));
+ +
if (getTriple().getOS() == llvm::Triple::RTEMS) if (getTriple().getOS() == llvm::Triple::RTEMS)
return; return;
@@ -783,6 +789,7 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs, @@ -681,6 +687,7 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/include")); addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/include"));
addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/usr/include")); addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/usr/include"));
+ addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/system/include")); + addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/system/include"));
if (!DriverArgs.hasArg(options::OPT_nobuiltininc) && getTriple().isMusl()) if (!DriverArgs.hasArg(options::OPT_nobuiltininc) && getTriple().isMusl())
addSystemInclude(DriverArgs, CC1Args, ResourceDirInclude); addSystemInclude(DriverArgs, CC1Args, ResourceDirInclude);
`}, `},
@@ -413,13 +391,13 @@ index 8ac8d4eb9181..f4d1347ab64d 100644
const bool IsRISCV = Triple.isRISCV(); const bool IsRISCV = Triple.isRISCV();
const bool IsCSKY = Triple.isCSKY(); const bool IsCSKY = Triple.isCSKY();
+ const bool IsRosa = Triple.getVendor() == llvm::Triple::Rosa; + const bool IsRosa = Triple.getVendor() == llvm::Triple::Rosa;
if (IsCSKY && !SelectedMultilibs.empty()) if (IsCSKY && !SelectedMultilibs.empty())
SysRoot = SysRoot + SelectedMultilibs.back().osSuffix(); SysRoot = SysRoot + SelectedMultilibs.back().osSuffix();
@@ -318,12 +319,23 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args) @@ -318,12 +319,23 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
const std::string OSLibDir = std::string(getOSLibDir(Triple, Args)); const std::string OSLibDir = std::string(getOSLibDir(Triple, Args));
const std::string MultiarchTriple = getMultiarchTriple(D, Triple, SysRoot); const std::string MultiarchTriple = getMultiarchTriple(D, Triple, SysRoot);
+ if (IsRosa) { + if (IsRosa) {
+ ExtraOpts.push_back("-rpath"); + ExtraOpts.push_back("-rpath");
+ ExtraOpts.push_back("/system/lib"); + ExtraOpts.push_back("/system/lib");
@@ -441,11 +419,11 @@ index 8ac8d4eb9181..f4d1347ab64d 100644
+ } + }
} }
Generic_GCC::AddMultilibPaths(D, SysRoot, OSLibDir, MultiarchTriple, Paths); Generic_GCC::AddMultilibPaths(D, SysRoot, OSLibDir, MultiarchTriple, Paths);
@@ -341,18 +353,30 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args) @@ -341,18 +353,30 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
Paths); Paths);
} }
- addPathIfExists(D, concat(SysRoot, "/usr/lib", MultiarchTriple), Paths); - addPathIfExists(D, concat(SysRoot, "/usr/lib", MultiarchTriple), Paths);
- addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir), Paths); - addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir), Paths);
+ if (!IsRosa) { + if (!IsRosa) {
@@ -464,9 +442,9 @@ index 8ac8d4eb9181..f4d1347ab64d 100644
+ else + else
+ addPathIfExists(D, concat(SysRoot, "/system", OSLibDir, ABIName), Paths); + addPathIfExists(D, concat(SysRoot, "/system", OSLibDir, ABIName), Paths);
} }
Generic_GCC::AddMultiarchPaths(D, SysRoot, OSLibDir, Paths); Generic_GCC::AddMultiarchPaths(D, SysRoot, OSLibDir, Paths);
- addPathIfExists(D, concat(SysRoot, "/lib"), Paths); - addPathIfExists(D, concat(SysRoot, "/lib"), Paths);
- addPathIfExists(D, concat(SysRoot, "/usr/lib"), Paths); - addPathIfExists(D, concat(SysRoot, "/usr/lib"), Paths);
+ if (!IsRosa) { + if (!IsRosa) {
@@ -476,7 +454,7 @@ index 8ac8d4eb9181..f4d1347ab64d 100644
+ addPathIfExists(D, concat(SysRoot, "/system/lib"), Paths); + addPathIfExists(D, concat(SysRoot, "/system/lib"), Paths);
+ } + }
} }
ToolChain::RuntimeLibType Linux::GetDefaultRuntimeLibType() const { ToolChain::RuntimeLibType Linux::GetDefaultRuntimeLibType() const {
@@ -457,6 +481,9 @@ std::string Linux::getDynamicLinker(const ArgList &Args) const { @@ -457,6 +481,9 @@ std::string Linux::getDynamicLinker(const ArgList &Args) const {
return Triple.isArch64Bit() ? "/system/bin/linker64" : "/system/bin/linker"; return Triple.isArch64Bit() ? "/system/bin/linker64" : "/system/bin/linker";
@@ -487,20 +465,20 @@ index 8ac8d4eb9181..f4d1347ab64d 100644
+ +
std::string ArchName; std::string ArchName;
bool IsArm = false; bool IsArm = false;
diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp
index 64324a3f8b01..15ce70b68217 100644 index 64324a3f8b01..15ce70b68217 100644
--- a/clang/tools/clang-installapi/Options.cpp --- a/clang/tools/clang-installapi/Options.cpp
+++ b/clang/tools/clang-installapi/Options.cpp +++ b/clang/tools/clang-installapi/Options.cpp
@@ -515,7 +515,7 @@ bool Options::processFrontendOptions(InputArgList &Args) { @@ -515,7 +515,7 @@ bool Options::processFrontendOptions(InputArgList &Args) {
FEOpts.FwkPaths = std::move(FrameworkPaths); FEOpts.FwkPaths = std::move(FrameworkPaths);
// Add default framework/library paths. // Add default framework/library paths.
- PathSeq DefaultLibraryPaths = {"/usr/lib", "/usr/local/lib"}; - PathSeq DefaultLibraryPaths = {"/usr/lib", "/usr/local/lib"};
+ PathSeq DefaultLibraryPaths = {"/usr/lib", "/system/lib", "/usr/local/lib"}; + PathSeq DefaultLibraryPaths = {"/usr/lib", "/system/lib", "/usr/local/lib"};
PathSeq DefaultFrameworkPaths = {"/Library/Frameworks", PathSeq DefaultFrameworkPaths = {"/Library/Frameworks",
"/System/Library/Frameworks"}; "/System/Library/Frameworks"};
`}, `},
}, },
}) })

View File

@@ -28,135 +28,185 @@ cd "$(mktemp -d)"
} }
func init() { artifactsF[Make] = Toolchain.newMake } func init() { artifactsF[Make] = Toolchain.newMake }
// MakeAttr holds the project-specific attributes that will be applied to a new // MakeHelper is the [Make] build system helper.
// [pkg.Artifact] compiled via [Make]. type MakeHelper struct {
type MakeAttr struct {
// Mount the source tree writable.
Writable bool
// Do not include default extras. // Do not include default extras.
OmitDefaults bool OmitDefaults bool
// Dependencies not provided by stage3.
NonStage3 []pkg.Artifact
// Additional environment variables. // Command to generate the build system.
Env []string Generate string
// Runs before configure. // Runs before make.
ScriptEarly string ScriptMakeEarly string
// Runs after configure. // Runs before check.
ScriptConfigured string ScriptCheckEarly string
// Runs after install. // Runs after install.
Script string Script string
// Remain in working directory set up during ScriptEarly. // Remain in current directory.
InPlace bool InPlace bool
// Whether to skip running the configure script.
SkipConfigure bool
// Alternative name for the configure script.
ConfigureName string
// Flags passed to the configure script. // Flags passed to the configure script.
Configure [][2]string Configure [][2]string
// Extra make targets. // Host target triple, zero value is equivalent to the Rosa OS triple.
Make []string Host string
// Target triple, zero value is equivalent to the Rosa OS triple. // Target triple, zero value is equivalent to the Rosa OS triple.
Build string Build string
// Extra make targets.
Make []string
// Whether to skip the check target. // Whether to skip the check target.
SkipCheck bool SkipCheck bool
// Name of the check target, zero value is equivalent to "check". // Name of the check target, zero value is equivalent to "check".
CheckName string Check []string
// Replaces the default install command.
// Suffix appended to the source pathname. Install string
SourceSuffix string
// Passed through to [Toolchain.New].
Flag int
} }
// NewViaMake returns a [pkg.Artifact] for compiling and installing via [Make]. var _ Helper = new(MakeHelper)
func (t Toolchain) NewViaMake(
name, version string,
source pkg.Artifact,
attr *MakeAttr,
extra ...pkg.Artifact,
) pkg.Artifact {
if name == "" || version == "" {
panic("names must be non-empty")
}
if attr == nil {
attr = new(MakeAttr)
}
build := `"${ROSA_TRIPLE}"`
if attr.Build != "" {
build = attr.Build
}
var configureFlags string // name returns its arguments joined with '-'.
if len(attr.Configure) > 0 { func (*MakeHelper) name(name, version string) string {
const sep = " \\\n\t" return name + "-" + version
configureFlags += sep + strings.Join( }
slices.Collect(func(yield func(string) bool) {
for _, v := range attr.Configure { // extra returns make and other optional dependencies.
s := v[0] func (attr *MakeHelper) extra(flag int) []PArtifact {
if v[1] == "" || (v[0] != "" && extra := []PArtifact{Make}
v[0][0] >= 'a' && if (attr == nil || !attr.OmitDefaults) && flag&TEarly == 0 {
v[0][0] <= 'z') { extra = append(extra,
s = "--" + s Gawk,
} Coreutils,
if v[1] != "" {
s += "=" + v[1]
}
if !yield(s) {
return
}
}
}),
sep,
) )
} }
return extra
}
var buildFlag string // wantsChmod returns whether the build system needs to be generated.
if attr.Build != `""` { func (attr *MakeHelper) wantsChmod() bool {
buildFlag = ` \ return attr != nil && attr.Generate != ""
--build=` + build }
// wantsWrite is equivalent to wantsChmod.
func (attr *MakeHelper) wantsWrite() bool { return attr.wantsChmod() }
// scriptEarly returns the optional build system generation segment.
func (attr *MakeHelper) scriptEarly() string {
if attr == nil {
return ""
} }
makeTargets := make([]string, 1, 2+len(attr.Make)) generate := attr.Generate
if !attr.SkipCheck { if len(generate) > 0 && generate[len(generate)-1] != '\n' {
if attr.CheckName == "" { generate += "\n"
makeTargets = append(makeTargets, "check") }
} else { return generate
makeTargets = append(makeTargets, attr.CheckName) }
// createDir returns false.
func (*MakeHelper) createDir() bool { return false }
// wantsDir requests a new directory in TMPDIR, or omits the cd statement if InPlace.
func (attr *MakeHelper) wantsDir() string {
if attr != nil && attr.InPlace {
return helperInPlace
}
return `"$(mktemp -d)"`
}
// script generates the cure script.
func (attr *MakeHelper) script(name string) string {
if attr == nil {
attr = new(MakeHelper)
}
var configure string
if !attr.SkipConfigure {
configure = attr.ConfigureName
if configure == "" {
configure += `/usr/src/` + name + `/configure \
--prefix=/system`
host := `"${ROSA_TRIPLE}"`
if attr.Host != "" {
host = attr.Host
}
if attr.Host != `""` {
configure += ` \
--host=` + host
}
build := `"${ROSA_TRIPLE}"`
if attr.Build != "" {
build = attr.Build
}
if attr.Build != `""` {
configure += ` \
--build=` + build
}
}
if len(attr.Configure) > 0 {
const sep = " \\\n\t"
configure += sep + strings.Join(
slices.Collect(func(yield func(string) bool) {
for _, v := range attr.Configure {
s := v[0]
if v[0] != "" &&
v[0][0] >= 'a' &&
v[0][0] <= 'z' {
s = "--" + s
} else if len(v[0]) > 1 &&
v[0][0] == 'D' &&
v[0][1] >= 'a' &&
v[0][1] <= 'z' {
s = "-" + s
}
if v[1] != "" {
s += "=" + v[1]
}
if !yield(s) {
return
}
}
}),
sep,
)
} }
} }
makeTargets = append(makeTargets, attr.Make...)
if len(makeTargets) == 1 { scriptMake := `
makeTargets = nil make \
"-j$(nproc)"`
if len(attr.Make) > 0 {
scriptMake += " \\\n\t" + strings.Join(attr.Make, " \\\n\t")
}
scriptMake += "\n"
if !attr.SkipCheck {
scriptMake += attr.ScriptCheckEarly + `make \
"-j$(nproc)" \
`
if len(attr.Check) > 0 {
scriptMake += strings.Join(attr.Check, " \\\n\t")
} else {
scriptMake += "check"
}
scriptMake += "\n"
} }
finalExtra := []pkg.Artifact{ scriptInstall := attr.Install
t.Load(Make), if scriptInstall == "" {
scriptInstall = "make DESTDIR=/work install"
} }
if attr.OmitDefaults || attr.Flag&TEarly == 0 { scriptInstall += "\n"
finalExtra = append(finalExtra,
t.Load(Gawk),
t.Load(Coreutils),
)
}
finalExtra = append(finalExtra, extra...)
scriptEarly := attr.ScriptEarly return configure +
if !attr.InPlace { attr.ScriptMakeEarly +
scriptEarly += "\ncd \"$(mktemp -d)\"" scriptMake +
} else if scriptEarly == "" { scriptInstall +
panic("cannot remain in root") attr.Script
}
return t.New(name+"-"+version, attr.Flag, stage3Concat(t,
attr.NonStage3,
finalExtra...,
), nil, attr.Env, scriptEarly+`
/usr/src/`+name+`/configure \
--prefix=/system`+buildFlag+configureFlags+attr.ScriptConfigured+`
make "-j$(nproc)"`+strings.Join(makeTargets, " ")+`
make DESTDIR=/work install
`+attr.Script, pkg.Path(AbsUsrSrc.Append(
name+attr.SourceSuffix,
), attr.Writable, source))
} }

View File

@@ -1,6 +1,11 @@
package rosa package rosa
import "hakurei.app/internal/pkg" import (
"slices"
"strings"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newMeson() pkg.Artifact { func (t Toolchain) newMeson() pkg.Artifact {
const ( const (
@@ -25,3 +30,97 @@ python3 setup.py \
))) )))
} }
func init() { artifactsF[Meson] = Toolchain.newMeson } func init() { artifactsF[Meson] = Toolchain.newMeson }
// MesonHelper is the [Meson] build system helper.
type MesonHelper struct {
// Runs after setup.
ScriptCompileEarly string
// Runs after compile.
ScriptCompiled string
// Runs after install.
Script string
// Flags passed to the setup command.
Setup [][2]string
// Whether to skip meson test.
SkipTest bool
}
var _ Helper = new(MesonHelper)
// name returns its arguments joined with '-'.
func (*MesonHelper) name(name, version string) string {
return name + "-" + version
}
// extra returns hardcoded meson runtime dependencies.
func (*MesonHelper) extra(int) []PArtifact {
return []PArtifact{
Python,
Meson,
Ninja,
PkgConfig,
CMake,
}
}
// wantsChmod returns false.
func (*MesonHelper) wantsChmod() bool { return false }
// wantsWrite returns false.
func (*MesonHelper) wantsWrite() bool { return false }
// scriptEarly returns the zero value.
func (*MesonHelper) scriptEarly() string { return "" }
// createDir returns false.
func (*MesonHelper) createDir() bool { return false }
// wantsDir requests a new directory in TMPDIR.
func (*MesonHelper) wantsDir() string { return `"$(mktemp -d)"` }
// script generates the cure script.
func (attr *MesonHelper) script(name string) string {
if attr == nil {
attr = new(MesonHelper)
}
scriptCompiled := attr.ScriptCompiled
if len(scriptCompiled) > 0 && scriptCompiled[0] != '\n' {
scriptCompiled = "\n" + scriptCompiled
}
var scriptTest string
if !attr.SkipTest {
scriptTest = `
meson test \
--print-errorlogs`
}
return `
cd "$(mktemp -d)"
meson setup \
` + strings.Join(slices.Collect(func(yield func(string) bool) {
for _, v := range append([][2]string{
{"prefix", "/system"},
{"buildtype", "release"},
}, attr.Setup...) {
s := "-" + v[0]
if len(v[0]) > 0 && v[0][0] != 'D' {
s = "-" + s
}
if v[1] != "" {
s += "=" + v[1]
}
if !yield(s) {
return
}
}
}), " \\\n\t") + ` \
. '/usr/src/` + name + `'
meson compile` + scriptCompiled + scriptTest + `
meson install \
--destdir=/work
` + attr.Script
}

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