#ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include "Ecore.h" #include "ecore_private.h" /* make mono happy - this is evil though... */ #undef SIGPWR typedef struct _Pid_Info Pid_Info; struct _Pid_Info { pid_t pid; int fd; }; static void _ecore_signal_exe_exit_delay(void *data, const Efl_Event *event); static void _ecore_signal_waitpid(Eina_Bool once, siginfo_t info); static void _ecore_signal_generic_free(void *data, void *event); typedef void (*Signal_Handler)(int sig, siginfo_t *si, void *foo); #define NUM_PIPES 5 static int sig_pipe[NUM_PIPES][2] = {{ -1 }}; // [0] == read, [1] == write static Eo *sig_pipe_handler[NUM_PIPES] = {NULL}; static Eina_Spinlock sig_pid_lock; static Eina_List *sig_pid_info_list = NULL; volatile int pipe_dead = 0; volatile int exit_signal_received = 0; typedef struct _Signal_Data { int sig; siginfo_t info; } Signal_Data; static void _ecore_signal_pipe_read(Eo *obj) { Signal_Data sdata; int ret; if (pipe_dead) return; for (unsigned int i = 0; i < NUM_PIPES; i++) { while (1) { ret = read(sig_pipe[i][0], &sdata, sizeof(sdata)); /* read as many signals as we can, trying again if we get interrupted */ if ((ret != sizeof(sdata)) && (errno != EINTR)) break; switch (sdata.sig) { case SIGPIPE: break; case SIGALRM: break; case SIGCHLD: _ecore_signal_waitpid(EINA_FALSE, sdata.info); break; case SIGUSR1: case SIGUSR2: { Ecore_Event_Signal_User *e = _ecore_event_signal_user_new(); if (e) { if (sdata.sig == SIGUSR1) e->number = 1; else e->number = 2; e->data = sdata.info; ecore_event_add(ECORE_EVENT_SIGNAL_USER, e, _ecore_signal_generic_free, NULL); } Eo *loop = efl_provider_find(obj, EFL_LOOP_CLASS); if (loop) { if (sdata.sig == SIGUSR1) efl_event_callback_call(loop, EFL_APP_EVENT_SIGNAL_USR1, NULL); else efl_event_callback_call(loop, EFL_APP_EVENT_SIGNAL_USR2, NULL); } } break; case SIGHUP: { Ecore_Event_Signal_Hup *e = _ecore_event_signal_hup_new(); if (e) { e->data = sdata.info; ecore_event_add(ECORE_EVENT_SIGNAL_HUP, e, _ecore_signal_generic_free, NULL); } Eo *loop = efl_provider_find(obj, EFL_LOOP_CLASS); if (loop) efl_event_callback_call(loop, EFL_APP_EVENT_SIGNAL_HUP, NULL); } break; case SIGQUIT: case SIGINT: case SIGTERM: { Ecore_Event_Signal_Exit *e = _ecore_event_signal_exit_new(); if (e) { if (sdata.sig == SIGQUIT) e->quit = 1; else if (sdata.sig == SIGINT) e->interrupt = 1; else e->terminate = 1; e->data = sdata.info; ecore_event_add(ECORE_EVENT_SIGNAL_EXIT, e, _ecore_signal_generic_free, NULL); } Eo *loop = efl_provider_find(obj, EFL_LOOP_CLASS); if (loop) efl_event_callback_call(loop, EFL_LOOP_EVENT_QUIT, NULL); } break; #ifdef SIGPWR case SIGPWR: { Ecore_Event_Signal_Power *e = _ecore_event_signal_power_new(); if (e) { e->data = sdata.info; ecore_event_add(ECORE_EVENT_SIGNAL_POWER, e, _ecore_signal_generic_free, NULL); } } break; #endif default: break; } } } } static void _ecore_signal_cb_read(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED) { _ecore_signal_pipe_read(event->object); } static void _ecore_signal_callback(int sig, siginfo_t *si, void *foo EINA_UNUSED) { Signal_Data sdata; memset(&sdata, 0, sizeof(Signal_Data)); sdata.sig = sig; sdata.info = *si; if (sdata.sig >= 0) { int err = errno; if (pipe_dead) return; for (unsigned int i = 0; i < NUM_PIPES; i++) { do { err = 0; const ssize_t bytes = write(sig_pipe[i][1], &sdata, sizeof(sdata)); if (EINA_UNLIKELY(bytes != sizeof(sdata))) { err = errno; if (err == EINTR) DBG("signal pipe %u full", i); else if (i == NUM_PIPES - 1) //only print errors on last pipe ERR("write() failed: %d: %s", err, strerror(err)); } errno = err; /* loop if we got preempted */ } while (err == EINTR); if (!err) break; } } switch (sig) { case SIGQUIT: case SIGINT: case SIGTERM: exit_signal_received = 1; break; default: break; } } static void _ecore_signal_callback_set(int sig, Signal_Handler func) { struct sigaction sa; sa.sa_sigaction = func; sa.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&sa.sa_mask); sigaction(sig, &sa, NULL); } static void _signalhandler_setup(void) { sigset_t newset; _ecore_signal_callback_set(SIGPIPE, _ecore_signal_callback); _ecore_signal_callback_set(SIGALRM, _ecore_signal_callback); _ecore_signal_callback_set(SIGCHLD, _ecore_signal_callback); _ecore_signal_callback_set(SIGUSR1, _ecore_signal_callback); _ecore_signal_callback_set(SIGUSR2, _ecore_signal_callback); _ecore_signal_callback_set(SIGHUP, _ecore_signal_callback); _ecore_signal_callback_set(SIGQUIT, _ecore_signal_callback); _ecore_signal_callback_set(SIGINT, _ecore_signal_callback); _ecore_signal_callback_set(SIGTERM, _ecore_signal_callback); #ifdef SIGPWR _ecore_signal_callback_set(SIGPWR, _ecore_signal_callback); #endif #ifndef _WIN32 sigemptyset(&newset); sigaddset(&newset, SIGPIPE); sigaddset(&newset, SIGALRM); sigaddset(&newset, SIGCHLD); sigaddset(&newset, SIGUSR1); sigaddset(&newset, SIGUSR2); sigaddset(&newset, SIGHUP); sigaddset(&newset, SIGQUIT); sigaddset(&newset, SIGINT); sigaddset(&newset, SIGTERM); # ifdef SIGPWR sigaddset(&newset, SIGPWR); # endif pthread_sigmask(SIG_UNBLOCK, &newset, NULL); #endif } static void _ecore_signal_pipe_init(void) { eina_spinlock_new(&sig_pid_lock); _signalhandler_setup(); if (sig_pipe[0][0] == -1) { for (unsigned int i = 0; i < NUM_PIPES; i++) { if (pipe(sig_pipe[i]) != 0) { CRI("failed setting up signal pipes! %s", strerror(errno)); for (unsigned int j = 0; j < i; j++) { close(sig_pipe[j][0]); close(sig_pipe[j][1]); } memset(sig_pipe, -1, sizeof(sig_pipe)); return; } eina_file_close_on_exec(sig_pipe[i][0], EINA_TRUE); eina_file_close_on_exec(sig_pipe[i][1], EINA_TRUE); if (fcntl(sig_pipe[i][0], F_SETFL, O_NONBLOCK) < 0) ERR("can't set pipe to NONBLOCK"); if (fcntl(sig_pipe[i][1], F_SETFL, O_NONBLOCK) < 0) ERR("can't set pipe to NONBLOCK"); efl_add(EFL_LOOP_HANDLER_CLASS, ML_OBJ, efl_loop_handler_fd_set(efl_added, sig_pipe[i][0]), efl_loop_handler_active_set(efl_added, EFL_LOOP_HANDLER_FLAGS_READ), efl_event_callback_add(efl_added, EFL_LOOP_HANDLER_EVENT_READ, _ecore_signal_cb_read, NULL), efl_wref_add(efl_added, &sig_pipe_handler[i]) ); } } } static void _ecore_signal_pipe_shutdown(void) { if (sig_pipe[0][0] != -1) { for (unsigned int i = 0; i < NUM_PIPES; i++) { close(sig_pipe[i][0]); close(sig_pipe[i][1]); efl_del(sig_pipe_handler[i]); } } memset(sig_pipe, -1, sizeof(sig_pipe)); eina_spinlock_free(&sig_pid_lock); } static void _ecore_signal_cb_fork(void *data EINA_UNUSED) { _ecore_signal_pipe_shutdown(); _ecore_signal_pipe_init(); } void _ecore_signal_init(void) { pipe_dead = 0; _ecore_signal_pipe_init(); ecore_fork_reset_callback_add(_ecore_signal_cb_fork, NULL); } void _ecore_signal_shutdown(void) { sigset_t newset; ecore_fork_reset_callback_del(_ecore_signal_cb_fork, NULL); pipe_dead = 1; // we probably should restore.. but not a good idea // pthread_sigmask(SIG_SETMASK, &sig_oldset, NULL); // at least do not trigger signal callback after shutdown #ifndef _WIN32 sigemptyset(&newset); sigaddset(&newset, SIGPIPE); sigaddset(&newset, SIGALRM); sigaddset(&newset, SIGCHLD); sigaddset(&newset, SIGUSR1); sigaddset(&newset, SIGUSR2); sigaddset(&newset, SIGHUP); sigaddset(&newset, SIGQUIT); sigaddset(&newset, SIGINT); sigaddset(&newset, SIGTERM); # ifdef SIGPWR sigaddset(&newset, SIGPWR); # endif pthread_sigmask(SIG_BLOCK, &newset, NULL); #endif _ecore_signal_pipe_shutdown(); exit_signal_received = 0; } void _ecore_signal_received_process(Eo *obj EINA_UNUSED, Efl_Loop_Data *pd EINA_UNUSED) { // do nothing - the efl loop handler read event will handle it } int _ecore_signal_count_get(Eo *obj EINA_UNUSED, Efl_Loop_Data *pd EINA_UNUSED) { // we will always have 0 signals be3cause they will instead be read from // a pipe fd and placed in a queue/list that // _ecore_signal_received_process() will then walk and process/do return 0; } void _ecore_signal_pid_lock(void) { eina_spinlock_take(&sig_pid_lock); } void _ecore_signal_pid_unlock(void) { eina_spinlock_release(&sig_pid_lock); } void _ecore_signal_pid_register(pid_t pid, int fd) { Pid_Info *pi = calloc(1, sizeof(Pid_Info)); if (!pi) return; pi->pid = pid; pi->fd = fd; sig_pid_info_list = eina_list_append(sig_pid_info_list, pi); } void _ecore_signal_pid_unregister(pid_t pid, int fd) { Eina_List *l; Pid_Info *pi; EINA_LIST_FOREACH(sig_pid_info_list, l, pi) { if ((pi->pid == pid) && (pi->fd == fd)) { sig_pid_info_list = eina_list_remove_list(sig_pid_info_list, l); free(pi); return; } } } static void _ecore_signal_exe_exit_delay(void *data, const Efl_Event *event) { Ecore_Exe_Event_Del *e = data; if (!e) return; _ecore_exe_doomsday_clock_set(e->exe, NULL); ecore_event_add(ECORE_EXE_EVENT_DEL, e, _ecore_exe_event_del_free, NULL); efl_del(event->object); } static void _ecore_signal_waitpid(Eina_Bool once, siginfo_t info) { pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { Ecore_Exe_Event_Del *e = _ecore_exe_event_del_new(); //FIXME: If this process is set respawn, respawn with a suitable backoff // period for those that need too much respawning. if (e) { if (WIFEXITED(status)) { e->exit_code = WEXITSTATUS(status); e->exited = 1; } else if (WIFSIGNALED(status)) { e->exit_signal = WTERMSIG(status); e->signalled = 1; } e->pid = pid; e->exe = _ecore_exe_find(pid); e->data = info; // No need to clone this. if ((e->exe) && (ecore_exe_flags_get(e->exe) & (ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_ERROR))) { /* We want to report the Last Words of the exe, so delay this event. * This is twice as relevant for stderr. * There are three possibilities here - * 1 There are no Last Words. * 2 There are Last Words, they are not ready to be read. * 3 There are Last Words, they are ready to be read. * * For 1 we don't want to delay, for 3 we want to delay. * 2 is the problem. If we check for data now and there * is none, then there is no way to differentiate 1 and 2. * If we don't delay, we may loose data, but if we do delay, * there may not be data and the exit event never gets sent. * * Any way you look at it, there has to be some time passed * before the exit event gets sent. So the strategy here is * to setup a timer event that will send the exit event after * an arbitrary, but brief, time. * * This is probably paranoid, for the less paraniod, we could * check to see for Last Words, and only delay if there are any. * This has it's own set of problems. */ efl_del(_ecore_exe_doomsday_clock_get(e->exe)); Efl_Loop_Timer *doomsday_clock = efl_add(EFL_LOOP_TIMER_CLASS, ML_OBJ, efl_loop_timer_interval_set(efl_added, 0.1), efl_event_callback_add (efl_added, EFL_LOOP_TIMER_EVENT_TIMER_TICK, _ecore_signal_exe_exit_delay, e)); _ecore_exe_doomsday_clock_set(e->exe, doomsday_clock); } else ecore_event_add(ECORE_EXE_EVENT_DEL, e, _ecore_exe_event_del_free, NULL); } // XXX: this is not brilliant. this ends up running from the main loop // reading the signal pipe to handle signals. that means handling // exe exits from children will be bottlenecked by how often // the main loop can wake up (or well latency may not be great). // this should probably have a dedicated thread ythat does a waitpid() // and blocks and waits sending results to the resulting pipe Eina_List *l, *ll; Pid_Info *pi; EINA_LIST_FOREACH_SAFE(sig_pid_info_list, l, ll, pi) { if (pi->pid == pid) { Ecore_Signal_Pid_Info pinfo; sig_pid_info_list = eina_list_remove_list (sig_pid_info_list, ll); pinfo.pid = pid; pinfo.info = info; if (WIFEXITED(status)) { pinfo.exit_code = WEXITSTATUS(status); pinfo.exit_signal = -1; } else if (WIFSIGNALED(status)) { pinfo.exit_code = -1; pinfo.exit_signal = WTERMSIG(status); } if (write(pi->fd, &pinfo, sizeof(Ecore_Signal_Pid_Info)) != sizeof(Ecore_Signal_Pid_Info)) { ERR("Can't write to custom exe exit info pipe"); } free(pi); break; } } if (once) break; } } static void _ecore_signal_generic_free(void *data EINA_UNUSED, void *event) { free(event); }