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  }