blob: 9f3c97267c4ce37335041c78e369c0fdb1e2dcc2 [file] [log] [blame]
#include "aos/ipc_lib/signalfd.h"
#include <string.h>
#include <sys/types.h>
#include <csignal>
#include <ostream>
#if __has_feature(memory_sanitizer)
#include <sanitizer/msan_interface.h>
#endif
#include <unistd.h>
#include <initializer_list>
#include "absl/log/check.h"
#include "absl/log/log.h"
namespace aos::ipc_lib {
namespace {
// Wrapper which propagates msan information.
// TODO(Brian): Drop this once we have <https://reviews.llvm.org/D82411> to
// intercept this function natively.
int wrapped_sigandset(sigset_t *dest, const sigset_t *left,
const sigset_t *right) {
#if __has_feature(memory_sanitizer)
if (left) {
__msan_check_mem_is_initialized(left, sizeof(*left));
}
if (right) {
__msan_check_mem_is_initialized(right, sizeof(*right));
}
#endif
const int r = sigandset(dest, left, right);
#if __has_feature(memory_sanitizer)
if (!r && dest) {
__msan_unpoison(dest, sizeof(*dest));
}
#endif
return r;
}
// Wrapper which propagates msan information.
// TODO(Brian): Drop this once we have
// <https://reviews.llvm.org/rG89ae290b58e20fc5f56b7bfae4b34e7fef06e1b1> to
// intercept this function natively.
int wrapped_pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset) {
#if __has_feature(memory_sanitizer)
if (set) {
__msan_check_mem_is_initialized(set, sizeof(*set));
}
#endif
const int r = pthread_sigmask(how, set, oldset);
#if __has_feature(memory_sanitizer)
if (!r && oldset) {
__msan_unpoison(oldset, sizeof(*oldset));
}
#endif
return r;
}
} // namespace
SignalFd::SignalFd(::std::initializer_list<unsigned int> signals) {
// Build up the mask with the provided signals.
CHECK_EQ(0, sigemptyset(&blocked_mask_));
for (int signal : signals) {
CHECK_EQ(0, sigaddset(&blocked_mask_, signal));
}
// Then build a signalfd. Make it nonblocking so it works well with an epoll
// loop, and have it close on exec.
PCHECK((fd_ = signalfd(-1, &blocked_mask_, SFD_NONBLOCK | SFD_CLOEXEC)) != 0);
// Now that we have a consumer of the signal, block the signals so the
// signalfd gets them. Record which ones we actually blocked, so we can
// unblock just those later.
sigset_t old_mask;
CHECK_EQ(0, wrapped_pthread_sigmask(SIG_BLOCK, &blocked_mask_, &old_mask));
for (int signal : signals) {
if (sigismember(&old_mask, signal)) {
LeaveSignalBlocked(signal);
}
}
}
namespace {
// sizeof(sigset_t) is larger than the bytes actually used to represent all
// signals. This size is only the bytes initialized. _NSIG is 1-indexed.
static constexpr size_t kSigSetSize = (_NSIG - 1) / 8;
// If the size of the mask changes, we should check that we still have
// correct behavior.
static_assert(kSigSetSize == 8 && kSigSetSize <= sizeof(sigset_t));
} // namespace
SignalFd::~SignalFd() {
// Unwind the constructor. Unblock the signals and close the fd. Verify nobody
// else unblocked the signals we're supposed to unblock in the meantime.
sigset_t old_mask;
CHECK_EQ(0, wrapped_pthread_sigmask(SIG_UNBLOCK, &blocked_mask_, &old_mask));
sigset_t unblocked_mask;
CHECK_EQ(0, wrapped_sigandset(&unblocked_mask, &blocked_mask_, &old_mask));
if (memcmp(&unblocked_mask, &blocked_mask_, kSigSetSize) != 0) {
LOG(FATAL) << "Some other code unblocked one or more of our signals";
}
PCHECK(close(fd_) == 0);
}
signalfd_siginfo SignalFd::Read() {
signalfd_siginfo result;
const int ret =
read(fd_, static_cast<void *>(&result), sizeof(signalfd_siginfo));
// If we didn't get the right amount of data, signal that there was a problem
// by setting the signal number to 0.
if (ret != static_cast<int>(sizeof(signalfd_siginfo))) {
result.ssi_signo = 0;
} else {
CHECK_NE(0u, result.ssi_signo);
}
return result;
}
void SignalFd::LeaveSignalBlocked(unsigned int signal) {
CHECK_EQ(0, sigdelset(&blocked_mask_, signal));
}
} // namespace aos::ipc_lib