| @@ -0,0 +1,112 @@ | |||
| package mount | |||
| import ( | |||
| "io" | |||
| "os" | |||
| "time" | |||
| ) | |||
| // 目录从设计上来说不需要打开操作,所以dirHandle就是fusefs的DirHandle接口的完整实现。 | |||
| type dirHandle struct { | |||
| dir FsDir | |||
| dirReader DirReader | |||
| } | |||
| func newDirHandle(dir FsDir, dirReader DirReader) *dirHandle { | |||
| return &dirHandle{dir: dir, dirReader: dirReader} | |||
| } | |||
| // Readdir reads the contents of the directory associated with file and returns | |||
| // a slice of up to n FileInfo values, as would be returned by Lstat, in | |||
| // directory order. Subsequent calls on the same file will yield further | |||
| // FileInfos. | |||
| // | |||
| // If n > 0, Readdir returns at most n FileInfo structures. In this case, if | |||
| // Readdir returns an empty slice, it will return a non-nil error explaining | |||
| // why. At the end of a directory, the error is io.EOF. | |||
| // | |||
| // If n <= 0, Readdir returns all the FileInfo from the directory in a single | |||
| // slice. In this case, if Readdir succeeds (reads all the way to the end of | |||
| // the directory), it returns the slice and a nil error. If it encounters an | |||
| // error before the end of the directory, Readdir returns the FileInfo read | |||
| // until that point and a non-nil error. | |||
| func (fh *dirHandle) Readdir(n int) (fis []os.FileInfo, err error) { | |||
| entries, err := fh.dirReader.Next(n) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if len(entries) == 0 { | |||
| return nil, io.EOF | |||
| } | |||
| var infos []os.FileInfo | |||
| for _, entry := range entries { | |||
| infos = append(infos, &fileInfo{entry}) | |||
| } | |||
| return infos, nil | |||
| } | |||
| // Readdirnames reads and returns a slice of names from the directory f. | |||
| // | |||
| // If n > 0, Readdirnames returns at most n names. In this case, if | |||
| // Readdirnames returns an empty slice, it will return a non-nil error | |||
| // explaining why. At the end of a directory, the error is io.EOF. | |||
| // | |||
| // If n <= 0, Readdirnames returns all the names from the directory in a single | |||
| // slice. In this case, if Readdirnames succeeds (reads all the way to the end | |||
| // of the directory), it returns the slice and a nil error. If it encounters an | |||
| // error before the end of the directory, Readdirnames returns the names read | |||
| // until that point and a non-nil error. | |||
| func (fh *dirHandle) Readdirnames(n int) (names []string, err error) { | |||
| entries, err := fh.dirReader.Next(n) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if len(entries) == 0 { | |||
| return nil, io.EOF | |||
| } | |||
| for _, entry := range entries { | |||
| names = append(names, entry.Name()) | |||
| } | |||
| return names, nil | |||
| } | |||
| // Close closes the handle | |||
| func (fh *dirHandle) Close() (err error) { | |||
| fh.dirReader.Close() | |||
| return nil | |||
| } | |||
| type fileInfo struct { | |||
| entry FsEntry | |||
| } | |||
| func (fi *fileInfo) Name() string { | |||
| return fi.entry.Name() | |||
| } | |||
| func (fi *fileInfo) Size() int64 { | |||
| return fi.entry.Size() | |||
| } | |||
| func (fi *fileInfo) Mode() os.FileMode { | |||
| return os.FileMode(fi.entry.Mode()) | |||
| } | |||
| // ModTime returns the modification time of the file | |||
| func (fi *fileInfo) ModTime() time.Time { | |||
| return fi.entry.ModTime() | |||
| } | |||
| func (fi *fileInfo) IsDir() bool { | |||
| return fi.entry.IsDir() | |||
| } | |||
| func (fi *fileInfo) Sys() interface{} { | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,206 @@ | |||
| package mount | |||
| import ( | |||
| "context" | |||
| "os" | |||
| "syscall" | |||
| fusefs "github.com/hanwen/go-fuse/v2/fs" | |||
| "github.com/hanwen/go-fuse/v2/fuse" | |||
| ) | |||
| type DirNode struct { | |||
| NodeBase | |||
| dir FsDir | |||
| } | |||
| func newDirNode(vfs *Vfs, dir FsDir) *DirNode { | |||
| return &DirNode{NodeBase: NodeBase{vfs: vfs}, dir: dir} | |||
| } | |||
| func (n *DirNode) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.AttrOut) syscall.Errno { | |||
| n.vfs.fillAttrOut(n.dir, out) | |||
| return 0 | |||
| } | |||
| // Setattr sets attributes for an Inode. | |||
| func (n *DirNode) Setattr(ctx context.Context, f fusefs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) { | |||
| n.vfs.fillAttrOut(n.dir, out) | |||
| _, ok := in.GetSize() | |||
| if ok { | |||
| return syscall.ENOSYS | |||
| } | |||
| modTime, ok := in.GetMTime() | |||
| if ok { | |||
| err := n.dir.SetModTime(modTime) | |||
| if err != nil { | |||
| return translateError(err) | |||
| } | |||
| out.Mtime = uint64(modTime.Unix()) | |||
| out.Mtimensec = uint32(modTime.Nanosecond()) | |||
| } | |||
| return 0 | |||
| } | |||
| var _ = (fusefs.NodeSetattrer)((*DirNode)(nil)) | |||
| // accessModeMask masks off the read modes from the flags | |||
| const accessModeMask = (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) | |||
| // Open opens an Inode (of regular file type) for reading. It | |||
| // is optional but recommended to return a FileHandle. | |||
| func (n *DirNode) Open(ctx context.Context, flags uint32) (fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) { | |||
| rdwrMode := int(flags) & accessModeMask | |||
| if rdwrMode != os.O_RDONLY { | |||
| return nil, 0, syscall.EPERM | |||
| } | |||
| reader, err := n.dir.ReadChildren() | |||
| if err != nil { | |||
| return nil, 0, translateError(err) | |||
| } | |||
| return newDirHandle(n.dir, reader), 0, 0 | |||
| } | |||
| var _ = (fusefs.NodeOpener)((*DirNode)(nil)) | |||
| func (n *DirNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (inode *fusefs.Inode, errno syscall.Errno) { | |||
| child, err := n.dir.Child(ctx, name) | |||
| if err != nil { | |||
| return nil, translateError(err) | |||
| } | |||
| switch child := child.(type) { | |||
| case FsDir: | |||
| node := newDirNode(n.vfs, child) | |||
| n.vfs.fillEntryOut(child, out) | |||
| return node.NewInode(ctx, node, fusefs.StableAttr{ | |||
| Mode: out.Attr.Mode, | |||
| }), 0 | |||
| case FsFile: | |||
| node := newFileNode(n.vfs, child) | |||
| n.vfs.fillEntryOut(child, out) | |||
| return node.NewInode(ctx, node, fusefs.StableAttr{ | |||
| Mode: out.Attr.Mode, | |||
| }), 0 | |||
| default: | |||
| return nil, syscall.EINVAL | |||
| } | |||
| } | |||
| var _ = (fusefs.NodeLookuper)((*DirNode)(nil)) | |||
| func (n *DirNode) Opendir(ctx context.Context) syscall.Errno { | |||
| return 0 | |||
| } | |||
| var _ = (fusefs.NodeOpendirer)((*DirNode)(nil)) | |||
| type dirStream struct { | |||
| reader DirReader | |||
| vfs *Vfs | |||
| } | |||
| func (s *dirStream) HasNext() bool { | |||
| return s.reader.HasNext() | |||
| } | |||
| func (s *dirStream) Next() (fuse.DirEntry, syscall.Errno) { | |||
| entries, err := s.reader.Next(1) | |||
| if err != nil { | |||
| return fuse.DirEntry{}, translateError(err) | |||
| } | |||
| entry := entries[0] | |||
| return fuse.DirEntry{ | |||
| Name: entry.Name(), | |||
| Mode: s.vfs.getMode(entry), | |||
| }, 0 | |||
| } | |||
| func (s *dirStream) Close() { | |||
| s.reader.Close() | |||
| } | |||
| func (n *DirNode) Readdir(ctx context.Context) (ds fusefs.DirStream, errno syscall.Errno) { | |||
| reader, err := n.dir.ReadChildren() | |||
| if err != nil { | |||
| return nil, translateError(err) | |||
| } | |||
| return &dirStream{reader: reader, vfs: n.vfs}, 0 | |||
| } | |||
| var _ = (fusefs.NodeReaddirer)((*DirNode)(nil)) | |||
| func (n *DirNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (inode *fusefs.Inode, errno syscall.Errno) { | |||
| newDir, err := n.dir.NewDir(ctx, name) | |||
| if err != nil { | |||
| return nil, translateError(err) | |||
| } | |||
| node := newDirNode(n.vfs, newDir) | |||
| n.vfs.fillEntryOut(newDir, out) | |||
| return node.NewInode(ctx, node, fusefs.StableAttr{ | |||
| Mode: out.Attr.Mode, | |||
| }), 0 | |||
| } | |||
| var _ = (fusefs.NodeMkdirer)((*DirNode)(nil)) | |||
| func (n *DirNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *fusefs.Inode, fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) { | |||
| newFile, err := n.dir.NewFile(ctx, name, flags) | |||
| if err != nil { | |||
| return nil, nil, 0, translateError(err) | |||
| } | |||
| hd, err := newFile.Open(flags) | |||
| if err != nil { | |||
| return nil, nil, 0, translateError(err) | |||
| } | |||
| n.vfs.fillEntryOut(newFile, out) | |||
| fileNode := newFileNode(n.vfs, newFile) | |||
| return fileNode.NewInode(ctx, fileNode, fusefs.StableAttr{ | |||
| Mode: out.Attr.Mode, | |||
| }), hd, 0, 0 | |||
| } | |||
| var _ = (fusefs.NodeCreater)((*DirNode)(nil)) | |||
| // Unlink should remove a child from this directory. If the | |||
| // return status is OK, the Inode is removed as child in the | |||
| // FS tree automatically. Default is to return EROFS. | |||
| func (n *DirNode) Unlink(ctx context.Context, name string) (errno syscall.Errno) { | |||
| return translateError(n.dir.RemoveChild(ctx, name)) | |||
| } | |||
| var _ = (fusefs.NodeUnlinker)((*DirNode)(nil)) | |||
| // Rmdir is like Unlink but for directories. | |||
| // Default is to return EROFS. | |||
| func (n *DirNode) Rmdir(ctx context.Context, name string) (errno syscall.Errno) { | |||
| return translateError(n.dir.RemoveChild(ctx, name)) | |||
| } | |||
| var _ = (fusefs.NodeRmdirer)((*DirNode)(nil)) | |||
| func (n *DirNode) Rename(ctx context.Context, oldName string, newParent fusefs.InodeEmbedder, newName string, flags uint32) (errno syscall.Errno) { | |||
| newParentNode, ok := newParent.(*DirNode) | |||
| if !ok { | |||
| return syscall.ENOTDIR | |||
| } | |||
| return translateError(n.dir.MoveChild(ctx, oldName, newName, newParentNode.dir)) | |||
| } | |||
| var _ = (fusefs.NodeRenamer)((*DirNode)(nil)) | |||
| @@ -0,0 +1,119 @@ | |||
| package mount | |||
| import ( | |||
| "context" | |||
| "io" | |||
| "syscall" | |||
| fusefs "github.com/hanwen/go-fuse/v2/fs" | |||
| "github.com/hanwen/go-fuse/v2/fuse" | |||
| ) | |||
| // 作为本模块的FileHandle与fusefs.FileHandle的桥梁,用于实现fusefs.FileHandle接口 | |||
| type fileHandle struct { | |||
| hd FileHandle | |||
| file FsFile | |||
| vfs *Vfs | |||
| } | |||
| func newFileHandle(hd FileHandle, file FsFile, vfs *Vfs) *fileHandle { | |||
| return &fileHandle{ | |||
| hd: hd, | |||
| file: file, | |||
| vfs: vfs, | |||
| } | |||
| } | |||
| // Read data from a file. The data should be returned as | |||
| // ReadResult, which may be constructed from the incoming | |||
| // `dest` buffer. | |||
| func (f *fileHandle) Read(ctx context.Context, dest []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) { | |||
| var n int | |||
| var err error | |||
| n, err = f.hd.ReadAt(dest, off) | |||
| if err == io.EOF { | |||
| err = nil | |||
| } | |||
| return fuse.ReadResultData(dest[:n]), translateError(err) | |||
| } | |||
| var _ fusefs.FileReader = (*fileHandle)(nil) | |||
| // Write the data into the file handle at given offset. After | |||
| // returning, the data will be reused and may not referenced. | |||
| func (f *fileHandle) Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) { | |||
| var n int | |||
| var err error | |||
| n, err = f.hd.WriteAt(data, off) | |||
| return uint32(n), translateError(err) | |||
| } | |||
| var _ fusefs.FileWriter = (*fileHandle)(nil) | |||
| // Flush is called for the close(2) call on a file descriptor. In case | |||
| // of a descriptor that was duplicated using dup(2), it may be called | |||
| // more than once for the same fileHandle. | |||
| func (f *fileHandle) Flush(ctx context.Context) syscall.Errno { | |||
| return translateError(f.hd.Close()) | |||
| } | |||
| var _ fusefs.FileFlusher = (*fileHandle)(nil) | |||
| // Release is called to before a fileHandle is forgotten. The | |||
| // kernel ignores the return value of this method, | |||
| // so any cleanup that requires specific synchronization or | |||
| // could fail with I/O errors should happen in Flush instead. | |||
| func (f *fileHandle) Release(ctx context.Context) syscall.Errno { | |||
| return translateError(f.hd.Release()) | |||
| } | |||
| var _ fusefs.FileReleaser = (*fileHandle)(nil) | |||
| // Fsync is a signal to ensure writes to the Inode are flushed | |||
| // to stable storage. | |||
| func (f *fileHandle) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) { | |||
| return translateError(f.hd.Sync()) | |||
| } | |||
| var _ fusefs.FileFsyncer = (*fileHandle)(nil) | |||
| // Getattr reads attributes for an Inode. The library will ensure that | |||
| // Mode and Ino are set correctly. For files that are not opened with | |||
| // FOPEN_DIRECTIO, Size should be set so it can be read correctly. If | |||
| // returning zeroed permissions, the default behavior is to change the | |||
| // mode of 0755 (directory) or 0644 (files). This can be switched off | |||
| // with the Options.NullPermissions setting. If blksize is unset, 4096 | |||
| // is assumed, and the 'blocks' field is set accordingly. | |||
| func (f *fileHandle) Getattr(ctx context.Context, out *fuse.AttrOut) (errno syscall.Errno) { | |||
| f.vfs.fillAttrOut(f.file, out) | |||
| return 0 | |||
| } | |||
| var _ fusefs.FileGetattrer = (*fileHandle)(nil) | |||
| // Setattr sets attributes for an Inode. | |||
| func (f *fileHandle) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) { | |||
| var err error | |||
| f.vfs.fillAttrOut(f.file, out) | |||
| size, ok := in.GetSize() | |||
| if ok { | |||
| err = f.file.Truncate(size) | |||
| if err != nil { | |||
| return translateError(err) | |||
| } | |||
| out.Attr.Size = size | |||
| } | |||
| mtime, ok := in.GetMTime() | |||
| if ok { | |||
| err = f.file.SetModTime(mtime) | |||
| if err != nil { | |||
| return translateError(err) | |||
| } | |||
| out.Attr.Mtime = uint64(mtime.Unix()) | |||
| out.Attr.Mtimensec = uint32(mtime.Nanosecond()) | |||
| } | |||
| return 0 | |||
| } | |||
| var _ fusefs.FileSetattrer = (*fileHandle)(nil) | |||
| @@ -0,0 +1,121 @@ | |||
| package mount | |||
| import ( | |||
| "context" | |||
| "syscall" | |||
| fusefs "github.com/hanwen/go-fuse/v2/fs" | |||
| "github.com/hanwen/go-fuse/v2/fuse" | |||
| ) | |||
| type FileNode struct { | |||
| NodeBase | |||
| file FsFile | |||
| } | |||
| func newFileNode(vfs *Vfs, file FsFile) *FileNode { | |||
| return &FileNode{ | |||
| NodeBase: NodeBase{vfs: vfs}, | |||
| file: file, | |||
| } | |||
| } | |||
| func (n *FileNode) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.AttrOut) syscall.Errno { | |||
| n.vfs.fillAttrOut(n.file, out) | |||
| return 0 | |||
| } | |||
| func (n *FileNode) Setattr(ctx context.Context, f fusefs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) { | |||
| n.vfs.fillAttrOut(n.file, out) | |||
| size, ok := in.GetSize() | |||
| if ok { | |||
| err := n.file.Truncate(size) | |||
| if err != nil { | |||
| return translateError(err) | |||
| } | |||
| out.Size = size | |||
| } | |||
| modTime, ok := in.GetMTime() | |||
| if ok { | |||
| err := n.file.SetModTime(modTime) | |||
| if err != nil { | |||
| return translateError(err) | |||
| } | |||
| out.Mtime = uint64(modTime.Unix()) | |||
| out.Mtimensec = uint32(modTime.Nanosecond()) | |||
| } | |||
| return 0 | |||
| } | |||
| var _ = (fusefs.NodeSetattrer)((*FileNode)(nil)) | |||
| func (n *FileNode) Open(ctx context.Context, flags uint32) (fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) { | |||
| // 只支持以下标志: | |||
| // os.O_RDONLY | |||
| // os.O_WRONLY | |||
| // os.O_RDWR | |||
| // os.O_APPEND | |||
| // os.O_TRUNC | |||
| // os.O_SYNC | |||
| // 忽略其他标志位 | |||
| hd, err := n.file.Open(flags) | |||
| if err != nil { | |||
| return nil, 0, translateError(err) | |||
| } | |||
| return hd, 0, 0 | |||
| } | |||
| var _ = (fusefs.NodeOpener)((*FileNode)(nil)) | |||
| func (n *FileNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (inode *fusefs.Inode, errno syscall.Errno) { | |||
| return nil, syscall.ENOTDIR | |||
| } | |||
| var _ = (fusefs.NodeLookuper)((*FileNode)(nil)) | |||
| func (n *FileNode) Opendir(ctx context.Context) syscall.Errno { | |||
| return syscall.ENOTDIR | |||
| } | |||
| var _ = (fusefs.NodeOpendirer)((*FileNode)(nil)) | |||
| func (n *FileNode) Readdir(ctx context.Context) (ds fusefs.DirStream, errno syscall.Errno) { | |||
| return nil, syscall.ENOTDIR | |||
| } | |||
| var _ = (fusefs.NodeReaddirer)((*FileNode)(nil)) | |||
| func (n *FileNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (inode *fusefs.Inode, errno syscall.Errno) { | |||
| return nil, syscall.ENOTDIR | |||
| } | |||
| var _ = (fusefs.NodeMkdirer)((*FileNode)(nil)) | |||
| func (n *FileNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *fusefs.Inode, fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) { | |||
| return nil, nil, 0, syscall.ENOTDIR | |||
| } | |||
| var _ = (fusefs.NodeCreater)((*FileNode)(nil)) | |||
| func (n *FileNode) Unlink(ctx context.Context, name string) (errno syscall.Errno) { | |||
| return syscall.ENOTDIR | |||
| } | |||
| var _ = (fusefs.NodeUnlinker)((*FileNode)(nil)) | |||
| func (n *FileNode) Rmdir(ctx context.Context, name string) (errno syscall.Errno) { | |||
| return syscall.ENOTDIR | |||
| } | |||
| var _ = (fusefs.NodeRmdirer)((*FileNode)(nil)) | |||
| func (n *FileNode) Rename(ctx context.Context, oldName string, newParent fusefs.InodeEmbedder, newName string, flags uint32) (errno syscall.Errno) { | |||
| return syscall.ENOTDIR | |||
| } | |||
| var _ = (fusefs.NodeRenamer)((*FileNode)(nil)) | |||
| @@ -0,0 +1,72 @@ | |||
| package mount | |||
| import ( | |||
| "context" | |||
| "syscall" | |||
| fusefs "github.com/hanwen/go-fuse/v2/fs" | |||
| "github.com/hanwen/go-fuse/v2/fuse" | |||
| ) | |||
| type NodeBase struct { | |||
| fusefs.Inode | |||
| vfs *Vfs | |||
| } | |||
| // Statfs implements statistics for the filesystem that holds this | |||
| // Inode. If not defined, the `out` argument will zeroed with an OK | |||
| // result. This is because OSX filesystems must Statfs, or the mount | |||
| // will not work. | |||
| func (n *NodeBase) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno { | |||
| const blockSize = 4096 | |||
| stats := n.vfs.fs.Stats() | |||
| // total, _, free := n.fsys.VFS.Statfs() | |||
| out.Blocks = uint64(stats.TotalDataBytes) / blockSize // Total data blocks in file system. | |||
| out.Bfree = 1e9 // Free blocks in file system. | |||
| out.Bavail = out.Bfree // Free blocks in file system if you're not root. | |||
| out.Files = uint64(stats.TotalObjectCount) // Total files in file system. | |||
| out.Ffree = 1e9 // Free files in file system. | |||
| out.Bsize = blockSize // Block size | |||
| out.NameLen = 255 // Maximum file name length? | |||
| out.Frsize = blockSize // Fragment size, smallest addressable data size in the file system. | |||
| return 0 | |||
| } | |||
| // Getxattr should read data for the given attribute into | |||
| // `dest` and return the number of bytes. If `dest` is too | |||
| // small, it should return ERANGE and the size of the attribute. | |||
| // If not defined, Getxattr will return ENOATTR. | |||
| func (n *NodeBase) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { | |||
| return 0, syscall.ENOSYS // we never implement this | |||
| } | |||
| var _ fusefs.NodeGetxattrer = (*NodeBase)(nil) | |||
| // Setxattr should store data for the given attribute. See | |||
| // setxattr(2) for information about flags. | |||
| // If not defined, Setxattr will return ENOATTR. | |||
| func (n *NodeBase) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno { | |||
| return syscall.ENOSYS // we never implement this | |||
| } | |||
| var _ fusefs.NodeSetxattrer = (*NodeBase)(nil) | |||
| // Removexattr should delete the given attribute. | |||
| // If not defined, Removexattr will return ENOATTR. | |||
| func (n *NodeBase) Removexattr(ctx context.Context, attr string) syscall.Errno { | |||
| return syscall.ENOSYS // we never implement this | |||
| } | |||
| var _ fusefs.NodeRemovexattrer = (*NodeBase)(nil) | |||
| // Listxattr should read all attributes (null terminated) into | |||
| // `dest`. If the `dest` buffer is too small, it should return ERANGE | |||
| // and the correct size. If not defined, return an empty list and | |||
| // success. | |||
| func (n *NodeBase) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) { | |||
| return 0, syscall.ENOSYS // we never implement this | |||
| } | |||
| var _ fusefs.NodeListxattrer = (*NodeBase)(nil) | |||
| @@ -0,0 +1,69 @@ | |||
| package mount | |||
| import ( | |||
| "context" | |||
| "errors" | |||
| "os" | |||
| "time" | |||
| ) | |||
| var ( | |||
| ErrNotExists = os.ErrNotExist | |||
| ErrExists = os.ErrExist | |||
| ErrNotEmpty = errors.New("directory not empty") | |||
| ErrPermission = os.ErrPermission | |||
| ) | |||
| type Fs interface { | |||
| Stats() FsStats | |||
| } | |||
| type FsStats struct { | |||
| TotalDataBytes int64 // 存储的总数据量 | |||
| TotalObjectCount int64 // 存储的总对象数量 | |||
| } | |||
| type FsEntry interface { | |||
| Name() string | |||
| Size() int64 | |||
| Mode() os.FileMode | |||
| ModTime() time.Time | |||
| CreateTime() time.Time | |||
| IsDir() bool | |||
| } | |||
| type FsDir interface { | |||
| FsEntry | |||
| // 如果不存在,应该返回ErrNotExists | |||
| SetModTime(time time.Time) error | |||
| Child(ctx context.Context, name string) (FsEntry, error) | |||
| Children(ctx context.Context) ([]FsEntry, error) | |||
| ReadChildren() (DirReader, error) | |||
| NewDir(ctx context.Context, name string) (FsDir, error) | |||
| NewFile(ctx context.Context, name string, flags uint32) (FsFile, error) | |||
| RemoveChild(ctx context.Context, name string) error | |||
| MoveChild(ctx context.Context, oldName string, newName string, newParent FsDir) error | |||
| } | |||
| type FsFile interface { | |||
| FsEntry | |||
| Truncate(size uint64) error | |||
| SetModTime(time time.Time) error | |||
| Open(flags uint32) (FileHandle, error) | |||
| } | |||
| type DirReader interface { | |||
| HasNext() bool | |||
| Next(n int) ([]FsEntry, error) | |||
| Close() | |||
| } | |||
| type FileHandle interface { | |||
| ReadAt(buf []byte, off int64) (int, error) | |||
| WriteAt(buf []byte, off int64) (int, error) | |||
| Sync() error | |||
| Close() error | |||
| Release() error | |||
| } | |||
| @@ -0,0 +1,90 @@ | |||
| package mount | |||
| import ( | |||
| "os" | |||
| "syscall" | |||
| "time" | |||
| "github.com/hanwen/go-fuse/v2/fuse" | |||
| ) | |||
| type Config struct { | |||
| GID uint32 `json:"gid"` | |||
| UID uint32 `json:"uid"` | |||
| AttrTimeout time.Duration `json:"attrTimeout"` | |||
| } | |||
| type Vfs struct { | |||
| fs Fs | |||
| config Config | |||
| } | |||
| func translateError(err error) syscall.Errno { | |||
| switch err { | |||
| case nil: | |||
| return 0 | |||
| case ErrNotExists, os.ErrNotExist: | |||
| return syscall.ENOENT | |||
| case ErrExists, os.ErrExist: | |||
| return syscall.EEXIST | |||
| case ErrPermission, os.ErrPermission: | |||
| return syscall.EACCES | |||
| case ErrNotEmpty: | |||
| return syscall.ENOTEMPTY | |||
| default: | |||
| return syscall.EIO | |||
| } | |||
| } | |||
| // get the Mode from a vfs Node | |||
| func (v *Vfs) getMode(node FsEntry) uint32 { | |||
| Mode := node.Mode().Perm() | |||
| if node.IsDir() { | |||
| Mode |= fuse.S_IFDIR | |||
| } else { | |||
| Mode |= fuse.S_IFREG | |||
| } | |||
| return uint32(Mode) | |||
| } | |||
| // fill in attr from node | |||
| func (v *Vfs) fillAttr(node FsEntry, attr *fuse.Attr) { | |||
| Size := uint64(node.Size()) | |||
| const BlockSize = 512 | |||
| Blocks := (Size + BlockSize - 1) / BlockSize | |||
| attr.Owner.Gid = v.config.GID | |||
| attr.Owner.Uid = v.config.UID | |||
| attr.Mode = v.getMode(node) | |||
| attr.Size = Size | |||
| attr.Nlink = 1 | |||
| attr.Blocks = Blocks | |||
| // attr.Blksize = BlockSize // not supported in freebsd/darwin, defaults to 4k if not set | |||
| createTime := node.CreateTime() | |||
| modTime := node.ModTime() | |||
| attr.Atime = uint64(modTime.Unix()) | |||
| attr.Atimensec = uint32(modTime.Nanosecond()) | |||
| attr.Mtime = uint64(modTime.Unix()) | |||
| attr.Mtimensec = uint32(modTime.Nanosecond()) | |||
| attr.Ctime = uint64(createTime.Unix()) | |||
| attr.Ctimensec = uint32(createTime.Nanosecond()) | |||
| } | |||
| func (v *Vfs) fillAttrOut(node FsEntry, out *fuse.AttrOut) { | |||
| v.fillAttr(node, &out.Attr) | |||
| out.SetTimeout(v.config.AttrTimeout) | |||
| } | |||
| func (v *Vfs) fillEntryOut(node FsEntry, out *fuse.EntryOut) { | |||
| v.fillAttr(node, &out.Attr) | |||
| out.SetAttrTimeout(v.config.AttrTimeout) | |||
| out.SetEntryTimeout(v.config.AttrTimeout) | |||
| } | |||
| @@ -13,6 +13,7 @@ require ( | |||
| github.com/gin-gonic/gin v1.7.7 | |||
| github.com/go-co-op/gocron/v2 v2.15.0 | |||
| github.com/go-sql-driver/mysql v1.8.1 | |||
| github.com/hanwen/go-fuse/v2 v2.7.2 | |||
| github.com/hashicorp/golang-lru/v2 v2.0.5 | |||
| github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible | |||
| github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.131 | |||
| @@ -98,6 +98,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | |||
| github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | |||
| github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= | |||
| github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= | |||
| github.com/hanwen/go-fuse/v2 v2.7.2 h1:SbJP1sUP+n1UF8NXBA14BuojmTez+mDgOk0bC057HQw= | |||
| github.com/hanwen/go-fuse/v2 v2.7.2/go.mod h1:ugNaD/iv5JYyS1Rcvi57Wz7/vrLQJo10mmketmoef48= | |||
| github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | |||
| github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= | |||
| github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | |||
| @@ -141,6 +143,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | |||
| github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | |||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | |||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | |||
| github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= | |||
| github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= | |||
| github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= | |||
| github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= | |||
| github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= | |||
| @@ -156,6 +160,8 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4 | |||
| github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | |||
| github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | |||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | |||
| github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= | |||
| github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= | |||
| github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | |||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | |||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | |||