diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-07-07 18:32:56 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-07-07 18:56:27 +0900 |
| commit | 6d55ee536e902d059380f79e870d06547627cc7c (patch) | |
| tree | aacf3e3ec42525cf0e7a674aa948359fd0c21ae2 /test | |
| parent | c8e8651694415d497b0800319b2ddda361b7651f (diff) | |
test: move nix files
These are too much clutter. Move them to test directory until the test suite replacement is upstreamed.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'test')
| -rw-r--r-- | test/default.nix | 2 | ||||
| -rw-r--r-- | test/flake.lock | 49 | ||||
| -rw-r--r-- | test/flake.nix | 255 | ||||
| -rw-r--r-- | test/hsu.nix | 23 | ||||
| -rw-r--r-- | test/interactive/raceattr.nix | 2 | ||||
| -rw-r--r-- | test/nixos.nix | 407 | ||||
| -rw-r--r-- | test/options.md | 1175 | ||||
| -rw-r--r-- | test/options.nix | 364 | ||||
| -rw-r--r-- | test/package.nix | 144 | ||||
| -rw-r--r-- | test/sandbox/default.nix | 2 | ||||
| -rw-r--r-- | test/sharefs/configuration.nix | 44 | ||||
| -rw-r--r-- | test/sharefs/default.nix | 44 | ||||
| -rw-r--r-- | test/sharefs/raceattr.go | 122 | ||||
| -rw-r--r-- | test/sharefs/test.py | 60 |
14 files changed, 2690 insertions, 3 deletions
diff --git a/test/default.nix b/test/default.nix index 76a490a7..81daa0a2 100644 --- a/test/default.nix +++ b/test/default.nix @@ -54,7 +54,7 @@ testers.nixosTest { # 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: { + package = (pkgs.callPackage ./package.nix { }).overrideAttrs (previousAttrs: { env = previousAttrs.env // { GOFLAGS = previousAttrs.env.GOFLAGS + " -race"; }; diff --git a/test/flake.lock b/test/flake.lock new file mode 100644 index 00000000..ce9fe45f --- /dev/null +++ b/test/flake.lock @@ -0,0 +1,49 @@ +{ + "nodes": { + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1780361225, + "narHash": "sha256-wnV9ttf4fPWNonBIQmvlrSlNpQYgx5HgWWd007mwIFA=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "e28654b71096e08c019d4861ca26acb646f583d8", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "release-26.05", + "repo": "home-manager", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1780453794, + "narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338", + "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/flake.nix b/test/flake.nix new file mode 100644 index 00000000..6c8cf100 --- /dev/null +++ b/test/flake.nix @@ -0,0 +1,255 @@ +{ + 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 = [ + "aarch64-linux" + "i686-linux" + "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) + runCommandLocal + callPackage + nixfmt + deadnix + statix + ; + in + { + hakurei = callPackage ./. { inherit system self; }; + race = callPackage ./. { + inherit system self; + withRace = true; + }; + + sandbox = callPackage ./sandbox { inherit self; }; + sandbox-race = callPackage ./sandbox { + inherit self; + withRace = true; + }; + + sharefs = callPackage ./sharefs { inherit system self; }; + + formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt ]; } '' + cd ${./.} + + echo "running nixfmt..." + nixfmt --width=256 --check . + + touch $out + ''; + + lint = + runCommandLocal "check-lint" + { + nativeBuildInputs = [ + deadnix + statix + ]; + } + '' + cd ${./.} + + echo "running deadnix..." + deadnix --fail + + echo "running statix..." + statix check . + + touch $out + ''; + } + ); + + 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_26 + 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; }; + sharefs = pkgs.linkFarm "sharefs" { + "bin/sharefs" = "${hakurei}/libexec/sharefs"; + "bin/mount.fuse.sharefs" = "${hakurei}/libexec/sharefs"; + }; + + dist = + pkgs.runCommand "${hakurei.name}-dist" + { + buildInputs = hakurei.targetPkgs ++ [ + pkgs.pkgsStatic.musl + ]; + } + '' + cd $(mktemp -d) \ + && cp -r ${hakurei.src}/. . \ + && chmod +w cmd && cp -r ${hsu.src}/. cmd/hsu/ \ + && chmod -R +w . + + CC="musl-clang -O3 -Werror -Qunused-arguments" \ + GOCACHE="$(mktemp -d)" \ + PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \ + DESTDIR="$out" \ + ./all.sh + ''; + } + ); + + devShells = forAllSystems ( + system: + let + inherit (self.packages.${system}) hakurei; + pkgs = nixpkgsFor.${system}; + in + { + default = pkgs.mkShell { + buildInputs = hakurei.targetPkgs; + hardeningDisable = [ "fortify" ]; + }; + withPackage = pkgs.mkShell { buildInputs = [ hakurei ] ++ hakurei.targetPkgs; }; + + vm = + let + nixos = nixpkgs.lib.nixosSystem { + inherit system; + modules = [ + { + environment = { + systemPackages = [ + (pkgs.buildFHSEnv { + pname = "hakurei-fhs"; + inherit (hakurei) version; + targetPkgs = _: hakurei.targetPkgs; + extraOutputsToInstall = [ "dev" ]; + profile = '' + export PKG_CONFIG_PATH="/usr/share/pkgconfig:$PKG_CONFIG_PATH" + ''; + }) + ]; + + hakurei = + let + # this is used for interactive vm testing during development, where tests might be broken + package = self.packages.${pkgs.stdenv.hostPlatform.system}.hakurei.override { + buildGo126Module = previousArgs: pkgs.pkgsStatic.buildGo126Module (previousArgs // { doCheck = false; }); + }; + in + { + inherit package; + hsuPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.hsu.override { hakurei = package; }; + }; + }; + } + + ./interactive/configuration.nix + ./interactive/vm.nix + ./interactive/hakurei.nix + ./interactive/trace.nix + ./interactive/raceattr.nix + + self.nixosModules.hakurei + home-manager.nixosModules.home-manager + ]; + }; + in + pkgs.mkShell { + buildInputs = [ nixos.config.system.build.vm ]; + shellHook = "exec run-nixos-vm $@"; + }; + + generateDoc = + let + inherit (pkgs) lib; + + doc = + let + eval = lib.evalModules { + specialArgs = { + inherit pkgs; + }; + modules = [ (import ./options.nix self.packages) ]; + }; + cleanEval = lib.filterAttrsRecursive (n: _: n != "_module") eval; + in + pkgs.nixosOptionsDoc { inherit (cleanEval) options; }; + docText = pkgs.runCommand "hakurei-module-docs.md" { } '' + cat ${doc.optionsCommonMark} > $out + sed -i '/*Declared by:*/,+1 d' $out + ''; + in + pkgs.mkShell { + shellHook = '' + exec cat ${docText} > options.md + ''; + }; + + generateSyscallTable = + let + GOARCH = { + x86_64-linux = "amd64"; + aarch64-linux = "arm64"; + }; + in + pkgs.mkShell { + shellHook = "exec ${pkgs.writeShellScript "generate-syscall-table" '' + set -e + ${pkgs.perl}/bin/perl \ + container/std/mksysnum_linux.pl \ + ${pkgs.linuxHeaders}/include/asm/unistd_64.h | \ + ${pkgs.go}/bin/gofmt > \ + container/std/syscall_linux_${GOARCH.${system}}.go + ''}"; + }; + } + ); + }; +} diff --git a/test/hsu.nix b/test/hsu.nix new file mode 100644 index 00000000..7acb3529 --- /dev/null +++ b/test/hsu.nix @@ -0,0 +1,23 @@ +{ + 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/interactive/raceattr.nix b/test/interactive/raceattr.nix index d94d7a7b..de54f4cf 100644 --- a/test/interactive/raceattr.nix +++ b/test/interactive/raceattr.nix @@ -16,7 +16,7 @@ src = builtins.path { name = "${pname}-src"; - path = lib.cleanSource ../../cmd/sharefs/test; + path = lib.cleanSource ../sharefs; filter = path: type: (type == "directory") || (type == "regular" && lib.hasSuffix ".go" path); }; vendorHash = null; diff --git a/test/nixos.nix b/test/nixos.nix new file mode 100644 index 00000000..49bfffb6 --- /dev/null +++ b/test/nixos.nix @@ -0,0 +1,407 @@ +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/options.md b/test/options.md new file mode 100644 index 00000000..6f6acddd --- /dev/null +++ b/test/options.md @@ -0,0 +1,1175 @@ +## environment\.hakurei\.enable + + + +Whether to enable hakurei\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.package + + + +The hakurei package to use\. + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation hakurei-static-x86_64-unknown-linux-musl-0.4.3> +``` + + + +## environment\.hakurei\.apps + +Declaratively configured hakurei apps\. + + + +*Type:* +attribute set of (submodule) + + + +*Default:* + +```nix +{ } +``` + + + +## environment\.hakurei\.apps\.\<name>\.enablements\.dbus + + + +Whether to proxy D-Bus\. + + + +*Type:* +null or boolean + + + +*Default:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.enablements\.pipewire + + + +Whether to share the PipeWire server via pipewire-pulse on a SecurityContext socket\. + + + +*Type:* +null or boolean + + + +*Default:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.enablements\.wayland + + + +Whether to share the Wayland server via security-context-v1\. + + + +*Type:* +null or boolean + + + +*Default:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.enablements\.x11 + + + +Whether to share the X11 socket and allow connection\. + + + +*Type:* +null or boolean + + + +*Default:* + +```nix +false +``` + + + +## environment\.hakurei\.apps\.\<name>\.packages + + + +List of extra packages to install via home-manager\. + + + +*Type:* +list of package + + + +*Default:* + +```nix +[ ] +``` + + + +## environment\.hakurei\.apps\.\<name>\.args + + + +Custom args\. +Setting this to null will default to script name\. + + + +*Type:* +null or (list of string) + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.command + + + +Command to run as the target user\. +Setting this to null will default command to launcher name\. +Has no effect when script is set\. + + + +*Type:* +null or string + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.dbus\.session + + + +D-Bus session bus custom configuration\. +Setting this to null will enable built-in defaults\. + + + +*Type:* +null or (function that evaluates to a(n) anything) + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.dbus\.system + + + +D-Bus system bus custom configuration\. +Setting this to null will disable the system bus proxy\. + + + +*Type:* +null or anything + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.devel + + + +Whether to enable debugging-related kernel interfaces\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.device + + + +Whether to enable access to all devices\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.env + + + +Environment variables to set for the initial process in the sandbox\. + + + +*Type:* +null or (attribute set of string) + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.extraConfig + + + +Extra home-manager configuration\. + + + +*Type:* +anything + + + +*Default:* + +```nix +{ } +``` + + + +## environment\.hakurei\.apps\.\<name>\.extraPaths + + + +Extra paths to make available to the container\. + + + +*Type:* +list of attribute set of anything + + + +*Default:* + +```nix +[ ] +``` + + + +## environment\.hakurei\.apps\.\<name>\.gpu + + + +Target process GPU and driver access\. +Setting this to null will enable GPU whenever X or Wayland is enabled\. + + + +*Type:* +null or boolean + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.groups + + + +List of groups to inherit from the privileged user\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +## environment\.hakurei\.apps\.\<name>\.hostAbstract + + + +Whether to enable share abstract unix socket scope\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.hostNet + + + +Whether to enable share host net namespace\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.identity + + + +Application identity\. Identity 0 is reserved for system services\. + + + +*Type:* +integer between 1 and 9999 (both inclusive) + + + +## environment\.hakurei\.apps\.\<name>\.insecureWayland + + + +Whether to enable direct access to the Wayland socket\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.mapRealUid + + + +Whether to enable mapping to priv-user uid\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.multiarch + + + +Whether to enable multiarch kernel-level support\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.name + + + +Name of the app’s launcher script\. + + + +*Type:* +string + + + +## environment\.hakurei\.apps\.\<name>\.nix + + + +Whether to enable nix daemon access\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.path + + + +Custom executable path\. +Setting this to null will default to the start script\. + + + +*Type:* +null or string + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.schedPolicy + + + +Scheduling policy to set for the container\. +The zero value retains the current scheduling policy\. + + + +*Type:* +null or one of “fifo”, “rr”, “batch”, “idle”, “deadline”, “ext” + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.schedPriority + + + +Scheduling priority to set for the container\. + + + +*Type:* +null or integer between 1 and 99 (both inclusive) + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.script + + + +Application launch script\. + + + +*Type:* +null or string + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.share + + + +Package containing share files\. +Setting this to null will default package name to wrapper name\. + + + +*Type:* +null or package + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.apps\.\<name>\.shareRuntime + + + +Whether to enable sharing of XDG_RUNTIME_DIR between containers under the same identity\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.shareTmpdir + + + +Whether to enable sharing of TMPDIR between containers under the same identity\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.shareUid + + + +Whether to enable sharing identity with another application\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.tty + + + +Whether to enable access to the controlling terminal\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.useCommonPaths + + + +Whether to enable common extra paths\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.userns + + + +Whether to enable user namespace creation\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.verbose + + + +Whether to enable launchers with verbose output\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + + + +## environment\.hakurei\.apps\.\<name>\.wait_delay + + + +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\. + + + +*Type:* +null or signed integer + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.commonPaths + + + +Common extra paths to make available to the container\. + + + +*Type:* +list of attribute set of anything + + + +*Default:* + +```nix +[ ] +``` + + + +## environment\.hakurei\.extraHomeConfig + + + +Extra home-manager configuration to merge with all target users\. + + + +*Type:* +anything + + + +## environment\.hakurei\.hsuPackage + + + +The hsu package to use\. + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation hakurei-hsu-0.4.3> +``` + + + +## environment\.hakurei\.sharefs\.package + + + +The sharefs package to use\. + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation sharefs> +``` + + + +## environment\.hakurei\.sharefs\.group + + + +Name of the group to run the sharefs daemon as\. + + + +*Type:* +string + + + +*Default:* + +```nix +"sharefs" +``` + + + +## environment\.hakurei\.sharefs\.name + + + +Host path to mount sharefs on\. + + + +*Type:* +string + + + +*Default:* + +```nix +"/sdcard" +``` + + + +## environment\.hakurei\.sharefs\.source + + + +Writable backing directory\. Setting this to null disables sharefs\. + + + +*Type:* +null or string + + + +*Default:* + +```nix +null +``` + + + +## environment\.hakurei\.sharefs\.user + + + +Name of the user to run the sharefs daemon as\. + + + +*Type:* +string + + + +*Default:* + +```nix +"sharefs" +``` + + + +## environment\.hakurei\.shell + + + +Absolute path to preferred shell\. + + + +*Type:* +string + + + +*Default:* + +```nix +"/run/current-system/sw/bin/bash" +``` + + + +## environment\.hakurei\.stateDir + + + +The state directory where app home directories are stored\. + + + +*Type:* +string + + + +## environment\.hakurei\.users + + + +Users allowed to spawn hakurei apps and their corresponding hakurei identity\. + + + +*Type:* +attribute set of integer between 0 and 99 (both inclusive) + + + +*Default:* + +```nix +{ } +``` + + diff --git a/test/options.nix b/test/options.nix new file mode 100644 index 00000000..f624b6f5 --- /dev/null +++ b/test/options.nix @@ -0,0 +1,364 @@ +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/package.nix b/test/package.nix new file mode 100644 index 00000000..46567b69 --- /dev/null +++ b/test/package.nix @@ -0,0 +1,144 @@ +{ + lib, + stdenv, + buildGo126Module, + 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_26, + clang, + xorgproto, + + # for check + util-linux, + nettools, + + glibc, # for ldd + withStatic ? stdenv.hostPlatform.isStatic, +}: + +buildGo126Module 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_26 + 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_26; + + targetPkgs = [ + go_1_26 + clang + xorgproto + util-linux + + # for go generate + wayland-protocols + wayland-scanner + ] + ++ buildInputs + ++ nativeBuildInputs; + }; +} diff --git a/test/sandbox/default.nix b/test/sandbox/default.nix index a0b8d13a..47a59de9 100644 --- a/test/sandbox/default.nix +++ b/test/sandbox/default.nix @@ -14,7 +14,7 @@ testers.nixosTest { # 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: { + package = (pkgs.callPackage ../package.nix { }).overrideAttrs (previousAttrs: { env = previousAttrs.env // { GOFLAGS = previousAttrs.env.GOFLAGS + " -race"; }; diff --git a/test/sharefs/configuration.nix b/test/sharefs/configuration.nix new file mode 100644 index 00000000..05d67dcb --- /dev/null +++ b/test/sharefs/configuration.nix @@ -0,0 +1,44 @@ +{ pkgs, ... }: +{ + users.users = { + alice = { + isNormalUser = true; + description = "Alice Foobar"; + password = "foobar"; + uid = 1000; + }; + }; + + home-manager.users.alice.home.stateVersion = "24.11"; + + # Automatically login on tty1 as a normal user: + services.getty.autologinUser = "alice"; + + environment = { + # For benchmarking sharefs: + systemPackages = [ pkgs.fsmark ]; + }; + + virtualisation = { + # Hopefully reduces spurious test failures: + memorySize = if pkgs.stdenv.hostPlatform.is32bit then 2046 else 8192; + + diskSize = 6 * 1024; + + qemu.options = [ + # Increase test performance: + "-smp 16" + ]; + }; + + environment.hakurei = rec { + enable = true; + stateDir = "/var/lib/hakurei"; + sharefs.source = "${stateDir}/sdcard"; + users.alice = 0; + + extraHomeConfig = { + home.stateVersion = "23.05"; + }; + }; +} diff --git a/test/sharefs/default.nix b/test/sharefs/default.nix new file mode 100644 index 00000000..e963eaa6 --- /dev/null +++ b/test/sharefs/default.nix @@ -0,0 +1,44 @@ +{ + testers, + + system, + self, +}: +testers.nixosTest { + name = "sharefs"; + nodes.machine = + { options, pkgs, ... }: + let + fhs = + let + hakurei = options.environment.hakurei.package.default; + in + pkgs.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: + (pkgs.writeShellScriptBin "sharefs-workload-hakurei-tests" '' + cp -r "${self.packages.${system}.hakurei.src}" "/sdcard/hakurei" && cd "/sdcard/hakurei" + ${fhs}/bin/hakurei-fhs -c 'ROSA_SKIP_BINFMT=1 CC="clang -O3 -Werror" go test ./...' + '') + ]; + + imports = [ + ./configuration.nix + + self.nixosModules.hakurei + self.inputs.home-manager.nixosModules.home-manager + ]; + }; + + testScript = builtins.readFile ./test.py; +} diff --git a/test/sharefs/raceattr.go b/test/sharefs/raceattr.go new file mode 100644 index 00000000..412cb2b3 --- /dev/null +++ b/test/sharefs/raceattr.go @@ -0,0 +1,122 @@ +//go:build raceattr + +// The raceattr program reproduces vfs inode file attribute race. +// +// Even though libfuse high-level API presents the address of a struct stat +// alongside struct fuse_context, file attributes are actually inherent to the +// inode, instead of the specific call from userspace. The kernel implementation +// in fs/fuse/xattr.c appears to make stale data in the inode (set by a previous +// call) impossible or very unlikely to reach userspace via the stat family of +// syscalls. However, when using default_permissions to have the VFS check +// permissions, this race still happens, despite the resulting struct stat being +// correct when overriding the check via capabilities otherwise. +// +// This program reproduces the failure, but because of its continuous nature, it +// is provided independent of the vm integration test suite. +package main + +import ( + "context" + "flag" + "log" + "os" + "os/signal" + "runtime" + "sync" + "sync/atomic" + "syscall" +) + +func newStatAs( + ctx context.Context, cancel context.CancelFunc, + n *atomic.Uint64, ok *atomic.Bool, + uid uint32, pathname string, + continuous bool, +) func() { + return func() { + runtime.LockOSThread() + defer cancel() + + if _, _, errno := syscall.Syscall( + syscall.SYS_SETUID, uintptr(uid), + 0, 0, + ); errno != 0 { + cancel() + log.Printf("cannot set uid to %d: %s", uid, errno) + } + + var stat syscall.Stat_t + for { + if ctx.Err() != nil { + return + } + + if err := syscall.Lstat(pathname, &stat); err != nil { + // SHAREFS_PERM_DIR not world executable, or + // SHAREFS_PERM_REG not world readable + if !continuous { + cancel() + } + ok.Store(true) + log.Printf("uid %d: %v", uid, err) + } else if stat.Uid != uid { + // appears to be unreachable + if !continuous { + cancel() + } + ok.Store(true) + log.Printf("got uid %d instead of %d", stat.Uid, uid) + } + n.Add(1) + } + } +} + +func main() { + log.SetFlags(0) + log.SetPrefix("raceattr: ") + + p := flag.String("target", "/sdcard/raceattr", "pathname of test file") + u0 := flag.Int("uid0", 1<<10-1, "first uid") + u1 := flag.Int("uid1", 1<<10-2, "second uid") + count := flag.Int("count", 1, "threads per uid") + continuous := flag.Bool("continuous", false, "keep running even after reproduce") + flag.Parse() + + if os.Geteuid() != 0 { + log.Fatal("this program must run as root") + } + + ctx, cancel := signal.NotifyContext( + context.Background(), + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGHUP, + ) + + if err := os.WriteFile(*p, nil, 0); err != nil { + log.Fatal(err) + } + + var ( + wg sync.WaitGroup + + n atomic.Uint64 + ok atomic.Bool + ) + + if *count < 1 { + *count = 1 + } + for range *count { + wg.Go(newStatAs(ctx, cancel, &n, &ok, uint32(*u0), *p, *continuous)) + if *u1 >= 0 { + wg.Go(newStatAs(ctx, cancel, &n, &ok, uint32(*u1), *p, *continuous)) + } + } + + wg.Wait() + if !*continuous && ok.Load() { + log.Printf("reproduced after %d calls", n.Load()) + } +} diff --git a/test/sharefs/test.py b/test/sharefs/test.py new file mode 100644 index 00000000..4b925c9c --- /dev/null +++ b/test/sharefs/test.py @@ -0,0 +1,60 @@ +start_all() +machine.wait_for_unit("multi-user.target") + +# To check sharefs version: +print(machine.succeed("sharefs -V")) + +# Make sure sharefs started: +machine.wait_for_unit("sdcard.mount") + +machine.succeed("mkdir /mnt") +def check_bad_opts_output(opts, want, source="/etc", privileged=False): + output = machine.fail(("" if privileged else "sudo -u alice -i ") + f"sharefs -f -o source={source},{opts} /mnt 2>&1") + if output != want: + raise Exception(f"unexpected output: {output}") + +# Malformed setuid/setgid representation: +check_bad_opts_output("setuid=ff", "sharefs: invalid value for option setuid\n") +check_bad_opts_output("setgid=ff", "sharefs: invalid value for option setgid\n") + +# Bounds check for setuid/setgid: +check_bad_opts_output("setuid=0", "sharefs: invalid value for option setuid\n") +check_bad_opts_output("setgid=0", "sharefs: invalid value for option setgid\n") +check_bad_opts_output("setuid=-1", "sharefs: invalid value for option setuid\n") +check_bad_opts_output("setgid=-1", "sharefs: invalid value for option setgid\n") + +# Non-root setuid/setgid: +check_bad_opts_output("setuid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") +check_bad_opts_output("setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") +check_bad_opts_output("setuid=1023,setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") +check_bad_opts_output("mkdir", "sharefs: mkdir has no effect when not starting as root\n") + +# Starting as root without setuid/setgid: +check_bad_opts_output("allow_other", "sharefs: setuid and setgid must not be 0\n", privileged=True) +check_bad_opts_output("setuid=1023", "sharefs: setuid and setgid must not be 0\n", privileged=True) +check_bad_opts_output("setgid=1023", "sharefs: setuid and setgid must not be 0\n", privileged=True) + +# Make sure nothing actually got mounted: +machine.fail("umount /mnt") +machine.succeed("rmdir /mnt") + +# Unprivileged mount/unmount: +machine.succeed("sudo -u alice -i mkdir /home/alice/{sdcard,persistent}") +machine.succeed("sudo -u alice -i sharefs -o source=/home/alice/persistent /home/alice/sdcard") +machine.succeed("sudo -u alice -i touch /home/alice/sdcard/check") +machine.succeed("sudo -u alice -i umount /home/alice/sdcard") +machine.succeed("sudo -u alice -i rm /home/alice/persistent/check") +machine.succeed("sudo -u alice -i rmdir /home/alice/{sdcard,persistent}") + +# Benchmark sharefs: +machine.succeed("fs_mark -v -d /sdcard/fs_mark -l /tmp/fs_log.txt") +machine.copy_from_vm("/tmp/fs_log.txt", "") + +# Check permissions: +machine.succeed("sudo -u sharefs touch /var/lib/hakurei/sdcard/fs_mark/.check") +machine.succeed("sudo -u sharefs rm /var/lib/hakurei/sdcard/fs_mark/.check") +machine.succeed("sudo -u alice rm -rf /sdcard/fs_mark") +machine.fail("ls /var/lib/hakurei/sdcard/fs_mark") + +# Run hakurei tests on sharefs: +machine.succeed("sudo -u alice -i sharefs-workload-hakurei-tests") |
