From 5f2f6db896424bf3b581f83b685f48c04096eca4 Mon Sep 17 00:00:00 2001
From: Alexander Graf <agraf@suse.de>
Date: Thu, 1 Dec 2011 19:00:01 +0100
Subject: [PATCH] XXX work around SA_RESTART race with boehm-gc (ARM only)

[AF: CPUState -> CPUArchState, adapt to reindentation]
[BR: fix references to opaque]
---
 linux-user/main.c    | 25 +++++++++------
 linux-user/qemu.h    |  3 ++
 linux-user/signal.c  | 22 +++++++++++++
 linux-user/syscall.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 130 insertions(+), 10 deletions(-)

diff --git a/linux-user/main.c b/linux-user/main.c
index af924dc..c7423e6 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -814,15 +814,22 @@ void cpu_loop(CPUARMState *env)
                             break;
                         }
                     } else {
-                        env->regs[0] = do_syscall(env,
-                                                  n,
-                                                  env->regs[0],
-                                                  env->regs[1],
-                                                  env->regs[2],
-                                                  env->regs[3],
-                                                  env->regs[4],
-                                                  env->regs[5],
-                                                  0, 0);
+                        TaskState *ts = cs->opaque;
+                        target_ulong r;
+                        r = do_syscall(env, n, env->regs[0], env->regs[1],
+                                       env->regs[2], env->regs[3], env->regs[4],
+                                       env->regs[5], 0, 0);
+                        if ((r == -EINTR) && ts->signal_restart &&
+                            syscall_restartable(n)) {
+                            if (env->thumb) {
+                                env->regs[15] -= 2;
+                            } else {
+                                env->regs[15] -= 4;
+                            }
+                        } else {
+                            env->regs[0] = r;
+                        }
+                        ts->signal_restart = 0;
                     }
                 } else {
                     goto error;
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index 36d4a73..a2c4e35 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -134,6 +134,8 @@ typedef struct TaskState {
     struct sigqueue sigqueue_table[MAX_SIGQUEUE_SIZE]; /* siginfo queue */
     struct sigqueue *first_free; /* first free siginfo queue entry */
     int signal_pending; /* non zero if a signal may be pending */
+    int signal_in_syscall; /* non zero if we are in do_syscall() */
+    int signal_restart; /* non zero if we need to restart a syscall */
 } __attribute__((aligned(16))) TaskState;
 
 extern char *exec_path;
@@ -199,6 +201,7 @@ int get_osversion(void);
 void init_qemu_uname_release(void);
 void fork_start(void);
 void fork_end(int child);
+int syscall_restartable(int syscall_nr);
 
 /* Creates the initial guest address space in the host memory space using
  * the given host start address hint and size.  The guest_start parameter
diff --git a/linux-user/signal.c b/linux-user/signal.c
index 1bcf16f..cfaf501 100644
--- a/linux-user/signal.c
+++ b/linux-user/signal.c
@@ -25,6 +25,7 @@
 #include <assert.h>
 #include <sys/ucontext.h>
 #include <sys/resource.h>
+#include <sched.h>
 
 #include "qemu.h"
 #include "qemu-common.h"
@@ -571,6 +572,11 @@ int queue_signal(CPUArchState *env, int sig, target_siginfo_t *info)
         k->pending = 1;
         /* signal that a new signal is pending */
         ts->signal_pending = 1;
+        /* check if we have to restart the current syscall */
+        if ((sigact_table[sig - 1].sa_flags & SA_RESTART) &&
+            ts->signal_in_syscall) {
+            ts->signal_restart = 1;
+        }
         return 1; /* indicates that the signal was queued */
     }
 }
@@ -706,8 +712,24 @@ int do_sigaction(int sig, const struct target_sigaction *act,
         if (host_sig != SIGSEGV && host_sig != SIGBUS) {
             sigfillset(&act1.sa_mask);
             act1.sa_flags = SA_SIGINFO;
+#ifdef TARGET_ARM
+            /* Breaks boehm-gc, we have to do this manually */
+            /*
+             * Unfortunately our hacks only work as long as we don't do parallel
+             * signal delivery and futexes, so let's do a dirty hack here to
+             * pin our guest process to a single host CPU if we're using the
+             * boehm-gc.
+             */
+            if ((k->sa_flags & TARGET_SA_RESTART) && host_sig == SIGPWR) {
+                cpu_set_t mask;
+                CPU_ZERO(&mask);
+                CPU_SET(0, &mask);
+                sched_setaffinity(0, sizeof(mask), &mask);
+            }
+#else
             if (k->sa_flags & TARGET_SA_RESTART)
                 act1.sa_flags |= SA_RESTART;
+#endif
             /* NOTE: it is important to update the host kernel signal
                ignore state to avoid getting unexpected interrupted
                syscalls */
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 2d28bdb..e491bec 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -5259,6 +5259,87 @@ static int do_open(void *cpu_env, const char *pathname, int flags, mode_t mode)
     return get_errno(open(path(pathname), flags, mode));
 }
 
+int syscall_restartable(int syscall_nr)
+{
+    switch (syscall_nr) {
+#ifdef TARGET_NR_sigsuspend
+    case TARGET_NR_sigsuspend:
+#endif
+#ifdef TARGET_NR_pause
+    case TARGET_NR_pause:
+#endif
+#ifdef TARGET_NR_setsockopt
+    case TARGET_NR_setsockopt:
+#endif
+#ifdef TARGET_NR_accept
+    case TARGET_NR_accept:
+#endif
+#ifdef TARGET_NR_recv
+    case TARGET_NR_recv:
+#endif
+#ifdef TARGET_NR_recvfrom
+    case TARGET_NR_recvfrom:
+#endif
+#ifdef TARGET_NR_recvmsg
+    case TARGET_NR_recvmsg:
+#endif
+#ifdef TARGET_NR_socketcall
+    case TARGET_NR_socketcall:
+#endif
+#ifdef TARGET_NR_connect
+    case TARGET_NR_connect:
+#endif
+#ifdef TARGET_NR_send
+    case TARGET_NR_send:
+#endif
+#ifdef TARGET_NR_sendmsg
+    case TARGET_NR_sendmsg:
+#endif
+#ifdef TARGET_NR_sendto
+    case TARGET_NR_sendto:
+#endif
+#ifdef TARGET_NR_poll
+    case TARGET_NR_poll:
+#endif
+#ifdef TARGET_NR_ppoll
+    case TARGET_NR_ppoll:
+#endif
+#if defined(TARGET_NR_select)
+    case TARGET_NR_select:
+#endif
+#ifdef TARGET_NR_pselect6
+    case TARGET_NR_pselect6:
+#endif
+#ifdef TARGET_NR__newselect
+    case TARGET_NR__newselect:
+#endif
+#ifdef TARGET_NR_msgrcv
+    case TARGET_NR_msgrcv:
+#endif
+#ifdef TARGET_NR_msgsnd
+    case TARGET_NR_msgsnd:
+#endif
+#ifdef TARGET_NR_semop
+    case TARGET_NR_semop:
+#endif
+#ifdef TARGET_NR_ipc
+    case TARGET_NR_ipc:
+#endif
+#ifdef TARGET_NR_clock_nanosleep
+    case TARGET_NR_clock_nanosleep:
+#endif
+    case TARGET_NR_rt_sigsuspend:
+    case TARGET_NR_rt_sigtimedwait:
+    case TARGET_NR_nanosleep:
+    case TARGET_NR_close:
+        /* can not be restarted */
+        return 0;
+    }
+
+    /* every other syscall can be restarted */
+    return 1;
+}
+
 /* do_syscall() should always have a single exit point at the end so
    that actions, such as logging of syscall results, can be performed.
    All errnos that do_syscall() returns must be -TARGET_<errcode>. */
@@ -5272,6 +5353,12 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
     struct stat st;
     struct statfs stfs;
     void *p;
+    TaskState *ts = (TaskState*)cpu->opaque;
+
+    if (!ts->signal_restart) {
+        /* remember syscall info for restart */
+        ts->signal_in_syscall = 1;
+    }
 
 #ifdef DEBUG
     gemu_log("syscall %d", num);
@@ -8473,7 +8560,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
 	cmd = target_to_host_fcntl_cmd(arg2);
         if (cmd == -TARGET_EINVAL) {
             ret = cmd;
-            break;
+            goto fail;
         }
 
         switch(arg2) {
@@ -9411,6 +9498,7 @@ fail:
 #endif
     if(do_strace)
         print_syscall_ret(num, ret);
+    ts->signal_in_syscall = 0;
     return ret;
 efault:
     ret = -TARGET_EFAULT;
