|  | package afero | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "syscall" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | // 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 { | 
|  | origErr := err | 
|  | if e, ok := err.(*os.PathError); ok { | 
|  | err = e.Err | 
|  | } | 
|  | if err == syscall.ENOENT || err == syscall.ENOTDIR { | 
|  | return u.base.Stat(name) | 
|  | } | 
|  | return nil, origErr | 
|  | } | 
|  | return fi, nil | 
|  | } | 
|  |  | 
|  | // 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) | 
|  | } |