diff options
| author | Ankush Patel | 2026-02-05 14:42:40 +1300 |
|---|---|---|
| committer | Ankush Patel | 2026-02-14 03:32:14 +1300 |
| commit | 903fc9d077a81d9224de4207d1672c0b1127cf42 (patch) | |
| tree | 05ae39d5ebbe41bb64d41d7e0f03df7dac596dae /internal/syscallcompat/sys_freebsd.go | |
| parent | 3191c18f67346c95e4dbdfd16b44256ddfe20b4f (diff) | |
Added basic support for FreeBSD.
Diffstat (limited to 'internal/syscallcompat/sys_freebsd.go')
| -rw-r--r-- | internal/syscallcompat/sys_freebsd.go | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/internal/syscallcompat/sys_freebsd.go b/internal/syscallcompat/sys_freebsd.go new file mode 100644 index 0000000..ec2c402 --- /dev/null +++ b/internal/syscallcompat/sys_freebsd.go @@ -0,0 +1,201 @@ +// Package syscallcompat wraps FreeBSD-specific syscalls +package syscallcompat + +import ( + "errors" + "fmt" + "time" + + "golang.org/x/sys/unix" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +const ( + O_DIRECT = unix.O_DIRECT + + // O_PATH is supported on FreeBSD, but is missing from the sys/unix package + // FreeBSD-15.0 /usr/src/sys/sys/fcntl.h:135 + O_PATH = 0x00400000 + + // Only defined on Linux, but we can emulate the functionality on FreeBSD + // in Renameat2() below + RENAME_NOREPLACE = 0x1 + RENAME_EXCHANGE = 0x2 + RENAME_WHITEOUT = 0x4 + + // ENODATA is only defined on Linux, but FreeBSD provides ENOATTR + ENODATA = unix.ENOATTR +) + +// EnospcPrealloc is supposed to preallocate ciphertext space without +// changing the file size. This guarantees that we don't run out of +// space while writing a ciphertext block (that would corrupt the block). +// +// The fallocate syscall isn't supported on FreeBSD with the same semantics +// as Linux, in particular the _FALLOC_FL_KEEP_SIZE mode isn't supported. +func EnospcPrealloc(fd int, off int64, len int64) (err error) { + return nil +} + +// Fallocate wraps the posix_fallocate() syscall. +// Fallocate returns an error if mode is not 0 +func Fallocate(fd int, mode uint32, off int64, len int64) (err error) { + if mode != 0 { + return errors.New("fallocate unsupported mode") + } + _, _, err = unix.Syscall(unix.SYS_POSIX_FALLOCATE, uintptr(fd), uintptr(off), uintptr(len)) + return err +} + +// Mknodat wraps the Mknodat syscall. +func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) { + return unix.Mknodat(dirfd, path, mode, uint64(dev)) +} + +// Dup3 wraps the Dup3 syscall. We want to use Dup3 rather than Dup2 because Dup2 +// is not implemented on arm64. +func Dup3(oldfd int, newfd int, flags int) (err error) { + return unix.Dup3(oldfd, newfd, flags) +} + +// FchmodatNofollow is like Fchmodat but never follows symlinks. +// +// This should be handled by the AT_SYMLINK_NOFOLLOW flag, but Linux +// does not implement it, so we have to perform an elaborate dance +// with O_PATH and /proc/self/fd. +// +// See also: Qemu implemented the same logic as fchmodat_nofollow(): +// https://git.qemu.org/?p=qemu.git;a=blob;f=hw/9pfs/9p-local.c#l335 +func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) { + // Open handle to the filename (but without opening the actual file). + // This succeeds even when we don't have read permissions to the file. + fd, err := unix.Openat(dirfd, path, unix.O_NOFOLLOW|O_PATH, 0) + if err != nil { + return err + } + defer unix.Close(fd) + + // Now we can check the type without the risk of race-conditions. + // Return syscall.ELOOP if it is a symlink. + var st unix.Stat_t + err = unix.Fstat(fd, &st) + if err != nil { + return err + } + if st.Mode&unix.S_IFMT == unix.S_IFLNK { + return unix.ELOOP + } + + // Change mode of the actual file. Fchmod does not work with O_PATH, + // but Chmod via /proc/self/fd works. + procPath := fmt.Sprintf("/proc/self/fd/%d", fd) + return unix.Chmod(procPath, mode) +} + +// LsetxattrUser runs the Lsetxattr syscall in the context of a different user. +// This is useful when setting ACLs, as the result depends on the user running +// the operation (see fuse-xfstests generic/375). +// +// If `context` is nil, this function behaves like ordinary Lsetxattr. +func LsetxattrUser(path string, attr string, data []byte, flags int, context *fuse.Context) (err error) { + f := func() (int, error) { + err := unix.Lsetxattr(path, attr, data, flags) + return -1, err + } + _, err = asUser(f, context) + return err +} + +func timesToTimespec(a *time.Time, m *time.Time) []unix.Timespec { + ts := make([]unix.Timespec, 2) + if a == nil { + ts[0] = unix.Timespec{Nsec: unix.UTIME_OMIT} + } else { + ts[0], _ = unix.TimeToTimespec(*a) + } + if m == nil { + ts[1] = unix.Timespec{Nsec: unix.UTIME_OMIT} + } else { + ts[1], _ = unix.TimeToTimespec(*m) + } + return ts +} + +// FutimesNano syscall. +func FutimesNano(fd int, a *time.Time, m *time.Time) (err error) { + ts := timesToTimespec(a, m) + // To avoid introducing a separate syscall wrapper for futimens() + // (as done in go-fuse, for example), we instead use the /proc/self/fd trick. + procPath := fmt.Sprintf("/proc/self/fd/%d", fd) + return unix.UtimesNanoAt(unix.AT_FDCWD, procPath, ts, 0) +} + +// UtimesNanoAtNofollow is like UtimesNanoAt but never follows symlinks. +// Retries on EINTR. +func UtimesNanoAtNofollow(dirfd int, path string, a *time.Time, m *time.Time) (err error) { + ts := timesToTimespec(a, m) + err = retryEINTR(func() error { + return unix.UtimesNanoAt(dirfd, path, ts, unix.AT_SYMLINK_NOFOLLOW) + }) + return err +} + +// Getdents syscall with "." and ".." filtered out. +func Getdents(fd int) ([]fuse.DirEntry, error) { + entries, _, err := emulateGetdents(fd) + return entries, err +} + +// GetdentsSpecial calls the Getdents syscall, +// with normal entries and "." / ".." split into two slices. +func GetdentsSpecial(fd int) (entries []fuse.DirEntry, entriesSpecial []fuse.DirEntry, err error) { + return emulateGetdents(fd) +} + +// Renameat2 does not exist on Darwin, so we have to wrap it here. +// Retries on EINTR. +func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) { + if flags&(RENAME_NOREPLACE|RENAME_EXCHANGE) == RENAME_NOREPLACE|RENAME_EXCHANGE { + return unix.EINVAL + } + if flags&(RENAME_NOREPLACE|RENAME_EXCHANGE) == RENAME_NOREPLACE|RENAME_EXCHANGE { + return unix.EINVAL + } + + if flags&RENAME_NOREPLACE != 0 { + var st unix.Stat_t + err = unix.Fstatat(newdirfd, newpath, &st, 0) + if err == nil { + // Assume newpath is an existing file if we can stat() it. + // On Linux, RENAME_NOREPLACE fails with EEXIST if newpath + // already exists. + return unix.EEXIST + } + } + if flags&RENAME_EXCHANGE != 0 { + // Note that on Linux, RENAME_EXCHANGE can handle oldpath and + // newpath of different file types (e.g. directory and + // symbolic link). On FreeBSD the file types must be the same. + var stold, stnew unix.Stat_t + err = unix.Fstatat(olddirfd, oldpath, &stold, 0) + if err != nil { + // Assume file does not exist if we can't stat() it. + // On Linux, RENAME_EXCHANGE requires both oldpath + // and newpath exist. + return unix.ENOENT + } + err = unix.Fstatat(newdirfd, newpath, &stnew, 0) + if err != nil { + // Assume file does not exist if we can't stat() it. + // On Linux, RENAME_EXCHANGE requires both oldpath + // and newpath exist. + return unix.ENOENT + } + } + if flags&RENAME_WHITEOUT != 0 { + return unix.EINVAL + } + + return unix.Renameat(olddirfd, oldpath, newdirfd, newpath) +} |
