diff mbox series

[v2,19/37] bsd-user: Add select, pselect, and ppoll to os-time.h

Message ID 20260518-misc-2026q2-v2-19-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 I/O multiplexing system call shims: select, pselect, and ppoll
with proper fd_set, timespec/timeval, and signal mask conversion.

Signed-off-by: Stacey Son <sson@FreeBSD.org>
Signed-off-by: Mikaël Urankar <mikael.urankar@gmail.com>
Signed-off-by: Kyle Evans <kevans@FreeBSD.org>
Signed-off-by: Warner Losh <imp@bsdimp.com>
Assisted-by: Claude Opus 4.6 (1M context)
---
 bsd-user/freebsd/os-time.h | 202 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 202 insertions(+)

Comments

Pierrick Bouvier May 22, 2026, 11:46 p.m. UTC | #1
On 5/18/2026 2:27 PM, Warner Losh wrote:
> Add I/O multiplexing system call shims: select, pselect, and ppoll
> with proper fd_set, timespec/timeval, and signal mask conversion.
> 
> Signed-off-by: Stacey Son <sson@FreeBSD.org>
> Signed-off-by: Mikaël Urankar <mikael.urankar@gmail.com>
> Signed-off-by: Kyle Evans <kevans@FreeBSD.org>
> Signed-off-by: Warner Losh <imp@bsdimp.com>
> Assisted-by: Claude Opus 4.6 (1M context)
> ---
>  bsd-user/freebsd/os-time.h | 202 +++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 202 insertions(+)
> 
> diff --git a/bsd-user/freebsd/os-time.h b/bsd-user/freebsd/os-time.h
> index 05fa043442..12c5ba02e8 100644
> --- a/bsd-user/freebsd/os-time.h
> +++ b/bsd-user/freebsd/os-time.h
> @@ -427,5 +427,207 @@ static inline abi_long do_freebsd_ktimer_gettime(abi_long arg1, abi_long arg2)
>      return ret;
>  }
>  
> +/* select(2) */
> +static inline abi_long do_freebsd_select(CPUArchState *env, int n,
> +        abi_ulong rfd_addr, abi_ulong wfd_addr, abi_ulong efd_addr,
> +        abi_ulong target_tv_addr)
> +{
> +    fd_set rfds, wfds, efds;
> +    fd_set *rfds_ptr, *wfds_ptr, *efds_ptr;
> +    struct timeval tv, *tvp;
> +    abi_long ret, error;
> +
> +    ret = copy_from_user_fdset_ptr(&rfds, &rfds_ptr, rfd_addr, n);
> +    if (ret != 0) {
> +        return ret;
> +    }
> +    ret = copy_from_user_fdset_ptr(&wfds, &wfds_ptr, wfd_addr, n);
> +    if (ret != 0) {
> +        return ret;
> +    }
> +    ret = copy_from_user_fdset_ptr(&efds, &efds_ptr, efd_addr, n);
> +    if (ret != 0) {
> +        return ret;
> +    }
> +

We can factor this with a macro

> +    if (target_tv_addr != 0) {
> +        if (t2h_freebsd_timeval(&tv, target_tv_addr)) {
> +            return -TARGET_EFAULT;
> +        }
> +        tvp = &tv;
> +    } else {
> +        tvp = NULL;
> +    }
> +
> +    ret = get_errno(safe_select(n, rfds_ptr, wfds_ptr, efds_ptr, tvp));
> +
> +    if (!is_error(ret)) {

We can simplify depth here by using an early return.
if (is_error(ret)) {
  return ret;
}

> +        if (rfd_addr != 0) {
> +            error = copy_to_user_fdset(rfd_addr, &rfds, n);
> +            if (error != 0) {
> +                return error;
> +            }
> +        }
> +        if (wfd_addr != 0) {
> +            error = copy_to_user_fdset(wfd_addr, &wfds, n);
> +            if (error != 0) {
> +                return error;
> +            }
> +        }
> +        if (efd_addr != 0) {
> +            error = copy_to_user_fdset(efd_addr, &efds, n);
> +            if (error != 0) {
> +                return error;
> +            }
> +        }

Same, reuse the macro defined above.

> +        if (target_tv_addr != 0) {
> +            error = h2t_freebsd_timeval(&tv, target_tv_addr);
> +            if (is_error(error)) {
> +                return error;
> +            }
> +        }
> +    }
> +    return ret;
> +}
> +
> +/* pselect(2) */
> +static inline abi_long do_freebsd_pselect(CPUArchState *env, int n,
> +        abi_ulong rfd_addr, abi_ulong wfd_addr, abi_ulong efd_addr,
> +        abi_ulong ts_addr, abi_ulong set_addr)
> +{
> +    CPUState *cpu = env_cpu(env);
> +    TaskState *tstate = cpu->opaque;
> +    fd_set rfds, wfds, efds;
> +    fd_set *rfds_ptr, *wfds_ptr, *efds_ptr;
> +    sigset_t *set_ptr;
> +    struct timespec ts, *ts_ptr;
> +    void *p;
> +    abi_long ret, error;
> +
> +    ret = copy_from_user_fdset_ptr(&rfds, &rfds_ptr, rfd_addr, n);
> +    if (is_error(ret)) {
> +        return ret;
> +    }
> +    ret = copy_from_user_fdset_ptr(&wfds, &wfds_ptr, wfd_addr, n);
> +    if (is_error(ret)) {
> +        return ret;
> +    }
> +    ret = copy_from_user_fdset_ptr(&efds, &efds_ptr, efd_addr, n);
> +    if (is_error(ret)) {
> +        return ret;
> +    }
> +

Same than above.

> +    /* Unlike select(), pselect() uses struct timespec instead of timeval */
> +    if (ts_addr) {
> +        if (t2h_freebsd_timespec(&ts, ts_addr)) {
> +            return -TARGET_EFAULT;
> +        }
> +        ts_ptr = &ts;
> +    } else {
> +        ts_ptr = NULL;
> +    }
> +
> +    if (set_addr != 0) {
> +        p = lock_user(VERIFY_READ, set_addr, sizeof(target_sigset_t), 1);
> +        if (p == NULL) {
> +            return -TARGET_EFAULT;
> +        }
> +        target_to_host_sigset(&tstate->sigsuspend_mask, p);
> +        unlock_user(p, set_addr, 0);
> +        set_ptr = &tstate->sigsuspend_mask;
> +    } else {
> +        set_ptr = NULL;
> +    }
> +
> +    ret = get_errno(safe_pselect(n, rfds_ptr, wfds_ptr, efds_ptr, ts_ptr,
> +        set_ptr));
> +    if (ret != -TARGET_ERESTART)  {
> +        tstate->in_sigsuspend = true;
> +    }
> +    if (!is_error(ret)) {
> +        if (rfd_addr != 0) {
> +            error = copy_to_user_fdset(rfd_addr, &rfds, n);
> +            if (is_error(error)) {
> +                return error;
> +            }
> +        }
> +        if (wfd_addr != 0) {
> +            error = copy_to_user_fdset(wfd_addr, &wfds, n);
> +            if (is_error(error)) {
> +                return error;
> +            }
> +        }
> +        if (efd_addr != 0) {
> +            error = copy_to_user_fdset(efd_addr, &efds, n);
> +            if (is_error(error)) {
> +                return error;
> +            }
> +        }
> +    }
> +    return ret;
> +}
> +
> +/* ppoll(2) */
> +static inline abi_long do_freebsd_ppoll(CPUArchState *env, abi_long arg1,
> +        abi_long arg2, abi_ulong arg3, abi_ulong arg4)
> +{
> +    CPUState *cpu = env_cpu(env);
> +    TaskState *tstate = cpu->opaque;
> +    abi_long ret;
> +    nfds_t i, nfds = arg2;
> +    struct pollfd *pfd;
> +    struct target_pollfd *target_pfd;
> +    struct timespec ts, *ts_ptr;
> +    sigset_t *set_ptr;
> +    void *p;
> +
> +    target_pfd = lock_user(VERIFY_WRITE, arg1,
> +                           sizeof(struct target_pollfd) * nfds, 1);
> +    if (!target_pfd) {
> +        return -TARGET_EFAULT;
> +    }
> +    pfd = alloca(sizeof(struct pollfd) * nfds);
> +    for (i = 0; i < nfds; i++) {
> +        pfd[i].fd = tswap32(target_pfd[i].fd);
> +        pfd[i].events = tswap16(target_pfd[i].events);
> +    }
> +
> +    /* Unlike poll(), ppoll() uses struct timespec. */
> +    if (arg3) {
> +        if (t2h_freebsd_timespec(&ts, arg3)) {
> +            return -TARGET_EFAULT;
> +        }
> +        ts_ptr = &ts;
> +    } else {
> +        ts_ptr = NULL;
> +    }
> +
> +    if (arg4 != 0) {
> +        p = lock_user(VERIFY_READ, arg4, sizeof(target_sigset_t), 1);
> +        if (p == NULL) {
> +            return -TARGET_EFAULT;
> +        }
> +        target_to_host_sigset(&tstate->sigsuspend_mask, p);
> +        unlock_user(p, arg4, 0);
> +        set_ptr = &tstate->sigsuspend_mask;
> +    } else {
> +        set_ptr = NULL;
> +    }
> +
> +    ret = get_errno(ppoll(pfd, nfds, ts_ptr, set_ptr));
> +    if (ret != -TARGET_ERESTART) {
> +        tstate->in_sigsuspend = true;
> +    }
> +    if (!is_error(ret)) {
> +        for (i = 0; i < nfds; i++) {
> +            target_pfd[i].revents = tswap16(pfd[i].revents);
> +        }
> +    }
> +    unlock_user(target_pfd, arg1, sizeof(struct target_pollfd) * nfds);
> +
> +    return ret;
> +}
> +
> +/* kqueue(2) */
>  
>  #endif /* FREEBSD_OS_TIME_H */
>
diff mbox series

Patch

diff --git a/bsd-user/freebsd/os-time.h b/bsd-user/freebsd/os-time.h
index 05fa043442..12c5ba02e8 100644
--- a/bsd-user/freebsd/os-time.h
+++ b/bsd-user/freebsd/os-time.h
@@ -427,5 +427,207 @@  static inline abi_long do_freebsd_ktimer_gettime(abi_long arg1, abi_long arg2)
     return ret;
 }
 
+/* select(2) */
+static inline abi_long do_freebsd_select(CPUArchState *env, int n,
+        abi_ulong rfd_addr, abi_ulong wfd_addr, abi_ulong efd_addr,
+        abi_ulong target_tv_addr)
+{
+    fd_set rfds, wfds, efds;
+    fd_set *rfds_ptr, *wfds_ptr, *efds_ptr;
+    struct timeval tv, *tvp;
+    abi_long ret, error;
+
+    ret = copy_from_user_fdset_ptr(&rfds, &rfds_ptr, rfd_addr, n);
+    if (ret != 0) {
+        return ret;
+    }
+    ret = copy_from_user_fdset_ptr(&wfds, &wfds_ptr, wfd_addr, n);
+    if (ret != 0) {
+        return ret;
+    }
+    ret = copy_from_user_fdset_ptr(&efds, &efds_ptr, efd_addr, n);
+    if (ret != 0) {
+        return ret;
+    }
+
+    if (target_tv_addr != 0) {
+        if (t2h_freebsd_timeval(&tv, target_tv_addr)) {
+            return -TARGET_EFAULT;
+        }
+        tvp = &tv;
+    } else {
+        tvp = NULL;
+    }
+
+    ret = get_errno(safe_select(n, rfds_ptr, wfds_ptr, efds_ptr, tvp));
+
+    if (!is_error(ret)) {
+        if (rfd_addr != 0) {
+            error = copy_to_user_fdset(rfd_addr, &rfds, n);
+            if (error != 0) {
+                return error;
+            }
+        }
+        if (wfd_addr != 0) {
+            error = copy_to_user_fdset(wfd_addr, &wfds, n);
+            if (error != 0) {
+                return error;
+            }
+        }
+        if (efd_addr != 0) {
+            error = copy_to_user_fdset(efd_addr, &efds, n);
+            if (error != 0) {
+                return error;
+            }
+        }
+        if (target_tv_addr != 0) {
+            error = h2t_freebsd_timeval(&tv, target_tv_addr);
+            if (is_error(error)) {
+                return error;
+            }
+        }
+    }
+    return ret;
+}
+
+/* pselect(2) */
+static inline abi_long do_freebsd_pselect(CPUArchState *env, int n,
+        abi_ulong rfd_addr, abi_ulong wfd_addr, abi_ulong efd_addr,
+        abi_ulong ts_addr, abi_ulong set_addr)
+{
+    CPUState *cpu = env_cpu(env);
+    TaskState *tstate = cpu->opaque;
+    fd_set rfds, wfds, efds;
+    fd_set *rfds_ptr, *wfds_ptr, *efds_ptr;
+    sigset_t *set_ptr;
+    struct timespec ts, *ts_ptr;
+    void *p;
+    abi_long ret, error;
+
+    ret = copy_from_user_fdset_ptr(&rfds, &rfds_ptr, rfd_addr, n);
+    if (is_error(ret)) {
+        return ret;
+    }
+    ret = copy_from_user_fdset_ptr(&wfds, &wfds_ptr, wfd_addr, n);
+    if (is_error(ret)) {
+        return ret;
+    }
+    ret = copy_from_user_fdset_ptr(&efds, &efds_ptr, efd_addr, n);
+    if (is_error(ret)) {
+        return ret;
+    }
+
+    /* Unlike select(), pselect() uses struct timespec instead of timeval */
+    if (ts_addr) {
+        if (t2h_freebsd_timespec(&ts, ts_addr)) {
+            return -TARGET_EFAULT;
+        }
+        ts_ptr = &ts;
+    } else {
+        ts_ptr = NULL;
+    }
+
+    if (set_addr != 0) {
+        p = lock_user(VERIFY_READ, set_addr, sizeof(target_sigset_t), 1);
+        if (p == NULL) {
+            return -TARGET_EFAULT;
+        }
+        target_to_host_sigset(&tstate->sigsuspend_mask, p);
+        unlock_user(p, set_addr, 0);
+        set_ptr = &tstate->sigsuspend_mask;
+    } else {
+        set_ptr = NULL;
+    }
+
+    ret = get_errno(safe_pselect(n, rfds_ptr, wfds_ptr, efds_ptr, ts_ptr,
+        set_ptr));
+    if (ret != -TARGET_ERESTART)  {
+        tstate->in_sigsuspend = true;
+    }
+    if (!is_error(ret)) {
+        if (rfd_addr != 0) {
+            error = copy_to_user_fdset(rfd_addr, &rfds, n);
+            if (is_error(error)) {
+                return error;
+            }
+        }
+        if (wfd_addr != 0) {
+            error = copy_to_user_fdset(wfd_addr, &wfds, n);
+            if (is_error(error)) {
+                return error;
+            }
+        }
+        if (efd_addr != 0) {
+            error = copy_to_user_fdset(efd_addr, &efds, n);
+            if (is_error(error)) {
+                return error;
+            }
+        }
+    }
+    return ret;
+}
+
+/* ppoll(2) */
+static inline abi_long do_freebsd_ppoll(CPUArchState *env, abi_long arg1,
+        abi_long arg2, abi_ulong arg3, abi_ulong arg4)
+{
+    CPUState *cpu = env_cpu(env);
+    TaskState *tstate = cpu->opaque;
+    abi_long ret;
+    nfds_t i, nfds = arg2;
+    struct pollfd *pfd;
+    struct target_pollfd *target_pfd;
+    struct timespec ts, *ts_ptr;
+    sigset_t *set_ptr;
+    void *p;
+
+    target_pfd = lock_user(VERIFY_WRITE, arg1,
+                           sizeof(struct target_pollfd) * nfds, 1);
+    if (!target_pfd) {
+        return -TARGET_EFAULT;
+    }
+    pfd = alloca(sizeof(struct pollfd) * nfds);
+    for (i = 0; i < nfds; i++) {
+        pfd[i].fd = tswap32(target_pfd[i].fd);
+        pfd[i].events = tswap16(target_pfd[i].events);
+    }
+
+    /* Unlike poll(), ppoll() uses struct timespec. */
+    if (arg3) {
+        if (t2h_freebsd_timespec(&ts, arg3)) {
+            return -TARGET_EFAULT;
+        }
+        ts_ptr = &ts;
+    } else {
+        ts_ptr = NULL;
+    }
+
+    if (arg4 != 0) {
+        p = lock_user(VERIFY_READ, arg4, sizeof(target_sigset_t), 1);
+        if (p == NULL) {
+            return -TARGET_EFAULT;
+        }
+        target_to_host_sigset(&tstate->sigsuspend_mask, p);
+        unlock_user(p, arg4, 0);
+        set_ptr = &tstate->sigsuspend_mask;
+    } else {
+        set_ptr = NULL;
+    }
+
+    ret = get_errno(ppoll(pfd, nfds, ts_ptr, set_ptr));
+    if (ret != -TARGET_ERESTART) {
+        tstate->in_sigsuspend = true;
+    }
+    if (!is_error(ret)) {
+        for (i = 0; i < nfds; i++) {
+            target_pfd[i].revents = tswap16(pfd[i].revents);
+        }
+    }
+    unlock_user(target_pfd, arg1, sizeof(struct target_pollfd) * nfds);
+
+    return ret;
+}
+
+/* kqueue(2) */
 
 #endif /* FREEBSD_OS_TIME_H */