aboutsummaryrefslogtreecommitdiff
path: root/bsd-user
diff options
context:
space:
mode:
authorWarner Losh <imp@bsdimp.com>2022-01-08 23:59:42 -0700
committerWarner Losh <imp@bsdimp.com>2022-01-30 17:13:44 -0700
commit394cf694273caf8ab8838588954d0fc2909ae2fa (patch)
tree89137d7c6d7b4c008cf00c009bcf54938eb4e482 /bsd-user
parentc885ae0e4ebf207c861bf651dcf9282677281c06 (diff)
bsd-user/signal.c: implement do_sigaction
Implement the meat of the sigaction(2) system call with do_sigaction and helper routiner block_signals (which is also used to implemement signal masking so it's global). Signed-off-by: Stacey Son <sson@FreeBSD.org> Signed-off-by: Kyle Evans <kevans@freebsd.org> Signed-off-by: Warner Losh <imp@bsdimp.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Diffstat (limited to 'bsd-user')
-rw-r--r--bsd-user/signal-common.h22
-rw-r--r--bsd-user/signal.c82
2 files changed, 104 insertions, 0 deletions
diff --git a/bsd-user/signal-common.h b/bsd-user/signal-common.h
index 786ec592d1..7ff8e8f2e4 100644
--- a/bsd-user/signal-common.h
+++ b/bsd-user/signal-common.h
@@ -9,7 +9,29 @@
#ifndef SIGNAL_COMMON_H
#define SIGNAL_COMMON_H
+/**
+ * block_signals: block all signals while handling this guest syscall
+ *
+ * Block all signals, and arrange that the signal mask is returned to
+ * its correct value for the guest before we resume execution of guest code.
+ * If this function returns non-zero, then the caller should immediately
+ * return -TARGET_ERESTARTSYS to the main loop, which will take the pending
+ * signal and restart execution of the syscall.
+ * If block_signals() returns zero, then the caller can continue with
+ * emulation of the system call knowing that no signals can be taken
+ * (and therefore that no race conditions will result).
+ * This should only be called once, because if it is called a second time
+ * it will always return non-zero. (Think of it like a mutex that can't
+ * be recursively locked.)
+ * Signals will be unblocked again by process_pending_signals().
+ *
+ * Return value: non-zero if there was a pending signal, zero if not.
+ */
+int block_signals(void); /* Returns non zero if signal pending */
+
long do_rt_sigreturn(CPUArchState *env);
+int do_sigaction(int sig, const struct target_sigaction *act,
+ struct target_sigaction *oact);
abi_long do_sigaltstack(abi_ulong uss_addr, abi_ulong uoss_addr, abi_ulong sp);
long do_sigreturn(CPUArchState *env, abi_ulong addr);
void force_sig_fault(int sig, int code, abi_ulong addr);
diff --git a/bsd-user/signal.c b/bsd-user/signal.c
index 150262a87e..5c94bd02e3 100644
--- a/bsd-user/signal.c
+++ b/bsd-user/signal.c
@@ -309,6 +309,25 @@ static void tswap_siginfo(target_siginfo_t *tinfo, const target_siginfo_t *info)
}
}
+int block_signals(void)
+{
+ TaskState *ts = (TaskState *)thread_cpu->opaque;
+ sigset_t set;
+
+ /*
+ * It's OK to block everything including SIGSEGV, because we won't run any
+ * further guest code before unblocking signals in
+ * process_pending_signals(). We depend on the FreeBSD behaivor here where
+ * this will only affect this thread's signal mask. We don't use
+ * pthread_sigmask which might seem more correct because that routine also
+ * does odd things with SIGCANCEL to implement pthread_cancel().
+ */
+ sigfillset(&set);
+ sigprocmask(SIG_SETMASK, &set, 0);
+
+ return qatomic_xchg(&ts->signal_pending, 1);
+}
+
/* Returns 1 if given signal should dump core if not handled. */
static int core_dump_signal(int sig)
{
@@ -554,6 +573,69 @@ static void host_signal_handler(int host_sig, siginfo_t *info, void *puc)
cpu_exit(thread_cpu);
}
+/* do_sigaction() return host values and errnos */
+int do_sigaction(int sig, const struct target_sigaction *act,
+ struct target_sigaction *oact)
+{
+ struct target_sigaction *k;
+ struct sigaction act1;
+ int host_sig;
+ int ret = 0;
+
+ if (sig < 1 || sig > TARGET_NSIG) {
+ return -TARGET_EINVAL;
+ }
+
+ if ((sig == TARGET_SIGKILL || sig == TARGET_SIGSTOP) &&
+ act != NULL && act->_sa_handler != TARGET_SIG_DFL) {
+ return -TARGET_EINVAL;
+ }
+
+ if (block_signals()) {
+ return -TARGET_ERESTART;
+ }
+
+ k = &sigact_table[sig - 1];
+ if (oact) {
+ oact->_sa_handler = tswapal(k->_sa_handler);
+ oact->sa_flags = tswap32(k->sa_flags);
+ oact->sa_mask = k->sa_mask;
+ }
+ if (act) {
+ k->_sa_handler = tswapal(act->_sa_handler);
+ k->sa_flags = tswap32(act->sa_flags);
+ k->sa_mask = act->sa_mask;
+
+ /* Update the host signal state. */
+ host_sig = target_to_host_signal(sig);
+ if (host_sig != SIGSEGV && host_sig != SIGBUS) {
+ memset(&act1, 0, sizeof(struct sigaction));
+ sigfillset(&act1.sa_mask);
+ act1.sa_flags = SA_SIGINFO;
+ if (k->sa_flags & TARGET_SA_RESTART) {
+ act1.sa_flags |= SA_RESTART;
+ }
+ /*
+ * Note: It is important to update the host kernel signal mask to
+ * avoid getting unexpected interrupted system calls.
+ */
+ if (k->_sa_handler == TARGET_SIG_IGN) {
+ act1.sa_sigaction = (void *)SIG_IGN;
+ } else if (k->_sa_handler == TARGET_SIG_DFL) {
+ if (fatal_signal(sig)) {
+ act1.sa_sigaction = host_signal_handler;
+ } else {
+ act1.sa_sigaction = (void *)SIG_DFL;
+ }
+ } else {
+ act1.sa_sigaction = host_signal_handler;
+ }
+ ret = sigaction(host_sig, &act1, NULL);
+ }
+ }
+ return ret;
+}
+
static inline abi_ulong get_sigframe(struct target_sigaction *ka,
CPUArchState *env, size_t frame_size)
{