| @@ -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/gin-gonic/gin v1.7.7 | ||||
| github.com/go-co-op/gocron/v2 v2.15.0 | github.com/go-co-op/gocron/v2 v2.15.0 | ||||
| github.com/go-sql-driver/mysql v1.8.1 | 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/hashicorp/golang-lru/v2 v2.0.5 | ||||
| github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible | github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible | ||||
| github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.131 | 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/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 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= | ||||
| github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= | 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.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 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= | ||||
| github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | 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/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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | 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.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.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= | ||||
| github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= | 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/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 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | ||||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | 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-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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | ||||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||