On 5/18/2026 2:27 PM, Warner Losh wrote:
> Add FreeBSD-specific file operation shims: pipe2, chflagsat,
> close_range, copy_file_range, and __specialfd. Also add the
> safe_copy_file_range and safe_ppoll syscall wrappers and the
> target_specialfd_eventfd type definitions.
>
> Signed-off-by: Stacey Son <[email protected]>
> Signed-off-by: Warner Losh <[email protected]>
> Assisted-by: Claude Opus 4.6 (1M context)
> ---
> bsd-user/freebsd/os-file.h | 129
> ++++++++++++++++++++++++++++++++++++++++++
> bsd-user/freebsd/os-syscall.c | 7 +++
> 2 files changed, 136 insertions(+)
>
> diff --git a/bsd-user/freebsd/os-file.h b/bsd-user/freebsd/os-file.h
> new file mode 100644
> index 0000000000..c1da52789a
> --- /dev/null
> +++ b/bsd-user/freebsd/os-file.h
> @@ -0,0 +1,129 @@
> +/*
> + * FreeBSD file related system call shims and definitions
> + *
> + * Copyright (c) 2014 Stacey D. Son
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +#ifndef FREEBSD_OS_FILE_H
> +#define FREEBSD_OS_FILE_H
> +
> +#include <sys/specialfd.h>
> +
> +/*
> + * Asynchronous I/O.
> + */
> +
> +/* pipe2(2) */
> +static abi_long do_bsd_pipe2(CPUArchState *env, abi_ulong pipedes, int flags)
> +{
> + int host_pipe[2];
> + int host_ret = pipe2(host_pipe, flags);
> + /* XXXss - flags should be translated from target to host. */
> +
> + if (host_ret == -1) {
> + return get_errno(host_ret);
> + }
> +
> + /*
> + * pipe2() returns it's second FD by copying it back to userspace and
> not in
> + * a second register like pipe(2): set_second_rval(env, host_pipe[1]);
> + *
> + * Copy the FD's back to userspace:
> + */
> + if (put_user_s32(host_pipe[0], pipedes) ||
> + put_user_s32(host_pipe[1], pipedes + sizeof(host_pipe[0]))) {
> + close(host_pipe[0]);
> + close(host_pipe[1]);
> + return -TARGET_EFAULT;
> + }
> + return 0;
> +}
> +
> +/* chflagsat(2) */
> +static inline abi_long do_bsd_chflagsat(int fd, abi_ulong path,
> + abi_ulong flags, int atflags)
> +{
> + abi_long ret;
> + void *p;
> +
> + LOCK_PATH(p, path);
> + ret = get_errno(chflagsat(fd, p, flags, atflags)); /* XXX path(p)? */
> + UNLOCK_PATH(p, path);
> +
> + return ret;
> +}
> +
> +/* close_range(2) */
> +static inline abi_long do_freebsd_close_range(unsigned int lowfd,
> + unsigned int highfd, int flags)
> +{
> +
> + return get_errno(close_range(lowfd, highfd, flags));
> +}
> +
> +ssize_t safe_copy_file_range(int, off_t *, int, off_t *, size_t, unsigned
> int);
> +
> +/* copy_file_range(2) */
> +static inline abi_long do_freebsd_copy_file_range(int infd,
> + abi_ulong inofftp, int outfd, abi_ulong outofftp, size_t len,
> + unsigned int flags)
> +{
> + off_t inoff, outoff, *inp, *outp;
> + abi_long ret;
> +
> + inp = outp = NULL;
> + if (inofftp != 0 && !access_ok(VERIFY_WRITE, inofftp, sizeof(off_t))) {
> + return -TARGET_EFAULT;
> + } else if (inofftp != 0) {
> + inoff = tswap64(*(off_t *)g2h_untagged(inofftp));
> + inp = &inoff;
> + }
> + if (outofftp != 0 && !access_ok(VERIFY_WRITE, outofftp, sizeof(off_t))) {
> + return -TARGET_EFAULT;
> + } else if (outofftp != 0) {
> + outoff = tswap64(*(off_t *)g2h_untagged(outofftp));
> + outp = &outoff;
> + }
> +
Very small nit, but this pattern is a bit weird to read:
Would be better with:
if (inofftp != 0) {
if (!access_ok(VERIFY_WRITE, inofftp, sizeof(off_t))) {
return -TARGET_EFAULT;
}
inoff = tswap64(*(off_t *)g2h_untagged(inofftp));
inp = &inoff;
}
> + ret = get_errno(safe_copy_file_range(infd, inp, outfd, outp, len,
> + flags));
> +
> + if (!is_error(ret)) {
> + if (inofftp != 0) {
> + *(off_t *)g2h_untagged(inofftp) = tswap64(inoff);
> + }
> + if (outofftp != 0) {
> + *(off_t *)g2h_untagged(outofftp) = tswap64(outoff);
> + }
> + }
> + return ret;
> +}
> +
> +static inline abi_long do_freebsd___specialfd(int type, abi_ulong req,
> + size_t len)
> +{
> + abi_long ret;
> +
> + ret = -TARGET_EINVAL;
> + switch (type) {
> + case TARGET_SPECIALFD_EVENT: {
> + struct specialfd_eventfd evfd;
> + struct target_specialfd_eventfd *target_eventfd;
> +
> + if (!lock_user_struct(VERIFY_READ, target_eventfd, req, 1)) {
> + return -TARGET_EFAULT;
> + }
> +
> + evfd.initval = tswap32(target_eventfd->initval);
> + evfd.flags = tswap32(target_eventfd->flags);
> + ret = get_errno(syscall(SYS___specialfd, type, &evfd, sizeof(evfd)));
> + unlock_user_struct(target_eventfd, req, 0);
> + break;
> + }
> + }
> +
> + return ret;
> +}
> +
> +#endif /* FREEBSD_OS_FILE_H */
> diff --git a/bsd-user/freebsd/os-syscall.c b/bsd-user/freebsd/os-syscall.c
> index fc6273a780..8a25be4ac7 100644
> --- a/bsd-user/freebsd/os-syscall.c
> +++ b/bsd-user/freebsd/os-syscall.c
> @@ -45,6 +45,7 @@
> #include "os-stat.h"
> #include "os-proc.h"
> #include "os-signal.h"
> +#include "os-file.h"
> #include "os-misc.h"
>
> /* I/O */
> @@ -66,6 +67,12 @@ safe_syscall3(ssize_t, writev, int, fd, const struct iovec
> *, iov, int, iovcnt);
> safe_syscall4(ssize_t, pwritev, int, fd, const struct iovec *, iov, int,
> iovcnt,
> off_t, offset);
>
> +safe_syscall4(int, ppoll, struct pollfd *, fds, nfds_t, nfds,
> + const struct timespec *, restrict_timeout, const sigset_t *,
> + restrict_newsigmask);
> +safe_syscall6(ssize_t, copy_file_range, int, infd, off_t *, inoffp, int,
> outfd,
> + off_t *, outoffp, size_t, len, unsigned int, flags);
> +
> /* used in os-proc */
> safe_syscall4(pid_t, wait4, pid_t, wpid, int *, status, int, options,
> struct rusage *, rusage);
>
Reviewed-by: Pierrick Bouvier <[email protected]>