mirror of
https://github.com/rclone/rclone
synced 2024-11-24 01:26:25 +01:00
378a3f4133
os.File.WriteAt returns an error if a file was opened with O_APPEND. This replaces it with os.File.Write if the file was opened with O_APPEND.
597 lines
16 KiB
Go
597 lines
16 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 {
|
|
name := node.Name()
|
|
if len(name) > mountlib.MaxLeafSize {
|
|
fs.Errorf(dirPath, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name)
|
|
continue
|
|
}
|
|
fill(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
|
|
}
|
|
var err error
|
|
if fsys.VFS.Opt.CacheMode < vfs.CacheModeWrites || handle.Node().Mode()&os.ModeAppend == 0 {
|
|
n, err = handle.WriteAt(buff, ofst)
|
|
} else {
|
|
n, err = handle.Write(buff)
|
|
}
|
|
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
|
|
}
|