mirror of
https://github.com/rclone/rclone.git
synced 2024-11-27 02:34:21 +08:00
c1aaff220d
This is an OS style file system abstraction with directory caching used in mount, cmount, serve webdav and serve http.
657 lines
16 KiB
Go
657 lines
16 KiB
Go
// +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/fs"
|
|
"github.com/ncw/rclone/vfs"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const fhUnset = ^uint64(0)
|
|
|
|
// FS represents the top level filing system
|
|
type FS struct {
|
|
VFS *vfs.VFS
|
|
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{
|
|
VFS: vfs.New(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 []vfs.Noder
|
|
}
|
|
|
|
func newOpenFiles(mark uint8) *openFiles {
|
|
return &openFiles{
|
|
mark: mark,
|
|
}
|
|
}
|
|
|
|
// Open a node returning a file handle
|
|
func (of *openFiles) Open(node vfs.Noder) (fh uint64) {
|
|
of.mu.Lock()
|
|
defer of.mu.Unlock()
|
|
var i int
|
|
var oldNode vfs.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 vfs.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 vfs.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 vfs.Node, errc int) {
|
|
node, err := fsys.VFS.Lookup(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 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 vfs.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 vfs.Node, errc int) {
|
|
if fh == fhUnset {
|
|
node, errc = fsys.lookupNode(path)
|
|
} else {
|
|
var n vfs.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 vfs.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 *vfs.Dir:
|
|
modTime = x.ModTime()
|
|
Mode = vfs.DirPerms | fuse.S_IFDIR
|
|
case *vfs.File:
|
|
modTime = x.ModTime()
|
|
Size = uint64(x.Size())
|
|
Blocks = (Size + 511) / 512
|
|
Mode = vfs.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 = vfs.UID
|
|
stat.Gid = vfs.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.(*vfs.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.DirEntry().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 vfs.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.(*vfs.File)
|
|
if !ok {
|
|
return -fuse.EIO
|
|
}
|
|
// Read the size so far
|
|
currentSize := file.Size()
|
|
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.(*vfs.ReadFileHandle)
|
|
if !ok {
|
|
// Can only read from read file handle
|
|
return -fuse.EIO
|
|
}
|
|
n, err := rfh.ReadAt(buff, ofst)
|
|
if err != nil {
|
|
return translateError(err)
|
|
}
|
|
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.(*vfs.WriteFileHandle)
|
|
if !ok {
|
|
// Can only write to write file handle
|
|
return -fuse.EIO
|
|
}
|
|
n, err := wfh.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 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 *vfs.ReadFileHandle:
|
|
err = x.Flush()
|
|
case *vfs.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 *vfs.ReadFileHandle:
|
|
err = x.Release()
|
|
case *vfs.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.RemoveName(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.RemoveName(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 *vfs.Dir:
|
|
err = x.SetModTime(t)
|
|
case *vfs.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.(vfs.Error); ok {
|
|
switch mErr {
|
|
case vfs.OK:
|
|
return 0
|
|
case vfs.ENOENT:
|
|
return -fuse.ENOENT
|
|
case vfs.ENOTEMPTY:
|
|
return -fuse.ENOTEMPTY
|
|
case vfs.EEXIST:
|
|
return -fuse.EEXIST
|
|
case vfs.ESPIPE:
|
|
return -fuse.ESPIPE
|
|
case vfs.EBADF:
|
|
return -fuse.EBADF
|
|
case vfs.EROFS:
|
|
return -fuse.EROFS
|
|
}
|
|
}
|
|
fs.Errorf(nil, "IO error: %v", err)
|
|
return -fuse.EIO
|
|
}
|