In this case, we attach a specific "host_to_target()" translator to the fd
returned by signalfd() to be able to byte-swap the signalfd_siginfo
structure provided by read().
This patch allows to execute the example program given by
man signalfd(2):
#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int
main(int argc, char *argv[])
{
sigset_t mask;
int sfd;
struct signalfd_siginfo fdsi;
ssize_t s;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGQUIT);
/* Block signals so that they aren't handled
according to their default dispositions */
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
handle_error("sigprocmask");
sfd = signalfd(-1, &mask, 0);
if (sfd == -1)
handle_error("signalfd");
for (;;) {
s = read(sfd, &fdsi, sizeof(struct signalfd_siginfo));
if (s != sizeof(struct signalfd_siginfo))
handle_error("read");
if (fdsi.ssi_signo == SIGINT) {
printf("Got SIGINT\n");
} else if (fdsi.ssi_signo == SIGQUIT) {
printf("Got SIGQUIT\n");
exit(EXIT_SUCCESS);
} else {
printf("Read unexpected signal\n");
}
}
}
$ ./signalfd_demo
^CGot SIGINT
^CGot SIGINT
^\Got SIGQUIT
Signed-off-by: Laurent Vivier <address@hidden>
---
v4: rebase on top of Riku's linux-user-for-upstream
add fd_trans_unregister() for open_by_handle_at.
v3: Combine struct definition and typedef
replace a = x ? y : z by if () { }
clear extra memory from g_realloc()
remove useless management of O_CLOEXEC
swap ssi_addr_lsb
fix host_flags checking
v2: Update commit message with example from man page
Use CamelCase for struct
Allocate entries in the fd array on demand
Clear fd entries on open(), close(),...
Swap ssi_errno
Try to manage dup() and O_CLOEXEC cases
Fix signalfd() parameters
merge signalfd() and signalfd4()
Change the API to only provide functions to process
data stream.
TargetFdTrans has an unused field, target_to_host, for symmetry
and which could used later with netlink() functions.
linux-user/syscall.c | 167
+++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 167 insertions(+)
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 98b5766..f4121af 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -60,6 +60,7 @@ int __clone2(int (*fn)(void *), void *child_stack_base,
#include <sys/statfs.h>
#include <utime.h>
#include <sys/sysinfo.h>
+#include <sys/signalfd.h>
//#include <sys/user.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
@@ -294,6 +295,54 @@ static bitmask_transtbl fcntl_flags_tbl[] = {
{ 0, 0, 0, 0 }
};
+typedef abi_long (*TargetFdFunc)(void *, size_t);
+typedef struct TargetFdTrans {
+ TargetFdFunc host_to_target;
+ TargetFdFunc target_to_host;
+} TargetFdTrans;
+
+static TargetFdTrans **target_fd_trans;
+
+static unsigned int target_fd_max;
+
+static TargetFdFunc fd_trans_host_to_target(int fd)
+{
+ if (fd < target_fd_max && target_fd_trans[fd]) {
+ return target_fd_trans[fd]->host_to_target;
+ }
+ return NULL;
+}
+
+static void fd_trans_register(int fd, TargetFdTrans *trans)
+{
+ unsigned int oldmax;
+
+ if (fd >= target_fd_max) {
+ oldmax = target_fd_max;
+ target_fd_max = ((fd >> 6) + 1) << 6; /* by slice of 64 entries */
+ target_fd_trans = g_realloc(target_fd_trans,
+ target_fd_max * sizeof(TargetFdTrans));
+ memset((void *)(target_fd_trans + oldmax), 0,
+ (target_fd_max - oldmax) * sizeof(TargetFdTrans *));
+ }
+ target_fd_trans[fd] = trans;
+}
+
+static void fd_trans_unregister(int fd)
+{
+ if (fd >= 0 && fd < target_fd_max) {
+ target_fd_trans[fd] = NULL;
+ }
+}
+
+static void fd_trans_dup(int oldfd, int newfd)
+{
+ fd_trans_unregister(newfd);
+ if (oldfd < target_fd_max && target_fd_trans[oldfd]) {
+ fd_trans_register(newfd, target_fd_trans[oldfd]);
+ }
+}
+
static int sys_getcwd1(char *buf, size_t size)
{
if (getcwd(buf, size) == NULL) {
@@ -5341,6 +5390,92 @@ static abi_long
do_open_by_handle_at(abi_long mount_fd, abi_long handle,
}
#endif
+#if defined(TARGET_NR_signalfd) || defined(TARGET_NR_signalfd4)
+
+/* signalfd siginfo conversion */
+
+static void
+host_to_target_signalfd_siginfo(struct signalfd_siginfo *tinfo,
+ const struct signalfd_siginfo *info)
+{
+ int sig = host_to_target_signal(info->ssi_signo);
+
+ /* linux/signalfd.h defines a ssi_addr_lsb
+ * not defined in sys/signalfd.h but used by some kernels
+ */
+
+#ifdef BUS_MCEERR_AO
+ if (tinfo->ssi_signo == SIGBUS &&
+ (tinfo->ssi_code == BUS_MCEERR_AR ||
+ tinfo->ssi_code == BUS_MCEERR_AO)) {
+ uint16_t *ssi_addr_lsb = (uint16_t *)(&info->ssi_addr + 1);
+ uint16_t *tssi_addr_lsb = (uint16_t *)(&tinfo->ssi_addr + 1);
+ *tssi_addr_lsb = tswap16(*ssi_addr_lsb);
+ }
+#endif
+
+ tinfo->ssi_signo = tswap32(sig);
+ tinfo->ssi_errno = tswap32(tinfo->ssi_errno);
+ tinfo->ssi_code = tswap32(info->ssi_code);
+ tinfo->ssi_pid = tswap32(info->ssi_pid);
+ tinfo->ssi_uid = tswap32(info->ssi_uid);
+ tinfo->ssi_fd = tswap32(info->ssi_fd);
+ tinfo->ssi_tid = tswap32(info->ssi_tid);
+ tinfo->ssi_band = tswap32(info->ssi_band);
+ tinfo->ssi_overrun = tswap32(info->ssi_overrun);
+ tinfo->ssi_trapno = tswap32(info->ssi_trapno);
+ tinfo->ssi_status = tswap32(info->ssi_status);
+ tinfo->ssi_int = tswap32(info->ssi_int);
+ tinfo->ssi_ptr = tswap64(info->ssi_ptr);
+ tinfo->ssi_utime = tswap64(info->ssi_utime);
+ tinfo->ssi_stime = tswap64(info->ssi_stime);
+ tinfo->ssi_addr = tswap64(info->ssi_addr);
+}
+
+static abi_long host_to_target_signalfd(void *buf, size_t len)
+{
+ int i;
+
+ for (i = 0; i < len; i += sizeof(struct signalfd_siginfo)) {
+ host_to_target_signalfd_siginfo(buf + i, buf + i);
+ }
+
+ return len;
+}
+
+static TargetFdTrans target_signalfd_trans = {
+ .host_to_target = host_to_target_signalfd,
+};
+
+static abi_long do_signalfd4(int fd, abi_long mask, int flags)
+{
+ int host_flags;
+ target_sigset_t *target_mask;
+ sigset_t host_mask;
+ abi_long ret;
+
+ if (flags & ~(TARGET_O_NONBLOCK | TARGET_O_CLOEXEC)) {
+ return -TARGET_EINVAL;
+ }
+ if (!lock_user_struct(VERIFY_READ, target_mask, mask, 1)) {
+ return -TARGET_EFAULT;
+ }
+
+ target_to_host_sigset(&host_mask, target_mask);
+
+ host_flags = target_to_host_bitmask(flags, fcntl_flags_tbl);
+
+ ret = get_errno(signalfd(fd, &host_mask, host_flags));
+ if (ret >= 0) {
+ fd_trans_register(ret, &target_signalfd_trans);
+ }
+
+ unlock_user_struct(target_mask, mask, 0);
+
+ return ret;
+}
+#endif
+
/* Map host to target signal numbers for the wait family of syscalls.
Assume all other status bits are the same. */
int host_to_target_waitstatus(int status)
@@ -5725,6 +5860,10 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
if (!(p = lock_user(VERIFY_WRITE, arg2, arg3, 0)))
goto efault;
ret = get_errno(read(arg1, p, arg3));
+ if (ret >= 0 &&
+ fd_trans_host_to_target(arg1)) {
+ ret = fd_trans_host_to_target(arg1)(p, ret);
+ }
unlock_user(p, arg2, ret);
}
break;
@@ -5741,6 +5880,7 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
ret = get_errno(do_openat(cpu_env, AT_FDCWD, p,
target_to_host_bitmask(arg2,
fcntl_flags_tbl),
arg3));
+ fd_trans_unregister(ret);
unlock_user(p, arg1, 0);
break;
#endif
@@ -5750,6 +5890,7 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
ret = get_errno(do_openat(cpu_env, arg1, p,
target_to_host_bitmask(arg3,
fcntl_flags_tbl),
arg4));
+ fd_trans_unregister(ret);
unlock_user(p, arg2, 0);
break;
#if defined(TARGET_NR_name_to_handle_at) && defined(CONFIG_OPEN_BY_HANDLE)
@@ -5760,9 +5901,11 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
#if defined(TARGET_NR_open_by_handle_at) && defined(CONFIG_OPEN_BY_HANDLE)
case TARGET_NR_open_by_handle_at:
ret = do_open_by_handle_at(arg1, arg2, arg3);
+ fd_trans_unregister(ret);
break;
#endif
case TARGET_NR_close:
+ fd_trans_unregister(arg1);
ret = get_errno(close(arg1));
break;
case TARGET_NR_brk:
@@ -5804,6 +5947,7 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
if (!(p = lock_user_string(arg1)))
goto efault;
ret = get_errno(creat(p, arg2));
+ fd_trans_unregister(ret);
unlock_user(p, arg1, 0);
break;
#endif
@@ -6251,6 +6395,9 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
#endif
case TARGET_NR_dup:
ret = get_errno(dup(arg1));
+ if (ret >= 0) {
+ fd_trans_dup(arg1, ret);
+ }
break;
#ifdef TARGET_NR_pipe
case TARGET_NR_pipe:
@@ -6348,11 +6495,17 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
#ifdef TARGET_NR_dup2
case TARGET_NR_dup2:
ret = get_errno(dup2(arg1, arg2));
+ if (ret >= 0) {
+ fd_trans_dup(arg1, arg2);
+ }
break;
#endif
#if defined(CONFIG_DUP3) && defined(TARGET_NR_dup3)
case TARGET_NR_dup3:
ret = get_errno(dup3(arg1, arg2, arg3));
+ if (ret >= 0) {
+ fd_trans_dup(arg1, arg2);
+ }
break;
#endif
#ifdef TARGET_NR_getppid /* not on alpha */
@@ -7348,6 +7501,7 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
#ifdef TARGET_NR_socket
case TARGET_NR_socket:
ret = do_socket(arg1, arg2, arg3);
+ fd_trans_unregister(ret);
break;
#endif
#ifdef TARGET_NR_socketpair
@@ -9601,6 +9755,7 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
#if defined(TARGET_NR_eventfd)
case TARGET_NR_eventfd:
ret = get_errno(eventfd(arg1, 0));
+ fd_trans_unregister(ret);
break;
#endif
#if defined(TARGET_NR_eventfd2)
@@ -9614,6 +9769,7 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
host_flags |= O_CLOEXEC;
}
ret = get_errno(eventfd(arg1, host_flags));
+ fd_trans_unregister(ret);
break;
}
#endif
@@ -9656,6 +9812,16 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
break;
#endif
#endif
+#if defined(TARGET_NR_signalfd4)
+ case TARGET_NR_signalfd4:
+ ret = do_signalfd4(arg1, arg2, arg4);
+ break;
+#endif
+#if defined(TARGET_NR_signalfd)
+ case TARGET_NR_signalfd:
+ ret = do_signalfd4(arg1, arg2, 0);
+ break;
+#endif
#if defined(CONFIG_EPOLL)
#if defined(TARGET_NR_epoll_create)
case TARGET_NR_epoll_create:
@@ -9927,6 +10093,7 @@ abi_long do_syscall(void *cpu_env, int
num, abi_long arg1,
timer_t htimer = g_posix_timers[timerid];
ret = get_errno(timer_getoverrun(htimer));
}
+ fd_trans_unregister(ret);
break;
}
#endif