aboutsummaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-10-06 22:23:20 +0900
committerOphestra <cat@gensokyo.uk>2026-10-06 23:09:19 +0900
commita9e2749f6654d0aa07b274a45c9177d10323f80a (patch)
tree291f5b23a67af036cbe5d374b3d3fb0240fc438c /test
parent19f36491f2e2a5029ac396c10408d653cad6c81b (diff)
internal/testsuite: move from test
This structure is a lot less clumsy than the old nix-centric layout. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'test')
-rw-r--r--test/hakurei/configuration.nix252
-rw-r--r--test/hakurei/default.nix81
-rw-r--r--test/hakurei/flake.lock49
-rw-r--r--test/hakurei/flake.nix76
-rw-r--r--test/hakurei/hsu.nix23
-rw-r--r--test/hakurei/nixos.nix407
-rw-r--r--test/hakurei/options.nix364
-rw-r--r--test/hakurei/package.nix144
-rw-r--r--test/hakurei/test.py315
-rw-r--r--test/internal/mountinfo/mountinfo.go177
-rw-r--r--test/internal/mountinfo/mountinfo_guard.go15
-rw-r--r--test/internal/mountinfo/mountinfo_test.go146
-rw-r--r--test/internal/testsuite/fs.go129
-rw-r--r--test/internal/testsuite/fs_test.go85
-rw-r--r--test/internal/testsuite/proc.go353
-rw-r--r--test/internal/testsuite/proc_test.go33
-rw-r--r--test/internal/testsuite/ptrace.go144
-rw-r--r--test/internal/testsuite/ptrace_test.go13
-rw-r--r--test/internal/testsuite/testsuite.go290
-rw-r--r--test/internal/testsuite/testsuite_guard.go15
-rw-r--r--test/internal/testsuite/testsuite_root.go17
-rw-r--r--test/sandbox/main.go409
-rw-r--r--test/sandbox/seccomp.patch18
-rw-r--r--test/sandbox/testdata/device.go134
-rw-r--r--test/sandbox/testdata/mapuid.go124
-rw-r--r--test/sandbox/testdata/pdlike.go141
-rw-r--r--test/sandbox/testdata/simple.go140
-rw-r--r--test/sandbox/testdata/sum.go22
-rw-r--r--test/sandbox/testdata/sum_amd64.go9
-rw-r--r--test/sandbox/testdata/sum_arm64.go9
-rw-r--r--test/sandbox/testdata/testdata.go125
-rw-r--r--test/sandbox/testdata/tty.go145
-rw-r--r--test/sandbox/tester/main.go224
-rw-r--r--test/sharefs/main.go119
-rw-r--r--test/sharefs/raceattr.go122
35 files changed, 0 insertions, 4869 deletions
diff --git a/test/hakurei/configuration.nix b/test/hakurei/configuration.nix
deleted file mode 100644
index 62d8239d..00000000
--- a/test/hakurei/configuration.nix
+++ /dev/null
@@ -1,252 +0,0 @@
-{
- lib,
- pkgs,
- config,
- ...
-}:
-{
- users.users = {
- alice = {
- isNormalUser = true;
- description = "Alice Foobar";
- password = "foobar";
- uid = 1000;
- };
- untrusted = {
- isNormalUser = true;
- description = "Untrusted user";
- password = "foobar";
- uid = 1001;
-
- # For deny unmapped uid test:
- packages = [ config.environment.hakurei.package ];
- };
- };
-
- home-manager.users.alice.home.stateVersion = "24.11";
-
- # Automatically login on tty1 as a normal user:
- services.getty.autologinUser = "alice";
-
- security.pam.loginLimits = [
- {
- domain = "@users";
- item = "rtprio";
- type = "-";
- value = 1;
- }
- ];
-
- environment = {
- systemPackages = with pkgs; [
- # For D-Bus tests:
- mako
- libnotify
- ];
-
- variables = {
- SWAYSOCK = "/tmp/sway-ipc.sock";
- WLR_RENDERER = "pixman";
- };
-
- # To help with OCR:
- etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
- main = {
- font = "inconsolata:size=14";
- };
- colors = rec {
- foreground = "000000";
- background = "ffffff";
- regular2 = foreground;
- };
- };
- };
-
- fonts.packages = [ pkgs.inconsolata ];
-
- # 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;
-
- # For PulseAudio tests:
- security.rtkit.enable = true;
- services.pipewire = {
- enable = true;
- alsa.enable = true;
- alsa.support32Bit = true;
- pulse.enable = true;
- jack.enable = true;
- };
-
- virtualisation = {
- # Hopefully reduces spurious test failures:
- memorySize = if pkgs.stdenv.hostPlatform.is32bit then 2046 else 8192;
-
- 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 Go test compiler performance:
- "-smp 16"
- ];
- };
-
- # Disk image is too small for some tests:
- boot.tmp.useTmpfs = true;
-
- environment.hakurei = {
- enable = true;
- stateDir = "/var/lib/hakurei";
- users.alice = 0;
-
- extraHomeConfig =
- { config, ... }:
- {
- # To test merge deduplication:
- options._hakurei.stateVersion = lib.mkOption { type = lib.types.str; };
-
- config = {
- home = { inherit (config._hakurei) stateVersion; };
- _hakurei.stateVersion = "23.05";
- };
- };
-
- commonPaths = [
- {
- type = "bind";
- src = "/var/tmp";
- write = true;
- }
- ];
-
- apps = {
- "cat.gensokyo.extern.bash.linger-timeout" = {
- name = "hakurei-check-linger-timeout";
- identity = 9999;
- share = pkgs.bash;
- packages = [ pkgs.bash ];
- command = ''
- sleep infinity & disown
- exit
- '';
- wait_delay = 1;
- enablements = {
- wayland = false;
- pipewire = false;
- };
- };
-
- "cat.gensokyo.extern.foot.noEnablements" = {
- name = "ne-foot";
- identity = 1;
- shareUid = true;
- verbose = true;
- share = pkgs.foot;
- packages = with pkgs; [
- foot
-
- # For wayland-info:
- wayland-utils
- ];
- command = "foot";
- enablements = {
- dbus = false;
- pipewire = false;
- };
- };
-
- "cat.gensokyo.extern.foot.noEnablements.immediate" = {
- name = "ne-foot-immediate";
- identity = 1;
- shareUid = true;
- verbose = true;
- wait_delay = -1;
- share = pkgs.foot;
- packages = [ ];
- command = "foot";
- enablements = {
- dbus = false;
- pipewire = false;
- };
- };
-
- "cat.gensokyo.extern.foot.pulseaudio" = {
- name = "pa-foot";
- identity = 2;
- verbose = true;
- share = pkgs.foot;
- packages = [ pkgs.foot ];
- command = "foot";
- enablements.dbus = false;
- };
-
- "cat.gensokyo.extern.Alacritty.x11" = {
- name = "x11-alacritty";
- identity = 1;
- shareUid = true;
- verbose = true;
- share = pkgs.alacritty;
- packages = with pkgs; [
- # For X11 terminal emulator:
- alacritty
-
- # For glinfo:
- mesa-demos
- ];
- command = "alacritty";
- enablements = {
- wayland = false;
- x11 = true;
- dbus = false;
- pipewire = false;
- };
- };
-
- "cat.gensokyo.extern.foot.directWayland" = {
- name = "da-foot";
- identity = 4;
- verbose = true;
- insecureWayland = true;
- share = pkgs.foot;
- packages = with pkgs; [
- foot
-
- # For wayland-info:
- wayland-utils
- ];
- command = "foot";
- enablements = {
- dbus = false;
- pipewire = false;
- };
- };
-
- "cat.gensokyo.extern.strace.wantFail" = {
- name = "strace-failure";
- identity = 5;
- verbose = true;
- share = pkgs.strace;
- command = "strace true";
- enablements = {
- wayland = false;
- x11 = false;
- dbus = false;
- pipewire = false;
- };
- };
- };
- };
-}
diff --git a/test/hakurei/default.nix b/test/hakurei/default.nix
deleted file mode 100644
index 81daa0a2..00000000
--- a/test/hakurei/default.nix
+++ /dev/null
@@ -1,81 +0,0 @@
-{
- lib,
- testers,
- buildFHSEnv,
- writeShellScriptBin,
-
- system,
- self,
- withRace ? false,
-}:
-
-testers.nixosTest {
- name = "hakurei" + (if withRace then "-race" else "");
- nodes.machine =
- { options, pkgs, ... }:
- let
- fhs =
- let
- hakurei = options.environment.hakurei.package.default;
- in
- buildFHSEnv {
- pname = "hakurei-fhs";
- inherit (hakurei) version;
- targetPkgs = _: hakurei.targetPkgs;
- extraOutputsToInstall = [ "dev" ];
- profile = ''
- export PKG_CONFIG_PATH="/usr/share/pkgconfig:$PKG_CONFIG_PATH"
- '';
- };
- in
- {
- environment.systemPackages = [
- # For go tests:
- (writeShellScriptBin "hakurei-test" ''
- # Assert hst CGO_ENABLED=0: ${
- with pkgs;
- runCommand "hakurei-hst-cgo" { nativeBuildInputs = [ self.packages.${system}.hakurei.go ]; } ''
- cp -r ${options.environment.hakurei.package.default.src} "$out"
- chmod -R +w "$out"
- cp ${writeText "hst_cgo_test.go" ''package hakurei_test;import("testing";"hakurei.app/hst");func TestTemplate(t *testing.T){hst.Template()}''} "$out/hst_cgo_test.go"
- (cd "$out" && HOME="$(mktemp -d)" CGO_ENABLED=0 go test .)
- ''
- }
-
- cd ${self.packages.${system}.hakurei.src}
- ${fhs}/bin/hakurei-fhs -c \
- 'CC="clang -O3 -Werror" go test --tags=noskip ${if withRace then "-race" else "-count 16"} ./...' \
- &> /tmp/hakurei-test.log && \
- touch /tmp/hakurei-test-ok
- touch /tmp/hakurei-test-done
- '')
- ];
-
- # Run with Go race detector:
- environment.hakurei = lib.mkIf withRace rec {
- # race detector does not support static linking
- package = (pkgs.callPackage ./package.nix { }).overrideAttrs (previousAttrs: {
- env = previousAttrs.env // {
- GOFLAGS = previousAttrs.env.GOFLAGS + " -race";
- };
- });
- hsuPackage = options.environment.hakurei.hsuPackage.default.override { hakurei = package; };
- };
-
- 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;
-}
diff --git a/test/hakurei/flake.lock b/test/hakurei/flake.lock
deleted file mode 100644
index 5537506a..00000000
--- a/test/hakurei/flake.lock
+++ /dev/null
@@ -1,49 +0,0 @@
-{
- "nodes": {
- "home-manager": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1787146702,
- "narHash": "sha256-YbRcLdU/yK4gWsQg7V8WTKZHfXL33g8+wSFUX3wyevs=",
- "owner": "nix-community",
- "repo": "home-manager",
- "rev": "173b7e8d40fdc8c296a9c99854314f17a3a1704c",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "ref": "release-26.05",
- "repo": "home-manager",
- "type": "github"
- }
- },
- "nixpkgs": {
- "locked": {
- "lastModified": 1787101114,
- "narHash": "sha256-gwrPcFf/rDjHPaVflbDZ040ZDmBTRj/7+s8ZmE2SaIM=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "b18a4b905f8d028dc4476412e6d6891728695379",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixos-26.05",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "home-manager": "home-manager",
- "nixpkgs": "nixpkgs"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/test/hakurei/flake.nix b/test/hakurei/flake.nix
deleted file mode 100644
index dc42b1cb..00000000
--- a/test/hakurei/flake.nix
+++ /dev/null
@@ -1,76 +0,0 @@
-{
- description = "hakurei container tool and nixos module";
-
- inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
-
- home-manager = {
- url = "github:nix-community/home-manager/release-26.05";
- inputs.nixpkgs.follows = "nixpkgs";
- };
- };
-
- outputs =
- {
- self,
- nixpkgs,
- home-manager,
- }:
- let
- supportedSystems = [ "x86_64-linux" ];
-
- forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
- nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
- in
- {
- nixosModules.hakurei = import ./nixos.nix self.packages;
-
- checks = forAllSystems (
- system:
- let
- pkgs = nixpkgsFor.${system};
-
- inherit (pkgs) callPackage;
- in
- {
- hakurei = callPackage ./. { inherit system self; };
- race = callPackage ./. {
- inherit system self;
- withRace = true;
- };
- }
- );
-
- packages = forAllSystems (
- system:
- let
- inherit (self.packages.${system}) hakurei hsu;
- pkgs = nixpkgsFor.${system};
- in
- {
- default = hakurei;
- hakurei = pkgs.pkgsStatic.callPackage ./package.nix {
- inherit (pkgs)
- # passthru.buildInputs
- go_1_27
- clang
-
- # nativeBuildInputs
- pkg-config
- wayland-scanner
- makeBinaryWrapper
-
- # appPackages
- glibc
- xdg-dbus-proxy
-
- # for check
- util-linux
- nettools
- ;
- };
- hsu = pkgs.callPackage ./hsu.nix { inherit (self.packages.${system}) hakurei; };
- }
- );
- };
-}
diff --git a/test/hakurei/hsu.nix b/test/hakurei/hsu.nix
deleted file mode 100644
index 5dd4cf5d..00000000
--- a/test/hakurei/hsu.nix
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- lib,
- buildGoModule,
- hakurei ? abort "hakurei package required",
-}:
-
-buildGoModule {
- pname = "${hakurei.pname}-hsu";
- inherit (hakurei) version;
-
- src = ../../cmd/hsu;
- inherit (hakurei) vendorHash;
- env.CGO_ENABLED = 0;
-
- preBuild = ''
- go mod init hsu >& /dev/null
- '';
-
- ldflags = lib.attrsets.foldlAttrs (
- ldflags: name: value:
- ldflags ++ [ "-X main.${name}=${value}" ]
- ) [ "-s -w" ] { hakureiPath = "${hakurei}/libexec/hakurei"; };
-}
diff --git a/test/hakurei/nixos.nix b/test/hakurei/nixos.nix
deleted file mode 100644
index 49bfffb6..00000000
--- a/test/hakurei/nixos.nix
+++ /dev/null
@@ -1,407 +0,0 @@
-packages:
-{
- lib,
- pkgs,
- config,
- ...
-}:
-
-let
- inherit (lib)
- lists
- attrsets
- mkMerge
- mkIf
- mapAttrs
- foldlAttrs
- optional
- optionals
- ;
-
- cfg = config.environment.hakurei;
-
- # userid*userOffset + appStart + appid
- getsubuid = userid: appid: userid * 100000 + 10000 + appid;
- getsubname = userid: appid: "u${toString userid}_a${toString appid}";
- getsubhome = userid: appid: "${cfg.stateDir}/u${toString userid}/a${toString appid}";
-
- mountpoints = {
- ${cfg.sharefs.name} = mkIf (cfg.sharefs.source != null) {
- depends = [ cfg.sharefs.source ];
- device = "sharefs";
- fsType = "fuse.sharefs";
- noCheck = true;
- options = [
- "rw"
- "noexec"
- "nosuid"
- "nodev"
- "noatime"
- "allow_other"
- "mkdir"
- "source=${cfg.sharefs.source}"
- "setuid=${toString config.users.users.${cfg.sharefs.user}.uid}"
- "setgid=${toString config.users.groups.${cfg.sharefs.group}.gid}"
- ];
- };
- };
-in
-
-{
- imports = [ (import ./options.nix packages) ];
-
- options = {
- # Forward declare a dummy option for VM filesystems since the real one won't exist
- # unless the VM module is actually imported.
- virtualisation.fileSystems = lib.mkOption { };
- };
-
- config = mkIf cfg.enable {
- assertions = [
- (
- let
- conflictingApps = foldlAttrs (
- acc: id: app:
- (
- acc
- ++ foldlAttrs (
- acc': id': app':
- if id == id' || app.shareUid && app'.shareUid || app.identity != app'.identity then acc' else acc' ++ [ id ]
- ) [ ] cfg.apps
- )
- ) [ ] cfg.apps;
- in
- {
- assertion = (lists.length conflictingApps) == 0;
- message = "the following hakurei apps have conflicting identities: " + (builtins.concatStringsSep ", " conflictingApps);
- }
- )
- ];
-
- security.wrappers.hsu = {
- source = "${cfg.hsuPackage}/bin/hsu";
- setuid = true;
- owner = "root";
- group = "root";
- };
-
- environment.etc.hsurc = {
- mode = "0400";
- text = foldlAttrs (
- acc: username: fid:
- "${toString config.users.users.${username}.uid} ${toString fid}\n" + acc
- ) "" cfg.users;
- };
-
- environment.systemPackages = optional (cfg.sharefs.source != null) cfg.sharefs.package;
- fileSystems = mountpoints;
- virtualisation.fileSystems = mountpoints;
-
- home-manager =
- let
- privPackages = mapAttrs (_: userid: {
- home.packages = foldlAttrs (
- acc: id: app:
- [
- (
- let
- extendDBusDefault = id: ext: {
- filter = true;
-
- talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
- own = [
- "${id}.*"
- "org.mpris.MediaPlayer2.${id}.*"
- ]
- ++ ext.own;
-
- inherit (ext) call broadcast;
- };
- dbusConfig =
- let
- default = {
- talk = [ ];
- own = [ ];
- call = { };
- broadcast = { };
- };
- in
- {
- session_bus = if app.dbus.session != null then (app.dbus.session (extendDBusDefault id)) else (extendDBusDefault id default);
- system_bus = app.dbus.system;
- };
- command = if app.command == null then app.name else app.command;
- script = if app.script == null then ("exec " + command + " $@") else app.script;
- isGraphical = if app.gpu != null then app.gpu else app.enablements.wayland || app.enablements.x11;
-
- conf = {
- inherit id;
- inherit (app) identity enablements;
- inherit (dbusConfig) session_bus system_bus;
- direct_wayland = app.insecureWayland;
- sched_policy = app.schedPolicy;
- sched_priority = app.schedPriority;
- groups = app.groups ++ optional (cfg.sharefs.source != null) cfg.sharefs.group;
-
- container = {
- inherit (app)
- wait_delay
- devel
- userns
- device
- tty
- multiarch
- env
- ;
- map_real_uid = app.mapRealUid;
- host_net = app.hostNet;
- host_abstract = app.hostAbstract;
- share_runtime = app.shareRuntime;
- share_tmpdir = app.shareTmpdir;
-
- filesystem =
- let
- bind = src: {
- type = "bind";
- inherit src;
- };
- optBind = src: {
- type = "bind";
- inherit src;
- optional = true;
- };
- optDevBind = src: {
- type = "bind";
- inherit src;
- dev = true;
- optional = true;
- };
- in
- [
- (bind "/bin")
- (bind "/usr/bin")
- (bind "/nix/store")
- (optBind "/sys/block")
- (optBind "/sys/bus")
- (optBind "/sys/class")
- (optBind "/sys/dev")
- (optBind "/sys/devices")
- ]
- ++ optionals app.nix [
- (bind "/nix/var")
- ]
- ++ optionals isGraphical [
- (optDevBind "/dev/dri")
- (optDevBind "/dev/nvidiactl")
- (optDevBind "/dev/nvidia-modeset")
- (optDevBind "/dev/nvidia-uvm")
- (optDevBind "/dev/nvidia-uvm-tools")
- (optDevBind "/dev/nvidia0")
- ]
- ++ optionals app.useCommonPaths cfg.commonPaths
- ++ app.extraPaths
- ++ [
- {
- type = "bind";
- dst = "/etc/";
- src = "/etc/";
- special = true;
- }
- {
- type = "link";
- dst = "/run/current-system";
- linkname = "/run/current-system";
- dereference = true;
- }
- ]
- ++ optionals (isGraphical && config.hardware.graphics.enable) (
- [
- {
- type = "link";
- dst = "/run/opengl-driver";
- linkname = config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver"."L+".argument;
- }
- ]
- ++ optionals (app.multiarch && config.hardware.graphics.enable32Bit) [
- {
- type = "link";
- dst = "/run/opengl-driver-32";
- linkname = config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver-32"."L+".argument;
- }
- ]
- )
- ++ [
- {
- type = "bind";
- src = getsubhome userid app.identity;
- write = true;
- ensure = true;
- }
- ];
-
- username = getsubname userid app.identity;
- inherit (cfg) shell;
- home = getsubhome userid app.identity;
-
- path =
- if app.path == null then
- pkgs.writeScript "${app.name}-start" ''
- #!${pkgs.zsh}${pkgs.zsh.shellPath}
- ${script}
- ''
- else
- app.path;
- args = if app.args == null then [ "${app.name}-start" ] else app.args;
- };
- };
-
- checkedConfig =
- name: value:
- let
- file = pkgs.writeText name (builtins.toJSON value);
- in
- pkgs.runCommand "checked-${name}" { nativeBuildInputs = [ cfg.package ]; } ''
- ln -vs ${file} "$out"
- hakurei show --no-store ${file}
- '';
- in
- pkgs.writeShellScriptBin app.name ''
- exec hakurei${if app.verbose then " -v" else ""}${if app.insecureWayland then " --insecure" else ""} run ${checkedConfig "hakurei-app-${app.name}.json" conf} $@
- ''
- )
- ]
- ++ (
- let
- pkg = if app.share != null then app.share else pkgs.${app.name};
- copy = source: "[ -d '${source}' ] && cp -Lrv '${source}' $out/share || true";
- in
- optional (app.enablements.wayland || app.enablements.x11) (
- pkgs.runCommand "${app.name}-share" { } ''
- mkdir -p $out/share
- ${copy "${pkg}/share/applications"}
- ${copy "${pkg}/share/pixmaps"}
- ${copy "${pkg}/share/icons"}
- ${copy "${pkg}/share/man"}
-
- if test -d "$out/share/applications"; then
- substituteInPlace $out/share/applications/* \
- --replace-warn '${pkg}/bin/' "" \
- --replace-warn '${pkg}/libexec/' ""
- fi
- ''
- )
- )
- ++ acc
- ) [ cfg.package ] cfg.apps;
- }) cfg.users;
- in
- {
- useUserPackages = false; # prevent users.users entries from being added
-
- users =
- mkMerge
- (foldlAttrs
- (
- acc: _: fid:
- foldlAttrs
- (
- acc: _: app:
- (
- let
- key = getsubname fid app.identity;
- in
- {
- usernames = acc.usernames // {
- ${key} = true;
- };
- merge = acc.merge ++ [
- {
- ${key} = mkMerge (
- [
- app.extraConfig
- { home.packages = app.packages; }
- ]
- ++ lib.optional (!attrsets.hasAttrByPath [ key ] acc.usernames) cfg.extraHomeConfig
- );
- }
- ];
- }
- )
- )
- {
- inherit (acc) usernames;
- merge = acc.merge ++ [ { ${getsubname fid 0} = cfg.extraHomeConfig; } ];
- }
- cfg.apps
- )
- {
- usernames = { };
- merge = [ privPackages ];
- }
- cfg.users
- ).merge;
- };
-
- users =
- let
- getuser = userid: appid: {
- isSystemUser = true;
- createHome = true;
- description = "Hakurei subordinate user ${toString appid} (u${toString userid})";
- group = getsubname userid appid;
- home = getsubhome userid appid;
- uid = getsubuid userid appid;
- };
- getgroup = userid: appid: { gid = getsubuid userid appid; };
- in
- {
- users = mkMerge (
- foldlAttrs
- (
- acc: username: fid:
- acc
- ++
- foldlAttrs
- (
- acc': _: app:
- acc' ++ [ { ${getsubname fid app.identity} = getuser fid app.identity; } ]
- )
- [
- {
- ${getsubname fid 0} = getuser fid 0;
- ${username}.extraGroups = [ cfg.sharefs.group ];
- }
- ]
- cfg.apps
- )
- (optional (cfg.sharefs.source != null) {
- ${cfg.sharefs.user} = {
- uid = lib.mkDefault 1023;
- inherit (cfg.sharefs) group;
- isSystemUser = true;
- home = cfg.sharefs.source;
- };
- })
- cfg.users
- );
-
- groups = mkMerge (
- foldlAttrs
- (
- acc: _: fid:
- acc
- ++ foldlAttrs (
- acc': _: app:
- acc' ++ [ { ${getsubname fid app.identity} = getgroup fid app.identity; } ]
- ) [ { ${getsubname fid 0} = getgroup fid 0; } ] cfg.apps
- )
- (optional (cfg.sharefs.source != null) {
- ${cfg.sharefs.group} = {
- gid = lib.mkDefault 1023;
- };
- })
- cfg.users
- );
- };
- };
-}
diff --git a/test/hakurei/options.nix b/test/hakurei/options.nix
deleted file mode 100644
index f624b6f5..00000000
--- a/test/hakurei/options.nix
+++ /dev/null
@@ -1,364 +0,0 @@
-packages:
-{
- lib,
- pkgs,
- config,
- ...
-}:
-
-let
- inherit (lib) types mkOption mkEnableOption;
-
- cfg = config.environment.hakurei;
-in
-
-{
- options = {
- environment.hakurei = {
- enable = mkEnableOption "hakurei";
-
- package = mkOption {
- type = types.package;
- default = packages.${pkgs.stdenv.hostPlatform.system}.hakurei;
- description = "The hakurei package to use.";
- };
-
- hsuPackage = mkOption {
- type = types.package;
- default = packages.${pkgs.stdenv.hostPlatform.system}.hsu;
- description = "The hsu package to use.";
- };
-
- users = mkOption {
- type =
- let
- inherit (types) attrsOf ints;
- in
- attrsOf (ints.between 0 99);
- description = ''
- Users allowed to spawn hakurei apps and their corresponding hakurei identity.
- '';
- };
-
- extraHomeConfig = mkOption {
- type = types.anything;
- description = ''
- Extra home-manager configuration to merge with all target users.
- '';
- };
-
- sharefs = {
- package = mkOption {
- type = types.package;
- default = pkgs.linkFarm "sharefs" {
- "bin/sharefs" = "${cfg.package}/libexec/sharefs";
- "bin/mount.fuse.sharefs" = "${cfg.package}/libexec/sharefs";
- };
- description = "The sharefs package to use.";
- };
-
- user = mkOption {
- type = types.str;
- default = "sharefs";
- description = ''
- Name of the user to run the sharefs daemon as.
- '';
- };
-
- group = mkOption {
- type = types.str;
- default = "sharefs";
- description = ''
- Name of the group to run the sharefs daemon as.
- '';
- };
-
- name = mkOption {
- type = types.str;
- default = "/sdcard";
- description = ''
- Host path to mount sharefs on.
- '';
- };
-
- source = mkOption {
- type = types.nullOr types.str;
- default = null;
- description = ''
- Writable backing directory. Setting this to null disables sharefs.
- '';
- };
- };
-
- apps = mkOption {
- type =
- let
- inherit (types)
- int
- ints
- str
- bool
- enum
- package
- anything
- submodule
- listOf
- attrsOf
- nullOr
- functionTo
- ;
- in
- attrsOf (submodule {
- options = {
- name = mkOption {
- type = str;
- description = ''
- Name of the app's launcher script.
- '';
- };
-
- verbose = mkEnableOption "launchers with verbose output";
-
- identity = mkOption {
- type = ints.between 1 9999;
- description = ''
- Application identity. Identity 0 is reserved for system services.
- '';
- };
- shareUid = mkEnableOption "sharing identity with another application";
-
- packages = mkOption {
- type = listOf package;
- default = [ ];
- description = ''
- List of extra packages to install via home-manager.
- '';
- };
-
- extraConfig = mkOption {
- type = anything;
- default = { };
- description = ''
- Extra home-manager configuration.
- '';
- };
-
- path = mkOption {
- type = nullOr str;
- default = null;
- description = ''
- Custom executable path.
- Setting this to null will default to the start script.
- '';
- };
-
- args = mkOption {
- type = nullOr (listOf str);
- default = null;
- description = ''
- Custom args.
- Setting this to null will default to script name.
- '';
- };
-
- script = mkOption {
- type = nullOr str;
- default = null;
- description = ''
- Application launch script.
- '';
- };
-
- command = mkOption {
- type = nullOr str;
- default = null;
- description = ''
- Command to run as the target user.
- Setting this to null will default command to launcher name.
- Has no effect when script is set.
- '';
- };
-
- groups = mkOption {
- type = listOf str;
- default = [ ];
- description = ''
- List of groups to inherit from the privileged user.
- '';
- };
-
- shareRuntime = mkEnableOption "sharing of XDG_RUNTIME_DIR between containers under the same identity";
- shareTmpdir = mkEnableOption "sharing of TMPDIR between containers under the same identity";
-
- dbus = {
- session = mkOption {
- type = nullOr (functionTo anything);
- default = null;
- description = ''
- D-Bus session bus custom configuration.
- Setting this to null will enable built-in defaults.
- '';
- };
-
- system = mkOption {
- type = nullOr anything;
- default = null;
- description = ''
- D-Bus system bus custom configuration.
- Setting this to null will disable the system bus proxy.
- '';
- };
- };
-
- env = mkOption {
- type = nullOr (attrsOf str);
- default = null;
- description = ''
- Environment variables to set for the initial process in the sandbox.
- '';
- };
-
- wait_delay = mkOption {
- type = nullOr int;
- default = null;
- description = ''
- Duration to wait for after interrupting a container's initial process in nanoseconds.
- A negative value causes the container to be terminated immediately on cancellation.
- Setting this to null defaults to five seconds.
- '';
- };
-
- devel = mkEnableOption "debugging-related kernel interfaces";
- userns = mkEnableOption "user namespace creation";
- tty = mkEnableOption "access to the controlling terminal";
- multiarch = mkEnableOption "multiarch kernel-level support";
-
- hostNet = mkEnableOption "share host net namespace" // {
- default = true;
- };
- hostAbstract = mkEnableOption "share abstract unix socket scope";
-
- schedPolicy = mkOption {
- type = nullOr (enum [
- "fifo"
- "rr"
- "batch"
- "idle"
- "deadline"
- "ext"
- ]);
- default = null;
- description = ''
- Scheduling policy to set for the container.
- The zero value retains the current scheduling policy.
- '';
- };
- schedPriority = mkOption {
- type = nullOr (ints.between 1 99);
- default = null;
- description = ''
- Scheduling priority to set for the container.
- '';
- };
-
- nix = mkEnableOption "nix daemon access";
- mapRealUid = mkEnableOption "mapping to priv-user uid";
- device = mkEnableOption "access to all devices";
- insecureWayland = mkEnableOption "direct access to the Wayland socket";
-
- gpu = mkOption {
- type = nullOr bool;
- default = null;
- description = ''
- Target process GPU and driver access.
- Setting this to null will enable GPU whenever X or Wayland is enabled.
- '';
- };
-
- useCommonPaths = mkEnableOption "common extra paths" // {
- default = true;
- };
-
- extraPaths = mkOption {
- type = listOf (attrsOf anything);
- default = [ ];
- description = ''
- Extra paths to make available to the container.
- '';
- };
-
- enablements = {
- wayland = mkOption {
- type = nullOr bool;
- default = true;
- description = ''
- Whether to share the Wayland server via security-context-v1.
- '';
- };
-
- x11 = mkOption {
- type = nullOr bool;
- default = false;
- description = ''
- Whether to share the X11 socket and allow connection.
- '';
- };
-
- dbus = mkOption {
- type = nullOr bool;
- default = true;
- description = ''
- Whether to proxy D-Bus.
- '';
- };
-
- pipewire = mkOption {
- type = nullOr bool;
- default = true;
- description = ''
- Whether to share the PipeWire server via pipewire-pulse on a SecurityContext socket.
- '';
- };
- };
-
- share = mkOption {
- type = nullOr package;
- default = null;
- description = ''
- Package containing share files.
- Setting this to null will default package name to wrapper name.
- '';
- };
- };
- });
- default = { };
- description = ''
- Declaratively configured hakurei apps.
- '';
- };
-
- commonPaths = mkOption {
- type = types.listOf (types.attrsOf types.anything);
- default = [ ];
- description = ''
- Common extra paths to make available to the container.
- '';
- };
-
- shell = mkOption {
- type = types.str;
- default = "/run/current-system/sw/bin/bash";
- description = ''
- Absolute path to preferred shell.
- '';
- };
-
- stateDir = mkOption {
- type = types.str;
- description = ''
- The state directory where app home directories are stored.
- '';
- };
- };
- };
-}
diff --git a/test/hakurei/package.nix b/test/hakurei/package.nix
deleted file mode 100644
index 0facf291..00000000
--- a/test/hakurei/package.nix
+++ /dev/null
@@ -1,144 +0,0 @@
-{
- lib,
- stdenv,
- buildGo127Module,
- makeBinaryWrapper,
- xdg-dbus-proxy,
- pkg-config,
- libffi,
- libseccomp,
- acl,
- wayland,
- wayland-protocols,
- wayland-scanner,
-
- libxcb,
- libxau,
- libxdmcp,
-
- # for sharefs
- fuse3,
-
- # for passthru.buildInputs
- go_1_27,
- clang,
- xorgproto,
-
- # for check
- util-linux,
- nettools,
-
- glibc, # for ldd
- withStatic ? stdenv.hostPlatform.isStatic,
-}:
-
-buildGo127Module rec {
- pname = "hakurei";
- version = with lib.strings; removePrefix "v" (trim (builtins.readFile ../../cmd/dist/VERSION));
-
- srcFiltered = builtins.path {
- name = "${pname}-src";
- path = lib.cleanSource ../../.;
- filter = path: type: !(type == "regular" && (lib.hasSuffix ".nix" path || lib.hasSuffix ".py" path)) && !(type == "directory" && lib.hasSuffix "/test" path) && !(type == "directory" && lib.hasSuffix "/cmd/hsu" path);
- };
- vendorHash = null;
-
- src = stdenv.mkDerivation {
- name = "${pname}-src-full";
- inherit version;
- enableParallelBuilding = true;
- src = srcFiltered;
-
- buildInputs = [
- wayland
- wayland-protocols
- ];
-
- nativeBuildInputs = [
- go_1_27
- pkg-config
- wayland-scanner
- ];
-
- buildPhase = "GOCACHE=$(mktemp -d) go generate ./...";
- installPhase = "cp -r . $out";
- };
-
- ldflags =
- lib.attrsets.foldlAttrs
- (
- ldflags: name: value:
- ldflags ++ [ "-X hakurei.app/internal/info.${name}=${value}" ]
- )
- (
- [ "-s -w" ]
- ++ lib.optionals withStatic [
- "-linkmode external"
- "-extldflags \"-static\""
- ]
- )
- {
- buildVersion = "v${version}";
- hakureiPath = "${placeholder "out"}/libexec/hakurei";
- hsuPath = "/run/wrappers/bin/hsu";
- };
-
- env = {
- # use clang instead of gcc
- CC = "clang -O3 -Werror";
- };
-
- buildInputs = [
- libffi
- libseccomp
- fuse3
- acl
- wayland
-
- libxcb
- libxau
- libxdmcp
- ];
-
- nativeBuildInputs = [
- pkg-config
- makeBinaryWrapper
-
- # for container example
- nettools
- ];
-
- postInstall =
- let
- appPackages = [
- glibc
- xdg-dbus-proxy
- ];
- in
- ''
- install -D --target-directory=$out/share/zsh/site-functions cmd/dist/comp/*
-
- mkdir "$out/libexec"
- mv "$out"/bin/* "$out/libexec/"
-
- makeBinaryWrapper "$out/libexec/hakurei" "$out/bin/hakurei" \
- --inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages}
- '';
-
- passthru = {
- go = go_1_27;
-
- targetPkgs = [
- go_1_27
- clang
- xorgproto
- util-linux
-
- # for go generate
- wayland-protocols
- wayland-scanner
- ]
- ++ buildInputs
- ++ nativeBuildInputs;
- };
-}
diff --git a/test/hakurei/test.py b/test/hakurei/test.py
deleted file mode 100644
index 98f08275..00000000
--- a/test/hakurei/test.py
+++ /dev/null
@@ -1,315 +0,0 @@
-import json
-import shlex
-
-q = shlex.quote
-NODE_GROUPS = ["nodes", "floating_nodes"]
-
-
-def swaymsg(command: str = "", succeed=True, type="command"):
- assert command != "" or type != "command", "Must specify command or type"
- shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
- with machine.nested(f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)):
- ret = (machine.succeed if succeed else machine.execute)(
- f"su - alice -c {shell}"
- )
-
- # execute also returns a status code, but disregard.
- if not succeed:
- _, ret = ret
-
- if not succeed and not ret:
- return None
-
- parsed = json.loads(ret)
- return parsed
-
-
-def walk(tree):
- yield tree
- for group in NODE_GROUPS:
- for node in tree.get(group, []):
- yield from walk(node)
-
-
-def wait_for_window(pattern):
- def func(last_chance):
- nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
-
- if last_chance:
- nodes = list(nodes)
- machine.log(f"Last call! Current list of windows: {nodes}")
-
- return any(pattern in name for name in nodes)
-
- retry(func)
-
-
-def collect_state_ui(name):
- swaymsg(f"exec hakurei ps > '/tmp/{name}.ps'")
- machine.wait_for_file(f"/tmp/{name}.ps")
- machine.copy_from_vm(f"/tmp/{name}.ps", "")
- swaymsg(f"exec hakurei --json ps > '/tmp/{name}.json'")
- machine.wait_for_file(f"/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]
-
- command = f"{name}-start"
- if not (instance['container']['path'].startswith("/nix/store/")) or not (instance['container']['path'].endswith(command)):
- raise Exception(f"unexpected path {instance['path']}")
-
- if len(instance['container']['args']) != 1 or instance['container']['args'][0] != command:
- raise Exception(f"unexpected args {instance['args']}")
-
- if instance['enablements'] != enablements:
- raise Exception(f"unexpected enablements {instance['enablements']['enablements']}")
-
-
-def hakurei(command):
- swaymsg(f"exec hakurei {command}")
-
-
-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")
-
-# Run hakurei Go tests outside of nix build in the background:
-swaymsg("exec hakurei-test")
-
-# Deny unmapped uid:
-denyOutput = machine.fail("sudo -u untrusted -i hakurei exec &>/dev/stdout")
-print(denyOutput)
-denyOutputVerbose = machine.fail("sudo -u untrusted -i hakurei -v exec &>/dev/stdout")
-print(denyOutputVerbose)
-
-# Direct hsu call:
-userid = machine.succeed("sudo -u alice -i hsu")
-if userid != "0":
- raise Exception(f"unexpected userid: {userid}")
-
-# Verify hsu fault behaviour:
-if denyOutput != "hsu: uid 1001 is not in the hsurc file\n":
- raise Exception(f"unexpected deny output:\n{denyOutput}")
-if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot retrieve user id from setuid wrapper: current user is not in the hsurc file\n":
- raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
-
-# Verify timeout behaviour:
-machine.succeed('sudo -u alice -i hakurei-check-linger-timeout > /var/tmp/linger-stdout 2> /var/tmp/linger-stderr || (cat /var/tmp/linger-stderr; false)')
-linger_stdout = machine.succeed("cat /var/tmp/linger-stdout")
-linger_stderr = machine.succeed("cat /var/tmp/linger-stderr")
-if linger_stdout != "":
- raise Exception(f"unexpected stdout: {linger_stdout}")
-if linger_stderr != "init: timeout exceeded waiting for lingering processes\n":
- raise Exception(f"unexpected stderr: {linger_stderr}")
-
-check_offset = 0
-
-
-def hakurei_identity(offset):
- return 1+check_offset+offset
-
-
-# Start hakurei permissive defaults outside Wayland session:
-print(machine.succeed("sudo -u alice -i hakurei -v exec -a 0 touch /tmp/pd-bare-ok"))
-machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-bare-ok")
-
-# Verify silent output permissive defaults:
-output = machine.succeed("sudo -u alice -i hakurei exec -a 0 true &>/dev/stdout")
-if output != "":
- raise Exception(f"unexpected output\n{output}")
-
-# Verify silent output permissive defaults signal:
-def silent_output_interrupt(flags):
- swaymsg("exec foot")
- wait_for_window("alice@machine")
- # identity 0 does not have home-manager
- machine.send_chars(f"exec hakurei exec {flags}-a 0 sh -c 'export PATH=/run/current-system/sw/bin:$PATH && touch /tmp/pd-silent-ready && sleep infinity' &>/tmp/pd-silent\n")
- machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-silent-ready")
- machine.succeed("rm /tmp/hakurei.0/tmpdir/0/pd-silent-ready")
- machine.send_key("ctrl-c")
- machine.wait_until_fails("pgrep foot")
- machine.wait_until_fails(f"pgrep -u alice -f 'hakurei exec {flags}-a 0 '")
- output = machine.succeed("cat /tmp/pd-silent && rm /tmp/pd-silent")
- if output != "":
- raise Exception(f"unexpected output\n{output}")
-
-
-silent_output_interrupt("")
-silent_output_interrupt("--dbus ") # this one is especially painful as it maintains a helper
-silent_output_interrupt("--wayland -X --dbus --pulse ")
-
-# Verify graceful failure on bad Wayland display name:
-print(machine.fail("sudo -u alice -i hakurei -v exec --wayland true"))
-
-# Start hakurei permissive defaults within Wayland session:
-hakurei('-v exec --wayland --dbus --dbus-log notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-ok')
-machine.wait_for_file("/tmp/dbus-ok")
-collect_state_ui("dbus_notify_exited")
-# not in pid namespace, verify termination
-machine.wait_until_fails("pgrep xdg-dbus-proxy")
-machine.succeed("pkill -9 mako")
-
-# Check revert type selection:
-hakurei("-v exec --wayland -X --dbus --pulse -u p0 foot && touch /tmp/p0-exit-ok")
-wait_for_window("p0@machine")
-print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
-hakurei("-v exec --wayland -X --dbus --pulse -u p1 foot && touch /tmp/p1-exit-ok")
-wait_for_window("p1@machine")
-print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
-machine.send_chars("exit\n")
-machine.wait_for_file("/tmp/p1-exit-ok")
-# Verify acl is kept alive:
-print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
-machine.send_chars("exit\n")
-machine.wait_for_file("/tmp/p0-exit-ok")
-machine.fail("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")
-
-# Check invalid identifier fd behaviour:
-machine.fail('echo \'{"container":{"shell":"/proc/nonexistent","home":"/proc/nonexistent","path":"/proc/nonexistent"}}\' | sudo -u alice -i hakurei -v run --identifier-fd 32767 - 2>&1 | tee > /tmp/invalid-identifier-fd')
-machine.wait_for_file("/tmp/invalid-identifier-fd")
-print(machine.succeed('grep "^hakurei: cannot write identifier: bad file descriptor$" /tmp/invalid-identifier-fd'))
-
-# Check interrupt shim behaviour:
-swaymsg("exec sh -c 'ne-foot; echo -n $? > /tmp/monitor-exit-code'")
-wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
-machine.succeed("pkill -INT -f 'hakurei -v run '")
-machine.wait_until_fails("pgrep foot")
-machine.wait_for_file("/tmp/monitor-exit-code")
-interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
-if interrupt_exit_code != 230:
- raise Exception(f"unexpected exit code {interrupt_exit_code}")
-
-# Check interrupt shim behaviour immediate termination:
-swaymsg("exec sh -c 'ne-foot-immediate; echo -n $? > /tmp/monitor-exit-code'")
-wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
-machine.succeed("pkill -INT -f 'hakurei -v run '")
-machine.wait_until_fails("pgrep foot")
-machine.wait_for_file("/tmp/monitor-exit-code")
-interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
-if interrupt_exit_code != 254:
- raise Exception(f"unexpected exit code {interrupt_exit_code}")
-
-# Check shim SIGCONT from unexpected process behaviour:
-swaymsg("exec sh -c 'ne-foot &> /tmp/shim-cont-unexpected-pid'")
-wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
-machine.succeed("pkill -CONT -f 'hakurei shim'")
-machine.succeed("pkill -INT -f 'hakurei -v run '")
-machine.wait_until_fails("pgrep foot")
-machine.wait_for_file("/tmp/shim-cont-unexpected-pid")
-print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid'))
-
-# Check setscheduler:
-sched_unset = int(machine.succeed("sudo -u alice -i hakurei -v exec cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
-if sched_unset != 0:
- raise Exception(f"unexpected unset policy: {sched_unset}")
-sched_idle = int(machine.succeed("sudo -u alice -i hakurei -v exec --policy=idle cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
-if sched_idle != 5:
- raise Exception(f"unexpected idle policy: {sched_idle}")
-sched_rr = int(machine.succeed("sudo -u alice -i hakurei -v exec --policy=rr cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
-if sched_rr != 2:
- raise Exception(f"unexpected round-robin policy: {sched_idle}")
-
-# Start app (foot) with Wayland enablement:
-swaymsg("exec ne-foot")
-wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
-machine.send_chars("clear; wayland-info && touch /var/tmp/client-ok\n")
-machine.wait_for_file("/var/tmp/client-ok")
-collect_state_ui("foot_wayland")
-check_state("ne-foot", {"wayland": True})
-# Verify lack of acl on XDG_RUNTIME_DIR:
-machine.fail(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(0) + 10000}")
-machine.send_chars("exit\n")
-machine.wait_until_fails("pgrep foot")
-machine.fail(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(0) + 10000}")
-
-# Test pipewire-pulse:
-swaymsg("exec pa-foot")
-wait_for_window(f"u0_a{hakurei_identity(1)}@machine")
-machine.send_chars("clear; pactl info && touch /var/tmp/pulse-ok\n")
-machine.wait_for_file("/var/tmp/pulse-ok")
-collect_state_ui("pulse_wayland")
-check_state("pa-foot", {"wayland": True, "pipewire": True})
-machine.fail("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +")
-machine.send_chars("exit\n")
-machine.wait_until_fails("pgrep foot")
-machine.wait_until_fails("pgrep -x hakurei")
-machine.succeed("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +")
-# Test PipeWire SecurityContext:
-machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pulse pactl info")
-machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pulse pactl set-sink-mute @DEFAULT_SINK@ toggle")
-# Test PipeWire direct access:
-machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 pw-dump")
-machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pipewire pw-dump")
-
-# Test XWayland (foot does not support X):
-swaymsg("exec x11-alacritty")
-wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
-machine.send_chars("clear; glinfo && touch /var/tmp/x11-ok\n")
-machine.wait_for_file("/var/tmp/x11-ok")
-collect_state_ui("alacritty_x11")
-check_state("x11-alacritty", {"x11": True})
-machine.send_chars("exit\n")
-machine.wait_until_fails("pgrep alacritty")
-
-# Start app (foot) with direct Wayland access:
-swaymsg("exec da-foot")
-wait_for_window(f"u0_a{hakurei_identity(3)}@machine")
-machine.send_chars("clear; wayland-info && touch /var/tmp/direct-ok\n")
-collect_state_ui("foot_direct")
-machine.wait_for_file("/var/tmp/direct-ok")
-check_state("da-foot", {"wayland": True})
-# Verify acl on XDG_RUNTIME_DIR:
-print(machine.succeed(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(3) + 10000}"))
-machine.send_chars("exit\n")
-machine.wait_until_fails("pgrep foot")
-# Verify acl cleanup on XDG_RUNTIME_DIR:
-machine.wait_until_fails(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(3) + 10000}")
-
-# Test syscall filter:
-print(machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 strace-failure"))
-
-# Start app (foot) with Wayland enablement from a terminal:
-swaymsg("exec foot $SHELL -c '(ne-foot) & disown && exec $SHELL'")
-wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
-machine.send_chars("clear; wayland-info && touch /var/tmp/term-ok\n")
-machine.wait_for_file("/var/tmp/term-ok")
-machine.send_key("alt-h")
-machine.send_chars("clear; hakurei show $(hakurei ps --short) && touch /tmp/ps-show-ok && exec cat\n")
-machine.wait_for_file("/tmp/ps-show-ok")
-collect_state_ui("foot_wayland_term")
-check_state("ne-foot", {"wayland": True})
-machine.send_key("alt-l")
-machine.send_chars("exit\n")
-wait_for_window("alice@machine")
-machine.send_key("ctrl-c")
-machine.wait_until_fails("pgrep foot")
-
-# Exit Sway and verify process exit status 0:
-machine.wait_until_fails("pgrep -x hakurei")
-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.succeed("find /run/user/1000/hakurei"))
-machine.succeed("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +")
-
-# Verify go test status:
-machine.wait_for_file("/tmp/hakurei-test-done")
-print(machine.succeed("cat /tmp/hakurei-test.log"))
-machine.wait_for_file("/tmp/hakurei-test-ok")
diff --git a/test/internal/mountinfo/mountinfo.go b/test/internal/mountinfo/mountinfo.go
deleted file mode 100644
index 19892cd2..00000000
--- a/test/internal/mountinfo/mountinfo.go
+++ /dev/null
@@ -1,177 +0,0 @@
-// Package mountinfo provides util-linux bindings for parsing
-// proc_pid_mountinfo(5).
-//
-// This package must never be used outside integration tests, a much better
-// implementation can be found in package vfs.
-//
-// Attempting to import this package outside testing causes the resulting
-// program to panic.
-package mountinfo
-
-/*
-#cgo linux pkg-config: --static mount
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <libmount.h>
-
-const char *HAKUREI_MOUNTINFO_PATH = "/proc/self/mountinfo";
-*/
-import "C"
-
-import (
- "errors"
- "fmt"
- "runtime"
- "unsafe"
-)
-
-var (
- // ErrParse is returned by [Open] when encountering a bad record.
- ErrParse = errors.New("invalid mountinfo record")
- // ErrIter is returned by [Open] if an iterator cannot be allocated.
- ErrIter = errors.New("cannot allocate iterator")
- // ErrIterAdvance is stored when the iterator is unable to advance.
- ErrIterAdvance = errors.New("unable to advance iterator")
-)
-
-type (
- // Iter refers to libmnt iterator state.
- Iter struct {
- // Last stored error.
- err error
- // Whether iteration has concluded.
- ok bool
- // Whether Close had already been called.
- closed bool
-
- tb *C.struct_libmnt_table
- itr *C.struct_libmnt_iter
-
- fs *C.struct_libmnt_fs
- }
-
- // Entry represents deterministic mountinfo parts of a libmnt_fs entry.
- Entry struct {
- // mount ID: a unique ID for the mount (may be reused after umount(2)).
- ID int `json:"id"`
- // parent ID: the ID of the parent mount (or of self for the root of
- // this mount namespace's mount tree).
- Parent int `json:"parent"`
- // root: the pathname of the directory in the filesystem which forms the
- // root of this mount.
- Root string `json:"root"`
- // mount point: the pathname of the mount point relative to the
- // process's root directory.
- Target string `json:"target"`
- // mount options: per-mount options (see mount(2)).
- VfsOptstr string `json:"vfs_optstr"`
- // filesystem type: the filesystem type in the form "type[.subtype]".
- FsType string `json:"fstype"`
- // mount source: filesystem-specific information or "none".
- Source string `json:"source"`
- // super options: per-superblock options (see mount(2)).
- FsOptstr string `json:"fs_optstr"`
- }
-)
-
-// Copy populates v with the current record.
-func (m *Iter) Copy(v *Entry) {
- if m.fs == nil {
- panic("invalid entry")
- }
- v.ID = int(C.mnt_fs_get_id(m.fs))
- v.Parent = int(C.mnt_fs_get_parent_id(m.fs))
- v.Root = C.GoString(C.mnt_fs_get_root(m.fs))
- v.Target = C.GoString(C.mnt_fs_get_target(m.fs))
- v.VfsOptstr = C.GoString(C.mnt_fs_get_vfs_options(m.fs))
- v.FsType = C.GoString(C.mnt_fs_get_fstype(m.fs))
- v.Source = C.GoString(C.mnt_fs_get_source(m.fs))
- v.FsOptstr = C.GoString(C.mnt_fs_get_fs_options(m.fs))
-}
-
-// Err returns the saved iterator error.
-func (m *Iter) Err() error { return m.err }
-
-// Open opens a mountinfo document. If name is an empty string, the mountinfo
-// document of the current process is opened instead.
-func Open(name string) (*Iter, error) {
- var m Iter
- if name == "" {
- m.tb = C.mnt_new_table_from_file(C.HAKUREI_MOUNTINFO_PATH)
- } else {
- _name := C.CString(name)
- m.tb = C.mnt_new_table_from_file(_name)
- C.free(unsafe.Pointer(_name))
- }
- if m.tb == nil {
- return nil, ErrParse
- }
- m.itr = C.mnt_new_iter(C.MNT_ITER_FORWARD)
- if m.itr == nil {
- C.mnt_unref_table(m.tb)
- return nil, ErrIter
- }
- m.ok = true
-
- runtime.SetFinalizer(&m, (*Iter).Close)
- return &m, nil
-}
-
-// Close frees the iterator.
-func (m *Iter) Close() {
- if m.closed {
- return
- }
- if m.tb == nil {
- panic("unref called before open")
- }
-
- C.mnt_unref_table(m.tb)
- C.mnt_free_iter(m.itr)
- m.closed = true
- runtime.SetFinalizer(m, nil)
-}
-
-// Reset resets the iterator to the first record for reuse.
-func (m *Iter) Reset() {
- if m.err != nil {
- panic("attempting to reset a faulted iterator")
- }
- m.ok = true
- C.mnt_reset_iter(m.itr, -1)
-}
-
-// Next advances the iterator to the next record. The record may be copied if
-// Next returns true.
-func (m *Iter) Next() bool {
- if !m.ok || m.err != nil {
- return false
- }
-
- r := C.mnt_table_next_fs(m.tb, m.itr, &m.fs)
- if r < 0 {
- m.err = ErrIterAdvance
- }
- m.ok = r == 0
- return m.ok
-}
-
-// EqualWithIgnore compares e with want, ignoring fields with the specified
-// ignore value.
-func (e *Entry) EqualWithIgnore(want *Entry, ignore string) bool {
- return (e.ID == want.ID || want.ID == -1) &&
- (e.Parent == want.Parent || want.Parent == -1) &&
- (e.Root == want.Root || want.Root == ignore) &&
- (e.Target == want.Target || want.Target == ignore) &&
- (e.VfsOptstr == want.VfsOptstr || want.VfsOptstr == ignore) &&
- (e.FsType == want.FsType || want.FsType == ignore) &&
- (e.Source == want.Source || want.Source == ignore) &&
- (e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore)
-}
-
-// String returns a text representation of e loosely following the kernel format.
-func (e *Entry) String() string {
- return fmt.Sprintf("%d %d %s %s %s %s %s %s",
- e.ID, e.Parent, e.Root, e.Target, e.VfsOptstr, e.FsType, e.Source, e.FsOptstr)
-}
diff --git a/test/internal/mountinfo/mountinfo_guard.go b/test/internal/mountinfo/mountinfo_guard.go
deleted file mode 100644
index aaf63ac4..00000000
--- a/test/internal/mountinfo/mountinfo_guard.go
+++ /dev/null
@@ -1,15 +0,0 @@
-//go:build !testsuite && !tester
-
-package mountinfo
-
-import (
- "os"
- "testing"
-)
-
-func init() {
- if !testing.Testing() {
- println("package mountinfo imported in non-testsuite program")
- os.Exit(1)
- }
-}
diff --git a/test/internal/mountinfo/mountinfo_test.go b/test/internal/mountinfo/mountinfo_test.go
deleted file mode 100644
index 45cfdf5f..00000000
--- a/test/internal/mountinfo/mountinfo_test.go
+++ /dev/null
@@ -1,146 +0,0 @@
-package mountinfo_test
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- "hakurei.app/test/internal/mountinfo"
-)
-
-func TestMountinfo(t *testing.T) {
- testCases := []struct {
- name string
-
- sample string
- want []*mountinfo.Entry
- }{
- {"util-linux", `15 20 0:3 / /proc rw,relatime - proc /proc rw
-16 20 0:15 / /sys rw,relatime - sysfs /sys rw
-17 20 0:5 / /dev rw,relatime - devtmpfs udev rw,size=1983516k,nr_inodes=495879,mode=755
-18 17 0:10 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-19 17 0:16 / /dev/shm rw,relatime - tmpfs tmpfs rw
-20 1 8:4 / / rw,noatime - ext3 /dev/sda4 rw,errors=continue,user_xattr,acl,barrier=0,data=ordered
-21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755
-22 21 0:18 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-23 21 0:19 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset
-24 21 0:20 / /sys/fs/cgroup/ns rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,ns
-25 21 0:21 / /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu
-26 21 0:22 / /sys/fs/cgroup/cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuacct
-27 21 0:23 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory
-28 21 0:24 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices
-29 21 0:25 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer
-30 21 0:26 / /sys/fs/cgroup/net_cls rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,net_cls
-31 21 0:27 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio
-32 16 0:28 / /sys/kernel/security rw,relatime - autofs systemd-1 rw,fd=22,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
-33 17 0:29 / /dev/hugepages rw,relatime - autofs systemd-1 rw,fd=23,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
-34 16 0:30 / /sys/kernel/debug rw,relatime - autofs systemd-1 rw,fd=24,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
-35 15 0:31 / /proc/sys/fs/binfmt_misc rw,relatime - autofs systemd-1 rw,fd=25,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
-36 17 0:32 / /dev/mqueue rw,relatime - autofs systemd-1 rw,fd=26,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
-37 15 0:14 / /proc/bus/usb rw,relatime - usbfs /proc/bus/usb rw
-38 33 0:33 / /dev/hugepages rw,relatime - hugetlbfs hugetlbfs rw
-39 36 0:12 / /dev/mqueue rw,relatime - mqueue mqueue rw
-40 20 8:6 / /boot rw,noatime - ext3 /dev/sda6 rw,errors=continue,barrier=0,data=ordered
-41 20 253:0 / /home/kzak rw,noatime - ext4 /dev/mapper/kzak-home rw,barrier=1,data=ordered
-42 35 0:34 / /proc/sys/fs/binfmt_misc rw,relatime - binfmt_misc none rw
-43 16 0:35 / /sys/fs/fuse/connections rw,relatime - fusectl fusectl rw
-44 41 0:36 / /home/kzak/.gvfs rw,nosuid,nodev,relatime - fuse.gvfs-fuse-daemon gvfs-fuse-daemon rw,user_id=500,group_id=500
-45 20 0:37 / /var/lib/nfs/rpc_pipefs rw,relatime - rpc_pipefs sunrpc rw
-47 20 0:38 / /mnt/sounds rw,relatime - cifs //foo.home/bar/ rw,unc=\\foo.home\bar,username=kzak,domain=SRGROUP,uid=0,noforceuid,gid=0,noforcegid,addr=192.168.111.1,posixpaths,serverino,acl,rsize=16384,wsize=57344
-49 20 0:56 / /mnt/test/foobar rw,relatime,nosymfollow shared:323 - tmpfs tmpfs rw`, []*mountinfo.Entry{
- e(15, 20, "/", "/proc", "rw,relatime", "proc", "/proc", "rw"),
- e(16, 20, "/", "/sys", "rw,relatime", "sysfs", "/sys", "rw"),
- e(17, 20, "/", "/dev", "rw,relatime", "devtmpfs", "udev", "rw,size=1983516k,nr_inodes=495879,mode=755"),
- e(18, 17, "/", "/dev/pts", "rw,relatime", "devpts", "devpts", "rw,gid=5,mode=620,ptmxmode=000"),
- e(19, 17, "/", "/dev/shm", "rw,relatime", "tmpfs", "tmpfs", "rw"),
- e(20, 1, "/", "/", "rw,noatime", "ext3", "/dev/sda4", "rw,errors=continue,user_xattr,acl,barrier=0,data=ordered"),
- e(21, 16, "/", "/sys/fs/cgroup", "rw,nosuid,nodev,noexec,relatime", "tmpfs", "tmpfs", "rw,mode=755"),
- e(22, 21, "/", "/sys/fs/cgroup/systemd", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd"),
- e(23, 21, "/", "/sys/fs/cgroup/cpuset", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,cpuset"),
- e(24, 21, "/", "/sys/fs/cgroup/ns", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,ns"),
- e(25, 21, "/", "/sys/fs/cgroup/cpu", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,cpu"),
- e(26, 21, "/", "/sys/fs/cgroup/cpuacct", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,cpuacct"),
- e(27, 21, "/", "/sys/fs/cgroup/memory", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,memory"),
- e(28, 21, "/", "/sys/fs/cgroup/devices", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,devices"),
- e(29, 21, "/", "/sys/fs/cgroup/freezer", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,freezer"),
- e(30, 21, "/", "/sys/fs/cgroup/net_cls", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,net_cls"),
- e(31, 21, "/", "/sys/fs/cgroup/blkio", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,blkio"),
- e(32, 16, "/", "/sys/kernel/security", "rw,relatime", "autofs", "systemd-1", "rw,fd=22,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"),
- e(33, 17, "/", "/dev/hugepages", "rw,relatime", "autofs", "systemd-1", "rw,fd=23,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"),
- e(34, 16, "/", "/sys/kernel/debug", "rw,relatime", "autofs", "systemd-1", "rw,fd=24,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"),
- e(35, 15, "/", "/proc/sys/fs/binfmt_misc", "rw,relatime", "autofs", "systemd-1", "rw,fd=25,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"),
- e(36, 17, "/", "/dev/mqueue", "rw,relatime", "autofs", "systemd-1", "rw,fd=26,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"),
- e(37, 15, "/", "/proc/bus/usb", "rw,relatime", "usbfs", "/proc/bus/usb", "rw"),
- e(38, 33, "/", "/dev/hugepages", "rw,relatime", "hugetlbfs", "hugetlbfs", "rw"),
- e(39, 36, "/", "/dev/mqueue", "rw,relatime", "mqueue", "mqueue", "rw"),
- e(40, 20, "/", "/boot", "rw,noatime", "ext3", "/dev/sda6", "rw,errors=continue,barrier=0,data=ordered"),
- e(41, 20, "/", "/home/kzak", "rw,noatime", "ext4", "/dev/mapper/kzak-home", "rw,barrier=1,data=ordered"),
- e(42, 35, "/", "/proc/sys/fs/binfmt_misc", "rw,relatime", "binfmt_misc", "none", "rw"),
- e(43, 16, "/", "/sys/fs/fuse/connections", "rw,relatime", "fusectl", "fusectl", "rw"),
- e(44, 41, "/", "/home/kzak/.gvfs", "rw,nosuid,nodev,relatime", "fuse.gvfs-fuse-daemon", "gvfs-fuse-daemon", "rw,user_id=500,group_id=500"),
- e(45, 20, "/", "/var/lib/nfs/rpc_pipefs", "rw,relatime", "rpc_pipefs", "sunrpc", "rw"),
- e(47, 20, "/", "/mnt/sounds", "rw,relatime", "cifs", "//foo.home/bar/", "rw,unc=\\\\foo.home\\bar,username=kzak,domain=SRGROUP,uid=0,noforceuid,gid=0,noforcegid,addr=192.168.111.1,posixpaths,serverino,acl,rsize=16384,wsize=57344"),
- e(49, 20, "/", "/mnt/test/foobar", "rw,relatime,nosymfollow", "tmpfs", "tmpfs", "rw"),
- }},
- }
-
- for _, tc := range testCases {
- name := filepath.Join(t.TempDir(), "sample")
- if err := os.WriteFile(name, []byte(tc.sample), 0400); err != nil {
- t.Fatalf("cannot write sample: %v", err)
- }
-
- t.Run(tc.name, func(t *testing.T) {
- m, err := mountinfo.Open(name)
- if err != nil {
- t.Fatalf("Open: error = %v", err)
- }
- t.Cleanup(m.Close)
-
- i := 0
- var ent mountinfo.Entry
- for m.Next() {
- m.Copy(&ent)
-
- if i == len(tc.want) {
- t.Errorf("Next: got more than %d entries", i)
- t.FailNow()
- }
- if !ent.EqualWithIgnore(tc.want[i], "\x00") {
- t.Errorf("Next: entry %d\n got: %#v\nwant: %#v", i,
- ent, &tc.want[i])
- t.FailNow()
- } else {
- t.Logf("%s", &ent)
- }
-
- i++
- }
-
- if err = m.Err(); err != nil {
- t.Fatalf("Err: %v", err)
- }
- })
-
- if err := os.Remove(name); err != nil {
- t.Fatalf("cannot remove %q: %v", name, err)
- }
- }
-}
-
-func e(
- id, parent int,
- root, target, vfsOptstr string,
- fsType, source, fsOptstr string,
-) *mountinfo.Entry {
- return &mountinfo.Entry{
- ID: id,
- Parent: parent,
- Root: root,
- Target: target,
- VfsOptstr: vfsOptstr,
- FsType: fsType,
- Source: source,
- FsOptstr: fsOptstr,
- }
-}
diff --git a/test/internal/testsuite/fs.go b/test/internal/testsuite/fs.go
deleted file mode 100644
index 9acd24b6..00000000
--- a/test/internal/testsuite/fs.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package testsuite
-
-import (
- "errors"
- "fmt"
- "io/fs"
- "path/filepath"
- "strings"
-)
-
-var (
- // ErrFSBadLength is returned by [FS.Compare] for a directory with an
- // unexpected amount of dents.
- ErrFSBadLength = errors.New("bad dir length")
- // ErrFSBadData is returned by [FS.Compare] for a file with unexpected
- // contents.
- ErrFSBadData = errors.New("data differs")
- // ErrFSBadMode is returned by [FS.Compare] for an entry with unexpected
- // mode.
- ErrFSBadMode = errors.New("mode differs")
- // ErrFSInvalidEnt is returned by [FS.Compare] if an invalid [FS] is visited.
- ErrFSInvalidEnt = errors.New("invalid entry condition")
-)
-
-// FS represents part of a filesystem hierarchy.
-type FS struct {
- // Expected mode of corresponding entry.
- Mode fs.FileMode `json:"mode"`
- // Expected directory contents. The directory is not descended if Dir is nil.
- Dir map[string]*FS `json:"dir"`
- // Expected file contents. The file is not read if Data is nil.
- Data *string `json:"data"`
-}
-
-// dprintf calls printf if it is non-nil.
-func dprintf(printf func(format string, a ...any), format string, a ...any) {
- if printf == nil {
- return
- }
- printf(format, a...)
-}
-
-// printDir prints a failed [FS.Compare] directory.
-func printDir(
- printf func(format string, a ...any),
- prefix string,
- dir []fs.DirEntry,
-) {
- names := make([]string, len(dir))
- for i, ent := range dir {
- name := ent.Name()
- if ent.IsDir() {
- name += "/"
- }
- names[i] = fmt.Sprintf("%q", name)
- }
- dprintf(printf, "[FAIL] d %s: %s", prefix, strings.Join(names, " "))
-}
-
-// Compare compares the contents of prefix against the hierarchy described by s.
-func (s *FS) Compare(
- printf func(format string, a ...any),
- prefix string,
- e fs.FS,
-) error {
- if s.Data != nil {
- if s.Dir != nil {
- panic("invalid state")
- }
- panic("invalid compare call")
- }
-
- if s.Dir == nil {
- dprintf(printf, "[ OK ] s %s", prefix)
- return nil
- }
-
- var dir []fs.DirEntry
- if d, err := fs.ReadDir(e, prefix); err != nil {
- return err
- } else if len(d) != len(s.Dir) {
- printDir(printf, prefix, d)
- return ErrFSBadLength
- } else {
- dir = d
- }
-
- for _, got := range dir {
- name := got.Name()
-
- if want, ok := s.Dir[name]; !ok {
- printDir(printf, prefix, dir)
- return fs.ErrNotExist
- } else if want.Dir != nil && !got.IsDir() {
- printDir(printf, prefix, dir)
- return ErrFSInvalidEnt
- } else {
- name = filepath.Join(prefix, name)
-
- if fi, err := got.Info(); err != nil {
- return err
- } else if fi.Mode() != want.Mode {
- dprintf(printf, "[FAIL] m %s: %#o, want %#o",
- name, uint32(fi.Mode()), uint32(want.Mode))
- return ErrFSBadMode
- }
-
- if want.Data != nil {
- if want.Dir != nil {
- panic("invalid state")
- }
- if v, err := fs.ReadFile(e, name); err != nil {
- return err
- } else if string(v) != *want.Data {
- dprintf(printf,
- "[FAIL] f %s\n\t got: %s\n\twant: %s",
- name, v, *want.Data,
- )
- return ErrFSBadData
- }
- dprintf(printf, "[ OK ] f %s", name)
- } else if err := want.Compare(printf, name, e); err != nil {
- return err
- }
- }
- }
- dprintf(printf, "[ OK ] d %s", prefix)
- return nil
-}
diff --git a/test/internal/testsuite/fs_test.go b/test/internal/testsuite/fs_test.go
deleted file mode 100644
index 5c93bb46..00000000
--- a/test/internal/testsuite/fs_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package testsuite_test
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io/fs"
- "testing"
- "testing/fstest"
-
- "hakurei.app/test/internal/testsuite"
-)
-
-func TestCompare(t *testing.T) {
- var (
- fsPasswdSample = "u0_a20:x:65534:65534:Hakurei:/var/lib/persist/module/hakurei/u0/a20:/run/current-system/sw/bin/zsh"
- fsGroupSample = "hakurei:x:65534:"
- )
-
- testCases := []struct {
- name string
-
- sample fstest.MapFS
- want *testsuite.FS
- wantOut string
- wantErr error
- }{
- {"skip", fstest.MapFS{}, &testsuite.FS{}, "[ OK ] s .\x00", nil},
- {"simple pass", fstest.MapFS{".hakurei": {Mode: 0x800001ed}},
- &testsuite.FS{Dir: map[string]*testsuite.FS{".hakurei": {Mode: 0x800001ed}}},
- "[ OK ] s .hakurei\x00[ OK ] d .\x00", nil},
- {"bad length", fstest.MapFS{".hakurei": {Mode: 0x800001ed}},
- &testsuite.FS{Dir: make(map[string]*testsuite.FS)},
- "[FAIL] d .: \".hakurei/\"\x00", testsuite.ErrFSBadLength},
- {"top level bad mode", fstest.MapFS{".hakurei": {Mode: 0x800001ed}},
- &testsuite.FS{Dir: map[string]*testsuite.FS{".hakurei": {Mode: 0xdeadbeef}}},
- "[FAIL] m .hakurei: 020000000755, want 033653337357\x00", testsuite.ErrFSBadMode},
- {"invalid entry condition", fstest.MapFS{"test": {Data: []byte{'0'}, Mode: 0644}},
- &testsuite.FS{Dir: map[string]*testsuite.FS{"test": {Dir: make(map[string]*testsuite.FS)}}},
- "[FAIL] d .: \"test\"\x00", testsuite.ErrFSInvalidEnt},
- {"nonexistent", fstest.MapFS{"test": {Data: []byte{'0'}, Mode: 0644}},
- &testsuite.FS{Dir: map[string]*testsuite.FS{".test": {}}},
- "[FAIL] d .: \"test\"\x00", fs.ErrNotExist},
- {"file", fstest.MapFS{"etc": {Mode: 0x800001c0},
- "etc/passwd": {Data: []byte(fsPasswdSample), Mode: 0644},
- "etc/group": {Data: []byte(fsGroupSample), Mode: 0644},
- }, &testsuite.FS{Dir: map[string]*testsuite.FS{"etc": {Mode: 0x800001c0, Dir: map[string]*testsuite.FS{
- "passwd": {Mode: 0x1a4, Data: &fsPasswdSample},
- "group": {Mode: 0x1a4, Data: &fsGroupSample},
- }}}}, "[ OK ] f etc/group\x00[ OK ] f etc/passwd\x00[ OK ] d etc\x00[ OK ] d .\x00", nil},
- {"file differ", fstest.MapFS{"etc": {Mode: 0x800001c0},
- "etc/passwd": {Data: []byte(fsPasswdSample), Mode: 0644},
- "etc/group": {Data: []byte(fsGroupSample), Mode: 0644},
- }, &testsuite.FS{Dir: map[string]*testsuite.FS{"etc": {Mode: 0x800001c0, Dir: map[string]*testsuite.FS{
- "passwd": {Mode: 0x1a4, Data: &fsGroupSample},
- "group": {Mode: 0x1a4, Data: &fsGroupSample},
- }}}}, "[ OK ] f etc/group\x00[FAIL] f etc/passwd\n\t got: u0_a20:x:65534:65534:Hakurei:/var/lib/persist/module/hakurei/u0/a20:/run/current-system/sw/bin/zsh\n\twant: hakurei:x:65534:\x00", testsuite.ErrFSBadData},
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- var buf bytes.Buffer
-
- err := tc.want.Compare(
- func(format string, a ...any) {
- _, _ = fmt.Fprintf(&buf, format+"\x00", a...)
- },
- ".", tc.sample,
- )
- if !errors.Is(err, tc.wantErr) {
- t.Errorf(
- "Compare: error = %v; wantErr %v",
- err, tc.wantErr,
- )
- }
-
- if buf.String() != tc.wantOut {
- t.Errorf(
- "Compare: output %q; want %q",
- &buf, tc.wantOut,
- )
- }
- })
- }
-}
diff --git a/test/internal/testsuite/proc.go b/test/internal/testsuite/proc.go
deleted file mode 100644
index e7ef1aed..00000000
--- a/test/internal/testsuite/proc.go
+++ /dev/null
@@ -1,353 +0,0 @@
-package testsuite
-
-import (
- "bytes"
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "syscall"
- "unsafe"
-
- "hakurei.app/fhs"
-)
-
-// Stat represents status information read from /proc/pid/stat.
-type Stat struct {
- // The process ID.
- PID int
- // The filename of the executable, with parenthesis stripped.
- Comm string
- // One of the following characters, indicating process state:
- //
- // R Running
- //
- // S Sleeping in an interruptible wait
- //
- // D Waiting in uninterruptible disk sleep
- //
- // Z Zombie
- //
- // T Stopped (on a signal) or (before Linux
- // 2.6.33) trace stopped
- //
- // t Tracing stop (Linux 2.6.33 onward)
- //
- // W Paging (only before Linux 2.6.0)
- //
- // X Dead (from Linux 2.6.0 onward)
- //
- // x Dead (Linux 2.6.33 to 3.13 only)
- //
- // K Wakekill (Linux 2.6.33 to 3.13 only)
- //
- // W Waking (Linux 2.6.33 to 3.13 only)
- //
- // P Parked (Linux 3.9 to 3.13 only)
- //
- // I Idle (Linux 4.14 onward)
- State byte
- // The process ID of the parent of this process.
- PPID int
- // The process group ID of the process.
- PGRP int
- // The session ID of the process.
- Session int
- // The controlling terminal of the process.
- TTYNR int
- // The ID of the foreground process group of the controlling terminal of the
- // process.
- TPGID int
- // The kernel flags word of the process. For bit meanings, see the PF_*
- // defines in the Linux kernel source file include/linux/sched.h.
- Flags uint
- // The number of minor faults the process has made which have not required
- // loading a memory page from disk.
- MinFlt uint
- // The number of minor faults that the process's waited-for children have
- // made.
- CMinFlt uint
- // The number of major faults the process has made which have required
- // loading a memory page from disk.
- MajFlt uint
- // The number of major faults that the process's waited-for children have
- // made.
- CMajFlt uint
- // Amount of time that this process has been scheduled in user mode,
- // measured in clock ticks.
- UTime uint
- // Amount of time that this process has been scheduled in kernel mode,
- // measured in clock ticks.
- STime uint
- // Amount of time that this process's waited-for children have been
- // scheduled in user mode, measured in clock ticks.
- CUTime int
- // Amount of time that this process's waited-for children have been
- // scheduled in kernel mode, measured in clock ticks.
- CSTime int
- // For processes running a real-time scheduling policy, this is the negated
- // scheduling priority, minus one.
- Priority int
- // The nice value, a value in the range 19 (low priority) to -20 (high
- // priority).
- Nice int
- // Number of threads in this process.
- NumThreads int
-
- // unmaintained field: itrealvalue
-
- // The time the process started after system boot. Since Linux 2.6, the
- // value is expressed in clock ticks.
- StartTime uint64
- // Virtual memory size in bytes.
- VSize uint
- // Resident set size in pages.
- RSS int
- // Soft limit in bytes on the rss of the process.
- RSSLim uint64
- // The address above which program text can run.
- StartCode uint64
- // The address below which program text can run.
- EndCode uint64
- // The address of the start (i.e., bottom) of the stack.
- StartStack uint64
- // The current value of ESP (stack pointer), as found in the kernel stack
- // page for the process.
- KSTKESP uint64
- // The current EIP (instruction pointer).
- KSTKEIP uint64
-
- // obsolete fields: signal, blocked, sigignore, sigcatch
-
- // This is the "channel" in which the process is waiting. It is the address
- // of a location in the kernel where the process is sleeping.
- WChan uint64
-
- // unmaintained fields: nswap, cnswap
-
- // Signal to be sent to parent when we die.
- ExitSignal int
- // CPU number last executed on.
- Processor int
- // Real-time scheduling priority, a number in the range 1 to 99 for processes
- // scheduled under a real-time policy, or 0, for non-real-time processes.
- RTPriority uint
- // Scheduling policy (see sched_setscheduler(2)). Decode using the SCHED_*
- // constants in linux/sched.h.
- Policy uint
- // Aggregated block I/O delays, measured in clock ticks (centiseconds).
- DelayAcctBlkIOTicks uint64
- // Guest time of the process (time spent running a virtual CPU for a guest
- // operating system), measured in clock ticks.
- GuestTime int
- // Guest time of the process's children, measured in clock ticks.
- CGuestTime int
-}
-
-// Executable is like [os.Executable], but for the process referred to by s.
-func (s *Stat) Executable() (string, error) {
- path, err := os.Readlink(filepath.Join(fhs.Proc, strconv.Itoa(s.PID), "exe"))
-
- // When the executable has been deleted then Readlink returns a
- // path appended with " (deleted)".
- return strings.TrimSuffix(path, " (deleted)"), err
-}
-
-// Stat populates stat with the proc filesystem entry referred to by s.
-func (s *Stat) Stat(stat *syscall.Stat_t) (err error) {
- err = syscall.Stat(filepath.Join(fhs.Proc, strconv.Itoa(s.PID)), stat)
- if err != nil {
- err = os.NewSyscallError("stat", err)
- }
- return
-}
-
-// Args reads arguments of the process referred to by s.
-func (s *Stat) Args() ([]string, error) {
- p, err := os.ReadFile(filepath.Join(fhs.Proc, strconv.Itoa(s.PID), "cmdline"))
- if err != nil {
- return nil, err
- }
- a := bytes.Split(p, []byte{0})
- if len(a) > 0 && len(a[len(a)-1]) == 0 {
- a = a[:len(a)-1]
- }
-
- args := make([]string, len(a))
- for i, arg := range a {
- args[i] = unsafe.String(unsafe.SliceData(arg), len(arg))
- }
- return args, nil
-}
-
-// ErrBadDelimiters is returned by [Stat.UnmarshalText] if one or both bytes of
-// the comm delimiter pair were missing or misplaced.
-var ErrBadDelimiters = errors.New("missing comm delimiters")
-
-// UnmarshalText populates the structure pointed to by s from text.
-func (s *Stat) UnmarshalText(text []byte) (err error) {
- var (
- discard uint64
- _uint64 = &discard
- _int64 = (*int64)(unsafe.Pointer(&discard))
-
- ld = bytes.Index(text, []byte("("))
- rd = bytes.LastIndex(text, []byte(")"))
- )
-
- if ld <= 0 || rd < 0 {
- return ErrBadDelimiters
- }
-
- if s.PID, err = strconv.Atoi(
- unsafe.String(unsafe.SliceData(text), ld-1),
- ); err != nil {
- return
- }
-
- s.Comm = string(text[ld+1 : rd])
-
- var (
- n int
-
- state string
- )
- n, err = fmt.Fscan(
- bytes.NewBuffer(text[rd+2:]),
- &state,
- &s.PPID,
- &s.PGRP,
- &s.Session,
- &s.TTYNR,
- &s.TPGID,
- &s.Flags,
- &s.MinFlt,
- &s.CMinFlt,
- &s.MajFlt,
- &s.CMajFlt,
- &s.UTime,
- &s.STime,
- &s.CUTime,
- &s.CSTime,
- &s.Priority,
- &s.Nice,
- &s.NumThreads,
- _int64,
- &s.StartTime,
- &s.VSize,
- &s.RSS,
- &s.RSSLim,
- &s.StartCode,
- &s.EndCode,
- &s.StartStack,
- &s.KSTKESP,
- &s.KSTKEIP,
- _uint64,
- _uint64,
- _uint64,
- _uint64,
- &s.WChan,
- _uint64,
- _uint64,
- &s.ExitSignal,
- &s.Processor,
- &s.RTPriority,
- &s.Policy,
- &s.DelayAcctBlkIOTicks,
- &s.GuestTime,
- &s.CGuestTime,
- )
- if err != nil {
- err = fmt.Errorf("field %d: %w", n, err)
- } else if len(state) != 1 {
- err = fmt.Errorf("invalid state %q", state)
- } else {
- s.State = state[0]
- }
- return
-}
-
-// A StatScanner continuously scans the proc filesystem for process status
-// information in /proc/pid/stat.
-type StatScanner struct {
- // Current entry.
- stat Stat
- // Cached top-level /proc entries.
- dents []os.DirEntry
- // Current progress through dents.
- i int
- // Whether the previous call to Scan had repopulated dents.
- wrapped bool
- // First stored error: a non-nil err disables the scanner.
- err error
-}
-
-// IsNotExist returns whether an error is [os.ErrNotExist] or ESRCH.
-func IsNotExist(err error) bool {
- return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ESRCH)
-}
-
-// Scan reads a process status information entry. It returns false if an
-// unrecoverable error is encountered, after which Scan no longer scans new
-// entries.
-func (s *StatScanner) Scan() bool {
- if s.err != nil {
- return false
- }
-
- if s.wrapped = s.i == len(s.dents); s.wrapped {
- if s.dents, s.err = os.ReadDir(fhs.Proc); s.err != nil {
- return false
- }
- s.i = 0
- if len(s.dents) == 0 {
- s.err = syscall.ENOTRECOVERABLE
- return false
- }
- }
-
- for s.i < len(s.dents) {
- dent := s.dents[s.i]
- s.i++
- if !dent.IsDir() {
- continue
- }
-
- pid, err := strconv.Atoi(dent.Name())
- if err != nil {
- continue
- }
-
- var p []byte
- p, err = os.ReadFile(filepath.Join(fhs.Proc, dent.Name(), "stat"))
- if err != nil {
- if IsNotExist(err) {
- continue
- }
- s.err = err
- return false
- }
-
- s.err = s.stat.UnmarshalText(p)
- if s.err == nil && pid != s.stat.PID {
- s.err = fmt.Errorf(
- "bad status information: dent=%d, stat=%d",
- pid, s.stat.PID,
- )
- }
- return s.err == nil
- }
- return s.Scan()
-}
-
-// Stat returns the address of the [Stat] structure populated by the last call
-// to Scan.
-func (s *StatScanner) Stat() *Stat { return &s.stat }
-
-// Err returns the stored error value.
-func (s *StatScanner) Err() error { return s.err }
-
-// Repopulated returns whether the last Scan call had re-read the proc filesystem.
-func (s *StatScanner) Repopulated() bool { return s.wrapped }
diff --git a/test/internal/testsuite/proc_test.go b/test/internal/testsuite/proc_test.go
deleted file mode 100644
index a8698e73..00000000
--- a/test/internal/testsuite/proc_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package testsuite_test
-
-import (
- "testing"
-
- "hakurei.app/test/internal/testsuite"
-)
-
-func BenchmarkStatScanner(b *testing.B) {
- var s testsuite.StatScanner
-
- for b.Loop() {
- if !s.Scan() {
- b.Fatal(s.Err())
- }
- }
-}
-
-func BenchmarkStatScannerFull(b *testing.B) {
- var s testsuite.StatScanner
-
- for b.Loop() {
- for s.Scan() {
- if s.Repopulated() {
- break
- }
- }
-
- if err := s.Err(); err != nil {
- b.Fatal(err)
- }
- }
-}
diff --git a/test/internal/testsuite/ptrace.go b/test/internal/testsuite/ptrace.go
deleted file mode 100644
index ccf0900c..00000000
--- a/test/internal/testsuite/ptrace.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package testsuite
-
-import (
- "crypto/sha512"
- "encoding/base64"
- "errors"
- "fmt"
- "os"
- "syscall"
- "unsafe"
-)
-
-const (
- // _PTRACE_ATTACH attaches to the process specified in pid.
- _PTRACE_ATTACH = 16
- // _PTRACE_DETACH restarts the stopped tracee as for PTRACE_CONT, but first
- // detaches from it.
- _PTRACE_DETACH = 17
-
- // _PTRACE_SECCOMP_GET_FILTER allows the tracer to dump the tracee's classic
- // BPF filters.
- _PTRACE_SECCOMP_GET_FILTER = 0x420c
-)
-
-// ptrace wraps the ptrace syscall.
-func ptrace(
- op uintptr,
- pid, addr int,
- data unsafe.Pointer,
-) (r uintptr, errno syscall.Errno) {
- r, _, errno = syscall.Syscall6(
- syscall.SYS_PTRACE,
- op,
- uintptr(pid),
- uintptr(addr),
- uintptr(data),
- 0, 0,
- )
- return
-}
-
-// ptraceAttach attaches to the process referred to by pid.
-func ptraceAttach(pid int) error {
- if _, errno := ptrace(_PTRACE_ATTACH, pid, 0, nil); errno != 0 {
- return os.NewSyscallError("PTRACE_ATTACH", errno)
- }
-
- var status syscall.WaitStatus
- for {
- if _, err := syscall.Wait4(
- pid,
- &status,
- syscall.WALL,
- nil,
- ); err != nil {
- if errors.Is(err, syscall.EINTR) {
- continue
- }
- return os.NewSyscallError("wait4", err)
- }
- switch {
- case status.Stopped():
- return nil
-
- case status.Continued():
- continue
-
- case status.Signaled():
- return fmt.Errorf(
- "tracee terminated by signal %s",
- status.Signal(),
- )
-
- case status.Exited():
- return fmt.Errorf(
- "tracee terminated unexpectedly with code %d",
- status.ExitStatus(),
- )
- }
- }
-}
-
-// ptraceDetach detaches from the attached process referred to by pid.
-func ptraceDetach(pid int) error {
- if _, errno := ptrace(_PTRACE_DETACH, pid, 0, nil); errno != 0 {
- return os.NewSyscallError("PTRACE_DETACH", errno)
- }
- return nil
-}
-
-// getFilter dumps the specified tracee's cBPF filter at the specified index
-// and returns the resulting payload. T must be eight bytes long and must not
-// contain pointers.
-func getFilter(pid, index int) ([]syscall.SockFilter, error) {
- var buf []syscall.SockFilter
- if n, errno := ptrace(
- _PTRACE_SECCOMP_GET_FILTER,
- pid, index, nil,
- ); errno != 0 {
- return nil, os.NewSyscallError("PTRACE_SECCOMP_GET_FILTER", errno)
- } else {
- buf = make([]syscall.SockFilter, n)
- }
- if _, errno := ptrace(
- _PTRACE_SECCOMP_GET_FILTER,
- pid, index, unsafe.Pointer(&buf[0]),
- ); errno != 0 {
- return nil, os.NewSyscallError("PTRACE_SECCOMP_GET_FILTER", errno)
- }
- return buf, nil
-}
-
-// CheckFilter checks the process at pid to have its first filter's contents
-// match the specified sha512 checksum.
-func CheckFilter(pid, index int, sum [sha512.Size]byte) (err error) {
- if err = ptraceAttach(pid); err != nil {
- return
- }
- defer func() {
- if detachErr := ptraceDetach(pid); err == nil {
- err = detachErr
- }
- }()
-
- var buf []syscall.SockFilter
- h := sha512.New()
- if buf, err = getFilter(pid, index); err != nil {
- return
- } else {
- h.Write(unsafe.Slice(
- (*byte)(unsafe.Pointer(&buf[0])),
- uintptr(len(buf))*unsafe.Sizeof(buf[0]),
- ))
- }
-
- if got := h.Sum(nil); string(got) != string(sum[:]) {
- return fmt.Errorf(
- "bad filter\n\t got: %s\n\twant: %s",
- base64.StdEncoding.EncodeToString(got),
- base64.StdEncoding.EncodeToString(sum[:]),
- )
- }
- return
-}
diff --git a/test/internal/testsuite/ptrace_test.go b/test/internal/testsuite/ptrace_test.go
deleted file mode 100644
index eaaed131..00000000
--- a/test/internal/testsuite/ptrace_test.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package testsuite
-
-import (
- "syscall"
- "testing"
- "unsafe"
-)
-
-func TestBlockSize(t *testing.T) {
- if sz := unsafe.Sizeof(syscall.SockFilter{}); sz != 8 {
- t.Fatalf("invalid filter block size %d", sz)
- }
-}
diff --git a/test/internal/testsuite/testsuite.go b/test/internal/testsuite/testsuite.go
deleted file mode 100644
index 456c36b4..00000000
--- a/test/internal/testsuite/testsuite.go
+++ /dev/null
@@ -1,290 +0,0 @@
-// Package testsuite provides many quick-and-dirty integration testing utilities.
-//
-// Attempting to import this package outside testing causes the resulting
-// program to panic.
-package testsuite
-
-import (
- "bufio"
- "context"
- "crypto/sha512"
- "errors"
- "log"
- "os"
- "os/exec"
- "os/signal"
- "sync"
- "syscall"
- "time"
-)
-
-// ReceiveSignals blocks until a termination signal arrives, and terminates.
-func ReceiveSignals() {
- s := make(chan os.Signal, 3)
- signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
- log.Fatalf("terminating on signal %s", <-s)
-}
-
-// MustRun runs command and terminates the testsuite on error.
-func MustRun(cred *syscall.Credential, extraEnv []string, command ...string) {
- cmd := exec.Command(command[0], command[1:]...)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGKILL,
- Credential: cred,
- }
- if len(extraEnv) != 0 {
- cmd.Env = append(cmd.Environ(), extraEnv...)
- }
- if err := cmd.Run(); err != nil {
- log.Fatal(err)
- }
-}
-
-// ErrUnexpectedSuccess is returned for processes expected to exit with a
-// non-zero code, but failed to do so.
-var ErrUnexpectedSuccess = errors.New("process unexpectedly exited with code 0")
-
-// MustFail runs command and terminates the testsuite if the program fails to
-// start or exits with code 0.
-func MustFail(cred *syscall.Credential, extraEnv []string, command ...string) {
- cmd := exec.Command(command[0], command[1:]...)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGKILL,
- Credential: cred,
- }
- if len(extraEnv) != 0 {
- cmd.Env = append(cmd.Environ(), extraEnv...)
- }
- if err := cmd.Run(); err == nil {
- log.Fatal(ErrUnexpectedSuccess)
- } else if e, ok := errors.AsType[*exec.ExitError](err); !ok {
- log.Fatal(err)
- } else if !e.Exited() {
- log.Fatal(e)
- }
-}
-
-// MustStart starts cmd and returns a channel delivering its wait error.
-func MustStart(cmd *exec.Cmd) (done <-chan error) {
- if err := cmd.Start(); err != nil {
- log.Fatal(err)
- }
- d := make(chan error)
- go func() { d <- cmd.Wait() }()
- return d
-}
-
-// MustStartWith wraps [MustStart] and creates the [exec.Cmd] object internally.
-func MustStartWith(
- ctx context.Context,
- cred *syscall.Credential,
- extraEnv []string,
- files []*os.File,
- command ...string,
-) (proc *os.Process, done <-chan error) {
- cmd := exec.CommandContext(ctx, command[0], command[1:]...)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- cmd.ExtraFiles = files
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGTERM,
- Credential: cred,
- }
- if len(extraEnv) != 0 {
- cmd.Env = append(cmd.Environ(), extraEnv...)
- }
- return cmd.Process, MustStart(cmd)
-}
-
-// MustCheckFilter is like [CheckFilter], but terminates the test suite if a
-// non-nil error is returned. Otherwise, the tracee is terminated after it
-// resumes.
-func MustCheckFilter(pid int, sum [sha512.Size]byte) {
- // podman installs its own filter
- if err := CheckFilter(pid, 1, sum); err != nil {
- log.Fatal(err)
- } else if err = syscall.Kill(pid, syscall.SIGTERM); err != nil {
- log.Fatalf("cannot terminate tracee: %v", err)
- }
-}
-
-// FilterTerminated returns a non-nil error if err is not an [exec.ExitError]
-// describing a process terminated by a syscall.SIGTERM signal.
-func FilterTerminated(err error) error {
- if err == nil {
- return ErrUnexpectedSuccess
- }
-
- e, ok := errors.AsType[*exec.ExitError](err)
- if !ok {
- return err
- }
-
- if e.ExitCode() == 0x80+int(syscall.SIGTERM) {
- return nil
- }
- return e
-}
-
-// Poll repeatedly runs command until it succeeds.
-func Poll(
- d time.Duration,
- cred *syscall.Credential,
- extraEnv []string,
- command ...string,
-) {
- for range time.NewTicker(d).C {
- cmd := exec.Command(command[0], command[1:]...)
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGKILL,
- Credential: cred,
- }
- if len(extraEnv) != 0 {
- cmd.Env = append(cmd.Environ(), extraEnv...)
- }
- if err := cmd.Run(); err != nil {
- if e, ok := errors.AsType[*exec.ExitError](err); ok && e.Exited() {
- continue
- }
- log.Fatal(err)
- }
- break
- }
-}
-
-const (
- // XDGRuntimeDir is the hardcoded XDG runtime directory for the user
- // described by [GetUser].
- XDGRuntimeDir = "/var/run/user/1000"
-
- // XDGRuntimeEnv is the environment variable string for XDG_RUNTIME_DIR.
- XDGRuntimeEnv = "XDG_RUNTIME_DIR=" + XDGRuntimeDir
-)
-
-// MustStartSessionBus starts a session bus that is never explicitly terminated.
-// The test suite is terminated if the session bus daemon terminates.
-func MustStartSessionBus(cred *syscall.Credential) (dbusEnv string) {
- r, w, err := os.Pipe()
- if err != nil {
- log.Fatal(err)
- }
-
- // this is never explicitly terminated
- _, done := MustStartWith(
- context.Background(), cred, nil, []*os.File{w},
- "dbus-daemon",
- "--print-address=3",
- "--address=unix:path="+XDGRuntimeDir+"/dbus",
- "--session",
- "--nofork",
- "--nopidfile",
- )
-
- go func() {
- if _err := <-done; _err != nil {
- log.Fatal(_err)
- }
- log.Fatal("session bus terminated unexpectedly")
- }()
-
- dbusEnv, err = bufio.NewReader(r).ReadString('\n')
- if err != nil {
- log.Fatal(err)
- }
- dbusEnv = dbusEnv[:len(dbusEnv)-1]
- log.Printf("dbus listening on %s", dbusEnv)
- dbusEnv = "DBUS_SESSION_BUS_ADDRESS=" + dbusEnv
-
- if err = r.Close(); err != nil {
- log.Fatal(err)
- }
- return
-}
-
-const (
- // SwayEnv is the environment variable string for the sway IPC socket.
- SwayEnv = "SWAYSOCK=" + XDGRuntimeDir + "/sway"
- // WaylandEnv is the environment variable string for the wayland display.
- WaylandEnv = "WAYLAND_DISPLAY=wayland-1"
-)
-
-// MustStartSway starts the sway wayland display server which must be terminated
-// by calling [TerminateSway].
-func MustStartSway(
- wg *sync.WaitGroup,
- cred *syscall.Credential,
- dbusEnv string,
-) {
- wg.Go(func() {
- // this is terminated via swaymsg
- _, done := MustStartWith(
- context.Background(), cred, []string{
- "WLR_BACKENDS=headless",
- XDGRuntimeEnv,
- SwayEnv,
- dbusEnv,
- }, nil,
- "sway",
- )
- if err := <-done; err != nil {
- log.Fatal(err)
- }
- })
-
- Poll(
- 50*time.Millisecond,
- cred,
- []string{SwayEnv},
- "swaymsg",
- )
- log.Printf("sway available via %s", SwayEnv)
-}
-
-// TerminateSway requests for the sway server to terminate via sway IPC.
-func TerminateSway(cred *syscall.Credential) {
- MustFail(cred, []string{SwayEnv}, "swaymsg", "exit")
-}
-
-// MustStartPipeWire starts a PipeWire server that is never explicitly
-// terminated. The test suite is terminated if the PipeWire server terminates.
-func MustStartPipeWire(cred *syscall.Credential, dbusEnv string) {
- // this is never explicitly terminated
- _, done := MustStartWith(
- context.Background(), cred, []string{
- XDGRuntimeEnv,
- dbusEnv,
- }, nil,
- "pipewire",
- )
-
- go func() {
- if _err := <-done; _err != nil {
- log.Fatal(_err)
- }
- log.Fatal("pipewire terminated unexpectedly")
- }()
-
- Poll(50*time.Millisecond, cred, []string{
- XDGRuntimeEnv,
- dbusEnv,
- },
- "wpctl",
- "status",
- )
-
- _, _done := MustStartWith(
- context.Background(), cred, []string{
- XDGRuntimeEnv,
- dbusEnv,
- }, nil,
- "wireplumber",
- )
-
- go func() {
- if _err := <-_done; _err != nil {
- log.Fatal(_err)
- }
- log.Fatal("wireplumber terminated unexpectedly")
- }()
-}
diff --git a/test/internal/testsuite/testsuite_guard.go b/test/internal/testsuite/testsuite_guard.go
deleted file mode 100644
index 0859d17f..00000000
--- a/test/internal/testsuite/testsuite_guard.go
+++ /dev/null
@@ -1,15 +0,0 @@
-//go:build !testsuite && !tester
-
-package testsuite
-
-import (
- "os"
- "testing"
-)
-
-func init() {
- if !testing.Testing() {
- println("package testsuite imported in non-testsuite program")
- os.Exit(1)
- }
-}
diff --git a/test/internal/testsuite/testsuite_root.go b/test/internal/testsuite/testsuite_root.go
deleted file mode 100644
index 11f503ef..00000000
--- a/test/internal/testsuite/testsuite_root.go
+++ /dev/null
@@ -1,17 +0,0 @@
-//go:build testsuite
-
-package testsuite
-
-import (
- "log"
- "os"
-)
-
-func init() {
- if os.Geteuid() != 0 {
- log.Fatal("this program must run as root")
- }
-
- log.SetFlags(0)
- log.SetPrefix("testsuite: ")
-}
diff --git a/test/sandbox/main.go b/test/sandbox/main.go
deleted file mode 100644
index 4961c04f..00000000
--- a/test/sandbox/main.go
+++ /dev/null
@@ -1,409 +0,0 @@
-//go:build testsuite
-
-// The sandbox test program runs cmd/hakurei with configurations simulating
-// several common workloads and inspects the resulting container states.
-package main
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "io"
- "log"
- "os"
- "os/exec"
- "path/filepath"
- "slices"
- "strconv"
- "strings"
- "sync"
- "sync/atomic"
- "syscall"
-
- "hakurei.app/check"
- "hakurei.app/fhs"
- "hakurei.app/hst"
- "hakurei.app/internal/store"
-
- "hakurei.app/test/internal/testsuite"
- "hakurei.app/test/sandbox/testdata"
-)
-
-// mustScanFor continuously scans the proc filesystem and calls f for each entry
-// visited.
-func mustScanFor(f func(ps *testsuite.StatScanner) bool) int {
- var ps testsuite.StatScanner
-
- for ps.Scan() {
- if f(&ps) {
- break
- }
- }
- if err := ps.Err(); err != nil {
- log.Fatal(err)
- }
- return ps.Stat().PID
-}
-
-// mustStart starts a hakurei container and returns the pid of a process within
-// the container. This process must be terminated by the caller.
-func mustStart(
- ctx context.Context,
- serial uint64,
- cred *syscall.Credential,
- files ...*os.File,
-) (pid int, done <-chan error) {
- _serial := strconv.FormatUint(serial, 10)
- _, done = testsuite.MustStartWith(
- ctx, cred, nil, files,
- "hakurei", "exec",
- "sleep", "infinity", _serial,
- )
-
- var stat syscall.Stat_t
- pid = mustScanFor(func(s *testsuite.StatScanner) bool {
- select {
- case err := <-done:
- if err == nil {
- log.Fatal("test process terminated unexpectedly")
- }
- log.Fatal(err)
- default:
- break
- }
-
- if s.Stat().Comm != "sleep" {
- return false
- }
-
- if args, err := s.Stat().Args(); err != nil {
- if testsuite.IsNotExist(err) {
- return false
- }
- log.Fatal(err)
- } else if !slices.Equal(args, []string{
- "sleep",
- "infinity",
- _serial,
- }) {
- return false
- }
-
- if err := s.Stat().Stat(&stat); err != nil {
- if testsuite.IsNotExist(err) {
- return false
- }
- log.Fatal(err)
- }
-
- id := hst.ToUser[uint32](0, 0)
- if stat.Uid != id || stat.Gid != id {
- return false
- }
-
- return true
- })
- return
-}
-
-func main() {
- go testsuite.ReceiveSignals()
-
- // the signal handler does not wait for termination
- ctx := context.Background()
-
- cred := syscall.Credential{Uid: 1000, Gid: 100}
- if err := os.MkdirAll("/opt/test-helper/bin", 0755); err != nil {
- log.Fatal(err)
- }
-
- var testToolDone <-chan error
- {
- cmd := exec.Command(
- "go", "build",
- "-o", "/opt/test-helper/bin",
- "-tags=tester",
- "-trimpath",
- "./test/sandbox/tester",
- )
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- testToolDone = testsuite.MustStart(cmd)
- }
-
- var wg sync.WaitGroup
- defer wg.Wait()
-
- var serial atomic.Uint64
- newSerial := func() uint64 { serial.Add(1); return serial.Load() }
-
- testsuite.MustRun(
- &cred, nil,
- "hakurei", "exec", "capsh", "--print",
- )
- wg.Go(func() {
- defer log.Println("validated capabilities/securebits in user namespace")
-
- testsuite.MustRun(
- &cred, nil,
- "hakurei", "exec", "capsh", "--has-no-new-privs",
- )
-
- for _, p := range []byte{'a', 'b', 'i', 'p'} {
- testsuite.MustFail(
- &cred, nil,
- "hakurei", "exec", "capsh", "--has-"+string(p)+"=CAP_SYS_ADMIN",
- )
- }
- testsuite.MustFail(
- &cred, nil,
- "hakurei", "exec", "umount", "-R", "/dev",
- )
- })
-
- wg.Go(func() {
- defer log.Println("validated pd seccomp outcome")
-
- c, cancel := context.WithCancel(ctx)
- defer cancel()
-
- pid, done := mustStart(c, newSerial(), &cred)
- testsuite.MustCheckFilter(pid, testdata.SumPD)
- if err := testsuite.FilterTerminated(<-done); err != nil {
- log.Fatal(err)
- }
- })
-
- wg.Go(func() {
- defer log.Println("validated fd leak")
-
- c, cancel := context.WithCancel(ctx)
- defer cancel()
-
- pid, done := mustStart(c, newSerial(), &cred, os.Stdin, os.Stdout, os.Stderr)
- prefix := filepath.Join(fhs.Proc, strconv.Itoa(pid), "fd")
-
- var fail bool
- if entries, err := os.ReadDir(prefix); err != nil {
- log.Fatal(err.Error())
- } else {
- for _, ent := range entries {
- var fd int
- if fd, err = strconv.Atoi(ent.Name()); err != nil {
- log.Fatal(err.Error())
- }
-
- // skip standard streams
- if fd <= 2 {
- continue
- }
- fail = true
-
- var d string
- if d, err = os.Readlink(filepath.Join(
- prefix,
- ent.Name(),
- )); err != nil {
- log.Fatal(err.Error())
- }
- log.Printf("extra fd %d -> %s", fd, d)
- }
- }
- if fail {
- log.Fatal("file descriptors leaked")
- }
-
- if err := syscall.Kill(pid, syscall.SIGTERM); err != nil {
- log.Fatalf("cannot terminate anchor: %v", err)
- } else if err = testsuite.FilterTerminated(<-done); err != nil {
- log.Fatal(err)
- }
- })
-
- if err := os.MkdirAll(testsuite.XDGRuntimeDir, 0700); err != nil {
- log.Fatal(err)
- } else if err = os.Chown(testsuite.XDGRuntimeDir, 1000, 1000); err != nil {
- log.Fatal(err)
- }
-
- var swg sync.WaitGroup
- defer swg.Wait()
- dbusEnv := testsuite.MustStartSessionBus(&cred)
- testsuite.MustStartSway(&swg, &cred, dbusEnv)
- defer testsuite.TerminateSway(&cred)
- testsuite.MustStartPipeWire(&cred, dbusEnv)
-
- if err := <-testToolDone; err != nil {
- log.Fatal(err)
- }
- log.Println("created test helper")
-
- s := store.New(check.MustAbs("/tmp/hakurei.0/state"))
- for name, tc := range testdata.All() {
- wg.Go(func() {
- cmd := exec.Command(
- "script", "/dev/null",
- "-E", "always",
- "-qec",
- "hakurei run "+
- "--identifier-fd=5"+
- " 4 1>&3",
- )
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGTERM,
- Credential: &cred,
- }
- var output bytes.Buffer
- cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, &output, &output
- cmd.Env = []string{
- "PATH=" + os.Getenv("PATH"),
- "TERM=xterm",
- testsuite.XDGRuntimeEnv,
- testsuite.WaylandEnv,
- "DISPLAY=:0",
- dbusEnv,
- }
-
- var err error
- var notify, _notify, _conf, conf, ident, _ident *os.File
- if notify, _notify, err = os.Pipe(); err != nil {
- log.Fatal(err)
- }
- cmd.ExtraFiles = append(cmd.ExtraFiles, _notify)
- if _conf, conf, err = os.Pipe(); err != nil {
- log.Fatal(err)
- }
- cmd.ExtraFiles = append(cmd.ExtraFiles, _conf)
- if ident, _ident, err = os.Pipe(); err != nil {
- log.Fatal(err)
- }
- cmd.ExtraFiles = append(cmd.ExtraFiles, _ident)
-
- done := testsuite.MustStart(cmd)
- wg.Go(func() {
- _err := <-done
- log.Printf("completed test case %s\n%s", name, output.String())
- if _err != nil {
- log.Fatalf("test case %s: %v", name, _err)
- }
- })
-
- if err = json.NewEncoder(conf).Encode(&tc.Hakurei); err != nil {
- log.Fatal(err)
- } else if err = conf.Close(); err != nil {
- log.Fatal(err)
- }
-
- var id hst.ID
- if _, err = io.ReadFull(ident, id[:]); err != nil {
- log.Fatal(err)
- } else if err = ident.Close(); err != nil {
- log.Fatal(err)
- }
-
- if _, err = io.ReadFull(notify, make([]byte, 8)); err != nil {
- log.Fatal(err)
- } else if err = notify.Close(); err != nil {
- log.Fatal(err)
- }
-
- var (
- ok bool
- p hst.State
- )
- entries, copyError := s.All()
- for entry := range entries {
- if entry.ID == id {
- ok = true
- if _, err = entry.Load(&p, nil); err != nil {
- log.Fatal(err)
- }
- break
- }
- }
- if err = copyError(); err != nil {
- log.Fatal(err)
- }
- if !ok {
- log.Fatalf("instance %s is not present in store", id)
- }
-
- var stat syscall.Stat_t
- pid := mustScanFor(func(ps *testsuite.StatScanner) bool {
- select {
- case err = <-done:
- if err == nil {
- log.Fatal("test process terminated unexpectedly")
- }
- log.Fatal(err)
- default:
- break
- }
-
- if ps.Stat().Comm != "test-helper" {
- return false
- }
-
- var args []string
- if args, err = ps.Stat().Args(); err != nil {
- if testsuite.IsNotExist(err) {
- return false
- }
- log.Fatal(err)
- } else if !slices.Equal(args, tc.Hakurei.Container.Args) {
- return false
- }
-
- if err = ps.Stat().Stat(&stat); err != nil {
- if testsuite.IsNotExist(err) {
- return false
- }
- log.Fatal(err)
- }
-
- uid := hst.ToUser[uint32](0, uint32(tc.Hakurei.Identity))
- if stat.Uid != uid || stat.Gid != uid {
- return false
- }
-
- var t []byte
- if t, err = os.ReadFile(filepath.Join(
- fhs.Proc,
- strconv.Itoa(ps.Stat().PPID),
- "stat",
- )); err != nil {
- if testsuite.IsNotExist(err) {
- return false
- }
- log.Fatal(err)
- }
-
- var _stat testsuite.Stat
- if err = _stat.UnmarshalText(t); err != nil {
- log.Fatal(err)
- }
- if _stat.PPID != p.ShimPID {
- return false
- }
-
- return true
- })
-
- testsuite.MustCheckFilter(
- pid,
- tc.Sum,
- )
- })
- }
-
- wg.Wait()
-
- if dents, err := os.ReadDir("/tmp"); err != nil {
- log.Fatal(err)
- } else {
- for _, dent := range dents {
- if name := dent.Name(); strings.HasPrefix(name, ".hakurei-shim-") {
- log.Fatalf("leftover shim work dir %q", name)
- }
- }
- }
-}
diff --git a/test/sandbox/seccomp.patch b/test/sandbox/seccomp.patch
deleted file mode 100644
index ddabc71e..00000000
--- a/test/sandbox/seccomp.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-diff --git a/kernel/seccomp.c b/kernel/seccomp.c
-index 25f62867a16d..7b63ccc8daf4 100644
---- a/kernel/seccomp.c
-+++ b/kernel/seccomp.c
-@@ -2216,8 +2216,12 @@ long seccomp_get_filter(struct task_struct *task, unsigned long filter_off,
- struct seccomp_filter *filter;
- struct sock_fprog_kern *fprog;
- long ret;
-+ struct user_namespace *user_ns = current_user_ns();
-
-- if (!capable(CAP_SYS_ADMIN) ||
-+ if (in_userns(user_ns, task_cred_xxx(task, user_ns))) {
-+ if (!ns_capable(user_ns, CAP_SYS_ADMIN))
-+ return -EACCES;
-+ } else if (!capable(CAP_SYS_ADMIN) ||
- current->seccomp.mode != SECCOMP_MODE_DISABLED) {
- return -EACCES;
- }
diff --git a/test/sandbox/testdata/device.go b/test/sandbox/testdata/device.go
deleted file mode 100644
index 89feb819..00000000
--- a/test/sandbox/testdata/device.go
+++ /dev/null
@@ -1,134 +0,0 @@
-//go:build testsuite || tester
-
-package testdata
-
-import (
- "os"
- "syscall"
-
- "hakurei.app/fhs"
- "hakurei.app/hst"
- "hakurei.app/test/internal/mountinfo"
- "hakurei.app/test/internal/testsuite"
-)
-
-var _ = TestCase{
- Hakurei: hst.Config{
- ID: "app.hakurei.sample.device",
- Enablements: new(hst.EWayland | hst.EPipeWire | hst.EDBus | hst.EX11),
- Identity: 4,
-
- Container: &hst.ContainerConfig{
- Hostname: "hakurei-sample-device",
-
- Filesystem: []hst.FilesystemConfigJSON{
- fcLinker,
- fcLib,
- fcTestHelper,
- },
-
- Username: "u0_a4",
- Shell: fhs.AbsUsrBin.Append("bash"),
- Home: hst.AbsPrivateTmp,
- Path: absTestHelper,
- Args: []string{"tester", "device"},
-
- Flags: hst.FDevice | hst.FShareTmpdir,
- },
- },
-
- // 0, PresetStrict
- Sum: sumSimple,
-
- Env: []string{
- "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
- "DISPLAY=unix:/tmp/.X11-unix/X0",
- "HOME=/.hakurei",
- "SHELL=/usr/bin/bash",
- "TERM=xterm",
- "USER=u0_a4",
- "WAYLAND_DISPLAY=wayland-0",
- "XDG_RUNTIME_DIR=/run/user/65534",
- "XDG_SESSION_CLASS=user",
- "XDG_SESSION_TYPE=wayland",
- "PULSE_SERVER=unix:/run/user/65534/pulse/native",
- },
-
- FS: &testsuite.FS{Dir: dir{
- ".hakurei": {Mode: os.ModeDir | 0755, Dir: dir{
- "test-helper": {Mode: 0755},
- }},
-
- // unstable host dev
- "dev": {Mode: os.ModeDir | 0755},
-
- "etc": {Mode: os.ModeDir | 0755, Dir: dir{
- "passwd": {Mode: 0600,
- Data: new("u0_a4:x:65534:65534:Hakurei:/.hakurei:/usr/bin/bash\n")},
- "group": {Mode: 0600,
- Data: new("hakurei:x:65534:\n")},
- }},
-
- "lib64": {Mode: os.ModeDir | 0755, Dir: dir{
- "ld-linux-x86-64.so.2": {Mode: os.ModeSymlink | 0777},
- }},
-
- "run": {Mode: os.ModeDir | 0755, Dir: dir{
- "user": {Mode: os.ModeDir | 0755, Dir: dir{
- "65534": {Mode: os.ModeDir | 0700, Dir: dir{
- "bus": {Mode: os.ModeSocket | 0775},
- "wayland-0": {Mode: os.ModeSocket | 070},
- "pulse": {Mode: os.ModeDir | 0700, Dir: dir{
- "native": {Mode: os.ModeSocket | 0777},
- }},
- }},
- }},
- }},
-
- "tmp": {Mode: os.ModeDir | 0770, Dir: dir{
- ".X11-unix": {Mode: os.ModeDir | 0755, Dir: dir{
- "X0": {Mode: os.ModeSocket | 0775},
- }},
- }},
-
- "lib": {Mode: os.ModeDir | 0755},
- "proc": {Mode: os.ModeDir | 0555},
- }},
-
- Mount: []*mountinfo.Entry{
- r("/sysroot", "/", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110003,gid=110003,inode64"),
- r("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"),
- r("/", "/.hakurei", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,size=4k,mode=755,uid=110003,gid=110003,inode64"),
-
- // host /dev in testing environment
- r("/", "/dev", "rw,nosuid", "tmpfs", "tmpfs", ignore),
- r("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,gid=100004,mode=620,ptmxmode=666"),
- r("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
- r("/kvm", "/dev/kvm", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/fuse", "/dev/fuse", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/", "/dev/shm", "rw,nosuid,nodev,noexec,relatime", "tmpfs", "shm", ignore),
- r("/null", "/dev/null", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/zero", "/dev/zero", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/full", "/dev/full", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/tty", "/dev/tty", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/random", "/dev/random", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/urandom", "/dev/urandom", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
-
- r("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,uid=110003,gid=110003,inode64"),
- r("/", "/run/user", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,size=16384k,mode=755,uid=110003,gid=110003,inode64"),
- r("/tmp/hakurei.0/tmpdir/4", "/tmp", "rw,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/etc/passwd", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110003,gid=110003,inode64"),
- r(ignore, "/etc/group", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110003,gid=110003,inode64"),
- r(ignore, "/run/user/65534/wayland-0", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/tmp/.X11-unix", "/tmp/.X11-unix", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/run/user/65534/bus", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/usr/lib", "/lib", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/opt/test-helper/bin/tester", "/.hakurei/test-helper", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/run/user/65534/pulse/native", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- },
-
- Seccomp: true,
-
- TrySocket: "/tmp/.X11-unix/X0",
- ErrnoAbstract: syscall.ECONNREFUSED,
-}.register("device")
diff --git a/test/sandbox/testdata/mapuid.go b/test/sandbox/testdata/mapuid.go
deleted file mode 100644
index 8dee7b7f..00000000
--- a/test/sandbox/testdata/mapuid.go
+++ /dev/null
@@ -1,124 +0,0 @@
-//go:build testsuite || tester
-
-package testdata
-
-import (
- "os"
- "syscall"
-
- "hakurei.app/fhs"
- "hakurei.app/hst"
- "hakurei.app/test/internal/mountinfo"
- "hakurei.app/test/internal/testsuite"
-)
-
-var _ = TestCase{
- Hakurei: hst.Config{
- ID: "app.hakurei.sample.mapuid",
- Enablements: new(hst.EWayland | hst.EPipeWire | hst.EDBus),
- Identity: 3,
-
- Container: &hst.ContainerConfig{
- Hostname: "hakurei-sample-mapuid",
-
- Filesystem: []hst.FilesystemConfigJSON{
- fcLinker,
- fcLib,
- fcTestHelper,
- },
-
- Username: "u0_a3",
- Shell: fhs.AbsUsrBin.Append("bash"),
- Home: hst.AbsPrivateTmp,
- Path: absTestHelper,
- Args: []string{"tester", "mapuid"},
-
- Flags: hst.FMapRealUID | hst.FShareRuntime | hst.FShareTmpdir,
- },
- },
-
- // 0, PresetStrict
- Sum: sumSimple,
-
- Env: []string{
- "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus",
- "HOME=/.hakurei",
- "SHELL=/usr/bin/bash",
- "TERM=xterm",
- "USER=u0_a3",
- "WAYLAND_DISPLAY=wayland-0",
- "XDG_RUNTIME_DIR=/run/user/1000",
- "XDG_SESSION_CLASS=user",
- "XDG_SESSION_TYPE=wayland",
- "PULSE_SERVER=unix:/run/user/1000/pulse/native",
- },
-
- FS: &testsuite.FS{Dir: dir{
- ".hakurei": {Mode: os.ModeDir | 0755, Dir: dir{
- "test-helper": {Mode: 0755},
- }},
-
- // unstable host dev
- "dev": {Mode: os.ModeDir | 0755},
-
- "etc": {Mode: os.ModeDir | 0755, Dir: dir{
- "passwd": {Mode: 0600,
- Data: new("u0_a3:x:1000:100:Hakurei:/.hakurei:/usr/bin/bash\n")},
- "group": {Mode: 0600,
- Data: new("hakurei:x:100:\n")},
- }},
-
- "lib64": {Mode: os.ModeDir | 0755, Dir: dir{
- "ld-linux-x86-64.so.2": {Mode: os.ModeSymlink | 0777},
- }},
-
- "run": {Mode: os.ModeDir | 0755, Dir: dir{
- "user": {Mode: os.ModeDir | 0755, Dir: dir{
- "1000": {Mode: os.ModeDir | 0770, Dir: dir{
- "bus": {Mode: os.ModeSocket | 0775},
- "wayland-0": {Mode: os.ModeSocket | 070},
- "pulse": {Mode: os.ModeDir | 0700, Dir: dir{
- "native": {Mode: os.ModeSocket | 0777},
- }},
- }},
- }},
- }},
-
- "tmp": {Mode: os.ModeDir | 0770, Dir: dir{}},
-
- "lib": {Mode: os.ModeDir | 0755},
- "proc": {Mode: os.ModeDir | 0555},
- }},
-
- Mount: []*mountinfo.Entry{
- r("/sysroot", "/", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110002,gid=110002,inode64"),
- r("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"),
- r("/", "/.hakurei", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,size=4k,mode=755,uid=110002,gid=110002,inode64"),
- r("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", "rw,mode=755,uid=110002,gid=110002,inode64"),
- r("/null", "/dev/null", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/zero", "/dev/zero", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/full", "/dev/full", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/random", "/dev/random", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/urandom", "/dev/urandom", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/tty", "/dev/tty", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
- r("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
- r("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,uid=110002,gid=110002,inode64"),
- r("/", "/run/user", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,size=16384k,mode=755,uid=110002,gid=110002,inode64"),
- r("/tmp/hakurei.0/runtime/3", "/run/user/1000", "rw,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/tmp/hakurei.0/tmpdir/3", "/tmp", "rw,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/etc/passwd", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110002,gid=110002,inode64"),
- r(ignore, "/etc/group", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110002,gid=110002,inode64"),
- r(ignore, "/run/user/1000/wayland-0", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/run/user/1000/bus", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/usr/lib", "/lib", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/opt/test-helper/bin/tester", "/.hakurei/test-helper", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/run/user/1000/pulse/native", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- },
-
- Seccomp: true,
-
- TrySocket: "/tmp/.X11-unix/X0",
- ErrnoAbstract: syscall.ECONNREFUSED,
- ErrnoPathname: syscall.ENOENT,
-}.register("mapuid")
diff --git a/test/sandbox/testdata/pdlike.go b/test/sandbox/testdata/pdlike.go
deleted file mode 100644
index 53d8062a..00000000
--- a/test/sandbox/testdata/pdlike.go
+++ /dev/null
@@ -1,141 +0,0 @@
-//go:build testsuite || tester
-
-package testdata
-
-import (
- "os"
- "syscall"
-
- "hakurei.app/fhs"
- "hakurei.app/hst"
- "hakurei.app/test/internal/mountinfo"
- "hakurei.app/test/internal/testsuite"
-)
-
-var _ = TestCase{
- Hakurei: hst.Config{
- ID: "app.hakurei.sample.pdlike",
- Enablements: new(hst.EWayland | hst.EPipeWire | hst.EDBus),
- Identity: 5,
-
- Container: &hst.ContainerConfig{
- Hostname: "hakurei-sample-pdlike",
-
- Filesystem: []hst.FilesystemConfigJSON{
- fcLinker,
- fcLib,
- fcTestHelper,
- },
-
- Username: "u0_a5",
- Shell: fhs.AbsUsrBin.Append("bash"),
- Home: hst.AbsPrivateTmp,
- Path: absTestHelper,
- Args: []string{"tester", "pdlike"},
-
- Flags: hst.FHostNet | hst.FTty | hst.FUserns | hst.FShareRuntime | hst.FShareTmpdir,
- },
- },
-
- // 0, PresetExt | PresetDenyDevel
- Sum: SumPD,
-
- Env: []string{
- "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
- "HOME=/.hakurei",
- "SHELL=/usr/bin/bash",
- "TERM=xterm",
- "USER=u0_a5",
- "WAYLAND_DISPLAY=wayland-0",
- "XDG_RUNTIME_DIR=/run/user/65534",
- "XDG_SESSION_CLASS=user",
- "XDG_SESSION_TYPE=wayland",
- "PULSE_SERVER=unix:/run/user/65534/pulse/native",
- },
-
- FS: &testsuite.FS{Dir: dir{
- ".hakurei": {Mode: os.ModeDir | 0755, Dir: dir{
- "test-helper": {Mode: 0755},
- }},
-
- "dev": {Mode: os.ModeDir | 0755, Dir: dir{
- "core": {Mode: os.ModeSymlink | 0777},
- "fd": {Mode: os.ModeSymlink | 0777},
- "full": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- "mqueue": {Mode: os.ModeDir | os.ModeSticky | 0777, Dir: dir{}},
- "null": {Mode: os.ModeDevice | os.ModeCharDevice | 0666, Data: new("")},
- "ptmx": {Mode: os.ModeSymlink | 0777},
- "pts": {Mode: os.ModeDir | 0755, Dir: dir{
- "ptmx": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- }},
- "random": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- "shm": {Mode: os.ModeDir | os.ModeSticky | 0777, Dir: dir{}},
- "stderr": {Mode: os.ModeSymlink | 0777},
- "stdin": {Mode: os.ModeSymlink | 0777},
- "stdout": {Mode: os.ModeSymlink | 0777},
- "tty": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- "urandom": {Mode: os.ModeDevice | os.ModeCharDevice | 0444},
- "zero": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- }},
-
- "etc": {Mode: os.ModeDir | 0755, Dir: dir{
- "passwd": {Mode: 0600,
- Data: new("u0_a5:x:65534:65534:Hakurei:/.hakurei:/usr/bin/bash\n")},
- "group": {Mode: 0600,
- Data: new("hakurei:x:65534:\n")},
- }},
-
- "lib64": {Mode: os.ModeDir | 0755, Dir: dir{
- "ld-linux-x86-64.so.2": {Mode: os.ModeSymlink | 0777},
- }},
-
- "run": {Mode: os.ModeDir | 0755, Dir: dir{
- "user": {Mode: os.ModeDir | 0755, Dir: dir{
- "65534": {Mode: os.ModeDir | 0770, Dir: dir{
- "bus": {Mode: os.ModeSocket | 0775},
- "wayland-0": {Mode: os.ModeSocket | 070},
- "pulse": {Mode: os.ModeDir | 0700, Dir: dir{
- "native": {Mode: os.ModeSocket | 0777},
- }},
- }},
- }},
- }},
-
- "tmp": {Mode: os.ModeDir | 0770, Dir: dir{}},
-
- "lib": {Mode: os.ModeDir | 0755},
- "proc": {Mode: os.ModeDir | 0555},
- }},
-
- Mount: []*mountinfo.Entry{
- r("/sysroot", "/", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110004,gid=110004,inode64"),
- r("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"),
- r("/", "/.hakurei", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,size=4k,mode=755,uid=110004,gid=110004,inode64"),
- r("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", "rw,mode=755,uid=110004,gid=110004,inode64"),
- r("/null", "/dev/null", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/zero", "/dev/zero", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/full", "/dev/full", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/random", "/dev/random", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/urandom", "/dev/urandom", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/tty", "/dev/tty", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
- r("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
- r("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,uid=110004,gid=110004,inode64"),
- r("/", "/run/user", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,size=16384k,mode=755,uid=110004,gid=110004,inode64"),
- r("/tmp/hakurei.0/runtime/5", "/run/user/65534", "rw,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/tmp/hakurei.0/tmpdir/5", "/tmp", "rw,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/etc/passwd", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110004,gid=110004,inode64"),
- r(ignore, "/etc/group", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110004,gid=110004,inode64"),
- r(ignore, "/run/user/65534/wayland-0", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/run/user/65534/bus", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/usr/lib", "/lib", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/opt/test-helper/bin/tester", "/.hakurei/test-helper", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/run/user/65534/pulse/native", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- },
-
- Seccomp: true,
-
- TrySocket: "/tmp/.X11-unix/X0",
- ErrnoAbstract: syscall.EPERM,
- ErrnoPathname: syscall.ENOENT,
-}.register("pdlike")
diff --git a/test/sandbox/testdata/simple.go b/test/sandbox/testdata/simple.go
deleted file mode 100644
index c410e626..00000000
--- a/test/sandbox/testdata/simple.go
+++ /dev/null
@@ -1,140 +0,0 @@
-//go:build testsuite || tester
-
-package testdata
-
-import (
- "os"
- "syscall"
-
- "hakurei.app/fhs"
- "hakurei.app/hst"
- "hakurei.app/test/internal/mountinfo"
- "hakurei.app/test/internal/testsuite"
-)
-
-var _ = TestCase{
- Hakurei: hst.Config{
- ID: "app.hakurei.sample.simple",
- Enablements: new(hst.EWayland | hst.EPipeWire | hst.EDBus),
- Identity: 1,
-
- Container: &hst.ContainerConfig{
- Hostname: "hakurei-sample-simple",
- Env: map[string]string{"HAKUREI_SAMPLE": "1"},
-
- Filesystem: []hst.FilesystemConfigJSON{
- fcLinker,
- fcLib,
- fcTestHelper,
- },
-
- Username: "u0_a1",
- Shell: fhs.AbsUsrBin.Append("bash"),
- Home: hst.AbsPrivateTmp,
- Path: absTestHelper,
- Args: []string{"tester", "simple"},
- },
- },
-
- // 0, PresetStrict
- Sum: sumSimple,
-
- Env: []string{
- "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
- "HAKUREI_SAMPLE=1",
- "HOME=/.hakurei",
- "SHELL=/usr/bin/bash",
- "TERM=xterm",
- "USER=u0_a1",
- "WAYLAND_DISPLAY=wayland-0",
- "XDG_RUNTIME_DIR=/run/user/65534",
- "XDG_SESSION_CLASS=user",
- "XDG_SESSION_TYPE=wayland",
- "PULSE_SERVER=unix:/run/user/65534/pulse/native",
- },
-
- FS: &testsuite.FS{Dir: dir{
- ".hakurei": {Mode: os.ModeDir | 0755, Dir: dir{
- "test-helper": {Mode: 0755},
- }},
-
- "dev": {Mode: os.ModeDir | 0755, Dir: dir{
- "core": {Mode: os.ModeSymlink | 0777},
- "fd": {Mode: os.ModeSymlink | 0777},
- "full": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- "mqueue": {Mode: os.ModeDir | os.ModeSticky | 0777, Dir: dir{}},
- "null": {Mode: os.ModeDevice | os.ModeCharDevice | 0666, Data: new("")},
- "ptmx": {Mode: os.ModeSymlink | 0777},
- "pts": {Mode: os.ModeDir | 0755, Dir: dir{
- "ptmx": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- }},
- "random": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- "shm": {Mode: os.ModeDir | os.ModeSticky | 0777, Dir: dir{}},
- "stderr": {Mode: os.ModeSymlink | 0777},
- "stdin": {Mode: os.ModeSymlink | 0777},
- "stdout": {Mode: os.ModeSymlink | 0777},
- "tty": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- "urandom": {Mode: os.ModeDevice | os.ModeCharDevice | 0444},
- "zero": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- }},
-
- "etc": {Mode: os.ModeDir | 0755, Dir: dir{
- "passwd": {Mode: 0600,
- Data: new("u0_a1:x:65534:65534:Hakurei:/.hakurei:/usr/bin/bash\n")},
- "group": {Mode: 0600,
- Data: new("hakurei:x:65534:\n")},
- }},
-
- "lib64": {Mode: os.ModeDir | 0755, Dir: dir{
- "ld-linux-x86-64.so.2": {Mode: os.ModeSymlink | 0777},
- }},
-
- "run": {Mode: os.ModeDir | 0755, Dir: dir{
- "user": {Mode: os.ModeDir | 0755, Dir: dir{
- "65534": {Mode: os.ModeDir | 0700, Dir: dir{
- "bus": {Mode: os.ModeSocket | 0775},
- "wayland-0": {Mode: os.ModeSocket | 070},
- "pulse": {Mode: os.ModeDir | 0700, Dir: dir{
- "native": {Mode: os.ModeSocket | 0777},
- }},
- }},
- }},
- }},
-
- "tmp": {Mode: os.ModeDir | os.ModeSticky | 0777, Dir: dir{}},
-
- "lib": {Mode: os.ModeDir | 0755},
- "proc": {Mode: os.ModeDir | 0555},
- }},
-
- Mount: []*mountinfo.Entry{
- r("/sysroot", "/", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110000,gid=110000,inode64"),
- r("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"),
- r("/", "/.hakurei", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,size=4k,mode=755,uid=110000,gid=110000,inode64"),
- r("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", "rw,mode=755,uid=110000,gid=110000,inode64"),
- r("/null", "/dev/null", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/zero", "/dev/zero", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/full", "/dev/full", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/random", "/dev/random", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/urandom", "/dev/urandom", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/tty", "/dev/tty", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
- r("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
- r("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,uid=110000,gid=110000,inode64"),
- r("/", "/run/user", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,size=16384k,mode=755,uid=110000,gid=110000,inode64"),
- r("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,uid=110000,gid=110000,inode64"),
- r(ignore, "/etc/passwd", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110000,gid=110000,inode64"),
- r(ignore, "/etc/group", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110000,gid=110000,inode64"),
- r(ignore, "/run/user/65534/wayland-0", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/run/user/65534/bus", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/usr/lib", "/lib", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/opt/test-helper/bin/tester", "/.hakurei/test-helper", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/run/user/65534/pulse/native", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- },
-
- Seccomp: true,
-
- TrySocket: "/tmp/.X11-unix/X0",
- ErrnoAbstract: syscall.ECONNREFUSED,
- ErrnoPathname: syscall.ENOENT,
-}.register("simple")
diff --git a/test/sandbox/testdata/sum.go b/test/sandbox/testdata/sum.go
deleted file mode 100644
index e4e8643a..00000000
--- a/test/sandbox/testdata/sum.go
+++ /dev/null
@@ -1,22 +0,0 @@
-//go:build testsuite || tester
-
-package testdata
-
-import (
- "crypto/sha512"
- "encoding/base64"
- "strconv"
-)
-
-// sum decodes s as [base64.StdEncoding] and panics if it is invalid or
-// unexpectedly sized.
-func sum(s string) [sha512.Size]byte {
- p, err := base64.StdEncoding.DecodeString(s)
- if err != nil {
- panic(err)
- }
- if len(p) != sha512.Size {
- panic("unexpected checksum sized " + strconv.Itoa(len(p)))
- }
- return ([sha512.Size]byte)(p)
-}
diff --git a/test/sandbox/testdata/sum_amd64.go b/test/sandbox/testdata/sum_amd64.go
deleted file mode 100644
index bd751105..00000000
--- a/test/sandbox/testdata/sum_amd64.go
+++ /dev/null
@@ -1,9 +0,0 @@
-//go:build testsuite || tester
-
-package testdata
-
-var (
- SumPD = sum("xpiwgf+Vev4XptlDdFN9N/KmP2+d112nVGVCQHqeMkduvaMxK6d4XX9hhUK8+vJ8on3MLd26hSBp0ovP6MrTmg==")
- sumSimple = sum("6IApjfK9Z1HQBA/CG8DtTAD5XcDXulBsJE2LjPaGbbqO9KMylvKHtmzMwdeOlwJll/hMx97BVz4UiWD701zXNQ==")
- sumTTY = sum("C3YAdHbByeJdv2dMKf32CaFlanAGPkkydlThtTYK09oG4aPjK/gOlhxVFq2D1Lnn6b3odqk3l+J2J9JVXCWFiw==")
-)
diff --git a/test/sandbox/testdata/sum_arm64.go b/test/sandbox/testdata/sum_arm64.go
deleted file mode 100644
index 1691828f..00000000
--- a/test/sandbox/testdata/sum_arm64.go
+++ /dev/null
@@ -1,9 +0,0 @@
-//go:build testsuite || tester
-
-package testdata
-
-var (
- SumPD = sum("QzzpuREoLW3MgCkxn7ebgWtg1aeV7I/JQ0TdAnYU1o8CMWapG7iB+q7u3Sbj2JR04UHlppqX6TuJhMqPFJmZgA==")
- sumSimple = sum("eTGFOKPchRMUtr2W8Q1YYayyqn4Ty43gYZ0PanZwnWfwHvP9Z+GVhisC+XEeW3abxNHrT8DfxBpyPInJaKkylw==")
- sumTTY = sum("zx9NyHQ2uo7JXSaLZjpjl7sLSlrGTYVX5sxSnYsPb2Xa06krYu0p2F7unG3eEmd1ek0PhgMuikXKG86t+jTPXg==")
-)
diff --git a/test/sandbox/testdata/testdata.go b/test/sandbox/testdata/testdata.go
deleted file mode 100644
index 3418d8ae..00000000
--- a/test/sandbox/testdata/testdata.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build testsuite || tester
-
-// Package testdata holds sandbox inspection test cases.
-package testdata
-
-import (
- "crypto/sha512"
- "iter"
- "log"
- "strconv"
- "syscall"
-
- "hakurei.app/check"
- "hakurei.app/fhs"
- "hakurei.app/hst"
- "hakurei.app/test/internal/mountinfo"
- "hakurei.app/test/internal/testsuite"
-)
-
-// A TestCase represents a named test case that may be requested by the caller.
-type TestCase struct {
- // Configuration of the inspected container.
- Hakurei hst.Config
- // Checksum of expected seccomp filter program.
- Sum [sha512.Size]byte
-
- // Expected environment. Skipped if nil.
- Env []string `json:"env,omitempty"`
- // Expected root filesystem. Skipped if nil.
- FS *testsuite.FS `json:"fs,omitempty"`
- // Expected mountinfo records. Skipped if nil.
- Mount []*mountinfo.Entry `json:"mount,omitempty"`
- // Whether to run seccomp checks.
- Seccomp bool `json:"seccomp,omitempty"`
-
- // Name of pathname and abstract sockets to attempt.
- TrySocket string `json:"try_socket,omitempty"`
- // Errno to expect attempting to reach the abstract socket.
- ErrnoAbstract syscall.Errno `json:"errno_abstract,omitempty"`
- // Errno to expect attempting to reach the pathname socket.
- ErrnoPathname syscall.Errno `json:"errno_pathname,omitempty"`
-}
-
-// testCases hold all named test cases.
-var testCases map[string]TestCase
-
-// fc returns c wrapped in its JSON adapter.
-func fc(c hst.FilesystemConfig) hst.FilesystemConfigJSON {
- return hst.FilesystemConfigJSON{
- FilesystemConfig: c,
- }
-}
-
-// ignore is the magic string for a mountinfo field to be ignored.
-const ignore = "//ignore"
-
-type dir = map[string]*testsuite.FS
-
-// r returns the address of a [mountinfo.Entry].
-func r(
- root, target, vfsOptstr string,
- fsType, source, fsOptstr string,
-) *mountinfo.Entry {
- return &mountinfo.Entry{
- ID: -1,
- Parent: -1,
- Root: root,
- Target: target,
- VfsOptstr: vfsOptstr,
- FsType: fsType,
- Source: source,
- FsOptstr: fsOptstr,
- }
-}
-
-var (
- // fcLinker is the dynamic linker symlink.
- fcLinker = fc(&hst.FSLink{
- Target: fhs.AbsRoot.Append("lib64", "ld-linux-x86-64.so.2"),
- Linkname: "../lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
- })
- // fcLib is the dynamic library bind mount.
- fcLib = fc(&hst.FSBind{Source: fhs.AbsRoot.Append("lib")})
-
- // absTestHelper is the absolute pathname of the test helper program.
- absTestHelper = hst.AbsPrivateTmp.Append("test-helper")
- // fcTestHelper is the test helper bind mount.
- fcTestHelper = fc(&hst.FSBind{
- Target: absTestHelper,
- Source: check.MustAbs("/opt/test-helper/bin/tester"),
- })
-)
-
-// register adds a test case to testCases.
-func (c TestCase) register(name string) (_ struct{}) {
- if testCases == nil {
- testCases = make(map[string]TestCase)
- }
-
- if _, ok := testCases[name]; ok {
- panic("attempting to register " + strconv.Quote(name) + " twice")
- }
- testCases[name] = c
- return
-}
-
-// Get returns the named test case, or terminates the program if name is invalid.
-func Get(name string) TestCase {
- tc, ok := testCases[name]
- if !ok {
- log.Fatalf("invalid test case %q", name)
- }
- return tc
-}
-
-// All returns an iterator over all named test cases.
-func All() iter.Seq2[string, TestCase] {
- return func(yield func(string, TestCase) bool) {
- for name, tc := range testCases {
- if !yield(name, tc) {
- return
- }
- }
- }
-}
diff --git a/test/sandbox/testdata/tty.go b/test/sandbox/testdata/tty.go
deleted file mode 100644
index 4788f484..00000000
--- a/test/sandbox/testdata/tty.go
+++ /dev/null
@@ -1,145 +0,0 @@
-//go:build testsuite || tester
-
-package testdata
-
-import (
- "os"
-
- "hakurei.app/fhs"
- "hakurei.app/hst"
- "hakurei.app/test/internal/mountinfo"
- "hakurei.app/test/internal/testsuite"
-)
-
-var _ = TestCase{
- Hakurei: hst.Config{
- ID: "app.hakurei.sample.tty",
- Enablements: new(hst.EWayland | hst.EPipeWire | hst.EDBus | hst.EX11),
- Identity: 2,
-
- Container: &hst.ContainerConfig{
- Hostname: "hakurei-sample-tty",
-
- Filesystem: []hst.FilesystemConfigJSON{
- fcLinker,
- fcLib,
- fcTestHelper,
- },
-
- Username: "u0_a2",
- Shell: fhs.AbsUsrBin.Append("bash"),
- Home: hst.AbsPrivateTmp,
- Path: absTestHelper,
- Args: []string{"tester", "tty"},
-
- Flags: hst.FHostNet | hst.FHostAbstract |
- hst.FTty | hst.FShareRuntime,
- },
- },
-
- // 0, PresetExt | PresetDenyNS | PresetDenyDevel
- Sum: sumTTY,
-
- Env: []string{
- "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
- "DISPLAY=:0",
- "HOME=/.hakurei",
- "SHELL=/usr/bin/bash",
- "TERM=xterm",
- "USER=u0_a2",
- "WAYLAND_DISPLAY=wayland-0",
- "XDG_RUNTIME_DIR=/run/user/65534",
- "XDG_SESSION_CLASS=user",
- "XDG_SESSION_TYPE=wayland",
- "PULSE_SERVER=unix:/run/user/65534/pulse/native",
- },
-
- FS: &testsuite.FS{Dir: dir{
- ".hakurei": {Mode: os.ModeDir | 0755, Dir: dir{
- "test-helper": {Mode: 0755},
- }},
-
- "dev": {Mode: os.ModeDir | 0755, Dir: dir{
- "core": {Mode: os.ModeSymlink | 0777},
- "fd": {Mode: os.ModeSymlink | 0777},
- "full": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- "mqueue": {Mode: os.ModeDir | os.ModeSticky | 0777, Dir: dir{}},
- "null": {Mode: os.ModeDevice | os.ModeCharDevice | 0666, Data: new("")},
- "ptmx": {Mode: os.ModeSymlink | 0777},
- "pts": {Mode: os.ModeDir | 0755, Dir: dir{
- "ptmx": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- }},
- "random": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- "shm": {Mode: os.ModeDir | os.ModeSticky | 0777, Dir: dir{}},
- "stderr": {Mode: os.ModeSymlink | 0777},
- "stdin": {Mode: os.ModeSymlink | 0777},
- "stdout": {Mode: os.ModeSymlink | 0777},
- "tty": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- "urandom": {Mode: os.ModeDevice | os.ModeCharDevice | 0444},
- "zero": {Mode: os.ModeDevice | os.ModeCharDevice | 0666},
- }},
-
- "etc": {Mode: os.ModeDir | 0755, Dir: dir{
- "passwd": {Mode: 0600,
- Data: new("u0_a2:x:65534:65534:Hakurei:/.hakurei:/usr/bin/bash\n")},
- "group": {Mode: 0600,
- Data: new("hakurei:x:65534:\n")},
- }},
-
- "lib64": {Mode: os.ModeDir | 0755, Dir: dir{
- "ld-linux-x86-64.so.2": {Mode: os.ModeSymlink | 0777},
- }},
-
- "run": {Mode: os.ModeDir | 0755, Dir: dir{
- "user": {Mode: os.ModeDir | 0755, Dir: dir{
- "65534": {Mode: os.ModeDir | 0770, Dir: dir{
- "bus": {Mode: os.ModeSocket | 0775},
- "wayland-0": {Mode: os.ModeSocket | 070},
- "pulse": {Mode: os.ModeDir | 0700, Dir: dir{
- "native": {Mode: os.ModeSocket | 0777},
- }},
- }},
- }},
- }},
-
- "tmp": {Mode: os.ModeDir | os.ModeSticky | 0777, Dir: dir{
- ".X11-unix": {Mode: os.ModeDir | 0755, Dir: dir{
- "X0": {Mode: os.ModeSocket | 0775},
- }},
- }},
-
- "lib": {Mode: os.ModeDir | 0755},
- "proc": {Mode: os.ModeDir | 0555},
- }},
-
- Mount: []*mountinfo.Entry{
- r("/sysroot", "/", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110001,gid=110001,inode64"),
- r("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"),
- r("/", "/.hakurei", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,size=4k,mode=755,uid=110001,gid=110001,inode64"),
- r("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", "rw,mode=755,uid=110001,gid=110001,inode64"),
- r("/null", "/dev/null", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/zero", "/dev/zero", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/full", "/dev/full", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/random", "/dev/random", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/urandom", "/dev/urandom", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/tty", "/dev/tty", "rw,nosuid,noexec,relatime", "devtmpfs", "devtmpfs", ignore),
- r("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
- r("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
- r("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,uid=110001,gid=110001,inode64"),
- r("/", "/run/user", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,size=16384k,mode=755,uid=110001,gid=110001,inode64"),
- r("/tmp/hakurei.0/runtime/2", "/run/user/65534", "rw,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", "rw,uid=110001,gid=110001,inode64"),
- r(ignore, "/etc/passwd", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110001,gid=110001,inode64"),
- r(ignore, "/etc/group", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", "rw,uid=110001,gid=110001,inode64"),
- r(ignore, "/run/user/65534/wayland-0", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/tmp/.X11-unix", "/tmp/.X11-unix", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/run/user/65534/bus", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/usr/lib", "/lib", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r("/opt/test-helper/bin/tester", "/.hakurei/test-helper", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- r(ignore, "/run/user/65534/pulse/native", "ro,nosuid,nodev,relatime", "overlay", "overlay", ignore),
- },
-
- Seccomp: true,
-
- TrySocket: "/tmp/.X11-unix/X0",
-}.register("tty")
diff --git a/test/sandbox/tester/main.go b/test/sandbox/tester/main.go
deleted file mode 100644
index 0782a5e0..00000000
--- a/test/sandbox/tester/main.go
+++ /dev/null
@@ -1,224 +0,0 @@
-//go:build tester
-
-// The sandbox tester runs within a cmd/hakurei container and validates its
-// state. Since the test environment is relatively predictable, the tester can
-// make various assumptions about the host.
-package main
-
-import (
- "errors"
- "log"
- "net"
- "os"
- "os/signal"
- "path/filepath"
- "syscall"
-
- "hakurei.app/test/internal/mountinfo"
- "hakurei.app/test/sandbox/testdata"
-)
-
-//#include <sys/quota.h>
-import "C"
-
-// mustAbs returns s, or terminates the program if s is not absolute.
-func mustAbs(s string) string {
- if !filepath.IsAbs(s) {
- log.Fatalf("%q is not absolute", s)
- }
- return s
-}
-
-func main() {
- log.SetFlags(0)
- log.SetPrefix("tester: ")
-
- if len(os.Args) != 2 {
- log.Fatal("tester requires 1 argument")
- }
- want := testdata.Get(os.Args[1])
- log.SetPrefix("tester: " + os.Args[1] + " ")
-
- checkWritableDirPaths := []string{
- "/dev/shm",
- "/tmp",
- os.Getenv("XDG_RUNTIME_DIR"),
- }
- for _, a := range checkWritableDirPaths {
- pathname := filepath.Join(mustAbs(a), ".hakurei-check")
- if err := os.WriteFile(pathname, make([]byte, 1<<8), 0600); err != nil {
- log.Fatalf("[FAIL] %s", err)
- } else if err = os.Remove(pathname); err != nil {
- log.Fatalf("[FAIL] %s", err)
- } else {
- log.Printf("[ OK ] %s is writable", a)
- }
- }
-
- if want.Env != nil {
- var (
- fail bool
- i int
- got string
- )
- for i, got = range os.Environ() {
- if i == len(want.Env) {
- log.Fatalf("got more than %d environment variables", len(want.Env))
- }
- if got != want.Env[i] {
- fail = true
- log.Printf("[FAIL] %s", got)
- } else {
- log.Printf("[ OK ] %s", got)
- }
- }
-
- i++
- if i != len(want.Env) {
- log.Fatalf("got %d environment variables, want %d", i, len(want.Env))
- }
-
- if fail {
- log.Fatalf("[FAIL] some environment variables did not match")
- }
- } else {
- log.Printf("[SKIP] skipping environ check")
- }
-
- if want.FS != nil {
- if err := want.FS.Compare(log.Printf, ".", os.DirFS("/")); err != nil {
- log.Fatalf("%v", err)
- }
- } else {
- log.Printf("[SKIP] skipping fs check")
- }
-
- if want.Mount != nil {
- var fail bool
-
- m, err := mountinfo.Open("")
- if err != nil {
- log.Fatal(err)
- }
-
- i := 0
- var ent mountinfo.Entry
- for m.Next() {
- m.Copy(&ent)
-
- if i == len(want.Mount) {
- log.Fatalf("got more than %d entries", i)
- }
- if !ent.EqualWithIgnore(want.Mount[i], "//ignore") {
- fail = true
- log.Printf("[FAIL] %s", &ent)
- } else {
- log.Printf("[ OK ] %s", &ent)
- }
-
- i++
- }
- if err = m.Err(); err != nil {
- log.Fatalf("%v", err)
- }
-
- if i != len(want.Mount) {
- log.Fatalf("got %d entries, want %d", i, len(want.Mount))
- }
-
- if fail {
- log.Fatalf("[FAIL] some mount points did not match")
- }
- } else {
- log.Printf("[SKIP] skipping mounts check")
- }
-
- if want.Seccomp {
- const NULL = 0
-
- for _, tc := range []struct {
- name string
- errno syscall.Errno
-
- trap, a1, a2, a3, a4, a5, a6 uintptr
- }{
- {"syslog", syscall.EPERM, syscall.SYS_SYSLOG, 0, NULL, NULL, NULL, NULL, NULL},
- {"acct", syscall.EPERM, syscall.SYS_ACCT, 0, NULL, NULL, NULL, NULL, NULL},
- {"quotactl", syscall.EPERM, syscall.SYS_QUOTACTL, C.Q_GETQUOTA, NULL, uintptr(os.Getuid()), NULL, NULL, NULL},
- {"add_key", syscall.EPERM, syscall.SYS_ADD_KEY, NULL, NULL, NULL, NULL, NULL, NULL},
- {"keyctl", syscall.EPERM, syscall.SYS_KEYCTL, NULL, NULL, NULL, NULL, NULL, NULL},
- {"request_key", syscall.EPERM, syscall.SYS_REQUEST_KEY, NULL, NULL, NULL, NULL, NULL, NULL},
- {"move_pages", syscall.EPERM, syscall.SYS_MOVE_PAGES, uintptr(os.Getpid()), NULL, NULL, NULL, NULL, NULL},
- {"mbind", syscall.EPERM, syscall.SYS_MBIND, NULL, NULL, NULL, NULL, NULL, NULL},
- {"get_mempolicy", syscall.EPERM, syscall.SYS_GET_MEMPOLICY, NULL, NULL, NULL, NULL, NULL, NULL},
- {"set_mempolicy", syscall.EPERM, syscall.SYS_SET_MEMPOLICY, NULL, NULL, NULL, NULL, NULL, NULL},
- {"migrate_pages", syscall.EPERM, syscall.SYS_MIGRATE_PAGES, NULL, NULL, NULL, NULL, NULL, NULL},
- } {
- if _, _, errno := syscall.Syscall6(tc.trap, tc.a1, tc.a2, tc.a3, tc.a4, tc.a5, tc.a6); errno != tc.errno {
- log.Fatalf("[FAIL] %s: %v, want %v", tc.name, errno, tc.errno)
- }
- log.Printf("[ OK ] %s: %v", tc.name, tc.errno)
- }
- } else {
- log.Printf("[SKIP] skipping seccomp check")
- }
-
- if want.TrySocket != "" {
- retry:
- abstractConn, abstractErr := net.Dial("unix", "@"+want.TrySocket)
- pathnameConn, pathnameErr := net.Dial("unix", want.TrySocket)
- ok := true
-
- if abstractErr == nil {
- if err := abstractConn.Close(); err != nil {
- ok = false
- log.Printf("Close: %v", err)
- }
- }
- if pathnameErr == nil {
- if err := pathnameConn.Close(); err != nil {
- ok = false
- log.Printf("Close: %v", err)
- }
- }
-
- if errors.Is(
- abstractErr,
- syscall.EAGAIN,
- ) || errors.Is(
- pathnameErr,
- syscall.EAGAIN,
- ) {
- goto retry
- }
-
- abstractWantErr := error(want.ErrnoAbstract)
- pathnameWantErr := error(want.ErrnoPathname)
- if want.ErrnoAbstract == 0 {
- abstractWantErr = nil
- }
- if want.ErrnoPathname == 0 {
- pathnameWantErr = nil
- }
-
- if !errors.Is(abstractErr, abstractWantErr) {
- ok = false
- log.Printf("abstractErr: %v, want %v", abstractErr, abstractWantErr)
- }
- if !errors.Is(pathnameErr, pathnameWantErr) {
- ok = false
- log.Printf("pathnameErr: %v, want %v", pathnameErr, pathnameWantErr)
- }
-
- if !ok {
- os.Exit(1)
- }
- }
-
- s := make(chan os.Signal, 1)
- signal.Notify(s, syscall.SIGTERM)
- if _, err := os.Stdout.Write(make([]byte, 8)); err != nil {
- log.Fatalf("cannot notify testsuite: %v", err)
- }
- <-s
-}
diff --git a/test/sharefs/main.go b/test/sharefs/main.go
deleted file mode 100644
index 80e6ab09..00000000
--- a/test/sharefs/main.go
+++ /dev/null
@@ -1,119 +0,0 @@
-//go:build testsuite
-
-// The sharefs test program checks cli behaviour and exercises the filesystem
-// implemented by cmd/sharefs using fs_mark.
-package main
-
-import (
- "errors"
- "log"
- "os"
- "os/exec"
- "strings"
- "syscall"
-
- "hakurei.app/test/internal/testsuite"
-)
-
-// checkBadOpts invokes cmd/sharefs with the specified options and compares
-// the resulting error message.
-func checkBadOpts(cred *syscall.Credential, opts, want string) {
- var buf strings.Builder
- buf.Grow(len(want))
-
- cmd := exec.Command(
- "sharefs",
- "-f",
- "-o", "source=/etc,"+opts,
- "/mnt",
- )
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGKILL,
- Credential: cred,
- }
- cmd.Stderr = &buf
- err := cmd.Run()
- if err == nil {
- log.Fatalf("opts=%q, unexpected success", opts)
- }
- if e, ok := errors.AsType[*exec.ExitError](err); !ok {
- log.Fatal(err)
- } else if !e.Exited() {
- log.Fatal(e)
- }
-
- if got := buf.String(); got != want {
- log.Fatalf("opts=%q\n\t got:%q\n\twant:%q", opts, got, want)
- }
-}
-
-func main() {
- go testsuite.ReceiveSignals()
-
- cred := syscall.Credential{Uid: 1000, Gid: 100}
- if err := os.Mkdir("result", 0755); err != nil {
- log.Fatal(err)
- }
-
- done := make(chan struct{})
- go func() {
- defer close(done)
-
- testsuite.MustRun(
- nil, nil,
- "fs_mark",
- "-v",
- "-d", "/sdcard/fs_mark",
- "-l", "result/fs_mark.log",
- )
- }()
-
- log.Println("checking malformed setuid/setgid representation")
- checkBadOpts(&cred, "setuid=ff", "sharefs: invalid value for option setuid\n")
- checkBadOpts(&cred, "setgid=ff", "sharefs: invalid value for option setgid\n")
-
- log.Println("checking bounds check for setuid/setgid")
- checkBadOpts(&cred, "setuid=0", "sharefs: invalid value for option setuid\n")
- checkBadOpts(&cred, "setgid=0", "sharefs: invalid value for option setgid\n")
- checkBadOpts(&cred, "setuid=-1", "sharefs: invalid value for option setuid\n")
- checkBadOpts(&cred, "setgid=-1", "sharefs: invalid value for option setgid\n")
-
- log.Println("checking non-root setuid/setgid")
- checkBadOpts(&cred, "setuid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n")
- checkBadOpts(&cred, "setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n")
- checkBadOpts(&cred, "setuid=1023,setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n")
- checkBadOpts(&cred, "mkdir", "sharefs: mkdir has no effect when not starting as root\n")
-
- log.Println("checking root without setuid/setgid")
- checkBadOpts(nil, "allow_other", "sharefs: setuid and setgid must not be 0\n")
- checkBadOpts(nil, "setuid=1023", "sharefs: setuid and setgid must not be 0\n")
- checkBadOpts(nil, "setgid=1023", "sharefs: setuid and setgid must not be 0\n")
-
- log.Println("verifying mount point")
- if err := os.Remove("/mnt"); err != nil {
- log.Fatal(err)
- }
-
- log.Println("checking unprivileged mount/unmount")
- testsuite.MustRun(&cred, nil, "mkdir", "/tmp/sdcard", "/tmp/persistent")
- testsuite.MustRun(&cred, nil, "sharefs", "-o", "source=/tmp/persistent", "/tmp/sdcard")
- testsuite.MustRun(&cred, nil, "touch", "/tmp/sdcard/check")
- testsuite.MustRun(&cred, nil, "umount", "/tmp/sdcard")
- testsuite.MustRun(&cred, nil, "rm", "/tmp/persistent/check")
- testsuite.MustRun(&cred, nil, "rmdir", "/tmp/sdcard", "/tmp/persistent")
-
- log.Println("waiting for fs_mark to complete")
- <-done
-
- const backingDir = "/var/lib/sdcard"
- sharefsCred := syscall.Credential{Uid: 1023, Gid: 1023}
- log.Println("checking permissions")
- testsuite.MustRun(&sharefsCred, nil, "touch", backingDir+"/fs_mark/.check")
- testsuite.MustRun(&sharefsCred, nil, "rm", backingDir+"/fs_mark/.check")
- testsuite.MustRun(&cred, nil, "rm", "-rf", "/sdcard/fs_mark")
- if _, err := os.ReadDir(backingDir + "/fs_mark"); err == nil {
- log.Fatal("fs_mark directory was not removed")
- } else if !errors.Is(err, os.ErrNotExist) {
- log.Fatal(err)
- }
-}
diff --git a/test/sharefs/raceattr.go b/test/sharefs/raceattr.go
deleted file mode 100644
index 412cb2b3..00000000
--- a/test/sharefs/raceattr.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build raceattr
-
-// The raceattr program reproduces vfs inode file attribute race.
-//
-// Even though libfuse high-level API presents the address of a struct stat
-// alongside struct fuse_context, file attributes are actually inherent to the
-// inode, instead of the specific call from userspace. The kernel implementation
-// in fs/fuse/xattr.c appears to make stale data in the inode (set by a previous
-// call) impossible or very unlikely to reach userspace via the stat family of
-// syscalls. However, when using default_permissions to have the VFS check
-// permissions, this race still happens, despite the resulting struct stat being
-// correct when overriding the check via capabilities otherwise.
-//
-// This program reproduces the failure, but because of its continuous nature, it
-// is provided independent of the vm integration test suite.
-package main
-
-import (
- "context"
- "flag"
- "log"
- "os"
- "os/signal"
- "runtime"
- "sync"
- "sync/atomic"
- "syscall"
-)
-
-func newStatAs(
- ctx context.Context, cancel context.CancelFunc,
- n *atomic.Uint64, ok *atomic.Bool,
- uid uint32, pathname string,
- continuous bool,
-) func() {
- return func() {
- runtime.LockOSThread()
- defer cancel()
-
- if _, _, errno := syscall.Syscall(
- syscall.SYS_SETUID, uintptr(uid),
- 0, 0,
- ); errno != 0 {
- cancel()
- log.Printf("cannot set uid to %d: %s", uid, errno)
- }
-
- var stat syscall.Stat_t
- for {
- if ctx.Err() != nil {
- return
- }
-
- if err := syscall.Lstat(pathname, &stat); err != nil {
- // SHAREFS_PERM_DIR not world executable, or
- // SHAREFS_PERM_REG not world readable
- if !continuous {
- cancel()
- }
- ok.Store(true)
- log.Printf("uid %d: %v", uid, err)
- } else if stat.Uid != uid {
- // appears to be unreachable
- if !continuous {
- cancel()
- }
- ok.Store(true)
- log.Printf("got uid %d instead of %d", stat.Uid, uid)
- }
- n.Add(1)
- }
- }
-}
-
-func main() {
- log.SetFlags(0)
- log.SetPrefix("raceattr: ")
-
- p := flag.String("target", "/sdcard/raceattr", "pathname of test file")
- u0 := flag.Int("uid0", 1<<10-1, "first uid")
- u1 := flag.Int("uid1", 1<<10-2, "second uid")
- count := flag.Int("count", 1, "threads per uid")
- continuous := flag.Bool("continuous", false, "keep running even after reproduce")
- flag.Parse()
-
- if os.Geteuid() != 0 {
- log.Fatal("this program must run as root")
- }
-
- ctx, cancel := signal.NotifyContext(
- context.Background(),
- syscall.SIGINT,
- syscall.SIGTERM,
- syscall.SIGHUP,
- )
-
- if err := os.WriteFile(*p, nil, 0); err != nil {
- log.Fatal(err)
- }
-
- var (
- wg sync.WaitGroup
-
- n atomic.Uint64
- ok atomic.Bool
- )
-
- if *count < 1 {
- *count = 1
- }
- for range *count {
- wg.Go(newStatAs(ctx, cancel, &n, &ok, uint32(*u0), *p, *continuous))
- if *u1 >= 0 {
- wg.Go(newStatAs(ctx, cancel, &n, &ok, uint32(*u1), *p, *continuous))
- }
- }
-
- wg.Wait()
- if !*continuous && ok.Load() {
- log.Printf("reproduced after %d calls", n.Load())
- }
-}