diff --git a/cmd/hakurei/print_test.go b/cmd/hakurei/print_test.go index b55c104..471cb35 100644 --- a/cmd/hakurei/print_test.go +++ b/cmd/hakurei/print_test.go @@ -284,7 +284,7 @@ App "dst": "/", "src": "/var/lib/hakurei/base/org.debian", "write": true, - "autoroot": true + "special": true }, { "type": "ephemeral", @@ -438,7 +438,7 @@ App "dst": "/", "src": "/var/lib/hakurei/base/org.debian", "write": true, - "autoroot": true + "special": true }, { "type": "ephemeral", @@ -646,7 +646,7 @@ func Test_printPs(t *testing.T) { "dst": "/", "src": "/var/lib/hakurei/base/org.debian", "write": true, - "autoroot": true + "special": true }, { "type": "ephemeral", diff --git a/hst/fs.go b/hst/fs.go index b33d049..a2a75a7 100644 --- a/hst/fs.go +++ b/hst/fs.go @@ -18,11 +18,19 @@ type FilesystemConfig interface { // Host returns a slice of all host paths used by this operation. Host() []*container.Absolute // Apply appends the [container.Op] implementing this operation. - Apply(ops *container.Ops) + Apply(z *ApplyState) fmt.Stringer } +// 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 +} + var ( ErrFSNull = errors.New("unexpected null in mount point") ) diff --git a/hst/fs_test.go b/hst/fs_test.go index 43825c6..af8738f 100644 --- a/hst/fs_test.go +++ b/hst/fs_test.go @@ -209,7 +209,7 @@ type stubFS struct { func (s stubFS) Valid() bool { return false } func (s stubFS) Path() *container.Absolute { panic("unreachable") } func (s stubFS) Host() []*container.Absolute { panic("unreachable") } -func (s stubFS) Apply(*container.Ops) { panic("unreachable") } +func (s stubFS) Apply(*hst.ApplyState) { panic("unreachable") } func (s stubFS) String() string { return "" } type sCheck struct { @@ -238,7 +238,7 @@ func checkFs(t *testing.T, testCases []fsTestCase) { t.Run("ops", func(t *testing.T) { ops := new(container.Ops) - tc.fs.Apply(ops) + tc.fs.Apply(&hst.ApplyState{AutoEtcPrefix: ":3", Ops: ops}) if !reflect.DeepEqual(ops, &tc.ops) { gotString := new(strings.Builder) for _, op := range *ops { diff --git a/hst/fsbind.go b/hst/fsbind.go index 50e8da5..496453b 100644 --- a/hst/fsbind.go +++ b/hst/fsbind.go @@ -25,17 +25,38 @@ type FSBind struct { // skip this mount point if the host path does not exist Optional bool `json:"optional,omitempty"` - // enable autoroot behaviour; - // this requires Target to be [container.AbsFHSRoot]. - AutoRoot bool `json:"autoroot,omitempty"` + // enable special behaviour: + // for autoroot, Target must be set to [container.AbsFHSRoot]; + // for autoetc, Target must be set to [container.AbsFHSEtc] + Special bool `json:"special,omitempty"` +} + +// IsAutoRoot returns whether this FSBind has autoroot behaviour enabled. +func (b *FSBind) IsAutoRoot() bool { + return b.Valid() && b.Special && b.Target.String() == container.FHSRoot +} + +// IsAutoEtc returns whether this FSBind has autoetc behaviour enabled. +func (b *FSBind) IsAutoEtc() bool { + return b.Valid() && b.Special && b.Target.String() == container.FHSEtc } func (b *FSBind) Valid() bool { if b == nil || b.Source == nil { return false } - if b.AutoRoot && (b.Target == nil || b.Target.String() != container.FHSRoot) { - return false + if b.Special { + if b.Target == nil { + return false + } else { + switch b.Target.String() { + case container.FHSRoot, container.FHSEtc: + break + + default: + return false + } + } } return true } @@ -57,7 +78,7 @@ func (b *FSBind) Host() []*container.Absolute { return []*container.Absolute{b.Source} } -func (b *FSBind) Apply(ops *container.Ops) { +func (b *FSBind) Apply(z *ApplyState) { if !b.Valid() { return } @@ -77,10 +98,15 @@ func (b *FSBind) Apply(ops *container.Ops) { flags |= container.BindOptional } - if !b.AutoRoot { - ops.Bind(b.Source, target, flags) - } else { - ops.Root(b.Source, flags) + switch { + case b.IsAutoRoot(): + z.Root(b.Source, flags) + + case b.IsAutoEtc(): + z.Etc(b.Source, z.AutoEtcPrefix) + + default: + z.Bind(b.Source, target, flags) } } @@ -96,15 +122,21 @@ func (b *FSBind) String() string { flagSym = "w" } - if b.AutoRoot { - prefix := "autoroot" - if flagSym != "" { - prefix += ":" + flagSym + if b.Special { + switch { + case b.IsAutoRoot(): + prefix := "autoroot" + if flagSym != "" { + prefix += ":" + flagSym + } + if b.Source.String() != container.FHSRoot { + return prefix + ":" + b.Source.String() + } + return prefix + + case b.IsAutoEtc(): + return "autoetc:" + b.Source.String() } - if b.Source.String() != container.FHSRoot { - return prefix + ":" + b.Source.String() - } - return prefix } g := 4 + len(b.Source.String()) diff --git a/hst/fsbind_test.go b/hst/fsbind_test.go index 750dd93..b2fb0f7 100644 --- a/hst/fsbind_test.go +++ b/hst/fsbind_test.go @@ -63,35 +63,45 @@ func TestFSBind(t *testing.T) { }}, m("/"), ms("/"), "*/"}, - {"autoroot nil target", &hst.FSBind{ - Source: m("/"), - AutoRoot: true, + {"special nil target", &hst.FSBind{ + Source: m("/"), + Special: true, }, false, nil, nil, nil, ""}, - {"autoroot bad target", &hst.FSBind{ - Source: m("/"), - Target: m("/etc/"), - AutoRoot: true, + {"special bad target", &hst.FSBind{ + Source: m("/"), + Target: m("/var/"), + Special: true, }, false, nil, nil, nil, ""}, {"autoroot pd", &hst.FSBind{ - Target: m("/"), - Source: m("/"), - Write: true, - AutoRoot: true, + Target: m("/"), + Source: m("/"), + Write: true, + Special: true, }, true, container.Ops{&container.AutoRootOp{ Host: m("/"), Flags: container.BindWritable, }}, m("/"), ms("/"), "autoroot:w"}, {"autoroot silly", &hst.FSBind{ - Target: m("/"), - Source: m("/etc"), - Write: true, - AutoRoot: true, + Target: m("/"), + Source: m("/etc"), + Write: true, + Special: true, }, true, container.Ops{&container.AutoRootOp{ Host: m("/etc"), Flags: container.BindWritable, }}, m("/"), ms("/etc"), "autoroot:w:/etc"}, + + {"autoetc", &hst.FSBind{ + Target: m("/etc/"), + Source: m("/etc/"), + Special: true, + }, true, container.Ops{ + &container.MkdirOp{Path: m("/etc/"), Perm: 0755}, + &container.BindMountOp{Source: m("/etc/"), Target: m("/etc/.host/:3")}, + &container.AutoEtcOp{Prefix: ":3"}, + }, m("/etc/"), ms("/etc/"), "autoetc:/etc/"}, }) } diff --git a/hst/fsephemeral.go b/hst/fsephemeral.go index 424a058..ad212fd 100644 --- a/hst/fsephemeral.go +++ b/hst/fsephemeral.go @@ -38,7 +38,7 @@ func (e *FSEphemeral) Host() []*container.Absolute { return nil } const fsEphemeralDefaultPerm = os.FileMode(0755) -func (e *FSEphemeral) Apply(ops *container.Ops) { +func (e *FSEphemeral) Apply(z *ApplyState) { if !e.Valid() { return } @@ -54,9 +54,9 @@ func (e *FSEphemeral) Apply(ops *container.Ops) { } if e.Write { - ops.Tmpfs(e.Target, size, perm) + z.Tmpfs(e.Target, size, perm) } else { - ops.Readonly(e.Target, perm) + z.Readonly(e.Target, perm) } } diff --git a/hst/fsoverlay.go b/hst/fsoverlay.go index 8d63e16..9b8e97f 100644 --- a/hst/fsoverlay.go +++ b/hst/fsoverlay.go @@ -62,15 +62,15 @@ func (o *FSOverlay) Host() []*container.Absolute { return p } -func (o *FSOverlay) Apply(op *container.Ops) { +func (o *FSOverlay) Apply(z *ApplyState) { if !o.Valid() { return } if o.Upper != nil && o.Work != nil { // rw - op.Overlay(o.Target, o.Upper, o.Work, o.Lower...) + z.Overlay(o.Target, o.Upper, o.Work, o.Lower...) } else { // ro - op.OverlayReadonly(o.Target, o.Lower...) + z.OverlayReadonly(o.Target, o.Lower...) } } diff --git a/hst/hst_test.go b/hst/hst_test.go index b8ec79c..897bf93 100644 --- a/hst/hst_test.go +++ b/hst/hst_test.go @@ -107,7 +107,7 @@ func TestTemplate(t *testing.T) { "dst": "/", "src": "/var/lib/hakurei/base/org.debian", "write": true, - "autoroot": true + "special": true }, { "type": "ephemeral", diff --git a/internal/app/container_linux.go b/internal/app/container_linux.go index 33c0c72..30477fe 100644 --- a/internal/app/container_linux.go +++ b/internal/app/container_linux.go @@ -40,9 +40,13 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid ForwardCancel: s.WaitDelay >= 0, } + as := &hst.ApplyState{ + AutoEtcPrefix: prefix, + } { ops := make(container.Ops, 0, preallocateOpsCount+len(s.Filesystem)+len(s.Link)) params.Ops = &ops + as.Ops = &ops } if s.Multiarch { @@ -81,10 +85,10 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid // if the first element targets /, it is inserted early and excluded from path hiding rootfs := filesystem[0].FilesystemConfig filesystem = filesystem[1:] - rootfs.Apply(params.Ops) + rootfs.Apply(as) // autoroot requires special handling during path hiding - if b, ok := rootfs.(*hst.FSBind); ok && b.Valid() && b.AutoRoot { + if b, ok := rootfs.(*hst.FSBind); ok && b.IsAutoRoot() { autoroot = b } } @@ -143,7 +147,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid if !c.Valid() { return nil, nil, fmt.Errorf("invalid filesystem at index %d", i) } - c.Apply(params.Ops) + c.Apply(as) // fs counter hidePathSourceCount += len(c.Host()) diff --git a/internal/app/seal_linux.go b/internal/app/seal_linux.go index fb6f39b..4e77e79 100644 --- a/internal/app/seal_linux.go +++ b/internal/app/seal_linux.go @@ -246,10 +246,10 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co Filesystem: []hst.FilesystemConfigJSON{ {&hst.FSBind{ - Target: container.AbsFHSRoot, - Source: container.AbsFHSRoot, - Write: true, - AutoRoot: true, + Target: container.AbsFHSRoot, + Source: container.AbsFHSRoot, + Write: true, + Special: true, }}, }, }