Files
cgofuse/fuse/host.go
Matheus Sampaio Queiroga a1a726eb4d
All checks were successful
Fuse test / go-test (push) Successful in 22s
Implement Lseek operation, and enhance error handling
- Update the FileSystemHost to remove functions SetCapCaseInsensitive, SetCapReaddirPlus, SetCapDeleteAccess, SetCapOpenTrunc, SetDirectIO, SetUseIno and expose CapCaseInsensitive, CapReaddirPlus, CapDeleteAccess, CapOpenTrunc, DirectIO, UseIno
- Added `FileSystemLseek` interface to support Lseek operation in FUSE.
- Implemented Lseek functionality in the host layer.
- Refactored error handling in `ErrorToStatus` to improve clarity and coverage.
- Enhanced `BasicStat` and `AppendDirEntry` functions to populate additional fields in the FUSE stat structure.
- Created utility functions for managing FUSE flags and permissions.
- fs:
  - Remove `Fd() uintptr` from base of File interface
  - Now fs use internal File Descriptor
  - Removed `FS() F` from `FileSystemMount[T, FileSystem[T]]`
- Unix fs:
  - Merge `_fuse` to `_FileSystemMount` and rename to FuseFs
  - Add new `FileSystemStatFst` to use direct fuse Statfs struct
  - Utimens don't return -ENOSYS if FileSystemUtimens not implemented
  - Add `Lseek` function

Signed-off-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
2025-08-10 17:27:52 -03:00

1124 lines
31 KiB
Go

//go:build cgo && (linux || darwin || freebsd || netbsd || openbsd)
package fuse
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"
"unsafe"
)
// FileSystemHost is used to host a file system.
type FileSystemHost struct {
fsop FileSystemInterface
fuse *c_struct_fuse
mntp string
mountErr error
done chan struct{}
mountInit, mountDestroy chan struct{}
// CapCaseInsensitive informs the host that the hosted file system is case insensitive
CapCaseInsensitive bool
// CapReaddirPlus informs the host that the hosted file system has the readdir-plus
// capability [Linux and Windows only]. A file system that has the readdir-plus capability can send
// full stat information during Readdir, thus avoiding extraneous Getattr calls.
CapReaddirPlus bool
// CapDeleteAccess informs the host that the hosted file system implements Access that
// understands the DELETE_OK flag [Windows only]. A file system can use this capability
// to deny delete access on Windows.
CapDeleteAccess bool
// CapOpenTrunc informs the host that the hosted file system can handle the O_TRUNC
// Open flag [Linux only].
CapOpenTrunc bool
// DirectIO causes the file system to disable page caching [FUSE3 only]. Must be set
// before Mount is called.
DirectIO bool
// UseIno causes the file system to use its own inode values [FUSE3 only]. Must be set
// before Mount is called.
UseIno bool
}
var (
hostGuard = sync.Mutex{}
hostTable = map[unsafe.Pointer]*FileSystemHost{}
)
func hostHandleNew(host *FileSystemHost) unsafe.Pointer {
p := c_malloc(1)
hostGuard.Lock()
hostTable[p] = host
hostGuard.Unlock()
return p
}
func hostHandleDel(p unsafe.Pointer) {
hostGuard.Lock()
delete(hostTable, p)
hostGuard.Unlock()
c_free(p)
}
func hostHandleGet(p unsafe.Pointer) *FileSystemHost {
hostGuard.Lock()
host := hostTable[p]
hostGuard.Unlock()
return host
}
func copyCstatvfsFromFusestatfs(dst *c_fuse_statvfs_t, src *Statfs_t) {
c_hostCstatvfsFromFusestatfs(dst,
c_uint64_t(src.Bsize),
c_uint64_t(src.Frsize),
c_uint64_t(src.Blocks),
c_uint64_t(src.Bfree),
c_uint64_t(src.Bavail),
c_uint64_t(src.Files),
c_uint64_t(src.Ffree),
c_uint64_t(src.Favail),
c_uint64_t(src.Fsid),
c_uint64_t(src.Flag),
c_uint64_t(src.Namemax))
}
func copyCstatFromFusestat(dst *c_fuse_stat_t, src *Stat_t) {
c_hostCstatFromFusestat(dst,
c_uint64_t(src.Dev),
c_uint64_t(src.Ino),
c_uint32_t(src.Mode),
c_uint32_t(src.Nlink),
c_uint32_t(src.Uid),
c_uint32_t(src.Gid),
c_uint64_t(src.Rdev),
c_int64_t(src.Size),
c_int64_t(src.Atim.Sec), c_int64_t(src.Atim.Nsec),
c_int64_t(src.Mtim.Sec), c_int64_t(src.Mtim.Nsec),
c_int64_t(src.Ctim.Sec), c_int64_t(src.Ctim.Nsec),
c_int64_t(src.Blksize),
c_int64_t(src.Blocks),
c_int64_t(src.Birthtim.Sec), c_int64_t(src.Birthtim.Nsec),
c_uint32_t(src.Flags))
}
func copyFusetimespecFromCtimespec(dst *Timespec, src *c_fuse_timespec_t) {
dst.Sec = int64(src.tv_sec)
dst.Nsec = int64(src.tv_nsec)
}
func recoverAsErrno(errc0 *c_int) {
if r := recover(); nil != r {
buf := make([]byte, 1<<16)
n := runtime.Stack(buf, true)
fmt.Fprintf(os.Stderr, "Host error: %s\n\n%s\n", r, buf[:n])
switch e := r.(type) {
case Error:
*errc0 = c_int(e)
default:
*errc0 = -c_int(EIO)
}
}
}
func hostGetattr(path0 *c_char, stat0 *c_fuse_stat_t, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
stat := &Stat_t{}
fifh := ^uint64(0)
if nil != fi0 {
fifh = uint64(fi0.fh)
}
errc := fsop.Getattr(path, stat, fifh)
copyCstatFromFusestat(stat0, stat)
return c_int(errc)
}
func hostReadlink(path0 *c_char, buff0 *c_char, size0 c_size_t) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc, rslt := fsop.Readlink(path)
buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
copy(buff[:size0-1], rslt)
rlen := len(rslt)
if c_size_t(rlen) < size0 {
buff[rlen] = 0
}
return c_int(errc)
}
func hostMknod(path0 *c_char, mode0 c_fuse_mode_t, dev0 c_fuse_dev_t) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Mknod(path, uint32(mode0), uint64(dev0))
return c_int(errc)
}
func hostMkdir(path0 *c_char, mode0 c_fuse_mode_t) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Mkdir(path, uint32(mode0))
return c_int(errc)
}
func hostUnlink(path0 *c_char) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Unlink(path)
return c_int(errc)
}
func hostRmdir(path0 *c_char) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Rmdir(path)
return c_int(errc)
}
func hostSymlink(target0 *c_char, newpath0 *c_char) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
target, newpath := c_GoString(target0), c_GoString(newpath0)
errc := fsop.Symlink(target, newpath)
return c_int(errc)
}
func hostRename(oldpath0 *c_char, newpath0 *c_char, flags c_uint32_t) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
oldpath, newpath := c_GoString(oldpath0), c_GoString(newpath0)
intf, ok := fsop.(FileSystemRename3)
if ok {
errc := intf.Rename3(oldpath, newpath, uint32(flags))
return c_int(errc)
} else {
if flags != 0 {
// man 2 rename: EINVAL when "the filesystem does not support one of the flags"
return -c_int(EINVAL)
}
errc := fsop.Rename(oldpath, newpath)
return c_int(errc)
}
}
func hostLink(oldpath0 *c_char, newpath0 *c_char) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
oldpath, newpath := c_GoString(oldpath0), c_GoString(newpath0)
errc := fsop.Link(oldpath, newpath)
return c_int(errc)
}
func hostChmod(path0 *c_char, mode0 c_fuse_mode_t, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
intf, ok := fsop.(FileSystemChmod3)
if ok {
fifh := ^uint64(0)
if nil != fi0 {
fifh = uint64(fi0.fh)
}
errc := intf.Chmod3(path, uint32(mode0), fifh)
return c_int(errc)
} else {
errc := fsop.Chmod(path, uint32(mode0))
return c_int(errc)
}
}
func hostChown(path0 *c_char, uid0 c_fuse_uid_t, gid0 c_fuse_gid_t, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
intf, ok := fsop.(FileSystemChown3)
if ok {
fifh := ^uint64(0)
if nil != fi0 {
fifh = uint64(fi0.fh)
}
errc := intf.Chown3(path, uint32(uid0), uint32(gid0), fifh)
return c_int(errc)
} else {
errc := fsop.Chown(path, uint32(uid0), uint32(gid0))
return c_int(errc)
}
}
func hostTruncate(path0 *c_char, size0 c_fuse_off_t, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
fifh := ^uint64(0)
if nil != fi0 {
fifh = uint64(fi0.fh)
}
errc := fsop.Truncate(path, int64(size0), fifh)
return c_int(errc)
}
func hostOpen(path0 *c_char, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
intf, ok := fsop.(FileSystemOpenEx)
if ok {
fi := FileInfo_t{Flags: int(fi0.flags)}
errc := intf.OpenEx(path, &fi)
c_hostAsgnCfileinfo(fi0,
c_bool(fi.DirectIo),
c_bool(fi.KeepCache),
c_bool(fi.NonSeekable),
c_uint64_t(fi.Fh))
return c_int(errc)
} else {
errc, rslt := fsop.Open(path, int(fi0.flags))
fi0.fh = c_uint64_t(rslt)
return c_int(errc)
}
}
func hostRead(path0 *c_char, buff0 *c_char, size0 c_size_t, ofst0 c_fuse_off_t, fi0 *c_struct_fuse_file_info) (nbyt0 c_int) {
defer recoverAsErrno(&nbyt0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
nbyt := fsop.Read(path, buff[:size0], int64(ofst0), uint64(fi0.fh))
return c_int(nbyt)
}
func hostWrite(path0 *c_char, buff0 *c_char, size0 c_size_t, ofst0 c_fuse_off_t,
fi0 *c_struct_fuse_file_info) (nbyt0 c_int) {
defer recoverAsErrno(&nbyt0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
nbyt := fsop.Write(path, buff[:size0], int64(ofst0), uint64(fi0.fh))
return c_int(nbyt)
}
func hostStatfs(path0 *c_char, stat0 *c_fuse_statvfs_t) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
stat := &Statfs_t{}
errc := fsop.Statfs(path, stat)
if errc == -ENOSYS {
stat = &Statfs_t{}
errc = 0
}
copyCstatvfsFromFusestatfs(stat0, stat)
return c_int(errc)
}
func hostFlush(path0 *c_char, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Flush(path, uint64(fi0.fh))
return c_int(errc)
}
func hostRelease(path0 *c_char, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Release(path, uint64(fi0.fh))
return c_int(errc)
}
func hostFsync(path0 *c_char, datasync c_int, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Fsync(path, datasync != 0, uint64(fi0.fh))
if errc == -ENOSYS {
errc = 0
}
return c_int(errc)
}
func hostSetxattr(path0 *c_char, name0 *c_char, buff0 *c_char, size0 c_size_t,
flags c_int) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
name := c_GoString(name0)
buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
errc := fsop.Setxattr(path, name, buff[:size0], int(flags))
return c_int(errc)
}
func hostGetxattr(path0 *c_char, name0 *c_char, buff0 *c_char, size0 c_size_t) (nbyt0 c_int) {
defer recoverAsErrno(&nbyt0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
name := c_GoString(name0)
errc, rslt := fsop.Getxattr(path, name)
if errc != 0 {
return c_int(errc)
}
if size0 != 0 {
if len(rslt) > int(size0) {
return -c_int(ERANGE)
}
buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
copy(buff[:size0], rslt)
}
return c_int(len(rslt))
}
func hostListxattr(path0 *c_char, buff0 *c_char, size0 c_size_t) (nbyt0 c_int) {
defer recoverAsErrno(&nbyt0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
size := int(size0)
nbyt := 0
fill := func(name1 string) bool {
nlen := len(name1)
if size != 0 {
if nbyt+nlen+1 > size {
return false
}
copy(buff[nbyt:nbyt+nlen], name1)
buff[nbyt+nlen] = 0
}
nbyt += nlen + 1
return true
}
errc := fsop.Listxattr(path, fill)
if errc != 0 {
return c_int(errc)
}
return c_int(nbyt)
}
func hostRemovexattr(path0 *c_char, name0 *c_char) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
name := c_GoString(name0)
errc := fsop.Removexattr(path, name)
return c_int(errc)
}
func hostOpendir(path0 *c_char, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc, rslt := fsop.Opendir(path)
if errc == -ENOSYS {
errc = 0
}
fi0.fh = c_uint64_t(rslt)
return c_int(errc)
}
func hostReaddir(path0 *c_char, buff0 unsafe.Pointer, fill0 c_fuse_fill_dir_t, ofst0 c_fuse_off_t, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
fill := func(name1 string, stat1 *Stat_t, off1 int64) bool {
name := c_CString(name1)
defer c_free(unsafe.Pointer(name))
if stat1 == nil {
return c_hostFilldir(fill0, buff0, name, nil, c_fuse_off_t(off1)) == 0
}
stat_ex := c_fuse_stat_ex_t{} // support WinFsp fuse_stat_ex
stat := (*c_fuse_stat_t)(unsafe.Pointer(&stat_ex))
copyCstatFromFusestat(stat, stat1)
return c_hostFilldir(fill0, buff0, name, stat, c_fuse_off_t(off1)) == 0
}
errc := fsop.Readdir(path, fill, int64(ofst0), uint64(fi0.fh))
return c_int(errc)
}
func hostReleasedir(path0 *c_char, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Releasedir(path, uint64(fi0.fh))
return c_int(errc)
}
func hostFsyncdir(path0 *c_char, datasync c_int, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Fsyncdir(path, datasync != 0, uint64(fi0.fh))
if errc == -ENOSYS {
errc = 0
}
return c_int(errc)
}
func hostInit(conn0 *c_struct_fuse_conn_info, conf0 *c_struct_fuse_config) (user_data unsafe.Pointer) {
defer func() { recover() }()
fctx := c_fuse_get_context()
user_data = fctx.private_data
host := hostHandleGet(user_data)
host.fuse = fctx.fuse
c_hostAsgnCconninfo(conn0, c_bool(host.CapCaseInsensitive), c_bool(host.CapReaddirPlus), c_bool(host.CapDeleteAccess), c_bool(host.CapOpenTrunc))
c_hostAsgnCconfig(conf0, c_bool(host.DirectIO), c_bool(host.UseIno))
host.fsop.Init()
if host.mountInit != nil {
host.mountInit <- struct{}{} // send signal to mount
close(host.mountInit) // close channel
}
return
}
func hostDestroy(user_data unsafe.Pointer) {
defer func() { recover() }()
if runtime.GOOS == "netbsd" {
user_data = c_fuse_get_context().private_data
}
host := hostHandleGet(user_data)
host.fsop.Destroy()
if host.mountDestroy != nil {
host.mountDestroy <- struct{}{} // send signal to mount
close(host.mountDestroy) // close channel
}
host.fuse = nil
}
func hostAccess(path0 *c_char, mask0 c_int) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Access(path, uint32(mask0))
return c_int(errc)
}
func hostCreate(path0 *c_char, mode0 c_fuse_mode_t, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
intf, ok := fsop.(FileSystemOpenEx)
if ok {
fi := FileInfo_t{Flags: int(fi0.flags)}
errc := intf.CreateEx(path, uint32(mode0), &fi)
if errc == -ENOSYS {
errc = fsop.Mknod(path, uint32(S_IFREG)|uint32(mode0), 0)
if errc == 0 {
errc = intf.OpenEx(path, &fi)
}
}
c_hostAsgnCfileinfo(fi0,
c_bool(fi.DirectIo),
c_bool(fi.KeepCache),
c_bool(fi.NonSeekable),
c_uint64_t(fi.Fh))
return c_int(errc)
} else {
errc, rslt := fsop.Create(path, int(fi0.flags), uint32(mode0))
if errc == -ENOSYS {
errc = fsop.Mknod(path, uint32(S_IFREG)|uint32(mode0), 0)
if errc == 0 {
errc, rslt = fsop.Open(path, int(fi0.flags))
}
}
fi0.fh = c_uint64_t(rslt)
return c_int(errc)
}
}
func hostFtruncate(path0 *c_char, size0 c_fuse_off_t, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
errc := fsop.Truncate(path, int64(size0), uint64(fi0.fh))
return c_int(errc)
}
func hostFgetattr(path0 *c_char, stat0 *c_fuse_stat_t,
fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
stat := &Stat_t{}
errc := fsop.Getattr(path, stat, uint64(fi0.fh))
copyCstatFromFusestat(stat0, stat)
return c_int(errc)
}
func hostUtimens(path0 *c_char, tmsp0 *c_fuse_timespec_t, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
path := c_GoString(path0)
tmsp := [2]Timespec{}
if nil == tmsp0 {
tmsp[0] = Now()
tmsp[1] = tmsp[0]
} else if tmsa := (*[2]c_fuse_timespec_t)(unsafe.Pointer(tmsp0)); UTIME_NOW == tmsa[0].tv_nsec &&
UTIME_NOW == tmsa[1].tv_nsec {
tmsp[0] = Now()
tmsp[1] = tmsp[0]
} else {
copyFusetimespecFromCtimespec(&tmsp[0], &tmsa[0])
copyFusetimespecFromCtimespec(&tmsp[1], &tmsa[1])
}
intf, ok := fsop.(FileSystemUtimens3)
if ok {
fifh := ^uint64(0)
if nil != fi0 {
fifh = uint64(fi0.fh)
}
errc := intf.Utimens3(path, tmsp[:], fifh)
return c_int(errc)
} else {
errc := fsop.Utimens(path, tmsp[:])
return c_int(errc)
}
}
func hostGetpath(path0 *c_char, buff0 *c_char, size0 c_size_t,
fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
intf, ok := fsop.(FileSystemGetpath)
if !ok {
return -c_int(ENOSYS)
}
path := c_GoString(path0)
fifh := ^uint64(0)
if nil != fi0 {
fifh = uint64(fi0.fh)
}
errc, rslt := intf.Getpath(path, fifh)
buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
copy(buff[:size0-1], rslt)
rlen := len(rslt)
if c_size_t(rlen) < size0 {
buff[rlen] = 0
}
return c_int(errc)
}
func hostSetchgtime(path0 *c_char, tmsp0 *c_fuse_timespec_t) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
intf, ok := fsop.(FileSystemSetchgtime)
if !ok {
// say we did it!
return 0
}
path := c_GoString(path0)
tmsp := Timespec{}
copyFusetimespecFromCtimespec(&tmsp, tmsp0)
errc := intf.Setchgtime(path, tmsp)
return c_int(errc)
}
func hostSetcrtime(path0 *c_char, tmsp0 *c_fuse_timespec_t) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
intf, ok := fsop.(FileSystemSetcrtime)
if !ok {
// say we did it!
return 0
}
path := c_GoString(path0)
tmsp := Timespec{}
copyFusetimespecFromCtimespec(&tmsp, tmsp0)
errc := intf.Setcrtime(path, tmsp)
return c_int(errc)
}
func hostChflags(path0 *c_char, flags c_uint32_t) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
intf, ok := fsop.(FileSystemChflags)
if !ok {
// say we did it!
return 0
}
path := c_GoString(path0)
errc := intf.Chflags(path, uint32(flags))
return c_int(errc)
}
func hostLseek(path0 *c_char, ofst0 c_fuse_off_t, whence0 c_int, fi0 *c_struct_fuse_file_info) (errc0 c_int) {
defer recoverAsErrno(&errc0)
fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
intf, ok := fsop.(FileSystemLseek)
if !ok {
return -c_int(ENOSYS)
}
path := c_GoString(path0)
errc := intf.Lseek(path, uint64(ofst0), int(whence0), uint64(fi0.fh))
return c_int(errc)
}
// NewFileSystemHost creates a file system host.
func NewFileSystemHost(fsop FileSystemInterface) *FileSystemHost {
host := new(FileSystemHost)
host.fsop = fsop
host.DirectIO = true
host.CapReaddirPlus = true
return host
}
// Mount mounts a file system on the given mountpoint with the mount options in opts.
//
// Many of the mount options in opts are specific to the underlying FUSE implementation.
// Some of the common options include:
//
// -h --help print help
// -V --version print FUSE version
// -d -o debug enable FUSE debug output
// -s disable multi-threaded operation
//
// Please refer to the individual FUSE implementation documentation for additional options.
//
// It is allowed for the mountpoint to be the empty string ("") in which case opts is assumed
// to contain the mountpoint. It is also allowed for opts to be nil, although in this case the
// mountpoint must be non-empty.
//
// For unmount filesystem passthroud context
func (host *FileSystemHost) Mount(ctx context.Context, mountpoint string, opts ...string) (err error) {
if host.fsop == nil {
return errors.New("cgofuse: set filesystem implementation before mounting")
} else if c_hostFuseInit() == 0 {
return errors.New("cgofuse: cannot find FUSE")
}
/*
* Command line handling
*
* We must prepare a command line to send to FUSE. This command line will look like this:
*
* execname [mountpoint] "-f" [opts...] NULL
*
* We add the "-f" option because Go cannot handle daemonization (at least on OSX).
*/
exec := "<UNKNOWN>"
if 0 < len(os.Args) {
exec = os.Args[0]
}
argc := len(opts) + 2
if mountpoint != "" {
argc++
}
argv := make([]*c_char, argc+1)
argv[0] = c_CString(exec)
defer c_free(unsafe.Pointer(argv[0]))
opti := 1
if mountpoint != "" {
argv[1] = c_CString(mountpoint)
defer c_free(unsafe.Pointer(argv[1]))
opti++
}
argv[opti] = c_CString("-f")
defer c_free(unsafe.Pointer(argv[opti]))
opti++
for i, opt := range opts {
argv[i+opti] = c_CString(opt)
defer c_free(unsafe.Pointer(argv[i+opti]))
}
/*
* Mountpoint extraction
*
* We need to determine the mountpoint that FUSE is going (to try) to use, so that we
* can unmount later.
*/
if mountpoint != "" {
host.mntp = mountpoint
} else {
outargs, _ := OptParse(opts, "")
if len(outargs) >= 1 {
host.mntp = outargs[0]
}
}
if host.mntp != "" {
if len(host.mntp) != 2 || host.mntp[1] != ':' {
abs, err := filepath.Abs(host.mntp)
if nil == err {
host.mntp = abs
}
}
}
/*
* Handle zombie mounts
*
* FUSE on UNIX does not automatically unmount the file system, leaving behind "zombie"
* mounts. So set things up to always unmount the file system (unless forcibly terminated).
* This has the added benefit that the file system Destroy() always gets called.
*/
host.done = make(chan struct{})
host.mountInit = make(chan struct{}, 1)
host.mountDestroy = make(chan struct{}, 1)
// Start this in another goroutine to don't lock thread
go func() {
defer func() { recover() }()
defer func() { host.mntp = "" }()
defer close(host.done)
hndl := hostHandleNew(host)
defer hostHandleDel(hndl)
// Tell FUSE to do its job!
if eerr := syscall.Errno(c_hostMount(c_int(argc), &argv[0], hndl)); eerr != 0 {
host.mountErr = eerr
err = eerr
}
// Send done to channel if avaible
if host.done != nil {
host.done <- struct{}{}
close(host.done)
}
}()
// wait for response from done or mountInit
select {
case <-host.done:
case <-host.mountInit:
}
// Add unmount
go func() {
if err != nil {
return
}
defer func() { recover() }()
// Wait to unmount
select {
case <-ctx.Done():
case <-host.done:
}
unmount(host, ctx)
}()
return
}
func unmount(host *FileSystemHost, ctx context.Context) {
mount := host.mntp
if mount == "" {
return
}
defer func() { recover() }()
mntp := c_CString(mount)
defer c_free(unsafe.Pointer(mntp))
if eerr := syscall.Errno(c_hostUnmount(host.fuse, mntp)); eerr != 0 {
err := error(eerr)
if host.mountErr != nil {
err = errors.Join(host.mountErr, eerr)
}
host.mountErr = err
_, f := context.WithCancelCause(ctx)
f(err)
}
if host.done != nil {
close(host.done)
}
}
// wait mount done process and return error ir present
func (host *FileSystemHost) Done() error {
defer func() { recover() }()
if host.done != nil {
<-host.done
}
if host.mountDestroy != nil {
select {
case <-host.mountDestroy:
case <-time.After(time.Second * 30):
}
}
return host.mountErr
}
// Notify notifies the operating system about a file change.
// The action is a combination of the fuse.NOTIFY_* constants.
func (host *FileSystemHost) Notify(path string, action uint32) bool {
if nil == host.fuse {
return false
}
if path == "" {
return false
}
p := c_CString(path)
defer c_free(unsafe.Pointer(p))
return c_hostNotify(host.fuse, p, c_uint32_t(action)) != 0
}
// Getcontext gets information related to a file system operation.
func Getcontext() (uid uint32, gid uint32, pid int) {
context := c_fuse_get_context()
uid = uint32(context.uid)
gid = uint32(context.gid)
pid = int(context.pid)
return
}
func optNormBool(opt string) string {
if i := strings.Index(opt, "=%"); i != -1 {
switch opt[i+2:] {
case "d", "o", "x", "X":
return opt
case "v":
return opt[:i+1]
default:
panic("unknown format " + opt[i+1:])
}
} else {
return opt
}
}
func optNormInt(opt string, modf string) string {
if i := strings.Index(opt, "=%"); i != -1 {
switch opt[i+2:] {
case "d", "o", "x", "X":
return opt[:i+2] + modf + opt[i+2:]
case "v":
return opt[:i+2] + modf + "i"
default:
panic("unknown format " + opt[i+1:])
}
} else if strings.HasSuffix(opt, "=") {
return opt + "%" + modf + "i"
} else {
return opt + "=%" + modf + "i"
}
}
func optNormStr(opt string) string {
if i := strings.Index(opt, "=%"); i != -1 {
switch opt[i+2:] {
case "s", "v":
return opt[:i+2] + "s"
default:
panic("unknown format " + opt[i+1:])
}
} else if strings.HasSuffix(opt, "=") {
return opt + "%s"
} else {
return opt + "=%s"
}
}
// OptParse parses the FUSE command line arguments in args as determined by format
// and stores the resulting values in vals, which must be pointers. It returns a
// list of unparsed arguments or nil if an error happens.
//
// The format may be empty or non-empty. An empty format is taken as a special
// instruction to OptParse to only return all non-option arguments in outargs.
//
// A non-empty format is a space separated list of acceptable FUSE options. Each
// option is matched with a corresponding pointer value in vals. The combination
// of the option and the type of the corresponding pointer value, determines how
// the option is used. The allowed pointer types are pointer to bool, pointer to
// an integer type and pointer to string.
//
// For pointer to bool types:
//
// -x Match -x without parameter.
// -foo --foo As above for -foo or --foo.
// foo Match "-o foo".
// -x= -foo= --foo= foo= Match option with parameter.
// -x=%VERB ... foo=%VERB Match option with parameter of syntax.
// Allowed verbs: d,o,x,X,v
// - d,o,x,X: set to true if parameter non-0.
// - v: set to true if parameter present.
//
// The formats -x=, and -x=%v are equivalent.
//
// For pointer to other types:
//
// -x Match -x with parameter (-x=PARAM).
// -foo --foo As above for -foo or --foo.
// foo Match "-o foo=PARAM".
// -x= -foo= --foo= foo= Match option with parameter.
// -x=%VERB ... foo=%VERB Match option with parameter of syntax.
// Allowed verbs for pointer to int types: d,o,x,X,v
// Allowed verbs for pointer to string types: s,v
//
// The formats -x, -x=, and -x=%v are equivalent.
//
// For example:
//
// var f bool
// var set_attr_timeout bool
// var attr_timeout int
// var umask uint32
// outargs, err := OptParse(args, "-f attr_timeout= attr_timeout umask=%o",
// &f, &set_attr_timeout, &attr_timeout, &umask)
//
// Will accept a command line of:
//
// $ program -f -o attr_timeout=42,umask=077
//
// And will set variables as follows:
//
// f == true
// set_attr_timeout == true
// attr_timeout == 42
// umask == 077
func OptParse(args []string, format string, vals ...any) (outargs []string, err error) {
if c_hostFuseInit() == 0 {
return nil, errors.New("cgofuse: cannot find FUSE")
}
defer func() {
if r := recover(); nil != r {
if s, ok := r.(string); ok {
outargs = nil
err = errors.New("OptParse: " + s)
} else {
panic(r)
}
}
}()
var opts []string
var nonopts bool
if format == "" {
opts = make([]string, 0)
nonopts = true
} else {
opts = strings.Split(format, " ")
}
align := int(2 * unsafe.Sizeof(c_size_t(0))) // match malloc alignment (usually 8 or 16)
fuse_opts := make([]c_struct_fuse_opt, len(opts)+1)
for i := 0; len(opts) > i; i++ {
var templ *c_char
switch vals[i].(type) {
case *bool:
templ = c_CString(optNormBool(opts[i]))
case *int:
templ = c_CString(optNormInt(opts[i], ""))
case *int8:
templ = c_CString(optNormInt(opts[i], "hh"))
case *int16:
templ = c_CString(optNormInt(opts[i], "h"))
case *int32:
templ = c_CString(optNormInt(opts[i], ""))
case *int64:
templ = c_CString(optNormInt(opts[i], "ll"))
case *uint:
templ = c_CString(optNormInt(opts[i], ""))
case *uint8:
templ = c_CString(optNormInt(opts[i], "hh"))
case *uint16:
templ = c_CString(optNormInt(opts[i], "h"))
case *uint32:
templ = c_CString(optNormInt(opts[i], ""))
case *uint64:
templ = c_CString(optNormInt(opts[i], "ll"))
case *uintptr:
templ = c_CString(optNormInt(opts[i], "ll"))
case *string:
templ = c_CString(optNormStr(opts[i]))
}
defer c_free(unsafe.Pointer(templ))
c_hostOptSet(&fuse_opts[i], templ, c_fuse_opt_offset_t(i*align), 1)
}
fuse_args := c_struct_fuse_args{}
defer c_fuse_opt_free_args(&fuse_args)
argc := 1 + len(args)
argp := c_calloc(c_size_t(argc+1), c_size_t(unsafe.Sizeof((*c_char)(nil))))
argv := (*[1 << 16]*c_char)(argp)
argv[0] = c_CString("<UNKNOWN>")
for i := 0; len(args) > i; i++ {
argv[1+i] = c_CString(args[i])
}
fuse_args.allocated = 1
fuse_args.argc = c_int(argc)
fuse_args.argv = (**c_char)(&argv[0])
data := c_calloc(c_size_t(len(opts)), c_size_t(align))
defer c_free(data)
if c_hostOptParse(&fuse_args, data, &fuse_opts[0], c_bool(nonopts)) == -1 {
panic("failed")
}
for i := 0; len(opts) > i; i++ {
switch v := vals[i].(type) {
case *bool:
*v = int(*(*c_int)(unsafe.Pointer(uintptr(data) + uintptr(i*align)))) != 0
case *int:
*v = int(*(*c_int)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *int8:
*v = int8(*(*c_int8_t)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *int16:
*v = int16(*(*c_int16_t)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *int32:
*v = int32(*(*c_int32_t)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *int64:
*v = int64(*(*c_int64_t)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *uint:
*v = uint(*(*c_unsigned)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *uint8:
*v = uint8(*(*c_uint8_t)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *uint16:
*v = uint16(*(*c_uint16_t)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *uint32:
*v = uint32(*(*c_uint32_t)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *uint64:
*v = uint64(*(*c_uint64_t)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *uintptr:
*v = uintptr(*(*c_uintptr_t)(unsafe.Pointer(uintptr(data) + uintptr(i*align))))
case *string:
s := *(**c_char)(unsafe.Pointer(uintptr(data) + uintptr(i*align)))
*v = c_GoString(s)
c_free(unsafe.Pointer(s))
}
}
if 1 >= fuse_args.argc {
outargs = make([]string, 0)
} else {
outargs = make([]string, fuse_args.argc-1)
for i := 1; int(fuse_args.argc) > i; i++ {
outargs[i-1] = c_GoString((*[1 << 16]*c_char)(unsafe.Pointer(fuse_args.argv))[i])
}
}
if nonopts && 1 <= len(outargs) && outargs[0] == "--" {
outargs = outargs[1:]
}
return
}
func init() {
c_hostStaticInit()
}