|
| 1 | +/* |
| 2 | + * Copyright (C) 2026 Greg Burd |
| 3 | + * |
| 4 | + * This work is open source software, licensed under the terms of the |
| 5 | + * BSD license as described in the LICENSE file in the top-level directory. |
| 6 | + */ |
| 7 | + |
| 8 | +// signalfd(2): deliver signals to a file descriptor instead of via a handler. |
| 9 | +// |
| 10 | +// OSv is a single process, so a signalfd watches a set of signals for the whole |
| 11 | +// image. When kill() raises a signal that some signalfd is watching, the |
| 12 | +// signal is queued to a single (arbitrary) watching fd (and its pollers woken) |
| 13 | +// instead of running the default action or a handler. This approximates Linux, |
| 14 | +// where a blocked signal is consumed by one reader rather than broadcast to |
| 15 | +// every signalfd; the fd chosen here is arbitrary (registry iteration order), |
| 16 | +// and the case of two signalfds watching the same signal diverges from Linux's |
| 17 | +// per-thread pending-set accounting. |
| 18 | +// |
| 19 | +// Note on Linux fidelity: on Linux a signal is consumed by a signalfd only |
| 20 | +// when it is blocked in the caller's signal mask; an unblocked signal still |
| 21 | +// runs its handler/default action. OSv's signal model is already a |
| 22 | +// simplification (see libc/signal.cc - kill() runs the handler on a fresh |
| 23 | +// thread rather than a designated one), and it does not track a precise |
| 24 | +// per-thread blocked mask here, so a watched signal is consumed by the |
| 25 | +// signalfd unconditionally. This is sufficient for the common use (an app |
| 26 | +// blocks the signals it hands to a signalfd and reads them through the fd) but |
| 27 | +// is not bit-for-bit Linux semantics. SIGKILL and SIGSTOP are filtered from |
| 28 | +// the watched mask so they always keep their uncatchable default behavior. |
| 29 | + |
| 30 | +#include <sys/signalfd.h> |
| 31 | +#include <fs/fs.hh> |
| 32 | +#include <libc/libc.hh> |
| 33 | +#include <osv/fcntl.h> |
| 34 | +#include <osv/mutex.h> |
| 35 | +#include <osv/condvar.h> |
| 36 | +#include <osv/poll.h> |
| 37 | +#include <osv/signal.hh> |
| 38 | + |
| 39 | +#include <unistd.h> |
| 40 | +#include <string.h> |
| 41 | + |
| 42 | +#include <deque> |
| 43 | +#include <set> |
| 44 | +#include <signal.h> |
| 45 | +#include <errno.h> |
| 46 | + |
| 47 | +namespace { |
| 48 | + |
| 49 | +class signal_fd; |
| 50 | + |
| 51 | +// Registry of all live signalfd objects, so kill() can find the ones watching a |
| 52 | +// given signal. Guarded by its own lock. |
| 53 | +mutex registry_lock; |
| 54 | +std::set<signal_fd*> registry; |
| 55 | + |
| 56 | +class signal_fd final : public special_file { |
| 57 | +public: |
| 58 | + signal_fd(const sigset_t& mask, int flags) |
| 59 | + : special_file(FREAD | flags, DTYPE_UNSPEC), _mask(mask) |
| 60 | + { |
| 61 | + WITH_LOCK(registry_lock) { registry.insert(this); } |
| 62 | + } |
| 63 | + |
| 64 | + int close() override { |
| 65 | + WITH_LOCK(registry_lock) { registry.erase(this); } |
| 66 | + return 0; |
| 67 | + } |
| 68 | + |
| 69 | + int read(struct uio* uio, int flags) override; |
| 70 | + int poll(int events) override; |
| 71 | + |
| 72 | + // Is this fd watching signal `signo` (1-based)? |
| 73 | + bool watches(int signo) const { |
| 74 | + return sigismember(&_mask, signo) == 1; |
| 75 | + } |
| 76 | + |
| 77 | + // Called from the signal path to enqueue a delivered signal. |
| 78 | + void deliver(const signalfd_siginfo& si) { |
| 79 | + WITH_LOCK(_mutex) { |
| 80 | + _queue.push_back(si); |
| 81 | + _blocked_reader.wake_all(); |
| 82 | + } |
| 83 | + poll_wake(this, POLLIN); |
| 84 | + } |
| 85 | + |
| 86 | + // Update the watched-signal set. Taken under registry_lock (the same lock |
| 87 | + // held while the signal-dispatch path calls watches()), so a concurrent |
| 88 | + // signalfd() mask update cannot race a delivery's read of _mask. |
| 89 | + void set_mask(const sigset_t& mask) { |
| 90 | + WITH_LOCK(registry_lock) { _mask = mask; } |
| 91 | + } |
| 92 | + |
| 93 | +private: |
| 94 | + mutable mutex _mutex; |
| 95 | + sigset_t _mask; |
| 96 | + condvar _blocked_reader; |
| 97 | + std::deque<signalfd_siginfo> _queue; |
| 98 | +}; |
| 99 | + |
| 100 | +int signal_fd::read(struct uio* uio, int flags) |
| 101 | +{ |
| 102 | + if (uio->uio_resid < (ssize_t)sizeof(signalfd_siginfo)) { |
| 103 | + return EINVAL; |
| 104 | + } |
| 105 | + |
| 106 | + WITH_LOCK(_mutex) { |
| 107 | + while (_queue.empty()) { |
| 108 | + if (f_flags & FNONBLOCK) { |
| 109 | + return EAGAIN; |
| 110 | + } |
| 111 | + _blocked_reader.wait(_mutex); |
| 112 | + } |
| 113 | + // Copy out as many queued siginfos as fit in the caller's buffer. |
| 114 | + // Peek, copy, and only dequeue after a successful copy so a failed |
| 115 | + // copy-out never silently drops a queued signal. (OSv's uiomove uses |
| 116 | + // memcpy and does not currently fail, but keep the ordering correct.) |
| 117 | + while (!_queue.empty() && |
| 118 | + uio->uio_resid >= (ssize_t)sizeof(signalfd_siginfo)) { |
| 119 | + signalfd_siginfo si = _queue.front(); |
| 120 | + int err = uiomove(&si, sizeof(si), uio); |
| 121 | + if (err) { |
| 122 | + // If nothing was copied yet, report the error; otherwise the |
| 123 | + // bytes already copied are a valid short read. |
| 124 | + return err; |
| 125 | + } |
| 126 | + _queue.pop_front(); |
| 127 | + } |
| 128 | + } |
| 129 | + return 0; |
| 130 | +} |
| 131 | + |
| 132 | +int signal_fd::poll(int events) |
| 133 | +{ |
| 134 | + int rc = 0; |
| 135 | + WITH_LOCK(_mutex) { |
| 136 | + if (!_queue.empty() && (events & POLLIN)) { |
| 137 | + rc |= POLLIN; |
| 138 | + } |
| 139 | + } |
| 140 | + return rc; |
| 141 | +} |
| 142 | + |
| 143 | +} // anonymous namespace |
| 144 | + |
| 145 | +// Called by kill() (libc/signal.cc) when a signal is raised. Returns true if a |
| 146 | +// signalfd consumed the signal (queued to exactly one arbitrary watching fd), |
| 147 | +// in which case the caller must not run the default action or a handler. |
| 148 | +bool osv_signalfd_deliver(int signo) |
| 149 | +{ |
| 150 | + signalfd_siginfo si; |
| 151 | + memset(&si, 0, sizeof(si)); |
| 152 | + si.ssi_signo = signo; |
| 153 | + si.ssi_pid = getpid(); |
| 154 | + |
| 155 | + // Deliver to a single (arbitrary) watching signalfd: approximate Linux, |
| 156 | + // where a blocked signal is consumed by one reader rather than broadcast to |
| 157 | + // every signalfd. Stop at the first match (registry iteration order). |
| 158 | + WITH_LOCK(registry_lock) { |
| 159 | + for (auto* sfd : registry) { |
| 160 | + if (sfd->watches(signo)) { |
| 161 | + sfd->deliver(si); |
| 162 | + return true; |
| 163 | + } |
| 164 | + } |
| 165 | + } |
| 166 | + return false; |
| 167 | +} |
| 168 | + |
| 169 | +extern "C" OSV_LIBC_API |
| 170 | +int signalfd(int fd, const sigset_t* mask, int flags) |
| 171 | +{ |
| 172 | + if (!mask || (flags & ~(SFD_CLOEXEC | SFD_NONBLOCK))) { |
| 173 | + return libc_error(EINVAL); |
| 174 | + } |
| 175 | + |
| 176 | + // SIGKILL and SIGSTOP cannot be caught, blocked, or consumed via a |
| 177 | + // signalfd (as with sigprocmask); drop them from the effective mask so they |
| 178 | + // always retain their default (uncatchable) behavior rather than being |
| 179 | + // silently swallowed by a signalfd reader. |
| 180 | + sigset_t effective = *mask; |
| 181 | + sigdelset(&effective, SIGKILL); |
| 182 | + sigdelset(&effective, SIGSTOP); |
| 183 | + |
| 184 | + int of = 0; |
| 185 | + if (flags & SFD_NONBLOCK) { |
| 186 | + of |= O_NONBLOCK; |
| 187 | + } |
| 188 | + if (flags & SFD_CLOEXEC) { |
| 189 | + of |= O_CLOEXEC; |
| 190 | + } |
| 191 | + |
| 192 | + if (fd == -1) { |
| 193 | + // Create a new signalfd. |
| 194 | + try { |
| 195 | + fileref f = make_file<signal_fd>(effective, of); |
| 196 | + fdesc newfd(f); |
| 197 | + return newfd.release(); |
| 198 | + } catch (int error) { |
| 199 | + return libc_error(error); |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + // Update the mask of an existing signalfd. (Per the signalfd(2) man page |
| 204 | + // the flags argument is only meaningful when creating a new fd, so it is |
| 205 | + // not re-applied here.) |
| 206 | + fileref f(fileref_from_fd(fd)); |
| 207 | + if (!f) { |
| 208 | + return libc_error(EBADF); |
| 209 | + } |
| 210 | + auto* sfd = dynamic_cast<signal_fd*>(f.get()); |
| 211 | + if (!sfd) { |
| 212 | + return libc_error(EINVAL); |
| 213 | + } |
| 214 | + sfd->set_mask(effective); |
| 215 | + return fd; |
| 216 | +} |
0 commit comments