mirror of
https://github.com/rclone/rclone.git
synced 2024-12-23 15:38:57 +01:00
0baafb158f
Before this change, files of unknown size (eg Google Docs) would appear in file listings with 0 size and would only allow 0 bytes to be read. This change sets the direct_io flag in the FUSE return which bypasses the cache for these files. This means that they can be read properly. This is compatible with some, but not all applications.
587 lines
15 KiB
Go
587 lines
15 KiB
Go
// +build cmount
|
|
// +build cgo
|
|
// +build linux darwin freebsd windows
|
|
|
|
package cmount
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/billziss-gh/cgofuse/fuse"
|
|
"github.com/pkg/errors"
|
|
"github.com/rclone/rclone/cmd/mountlib"
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/log"
|
|
"github.com/rclone/rclone/vfs"
|
|
"github.com/rclone/rclone/vfs/vfsflags"
|
|
)
|
|
|
|
const fhUnset = ^uint64(0)
|
|
|
|
// FS represents the top level filing system
|
|
type FS struct {
|
|
VFS *vfs.VFS
|
|
f fs.Fs
|
|
ready chan (struct{})
|
|
mu sync.Mutex // to protect the below
|
|
handles []vfs.Handle
|
|
}
|
|
|
|
// NewFS makes a new FS
|
|
func NewFS(f fs.Fs) *FS {
|
|
fsys := &FS{
|
|
VFS: vfs.New(f, &vfsflags.Opt),
|
|
f: f,
|
|
ready: make(chan (struct{})),
|
|
}
|
|
return fsys
|
|
}
|
|
|
|
// Open a handle returning an integer file handle
|
|
func (fsys *FS) openHandle(handle vfs.Handle) (fh uint64) {
|
|
fsys.mu.Lock()
|
|
defer fsys.mu.Unlock()
|
|
var i int
|
|
var oldHandle vfs.Handle
|
|
for i, oldHandle = range fsys.handles {
|
|
if oldHandle == nil {
|
|
fsys.handles[i] = handle
|
|
goto found
|
|
}
|
|
}
|
|
fsys.handles = append(fsys.handles, handle)
|
|
i = len(fsys.handles) - 1
|
|
found:
|
|
return uint64(i)
|
|
}
|
|
|
|
// get the handle for fh, call with the lock held
|
|
func (fsys *FS) _getHandle(fh uint64) (i int, handle vfs.Handle, errc int) {
|
|
if fh > uint64(len(fsys.handles)) {
|
|
fs.Debugf(nil, "Bad file handle: too big: 0x%X", fh)
|
|
return i, nil, -fuse.EBADF
|
|
}
|
|
i = int(fh)
|
|
handle = fsys.handles[i]
|
|
if handle == nil {
|
|
fs.Debugf(nil, "Bad file handle: nil handle: 0x%X", fh)
|
|
return i, nil, -fuse.EBADF
|
|
}
|
|
return i, handle, 0
|
|
}
|
|
|
|
// Get the handle for the file handle
|
|
func (fsys *FS) getHandle(fh uint64) (handle vfs.Handle, errc int) {
|
|
fsys.mu.Lock()
|
|
_, handle, errc = fsys._getHandle(fh)
|
|
fsys.mu.Unlock()
|
|
return
|
|
}
|
|
|
|
// Close the handle
|
|
func (fsys *FS) closeHandle(fh uint64) (errc int) {
|
|
fsys.mu.Lock()
|
|
i, _, errc := fsys._getHandle(fh)
|
|
if errc == 0 {
|
|
fsys.handles[i] = nil
|
|
}
|
|
fsys.mu.Unlock()
|
|
return
|
|
}
|
|
|
|
// lookup a Node given a path
|
|
func (fsys *FS) lookupNode(path string) (node vfs.Node, errc int) {
|
|
node, err := fsys.VFS.Stat(path)
|
|
return node, translateError(err)
|
|
}
|
|
|
|
// lookup a Dir given a path
|
|
func (fsys *FS) lookupDir(path string) (dir *vfs.Dir, errc int) {
|
|
node, errc := fsys.lookupNode(path)
|
|
if errc != 0 {
|
|
return nil, errc
|
|
}
|
|
dir, ok := node.(*vfs.Dir)
|
|
if !ok {
|
|
return nil, -fuse.ENOTDIR
|
|
}
|
|
return dir, 0
|
|
}
|
|
|
|
// lookup a parent Dir given a path returning the dir and the leaf
|
|
func (fsys *FS) lookupParentDir(filePath string) (leaf string, dir *vfs.Dir, errc int) {
|
|
parentDir, leaf := path.Split(filePath)
|
|
dir, errc = fsys.lookupDir(parentDir)
|
|
return leaf, dir, errc
|
|
}
|
|
|
|
// lookup a File given a path
|
|
func (fsys *FS) lookupFile(path string) (file *vfs.File, errc int) {
|
|
node, errc := fsys.lookupNode(path)
|
|
if errc != 0 {
|
|
return nil, errc
|
|
}
|
|
file, ok := node.(*vfs.File)
|
|
if !ok {
|
|
return nil, -fuse.EISDIR
|
|
}
|
|
return file, 0
|
|
}
|
|
|
|
// get a node and handle from the path or from the fh if not fhUnset
|
|
//
|
|
// handle may be nil
|
|
func (fsys *FS) getNode(path string, fh uint64) (node vfs.Node, handle vfs.Handle, errc int) {
|
|
if fh == fhUnset {
|
|
node, errc = fsys.lookupNode(path)
|
|
} else {
|
|
handle, errc = fsys.getHandle(fh)
|
|
if errc == 0 {
|
|
node = handle.Node()
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// stat fills up the stat block for Node
|
|
func (fsys *FS) stat(node vfs.Node, stat *fuse.Stat_t) (errc int) {
|
|
Size := uint64(node.Size())
|
|
Blocks := (Size + 511) / 512
|
|
modTime := node.ModTime()
|
|
Mode := node.Mode().Perm()
|
|
if node.IsDir() {
|
|
Mode |= fuse.S_IFDIR
|
|
} else {
|
|
Mode |= fuse.S_IFREG
|
|
}
|
|
//stat.Dev = 1
|
|
stat.Ino = node.Inode() // FIXME do we need to set the inode number?
|
|
stat.Mode = uint32(Mode)
|
|
stat.Nlink = 1
|
|
stat.Uid = fsys.VFS.Opt.UID
|
|
stat.Gid = fsys.VFS.Opt.GID
|
|
//stat.Rdev
|
|
stat.Size = int64(Size)
|
|
t := fuse.NewTimespec(modTime)
|
|
stat.Atim = t
|
|
stat.Mtim = t
|
|
stat.Ctim = t
|
|
stat.Blksize = 512
|
|
stat.Blocks = int64(Blocks)
|
|
stat.Birthtim = t
|
|
// fs.Debugf(nil, "stat = %+v", *stat)
|
|
return 0
|
|
}
|
|
|
|
// Init is called after the filesystem is ready
|
|
func (fsys *FS) Init() {
|
|
defer log.Trace(fsys.f, "")("")
|
|
close(fsys.ready)
|
|
}
|
|
|
|
// Destroy is called when it is unmounted (note that depending on how
|
|
// the file system is terminated the file system may not receive the
|
|
// Destroy call).
|
|
func (fsys *FS) Destroy() {
|
|
defer log.Trace(fsys.f, "")("")
|
|
}
|
|
|
|
// Getattr reads the attributes for path
|
|
func (fsys *FS) Getattr(path string, stat *fuse.Stat_t, fh uint64) (errc int) {
|
|
defer log.Trace(path, "fh=0x%X", fh)("errc=%v", &errc)
|
|
node, _, errc := fsys.getNode(path, fh)
|
|
if errc == 0 {
|
|
errc = fsys.stat(node, stat)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Opendir opens path as a directory
|
|
func (fsys *FS) Opendir(path string) (errc int, fh uint64) {
|
|
defer log.Trace(path, "")("errc=%d, fh=0x%X", &errc, &fh)
|
|
handle, err := fsys.VFS.OpenFile(path, os.O_RDONLY, 0777)
|
|
if err != nil {
|
|
return translateError(err), fhUnset
|
|
}
|
|
return 0, fsys.openHandle(handle)
|
|
}
|
|
|
|
// Readdir reads the directory at dirPath
|
|
func (fsys *FS) Readdir(dirPath string,
|
|
fill func(name string, stat *fuse.Stat_t, ofst int64) bool,
|
|
ofst int64,
|
|
fh uint64) (errc int) {
|
|
itemsRead := -1
|
|
defer log.Trace(dirPath, "ofst=%d, fh=0x%X", ofst, fh)("items=%d, errc=%d", &itemsRead, &errc)
|
|
|
|
node, errc := fsys.getHandle(fh)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
|
|
items, err := node.Readdir(-1)
|
|
if err != nil {
|
|
return translateError(err)
|
|
}
|
|
|
|
// Optionally, create a struct stat that describes the file as
|
|
// for getattr (but FUSE only looks at st_ino and the
|
|
// file-type bits of st_mode).
|
|
//
|
|
// FIXME If you call host.SetCapReaddirPlus() then WinFsp will
|
|
// use the full stat information - a Useful optimization on
|
|
// Windows.
|
|
//
|
|
// NB we are using the first mode for readdir: The readdir
|
|
// implementation ignores the offset parameter, and passes
|
|
// zero to the filler function's offset. The filler function
|
|
// will not return '1' (unless an error happens), so the whole
|
|
// directory is read in a single readdir operation.
|
|
fill(".", nil, 0)
|
|
fill("..", nil, 0)
|
|
for _, item := range items {
|
|
node, ok := item.(vfs.Node)
|
|
if ok {
|
|
fill(node.Name(), nil, 0)
|
|
}
|
|
}
|
|
itemsRead = len(items)
|
|
return 0
|
|
}
|
|
|
|
// Releasedir finished reading the directory
|
|
func (fsys *FS) Releasedir(path string, fh uint64) (errc int) {
|
|
defer log.Trace(path, "fh=0x%X", fh)("errc=%d", &errc)
|
|
return fsys.closeHandle(fh)
|
|
}
|
|
|
|
// Statfs reads overall stats on the filessystem
|
|
func (fsys *FS) Statfs(path string, stat *fuse.Statfs_t) (errc int) {
|
|
defer log.Trace(path, "")("stat=%+v, errc=%d", stat, &errc)
|
|
const blockSize = 4096
|
|
const fsBlocks = (1 << 50) / blockSize
|
|
stat.Blocks = fsBlocks // Total data blocks in file system.
|
|
stat.Bfree = fsBlocks // Free blocks in file system.
|
|
stat.Bavail = fsBlocks // Free blocks in file system if you're not root.
|
|
stat.Files = 1e9 // Total files in file system.
|
|
stat.Ffree = 1e9 // Free files in file system.
|
|
stat.Bsize = blockSize // Block size
|
|
stat.Namemax = 255 // Maximum file name length?
|
|
stat.Frsize = blockSize // Fragment size, smallest addressable data size in the file system.
|
|
total, used, free := fsys.VFS.Statfs()
|
|
if total >= 0 {
|
|
stat.Blocks = uint64(total) / blockSize
|
|
}
|
|
if used >= 0 {
|
|
stat.Bfree = stat.Blocks - uint64(used)/blockSize
|
|
}
|
|
if free >= 0 {
|
|
stat.Bavail = uint64(free) / blockSize
|
|
}
|
|
mountlib.ClipBlocks(&stat.Blocks)
|
|
mountlib.ClipBlocks(&stat.Bfree)
|
|
mountlib.ClipBlocks(&stat.Bavail)
|
|
return 0
|
|
}
|
|
|
|
// Open opens a file
|
|
func (fsys *FS) Open(path string, flags int) (errc int, fh uint64) {
|
|
defer log.Trace(path, "flags=0x%X", flags)("errc=%d, fh=0x%X", &errc, &fh)
|
|
|
|
// translate the fuse flags to os flags
|
|
flags = translateOpenFlags(flags)
|
|
handle, err := fsys.VFS.OpenFile(path, flags, 0777)
|
|
if err != nil {
|
|
return translateError(err), fhUnset
|
|
}
|
|
|
|
// FIXME add support for unknown length files setting direct_io
|
|
// See: https://github.com/billziss-gh/cgofuse/issues/38
|
|
|
|
return 0, fsys.openHandle(handle)
|
|
}
|
|
|
|
// Create creates and opens a file.
|
|
func (fsys *FS) Create(filePath string, flags int, mode uint32) (errc int, fh uint64) {
|
|
defer log.Trace(filePath, "flags=0x%X, mode=0%o", flags, mode)("errc=%d, fh=0x%X", &errc, &fh)
|
|
leaf, parentDir, errc := fsys.lookupParentDir(filePath)
|
|
if errc != 0 {
|
|
return errc, fhUnset
|
|
}
|
|
file, err := parentDir.Create(leaf, flags)
|
|
if err != nil {
|
|
return translateError(err), fhUnset
|
|
}
|
|
// translate the fuse flags to os flags
|
|
flags = translateOpenFlags(flags) | os.O_CREATE
|
|
handle, err := file.Open(flags)
|
|
if err != nil {
|
|
return translateError(err), fhUnset
|
|
}
|
|
return 0, fsys.openHandle(handle)
|
|
}
|
|
|
|
// Truncate truncates a file to size
|
|
func (fsys *FS) Truncate(path string, size int64, fh uint64) (errc int) {
|
|
defer log.Trace(path, "size=%d, fh=0x%X", size, fh)("errc=%d", &errc)
|
|
node, handle, errc := fsys.getNode(path, fh)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
var err error
|
|
if handle != nil {
|
|
err = handle.Truncate(size)
|
|
} else {
|
|
err = node.Truncate(size)
|
|
}
|
|
if err != nil {
|
|
return translateError(err)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Read data from file handle
|
|
func (fsys *FS) Read(path string, buff []byte, ofst int64, fh uint64) (n int) {
|
|
defer log.Trace(path, "ofst=%d, fh=0x%X", ofst, fh)("n=%d", &n)
|
|
handle, errc := fsys.getHandle(fh)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
n, err := handle.ReadAt(buff, ofst)
|
|
if err == io.EOF {
|
|
} else if err != nil {
|
|
return translateError(err)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// Write data to file handle
|
|
func (fsys *FS) Write(path string, buff []byte, ofst int64, fh uint64) (n int) {
|
|
defer log.Trace(path, "ofst=%d, fh=0x%X", ofst, fh)("n=%d", &n)
|
|
handle, errc := fsys.getHandle(fh)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
n, err := handle.WriteAt(buff, ofst)
|
|
if err != nil {
|
|
return translateError(err)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// Flush flushes an open file descriptor or path
|
|
func (fsys *FS) Flush(path string, fh uint64) (errc int) {
|
|
defer log.Trace(path, "fh=0x%X", fh)("errc=%d", &errc)
|
|
handle, errc := fsys.getHandle(fh)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
return translateError(handle.Flush())
|
|
}
|
|
|
|
// Release closes the file if still open
|
|
func (fsys *FS) Release(path string, fh uint64) (errc int) {
|
|
defer log.Trace(path, "fh=0x%X", fh)("errc=%d", &errc)
|
|
handle, errc := fsys.getHandle(fh)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
_ = fsys.closeHandle(fh)
|
|
return translateError(handle.Release())
|
|
}
|
|
|
|
// Unlink removes a file.
|
|
func (fsys *FS) Unlink(filePath string) (errc int) {
|
|
defer log.Trace(filePath, "")("errc=%d", &errc)
|
|
leaf, parentDir, errc := fsys.lookupParentDir(filePath)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
return translateError(parentDir.RemoveName(leaf))
|
|
}
|
|
|
|
// Mkdir creates a directory.
|
|
func (fsys *FS) Mkdir(dirPath string, mode uint32) (errc int) {
|
|
defer log.Trace(dirPath, "mode=0%o", mode)("errc=%d", &errc)
|
|
leaf, parentDir, errc := fsys.lookupParentDir(dirPath)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
_, err := parentDir.Mkdir(leaf)
|
|
return translateError(err)
|
|
}
|
|
|
|
// Rmdir removes a directory
|
|
func (fsys *FS) Rmdir(dirPath string) (errc int) {
|
|
defer log.Trace(dirPath, "")("errc=%d", &errc)
|
|
leaf, parentDir, errc := fsys.lookupParentDir(dirPath)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
return translateError(parentDir.RemoveName(leaf))
|
|
}
|
|
|
|
// Rename renames a file.
|
|
func (fsys *FS) Rename(oldPath string, newPath string) (errc int) {
|
|
defer log.Trace(oldPath, "newPath=%q", newPath)("errc=%d", &errc)
|
|
return translateError(fsys.VFS.Rename(oldPath, newPath))
|
|
}
|
|
|
|
// Utimens changes the access and modification times of a file.
|
|
func (fsys *FS) Utimens(path string, tmsp []fuse.Timespec) (errc int) {
|
|
defer log.Trace(path, "tmsp=%+v", tmsp)("errc=%d", &errc)
|
|
node, errc := fsys.lookupNode(path)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
var t time.Time
|
|
if tmsp == nil || len(tmsp) < 2 {
|
|
t = time.Now()
|
|
} else {
|
|
t = tmsp[1].Time()
|
|
}
|
|
return translateError(node.SetModTime(t))
|
|
}
|
|
|
|
// Mknod creates a file node.
|
|
func (fsys *FS) Mknod(path string, mode uint32, dev uint64) (errc int) {
|
|
defer log.Trace(path, "mode=0x%X, dev=0x%X", mode, dev)("errc=%d", &errc)
|
|
return -fuse.ENOSYS
|
|
}
|
|
|
|
// Fsync synchronizes file contents.
|
|
func (fsys *FS) Fsync(path string, datasync bool, fh uint64) (errc int) {
|
|
defer log.Trace(path, "datasync=%v, fh=0x%X", datasync, fh)("errc=%d", &errc)
|
|
// This is a no-op for rclone
|
|
return 0
|
|
}
|
|
|
|
// Link creates a hard link to a file.
|
|
func (fsys *FS) Link(oldpath string, newpath string) (errc int) {
|
|
defer log.Trace(oldpath, "newpath=%q", newpath)("errc=%d", &errc)
|
|
return -fuse.ENOSYS
|
|
}
|
|
|
|
// Symlink creates a symbolic link.
|
|
func (fsys *FS) Symlink(target string, newpath string) (errc int) {
|
|
defer log.Trace(target, "newpath=%q", newpath)("errc=%d", &errc)
|
|
return -fuse.ENOSYS
|
|
}
|
|
|
|
// Readlink reads the target of a symbolic link.
|
|
func (fsys *FS) Readlink(path string) (errc int, linkPath string) {
|
|
defer log.Trace(path, "")("linkPath=%q, errc=%d", &linkPath, &errc)
|
|
return -fuse.ENOSYS, ""
|
|
}
|
|
|
|
// Chmod changes the permission bits of a file.
|
|
func (fsys *FS) Chmod(path string, mode uint32) (errc int) {
|
|
defer log.Trace(path, "mode=0%o", mode)("errc=%d", &errc)
|
|
// This is a no-op for rclone
|
|
return 0
|
|
}
|
|
|
|
// Chown changes the owner and group of a file.
|
|
func (fsys *FS) Chown(path string, uid uint32, gid uint32) (errc int) {
|
|
defer log.Trace(path, "uid=%d, gid=%d", uid, gid)("errc=%d", &errc)
|
|
// This is a no-op for rclone
|
|
return 0
|
|
}
|
|
|
|
// Access checks file access permissions.
|
|
func (fsys *FS) Access(path string, mask uint32) (errc int) {
|
|
defer log.Trace(path, "mask=0%o", mask)("errc=%d", &errc)
|
|
// This is a no-op for rclone
|
|
return 0
|
|
}
|
|
|
|
// Fsyncdir synchronizes directory contents.
|
|
func (fsys *FS) Fsyncdir(path string, datasync bool, fh uint64) (errc int) {
|
|
defer log.Trace(path, "datasync=%v, fh=0x%X", datasync, fh)("errc=%d", &errc)
|
|
// This is a no-op for rclone
|
|
return 0
|
|
}
|
|
|
|
// Setxattr sets extended attributes.
|
|
func (fsys *FS) Setxattr(path string, name string, value []byte, flags int) (errc int) {
|
|
return -fuse.ENOSYS
|
|
}
|
|
|
|
// Getxattr gets extended attributes.
|
|
func (fsys *FS) Getxattr(path string, name string) (errc int, value []byte) {
|
|
return -fuse.ENOSYS, nil
|
|
}
|
|
|
|
// Removexattr removes extended attributes.
|
|
func (fsys *FS) Removexattr(path string, name string) (errc int) {
|
|
return -fuse.ENOSYS
|
|
}
|
|
|
|
// Listxattr lists extended attributes.
|
|
func (fsys *FS) Listxattr(path string, fill func(name string) bool) (errc int) {
|
|
return -fuse.ENOSYS
|
|
}
|
|
|
|
// Translate errors from mountlib
|
|
func translateError(err error) (errc int) {
|
|
if err == nil {
|
|
return 0
|
|
}
|
|
switch errors.Cause(err) {
|
|
case vfs.OK:
|
|
return 0
|
|
case vfs.ENOENT:
|
|
return -fuse.ENOENT
|
|
case vfs.EEXIST:
|
|
return -fuse.EEXIST
|
|
case vfs.EPERM:
|
|
return -fuse.EPERM
|
|
case vfs.ECLOSED:
|
|
return -fuse.EBADF
|
|
case vfs.ENOTEMPTY:
|
|
return -fuse.ENOTEMPTY
|
|
case vfs.ESPIPE:
|
|
return -fuse.ESPIPE
|
|
case vfs.EBADF:
|
|
return -fuse.EBADF
|
|
case vfs.EROFS:
|
|
return -fuse.EROFS
|
|
case vfs.ENOSYS:
|
|
return -fuse.ENOSYS
|
|
case vfs.EINVAL:
|
|
return -fuse.EINVAL
|
|
}
|
|
fs.Errorf(nil, "IO error: %v", err)
|
|
return -fuse.EIO
|
|
}
|
|
|
|
// Translate Open Flags from FUSE to os (as used in the vfs layer)
|
|
func translateOpenFlags(inFlags int) (outFlags int) {
|
|
switch inFlags & fuse.O_ACCMODE {
|
|
case fuse.O_RDONLY:
|
|
outFlags = os.O_RDONLY
|
|
case fuse.O_WRONLY:
|
|
outFlags = os.O_WRONLY
|
|
case fuse.O_RDWR:
|
|
outFlags = os.O_RDWR
|
|
}
|
|
if inFlags&fuse.O_APPEND != 0 {
|
|
outFlags |= os.O_APPEND
|
|
}
|
|
if inFlags&fuse.O_CREAT != 0 {
|
|
outFlags |= os.O_CREATE
|
|
}
|
|
if inFlags&fuse.O_EXCL != 0 {
|
|
outFlags |= os.O_EXCL
|
|
}
|
|
if inFlags&fuse.O_TRUNC != 0 {
|
|
outFlags |= os.O_TRUNC
|
|
}
|
|
// NB O_SYNC isn't defined by fuse
|
|
return outFlags
|
|
}
|