forked from TrueCloudLab/rclone
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:
parent
3e0c91ba4b
commit
a5dc62f6c1
5 changed files with 243 additions and 0 deletions
91
vfs/dir_handle.go
Normal file
91
vfs/dir_handle.go
Normal 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
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ const (
|
||||||
ESPIPE
|
ESPIPE
|
||||||
EBADF
|
EBADF
|
||||||
EROFS
|
EROFS
|
||||||
|
ENOSYS
|
||||||
)
|
)
|
||||||
|
|
||||||
// Errors which have exact counterparts in os
|
// Errors which have exact counterparts in os
|
||||||
|
@ -33,6 +34,7 @@ var errorNames = []string{
|
||||||
ESPIPE: "Illegal seek",
|
ESPIPE: "Illegal seek",
|
||||||
EBADF: "Bad file descriptor",
|
EBADF: "Bad file descriptor",
|
||||||
EROFS: "Read only file system",
|
EROFS: "Read only file system",
|
||||||
|
ENOSYS: "Function not implemented",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error renders the error as a string
|
// Error renders the error as a string
|
||||||
|
|
|
@ -2,6 +2,7 @@ package vfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ncw/rclone/fs"
|
"github.com/ncw/rclone/fs"
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
|
|
||||||
// ReadFileHandle is an open for read file handle on a File
|
// ReadFileHandle is an open for read file handle on a File
|
||||||
type ReadFileHandle struct {
|
type ReadFileHandle struct {
|
||||||
|
baseHandle
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
closed bool // set if handle has been closed
|
closed bool // set if handle has been closed
|
||||||
r *fs.Account
|
r *fs.Account
|
||||||
|
@ -353,6 +355,11 @@ func (fh *ReadFileHandle) Size() int64 {
|
||||||
return fh.o.Size()
|
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
|
// Close closes the file calling Flush then Release
|
||||||
func (fh *ReadFileHandle) Close() error {
|
func (fh *ReadFileHandle) Close() error {
|
||||||
err := fh.Flush()
|
err := fh.Flush()
|
||||||
|
|
136
vfs/vfs.go
136
vfs/vfs.go
|
@ -6,12 +6,17 @@
|
||||||
// should behave in an identical fashion. The objects also obey Go's
|
// should behave in an identical fashion. The objects also obey Go's
|
||||||
// standard interfaces.
|
// 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
|
// It also includes directory caching
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
@ -75,6 +80,58 @@ var (
|
||||||
_ Noder = (*WriteFileHandle)(nil)
|
_ 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
|
// VFS represents the top level filing system
|
||||||
type VFS struct {
|
type VFS struct {
|
||||||
f fs.Fs
|
f fs.Fs
|
||||||
|
@ -146,6 +203,7 @@ func newInode() (inode uint64) {
|
||||||
// It is the equivalent of os.Stat - Node contains the os.FileInfo
|
// It is the equivalent of os.Stat - Node contains the os.FileInfo
|
||||||
// interface.
|
// interface.
|
||||||
func (vfs *VFS) Stat(path string) (node Node, err error) {
|
func (vfs *VFS) Stat(path string) (node Node, err error) {
|
||||||
|
path = strings.Trim(path, "/")
|
||||||
node = vfs.root
|
node = vfs.root
|
||||||
for path != "" {
|
for path != "" {
|
||||||
i := strings.IndexRune(path, '/')
|
i := strings.IndexRune(path, '/')
|
||||||
|
@ -170,3 +228,81 @@ func (vfs *VFS) Stat(path string) (node Node, err error) {
|
||||||
}
|
}
|
||||||
return
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package vfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
|
|
||||||
// WriteFileHandle is an open for write handle on a File
|
// WriteFileHandle is an open for write handle on a File
|
||||||
type WriteFileHandle struct {
|
type WriteFileHandle struct {
|
||||||
|
baseHandle
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
closed bool // set if handle has been closed
|
closed bool // set if handle has been closed
|
||||||
remote string
|
remote string
|
||||||
|
@ -195,6 +197,11 @@ func (fh *WriteFileHandle) Release() error {
|
||||||
return err
|
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
|
// Close closes the file calling Flush then Release
|
||||||
func (fh *WriteFileHandle) Close() error {
|
func (fh *WriteFileHandle) Close() error {
|
||||||
err := fh.Flush()
|
err := fh.Flush()
|
||||||
|
|
Loading…
Reference in a new issue