overdue
This commit is contained in:
583
.rclone_repo/cmd/cmount/fs.go
Executable file
583
.rclone_repo/cmd/cmount/fs.go
Executable file
@@ -0,0 +1,583 @@
|
||||
// +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/ncw/rclone/cmd/mountlib"
|
||||
"github.com/ncw/rclone/fs"
|
||||
"github.com/ncw/rclone/fs/log"
|
||||
"github.com/ncw/rclone/vfs"
|
||||
"github.com/ncw/rclone/vfs/vfsflags"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
243
.rclone_repo/cmd/cmount/mount.go
Executable file
243
.rclone_repo/cmd/cmount/mount.go
Executable file
@@ -0,0 +1,243 @@
|
||||
// Package cmount implents a FUSE mounting system for rclone remotes.
|
||||
//
|
||||
// This uses the cgo based cgofuse library
|
||||
|
||||
// +build cmount
|
||||
// +build cgo
|
||||
// +build linux darwin freebsd windows
|
||||
|
||||
package cmount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/billziss-gh/cgofuse/fuse"
|
||||
"github.com/ncw/rclone/cmd/mountlib"
|
||||
"github.com/ncw/rclone/fs"
|
||||
"github.com/ncw/rclone/vfs"
|
||||
"github.com/ncw/rclone/vfs/vfsflags"
|
||||
"github.com/okzk/sdnotify"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
name := "cmount"
|
||||
if runtime.GOOS == "windows" {
|
||||
name = "mount"
|
||||
}
|
||||
mountlib.NewMountCommand(name, Mount)
|
||||
}
|
||||
|
||||
// mountOptions configures the options from the command line flags
|
||||
func mountOptions(device string, mountpoint string) (options []string) {
|
||||
// Options
|
||||
options = []string{
|
||||
"-o", "fsname=" + device,
|
||||
"-o", "subtype=rclone",
|
||||
"-o", fmt.Sprintf("max_readahead=%d", mountlib.MaxReadAhead),
|
||||
"-o", fmt.Sprintf("attr_timeout=%g", mountlib.AttrTimeout.Seconds()),
|
||||
// This causes FUSE to supply O_TRUNC with the Open
|
||||
// call which is more efficient for cmount. However
|
||||
// it does not work with cgofuse on Windows with
|
||||
// WinFSP so cmount must work with or without it.
|
||||
"-o", "atomic_o_trunc",
|
||||
}
|
||||
if mountlib.DebugFUSE {
|
||||
options = append(options, "-o", "debug")
|
||||
}
|
||||
|
||||
// OSX options
|
||||
if runtime.GOOS == "darwin" {
|
||||
options = append(options, "-o", "volname="+mountlib.VolumeName)
|
||||
if mountlib.NoAppleDouble {
|
||||
options = append(options, "-o", "noappledouble")
|
||||
}
|
||||
if mountlib.NoAppleXattr {
|
||||
options = append(options, "-o", "noapplexattr")
|
||||
}
|
||||
}
|
||||
|
||||
// Windows options
|
||||
if runtime.GOOS == "windows" {
|
||||
// These cause WinFsp to mean the current user
|
||||
options = append(options, "-o", "uid=-1")
|
||||
options = append(options, "-o", "gid=-1")
|
||||
options = append(options, "--FileSystemName=rclone")
|
||||
}
|
||||
|
||||
if mountlib.AllowNonEmpty {
|
||||
options = append(options, "-o", "nonempty")
|
||||
}
|
||||
if mountlib.AllowOther {
|
||||
options = append(options, "-o", "allow_other")
|
||||
}
|
||||
if mountlib.AllowRoot {
|
||||
options = append(options, "-o", "allow_root")
|
||||
}
|
||||
if mountlib.DefaultPermissions {
|
||||
options = append(options, "-o", "default_permissions")
|
||||
}
|
||||
if vfsflags.Opt.ReadOnly {
|
||||
options = append(options, "-o", "ro")
|
||||
}
|
||||
if mountlib.WritebackCache {
|
||||
// FIXME? options = append(options, "-o", WritebackCache())
|
||||
}
|
||||
if mountlib.DaemonTimeout != 0 {
|
||||
options = append(options, "-o", fmt.Sprintf("daemon_timeout=%d", int(mountlib.DaemonTimeout.Seconds())))
|
||||
}
|
||||
for _, option := range mountlib.ExtraOptions {
|
||||
options = append(options, "-o", option)
|
||||
}
|
||||
for _, option := range mountlib.ExtraFlags {
|
||||
options = append(options, option)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// waitFor runs fn() until it returns true or the timeout expires
|
||||
func waitFor(fn func() bool) (ok bool) {
|
||||
const totalWait = 10 * time.Second
|
||||
const individualWait = 10 * time.Millisecond
|
||||
for i := 0; i < int(totalWait/individualWait); i++ {
|
||||
ok = fn()
|
||||
if ok {
|
||||
return ok
|
||||
}
|
||||
time.Sleep(individualWait)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// mount the file system
|
||||
//
|
||||
// The mount point will be ready when this returns.
|
||||
//
|
||||
// returns an error, and an error channel for the serve process to
|
||||
// report an error when fusermount is called.
|
||||
func mount(f fs.Fs, mountpoint string) (*vfs.VFS, <-chan error, func() error, error) {
|
||||
fs.Debugf(f, "Mounting on %q", mountpoint)
|
||||
|
||||
// Check the mountpoint - in Windows the mountpoint musn't exist before the mount
|
||||
if runtime.GOOS != "windows" {
|
||||
fi, err := os.Stat(mountpoint)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(err, "mountpoint")
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return nil, nil, nil, errors.New("mountpoint is not a directory")
|
||||
}
|
||||
}
|
||||
|
||||
// Create underlying FS
|
||||
fsys := NewFS(f)
|
||||
host := fuse.NewFileSystemHost(fsys)
|
||||
|
||||
// Create options
|
||||
options := mountOptions(f.Name()+":"+f.Root(), mountpoint)
|
||||
fs.Debugf(f, "Mounting with options: %q", options)
|
||||
|
||||
// Serve the mount point in the background returning error to errChan
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
var err error
|
||||
ok := host.Mount(mountpoint, options)
|
||||
if !ok {
|
||||
err = errors.New("mount failed")
|
||||
fs.Errorf(f, "Mount failed")
|
||||
}
|
||||
errChan <- err
|
||||
}()
|
||||
|
||||
// unmount
|
||||
unmount := func() error {
|
||||
// Shutdown the VFS
|
||||
fsys.VFS.Shutdown()
|
||||
fs.Debugf(nil, "Calling host.Unmount")
|
||||
if host.Unmount() {
|
||||
fs.Debugf(nil, "host.Unmount succeeded")
|
||||
if runtime.GOOS == "windows" {
|
||||
if !waitFor(func() bool {
|
||||
_, err := os.Stat(mountpoint)
|
||||
return err != nil
|
||||
}) {
|
||||
fs.Errorf(nil, "mountpoint %q didn't disappear after unmount - continuing anyway", mountpoint)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
fs.Debugf(nil, "host.Unmount failed")
|
||||
return errors.New("host unmount failed")
|
||||
}
|
||||
|
||||
// Wait for the filesystem to become ready, checking the file
|
||||
// system didn't blow up before starting
|
||||
select {
|
||||
case err := <-errChan:
|
||||
err = errors.Wrap(err, "mount stopped before calling Init")
|
||||
return nil, nil, nil, err
|
||||
case <-fsys.ready:
|
||||
}
|
||||
|
||||
// Wait for the mount point to be available on Windows
|
||||
// On Windows the Init signal comes slightly before the mount is ready
|
||||
if runtime.GOOS == "windows" {
|
||||
if !waitFor(func() bool {
|
||||
_, err := os.Stat(mountpoint)
|
||||
return err == nil
|
||||
}) {
|
||||
fs.Errorf(nil, "mountpoint %q didn't became available on mount - continuing anyway", mountpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return fsys.VFS, errChan, unmount, nil
|
||||
}
|
||||
|
||||
// Mount mounts the remote at mountpoint.
|
||||
//
|
||||
// If noModTime is set then it
|
||||
func Mount(f fs.Fs, mountpoint string) error {
|
||||
// Mount it
|
||||
FS, errChan, _, err := mount(f, mountpoint)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to mount FUSE fs")
|
||||
}
|
||||
|
||||
// Note cgofuse unmounts the fs on SIGINT etc
|
||||
|
||||
sigHup := make(chan os.Signal, 1)
|
||||
signal.Notify(sigHup, syscall.SIGHUP)
|
||||
|
||||
if err := sdnotify.Ready(); err != nil && err != sdnotify.ErrSdNotifyNoSocket {
|
||||
return errors.Wrap(err, "failed to notify systemd")
|
||||
}
|
||||
|
||||
waitloop:
|
||||
for {
|
||||
select {
|
||||
// umount triggered outside the app
|
||||
case err = <-errChan:
|
||||
break waitloop
|
||||
// user sent SIGHUP to clear the cache
|
||||
case <-sigHup:
|
||||
root, err := FS.Root()
|
||||
if err != nil {
|
||||
fs.Errorf(f, "Error reading root: %v", err)
|
||||
} else {
|
||||
root.ForgetAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = sdnotify.Stopping()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to umount FUSE fs")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
19
.rclone_repo/cmd/cmount/mount_test.go
Executable file
19
.rclone_repo/cmd/cmount/mount_test.go
Executable file
@@ -0,0 +1,19 @@
|
||||
// +build cmount
|
||||
// +build cgo
|
||||
// +build linux darwin freebsd windows
|
||||
// +build !race !windows
|
||||
|
||||
// FIXME this doesn't work with the race detector under Windows either
|
||||
// hanging or producing lots of differences.
|
||||
|
||||
package cmount
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncw/rclone/cmd/mountlib/mounttest"
|
||||
)
|
||||
|
||||
func TestMount(t *testing.T) {
|
||||
mounttest.RunTests(t, mount)
|
||||
}
|
||||
6
.rclone_repo/cmd/cmount/mount_unsupported.go
Executable file
6
.rclone_repo/cmd/cmount/mount_unsupported.go
Executable file
@@ -0,0 +1,6 @@
|
||||
// Build for cmount for unsupported platforms to stop go complaining
|
||||
// about "no buildable Go source files "
|
||||
|
||||
// +build !linux,!darwin,!freebsd,!windows !cgo !cmount
|
||||
|
||||
package cmount
|
||||
Reference in New Issue
Block a user