tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/fs/unionfs/fs.go (about) 1 package unionfs 2 3 import ( 4 "fmt" 5 "io/fs" 6 "os" 7 "syscall" 8 "time" 9 10 "tractor.dev/toolkit-go/engine/fs/fsutil" 11 "tractor.dev/toolkit-go/engine/fs/watchfs" 12 ) 13 14 type FS struct { 15 base fs.FS 16 overlay fs.FS 17 } 18 19 func New(base, overlay fs.FS) *FS { 20 return &FS{ 21 base: base, 22 overlay: overlay, 23 } 24 } 25 26 func isNotExist(err error) bool { 27 if e, ok := err.(*os.PathError); ok { 28 err = e.Err 29 } 30 if err == os.ErrNotExist || err == syscall.ENOENT || err == syscall.ENOTDIR { 31 return true 32 } 33 return false 34 } 35 36 func (u *FS) isBaseFile(name string) (bool, error) { 37 if _, err := fs.Stat(u.overlay, name); err == nil { 38 return false, nil 39 } 40 _, err := fs.Stat(u.base, name) 41 if err != nil { 42 if oerr, ok := err.(*os.PathError); ok { 43 if oerr.Err == os.ErrNotExist || oerr.Err == syscall.ENOENT || oerr.Err == syscall.ENOTDIR { 44 return false, nil 45 } 46 } 47 if err == syscall.ENOENT { 48 return false, nil 49 } 50 } 51 return true, err 52 } 53 54 // This function handles the 9 different possibilities caused 55 // by the union which are the intersection of the following... 56 // 57 // layer: doesn't exist, exists as a file, and exists as a directory 58 // base: doesn't exist, exists as a file, and exists as a directory 59 func (u *FS) Open(name string) (fs.File, error) { 60 // Since the overlay overrides the base we check that first 61 b, err := u.isBaseFile(name) 62 if err != nil { 63 return nil, err 64 } 65 66 // If overlay doesn't exist, return the base (base state irrelevant) 67 if b { 68 return u.base.Open(name) 69 } 70 71 // If overlay is a file, return it (base state irrelevant) 72 dir, err := fsutil.IsDir(u.overlay, name) 73 if err != nil { 74 return nil, err 75 } 76 if !dir { 77 return u.overlay.Open(name) 78 } 79 80 // Overlay is a directory, base state now matters. 81 // Base state has 3 states to check but 2 outcomes: 82 // A. It's a file or non-readable in the base (return just the overlay) 83 // B. It's an accessible directory in the base (return a UnionFile) 84 85 // If base is file or nonreadable, return overlay 86 dir, err = fsutil.IsDir(u.base, name) 87 if !dir || err != nil { 88 return u.overlay.Open(name) 89 } 90 91 // Both base & layer are directories 92 // Return union file (if opens are without error) 93 bfile, bErr := u.base.Open(name) 94 lfile, lErr := u.overlay.Open(name) 95 96 // If either have errors at this point something is very wrong. Return nil and the errors 97 if bErr != nil || lErr != nil { 98 return nil, fmt.Errorf("BaseErr: %v\nOverlayErr: %v", bErr, lErr) 99 } 100 101 return &File{Base: bfile, Layer: lfile}, nil 102 } 103 104 func (u *FS) OpenFile(name string, flag int, perm os.FileMode) (fs.File, error) { 105 b, err := u.isBaseFile(name) 106 if err != nil { 107 return nil, err 108 } 109 110 if flag&(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 { 111 return nil, fs.ErrPermission 112 } 113 if b { 114 of, ok := u.base.(interface { 115 OpenFile(name string, flag int, perm fs.FileMode) (fs.File, error) 116 }) 117 if !ok { 118 return nil, fs.ErrPermission 119 } 120 return of.OpenFile(name, flag, perm) 121 } 122 of, ok := u.overlay.(interface { 123 OpenFile(name string, flag int, perm fs.FileMode) (fs.File, error) 124 }) 125 if !ok { 126 return nil, fs.ErrPermission 127 } 128 return of.OpenFile(name, flag, perm) 129 } 130 131 func (u *FS) Stat(name string) (fi fs.FileInfo, err error) { 132 fi, err = fs.Stat(u.overlay, name) 133 if err != nil { 134 if isNotExist(err) { 135 return fs.Stat(u.base, name) 136 } 137 return nil, err 138 } 139 return fi, nil 140 } 141 142 func (u *FS) Watch(name string, cfg *watchfs.Config) (*watchfs.Watch, error) { 143 w, err := watch(u.overlay, name, cfg) 144 if err != nil { 145 if isNotExist(err) { 146 return watch(u.base, name, cfg) 147 } 148 return nil, err 149 } 150 return w, nil 151 } 152 153 func (fs *FS) Create(name string) (fs.File, error) { 154 return nil, syscall.EPERM 155 } 156 157 func (fs *FS) Mkdir(name string, perm os.FileMode) error { 158 return syscall.EPERM 159 } 160 161 func (fs *FS) MkdirAll(path string, perm os.FileMode) error { 162 return syscall.EPERM 163 } 164 165 func (fs *FS) Remove(name string) error { 166 return syscall.EPERM 167 } 168 169 func (fs *FS) RemoveAll(path string) error { 170 return syscall.EPERM 171 } 172 173 func (fs *FS) Rename(oldname, newname string) error { 174 return syscall.EPERM 175 } 176 177 func (fs *FS) Chmod(name string, mode os.FileMode) error { 178 return syscall.EPERM 179 } 180 181 func (fs *FS) Chown(name string, uid, gid int) error { 182 return syscall.EPERM 183 } 184 185 func (fs *FS) Chtimes(name string, atime time.Time, mtime time.Time) error { 186 return syscall.EPERM 187 } 188 189 type watchFS interface { 190 Watch(name string, cfg *watchfs.Config) (*watchfs.Watch, error) 191 } 192 193 func watch(fsys fs.FS, name string, cfg *watchfs.Config) (*watchfs.Watch, error) { 194 if fsys, ok := fsys.(watchFS); ok { 195 return fsys.Watch(name, cfg) 196 } 197 198 return nil, fmt.Errorf("watch %s: operation not supported", name) 199 }