container/ops: implement overlay op
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Sandbox (push) Successful in 2m2s
Test / Hakurei (push) Successful in 2m57s
Test / Hpkg (push) Successful in 3m54s
Test / Sandbox (race detector) (push) Successful in 4m6s
Test / Hakurei (race detector) (push) Successful in 4m51s
Test / Flake checks (push) Successful in 1m22s
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Sandbox (push) Successful in 2m2s
Test / Hakurei (push) Successful in 2m57s
Test / Hpkg (push) Successful in 3m54s
Test / Sandbox (race detector) (push) Successful in 4m6s
Test / Hakurei (race detector) (push) Successful in 4m51s
Test / Flake checks (push) Successful in 1m22s
There are significant limitations to using the overlay mount, and the implementation in the kernel is quite quirky. For now the Op is quite robust, however a higher level interface for it has not been decided yet. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
167
container/ops.go
167
container/ops.go
@@ -13,6 +13,17 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// intermediate root file name pattern for [MountOverlayOp.Upper];
|
||||
// remains after apply returns
|
||||
intermediatePatternOverlayUpper = "overlay.upper.*"
|
||||
// intermediate root file name pattern for [MountOverlayOp.Work];
|
||||
// remains after apply returns
|
||||
intermediatePatternOverlayWork = "overlay.work.*"
|
||||
// intermediate root file name pattern for [TmpfileOp]
|
||||
intermediatePatternTmpfile = "tmp.*"
|
||||
)
|
||||
|
||||
type (
|
||||
Ops []Op
|
||||
|
||||
@@ -337,6 +348,160 @@ func (t *MountTmpfsOp) Is(op Op) bool { vt, ok := op.(*MountTmpfsOp); return ok
|
||||
func (*MountTmpfsOp) prefix() string { return "mounting" }
|
||||
func (t *MountTmpfsOp) String() string { return fmt.Sprintf("tmpfs on %q size %d", t.Path, t.Size) }
|
||||
|
||||
func init() { gob.Register(new(MountOverlayOp)) }
|
||||
|
||||
// Overlay appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target].
|
||||
func (f *Ops) Overlay(target, state, work string, layers ...string) *Ops {
|
||||
*f = append(*f, &MountOverlayOp{
|
||||
Target: target,
|
||||
Lower: layers,
|
||||
Upper: state,
|
||||
Work: work,
|
||||
})
|
||||
return f
|
||||
}
|
||||
|
||||
// OverlayEphemeral appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target]
|
||||
// with an ephemeral upperdir and workdir.
|
||||
func (f *Ops) OverlayEphemeral(target string, layers ...string) *Ops {
|
||||
return f.Overlay(target, SourceTmpfsEphemeral, zeroString, layers...)
|
||||
}
|
||||
|
||||
// OverlayReadonly appends an [Op] that mounts the overlay pseudo filesystem readonly on [MountOverlayOp.Target]
|
||||
func (f *Ops) OverlayReadonly(target string, layers ...string) *Ops {
|
||||
return f.Overlay(target, zeroString, zeroString, layers...)
|
||||
}
|
||||
|
||||
type MountOverlayOp struct {
|
||||
Target string
|
||||
|
||||
// formatted for [OptionOverlayLowerdir], resolved, prefixed and escaped during early;
|
||||
Lower []string
|
||||
// formatted for [OptionOverlayUpperdir], resolved, prefixed and escaped during early;
|
||||
//
|
||||
// If Work is an empty string and Upper holds the special value [SourceTmpfsEphemeral],
|
||||
// an ephemeral upperdir and workdir will be set up.
|
||||
//
|
||||
// If both Work and Upper are empty strings, upperdir and workdir is omitted and the overlay is mounted readonly.
|
||||
Upper string
|
||||
// formatted for [OptionOverlayWorkdir], resolved, prefixed and escaped during early;
|
||||
Work string
|
||||
|
||||
ephemeral bool
|
||||
}
|
||||
|
||||
func (o *MountOverlayOp) early(*Params) error {
|
||||
if o.Work == zeroString {
|
||||
switch o.Upper {
|
||||
case SourceTmpfsEphemeral: // ephemeral
|
||||
o.ephemeral = true // intermediate root not yet available
|
||||
|
||||
case zeroString: // readonly
|
||||
|
||||
default:
|
||||
return msg.WrapErr(EINVAL, fmt.Sprintf("upperdir has unexpected value %q", o.Upper))
|
||||
}
|
||||
}
|
||||
|
||||
if !o.ephemeral {
|
||||
if o.Upper != o.Work && (o.Upper == zeroString || o.Work == zeroString) {
|
||||
// unreachable
|
||||
return msg.WrapErr(ENOTRECOVERABLE, "impossible overlay state reached")
|
||||
}
|
||||
|
||||
if o.Upper != zeroString {
|
||||
if !path.IsAbs(o.Upper) {
|
||||
return msg.WrapErr(EBADE, fmt.Sprintf("upperdir %q is not absolute", o.Upper))
|
||||
}
|
||||
if v, err := filepath.EvalSymlinks(o.Upper); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
} else {
|
||||
o.Upper = escapeOverlayDataSegment(toHost(v))
|
||||
}
|
||||
}
|
||||
|
||||
if o.Work != zeroString {
|
||||
if !path.IsAbs(o.Work) {
|
||||
return msg.WrapErr(EBADE, fmt.Sprintf("workdir %q is not absolute", o.Work))
|
||||
}
|
||||
if v, err := filepath.EvalSymlinks(o.Work); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
} else {
|
||||
o.Work = escapeOverlayDataSegment(toHost(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := range o.Lower {
|
||||
if !path.IsAbs(o.Lower[i]) {
|
||||
return msg.WrapErr(EBADE, fmt.Sprintf("lowerdir %q is not absolute", o.Lower[i]))
|
||||
}
|
||||
|
||||
if v, err := filepath.EvalSymlinks(o.Lower[i]); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
} else {
|
||||
o.Lower[i] = escapeOverlayDataSegment(toHost(v))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *MountOverlayOp) apply(params *Params) error {
|
||||
if !path.IsAbs(o.Target) {
|
||||
return msg.WrapErr(EBADE, fmt.Sprintf("path %q is not absolute", o.Target))
|
||||
}
|
||||
target := toSysroot(o.Target)
|
||||
if err := os.MkdirAll(target, params.ParentPerm); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
}
|
||||
|
||||
if o.ephemeral {
|
||||
var err error
|
||||
// these directories are created internally, therefore early (absolute, symlink, prefix, escape) is bypassed
|
||||
if o.Upper, err = os.MkdirTemp(FHSRoot, intermediatePatternOverlayUpper); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
}
|
||||
if o.Work, err = os.MkdirTemp(FHSRoot, intermediatePatternOverlayWork); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
}
|
||||
}
|
||||
|
||||
options := make([]string, 0, 4)
|
||||
|
||||
if o.Upper == zeroString && o.Work == zeroString { // readonly
|
||||
if len(o.Lower) < 2 {
|
||||
return msg.WrapErr(EINVAL, "readonly overlay requires at least two lowerdir")
|
||||
}
|
||||
// "upperdir=" and "workdir=" may be omitted. In that case the overlay will be read-only
|
||||
} else {
|
||||
if len(o.Lower) == 0 {
|
||||
return msg.WrapErr(EINVAL, "overlay requires at least one lowerdir")
|
||||
}
|
||||
options = append(options,
|
||||
OptionOverlayUpperdir+"="+o.Upper,
|
||||
OptionOverlayWorkdir+"="+o.Work)
|
||||
}
|
||||
options = append(options,
|
||||
OptionOverlayLowerdir+"="+strings.Join(o.Lower, SpecialOverlayPath),
|
||||
OptionOverlayUserxattr)
|
||||
|
||||
return wrapErrSuffix(Mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, SpecialOverlayOption)),
|
||||
fmt.Sprintf("cannot mount overlay on %q:", o.Target))
|
||||
}
|
||||
|
||||
func (o *MountOverlayOp) Is(op Op) bool {
|
||||
vo, ok := op.(*MountOverlayOp)
|
||||
return ok &&
|
||||
o.Target == vo.Target &&
|
||||
slices.Equal(o.Lower, vo.Lower) &&
|
||||
o.Upper == vo.Upper &&
|
||||
o.Work == vo.Work
|
||||
}
|
||||
func (*MountOverlayOp) prefix() string { return "mounting" }
|
||||
func (o *MountOverlayOp) String() string {
|
||||
return fmt.Sprintf("overlay on %q with %d layers", o.Target, len(o.Lower))
|
||||
}
|
||||
|
||||
func init() { gob.Register(new(SymlinkOp)) }
|
||||
|
||||
// Link appends an [Op] that creates a symlink in the container filesystem.
|
||||
@@ -436,7 +601,7 @@ func (t *TmpfileOp) apply(params *Params) error {
|
||||
}
|
||||
|
||||
var tmpPath string
|
||||
if f, err := os.CreateTemp(FHSRoot, "tmp.*"); err != nil {
|
||||
if f, err := os.CreateTemp(FHSRoot, intermediatePatternTmpfile); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
} else if _, err = f.Write(t.Data); err != nil {
|
||||
return wrapErrSuffix(err,
|
||||
|
||||
Reference in New Issue
Block a user