// +build cmount // +build cgo // +build linux darwin freebsd windows package cmount import ( "os" "path" "runtime" "sync" "time" "github.com/billziss-gh/cgofuse/fuse" "github.com/ncw/rclone/cmd/mountlib" "github.com/ncw/rclone/fs" "github.com/pkg/errors" ) const fhUnset = ^uint64(0) // FS represents the top level filing system type FS struct { FS *mountlib.FS f fs.Fs openDirs *openFiles openFilesWr *openFiles openFilesRd *openFiles ready chan (struct{}) } // NewFS makes a new FS func NewFS(f fs.Fs) *FS { fsys := &FS{ FS: mountlib.NewFS(f), f: f, openDirs: newOpenFiles(0x01), openFilesWr: newOpenFiles(0x02), openFilesRd: newOpenFiles(0x03), ready: make(chan (struct{})), } return fsys } type openFiles struct { mu sync.Mutex mark uint8 nodes []mountlib.Noder } func newOpenFiles(mark uint8) *openFiles { return &openFiles{ mark: mark, } } // Open a node returning a file handle func (of *openFiles) Open(node mountlib.Noder) (fh uint64) { of.mu.Lock() defer of.mu.Unlock() var i int var oldNode mountlib.Noder for i, oldNode = range of.nodes { if oldNode == nil { of.nodes[i] = node goto found } } of.nodes = append(of.nodes, node) i = len(of.nodes) - 1 found: return uint64((i << 8) | int(of.mark)) } // InRange to see if this fh could be one of ours func (of *openFiles) InRange(fh uint64) bool { return uint8(fh) == of.mark } // get the node for fh, call with the lock held func (of *openFiles) get(fh uint64) (i int, node mountlib.Noder, errc int) { receivedMark := uint8(fh) if receivedMark != of.mark { fs.Debugf(nil, "Bad file handle: bad mark 0x%X != 0x%X: 0x%X", receivedMark, of.mark, fh) return i, nil, -fuse.EBADF } i64 := fh >> 8 if i64 > uint64(len(of.nodes)) { fs.Debugf(nil, "Bad file handle: too big: 0x%X", fh) return i, nil, -fuse.EBADF } i = int(i64) node = of.nodes[i] if node == nil { fs.Debugf(nil, "Bad file handle: nil node: 0x%X", fh) return i, nil, -fuse.EBADF } return i, node, 0 } // Get the node for the file handle func (of *openFiles) Get(fh uint64) (node mountlib.Noder, errc int) { of.mu.Lock() _, node, errc = of.get(fh) of.mu.Unlock() return } // Close the node func (of *openFiles) Close(fh uint64) (errc int) { of.mu.Lock() i, _, errc := of.get(fh) if errc == 0 { of.nodes[i] = nil } of.mu.Unlock() return } // lookup a Node given a path func (fsys *FS) lookupNode(path string) (node mountlib.Node, errc int) { node, err := fsys.FS.Lookup(path) return node, translateError(err) } // lookup a Dir given a path func (fsys *FS) lookupDir(path string) (dir *mountlib.Dir, errc int) { node, errc := fsys.lookupNode(path) if errc != 0 { return nil, errc } dir, ok := node.(*mountlib.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 *mountlib.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 *mountlib.File, errc int) { node, errc := fsys.lookupNode(path) if errc != 0 { return nil, errc } file, ok := node.(*mountlib.File) if !ok { return nil, -fuse.EISDIR } return file, 0 } // Get the underlying openFile handle from the file handle func (fsys *FS) getOpenFilesFromFh(fh uint64) (of *openFiles, errc int) { switch { case fsys.openFilesRd.InRange(fh): return fsys.openFilesRd, 0 case fsys.openFilesWr.InRange(fh): return fsys.openFilesWr, 0 case fsys.openDirs.InRange(fh): return fsys.openDirs, 0 } return nil, -fuse.EBADF } // Get the underlying handle from the file handle func (fsys *FS) getHandleFromFh(fh uint64) (handle mountlib.Noder, errc int) { of, errc := fsys.getOpenFilesFromFh(fh) if errc != 0 { return nil, errc } return of.Get(fh) } // get a node from the path or from the fh if not fhUnset func (fsys *FS) getNode(path string, fh uint64) (node mountlib.Node, errc int) { if fh == fhUnset { node, errc = fsys.lookupNode(path) } else { var n mountlib.Noder n, errc = fsys.getHandleFromFh(fh) if errc == 0 { node = n.Node() } } return } // stat fills up the stat block for Node func (fsys *FS) stat(node mountlib.Node, stat *fuse.Stat_t) (errc int) { var Size uint64 var Blocks uint64 var modTime time.Time var Mode os.FileMode switch x := node.(type) { case *mountlib.Dir: modTime = x.ModTime() Mode = mountlib.DirPerms | fuse.S_IFDIR case *mountlib.File: var err error modTime, Size, Blocks, err = x.Attr(mountlib.NoModTime) if err != nil { return translateError(err) } Mode = mountlib.FilePerms | 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 = mountlib.UID stat.Gid = mountlib.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 fs.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 fs.Trace(fsys.f, "")("") } // Getattr reads the attributes for path func (fsys *FS) Getattr(path string, stat *fuse.Stat_t, fh uint64) (errc int) { defer fs.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 fs.Trace(path, "")("errc=%d, fh=0x%X", &errc, &fh) dir, errc := fsys.lookupDir(path) if errc == 0 { fh = fsys.openDirs.Open(dir) } else { fh = fhUnset } return } // 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 fs.Trace(dirPath, "ofst=%d, fh=0x%X", ofst, fh)("items=%d, errc=%d", &itemsRead, &errc) node, errc := fsys.openDirs.Get(fh) if errc != 0 { return errc } dir, ok := node.(*mountlib.Dir) if !ok { return -fuse.ENOTDIR } items, err := dir.ReadDirAll() 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 { name := path.Base(item.Obj.Remote()) 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 fs.Trace(path, "fh=0x%X", fh)("errc=%d", &errc) return fsys.openDirs.Close(fh) } // Statfs reads overall stats on the filessystem func (fsys *FS) Statfs(path string, stat *fuse.Statfs_t) (errc int) { defer fs.Trace(path, "")("stat=%+v, errc=%d", stat, &errc) const blockSize = 4096 fsBlocks := uint64(1 << 50) if runtime.GOOS == "windows" { fsBlocks = (1 << 43) - 1 } 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. return 0 } // Open opens a file func (fsys *FS) Open(path string, flags int) (errc int, fh uint64) { defer fs.Trace(path, "flags=0x%X", flags)("errc=%d, fh=0x%X", &errc, &fh) file, errc := fsys.lookupFile(path) if errc != 0 { return errc, fhUnset } rdwrMode := flags & fuse.O_ACCMODE var err error var handle mountlib.Noder switch { case rdwrMode == fuse.O_RDONLY: handle, err = file.OpenRead() if err != nil { return translateError(err), fhUnset } return 0, fsys.openFilesRd.Open(handle) case rdwrMode == fuse.O_WRONLY || (rdwrMode == fuse.O_RDWR && (flags&fuse.O_TRUNC) != 0): handle, err = file.OpenWrite() if err != nil { return translateError(err), fhUnset } return 0, fsys.openFilesWr.Open(handle) case rdwrMode == fuse.O_RDWR: fs.Errorf(path, "Can't open for Read and Write") return -fuse.EPERM, fhUnset } fs.Errorf(path, "Can't figure out how to open with flags: 0x%X", flags) return -fuse.EPERM, fhUnset } // Create creates and opens a file. func (fsys *FS) Create(filePath string, flags int, mode uint32) (errc int, fh uint64) { defer fs.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 } _, handle, err := parentDir.Create(leaf) if err != nil { return translateError(err), fhUnset } return 0, fsys.openFilesWr.Open(handle) } // Truncate truncates a file to size func (fsys *FS) Truncate(path string, size int64, fh uint64) (errc int) { defer fs.Trace(path, "size=%d, fh=0x%X", size, fh)("errc=%d", &errc) node, errc := fsys.getNode(path, fh) if errc != 0 { return errc } file, ok := node.(*mountlib.File) if !ok { return -fuse.EIO } // Read the size so far _, currentSize, _, err := file.Attr(true) if err != nil { return translateError(err) } fs.Debugf(path, "truncate to %d, currentSize %d", size, currentSize) if int64(currentSize) != size { fs.Errorf(path, "Can't truncate files") return -fuse.EPERM } return 0 } func (fsys *FS) Read(path string, buff []byte, ofst int64, fh uint64) (n int) { defer fs.Trace(path, "ofst=%d, fh=0x%X", ofst, fh)("n=%d", &n) // FIXME detect seek handle, errc := fsys.openFilesRd.Get(fh) if errc != 0 { return errc } rfh, ok := handle.(*mountlib.ReadFileHandle) if !ok { // Can only read from read file handle return -fuse.EIO } data, err := rfh.Read(int64(len(buff)), ofst) if err != nil { return translateError(err) } n = copy(buff, data) return n } func (fsys *FS) Write(path string, buff []byte, ofst int64, fh uint64) (n int) { defer fs.Trace(path, "ofst=%d, fh=0x%X", ofst, fh)("n=%d", &n) // FIXME detect seek handle, errc := fsys.openFilesWr.Get(fh) if errc != 0 { return errc } wfh, ok := handle.(*mountlib.WriteFileHandle) if !ok { // Can only write to write file handle return -fuse.EIO } // FIXME made Write return int and Read take int since must fit in RAM n64, err := wfh.Write(buff, ofst) if err != nil { return translateError(err) } return int(n64) } // Flush flushes an open file descriptor or path func (fsys *FS) Flush(path string, fh uint64) (errc int) { defer fs.Trace(path, "fh=0x%X", fh)("errc=%d", &errc) handle, errc := fsys.getHandleFromFh(fh) if errc != 0 { return errc } var err error switch x := handle.(type) { case *mountlib.ReadFileHandle: err = x.Flush() case *mountlib.WriteFileHandle: err = x.Flush() default: return -fuse.EIO } return translateError(err) } // Release closes the file if still open func (fsys *FS) Release(path string, fh uint64) (errc int) { defer fs.Trace(path, "fh=0x%X", fh)("errc=%d", &errc) of, errc := fsys.getOpenFilesFromFh(fh) if errc != 0 { return errc } handle, errc := of.Get(fh) if errc != 0 { return errc } _ = of.Close(fh) var err error switch x := handle.(type) { case *mountlib.ReadFileHandle: err = x.Release() case *mountlib.WriteFileHandle: err = x.Release() default: return -fuse.EIO } return translateError(err) } // Unlink removes a file. func (fsys *FS) Unlink(filePath string) (errc int) { defer fs.Trace(filePath, "")("errc=%d", &errc) leaf, parentDir, errc := fsys.lookupParentDir(filePath) if errc != 0 { return errc } return translateError(parentDir.Remove(leaf)) } // Mkdir creates a directory. func (fsys *FS) Mkdir(dirPath string, mode uint32) (errc int) { defer fs.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 fs.Trace(dirPath, "")("errc=%d", &errc) leaf, parentDir, errc := fsys.lookupParentDir(dirPath) if errc != 0 { return errc } return translateError(parentDir.Remove(leaf)) } // Rename renames a file. func (fsys *FS) Rename(oldPath string, newPath string) (errc int) { defer fs.Trace(oldPath, "newPath=%q", newPath)("errc=%d", &errc) oldLeaf, oldParentDir, errc := fsys.lookupParentDir(oldPath) if errc != 0 { return errc } newLeaf, newParentDir, errc := fsys.lookupParentDir(newPath) if errc != 0 { return errc } return translateError(oldParentDir.Rename(oldLeaf, newLeaf, newParentDir)) } // Utimens changes the access and modification times of a file. func (fsys *FS) Utimens(path string, tmsp []fuse.Timespec) (errc int) { defer fs.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() } var err error switch x := node.(type) { case *mountlib.Dir: err = x.SetModTime(t) case *mountlib.File: err = x.SetModTime(t) } return translateError(err) } // Mknod creates a file node. func (fsys *FS) Mknod(path string, mode uint32, dev uint64) (errc int) { defer fs.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 fs.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 fs.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 fs.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 fs.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 fs.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 fs.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 fs.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 fs.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 } cause := errors.Cause(err) if mErr, ok := cause.(mountlib.Error); ok { switch mErr { case mountlib.OK: return 0 case mountlib.ENOENT: return -fuse.ENOENT case mountlib.ENOTEMPTY: return -fuse.ENOTEMPTY case mountlib.EEXIST: return -fuse.EEXIST case mountlib.ESPIPE: return -fuse.ESPIPE case mountlib.EBADF: return -fuse.EBADF case mountlib.EROFS: return -fuse.EROFS } } fs.Errorf(nil, "IO error: %v", err) return -fuse.EIO }