All checks were successful
Fuse test / go-test (push) Successful in 32s
Signed-off-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
481 lines
11 KiB
Go
481 lines
11 KiB
Go
//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/calls"
|
|
"sirherobrine23.com.br/Sirherobrine23/cgofuse/fuse/utils"
|
|
)
|
|
|
|
type FileSystemStatFst interface {
|
|
Statfs(path string, stat *fuse.Statfs_t) error
|
|
}
|
|
|
|
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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.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 -int(calls.ENOENT)
|
|
}
|