internal/outcome: improve doc comments
All checks were successful
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 4m21s
Test / ShareFS (push) Successful in 4m49s
Test / Hakurei (race detector) (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 2m24s
Test / Flake checks (push) Successful in 1m17s
All checks were successful
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 4m21s
Test / ShareFS (push) Successful in 4m49s
Test / Hakurei (race detector) (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 2m24s
Test / Flake checks (push) Successful in 1m17s
This improves readability on smaller displays. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -38,6 +38,7 @@ func (h *Hsu) ensureDispatcher() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ID returns the current user hsurc identifier.
|
// ID returns the current user hsurc identifier.
|
||||||
|
//
|
||||||
// [ErrHsuAccess] is returned if the current user is not in hsurc.
|
// [ErrHsuAccess] is returned if the current user is not in hsurc.
|
||||||
func (h *Hsu) ID() (int, error) {
|
func (h *Hsu) ID() (int, error) {
|
||||||
h.ensureDispatcher()
|
h.ensureDispatcher()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Package outcome implements the outcome of the privileged and container sides of a hakurei container.
|
// Package outcome implements the outcome of the privileged and container sides
|
||||||
|
// of a hakurei container.
|
||||||
package outcome
|
package outcome
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -27,8 +28,9 @@ func Info() *hst.Info {
|
|||||||
return &hi
|
return &hi
|
||||||
}
|
}
|
||||||
|
|
||||||
// envAllocSize is the initial size of the env map pre-allocated when the configured env map is nil.
|
// envAllocSize is the initial size of the env map pre-allocated when the
|
||||||
// It should be large enough to fit all insertions by outcomeOp.toContainer.
|
// configured env map is nil. It should be large enough to fit all insertions by
|
||||||
|
// outcomeOp.toContainer.
|
||||||
const envAllocSize = 1 << 6
|
const envAllocSize = 1 << 6
|
||||||
|
|
||||||
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
||||||
@@ -43,7 +45,8 @@ func (s *stringPair[T]) unwrap() T { return s.v }
|
|||||||
func (s *stringPair[T]) String() string { return s.s }
|
func (s *stringPair[T]) String() string { return s.s }
|
||||||
|
|
||||||
// outcomeState is copied to the shim process and available while applying outcomeOp.
|
// outcomeState is copied to the shim process and available while applying outcomeOp.
|
||||||
// This is transmitted from the priv side to the shim, so exported fields should be kept to a minimum.
|
// This is transmitted from the priv side to the shim, so exported fields should
|
||||||
|
// be kept to a minimum.
|
||||||
type outcomeState struct {
|
type outcomeState struct {
|
||||||
// Params only used by the shim process. Populated by populateEarly.
|
// Params only used by the shim process. Populated by populateEarly.
|
||||||
Shim *shimParams
|
Shim *shimParams
|
||||||
@@ -89,10 +92,15 @@ func (s *outcomeState) valid() bool {
|
|||||||
s.Paths != nil
|
s.Paths != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newOutcomeState returns the address of a new outcomeState with its exported fields populated via syscallDispatcher.
|
// newOutcomeState returns the address of a new outcomeState with its exported
|
||||||
|
// fields populated via syscallDispatcher.
|
||||||
func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *hst.Config, hsu *Hsu) *outcomeState {
|
func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *hst.Config, hsu *Hsu) *outcomeState {
|
||||||
s := outcomeState{
|
s := outcomeState{
|
||||||
Shim: &shimParams{PrivPID: k.getpid(), Verbose: msg.IsVerbose()},
|
Shim: &shimParams{
|
||||||
|
PrivPID: k.getpid(),
|
||||||
|
Verbose: msg.IsVerbose(),
|
||||||
|
},
|
||||||
|
|
||||||
ID: id,
|
ID: id,
|
||||||
Identity: config.Identity,
|
Identity: config.Identity,
|
||||||
UserID: hsu.MustID(msg),
|
UserID: hsu.MustID(msg),
|
||||||
@@ -121,6 +129,7 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// populateLocal populates unexported fields from transmitted exported fields.
|
// populateLocal populates unexported fields from transmitted exported fields.
|
||||||
|
//
|
||||||
// These fields are cheaper to recompute per-process.
|
// These fields are cheaper to recompute per-process.
|
||||||
func (s *outcomeState) populateLocal(k syscallDispatcher, msg message.Msg) error {
|
func (s *outcomeState) populateLocal(k syscallDispatcher, msg message.Msg) error {
|
||||||
if !s.valid() || k == nil || msg == nil {
|
if !s.valid() || k == nil || msg == nil {
|
||||||
@@ -136,7 +145,10 @@ func (s *outcomeState) populateLocal(k syscallDispatcher, msg message.Msg) error
|
|||||||
s.id = &stringPair[hst.ID]{*s.ID, s.ID.String()}
|
s.id = &stringPair[hst.ID]{*s.ID, s.ID.String()}
|
||||||
|
|
||||||
s.Copy(&s.sc, s.UserID)
|
s.Copy(&s.sc, s.UserID)
|
||||||
msg.Verbosef("process share directory at %q, runtime directory at %q", s.sc.SharePath, s.sc.RunDirPath)
|
msg.Verbosef(
|
||||||
|
"process share directory at %q, runtime directory at %q",
|
||||||
|
s.sc.SharePath, s.sc.RunDirPath,
|
||||||
|
)
|
||||||
|
|
||||||
s.identity = newInt(s.Identity)
|
s.identity = newInt(s.Identity)
|
||||||
s.mapuid, s.mapgid = newInt(s.Mapuid), newInt(s.Mapgid)
|
s.mapuid, s.mapgid = newInt(s.Mapuid), newInt(s.Mapgid)
|
||||||
@@ -146,17 +158,25 @@ func (s *outcomeState) populateLocal(k syscallDispatcher, msg message.Msg) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// instancePath returns a path formatted for outcomeStateSys.instance.
|
// instancePath returns a path formatted for outcomeStateSys.instance.
|
||||||
|
//
|
||||||
// This method must only be called from outcomeOp.toContainer if
|
// This method must only be called from outcomeOp.toContainer if
|
||||||
// outcomeOp.toSystem has already called outcomeStateSys.instance.
|
// outcomeOp.toSystem has already called outcomeStateSys.instance.
|
||||||
func (s *outcomeState) instancePath() *check.Absolute { return s.sc.SharePath.Append(s.id.String()) }
|
func (s *outcomeState) instancePath() *check.Absolute {
|
||||||
|
return s.sc.SharePath.Append(s.id.String())
|
||||||
|
}
|
||||||
|
|
||||||
// runtimePath returns a path formatted for outcomeStateSys.runtime.
|
// runtimePath returns a path formatted for outcomeStateSys.runtime.
|
||||||
|
//
|
||||||
// This method must only be called from outcomeOp.toContainer if
|
// This method must only be called from outcomeOp.toContainer if
|
||||||
// outcomeOp.toSystem has already called outcomeStateSys.runtime.
|
// outcomeOp.toSystem has already called outcomeStateSys.runtime.
|
||||||
func (s *outcomeState) runtimePath() *check.Absolute { return s.sc.RunDirPath.Append(s.id.String()) }
|
func (s *outcomeState) runtimePath() *check.Absolute {
|
||||||
|
return s.sc.RunDirPath.Append(s.id.String())
|
||||||
|
}
|
||||||
|
|
||||||
// outcomeStateSys wraps outcomeState and [system.I]. Used on the priv side only.
|
// outcomeStateSys wraps outcomeState and [system.I]. Used on the priv side only.
|
||||||
// Implementations of outcomeOp must not access fields other than sys unless explicitly stated.
|
//
|
||||||
|
// Implementations of outcomeOp must not access fields other than sys unless
|
||||||
|
// explicitly stated.
|
||||||
type outcomeStateSys struct {
|
type outcomeStateSys struct {
|
||||||
// Whether XDG_RUNTIME_DIR is used post hsu.
|
// Whether XDG_RUNTIME_DIR is used post hsu.
|
||||||
useRuntimeDir bool
|
useRuntimeDir bool
|
||||||
@@ -219,6 +239,7 @@ func (state *outcomeStateSys) ensureRuntimeDir() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// instance returns the pathname to a process-specific directory within TMPDIR.
|
// instance returns the pathname to a process-specific directory within TMPDIR.
|
||||||
|
//
|
||||||
// This directory must only hold entries bound to [system.Process].
|
// This directory must only hold entries bound to [system.Process].
|
||||||
func (state *outcomeStateSys) instance() *check.Absolute {
|
func (state *outcomeStateSys) instance() *check.Absolute {
|
||||||
if state.sharePath != nil {
|
if state.sharePath != nil {
|
||||||
@@ -230,6 +251,7 @@ func (state *outcomeStateSys) instance() *check.Absolute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runtime returns the pathname to a process-specific directory within XDG_RUNTIME_DIR.
|
// runtime returns the pathname to a process-specific directory within XDG_RUNTIME_DIR.
|
||||||
|
//
|
||||||
// This directory must only hold entries bound to [system.Process].
|
// This directory must only hold entries bound to [system.Process].
|
||||||
func (state *outcomeStateSys) runtime() *check.Absolute {
|
func (state *outcomeStateSys) runtime() *check.Absolute {
|
||||||
if state.runtimeSharePath != nil {
|
if state.runtimeSharePath != nil {
|
||||||
@@ -242,22 +264,29 @@ func (state *outcomeStateSys) runtime() *check.Absolute {
|
|||||||
return state.runtimeSharePath
|
return state.runtimeSharePath
|
||||||
}
|
}
|
||||||
|
|
||||||
// outcomeStateParams wraps outcomeState and [container.Params]. Used on the shim side only.
|
// outcomeStateParams wraps outcomeState and [container.Params].
|
||||||
|
//
|
||||||
|
// Used on the shim side only.
|
||||||
type outcomeStateParams struct {
|
type outcomeStateParams struct {
|
||||||
// Overrides the embedded [container.Params] in [container.Container]. The Env field must not be used.
|
// Overrides the embedded [container.Params] in [container.Container].
|
||||||
|
//
|
||||||
|
// The Env field must not be used.
|
||||||
params *container.Params
|
params *container.Params
|
||||||
// Collapsed into the Env slice in [container.Params] by the final outcomeOp.
|
// Collapsed into the Env slice in [container.Params] by the final outcomeOp.
|
||||||
env map[string]string
|
env map[string]string
|
||||||
|
|
||||||
// Filesystems with the optional root sliced off if present. Populated by spParamsOp.
|
// Filesystems with the optional root sliced off if present.
|
||||||
// Safe for use by spFilesystemOp.
|
//
|
||||||
|
// Populated by spParamsOp. Safe for use by spFilesystemOp.
|
||||||
filesystem []hst.FilesystemConfigJSON
|
filesystem []hst.FilesystemConfigJSON
|
||||||
|
|
||||||
// Inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` via mapped uid.
|
// Inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` via mapped uid.
|
||||||
|
//
|
||||||
// Populated by spRuntimeOp.
|
// Populated by spRuntimeOp.
|
||||||
runtimeDir *check.Absolute
|
runtimeDir *check.Absolute
|
||||||
|
|
||||||
// Path to pipewire-pulse server.
|
// Path to pipewire-pulse server.
|
||||||
|
//
|
||||||
// Populated by spPipeWireOp if DirectPipeWire is false.
|
// Populated by spPipeWireOp if DirectPipeWire is false.
|
||||||
pipewirePulsePath *check.Absolute
|
pipewirePulsePath *check.Absolute
|
||||||
|
|
||||||
@@ -265,25 +294,32 @@ type outcomeStateParams struct {
|
|||||||
*outcomeState
|
*outcomeState
|
||||||
}
|
}
|
||||||
|
|
||||||
// errNotEnabled is returned by outcomeOp.toSystem and used internally to exclude an outcomeOp from transmission.
|
// errNotEnabled is returned by outcomeOp.toSystem and used internally to
|
||||||
|
// exclude an outcomeOp from transmission.
|
||||||
var errNotEnabled = errors.New("op not enabled in the configuration")
|
var errNotEnabled = errors.New("op not enabled in the configuration")
|
||||||
|
|
||||||
// An outcomeOp inflicts an outcome on [system.I] and contains enough information to
|
// An outcomeOp inflicts an outcome on [system.I] and contains enough
|
||||||
// inflict it on [container.Params] in a separate process.
|
// information to inflict it on [container.Params] in a separate process.
|
||||||
// An implementation of outcomeOp must store cross-process states in exported fields only.
|
//
|
||||||
|
// An implementation of outcomeOp must store cross-process states in exported
|
||||||
|
// fields only.
|
||||||
type outcomeOp interface {
|
type outcomeOp interface {
|
||||||
// toSystem inflicts the current outcome on [system.I] in the priv side process.
|
// toSystem inflicts the current outcome on [system.I] in the priv side process.
|
||||||
toSystem(state *outcomeStateSys) error
|
toSystem(state *outcomeStateSys) error
|
||||||
|
|
||||||
// toContainer inflicts the current outcome on [container.Params] in the shim process.
|
// toContainer inflicts the current outcome on [container.Params] in the
|
||||||
// The implementation must not write to the Env field of [container.Params] as it will be overwritten
|
// shim process.
|
||||||
// by flattened env map.
|
//
|
||||||
|
// Implementations must not write to the Env field of [container.Params]
|
||||||
|
// as it will be overwritten by flattened env map.
|
||||||
toContainer(state *outcomeStateParams) error
|
toContainer(state *outcomeStateParams) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// toSystem calls the outcomeOp.toSystem method on all outcomeOp implementations and populates shimParams.Ops.
|
// toSystem calls the outcomeOp.toSystem method on all outcomeOp implementations
|
||||||
// This function assumes the caller has already called the Validate method on [hst.Config]
|
// and populates shimParams.Ops.
|
||||||
// and checked that it returns nil.
|
//
|
||||||
|
// This function assumes the caller has already called the Validate method on
|
||||||
|
// [hst.Config] and checked that it returns nil.
|
||||||
func (state *outcomeStateSys) toSystem() error {
|
func (state *outcomeStateSys) toSystem() error {
|
||||||
if state.Shim == nil || state.Shim.Ops != nil {
|
if state.Shim == nil || state.Shim.Ops != nil {
|
||||||
return newWithMessage("invalid ops state reached")
|
return newWithMessage("invalid ops state reached")
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewStore returns the address of a new instance of [store.Store].
|
// NewStore returns the address of a new instance of [store.Store].
|
||||||
func NewStore(sc *hst.Paths) *store.Store { return store.New(sc.SharePath.Append("state")) }
|
func NewStore(sc *hst.Paths) *store.Store {
|
||||||
|
return store.New(sc.SharePath.Append("state"))
|
||||||
|
}
|
||||||
|
|
||||||
// main carries out outcome and terminates. main does not return.
|
// main carries out outcome and terminates. main does not return.
|
||||||
func (k *outcome) main(msg message.Msg, identifierFd int) {
|
func (k *outcome) main(msg message.Msg, identifierFd int) {
|
||||||
@@ -116,7 +118,11 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
|||||||
processStatePrev, processStateCur = processStateCur, processState
|
processStatePrev, processStateCur = processStateCur, processState
|
||||||
|
|
||||||
if !processTime.IsZero() && processStatePrev != processLifecycle {
|
if !processTime.IsZero() && processStatePrev != processLifecycle {
|
||||||
msg.Verbosef("state %d took %.2f ms", processStatePrev, float64(time.Since(processTime).Nanoseconds())/1e6)
|
msg.Verbosef(
|
||||||
|
"state %d took %.2f ms",
|
||||||
|
processStatePrev,
|
||||||
|
float64(time.Since(processTime).Nanoseconds())/1e6,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
processTime = time.Now()
|
processTime = time.Now()
|
||||||
|
|
||||||
@@ -141,7 +147,10 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
|||||||
|
|
||||||
case processCommit:
|
case processCommit:
|
||||||
if isBeforeRevert {
|
if isBeforeRevert {
|
||||||
perrorFatal(newWithMessage("invalid transition to commit state"), "commit", processLifecycle)
|
perrorFatal(
|
||||||
|
newWithMessage("invalid transition to commit state"),
|
||||||
|
"commit", processLifecycle,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,15 +247,26 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
|||||||
|
|
||||||
case <-func() chan struct{} {
|
case <-func() chan struct{} {
|
||||||
w := make(chan struct{})
|
w := make(chan struct{})
|
||||||
// this ties processLifecycle to ctx with the additional compensated timeout duration
|
// This ties processLifecycle to ctx with the additional
|
||||||
// to allow transition to the next state on a locked up shim
|
// compensated timeout duration to allow transition to the next
|
||||||
go func() { <-ctx.Done(); time.Sleep(k.state.Shim.WaitDelay + shimWaitTimeout); close(w) }()
|
// state on a locked up shim.
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
time.Sleep(k.state.Shim.WaitDelay + shimWaitTimeout)
|
||||||
|
close(w)
|
||||||
|
}()
|
||||||
return w
|
return w
|
||||||
}():
|
}():
|
||||||
// this is only reachable when wait did not return within shimWaitTimeout, after its WaitDelay has elapsed.
|
// This is only reachable when wait did not return within
|
||||||
// This is different from the container failing to terminate within its timeout period, as that is enforced
|
// shimWaitTimeout, after its WaitDelay has elapsed. This is
|
||||||
// by the shim. This path is instead reached when there is a lockup in shim preventing it from completing.
|
// different from the container failing to terminate within its
|
||||||
msg.GetLogger().Printf("process %d did not terminate", shimCmd.Process.Pid)
|
// timeout period, as that is enforced by the shim. This path is
|
||||||
|
// instead reached when there is a lockup in shim preventing it
|
||||||
|
// from completing.
|
||||||
|
msg.GetLogger().Printf(
|
||||||
|
"process %d did not terminate",
|
||||||
|
shimCmd.Process.Pid,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
msg.Resume()
|
msg.Resume()
|
||||||
|
|
||||||
@@ -271,8 +291,8 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
|||||||
ec := system.Process
|
ec := system.Process
|
||||||
|
|
||||||
if entries, _, err := handle.Entries(); err != nil {
|
if entries, _, err := handle.Entries(); err != nil {
|
||||||
// it is impossible to continue from this point,
|
// it is impossible to continue from this point, per-process
|
||||||
// per-process state will be reverted to limit damage
|
// state will be reverted to limit damage
|
||||||
perror(err, "read store segment entries")
|
perror(err, "read store segment entries")
|
||||||
} else {
|
} else {
|
||||||
// accumulate enablements of remaining instances
|
// accumulate enablements of remaining instances
|
||||||
@@ -295,7 +315,10 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
|||||||
if n == 0 {
|
if n == 0 {
|
||||||
ec |= system.User
|
ec |= system.User
|
||||||
} else {
|
} else {
|
||||||
msg.Verbosef("found %d instances, cleaning up without user-scoped operations", n)
|
msg.Verbosef(
|
||||||
|
"found %d instances, cleaning up without user-scoped operations",
|
||||||
|
n,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ec |= rt ^ (hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse)
|
ec |= rt ^ (hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse)
|
||||||
if msg.IsVerbose() {
|
if msg.IsVerbose() {
|
||||||
@@ -335,7 +358,9 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
|||||||
|
|
||||||
// start starts the shim via cmd/hsu.
|
// start starts the shim via cmd/hsu.
|
||||||
//
|
//
|
||||||
// If successful, a [time.Time] value for [hst.State] is stored in the value pointed to by startTime.
|
// If successful, a [time.Time] value for [hst.State] is stored in the value
|
||||||
|
// pointed to by startTime.
|
||||||
|
//
|
||||||
// The resulting [exec.Cmd] and write end of the shim setup pipe is returned.
|
// The resulting [exec.Cmd] and write end of the shim setup pipe is returned.
|
||||||
func (k *outcome) start(ctx context.Context, msg message.Msg,
|
func (k *outcome) start(ctx context.Context, msg message.Msg,
|
||||||
hsuPath *check.Absolute,
|
hsuPath *check.Absolute,
|
||||||
|
|||||||
@@ -37,9 +37,12 @@ const (
|
|||||||
shimMsgBadPID = C.HAKUREI_SHIM_BAD_PID
|
shimMsgBadPID = C.HAKUREI_SHIM_BAD_PID
|
||||||
)
|
)
|
||||||
|
|
||||||
// setupContSignal sets up the SIGCONT signal handler for the cross-uid shim exit hack.
|
// setupContSignal sets up the SIGCONT signal handler for the cross-uid shim
|
||||||
// The signal handler is implemented in C, signals can be processed by reading from the returned reader.
|
// exit hack.
|
||||||
// The returned function must be called after all signal processing concludes.
|
//
|
||||||
|
// The signal handler is implemented in C, signals can be processed by reading
|
||||||
|
// from the returned reader. The returned function must be called after all
|
||||||
|
// signal processing concludes.
|
||||||
func setupContSignal(pid int) (io.ReadCloser, func(), error) {
|
func setupContSignal(pid int) (io.ReadCloser, func(), error) {
|
||||||
if r, w, err := os.Pipe(); err != nil {
|
if r, w, err := os.Pipe(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -51,22 +54,28 @@ func setupContSignal(pid int) (io.ReadCloser, func(), error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// shimEnv is the name of the environment variable storing decimal representation of
|
// shimEnv is the name of the environment variable storing decimal representation
|
||||||
// setup pipe fd for [container.Receive].
|
// of setup pipe fd for [container.Receive].
|
||||||
const shimEnv = "HAKUREI_SHIM"
|
const shimEnv = "HAKUREI_SHIM"
|
||||||
|
|
||||||
// shimParams is embedded in outcomeState and transmitted from priv side to shim.
|
// shimParams is embedded in outcomeState and transmitted from priv side to shim.
|
||||||
type shimParams struct {
|
type shimParams struct {
|
||||||
// Priv side pid, checked against ppid in signal handler for the syscall.SIGCONT hack.
|
// Priv side pid, checked against ppid in signal handler for the
|
||||||
|
// syscall.SIGCONT hack.
|
||||||
PrivPID int
|
PrivPID int
|
||||||
|
|
||||||
// Duration to wait for after the initial process receives os.Interrupt before the container is killed.
|
// Duration to wait for after the initial process receives os.Interrupt
|
||||||
|
// before the container is killed.
|
||||||
|
//
|
||||||
// Limits are enforced on the priv side.
|
// Limits are enforced on the priv side.
|
||||||
WaitDelay time.Duration
|
WaitDelay time.Duration
|
||||||
|
|
||||||
// Verbosity pass through from [message.Msg].
|
// Verbosity pass through from [message.Msg].
|
||||||
Verbose bool
|
Verbose bool
|
||||||
|
|
||||||
|
// Copied from [hst.Config].
|
||||||
|
SchedPolicy std.SchedPolicy
|
||||||
|
|
||||||
// Outcome setup ops, contains setup state. Populated by outcome.finalise.
|
// Outcome setup ops, contains setup state. Populated by outcome.finalise.
|
||||||
Ops []outcomeOp
|
Ops []outcomeOp
|
||||||
}
|
}
|
||||||
@@ -77,7 +86,9 @@ func (p *shimParams) valid() bool { return p != nil && p.PrivPID > 0 }
|
|||||||
// shimName is the prefix used by log.std in the shim process.
|
// shimName is the prefix used by log.std in the shim process.
|
||||||
const shimName = "shim"
|
const shimName = "shim"
|
||||||
|
|
||||||
// Shim is called by the main function of the shim process and runs as the unconstrained target user.
|
// Shim is called by the main function of the shim process and runs as the
|
||||||
|
// unconstrained target user.
|
||||||
|
//
|
||||||
// Shim does not return.
|
// Shim does not return.
|
||||||
func Shim(msg message.Msg) {
|
func Shim(msg message.Msg) {
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
@@ -131,7 +142,8 @@ func (sp *shimPrivate) destroy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// shimPipeWireTimeout is the duration pipewire-pulse is allowed to run before its socket becomes available.
|
// shimPipeWireTimeout is the duration pipewire-pulse is allowed to run
|
||||||
|
// before its socket becomes available.
|
||||||
shimPipeWireTimeout = 5 * time.Second
|
shimPipeWireTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ const varRunNscd = fhs.Var + "run/nscd"
|
|||||||
|
|
||||||
func init() { gob.Register(new(spParamsOp)) }
|
func init() { gob.Register(new(spParamsOp)) }
|
||||||
|
|
||||||
// spParamsOp initialises unordered fields of [container.Params] and the optional root filesystem.
|
// spParamsOp initialises unordered fields of [container.Params] and the
|
||||||
|
// optional root filesystem.
|
||||||
|
//
|
||||||
// This outcomeOp is hardcoded to always run first.
|
// This outcomeOp is hardcoded to always run first.
|
||||||
type spParamsOp struct {
|
type spParamsOp struct {
|
||||||
// Value of $TERM, stored during toSystem.
|
// Value of $TERM, stored during toSystem.
|
||||||
@@ -67,8 +69,8 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
|||||||
state.params.Args = state.Container.Args
|
state.params.Args = state.Container.Args
|
||||||
}
|
}
|
||||||
|
|
||||||
// the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
|
// The container is cancelled when shim is requested to exit or receives an
|
||||||
// this behaviour is implemented in the shim
|
// interrupt or termination signal. This behaviour is implemented in the shim.
|
||||||
state.params.ForwardCancel = state.Shim.WaitDelay > 0
|
state.params.ForwardCancel = state.Shim.WaitDelay > 0
|
||||||
|
|
||||||
if state.Container.Flags&hst.FMultiarch != 0 {
|
if state.Container.Flags&hst.FMultiarch != 0 {
|
||||||
@@ -115,7 +117,8 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
|||||||
} else {
|
} else {
|
||||||
state.params.Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice)
|
state.params.Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice)
|
||||||
}
|
}
|
||||||
// /dev is mounted readonly later on, this prevents /dev/shm from going readonly with it
|
// /dev is mounted readonly later on, this prevents /dev/shm from going
|
||||||
|
// readonly with it
|
||||||
state.params.Tmpfs(fhs.AbsDevShm, 0, 01777)
|
state.params.Tmpfs(fhs.AbsDevShm, 0, 01777)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -123,7 +126,9 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
|||||||
|
|
||||||
func init() { gob.Register(new(spFilesystemOp)) }
|
func init() { gob.Register(new(spFilesystemOp)) }
|
||||||
|
|
||||||
// spFilesystemOp applies configured filesystems to [container.Params], excluding the optional root filesystem.
|
// spFilesystemOp applies configured filesystems to [container.Params],
|
||||||
|
// excluding the optional root filesystem.
|
||||||
|
//
|
||||||
// This outcomeOp is hardcoded to always run last.
|
// This outcomeOp is hardcoded to always run last.
|
||||||
type spFilesystemOp struct {
|
type spFilesystemOp struct {
|
||||||
// Matched paths to cover. Stored during toSystem.
|
// Matched paths to cover. Stored during toSystem.
|
||||||
@@ -297,8 +302,8 @@ func (s *spFilesystemOp) toContainer(state *outcomeStateParams) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveRoot handles the root filesystem special case for [hst.FilesystemConfig] and additionally resolves autoroot
|
// resolveRoot handles the root filesystem special case for [hst.FilesystemConfig]
|
||||||
// as it requires special handling during path hiding.
|
// and additionally resolves autoroot as it requires special handling during path hiding.
|
||||||
func resolveRoot(c *hst.ContainerConfig) (rootfs hst.FilesystemConfig, filesystem []hst.FilesystemConfigJSON, autoroot *hst.FSBind) {
|
func resolveRoot(c *hst.ContainerConfig) (rootfs hst.FilesystemConfig, filesystem []hst.FilesystemConfigJSON, autoroot *hst.FSBind) {
|
||||||
// root filesystem special case
|
// root filesystem special case
|
||||||
filesystem = c.Filesystem
|
filesystem = c.Filesystem
|
||||||
@@ -316,7 +321,8 @@ func resolveRoot(c *hst.ContainerConfig) (rootfs hst.FilesystemConfig, filesyste
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
|
// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors
|
||||||
|
// unwrapping to [fs.ErrNotExist].
|
||||||
func evalSymlinks(msg message.Msg, k syscallDispatcher, v *string) error {
|
func evalSymlinks(msg message.Msg, k syscallDispatcher, v *string) error {
|
||||||
if p, err := k.evalSymlinks(*v); err != nil {
|
if p, err := k.evalSymlinks(*v); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
func init() { gob.Register(new(spDBusOp)) }
|
func init() { gob.Register(new(spDBusOp)) }
|
||||||
|
|
||||||
// spDBusOp maintains an xdg-dbus-proxy instance for the container.
|
// spDBusOp maintains an xdg-dbus-proxy instance for the container.
|
||||||
|
//
|
||||||
// Runs after spRuntimeOp.
|
// Runs after spRuntimeOp.
|
||||||
type spDBusOp struct {
|
type spDBusOp struct {
|
||||||
// Whether to bind the system bus socket. Populated during toSystem.
|
// Whether to bind the system bus socket. Populated during toSystem.
|
||||||
|
|||||||
@@ -13,9 +13,12 @@ const pipewirePulseName = "pipewire-pulse"
|
|||||||
func init() { gob.Register(new(spPipeWireOp)) }
|
func init() { gob.Register(new(spPipeWireOp)) }
|
||||||
|
|
||||||
// spPipeWireOp exports the PipeWire server to the container via SecurityContext.
|
// spPipeWireOp exports the PipeWire server to the container via SecurityContext.
|
||||||
|
//
|
||||||
// Runs after spRuntimeOp.
|
// Runs after spRuntimeOp.
|
||||||
type spPipeWireOp struct {
|
type spPipeWireOp struct {
|
||||||
// Path to pipewire-pulse server. Populated during toSystem if DirectPipeWire is false.
|
// Path to pipewire-pulse server.
|
||||||
|
//
|
||||||
|
// Populated during toSystem if DirectPipeWire is false.
|
||||||
CompatServerPath *check.Absolute
|
CompatServerPath *check.Absolute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const pulseCookieSizeMax = 1 << 8
|
|||||||
func init() { gob.Register(new(spPulseOp)) }
|
func init() { gob.Register(new(spPulseOp)) }
|
||||||
|
|
||||||
// spPulseOp exports the PulseAudio server to the container.
|
// spPulseOp exports the PulseAudio server to the container.
|
||||||
|
//
|
||||||
// Runs after spRuntimeOp.
|
// Runs after spRuntimeOp.
|
||||||
type spPulseOp struct {
|
type spPulseOp struct {
|
||||||
// PulseAudio cookie data, populated during toSystem if a cookie is present.
|
// PulseAudio cookie data, populated during toSystem if a cookie is present.
|
||||||
@@ -37,24 +38,40 @@ func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
|
|||||||
|
|
||||||
if _, err := state.k.stat(pulseRuntimeDir.String()); err != nil {
|
if _, err := state.k.stat(pulseRuntimeDir.String()); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
|
return &hst.AppError{Step: fmt.Sprintf(
|
||||||
|
"access PulseAudio directory %q",
|
||||||
|
pulseRuntimeDir,
|
||||||
|
), Err: err}
|
||||||
}
|
}
|
||||||
return newWithMessageError(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir), err)
|
return newWithMessageError(fmt.Sprintf(
|
||||||
|
"PulseAudio directory %q not found",
|
||||||
|
pulseRuntimeDir,
|
||||||
|
), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi, err := state.k.stat(pulseSocket.String()); err != nil {
|
if fi, err := state.k.stat(pulseSocket.String()); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
|
return &hst.AppError{Step: fmt.Sprintf(
|
||||||
|
"access PulseAudio socket %q",
|
||||||
|
pulseSocket,
|
||||||
|
), Err: err}
|
||||||
}
|
}
|
||||||
return newWithMessageError(fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir), err)
|
return newWithMessageError(fmt.Sprintf(
|
||||||
|
"PulseAudio directory %q found but socket does not exist",
|
||||||
|
pulseRuntimeDir,
|
||||||
|
), err)
|
||||||
} else {
|
} else {
|
||||||
if m := fi.Mode(); m&0o006 != 0o006 {
|
if m := fi.Mode(); m&0o006 != 0o006 {
|
||||||
return newWithMessage(fmt.Sprintf("unexpected permissions on %q: %s", pulseSocket, m))
|
return newWithMessage(fmt.Sprintf(
|
||||||
|
"unexpected permissions on %q: %s",
|
||||||
|
pulseSocket, m,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pulse socket is world writable and its parent directory DAC permissions prevents access;
|
// PulseAudio socket is world writable and its parent directory DAC
|
||||||
// hard link to target-executable share directory to grant access
|
// permissions prevents access. Hard link to target-executable share
|
||||||
|
// directory to grant access
|
||||||
state.sys.Link(pulseSocket, state.runtime().Append("pulse"))
|
state.sys.Link(pulseSocket, state.runtime().Append("pulse"))
|
||||||
|
|
||||||
// load up to pulseCookieSizeMax bytes of pulse cookie for transmission to shim
|
// load up to pulseCookieSizeMax bytes of pulse cookie for transmission to shim
|
||||||
@@ -62,7 +79,13 @@ func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
|
|||||||
return err
|
return err
|
||||||
} else if a != nil {
|
} else if a != nil {
|
||||||
s.Cookie = new([pulseCookieSizeMax]byte)
|
s.Cookie = new([pulseCookieSizeMax]byte)
|
||||||
if s.CookieSize, err = loadFile(state.msg, state.k, "PulseAudio cookie", a.String(), s.Cookie[:]); err != nil {
|
if s.CookieSize, err = loadFile(
|
||||||
|
state.msg,
|
||||||
|
state.k,
|
||||||
|
"PulseAudio cookie",
|
||||||
|
a.String(),
|
||||||
|
s.Cookie[:],
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -101,8 +124,9 @@ func (s *spPulseOp) commonPaths(state *outcomeState) (pulseRuntimeDir, pulseSock
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// discoverPulseCookie attempts to discover the pathname of the PulseAudio cookie of the current user.
|
// discoverPulseCookie attempts to discover the pathname of the PulseAudio
|
||||||
// If both returned pathname and error are nil, the cookie is likely unavailable and can be silently skipped.
|
// cookie of the current user. If both returned pathname and error are nil, the
|
||||||
|
// cookie is likely unavailable and can be silently skipped.
|
||||||
func discoverPulseCookie(k syscallDispatcher) (*check.Absolute, error) {
|
func discoverPulseCookie(k syscallDispatcher) (*check.Absolute, error) {
|
||||||
const paLocateStep = "locate PulseAudio cookie"
|
const paLocateStep = "locate PulseAudio cookie"
|
||||||
|
|
||||||
@@ -186,7 +210,10 @@ func loadFile(
|
|||||||
&os.PathError{Op: "stat", Path: pathname, Err: syscall.ENOMEM},
|
&os.PathError{Op: "stat", Path: pathname, Err: syscall.ENOMEM},
|
||||||
)
|
)
|
||||||
} else if s < int64(n) {
|
} else if s < int64(n) {
|
||||||
msg.Verbosef("%s at %q is %d bytes shorter than expected", description, pathname, int64(n)-s)
|
msg.Verbosef(
|
||||||
|
"%s at %q is %d bytes shorter than expected",
|
||||||
|
description, pathname, int64(n)-s,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
msg.Verbosef("loading %d bytes from %q", n, pathname)
|
msg.Verbosef("loading %d bytes from %q", n, pathname)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ const (
|
|||||||
|
|
||||||
// spRuntimeOp sets up XDG_RUNTIME_DIR inside the container.
|
// spRuntimeOp sets up XDG_RUNTIME_DIR inside the container.
|
||||||
type spRuntimeOp struct {
|
type spRuntimeOp struct {
|
||||||
// SessionType determines the value of envXDGSessionType. Populated during toSystem.
|
// SessionType determines the value of envXDGSessionType.
|
||||||
|
//
|
||||||
|
// Populated during toSystem.
|
||||||
SessionType uintptr
|
SessionType uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,12 @@ import (
|
|||||||
func init() { gob.Register(new(spWaylandOp)) }
|
func init() { gob.Register(new(spWaylandOp)) }
|
||||||
|
|
||||||
// spWaylandOp exports the Wayland display server to the container.
|
// spWaylandOp exports the Wayland display server to the container.
|
||||||
|
//
|
||||||
// Runs after spRuntimeOp.
|
// Runs after spRuntimeOp.
|
||||||
type spWaylandOp struct {
|
type spWaylandOp struct {
|
||||||
// Path to host wayland socket. Populated during toSystem if DirectWayland is true.
|
// Path to host wayland socket.
|
||||||
|
//
|
||||||
|
// Populated during toSystem if DirectWayland is true.
|
||||||
SocketPath *check.Absolute
|
SocketPath *check.Absolute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ func (s *spX11Op) toSystem(state *outcomeStateSys) error {
|
|||||||
if socketPath != nil {
|
if socketPath != nil {
|
||||||
if _, err := state.k.stat(socketPath.String()); err != nil {
|
if _, err := state.k.stat(socketPath.String()); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
|
return &hst.AppError{Step: fmt.Sprintf(
|
||||||
|
"access X11 socket %q",
|
||||||
|
socketPath,
|
||||||
|
), Err: err}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.sys.UpdatePermType(hst.EX11, socketPath, acl.Read, acl.Write, acl.Execute)
|
state.sys.UpdatePermType(hst.EX11, socketPath, acl.Read, acl.Write, acl.Execute)
|
||||||
|
|||||||
Reference in New Issue
Block a user