diff --git a/hst/fs.go b/hst/fs.go index 4508e97..07ed6b4 100644 --- a/hst/fs.go +++ b/hst/fs.go @@ -4,9 +4,9 @@ import ( "encoding/json" "errors" "fmt" + "os" "reflect" - "hakurei.app/container" "hakurei.app/container/check" ) @@ -24,12 +24,35 @@ type FilesystemConfig interface { fmt.Stringer } +// The Ops interface enables [FilesystemConfig] to queue container ops without depending on the container package. +type Ops interface { + // Tmpfs appends an op that mounts tmpfs on a container path. + Tmpfs(target *check.Absolute, size int, perm os.FileMode) Ops + // Readonly appends an op that mounts read-only tmpfs on a container path. + Readonly(target *check.Absolute, perm os.FileMode) Ops + + // Bind appends an op that bind mounts a host path on a container path. + Bind(source, target *check.Absolute, flags int) Ops + // Overlay appends an op that mounts the overlay pseudo filesystem. + Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) Ops + // OverlayReadonly appends an op that mounts the overlay pseudo filesystem readonly. + OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) Ops + + // Link appends an op that creates a symlink in the container filesystem. + Link(target *check.Absolute, linkName string, dereference bool) Ops + + // Root appends an op that expands a directory into a toplevel bind mount mirror on container root. + Root(host *check.Absolute, flags int) Ops + // Etc appends an op that expands host /etc into a toplevel symlink mirror with /etc semantics. + Etc(host *check.Absolute, prefix string) Ops +} + // ApplyState holds the address of [container.Ops] and any relevant application state. type ApplyState struct { // AutoEtcPrefix is the prefix for [container.AutoEtcOp]. AutoEtcPrefix string - *container.Ops + Ops } var ( diff --git a/hst/fs_test.go b/hst/fs_test.go index 35d58ad..8350be5 100644 --- a/hst/fs_test.go +++ b/hst/fs_test.go @@ -3,6 +3,7 @@ package hst_test import ( "encoding/json" "errors" + "os" "reflect" "strings" "syscall" @@ -249,7 +250,7 @@ func checkFs(t *testing.T, testCases []fsTestCase) { t.Run("ops", func(t *testing.T) { ops := new(container.Ops) - tc.fs.Apply(&hst.ApplyState{AutoEtcPrefix: ":3", Ops: ops}) + tc.fs.Apply(&hst.ApplyState{AutoEtcPrefix: ":3", Ops: opsAdapter{ops}}) if !reflect.DeepEqual(ops, &tc.ops) { gotString := new(strings.Builder) for _, op := range *ops { @@ -288,6 +289,40 @@ func checkFs(t *testing.T, testCases []fsTestCase) { } } +type opsAdapter struct{ *container.Ops } + +func (p opsAdapter) Tmpfs(target *check.Absolute, size int, perm os.FileMode) hst.Ops { + return opsAdapter{p.Ops.Tmpfs(target, size, perm)} +} + +func (p opsAdapter) Readonly(target *check.Absolute, perm os.FileMode) hst.Ops { + return opsAdapter{p.Ops.Readonly(target, perm)} +} + +func (p opsAdapter) Bind(source, target *check.Absolute, flags int) hst.Ops { + return opsAdapter{p.Ops.Bind(source, target, flags)} +} + +func (p opsAdapter) Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) hst.Ops { + return opsAdapter{p.Ops.Overlay(target, state, work, layers...)} +} + +func (p opsAdapter) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) hst.Ops { + return opsAdapter{p.Ops.OverlayReadonly(target, layers...)} +} + +func (p opsAdapter) Link(target *check.Absolute, linkName string, dereference bool) hst.Ops { + return opsAdapter{p.Ops.Link(target, linkName, dereference)} +} + +func (p opsAdapter) Root(host *check.Absolute, flags int) hst.Ops { + return opsAdapter{p.Ops.Root(host, flags)} +} + +func (p opsAdapter) Etc(host *check.Absolute, prefix string) hst.Ops { + return opsAdapter{p.Ops.Etc(host, prefix)} +} + func m(pathname string) *check.Absolute { return check.MustAbs(pathname) } func ms(pathnames ...string) []*check.Absolute { as := make([]*check.Absolute, len(pathnames)) diff --git a/internal/app/spcontainer.go b/internal/app/spcontainer.go index 96afecb..bce0e20 100644 --- a/internal/app/spcontainer.go +++ b/internal/app/spcontainer.go @@ -3,6 +3,7 @@ package app import ( "errors" "io/fs" + "os" "path" "strconv" "syscall" @@ -88,7 +89,7 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error { state.as.AutoEtcPrefix = state.id.String() ops := make(container.Ops, 0, preallocateOpsCount+len(state.Container.Filesystem)) state.params.Ops = &ops - state.as.Ops = &ops + state.as.Ops = opsAdapter{&ops} } rootfs, filesystem, _ := resolveRoot(state.Container) @@ -304,3 +305,38 @@ func evalSymlinks(msg container.Msg, k syscallDispatcher, v *string) error { } return nil } + +// opsAdapter implements [hst.Ops] on [container.Ops]. +type opsAdapter struct{ *container.Ops } + +func (p opsAdapter) Tmpfs(target *check.Absolute, size int, perm os.FileMode) hst.Ops { + return opsAdapter{p.Ops.Tmpfs(target, size, perm)} +} + +func (p opsAdapter) Readonly(target *check.Absolute, perm os.FileMode) hst.Ops { + return opsAdapter{p.Ops.Readonly(target, perm)} +} + +func (p opsAdapter) Bind(source, target *check.Absolute, flags int) hst.Ops { + return opsAdapter{p.Ops.Bind(source, target, flags)} +} + +func (p opsAdapter) Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) hst.Ops { + return opsAdapter{p.Ops.Overlay(target, state, work, layers...)} +} + +func (p opsAdapter) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) hst.Ops { + return opsAdapter{p.Ops.OverlayReadonly(target, layers...)} +} + +func (p opsAdapter) Link(target *check.Absolute, linkName string, dereference bool) hst.Ops { + return opsAdapter{p.Ops.Link(target, linkName, dereference)} +} + +func (p opsAdapter) Root(host *check.Absolute, flags int) hst.Ops { + return opsAdapter{p.Ops.Root(host, flags)} +} + +func (p opsAdapter) Etc(host *check.Absolute, prefix string) hst.Ops { + return opsAdapter{p.Ops.Etc(host, prefix)} +}