prime8dev

prime8dev
git clone https://git.prime8.dev/prime8dev.git
Log | Files | Refs | README | LICENSE

overlay.go (6831B)


      1 package vfs
      2 
      3 import (
      4 	"errors"
      5 	"path"
      6 	"sort"
      7 	"strings"
      8 )
      9 
     10 // Writable layer for the shell. The generated tree (Build) stays read-only and
     11 // shared; each shell session keeps an Overlay of what it created, changed or
     12 // deleted, and FS replays it over a private clone of the base on every
     13 // command. The overlay lives in memory only (an SSH session loses it on
     14 // disconnect) and is capped so a public session cannot grow without bound.
     15 
     16 const (
     17 	// OverlayQuota caps the bytes (paths + contents) one session may write.
     18 	OverlayQuota = 1 << 20
     19 	// OverlayMaxEntries caps how many paths one session may touch.
     20 	OverlayMaxEntries = 4096
     21 )
     22 
     23 // Errors carry the text a shell prints after "cmd: 'path': ".
     24 var (
     25 	ErrNotExist   = errors.New("No such file or directory")
     26 	ErrNotDir     = errors.New("Not a directory")
     27 	ErrIsDir      = errors.New("Is a directory")
     28 	ErrExist      = errors.New("File exists")
     29 	ErrNotEmpty   = errors.New("Directory not empty")
     30 	ErrPermission = errors.New("Permission denied")
     31 	ErrNoSpace    = errors.New("No space left on device")
     32 )
     33 
     34 // overlayEntry is one changed path. A nil *overlayEntry in the map is a
     35 // whiteout (deleted base node). fresh marks a directory that replaces the
     36 // base directory at the same path instead of merging with it.
     37 type overlayEntry struct {
     38 	dir     bool
     39 	fresh   bool
     40 	content string
     41 }
     42 
     43 func (e *overlayEntry) size(p string) int {
     44 	if e == nil {
     45 		return len(p)
     46 	}
     47 	return len(p) + len(e.content)
     48 }
     49 
     50 // Overlay is a session's set of changes, keyed by absolute path.
     51 type Overlay struct {
     52 	entries map[string]*overlayEntry
     53 	used    int
     54 }
     55 
     56 // NewOverlay returns an empty overlay.
     57 func NewOverlay() *Overlay {
     58 	return &Overlay{entries: map[string]*overlayEntry{}}
     59 }
     60 
     61 // Changes is the number of paths the overlay touches.
     62 func (o *Overlay) Changes() int { return len(o.entries) }
     63 
     64 // Used is the bytes counted against OverlayQuota.
     65 func (o *Overlay) Used() int { return o.used }
     66 
     67 // Reset drops every change.
     68 func (o *Overlay) Reset() {
     69 	o.entries = map[string]*overlayEntry{}
     70 	o.used = 0
     71 }
     72 
     73 func (o *Overlay) set(p string, e *overlayEntry) error {
     74 	old, had := o.entries[p]
     75 	next := o.used + e.size(p)
     76 	if had {
     77 		next -= old.size(p)
     78 	}
     79 	if next > OverlayQuota || (!had && len(o.entries) >= OverlayMaxEntries) {
     80 		return ErrNoSpace
     81 	}
     82 	o.entries[p] = e
     83 	o.used = next
     84 	return nil
     85 }
     86 
     87 func (o *Overlay) drop(p string) {
     88 	if e, ok := o.entries[p]; ok {
     89 		o.used -= e.size(p)
     90 		delete(o.entries, p)
     91 	}
     92 }
     93 
     94 // Clone deep-copies a node.
     95 func (n *Node) Clone() *Node {
     96 	c := &Node{Name: n.Name, Dir: n.Dir, Content: n.Content}
     97 	if n.Dir {
     98 		c.Children = make(map[string]*Node, len(n.Children))
     99 		for k, v := range n.Children {
    100 			c.Children[k] = v.Clone()
    101 		}
    102 	}
    103 	return c
    104 }
    105 
    106 // FS is a writable view: a private clone of base with the overlay applied.
    107 // Mutations update both the view and the overlay; base is never touched.
    108 type FS struct {
    109 	base *Node
    110 	root *Node
    111 	home string
    112 	ov   *Overlay
    113 }
    114 
    115 // NewFS clones base and replays ov over it. home (and /tmp) are writable.
    116 func NewFS(base *Node, home string, ov *Overlay) *FS {
    117 	if ov == nil {
    118 		ov = NewOverlay()
    119 	}
    120 	f := &FS{base: base, root: base.Clone(), home: home, ov: ov}
    121 	f.apply()
    122 	return f
    123 }
    124 
    125 func depth(p string) int { return strings.Count(p, "/") }
    126 
    127 func (f *FS) apply() {
    128 	keys := make([]string, 0, len(f.ov.entries))
    129 	for k := range f.ov.entries {
    130 		keys = append(keys, k)
    131 	}
    132 	sort.Slice(keys, func(i, j int) bool {
    133 		if di, dj := depth(keys[i]), depth(keys[j]); di != dj {
    134 			return di < dj
    135 		}
    136 		return keys[i] < keys[j]
    137 	})
    138 	for _, k := range keys {
    139 		parent := f.root.Lookup(path.Dir(k))
    140 		if parent == nil || !parent.Dir {
    141 			continue
    142 		}
    143 		name := path.Base(k)
    144 		e := f.ov.entries[k]
    145 		switch {
    146 		case e == nil:
    147 			delete(parent.Children, name)
    148 		case e.dir:
    149 			if cur, ok := parent.Children[name]; e.fresh || !ok || !cur.Dir {
    150 				parent.Children[name] = NewDir(name)
    151 			}
    152 		default:
    153 			parent.AddFile(name, e.content)
    154 		}
    155 	}
    156 }
    157 
    158 // Root is the merged tree. Treat it as read-only; mutate through FS.
    159 func (f *FS) Root() *Node { return f.root }
    160 
    161 // Get looks up an absolute path in the merged tree.
    162 func (f *FS) Get(abs string) *Node { return f.root.Lookup(abs) }
    163 
    164 func inside(abs, base string) bool {
    165 	return abs == base || strings.HasPrefix(abs, strings.TrimSuffix(base, "/")+"/")
    166 }
    167 
    168 // Writable: the session owns its home and /tmp; the rest belongs to root.
    169 func (f *FS) Writable(abs string) bool {
    170 	return inside(abs, f.home) || inside(abs, "/tmp")
    171 }
    172 
    173 // CheckCreate reports why abs cannot be created, replaced or removed.
    174 func (f *FS) CheckCreate(abs string) error {
    175 	if abs == "/" {
    176 		return ErrPermission
    177 	}
    178 	parent := f.Get(path.Dir(abs))
    179 	switch {
    180 	case parent == nil:
    181 		return ErrNotExist
    182 	case !parent.Dir:
    183 		return ErrNotDir
    184 	case !f.Writable(path.Dir(abs)) || !f.Writable(abs):
    185 		return ErrPermission
    186 	}
    187 	return nil
    188 }
    189 
    190 func (f *FS) inBase(abs string) bool { return f.base.Lookup(abs) != nil }
    191 
    192 // Mkdir creates (or replaces) a directory at abs. Call CheckCreate first.
    193 func (f *FS) Mkdir(abs string) error {
    194 	parent := f.Get(path.Dir(abs))
    195 	if parent == nil || !parent.Dir {
    196 		return ErrNotExist
    197 	}
    198 	if err := f.ov.set(abs, &overlayEntry{dir: true, fresh: f.inBase(abs)}); err != nil {
    199 		return err
    200 	}
    201 	name := path.Base(abs)
    202 	parent.Children[name] = NewDir(name)
    203 	return nil
    204 }
    205 
    206 // Write creates or replaces the file at abs. Call CheckCreate first.
    207 func (f *FS) Write(abs, content string) error {
    208 	parent := f.Get(path.Dir(abs))
    209 	if parent == nil || !parent.Dir {
    210 		return ErrNotExist
    211 	}
    212 	if err := f.ov.set(abs, &overlayEntry{content: content}); err != nil {
    213 		return err
    214 	}
    215 	parent.AddFile(path.Base(abs), content)
    216 	return nil
    217 }
    218 
    219 // Remove deletes abs and everything under it. Call CheckCreate first.
    220 func (f *FS) Remove(abs string) error {
    221 	parent := f.Get(path.Dir(abs))
    222 	if parent == nil || !parent.Dir {
    223 		return ErrNotExist
    224 	}
    225 	prefix := abs + "/"
    226 	for k := range f.ov.entries {
    227 		if strings.HasPrefix(k, prefix) {
    228 			f.ov.drop(k)
    229 		}
    230 	}
    231 	if f.inBase(abs) {
    232 		if err := f.ov.set(abs, nil); err != nil {
    233 			return err
    234 		}
    235 	} else {
    236 		f.ov.drop(abs)
    237 	}
    238 	delete(parent.Children, path.Base(abs))
    239 	return nil
    240 }
    241 
    242 // Copy materializes n (a whole subtree for a directory) at abs.
    243 func (f *FS) Copy(n *Node, abs string) error {
    244 	n = n.Clone() // n may live under abs's old subtree
    245 	if f.Get(abs) != nil {
    246 		if err := f.Remove(abs); err != nil {
    247 			return err
    248 		}
    249 	}
    250 	if !n.Dir {
    251 		return f.Write(abs, n.Content)
    252 	}
    253 	if err := f.Mkdir(abs); err != nil {
    254 		return err
    255 	}
    256 	for _, c := range n.SortedChildren() {
    257 		if err := f.Copy(c, path.Join(abs, c.Name)); err != nil {
    258 			return err
    259 		}
    260 	}
    261 	return nil
    262 }
    263 
    264 // Count is the number of nodes in n's subtree, n included.
    265 func Count(n *Node) int {
    266 	if !n.Dir {
    267 		return 1
    268 	}
    269 	total := 1
    270 	for _, c := range n.Children {
    271 		total += Count(c)
    272 	}
    273 	return total
    274 }