Git backed by object storage because you can't stop me
git object-storage kefka
0

Configure Feed

Select the types of activity you want to include in your feed.

objgit / internal / mountfs / mountfs.go
6.7 kB 237 lines
1// Package mountfs composes several billy filesystems into one, dispatching by 2// the first path component. objgit uses it to give a hook sandbox a read-only 3// /src (a git tree) alongside a writable /tmp (an in-memory scratch fs) under a 4// single filesystem, since the kefka shell mounts exactly one. 5// 6// The root directory is virtual: it only lists the configured mount points and 7// cannot itself be written to. 8package mountfs 9 10import ( 11 "io/fs" 12 "os" 13 "path" 14 "sort" 15 "strings" 16 "time" 17 18 "github.com/go-git/go-billy/v6" 19) 20 21// FS routes operations to a mounted filesystem chosen by the leading path 22// component. 23type FS struct { 24 mounts map[string]billy.Filesystem 25 names []string // sorted mount names, for a stable root listing 26} 27 28// New builds a composite filesystem. Keys are top-level directory names (e.g. 29// "src", "tmp") without slashes. 30func New(mounts map[string]billy.Filesystem) *FS { 31 names := make([]string, 0, len(mounts)) 32 for name := range mounts { 33 names = append(names, name) 34 } 35 sort.Strings(names) 36 return &FS{mounts: mounts, names: names} 37} 38 39// route resolves p to a mount and a path relative to it. isRoot is true when p 40// addresses the virtual root itself. isMount is true when p addresses a mount 41// point exactly (rel == "."). 42func (f *FS) route(p string) (sub billy.Filesystem, rel string, isRoot, isMount bool, err error) { 43 clean := path.Clean("/" + p)[1:] // strip leading slash; root -> "" 44 if clean == "" { 45 return nil, "", true, false, nil 46 } 47 name, rest, _ := strings.Cut(clean, "/") 48 sub, ok := f.mounts[name] 49 if !ok { 50 return nil, "", false, false, fs.ErrNotExist 51 } 52 if rest == "" { 53 return sub, ".", false, true, nil 54 } 55 return sub, rest, false, false, nil 56} 57 58func pathErr(op, name string, err error) error { 59 return &os.PathError{Op: op, Path: name, Err: err} 60} 61 62func (f *FS) Open(filename string) (billy.File, error) { 63 return f.OpenFile(filename, os.O_RDONLY, 0) 64} 65 66func (f *FS) OpenFile(filename string, flag int, perm fs.FileMode) (billy.File, error) { 67 sub, rel, isRoot, isMount, err := f.route(filename) 68 if err != nil { 69 return nil, pathErr("open", filename, err) 70 } 71 if isRoot || isMount { 72 return nil, pathErr("open", filename, billy.ErrNotSupported) 73 } 74 return sub.OpenFile(rel, flag, perm) 75} 76 77func (f *FS) Stat(filename string) (fs.FileInfo, error) { 78 sub, rel, isRoot, isMount, err := f.route(filename) 79 if err != nil { 80 return nil, pathErr("stat", filename, err) 81 } 82 if isRoot { 83 return dirInfo{name: "/"}, nil 84 } 85 if isMount { 86 return dirInfo{name: path.Base(path.Clean("/" + filename))}, nil 87 } 88 return sub.Stat(rel) 89} 90 91func (f *FS) Lstat(filename string) (fs.FileInfo, error) { 92 sub, rel, isRoot, isMount, err := f.route(filename) 93 if err != nil { 94 return nil, pathErr("lstat", filename, err) 95 } 96 if isRoot || isMount { 97 return f.Stat(filename) 98 } 99 if sym, ok := sub.(billy.Symlink); ok { 100 return sym.Lstat(rel) 101 } 102 return sub.Stat(rel) 103} 104 105func (f *FS) ReadDir(p string) ([]fs.DirEntry, error) { 106 sub, rel, isRoot, _, err := f.route(p) 107 if err != nil { 108 return nil, pathErr("readdir", p, err) 109 } 110 if isRoot { 111 out := make([]fs.DirEntry, 0, len(f.names)) 112 for _, name := range f.names { 113 out = append(out, dirInfo{name: name}) 114 } 115 return out, nil 116 } 117 return sub.ReadDir(rel) 118} 119 120func (f *FS) Readlink(link string) (string, error) { 121 sub, rel, isRoot, isMount, err := f.route(link) 122 if err != nil { 123 return "", pathErr("readlink", link, err) 124 } 125 if isRoot || isMount { 126 return "", pathErr("readlink", link, billy.ErrNotSupported) 127 } 128 if sym, ok := sub.(billy.Symlink); ok { 129 return sym.Readlink(rel) 130 } 131 return "", pathErr("readlink", link, billy.ErrNotSupported) 132} 133 134func (f *FS) Symlink(target, link string) error { 135 sub, rel, isRoot, isMount, err := f.route(link) 136 if err != nil { 137 return pathErr("symlink", link, err) 138 } 139 if isRoot || isMount { 140 return pathErr("symlink", link, billy.ErrReadOnly) 141 } 142 if sym, ok := sub.(billy.Symlink); ok { 143 return sym.Symlink(target, rel) 144 } 145 return pathErr("symlink", link, billy.ErrNotSupported) 146} 147 148func (f *FS) Create(filename string) (billy.File, error) { 149 return f.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666) 150} 151 152func (f *FS) Remove(filename string) error { 153 sub, rel, isRoot, isMount, err := f.route(filename) 154 if err != nil { 155 return pathErr("remove", filename, err) 156 } 157 if isRoot || isMount { 158 return pathErr("remove", filename, billy.ErrReadOnly) 159 } 160 return sub.Remove(rel) 161} 162 163func (f *FS) MkdirAll(filename string, perm fs.FileMode) error { 164 sub, rel, isRoot, isMount, err := f.route(filename) 165 if err != nil { 166 return pathErr("mkdir", filename, err) 167 } 168 if isRoot || isMount { 169 return pathErr("mkdir", filename, billy.ErrReadOnly) 170 } 171 return sub.MkdirAll(rel, perm) 172} 173 174// Rename only works within a single mount. 175func (f *FS) Rename(oldpath, newpath string) error { 176 oldSub, oldRel, oldRoot, oldMount, err := f.route(oldpath) 177 if err != nil { 178 return pathErr("rename", oldpath, err) 179 } 180 newSub, newRel, newRoot, newMount, err := f.route(newpath) 181 if err != nil { 182 return pathErr("rename", newpath, err) 183 } 184 if oldRoot || oldMount || newRoot || newMount || oldSub != newSub { 185 return pathErr("rename", oldpath, billy.ErrNotSupported) 186 } 187 return oldSub.Rename(oldRel, newRel) 188} 189 190func (f *FS) TempFile(dir, prefix string) (billy.File, error) { 191 sub, rel, isRoot, _, err := f.route(dir) 192 if err != nil { 193 return nil, pathErr("tempfile", dir, err) 194 } 195 if isRoot { 196 return nil, pathErr("tempfile", dir, billy.ErrReadOnly) 197 } 198 return sub.TempFile(rel, prefix) 199} 200 201// Chroot returns the mounted filesystem (optionally further chrooted) so callers 202// that chroot into /src or /tmp keep working. 203func (f *FS) Chroot(p string) (billy.Filesystem, error) { 204 sub, rel, isRoot, isMount, err := f.route(p) 205 if err != nil { 206 return nil, pathErr("chroot", p, err) 207 } 208 if isRoot { 209 return f, nil 210 } 211 if isMount { 212 return sub, nil 213 } 214 return sub.Chroot(rel) 215} 216 217func (f *FS) Root() string { return "/" } 218 219func (f *FS) Join(elem ...string) string { return path.Join(elem...) } 220 221// dirInfo describes the virtual root and the mount-point directories. 222type dirInfo struct{ name string } 223 224func (d dirInfo) Name() string { return d.name } 225func (d dirInfo) Size() int64 { return 0 } 226func (d dirInfo) Mode() fs.FileMode { return fs.ModeDir | 0o755 } 227func (d dirInfo) ModTime() time.Time { return time.Time{} } 228func (d dirInfo) IsDir() bool { return true } 229func (d dirInfo) Sys() any { return nil } 230func (d dirInfo) Type() fs.FileMode { return fs.ModeDir } 231func (d dirInfo) Info() (fs.FileInfo, error) { return d, nil } 232 233var ( 234 _ billy.Filesystem = (*FS)(nil) 235 _ fs.FileInfo = dirInfo{} 236 _ fs.DirEntry = dirInfo{} 237)