vfs: Make file handles compatible with OS

* Implement directory handles
  * Unify OpenFile
  * Add all the methods to match *os.File
  * Add StatParent and Rename methods to VFS
This commit is contained in:
Nick Craig-Wood 2017-10-29 21:11:17 +00:00
parent 3e0c91ba4b
commit a5dc62f6c1
5 changed files with 243 additions and 0 deletions

91
vfs/dir_handle.go Normal file
View File

@ -0,0 +1,91 @@
package vfs
import (
"io"
"os"
)
// DirHandle represents an open directory
type DirHandle struct {
baseHandle
d *Dir
fis []os.FileInfo // where Readdir got to
}
// newDirHandle opens a directory for read
func newDirHandle(d *Dir) *DirHandle {
return &DirHandle{
d: d,
}
}
// Stat returns info about the current directory
func (fh *DirHandle) Stat() (fi os.FileInfo, err error) {
return fh.d, nil
}
// Readdir reads the contents of the directory associated with file and returns
// a slice of up to n FileInfo values, as would be returned by Lstat, in
// directory order. Subsequent calls on the same file will yield further
// FileInfos.
//
// If n > 0, Readdir returns at most n FileInfo structures. In this case, if
// Readdir returns an empty slice, it will return a non-nil error explaining
// why. At the end of a directory, the error is io.EOF.
//
// If n <= 0, Readdir returns all the FileInfo from the directory in a single
// slice. In this case, if Readdir succeeds (reads all the way to the end of
// the directory), it returns the slice and a nil error. If it encounters an
// error before the end of the directory, Readdir returns the FileInfo read
// until that point and a non-nil error.
func (fh *DirHandle) Readdir(n int) (fis []os.FileInfo, err error) {
if fh.fis == nil {
nodes, err := fh.d.ReadDirAll()
if err != nil {
return nil, err
}
fh.fis = []os.FileInfo{}
for _, node := range nodes {
fh.fis = append(fh.fis, node)
}
}
nn := len(fh.fis)
if n > 0 {
if nn == 0 {
return nil, io.EOF
}
if nn > n {
nn = n
}
}
fis, fh.fis = fh.fis[:nn], fh.fis[nn:]
return fis, nil
}
// Readdirnames reads and returns a slice of names from the directory f.
//
// If n > 0, Readdirnames returns at most n names. In this case, if
// Readdirnames returns an empty slice, it will return a non-nil error
// explaining why. At the end of a directory, the error is io.EOF.
//
// If n <= 0, Readdirnames returns all the names from the directory in a single
// slice. In this case, if Readdirnames succeeds (reads all the way to the end
// of the directory), it returns the slice and a nil error. If it encounters an
// error before the end of the directory, Readdirnames returns the names read
// until that point and a non-nil error.
func (fh *DirHandle) Readdirnames(n int) (names []string, err error) {
nodes, err := fh.Readdir(n)
if err != nil {
return nil, err
}
for _, node := range nodes {
names = append(names, node.Name())
}
return names, nil
}
// Close closes the handle
func (fh *DirHandle) Close() (err error) {
fh.fis = nil
return nil
}

View File

@ -19,6 +19,7 @@ const (
ESPIPE
EBADF
EROFS
ENOSYS
)
// Errors which have exact counterparts in os
@ -33,6 +34,7 @@ var errorNames = []string{
ESPIPE: "Illegal seek",
EBADF: "Bad file descriptor",
EROFS: "Read only file system",
ENOSYS: "Function not implemented",
}
// Error renders the error as a string

View File

@ -2,6 +2,7 @@ package vfs
import (
"io"
"os"
"sync"
"github.com/ncw/rclone/fs"
@ -10,6 +11,7 @@ import (
// ReadFileHandle is an open for read file handle on a File
type ReadFileHandle struct {
baseHandle
mu sync.Mutex
closed bool // set if handle has been closed
r *fs.Account
@ -353,6 +355,11 @@ func (fh *ReadFileHandle) Size() int64 {
return fh.o.Size()
}
// Stat returns info about the file
func (fh *ReadFileHandle) Stat() (os.FileInfo, error) {
return fh.file, nil
}
// Close closes the file calling Flush then Release
func (fh *ReadFileHandle) Close() error {
err := fh.Flush()

View File

@ -6,12 +6,17 @@
// should behave in an identical fashion. The objects also obey Go's
// standard interfaces.
//
// Note that paths don't start or end with /, so the root directory
// may be referred to as "". However Stat strips slashes so you can
// use paths with slashes in.
//
// It also includes directory caching
package vfs
import (
"fmt"
"os"
"path"
"strings"
"sync/atomic"
"time"
@ -75,6 +80,58 @@ var (
_ Noder = (*WriteFileHandle)(nil)
)
// Handle is the interface statisified by open files or directories.
// It is the methods on *os.File. Not all of them are supported.
type Handle interface {
Chdir() error
Chmod(mode os.FileMode) error
Chown(uid, gid int) error
Close() error
Fd() uintptr
Name() string
Read(b []byte) (n int, err error)
ReadAt(b []byte, off int64) (n int, err error)
Readdir(n int) ([]os.FileInfo, error)
Readdirnames(n int) (names []string, err error)
Seek(offset int64, whence int) (ret int64, err error)
Stat() (os.FileInfo, error)
Sync() error
Truncate(size int64) error
Write(b []byte) (n int, err error)
WriteAt(b []byte, off int64) (n int, err error)
WriteString(s string) (n int, err error)
}
// baseHandle implements all the missing methods
type baseHandle struct{}
func (h baseHandle) Chdir() error { return ENOSYS }
func (h baseHandle) Chmod(mode os.FileMode) error { return ENOSYS }
func (h baseHandle) Chown(uid, gid int) error { return ENOSYS }
func (h baseHandle) Close() error { return ENOSYS }
func (h baseHandle) Fd() uintptr { return 0 }
func (h baseHandle) Name() string { return "" }
func (h baseHandle) Read(b []byte) (n int, err error) { return 0, ENOSYS }
func (h baseHandle) ReadAt(b []byte, off int64) (n int, err error) { return 0, ENOSYS }
func (h baseHandle) Readdir(n int) ([]os.FileInfo, error) { return nil, ENOSYS }
func (h baseHandle) Readdirnames(n int) (names []string, err error) { return nil, ENOSYS }
func (h baseHandle) Seek(offset int64, whence int) (ret int64, err error) { return 0, ENOSYS }
func (h baseHandle) Stat() (os.FileInfo, error) { return nil, ENOSYS }
func (h baseHandle) Sync() error { return nil }
func (h baseHandle) Truncate(size int64) error { return ENOSYS }
func (h baseHandle) Write(b []byte) (n int, err error) { return 0, ENOSYS }
func (h baseHandle) WriteAt(b []byte, off int64) (n int, err error) { return 0, ENOSYS }
func (h baseHandle) WriteString(s string) (n int, err error) { return 0, ENOSYS }
// Check interfaces
var (
_ Handle = (*baseHandle)(nil)
_ Handle = (*ReadFileHandle)(nil)
_ Handle = (*WriteFileHandle)(nil)
_ Handle = (*DirHandle)(nil)
_ Handle = (*os.File)(nil)
)
// VFS represents the top level filing system
type VFS struct {
f fs.Fs
@ -146,6 +203,7 @@ func newInode() (inode uint64) {
// It is the equivalent of os.Stat - Node contains the os.FileInfo
// interface.
func (vfs *VFS) Stat(path string) (node Node, err error) {
path = strings.Trim(path, "/")
node = vfs.root
for path != "" {
i := strings.IndexRune(path, '/')
@ -170,3 +228,81 @@ func (vfs *VFS) Stat(path string) (node Node, err error) {
}
return
}
// StatParent finds the parent directory and the leaf name of a path
func (vfs *VFS) StatParent(name string) (dir *Dir, leaf string, err error) {
name = strings.Trim(name, "/")
parent, leaf := path.Split(name)
node, err := vfs.Stat(parent)
if err != nil {
return nil, "", err
}
if node.IsFile() {
return nil, "", os.ErrExist
}
dir = node.(*Dir)
return dir, leaf, nil
}
// OpenFile a file according to the flags and perm provided
func (vfs *VFS) OpenFile(name string, flags int, perm os.FileMode) (fd Handle, err error) {
rdwrMode := flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)
var read bool
switch {
case rdwrMode == os.O_RDONLY:
read = true
case rdwrMode == os.O_WRONLY || (rdwrMode == os.O_RDWR && (flags&os.O_TRUNC) != 0):
read = false
case rdwrMode == os.O_RDWR:
fs.Errorf(name, "Can't open for Read and Write")
return nil, os.ErrPermission
default:
fs.Errorf(name, "Can't figure out how to open with flags: 0x%X", flags)
return nil, os.ErrPermission
}
node, err := vfs.Stat(name)
if err != nil {
if err == os.ErrNotExist && !read {
return vfs.createFile(name, flags, perm)
}
return nil, err
}
if node.IsFile() {
file := node.(*File)
if read {
fd, err = file.OpenRead()
} else {
fd, err = file.OpenWrite()
}
} else {
fd, err = newDirHandle(node.(*Dir)), nil
}
return fd, err
}
func (vfs *VFS) createFile(name string, flags int, perm os.FileMode) (fd Handle, err error) {
dir, leaf, err := vfs.StatParent(name)
if err != nil {
return nil, err
}
_, fd, err = dir.Create(leaf)
return fd, err
}
// Rename oldName to newName
func (vfs *VFS) Rename(oldName, newName string) error {
// find the parent directories
oldDir, oldLeaf, err := vfs.StatParent(oldName)
if err != nil {
return err
}
newDir, newLeaf, err := vfs.StatParent(newName)
if err != nil {
return err
}
err = oldDir.Rename(oldLeaf, newLeaf, newDir)
if err != nil {
return err
}
return nil
}

View File

@ -2,6 +2,7 @@ package vfs
import (
"io"
"os"
"sync"
"time"
@ -10,6 +11,7 @@ import (
// WriteFileHandle is an open for write handle on a File
type WriteFileHandle struct {
baseHandle
mu sync.Mutex
closed bool // set if handle has been closed
remote string
@ -195,6 +197,11 @@ func (fh *WriteFileHandle) Release() error {
return err
}
// Stat returns info about the file
func (fh *WriteFileHandle) Stat() (os.FileInfo, error) {
return fh.file, nil
}
// Close closes the file calling Flush then Release
func (fh *WriteFileHandle) Close() error {
err := fh.Flush()