| package afero |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| "syscall" |
| "time" |
| ) |
| |
| var _ Lstater = (*CopyOnWriteFs)(nil) |
| |
| // The CopyOnWriteFs is a union filesystem: a read only base file system with |
| // a possibly writeable layer on top. Changes to the file system will only |
| // be made in the overlay: Changing an existing file in the base layer which |
| // is not present in the overlay will copy the file to the overlay ("changing" |
| // includes also calls to e.g. Chtimes() and Chmod()). |
| // |
| // Reading directories is currently only supported via Open(), not OpenFile(). |
| type CopyOnWriteFs struct { |
| base Fs |
| layer Fs |
| } |
| |
| func NewCopyOnWriteFs(base Fs, layer Fs) Fs { |
| return &CopyOnWriteFs{base: base, layer: layer} |
| } |
| |
| // Returns true if the file is not in the overlay |
| func (u *CopyOnWriteFs) isBaseFile(name string) (bool, error) { |
| if _, err := u.layer.Stat(name); err == nil { |
| return false, nil |
| } |
| _, err := u.base.Stat(name) |
| if err != nil { |
| if oerr, ok := err.(*os.PathError); ok { |
| if oerr.Err == os.ErrNotExist || oerr.Err == syscall.ENOENT || oerr.Err == syscall.ENOTDIR { |
| return false, nil |
| } |
| } |
| if err == syscall.ENOENT { |
| return false, nil |
| } |
| } |
| return true, err |
| } |
| |
| func (u *CopyOnWriteFs) copyToLayer(name string) error { |
| return copyToLayer(u.base, u.layer, name) |
| } |
| |
| func (u *CopyOnWriteFs) Chtimes(name string, atime, mtime time.Time) error { |
| b, err := u.isBaseFile(name) |
| if err != nil { |
| return err |
| } |
| if b { |
| if err := u.copyToLayer(name); err != nil { |
| return err |
| } |
| } |
| return u.layer.Chtimes(name, atime, mtime) |
| } |
| |
| func (u *CopyOnWriteFs) Chmod(name string, mode os.FileMode) error { |
| b, err := u.isBaseFile(name) |
| if err != nil { |
| return err |
| } |
| if b { |
| if err := u.copyToLayer(name); err != nil { |
| return err |
| } |
| } |
| return u.layer.Chmod(name, mode) |
| } |
| |
| func (u *CopyOnWriteFs) Stat(name string) (os.FileInfo, error) { |
| fi, err := u.layer.Stat(name) |
| if err != nil { |
| isNotExist := u.isNotExist(err) |
| if isNotExist { |
| return u.base.Stat(name) |
| } |
| return nil, err |
| } |
| return fi, nil |
| } |
| |
| func (u *CopyOnWriteFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { |
| llayer, ok1 := u.layer.(Lstater) |
| lbase, ok2 := u.base.(Lstater) |
| |
| if ok1 { |
| fi, b, err := llayer.LstatIfPossible(name) |
| if err == nil { |
| return fi, b, nil |
| } |
| |
| if !u.isNotExist(err) { |
| return nil, b, err |
| } |
| } |
| |
| if ok2 { |
| fi, b, err := lbase.LstatIfPossible(name) |
| if err == nil { |
| return fi, b, nil |
| } |
| if !u.isNotExist(err) { |
| return nil, b, err |
| } |
| } |
| |
| fi, err := u.Stat(name) |
| |
| return fi, false, err |
| } |
| |
| func (u *CopyOnWriteFs) isNotExist(err error) bool { |
| if e, ok := err.(*os.PathError); ok { |
| err = e.Err |
| } |
| if err == os.ErrNotExist || err == syscall.ENOENT || err == syscall.ENOTDIR { |
| return true |
| } |
| return false |
| } |
| |
| // Renaming files present only in the base layer is not permitted |
| func (u *CopyOnWriteFs) Rename(oldname, newname string) error { |
| b, err := u.isBaseFile(oldname) |
| if err != nil { |
| return err |
| } |
| if b { |
| return syscall.EPERM |
| } |
| return u.layer.Rename(oldname, newname) |
| } |
| |
| // Removing files present only in the base layer is not permitted. If |
| // a file is present in the base layer and the overlay, only the overlay |
| // will be removed. |
| func (u *CopyOnWriteFs) Remove(name string) error { |
| err := u.layer.Remove(name) |
| switch err { |
| case syscall.ENOENT: |
| _, err = u.base.Stat(name) |
| if err == nil { |
| return syscall.EPERM |
| } |
| return syscall.ENOENT |
| default: |
| return err |
| } |
| } |
| |
| func (u *CopyOnWriteFs) RemoveAll(name string) error { |
| err := u.layer.RemoveAll(name) |
| switch err { |
| case syscall.ENOENT: |
| _, err = u.base.Stat(name) |
| if err == nil { |
| return syscall.EPERM |
| } |
| return syscall.ENOENT |
| default: |
| return err |
| } |
| } |
| |
| func (u *CopyOnWriteFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { |
| b, err := u.isBaseFile(name) |
| if err != nil { |
| return nil, err |
| } |
| |
| if flag&(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 { |
| if b { |
| if err = u.copyToLayer(name); err != nil { |
| return nil, err |
| } |
| return u.layer.OpenFile(name, flag, perm) |
| } |
| |
| dir := filepath.Dir(name) |
| isaDir, err := IsDir(u.base, dir) |
| if err != nil && !os.IsNotExist(err) { |
| return nil, err |
| } |
| if isaDir { |
| if err = u.layer.MkdirAll(dir, 0777); err != nil { |
| return nil, err |
| } |
| return u.layer.OpenFile(name, flag, perm) |
| } |
| |
| isaDir, err = IsDir(u.layer, dir) |
| if err != nil { |
| return nil, err |
| } |
| if isaDir { |
| return u.layer.OpenFile(name, flag, perm) |
| } |
| |
| return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOTDIR} // ...or os.ErrNotExist? |
| } |
| if b { |
| return u.base.OpenFile(name, flag, perm) |
| } |
| return u.layer.OpenFile(name, flag, perm) |
| } |
| |
| // This function handles the 9 different possibilities caused |
| // by the union which are the intersection of the following... |
| // layer: doesn't exist, exists as a file, and exists as a directory |
| // base: doesn't exist, exists as a file, and exists as a directory |
| func (u *CopyOnWriteFs) Open(name string) (File, error) { |
| // Since the overlay overrides the base we check that first |
| b, err := u.isBaseFile(name) |
| if err != nil { |
| return nil, err |
| } |
| |
| // If overlay doesn't exist, return the base (base state irrelevant) |
| if b { |
| return u.base.Open(name) |
| } |
| |
| // If overlay is a file, return it (base state irrelevant) |
| dir, err := IsDir(u.layer, name) |
| if err != nil { |
| return nil, err |
| } |
| if !dir { |
| return u.layer.Open(name) |
| } |
| |
| // Overlay is a directory, base state now matters. |
| // Base state has 3 states to check but 2 outcomes: |
| // A. It's a file or non-readable in the base (return just the overlay) |
| // B. It's an accessible directory in the base (return a UnionFile) |
| |
| // If base is file or nonreadable, return overlay |
| dir, err = IsDir(u.base, name) |
| if !dir || err != nil { |
| return u.layer.Open(name) |
| } |
| |
| // Both base & layer are directories |
| // Return union file (if opens are without error) |
| bfile, bErr := u.base.Open(name) |
| lfile, lErr := u.layer.Open(name) |
| |
| // If either have errors at this point something is very wrong. Return nil and the errors |
| if bErr != nil || lErr != nil { |
| return nil, fmt.Errorf("BaseErr: %v\nOverlayErr: %v", bErr, lErr) |
| } |
| |
| return &UnionFile{Base: bfile, Layer: lfile}, nil |
| } |
| |
| func (u *CopyOnWriteFs) Mkdir(name string, perm os.FileMode) error { |
| dir, err := IsDir(u.base, name) |
| if err != nil { |
| return u.layer.MkdirAll(name, perm) |
| } |
| if dir { |
| return syscall.EEXIST |
| } |
| return u.layer.MkdirAll(name, perm) |
| } |
| |
| func (u *CopyOnWriteFs) Name() string { |
| return "CopyOnWriteFs" |
| } |
| |
| func (u *CopyOnWriteFs) MkdirAll(name string, perm os.FileMode) error { |
| dir, err := IsDir(u.base, name) |
| if err != nil { |
| return u.layer.MkdirAll(name, perm) |
| } |
| if dir { |
| return syscall.EEXIST |
| } |
| return u.layer.MkdirAll(name, perm) |
| } |
| |
| func (u *CopyOnWriteFs) Create(name string) (File, error) { |
| return u.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666) |
| } |