Path: blob/main/vendor/github.com/spf13/afero/copyOnWriteFs.go
2875 views
package afero12import (3"fmt"4"os"5"path/filepath"6"syscall"7"time"8)910var _ Lstater = (*CopyOnWriteFs)(nil)1112// The CopyOnWriteFs is a union filesystem: a read only base file system with13// a possibly writeable layer on top. Changes to the file system will only14// be made in the overlay: Changing an existing file in the base layer which15// is not present in the overlay will copy the file to the overlay ("changing"16// includes also calls to e.g. Chtimes(), Chmod() and Chown()).17//18// Reading directories is currently only supported via Open(), not OpenFile().19type CopyOnWriteFs struct {20base Fs21layer Fs22}2324func NewCopyOnWriteFs(base Fs, layer Fs) Fs {25return &CopyOnWriteFs{base: base, layer: layer}26}2728// Returns true if the file is not in the overlay29func (u *CopyOnWriteFs) isBaseFile(name string) (bool, error) {30if _, err := u.layer.Stat(name); err == nil {31return false, nil32}33_, err := u.base.Stat(name)34if err != nil {35if oerr, ok := err.(*os.PathError); ok {36if oerr.Err == os.ErrNotExist || oerr.Err == syscall.ENOENT ||37oerr.Err == syscall.ENOTDIR {38return false, nil39}40}41if err == syscall.ENOENT {42return false, nil43}44}45return true, err46}4748func (u *CopyOnWriteFs) copyToLayer(name string) error {49return copyToLayer(u.base, u.layer, name)50}5152func (u *CopyOnWriteFs) Chtimes(name string, atime, mtime time.Time) error {53b, err := u.isBaseFile(name)54if err != nil {55return err56}57if b {58if err := u.copyToLayer(name); err != nil {59return err60}61}62return u.layer.Chtimes(name, atime, mtime)63}6465func (u *CopyOnWriteFs) Chmod(name string, mode os.FileMode) error {66b, err := u.isBaseFile(name)67if err != nil {68return err69}70if b {71if err := u.copyToLayer(name); err != nil {72return err73}74}75return u.layer.Chmod(name, mode)76}7778func (u *CopyOnWriteFs) Chown(name string, uid, gid int) error {79b, err := u.isBaseFile(name)80if err != nil {81return err82}83if b {84if err := u.copyToLayer(name); err != nil {85return err86}87}88return u.layer.Chown(name, uid, gid)89}9091func (u *CopyOnWriteFs) Stat(name string) (os.FileInfo, error) {92fi, err := u.layer.Stat(name)93if err != nil {94isNotExist := u.isNotExist(err)95if isNotExist {96return u.base.Stat(name)97}98return nil, err99}100return fi, nil101}102103func (u *CopyOnWriteFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {104llayer, ok1 := u.layer.(Lstater)105lbase, ok2 := u.base.(Lstater)106107if ok1 {108fi, b, err := llayer.LstatIfPossible(name)109if err == nil {110return fi, b, nil111}112113if !u.isNotExist(err) {114return nil, b, err115}116}117118if ok2 {119fi, b, err := lbase.LstatIfPossible(name)120if err == nil {121return fi, b, nil122}123if !u.isNotExist(err) {124return nil, b, err125}126}127128fi, err := u.Stat(name)129130return fi, false, err131}132133func (u *CopyOnWriteFs) SymlinkIfPossible(oldname, newname string) error {134if slayer, ok := u.layer.(Linker); ok {135return slayer.SymlinkIfPossible(oldname, newname)136}137138return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: ErrNoSymlink}139}140141func (u *CopyOnWriteFs) ReadlinkIfPossible(name string) (string, error) {142if rlayer, ok := u.layer.(LinkReader); ok {143return rlayer.ReadlinkIfPossible(name)144}145146if rbase, ok := u.base.(LinkReader); ok {147return rbase.ReadlinkIfPossible(name)148}149150return "", &os.PathError{Op: "readlink", Path: name, Err: ErrNoReadlink}151}152153func (u *CopyOnWriteFs) isNotExist(err error) bool {154if e, ok := err.(*os.PathError); ok {155err = e.Err156}157if err == os.ErrNotExist || err == syscall.ENOENT || err == syscall.ENOTDIR {158return true159}160return false161}162163// Renaming files present only in the base layer is not permitted164func (u *CopyOnWriteFs) Rename(oldname, newname string) error {165b, err := u.isBaseFile(oldname)166if err != nil {167return err168}169if b {170return syscall.EPERM171}172return u.layer.Rename(oldname, newname)173}174175// Removing files present only in the base layer is not permitted. If176// a file is present in the base layer and the overlay, only the overlay177// will be removed.178func (u *CopyOnWriteFs) Remove(name string) error {179err := u.layer.Remove(name)180switch err {181case syscall.ENOENT:182_, err = u.base.Stat(name)183if err == nil {184return syscall.EPERM185}186return syscall.ENOENT187default:188return err189}190}191192func (u *CopyOnWriteFs) RemoveAll(name string) error {193err := u.layer.RemoveAll(name)194switch err {195case syscall.ENOENT:196_, err = u.base.Stat(name)197if err == nil {198return syscall.EPERM199}200return syscall.ENOENT201default:202return err203}204}205206func (u *CopyOnWriteFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {207b, err := u.isBaseFile(name)208if err != nil {209return nil, err210}211212if flag&(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 {213if b {214if err = u.copyToLayer(name); err != nil {215return nil, err216}217return u.layer.OpenFile(name, flag, perm)218}219220dir := filepath.Dir(name)221isaDir, err := IsDir(u.base, dir)222if err != nil && !os.IsNotExist(err) {223return nil, err224}225if isaDir {226if err = u.layer.MkdirAll(dir, 0o777); err != nil {227return nil, err228}229return u.layer.OpenFile(name, flag, perm)230}231232isaDir, err = IsDir(u.layer, dir)233if err != nil {234return nil, err235}236if isaDir {237return u.layer.OpenFile(name, flag, perm)238}239240return nil, &os.PathError{241Op: "open",242Path: name,243Err: syscall.ENOTDIR,244} // ...or os.ErrNotExist?245}246if b {247return u.base.OpenFile(name, flag, perm)248}249return u.layer.OpenFile(name, flag, perm)250}251252// This function handles the 9 different possibilities caused253// by the union which are the intersection of the following...254//255// layer: doesn't exist, exists as a file, and exists as a directory256// base: doesn't exist, exists as a file, and exists as a directory257func (u *CopyOnWriteFs) Open(name string) (File, error) {258// Since the overlay overrides the base we check that first259b, err := u.isBaseFile(name)260if err != nil {261return nil, err262}263264// If overlay doesn't exist, return the base (base state irrelevant)265if b {266return u.base.Open(name)267}268269// If overlay is a file, return it (base state irrelevant)270dir, err := IsDir(u.layer, name)271if err != nil {272return nil, err273}274if !dir {275return u.layer.Open(name)276}277278// Overlay is a directory, base state now matters.279// Base state has 3 states to check but 2 outcomes:280// A. It's a file or non-readable in the base (return just the overlay)281// B. It's an accessible directory in the base (return a UnionFile)282283// If base is file or nonreadable, return overlay284dir, err = IsDir(u.base, name)285if !dir || err != nil {286return u.layer.Open(name)287}288289// Both base & layer are directories290// Return union file (if opens are without error)291bfile, bErr := u.base.Open(name)292lfile, lErr := u.layer.Open(name)293294// If either have errors at this point something is very wrong. Return nil and the errors295if bErr != nil || lErr != nil {296return nil, fmt.Errorf("BaseErr: %v\nOverlayErr: %v", bErr, lErr)297}298299return &UnionFile{Base: bfile, Layer: lfile}, nil300}301302func (u *CopyOnWriteFs) Mkdir(name string, perm os.FileMode) error {303dir, err := IsDir(u.base, name)304if err != nil {305return u.layer.MkdirAll(name, perm)306}307if dir {308return ErrFileExists309}310return u.layer.MkdirAll(name, perm)311}312313func (u *CopyOnWriteFs) Name() string {314return "CopyOnWriteFs"315}316317func (u *CopyOnWriteFs) MkdirAll(name string, perm os.FileMode) error {318dir, err := IsDir(u.base, name)319if err != nil {320return u.layer.MkdirAll(name, perm)321}322if dir {323// This is in line with how os.MkdirAll behaves.324return nil325}326return u.layer.MkdirAll(name, perm)327}328329func (u *CopyOnWriteFs) Create(name string) (File, error) {330return u.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0o666)331}332333334