//go:build cgo && (linux || darwin || freebsd || netbsd || openbsd) package fs import ( "context" "io" "io/fs" "os" "slices" "sync" "syscall" "sirherobrine23.com.br/Sirherobrine23/cgofuse/fuse" "sirherobrine23.com.br/Sirherobrine23/cgofuse/fuse/utils" ) type FuseFs[T File, F FileSystem[T]] struct { fuse.FileSystemBase // Fs Base Target string FS F FdOpen *mapSync[uint64, T] // Map Filesytem virtual and local opened Host *fuse.FileSystemHost // Fuse Mount } func (fs FuseFs[_, _]) Done() { fs.Host.Done() } func (fs FuseFs[_, _]) Mount(ctx context.Context) error { return fs.Host.Mount(ctx, fs.Target) } // Mount fuse func New[T File](target string, fs FileSystem[T]) FileSystemMount[T, FileSystem[T]] { mount := new(FuseFs[T, FileSystem[T]]) mount.Target = target mount.FS = fs mount.FdOpen = (*mapSync[uint64, T])(&sync.Map{}) mount.Host = fuse.NewFileSystemHost(mount) return mount } func (fs FuseFs[_, _]) SetOption(args ...any) { if len(args) >= 1 { switch v := args[0].(type) { case string: switch v { case "capCaseInsensitive": if len(args) >= 2 { if v, ok := args[1].(bool); ok { fs.Host.CapCaseInsensitive = (v) } } else { fs.Host.CapCaseInsensitive = (true) } case "capReaddirPlus": if len(args) >= 2 { if v, ok := args[1].(bool); ok { fs.Host.CapReaddirPlus = (v) } } else { fs.Host.CapReaddirPlus = (true) } case "capReadlink": if len(args) >= 2 { if v, ok := args[1].(bool); ok { fs.Host.CapReaddirPlus = (v) } } else { fs.Host.CapReaddirPlus = (true) } case "capDeleteAccess": if len(args) >= 2 { if v, ok := args[1].(bool); ok { fs.Host.CapDeleteAccess = (v) } } else { fs.Host.CapDeleteAccess = (true) } case "capOpenTrunc": if len(args) >= 2 { if v, ok := args[1].(bool); ok { fs.Host.CapOpenTrunc = (v) } } else { fs.Host.CapOpenTrunc = (true) } case "directIO": if len(args) >= 2 { if v, ok := args[1].(bool); ok { fs.Host.DirectIO = (v) } } else { fs.Host.DirectIO = (true) } case "useIno": if len(args) >= 2 { if v, ok := args[1].(bool); ok { fs.Host.UseIno = (v) } } else { fs.Host.UseIno = (true) } } } } } func (fused *FuseFs[T, F]) Init() { if v, ok := any(fused.FS).(FileSystemInitDestroy); ok { v.Init() } } func (fused *FuseFs[T, F]) Destroy() { if v, ok := any(fused.FS).(FileSystemInitDestroy); ok { v.Destroy() } } func (fused *FuseFs[T, F]) fdDelete(fd uintptr) { fused.FdOpen.Delete(uint64(fd)) } func (fused *FuseFs[T, F]) Fdd(file T) uintptr { if File(file) == nil { return 0 } for key, value := range fused.FdOpen.Seq() { if File(file) == nil || File(value) == nil { continue } else if File(file) == File(value) { return uintptr(key) } } var existFd []uintptr for key, value := range fused.FdOpen.Seq() { if File(value) == nil { continue } existFd = append(existFd, uintptr(key)) } var fd uintptr for index := range ^(uintptr(0)) { if index == 0 { continue } if !slices.Contains(existFd, index) { fd = index break } } fused.FdOpen.Store(uint64(fd), file) return fd } // Create creates and opens a file. func (fused *FuseFs[T, F]) Create(path string, flags int, mode uint32) (int, uint64) { file, err := fused.FS.OpenFile(path, flags|syscall.O_CREAT, fs.FileMode(mode)) if err != nil { return utils.ErrorToStatus(err), ^uint64(0) } fd := uint64(fused.Fdd(file)) fused.FdOpen.Store(fd, file) return 0, fd } // Open opens a file. func (fused *FuseFs[T, F]) Open(path string, flags int) (int, uint64) { file, err := fused.FS.OpenFile(path, flags, 0) if err != nil { return utils.ErrorToStatus(err), ^uint64(0) } fd := uint64(fused.Fdd(file)) fused.FdOpen.Store(fd, file) return 0, fd } func (fused *FuseFs[T, F]) Release(path string, fh uint64) int { if v, ok := fused.FdOpen.LoadAndDelete(fh); ok { fused.fdDelete(uintptr(fh)) return utils.ErrorToStatus(v.Close()) } return -fuse.ENOENT } func (fused *FuseFs[T, F]) Read(path string, buf []byte, ofst int64, fh uint64) int { r, ok := fused.FdOpen.Load(fh) if !ok { return -fuse.ENOENT } // Shorts read buffers if fused.Host.DirectIO && fuse.Drive == fuse.Fuse3 { n, err := r.ReadAt(buf, ofst) if err != nil { if err == io.EOF && n > 0 { return n } return utils.ErrorToStatus(err) } return n } var err error var n int // Read all buffer bufMin := int64(len(buf)) if stat, err := r.Stat(); err == nil { bufMin = min(stat.Size()-ofst, int64(len(buf))) if ofst >= stat.Size() { return 0 } } for n < int(bufMin) && err == nil { var nn int nn, err = r.ReadAt(buf[n:bufMin], ofst+int64(n)) n += nn if err == io.EOF && n == 0 { return 0 } } if err == nil { return n } return utils.ErrorToStatus(err) } func (fused *FuseFs[T, F]) Write(path string, buf []byte, ofst int64, fh uint64) int { r, ok := fused.FdOpen.Load(fh) if !ok { return -fuse.ENOENT } if fused.Host.DirectIO && fuse.Drive == fuse.Fuse3 { n, err := r.WriteAt(buf, ofst) if err != nil { return utils.ErrorToStatus(err) } return n } var err error var n int for n < int(len(buf)) && err == nil { var nn int nn, err = r.WriteAt(buf[n:], ofst+int64(n)) n += nn if err == io.EOF && n == 0 { return 0 } } if err == nil { return n } return utils.ErrorToStatus(err) } func (fused *FuseFs[T, F]) Flush(path string, fh uint64) int { if v, ok := fused.FdOpen.Load(fh); ok { return utils.ErrorToStatus(v.Sync()) } return -fuse.ENOENT } func (fused *FuseFs[T, F]) Fsync(path string, datasync bool, fh uint64) int { if v, ok := fused.FdOpen.Load(fh); ok { return utils.ErrorToStatus(v.Sync()) } return -fuse.ENOENT } func (fused *FuseFs[T, F]) Mkdir(path string, mode uint32) int { return utils.ErrorToStatus(fused.FS.Mkdir(path, fs.FileMode(mode))) } func (fused *FuseFs[T, F]) Unlink(path string) int { if v, ok := any(fused.FS).(FileSystemUnlink); ok { return utils.ErrorToStatus(v.Unlink(path)) } return utils.ErrorToStatus(fused.FS.Remove(path)) } func (fused *FuseFs[T, F]) Rmdir(path string) int { if v, ok := any(fused.FS).(FileSystemRmdir); ok { return utils.ErrorToStatus(v.Rmdir(path)) } return utils.ErrorToStatus(fused.FS.Remove(path)) } func (fused *FuseFs[T, F]) Access(path string, mask uint32) int { if v, ok := any(fused.FS).(FileSystemAccess); ok { return utils.ErrorToStatus(v.Access(path, mask)) } return -fuse.ENOSYS } func (fused *FuseFs[T, F]) Link(oldpath string, newpath string) int { if v, ok := any(fused.FS).(FileSystemLink); ok { return utils.ErrorToStatus(v.Link(oldpath, newpath)) } return -fuse.ENOSYS } func (fused *FuseFs[T, F]) Symlink(target string, newpath string) int { if v, ok := any(fused.FS).(FileSystemSymlink); ok { return utils.ErrorToStatus(v.Symlink(target, newpath)) } return -fuse.ENOSYS } func (fused *FuseFs[T, F]) Readlink(path string) (int, string) { if v, ok := any(fused.FS).(FileSystemReadlink); ok { target, err := v.Readlink(path) if err != nil { return utils.ErrorToStatus(err), "" } return 0, target } return -fuse.ENOSYS, "" } func (fused *FuseFs[T, F]) Rename(oldpath string, newpath string) int { err := fused.FS.Rename(oldpath, newpath) return utils.ErrorToStatus(err) } func (fused *FuseFs[T, F]) Chmod(path string, mode uint32) int { if v, ok := any(fused.FS).(FileSystemChmod); ok { return utils.ErrorToStatus(v.Chmod(path, fs.FileMode(mode))) } return -fuse.ENOSYS } func (fused *FuseFs[T, F]) Chown(path string, uid uint32, gid uint32) int { if v, ok := any(fused.FS).(FileSystemChown); ok { return utils.ErrorToStatus(v.Chown(path, int(uid), int(gid))) } return -fuse.ENOSYS } func (fused *FuseFs[T, F]) Getattr(path string, stat *fuse.Stat_t, fh uint64) int { fsstat, err := fs.FileInfo(nil), error(nil) if v, ok := fused.FdOpen.Load(fh); ok { fsstat, err = v.Stat() } else { fsstat, err = fused.FS.Stat(path) } if err != nil { return utils.ErrorToStatus(err) } utils.AppendStat(fsstat, stat) return 0 } func (fused *FuseFs[T, F]) Statfs(path string, stat *fuse.Statfs_t) int { if v, ok := any(fused.FS).(FileSystemStatFst); ok { return utils.ErrorToStatus(v.Statfs(path, stat)) } stat.Namemax = syscall.NAME_MAX stat.Bsize = 4096 stat.Frsize = stat.Bsize stat.Flag = 0 stat.Files = 0 stat.Ffree = 0 stat.Favail = 0 stat.Blocks = 1204 << 25 stat.Bfree = uint64(float64(stat.Blocks) * (0.99)) stat.Bavail = stat.Bfree if v, ok := any(fused.FS).(FileSystemStatFS); ok { if total, free, err := v.Statfs(path); err == nil { stat.Blocks = total / stat.Bsize stat.Bfree = free / stat.Bsize stat.Bavail = free / stat.Bsize } } return 0 } func (fused *FuseFs[T, F]) Truncate(path string, size int64, fh uint64) int { if v, ok := fused.FdOpen.Load(fh); ok { return utils.ErrorToStatus(v.Truncate(size)) } fd, err := fused.FS.OpenFile(path, os.O_RDWR, 0) if err != nil { return utils.ErrorToStatus(err) } defer fd.Close() return utils.ErrorToStatus(fd.Truncate(size)) } func (fused *FuseFs[T, F]) Opendir(path string) (int, uint64) { file, err := fused.FS.OpenFile(path, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) if err != nil { return utils.ErrorToStatus(err), ^uint64(0) } fd := uint64(fused.Fdd(file)) fused.FdOpen.Store(fd, file) return 0, fd } func (fused *FuseFs[T, F]) Releasedir(path string, fh uint64) int { if v, ok := fused.FdOpen.LoadAndDelete(fh); ok { fused.fdDelete(uintptr(fh)) return utils.ErrorToStatus(v.Close()) } return -fuse.ENOENT } func (fused *FuseFs[T, F]) Fsyncdir(path string, datasync bool, fh uint64) int { if v, ok := fused.FdOpen.Load(fh); ok { return utils.ErrorToStatus(v.Sync()) } return -fuse.ENOENT } func (fused *FuseFs[T, F]) Readdir(path string, fill fuse.StatFill, ofst int64, fh uint64) int { files, err := fused.FS.ReadDir(path) if err != nil { return utils.ErrorToStatus(err) } if ofst == 0 { fill(".", nil, 0) fill("..", nil, 0) } // Start from the ofst if the files are minimum for ofst if ofst > 0 && int(ofst) < len(files) { files = files[ofst:] if len(files) == 0 { return -fuse.ENOENT } } for _, file := range files { if !fill(file.Name(), utils.FromEntry(file), 0) { break } } return 0 } func (fused *FuseFs[T, F]) Utimens(path string, tmsp []fuse.Timespec) int { if v, ok := any(fused.FS).(FileSystemUtimens); ok { return utils.ErrorToStatus(v.Utimens(path, tmsp[0].Time(), tmsp[1].Time())) } // Return stats error for touch command example return fused.Getattr(path, &fuse.Stat_t{}, 0) } func (fused *FuseFs[T, F]) Lseek(path string, offset uint64, whence int, fh uint64) int { if v, ok := fused.FdOpen.Load(fh); ok { _, err := v.Seek(int64(offset), whence) return utils.ErrorToStatus(err) } return -fuse.ENOENT }