Compare commits
9 Commits
87781c7658
...
42700ee3be
| Author | SHA1 | Date | |
|---|---|---|---|
|
42700ee3be
|
|||
|
e9fb1d7be5
|
|||
|
dafe9f8efc
|
|||
|
96dd7abd80
|
|||
|
d5fb179012
|
|||
|
462863e290
|
|||
|
2786611b88
|
|||
|
791a1dfa55
|
|||
|
564db6863b
|
@@ -191,7 +191,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
if flagPulse {
|
if flagPulse {
|
||||||
config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSDaemon{
|
config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSDaemon{
|
||||||
Target: fhs.AbsRunUser.Append(strconv.Itoa(container.OverflowUid(msg)), "pulse/native"),
|
Target: fhs.AbsRunUser.Append(strconv.Itoa(container.OverflowUid(msg)), "pulse/native"),
|
||||||
Exec: shell, Args: []string{"-lc", "pipewire-pulse"},
|
Exec: shell, Args: []string{"-lc", "exec pipewire-pulse"},
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ type syscallDispatcher interface {
|
|||||||
notify(c chan<- os.Signal, sig ...os.Signal)
|
notify(c chan<- os.Signal, sig ...os.Signal)
|
||||||
// start starts [os/exec.Cmd].
|
// start starts [os/exec.Cmd].
|
||||||
start(c *exec.Cmd) error
|
start(c *exec.Cmd) error
|
||||||
// wait waits on [os/exec.Cmd].
|
|
||||||
wait(c *exec.Cmd) error
|
|
||||||
// signal signals the underlying process of [os/exec.Cmd].
|
// signal signals the underlying process of [os/exec.Cmd].
|
||||||
signal(c *exec.Cmd, sig os.Signal) error
|
signal(c *exec.Cmd, sig os.Signal) error
|
||||||
// evalSymlinks provides [filepath.EvalSymlinks].
|
// evalSymlinks provides [filepath.EvalSymlinks].
|
||||||
@@ -172,7 +170,6 @@ func (direct) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) erro
|
|||||||
}
|
}
|
||||||
func (direct) notify(c chan<- os.Signal, sig ...os.Signal) { signal.Notify(c, sig...) }
|
func (direct) notify(c chan<- os.Signal, sig ...os.Signal) { signal.Notify(c, sig...) }
|
||||||
func (direct) start(c *exec.Cmd) error { return c.Start() }
|
func (direct) start(c *exec.Cmd) error { return c.Start() }
|
||||||
func (direct) wait(c *exec.Cmd) error { return c.Wait() }
|
|
||||||
func (direct) signal(c *exec.Cmd, sig os.Signal) error { return c.Process.Signal(sig) }
|
func (direct) signal(c *exec.Cmd, sig os.Signal) error { return c.Process.Signal(sig) }
|
||||||
func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
||||||
|
|
||||||
|
|||||||
@@ -493,21 +493,6 @@ func (k *kstub) start(c *exec.Cmd) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) wait(c *exec.Cmd) error {
|
|
||||||
k.Helper()
|
|
||||||
expect := k.Expects("wait")
|
|
||||||
err := expect.Error(
|
|
||||||
stub.CheckArg(k.Stub, "c.Path", c.Path, 0),
|
|
||||||
stub.CheckArgReflect(k.Stub, "c.Args", c.Args, 1),
|
|
||||||
stub.CheckArgReflect(k.Stub, "c.Env", c.Env, 2),
|
|
||||||
stub.CheckArg(k.Stub, "c.Dir", c.Dir, 3))
|
|
||||||
|
|
||||||
if mgc, ok := expect.Ret.(uintptr); ok && mgc == stub.PanicExit {
|
|
||||||
panic(stub.PanicExit)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kstub) signal(c *exec.Cmd, sig os.Signal) error {
|
func (k *kstub) signal(c *exec.Cmd, sig os.Signal) error {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
expect := k.Expects("signal")
|
expect := k.Expects("signal")
|
||||||
|
|||||||
@@ -7,31 +7,36 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
// messageFromError returns a printable error message for a supported concrete type.
|
// messageFromError returns a printable error message for a supported concrete type.
|
||||||
func messageFromError(err error) (string, bool) {
|
func messageFromError(err error) (m string, ok bool) {
|
||||||
if m, ok := messagePrefixP[MountError]("cannot ", err); ok {
|
if m, ok = messagePrefixP[MountError]("cannot ", err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefixP[os.PathError]("cannot ", err); ok {
|
if m, ok = messagePrefixP[os.PathError]("cannot ", err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefixP[check.AbsoluteError]("", err); ok {
|
if m, ok = messagePrefixP[check.AbsoluteError](zeroString, err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefix[OpRepeatError]("", err); ok {
|
if m, ok = messagePrefix[OpRepeatError](zeroString, err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefix[OpStateError]("", err); ok {
|
if m, ok = messagePrefix[OpStateError](zeroString, err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if m, ok := messagePrefixP[vfs.DecoderError]("cannot ", err); ok {
|
if m, ok = messagePrefixP[vfs.DecoderError]("cannot ", err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefix[TmpfsSizeError]("", err); ok {
|
if m, ok = messagePrefix[TmpfsSizeError](zeroString, err); ok {
|
||||||
return m, ok
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, ok = message.GetMessage(err); ok {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return zeroString, false
|
return zeroString, false
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -19,24 +21,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
/* intermediate tmpfs mount point
|
/* intermediateHostPath is the pathname of the intermediate tmpfs mount point.
|
||||||
|
|
||||||
this path might seem like a weird choice, however there are many good reasons to use it:
|
This path might seem like a weird choice, however there are many good reasons to use it:
|
||||||
- the contents of this path is never exposed to the container:
|
- The contents of this path is never exposed to the container:
|
||||||
the tmpfs root established here effectively becomes anonymous after pivot_root
|
The tmpfs root established here effectively becomes anonymous after pivot_root.
|
||||||
- it is safe to assume this path exists and is a directory:
|
- It is safe to assume this path exists and is a directory:
|
||||||
this program will not work correctly without a proper /proc and neither will most others
|
This program will not work correctly without a proper /proc and neither will most others.
|
||||||
- this path belongs to the container init:
|
- This path belongs to the container init:
|
||||||
the container init is not any more privileged or trusted than the rest of the container
|
The container init is not any more privileged or trusted than the rest of the container.
|
||||||
- this path is only accessible by init and root:
|
- This path is only accessible by init and root:
|
||||||
the container init sets SUID_DUMP_DISABLE and terminates if that fails;
|
The container init sets SUID_DUMP_DISABLE and terminates if that fails.
|
||||||
|
|
||||||
it should be noted that none of this should become relevant at any point since the resulting
|
It should be noted that none of this should become relevant at any point since the resulting
|
||||||
intermediate root tmpfs should be effectively anonymous */
|
intermediate root tmpfs should be effectively anonymous. */
|
||||||
intermediateHostPath = fhs.Proc + "self/fd"
|
intermediateHostPath = fhs.Proc + "self/fd"
|
||||||
|
|
||||||
// setup params file descriptor
|
// setupEnv is the name of the environment variable holding the string representation of
|
||||||
|
// the read end file descriptor of the setup params pipe.
|
||||||
setupEnv = "HAKUREI_SETUP"
|
setupEnv = "HAKUREI_SETUP"
|
||||||
|
|
||||||
|
// exitUnexpectedWait4 is the exit code if wait4 returns an unexpected errno.
|
||||||
|
exitUnexpectedWait4 = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -64,12 +70,29 @@ type (
|
|||||||
// setupState persists context between Ops.
|
// setupState persists context between Ops.
|
||||||
setupState struct {
|
setupState struct {
|
||||||
nonrepeatable uintptr
|
nonrepeatable uintptr
|
||||||
|
|
||||||
|
// Whether early reaping has concluded. Must only be accessed in the wait4 loop.
|
||||||
|
processConcluded bool
|
||||||
|
// Process to syscall.WaitStatus populated in the wait4 loop. Freed after early reaping concludes.
|
||||||
|
process map[int]WaitStatus
|
||||||
|
// Synchronises access to process.
|
||||||
|
processMu sync.RWMutex
|
||||||
|
|
||||||
*Params
|
*Params
|
||||||
context.Context
|
context.Context
|
||||||
message.Msg
|
message.Msg
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// terminated returns whether the specified pid has been reaped, and its
|
||||||
|
// syscall.WaitStatus if it had. This is only usable by [Op].
|
||||||
|
func (state *setupState) terminated(pid int) (wstatus WaitStatus, ok bool) {
|
||||||
|
state.processMu.RLock()
|
||||||
|
wstatus, ok = state.process[pid]
|
||||||
|
state.processMu.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Grow grows the slice Ops points to using [slices.Grow].
|
// Grow grows the slice Ops points to using [slices.Grow].
|
||||||
func (f *Ops) Grow(n int) { *f = slices.Grow(*f, n) }
|
func (f *Ops) Grow(n int) { *f = slices.Grow(*f, n) }
|
||||||
|
|
||||||
@@ -185,7 +208,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
state := &setupState{Params: ¶ms.Params, Msg: msg, Context: ctx}
|
state := &setupState{process: make(map[int]WaitStatus), Params: ¶ms.Params, Msg: msg, Context: ctx}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
/* early is called right before pivot_root into intermediate root;
|
/* early is called right before pivot_root into intermediate root;
|
||||||
@@ -336,35 +359,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
k.umask(oldmask)
|
k.umask(oldmask)
|
||||||
|
|
||||||
// called right before startup of initial process, all state changes to the
|
// winfo represents an exited process from wait4.
|
||||||
// current process is prohibited during late
|
|
||||||
for i, op := range *params.Ops {
|
|
||||||
// ops already checked during early setup
|
|
||||||
if err := op.late(state, k); err != nil {
|
|
||||||
if m, ok := messageFromError(err); ok {
|
|
||||||
k.fatal(msg, m)
|
|
||||||
} else {
|
|
||||||
k.fatalf(msg, "cannot complete op at index %d: %v", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := closeSetup(); err != nil {
|
|
||||||
k.fatalf(msg, "cannot close setup pipe: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(params.Path.String())
|
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
||||||
cmd.Args = params.Args
|
|
||||||
cmd.Env = params.Env
|
|
||||||
cmd.ExtraFiles = extraFiles
|
|
||||||
cmd.Dir = params.Dir.String()
|
|
||||||
|
|
||||||
msg.Verbosef("starting initial program %s", params.Path)
|
|
||||||
if err := k.start(cmd); err != nil {
|
|
||||||
k.fatalf(msg, "%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
type winfo struct {
|
type winfo struct {
|
||||||
wpid int
|
wpid int
|
||||||
wstatus WaitStatus
|
wstatus WaitStatus
|
||||||
@@ -374,9 +369,13 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
// when there are no longer any processes left to reap
|
// when there are no longer any processes left to reap
|
||||||
info := make(chan winfo, 1)
|
info := make(chan winfo, 1)
|
||||||
|
|
||||||
|
// whether initial process has started
|
||||||
|
var initialProcessStarted atomic.Bool
|
||||||
|
|
||||||
k.new(func(k syscallDispatcher) {
|
k.new(func(k syscallDispatcher) {
|
||||||
k.lockOSThread()
|
k.lockOSThread()
|
||||||
|
|
||||||
|
wait4:
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
wpid = -2
|
wpid = -2
|
||||||
@@ -390,7 +389,21 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if wpid != -2 {
|
if wpid != -2 {
|
||||||
info <- winfo{wpid, wstatus}
|
if !state.processConcluded {
|
||||||
|
state.processMu.Lock()
|
||||||
|
if state.process == nil {
|
||||||
|
// early reaping has already concluded at this point
|
||||||
|
state.processConcluded = true
|
||||||
|
info <- winfo{wpid, wstatus}
|
||||||
|
} else {
|
||||||
|
// initial process has not yet been created, and the
|
||||||
|
// info channel is not yet being received from
|
||||||
|
state.process[wpid] = wstatus
|
||||||
|
}
|
||||||
|
state.processMu.Unlock()
|
||||||
|
} else {
|
||||||
|
info <- winfo{wpid, wstatus}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = EINTR
|
err = EINTR
|
||||||
@@ -398,13 +411,55 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
wpid, err = k.wait4(-1, &wstatus, 0, nil)
|
wpid, err = k.wait4(-1, &wstatus, 0, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !errors.Is(err, ECHILD) {
|
if !errors.Is(err, ECHILD) {
|
||||||
k.printf(msg, "unexpected wait4 response: %v", err)
|
k.printf(msg, "unexpected wait4 response: %v", err)
|
||||||
|
} else if !initialProcessStarted.Load() {
|
||||||
|
// initial process has not yet been reached and all daemons
|
||||||
|
// terminated or none were started in the first place
|
||||||
|
time.Sleep(500 * time.Microsecond)
|
||||||
|
goto wait4
|
||||||
}
|
}
|
||||||
|
|
||||||
close(info)
|
close(info)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// called right before startup of initial process, all state changes to the
|
||||||
|
// current process is prohibited during late
|
||||||
|
for i, op := range *params.Ops {
|
||||||
|
// ops already checked during early setup
|
||||||
|
if err := op.late(state, k); err != nil {
|
||||||
|
if m, ok := messageFromError(err); ok {
|
||||||
|
k.fatal(msg, m)
|
||||||
|
} else if errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
k.fatalf(msg, "%s deadline exceeded", op.String())
|
||||||
|
} else {
|
||||||
|
k.fatalf(msg, "cannot complete op at index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// early reaping has concluded, this must happen before initial process is created
|
||||||
|
state.processMu.Lock()
|
||||||
|
state.process = nil
|
||||||
|
state.processMu.Unlock()
|
||||||
|
|
||||||
|
if err := closeSetup(); err != nil {
|
||||||
|
k.fatalf(msg, "cannot close setup pipe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(params.Path.String())
|
||||||
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
cmd.Args = params.Args
|
||||||
|
cmd.Env = params.Env
|
||||||
|
cmd.ExtraFiles = extraFiles
|
||||||
|
cmd.Dir = params.Dir.String()
|
||||||
|
|
||||||
|
msg.Verbosef("starting initial process %s", params.Path)
|
||||||
|
if err := k.start(cmd); err != nil {
|
||||||
|
k.fatalf(msg, "%v", err)
|
||||||
|
}
|
||||||
|
initialProcessStarted.Store(true)
|
||||||
|
|
||||||
// handle signals to dump withheld messages
|
// handle signals to dump withheld messages
|
||||||
sig := make(chan os.Signal, 2)
|
sig := make(chan os.Signal, 2)
|
||||||
k.notify(sig, CancelSignal,
|
k.notify(sig, CancelSignal,
|
||||||
@@ -413,7 +468,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
// closed after residualProcessTimeout has elapsed after initial process death
|
// closed after residualProcessTimeout has elapsed after initial process death
|
||||||
timeout := make(chan struct{})
|
timeout := make(chan struct{})
|
||||||
|
|
||||||
r := 2
|
r := exitUnexpectedWait4
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case s := <-sig:
|
case s := <-sig:
|
||||||
|
|||||||
@@ -1983,11 +1983,20 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(13)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(13)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, nil, stub.UniqueError(12)),
|
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, nil, stub.UniqueError(12)),
|
||||||
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(12)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(12)}}, nil, nil),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* wait4 */
|
||||||
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
|
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
||||||
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil, stub.PanicExit}, 0, syscall.ECHILD),
|
||||||
|
}}},
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"lowlastcap signaled cancel forward error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
{"lowlastcap signaled cancel forward error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||||
@@ -2062,10 +2071,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(10)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(10)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- CancelSignal }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- CancelSignal }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{"forwarding context cancellation"}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{"forwarding context cancellation"}}, nil, nil),
|
||||||
// magicWait4Signal as ret causes wait4 stub to unblock
|
// magicWait4Signal as ret causes wait4 stub to unblock
|
||||||
@@ -2162,10 +2171,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- syscall.SIGQUIT }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- syscall.SIGQUIT }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"got %s, forwarding to initial process", []any{"quit"}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"got %s, forwarding to initial process", []any{"quit"}}, nil, nil),
|
||||||
// magicWait4Signal as ret causes wait4 stub to unblock
|
// magicWait4Signal as ret causes wait4 stub to unblock
|
||||||
@@ -2262,10 +2271,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"got %s", []any{"interrupt"}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"got %s", []any{"interrupt"}}, nil, nil),
|
||||||
call("beforeExit", stub.ExpectArgs{}, nil, nil),
|
call("beforeExit", stub.ExpectArgs{}, nil, nil),
|
||||||
@@ -2353,10 +2362,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(5)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(5)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
@@ -2448,10 +2457,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(3)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(3)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
@@ -2586,10 +2595,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(1)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(1)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
@@ -2728,10 +2737,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(11), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(11), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(12), "extra file 2"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(12), "extra file 2"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(0)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(0)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/bin/zsh")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/bin/zsh")}}, nil, nil),
|
||||||
call("start", stub.ExpectArgs{"/bin/zsh", []string{"zsh", "-c", "exec vim"}, []string{"DISPLAY=:0"}, "/.hakurei"}, &os.Process{Pid: 0xcafe}, nil),
|
call("start", stub.ExpectArgs{"/bin/zsh", []string{"zsh", "-c", "exec vim"}, []string{"DISPLAY=:0"}, "/.hakurei"}, &os.Process{Pid: 0xcafe}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"slices"
|
"slices"
|
||||||
"sync/atomic"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -41,6 +41,38 @@ type DaemonOp struct {
|
|||||||
Args []string
|
Args []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// earlyTerminationError is returned by [DaemonOp] when a daemon terminates
|
||||||
|
// before [DaemonOp.Target] appears.
|
||||||
|
type earlyTerminationError struct {
|
||||||
|
// Returned by [DaemonOp.String].
|
||||||
|
op string
|
||||||
|
// Copied from wait4 loop.
|
||||||
|
wstatus syscall.WaitStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *earlyTerminationError) Error() string {
|
||||||
|
res := ""
|
||||||
|
switch {
|
||||||
|
case e.wstatus.Exited():
|
||||||
|
res = "exit status " + strconv.Itoa(e.wstatus.ExitStatus())
|
||||||
|
case e.wstatus.Signaled():
|
||||||
|
res = "signal: " + e.wstatus.Signal().String()
|
||||||
|
case e.wstatus.Stopped():
|
||||||
|
res = "stop signal: " + e.wstatus.StopSignal().String()
|
||||||
|
if e.wstatus.StopSignal() == syscall.SIGTRAP && e.wstatus.TrapCause() != 0 {
|
||||||
|
res += " (trap " + strconv.Itoa(e.wstatus.TrapCause()) + ")"
|
||||||
|
}
|
||||||
|
case e.wstatus.Continued():
|
||||||
|
res = "continued"
|
||||||
|
}
|
||||||
|
if e.wstatus.CoreDump() {
|
||||||
|
res += " (core dumped)"
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *earlyTerminationError) Message() string { return e.op + " " + e.Error() }
|
||||||
|
|
||||||
func (d *DaemonOp) Valid() bool { return d != nil && d.Target != nil && d.Path != nil }
|
func (d *DaemonOp) Valid() bool { return d != nil && d.Target != nil && d.Path != nil }
|
||||||
func (d *DaemonOp) early(*setupState, syscallDispatcher) error { return nil }
|
func (d *DaemonOp) early(*setupState, syscallDispatcher) error { return nil }
|
||||||
func (d *DaemonOp) apply(*setupState, syscallDispatcher) error { return nil }
|
func (d *DaemonOp) apply(*setupState, syscallDispatcher) error { return nil }
|
||||||
@@ -59,17 +91,9 @@ func (d *DaemonOp) late(state *setupState, k syscallDispatcher) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var done atomic.Pointer[error]
|
|
||||||
k.new(func(k syscallDispatcher) {
|
|
||||||
err := k.wait(cmd)
|
|
||||||
done.Store(&err)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
state.Verbosef("%s %v", d.String(), err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
deadline := time.Now().Add(daemonTimeout)
|
deadline := time.Now().Add(daemonTimeout)
|
||||||
|
var wstatusErr error
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if _, err := k.stat(d.Target.String()); err != nil {
|
if _, err := k.stat(d.Target.String()); err != nil {
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
@@ -82,8 +106,13 @@ func (d *DaemonOp) late(state *setupState, k syscallDispatcher) error {
|
|||||||
return context.DeadlineExceeded
|
return context.DeadlineExceeded
|
||||||
}
|
}
|
||||||
|
|
||||||
if errP := done.Load(); errP != nil {
|
if wstatusErr != nil {
|
||||||
return *errP
|
return wstatusErr
|
||||||
|
}
|
||||||
|
if wstatus, ok := state.terminated(cmd.Process.Pid); ok {
|
||||||
|
// check once again: process could have satisfied Target between stat and the lookup
|
||||||
|
wstatusErr = &earlyTerminationError{d.String(), wstatus}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(500 * time.Microsecond)
|
time.Sleep(500 * time.Microsecond)
|
||||||
|
|||||||
@@ -6,8 +6,36 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestEarlyTerminationError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{"exited", &earlyTerminationError{
|
||||||
|
`daemon providing "/run/user/1971/pulse/native"`, 127 << 8,
|
||||||
|
}, "exit status 127", `daemon providing "/run/user/1971/pulse/native" exit status 127`},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if got := tc.err.Error(); got != tc.want {
|
||||||
|
t.Errorf("Error: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
if got := tc.err.(message.Error).Message(); got != tc.msg {
|
||||||
|
t.Errorf("Message: %s, want %s", got, tc.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDaemonOp(t *testing.T) {
|
func TestDaemonOp(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -23,15 +51,12 @@ func TestDaemonOp(t *testing.T) {
|
|||||||
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting %s", []any{`daemon providing "/run/user/1971/pulse/native"`}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting %s", []any{`daemon providing "/run/user/1971/pulse/native"`}}, nil, nil),
|
||||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/pipewire-pulse", []string{"/run/current-system/sw/bin/pipewire-pulse", "-v"}, []string{"\x00"}, "/"}, &os.Process{Pid: 0xcafe}, nil),
|
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/pipewire-pulse", []string{"/run/current-system/sw/bin/pipewire-pulse", "-v"}, []string{"\x00"}, "/"}, &os.Process{Pid: 0xcafe}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), os.ErrNotExist),
|
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), os.ErrNotExist),
|
||||||
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), os.ErrNotExist),
|
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), os.ErrNotExist),
|
||||||
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), os.ErrNotExist),
|
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), os.ErrNotExist),
|
||||||
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), nil),
|
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), nil),
|
||||||
call("verbosef", stub.ExpectArgs{"daemon process %d ready", []any{0xcafe}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"daemon process %d ready", []any{0xcafe}}, nil, nil),
|
||||||
}, Tracks: []stub.Expect{{Calls: []stub.Call{
|
}}, nil},
|
||||||
call("wait", stub.ExpectArgs{"/run/current-system/sw/bin/pipewire-pulse", []string{"/run/current-system/sw/bin/pipewire-pulse", "-v"}, []string{"\x00"}, "/"}, uintptr(stub.PanicExit), nil),
|
|
||||||
}}}}, nil},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpsValid(t, []opValidTestCase{
|
checkOpsValid(t, []opValidTestCase{
|
||||||
|
|||||||
@@ -674,6 +674,12 @@ func (ctx *Context) consume(receiveRemaining []byte) (remaining []byte, err erro
|
|||||||
if err = header.UnmarshalBinary(remaining[:SizeHeader]); err != nil {
|
if err = header.UnmarshalBinary(remaining[:SizeHeader]); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remote sequence sometimes do not start with 0
|
||||||
|
if ctx.remoteSequence == 0 {
|
||||||
|
ctx.remoteSequence = header.Sequence
|
||||||
|
}
|
||||||
|
|
||||||
if header.Sequence != ctx.remoteSequence {
|
if header.Sequence != ctx.remoteSequence {
|
||||||
return remaining, UnexpectedSequenceError(header.Sequence)
|
return remaining, UnexpectedSequenceError(header.Sequence)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ in
|
|||||||
path = cfg.shell;
|
path = cfg.shell;
|
||||||
args = [
|
args = [
|
||||||
"-lc"
|
"-lc"
|
||||||
"pipewire-pulse"
|
"exec pipewire-pulse"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
++ [
|
++ [
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "hakurei";
|
pname = "hakurei";
|
||||||
version = "0.3.1";
|
version = "0.3.2";
|
||||||
|
|
||||||
srcFiltered = builtins.path {
|
srcFiltered = builtins.path {
|
||||||
name = "${pname}-src";
|
name = "${pname}-src";
|
||||||
|
|||||||
@@ -18,6 +18,27 @@
|
|||||||
pipewire = false;
|
pipewire = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"cat.gensokyo.extern.foot.badDaemon" = {
|
||||||
|
name = "bd-foot";
|
||||||
|
identity = 1;
|
||||||
|
shareUid = true;
|
||||||
|
verbose = true;
|
||||||
|
share = pkgs.foot;
|
||||||
|
packages = [ pkgs.foot ];
|
||||||
|
command = "foot";
|
||||||
|
enablements = {
|
||||||
|
dbus = false;
|
||||||
|
};
|
||||||
|
extraPaths = [
|
||||||
|
{
|
||||||
|
type = "daemon";
|
||||||
|
dst = "/proc/nonexistent";
|
||||||
|
path = "/usr/bin/env";
|
||||||
|
args = [ "false" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
extraHomeConfig.home.stateVersion = "23.05";
|
extraHomeConfig.home.stateVersion = "23.05";
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ collect_state_ui("pipewire_wayland")
|
|||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
# Test PipeWire SecurityContext:
|
# Test PipeWire SecurityContext:
|
||||||
|
machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pulse pactl info")
|
||||||
machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pulse pactl set-sink-mute @DEFAULT_SINK@ toggle")
|
machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pulse pactl set-sink-mute @DEFAULT_SINK@ toggle")
|
||||||
|
|
||||||
# Test XWayland (foot does not support X):
|
# Test XWayland (foot does not support X):
|
||||||
|
|||||||
Reference in New Issue
Block a user