All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 34s
				
			Test / Sandbox (push) Successful in 2m12s
				
			Test / Hakurei (push) Successful in 3m8s
				
			Test / Hpkg (push) Successful in 4m9s
				
			Test / Sandbox (race detector) (push) Successful in 4m31s
				
			Test / Hakurei (race detector) (push) Successful in 5m25s
				
			Test / Flake checks (push) Successful in 1m40s
				
			This is used in hst to format strings. Signed-off-by: Ophestra <cat@gensokyo.uk>
		
			
				
	
	
		
			219 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package container
 | |
| 
 | |
| import (
 | |
| 	"encoding/gob"
 | |
| 	"fmt"
 | |
| 	"slices"
 | |
| 	"strings"
 | |
| 
 | |
| 	"hakurei.app/container/check"
 | |
| 	"hakurei.app/container/fhs"
 | |
| )
 | |
| 
 | |
| 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)) }
 | |
| 
 | |
| const (
 | |
| 	// OverlayEphemeralUnexpectedUpper is set when [MountOverlayOp.Work] is nil
 | |
| 	// and [MountOverlayOp.Upper] holds an unexpected value.
 | |
| 	OverlayEphemeralUnexpectedUpper = iota
 | |
| 	// OverlayReadonlyLower is set when [MountOverlayOp.Lower] contains less than
 | |
| 	// two entries when mounting readonly.
 | |
| 	OverlayReadonlyLower
 | |
| 	// OverlayEmptyLower is set when [MountOverlayOp.Lower] has length of zero.
 | |
| 	OverlayEmptyLower
 | |
| )
 | |
| 
 | |
| // OverlayArgumentError is returned for [MountOverlayOp] supplied with invalid argument.
 | |
| type OverlayArgumentError struct {
 | |
| 	Type  uintptr
 | |
| 	Value string
 | |
| }
 | |
| 
 | |
| func (e *OverlayArgumentError) Error() string {
 | |
| 	switch e.Type {
 | |
| 	case OverlayEphemeralUnexpectedUpper:
 | |
| 		return fmt.Sprintf("upperdir has unexpected value %q", e.Value)
 | |
| 
 | |
| 	case OverlayReadonlyLower:
 | |
| 		return "readonly overlay requires at least two lowerdir"
 | |
| 
 | |
| 	case OverlayEmptyLower:
 | |
| 		return "overlay requires at least one lowerdir"
 | |
| 
 | |
| 	default:
 | |
| 		return fmt.Sprintf("invalid overlay argument error %#x", e.Type)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Overlay appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target].
 | |
| func (f *Ops) Overlay(target, state, work *check.Absolute, layers ...*check.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 *check.Absolute, layers ...*check.Absolute) *Ops {
 | |
| 	return f.Overlay(target, fhs.AbsRoot, nil, layers...)
 | |
| }
 | |
| 
 | |
| // OverlayReadonly appends an [Op] that mounts the overlay pseudo filesystem readonly on [MountOverlayOp.Target]
 | |
| func (f *Ops) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) *Ops {
 | |
| 	return f.Overlay(target, nil, nil, layers...)
 | |
| }
 | |
| 
 | |
| // MountOverlayOp mounts [FstypeOverlay] on container path Target.
 | |
| type MountOverlayOp struct {
 | |
| 	Target *check.Absolute
 | |
| 
 | |
| 	// Any filesystem, does not need to be on a writable filesystem.
 | |
| 	Lower []*check.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 [fhs.AbsRoot],
 | |
| 	// 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 *check.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 *check.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 fhs.Root: // ephemeral
 | |
| 			o.ephemeral = true // intermediate root not yet available
 | |
| 
 | |
| 		default:
 | |
| 			return &OverlayArgumentError{OverlayEphemeralUnexpectedUpper, o.Upper.String()}
 | |
| 		}
 | |
| 	}
 | |
| 	// readonly handled in apply
 | |
| 
 | |
| 	if !o.ephemeral {
 | |
| 		if o.Upper != o.Work && (o.Upper == nil || o.Work == nil) {
 | |
| 			// unreachable
 | |
| 			return OpStateError("overlay")
 | |
| 		}
 | |
| 
 | |
| 		if o.Upper != nil {
 | |
| 			if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
 | |
| 				return err
 | |
| 			} else {
 | |
| 				o.upper = check.EscapeOverlayDataSegment(toHost(v))
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if o.Work != nil {
 | |
| 			if v, err := k.evalSymlinks(o.Work.String()); err != nil {
 | |
| 				return err
 | |
| 			} else {
 | |
| 				o.work = check.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 err
 | |
| 		} else {
 | |
| 			o.lower[i] = check.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 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(fhs.Root, intermediatePatternOverlayUpper); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if o.work, err = k.mkdirTemp(fhs.Root, intermediatePatternOverlayWork); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	options := make([]string, 0, 4)
 | |
| 
 | |
| 	if o.upper == zeroString && o.work == zeroString { // readonly
 | |
| 		if len(o.Lower) < 2 {
 | |
| 			return &OverlayArgumentError{OverlayReadonlyLower, zeroString}
 | |
| 		}
 | |
| 		// "upperdir=" and "workdir=" may be omitted. In that case the overlay will be read-only
 | |
| 	} else {
 | |
| 		if len(o.Lower) == 0 {
 | |
| 			return &OverlayArgumentError{OverlayEmptyLower, zeroString}
 | |
| 		}
 | |
| 		options = append(options,
 | |
| 			OptionOverlayUpperdir+"="+o.upper,
 | |
| 			OptionOverlayWorkdir+"="+o.work)
 | |
| 	}
 | |
| 	options = append(options,
 | |
| 		OptionOverlayLowerdir+"="+strings.Join(o.lower, check.SpecialOverlayPath),
 | |
| 		OptionOverlayUserxattr)
 | |
| 
 | |
| 	return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, check.SpecialOverlayOption))
 | |
| }
 | |
| 
 | |
| 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, v *check.Absolute) bool { return a.Is(v) }) &&
 | |
| 		o.Upper.Is(vo.Upper) && o.Work.Is(vo.Work)
 | |
| }
 | |
| func (*MountOverlayOp) prefix() (string, bool) { return "mounting", true }
 | |
| func (o *MountOverlayOp) String() string {
 | |
| 	return fmt.Sprintf("overlay on %q with %d layers", o.Target, len(o.Lower))
 | |
| }
 |