forked from TrueCloudLab/restic
f9dbcd2531
Depending on parameters the paths in a snapshot do not directly correspond to real paths on the filesystem. Therefore, reject funcs must use the FS interface to work correctly.
317 lines
7.4 KiB
Go
317 lines
7.4 KiB
Go
package fs
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/restic/restic/internal/errors"
|
|
)
|
|
|
|
// Reader is a file system which provides a directory with a single file. When
|
|
// this file is opened for reading, the reader is passed through. The file can
|
|
// be opened once, all subsequent open calls return syscall.EIO. For Lstat(),
|
|
// the provided FileInfo is returned.
|
|
type Reader struct {
|
|
Name string
|
|
io.ReadCloser
|
|
|
|
// for FileInfo
|
|
Mode os.FileMode
|
|
ModTime time.Time
|
|
Size int64
|
|
|
|
AllowEmptyFile bool
|
|
|
|
open sync.Once
|
|
}
|
|
|
|
// statically ensure that Local implements FS.
|
|
var _ FS = &Reader{}
|
|
|
|
// VolumeName returns leading volume name, for the Reader file system it's
|
|
// always the empty string.
|
|
func (fs *Reader) VolumeName(_ string) string {
|
|
return ""
|
|
}
|
|
|
|
func (fs *Reader) fi() os.FileInfo {
|
|
return fakeFileInfo{
|
|
name: fs.Name,
|
|
size: fs.Size,
|
|
mode: fs.Mode,
|
|
modtime: fs.ModTime,
|
|
}
|
|
}
|
|
|
|
// OpenFile is the generalized open call; most users will use Open
|
|
// or Create instead. It opens the named file with specified flag
|
|
// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful,
|
|
// methods on the returned File can be used for I/O.
|
|
// If there is an error, it will be of type *os.PathError.
|
|
func (fs *Reader) OpenFile(name string, flag int, _ os.FileMode) (f File, err error) {
|
|
if flag & ^(O_RDONLY|O_NOFOLLOW) != 0 {
|
|
return nil, pathError("open", name,
|
|
fmt.Errorf("invalid combination of flags 0x%x", flag))
|
|
}
|
|
|
|
switch name {
|
|
case fs.Name:
|
|
fs.open.Do(func() {
|
|
f = newReaderFile(fs.ReadCloser, fs.fi(), fs.AllowEmptyFile)
|
|
})
|
|
|
|
if f == nil {
|
|
return nil, pathError("open", name, syscall.EIO)
|
|
}
|
|
|
|
return f, nil
|
|
case "/", ".":
|
|
f = fakeDir{
|
|
entries: []os.FileInfo{fs.fi()},
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
return nil, pathError("open", name, syscall.ENOENT)
|
|
}
|
|
|
|
// Stat returns a FileInfo describing the named file. If there is an error, it
|
|
// will be of type *os.PathError.
|
|
func (fs *Reader) Stat(name string) (os.FileInfo, error) {
|
|
return fs.Lstat(name)
|
|
}
|
|
|
|
// Lstat returns the FileInfo structure describing the named file.
|
|
// If the file is a symbolic link, the returned FileInfo
|
|
// describes the symbolic link. Lstat makes no attempt to follow the link.
|
|
// If there is an error, it will be of type *os.PathError.
|
|
func (fs *Reader) Lstat(name string) (os.FileInfo, error) {
|
|
getDirInfo := func(name string) os.FileInfo {
|
|
fi := fakeFileInfo{
|
|
name: fs.Base(name),
|
|
size: 0,
|
|
mode: os.ModeDir | 0755,
|
|
modtime: time.Now(),
|
|
}
|
|
return fi
|
|
}
|
|
|
|
switch name {
|
|
case fs.Name:
|
|
return fs.fi(), nil
|
|
case "/", ".":
|
|
return getDirInfo(name), nil
|
|
}
|
|
|
|
dir := fs.Dir(fs.Name)
|
|
for {
|
|
if dir == "/" || dir == "." {
|
|
break
|
|
}
|
|
if name == dir {
|
|
return getDirInfo(name), nil
|
|
}
|
|
dir = fs.Dir(dir)
|
|
}
|
|
|
|
return nil, pathError("lstat", name, os.ErrNotExist)
|
|
}
|
|
|
|
func (fs *Reader) DeviceID(_ os.FileInfo) (deviceID uint64, err error) {
|
|
return 0, errors.New("Device IDs are not supported")
|
|
}
|
|
|
|
// Join joins any number of path elements into a single path, adding a
|
|
// Separator if necessary. Join calls Clean on the result; in particular, all
|
|
// empty strings are ignored. On Windows, the result is a UNC path if and only
|
|
// if the first path element is a UNC path.
|
|
func (fs *Reader) Join(elem ...string) string {
|
|
return path.Join(elem...)
|
|
}
|
|
|
|
// Separator returns the OS and FS dependent separator for dirs/subdirs/files.
|
|
func (fs *Reader) Separator() string {
|
|
return "/"
|
|
}
|
|
|
|
// IsAbs reports whether the path is absolute. For the Reader, this is always the case.
|
|
func (fs *Reader) IsAbs(_ string) bool {
|
|
return true
|
|
}
|
|
|
|
// Abs returns an absolute representation of path. If the path is not absolute
|
|
// it will be joined with the current working directory to turn it into an
|
|
// absolute path. The absolute path name for a given file is not guaranteed to
|
|
// be unique. Abs calls Clean on the result.
|
|
//
|
|
// For the Reader, all paths are absolute.
|
|
func (fs *Reader) Abs(p string) (string, error) {
|
|
return path.Clean(p), nil
|
|
}
|
|
|
|
// Clean returns the cleaned path. For details, see filepath.Clean.
|
|
func (fs *Reader) Clean(p string) string {
|
|
return path.Clean(p)
|
|
}
|
|
|
|
// Base returns the last element of p.
|
|
func (fs *Reader) Base(p string) string {
|
|
return path.Base(p)
|
|
}
|
|
|
|
// Dir returns p without the last element.
|
|
func (fs *Reader) Dir(p string) string {
|
|
return path.Dir(p)
|
|
}
|
|
|
|
func newReaderFile(rd io.ReadCloser, fi os.FileInfo, allowEmptyFile bool) *readerFile {
|
|
return &readerFile{
|
|
ReadCloser: rd,
|
|
AllowEmptyFile: allowEmptyFile,
|
|
fakeFile: fakeFile{
|
|
FileInfo: fi,
|
|
name: fi.Name(),
|
|
},
|
|
}
|
|
}
|
|
|
|
type readerFile struct {
|
|
io.ReadCloser
|
|
AllowEmptyFile, bytesRead bool
|
|
|
|
fakeFile
|
|
}
|
|
|
|
// ErrFileEmpty is returned inside a *os.PathError by Read() for the file
|
|
// opened from the fs provided by Reader when no data could be read and
|
|
// AllowEmptyFile is not set.
|
|
var ErrFileEmpty = errors.New("no data read")
|
|
|
|
func (r *readerFile) Read(p []byte) (int, error) {
|
|
n, err := r.ReadCloser.Read(p)
|
|
if n > 0 {
|
|
r.bytesRead = true
|
|
}
|
|
|
|
// return an error if we did not read any data
|
|
if err == io.EOF && !r.AllowEmptyFile && !r.bytesRead {
|
|
return n, pathError("read", r.fakeFile.name, ErrFileEmpty)
|
|
}
|
|
|
|
return n, err
|
|
}
|
|
|
|
func (r *readerFile) Close() error {
|
|
return r.ReadCloser.Close()
|
|
}
|
|
|
|
// ensure that readerFile implements File
|
|
var _ File = &readerFile{}
|
|
|
|
// fakeFile implements all File methods, but only returns errors for anything
|
|
// except Stat() and Name().
|
|
type fakeFile struct {
|
|
name string
|
|
os.FileInfo
|
|
}
|
|
|
|
// ensure that fakeFile implements File
|
|
var _ File = fakeFile{}
|
|
|
|
func (f fakeFile) Fd() uintptr {
|
|
return 0
|
|
}
|
|
|
|
func (f fakeFile) Readdirnames(_ int) ([]string, error) {
|
|
return nil, pathError("readdirnames", f.name, os.ErrInvalid)
|
|
}
|
|
|
|
func (f fakeFile) Readdir(_ int) ([]os.FileInfo, error) {
|
|
return nil, pathError("readdir", f.name, os.ErrInvalid)
|
|
}
|
|
|
|
func (f fakeFile) Seek(int64, int) (int64, error) {
|
|
return 0, pathError("seek", f.name, os.ErrInvalid)
|
|
}
|
|
|
|
func (f fakeFile) Read(_ []byte) (int, error) {
|
|
return 0, pathError("read", f.name, os.ErrInvalid)
|
|
}
|
|
|
|
func (f fakeFile) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (f fakeFile) Stat() (os.FileInfo, error) {
|
|
return f.FileInfo, nil
|
|
}
|
|
|
|
func (f fakeFile) Name() string {
|
|
return f.name
|
|
}
|
|
|
|
// fakeDir implements Readdirnames and Readdir, everything else is delegated to fakeFile.
|
|
type fakeDir struct {
|
|
entries []os.FileInfo
|
|
fakeFile
|
|
}
|
|
|
|
func (d fakeDir) Readdirnames(n int) ([]string, error) {
|
|
if n > 0 {
|
|
return nil, pathError("readdirnames", d.name, errors.New("not implemented"))
|
|
}
|
|
names := make([]string, 0, len(d.entries))
|
|
for _, entry := range d.entries {
|
|
names = append(names, entry.Name())
|
|
}
|
|
|
|
return names, nil
|
|
}
|
|
|
|
func (d fakeDir) Readdir(n int) ([]os.FileInfo, error) {
|
|
if n > 0 {
|
|
return nil, pathError("readdir", d.name, errors.New("not implemented"))
|
|
}
|
|
return d.entries, nil
|
|
}
|
|
|
|
// fakeFileInfo implements the bare minimum of os.FileInfo.
|
|
type fakeFileInfo struct {
|
|
name string
|
|
size int64
|
|
mode os.FileMode
|
|
modtime time.Time
|
|
}
|
|
|
|
func (fi fakeFileInfo) Name() string {
|
|
return fi.name
|
|
}
|
|
|
|
func (fi fakeFileInfo) Size() int64 {
|
|
return fi.size
|
|
}
|
|
|
|
func (fi fakeFileInfo) Mode() os.FileMode {
|
|
return fi.mode
|
|
}
|
|
|
|
func (fi fakeFileInfo) ModTime() time.Time {
|
|
return fi.modtime
|
|
}
|
|
|
|
func (fi fakeFileInfo) IsDir() bool {
|
|
return fi.mode&os.ModeDir > 0
|
|
}
|
|
|
|
func (fi fakeFileInfo) Sys() interface{} {
|
|
return nil
|
|
}
|
|
|
|
func pathError(op, name string, err error) *os.PathError {
|
|
return &os.PathError{Op: op, Path: name, Err: err}
|
|
}
|