4 Commits

Author SHA1 Message Date
mae
15dadee24a cmd/irdump: formatted disassembly 2026-02-08 03:09:20 -06:00
mae
58431161b5 cmd/irdump: basic disassembler 2026-02-08 03:09:19 -06:00
mae
c60762fe85 cmd/irdump: create cli 2026-02-07 20:34:23 -06:00
6ee3ed1711 internal/rosa/go: alternative bootstrap path
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m44s
Test / ShareFS (push) Successful in 4m13s
Test / Hpkg (push) Successful in 4m47s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hakurei (race detector) (push) Successful in 3m29s
Test / Hakurei (push) Successful in 2m34s
Test / Flake checks (push) Successful in 1m40s
For targets where the bootstrap toolchain is not available.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-08 01:45:21 +09:00
144 changed files with 3760 additions and 34652 deletions

View File

@@ -89,6 +89,23 @@ 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:
@@ -97,6 +114,7 @@ jobs:
- sandbox - sandbox
- sandbox-race - sandbox-race
- sharefs - sharefs
- hpkg
runs-on: nix runs-on: nix
steps: steps:
- name: Checkout - name: Checkout

5
.github/workflows/README vendored Normal file
View File

@@ -0,0 +1,5 @@
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.

46
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
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-**

48
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
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,7 +28,6 @@ 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,51 +15,164 @@
<a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a> <a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
</p> </p>
Hakurei is a tool for running sandboxed desktop applications as dedicated Hakurei is a tool for running sandboxed graphical applications as dedicated subordinate users on the Linux kernel.
subordinate users on the Linux kernel. It implements the application container It implements the application container of [planterette (WIP)](https://git.gensokyo.uk/security/planterette),
of [planterette (WIP)](https://git.gensokyo.uk/security/planterette), a a self-contained Android-like package manager with modern security features.
self-contained Android-like package manager with modern security features.
Interaction with hakurei happens entirely through structures described by ## NixOS Module usage
package [hst](https://pkg.go.dev/hakurei.app/hst). No native API is available
due to internal details of uid isolation.
## Notable Packages The NixOS module currently requires home-manager to configure subordinate users. Full module documentation can be found [here](options.md).
Package [container](https://pkg.go.dev/hakurei.app/container) is general purpose To use the module, import it into your configuration with
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).
Package [internal/pkg](https://pkg.go.dev/hakurei.app/internal/pkg) provides ```nix
infrastructure for hermetic builds. This replaces the legacy nix-based testing {
framework and serves as the build system of Rosa OS, currently developed under inputs = {
package [internal/rosa](https://pkg.go.dev/hakurei.app/internal/rosa). nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
## Dependencies hakurei = {
url = "git+https://git.gensokyo.uk/security/hakurei";
`container` depends on: # Optional but recommended to limit the size of your system closure.
inputs.nixpkgs.follows = "nixpkgs";
};
};
- [libseccomp](https://github.com/seccomp/libseccomp) to generate BPF programs. outputs = { self, nixpkgs, hakurei, ... }:
{
nixosConfigurations.hakurei = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
hakurei.nixosModules.hakurei
];
};
};
}
```
`cmd/hakurei` depends on: This adds the `environment.hakurei` option:
- [acl](https://savannah.nongnu.org/projects/acl/) to export sockets to ```nix
subordinate users. { pkgs, ... }:
- [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;
};
- [fuse](https://github.com/libfuse/libfuse) to implement the filesystem. commonPaths = [
{
src = "/sdcard";
write = true;
}
];
New dependencies will generally not be added. Patches adding new dependencies extraHomeConfig = {
are very likely to be rejected. home.stateVersion = "23.05";
};
## NixOS Module (deprecated) apps = {
"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 = { };
};
};
};
The NixOS module is in maintenance mode and will be removed once planterette is "org.claws_mail.Claws-Mail" = {
feature-complete. Full module documentation can be found [here](options.md). name = "claws-mail";
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;
};
};
};
};
}
```

View File

@@ -1,58 +0,0 @@
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)
}
}
}
}
}

7
cmd/hpkg/README Normal file
View File

@@ -0,0 +1,7 @@
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.

173
cmd/hpkg/app.go Normal file
View File

@@ -0,0 +1,173 @@
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
}
}

256
cmd/hpkg/build.nix Normal file
View File

@@ -0,0 +1,256 @@
{
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"
'';
}

335
cmd/hpkg/main.go Normal file
View File

@@ -0,0 +1,335 @@
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")
}

117
cmd/hpkg/paths.go Normal file
View File

@@ -0,0 +1,117 @@
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}},
}...)
}

61
cmd/hpkg/proc.go Normal file
View File

@@ -0,0 +1,61 @@
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

@@ -0,0 +1,62 @@
{ 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";
};
};
}

34
cmd/hpkg/test/default.nix Normal file
View File

@@ -0,0 +1,34 @@
{
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;
}

48
cmd/hpkg/test/foot.nix Normal file
View File

@@ -0,0 +1,48 @@
{
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 "$@"
'';
}

110
cmd/hpkg/test/test.py Normal file
View File

@@ -0,0 +1,110 @@
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"))

130
cmd/hpkg/with.go Normal file
View File

@@ -0,0 +1,130 @@
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)
}

76
cmd/irdump/main.go Normal file
View File

@@ -0,0 +1,76 @@
package main
import (
"errors"
"log"
"os"
"hakurei.app/command"
"hakurei.app/internal/pkg"
)
func main() {
log.SetFlags(0)
log.SetPrefix("irdump: ")
var (
flagOutput string
flagReal bool
flagHeader bool
flagForce bool
flagRaw bool
)
c := command.New(os.Stderr, log.Printf, "irdump", func(args []string) (err error) {
var input *os.File
if len(args) != 1 {
return errors.New("irdump requires 1 argument")
}
if input, err = os.Open(args[0]); err != nil {
return
}
defer input.Close()
var output *os.File
if flagOutput == "" {
output = os.Stdout
} else {
defer output.Close()
if output, err = os.Create(flagOutput); err != nil {
return
}
}
var out string
if out, err = pkg.Disassemble(input, flagReal, flagHeader, flagForce, flagRaw); err != nil {
return
}
if _, err = output.WriteString(out); err != nil {
return
}
return
}).Flag(
&flagOutput,
"o", command.StringFlag(""),
"Output file for asm (leave empty for stdout)",
).Flag(
&flagReal,
"r", command.BoolFlag(false),
"skip label generation; idents print real value",
).Flag(
&flagHeader,
"H", command.BoolFlag(false),
"display artifact headers",
).Flag(
&flagForce,
"f", command.BoolFlag(false),
"force display (skip validations)",
).Flag(
&flagRaw,
"R", command.BoolFlag(false),
"don't format output",
)
c.MustParse(os.Args[1:], func(err error) {
log.Fatal(err)
})
}

View File

@@ -4,26 +4,17 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"log" "log"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"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"
@@ -60,16 +51,10 @@ 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
@@ -85,11 +70,6 @@ func main() {
cache.SetThreshold(1 << flagTShift) cache.SetThreshold(1 << flagTShift)
} }
} }
if flagIdle {
pkg.SchedPolicy = container.SCHED_IDLE
}
return return
}).Flag( }).Flag(
&flagQuiet, &flagQuiet,
@@ -101,16 +81,12 @@ 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("$MBF_CACHE_DIR"), "d", command.StringFlag("cache"),
"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",
) )
{ {
@@ -133,234 +109,13 @@ func main() {
) )
} }
{
var (
flagStatus bool
flagReport string
)
c.NewCommand(
"info",
"Display out-of-band metadata of an artifact",
func(args []string) (err error) {
if len(args) == 0 {
return errors.New("info requires at least 1 argument")
}
var r *rosa.Report
if flagReport != "" {
if r, err = rosa.OpenReport(flagReport); err != nil {
return err
}
defer func() {
if closeErr := r.Close(); err == nil {
err = closeErr
}
}()
defer r.HandleAccess(&err)()
}
for i, name := range args {
if p, ok := rosa.ResolveName(name); !ok {
return fmt.Errorf("unknown artifact %q", name)
} else {
var suffix string
if version := rosa.Std.Version(p); version != rosa.Unversioned {
suffix += "-" + version
}
fmt.Println("name : " + name + suffix)
meta := rosa.GetMetadata(p)
fmt.Println("description : " + meta.Description)
if meta.Website != "" {
fmt.Println("website : " +
strings.TrimSuffix(meta.Website, "/"))
}
const statusPrefix = "status : "
if flagStatus {
if r == nil {
var f io.ReadSeekCloser
f, err = cache.OpenStatus(rosa.Std.Load(p))
if err != nil {
if errors.Is(err, os.ErrNotExist) {
fmt.Println(
statusPrefix + "not yet cured",
)
} else {
return
}
} else {
fmt.Print(statusPrefix)
_, err = io.Copy(os.Stdout, f)
if err = errors.Join(err, f.Close()); err != nil {
return
}
}
} else {
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
if status == nil {
fmt.Println(
statusPrefix + "not in report",
)
} else {
fmt.Println("size :", n)
fmt.Print(statusPrefix)
if _, err = os.Stdout.Write(status); err != nil {
return
}
}
}
}
if i != len(args)-1 {
fmt.Println()
}
}
}
return nil
},
).
Flag(
&flagStatus,
"status", command.BoolFlag(false),
"Display cure status if available",
).
Flag(
&flagReport,
"report", command.StringFlag(""),
"Load cure status from this report file instead of cache",
)
}
c.NewCommand(
"report",
"Generate an artifact cure report for the current cache",
func(args []string) (err error) {
var w *os.File
switch len(args) {
case 0:
w = os.Stdout
case 1:
if w, err = os.OpenFile(
args[0],
os.O_CREATE|os.O_EXCL|syscall.O_WRONLY,
0400,
); err != nil {
return
}
defer func() {
closeErr := w.Close()
if err == nil {
err = closeErr
}
}()
default:
return errors.New("report requires 1 argument")
}
if container.Isatty(int(w.Fd())) {
return errors.New("output appears to be a terminal")
}
return rosa.WriteReport(msg, w, cache)
},
)
{
var flagJobs int
c.NewCommand("updates", command.UsageInternal, func([]string) error {
var (
errsMu sync.Mutex
errs []error
n atomic.Uint64
)
w := make(chan rosa.PArtifact)
var wg sync.WaitGroup
for range max(flagJobs, 1) {
wg.Go(func() {
for p := range w {
meta := rosa.GetMetadata(p)
if meta.ID == 0 {
continue
}
v, err := meta.GetVersions(ctx)
if err != nil {
errsMu.Lock()
errs = append(errs, err)
errsMu.Unlock()
continue
}
if current, latest :=
rosa.Std.Version(p),
meta.GetLatest(v); current != latest {
n.Add(1)
log.Printf("%s %s < %s", meta.Name, current, latest)
continue
}
msg.Verbosef("%s is up to date", meta.Name)
}
})
}
done:
for i := range rosa.PresetEnd {
select {
case w <- rosa.PArtifact(i):
break
case <-ctx.Done():
break done
}
}
close(w)
wg.Wait()
if v := n.Load(); v > 0 {
errs = append(errs, errors.New(strconv.Itoa(int(v))+
" package(s) are out of date"))
}
return errors.Join(errs...)
}).
Flag(
&flagJobs,
"j", command.IntFlag(32),
"Maximum number of simultaneous connections",
)
}
{
var (
flagGentoo string
flagChecksum string
flagStage0 bool
)
c.NewCommand( c.NewCommand(
"stage3", "stage3",
"Check for toolchain 3-stage non-determinism", "Check for toolchain 3-stage non-determinism",
func(args []string) (err error) { func(args []string) (err error) {
t := rosa.Std _, _, _, stage1 := (rosa.Std - 2).NewLLVM()
if flagGentoo != "" { _, _, _, stage2 := (rosa.Std - 1).NewLLVM()
t -= 3 // magic number to discourage misuse _, _, _, stage3 := rosa.Std.NewLLVM()
var checksum pkg.Checksum
if len(flagChecksum) != 0 {
if err = pkg.Decode(&checksum, flagChecksum); err != nil {
return
}
}
rosa.SetGentooStage3(flagGentoo, checksum)
}
_, _, _, stage1 := (t - 2).NewLLVM()
_, _, _, stage2 := (t - 1).NewLLVM()
_, _, _, stage3 := t.NewLLVM()
var ( var (
pathname *check.Absolute pathname *check.Absolute
checksum [2]unique.Handle[pkg.Checksum] checksum [2]unique.Handle[pkg.Checksum]
@@ -391,35 +146,9 @@ func main() {
"("+pkg.Encode(checksum[0].Value())+")", "("+pkg.Encode(checksum[0].Value())+")",
) )
} }
if flagStage0 {
if pathname, _, err = cache.Cure(
t.Load(rosa.Stage0),
); err != nil {
return err
}
log.Println(pathname)
}
return 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 (
@@ -433,7 +162,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("unknown artifact %q", args[0]) return fmt.Errorf("unsupported 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 {
@@ -466,157 +195,10 @@ 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()
} }
if w, ok := err.(interface{ Unwrap() []error }); !ok {
log.Fatal(err) log.Fatal(err)
} else {
errs := w.Unwrap()
for i, e := range errs {
if i == len(errs)-1 {
log.Fatal(e)
}
log.Println(e)
}
}
}) })
} }

View File

@@ -33,7 +33,6 @@ 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"
@@ -442,7 +441,12 @@ 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)
z.Path = fhs.AbsProcSelfExe if a, err := check.NewAbs(container.MustExecutable(msg)); err != nil {
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,7 +10,8 @@ import (
func init() { gob.Register(new(AutoEtcOp)) } func init() { gob.Register(new(AutoEtcOp)) }
// Etc is a helper for appending [AutoEtcOp] to [Ops]. // Etc appends an [Op] that 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.
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)
@@ -19,9 +20,6 @@ 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,15 +11,13 @@ import (
func init() { gob.Register(new(AutoRootOp)) } func init() { gob.Register(new(AutoRootOp)) }
// Root is a helper for appending [AutoRootOp] to [Ops]. // Root appends an [Op] that 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.
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,16 +50,10 @@ func capset(hdrp *capHeader, datap *[2]capData) error {
} }
// capBoundingSetDrop drops a capability from the calling thread's capability bounding set. // capBoundingSetDrop drops a capability from the calling thread's capability bounding set.
func capBoundingSetDrop(cap uintptr) error { func capBoundingSetDrop(cap uintptr) error { return Prctl(syscall.PR_CAPBSET_DROP, cap, 0) }
return Prctl(syscall.PR_CAPBSET_DROP, cap, 0)
}
// capAmbientClearAll clears the ambient capability set of the calling thread. // capAmbientClearAll clears the ambient capability set of the calling thread.
func capAmbientClearAll() error { func capAmbientClearAll() error { return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0) }
return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0)
}
// capAmbientRaise adds to the ambient capability set of the calling thread. // capAmbientRaise adds to the ambient capability set of the calling thread.
func capAmbientRaise(cap uintptr) error { func capAmbientRaise(cap uintptr) error { return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap) }
return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap)
}

View File

@@ -11,8 +11,7 @@ const (
SpecialOverlayPath = ":" SpecialOverlayPath = ":"
) )
// EscapeOverlayDataSegment escapes a string for formatting into the data // EscapeOverlayDataSegment escapes a string for formatting into the data argument of an overlay mount call.
// 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,5 +1,4 @@
// Package container implements unprivileged Linux containers with built-in // Package container implements unprivileged Linux containers with built-in support for syscall filtering.
// support for syscall filtering.
package container package container
import ( import (
@@ -38,30 +37,24 @@ 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, with // ExtraFiles passed through to initial process in the container,
// behaviour identical to its [exec.Cmd] counterpart. // with behaviour identical to its [exec.Cmd] counterpart.
ExtraFiles []*os.File ExtraFiles []*os.File
// Write end of a pipe connected to the init to deliver [Params]. // param pipe for shim and init
setup *os.File setup *os.File
// Cancels the context passed to the underlying cmd. // cancels cmd
cancel context.CancelFunc cancel context.CancelFunc
// Closed after Wait returns. Keeps the spawning thread alive. // closed after Wait returns
wait chan struct{} wait chan struct{}
Stdin io.Reader Stdin io.Reader
Stdout io.Writer Stdout io.Writer
Stderr io.Writer Stderr io.Writer
// Custom cancellation behaviour for the underlying [exec.Cmd]. Must
// deliver [CancelSignal] before returning.
Cancel func(cmd *exec.Cmd) error Cancel func(cmd *exec.Cmd) error
// Copied to the underlying [exec.Cmd].
WaitDelay time.Duration WaitDelay time.Duration
cmd *exec.Cmd cmd *exec.Cmd
@@ -290,11 +283,7 @@ 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{ return &StartError{true, "set up params stream", err, false, false}
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)}
@@ -306,16 +295,10 @@ func (p *Container) Start() error {
runtime.LockOSThread() runtime.LockOSThread()
p.wait = make(chan struct{}) p.wait = make(chan struct{})
// setup depending on per-thread state must happen here done <- func() error { // setup depending on per-thread state must happen here
done <- func() error { // PR_SET_NO_NEW_PRIVS: depends on per-thread state but acts on all processes created from that thread
// 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{ return &StartError{true, "prctl(PR_SET_NO_NEW_PRIVS)", err, false, false}
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
@@ -327,40 +310,28 @@ 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 // landlock can be skipped here as it restricts access to resources
// to resources already covered by namespaces (pid) // already covered by namespaces (pid)
goto landlockOut goto landlockOut
} }
return &StartError{Step: "get landlock ABI", Err: err} return &StartError{false, "get landlock ABI", err, false, false}
} 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{ return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false}
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{ return &StartError{true, "create landlock ruleset", err, false, false}
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{ return &StartError{true, "enforce landlock ruleset", err, false, false}
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)
@@ -371,30 +342,9 @@ 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{ return &StartError{false, "start container init", err, false, true}
Step: "start container init",
Err: err,
Passthrough: true,
}
} }
return nil return nil
}() }()
@@ -406,7 +356,6 @@ 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 {
@@ -416,21 +365,12 @@ 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{ return &StartError{true, "set init pipe deadline", err, false, true}
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{ return &StartError{false, "invalid executable pathname", EINVAL, true, false}
Step: "invalid executable pathname",
Err: EINVAL,
Origin: true,
}
} }
// do not transmit nil // do not transmit nil
@@ -455,8 +395,7 @@ func (p *Container) Serve() error {
return err return err
} }
// Wait blocks until the container init process to exit and releases any // Wait waits for the container init process to exit and releases any resources associated with the [Container].
// 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
@@ -501,13 +440,11 @@ func (p *Container) StderrPipe() (r io.ReadCloser, err error) {
} }
func (p *Container) String() string { func (p *Container) String() string {
return fmt.Sprintf( return fmt.Sprintf("argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x",
"argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x", p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets))
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets),
)
} }
// ProcessState returns the address of os.ProcessState held by the underlying [exec.Cmd]. // ProcessState returns the address to 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
@@ -515,8 +452,7 @@ func (p *Container) ProcessState() *os.ProcessState {
return p.cmd.ProcessState return p.cmd.ProcessState
} }
// New returns the address to a new instance of [Container]. This value requires // New returns the address to a new instance of [Container] that requires further initialisation before use.
// 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)
@@ -525,18 +461,12 @@ 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, fhs.ProcSelfExe) p.cmd = exec.CommandContext(c, MustExecutable(msg))
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( func NewCommand(ctx context.Context, msg message.Msg, pathname *check.Absolute, name string, args ...string) *Container {
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

@@ -274,13 +274,13 @@ var containerTestCases = []struct {
Dev(check.MustAbs("/dev"), true), Dev(check.MustAbs("/dev"), true),
), ),
earlyMnt( earlyMnt(
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", ignore, ignore), ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
ent("/null", "/dev/null", ignore, "devtmpfs", ignore, ignore), ent("/null", "/dev/null", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/zero", "/dev/zero", ignore, "devtmpfs", ignore, ignore), ent("/zero", "/dev/zero", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/full", "/dev/full", ignore, "devtmpfs", ignore, ignore), ent("/full", "/dev/full", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/random", "/dev/random", ignore, "devtmpfs", ignore, ignore), ent("/random", "/dev/random", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/urandom", "/dev/urandom", ignore, "devtmpfs", ignore, ignore), ent("/urandom", "/dev/urandom", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/tty", "/dev/tty", ignore, "devtmpfs", ignore, 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/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/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore), ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
@@ -292,13 +292,13 @@ var containerTestCases = []struct {
Dev(check.MustAbs("/dev"), false), Dev(check.MustAbs("/dev"), false),
), ),
earlyMnt( earlyMnt(
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", ignore, ignore), ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
ent("/null", "/dev/null", ignore, "devtmpfs", ignore, ignore), ent("/null", "/dev/null", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/zero", "/dev/zero", ignore, "devtmpfs", ignore, ignore), ent("/zero", "/dev/zero", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/full", "/dev/full", ignore, "devtmpfs", ignore, ignore), ent("/full", "/dev/full", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/random", "/dev/random", ignore, "devtmpfs", ignore, ignore), ent("/random", "/dev/random", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/urandom", "/dev/urandom", ignore, "devtmpfs", ignore, ignore), ent("/urandom", "/dev/urandom", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/tty", "/dev/tty", ignore, "devtmpfs", ignore, 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/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore), ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
), ),
@@ -690,22 +690,14 @@ func init() {
return fmt.Errorf("got more than %d entries", len(mnt)) return fmt.Errorf("got more than %d entries", len(mnt))
} }
// ugly hack but should be reliable and is less likely to // ugly hack but should be reliable and is less likely to false negative than comparing by parsed flags
//false negative than comparing by parsed flags cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",relatime")
for _, s := range []string{ cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",noatime")
"relatime", mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",relatime")
"noatime", mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",noatime")
} {
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ","+s) cur.FsOptstr = strings.Replace(cur.FsOptstr, ",seclabel", "", 1)
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ","+s) mnt[i].FsOptstr = strings.Replace(mnt[i].FsOptstr, ",seclabel", "", 1)
}
for _, s := range []string{
"seclabel",
"inode64",
} {
cur.FsOptstr = strings.Replace(cur.FsOptstr, ","+s, "", 1)
mnt[i].FsOptstr = strings.Replace(mnt[i].FsOptstr, ","+s, "", 1)
}
if !cur.EqualWithIgnore(mnt[i], "\x00") { if !cur.EqualWithIgnore(mnt[i], "\x00") {
fail = true fail = true
@@ -773,13 +765,14 @@ 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(fhs.AbsProcSelfExe, absHelperInnerPath, 0) c.Bind(executable, absHelperInnerPath, 0)
// in case test has cgo enabled // in case test has cgo enabled
if entries, err := ldd.Resolve(ctx, msg, nil); err != nil { if entries, err := ldd.Resolve(ctx, msg, executable); 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,8 +21,7 @@ type osFile interface {
fs.File fs.File
} }
// syscallDispatcher provides methods that make state-dependent system calls as // syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
// 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,11 +238,8 @@ 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 // check happens in Close, and cleanup is not guaranteed to run, so relying on it for sloppy implementations will cause sporadic test results
// 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)
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,8 +43,7 @@ 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 // While this is usable for pointer errors, such use should be avoided as nil check is omitted.
// 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,9 +28,6 @@ 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,8 +42,6 @@ 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,8 +9,7 @@ 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 // Run points to a "tmpfs" file system for system packages to place runtime data, socket files, and similar.
// 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.
@@ -18,12 +17,10 @@ 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 // UsrBin points to binaries and executables for user commands that shall appear in the $PATH search path.
// appear in the $PATH search path.
UsrBin = Usr + "bin/" UsrBin = Usr + "bin/"
// Var points to persistent, variable system data. Writable during normal // Var points to persistent, variable system data. Writable during normal system operation.
// system operation.
Var = "/var/" Var = "/var/"
// VarLib points to persistent system data. // VarLib points to persistent system data.
VarLib = Var + "lib/" VarLib = Var + "lib/"
@@ -32,20 +29,12 @@ 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 // DevShm is the place for POSIX shared memory segments, as created via shm_open(3).
// shm_open(3).
DevShm = "/dev/shm/" DevShm = "/dev/shm/"
// Proc points to a virtual kernel file system exposing the process list and // Proc points to a virtual kernel file system exposing the process list and other functionality.
// other functionality.
Proc = "/proc/" Proc = "/proc/"
// ProcSys points to a hierarchy below /proc/ that exposes a number of // ProcSys points to a hierarchy below /proc/ that exposes a number of kernel tunables.
// kernel tunables.
ProcSys = Proc + "sys/" ProcSys = Proc + "sys/"
// ProcSelf resolves to the process's own /proc/pid directory. // Sys points to a virtual kernel file system exposing discovered devices and other functionality.
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 It should be noted that none of this should become relevant at any point since the resulting
since the resulting intermediate root tmpfs should be effectively anonymous. */ 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 // setupEnv is the name of the environment variable holding the string representation of
// representation of the read end file descriptor of the setup params pipe. // 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,8 +59,7 @@ 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 // prefix returns a log message prefix, and whether this Op prints no identifying message on its own.
// identifying message on its own.
prefix() (string, bool) prefix() (string, bool)
Is(op Op) bool Is(op Op) bool
@@ -72,11 +71,9 @@ type (
setupState struct { setupState struct {
nonrepeatable uintptr nonrepeatable uintptr
// Whether early reaping has concluded. Must only be accessed in the // Whether early reaping has concluded. Must only be accessed in the wait4 loop.
// wait4 loop.
processConcluded bool processConcluded bool
// Process to syscall.WaitStatus populated in the wait4 loop. Freed // Process to syscall.WaitStatus populated in the wait4 loop. Freed after early reaping concludes.
// 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
@@ -219,10 +216,9 @@ 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 this step is mostly for gathering information that would otherwise be difficult to obtain
difficult to obtain via library functions after pivot_root, and via library functions after pivot_root, and implementations are expected to avoid changing
implementations are expected to avoid changing the state of the mount the state of the mount namespace */
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)
@@ -262,10 +258,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. This /* apply is called right after pivot_root and entering the new root;
step sets up the container filesystem, and implementations are expected to this step sets up the container filesystem, and implementations are expected to keep the host root
keep the host root and sysroot mount points intact but otherwise can do and sysroot mount points intact but otherwise can do whatever they need to;
whatever they need to. Calling chdir is allowed but discouraged. */ 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,16 +12,14 @@ import (
func init() { gob.Register(new(BindMountOp)) } func init() { gob.Register(new(BindMountOp)) }
// Bind is a helper for appending [BindMountOp] to [Ops]. // Bind appends an [Op] that bind mounts host path [BindMountOp.Source] on container path [BindMountOp.Target].
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 creates a bind mount from host path Source to container path Target. // BindMountOp bind mounts host path Source on 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,7 +24,8 @@ const (
daemonTimeout = 5 * time.Second daemonTimeout = 5 * time.Second
) )
// Daemon is a helper for appending [DaemonOp] to [Ops]. // Daemon appends an [Op] that starts a daemon in the container and blocks until
// [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,9 +19,7 @@ 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 is a helper for appending [MkdirOp] to [Ops]. // Mkdir appends an [Op] that creates a directory in the container filesystem.
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,11 +54,8 @@ func (e *OverlayArgumentError) Error() string {
} }
} }
// Overlay is a helper for appending [MountOverlayOp] to [Ops]. // Overlay appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target].
func (f *Ops) Overlay( func (f *Ops) Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) *Ops {
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,
@@ -68,12 +65,13 @@ func (f *Ops) Overlay(
return f return f
} }
// OverlayEphemeral appends a [MountOverlayOp] with an ephemeral upperdir and workdir. // OverlayEphemeral appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target]
// 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 a readonly [MountOverlayOp]. // OverlayReadonly appends an [Op] that mounts the overlay pseudo filesystem readonly on [MountOverlayOp.Target]
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...)
} }
@@ -84,34 +82,25 @@ 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]. // formatted for [OptionOverlayLowerdir], resolved, prefixed and escaped during early
//
// 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], an // If Work is nil and Upper holds the special value [fhs.AbsRoot],
// ephemeral upperdir and workdir will be set up. // an ephemeral upperdir and workdir will be set up.
// //
// If both Work and Upper are nil, upperdir and workdir is omitted and the // If both Work and Upper are nil, upperdir and workdir is omitted and the overlay is mounted readonly.
// overlay is mounted readonly.
Upper *check.Absolute Upper *check.Absolute
// Formatted for [OptionOverlayUpperdir]. // formatted for [OptionOverlayUpperdir], resolved, prefixed and escaped during early
//
// 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]. // formatted for [OptionOverlayWorkdir], resolved, prefixed and escaped during early
//
// 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 is a helper for appending [TmpfileOp] to [Ops]. // Place appends an [Op] that places a file in container path [TmpfileOp.Path] containing [TmpfileOp.Data].
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.*"}, (*checkedOsFile)(nil), stub.UniqueError(5)), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, 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.Close", sampleDataString, stub.UniqueError(3)), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", 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.ensureFile", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", 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.bindMount", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", 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.bindMount", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, stub.UniqueError(1)), call("bindMount", stub.ExpectArgs{"tmp.32768", "/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.remove", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", 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.remove", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil), call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
call("remove", stub.ExpectArgs{"tmp.remove"}, nil, stub.UniqueError(0)), call("remove", stub.ExpectArgs{"tmp.32768"}, 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.success", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", 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.success", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil), call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
call("remove", stub.ExpectArgs{"tmp.success"}, nil, nil), call("remove", stub.ExpectArgs{"tmp.32768"}, nil, nil),
}, nil}, }, nil},
}) })

View File

@@ -10,7 +10,7 @@ import (
func init() { gob.Register(new(MountProcOp)) } func init() { gob.Register(new(MountProcOp)) }
// Proc is a helper for appending [MountProcOp] to [Ops]. // Proc appends an [Op] that mounts a private instance of proc.
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 is a helper for appending [RemountOp] to [Ops]. // Remount appends an [Op] that applies [RemountOp.Flags] on container path [RemountOp.Target].
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,7 +38,6 @@ 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:
@@ -117,7 +116,6 @@ 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:
@@ -154,7 +152,6 @@ 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:
@@ -187,12 +184,10 @@ 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 // Bitmask of scopes restricting a Landlock domain from accessing outside resources (e.g. IPCs).
// 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"
@@ -213,7 +208,6 @@ 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
@@ -222,13 +216,10 @@ func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
size = unsafe.Sizeof(*rulesetAttr) size = unsafe.Sizeof(*rulesetAttr)
} }
rulesetFd, _, errno := syscall.Syscall( rulesetFd, _, errno := syscall.Syscall(std.SYS_LANDLOCK_CREATE_RULESET, pointer, size, flags)
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
} }
@@ -239,19 +230,12 @@ 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( r, _, errno := syscall.Syscall(std.SYS_LANDLOCK_RESTRICT_SELF, uintptr(rulesetFd), flags, 0)
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.Int(binary.NativeEndian.Uint32(m.Data))) errno := Errno(-std.ScmpInt(binary.NativeEndian.Uint32(m.Data)))
if errno == 0 { if errno == 0 {
return nil return nil
} }

View File

@@ -15,10 +15,7 @@ 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,22 +88,18 @@ var resPrefix = [...]string{
7: "seccomp_load failed", 7: "seccomp_load failed",
} }
// cbAllocateBuffer is the function signature for the function handle passed to // cbAllocateBuffer is the function signature for the function handle passed to hakurei_export_filter
// hakurei_scmp_make_filter which allocates the buffer that the resulting bpf // which allocates the buffer that the resulting bpf program is copied into, and writes its slice header
// program is copied into, and writes its slice header to a value held by the caller. // 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 // makeFilter generates a bpf program from a slice of [std.NativeRule] and writes the resulting byte slice to p.
// writes the resulting byte slice to p. The filter is installed to the current // The filter is installed to the current process if p is nil.
// 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
@@ -174,8 +170,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 // Load generates a bpf program from a slice of [std.NativeRule] and enforces it on the current process.
// on the current process. Errors returned by libseccomp is wrapped in [LibraryError]. // 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

@@ -1,27 +0,0 @@
package seccomp_test
import (
. "hakurei.app/container/seccomp"
. "hakurei.app/container/std"
)
var bpfExpected = bpfLookup{
{AllowMultiarch | AllowCAN |
AllowBluetooth, PresetExt |
PresetDenyNS | PresetDenyTTY | PresetDenyDevel |
PresetLinux32}: toHash(
"a1c4ffa35f4bfbf38061184760b9a09edfcb4964c3b534395e47327b83f3fb61f2f9573ddfcc4772424cc2f5dd12fd32471e6531dbe10e85eda3797dd4fa179f"),
{0, 0}: toHash(
"f3910fd727d087def593e3876c2c6ab9ace71d82ec8cbc992a26223e7bba85e1d7a0b56c5fc6303703f24595825dad8561637edaedd5384b34a6cd080946633c"),
{0, PresetExt}: toHash(
"741438c5e3f11c36c92ae8c5934f13440675c6e719541c2dbffeda79a10081bcfd9ad8314a60c1d1f53db86c8080c13fffa3bbcf7fe753935679b4b902737286"),
{0, PresetStrict}: toHash(
"79e9e464d02405c6d74fd2c771bd72a1311e488221c73a9c32db9270219837c54fccec2f36fe2474895547e60c311514567e2e6cf4e7a7fcf909c1ecd1e254a7"),
{0, PresetDenyNS | PresetDenyTTY | PresetDenyDevel}: toHash(
"3c443715a6c1e557a284862ea8efb70a5d4ecbe67d1226627323e861cd3646fb3e7768ec5b94b93760b7f652cf6916f66e317a4fbf8716d10c3673aa4fc3ae58"),
{0, PresetExt | PresetDenyDevel}: toHash(
"4448a74e8cc75a4ab63799c4f2cc2a5af63e5f4e8e9b8ac15a1873d647dfa67a4c67b39ed466d8dd32abc64136d401879fc6185c9ab00feeaf59ccf4305f8201"),
{0, PresetExt | PresetDenyNS | PresetDenyDevel}: toHash(
"c7c86e793cb7192f5f6c735f372cda27eb43ae1045e587f8eadb64c849520a3280b6570a3d7b601d32cddb38021585a2234db38e506cebfd10aa3d6c75440f17"),
}

View File

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

View File

@@ -12,7 +12,6 @@ my %syscall_cutoff_arch = (
"x86" => 340, "x86" => 340,
"x86_64" => 302, "x86_64" => 302,
"aarch64" => 281, "aarch64" => 281,
"riscv64" => 281,
); );
print <<EOF; print <<EOF;

View File

@@ -7,28 +7,24 @@ 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 Int ScmpSyscall ScmpInt
// ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno]. // ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno].
ScmpErrno Int ScmpErrno ScmpInt
// ScmpCompare is equivalent to enum scmp_compare; // ScmpCompare is equivalent to enum scmp_compare;
ScmpCompare Uint ScmpCompare ScmpUint
// ScmpDatum is equivalent to scmp_datum_t. // ScmpDatum is equivalent to scmp_datum_t.
ScmpDatum uint64 ScmpDatum uint64
// ScmpArgCmp is equivalent to struct scmp_arg_cmp. // ScmpArgCmp is equivalent to struct scmp_arg_cmp.
ScmpArgCmp struct { ScmpArgCmp struct {
// argument number, starting at 0 // argument number, starting at 0
Arg Uint `json:"arg"` Arg ScmpUint `json:"arg"`
// the comparison op, e.g. SCMP_CMP_* // the comparison op, e.g. SCMP_CMP_*
Op ScmpCompare `json:"op"` Op ScmpCompare `json:"op"`

View File

@@ -1,55 +0,0 @@
package std
import "syscall"
const (
SYS_NEWFSTATAT = syscall.SYS_FSTATAT
)
var syscallNumExtra = map[string]ScmpSyscall{
"uselib": SNR_USELIB,
"clock_adjtime64": SNR_CLOCK_ADJTIME64,
"clock_settime64": SNR_CLOCK_SETTIME64,
"umount": SNR_UMOUNT,
"chown": SNR_CHOWN,
"chown32": SNR_CHOWN32,
"fchown32": SNR_FCHOWN32,
"lchown": SNR_LCHOWN,
"lchown32": SNR_LCHOWN32,
"setgid32": SNR_SETGID32,
"setgroups32": SNR_SETGROUPS32,
"setregid32": SNR_SETREGID32,
"setresgid32": SNR_SETRESGID32,
"setresuid32": SNR_SETRESUID32,
"setreuid32": SNR_SETREUID32,
"setuid32": SNR_SETUID32,
"modify_ldt": SNR_MODIFY_LDT,
"subpage_prot": SNR_SUBPAGE_PROT,
"switch_endian": SNR_SWITCH_ENDIAN,
"vm86": SNR_VM86,
"vm86old": SNR_VM86OLD,
}
const (
SNR_USELIB ScmpSyscall = __PNR_uselib
SNR_CLOCK_ADJTIME64 ScmpSyscall = __PNR_clock_adjtime64
SNR_CLOCK_SETTIME64 ScmpSyscall = __PNR_clock_settime64
SNR_UMOUNT ScmpSyscall = __PNR_umount
SNR_CHOWN ScmpSyscall = __PNR_chown
SNR_CHOWN32 ScmpSyscall = __PNR_chown32
SNR_FCHOWN32 ScmpSyscall = __PNR_fchown32
SNR_LCHOWN ScmpSyscall = __PNR_lchown
SNR_LCHOWN32 ScmpSyscall = __PNR_lchown32
SNR_SETGID32 ScmpSyscall = __PNR_setgid32
SNR_SETGROUPS32 ScmpSyscall = __PNR_setgroups32
SNR_SETREGID32 ScmpSyscall = __PNR_setregid32
SNR_SETRESGID32 ScmpSyscall = __PNR_setresgid32
SNR_SETRESUID32 ScmpSyscall = __PNR_setresuid32
SNR_SETREUID32 ScmpSyscall = __PNR_setreuid32
SNR_SETUID32 ScmpSyscall = __PNR_setuid32
SNR_MODIFY_LDT ScmpSyscall = __PNR_modify_ldt
SNR_SUBPAGE_PROT ScmpSyscall = __PNR_subpage_prot
SNR_SWITCH_ENDIAN ScmpSyscall = __PNR_switch_endian
SNR_VM86 ScmpSyscall = __PNR_vm86
SNR_VM86OLD ScmpSyscall = __PNR_vm86old
)

View File

@@ -1,719 +0,0 @@
// mksysnum_linux.pl /usr/include/riscv64-linux-gnu/asm/unistd.h
// Code generated by the command above; DO NOT EDIT.
package std
import . "syscall"
var syscallNum = map[string]ScmpSyscall{
"io_setup": SNR_IO_SETUP,
"io_destroy": SNR_IO_DESTROY,
"io_submit": SNR_IO_SUBMIT,
"io_cancel": SNR_IO_CANCEL,
"io_getevents": SNR_IO_GETEVENTS,
"setxattr": SNR_SETXATTR,
"lsetxattr": SNR_LSETXATTR,
"fsetxattr": SNR_FSETXATTR,
"getxattr": SNR_GETXATTR,
"lgetxattr": SNR_LGETXATTR,
"fgetxattr": SNR_FGETXATTR,
"listxattr": SNR_LISTXATTR,
"llistxattr": SNR_LLISTXATTR,
"flistxattr": SNR_FLISTXATTR,
"removexattr": SNR_REMOVEXATTR,
"lremovexattr": SNR_LREMOVEXATTR,
"fremovexattr": SNR_FREMOVEXATTR,
"getcwd": SNR_GETCWD,
"lookup_dcookie": SNR_LOOKUP_DCOOKIE,
"eventfd2": SNR_EVENTFD2,
"epoll_create1": SNR_EPOLL_CREATE1,
"epoll_ctl": SNR_EPOLL_CTL,
"epoll_pwait": SNR_EPOLL_PWAIT,
"dup": SNR_DUP,
"dup3": SNR_DUP3,
"fcntl": SNR_FCNTL,
"inotify_init1": SNR_INOTIFY_INIT1,
"inotify_add_watch": SNR_INOTIFY_ADD_WATCH,
"inotify_rm_watch": SNR_INOTIFY_RM_WATCH,
"ioctl": SNR_IOCTL,
"ioprio_set": SNR_IOPRIO_SET,
"ioprio_get": SNR_IOPRIO_GET,
"flock": SNR_FLOCK,
"mknodat": SNR_MKNODAT,
"mkdirat": SNR_MKDIRAT,
"unlinkat": SNR_UNLINKAT,
"symlinkat": SNR_SYMLINKAT,
"linkat": SNR_LINKAT,
"umount2": SNR_UMOUNT2,
"mount": SNR_MOUNT,
"pivot_root": SNR_PIVOT_ROOT,
"nfsservctl": SNR_NFSSERVCTL,
"statfs": SNR_STATFS,
"fstatfs": SNR_FSTATFS,
"truncate": SNR_TRUNCATE,
"ftruncate": SNR_FTRUNCATE,
"fallocate": SNR_FALLOCATE,
"faccessat": SNR_FACCESSAT,
"chdir": SNR_CHDIR,
"fchdir": SNR_FCHDIR,
"chroot": SNR_CHROOT,
"fchmod": SNR_FCHMOD,
"fchmodat": SNR_FCHMODAT,
"fchownat": SNR_FCHOWNAT,
"fchown": SNR_FCHOWN,
"openat": SNR_OPENAT,
"close": SNR_CLOSE,
"vhangup": SNR_VHANGUP,
"pipe2": SNR_PIPE2,
"quotactl": SNR_QUOTACTL,
"getdents64": SNR_GETDENTS64,
"lseek": SNR_LSEEK,
"read": SNR_READ,
"write": SNR_WRITE,
"readv": SNR_READV,
"writev": SNR_WRITEV,
"pread64": SNR_PREAD64,
"pwrite64": SNR_PWRITE64,
"preadv": SNR_PREADV,
"pwritev": SNR_PWRITEV,
"sendfile": SNR_SENDFILE,
"pselect6": SNR_PSELECT6,
"ppoll": SNR_PPOLL,
"signalfd4": SNR_SIGNALFD4,
"vmsplice": SNR_VMSPLICE,
"splice": SNR_SPLICE,
"tee": SNR_TEE,
"readlinkat": SNR_READLINKAT,
"newfstatat": SNR_NEWFSTATAT,
"fstat": SNR_FSTAT,
"sync": SNR_SYNC,
"fsync": SNR_FSYNC,
"fdatasync": SNR_FDATASYNC,
"sync_file_range": SNR_SYNC_FILE_RANGE,
"timerfd_create": SNR_TIMERFD_CREATE,
"timerfd_settime": SNR_TIMERFD_SETTIME,
"timerfd_gettime": SNR_TIMERFD_GETTIME,
"utimensat": SNR_UTIMENSAT,
"acct": SNR_ACCT,
"capget": SNR_CAPGET,
"capset": SNR_CAPSET,
"personality": SNR_PERSONALITY,
"exit": SNR_EXIT,
"exit_group": SNR_EXIT_GROUP,
"waitid": SNR_WAITID,
"set_tid_address": SNR_SET_TID_ADDRESS,
"unshare": SNR_UNSHARE,
"futex": SNR_FUTEX,
"set_robust_list": SNR_SET_ROBUST_LIST,
"get_robust_list": SNR_GET_ROBUST_LIST,
"nanosleep": SNR_NANOSLEEP,
"getitimer": SNR_GETITIMER,
"setitimer": SNR_SETITIMER,
"kexec_load": SNR_KEXEC_LOAD,
"init_module": SNR_INIT_MODULE,
"delete_module": SNR_DELETE_MODULE,
"timer_create": SNR_TIMER_CREATE,
"timer_gettime": SNR_TIMER_GETTIME,
"timer_getoverrun": SNR_TIMER_GETOVERRUN,
"timer_settime": SNR_TIMER_SETTIME,
"timer_delete": SNR_TIMER_DELETE,
"clock_settime": SNR_CLOCK_SETTIME,
"clock_gettime": SNR_CLOCK_GETTIME,
"clock_getres": SNR_CLOCK_GETRES,
"clock_nanosleep": SNR_CLOCK_NANOSLEEP,
"syslog": SNR_SYSLOG,
"ptrace": SNR_PTRACE,
"sched_setparam": SNR_SCHED_SETPARAM,
"sched_setscheduler": SNR_SCHED_SETSCHEDULER,
"sched_getscheduler": SNR_SCHED_GETSCHEDULER,
"sched_getparam": SNR_SCHED_GETPARAM,
"sched_setaffinity": SNR_SCHED_SETAFFINITY,
"sched_getaffinity": SNR_SCHED_GETAFFINITY,
"sched_yield": SNR_SCHED_YIELD,
"sched_get_priority_max": SNR_SCHED_GET_PRIORITY_MAX,
"sched_get_priority_min": SNR_SCHED_GET_PRIORITY_MIN,
"sched_rr_get_interval": SNR_SCHED_RR_GET_INTERVAL,
"restart_syscall": SNR_RESTART_SYSCALL,
"kill": SNR_KILL,
"tkill": SNR_TKILL,
"tgkill": SNR_TGKILL,
"sigaltstack": SNR_SIGALTSTACK,
"rt_sigsuspend": SNR_RT_SIGSUSPEND,
"rt_sigaction": SNR_RT_SIGACTION,
"rt_sigprocmask": SNR_RT_SIGPROCMASK,
"rt_sigpending": SNR_RT_SIGPENDING,
"rt_sigtimedwait": SNR_RT_SIGTIMEDWAIT,
"rt_sigqueueinfo": SNR_RT_SIGQUEUEINFO,
"rt_sigreturn": SNR_RT_SIGRETURN,
"setpriority": SNR_SETPRIORITY,
"getpriority": SNR_GETPRIORITY,
"reboot": SNR_REBOOT,
"setregid": SNR_SETREGID,
"setgid": SNR_SETGID,
"setreuid": SNR_SETREUID,
"setuid": SNR_SETUID,
"setresuid": SNR_SETRESUID,
"getresuid": SNR_GETRESUID,
"setresgid": SNR_SETRESGID,
"getresgid": SNR_GETRESGID,
"setfsuid": SNR_SETFSUID,
"setfsgid": SNR_SETFSGID,
"times": SNR_TIMES,
"setpgid": SNR_SETPGID,
"getpgid": SNR_GETPGID,
"getsid": SNR_GETSID,
"setsid": SNR_SETSID,
"getgroups": SNR_GETGROUPS,
"setgroups": SNR_SETGROUPS,
"uname": SNR_UNAME,
"sethostname": SNR_SETHOSTNAME,
"setdomainname": SNR_SETDOMAINNAME,
"getrlimit": SNR_GETRLIMIT,
"setrlimit": SNR_SETRLIMIT,
"getrusage": SNR_GETRUSAGE,
"umask": SNR_UMASK,
"prctl": SNR_PRCTL,
"getcpu": SNR_GETCPU,
"gettimeofday": SNR_GETTIMEOFDAY,
"settimeofday": SNR_SETTIMEOFDAY,
"adjtimex": SNR_ADJTIMEX,
"getpid": SNR_GETPID,
"getppid": SNR_GETPPID,
"getuid": SNR_GETUID,
"geteuid": SNR_GETEUID,
"getgid": SNR_GETGID,
"getegid": SNR_GETEGID,
"gettid": SNR_GETTID,
"sysinfo": SNR_SYSINFO,
"mq_open": SNR_MQ_OPEN,
"mq_unlink": SNR_MQ_UNLINK,
"mq_timedsend": SNR_MQ_TIMEDSEND,
"mq_timedreceive": SNR_MQ_TIMEDRECEIVE,
"mq_notify": SNR_MQ_NOTIFY,
"mq_getsetattr": SNR_MQ_GETSETATTR,
"msgget": SNR_MSGGET,
"msgctl": SNR_MSGCTL,
"msgrcv": SNR_MSGRCV,
"msgsnd": SNR_MSGSND,
"semget": SNR_SEMGET,
"semctl": SNR_SEMCTL,
"semtimedop": SNR_SEMTIMEDOP,
"semop": SNR_SEMOP,
"shmget": SNR_SHMGET,
"shmctl": SNR_SHMCTL,
"shmat": SNR_SHMAT,
"shmdt": SNR_SHMDT,
"socket": SNR_SOCKET,
"socketpair": SNR_SOCKETPAIR,
"bind": SNR_BIND,
"listen": SNR_LISTEN,
"accept": SNR_ACCEPT,
"connect": SNR_CONNECT,
"getsockname": SNR_GETSOCKNAME,
"getpeername": SNR_GETPEERNAME,
"sendto": SNR_SENDTO,
"recvfrom": SNR_RECVFROM,
"setsockopt": SNR_SETSOCKOPT,
"getsockopt": SNR_GETSOCKOPT,
"shutdown": SNR_SHUTDOWN,
"sendmsg": SNR_SENDMSG,
"recvmsg": SNR_RECVMSG,
"readahead": SNR_READAHEAD,
"brk": SNR_BRK,
"munmap": SNR_MUNMAP,
"mremap": SNR_MREMAP,
"add_key": SNR_ADD_KEY,
"request_key": SNR_REQUEST_KEY,
"keyctl": SNR_KEYCTL,
"clone": SNR_CLONE,
"execve": SNR_EXECVE,
"mmap": SNR_MMAP,
"fadvise64": SNR_FADVISE64,
"swapon": SNR_SWAPON,
"swapoff": SNR_SWAPOFF,
"mprotect": SNR_MPROTECT,
"msync": SNR_MSYNC,
"mlock": SNR_MLOCK,
"munlock": SNR_MUNLOCK,
"mlockall": SNR_MLOCKALL,
"munlockall": SNR_MUNLOCKALL,
"mincore": SNR_MINCORE,
"madvise": SNR_MADVISE,
"remap_file_pages": SNR_REMAP_FILE_PAGES,
"mbind": SNR_MBIND,
"get_mempolicy": SNR_GET_MEMPOLICY,
"set_mempolicy": SNR_SET_MEMPOLICY,
"migrate_pages": SNR_MIGRATE_PAGES,
"move_pages": SNR_MOVE_PAGES,
"rt_tgsigqueueinfo": SNR_RT_TGSIGQUEUEINFO,
"perf_event_open": SNR_PERF_EVENT_OPEN,
"accept4": SNR_ACCEPT4,
"recvmmsg": SNR_RECVMMSG,
"wait4": SNR_WAIT4,
"prlimit64": SNR_PRLIMIT64,
"fanotify_init": SNR_FANOTIFY_INIT,
"fanotify_mark": SNR_FANOTIFY_MARK,
"name_to_handle_at": SNR_NAME_TO_HANDLE_AT,
"open_by_handle_at": SNR_OPEN_BY_HANDLE_AT,
"clock_adjtime": SNR_CLOCK_ADJTIME,
"syncfs": SNR_SYNCFS,
"setns": SNR_SETNS,
"sendmmsg": SNR_SENDMMSG,
"process_vm_readv": SNR_PROCESS_VM_READV,
"process_vm_writev": SNR_PROCESS_VM_WRITEV,
"kcmp": SNR_KCMP,
"finit_module": SNR_FINIT_MODULE,
"sched_setattr": SNR_SCHED_SETATTR,
"sched_getattr": SNR_SCHED_GETATTR,
"renameat2": SNR_RENAMEAT2,
"seccomp": SNR_SECCOMP,
"getrandom": SNR_GETRANDOM,
"memfd_create": SNR_MEMFD_CREATE,
"bpf": SNR_BPF,
"execveat": SNR_EXECVEAT,
"userfaultfd": SNR_USERFAULTFD,
"membarrier": SNR_MEMBARRIER,
"mlock2": SNR_MLOCK2,
"copy_file_range": SNR_COPY_FILE_RANGE,
"preadv2": SNR_PREADV2,
"pwritev2": SNR_PWRITEV2,
"pkey_mprotect": SNR_PKEY_MPROTECT,
"pkey_alloc": SNR_PKEY_ALLOC,
"pkey_free": SNR_PKEY_FREE,
"statx": SNR_STATX,
"io_pgetevents": SNR_IO_PGETEVENTS,
"rseq": SNR_RSEQ,
"kexec_file_load": SNR_KEXEC_FILE_LOAD,
"pidfd_send_signal": SNR_PIDFD_SEND_SIGNAL,
"io_uring_setup": SNR_IO_URING_SETUP,
"io_uring_enter": SNR_IO_URING_ENTER,
"io_uring_register": SNR_IO_URING_REGISTER,
"open_tree": SNR_OPEN_TREE,
"move_mount": SNR_MOVE_MOUNT,
"fsopen": SNR_FSOPEN,
"fsconfig": SNR_FSCONFIG,
"fsmount": SNR_FSMOUNT,
"fspick": SNR_FSPICK,
"pidfd_open": SNR_PIDFD_OPEN,
"clone3": SNR_CLONE3,
"close_range": SNR_CLOSE_RANGE,
"openat2": SNR_OPENAT2,
"pidfd_getfd": SNR_PIDFD_GETFD,
"faccessat2": SNR_FACCESSAT2,
"process_madvise": SNR_PROCESS_MADVISE,
"epoll_pwait2": SNR_EPOLL_PWAIT2,
"mount_setattr": SNR_MOUNT_SETATTR,
"quotactl_fd": SNR_QUOTACTL_FD,
"landlock_create_ruleset": SNR_LANDLOCK_CREATE_RULESET,
"landlock_add_rule": SNR_LANDLOCK_ADD_RULE,
"landlock_restrict_self": SNR_LANDLOCK_RESTRICT_SELF,
"memfd_secret": SNR_MEMFD_SECRET,
"process_mrelease": SNR_PROCESS_MRELEASE,
"futex_waitv": SNR_FUTEX_WAITV,
"set_mempolicy_home_node": SNR_SET_MEMPOLICY_HOME_NODE,
"cachestat": SNR_CACHESTAT,
"fchmodat2": SNR_FCHMODAT2,
"map_shadow_stack": SNR_MAP_SHADOW_STACK,
"futex_wake": SNR_FUTEX_WAKE,
"futex_wait": SNR_FUTEX_WAIT,
"futex_requeue": SNR_FUTEX_REQUEUE,
"statmount": SNR_STATMOUNT,
"listmount": SNR_LISTMOUNT,
"lsm_get_self_attr": SNR_LSM_GET_SELF_ATTR,
"lsm_set_self_attr": SNR_LSM_SET_SELF_ATTR,
"lsm_list_modules": SNR_LSM_LIST_MODULES,
"mseal": SNR_MSEAL,
"setxattrat": SNR_SETXATTRAT,
"getxattrat": SNR_GETXATTRAT,
"listxattrat": SNR_LISTXATTRAT,
"removexattrat": SNR_REMOVEXATTRAT,
}
const (
SYS_USERFAULTFD = 282
SYS_MEMBARRIER = 283
SYS_MLOCK2 = 284
SYS_COPY_FILE_RANGE = 285
SYS_PREADV2 = 286
SYS_PWRITEV2 = 287
SYS_PKEY_MPROTECT = 288
SYS_PKEY_ALLOC = 289
SYS_PKEY_FREE = 290
SYS_STATX = 291
SYS_IO_PGETEVENTS = 292
SYS_RSEQ = 293
SYS_KEXEC_FILE_LOAD = 294
SYS_PIDFD_SEND_SIGNAL = 424
SYS_IO_URING_SETUP = 425
SYS_IO_URING_ENTER = 426
SYS_IO_URING_REGISTER = 427
SYS_OPEN_TREE = 428
SYS_MOVE_MOUNT = 429
SYS_FSOPEN = 430
SYS_FSCONFIG = 431
SYS_FSMOUNT = 432
SYS_FSPICK = 433
SYS_PIDFD_OPEN = 434
SYS_CLONE3 = 435
SYS_CLOSE_RANGE = 436
SYS_OPENAT2 = 437
SYS_PIDFD_GETFD = 438
SYS_FACCESSAT2 = 439
SYS_PROCESS_MADVISE = 440
SYS_EPOLL_PWAIT2 = 441
SYS_MOUNT_SETATTR = 442
SYS_QUOTACTL_FD = 443
SYS_LANDLOCK_CREATE_RULESET = 444
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_MEMFD_SECRET = 447
SYS_PROCESS_MRELEASE = 448
SYS_FUTEX_WAITV = 449
SYS_SET_MEMPOLICY_HOME_NODE = 450
SYS_CACHESTAT = 451
SYS_FCHMODAT2 = 452
SYS_MAP_SHADOW_STACK = 453
SYS_FUTEX_WAKE = 454
SYS_FUTEX_WAIT = 455
SYS_FUTEX_REQUEUE = 456
SYS_STATMOUNT = 457
SYS_LISTMOUNT = 458
SYS_LSM_GET_SELF_ATTR = 459
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
SYS_SETXATTRAT = 463
SYS_GETXATTRAT = 464
SYS_LISTXATTRAT = 465
SYS_REMOVEXATTRAT = 466
SYS_OPEN_TREE_ATTR = 467
SYS_FILE_GETATTR = 468
SYS_FILE_SETATTR = 469
)
const (
SNR_IO_SETUP ScmpSyscall = SYS_IO_SETUP
SNR_IO_DESTROY ScmpSyscall = SYS_IO_DESTROY
SNR_IO_SUBMIT ScmpSyscall = SYS_IO_SUBMIT
SNR_IO_CANCEL ScmpSyscall = SYS_IO_CANCEL
SNR_IO_GETEVENTS ScmpSyscall = SYS_IO_GETEVENTS
SNR_SETXATTR ScmpSyscall = SYS_SETXATTR
SNR_LSETXATTR ScmpSyscall = SYS_LSETXATTR
SNR_FSETXATTR ScmpSyscall = SYS_FSETXATTR
SNR_GETXATTR ScmpSyscall = SYS_GETXATTR
SNR_LGETXATTR ScmpSyscall = SYS_LGETXATTR
SNR_FGETXATTR ScmpSyscall = SYS_FGETXATTR
SNR_LISTXATTR ScmpSyscall = SYS_LISTXATTR
SNR_LLISTXATTR ScmpSyscall = SYS_LLISTXATTR
SNR_FLISTXATTR ScmpSyscall = SYS_FLISTXATTR
SNR_REMOVEXATTR ScmpSyscall = SYS_REMOVEXATTR
SNR_LREMOVEXATTR ScmpSyscall = SYS_LREMOVEXATTR
SNR_FREMOVEXATTR ScmpSyscall = SYS_FREMOVEXATTR
SNR_GETCWD ScmpSyscall = SYS_GETCWD
SNR_LOOKUP_DCOOKIE ScmpSyscall = SYS_LOOKUP_DCOOKIE
SNR_EVENTFD2 ScmpSyscall = SYS_EVENTFD2
SNR_EPOLL_CREATE1 ScmpSyscall = SYS_EPOLL_CREATE1
SNR_EPOLL_CTL ScmpSyscall = SYS_EPOLL_CTL
SNR_EPOLL_PWAIT ScmpSyscall = SYS_EPOLL_PWAIT
SNR_DUP ScmpSyscall = SYS_DUP
SNR_DUP3 ScmpSyscall = SYS_DUP3
SNR_FCNTL ScmpSyscall = SYS_FCNTL
SNR_INOTIFY_INIT1 ScmpSyscall = SYS_INOTIFY_INIT1
SNR_INOTIFY_ADD_WATCH ScmpSyscall = SYS_INOTIFY_ADD_WATCH
SNR_INOTIFY_RM_WATCH ScmpSyscall = SYS_INOTIFY_RM_WATCH
SNR_IOCTL ScmpSyscall = SYS_IOCTL
SNR_IOPRIO_SET ScmpSyscall = SYS_IOPRIO_SET
SNR_IOPRIO_GET ScmpSyscall = SYS_IOPRIO_GET
SNR_FLOCK ScmpSyscall = SYS_FLOCK
SNR_MKNODAT ScmpSyscall = SYS_MKNODAT
SNR_MKDIRAT ScmpSyscall = SYS_MKDIRAT
SNR_UNLINKAT ScmpSyscall = SYS_UNLINKAT
SNR_SYMLINKAT ScmpSyscall = SYS_SYMLINKAT
SNR_LINKAT ScmpSyscall = SYS_LINKAT
SNR_UMOUNT2 ScmpSyscall = SYS_UMOUNT2
SNR_MOUNT ScmpSyscall = SYS_MOUNT
SNR_PIVOT_ROOT ScmpSyscall = SYS_PIVOT_ROOT
SNR_NFSSERVCTL ScmpSyscall = SYS_NFSSERVCTL
SNR_STATFS ScmpSyscall = SYS_STATFS
SNR_FSTATFS ScmpSyscall = SYS_FSTATFS
SNR_TRUNCATE ScmpSyscall = SYS_TRUNCATE
SNR_FTRUNCATE ScmpSyscall = SYS_FTRUNCATE
SNR_FALLOCATE ScmpSyscall = SYS_FALLOCATE
SNR_FACCESSAT ScmpSyscall = SYS_FACCESSAT
SNR_CHDIR ScmpSyscall = SYS_CHDIR
SNR_FCHDIR ScmpSyscall = SYS_FCHDIR
SNR_CHROOT ScmpSyscall = SYS_CHROOT
SNR_FCHMOD ScmpSyscall = SYS_FCHMOD
SNR_FCHMODAT ScmpSyscall = SYS_FCHMODAT
SNR_FCHOWNAT ScmpSyscall = SYS_FCHOWNAT
SNR_FCHOWN ScmpSyscall = SYS_FCHOWN
SNR_OPENAT ScmpSyscall = SYS_OPENAT
SNR_CLOSE ScmpSyscall = SYS_CLOSE
SNR_VHANGUP ScmpSyscall = SYS_VHANGUP
SNR_PIPE2 ScmpSyscall = SYS_PIPE2
SNR_QUOTACTL ScmpSyscall = SYS_QUOTACTL
SNR_GETDENTS64 ScmpSyscall = SYS_GETDENTS64
SNR_LSEEK ScmpSyscall = SYS_LSEEK
SNR_READ ScmpSyscall = SYS_READ
SNR_WRITE ScmpSyscall = SYS_WRITE
SNR_READV ScmpSyscall = SYS_READV
SNR_WRITEV ScmpSyscall = SYS_WRITEV
SNR_PREAD64 ScmpSyscall = SYS_PREAD64
SNR_PWRITE64 ScmpSyscall = SYS_PWRITE64
SNR_PREADV ScmpSyscall = SYS_PREADV
SNR_PWRITEV ScmpSyscall = SYS_PWRITEV
SNR_SENDFILE ScmpSyscall = SYS_SENDFILE
SNR_PSELECT6 ScmpSyscall = SYS_PSELECT6
SNR_PPOLL ScmpSyscall = SYS_PPOLL
SNR_SIGNALFD4 ScmpSyscall = SYS_SIGNALFD4
SNR_VMSPLICE ScmpSyscall = SYS_VMSPLICE
SNR_SPLICE ScmpSyscall = SYS_SPLICE
SNR_TEE ScmpSyscall = SYS_TEE
SNR_READLINKAT ScmpSyscall = SYS_READLINKAT
SNR_NEWFSTATAT ScmpSyscall = SYS_NEWFSTATAT
SNR_FSTAT ScmpSyscall = SYS_FSTAT
SNR_SYNC ScmpSyscall = SYS_SYNC
SNR_FSYNC ScmpSyscall = SYS_FSYNC
SNR_FDATASYNC ScmpSyscall = SYS_FDATASYNC
SNR_SYNC_FILE_RANGE ScmpSyscall = SYS_SYNC_FILE_RANGE
SNR_TIMERFD_CREATE ScmpSyscall = SYS_TIMERFD_CREATE
SNR_TIMERFD_SETTIME ScmpSyscall = SYS_TIMERFD_SETTIME
SNR_TIMERFD_GETTIME ScmpSyscall = SYS_TIMERFD_GETTIME
SNR_UTIMENSAT ScmpSyscall = SYS_UTIMENSAT
SNR_ACCT ScmpSyscall = SYS_ACCT
SNR_CAPGET ScmpSyscall = SYS_CAPGET
SNR_CAPSET ScmpSyscall = SYS_CAPSET
SNR_PERSONALITY ScmpSyscall = SYS_PERSONALITY
SNR_EXIT ScmpSyscall = SYS_EXIT
SNR_EXIT_GROUP ScmpSyscall = SYS_EXIT_GROUP
SNR_WAITID ScmpSyscall = SYS_WAITID
SNR_SET_TID_ADDRESS ScmpSyscall = SYS_SET_TID_ADDRESS
SNR_UNSHARE ScmpSyscall = SYS_UNSHARE
SNR_FUTEX ScmpSyscall = SYS_FUTEX
SNR_SET_ROBUST_LIST ScmpSyscall = SYS_SET_ROBUST_LIST
SNR_GET_ROBUST_LIST ScmpSyscall = SYS_GET_ROBUST_LIST
SNR_NANOSLEEP ScmpSyscall = SYS_NANOSLEEP
SNR_GETITIMER ScmpSyscall = SYS_GETITIMER
SNR_SETITIMER ScmpSyscall = SYS_SETITIMER
SNR_KEXEC_LOAD ScmpSyscall = SYS_KEXEC_LOAD
SNR_INIT_MODULE ScmpSyscall = SYS_INIT_MODULE
SNR_DELETE_MODULE ScmpSyscall = SYS_DELETE_MODULE
SNR_TIMER_CREATE ScmpSyscall = SYS_TIMER_CREATE
SNR_TIMER_GETTIME ScmpSyscall = SYS_TIMER_GETTIME
SNR_TIMER_GETOVERRUN ScmpSyscall = SYS_TIMER_GETOVERRUN
SNR_TIMER_SETTIME ScmpSyscall = SYS_TIMER_SETTIME
SNR_TIMER_DELETE ScmpSyscall = SYS_TIMER_DELETE
SNR_CLOCK_SETTIME ScmpSyscall = SYS_CLOCK_SETTIME
SNR_CLOCK_GETTIME ScmpSyscall = SYS_CLOCK_GETTIME
SNR_CLOCK_GETRES ScmpSyscall = SYS_CLOCK_GETRES
SNR_CLOCK_NANOSLEEP ScmpSyscall = SYS_CLOCK_NANOSLEEP
SNR_SYSLOG ScmpSyscall = SYS_SYSLOG
SNR_PTRACE ScmpSyscall = SYS_PTRACE
SNR_SCHED_SETPARAM ScmpSyscall = SYS_SCHED_SETPARAM
SNR_SCHED_SETSCHEDULER ScmpSyscall = SYS_SCHED_SETSCHEDULER
SNR_SCHED_GETSCHEDULER ScmpSyscall = SYS_SCHED_GETSCHEDULER
SNR_SCHED_GETPARAM ScmpSyscall = SYS_SCHED_GETPARAM
SNR_SCHED_SETAFFINITY ScmpSyscall = SYS_SCHED_SETAFFINITY
SNR_SCHED_GETAFFINITY ScmpSyscall = SYS_SCHED_GETAFFINITY
SNR_SCHED_YIELD ScmpSyscall = SYS_SCHED_YIELD
SNR_SCHED_GET_PRIORITY_MAX ScmpSyscall = SYS_SCHED_GET_PRIORITY_MAX
SNR_SCHED_GET_PRIORITY_MIN ScmpSyscall = SYS_SCHED_GET_PRIORITY_MIN
SNR_SCHED_RR_GET_INTERVAL ScmpSyscall = SYS_SCHED_RR_GET_INTERVAL
SNR_RESTART_SYSCALL ScmpSyscall = SYS_RESTART_SYSCALL
SNR_KILL ScmpSyscall = SYS_KILL
SNR_TKILL ScmpSyscall = SYS_TKILL
SNR_TGKILL ScmpSyscall = SYS_TGKILL
SNR_SIGALTSTACK ScmpSyscall = SYS_SIGALTSTACK
SNR_RT_SIGSUSPEND ScmpSyscall = SYS_RT_SIGSUSPEND
SNR_RT_SIGACTION ScmpSyscall = SYS_RT_SIGACTION
SNR_RT_SIGPROCMASK ScmpSyscall = SYS_RT_SIGPROCMASK
SNR_RT_SIGPENDING ScmpSyscall = SYS_RT_SIGPENDING
SNR_RT_SIGTIMEDWAIT ScmpSyscall = SYS_RT_SIGTIMEDWAIT
SNR_RT_SIGQUEUEINFO ScmpSyscall = SYS_RT_SIGQUEUEINFO
SNR_RT_SIGRETURN ScmpSyscall = SYS_RT_SIGRETURN
SNR_SETPRIORITY ScmpSyscall = SYS_SETPRIORITY
SNR_GETPRIORITY ScmpSyscall = SYS_GETPRIORITY
SNR_REBOOT ScmpSyscall = SYS_REBOOT
SNR_SETREGID ScmpSyscall = SYS_SETREGID
SNR_SETGID ScmpSyscall = SYS_SETGID
SNR_SETREUID ScmpSyscall = SYS_SETREUID
SNR_SETUID ScmpSyscall = SYS_SETUID
SNR_SETRESUID ScmpSyscall = SYS_SETRESUID
SNR_GETRESUID ScmpSyscall = SYS_GETRESUID
SNR_SETRESGID ScmpSyscall = SYS_SETRESGID
SNR_GETRESGID ScmpSyscall = SYS_GETRESGID
SNR_SETFSUID ScmpSyscall = SYS_SETFSUID
SNR_SETFSGID ScmpSyscall = SYS_SETFSGID
SNR_TIMES ScmpSyscall = SYS_TIMES
SNR_SETPGID ScmpSyscall = SYS_SETPGID
SNR_GETPGID ScmpSyscall = SYS_GETPGID
SNR_GETSID ScmpSyscall = SYS_GETSID
SNR_SETSID ScmpSyscall = SYS_SETSID
SNR_GETGROUPS ScmpSyscall = SYS_GETGROUPS
SNR_SETGROUPS ScmpSyscall = SYS_SETGROUPS
SNR_UNAME ScmpSyscall = SYS_UNAME
SNR_SETHOSTNAME ScmpSyscall = SYS_SETHOSTNAME
SNR_SETDOMAINNAME ScmpSyscall = SYS_SETDOMAINNAME
SNR_GETRLIMIT ScmpSyscall = SYS_GETRLIMIT
SNR_SETRLIMIT ScmpSyscall = SYS_SETRLIMIT
SNR_GETRUSAGE ScmpSyscall = SYS_GETRUSAGE
SNR_UMASK ScmpSyscall = SYS_UMASK
SNR_PRCTL ScmpSyscall = SYS_PRCTL
SNR_GETCPU ScmpSyscall = SYS_GETCPU
SNR_GETTIMEOFDAY ScmpSyscall = SYS_GETTIMEOFDAY
SNR_SETTIMEOFDAY ScmpSyscall = SYS_SETTIMEOFDAY
SNR_ADJTIMEX ScmpSyscall = SYS_ADJTIMEX
SNR_GETPID ScmpSyscall = SYS_GETPID
SNR_GETPPID ScmpSyscall = SYS_GETPPID
SNR_GETUID ScmpSyscall = SYS_GETUID
SNR_GETEUID ScmpSyscall = SYS_GETEUID
SNR_GETGID ScmpSyscall = SYS_GETGID
SNR_GETEGID ScmpSyscall = SYS_GETEGID
SNR_GETTID ScmpSyscall = SYS_GETTID
SNR_SYSINFO ScmpSyscall = SYS_SYSINFO
SNR_MQ_OPEN ScmpSyscall = SYS_MQ_OPEN
SNR_MQ_UNLINK ScmpSyscall = SYS_MQ_UNLINK
SNR_MQ_TIMEDSEND ScmpSyscall = SYS_MQ_TIMEDSEND
SNR_MQ_TIMEDRECEIVE ScmpSyscall = SYS_MQ_TIMEDRECEIVE
SNR_MQ_NOTIFY ScmpSyscall = SYS_MQ_NOTIFY
SNR_MQ_GETSETATTR ScmpSyscall = SYS_MQ_GETSETATTR
SNR_MSGGET ScmpSyscall = SYS_MSGGET
SNR_MSGCTL ScmpSyscall = SYS_MSGCTL
SNR_MSGRCV ScmpSyscall = SYS_MSGRCV
SNR_MSGSND ScmpSyscall = SYS_MSGSND
SNR_SEMGET ScmpSyscall = SYS_SEMGET
SNR_SEMCTL ScmpSyscall = SYS_SEMCTL
SNR_SEMTIMEDOP ScmpSyscall = SYS_SEMTIMEDOP
SNR_SEMOP ScmpSyscall = SYS_SEMOP
SNR_SHMGET ScmpSyscall = SYS_SHMGET
SNR_SHMCTL ScmpSyscall = SYS_SHMCTL
SNR_SHMAT ScmpSyscall = SYS_SHMAT
SNR_SHMDT ScmpSyscall = SYS_SHMDT
SNR_SOCKET ScmpSyscall = SYS_SOCKET
SNR_SOCKETPAIR ScmpSyscall = SYS_SOCKETPAIR
SNR_BIND ScmpSyscall = SYS_BIND
SNR_LISTEN ScmpSyscall = SYS_LISTEN
SNR_ACCEPT ScmpSyscall = SYS_ACCEPT
SNR_CONNECT ScmpSyscall = SYS_CONNECT
SNR_GETSOCKNAME ScmpSyscall = SYS_GETSOCKNAME
SNR_GETPEERNAME ScmpSyscall = SYS_GETPEERNAME
SNR_SENDTO ScmpSyscall = SYS_SENDTO
SNR_RECVFROM ScmpSyscall = SYS_RECVFROM
SNR_SETSOCKOPT ScmpSyscall = SYS_SETSOCKOPT
SNR_GETSOCKOPT ScmpSyscall = SYS_GETSOCKOPT
SNR_SHUTDOWN ScmpSyscall = SYS_SHUTDOWN
SNR_SENDMSG ScmpSyscall = SYS_SENDMSG
SNR_RECVMSG ScmpSyscall = SYS_RECVMSG
SNR_READAHEAD ScmpSyscall = SYS_READAHEAD
SNR_BRK ScmpSyscall = SYS_BRK
SNR_MUNMAP ScmpSyscall = SYS_MUNMAP
SNR_MREMAP ScmpSyscall = SYS_MREMAP
SNR_ADD_KEY ScmpSyscall = SYS_ADD_KEY
SNR_REQUEST_KEY ScmpSyscall = SYS_REQUEST_KEY
SNR_KEYCTL ScmpSyscall = SYS_KEYCTL
SNR_CLONE ScmpSyscall = SYS_CLONE
SNR_EXECVE ScmpSyscall = SYS_EXECVE
SNR_MMAP ScmpSyscall = SYS_MMAP
SNR_FADVISE64 ScmpSyscall = SYS_FADVISE64
SNR_SWAPON ScmpSyscall = SYS_SWAPON
SNR_SWAPOFF ScmpSyscall = SYS_SWAPOFF
SNR_MPROTECT ScmpSyscall = SYS_MPROTECT
SNR_MSYNC ScmpSyscall = SYS_MSYNC
SNR_MLOCK ScmpSyscall = SYS_MLOCK
SNR_MUNLOCK ScmpSyscall = SYS_MUNLOCK
SNR_MLOCKALL ScmpSyscall = SYS_MLOCKALL
SNR_MUNLOCKALL ScmpSyscall = SYS_MUNLOCKALL
SNR_MINCORE ScmpSyscall = SYS_MINCORE
SNR_MADVISE ScmpSyscall = SYS_MADVISE
SNR_REMAP_FILE_PAGES ScmpSyscall = SYS_REMAP_FILE_PAGES
SNR_MBIND ScmpSyscall = SYS_MBIND
SNR_GET_MEMPOLICY ScmpSyscall = SYS_GET_MEMPOLICY
SNR_SET_MEMPOLICY ScmpSyscall = SYS_SET_MEMPOLICY
SNR_MIGRATE_PAGES ScmpSyscall = SYS_MIGRATE_PAGES
SNR_MOVE_PAGES ScmpSyscall = SYS_MOVE_PAGES
SNR_RT_TGSIGQUEUEINFO ScmpSyscall = SYS_RT_TGSIGQUEUEINFO
SNR_PERF_EVENT_OPEN ScmpSyscall = SYS_PERF_EVENT_OPEN
SNR_ACCEPT4 ScmpSyscall = SYS_ACCEPT4
SNR_RECVMMSG ScmpSyscall = SYS_RECVMMSG
SNR_WAIT4 ScmpSyscall = SYS_WAIT4
SNR_PRLIMIT64 ScmpSyscall = SYS_PRLIMIT64
SNR_FANOTIFY_INIT ScmpSyscall = SYS_FANOTIFY_INIT
SNR_FANOTIFY_MARK ScmpSyscall = SYS_FANOTIFY_MARK
SNR_NAME_TO_HANDLE_AT ScmpSyscall = SYS_NAME_TO_HANDLE_AT
SNR_OPEN_BY_HANDLE_AT ScmpSyscall = SYS_OPEN_BY_HANDLE_AT
SNR_CLOCK_ADJTIME ScmpSyscall = SYS_CLOCK_ADJTIME
SNR_SYNCFS ScmpSyscall = SYS_SYNCFS
SNR_SETNS ScmpSyscall = SYS_SETNS
SNR_SENDMMSG ScmpSyscall = SYS_SENDMMSG
SNR_PROCESS_VM_READV ScmpSyscall = SYS_PROCESS_VM_READV
SNR_PROCESS_VM_WRITEV ScmpSyscall = SYS_PROCESS_VM_WRITEV
SNR_KCMP ScmpSyscall = SYS_KCMP
SNR_FINIT_MODULE ScmpSyscall = SYS_FINIT_MODULE
SNR_SCHED_SETATTR ScmpSyscall = SYS_SCHED_SETATTR
SNR_SCHED_GETATTR ScmpSyscall = SYS_SCHED_GETATTR
SNR_RENAMEAT2 ScmpSyscall = SYS_RENAMEAT2
SNR_SECCOMP ScmpSyscall = SYS_SECCOMP
SNR_GETRANDOM ScmpSyscall = SYS_GETRANDOM
SNR_MEMFD_CREATE ScmpSyscall = SYS_MEMFD_CREATE
SNR_BPF ScmpSyscall = SYS_BPF
SNR_EXECVEAT ScmpSyscall = SYS_EXECVEAT
SNR_USERFAULTFD ScmpSyscall = SYS_USERFAULTFD
SNR_MEMBARRIER ScmpSyscall = SYS_MEMBARRIER
SNR_MLOCK2 ScmpSyscall = SYS_MLOCK2
SNR_COPY_FILE_RANGE ScmpSyscall = SYS_COPY_FILE_RANGE
SNR_PREADV2 ScmpSyscall = SYS_PREADV2
SNR_PWRITEV2 ScmpSyscall = SYS_PWRITEV2
SNR_PKEY_MPROTECT ScmpSyscall = SYS_PKEY_MPROTECT
SNR_PKEY_ALLOC ScmpSyscall = SYS_PKEY_ALLOC
SNR_PKEY_FREE ScmpSyscall = SYS_PKEY_FREE
SNR_STATX ScmpSyscall = SYS_STATX
SNR_IO_PGETEVENTS ScmpSyscall = SYS_IO_PGETEVENTS
SNR_RSEQ ScmpSyscall = SYS_RSEQ
SNR_KEXEC_FILE_LOAD ScmpSyscall = SYS_KEXEC_FILE_LOAD
SNR_PIDFD_SEND_SIGNAL ScmpSyscall = SYS_PIDFD_SEND_SIGNAL
SNR_IO_URING_SETUP ScmpSyscall = SYS_IO_URING_SETUP
SNR_IO_URING_ENTER ScmpSyscall = SYS_IO_URING_ENTER
SNR_IO_URING_REGISTER ScmpSyscall = SYS_IO_URING_REGISTER
SNR_OPEN_TREE ScmpSyscall = SYS_OPEN_TREE
SNR_MOVE_MOUNT ScmpSyscall = SYS_MOVE_MOUNT
SNR_FSOPEN ScmpSyscall = SYS_FSOPEN
SNR_FSCONFIG ScmpSyscall = SYS_FSCONFIG
SNR_FSMOUNT ScmpSyscall = SYS_FSMOUNT
SNR_FSPICK ScmpSyscall = SYS_FSPICK
SNR_PIDFD_OPEN ScmpSyscall = SYS_PIDFD_OPEN
SNR_CLONE3 ScmpSyscall = SYS_CLONE3
SNR_CLOSE_RANGE ScmpSyscall = SYS_CLOSE_RANGE
SNR_OPENAT2 ScmpSyscall = SYS_OPENAT2
SNR_PIDFD_GETFD ScmpSyscall = SYS_PIDFD_GETFD
SNR_FACCESSAT2 ScmpSyscall = SYS_FACCESSAT2
SNR_PROCESS_MADVISE ScmpSyscall = SYS_PROCESS_MADVISE
SNR_EPOLL_PWAIT2 ScmpSyscall = SYS_EPOLL_PWAIT2
SNR_MOUNT_SETATTR ScmpSyscall = SYS_MOUNT_SETATTR
SNR_QUOTACTL_FD ScmpSyscall = SYS_QUOTACTL_FD
SNR_LANDLOCK_CREATE_RULESET ScmpSyscall = SYS_LANDLOCK_CREATE_RULESET
SNR_LANDLOCK_ADD_RULE ScmpSyscall = SYS_LANDLOCK_ADD_RULE
SNR_LANDLOCK_RESTRICT_SELF ScmpSyscall = SYS_LANDLOCK_RESTRICT_SELF
SNR_MEMFD_SECRET ScmpSyscall = SYS_MEMFD_SECRET
SNR_PROCESS_MRELEASE ScmpSyscall = SYS_PROCESS_MRELEASE
SNR_FUTEX_WAITV ScmpSyscall = SYS_FUTEX_WAITV
SNR_SET_MEMPOLICY_HOME_NODE ScmpSyscall = SYS_SET_MEMPOLICY_HOME_NODE
SNR_CACHESTAT ScmpSyscall = SYS_CACHESTAT
SNR_FCHMODAT2 ScmpSyscall = SYS_FCHMODAT2
SNR_MAP_SHADOW_STACK ScmpSyscall = SYS_MAP_SHADOW_STACK
SNR_FUTEX_WAKE ScmpSyscall = SYS_FUTEX_WAKE
SNR_FUTEX_WAIT ScmpSyscall = SYS_FUTEX_WAIT
SNR_FUTEX_REQUEUE ScmpSyscall = SYS_FUTEX_REQUEUE
SNR_STATMOUNT ScmpSyscall = SYS_STATMOUNT
SNR_LISTMOUNT ScmpSyscall = SYS_LISTMOUNT
SNR_LSM_GET_SELF_ATTR ScmpSyscall = SYS_LSM_GET_SELF_ATTR
SNR_LSM_SET_SELF_ATTR ScmpSyscall = SYS_LSM_SET_SELF_ATTR
SNR_LSM_LIST_MODULES ScmpSyscall = SYS_LSM_LIST_MODULES
SNR_MSEAL ScmpSyscall = SYS_MSEAL
SNR_SETXATTRAT ScmpSyscall = SYS_SETXATTRAT
SNR_GETXATTRAT ScmpSyscall = SYS_GETXATTRAT
SNR_LISTXATTRAT ScmpSyscall = SYS_LISTXATTRAT
SNR_REMOVEXATTRAT ScmpSyscall = SYS_REMOVEXATTRAT
SNR_OPEN_TREE_ATTR ScmpSyscall = SYS_OPEN_TREE_ATTR
SNR_FILE_GETATTR ScmpSyscall = SYS_FILE_GETATTR
SNR_FILE_SETATTR ScmpSyscall = SYS_FILE_SETATTR
)

View File

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

View File

@@ -3,8 +3,6 @@ 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.
@@ -43,49 +41,6 @@ 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,8 +2,6 @@ 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,7 +24,6 @@ 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
@@ -52,8 +51,7 @@ func (e *DecoderError) Error() string {
} }
type ( type (
// A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from // A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from an input stream.
// an input stream.
MountInfoDecoder struct { MountInfoDecoder struct {
s *bufio.Scanner s *bufio.Scanner
m *MountInfo m *MountInfo
@@ -74,16 +72,13 @@ 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 // parent ID: the ID of the parent mount (or of self for the root of this mount namespace's mount tree).
// 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: the pathname of the directory in the filesystem which forms the root of this mount.
// root of this mount.
Root string `json:"root"` Root string `json:"root"`
// mount point: the pathname of the mount point relative to the // mount point: the pathname of the mount point relative to the process's root directory.
// 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"`
@@ -131,8 +126,7 @@ 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 // The decoder introduces its own buffering and may read data from r beyond the mountinfo entries requested.
// 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)}
} }
@@ -277,8 +271,6 @@ 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) &&
@@ -292,8 +284,6 @@ 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,7 +6,6 @@ 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 {
@@ -28,7 +27,6 @@ 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

6
dist/release.sh vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh -e #!/bin/sh -e
cd "$(dirname -- "$0")/.." cd "$(dirname -- "$0")/.."
VERSION="${HAKUREI_VERSION:-untagged}" VERSION="${HAKUREI_VERSION:-untagged}"
pname="hakurei-${VERSION}-$(go env GOARCH)" pname="hakurei-${VERSION}"
out="${DESTDIR:-dist}/${pname}" out="${DESTDIR:-dist}/${pname}"
echo '# Preparing distribution files.' echo '# Preparing distribution files.'
@@ -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= -linkmode external -extldflags=-static -buildid= -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= -linkmode external -extldflags=-static' ./... go test -ldflags='-buildid= -extldflags=-static' ./...
echo echo
echo '# Creating distribution.' echo '# Creating distribution.'

View File

@@ -29,6 +29,20 @@
{ {
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
@@ -57,6 +71,8 @@
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 ${./.}
@@ -111,6 +127,11 @@
glibc glibc
xdg-dbus-proxy xdg-dbus-proxy
# hpkg
zstd
gnutar
coreutils
# for check # for check
util-linux util-linux
nettools nettools
@@ -198,7 +219,7 @@
./test/interactive/trace.nix ./test/interactive/trace.nix
self.nixosModules.hakurei self.nixosModules.hakurei
home-manager.nixosModules.home-manager self.inputs.home-manager.nixosModules.home-manager
]; ];
}; };
in in

View File

@@ -8,6 +8,7 @@
package filelock package filelock
import ( import (
"errors"
"io/fs" "io/fs"
) )
@@ -73,3 +74,10 @@ 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/fhs" "hakurei.app/container"
"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(), fhs.ProcSelfExe, "-test.run=^$") cmd := testexec.CommandContext(t, t.Context(), container.MustExecutable(nil), "-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,11 +94,6 @@ 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/fhs" "hakurei.app/container"
"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(), fhs.ProcSelfExe, "-test.run=^"+t.Name()+"$") cmd := testexec.CommandContext(t, t.Context(), container.MustExecutable(nil), "-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{})

216
internal/pkg/asm.go Normal file
View File

@@ -0,0 +1,216 @@
package pkg
import (
"encoding/binary"
"fmt"
"io"
"strconv"
"strings"
)
type asmOutLine struct {
pos int
word int
kindData int64
valueData []byte
indent int
kind string
value string
}
var spacingLine = asmOutLine{
pos: -1,
kindData: -1,
valueData: nil,
indent: 0,
kind: "",
value: "",
}
func Disassemble(r io.Reader, real bool, showHeader bool, force bool, raw bool) (s string, err error) {
var lines []asmOutLine
sb := new(strings.Builder)
header := true
pos := new(int)
for err == nil {
if header {
var kind uint64
var size uint64
var bsize []byte
p := *pos
if _, kind, err = nextUint64(r, pos); err != nil {
break
}
if bsize, size, err = nextUint64(r, pos); err != nil {
break
}
if showHeader {
lines = append(lines, asmOutLine{p, 8, int64(kind), bsize, 0, "head " + intToKind(kind), ""})
}
for i := 0; uint64(i) < size; i++ {
var did Checksum
var dkind uint64
p := *pos
if _, dkind, err = nextUint64(r, pos); err != nil {
break
}
if _, did, err = nextIdent(r, pos); err != nil {
break
}
if showHeader {
lines = append(lines, asmOutLine{p, 8, int64(dkind), nil, 1, intToKind(dkind), Encode(did)})
}
}
header = false
}
var k uint32
p := *pos
if _, k, err = nextUint32(r, pos); err != nil {
break
}
kind := IRValueKind(k)
switch kind {
case IRKindEnd:
var a uint32
var ba []byte
if ba, a, err = nextUint32(r, pos); err != nil {
break
}
if a&1 != 0 {
var sum Checksum
if _, sum, err = nextIdent(r, pos); err != nil {
break
}
lines = append(lines, asmOutLine{p, 4, int64(kind), ba, 1, "end ", Encode(sum)})
} else {
lines = append(lines, asmOutLine{p, 4, int64(kind), []byte{0, 0, 0, 0}, 1, "end ", ""})
}
lines = append(lines, spacingLine)
header = true
continue
case IRKindIdent:
var a []byte
// discard ancillary
if a, _, err = nextUint32(r, pos); err != nil {
break
}
var sum Checksum
if _, sum, err = nextIdent(r, pos); err != nil {
break
}
lines = append(lines, asmOutLine{p, 4, int64(kind), a, 1, "id ", Encode(sum)})
continue
case IRKindUint32:
var i uint32
var bi []byte
if bi, i, err = nextUint32(r, pos); err != nil {
break
}
lines = append(lines, asmOutLine{p, 4, int64(kind), bi, 1, "int ", strconv.FormatUint(uint64(i), 10)})
case IRKindString:
var l uint32
var bl []byte
if bl, l, err = nextUint32(r, pos); err != nil {
break
}
s := make([]byte, l+(wordSize-(l)%wordSize)%wordSize)
var n int
if n, err = r.Read(s); err != nil {
break
}
*pos = *pos + n
lines = append(lines, asmOutLine{p, 4, int64(kind), bl, 1, "str ", strconv.Quote(string(s[:l]))})
continue
default:
var bi []byte
if bi, _, err = nextUint32(r, pos); err != nil {
break
}
lines = append(lines, asmOutLine{p, 4, int64(kind), bi, 1, "????", ""})
}
}
if err != io.EOF {
return
}
err = nil
for _, line := range lines {
if raw {
if line.pos != -1 {
sb.WriteString(fmt.Sprintf("%s\t%s\n", line.kind, line.value))
}
} else {
if line.pos == -1 {
sb.WriteString("\n")
} else if line.word == 4 {
sb.WriteString(fmt.Sprintf("%06x: %04x %04x%s %s %s\n", line.pos, binary.LittleEndian.AppendUint32(nil, uint32(line.kindData)), line.valueData, headerSpacing(showHeader), line.kind, line.value))
} else {
kind := binary.LittleEndian.AppendUint64(nil, uint64(line.kindData))
value := line.valueData
if len(value) == 8 {
sb.WriteString(fmt.Sprintf("%06x: %04x %04x %04x %04x %s %s\n", line.pos, kind[:4], kind[4:], value[:4], value[4:], line.kind, line.value))
} else {
sb.WriteString(fmt.Sprintf("%06x: %04x %04x %s %s\n", line.pos, kind[:4], kind[4:], line.kind, line.value))
}
}
}
}
return sb.String(), err
}
func nextUint32(r io.Reader, pos *int) ([]byte, uint32, error) {
i := make([]byte, 4)
_, err := r.Read(i)
if err != nil {
return i, 0, err
}
p := *pos + 4
*pos = p
return i, binary.LittleEndian.Uint32(i), nil
}
func nextUint64(r io.Reader, pos *int) ([]byte, uint64, error) {
i := make([]byte, 8)
_, err := r.Read(i)
if err != nil {
return i, 0, err
}
p := *pos + 8
*pos = p
return i, binary.LittleEndian.Uint64(i), nil
}
func nextIdent(r io.Reader, pos *int) ([]byte, Checksum, error) {
i := make([]byte, 48)
if _, err := r.Read(i); err != nil {
return i, Checksum{}, err
}
p := *pos + 48
*pos = p
return i, Checksum(i), nil
}
func intToKind(i uint64) string {
switch Kind(i) {
case KindHTTPGet:
return "http"
case KindTar:
return "tar "
case KindExec:
return "exec"
case KindExecNet:
return "exen"
case KindFile:
return "file"
default:
return fmt.Sprintf("$%d ", i-KindCustomOffset)
}
}
func headerSpacing(showHeader bool) string {
if showHeader {
return " "
}
return ""
}

View File

@@ -23,7 +23,7 @@ import (
"hakurei.app/message" "hakurei.app/message"
) )
// AbsWork is the container pathname [TContext.GetWorkDir] is mounted on. // AbsWork is the container pathname [CureContext.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,23 +39,22 @@ type ExecPath struct {
W bool W bool
} }
// SchedPolicy is the [container] scheduling policy. // layers returns pathnames collected from A deduplicated by checksum.
var SchedPolicy int func (p *ExecPath) layers(f *FContext) []*check.Absolute {
msg := f.GetMessage()
// PromoteLayers returns artifacts with identical-by-content layers promoted to layers := make([]*check.Absolute, 0, len(p.A))
// the highest priority instance, as if mounted via [ExecPath]. checksums := make(map[unique.Handle[Checksum]]struct{}, len(p.A))
func PromoteLayers( for i := range p.A {
artifacts []Artifact, d := p.A[len(p.A)-1-i]
getArtifact func(Artifact) (*check.Absolute, unique.Handle[Checksum]), pathname, checksum := f.GetArtifact(d)
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 {
report(len(artifacts)-1-i, d) if msg.IsVerbose() {
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{}{}
@@ -65,19 +64,6 @@ func PromoteLayers(
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}
@@ -361,7 +347,6 @@ const (
// scanVerbose prefixes program output for a verbose [message.Msg]. // scanVerbose prefixes program output for a verbose [message.Msg].
func scanVerbose( func scanVerbose(
msg message.Msg, msg message.Msg,
cancel context.CancelFunc,
done chan<- struct{}, done chan<- struct{},
prefix string, prefix string,
r io.Reader, r io.Reader,
@@ -376,15 +361,10 @@ func scanVerbose(
msg.Verbose(prefix, s.Text()) msg.Verbose(prefix, s.Text())
} }
if err := s.Err(); err != nil && !errors.Is(err, os.ErrClosed) { if err := s.Err(); err != nil && !errors.Is(err, os.ErrClosed) {
cancel()
msg.Verbose("*"+prefix, err) msg.Verbose("*"+prefix, err)
} }
} }
// 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) {
@@ -408,22 +388,15 @@ 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 = SeccompPresets z.SeccompPresets |= std.PresetStrict & ^std.PresetDenyNS
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"
} }
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1 z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
var status io.Writer
if status, err = f.GetStatusWriter(); err != nil {
return
}
if msg := f.GetMessage(); msg.IsVerbose() { if msg := f.GetMessage(); msg.IsVerbose() {
var stdout, stderr io.ReadCloser var stdout, stderr io.ReadCloser
if stdout, err = z.StdoutPipe(); err != nil { if stdout, err = z.StdoutPipe(); err != nil {
@@ -440,26 +413,10 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
} }
}() }()
brStdout, brStderr := f.cache.getReader(stdout), f.cache.getReader(stderr)
stdoutDone, stderrDone := make(chan struct{}), make(chan struct{}) stdoutDone, stderrDone := make(chan struct{}), make(chan struct{})
go scanVerbose( go scanVerbose(msg, stdoutDone, "("+a.name+":1)", stdout)
msg, cancel, stdoutDone, go scanVerbose(msg, stderrDone, "("+a.name+":2)", stderr)
"("+a.name+":1)", defer func() { <-stdoutDone; <-stderrDone }()
io.TeeReader(brStdout, status),
)
go scanVerbose(
msg, cancel, stderrDone,
"("+a.name+":2)",
io.TeeReader(brStderr, status),
)
defer func() {
<-stdoutDone
<-stderrDone
f.cache.putReader(brStdout)
f.cache.putReader(brStderr)
}()
} else {
z.Stdout, z.Stderr = status, status
} }
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args

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 << 24 const irMaxStringLength = 1 << 20
// 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,13 +310,6 @@ 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

@@ -28,21 +28,15 @@ import (
"unsafe" "unsafe"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/internal/info"
"hakurei.app/internal/lockedfile" "hakurei.app/internal/lockedfile"
"hakurei.app/message" "hakurei.app/message"
) )
const (
// programName is the string identifying this build system.
programName = "internal/pkg"
)
type ( type (
// A Checksum is a SHA-384 checksum computed for a cured [Artifact]. // A Checksum is a SHA-384 checksum computed for a cured [Artifact].
Checksum = [sha512.Size384]byte Checksum = [sha512.Size384]byte
// An ID is a unique identifier returned by [KnownIdent.ID]. This value must // An ID is a unique identifier returned by [Artifact.ID]. This value must
// be deterministically determined ahead of time. // be deterministically determined ahead of time.
ID Checksum ID Checksum
) )
@@ -71,75 +65,18 @@ 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
// Target [Artifact] encoded identifier.
ids string
// Pathname status was created at.
statusPath *check.Absolute
// File statusHeader and logs are written to.
status *os.File
// Error value during prepareStatus.
statusErr error
common
}
// statusHeader is the header written to all status files in dirStatus.
var statusHeader = func() string {
s := programName
if v := info.Version(); v != info.FallbackVersion {
s += " " + v
}
s += " (" + runtime.GOARCH + ")"
if name, err := os.Hostname(); err == nil {
s += " on " + name
}
s += "\n\n"
return s
}()
// prepareStatus initialises the status file once.
func (t *TContext) prepareStatus() error {
if t.statusPath != nil || t.status != nil {
return t.statusErr
}
t.statusPath = t.cache.base.Append(
dirStatus,
t.ids,
)
if t.status, t.statusErr = os.OpenFile(
t.statusPath.String(),
syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY,
0400,
); t.statusErr != nil {
return t.statusErr
}
_, t.statusErr = t.status.WriteString(statusHeader)
return t.statusErr
}
// GetStatusWriter returns a [io.Writer] for build logs. The caller must not
// seek this writer before the position it was first returned in.
func (t *TContext) GetStatusWriter() (io.Writer, error) {
err := t.prepareStatus()
return t.status, err
} }
// destroy destroys the temporary directory and joins its errors with the error // destroy destroys the temporary directory and joins its errors with the error
@@ -147,15 +84,12 @@ func (t *TContext) GetStatusWriter() (io.Writer, error) {
// directory is removed similarly. [Cache] is responsible for making sure work // directory is removed similarly. [Cache] is responsible for making sure work
// is never left behind for a successful [Cache.Cure]. // is never left behind for a successful [Cache.Cure].
// //
// If implementation had requested status, it is closed with error joined with
// the error referred to by errP. If the error referred to by errP is non-nil,
// the status file is removed from the filesystem.
//
// destroy must be deferred by [Cache.Cure] if [TContext] is passed to any Cure // destroy must be deferred by [Cache.Cure] if [TContext] is passed to any Cure
// implementation. It should not be called prior to that point. // implementation. It should not be called prior to that point.
func (t *TContext) destroy(errP *error) { func (t *TContext) destroy(errP *error) {
if chmodErr, removeErr := removeAll(t.temp); chmodErr != nil || removeErr != nil { if chmodErr, removeErr := removeAll(t.temp); chmodErr != nil || removeErr != nil {
*errP = errors.Join(*errP, chmodErr, removeErr) *errP = errors.Join(*errP, chmodErr, removeErr)
return
} }
if *errP != nil { if *errP != nil {
@@ -163,31 +97,17 @@ func (t *TContext) destroy(errP *error) {
if chmodErr != nil || removeErr != nil { if chmodErr != nil || removeErr != nil {
*errP = errors.Join(*errP, chmodErr, removeErr) *errP = errors.Join(*errP, chmodErr, removeErr)
} else if errors.Is(*errP, os.ErrExist) { } else if errors.Is(*errP, os.ErrExist) {
var linkError *os.LinkError
if errors.As(*errP, &linkError) && linkError != nil &&
linkError.Op == "rename" {
// two artifacts may be backed by the same file // two artifacts may be backed by the same file
*errP = nil *errP = nil
} }
} }
}
if t.status != nil {
if err := t.status.Close(); err != nil {
*errP = errors.Join(*errP, err)
}
if *errP != nil {
*errP = errors.Join(*errP, os.Remove(t.statusPath.String()))
}
t.status = nil
}
} }
// Unwrap returns the underlying [context.Context]. // Unwrap returns the underlying [context.Context].
func (c *common) Unwrap() context.Context { return c.cache.ctx } func (t *TContext) Unwrap() context.Context { return t.cache.ctx }
// GetMessage returns [message.Msg] held by the underlying [Cache]. // GetMessage returns [message.Msg] held by the underlying [Cache].
func (c *common) GetMessage() message.Msg { return c.cache.msg } func (t *TContext) GetMessage() message.Msg { return t.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]
@@ -206,13 +126,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 (c *common) Open(a Artifact) (r io.ReadCloser, err error) { func (t *TContext) Open(a Artifact) (r io.ReadCloser, err error) {
if f, ok := a.(FileArtifact); ok { if f, ok := a.(FileArtifact); ok {
return c.cache.openFile(f) return t.cache.openFile(f)
} }
var pathname *check.Absolute var pathname *check.Absolute
if pathname, _, err = c.cache.Cure(a); err != nil { if pathname, _, err = t.cache.Cure(a); err != nil {
return return
} }
@@ -244,7 +164,7 @@ type FContext struct {
} }
// InvalidLookupError is the identifier of non-dependency [Artifact] looked up // InvalidLookupError is the identifier of non-dependency [Artifact] looked up
// via [FContext.GetArtifact] by a misbehaving [Artifact] implementation. // via [FContext.Pathname] by a misbehaving [Artifact] implementation.
type InvalidLookupError ID type InvalidLookupError ID
func (e InvalidLookupError) Error() string { func (e InvalidLookupError) Error() string {
@@ -271,7 +191,14 @@ 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{ common } type RContext struct {
// 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
@@ -450,9 +377,6 @@ const (
// dirChecksum is the directory name appended to Cache.base for storing // dirChecksum is the directory name appended to Cache.base for storing
// artifacts named after their [Checksum]. // artifacts named after their [Checksum].
dirChecksum = "checksum" dirChecksum = "checksum"
// dirStatus is the directory name appended to Cache.base for storing
// artifact metadata and logs named after their [ID].
dirStatus = "status"
// dirWork is the directory name appended to Cache.base for working // dirWork is the directory name appended to Cache.base for working
// pathnames set up during [Cache.Cure]. // pathnames set up during [Cache.Cure].
@@ -542,7 +466,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.
brPool, bwPool sync.Pool bufioPool 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()
@@ -624,26 +548,6 @@ 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.
@@ -665,9 +569,6 @@ type ScrubError struct {
// Dangling identifier symlinks. This can happen if the content-addressed // Dangling identifier symlinks. This can happen if the content-addressed
// entry was removed while scrubbing due to a checksum mismatch. // entry was removed while scrubbing due to a checksum mismatch.
DanglingIdentifiers []ID DanglingIdentifiers []ID
// Dangling status files. This can happen if a dangling status symlink was
// removed while scrubbing.
DanglingStatus []ID
// Miscellaneous errors, including [os.ReadDir] on checksum and identifier // Miscellaneous errors, including [os.ReadDir] on checksum and identifier
// directories, [Decode] on entry names and [os.RemoveAll] on inconsistent // directories, [Decode] on entry names and [os.RemoveAll] on inconsistent
// entries. // entries.
@@ -719,13 +620,6 @@ func (e *ScrubError) Error() string {
} }
segments = append(segments, s) segments = append(segments, s)
} }
if len(e.DanglingStatus) > 0 {
s := "dangling status:\n"
for _, id := range e.DanglingStatus {
s += Encode(id) + "\n"
}
segments = append(segments, s)
}
if len(e.Errs) > 0 { if len(e.Errs) > 0 {
s := "errors during scrub:\n" s := "errors during scrub:\n"
for pathname, errs := range e.errs { for pathname, errs := range e.errs {
@@ -904,36 +798,6 @@ func (c *Cache) Scrub(checks int) error {
wg.Wait() wg.Wait()
} }
dir = c.base.Append(dirStatus)
if entries, readdirErr := os.ReadDir(dir.String()); readdirErr != nil {
if !errors.Is(readdirErr, os.ErrNotExist) {
addErr(dir, readdirErr)
}
} else {
wg.Add(len(entries))
for _, ent := range entries {
w <- checkEntry{ent, func(ent os.DirEntry, want *Checksum) bool {
got := p.Get().(*Checksum)
defer p.Put(got)
if _, err := os.Stat(c.base.Append(
dirIdentifier,
ent.Name(),
).String()); err != nil {
if !errors.Is(err, os.ErrNotExist) {
addErr(dir.Append(ent.Name()), err)
}
seMu.Lock()
se.DanglingStatus = append(se.DanglingStatus, *want)
seMu.Unlock()
return false
}
return true
}}
}
wg.Wait()
}
if len(c.identPending) > 0 { if len(c.identPending) > 0 {
addErr(c.base, errors.New( addErr(c.base, errors.New(
"scrub began with pending artifacts", "scrub began with pending artifacts",
@@ -964,7 +828,6 @@ func (c *Cache) Scrub(checks int) error {
if len(se.ChecksumMismatches) > 0 || if len(se.ChecksumMismatches) > 0 ||
len(se.DanglingIdentifiers) > 0 || len(se.DanglingIdentifiers) > 0 ||
len(se.DanglingStatus) > 0 ||
len(se.Errs) > 0 { len(se.Errs) > 0 {
slices.SortFunc(se.ChecksumMismatches, func(a, b ChecksumMismatchError) int { slices.SortFunc(se.ChecksumMismatches, func(a, b ChecksumMismatchError) int {
return bytes.Compare(a.Want[:], b.Want[:]) return bytes.Compare(a.Want[:], b.Want[:])
@@ -972,9 +835,6 @@ func (c *Cache) Scrub(checks int) error {
slices.SortFunc(se.DanglingIdentifiers, func(a, b ID) int { slices.SortFunc(se.DanglingIdentifiers, func(a, b ID) int {
return bytes.Compare(a[:], b[:]) return bytes.Compare(a[:], b[:])
}) })
slices.SortFunc(se.DanglingStatus, func(a, b ID) int {
return bytes.Compare(a[:], b[:])
})
return &se return &se
} else { } else {
return nil return nil
@@ -1065,17 +925,16 @@ func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
return return
} }
id := c.Ident(f)
if c.msg.IsVerbose() { if c.msg.IsVerbose() {
rn := reportName(f, id) rn := reportName(f, c.Ident(f))
c.msg.Verbosef("curing %s in memory...", rn) c.msg.Verbosef("curing %s to memory...", rn)
defer func() { defer func() {
if err == nil { if err == nil {
c.msg.Verbosef("opened %s for reading", rn) c.msg.Verbosef("cured %s to memory", rn)
} }
}() }()
} }
return f.Cure(&RContext{common{c}}) return f.Cure(&RContext{c})
} }
return return
} }
@@ -1272,9 +1131,6 @@ 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
@@ -1358,6 +1214,13 @@ 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.
@@ -1440,6 +1303,9 @@ 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,
@@ -1571,7 +1437,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{common{c}}) r, err = f.Cure(&RContext{c})
if err == nil { if err == nil {
if checksumPathname == nil || c.IsStrict() { if checksumPathname == nil || c.IsStrict() {
h := sha512.New384() h := sha512.New384()
@@ -1647,12 +1513,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
return return
} }
t := TContext{ t := TContext{c, c.base.Append(dirWork, ids), c.base.Append(dirTemp, ids)}
c.base.Append(dirWork, ids),
c.base.Append(dirTemp, ids),
ids, nil, nil, nil,
common{c},
}
switch ca := a.(type) { switch ca := a.(type) {
case TrivialArtifact: case TrivialArtifact:
defer t.destroy(&err) defer t.destroy(&err)
@@ -1790,18 +1651,6 @@ func (pending *pendingArtifactDep) cure(c *Cache) {
pending.errsMu.Unlock() pending.errsMu.Unlock()
} }
// OpenStatus attempts to open the status file associated to an [Artifact]. If
// err is nil, the caller must close the resulting reader.
func (c *Cache) OpenStatus(a Artifact) (r io.ReadSeekCloser, err error) {
c.identMu.RLock()
r, err = os.Open(c.base.Append(
dirStatus,
Encode(c.Ident(a).Value())).String(),
)
c.identMu.RUnlock()
return
}
// Close cancels all pending cures and waits for them to clean up. // Close cancels all pending cures and waits for them to clean up.
func (c *Cache) Close() { func (c *Cache) Close() {
c.closeOnce.Do(func() { c.closeOnce.Do(func() {
@@ -1850,7 +1699,6 @@ func open(
for _, name := range []string{ for _, name := range []string{
dirIdentifier, dirIdentifier,
dirChecksum, dirChecksum,
dirStatus,
dirWork, dirWork,
} { } {
if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil && if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil &&
@@ -1865,16 +1713,13 @@ 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

@@ -314,11 +314,6 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
t.Fatal(err) t.Fatal(err)
} }
// destroy non-deterministic status files
if err := os.RemoveAll(base.Append("status").String()); err != nil {
t.Fatal(err)
}
var checksum pkg.Checksum var checksum pkg.Checksum
if err := pkg.HashDir(&checksum, base); err != nil { if err := pkg.HashDir(&checksum, base); err != nil {
t.Fatalf("HashDir: error = %v", err) t.Fatalf("HashDir: error = %v", err)
@@ -387,9 +382,6 @@ func cureMany(t *testing.T, c *pkg.Cache, steps []cureStep) {
} else if step.pathname != ignorePathname && !pathname.Is(step.pathname) { } else if step.pathname != ignorePathname && !pathname.Is(step.pathname) {
t.Fatalf("Cure: pathname = %q, want %q", pathname, step.pathname) t.Fatalf("Cure: pathname = %q, want %q", pathname, step.pathname)
} else if checksum != makeChecksumH(step.checksum) { } else if checksum != makeChecksumH(step.checksum) {
if checksum == (unique.Handle[pkg.Checksum]{}) {
checksum = unique.Make(pkg.Checksum{})
}
t.Fatalf( t.Fatalf(
"Cure: checksum = %s, want %s", "Cure: checksum = %s, want %s",
pkg.Encode(checksum.Value()), pkg.Encode(step.checksum), pkg.Encode(checksum.Value()), pkg.Encode(step.checksum),

View File

@@ -10,7 +10,8 @@ import (
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
"path"
"hakurei.app/container/check"
) )
const ( const (
@@ -99,6 +100,7 @@ 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
@@ -114,9 +116,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
err = closeErr err = closeErr
} }
}(tr) }(tr)
br := t.cache.getReader(tr) tr = io.NopCloser(tr)
defer t.cache.putReader(br)
tr = io.NopCloser(br)
switch a.compression { switch a.compression {
case TarUncompressed: case TarUncompressed:
@@ -137,24 +137,14 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
} }
type dirTargetPerm struct { type dirTargetPerm struct {
path string path *check.Absolute
mode fs.FileMode mode fs.FileMode
} }
var madeDirectories []dirTargetPerm var madeDirectories []dirTargetPerm
if err = os.MkdirAll(t.GetTempDir().String(), 0700); err != nil { if err = os.MkdirAll(temp.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)
@@ -168,8 +158,9 @@ 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 = root.MkdirAll(path.Dir(header.Name), 0700); err != nil { if err = os.MkdirAll(pathname.Dir().String(), 0700); err != nil {
return return
} }
} }
@@ -177,8 +168,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 = root.OpenFile( if f, err = os.OpenFile(
header.Name, pathname.String(),
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 {
@@ -193,29 +184,26 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
break break
case tar.TypeLink: case tar.TypeLink:
if err = root.Link( if err = os.Link(
header.Linkname, temp.Append(header.Linkname).String(),
header.Name, pathname.String(),
); err != nil { ); err != nil {
return return
} }
break break
case tar.TypeSymlink: case tar.TypeSymlink:
if err = root.Symlink( if err = os.Symlink(header.Linkname, pathname.String()); err != nil {
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: header.Name, path: pathname,
mode: header.FileInfo().Mode(), mode: header.FileInfo().Mode(),
}) })
if err = root.MkdirAll(header.Name, 0700); err != nil { if err = os.MkdirAll(pathname.String(), 0700); err != nil {
return return
} }
break break
@@ -232,7 +220,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 = root.Chmod(e.path, e.mode&0500); err != nil { if err = os.Chmod(e.path.String(), e.mode&0500); err != nil {
return return
} }
} }
@@ -240,7 +228,6 @@ 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

@@ -2,19 +2,18 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newAttr() (pkg.Artifact, string) { func (t Toolchain) newAttr() pkg.Artifact {
const ( const (
version = "2.5.2" version = "2.5.2"
checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l" checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l"
) )
return t.NewPackage("attr", version, pkg.NewHTTPGetTar( return t.NewViaMake("attr", version, t.NewPatchedSource(
"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)
@@ -39,9 +38,7 @@ index f12e4af..6a3c1e9 100644
#include <attr/attributes.h> #include <attr/attributes.h>
-- --
cgit v1.1`}, cgit v1.1`}, [2]string{"musl-errno", `diff --git a/test/attr.test b/test/attr.test
{"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
@@ -55,52 +52,39 @@ index 6ce2f9b..e9bde92 100644
$ 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
`, `,
}, (*MakeHelper)(nil), Configure: [][2]string{
Perl, {"enable-static"},
), version },
},
t.Load(Perl),
)
} }
func init() { func init() { artifactsF[Attr] = Toolchain.newAttr }
artifactsM[Attr] = Metadata{
f: Toolchain.newAttr,
Name: "attr", func (t Toolchain) newACL() pkg.Artifact {
Description: "Commands for Manipulating Filesystem Extended Attributes",
Website: "https://savannah.nongnu.org/projects/attr/",
ID: 137,
}
}
func (t Toolchain) newACL() (pkg.Artifact, string) {
const ( const (
version = "2.3.2" version = "2.3.2"
checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P" checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P"
) )
return t.NewPackage("acl", version, pkg.NewHTTPGetTar( return t.NewViaMake("acl", version, pkg.NewHTTPGetTar(
nil, "https://download.savannah.nongnu.org/releases/acl/"+ nil,
"https://download.savannah.nongnu.org/releases/acl/"+
"acl-"+version+".tar.gz", "acl-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), nil, &MakeHelper{ ), &MakeAttr{
Configure: [][2]string{
{"enable-static"},
},
// makes assumptions about uid_map/gid_map // makes assumptions about uid_map/gid_map
SkipCheck: true, SkipCheck: true,
}, },
Attr, t.Load(Attr),
), version )
}
func init() {
artifactsM[ACL] = Metadata{
f: Toolchain.newACL,
Name: "acl",
Description: "Commands for Manipulating POSIX Access Control Lists",
Website: "https://savannah.nongnu.org/projects/acl/",
ID: 16,
}
} }
func init() { artifactsF[ACL] = Toolchain.newACL }

View File

@@ -1,11 +1,6 @@
package rosa package rosa
import ( import (
"context"
"encoding/json"
"errors"
"net/http"
"strconv"
"sync" "sync"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
@@ -15,44 +10,20 @@ import (
type PArtifact int type PArtifact int
const ( const (
LLVMCompilerRT PArtifact = iota ACL PArtifact = iota
LLVMRuntimes
LLVMClang
// ImageInitramfs is the Rosa OS initramfs archive.
ImageInitramfs
// 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
GMP
GLib
Gawk Gawk
GenInitCPIO GMP
Gettext Gettext
Git Git
Go Go
@@ -61,11 +32,10 @@ const (
Gzip Gzip
Hakurei Hakurei
HakureiDist HakureiDist
Kmod IniConfig
KernelHeaders
LibXau LibXau
Libcap
Libexpat Libexpat
Libiconv
Libpsl Libpsl
Libffi Libffi
Libgd Libgd
@@ -73,53 +43,31 @@ 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
Patch Patch
Perl Perl
PerlLocaleGettext
PerlMIMECharset
PerlModuleBuild
PerlPodParser
PerlSGMLS
PerlTermReadKey
PerlTextCharWidth
PerlTextWrapI18N
PerlUnicodeGCString
PerlYAMLTiny
PkgConfig PkgConfig
Procps Pluggy
PyTest
Pygments
Python Python
PythonIniConfig
PythonPackaging
PythonPluggy
PythonPyTest
PythonPygments
QEMU
Rsync Rsync
Sed Sed
Setuptools Setuptools
SquashfsTools
TamaGo
Tar
Texinfo
Toybox Toybox
toyboxEarly toyboxEarly
Unzip Unzip
UtilLinux utilMacros
Wayland Wayland
WaylandProtocols WaylandProtocols
XCB XCB
@@ -127,150 +75,101 @@ const (
Xproto Xproto
XZ XZ
Zlib Zlib
Zstd
// PresetUnexportedStart is the first unexported preset. buildcatrust
PresetUnexportedStart
buildcatrust = iota - 1
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 // _presetEnd is the total number of presets and does not denote a preset.
// part of the [Std] toolchain. _presetEnd
Stage0
// PresetEnd is the total number of presets and does not denote a preset.
PresetEnd
) )
// Metadata is stage-agnostic information of a [PArtifact] not directly
// representable in the resulting [pkg.Artifact].
type Metadata struct {
f func(t Toolchain) (a pkg.Artifact, version string)
// Unique package name.
Name string `json:"name"`
// Short user-facing description.
Description string `json:"description"`
// Project home page.
Website string `json:"website,omitempty"`
// Project identifier on [Anitya].
//
// [Anitya]: https://release-monitoring.org/
ID int `json:"-"`
// Optional custom version checking behaviour.
latest func(v *Versions) string
}
// GetLatest returns the latest version described by v.
func (meta *Metadata) GetLatest(v *Versions) string {
if meta.latest != nil {
return meta.latest(v)
}
return v.Latest
}
// Unversioned denotes an unversioned [PArtifact].
const Unversioned = "\x00"
// UnpopulatedIDError is returned by [Metadata.GetLatest] for an instance of
// [Metadata] where ID is not populated.
type UnpopulatedIDError struct{}
func (UnpopulatedIDError) Unwrap() error { return errors.ErrUnsupported }
func (UnpopulatedIDError) Error() string { return "Anitya ID is not populated" }
// Versions are package versions returned by Anitya.
type Versions struct {
// The latest version for the project, as determined by the version sorting algorithm.
Latest string `json:"latest_version"`
// List of all versions that arent flagged as pre-release.
Stable []string `json:"stable_versions"`
// List of all versions stored, sorted from newest to oldest.
All []string `json:"versions"`
}
// getStable returns the first Stable version, or Latest if that is unavailable.
func (v *Versions) getStable() string {
if len(v.Stable) == 0 {
return v.Latest
}
return v.Stable[0]
}
// GetVersions returns versions fetched from Anitya.
func (meta *Metadata) GetVersions(ctx context.Context) (*Versions, error) {
if meta.ID == 0 {
return nil, UnpopulatedIDError{}
}
var resp *http.Response
if req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"https://release-monitoring.org/api/v2/versions/?project_id="+
strconv.Itoa(meta.ID),
nil,
); err != nil {
return nil, err
} else {
req.Header.Set("User-Agent", "Rosa/1.1")
if resp, err = http.DefaultClient.Do(req); err != nil {
return nil, err
}
}
var v Versions
err := json.NewDecoder(resp.Body).Decode(&v)
return &v, errors.Join(err, resp.Body.Close())
}
var ( var (
// artifactsM is an array of [PArtifact] metadata. // artifactsF is an array of functions for the result of [PArtifact].
artifactsM [PresetEnd]Metadata artifactsF [_presetEnd]func(t Toolchain) pkg.Artifact
// artifacts stores the result of Metadata.f. // artifacts stores the result of artifactsF.
artifacts [_toolchainEnd][len(artifactsM)]pkg.Artifact artifacts [_toolchainEnd][len(artifactsF)]pkg.Artifact
// versions stores the version of [PArtifact].
versions [_toolchainEnd][len(artifactsM)]string
// artifactsOnce is for lazy initialisation of artifacts. // artifactsOnce is for lazy initialisation of artifacts.
artifactsOnce [_toolchainEnd][len(artifactsM)]sync.Once artifactsOnce [_toolchainEnd][len(artifactsF)]sync.Once
) )
// GetMetadata returns [Metadata] of a [PArtifact].
func GetMetadata(p PArtifact) *Metadata { return &artifactsM[p] }
// Load returns the resulting [pkg.Artifact] of [PArtifact]. // Load returns the resulting [pkg.Artifact] of [PArtifact].
func (t Toolchain) Load(p PArtifact) pkg.Artifact { func (t Toolchain) Load(p PArtifact) pkg.Artifact {
artifactsOnce[t][p].Do(func() { artifactsOnce[t][p].Do(func() {
artifacts[t][p], versions[t][p] = artifactsM[p].f(t) artifacts[t][p] = artifactsF[p](t)
}) })
return artifacts[t][p] return artifacts[t][p]
} }
// Version returns the version string of [PArtifact].
func (t Toolchain) Version(p PArtifact) string {
artifactsOnce[t][p].Do(func() {
artifacts[t][p], versions[t][p] = artifactsM[p].f(t)
})
return versions[t][p]
}
// 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) {
for i := range PresetUnexportedStart { p, ok = map[string]PArtifact{
if name == artifactsM[i].Name { "acl": ACL,
return i, true "attr": Attr,
} "autoconf": Autoconf,
} "automake": Automake,
return 0, false "bash": Bash,
"binutils": Binutils,
"cmake": CMake,
"coreutils": Coreutils,
"curl": Curl,
"diffutils": Diffutils,
"findutils": Findutils,
"fuse": Fuse,
"gawk": Gawk,
"gmp": GMP,
"gettext": Gettext,
"git": Git,
"go": Go,
"gperf": Gperf,
"grep": Grep,
"gzip": Gzip,
"hakurei": Hakurei,
"hakurei-dist": HakureiDist,
"iniconfig": IniConfig,
"kernel-headers": KernelHeaders,
"libXau": LibXau,
"libexpat": Libexpat,
"libpsl": Libpsl,
"libseccomp": Libseccomp,
"libucontext": Libucontext,
"libxml2": Libxml2,
"libffi": Libffi,
"libgd": Libgd,
"libtool": Libtool,
"m4": M4,
"mpc": MPC,
"mpfr": MPFR,
"make": Make,
"meson": Meson,
"mksh": Mksh,
"nss": NSS,
"nss-cacert": NSSCACert,
"ninja": Ninja,
"openssl": OpenSSL,
"packaging": Packaging,
"patch": Patch,
"perl": Perl,
"pkg-config": PkgConfig,
"pluggy": Pluggy,
"pytest": PyTest,
"pygments": Pygments,
"python": Python,
"rsync": Rsync,
"sed": Sed,
"setuptools": Setuptools,
"toybox": Toybox,
"unzip": Unzip,
"wayland": Wayland,
"wayland-protocols": WaylandProtocols,
"xcb": XCB,
"xcb-proto": XCBProto,
"xproto": Xproto,
"xz": XZ,
"zlib": Zlib,
}[name]
return
} }

View File

@@ -1,66 +0,0 @@
package rosa_test
import (
"testing"
"hakurei.app/internal/rosa"
)
func TestLoad(t *testing.T) {
t.Parallel()
for i := range rosa.PresetEnd {
p := rosa.PArtifact(i)
t.Run(rosa.GetMetadata(p).Name, func(t *testing.T) {
t.Parallel()
rosa.Std.Load(p)
})
}
}
func TestResolveName(t *testing.T) {
t.Parallel()
for i := range rosa.PresetUnexportedStart {
p := i
name := rosa.GetMetadata(p).Name
t.Run(name, func(t *testing.T) {
t.Parallel()
if got, ok := rosa.ResolveName(name); !ok {
t.Fatal("ResolveName: ok = false")
} else if got != p {
t.Fatalf("ResolveName: %d, want %d", got, p)
}
})
}
}
func TestResolveNameUnexported(t *testing.T) {
t.Parallel()
for i := rosa.PresetUnexportedStart; i < rosa.PresetEnd; i++ {
p := i
name := rosa.GetMetadata(p).Name
t.Run(name, func(t *testing.T) {
t.Parallel()
if got, ok := rosa.ResolveName(name); ok {
t.Fatalf("ResolveName: resolved unexported preset %d", got)
}
})
}
}
func TestUnique(t *testing.T) {
t.Parallel()
names := make(map[string]struct{})
for i := range rosa.PresetEnd {
name := rosa.GetMetadata(rosa.PArtifact(i)).Name
if _, ok := names[name]; ok {
t.Fatalf("name %s is not unique", name)
}
names[name] = struct{}{}
}
}

View File

@@ -1,36 +0,0 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newArgpStandalone() (pkg.Artifact, string) {
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,
), version
}
func init() {
artifactsM[ArgpStandalone] = Metadata{
f: Toolchain.newArgpStandalone,
Name: "argp-standalone",
Description: "hierarchical argument parsing library broken out from glibc",
Website: "http://www.lysator.liu.se/~nisse/misc/",
}
}

View File

@@ -1,38 +0,0 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newBzip2() (pkg.Artifact, string) {
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",
}), version
}
func init() {
artifactsM[Bzip2] = Metadata{
f: Toolchain.newBzip2,
Name: "bzip2",
Description: "a freely available, patent free, high-quality data compressor",
Website: "https://sourceware.org/bzip2/",
ID: 237,
}
}

View File

@@ -1,169 +1,82 @@
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, string) { func (t Toolchain) newCMake() pkg.Artifact {
const ( const (
version = "4.2.3" version = "4.2.1"
checksum = "Y4uYGnLrDQX78UdzH7fMzfok46Nt_1taDIHSmqgboU1yFi6f0iAXBDegMCu4eS-J" checksum = "Y3OdbMsob6Xk2y1DCME6z4Fryb5_TkFD7knRT8dTNIRtSqbiCJyyDN9AxggN_I75"
) )
return t.NewPackage("cmake", version, pkg.NewHTTPGetTar( return t.New("cmake-"+version, 0, []pkg.Artifact{
t.Load(Make),
t.Load(KernelHeaders),
}, nil, nil, `
cd "$(mktemp -d)"
/usr/src/cmake/bootstrap \
--prefix=/system \
--parallel="$(nproc)" \
-- \
-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
"cmake", version, pkg.NewHTTPGetTar(
nil, "https://github.com/Kitware/CMake/releases/download/"+ nil, "https://github.com/Kitware/CMake/releases/download/"+
"v"+version+"/cmake-"+version+".tar.gz", "v"+version+"/cmake-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &PackageAttr{ ), false,
// test suite expects writable source tree )))
Writable: true,
// expected to be writable in the copy made during bootstrap
Chmod: true,
Patches: [][2]string{
{"bootstrap-test-no-openssl", `diff --git a/Tests/BootstrapTest.cmake b/Tests/BootstrapTest.cmake
index 137de78bc1..b4da52e664 100644
--- 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,
), version
} }
func init() { func init() { artifactsF[CMake] = Toolchain.newCMake }
artifactsM[CMake] = Metadata{
f: Toolchain.newCMake,
Name: "cmake",
Description: "cross-platform, open-source build system",
Website: "https://cmake.org/",
ID: 306,
}
}
// CMakeHelper is the [CMake] build system helper.
type CMakeHelper struct {
// Joined with name with a dash if non-empty.
Variant string
// CMakeAttr holds the project-specific attributes that will be applied to a new
// [pkg.Artifact] compiled via [CMake].
type CMakeAttr struct {
// 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
} }
var _ Helper = new(CMakeHelper) // NewViaCMake returns a [pkg.Artifact] for compiling and installing via [CMake].
func (t Toolchain) NewViaCMake(
// name returns its arguments and an optional variant string joined with '-'. name, version, variant string,
func (attr *CMakeHelper) name(name, version string) string { source pkg.Artifact,
if attr != nil && attr.Variant != "" { attr *CMakeAttr,
name += "-" + attr.Variant extra ...pkg.Artifact,
) 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 = &CMakeHelper{ attr = &CMakeAttr{
Cache: [][2]string{ Cache: [][2]string{
{"CMAKE_BUILD_TYPE", "Release"}, {"CMAKE_BUILD_TYPE", "Release"},
}, },
@@ -173,21 +86,40 @@ func (attr *CMakeHelper) script(name string) string {
panic("CACHE must be non-empty") panic("CACHE must be non-empty")
} }
return ` scriptConfigured := "cmake --build .\ncmake --install .\n"
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=/work/system \ -DCMAKE_INSTALL_PREFIX="${ROSA_INSTALL_PREFIX}" \
'/usr/src/` + name + `/` + path.Join(attr.Append...) + `' "${ROSA_CMAKE_SOURCE}"
cmake --build . `+scriptConfigured+attr.Script, slices.Concat([]pkg.ExecPath{
cmake --install . pkg.Path(sourcePath, attr.Writable, source),
` + attr.Script }, attr.Paths)...)
} }

View File

@@ -2,39 +2,31 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newCurl() (pkg.Artifact, string) { func (t Toolchain) newCurl() pkg.Artifact {
const ( const (
version = "8.18.0" version = "8.18.0"
checksum = "YpOolP_sx1DIrCEJ3elgVAu0wTLDS-EZMZFvOP0eha7FaLueZUlEpuMwDzJNyi7i" checksum = "YpOolP_sx1DIrCEJ3elgVAu0wTLDS-EZMZFvOP0eha7FaLueZUlEpuMwDzJNyi7i"
) )
return t.NewPackage("curl", version, pkg.NewHTTPGetTar( return t.NewViaMake("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,
), nil, &MakeHelper{ ), &MakeAttr{
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"},
}, },
Check: []string{ ScriptConfigured: `
"TFLAGS=-j256", make "-j$(nproc)"
"check", `,
}, },
}, t.Load(Perl),
Perl,
Libpsl, t.Load(Libpsl),
OpenSSL, t.Load(OpenSSL),
), version )
}
func init() {
artifactsM[Curl] = Metadata{
f: Toolchain.newCurl,
Name: "curl",
Description: "command line tool and library for transferring data with URLs",
Website: "https://curl.se/",
ID: 381,
}
} }
func init() { artifactsF[Curl] = Toolchain.newCurl }

View File

@@ -1,43 +0,0 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newDTC() (pkg.Artifact, string) {
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,
), version
}
func init() {
artifactsM[DTC] = Metadata{
f: Toolchain.newDTC,
Name: "dtc",
Description: "The Device Tree Compiler",
Website: "https://git.kernel.org/pub/scm/utils/dtc/dtc.git/",
ID: 16911,
}
}

View File

@@ -1,51 +0,0 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newElfutils() (pkg.Artifact, string) {
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,
), version
}
func init() {
artifactsM[Elfutils] = Metadata{
f: Toolchain.newElfutils,
Name: "elfutils",
Description: "utilities and libraries to handle ELF files and DWARF data",
Website: "https://sourceware.org/elfutils/",
ID: 5679,
}
}

View File

@@ -1,61 +0,0 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newFakeroot() (pkg.Artifact, string) {
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,
), version
}
func init() {
artifactsM[Fakeroot] = Metadata{
f: Toolchain.newFakeroot,
Name: "fakeroot",
Description: "tool for simulating superuser privileges",
Website: "https://salsa.debian.org/clint/fakeroot",
ID: 12048,
}
}

View File

@@ -1,31 +0,0 @@
package rosa
import (
"hakurei.app/internal/pkg"
)
func (t Toolchain) newFlex() (pkg.Artifact, string) {
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,
), version
}
func init() {
artifactsM[Flex] = Metadata{
f: Toolchain.newFlex,
Name: "flex",
Description: "scanner generator for lexing in C and C++",
Website: "https://github.com/westes/flex/",
ID: 819,
}
}

View File

@@ -2,45 +2,44 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newFuse() (pkg.Artifact, string) { func (t Toolchain) newFuse() pkg.Artifact {
const ( const (
version = "3.18.1" version = "3.18.1"
checksum = "COb-BgJRWXLbt9XUkNeuiroQizpMifXqxgieE1SlkMXhs_WGSyJStrmyewAw2hd6" checksum = "COb-BgJRWXLbt9XUkNeuiroQizpMifXqxgieE1SlkMXhs_WGSyJStrmyewAw2hd6"
) )
return t.NewPackage("fuse", version, pkg.NewHTTPGetTar( return t.New("fuse-"+version, 0, []pkg.Artifact{
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,
},
PythonIniConfig,
PythonPackaging,
PythonPluggy,
PythonPygments,
PythonPyTest,
KernelHeaders,
), version
}
func init() {
artifactsM[Fuse] = Metadata{
f: Toolchain.newFuse,
Name: "fuse",
Description: "the reference implementation of the Linux FUSE interface",
Website: "https://github.com/libfuse/libfuse/",
ID: 861,
}
} }
func init() { artifactsF[Fuse] = Toolchain.newFuse }

View File

@@ -1,30 +1,40 @@
package rosa package rosa
import "hakurei.app/internal/pkg" import (
"hakurei.app/internal/pkg"
)
func (t Toolchain) newGit() (pkg.Artifact, string) { func (t Toolchain) newGit() pkg.Artifact {
const ( const (
version = "2.53.0" version = "2.52.0"
checksum = "rlqSTeNgSeVKJA7nvzGqddFH8q3eFEPB4qRZft-4zth8wTHnbTbm7J90kp_obHGm" checksum = "uH3J1HAN_c6PfGNJd2OBwW4zo36n71wmkdvityYnrh8Ak0D1IifiAvEWz9Vi9DmS"
) )
return t.NewPackage("git", version, pkg.NewHTTPGetTar( return t.NewViaMake("git", version, t.NewPatchedSource(
"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,
ScriptEarly: ` ), &MakeAttr{
ln -s ../../system/bin/perl /usr/bin/ || true
`,
// uses source tree as scratch space // uses source tree as scratch space
EnterSource: true, Writable: true,
}, &MakeHelper{
InPlace: true, InPlace: true,
Generate: "make configure",
ScriptMakeEarly: ` // test suite in subdirectory
SkipCheck: true,
Make: []string{"all"},
ScriptEarly: `
cd /usr/src/git
make configure
`,
Script: `
ln -s ../../system/bin/perl /usr/bin/ || true
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
@@ -46,36 +56,26 @@ 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,
Zlib, t.Load(Zlib),
Curl, t.Load(Curl),
OpenSSL, t.Load(OpenSSL),
Libexpat, t.Load(Libexpat),
), version )
}
func init() {
artifactsM[Git] = Metadata{
f: Toolchain.newGit,
Name: "git",
Description: "distributed version control system",
Website: "https://www.git-scm.com/",
ID: 5350,
}
} }
func init() { artifactsF[Git] = Toolchain.newGit }
// NewViaGit returns a [pkg.Artifact] for cloning a git repository. // NewViaGit returns a [pkg.Artifact] for cloning a git repository.
func (t Toolchain) NewViaGit( func (t Toolchain) NewViaGit(

File diff suppressed because it is too large Load Diff

View File

@@ -62,12 +62,10 @@ ln -s \
))) )))
} }
func (t Toolchain) newGoLatest() (pkg.Artifact, string) { func (t Toolchain) newGoLatest() pkg.Artifact {
var ( var (
bootstrapEnv []string bootstrapEnv []string
bootstrapExtra []pkg.Artifact bootstrapExtra []pkg.Artifact
finalEnv []string
) )
switch runtime.GOARCH { switch runtime.GOARCH {
case "amd64": case "amd64":
@@ -90,8 +88,6 @@ func (t Toolchain) newGoLatest() (pkg.Artifact, string) {
t.Load(gcc), t.Load(gcc),
) )
finalEnv = append(finalEnv, "CGO_ENABLED=0")
default: default:
panic("unsupported target " + runtime.GOARCH) panic("unsupported target " + runtime.GOARCH)
} }
@@ -102,7 +98,6 @@ func (t Toolchain) newGoLatest() (pkg.Artifact, string) {
append(bootstrapEnv, "CGO_ENABLED=0"), ` append(bootstrapEnv, "CGO_ENABLED=0"), `
rm \ rm \
crypto/tls/handshake_client_test.go \ crypto/tls/handshake_client_test.go \
cmd/pprof/pprof_test.go \
os/os_unix_test.go os/os_unix_test.go
sed -i \ sed -i \
's/os\.Getenv("GCCGO")$/"nonexistent"/' \ 's/os\.Getenv("GCCGO")$/"nonexistent"/' \
@@ -133,7 +128,7 @@ echo \
go123 := t.newGo( go123 := t.newGo(
"1.23.12", "1.23.12",
"wcI32bl1tkqbgcelGtGWPI4RtlEddd-PTd76Eb-k7nXA5LbE9yTNdIL9QSOOxMOs", "wcI32bl1tkqbgcelGtGWPI4RtlEddd-PTd76Eb-k7nXA5LbE9yTNdIL9QSOOxMOs",
[]string{"CGO_ENABLED=0"}, ` nil, `
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
@@ -141,9 +136,9 @@ sed -i \
) )
go125 := t.newGo( go125 := t.newGo(
"1.25.7", "1.25.6",
"fyylHdBVRUobnBjYj3NKBaYPUw3kGmo2mEELiZonOYurPfbarNU1x77B99Fjut7Q", "x0z430qoDvQbbw_fftjW0rh_GSoh0VJhPzttWk_0hj9yz9AKOjuwRMupF_Q0dbt7",
[]string{"CGO_ENABLED=0"}, ` nil, `
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,31 +148,6 @@ rm \
`, go123, `, go123,
) )
const ( return go125
version = "1.26.1"
checksum = "DdC5Ea-aCYPUHNObQh_09uWU0vn4e-8Ben850Vq-5OoamDRrXhuYI4YQ_BOFgaT0"
)
return t.newGo(
version,
checksum,
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,
), version
}
func init() {
artifactsM[Go] = Metadata{
f: Toolchain.newGoLatest,
Name: "go",
Description: "the Go programming language toolchain",
Website: "https://go.dev/",
ID: 1227,
}
} }
func init() { artifactsF[Go] = Toolchain.newGoLatest }

View File

@@ -1,61 +0,0 @@
package rosa
import (
"strings"
"hakurei.app/container/fhs"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newGLib() (pkg.Artifact, string) {
const (
version = "2.87.3"
checksum = "iKSLpzZZVfmAZZmqfO1y6uHdlIks4hzPWrqeUCp4ZeQjrPFA3aAa4OmrBYMNS-Si"
)
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,
PythonPackaging,
Bash,
PCRE2,
Libffi,
Zlib,
), version
}
func init() {
artifactsM[GLib] = Metadata{
f: Toolchain.newGLib,
Name: "glib",
Description: "the GNU library of miscellaneous stuff",
Website: "https://developer.gnome.org/glib/",
ID: 10024,
}
}

View File

@@ -1,9 +1,15 @@
package rosa package rosa
import "hakurei.app/internal/pkg" import (
"hakurei.app/internal/pkg"
)
func (t Toolchain) newHakurei(suffix, script string) pkg.Artifact { func (t Toolchain) newHakurei(suffix, script string) pkg.Artifact {
return t.New("hakurei"+suffix+"-"+hakureiVersion, 0, []pkg.Artifact{ const (
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),
@@ -37,10 +43,201 @@ echo
chmod -R +w /usr/src/hakurei chmod -R +w /usr/src/hakurei
cd /usr/src/hakurei cd /usr/src/hakurei
HAKUREI_VERSION='v`+hakureiVersion+`' HAKUREI_VERSION='v`+version+`'
`+script, pkg.Path(AbsUsrSrc.Append("hakurei"), true, t.NewPatchedSource( `+script, pkg.Path(AbsUsrSrc.Append("hakurei"), true, t.NewPatchedSource("hakurei", version, pkg.NewHTTPGetTar(
"hakurei", hakureiVersion, hakureiSource, true, hakureiPatches..., nil, "https://git.gensokyo.uk/security/hakurei/archive/"+
)), pkg.Path(AbsUsrSrc.Append("hostname", "main.go"), false, pkg.NewFile( "v"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), true, [2]string{"dist", `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)`}),
), pkg.Path(AbsUsrSrc.Append("hostname", "main.go"), false, pkg.NewFile(
"hostname.go", "hostname.go",
[]byte(` []byte(`
package main package main
@@ -58,8 +255,7 @@ func main() {
))) )))
} }
func init() { func init() {
artifactsM[Hakurei] = Metadata{ artifactsF[Hakurei] = func(t Toolchain) pkg.Artifact {
f: func(t Toolchain) (pkg.Artifact, string) {
return t.newHakurei("", ` return t.newHakurei("", `
mkdir -p /work/system/libexec/hakurei/ mkdir -p /work/system/libexec/hakurei/
@@ -67,7 +263,6 @@ 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
@@ -76,7 +271,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= -linkmode external -extldflags=-static' ./... go test -ldflags='-buildid= -extldflags=-static' ./...
echo echo
mkdir -p /work/system/bin/ mkdir -p /work/system/bin/
@@ -84,25 +279,12 @@ mkdir -p /work/system/bin/
hakurei \ hakurei \
sharefs \ sharefs \
../../bin/) ../../bin/)
`), hakureiVersion `)
},
Name: "hakurei",
Description: "low-level userspace tooling for Rosa OS",
Website: "https://hakurei.app/",
ID: 388834,
} }
artifactsM[HakureiDist] = Metadata{ artifactsF[HakureiDist] = func(t Toolchain) pkg.Artifact {
f: func(t Toolchain) (pkg.Artifact, string) {
return t.newHakurei("-dist", ` return t.newHakurei("-dist", `
export HAKUREI_VERSION export HAKUREI_VERSION
DESTDIR=/work /usr/src/hakurei/dist/release.sh DESTDIR=/work /usr/src/hakurei/dist/release.sh
`), hakureiVersion `)
},
Name: "hakurei-dist",
Description: "low-level userspace tooling for Rosa OS (distribution tarball)",
Website: "https://hakurei.app/",
} }
} }

View File

@@ -1,26 +0,0 @@
//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

@@ -1,51 +0,0 @@
//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{
`},
}

View File

@@ -1,26 +0,0 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newImageInitramfs() (pkg.Artifact, string) {
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
`)))), Unversioned
}
func init() {
artifactsM[ImageInitramfs] = Metadata{
Name: "initramfs-image",
Description: "Rosa OS initramfs image",
f: Toolchain.newImageInitramfs,
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
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

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

View File

@@ -1,43 +0,0 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newKmod() (pkg.Artifact, string) {
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,
), version
}
func init() {
artifactsM[Kmod] = Metadata{
f: Toolchain.newKmod,
Name: "kmod",
Description: "a set of tools to handle common tasks with Linux kernel modules",
Website: "https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git",
ID: 1517,
}
}

View File

@@ -1,54 +0,0 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newLibcap() (pkg.Artifact, string) {
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,
), version
}
func init() {
artifactsM[Libcap] = Metadata{
f: Toolchain.newLibcap,
Name: "libcap",
Description: "a library for getting and setting POSIX.1e draft 15 capabilities",
Website: "https://sites.google.com/site/fullycapable/",
ID: 1569,
}
}

View File

@@ -6,29 +6,23 @@ import (
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
) )
func (t Toolchain) newLibexpat() (pkg.Artifact, string) { func (t Toolchain) newLibexpat() pkg.Artifact {
const ( const (
version = "2.7.4" version = "2.7.3"
checksum = "W6NI2FESBjrTqRPcvs15fK5c3nwF6f9RT8U-XHKQKblXVzJB3nt_ez5B5jO0ZVDG" checksum = "GmkoD23nRi9cMT0cgG1XRMrZWD82UcOMzkkvP1gkwSFWCBgeSXMuoLpa8-v8kxW-"
) )
return t.NewPackage("libexpat", version, pkg.NewHTTPGetTar( return t.NewViaMake("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,
), nil, (*MakeHelper)(nil), ), &MakeAttr{
Bash, Configure: [][2]string{
), version {"enable-static"},
} },
func init() { },
artifactsM[Libexpat] = Metadata{ t.Load(Bash),
f: Toolchain.newLibexpat, )
Name: "libexpat",
Description: "a stream-oriented XML parser library",
Website: "https://libexpat.github.io/",
ID: 770,
}
} }
func init() { artifactsF[Libexpat] = Toolchain.newLibexpat }

View File

@@ -2,28 +2,22 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newLibffi() (pkg.Artifact, string) { func (t Toolchain) newLibffi() pkg.Artifact {
const ( const (
version = "3.5.2" version = "3.4.5"
checksum = "2_Q-ZNBBbVhltfL5zEr0wljxPegUimTK4VeMSiwJEGksls3n4gj3lV0Ly3vviSFH" checksum = "apIJzypF4rDudeRoI_n3K7N-zCeBLTbQlHRn9NSAZqdLAWA80mR0gXPTpHsL7oMl"
) )
return t.NewPackage("libffi", version, pkg.NewHTTPGetTar( return t.NewViaMake("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,
), nil, (*MakeHelper)(nil), ), &MakeAttr{
KernelHeaders, Configure: [][2]string{
), version {"enable-static"},
} },
func init() { },
artifactsM[Libffi] = Metadata{ t.Load(KernelHeaders),
f: Toolchain.newLibffi, )
Name: "libffi",
Description: "a portable, high level programming interface to various calling conventions",
Website: "https://sourceware.org/libffi/",
ID: 1611,
}
} }
func init() { artifactsF[Libffi] = Toolchain.newLibffi }

View File

@@ -2,35 +2,29 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newLibgd() (pkg.Artifact, string) { func (t Toolchain) newLibgd() pkg.Artifact {
const ( const (
version = "2.3.3" version = "2.3.3"
checksum = "8T-sh1_FJT9K9aajgxzh8ot6vWIF-xxjcKAHvTak9MgGUcsFfzP8cAvvv44u2r36" checksum = "8T-sh1_FJT9K9aajgxzh8ot6vWIF-xxjcKAHvTak9MgGUcsFfzP8cAvvv44u2r36"
) )
return t.NewPackage("libgd", version, pkg.NewHTTPGetTar( return t.NewViaMake("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,
), &PackageAttr{ ), &MakeAttr{
OmitDefaults: true,
Env: []string{ Env: []string{
"TMPDIR=/dev/shm/gd", "TMPDIR=/dev/shm/gd",
}, },
ScriptEarly: ` ScriptEarly: `
mkdir /dev/shm/gd mkdir /dev/shm/gd
`, `,
}, (*MakeHelper)(nil), Configure: [][2]string{
Zlib, {"enable-static"},
), version },
} },
func init() { t.Load(Zlib),
artifactsM[Libgd] = Metadata{ )
f: Toolchain.newLibgd,
Name: "libgd",
Description: "an open source code library for the dynamic creation of images",
Website: "https://libgd.github.io/",
ID: 880,
}
} }
func init() { artifactsF[Libgd] = Toolchain.newLibgd }

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