Files
cgofuse/fs/fs_unix.go
Matheus Sampaio Queiroga 9a774478a2
All checks were successful
Fuse test / go-test (push) Successful in 32s
Merge winfsp to fuse and update fs
Signed-off-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
2025-08-18 19:07:05 -03:00

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)
}