diff --git a/backupfs.go b/backupfs.go index 659ea6a..dc50fa8 100644 --- a/backupfs.go +++ b/backupfs.go @@ -520,7 +520,11 @@ func (fsys *BackupFS) tryRemoveBackup(name string) (err error) { } }() // remove file/symlink - return fsys.backup.Remove(name) + err := fsys.backup.Remove(name) + if err != nil { + return err + } + return nil } dirs := make([]string, 0) @@ -548,7 +552,11 @@ func (fsys *BackupFS) tryRemoveBackup(name string) (err error) { } // delete files - return fsys.backup.Remove(path) + err = fsys.backup.Remove(path) + if err != nil { + return err + } + return nil }) if err != nil { return err @@ -715,7 +723,11 @@ func (fsys *BackupFS) OpenFile(name string, flag int, perm fs.FileMode) (File, e if flag == os.O_RDONLY { // in read only mode the perm is not used. - return fsys.base.OpenFile(name, os.O_RDONLY, 0) + f, err := fsys.base.OpenFile(name, os.O_RDONLY, 0) + if err != nil { + return nil, &os.PathError{Op: "open", Path: name, Err: fmt.Errorf("open failed: %w", err)} + } + return f, nil } // not read only opening -> backup diff --git a/hidden_utils.go b/hidden_utils.go deleted file mode 100644 index 4970a17..0000000 --- a/hidden_utils.go +++ /dev/null @@ -1,102 +0,0 @@ -package backupfs - -import ( - "io/fs" - "os" - "path/filepath" - "strings" -) - -func isParentOfHiddenDir(name string, hiddenPaths []string) (bool, error) { - if len(hiddenPaths) == 0 { - return false, nil - } - - // file normalization allows to use a single filepath separator - name = filepath.Clean(filepath.FromSlash(name)) - - for _, hiddenDir := range hiddenPaths { - isParentOfHiddenDir, err := dirContains(name, hiddenDir) - if err != nil { - return false, err - } - if isParentOfHiddenDir { - return true, nil - } - - } - return false, nil -} - -const relParent = ".." + string(os.PathSeparator) - -func dirContains(parent, subdir string) (bool, error) { - relPath, err := filepath.Rel(parent, subdir) - if err != nil { - return false, err - } - relPath = filepath.FromSlash(relPath) - - isSameDir := relPath == "." - outsideOfparentDir := strings.HasPrefix(relPath, relParent) || relPath == ".." - - return !isSameDir && !outsideOfparentDir, nil -} - -func isInHiddenPath(name, hiddenDir string) (relPath string, inHiddenPath bool, err error) { - relPath, err = filepath.Rel(hiddenDir, name) - if err != nil { - return "", false, &os.PathError{Op: "is_hidden", Path: name, Err: err} - } - - relPath = filepath.FromSlash(relPath) - - // no ../ prefix - // -> does not lie outside of hidden dir - outsideOfHiddenDir := strings.HasPrefix(relPath, relParent) - isParentDir := relPath == ".." - isHiddenDir := relPath == "." - - if !isHiddenDir && (outsideOfHiddenDir || isParentDir) { - return relPath, false, nil - } - - return relPath, true, nil -} - -// hiddenPaths should be normalized (filepath.Clean result values) -func isHidden(name string, hiddenPaths []string) (bool, error) { - if len(hiddenPaths) == 0 { - return false, nil - } - - // file normalization allows to use a single filepath separator - name = filepath.Clean(filepath.FromSlash(name)) - - for _, hiddenDir := range hiddenPaths { - _, hidden, err := isInHiddenPath(name, hiddenDir) - if err != nil { - return false, err - } - if hidden { - return true, nil - } - } - return false, nil -} - -func allFiles(fsys FS, dir string) ([]string, error) { - files := make([]string, 0) - - err := Walk(fsys, dir, func(path string, info fs.FileInfo, err error) error { - if err != nil { - return err - } - files = append(files, path) - return nil - }) - if err != nil { - return nil, err - } - return files, nil -} diff --git a/hiddenfs.go b/hiddenfs.go index d0911b3..5260cd9 100644 --- a/hiddenfs.go +++ b/hiddenfs.go @@ -7,6 +7,7 @@ import ( "path" "path/filepath" "sort" + "strings" "time" ) @@ -32,6 +33,7 @@ func NewHiddenFS(base FS, hiddenPaths ...string) *HiddenFS { normalizedHiddenPaths = append(normalizedHiddenPaths, filepath.Clean(filepath.FromSlash(p))) } + sort.Sort(byMostFilePathSeparators(normalizedHiddenPaths)) return &HiddenFS{ base: base, hiddenPaths: normalizedHiddenPaths, @@ -90,7 +92,11 @@ func (s *HiddenFS) Mkdir(name string, perm fs.FileMode) error { if hidden { return &os.PathError{Op: "mkdir", Path: name, Err: ErrHiddenPermission} } - return s.base.Mkdir(name, perm) + err = s.base.Mkdir(name, perm) + if err != nil { + return err + } + return nil } // MkdirAll creates a directory path and all parents that does not exist @@ -128,11 +134,11 @@ func (s *HiddenFS) OpenFile(name string, flag int, perm fs.FileMode) (File, erro return nil, &os.PathError{Op: "open", Path: name, Err: ErrHiddenNotExist} } f, err := s.base.OpenFile(name, flag, perm) - if err != nil || f == nil { + if err != nil { return nil, err } - return newHiddenFSFile(f, name, s.hiddenPaths), nil + return newHiddenFile(f, name, s.hiddenPaths), nil } // Remove removes a file identified by name, returning an error, if any @@ -146,7 +152,11 @@ func (s *HiddenFS) Remove(name string) error { return &os.PathError{Op: "remove", Path: name, Err: ErrHiddenNotExist} } - return s.base.Remove(name) + err = s.base.Remove(name) + if err != nil { + return err + } + return nil } // RemoveAll removes a directory path and any children it contains. It @@ -200,7 +210,11 @@ func (s *HiddenFS) RemoveAll(name string) error { } // file or symlink or whatever else - return s.Remove(path) + err = s.Remove(path) + if err != nil { + return err + } + return nil }) if err != nil { return &os.PathError{Op: "remove_all", Path: name, Err: err} @@ -244,7 +258,11 @@ func (s *HiddenFS) Rename(oldname, newname string) error { return &os.PathError{Op: "rename", Path: newname, Err: ErrHiddenPermission} } - return s.base.Rename(oldname, newname) + err = s.base.Rename(oldname, newname) + if err != nil { + return err + } + return nil } // Stat returns a FileInfo describing the named file, or an error, if any @@ -257,7 +275,11 @@ func (s *HiddenFS) Stat(name string) (fs.FileInfo, error) { if hidden { return nil, &os.PathError{Op: "stat", Path: name, Err: ErrHiddenNotExist} } - return s.base.Stat(name) + fi, err := s.base.Stat(name) + if err != nil { + return nil, err + } + return fi, nil } // The name of this FileSystem @@ -275,7 +297,11 @@ func (s *HiddenFS) Chmod(name string, mode fs.FileMode) error { return &os.PathError{Op: "chmod", Path: name, Err: ErrHiddenNotExist} } - return s.base.Chmod(name, mode) + err = s.base.Chmod(name, mode) + if err != nil { + return err + } + return nil } // Chown changes the uid and gid of the named file. @@ -287,7 +313,11 @@ func (s *HiddenFS) Chown(name string, uid, gid int) error { if hidden { return &os.PathError{Op: "chown", Path: name, Err: ErrHiddenNotExist} } - return s.base.Chown(name, uid, gid) + err = s.base.Chown(name, uid, gid) + if err != nil { + return err + } + return nil } // Chtimes changes the access and modification times of the named file @@ -299,7 +329,11 @@ func (s *HiddenFS) Chtimes(name string, atime, mtime time.Time) error { if hidden { return &os.PathError{Op: "chtimes", Path: name, Err: ErrHiddenNotExist} } - return s.base.Chtimes(name, atime, mtime) + err = s.base.Chtimes(name, atime, mtime) + if err != nil { + return err + } + return nil } // Lstat will call Lstat if the filesystem itself is, or it delegates to, the os filesystem. @@ -313,7 +347,11 @@ func (s *HiddenFS) Lstat(name string) (fs.FileInfo, error) { if hidden { return nil, &os.PathError{Op: "lstat", Path: name, Err: ErrHiddenNotExist} } - return s.base.Lstat(name) + fi, err := s.base.Lstat(name) + if err != nil { + return nil, err + } + return fi, nil } // Symlink changes the access and modification times of the named file @@ -351,7 +389,11 @@ func (s *HiddenFS) Symlink(oldname, newname string) error { return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: ErrHiddenPermission} } - return s.base.Symlink(oldname, newname) + err = s.base.Symlink(oldname, newname) + if err != nil { + return err + } + return nil } func (s *HiddenFS) Readlink(name string) (string, error) { @@ -363,7 +405,11 @@ func (s *HiddenFS) Readlink(name string) (string, error) { if hidden { return "", &os.PathError{Op: "readlink", Path: name, Err: ErrHiddenNotExist} } - return s.base.Readlink(name) + link, err := s.base.Readlink(name) + if err != nil { + return "", err + } + return link, nil } func (s *HiddenFS) Lchown(name string, uid, gid int) error { @@ -375,5 +421,103 @@ func (s *HiddenFS) Lchown(name string, uid, gid int) error { return &os.PathError{Op: "lchown", Path: name, Err: ErrHiddenNotExist} } - return s.base.Lchown(name, uid, gid) + err = s.base.Lchown(name, uid, gid) + if err != nil { + return err + } + return nil +} + +func isParentOfHiddenDir(name string, hiddenPaths []string) (bool, error) { + if len(hiddenPaths) == 0 { + return false, nil + } + + // file normalization allows to use a single filepath separator + name = filepath.Clean(filepath.FromSlash(name)) + + for _, hiddenDir := range hiddenPaths { + isParentOfHiddenDir, err := dirContains(name, hiddenDir) + if err != nil { + return false, err + } + if isParentOfHiddenDir { + return true, nil + } + + } + return false, nil +} + +const relParent = ".." + string(os.PathSeparator) + +func dirContains(parent, subdir string) (bool, error) { + relPath, err := filepath.Rel(parent, subdir) + if err != nil { + return false, err + } + relPath = filepath.FromSlash(relPath) + + isSameDir := relPath == "." + outsideOfparentDir := strings.HasPrefix(relPath, relParent) || relPath == ".." + + return !isSameDir && !outsideOfparentDir, nil +} + +func isInHiddenPath(name, hiddenDir string) (relPath string, inHiddenPath bool, err error) { + relPath, err = filepath.Rel(hiddenDir, name) + if err != nil { + return "", false, &os.PathError{Op: "is_hidden", Path: name, Err: err} + } + + relPath = filepath.FromSlash(relPath) + + // no ../ prefix + // -> does not lie outside of hidden dir + outsideOfHiddenDir := strings.HasPrefix(relPath, relParent) + isParentDir := relPath == ".." + isHiddenDir := relPath == "." + + if !isHiddenDir && (outsideOfHiddenDir || isParentDir) { + return relPath, false, nil + } + + return relPath, true, nil +} + +// hiddenPaths should be normalized (filepath.Clean result values) +func isHidden(name string, hiddenPaths []string) (bool, error) { + if len(hiddenPaths) == 0 { + return false, nil + } + + // file normalization allows to use a single filepath separator + name = filepath.Clean(filepath.FromSlash(name)) + + for _, hiddenDir := range hiddenPaths { + _, hidden, err := isInHiddenPath(name, hiddenDir) + if err != nil { + return false, err + } + if hidden { + return true, nil + } + } + return false, nil +} + +func allFiles(fsys FS, dir string) ([]string, error) { + files := make([]string, 0) + + err := Walk(fsys, dir, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + files = append(files, path) + return nil + }) + if err != nil { + return nil, err + } + return files, nil } diff --git a/hiddenfs_file.go b/hiddenfs_file.go index 6362789..656ce57 100644 --- a/hiddenfs_file.go +++ b/hiddenfs_file.go @@ -7,26 +7,26 @@ import ( "path/filepath" ) -var _ File = (*hiddenFSFile)(nil) +var _ File = (*hiddenFile)(nil) -func newHiddenFSFile(f File, filePath string, hiddenPaths []string) *hiddenFSFile { - return &hiddenFSFile{ +func newHiddenFile(f File, filePath string, hiddenPaths []string) *hiddenFile { + return &hiddenFile{ filePath: filePath, f: f, hiddenPaths: hiddenPaths, } } -type hiddenFSFile struct { +type hiddenFile struct { f File filePath string hiddenPaths []string } -func (hf *hiddenFSFile) Name() string { +func (hf *hiddenFile) Name() string { return hf.f.Name() } -func (hf *hiddenFSFile) Readdir(count int) ([]fs.FileInfo, error) { +func (hf *hiddenFile) Readdir(count int) ([]fs.FileInfo, error) { var availableFiles []fs.FileInfo if count > 0 { availableFiles = make([]fs.FileInfo, 0, count) @@ -78,7 +78,7 @@ func (hf *hiddenFSFile) Readdir(count int) ([]fs.FileInfo, error) { return availableFiles, nil } -func (hf *hiddenFSFile) Readdirnames(count int) ([]string, error) { +func (hf *hiddenFile) Readdirnames(count int) ([]string, error) { var availableFiles []string if count > 0 { availableFiles = make([]string, 0, count) @@ -131,39 +131,39 @@ func (hf *hiddenFSFile) Readdirnames(count int) ([]string, error) { return availableFiles, nil } -func (hf *hiddenFSFile) Stat() (fs.FileInfo, error) { +func (hf *hiddenFile) Stat() (fs.FileInfo, error) { return hf.f.Stat() } -func (hf *hiddenFSFile) Sync() error { +func (hf *hiddenFile) Sync() error { return hf.f.Sync() } -func (hf *hiddenFSFile) Truncate(size int64) error { +func (hf *hiddenFile) Truncate(size int64) error { return hf.f.Truncate(size) } -func (hf *hiddenFSFile) WriteString(s string) (ret int, err error) { +func (hf *hiddenFile) WriteString(s string) (ret int, err error) { return hf.f.WriteString(s) } -func (hf *hiddenFSFile) Close() error { +func (hf *hiddenFile) Close() error { return hf.f.Close() } -func (hf *hiddenFSFile) Read(p []byte) (n int, err error) { +func (hf *hiddenFile) Read(p []byte) (n int, err error) { return hf.f.Read(p) } -func (hf *hiddenFSFile) ReadAt(p []byte, off int64) (n int, err error) { +func (hf *hiddenFile) ReadAt(p []byte, off int64) (n int, err error) { return hf.f.ReadAt(p, off) } -func (hf *hiddenFSFile) Seek(offset int64, whence int) (int64, error) { +func (hf *hiddenFile) Seek(offset int64, whence int) (int64, error) { return hf.f.Seek(offset, whence) } -func (hf *hiddenFSFile) Write(p []byte) (n int, err error) { +func (hf *hiddenFile) Write(p []byte) (n int, err error) { return hf.f.Write(p) } -func (hf *hiddenFSFile) WriteAt(p []byte, off int64) (n int, err error) { +func (hf *hiddenFile) WriteAt(p []byte, off int64) (n int, err error) { return hf.f.WriteAt(p, off) } diff --git a/osfs.go b/osfs.go index 131ca70..8d75634 100644 --- a/osfs.go +++ b/osfs.go @@ -19,52 +19,88 @@ type OSFS struct{} // Create creates a file in the filesystem, returning the file and an // error, if any happens. func (OSFS) Create(name string) (File, error) { - return os.Create(name) + f, err := os.Create(name) + if err != nil { + return nil, err + } + return f, nil } // Mkdir creates a directory in the filesystem, return an error if any // happens. func (OSFS) Mkdir(name string, perm fs.FileMode) error { - return os.Mkdir(name, perm) + err := os.Mkdir(name, perm) + if err != nil { + return err + } + return nil } // MkdirAll creates a directory path and all parents that does not exist // yet. func (OSFS) MkdirAll(path string, perm fs.FileMode) error { - return os.MkdirAll(path, perm) + err := os.MkdirAll(path, perm) + if err != nil { + return err + } + return nil } // Open opens a file, returning it or an error, if any happens. func (OSFS) Open(name string) (File, error) { - return os.Open(name) + f, err := os.Open(name) + if err != nil { + return nil, err + } + return f, nil } // OpenFile opens a file using the given flags and the given mode. func (OSFS) OpenFile(name string, flag int, perm fs.FileMode) (File, error) { - return os.OpenFile(name, flag, perm) + f, err := os.OpenFile(name, flag, perm) + if err != nil { + return nil, err + } + return f, nil } // Remove removes a file identified by name, returning an error, if any // happens. func (OSFS) Remove(name string) error { - return os.Remove(name) + err := os.Remove(name) + if err != nil { + return err + } + return nil } // RemoveAll removes a directory path and any children it contains. It // does not fail if the path does not exist (return nil). func (OSFS) RemoveAll(path string) error { - return os.RemoveAll(path) + err := os.RemoveAll(path) + if err != nil { + return err + } + return nil } // Rename renames a file. func (OSFS) Rename(oldname, newname string) error { - return os.Rename(oldname, newname) + err := os.Rename(oldname, newname) + if err != nil { + return err + } + return nil } // Stat returns a FileInfo describing the named file, or an error, if any // happens. func (OSFS) Stat(name string) (fs.FileInfo, error) { - return os.Stat(name) + fi, err := os.Stat(name) + if err != nil { + return nil, err + } + return fi, nil } // The name of this FileSystem @@ -74,30 +110,58 @@ func (OSFS) Name() string { // Chmod changes the mode of the named file to mode. func (OSFS) Chmod(name string, mode fs.FileMode) error { - return os.Chmod(name, mode) + err := os.Chmod(name, mode) + if err != nil { + return err + } + return nil } // Chown changes the uid and gid of the named file. // TODO: improve windows support func (OSFS) Chown(name string, uid, gid int) error { - return os.Chown(name, uid, gid) + err := os.Chown(name, uid, gid) + if err != nil { + return err + } + return nil } // Chtimes changes the access and modification times of the named file func (OSFS) Chtimes(name string, atime time.Time, mtime time.Time) error { - return os.Chtimes(name, atime, mtime) + err := os.Chtimes(name, atime, mtime) + if err != nil { + return err + } + return nil } func (OSFS) Lstat(name string) (fs.FileInfo, error) { - return os.Lstat(name) + fi, err := os.Lstat(name) + if err != nil { + return nil, err + } + return fi, nil } func (OSFS) Symlink(oldname, newname string) error { - return os.Symlink(oldname, newname) + err := os.Symlink(oldname, newname) + if err != nil { + return err + } + return nil } func (OSFS) Readlink(name string) (string, error) { - return os.Readlink(name) + link, err := os.Readlink(name) + if err != nil { + return "", err + } + return link, nil } // TODO: improve windows support func (OSFS) Lchown(name string, uid int, gid int) error { - return os.Lchown(name, uid, gid) + err := os.Lchown(name, uid, gid) + if err != nil { + return err + } + return nil } diff --git a/prefixfs.go b/prefixfs.go index 704c901..2428f10 100644 --- a/prefixfs.go +++ b/prefixfs.go @@ -37,7 +37,7 @@ func (s *PrefixFS) prefixPath(name string) (string, error) { volume := filepath.VolumeName(name) if volume != "" { - // interestind for windows, as this backup mechanism does not exactly work + // interesting for windows, as this backup mechanism does not exactly work // with prefixed directories otherwise. A colon is not allowed inisde of the file path. // prefix path with volume letter but without the : volumeName := strings.TrimRight(volume, ":\\/") @@ -47,7 +47,7 @@ func (s *PrefixFS) prefixPath(name string) (string, error) { p := filepath.Join(s.prefix, filepath.Clean(name)) if !strings.HasPrefix(p, s.prefix) { - return "", fs.ErrNotExist + return "", syscall.EPERM } return p, nil } @@ -57,24 +57,28 @@ func (s *PrefixFS) prefixPath(name string) (string, error) { func (s *PrefixFS) Create(name string) (File, error) { path, err := s.prefixPath(name) if err != nil { - return nil, err + return nil, &fs.PathError{Op: "create", Path: name, Err: err} } f, err := s.base.Create(path) if err != nil { return nil, err } - return &PrefixFile{f: f, prefix: s.prefix}, nil + return &prefixFile{f: f, prefix: s.prefix}, nil } // Mkdir creates a directory in the filesystem, return an error if any // happens. func (s *PrefixFS) Mkdir(name string, perm fs.FileMode) error { path, err := s.prefixPath(name) + if err != nil { + return &fs.PathError{Op: "mkdir", Path: name, Err: err} + } + err = s.base.Mkdir(path, perm) if err != nil { return err } - return s.base.Mkdir(path, perm) + return nil } // MkdirAll creates a directory path and all parents that does not exist @@ -82,10 +86,14 @@ func (s *PrefixFS) Mkdir(name string, perm fs.FileMode) error { func (s *PrefixFS) MkdirAll(name string, perm fs.FileMode) error { path, err := s.prefixPath(name) if err != nil { - return err + return &fs.PathError{Op: "mkdir_all", Path: name, Err: err} } - return s.base.MkdirAll(path, perm) + err = s.base.MkdirAll(path, perm) + if err != nil { + return err + } + return nil } // Open opens a file, returning it or an error, if any happens. @@ -93,7 +101,7 @@ func (s *PrefixFS) MkdirAll(name string, perm fs.FileMode) error { func (s *PrefixFS) Open(name string) (File, error) { path, err := s.prefixPath(name) if err != nil { - return nil, err + return nil, &fs.PathError{Op: "open", Path: name, Err: err} } f, err := s.base.Open(path) @@ -101,14 +109,14 @@ func (s *PrefixFS) Open(name string) (File, error) { return nil, err } - return &PrefixFile{f: f, prefix: s.prefix}, nil + return &prefixFile{f: f, prefix: s.prefix}, nil } // OpenFile opens a file using the given flags and the given mode. func (s *PrefixFS) OpenFile(name string, flag int, perm fs.FileMode) (File, error) { path, err := s.prefixPath(name) if err != nil { - return nil, err + return nil, &fs.PathError{Op: "open_file", Path: name, Err: err} } f, err := s.base.OpenFile(path, flag, perm) @@ -116,7 +124,7 @@ func (s *PrefixFS) OpenFile(name string, flag int, perm fs.FileMode) (File, erro return nil, err } - return &PrefixFile{f: f, prefix: s.prefix}, nil + return &prefixFile{f: f, prefix: s.prefix}, nil } // Remove removes a file identified by name, returning an error, if any @@ -124,34 +132,46 @@ func (s *PrefixFS) OpenFile(name string, flag int, perm fs.FileMode) (File, erro func (s *PrefixFS) Remove(name string) error { path, err := s.prefixPath(name) if err != nil { - return err + return &fs.PathError{Op: "remove", Path: name, Err: err} } - return s.base.Remove(path) + err = s.base.Remove(path) + if err != nil { + return err + } + return nil } // RemoveAll removes a directory path and any children it contains. It // does not fail if the path does not exist (return nil). func (s *PrefixFS) RemoveAll(name string) error { path, err := s.prefixPath(name) + if err != nil { + return &fs.PathError{Op: "remove_all", Path: name, Err: err} + } + err = s.base.RemoveAll(path) if err != nil { return err } - return s.base.RemoveAll(path) + return nil } // Rename renames a file. func (s *PrefixFS) Rename(oldname, newname string) error { oldpath, err := s.prefixPath(oldname) if err != nil { - return err + return &fs.PathError{Op: "rename", Path: oldname, Err: err} } newpath, err := s.prefixPath(newname) if err != nil { - return syscall.EPERM + return &fs.PathError{Op: "rename", Path: newname, Err: err} } - return s.base.Rename(oldpath, newpath) + err = s.base.Rename(oldpath, newpath) + if err != nil { + return err + } + return nil } // Stat returns a FileInfo describing the named file, or an error, if any @@ -159,7 +179,7 @@ func (s *PrefixFS) Rename(oldname, newname string) error { func (s *PrefixFS) Stat(name string) (fs.FileInfo, error) { path, err := s.prefixPath(name) if err != nil { - return nil, err + return nil, &fs.PathError{Op: "stat", Path: name, Err: err} } fi, err := s.base.Stat(path) @@ -182,26 +202,38 @@ func (s *PrefixFS) Chmod(name string, mode fs.FileMode) error { return err } - return s.base.Chmod(path, mode) + err = s.base.Chmod(path, mode) + if err != nil { + return err + } + return nil } // Chown changes the uid and gid of the named file. func (s *PrefixFS) Chown(name string, uid, gid int) error { path, err := s.prefixPath(name) if err != nil { - return err + return &fs.PathError{Op: "chown", Path: name, Err: err} } - return s.base.Chown(path, uid, gid) + err = s.base.Chown(path, uid, gid) + if err != nil { + return err + } + return nil } // Chtimes changes the access and modification times of the named file func (s *PrefixFS) Chtimes(name string, atime, mtime time.Time) error { path, err := s.prefixPath(name) + if err != nil { + return &fs.PathError{Op: "chtimes", Path: name, Err: err} + } + err = s.base.Chtimes(path, atime, mtime) if err != nil { return err } - return s.base.Chtimes(path, atime, mtime) + return nil } // Lstat will call Lstat if the filesystem itself is, or it delegates to, the os filesystem. @@ -210,7 +242,7 @@ func (s *PrefixFS) Chtimes(name string, atime, mtime time.Time) error { func (s *PrefixFS) Lstat(name string) (fs.FileInfo, error) { path, err := s.prefixPath(name) if err != nil { - return nil, err + return nil, &fs.PathError{Op: "lstat", Path: name, Err: err} } fi, err := s.base.Lstat(path) @@ -243,16 +275,20 @@ func (s *PrefixFS) Symlink(oldname, newname string) error { newPath, err := s.prefixPath(newname) if err != nil { - return err + return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err} } - return s.base.Symlink(oldPath, newPath) + err = s.base.Symlink(oldPath, newPath) + if err != nil { + return err + } + return nil } func (s *PrefixFS) Readlink(name string) (string, error) { path, err := s.prefixPath(name) if err != nil { - return "", err + return "", &fs.PathError{Op: "readlink", Path: name, Err: err} } linkedPath, err := s.base.Readlink(path) @@ -268,8 +304,12 @@ func (s *PrefixFS) Readlink(name string) (string, error) { func (s *PrefixFS) Lchown(name string, uid, gid int) error { path, err := s.prefixPath(name) if err != nil { - return err + return &fs.PathError{Op: "lchown", Path: name, Err: err} } - return s.base.Lchown(path, uid, gid) + err = s.base.Lchown(path, uid, gid) + if err != nil { + return err + } + return nil } diff --git a/prefixfs_file.go b/prefixfs_file.go index 97f98c5..c5d6392 100644 --- a/prefixfs_file.go +++ b/prefixfs_file.go @@ -5,57 +5,61 @@ import ( "strings" ) -var _ File = (*PrefixFile)(nil) +var _ File = (*prefixFile)(nil) -type PrefixFile struct { +type prefixFile struct { f File // this prefix is clean due to th eFS prefix being clean prefix string } -func (pf *PrefixFile) Name() string { +func (pf *prefixFile) Name() string { // hide the existence of the prefix return strings.TrimPrefix(pf.f.Name(), pf.prefix) } -func (pf *PrefixFile) Readdir(count int) ([]fs.FileInfo, error) { +func (pf *prefixFile) Readdir(count int) ([]fs.FileInfo, error) { return pf.f.Readdir(count) } -func (pf *PrefixFile) Readdirnames(n int) ([]string, error) { +func (pf *prefixFile) Readdirnames(n int) ([]string, error) { return pf.f.Readdirnames(n) } -func (pf *PrefixFile) Stat() (fs.FileInfo, error) { +func (pf *prefixFile) Stat() (fs.FileInfo, error) { return pf.f.Stat() } -func (pf *PrefixFile) Sync() error { +func (pf *prefixFile) Sync() error { return pf.f.Sync() } -func (pf *PrefixFile) Truncate(size int64) error { +func (pf *prefixFile) Truncate(size int64) error { return pf.f.Truncate(size) } -func (pf *PrefixFile) WriteString(s string) (ret int, err error) { +func (pf *prefixFile) WriteString(s string) (ret int, err error) { return pf.f.WriteString(s) } -func (pf *PrefixFile) Close() error { - return pf.f.Close() +func (pf *prefixFile) Close() error { + err := pf.f.Close() + if err != nil { + return err + } + return nil } -func (pf *PrefixFile) Read(p []byte) (n int, err error) { +func (pf *prefixFile) Read(p []byte) (n int, err error) { return pf.f.Read(p) } -func (pf *PrefixFile) ReadAt(p []byte, off int64) (n int, err error) { +func (pf *prefixFile) ReadAt(p []byte, off int64) (n int, err error) { return pf.f.ReadAt(p, off) } -func (pf *PrefixFile) Seek(offset int64, whence int) (int64, error) { +func (pf *prefixFile) Seek(offset int64, whence int) (int64, error) { return pf.f.Seek(offset, whence) } -func (pf *PrefixFile) Write(p []byte) (n int, err error) { +func (pf *prefixFile) Write(p []byte) (n int, err error) { return pf.f.Write(p) } -func (pf *PrefixFile) WriteAt(p []byte, off int64) (n int, err error) { +func (pf *prefixFile) WriteAt(p []byte, off int64) (n int, err error) { return pf.f.WriteAt(p, off) } diff --git a/volumefs.go b/volumefs.go index c70048f..0881936 100644 --- a/volumefs.go +++ b/volumefs.go @@ -6,6 +6,7 @@ import ( "path" "path/filepath" "strings" + "syscall" "time" ) @@ -14,7 +15,7 @@ var ( _ FS = (*VolumeFS)(nil) ) -type volumeFile = PrefixFile +type volumeFile = prefixFile type volumeFileInfo = prefixFileInfo // VolumeFS is specifically designed to prefix absolute paths with a defined volume like C:, D:, E: etc. @@ -35,7 +36,7 @@ func (v *VolumeFS) prefixPath(name string) (string, error) { volumePrefix := filepath.VolumeName(name) if volumePrefix != "" { - return "", os.ErrNotExist + return "", syscall.EPERM } return filepath.Clean(filepath.Join(v.volume, name)), nil @@ -53,7 +54,7 @@ func NewVolumeFS(volume string, fs FS) *VolumeFS { func (v *VolumeFS) Create(name string) (File, error) { path, err := v.prefixPath(name) if err != nil { - return nil, err + return nil, &fs.PathError{Op: "create", Path: name, Err: err} } f, err := v.base.Create(path) @@ -69,10 +70,14 @@ func (v *VolumeFS) Create(name string) (File, error) { func (v *VolumeFS) Mkdir(name string, perm fs.FileMode) error { path, err := v.prefixPath(name) if err != nil { - return err + return &fs.PathError{Op: "mkdir", Path: name, Err: err} } - return v.base.Mkdir(path, perm) + err = v.base.Mkdir(path, perm) + if err != nil { + return err + } + return nil } // MkdirAll creates a directory path and all parents that does not exist @@ -80,10 +85,14 @@ func (v *VolumeFS) Mkdir(name string, perm fs.FileMode) error { func (v *VolumeFS) MkdirAll(name string, perm fs.FileMode) error { path, err := v.prefixPath(name) if err != nil { - return err + return &fs.PathError{Op: "mkdir_all", Path: name, Err: err} } - return v.base.MkdirAll(path, perm) + err = v.base.MkdirAll(path, perm) + if err != nil { + return err + } + return nil } // Open opens a file, returning it or an error, if any happens. @@ -91,7 +100,7 @@ func (v *VolumeFS) MkdirAll(name string, perm fs.FileMode) error { func (v *VolumeFS) Open(name string) (File, error) { path, err := v.prefixPath(name) if err != nil { - return nil, err + return nil, &fs.PathError{Op: "open", Path: name, Err: err} } f, err := v.base.Open(path) @@ -106,7 +115,7 @@ func (v *VolumeFS) Open(name string) (File, error) { func (v *VolumeFS) OpenFile(name string, flag int, perm fs.FileMode) (File, error) { path, err := v.prefixPath(name) if err != nil { - return nil, err + return nil, &fs.PathError{Op: "open_file", Path: name, Err: err} } f, err := v.base.OpenFile(path, flag, perm) @@ -122,10 +131,14 @@ func (v *VolumeFS) OpenFile(name string, flag int, perm fs.FileMode) (File, erro func (v *VolumeFS) Remove(name string) error { path, err := v.prefixPath(name) if err != nil { - return err + return &fs.PathError{Op: "remove", Path: name, Err: err} } - return v.base.Remove(path) + err = v.base.Remove(path) + if err != nil { + return err + } + return nil } // RemoveAll removes a directory path and any children it contains. It @@ -133,24 +146,32 @@ func (v *VolumeFS) Remove(name string) error { func (v *VolumeFS) RemoveAll(name string) error { path, err := v.prefixPath(name) if err != nil { - return err + return &fs.PathError{Op: "remove_all", Path: name, Err: err} } - return v.base.RemoveAll(path) + err = v.base.RemoveAll(path) + if err != nil { + return err + } + return nil } // Rename renames a file. func (v *VolumeFS) Rename(oldname, newname string) error { oldpath, err := v.prefixPath(oldname) if err != nil { - return err + return &fs.PathError{Op: "rename", Path: newname, Err: err} } newpath, err := v.prefixPath(newname) if err != nil { - return err + return &fs.PathError{Op: "rename", Path: newname, Err: err} } - return v.base.Rename(oldpath, newpath) + err = v.base.Rename(oldpath, newpath) + if err != nil { + return err + } + return nil } // Stat returns a FileInfo describing the named file, or an error, if any @@ -158,7 +179,7 @@ func (v *VolumeFS) Rename(oldname, newname string) error { func (v *VolumeFS) Stat(name string) (fs.FileInfo, error) { path, err := v.prefixPath(name) if err != nil { - return nil, err + return nil, &fs.PathError{Op: "stat", Path: name, Err: err} } fi, err := v.base.Stat(path) @@ -178,29 +199,41 @@ func (v *VolumeFS) Name() string { func (v *VolumeFS) Chmod(name string, mode fs.FileMode) error { path, err := v.prefixPath(name) if err != nil { - return err + return &fs.PathError{Op: "chmod", Path: name, Err: err} } - return v.base.Chmod(path, mode) + err = v.base.Chmod(path, mode) + if err != nil { + return err + } + return nil } // Chown changes the uid and gid of the named file. func (v *VolumeFS) Chown(name string, uid, gid int) error { path, err := v.prefixPath(name) if err != nil { - return err + return &fs.PathError{Op: "chown", Path: name, Err: err} } - return v.base.Chown(path, uid, gid) + err = v.base.Chown(path, uid, gid) + if err != nil { + return err + } + return nil } // Chtimes changes the access and modification times of the named file func (v *VolumeFS) Chtimes(name string, atime, mtime time.Time) error { path, err := v.prefixPath(name) + if err != nil { + return &fs.PathError{Op: "chtimes", Path: name, Err: err} + } + err = v.base.Chtimes(path, atime, mtime) if err != nil { return err } - return v.base.Chtimes(path, atime, mtime) + return nil } // Lstat will call Lstat if the filesystem itself is, or it delegates to, the os filesystem. @@ -209,7 +242,7 @@ func (v *VolumeFS) Chtimes(name string, atime, mtime time.Time) error { func (v *VolumeFS) Lstat(name string) (fs.FileInfo, error) { path, err := v.prefixPath(name) if err != nil { - return nil, err + return nil, &fs.PathError{Op: "lstat", Path: name, Err: err} } fi, err := v.base.Lstat(path) @@ -233,6 +266,7 @@ func (v *VolumeFS) Symlink(oldname, newname string) error { oldPath, err = v.prefixPath(oldname) } else { // relative path symlink + // TODO: oldname could escape the volume prefix using relative paths oldPath = oldname } @@ -242,16 +276,20 @@ func (v *VolumeFS) Symlink(oldname, newname string) error { newPath, err := v.prefixPath(newname) if err != nil { - return err + return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err} } - return v.base.Symlink(oldPath, newPath) + err = v.base.Symlink(oldPath, newPath) + if err != nil { + return err + } + return nil } func (v *VolumeFS) Readlink(name string) (string, error) { path, err := v.prefixPath(name) if err != nil { - return "", err + return "", &fs.PathError{Op: "readlink", Path: name, Err: err} } linkedPath, err := v.base.Readlink(path) @@ -265,10 +303,14 @@ func (v *VolumeFS) Readlink(name string) (string, error) { func (v *VolumeFS) Lchown(name string, uid, gid int) error { path, err := v.prefixPath(name) + if err != nil { + return &fs.PathError{Op: "lchown", Path: name, Err: err} + } + err = v.base.Lchown(path, uid, gid) if err != nil { return err } - return v.base.Lchown(path, uid, gid) + return nil } // TrimVolume trims the volume prefix of a given filepath. C:\A\B\C -> \A\B\C