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(path string) string { return "" } // Open opens a file for reading. func (fs *Reader) Open(name string) (f File, err error) { switch name { case fs.Name: fs.open.Do(func() { f = newReaderFile(fs.ReadCloser, fs.fi(), fs.AllowEmptyFile) }) if f == nil { return nil, syscall.EIO } return f, nil case "/", ".": f = fakeDir{ entries: []os.FileInfo{fs.fi()}, } return f, nil } return nil, syscall.ENOENT } 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 *PathError. func (fs *Reader) OpenFile(name string, flag int, perm os.FileMode) (f File, err error) { if flag & ^(O_RDONLY|O_NOFOLLOW) != 0 { return nil, errors.Errorf("invalid combination of flags 0x%x", flag) } fs.open.Do(func() { f = newReaderFile(fs.ReadCloser, fs.fi(), fs.AllowEmptyFile) }) if f == nil { return nil, syscall.EIO } return f, nil } // Stat returns a FileInfo describing the named file. If there is an error, it // will be of type *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 *PathError. func (fs *Reader) Lstat(name string) (os.FileInfo, error) { switch name { case fs.Name: return fs.fi(), nil case "/", ".": fi := fakeFileInfo{ name: name, size: 0, mode: 0755, modtime: time.Now(), } return fi, nil } return nil, os.ErrNotExist } // 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(p 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 { fmt.Printf("reader: %d bytes read, err %v, bytesRead %v, allowEmpty %v\n", n, err, r.bytesRead, r.AllowEmptyFile) return n, &os.PathError{ Path: r.fakeFile.name, Op: "read", Err: 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(n int) ([]string, error) { return nil, os.ErrInvalid } func (f fakeFile) Readdir(n int) ([]os.FileInfo, error) { return nil, os.ErrInvalid } func (f fakeFile) Seek(int64, int) (int64, error) { return 0, os.ErrInvalid } func (f fakeFile) Write(p []byte) (int, error) { return 0, os.ErrInvalid } func (f fakeFile) Read(p []byte) (int, error) { return 0, 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, 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, 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 sys interface{} } 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 fi.sys }