/* * * Embedded Linux library * * Copyright (C) 2011-2014 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "util.h" #include "io.h" #include "queue.h" #include "signal.h" #include "private.h" /** * SECTION:signal * @short_description: Unix signal support * * Unix signal support */ /** * l_signal: * * Opague object representing the signal. */ struct l_signal { struct signal_desc *desc; l_signal_notify_cb_t callback; void *user_data; l_signal_destroy_cb_t destroy; }; struct signal_desc { uint32_t signo; struct l_queue *callbacks; }; static struct l_io *signalfd_io = NULL; static struct l_queue *signal_list = NULL; static sigset_t signal_mask; static void handle_callback(struct signal_desc *desc) { const struct l_queue_entry *entry; for (entry = l_queue_get_entries(desc->callbacks); entry; entry = entry->next) { struct l_signal *signal = entry->data; if (signal->callback) signal->callback(signal->user_data); } } static bool desc_match_signo(const void *a, const void *b) { const struct signal_desc *desc = a; uint32_t signo = L_PTR_TO_UINT(b); return (desc->signo == signo); } static bool signalfd_read_cb(struct l_io *io, void *user_data) { int fd = l_io_get_fd(io); struct signal_desc *desc; struct signalfd_siginfo si; ssize_t result; result = read(fd, &si, sizeof(si)); if (result != sizeof(si)) return true; desc = l_queue_find(signal_list, desc_match_signo, L_UINT_TO_PTR(si.ssi_signo)); if (desc) handle_callback(desc); return true; } static bool signalfd_add(int signo) { int fd; if (!signalfd_io) { fd = -1; sigemptyset(&signal_mask); } else fd = l_io_get_fd(signalfd_io); sigaddset(&signal_mask, signo); fd = signalfd(fd, &signal_mask, SFD_CLOEXEC); if (fd < 0) return false; if (signalfd_io) return true; signalfd_io = l_io_new(fd); if (!signalfd_io) { close(fd); return false; } l_io_set_close_on_destroy(signalfd_io, true); if (!l_io_set_read_handler(signalfd_io, signalfd_read_cb, NULL, NULL)) { l_io_destroy(signalfd_io); return false; } signal_list = l_queue_new(); return true; } static void signalfd_remove(int signo) { if (!signalfd_io) return; sigdelset(&signal_mask, signo); if (!sigisemptyset(&signal_mask)) { signalfd(l_io_get_fd(signalfd_io), &signal_mask, SFD_CLOEXEC); return; } l_io_destroy(signalfd_io); signalfd_io = NULL; l_queue_destroy(signal_list, NULL); signal_list = NULL; } /** * l_signal_create: * @callback: signal callback function * @user_data: user data provided to signal callback function * @destroy: destroy function for user data * * Create new signal callback handling for a given set of signals. * * Returns: a newly allocated #l_signal object **/ LIB_EXPORT struct l_signal *l_signal_create(uint32_t signo, l_signal_notify_cb_t callback, void *user_data, l_signal_destroy_cb_t destroy) { struct l_signal *signal; struct signal_desc *desc; sigset_t mask, oldmask; if (signo <= 1 || signo >= _NSIG) return NULL; signal = l_new(struct l_signal, 1); signal->callback = callback; signal->destroy = destroy; signal->user_data = user_data; desc = l_queue_find(signal_list, desc_match_signo, L_UINT_TO_PTR(signo)); if (desc) goto done; sigemptyset(&mask); sigaddset(&mask, signo); if (sigprocmask(SIG_BLOCK, &mask, &oldmask) < 0) { l_free(signal); return NULL; } if (!signalfd_add(signo)) { sigprocmask(SIG_SETMASK, &oldmask, NULL); l_free(signal); return NULL; } desc = l_new(struct signal_desc, 1); desc->signo = signo; desc->callbacks = l_queue_new(); l_queue_push_tail(signal_list, desc); done: l_queue_push_tail(desc->callbacks, signal); signal->desc = desc; return signal; } /** * l_signal_remove: * @signal: signal object * * Remove signal handling. **/ LIB_EXPORT void l_signal_remove(struct l_signal *signal) { struct signal_desc *desc; sigset_t mask; if (!signal) return; desc = signal->desc; l_queue_remove(desc->callbacks, signal); /* * As long as the signal descriptor has callbacks registered, it is * still needed to be active. */ if (!l_queue_isempty(desc->callbacks)) goto done; if (!l_queue_remove(signal_list, desc)) goto done; sigemptyset(&mask); sigaddset(&mask, desc->signo); /* * When the number of signals goes to zero, then this will close * the signalfd file descriptor, otherwise it will only adjust the * signal mask to account for the removed signal. * */ signalfd_remove(desc->signo); sigprocmask(SIG_UNBLOCK, &mask, NULL); l_queue_destroy(desc->callbacks, NULL); l_free(desc); done: if (signal->destroy) signal->destroy(signal->user_data); l_free(signal); }