diff mbox series

[v2,07/37] bsd-user: Add os-file.h with pipe2, chflagsat, close_range, and more

Message ID 20260518-misc-2026q2-v2-7-6c16fe448301@bsdimp.com
State New
Headers show
Series bsd-user: Upstream most of the remaining system calls | expand

Commit Message

Warner Losh May 18, 2026, 9:27 p.m. UTC
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 <sson@FreeBSD.org>
Signed-off-by: Warner Losh <imp@bsdimp.com>
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(+)

Comments

Pierrick Bouvier May 22, 2026, 10:54 p.m. UTC | #1
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 <sson@FreeBSD.org>
> Signed-off-by: Warner Losh <imp@bsdimp.com>
> 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 <pierrick.bouvier@oss.qualcomm.com>
Warner Losh May 25, 2026, 9:32 p.m. UTC | #2
On Fri, May 22, 2026 at 4:54 PM Pierrick Bouvier <
pierrick.bouvier@oss.qualcomm.com> wrote:

> 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 <sson@FreeBSD.org>
> > Signed-off-by: Warner Losh <imp@bsdimp.com>
> > 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;
> }
>

Yes. That's a better pattern. I've redone this and outoffp and had claude
double check.
You can check in the next rev :)


> > +    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 <pierrick.bouvier@oss.qualcomm.com>
>

Thanks!

Warner
diff mbox series

Patch

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;
+    }
+
+    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);