From d0a3c6a2f3ac4da3921fa525cf1ee5ea7d68eb1c Mon Sep 17 00:00:00 2001 From: Ophestra Date: Mon, 15 Dec 2025 12:32:05 +0900 Subject: [PATCH] internal/outcome: optional shim private dir This is a private work directory owned by the specific shim. Useful for sockets owned by this instance of the shim and requires no direct assistance from the priv-side process. Signed-off-by: Ophestra --- internal/outcome/dispatcher.go | 6 +++ internal/outcome/dispatcher_test.go | 2 + internal/outcome/shim.go | 57 +++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/internal/outcome/dispatcher.go b/internal/outcome/dispatcher.go index a5339b2..0813f81 100644 --- a/internal/outcome/dispatcher.go +++ b/internal/outcome/dispatcher.go @@ -53,6 +53,10 @@ type syscallDispatcher interface { readdir(name string) ([]os.DirEntry, error) // tempdir provides [os.TempDir]. tempdir() string + // mkdir provides [os.Mkdir]. + mkdir(name string, perm os.FileMode) error + // removeAll provides [os.RemoveAll]. + removeAll(path string) error // exit provides [os.Exit]. exit(code int) @@ -121,6 +125,8 @@ func (direct) stat(name string) (os.FileInfo, error) { return os.Stat(name) func (direct) open(name string) (osFile, error) { return os.Open(name) } func (direct) readdir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) } func (direct) tempdir() string { return os.TempDir() } +func (direct) mkdir(name string, perm os.FileMode) error { return os.Mkdir(name, perm) } +func (direct) removeAll(path string) error { return os.RemoveAll(path) } func (direct) exit(code int) { os.Exit(code) } func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) } diff --git a/internal/outcome/dispatcher_test.go b/internal/outcome/dispatcher_test.go index 58a23e0..4cfa0fa 100644 --- a/internal/outcome/dispatcher_test.go +++ b/internal/outcome/dispatcher_test.go @@ -701,6 +701,8 @@ func (panicDispatcher) stat(string) (os.FileInfo, error) { pa func (panicDispatcher) open(string) (osFile, error) { panic("unreachable") } func (panicDispatcher) readdir(string) ([]os.DirEntry, error) { panic("unreachable") } func (panicDispatcher) tempdir() string { panic("unreachable") } +func (panicDispatcher) mkdir(string, os.FileMode) error { panic("unreachable") } +func (panicDispatcher) removeAll(string) error { panic("unreachable") } func (panicDispatcher) exit(int) { panic("unreachable") } func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") } func (panicDispatcher) prctl(uintptr, uintptr, uintptr) error { panic("unreachable") } diff --git a/internal/outcome/shim.go b/internal/outcome/shim.go index bde79be..59b1236 100644 --- a/internal/outcome/shim.go +++ b/internal/outcome/shim.go @@ -14,6 +14,7 @@ import ( "time" "hakurei.app/container" + "hakurei.app/container/check" "hakurei.app/container/seccomp" "hakurei.app/container/std" "hakurei.app/hst" @@ -83,6 +84,55 @@ func Shim(msg message.Msg) { shimEntrypoint(direct{msg}) } +// A shimPrivate holds state of the private work directory owned by shim. +type shimPrivate struct { + // Path to directory if created. + pathname *check.Absolute + + k syscallDispatcher + id *stringPair[hst.ID] +} + +// unwrap returns the underlying pathname. +func (sp *shimPrivate) unwrap() *check.Absolute { + if sp.pathname == nil { + if a, err := check.NewAbs(sp.k.tempdir()); err != nil { + sp.k.fatal(err) + panic("unreachable") + } else { + pathname := a.Append(".hakurei-shim-" + sp.id.String()) + sp.k.getMsg().Verbosef("creating private work directory %q", pathname) + if err = sp.k.mkdir(pathname.String(), 0700); err != nil { + sp.k.fatal(err) + panic("unreachable") + } + sp.pathname = pathname + return sp.unwrap() + } + } else { + return sp.pathname + } +} + +// String returns the absolute pathname to the directory held by shimPrivate. +func (sp *shimPrivate) String() string { return sp.unwrap().String() } + +// destroy removes the directory held by shimPrivate. +func (sp *shimPrivate) destroy() { + defer func() { sp.pathname = nil }() + if sp.pathname != nil { + sp.k.getMsg().Verbosef("destroying private work directory %q", sp.pathname) + if err := sp.k.removeAll(sp.pathname.String()); err != nil { + sp.k.getMsg().GetLogger().Println(err) + } + } +} + +const ( + // shimPipeWireTimeout is the duration pipewire-pulse is allowed to run before its socket becomes available. + shimPipeWireTimeout = 5 * time.Second +) + func shimEntrypoint(k syscallDispatcher) { msg := k.getMsg() if msg == nil { @@ -208,6 +258,7 @@ func shimEntrypoint(k syscallDispatcher) { ctx, stop := k.notifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) cancelContainer.Store(&stop) + sp := shimPrivate{k: k, id: state.id} z := container.New(ctx, msg) z.Params = *stateParams.params z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr @@ -225,9 +276,11 @@ func shimEntrypoint(k syscallDispatcher) { } } printMessageError(f, "cannot start container:", err) + sp.destroy() k.exit(hst.ExitFailure) } if err := k.containerServe(z); err != nil { + sp.destroy() printMessageError(func(v ...any) { k.fatal(fmt.Sprintln(v...)) }, "cannot configure container:", err) } @@ -236,10 +289,13 @@ func shimEntrypoint(k syscallDispatcher) { seccomp.Preset(std.PresetStrict, seccomp.AllowMultiarch), seccomp.AllowMultiarch, ); err != nil { + sp.destroy() k.fatalf("cannot load syscall filter: %v", err) } if err := k.containerWait(z); err != nil { + sp.destroy() + var exitError *exec.ExitError if !errors.As(err, &exitError) { if errors.Is(err, context.Canceled) { @@ -250,4 +306,5 @@ func shimEntrypoint(k syscallDispatcher) { } k.exit(exitError.ExitCode()) } + sp.destroy() }