aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app/outcome.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/app/outcome.go')
-rw-r--r--internal/app/outcome.go191
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
+}