/* * This file is part of Jehanne. * * Copyright (C) 2017 Giacomo Tesio * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, version 3 of the License. * * Jehanne is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Jehanne. If not, see . */ /* POSIX signals have weird semantics that are hard to emulate in a * sane system without giving up on its sanity. * * We distinguish control signals (SIGKILL, SIGSTOP, SIGCONT, * SIGABRT, SIGIOT), SIGCHLD/SIGCLD, timers' wakeups * (SIGALRM, SIGPROF, SIGVTALRM) and all the others. * * TRASMISSION * ----------- * Signal transmission depends on the relation between the sender * and the receiver: * 1) if sender and receiver have no relation the signal is translated * to a note and sent to the receiver(s); * 2) if sender == receiver the signal trampoline is directly invoked * for all signals except control ones and the default disposition occurs if * the trampoline does not handle the signal * 3) if sender is parent or child of receiver the transmision * differs from the default if libposix_emulate_SIGCHLD() has been * called during library initialization * * Since notes in Jehanne are not reentrant, signals translated to * notes will be enqueued in kernel. A special machinery is implemented * for timers, so that they can be used in signal handlers to wakeup * the calling process. * * For all the signals except SIGCONT, the burden of interpreting the * signal is on the receiver: the sender just send the signal. * * The receiver will translate the note back to the appropriate signal * number and invoke the trampoline: if trampoline returns 0 no function * registered with signal() handled the signal and the library will * invoke the default disposition associated to the signal. * * CONTROL MESSAGES * ---------------- * Control messages are translated by the receiver to equivalent actions: * - SIGKILL => write "kill" to the control file of the current process * - SIGSTOP => write "stop" to the control file of the current process * - SIGABRT/SIGIOT => invoke the registered signal handlers and abort * the current process * - SIGCONT => invoke the registered signal handlers via the signal * trampoline and continue; note that (since a stopped * process cannot resume itself) the sender will write * "start" to the control file of the receiver before * sending the note (unless SIGCHLD emulation is enable). * * SIGCHLD/SIGCLD * -------------- * Jehanne (like Plan 9) does not support a SIGCHLD equivalent. * The user space emulation provided here is quite expensive, so it's * disabled by default. * Calling libposix_emulate_SIGCHLD() during libposix initialization * will enable this machinery for the whole life of the process. * * Such emulation changes the way POSIX_fork and POSIX_kill works. * * Each fork() will spawn two additional processes that are designed * to proxy signals between the parent and the desired child: * * parent * +- proxy from parent to child (P2C) * +- proxy from child to parent (C2P) * +- child * * Fork will return the pid of P2C to the parent, so that the * parent process will see the first proxy as its child; the child will * see the second as its parent. Each proxy waits for its child and * forwards the notes it receive to its designed target. * Finally fork in child will return 0. * * When the child exits, C2P will send a SIGCHLD note to parent and * then exit with the same status. Then P2C will exit with * the same status too. * * As the parent process will see P2C as its child, it will send any * signal to it via kill and will wait it on SIGCHLD. * However, when this machinary is enabled, kill will treat all signals * for forked children in the same way, sending them to P2C. * It's P2C's responsibility to translate control messages as required, * so that SIGCONT will work as expected. * * TIMERS * ------ * The functions alarm() and setitimer() generate SIGALRM, SIGPROF * or SIGVTALRM for the current process. We want timers to be able to * expire in a signal handler (interrupting a blocking syscall) but * without giving up the simplicity of notes. * * We allocate these timers on libposix initialization. When normal * code is running timers will be implemented via Jehanne's alarms, * producing a note on expiration that will be mapped to the proper * signal for the trampoline by the receiving process. * * However, when a signal handler is being executed, the timers will * be implemented using the awake syscall that will be able * to interrupt blocking syscalls in the signal handler. * When a signal handler returns, if the timer has not be cleared and * did not expired, the wakeup is cleared and replaced with an alarm * note. */ #include #include #include #include "internal.h" unsigned char *__signals_to_code_map; unsigned char *__code_to_signal_map; int *__handling_external_signal; int *__restart_syscall; static int __sigrtmin; static int __sigrtmax; int __min_known_sig; int __max_known_sig; static PosixSignalTrampoline __libposix_signal_trampoline; typedef enum PosixSignalDisposition { IgnoreWithNoEffect = 0, TerminateTheProcess, TerminateTheProcessAndCoreDump, StopTheProcess, ResumeTheProcess } PosixSignalDisposition; static PosixError note_all_writable_processes(int sig) { // TODO: loop over writable note files and post note. return PosixEPERM; } static void terminated_by_signal(int sig) { char buf[64]; __libposix_free_wait_list(); snprint(buf, sizeof(buf), __POSIX_EXIT_SIGNAL_PREFIX "%d", sig); exits(buf); } int __libposix_send_control_msg(int pid, char *msg) { int fd, n; char buf[256]; n = snprint(buf, sizeof(buf), "/proc/%d/ctl", pid); if(n < 0) goto ErrorBeforeOpen; fd = open(buf, OWRITE); if(fd < 0) goto ErrorBeforeOpen; n = snprint(buf, sizeof(buf), "%s", msg); if(n < 0) goto ErrorAfterOpen; if(write(fd, buf, n) < n) goto ErrorAfterOpen; close(fd); return 1; ErrorAfterOpen: close(fd); ErrorBeforeOpen: return 0; } /* Executes a PosixSignalDisposition. */ static int execute_disposition(int sig, PosixSignalDisposition action) { int aborted_by_signal; switch(action){ case ResumeTheProcess: // the sender resumed us already case IgnoreWithNoEffect: *__restart_syscall = 1; return 1; case TerminateTheProcess: terminated_by_signal(sig); break; case TerminateTheProcessAndCoreDump: aborted_by_signal = 0; assert(aborted_by_signal); break; case StopTheProcess: return __libposix_send_control_msg(getpid(), "stop"); } return 0; } static PosixSignalDisposition default_signal_disposition(int code) { PosixSignals signal; // see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html if(__sigrtmin != 0 && __sigrtmax != 0 &&(code >= __sigrtmin || code <= __sigrtmax)) return TerminateTheProcess; signal = __code_to_signal_map[code]; switch(signal){ default: sysfatal("libposix: undefined signal %d", code); case PosixSIGALRM: case PosixSIGHUP: case PosixSIGINT: case PosixSIGKILL: case PosixSIGPIPE: case PosixSIGTERM: case PosixSIGUSR1: case PosixSIGUSR2: case PosixSIGPOLL: case PosixSIGPROF: case PosixSIGVTALRM: return TerminateTheProcess; case PosixSIGSTOP: case PosixSIGTSTP: case PosixSIGTTIN: case PosixSIGTTOU: return StopTheProcess; case PosixSIGABRT: case PosixSIGILL: case PosixSIGFPE: case PosixSIGBUS: case PosixSIGQUIT: case PosixSIGSEGV: case PosixSIGSYS: case PosixSIGTRAP: case PosixSIGXCPU: case PosixSIGXFSZ: return TerminateTheProcessAndCoreDump; case PosixSIGCHLD: case PosixSIGCLD: case PosixSIGURG: return IgnoreWithNoEffect; case PosixSIGCONT: return ResumeTheProcess; } } PosixError __libposix_receive_signal(int sig) { PosixSignalAction action = SignalDefault; PosixSignals psig = __code_to_signal_map[sig]; PosixSignalDisposition disposition; if(psig != PosixSIGKILL && psig != PosixSIGSTOP) action = __libposix_signal_trampoline(sig); if(psig == PosixSIGABRT) action = SignalDefault; // lets abort despite the user will switch(action){ case SignalCatched: break; case SignalIgnored: *__restart_syscall = 1; break; case SignalDefault: disposition = default_signal_disposition(sig); if(!execute_disposition(sig, disposition)) return PosixEPERM; break; case SignalError: return PosixEINVAL; } return 0; } PosixError __libposix_notify_signal_to_process(int pid, int signal) { char buf[128]; int fd, n; snprint(buf, sizeof(buf), "/proc/%d/note", pid); if((fd = open(buf, OWRITE)) < 0){ if(access(buf, AEXIST) == 0) return PosixEPERM; else return PosixESRCH; } n = snprint(buf, sizeof(buf), __POSIX_SIGNAL_PREFIX "%d", signal); write(fd, buf, n); close(fd); return 0; } static PosixError notify_signal_to_group(int pid, int signal) { char buf[128]; int fd, n; snprint(buf, sizeof(buf), "/proc/%d/notepg", pid); if((fd = open(buf, OWRITE)) < 0){ if(access(buf, AEXIST) == 0) return PosixEPERM; else return PosixESRCH; } n = snprint(buf, sizeof(buf), __POSIX_SIGNAL_PREFIX "%d", signal); write(fd, buf, n); close(fd); return 0; } static PosixError dispatch_signal(int pid, int sig) { PosixSignals signal; PosixError error; switch(pid){ case 0: return notify_signal_to_group(getpid(), sig); case -1: return note_all_writable_processes(sig); default: if(pid < 0) return notify_signal_to_group(-pid, sig); break; } signal = __code_to_signal_map[sig]; error = __libposix_notify_signal_to_process(pid, sig); if(signal == PosixSIGCONT && !__libposix_is_child(pid)) __libposix_send_control_msg(pid, "start"); return error; } int POSIX_kill(int *errnop, int pid, int sig) { PosixSignals signal; PosixError perror; signal = __code_to_signal_map[sig]; if(signal == 0 &&(__sigrtmin != 0 && __sigrtmax != 0) &&(sig < __sigrtmin || sig > __sigrtmax)) sysfatal("libposix: undefined signal %d", sig); if(pid == getpid()) perror = __libposix_receive_signal(sig); else perror = dispatch_signal(pid, sig); if(perror != 0){ *errnop = __libposix_get_errno(perror); return -1; } return 0; } static int translate_jehanne_note(char *note) { // TODO: implement return 0; } int __libposix_note_to_signal(char *note) { return atoi(note+__POSIX_SIGNAL_PREFIX_LEN); } int __libposix_note_handler(void *ureg, char *note) { int sig; PosixError error; if(strncmp(note, __POSIX_SIGNAL_PREFIX, __POSIX_SIGNAL_PREFIX_LEN) != 0) return translate_jehanne_note(note); // TODO: should we translate common notes? sig = __libposix_note_to_signal(note); if(sig < __min_known_sig || sig > __max_known_sig) sysfatal("libposix: '%s' does not carry a signal", note); *__handling_external_signal = 1; error = __libposix_receive_signal(sig); *__handling_external_signal = 0; return error == 0; } int libposix_define_realtime_signals(int sigrtmin, int sigrtmax) { if(sigrtmin >= 256 || sigrtmin <=0) sysfatal("libposix: invalid SIGRTMIN %d (must be positive and less then 256)", sigrtmin); if(sigrtmax >= 256 || sigrtmax <=0) sysfatal("libposix: invalid SIGRTMAX %d (must be positive and less then 256)", sigrtmax); if(sigrtmax <= sigrtmin) sysfatal("libposix: invalid SIGRTMAX %d (must be greater than SIGRTMIN %d)", sigrtmax, sigrtmin); if(__libposix_initialized()) return 0; __sigrtmin = sigrtmin; __sigrtmax = sigrtmax; if(sigrtmin < __min_known_sig || __min_known_sig == 0) __min_known_sig = sigrtmin; if(sigrtmax > __max_known_sig || __max_known_sig == 0) __max_known_sig = sigrtmax; return 1; } int libposix_define_signal(PosixSignals signal, int code) { if(signal >= PosixNumberOfSignals) sysfatal("libposix: unknown PosixSignal %d", signal); if(code >= 256 || code <=0) sysfatal("libposix: invalid signal number %d (must be positive and less then 256)", code); if(__libposix_initialized()) return 0; __signals_to_code_map[signal] = (unsigned char)code; if(signal != PosixSIGCLD || code != __signals_to_code_map[PosixSIGCHLD]){ /* if SIGCHLD == SIGCLD then we use PosixSIGCHLD to identify the signal */ __code_to_signal_map[code] = (unsigned char)signal; } if(code < __min_known_sig || __min_known_sig == 0) __min_known_sig = code; if(code > __max_known_sig || __max_known_sig == 0) __max_known_sig = code; return 1; } int libposix_set_signal_trampoline(PosixSignalTrampoline trampoline) { if(__libposix_initialized()) return 0; if(trampoline == nil) return 0; __libposix_signal_trampoline = trampoline; return 1; } int __libposix_restart_syscall(void) { int r = *__restart_syscall; *__restart_syscall = 0; return r; } void __libposix_signal_check_conf(void) { if(__libposix_signal_trampoline == nil) sysfatal("libposix: no signal trampoline"); }