Files
William Gill e96c71cf16
Some checks failed
Release / Build & Vet (push) Successful in 1m29s
Release / Test (push) Successful in 1m35s
Release / Lint (push) Successful in 2m7s
Release / GoReleaser Check (push) Successful in 1m25s
Release / Build & Release (push) Failing after 2m27s
style: Run gofmt on all Go source files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 23:12:41 -05:00

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