All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 34s
				
			Test / Sandbox (push) Successful in 2m7s
				
			Test / Hakurei (push) Successful in 3m12s
				
			Test / Hpkg (push) Successful in 4m3s
				
			Test / Sandbox (race detector) (push) Successful in 4m31s
				
			Test / Hakurei (race detector) (push) Successful in 5m7s
				
			Test / Flake checks (push) Successful in 1m23s
				
			This is for supporting overlay mounts for autoroot. Signed-off-by: Ophestra <cat@gensokyo.uk>
		
			
				
	
	
		
			185 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package container
 | |
| 
 | |
| import (
 | |
| 	"encoding/gob"
 | |
| 	"fmt"
 | |
| 	"io/fs"
 | |
| 	"slices"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| 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.*"
 | |
| )
 | |
| 
 | |
| 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 *Absolute, layers ...*Absolute) *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 *Absolute, layers ...*Absolute) *Ops {
 | |
| 	return f.Overlay(target, AbsFHSRoot, nil, layers...)
 | |
| }
 | |
| 
 | |
| // OverlayReadonly appends an [Op] that mounts the overlay pseudo filesystem readonly on [MountOverlayOp.Target]
 | |
| func (f *Ops) OverlayReadonly(target *Absolute, layers ...*Absolute) *Ops {
 | |
| 	return f.Overlay(target, nil, nil, layers...)
 | |
| }
 | |
| 
 | |
| // MountOverlayOp mounts [FstypeOverlay] on container path Target.
 | |
| type MountOverlayOp struct {
 | |
| 	Target *Absolute
 | |
| 
 | |
| 	// Any filesystem, does not need to be on a writable filesystem.
 | |
| 	Lower []*Absolute
 | |
| 	// formatted for [OptionOverlayLowerdir], resolved, prefixed and escaped during early
 | |
| 	lower []string
 | |
| 	// The upperdir is normally on a writable filesystem.
 | |
| 	//
 | |
| 	// If Work is nil and Upper holds the special value [AbsFHSRoot],
 | |
| 	// an ephemeral upperdir and workdir will be set up.
 | |
| 	//
 | |
| 	// If both Work and Upper are nil, upperdir and workdir is omitted and the overlay is mounted readonly.
 | |
| 	Upper *Absolute
 | |
| 	// formatted for [OptionOverlayUpperdir], resolved, prefixed and escaped during early
 | |
| 	upper string
 | |
| 	// The workdir needs to be an empty directory on the same filesystem as upperdir.
 | |
| 	Work *Absolute
 | |
| 	// formatted for [OptionOverlayWorkdir], resolved, prefixed and escaped during early
 | |
| 	work string
 | |
| 
 | |
| 	ephemeral bool
 | |
| 
 | |
| 	// used internally for mounting to the intermediate root
 | |
| 	noPrefix bool
 | |
| }
 | |
| 
 | |
| func (o *MountOverlayOp) Valid() bool {
 | |
| 	if o == nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	if o.Work != nil && o.Upper == nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	if slices.Contains(o.Lower, nil) {
 | |
| 		return false
 | |
| 	}
 | |
| 	return o.Target != nil
 | |
| }
 | |
| 
 | |
| func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
 | |
| 	if o.Work == nil && o.Upper != nil {
 | |
| 		switch o.Upper.String() {
 | |
| 		case FHSRoot: // ephemeral
 | |
| 			o.ephemeral = true // intermediate root not yet available
 | |
| 
 | |
| 		default:
 | |
| 			return msg.WrapErr(fs.ErrInvalid, fmt.Sprintf("upperdir has unexpected value %q", o.Upper))
 | |
| 		}
 | |
| 	}
 | |
| 	// readonly handled in apply
 | |
| 
 | |
| 	if !o.ephemeral {
 | |
| 		if o.Upper != o.Work && (o.Upper == nil || o.Work == nil) {
 | |
| 			// unreachable
 | |
| 			return msg.WrapErr(fs.ErrClosed, "impossible overlay state reached")
 | |
| 		}
 | |
| 
 | |
| 		if o.Upper != nil {
 | |
| 			if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
 | |
| 				return wrapErrSelf(err)
 | |
| 			} else {
 | |
| 				o.upper = EscapeOverlayDataSegment(toHost(v))
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if o.Work != nil {
 | |
| 			if v, err := k.evalSymlinks(o.Work.String()); err != nil {
 | |
| 				return wrapErrSelf(err)
 | |
| 			} else {
 | |
| 				o.work = EscapeOverlayDataSegment(toHost(v))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	o.lower = make([]string, len(o.Lower))
 | |
| 	for i, a := range o.Lower { // nil checked in Valid
 | |
| 		if v, err := k.evalSymlinks(a.String()); err != nil {
 | |
| 			return wrapErrSelf(err)
 | |
| 		} else {
 | |
| 			o.lower[i] = EscapeOverlayDataSegment(toHost(v))
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
 | |
| 	target := o.Target.String()
 | |
| 	if !o.noPrefix {
 | |
| 		target = toSysroot(target)
 | |
| 	}
 | |
| 	if err := k.mkdirAll(target, state.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 = k.mkdirTemp(FHSRoot, intermediatePatternOverlayUpper); err != nil {
 | |
| 			return wrapErrSelf(err)
 | |
| 		}
 | |
| 		if o.work, err = k.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(fs.ErrInvalid, "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(fs.ErrInvalid, "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(k.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.Valid() && vo.Valid() &&
 | |
| 		o.Target.Is(vo.Target) &&
 | |
| 		slices.EqualFunc(o.Lower, vo.Lower, func(a *Absolute, v *Absolute) bool { return a.Is(v) }) &&
 | |
| 		o.Upper.Is(vo.Upper) && o.Work.Is(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))
 | |
| }
 |