$NetBSD$ --- src/arch/netbsd/async/signal.c.orig 2017-11-02 06:34:33.815443408 +0000 +++ src/arch/netbsd/async/signal.c @@ -0,0 +1,1210 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "emu.h" +#ifdef __linux__ +#include "sys_vm86.h" +#endif +#include "bios.h" +#include "mouse.h" +#include "video.h" +#include "vgaemu.h" +#include "vgatext.h" +#include "render.h" +#include "timers.h" +#include "int.h" +#include "lowmem.h" +#include "coopth.h" +#include "dpmi.h" +#include "pic.h" +#include "ipx.h" +#include "pktdrvr.h" +#include "iodev.h" +#include "serial.h" +#include "debug.h" +#include "mhpdbg.h" +#include "utilities.h" +#include "userhook.h" +#include "ringbuf.h" +#include "dosemu_config.h" +#include "sound.h" +#include "cpu-emu.h" +#include "sig.h" + +#define SIGALTSTACK_WA_DEFAULT 1 +#if SIGALTSTACK_WA_DEFAULT + #ifdef DISABLE_SYSTEM_WA + #ifdef SS_AUTODISARM + #define SIGALTSTACK_WA 0 + #else + #ifdef WARN_UNDISABLED_WA + #warning Not disabling SIGALTSTACK_WA, update your kernel + #endif + #define SIGALTSTACK_WA 1 + #endif + #else + /* work-around sigaltstack badness - disable when kernel is fixed */ + #define SIGALTSTACK_WA 1 + #endif + #if defined(WARN_OUTDATED_WA) && defined(SS_AUTODISARM) + #warning SIGALTSTACK_WA is outdated + #endif +#else + #define SIGALTSTACK_WA 0 +#endif +#if SIGALTSTACK_WA +#include "mcontext.h" +#include "mapping.h" +#endif +/* SS_AUTODISARM is a dosemu-specific sigaltstack extension supported + * by some kernels */ +#ifndef SS_AUTODISARM +#define SS_AUTODISARM (1U << 31) /* disable sas during sighandling */ +#endif + +#ifdef __x86_64__ + #define SIGRETURN_WA_DEFAULT 1 +#else + #define SIGRETURN_WA_DEFAULT 0 +#endif +#if SIGRETURN_WA_DEFAULT + #ifdef DISABLE_SYSTEM_WA + #ifdef UC_SIGCONTEXT_SS + #define SIGRETURN_WA 0 + #else + #ifdef WARN_UNDISABLED_WA + #warning Not disabling SIGRETURN_WA, update your kernel + #endif + #define SIGRETURN_WA 1 + #endif + #else + /* work-around sigreturn badness - disable when kernel is fixed */ + #define SIGRETURN_WA 1 + #endif + #if defined(WARN_OUTDATED_WA) && defined(UC_SIGCONTEXT_SS) + #warning SIGRETURN_WA is outdated + #endif +#else + #define SIGRETURN_WA 0 +#endif + +/* Variables for keeping track of signals */ +#define MAX_SIG_QUEUE_SIZE 50 +#define MAX_SIG_DATA_SIZE 128 +static u_short SIGNAL_head=0; u_short SIGNAL_tail=0; +struct SIGNAL_queue { + void (*signal_handler)(void *); + char arg[MAX_SIG_DATA_SIZE]; + size_t arg_size; + const char *name; +}; +static struct SIGNAL_queue signal_queue[MAX_SIG_QUEUE_SIZE]; + +#define MAX_SIGCHLD_HANDLERS 10 +struct sigchld_hndl { + pid_t pid; + void (*handler)(void); + int enabled; +}; +static struct sigchld_hndl chld_hndl[MAX_SIGCHLD_HANDLERS]; +static int chd_hndl_num; + +#define MAX_SIGALRM_HANDLERS 50 +struct sigalrm_hndl { + void (*handler)(void); +}; +static struct sigalrm_hndl alrm_hndl[MAX_SIGALRM_HANDLERS]; +static int alrm_hndl_num; + +static sigset_t q_mask; +static sigset_t nonfatal_q_mask; +static sigset_t fatal_q_mask; +static void *cstack; +#if SIGALTSTACK_WA +static void *backup_stack; +static int need_sas_wa; +#endif +#if SIGRETURN_WA +static int need_sr_wa; +#endif +static int block_all_sigs; + +static int sh_tid; +static int in_handle_signals; +static void handle_signals_force_enter(int tid, int sl_state); +static void handle_signals_force_leave(int tid); +static void async_awake(void *arg); +static int event_fd; +static struct rng_s cbks; +#define MAX_CBKS 1000 +static pthread_mutex_t cbk_mtx = PTHREAD_MUTEX_INITIALIZER; + +struct eflags_fs_gs eflags_fs_gs; + +static void (*sighandlers[NSIG])(struct sigcontext *, siginfo_t *); +static void (*qsighandlers[NSIG])(int sig, siginfo_t *si, void *uc); + +static void sigalrm(struct sigcontext *, siginfo_t *); +static void sigio(struct sigcontext *, siginfo_t *); +static void sigasync(int sig, siginfo_t *si, void *uc); +static void leavedos_sig(int sig); + +static void _newsetqsig(int sig, void (*fun)(int sig, siginfo_t *si, void *uc)) +{ + if (qsighandlers[sig]) + return; + /* collect this mask so that all async (fatal and non-fatal) + * signals can be blocked by threads */ + sigaddset(&q_mask, sig); + qsighandlers[sig] = fun; +} + +static void newsetqsig(int sig, void (*fun)(int sig, siginfo_t *si, void *uc)) +{ +#if SIGRETURN_WA + sigaddset(&fatal_q_mask, sig); +#endif + _newsetqsig(sig, fun); +} + +static void qsig_init(void) +{ + struct sigaction sa; + int i; + + sa.sa_flags = SA_RESTART | SA_ONSTACK | SA_SIGINFO; + if (block_all_sigs) + { + /* initially block all async signals. */ + sa.sa_mask = q_mask; + } + else + { + /* block all non-fatal async signals */ + sa.sa_mask = nonfatal_q_mask; + } + for (i = 0; i < NSIG; i++) { + if (qsighandlers[i]) { + sa.sa_sigaction = qsighandlers[i]; + sigaction(i, &sa, NULL); + } + } +} + +/* registers non-emergency async signals */ +void registersig(int sig, void (*fun)(struct sigcontext *, siginfo_t *)) +{ + /* first need to collect the mask, then register all handlers + * because the same mask of non-emergency async signals + * is used for every handler */ + sigaddset(&nonfatal_q_mask, sig); + _newsetqsig(sig, sigasync); + sighandlers[sig] = fun; +} + +static void newsetsig(int sig, void (*fun)(int sig, siginfo_t *si, void *uc)) +{ + struct sigaction sa; + + sa.sa_flags = SA_RESTART | SA_ONSTACK | SA_SIGINFO; + if (kernel_version_code >= KERNEL_VERSION(2, 6, 14)) + sa.sa_flags |= SA_NODEFER; + if (block_all_sigs) + { + /* initially block all async signals. */ + sa.sa_mask = q_mask; + } + else + { + /* block all non-fatal async signals */ + sa.sa_mask = nonfatal_q_mask; + } + sa.sa_sigaction = fun; + sigaction(sig, &sa, NULL); +} + +/* init_handler puts the handler in a sane state that glibc + expects. That means restoring fs and gs for vm86 (necessary for + 2.4 kernels) and fs, gs and eflags for DPMI. */ +SIG_PROTO_PFX +static void __init_handler(struct sigcontext *scp, int async) +{ +#ifdef __x86_64__ + unsigned short __ss; +#endif + /* + * FIRST thing to do in signal handlers - to avoid being trapped into int0x11 + * forever, we must restore the eflags. + */ + loadflags(eflags_fs_gs.eflags); + +#ifdef __x86_64__ + /* ds,es, and ss are ignored in 64-bit mode and not present or + saved in the sigcontext, so we need to do it ourselves + (using the 3 high words of the trapno field). + fs and gs are set to 0 in the sigcontext, so we also need + to save those ourselves */ + _ds = getsegment(ds); + _es = getsegment(es); + /* some kernels save and switch ss, some do not... The simplest + * thing is to assume that if the ss is from GDT, then it is already + * saved. */ + __ss = getsegment(ss); + if (DPMIValidSelector(__ss)) + _ss = __ss; + _fs = getsegment(fs); + _gs = getsegment(gs); + if (_cs == 0) { + if (config.dpmi && config.cpuemu < 4) { + fprintf(stderr, "Cannot run DPMI code natively "); + if (kernel_version_code < KERNEL_VERSION(2, 6, 15)) + fprintf(stderr, "because your Linux kernel is older than version 2.6.15.\n"); + else + fprintf(stderr, "for unknown reasons.\nPlease contact linux-msdos@vger.kernel.org.\n"); + fprintf(stderr, "Set $_cpu_emu=\"full\" or \"fullsim\" to avoid this message.\n"); + } + config.cpu_vm = CPUVM_EMU; + config.cpuemu = 4; + _cs = getsegment(cs); + } +#endif + + if (in_vm86) { +#ifdef __i386__ +#ifdef X86_EMULATOR + if (config.cpu_vm != CPUVM_EMU) +#endif + { + if (getsegment(fs) != eflags_fs_gs.fs) + loadregister(fs, eflags_fs_gs.fs); + if (getsegment(gs) != eflags_fs_gs.gs) + loadregister(gs, eflags_fs_gs.gs); + } +#endif + return; + } + +#if SIGRETURN_WA + if (need_sr_wa && !DPMIValidSelector(_cs)) + dpmi_iret_unwind(scp); +#endif + +#if 0 + /* for async signals need to restore fs/gs even if dosemu code + * was interrupted, because it can be interrupted in a switching + * routine when fs or gs are already switched but cs is not */ + if (!DPMIValidSelector(_cs) && !async) + return; +#else + /* as DIRECT_DPMI_SWITCH support is now removed, the above comment + * applies only to DPMI_iret, which is now unwound. + * We don't need to restore segregs for async signals any more. */ + if (!DPMIValidSelector(_cs)) + return; +#endif + + /* restore %fs and %gs for compatibility with NPTL. */ + if (getsegment(fs) != eflags_fs_gs.fs) + loadregister(fs, eflags_fs_gs.fs); + if (getsegment(gs) != eflags_fs_gs.gs) + loadregister(gs, eflags_fs_gs.gs); +#ifdef __x86_64__ + loadregister(ds, eflags_fs_gs.ds); + loadregister(es, eflags_fs_gs.es); + /* kernel has the following rule: non-zero selector means 32bit base + * in GDT. Zero selector means 64bit base, set via msr. + * So if we set selector to 0, need to use also prctl(ARCH_SET_xS). + * Also, if the bases are not used they are 0 so no need to restore, + * which saves a syscall */ + if (!eflags_fs_gs.fs && eflags_fs_gs.fsbase) + dosemu_arch_prctl(ARCH_SET_FS, eflags_fs_gs.fsbase); + if (!eflags_fs_gs.gs && eflags_fs_gs.gsbase) + dosemu_arch_prctl(ARCH_SET_GS, eflags_fs_gs.gsbase); +#endif +} + +SIG_PROTO_PFX +void init_handler(struct sigcontext *scp, int async) +{ + /* Async signals are initially blocked. + * If we don't block them, nested sighandler will clobber SS + * before we manage to save it. + * Even if the nested sighandler tries hard, it can't properly + * restore SS, at least until the proper sigreturn() support is in. + * For kernels that have the proper SS support, only nonfatal + * async signals are initially blocked. They need to be blocked + * because of sas wa and because they should not interrupt + * deinit_handler() after it changed %fs. In this case, however, + * we can block them later at the right places, but this will + * cost a syscall per every signal. + * Note: in 64bit mode some segment registers are neither saved nor + * restored by the signal dispatching code in kernel, so we have + * to restore them by hands. + * Note: most async signals are left blocked, we unblock only few. + * Sync signals like SIGSEGV are never blocked. + */ + __init_handler(scp, async); + if (!block_all_sigs) + return; +#if SIGALTSTACK_WA + /* for SAS WA we unblock the fatal signals even later if we came + * from DPMI, as then we'll be switching stacks which is racy when + * async signals enabled. */ + if (need_sas_wa && DPMIValidSelector(_cs)) + return; +#endif + /* either came from dosemu/vm86 or having SS_AUTODISARM - + * then we can unblock any signals we want. For now leave nonfatal + * signals blocked as they are rarely needed inside sighandlers + * (needed only for instremu, see + * https://github.com/stsp/dosemu2/issues/477 + * ) */ + sigprocmask(SIG_UNBLOCK, &fatal_q_mask, NULL); +} + +SIG_PROTO_PFX +void deinit_handler(struct sigcontext *scp, unsigned long *uc_flags) +{ + /* in fullsim mode nothing to do */ + if (CONFIG_CPUSIM && config.cpuemu >= 4) + return; + + if (!DPMIValidSelector(_cs)) + return; + +#ifdef __x86_64__ +#ifndef UC_SIGCONTEXT_SS +/* + * UC_SIGCONTEXT_SS will be set when delivering 64-bit or x32 signals on + * kernels that save SS in the sigcontext. Kernels that set UC_SIGCONTEXT_SS + * allow signal handlers to set UC_RESTORE_SS; if UC_RESTORE_SS is set, + * then sigreturn will restore SS. + * + * For compatibility with old programs, the kernel will *not* set + * UC_RESTORE_SS when delivering signals. + */ +#define UC_SIGCONTEXT_SS 0x2 +#define UC_STRICT_RESTORE_SS 0x4 +#endif + + if (*uc_flags & UC_SIGCONTEXT_SS) { + /* + * On Linux 4.4 (possibly) and up, the kernel can fully restore + * SS and ESP, so we don't need any special tricks. To avoid confusion, + * force strict restore. (Some 4.1 versions support this as well but + * without the uc_flags bits. It's not trying to detect those kernels.) + */ + *uc_flags |= UC_STRICT_RESTORE_SS; + } else { +#if SIGRETURN_WA + if (!need_sr_wa) { + need_sr_wa = 1; + warn("Enabling sigreturn() work-around\n"); + } + dpmi_iret_setup(scp); +#else + error("Your kernel does not support UC_STRICT_RESTORE_SS and the " + "work-around in dosemu is not enabled.\n"); + leavedos_sig(11); +#endif + } + + if (_fs != getsegment(fs)) + loadregister(fs, _fs); + if (_gs != getsegment(gs)) + loadregister(gs, _gs); + + loadregister(ds, _ds); + loadregister(es, _es); +#endif +} + +static int ld_sig; +static void leavedos_call(void *arg) +{ + int *sig = arg; + leavedos(*sig); +} + +int sigchld_register_handler(pid_t pid, void (*handler)(void)) +{ + assert(chd_hndl_num < MAX_SIGCHLD_HANDLERS); + chld_hndl[chd_hndl_num].handler = handler; + chld_hndl[chd_hndl_num].pid = pid; + chld_hndl[chd_hndl_num].enabled = 1; + chd_hndl_num++; + return 0; +} + +int sigchld_enable_handler(pid_t pid, int on) +{ + int i; + for (i = 0; i < chd_hndl_num; i++) { + if (chld_hndl[i].pid == pid) + break; + } + if (i >= chd_hndl_num) + return -1; + chld_hndl[i].enabled = on; + return 0; +} + +static void cleanup_child(void *arg) +{ + int i, status; + pid_t pid2, pid = *(pid_t *)arg; + + for (i = 0; i < chd_hndl_num; i++) { + if (chld_hndl[i].pid == pid) + break; + } + if (i >= chd_hndl_num) + return; + if (!chld_hndl[i].enabled) + return; + pid2 = waitpid(pid, &status, WNOHANG); + if (pid2 != pid) + return; + if (chld_hndl[i].handler) + chld_hndl[i].handler(); +} + +/* this cleaning up is necessary to avoid the port server becoming + a zombie process */ +static void sig_child(struct sigcontext *scp, siginfo_t *si) +{ + SIGNAL_save(cleanup_child, &si->si_pid, sizeof(si->si_pid), __func__); +} + +int sigalrm_register_handler(void (*handler)(void)) +{ + assert(alrm_hndl_num < MAX_SIGALRM_HANDLERS); + alrm_hndl[alrm_hndl_num].handler = handler; + alrm_hndl_num++; + return 0; +} + +void leavedos_from_sig(int sig) +{ + /* anything more sophisticated? */ + leavedos_main(sig); +} + +static void leavedos_sig(int sig) +{ + dbug_printf("Terminating on signal %i\n", sig); + SIGNAL_save(leavedos_call, &sig, sizeof(sig), __func__); + /* abort current sighandlers */ + if (in_handle_signals) { + g_printf("Interrupting active signal handlers\n"); + in_handle_signals = 0; + } +} + +__attribute__((noinline)) +static void _leavedos_signal(int sig, struct sigcontext *scp) +{ + if (ld_sig) { + /* don't print anything - may lock up */ +#if 0 + error("gracefull exit failed, aborting (sig=%i)\n", sig); +#endif + _exit(sig); + } + ld_sig = sig; + leavedos_sig(sig); + if (!in_vm86) + dpmi_sigio(scp); +} + +SIG_PROTO_PFX +static void leavedos_signal(int sig, siginfo_t *si, void *uc) +{ + ucontext_t *uct = uc; + struct sigcontext *scp = (struct sigcontext *)&uct->uc_mcontext; + init_handler(scp, 1); + _leavedos_signal(sig, scp); + deinit_handler(scp, &uct->uc_flags); +} + +SIG_PROTO_PFX +static void abort_signal(int sig, siginfo_t *si, void *uc) +{ + struct sigcontext *scp = + (struct sigcontext *)&((ucontext_t *)uc)->uc_mcontext; + init_handler(scp, 0); + gdb_debug(); + _exit(sig); +} + +/* Silly Interrupt Generator Initialization/Closedown */ + +#ifdef SIG +SillyG_t *SillyG = 0; +static SillyG_t SillyG_[16 + 1]; +#endif + +/* + * DANG_BEGIN_FUNCTION SIG_init + * + * description: Allow DOSEMU to be made aware when a hard interrupt occurs + * The IRQ numbers to monitor are taken from config.sillyint, each bit + * corresponding to one IRQ. The higher 16 bit are defining the use of + * SIGIO + * + * DANG_END_FUNCTION + */ +void SIG_init(void) +{ +#if defined(SIG) + PRIV_SAVE_AREA + /* Get in touch with Silly Interrupt Handling */ + if (config.sillyint) { + char prio_table[] = + {8, 9, 10, 11, 12, 14, 15, 3, 4, 5, 6, 7}; + int i, + irq; + SillyG_t *sg = SillyG_; + for (i = 0; i < sizeof(prio_table); i++) { + irq = prio_table[i]; + if (config.sillyint & (1 << irq)) { + int ret; + enter_priv_on(); + ret = vm86_plus(VM86_REQUEST_IRQ, (SIGIO << 8) | irq); + leave_priv_setting(); + if ( ret > 0) { + g_printf("Gonna monitor the IRQ %d you requested\n", irq); + sg->fd = -1; + sg->irq = irq; + g_printf("SIG: IRQ%d, enabling PIC-level %ld\n", irq, pic_irq_list[irq]); + sg++; + } + } + } + sg->fd = 0; + if (sg != SillyG_) + SillyG = SillyG_; + } +#endif +} + +void SIG_close(void) +{ +#if defined(SIG) + if (SillyG) { + SillyG_t *sg = SillyG; + while (sg->fd) { + vm86_plus(VM86_FREE_IRQ, sg->irq); + sg++; + } + g_printf("Closing all IRQ you opened!\n"); + } +#endif +} + +void sig_ctx_prepare(int tid) +{ + rm_stack_enter(); + clear_IF(); +} + +void sig_ctx_restore(int tid) +{ + rm_stack_leave(); +} + +static void signal_thr_post(int tid) +{ + in_handle_signals--; +} + +static void signal_thr(void *arg) +{ + struct SIGNAL_queue *sig = &signal_queue[SIGNAL_head]; + struct SIGNAL_queue sig_c; // local copy for signal-safety + sig_c.signal_handler = signal_queue[SIGNAL_head].signal_handler; + sig_c.arg_size = sig->arg_size; + if (sig->arg_size) + memcpy(sig_c.arg, sig->arg, sig->arg_size); + sig_c.name = sig->name; + SIGNAL_head = (SIGNAL_head + 1) % MAX_SIG_QUEUE_SIZE; + if (debug_level('g') > 5) + g_printf("Processing signal %s\n", sig_c.name); + sig_c.signal_handler(sig_c.arg); +} + +static void sigstack_init(void) +{ +#ifndef MAP_STACK +#define MAP_STACK 0 +#endif + + /* sigaltstack_wa is optional. See if we need it. */ + stack_t dummy = { .ss_flags = SS_DISABLE | SS_AUTODISARM }; + int err = sigaltstack(&dummy, NULL); +#if SIGALTSTACK_WA + if ((err && errno == EINVAL) +#ifdef __i386__ + /* kernels before 4.11 had the needed functionality only for 64bits */ + || kernel_version_code < KERNEL_VERSION(4, 11, 0) +#endif + ) + { + need_sas_wa = 1; + warn("Enabling sigaltstack() work-around\n"); + /* for SAS WA block all signals. If we dont, there is a + * race that the signal can come after we switched to backup stack + * but before we disabled sigaltstack. We unblock the fatal signals + * later, only right before switching back to dosemu. */ + block_all_sigs = 1; + } + + if (need_sas_wa) { + cstack = alloc_mapping(MAPPING_SHARED, SIGSTACK_SIZE); + if (cstack == MAP_FAILED) { + error("Unable to allocate stack\n"); + config.exitearly = 1; + } + backup_stack = alias_mapping_high(MAPPING_OTHER, SIGSTACK_SIZE, + PROT_READ | PROT_WRITE, cstack); + if (backup_stack == MAP_FAILED) { + error("Unable to allocate stack\n"); + config.exitearly = 1; + } + } else { + cstack = mmap(NULL, SIGSTACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (cstack == MAP_FAILED) { + error("Unable to allocate stack\n"); + config.exitearly = 1; + } + } +#else + if ((err && errno == EINVAL) +#ifdef __i386__ + || kernel_version_code < KERNEL_VERSION(4, 11, 0) +#endif + ) + { + error("Your kernel does not support SS_AUTODISARM and the " + "work-around in dosemu is not enabled.\n"); + config.exitearly = 1; + } + cstack = mmap(NULL, SIGSTACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (cstack == MAP_FAILED) { + error("Unable to allocate stack\n"); + config.exitearly = 1; + } +#endif +} + +/* DANG_BEGIN_FUNCTION signal_pre_init + * + * description: + * Initialize the signals to have NONE being blocked. + * Currently this is NOT of much use to DOSEMU. + * + * DANG_END_FUNCTION + * + */ +void +signal_pre_init(void) +{ + /* initialize user data & code selector values (used by DPMI code) */ + /* And save %fs, %gs for NPTL */ + eflags_fs_gs.fs = getsegment(fs); + eflags_fs_gs.gs = getsegment(gs); + eflags_fs_gs.eflags = getflags(); + dbug_printf("initial register values: fs: 0x%04x gs: 0x%04x eflags: 0x%04lx\n", + eflags_fs_gs.fs, eflags_fs_gs.gs, eflags_fs_gs.eflags); +#ifdef __x86_64__ + eflags_fs_gs.ds = getsegment(ds); + eflags_fs_gs.es = getsegment(es); + eflags_fs_gs.ss = getsegment(ss); + /* get long fs and gs bases. If they are in the first 32 bits + normal 386-style fs/gs switching can happen so we can ignore + fsbase/gsbase */ + dosemu_arch_prctl(ARCH_GET_FS, &eflags_fs_gs.fsbase); + if ((unsigned long)eflags_fs_gs.fsbase <= 0xffffffff) + eflags_fs_gs.fsbase = 0; + dosemu_arch_prctl(ARCH_GET_GS, &eflags_fs_gs.gsbase); + if ((unsigned long)eflags_fs_gs.gsbase <= 0xffffffff) + eflags_fs_gs.gsbase = 0; + dbug_printf("initial segment bases: fs: %p gs: %p\n", + eflags_fs_gs.fsbase, eflags_fs_gs.gsbase); +#endif + + /* first set up the blocking mask: registersig() and newsetqsig() + * adds to it */ + sigemptyset(&q_mask); + sigemptyset(&nonfatal_q_mask); + registersig(SIGALRM, sigalrm); + registersig(SIGIO, sigio); + registersig(SIGCHLD, sig_child); + newsetqsig(SIGQUIT, leavedos_signal); + newsetqsig(SIGINT, leavedos_signal); /* for "graceful" shutdown for ^C too*/ + newsetqsig(SIGHUP, leavedos_signal); /* for "graceful" shutdown */ + newsetqsig(SIGTERM, leavedos_signal); + /* below ones are initialized by other subsystems */ + registersig(SIGPROF, NULL); + registersig(SIG_ACQUIRE, NULL); + registersig(SIG_RELEASE, NULL); + /* mask is set up, now start using it */ + qsig_init(); + newsetsig(SIGILL, dosemu_fault); + newsetsig(SIGFPE, dosemu_fault); + newsetsig(SIGTRAP, dosemu_fault); + newsetsig(SIGBUS, dosemu_fault); + newsetsig(SIGABRT, abort_signal); + newsetsig(SIGSEGV, dosemu_fault); + + /* block async signals so that threads inherit the blockage */ + sigprocmask(SIG_BLOCK, &q_mask, NULL); + + signal(SIGPIPE, SIG_IGN); + dosemu_pthread_self = pthread_self(); +} + +void +signal_init(void) +{ + sigstack_init(); +#if SIGRETURN_WA + /* 4.6+ are able to correctly restore SS */ + if (kernel_version_code < KERNEL_VERSION(4, 6, 0)) { + need_sr_wa = 1; + warn("Enabling sigreturn() work-around for old kernel\n"); + /* block all sigs for SR WA. If we dont, the signal can come before + * SS is saved, but we can't restore SS on signal exit. */ + block_all_sigs = 1; + } +#endif + + sh_tid = coopth_create("signal handling"); + /* normally we don't need ctx handlers because the thread is detached. + * But some crazy code (vbe.c) can call coopth_attach() on it, so we + * set up the handlers just in case. */ + coopth_set_ctx_handlers(sh_tid, sig_ctx_prepare, sig_ctx_restore); + coopth_set_sleep_handlers(sh_tid, handle_signals_force_enter, + handle_signals_force_leave); + coopth_set_permanent_post_handler(sh_tid, signal_thr_post); + coopth_set_detached(sh_tid); + + event_fd = eventfd(0, EFD_CLOEXEC); + add_to_io_select(event_fd, async_awake, NULL); + rng_init(&cbks, MAX_CBKS, sizeof(struct callback_s)); + + /* unblock async signals in main thread */ + pthread_sigmask(SIG_UNBLOCK, &q_mask, NULL); +} + +void signal_done(void) +{ + struct itimerval itv; + + itv.it_interval.tv_sec = itv.it_interval.tv_usec = 0; + itv.it_value = itv.it_interval; + if (setitimer(ITIMER_REAL, &itv, NULL) == -1) + g_printf("can't turn off timer at shutdown: %s\n", strerror(errno)); + registersig(SIGALRM, NULL); + registersig(SIGIO, NULL); + registersig(SIGCHLD, NULL); + signal(SIGCHLD, SIG_DFL); + SIGNAL_head = SIGNAL_tail; +} + +static void handle_signals_force_enter(int tid, int sl_state) +{ + if (!in_handle_signals) { + dosemu_error("in_handle_signals=0\n"); + return; + } + in_handle_signals--; +} + +static void handle_signals_force_leave(int tid) +{ + in_handle_signals++; +} + +int signal_pending(void) +{ + return (SIGNAL_head != SIGNAL_tail); +} + +/* + * DANG_BEGIN_FUNCTION handle_signals + * + * description: + * Due to signals happening at any time, the actual work to be done + * because a signal occurs is done here in a serial fashion. + * + * The concept, should this eventualy work, is that a signal should only + * flag that it has occurred and let DOSEMU deal with it in an orderly + * fashion as it executes the rest of it's code. + * + * DANG_END_FUNCTION + * + */ +void handle_signals(void) +{ + while (signal_pending() && !in_handle_signals) { + in_handle_signals++; + coopth_start(sh_tid, signal_thr, NULL); + coopth_run_tid(sh_tid); + } +} + +/* ============================================================== + * + * This is called by default at around 100Hz. + * (see timer_interrupt_init() in init.c) + * + * The actual formulas, starting with the configurable parameter + * config.freq, are: + * config.freq default=18 + * config.update = 1E6/config.freq default=54945 + * timer tick(us) = config.update/6 default=9157.5us + * = 166667/config.freq + * timer tick(Hz) = 6*config.freq default=100Hz + * + * 6 is the magical TIMER_DIVISOR macro used to get 100Hz + * + * This call should NOT be used if you need timing accuracy - many + * signals can get lost e.g. when kernel accesses disk, and the whole + * idea of timing-by-counting is plain wrong. We'll need the Pentium + * counter here. + * ============================================================== */ + +static void SIGALRM_call(void *arg) +{ + static int first = 0; + static hitimer_t cnt200 = 0; + static hitimer_t cnt1000 = 0; + int i; + + if (first==0) { + cnt200 = + cnt1000 = + pic_sys_time; /* initialize */ + first = 1; + } + + if (video_initialized && !config.vga) + update_screen(); + + for (i = 0; i < alrm_hndl_num; i++) + alrm_hndl[i].handler(); + + if (config.rdtsc) + update_cputime_TSCBase(); + timer_tick(); + +#if 0 +/* + * DANG_BEGIN_REMARK + * Check for keyboard coming from client + * For now, first byte is interrupt requests from Client + * DANG_END_REMARK + */ + if (*(u_char *)(shared_qf_memory + CLIENT_REQUEST_FLAG_AREA) & 0x40) { + k_printf("KBD: Client sent key\n"); + pic_request (PIC_IRQ1); + *(u_char *)(shared_qf_memory + CLIENT_REQUEST_FLAG_AREA) &= ~0x40; + } +#endif + + io_select(); /* we need this in order to catch lost SIGIOs */ + /* catch user hooks here */ + if (uhook_fdin != -1) uhook_poll(); + + alarm_idle(); + + /* Here we 'type in' prestrokes from commandline, as long as there are any + * Were won't overkill dosemu, hence we type at a speed of 14cps + */ + if (config.pre_stroke) { + static int count=-1; + if (--count < 0) { + count = type_in_pre_strokes(); + if (count <0) count =7; /* with HZ=100 we have a stroke rate of 14cps */ + } + } + + /* this should be for per-second activities, it is actually at + * 200ms more or less (PARTIALS=5) */ + if ((pic_sys_time-cnt200) >= (PIT_TICK_RATE/PARTIALS)) { + cnt200 = pic_sys_time; +/* g_printf("**** ALRM: %dms\n",(1000/PARTIALS)); */ + + printer_tick(0); + floppy_tick(); + } + +/* We update the RTC from here if it has not been defined as a thread */ + + /* this is for EXACT per-second activities (can produce bursts) */ + if ((pic_sys_time-cnt1000) >= PIT_TICK_RATE) { + cnt1000 += PIT_TICK_RATE; +/* g_printf("**** ALRM: 1sec\n"); */ + rtc_update(); + } +} + +/* DANG_BEGIN_FUNCTION SIGNAL_save + * + * arguments: + * context - signal context to save. + * signal_call - signal handling routine to be called. + * + * description: + * Save into an array structure queue the signal context of the current + * signal as well as the function to call for dealing with this signal. + * This is a queue because any signal may occur multiple times before + * DOSEMU deals with it down the road. + * + * DANG_END_FUNCTION + * + */ +void SIGNAL_save(void (*signal_call)(void *), void *arg, size_t len, + const char *name) +{ + signal_queue[SIGNAL_tail].signal_handler = signal_call; + signal_queue[SIGNAL_tail].arg_size = len; + assert(len <= MAX_SIG_DATA_SIZE); + if (len) + memcpy(signal_queue[SIGNAL_tail].arg, arg, len); + signal_queue[SIGNAL_tail].name = name; + SIGNAL_tail = (SIGNAL_tail + 1) % MAX_SIG_QUEUE_SIZE; + if (in_dpmi_pm()) + dpmi_return_request(); +} + + +/* + * DANG_BEGIN_FUNCTION SIGIO_call + * + * description: + * Whenever I/O occurs on devices allowing SIGIO to occur, DOSEMU + * will be flagged to run this call which inturn checks which + * fd(s) was set and execute the proper routine to get the I/O + * from that device. + * + * DANG_END_FUNCTION + * + */ +static void SIGIO_call(void *arg){ + /* Call select to see if any I/O is ready on devices */ + io_select(); +} + +#ifdef __linux__ +static void sigio(struct sigcontext *scp, siginfo_t *si) +{ + /* prints non reentrant! dont do! */ +#if 0 + g_printf("got SIGIO\n"); +#endif + e_gen_sigalrm(scp); + SIGNAL_save(SIGIO_call, NULL, 0, __func__); + if (!in_vm86) + dpmi_sigio(scp); +} + +static void sigalrm(struct sigcontext *scp, siginfo_t *si) +{ + if(e_gen_sigalrm(scp)) { + SIGNAL_save(SIGALRM_call, NULL, 0, __func__); + if (!in_vm86) + dpmi_sigio(scp); + } +} + +__attribute__((noinline)) +static void sigasync0(int sig, struct sigcontext *scp, siginfo_t *si) +{ + pthread_t tid = pthread_self(); + if (!pthread_equal(tid, dosemu_pthread_self)) { + char name[128]; + pthread_getname_np(tid, name, sizeof(name)); + dosemu_error("Async signal %i from thread %s\n", sig, name); + } + if (sighandlers[sig]) + sighandlers[sig](scp, si); +} + +SIG_PROTO_PFX +static void sigasync(int sig, siginfo_t *si, void *uc) +{ + ucontext_t *uct = uc; + struct sigcontext *scp = (struct sigcontext *)&uct->uc_mcontext; + init_handler(scp, 1); + sigasync0(sig, scp, si); + deinit_handler(scp, &uct->uc_flags); +} +#endif + + +void do_periodic_stuff(void) +{ + check_leavedos(); + handle_signals(); +#ifdef USE_MHPDBG + /* mhp_debug() must be called exactly after handle_signals() + * and before coopth_run(). handle_signals() feeds input to + * debugger, and coopth_run() runs DPMI (oops!). We need to + * run debugger before DPMI to not lose single-steps. */ + if (mhpdbg.active) + mhp_debug(DBG_POLL, 0, 0); +#endif + coopth_run(); + + if (video_initialized && Video && Video->change_config) + update_xtitle(); +} + +void add_thread_callback(void (*cb)(void *), void *arg, const char *name) +{ + if (cb) { + struct callback_s cbk; + int i; + cbk.func = cb; + cbk.arg = arg; + cbk.name = name; + pthread_mutex_lock(&cbk_mtx); + i = rng_put(&cbks, &cbk); + g_printf("callback %s added, %i queued\n", name, rng_count(&cbks)); + pthread_mutex_unlock(&cbk_mtx); + if (!i) + error("callback queue overflow, %s\n", name); + } + eventfd_write(event_fd, 1); + /* unfortunately eventfd does not support SIGIO :( So we kill ourself. */ + pthread_kill(dosemu_pthread_self, SIGIO); +} + +static void async_awake(void *arg) +{ + struct callback_s cbk; + int i; + eventfd_t val; + eventfd_read(event_fd, &val); + g_printf("processing %"PRId64" callbacks\n", val); + do { + pthread_mutex_lock(&cbk_mtx); + i = rng_get(&cbks, &cbk); + pthread_mutex_unlock(&cbk_mtx); + if (i) + cbk.func(cbk.arg); + } while (i); +} + +static int saved_fc; + +void signal_switch_to_dosemu(void) +{ + saved_fc = fault_cnt; + fault_cnt = 0; +} + +void signal_switch_to_dpmi(void) +{ + fault_cnt = saved_fc; +} + +#if SIGALTSTACK_WA +static void signal_sas_wa(void) +{ + int err; + stack_t ss = {}; + m_ucontext_t hack; + unsigned char *sp; + unsigned char *top = cstack + SIGSTACK_SIZE; + unsigned char *btop = backup_stack + SIGSTACK_SIZE; + ptrdiff_t delta; + + if (getmcontext(&hack) == 0) { + sp = alloca(sizeof(void *)); + delta = top - sp; + asm volatile( +#ifdef __x86_64__ + "mov %0, %%rsp\n" +#else + "mov %0, %%esp\n" +#endif + :: "r"(btop - delta) : "sp"); + } else { + sigprocmask(SIG_UNBLOCK, &fatal_q_mask, NULL); + return; + } + + ss.ss_flags = SS_DISABLE; + /* sas will re-enable itself when returning from sighandler */ + err = sigaltstack(&ss, NULL); + if (err) + perror("sigaltstack"); + + setmcontext(&hack); +} +#endif + +void signal_return_to_dosemu(void) +{ +#if SIGALTSTACK_WA + if (need_sas_wa) + signal_sas_wa(); +#endif +} + +void signal_return_to_dpmi(void) +{ +} + +void signal_set_altstack(int on) +{ + stack_t stk; + + if (!on) { + stk.ss_sp = NULL; + stk.ss_size = 0; + stk.ss_flags = SS_DISABLE; + } else { + stk.ss_sp = cstack; + stk.ss_size = SIGSTACK_SIZE; +#if SIGALTSTACK_WA + stk.ss_flags = SS_ONSTACK | (need_sas_wa ? 0 : SS_AUTODISARM); +#else + stk.ss_flags = SS_ONSTACK | SS_AUTODISARM; +#endif + } + sigaltstack(&stk, NULL); +} + +void signal_unblock_async_sigs(void) +{ + /* unblock only nonfatal, fatals should already be unblocked */ + sigprocmask(SIG_UNBLOCK, &nonfatal_q_mask, NULL); +} + +void signal_restore_async_sigs(void) +{ + /* block sigs even for !sas_wa because if deinit_handler is + * interrupted after changing %fs, we are in troubles */ + sigprocmask(SIG_BLOCK, &nonfatal_q_mask, NULL); +}