// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT //go:build freebsd package nfsfs import ( "syscall" ) // The whence values of the hole seeking of FreeBSD, sys/sys/unistd.h. const ( seekData = 3 seekHole = 4 ) // SeekHole finds the next hole at or after the offset. The eof flag // reports that none of the requested content follows: every file // carries a virtual hole at its end, so a dense tail answers the file // size with eof set, RFC 7862 section 15.11. ErrBeyondEOF answers a // request that starts past the end. func (l *Local) SeekHole(h Handle, offset int64) (int64, bool, error) { return l.seek(h, offset, seekHole) } // SeekData finds the next data byte at or after the offset. The eof // flag reports that no data follows the offset, and the answer names // the file size; ErrBeyondEOF answers a request that starts past the // end. func (l *Local) SeekData(h Handle, offset int64) (int64, bool, error) { return l.seek(h, offset, seekData) } func (l *Local) seek(h Handle, offset int64, whence int) (int64, bool, error) { // The seek opens the same revalidated, unfollowed descriptor every // other data path uses, so a name swapped for a link never serves // bytes from outside the export. f, _, err := l.openVerified(h, syscall.O_RDONLY) if err != nil { return 0, false, err } defer f.Close() fd := int(f.Fd()) size, err := fstatSize(fd) if err != nil { return 0, false, err } if offset > size { return 0, false, ErrBeyondEOF } at, err := syscall.Seek(fd, offset, whence) if err == syscall.ENXIO { // The range from the offset to the end carries none of the // requested content: the answer is the end of the file with the // eof flag set. return size, true, nil } if err != nil { return 0, false, err } if at >= size { // The virtual hole at the end of every file: the eof flag tells // the client the search is over. return size, true, nil } return at, false, nil } // Allocate reserves a range with posix_fallocate, the reservation call // of FreeBSD: it grows the file to offset plus length where the range // runs past the end, the behaviour RFC 7862 section 15.1 requires of // ALLOCATE. The handle is opened without following a final symlink // and revalidated against the descriptor before anything is reserved. func (l *Local) Allocate(h Handle, offset, length int64) error { kind, _, _, _, err := l.resolve(h) if err != nil { return err } if kind != typeFile { return ErrIsDir } f, _, err := l.openVerified(h, syscall.O_WRONLY) if err != nil { return err } defer f.Close() // posix_fallocate reports its failure as its return value, an // errno, and never through the system call errno itself. ret, _, _ := syscall.Syscall6(syscall.SYS_POSIX_FALLOCATE, f.Fd(), uintptr(offset), uintptr(length), 0, 0, 0) if ret != 0 { return syscall.Errno(ret) } return nil } // Deallocate answers ErrNoSparse: the system call surface of FreeBSD // carries no call that punches a hole into a range and keeps the file // size, unlike the fallocate modes of Linux. func (l *Local) Deallocate(h Handle, offset, length int64) error { return ErrNoSparse } // fstatSize reads the size of the open file. func fstatSize(fd int) (int64, error) { var st syscall.Stat_t if err := syscall.Fstat(fd, &st); err != nil { return 0, err } return st.Size, nil }