104 lines
2.9 KiB
Go
104 lines
2.9 KiB
Go
package scoutfs
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const ioctlMagic = 0xE8
|
|
|
|
// Linux ioctl direction bits.
|
|
const (
|
|
iocNone uintptr = 0
|
|
iocWrite uintptr = 1
|
|
iocRead uintptr = 2
|
|
)
|
|
|
|
func ioc(dir, typ, nr, size uintptr) uintptr {
|
|
return (dir << 30) | (size << 16) | (typ << 8) | nr
|
|
}
|
|
|
|
func iocW(nr, size uintptr) uintptr { return ioc(iocWrite, ioctlMagic, nr, size) }
|
|
func iocR(nr, size uintptr) uintptr { return ioc(iocRead, ioctlMagic, nr, size) }
|
|
func iocWR(nr, size uintptr) uintptr { return ioc(iocRead|iocWrite, ioctlMagic, nr, size) }
|
|
|
|
// Ioctl request numbers matching ScoutFS v1.29 kmod/src/ioctl.h.
|
|
var (
|
|
iocWalkInodes = iocW(1, unsafe.Sizeof(WalkInodesRequest{}))
|
|
iocInoPath = iocW(2, unsafe.Sizeof(InoPathRequest{}))
|
|
iocStatMore = iocR(5, unsafe.Sizeof(StatMore{}))
|
|
iocListXattrHidden = iocWR(8, unsafe.Sizeof(ListXattrHiddenRequest{}))
|
|
iocSearchXattrs = iocW(9, unsafe.Sizeof(SearchXattrsRequest{}))
|
|
iocStatfsMore = iocR(10, unsafe.Sizeof(StatfsMore{}))
|
|
iocGetAllocatedInos = iocW(16, unsafe.Sizeof(GetAllocatedInosRequest{}))
|
|
iocGetReferringEntries = iocW(17, unsafe.Sizeof(GetReferringEntriesRequest{}))
|
|
iocGetAttrX = iocW(18, unsafe.Sizeof(InodeAttrX{}))
|
|
iocSetAttrX = iocW(19, unsafe.Sizeof(InodeAttrX{}))
|
|
iocReadXattrIndex = iocR(23, unsafe.Sizeof(ReadXattrIndexRequest{}))
|
|
)
|
|
|
|
// Sentinel errors for common ioctl failure modes.
|
|
var (
|
|
ErrNotScoutFS = errors.New("not a ScoutFS filesystem")
|
|
ErrInodeNotFound = errors.New("inode not found")
|
|
ErrPermission = errors.New("permission denied")
|
|
ErrNoSpace = errors.New("no space left on device")
|
|
)
|
|
|
|
// mapErrno translates common syscall errors to domain-specific sentinels.
|
|
func mapErrno(errno unix.Errno) error {
|
|
switch errno {
|
|
case unix.ENOTTY:
|
|
return ErrNotScoutFS
|
|
case unix.ENOENT:
|
|
return ErrInodeNotFound
|
|
case unix.EACCES, unix.EPERM:
|
|
return ErrPermission
|
|
case unix.ENOSPC:
|
|
return ErrNoSpace
|
|
default:
|
|
return errno
|
|
}
|
|
}
|
|
|
|
func ioctlRaw(fd uintptr, req uintptr, arg unsafe.Pointer) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, req, uintptr(arg))
|
|
if errno != 0 {
|
|
return mapErrno(errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Client wraps a file descriptor to a ScoutFS mount point and exposes
|
|
// high-level methods for each ioctl. Create one with NewClient.
|
|
type Client struct {
|
|
file *os.File
|
|
mountPath string
|
|
}
|
|
|
|
// NewClient opens the given ScoutFS mount path and returns a Client.
|
|
// The caller must call Close when done.
|
|
func NewClient(mountPath string) (*Client, error) {
|
|
if mountPath == "" {
|
|
panic("NewClient: mountPath must not be empty")
|
|
}
|
|
f, err := os.Open(mountPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening mount path %q: %w", mountPath, err)
|
|
}
|
|
return &Client{file: f, mountPath: mountPath}, nil
|
|
}
|
|
|
|
// Close releases the underlying file descriptor.
|
|
func (c *Client) Close() error {
|
|
return c.file.Close()
|
|
}
|
|
|
|
func (c *Client) fd() uintptr {
|
|
return c.file.Fd()
|
|
}
|