diff options
| -rw-r--r-- | cmd/dist/main.go | 37 | ||||
| -rw-r--r-- | cmd/hakurei/command.go | 8 | ||||
| -rw-r--r-- | cmd/hakurei/command_test.go | 6 | ||||
| -rw-r--r-- | cmd/hakurei/testsuite/sandbox/main.go | 191 | ||||
| -rw-r--r-- | hst/container.go | 7 | ||||
| -rw-r--r-- | internal/outcome/outcome.go | 9 | ||||
| -rw-r--r-- | internal/outcome/shim.go | 7 | ||||
| -rw-r--r-- | internal/testsuite/testsuite.go | 361 | ||||
| -rw-r--r-- | internal/wayland/security-context-v1-protocol.c | 2 | ||||
| -rw-r--r-- | internal/wayland/security-context-v1-protocol.h | 2 |
10 files changed, 433 insertions, 197 deletions
diff --git a/cmd/dist/main.go b/cmd/dist/main.go index 1034f282..40a0583c 100644 --- a/cmd/dist/main.go +++ b/cmd/dist/main.go @@ -4,6 +4,7 @@ package main import ( "archive/tar" + "bytes" "compress/gzip" "context" "crypto/sha512" @@ -57,6 +58,7 @@ func main() { verbose := os.Getenv("VERBOSE") != "" runTests := os.Getenv("HAKUREI_DIST_MAKE") == "" race := os.Getenv("HAKUREI_RACE") != "" + dynamic := os.Getenv("HAKUREI_DYNAMIC") != "" version = getenv("HAKUREI_VERSION", strings.TrimSpace(version)) prefix := getenv("PREFIX", "/usr/local") destdir := getenv("DESTDIR", "dist") @@ -69,6 +71,12 @@ func main() { version += "-race" } + if err := os.Setenv("CC", "clang"); err != nil { + log.Fatal(err) + } else if err = os.Setenv("CXX", "clang++"); err != nil { + log.Fatal(err) + } + if err := os.MkdirAll(destdir, 0755); err != nil { log.Fatal(err) } @@ -94,15 +102,36 @@ func main() { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() + const stubFlag = "-buildvcs=false" verboseFlag := "-v" if !verbose { - verboseFlag = "-buildvcs=false" + verboseFlag = stubFlag } raceFlag := "-race" if !race { - raceFlag = "-buildvcs=false" + raceFlag = stubFlag + } + + ldflagsStatic := " -extldflags=-static" + if dynamic { + cmd := exec.CommandContext(ctx, "llvm-config", "--host-target") + cmd.Stderr = os.Stderr + var p []byte + p, err = cmd.Output() + if err != nil { + log.Fatal(err) + } + p = bytes.TrimSpace(p) + _, abi, ok := strings.CutLast(string(p), "-") + if !ok { + log.Fatalf("invalid target %q", string(p)) + } + version += "-" + abi + + log.Println("WARNING: dynamic linking is not supported") + ldflagsStatic = "" } log.Printf("Building hakurei %s for %s/%s.", version, runtime.GOOS, runtime.GOARCH) @@ -112,7 +141,7 @@ func main() { "-trimpath", verboseFlag, raceFlag, "-o", s, "-ldflags=-s -w "+ - "-buildid= -linkmode external -extldflags=-static "+ + "-buildid= -linkmode external"+ldflagsStatic+" "+ "-X hakurei.app/internal/info.buildVersion="+version+" "+ "-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+ "-X hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu", @@ -136,7 +165,7 @@ func main() { log.Println("##### Testing Hakurei.") mustRun( ctx, nil, "go", "test", raceFlag, - "-ldflags=-buildid= -linkmode external -extldflags=-static", + "-ldflags=-buildid= -linkmode external"+ldflagsStatic, "./...", ) log.Println() diff --git a/cmd/hakurei/command.go b/cmd/hakurei/command.go index 76004d4f..f14945ad 100644 --- a/cmd/hakurei/command.go +++ b/cmd/hakurei/command.go @@ -106,7 +106,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr flagSchedPolicy string flagSchedPriority int - flagPrivateRuntime, flagPrivateTmpdir bool + flagNoWait, flagPrivateRuntime, flagPrivateTmpdir bool flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool ) @@ -194,6 +194,10 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr }, } + if flagNoWait { + config.Container.AdoptWaitDelay = -1 + } + if err := config.SchedPolicy.UnmarshalText( []byte(flagSchedPolicy), ); err != nil { @@ -315,6 +319,8 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr "Scheduling policy to set for the container"). Flag(&flagSchedPriority, "priority", command.IntFlag(0), "Scheduling priority to set for the container"). + Flag(&flagNoWait, "no-wait", command.BoolFlag(false), + "Do not wait for lingering processes to terminate"). Flag(&flagPrivateRuntime, "private-runtime", command.BoolFlag(false), "Do not share XDG_RUNTIME_DIR between containers under the same identity"). Flag(&flagPrivateTmpdir, "private-tmpdir", command.BoolFlag(false), diff --git a/cmd/hakurei/command_test.go b/cmd/hakurei/command_test.go index 059e18b1..49a1f9d4 100644 --- a/cmd/hakurei/command_test.go +++ b/cmd/hakurei/command_test.go @@ -39,8 +39,8 @@ commands: [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--policy <value>] [--priority <int>] - [--private-runtime] [--private-tmpdir] [--wayland] [-X] - [--dbus] [--pipewire] [--pulse] <command> [<args>] + [--no-wait] [--private-runtime] [--private-tmpdir] + [--wayland] [-X] [--dbus] [--pipewire] [--pulse] <command> [<args>] flags: -X Enable direct connection to X11 @@ -62,6 +62,8 @@ flags: Reverse-DNS style Application identifier, leave empty to inherit instance identifier -mpris Allow owning MPRIS D-Bus path, has no effect if custom config is available + -no-wait + Do not wait for lingering processes to terminate -pipewire Enable connection to PipeWire via SecurityContext -policy string diff --git a/cmd/hakurei/testsuite/sandbox/main.go b/cmd/hakurei/testsuite/sandbox/main.go index 88e028e1..a85ec485 100644 --- a/cmd/hakurei/testsuite/sandbox/main.go +++ b/cmd/hakurei/testsuite/sandbox/main.go @@ -5,11 +5,8 @@ package main import ( - "bytes" "context" - "encoding/json" "fmt" - "io" "log" "os" "os/exec" @@ -21,31 +18,13 @@ import ( "sync" "syscall" - "hakurei.app/check" "hakurei.app/fhs" "hakurei.app/hst" - "hakurei.app/internal/store" "hakurei.app/internal/testsuite" "hakurei.app/cmd/hakurei/testsuite/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( @@ -56,14 +35,14 @@ func mustStart( ) (pid int, done <-chan error) { _serial := strconv.FormatUint(serial, 10) _, done = testsuite.MustStartWith( - ctx, cred, nil, files, + ctx, cred, false, nil, files, "hakurei", "exec", "-a", strconv.Itoa(identity), "sleep", "infinity", _serial, ) var stat syscall.Stat_t - pid = mustScanFor(func(s *testsuite.StatScanner) bool { + pid = testsuite.MustScanFor(func(s *testsuite.StatScanner) bool { select { case err := <-done: if err == nil { @@ -223,12 +202,12 @@ func main() { for _, p := range []byte{'a', 'b', 'i', 'p'} { testsuite.MustFail( - &cred, nil, + &cred, nil, false, "hakurei", "exec", "capsh", "--has-"+string(p)+"=CAP_SYS_ADMIN", ) } testsuite.MustFail( - &cred, nil, + &cred, nil, false, "hakurei", "exec", "umount", "-R", "/dev", ) }) @@ -292,12 +271,7 @@ func main() { } }) - 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) - } - + testsuite.MustPrepareUserDirs(&cred) var swg sync.WaitGroup defer swg.Wait() dbusEnv := testsuite.MustStartSessionBus(&cred) @@ -310,156 +284,17 @@ func main() { } 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 + _, pid := testsuite.MustStartHakurei( + ctx, &wg, &cred, dbusEnv, + func(err error, output string) { + log.Printf("completed test case %s\n%s", name, output) + if err != nil { + log.Fatalf("test case %s: %v", name, err) } - 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 - }) + }, true, false, &tc.Hakurei, + ) testsuite.MustCheckFilter( pid, diff --git a/hst/container.go b/hst/container.go index 824b0982..c4cd2712 100644 --- a/hst/container.go +++ b/hst/container.go @@ -141,6 +141,13 @@ type ContainerConfig struct { // [WaitDelayDefault]. WaitDelay time.Duration `json:"wait_delay,omitempty"` + // Duration in nanoseconds to wait for lingering processes after the initial + // process terminates. + // + // Defaults to five seconds if zero or greater than five seconds. Values + // lesser than zero is equivalent to zero. + AdoptWaitDelay time.Duration `json:"adopt_wait_delay,omitempty"` + // Initial process environment variables. Env map[string]string `json:"env"` diff --git a/internal/outcome/outcome.go b/internal/outcome/outcome.go index 414b421f..19ca9c7b 100644 --- a/internal/outcome/outcome.go +++ b/internal/outcome/outcome.go @@ -6,6 +6,7 @@ import ( "errors" "maps" "strconv" + "time" "hakurei.app/check" "hakurei.app/container" @@ -125,6 +126,14 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *h s.Shim.WaitDelay = s.Container.WaitDelay } + if s.Container.AdoptWaitDelay > 5*time.Second { + s.Shim.AdoptWaitDelay = 5 * time.Second + } else if s.Container.AdoptWaitDelay < 0 { + s.Shim.AdoptWaitDelay = -1 + } else { + s.Shim.AdoptWaitDelay = s.Container.AdoptWaitDelay + } + if s.Container.Flags&hst.FMapRealUID != 0 { s.Mapuid, s.Mapgid = k.getuid(), k.getgid() } else { diff --git a/internal/outcome/shim.go b/internal/outcome/shim.go index 6e039b65..2e7b0910 100644 --- a/internal/outcome/shim.go +++ b/internal/outcome/shim.go @@ -72,6 +72,12 @@ type shimParams struct { // Limits are enforced on the priv side. WaitDelay time.Duration + // Duration to wait after the initial process terminates before all + // lingering processes are killed. + // + // Limits are enforced on the priv side. + AdoptWaitDelay time.Duration + // Verbosity pass through from [message.Msg]. Verbose bool @@ -286,6 +292,7 @@ func shimEntrypoint(k syscallDispatcher) { // bounds and default enforced in finalise.go z.WaitDelay = state.Shim.WaitDelay + z.AdoptWaitDelay = state.Shim.AdoptWaitDelay if stateParams.pipewirePulsePath != nil { zpw := container.NewCommand(ctx, msg, stateParams.pipewirePulsePath, pipewirePulseName) diff --git a/internal/testsuite/testsuite.go b/internal/testsuite/testsuite.go index 2c82e1e9..fee4952c 100644 --- a/internal/testsuite/testsuite.go +++ b/internal/testsuite/testsuite.go @@ -8,18 +8,27 @@ import ( "bufio" "context" "crypto/sha512" + "encoding/json" "errors" + "io" "log" "os" "os/exec" "os/signal" "os/user" + "path/filepath" + "slices" "strconv" "strings" "sync" "sync/atomic" "syscall" "time" + + "hakurei.app/check" + "hakurei.app/fhs" + "hakurei.app/hst" + "hakurei.app/internal/store" ) // serial is the next value returned by [NewSerial]. @@ -101,9 +110,16 @@ 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) { +func MustFail( + cred *syscall.Credential, + extraEnv []string, + quiet bool, + command ...string, +) { cmd := exec.Command(command[0], command[1:]...) - cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if !quiet { + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + } cmd.SysProcAttr = &syscall.SysProcAttr{ Pdeathsig: syscall.SIGKILL, Credential: cred, @@ -132,12 +148,15 @@ func MustStart(cmd *exec.Cmd) (done <-chan error) { func MustStartWith( ctx context.Context, cred *syscall.Credential, + quiet bool, 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 + if !quiet { + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + } cmd.ExtraFiles = files cmd.SysProcAttr = &syscall.SysProcAttr{ Pdeathsig: syscall.SIGTERM, @@ -147,6 +166,71 @@ func MustStartWith( return cmd.Process, MustStart(cmd) } +// MustOutput is like [MustRun] and [MustFail], but collects the entire output +// in memory and returns them as strings. +func MustOutput( + ctx context.Context, + cred *syscall.Credential, + extraEnv []string, + exitCode int, + name string, + command ...string, +) (string, string) { + var stdout, stderr strings.Builder + cmd := exec.CommandContext(ctx, command[0], command[1:]...) + cmd.Stdout, cmd.Stderr = &stdout, &stderr + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, + Credential: cred, + } + MustAppendEnv(cmd, extraEnv...) + + err := cmd.Run() + if err == nil { + if exitCode == 0 { + return stdout.String(), stderr.String() + } + log.Fatalf( + "%s succeeded unexpectedly"+ + "\nstdout = %q\nstderr = %q", + name, + stdout.String(), + stderr.String(), + ) + } + + if e, ok := errors.AsType[*exec.ExitError](err); !ok { + log.Fatal(err) + } else if e.ExitCode() != exitCode { + log.Fatalf( + "%s: %v"+ + "\nstdout = %q\nstderr = %q", + name, e, + stdout.String(), + stderr.String(), + ) + } + return stdout.String(), stderr.String() +} + +// AssertOutput returns a function that terminates the program if called with +// arguments differing from the ones it is initialised with. +func AssertOutput(action, stdout, stderr string) ( + assert func(stdout, stderr string), +) { + return func(gotStdout, gotStderr string) { + if stdout != gotStdout || stderr != gotStderr { + log.Fatalf( + "unexpected output %s"+ + "\nstdout = %q\nstderr = %q", + action, + gotStdout, + gotStderr, + ) + } + } +} + // MustCheckFilter is like [CheckFilter], but terminates the test suite if a // non-nil error is returned. Otherwise, the tracee is terminated after it // resumes. @@ -202,14 +286,47 @@ func Poll( } const ( - // XDGRuntimeDir is the hardcoded XDG runtime directory for the user - // described by [GetUser]. + // XDGRuntimeDir is the hardcoded XDG runtime directory of the only user + // mapped in hsurc. XDGRuntimeDir = "/var/run/user/1000" // XDGRuntimeEnv is the environment variable string for XDG_RUNTIME_DIR. XDGRuntimeEnv = "XDG_RUNTIME_DIR=" + XDGRuntimeDir + + // HakureiPrefix is the hakurei work directory of the only user mapped in + // hsurc. + HakureiPrefix = "/tmp/hakurei.0/" ) +// MustPrepareUserDirs creates user-specific directories. +func MustPrepareUserDirs(cred *syscall.Credential) { + if err := os.MkdirAll(XDGRuntimeDir, 0700); err != nil { + log.Fatal(err) + } else if err = os.Chown( + XDGRuntimeDir, + int(cred.Uid), + int(cred.Gid), + ); err != nil { + log.Fatal(err) + } +} + +// SessionEnv returns environment variables referring to the hosted session. +func SessionEnv(dbusEnv string) []string { + return []string{ + "PATH=" + os.Getenv("PATH"), + "TERM=xterm", + XDGRuntimeEnv, + WaylandEnv, + "DISPLAY=:0", + dbusEnv, + } +} + +// quietServices is initialised from the environment and controls whether +// session services should have their outputs connected. +var quietServices = os.Getenv("HAKUREI_VERBOSE_SERVICES") == "" + // 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) { @@ -220,7 +337,7 @@ func MustStartSessionBus(cred *syscall.Credential) (dbusEnv string) { // this is never explicitly terminated _, done := MustStartWith( - context.Background(), cred, nil, []*os.File{w}, + context.Background(), cred, quietServices, nil, []*os.File{w}, "dbus-daemon", "--print-address=3", "--address=unix:path="+XDGRuntimeDir+"/dbus", @@ -267,7 +384,7 @@ func MustStartSway( wg.Go(func() { // this is terminated via swaymsg _, done := MustStartWith( - context.Background(), cred, []string{ + context.Background(), cred, quietServices, []string{ "WLR_BACKENDS=headless", XDGRuntimeEnv, SwayEnv, @@ -291,7 +408,7 @@ func MustStartSway( // TerminateSway requests for the sway server to terminate via sway IPC. func TerminateSway(cred *syscall.Credential) { - MustFail(cred, []string{SwayEnv}, "swaymsg", "exit") + MustFail(cred, []string{SwayEnv}, quietServices, "swaymsg", "exit") } // MustStartPipeWire starts a PipeWire server that is never explicitly @@ -299,7 +416,7 @@ func TerminateSway(cred *syscall.Credential) { func MustStartPipeWire(cred *syscall.Credential, dbusEnv string) { // this is never explicitly terminated _, done := MustStartWith( - context.Background(), cred, []string{ + context.Background(), cred, quietServices, []string{ XDGRuntimeEnv, dbusEnv, }, nil, @@ -322,7 +439,7 @@ func MustStartPipeWire(cred *syscall.Credential, dbusEnv string) { ) _, _done := MustStartWith( - context.Background(), cred, []string{ + context.Background(), cred, quietServices, []string{ XDGRuntimeEnv, dbusEnv, }, nil, @@ -336,3 +453,227 @@ func MustStartPipeWire(cred *syscall.Credential, dbusEnv string) { log.Fatal("wireplumber terminated unexpectedly") }() } + +// MustScanFor continuously scans the proc filesystem and calls f for each entry +// visited. +func MustScanFor(f func(ps *StatScanner) bool) int { + var ps StatScanner + + for ps.Scan() { + if f(&ps) { + break + } + } + if err := ps.Err(); err != nil { + log.Fatal(err) + } + return ps.Stat().PID +} + +// StartHakurei starts a hakurei container configured by c. +func StartHakurei( + ctx context.Context, + wg *sync.WaitGroup, + cred *syscall.Credential, + dbusEnv string, + processCommand func(cmd *exec.Cmd), + onDone func(err error, output string), + scan, wait, verbose bool, + c *hst.Config, +) (p hst.State, pid int, err error) { + s := store.New(check.MustAbs("/tmp/hakurei.0/state")) + + hakureiFlags := "" + if verbose { + hakureiFlags += " -v" + } + + cmd := exec.CommandContext(ctx, + "script", "/dev/null", + "-E", "always", + "-qec", + "hakurei"+hakureiFlags+" run "+ + "--identifier-fd=5"+ + " 4 1>&3", + ) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, + Credential: cred, + } + var output strings.Builder + cmd.Stdout, cmd.Stderr = &output, &output + cmd.Env = SessionEnv(dbusEnv) + + var notify, _notify, _conf, conf, ident, _ident *os.File + if notify, _notify, err = os.Pipe(); err != nil { + return + } + cmd.ExtraFiles = append(cmd.ExtraFiles, _notify) + if _conf, conf, err = os.Pipe(); err != nil { + _, _ = notify.Close(), _notify.Close() + return + } + cmd.ExtraFiles = append(cmd.ExtraFiles, _conf) + if ident, _ident, err = os.Pipe(); err != nil { + _, _ = notify.Close(), _notify.Close() + _, _ = conf.Close(), _conf.Close() + return + } + cmd.ExtraFiles = append(cmd.ExtraFiles, _ident) + if processCommand != nil { + processCommand(cmd) + } + + done := MustStart(cmd) + wg.Go(func() { _err := <-done; onDone(_err, output.String()) }) + + if err = errors.Join( + _notify.Close(), + _conf.Close(), + _ident.Close(), + ); err != nil { + _, _, _ = notify.Close(), conf.Close(), ident.Close() + return + } + + if err = json.NewEncoder(conf).Encode(c); err != nil { + _, _, _ = notify.Close(), conf.Close(), ident.Close() + return + } else if err = conf.Close(); err != nil { + _, _ = notify.Close(), ident.Close() + return + } + + if !scan && len(cmd.ExtraFiles) != 3 { + err = errors.Join(notify.Close(), ident.Close()) + return + } + + var id hst.ID + if _, err = io.ReadFull(ident, id[:]); err != nil { + _, _ = notify.Close(), ident.Close() + return + } else if err = ident.Close(); err != nil { + _ = notify.Close() + return + } + + if !scan { + err = notify.Close() + return + } + + if wait { + if _, err = io.ReadFull(notify, make([]byte, 8)); err != nil { + _ = notify.Close() + return + } + } + if err = notify.Close(); err != nil { + return + } + + var ok bool + 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 *StatScanner) bool { + select { + case err = <-done: + if err == nil { + log.Fatal("test process terminated unexpectedly") + } + log.Fatal(err) + default: + break + } + + var args []string + if args, err = ps.Stat().Args(); err != nil { + if IsNotExist(err) { + return false + } + log.Fatal(err) + } else if !slices.Equal(args, c.Container.Args) { + return false + } + + if err = ps.Stat().Stat(&stat); err != nil { + if IsNotExist(err) { + return false + } + log.Fatal(err) + } + + uid := hst.ToUser[uint32](0, uint32(c.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 IsNotExist(err) { + return false + } + log.Fatal(err) + } + + var _stat Stat + if err = _stat.UnmarshalText(t); err != nil { + log.Fatal(err) + } + if _stat.PPID != p.ShimPID { + return false + } + + return true + }) + return +} + +// MustStartHakurei is like [StartHakurei], but terminates the test suite if +// an error is encountered. +func MustStartHakurei( + ctx context.Context, + wg *sync.WaitGroup, + cred *syscall.Credential, + dbusEnv string, + onDone func(err error, output string), + wait, verbose bool, + c *hst.Config, +) (p hst.State, pid int) { + _ctx, cancel := context.WithCancel(ctx) + + var err error + p, pid, err = StartHakurei( + _ctx, wg, cred, dbusEnv, + nil, func(err error, output string) { + defer cancel() + onDone(err, output) + }, true, wait, verbose, c, + ) + if err != nil { + log.Printf("cannot start hakurei container: %v", err) + cancel() + } + return +} diff --git a/internal/wayland/security-context-v1-protocol.c b/internal/wayland/security-context-v1-protocol.c index 5b15b1b4..54cd69d2 100644 --- a/internal/wayland/security-context-v1-protocol.c +++ b/internal/wayland/security-context-v1-protocol.c @@ -1,4 +1,4 @@ -/* Generated by wayland-scanner 1.23.1 */ +/* Generated by wayland-scanner 1.26.0 */ /* * Copyright © 2021 Simon Ser diff --git a/internal/wayland/security-context-v1-protocol.h b/internal/wayland/security-context-v1-protocol.h index f973619e..0b5bc69e 100644 --- a/internal/wayland/security-context-v1-protocol.h +++ b/internal/wayland/security-context-v1-protocol.h @@ -1,4 +1,4 @@ -/* Generated by wayland-scanner 1.23.1 */ +/* Generated by wayland-scanner 1.26.0 */ #ifndef SECURITY_CONTEXT_V1_CLIENT_PROTOCOL_H #define SECURITY_CONTEXT_V1_CLIENT_PROTOCOL_H |
