diff options
Diffstat (limited to 'internal/app/outcome.go')
| -rw-r--r-- | internal/app/outcome.go | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/internal/app/outcome.go b/internal/app/outcome.go new file mode 100644 index 00000000..f850d773 --- /dev/null +++ b/internal/app/outcome.go @@ -0,0 +1,191 @@ +package app + +import ( + "strconv" + + "hakurei.app/container" + "hakurei.app/hst" + "hakurei.app/internal/app/state" + "hakurei.app/system" + "hakurei.app/system/acl" +) + +func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} } + +// stringPair stores a value and its string representation. +type stringPair[T comparable] struct { + v T + s string +} + +func (s *stringPair[T]) unwrap() T { return s.v } +func (s *stringPair[T]) String() string { return s.s } + +// 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. +type outcomeState struct { + // Generated and accounted for by the caller. + ID *state.ID + // Copied from ID. + id *stringPair[state.ID] + + // Copied from the [hst.Config] field of the same name. + Identity int + // Copied from Identity. + identity *stringPair[int] + // Returned by [Hsu.MustIDMsg]. + UserID int + // Target init namespace uid resolved from UserID and identity. + uid *stringPair[int] + + // Included as part of [hst.Config], transmitted as-is unless permissive defaults. + Container *hst.ContainerConfig + + // Mapped credentials within container user namespace. + Mapuid, Mapgid int + // Copied from their respective exported values. + mapuid, mapgid *stringPair[int] + + // Copied from [EnvPaths] per-process. + sc hst.Paths + *EnvPaths + + // Matched paths to cover. Populated by spFilesystemOp. + HidePaths []*container.Absolute + + // Copied via populateLocal. + k syscallDispatcher + // Copied via populateLocal. + msg container.Msg +} + +// valid checks outcomeState to be safe for use with outcomeOp. +func (s *outcomeState) valid() bool { + return s != nil && + s.ID != nil && + s.Container != nil && + s.EnvPaths != nil +} + +// populateLocal populates unexported fields from transmitted exported fields. +// These fields are cheaper to recompute per-process. +func (s *outcomeState) populateLocal(k syscallDispatcher, msg container.Msg) error { + if !s.valid() || k == nil || msg == nil { + return newWithMessage("impossible outcome state reached") + } + + if s.k != nil || s.msg != nil { + panic("attempting to call populateLocal twice") + } + s.k = k + s.msg = msg + + s.id = &stringPair[state.ID]{*s.ID, s.ID.String()} + + s.Copy(&s.sc, s.UserID) + msg.Verbosef("process share directory at %q, runtime directory at %q", s.sc.SharePath, s.sc.RunDirPath) + + s.identity = newInt(s.Identity) + s.mapuid, s.mapgid = newInt(s.Mapuid), newInt(s.Mapgid) + s.uid = newInt(HsuUid(s.UserID, s.identity.unwrap())) + + return nil +} + +// instancePath returns a path formatted for outcomeStateSys.instance. +// This method must only be called from outcomeOp.toContainer if +// outcomeOp.toSystem has already called outcomeStateSys.instance. +func (s *outcomeState) instancePath() *container.Absolute { + return s.sc.SharePath.Append(s.id.String()) +} + +// runtimePath returns a path formatted for outcomeStateSys.runtime. +// This method must only be called from outcomeOp.toContainer if +// outcomeOp.toSystem has already called outcomeStateSys.runtime. +func (s *outcomeState) runtimePath() *container.Absolute { + return s.sc.RunDirPath.Append(s.id.String()) +} + +// 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. +type outcomeStateSys struct { + // Whether XDG_RUNTIME_DIR is used post hsu. + useRuntimeDir bool + // Process-specific directory in TMPDIR, nil if unused. + sharePath *container.Absolute + // Process-specific directory in XDG_RUNTIME_DIR, nil if unused. + runtimeSharePath *container.Absolute + + sys *system.I + *outcomeState +} + +// ensureRuntimeDir must be called if access to paths within XDG_RUNTIME_DIR is required. +func (state *outcomeStateSys) ensureRuntimeDir() { + if state.useRuntimeDir { + return + } + state.useRuntimeDir = true + state.sys.Ensure(state.sc.RunDirPath, 0700) + state.sys.UpdatePermType(system.User, state.sc.RunDirPath, acl.Execute) + state.sys.Ensure(state.sc.RuntimePath, 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset + state.sys.UpdatePermType(system.User, state.sc.RuntimePath, acl.Execute) +} + +// instance returns the pathname to a process-specific directory within TMPDIR. +// This directory must only hold entries bound to [system.Process]. +func (state *outcomeStateSys) instance() *container.Absolute { + if state.sharePath != nil { + return state.sharePath + } + state.sharePath = state.instancePath() + state.sys.Ephemeral(system.Process, state.sharePath, 0711) + return state.sharePath +} + +// runtime returns the pathname to a process-specific directory within XDG_RUNTIME_DIR. +// This directory must only hold entries bound to [system.Process]. +func (state *outcomeStateSys) runtime() *container.Absolute { + if state.runtimeSharePath != nil { + return state.runtimeSharePath + } + state.ensureRuntimeDir() + state.runtimeSharePath = state.runtimePath() + state.sys.Ephemeral(system.Process, state.runtimeSharePath, 0700) + state.sys.UpdatePerm(state.runtimeSharePath, acl.Execute) + return state.runtimeSharePath +} + +// outcomeStateParams wraps outcomeState and [container.Params]. Used on the shim side only. +type outcomeStateParams struct { + // Overrides the embedded [container.Params] in [container.Container]. The Env field must not be used. + params *container.Params + // Collapsed into the Env slice in [container.Params] after every call to outcomeOp.toContainer completes. + env map[string]string + + // Filesystems with the optional root sliced off if present. Populated by spParamsOp. + // Safe for use by spFilesystemOp. + filesystem []hst.FilesystemConfigJSON + + // Inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` via mapped uid. + // Populated by spRuntimeOp. + runtimeDir *container.Absolute + + as hst.ApplyState + *outcomeState +} + +// TODO(ophestra): register outcomeOp implementations (params to shim) + +// An outcomeOp inflicts an outcome on [system.I] and contains enough information to +// inflict it on [container.Params] in a separate process. +// An implementation of outcomeOp must store cross-process states in exported fields only. +type outcomeOp interface { + // toSystem inflicts the current outcome on [system.I] in the priv side process. + toSystem(state *outcomeStateSys, config *hst.Config) error + + // toContainer inflicts the current outcome on [container.Params] in the shim process. + // The implementation must not write to the Env field of [container.Params] as it will be overwritten + // by flattened env map. + toContainer(state *outcomeStateParams) error +} |
