| Message ID | 20260518-misc-2026q2-v2-15-6c16fe448301@bsdimp.com |
|---|---|
| State | New |
| Headers | show |
| Series | bsd-user: Upstream most of the remaining system calls | expand |
On 5/18/2026 2:27 PM, Warner Losh wrote: > Add target-to-host and host-to-target control message conversion > functions (t2h_freebsd_cmsg and h2t_freebsd_cmsg) for sendmsg/recvmsg > support, and add os-socket.c to the FreeBSD build. > > 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: Michal Meloun <mmel@FreeBSD.org> > Signed-off-by: Warner Losh <imp@bsdimp.com> > Assisted-by: Claude Opus 4.6 (1M context) > --- > bsd-user/freebsd/meson.build | 8 +- > bsd-user/freebsd/os-socket.c | 234 +++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 240 insertions(+), 2 deletions(-) > > diff --git a/bsd-user/freebsd/meson.build b/bsd-user/freebsd/meson.build > index 38f2debf7e..0fc779749d 100644 > --- a/bsd-user/freebsd/meson.build > +++ b/bsd-user/freebsd/meson.build > @@ -4,9 +4,13 @@ bsd_syscall_nr = custom_target('bsd-syscall-h', > command: [sh, meson.current_source_dir() / 'scripts/syscallhdr.sh', '@INPUT@', '@OUTPUT@', 'FREEBSD']) > > bsd_user_ss.add(files( > - 'os-stat.c', > + 'os-extattr.c', > 'os-proc.c', > + 'os-socket.c', > + 'os-stat.c', > 'os-sys.c', > - 'os-syscall.c'), > + 'os-syscall.c', > + 'os-thread.c', > + 'os-time.c'), > bsd_syscall_nr > ) Most of additions here seem to belong to previous patches. > diff --git a/bsd-user/freebsd/os-socket.c b/bsd-user/freebsd/os-socket.c > new file mode 100644 > index 0000000000..8eb728240d > --- /dev/null > +++ b/bsd-user/freebsd/os-socket.c > @@ -0,0 +1,234 @@ > +/* > + * FreeBSD socket related system call helpers > + * > + * Copyright (c) 2013 Stacey D. Son > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > +#include "qemu/osdep.h" > + > +#include <sys/types.h> > +#include <sys/socket.h> > +#include <netinet/in.h> > + > +#include "qemu.h" > +#include "qemu-os.h" > + > +abi_long t2h_freebsd_cmsg(struct msghdr *msgh, > + struct target_msghdr *target_msgh) > +{ > + struct cmsghdr *cmsg = CMSG_FIRSTHDR(msgh); > + socklen_t msg_controllen; > + abi_ulong target_cmsg_addr; > + struct target_cmsghdr *target_cmsg, *target_cmsg_start; > + socklen_t space = 0; > + > + msg_controllen = tswap32(target_msgh->msg_controllen); > + if (msg_controllen < sizeof(struct target_cmsghdr)) { > + goto the_end; > + } > + target_cmsg_addr = tswapal(target_msgh->msg_control); > + target_cmsg = lock_user(VERIFY_READ, target_cmsg_addr, msg_controllen, 1); > + target_cmsg_start = target_cmsg; > + if (!target_cmsg) { > + return -TARGET_EFAULT; > + } > + > + while (cmsg && target_cmsg) { > + void *data = CMSG_DATA(cmsg); > + void *target_data = TARGET_CMSG_DATA(target_cmsg); > + int len = (unsigned char *)(target_cmsg) + > + tswap32(target_cmsg->cmsg_len) - (unsigned char *)target_data; > + > + space += CMSG_SPACE(len); > + if (space > msgh->msg_controllen) { > + space -= CMSG_SPACE(len); > + /* > + * This is a QEMU bug, since we allocated the payload area ourselves > + * (unlike overflow in host-to-target conversion, which is just the > + * guest giving us a buffer that's too small). It can't happen for > + * the payload types we currently support; if it becomes an issue in > + * future we would need to improve our allocation strategy to > + * something more intelligent than "twice the size of the target > + * buffer we're reading from". > + */ > + gemu_log("Host cmsg overflow\n"); > + break; > + } > + > + if (tswap32(target_cmsg->cmsg_level) == TARGET_SOL_SOCKET) { > + cmsg->cmsg_level = SOL_SOCKET; > + } else { > + cmsg->cmsg_level = tswap32(target_cmsg->cmsg_level); > + } > + cmsg->cmsg_type = tswap32(target_cmsg->cmsg_type); > + cmsg->cmsg_len = CMSG_LEN(len); > + > + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { > + int *fd = (int *)data; > + int *target_fd = (int *)target_data; > + int i, numfds = len / sizeof(int); > + > + for (i = 0; i < numfds; i++) { > + __get_user(fd[i], target_fd + i); > + } > + } else if ((cmsg->cmsg_level == SOL_SOCKET) && > + (cmsg->cmsg_type == SCM_TIMESTAMP) && > + (len == sizeof(struct target_freebsd_timeval))) { > + /* copy struct timeval to host */ > + struct timeval *tv = (struct timeval *)data; > + struct target_freebsd_timeval *target_tv = > + (struct target_freebsd_timeval *)target_data; > + __get_user(tv->tv_sec, &target_tv->tv_sec); > + __get_user(tv->tv_usec, &target_tv->tv_usec); > + } else { > + gemu_log("Unsupported target ancillary data: %d/%d\n", > + cmsg->cmsg_level, cmsg->cmsg_type); > + memcpy(data, target_data, len); > + } > + > + cmsg = CMSG_NXTHDR(msgh, cmsg); > + target_cmsg = TARGET_CMSG_NXTHDR(target_msgh, target_cmsg, > + target_cmsg_start); > + } > + unlock_user(target_cmsg_start, target_cmsg_addr, 0); > +the_end: > + msgh->msg_controllen = space; > + return 0; > +} > + > +abi_long h2t_freebsd_cmsg(struct target_msghdr *target_msgh, > + struct msghdr *msgh) > +{ > + struct cmsghdr *cmsg = CMSG_FIRSTHDR(msgh); > + socklen_t msg_controllen; > + abi_ulong target_cmsg_addr; > + struct target_cmsghdr *target_cmsg, *target_cmsg_start; > + socklen_t space = 0; > + > + msg_controllen = tswap32(target_msgh->msg_controllen); > + if (msg_controllen < sizeof(struct target_cmsghdr)) { > + goto the_end; > + } > + target_cmsg_addr = tswapal(target_msgh->msg_control); > + target_cmsg = lock_user(VERIFY_WRITE, target_cmsg_addr, msg_controllen, 0); > + target_cmsg_start = target_cmsg; > + if (!target_cmsg) { > + return -TARGET_EFAULT; > + } > + > + while (cmsg && target_cmsg) { > + void *data = CMSG_DATA(cmsg); > + void *target_data = TARGET_CMSG_DATA(target_cmsg); > + int len = (unsigned char *)(cmsg) + cmsg->cmsg_len - > + (unsigned char *)data; > + > + int tgt_len, tgt_space; > + > + /* > + * We never copy a half-header but may copy half-data; this is Linux's > + * behaviour in put_cmsg(). Note that truncation here is a guest problem > + * (which we report to the guest via the CTRUNC bit), unlike truncation > + * in target_to_host_cmsg, which is a QEMU bug. > + */ > + if (msg_controllen < sizeof(struct target_cmsghdr)) { > + target_msgh->msg_flags |= tswap32(MSG_CTRUNC); > + break; > + } > + > + if (cmsg->cmsg_level == SOL_SOCKET) { > + target_cmsg->cmsg_level = tswap32(TARGET_SOL_SOCKET); > + } else { > + target_cmsg->cmsg_level = tswap32(cmsg->cmsg_level); > + } > + target_cmsg->cmsg_type = tswap32(cmsg->cmsg_type); > + > + /* > + * Payload types which need a different size of payload on the target > + * must adjust tgt_len here. > + */ > + tgt_len = len; > + switch (cmsg->cmsg_level) { > + case SOL_SOCKET: > + switch (cmsg->cmsg_type) { > + case SCM_TIMESTAMP: > + tgt_len = sizeof(struct target_freebsd_timeval); > + break; > + default: > + break; > + } > + break; > + default: > + break; > + } > + > + if (msg_controllen < TARGET_CMSG_LEN(tgt_len)) { > + target_msgh->msg_flags |= tswap32(MSG_CTRUNC); > + tgt_len = msg_controllen - sizeof(struct target_cmsghdr); > + } > + > + /* > + * We must now copy-and-convert len bytes of payload into tgt_len bytes > + * of destination space. Bear in mind that in both source and > + * destination we may be dealing with a truncated value! > + */ > + switch (cmsg->cmsg_level) { > + case SOL_SOCKET: > + switch (cmsg->cmsg_type) { > + case SCM_RIGHTS: > + { > + int *fd = (int *)data; > + int *target_fd = (int *)target_data; > + int i, numfds = tgt_len / sizeof(int); > + > + for (i = 0; i < numfds; i++) { > + __put_user(fd[i], target_fd + i); > + } > + break; > + } > + case SCM_TIMESTAMP: > + { > + struct timeval *tv = (struct timeval *)data; > + struct target_freebsd_timeval *target_tv = > + (struct target_freebsd_timeval *)target_data; > + > + if (len != sizeof(struct timeval) || > + tgt_len != sizeof(struct target_freebsd_timeval)) { > + goto unimplemented; > + } > + > + /* copy struct timeval to target */ > + __put_user(tv->tv_sec, &target_tv->tv_sec); > + __put_user(tv->tv_usec, &target_tv->tv_usec); > + break; > + } > + default: > + goto unimplemented; > + } > + break; > + default: > + unimplemented: > + gemu_log("Unsupported host ancillary data: %d/%d\n", > + cmsg->cmsg_level, cmsg->cmsg_type); > + memcpy(target_data, data, MIN(len, tgt_len)); > + if (tgt_len > len) { > + memset(target_data + len, 0, tgt_len - len); > + } > + } > + > + target_cmsg->cmsg_len = tswap32(TARGET_CMSG_LEN(tgt_len)); > + tgt_space = TARGET_CMSG_SPACE(tgt_len); > + if (msg_controllen < tgt_space) { > + tgt_space = msg_controllen; > + } > + msg_controllen -= tgt_space; > + space += tgt_space; > + cmsg = CMSG_NXTHDR(msgh, cmsg); > + target_cmsg = TARGET_CMSG_NXTHDR(target_msgh, target_cmsg, > + target_cmsg_start); > + } > + unlock_user(target_cmsg_start, target_cmsg_addr, space); > +the_end: > + target_msgh->msg_controllen = tswap32(space); > + return 0; > +} >
On Fri, May 22, 2026 at 5:31 PM Pierrick Bouvier < pierrick.bouvier@oss.qualcomm.com> wrote: > On 5/18/2026 2:27 PM, Warner Losh wrote: > > Add target-to-host and host-to-target control message conversion > > functions (t2h_freebsd_cmsg and h2t_freebsd_cmsg) for sendmsg/recvmsg > > support, and add os-socket.c to the FreeBSD build. > > > > 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: Michal Meloun <mmel@FreeBSD.org> > > Signed-off-by: Warner Losh <imp@bsdimp.com> > > Assisted-by: Claude Opus 4.6 (1M context) > > --- > > bsd-user/freebsd/meson.build | 8 +- > > bsd-user/freebsd/os-socket.c | 234 > +++++++++++++++++++++++++++++++++++++++++++ > > 2 files changed, 240 insertions(+), 2 deletions(-) > > > > diff --git a/bsd-user/freebsd/meson.build b/bsd-user/freebsd/meson.build > > index 38f2debf7e..0fc779749d 100644 > > --- a/bsd-user/freebsd/meson.build > > +++ b/bsd-user/freebsd/meson.build > > @@ -4,9 +4,13 @@ bsd_syscall_nr = custom_target('bsd-syscall-h', > > command: [sh, meson.current_source_dir() / 'scripts/syscallhdr.sh', > '@INPUT@', '@OUTPUT@', 'FREEBSD']) > > > > bsd_user_ss.add(files( > > - 'os-stat.c', > > + 'os-extattr.c', > > 'os-proc.c', > > + 'os-socket.c', > > + 'os-stat.c', > > 'os-sys.c', > > - 'os-syscall.c'), > > + 'os-syscall.c', > > + 'os-thread.c', > > + 'os-time.c'), > > bsd_syscall_nr > > ) > > Most of additions here seem to belong to previous patches. > I'll try to move these backwards in the series. I may have to reorder some things as well, since this is a known good compile point. It's worth trying. Warner > > diff --git a/bsd-user/freebsd/os-socket.c b/bsd-user/freebsd/os-socket.c > > new file mode 100644 > > index 0000000000..8eb728240d > > --- /dev/null > > +++ b/bsd-user/freebsd/os-socket.c > > @@ -0,0 +1,234 @@ > > +/* > > + * FreeBSD socket related system call helpers > > + * > > + * Copyright (c) 2013 Stacey D. Son > > + * > > + * SPDX-License-Identifier: GPL-2.0-or-later > > + */ > > +#include "qemu/osdep.h" > > + > > +#include <sys/types.h> > > +#include <sys/socket.h> > > +#include <netinet/in.h> > > + > > +#include "qemu.h" > > +#include "qemu-os.h" > > + > > +abi_long t2h_freebsd_cmsg(struct msghdr *msgh, > > + struct target_msghdr *target_msgh) > > +{ > > + struct cmsghdr *cmsg = CMSG_FIRSTHDR(msgh); > > + socklen_t msg_controllen; > > + abi_ulong target_cmsg_addr; > > + struct target_cmsghdr *target_cmsg, *target_cmsg_start; > > + socklen_t space = 0; > > + > > + msg_controllen = tswap32(target_msgh->msg_controllen); > > + if (msg_controllen < sizeof(struct target_cmsghdr)) { > > + goto the_end; > > + } > > + target_cmsg_addr = tswapal(target_msgh->msg_control); > > + target_cmsg = lock_user(VERIFY_READ, target_cmsg_addr, > msg_controllen, 1); > > + target_cmsg_start = target_cmsg; > > + if (!target_cmsg) { > > + return -TARGET_EFAULT; > > + } > > + > > + while (cmsg && target_cmsg) { > > + void *data = CMSG_DATA(cmsg); > > + void *target_data = TARGET_CMSG_DATA(target_cmsg); > > + int len = (unsigned char *)(target_cmsg) + > > + tswap32(target_cmsg->cmsg_len) - (unsigned char > *)target_data; > > + > > + space += CMSG_SPACE(len); > > + if (space > msgh->msg_controllen) { > > + space -= CMSG_SPACE(len); > > + /* > > + * This is a QEMU bug, since we allocated the payload area > ourselves > > + * (unlike overflow in host-to-target conversion, which is > just the > > + * guest giving us a buffer that's too small). It can't > happen for > > + * the payload types we currently support; if it becomes an > issue in > > + * future we would need to improve our allocation strategy > to > > + * something more intelligent than "twice the size of the > target > > + * buffer we're reading from". > > + */ > > + gemu_log("Host cmsg overflow\n"); > > + break; > > + } > > + > > + if (tswap32(target_cmsg->cmsg_level) == TARGET_SOL_SOCKET) { > > + cmsg->cmsg_level = SOL_SOCKET; > > + } else { > > + cmsg->cmsg_level = tswap32(target_cmsg->cmsg_level); > > + } > > + cmsg->cmsg_type = tswap32(target_cmsg->cmsg_type); > > + cmsg->cmsg_len = CMSG_LEN(len); > > + > > + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == > SCM_RIGHTS) { > > + int *fd = (int *)data; > > + int *target_fd = (int *)target_data; > > + int i, numfds = len / sizeof(int); > > + > > + for (i = 0; i < numfds; i++) { > > + __get_user(fd[i], target_fd + i); > > + } > > + } else if ((cmsg->cmsg_level == SOL_SOCKET) && > > + (cmsg->cmsg_type == SCM_TIMESTAMP) && > > + (len == sizeof(struct target_freebsd_timeval))) { > > + /* copy struct timeval to host */ > > + struct timeval *tv = (struct timeval *)data; > > + struct target_freebsd_timeval *target_tv = > > + (struct target_freebsd_timeval *)target_data; > > + __get_user(tv->tv_sec, &target_tv->tv_sec); > > + __get_user(tv->tv_usec, &target_tv->tv_usec); > > + } else { > > + gemu_log("Unsupported target ancillary data: %d/%d\n", > > + cmsg->cmsg_level, cmsg->cmsg_type); > > + memcpy(data, target_data, len); > > + } > > + > > + cmsg = CMSG_NXTHDR(msgh, cmsg); > > + target_cmsg = TARGET_CMSG_NXTHDR(target_msgh, target_cmsg, > > + target_cmsg_start); > > + } > > + unlock_user(target_cmsg_start, target_cmsg_addr, 0); > > +the_end: > > + msgh->msg_controllen = space; > > + return 0; > > +} > > + > > +abi_long h2t_freebsd_cmsg(struct target_msghdr *target_msgh, > > + struct msghdr *msgh) > > +{ > > + struct cmsghdr *cmsg = CMSG_FIRSTHDR(msgh); > > + socklen_t msg_controllen; > > + abi_ulong target_cmsg_addr; > > + struct target_cmsghdr *target_cmsg, *target_cmsg_start; > > + socklen_t space = 0; > > + > > + msg_controllen = tswap32(target_msgh->msg_controllen); > > + if (msg_controllen < sizeof(struct target_cmsghdr)) { > > + goto the_end; > > + } > > + target_cmsg_addr = tswapal(target_msgh->msg_control); > > + target_cmsg = lock_user(VERIFY_WRITE, target_cmsg_addr, > msg_controllen, 0); > > + target_cmsg_start = target_cmsg; > > + if (!target_cmsg) { > > + return -TARGET_EFAULT; > > + } > > + > > + while (cmsg && target_cmsg) { > > + void *data = CMSG_DATA(cmsg); > > + void *target_data = TARGET_CMSG_DATA(target_cmsg); > > + int len = (unsigned char *)(cmsg) + cmsg->cmsg_len - > > + (unsigned char *)data; > > + > > + int tgt_len, tgt_space; > > + > > + /* > > + * We never copy a half-header but may copy half-data; this is > Linux's > > + * behaviour in put_cmsg(). Note that truncation here is a > guest problem > > + * (which we report to the guest via the CTRUNC bit), unlike > truncation > > + * in target_to_host_cmsg, which is a QEMU bug. > > + */ > > + if (msg_controllen < sizeof(struct target_cmsghdr)) { > > + target_msgh->msg_flags |= tswap32(MSG_CTRUNC); > > + break; > > + } > > + > > + if (cmsg->cmsg_level == SOL_SOCKET) { > > + target_cmsg->cmsg_level = tswap32(TARGET_SOL_SOCKET); > > + } else { > > + target_cmsg->cmsg_level = tswap32(cmsg->cmsg_level); > > + } > > + target_cmsg->cmsg_type = tswap32(cmsg->cmsg_type); > > + > > + /* > > + * Payload types which need a different size of payload on the > target > > + * must adjust tgt_len here. > > + */ > > + tgt_len = len; > > + switch (cmsg->cmsg_level) { > > + case SOL_SOCKET: > > + switch (cmsg->cmsg_type) { > > + case SCM_TIMESTAMP: > > + tgt_len = sizeof(struct target_freebsd_timeval); > > + break; > > + default: > > + break; > > + } > > + break; > > + default: > > + break; > > + } > > + > > + if (msg_controllen < TARGET_CMSG_LEN(tgt_len)) { > > + target_msgh->msg_flags |= tswap32(MSG_CTRUNC); > > + tgt_len = msg_controllen - sizeof(struct target_cmsghdr); > > + } > > + > > + /* > > + * We must now copy-and-convert len bytes of payload into > tgt_len bytes > > + * of destination space. Bear in mind that in both source and > > + * destination we may be dealing with a truncated value! > > + */ > > + switch (cmsg->cmsg_level) { > > + case SOL_SOCKET: > > + switch (cmsg->cmsg_type) { > > + case SCM_RIGHTS: > > + { > > + int *fd = (int *)data; > > + int *target_fd = (int *)target_data; > > + int i, numfds = tgt_len / sizeof(int); > > + > > + for (i = 0; i < numfds; i++) { > > + __put_user(fd[i], target_fd + i); > > + } > > + break; > > + } > > + case SCM_TIMESTAMP: > > + { > > + struct timeval *tv = (struct timeval *)data; > > + struct target_freebsd_timeval *target_tv = > > + (struct target_freebsd_timeval *)target_data; > > + > > + if (len != sizeof(struct timeval) || > > + tgt_len != sizeof(struct target_freebsd_timeval)) { > > + goto unimplemented; > > + } > > + > > + /* copy struct timeval to target */ > > + __put_user(tv->tv_sec, &target_tv->tv_sec); > > + __put_user(tv->tv_usec, &target_tv->tv_usec); > > + break; > > + } > > + default: > > + goto unimplemented; > > + } > > + break; > > + default: > > + unimplemented: > > + gemu_log("Unsupported host ancillary data: %d/%d\n", > > + cmsg->cmsg_level, cmsg->cmsg_type); > > + memcpy(target_data, data, MIN(len, tgt_len)); > > + if (tgt_len > len) { > > + memset(target_data + len, 0, tgt_len - len); > > + } > > + } > > + > > + target_cmsg->cmsg_len = tswap32(TARGET_CMSG_LEN(tgt_len)); > > + tgt_space = TARGET_CMSG_SPACE(tgt_len); > > + if (msg_controllen < tgt_space) { > > + tgt_space = msg_controllen; > > + } > > + msg_controllen -= tgt_space; > > + space += tgt_space; > > + cmsg = CMSG_NXTHDR(msgh, cmsg); > > + target_cmsg = TARGET_CMSG_NXTHDR(target_msgh, target_cmsg, > > + target_cmsg_start); > > + } > > + unlock_user(target_cmsg_start, target_cmsg_addr, space); > > +the_end: > > + target_msgh->msg_controllen = tswap32(space); > > + return 0; > > +} > > > >
diff --git a/bsd-user/freebsd/meson.build b/bsd-user/freebsd/meson.build index 38f2debf7e..0fc779749d 100644 --- a/bsd-user/freebsd/meson.build +++ b/bsd-user/freebsd/meson.build @@ -4,9 +4,13 @@ bsd_syscall_nr = custom_target('bsd-syscall-h', command: [sh, meson.current_source_dir() / 'scripts/syscallhdr.sh', '@INPUT@', '@OUTPUT@', 'FREEBSD']) bsd_user_ss.add(files( - 'os-stat.c', + 'os-extattr.c', 'os-proc.c', + 'os-socket.c', + 'os-stat.c', 'os-sys.c', - 'os-syscall.c'), + 'os-syscall.c', + 'os-thread.c', + 'os-time.c'), bsd_syscall_nr ) diff --git a/bsd-user/freebsd/os-socket.c b/bsd-user/freebsd/os-socket.c new file mode 100644 index 0000000000..8eb728240d --- /dev/null +++ b/bsd-user/freebsd/os-socket.c @@ -0,0 +1,234 @@ +/* + * FreeBSD socket related system call helpers + * + * Copyright (c) 2013 Stacey D. Son + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "qemu/osdep.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include "qemu.h" +#include "qemu-os.h" + +abi_long t2h_freebsd_cmsg(struct msghdr *msgh, + struct target_msghdr *target_msgh) +{ + struct cmsghdr *cmsg = CMSG_FIRSTHDR(msgh); + socklen_t msg_controllen; + abi_ulong target_cmsg_addr; + struct target_cmsghdr *target_cmsg, *target_cmsg_start; + socklen_t space = 0; + + msg_controllen = tswap32(target_msgh->msg_controllen); + if (msg_controllen < sizeof(struct target_cmsghdr)) { + goto the_end; + } + target_cmsg_addr = tswapal(target_msgh->msg_control); + target_cmsg = lock_user(VERIFY_READ, target_cmsg_addr, msg_controllen, 1); + target_cmsg_start = target_cmsg; + if (!target_cmsg) { + return -TARGET_EFAULT; + } + + while (cmsg && target_cmsg) { + void *data = CMSG_DATA(cmsg); + void *target_data = TARGET_CMSG_DATA(target_cmsg); + int len = (unsigned char *)(target_cmsg) + + tswap32(target_cmsg->cmsg_len) - (unsigned char *)target_data; + + space += CMSG_SPACE(len); + if (space > msgh->msg_controllen) { + space -= CMSG_SPACE(len); + /* + * This is a QEMU bug, since we allocated the payload area ourselves + * (unlike overflow in host-to-target conversion, which is just the + * guest giving us a buffer that's too small). It can't happen for + * the payload types we currently support; if it becomes an issue in + * future we would need to improve our allocation strategy to + * something more intelligent than "twice the size of the target + * buffer we're reading from". + */ + gemu_log("Host cmsg overflow\n"); + break; + } + + if (tswap32(target_cmsg->cmsg_level) == TARGET_SOL_SOCKET) { + cmsg->cmsg_level = SOL_SOCKET; + } else { + cmsg->cmsg_level = tswap32(target_cmsg->cmsg_level); + } + cmsg->cmsg_type = tswap32(target_cmsg->cmsg_type); + cmsg->cmsg_len = CMSG_LEN(len); + + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + int *fd = (int *)data; + int *target_fd = (int *)target_data; + int i, numfds = len / sizeof(int); + + for (i = 0; i < numfds; i++) { + __get_user(fd[i], target_fd + i); + } + } else if ((cmsg->cmsg_level == SOL_SOCKET) && + (cmsg->cmsg_type == SCM_TIMESTAMP) && + (len == sizeof(struct target_freebsd_timeval))) { + /* copy struct timeval to host */ + struct timeval *tv = (struct timeval *)data; + struct target_freebsd_timeval *target_tv = + (struct target_freebsd_timeval *)target_data; + __get_user(tv->tv_sec, &target_tv->tv_sec); + __get_user(tv->tv_usec, &target_tv->tv_usec); + } else { + gemu_log("Unsupported target ancillary data: %d/%d\n", + cmsg->cmsg_level, cmsg->cmsg_type); + memcpy(data, target_data, len); + } + + cmsg = CMSG_NXTHDR(msgh, cmsg); + target_cmsg = TARGET_CMSG_NXTHDR(target_msgh, target_cmsg, + target_cmsg_start); + } + unlock_user(target_cmsg_start, target_cmsg_addr, 0); +the_end: + msgh->msg_controllen = space; + return 0; +} + +abi_long h2t_freebsd_cmsg(struct target_msghdr *target_msgh, + struct msghdr *msgh) +{ + struct cmsghdr *cmsg = CMSG_FIRSTHDR(msgh); + socklen_t msg_controllen; + abi_ulong target_cmsg_addr; + struct target_cmsghdr *target_cmsg, *target_cmsg_start; + socklen_t space = 0; + + msg_controllen = tswap32(target_msgh->msg_controllen); + if (msg_controllen < sizeof(struct target_cmsghdr)) { + goto the_end; + } + target_cmsg_addr = tswapal(target_msgh->msg_control); + target_cmsg = lock_user(VERIFY_WRITE, target_cmsg_addr, msg_controllen, 0); + target_cmsg_start = target_cmsg; + if (!target_cmsg) { + return -TARGET_EFAULT; + } + + while (cmsg && target_cmsg) { + void *data = CMSG_DATA(cmsg); + void *target_data = TARGET_CMSG_DATA(target_cmsg); + int len = (unsigned char *)(cmsg) + cmsg->cmsg_len - + (unsigned char *)data; + + int tgt_len, tgt_space; + + /* + * We never copy a half-header but may copy half-data; this is Linux's + * behaviour in put_cmsg(). Note that truncation here is a guest problem + * (which we report to the guest via the CTRUNC bit), unlike truncation + * in target_to_host_cmsg, which is a QEMU bug. + */ + if (msg_controllen < sizeof(struct target_cmsghdr)) { + target_msgh->msg_flags |= tswap32(MSG_CTRUNC); + break; + } + + if (cmsg->cmsg_level == SOL_SOCKET) { + target_cmsg->cmsg_level = tswap32(TARGET_SOL_SOCKET); + } else { + target_cmsg->cmsg_level = tswap32(cmsg->cmsg_level); + } + target_cmsg->cmsg_type = tswap32(cmsg->cmsg_type); + + /* + * Payload types which need a different size of payload on the target + * must adjust tgt_len here. + */ + tgt_len = len; + switch (cmsg->cmsg_level) { + case SOL_SOCKET: + switch (cmsg->cmsg_type) { + case SCM_TIMESTAMP: + tgt_len = sizeof(struct target_freebsd_timeval); + break; + default: + break; + } + break; + default: + break; + } + + if (msg_controllen < TARGET_CMSG_LEN(tgt_len)) { + target_msgh->msg_flags |= tswap32(MSG_CTRUNC); + tgt_len = msg_controllen - sizeof(struct target_cmsghdr); + } + + /* + * We must now copy-and-convert len bytes of payload into tgt_len bytes + * of destination space. Bear in mind that in both source and + * destination we may be dealing with a truncated value! + */ + switch (cmsg->cmsg_level) { + case SOL_SOCKET: + switch (cmsg->cmsg_type) { + case SCM_RIGHTS: + { + int *fd = (int *)data; + int *target_fd = (int *)target_data; + int i, numfds = tgt_len / sizeof(int); + + for (i = 0; i < numfds; i++) { + __put_user(fd[i], target_fd + i); + } + break; + } + case SCM_TIMESTAMP: + { + struct timeval *tv = (struct timeval *)data; + struct target_freebsd_timeval *target_tv = + (struct target_freebsd_timeval *)target_data; + + if (len != sizeof(struct timeval) || + tgt_len != sizeof(struct target_freebsd_timeval)) { + goto unimplemented; + } + + /* copy struct timeval to target */ + __put_user(tv->tv_sec, &target_tv->tv_sec); + __put_user(tv->tv_usec, &target_tv->tv_usec); + break; + } + default: + goto unimplemented; + } + break; + default: + unimplemented: + gemu_log("Unsupported host ancillary data: %d/%d\n", + cmsg->cmsg_level, cmsg->cmsg_type); + memcpy(target_data, data, MIN(len, tgt_len)); + if (tgt_len > len) { + memset(target_data + len, 0, tgt_len - len); + } + } + + target_cmsg->cmsg_len = tswap32(TARGET_CMSG_LEN(tgt_len)); + tgt_space = TARGET_CMSG_SPACE(tgt_len); + if (msg_controllen < tgt_space) { + tgt_space = msg_controllen; + } + msg_controllen -= tgt_space; + space += tgt_space; + cmsg = CMSG_NXTHDR(msgh, cmsg); + target_cmsg = TARGET_CMSG_NXTHDR(target_msgh, target_cmsg, + target_cmsg_start); + } + unlock_user(target_cmsg_start, target_cmsg_addr, space); +the_end: + target_msgh->msg_controllen = tswap32(space); + return 0; +}