diff --git a/client2/internal/mount/dir_handle.go b/client2/internal/mount/dir_handle.go new file mode 100644 index 0000000..d90a9fc --- /dev/null +++ b/client2/internal/mount/dir_handle.go @@ -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 +} diff --git a/client2/internal/mount/dir_node.go b/client2/internal/mount/dir_node.go new file mode 100644 index 0000000..e7dd48e --- /dev/null +++ b/client2/internal/mount/dir_node.go @@ -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)) diff --git a/client2/internal/mount/file_handle.go b/client2/internal/mount/file_handle.go new file mode 100644 index 0000000..8409f7a --- /dev/null +++ b/client2/internal/mount/file_handle.go @@ -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) diff --git a/client2/internal/mount/file_node.go b/client2/internal/mount/file_node.go new file mode 100644 index 0000000..5ab8cdf --- /dev/null +++ b/client2/internal/mount/file_node.go @@ -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)) diff --git a/client2/internal/mount/node.go b/client2/internal/mount/node.go new file mode 100644 index 0000000..4e0cc70 --- /dev/null +++ b/client2/internal/mount/node.go @@ -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) diff --git a/client2/internal/mount/types.go b/client2/internal/mount/types.go new file mode 100644 index 0000000..4aa1a50 --- /dev/null +++ b/client2/internal/mount/types.go @@ -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 +} diff --git a/client2/internal/mount/vfs.go b/client2/internal/mount/vfs.go new file mode 100644 index 0000000..33e5c50 --- /dev/null +++ b/client2/internal/mount/vfs.go @@ -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) +} diff --git a/go.mod b/go.mod index 9dcf692..e967f72 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 4262e4a..861574a 100644 --- a/go.sum +++ b/go.sum @@ -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=