From 762e047ec9c4d1dad7c8f72099fa59f1fdf0ab50 Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Sun, 12 Dec 2021 01:01:42 +0200 Subject: [PATCH] Kernel+LibC: Implement sigtimedwait() This includes a new Thread::Blocker called SignalBlocker which blocks until a signal of a matching type is pending. The current Blocker implementation in the Kernel is very complicated, but cleaning it up is a different yak for a different day. --- Kernel/API/Syscall.h | 1 + Kernel/Process.h | 1 + Kernel/Syscalls/sigaction.cpp | 31 ++++++++++++++++++++++ Kernel/Thread.cpp | 1 + Kernel/Thread.h | 37 ++++++++++++++++++++++++++ Kernel/ThreadBlockers.cpp | 42 ++++++++++++++++++++++++++++++ Userland/Libraries/LibC/signal.cpp | 6 +++++ Userland/Libraries/LibC/signal.h | 1 + 8 files changed, 120 insertions(+) diff --git a/Kernel/API/Syscall.h b/Kernel/API/Syscall.h index bacfc5c750..8d6c97276e 100644 --- a/Kernel/API/Syscall.h +++ b/Kernel/API/Syscall.h @@ -176,6 +176,7 @@ enum class NeedsBigProcessLock { S(sigpending, NeedsBigProcessLock::Yes) \ S(sigprocmask, NeedsBigProcessLock::Yes) \ S(sigreturn, NeedsBigProcessLock::Yes) \ + S(sigtimedwait, NeedsBigProcessLock::Yes) \ S(socket, NeedsBigProcessLock::Yes) \ S(socketpair, NeedsBigProcessLock::Yes) \ S(stat, NeedsBigProcessLock::Yes) \ diff --git a/Kernel/Process.h b/Kernel/Process.h index bd668c1dfb..43d4b0e478 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -338,6 +338,7 @@ public: ErrorOr sys$sigaltstack(Userspace ss, Userspace old_ss); ErrorOr sys$sigprocmask(int how, Userspace set, Userspace old_set); ErrorOr sys$sigpending(Userspace); + ErrorOr sys$sigtimedwait(Userspace, Userspace, Userspace); ErrorOr sys$getgroups(size_t, Userspace); ErrorOr sys$setgroups(size_t, Userspace); ErrorOr sys$pipe(int pipefd[2], int flags); diff --git a/Kernel/Syscalls/sigaction.cpp b/Kernel/Syscalls/sigaction.cpp index 343ff92e1e..7d30e577f4 100644 --- a/Kernel/Syscalls/sigaction.cpp +++ b/Kernel/Syscalls/sigaction.cpp @@ -307,4 +307,35 @@ ErrorOr Process::sys$sigaltstack(Userspace ss, Userspac return 0; } +// https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigtimedwait.html +ErrorOr Process::sys$sigtimedwait(Userspace set, Userspace info, Userspace timeout) +{ + VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this) + REQUIRE_PROMISE(sigaction); + + sigset_t set_value; + TRY(copy_from_user(&set_value, set)); + + Thread::BlockTimeout block_timeout = {}; + if (timeout) { + auto timeout_time = TRY(copy_time_from_user(timeout)); + block_timeout = Thread::BlockTimeout(false, &timeout_time); + } + + siginfo_t info_value = {}; + auto block_result = Thread::current()->block(block_timeout, set_value, info_value); + if (block_result.was_interrupted()) + return EINTR; + // We check for an unset signal instead of directly checking for a timeout interruption + // in order to allow polling the pending signals by setting the timeout to 0. + if (info_value.si_signo == SIGINVAL) { + VERIFY(block_result == Thread::BlockResult::InterruptedByTimeout); + return EAGAIN; + } + + if (info) + TRY(copy_to_user(info, &info_value)); + return info_value.si_signo; +} + } diff --git a/Kernel/Thread.cpp b/Kernel/Thread.cpp index d0103ffb87..15bfd135b9 100644 --- a/Kernel/Thread.cpp +++ b/Kernel/Thread.cpp @@ -637,6 +637,7 @@ void Thread::send_signal(u8 signal, [[maybe_unused]] Process* sender) m_pending_signals |= 1 << (signal - 1); m_have_any_unmasked_pending_signals.store((pending_signals_for_state() & ~m_signal_mask) != 0, AK::memory_order_release); + m_signal_blocker_set.unblock_all_blockers_whose_conditions_are_met(); if (!has_unmasked_pending_signals()) return; diff --git a/Kernel/Thread.h b/Kernel/Thread.h index a225191ca5..d085790203 100644 --- a/Kernel/Thread.h +++ b/Kernel/Thread.h @@ -292,6 +292,7 @@ public: Queue, Routing, Sleep, + Signal, Wait }; virtual ~Blocker(); @@ -698,6 +699,41 @@ public: bool m_did_unblock { false }; }; + class SignalBlocker final : public Blocker { + public: + explicit SignalBlocker(sigset_t pending_set, siginfo_t& result); + virtual StringView state_string() const override { return "Pending Signal"sv; } + virtual Type blocker_type() const override { return Type::Signal; } + void will_unblock_immediately_without_blocking(UnblockImmediatelyReason) override; + virtual bool setup_blocker() override; + bool check_pending_signals(bool from_add_blocker); + + private: + sigset_t m_pending_set { 0 }; + siginfo_t& m_result; + bool m_did_unblock { false }; + }; + + class SignalBlockerSet final : public BlockerSet { + public: + void unblock_all_blockers_whose_conditions_are_met() + { + BlockerSet::unblock_all_blockers_whose_conditions_are_met([&](auto& b, void*, bool&) { + VERIFY(b.blocker_type() == Blocker::Type::Signal); + auto& blocker = static_cast(b); + return blocker.check_pending_signals(false); + }); + } + + private: + bool should_add_blocker(Blocker& b, void*) override + { + VERIFY(b.blocker_type() == Blocker::Type::Signal); + auto& blocker = static_cast(b); + return !blocker.check_pending_signals(true); + } + }; + class WaitBlocker final : public Blocker { public: enum class UnblockFlags { @@ -1302,6 +1338,7 @@ private: u32 m_signal_mask { 0 }; FlatPtr m_alternative_signal_stack { 0 }; FlatPtr m_alternative_signal_stack_size { 0 }; + SignalBlockerSet m_signal_blocker_set; FlatPtr m_kernel_stack_base { 0 }; FlatPtr m_kernel_stack_top { 0 }; OwnPtr m_kernel_stack_region; diff --git a/Kernel/ThreadBlockers.cpp b/Kernel/ThreadBlockers.cpp index 111746de08..2ad4109a82 100644 --- a/Kernel/ThreadBlockers.cpp +++ b/Kernel/ThreadBlockers.cpp @@ -446,6 +446,48 @@ void Thread::SelectBlocker::was_unblocked(bool did_timeout) } } +Thread::SignalBlocker::SignalBlocker(sigset_t pending_set, siginfo_t& result) + : m_pending_set(pending_set) + , m_result(result) +{ +} + +void Thread::SignalBlocker::will_unblock_immediately_without_blocking(UnblockImmediatelyReason unblock_immediately_reason) +{ + if (unblock_immediately_reason != UnblockImmediatelyReason::TimeoutInThePast) + return; + // If the specified timeout is 0 the caller is simply trying to poll once for pending signals, + // so simply calling check_pending_signals should populate the requested information. + check_pending_signals(false); +} + +bool Thread::SignalBlocker::setup_blocker() +{ + return add_to_blocker_set(thread().m_signal_blocker_set); +} + +bool Thread::SignalBlocker::check_pending_signals(bool from_add_blocker) +{ + { + SpinlockLocker lock(m_lock); + if (m_did_unblock) + return false; + + auto matching_pending_signal = __builtin_ffsl(thread().pending_signals() & m_pending_set); + if (matching_pending_signal == 0) + return false; + + m_did_unblock = true; + m_result = {}; + m_result.si_signo = matching_pending_signal; + m_result.si_code = 0; // FIXME: How can we determine this? + } + + if (!from_add_blocker) + unblock_from_blocker(); + return true; +} + Thread::WaitBlockerSet::ProcessBlockInfo::ProcessBlockInfo(NonnullRefPtr&& process, WaitBlocker::UnblockFlags flags, u8 signal) : process(move(process)) , flags(flags) diff --git a/Userland/Libraries/LibC/signal.cpp b/Userland/Libraries/LibC/signal.cpp index 3f43db5496..20561ed6f0 100644 --- a/Userland/Libraries/LibC/signal.cpp +++ b/Userland/Libraries/LibC/signal.cpp @@ -163,6 +163,12 @@ int sigsuspend(const sigset_t* set) return pselect(0, nullptr, nullptr, nullptr, nullptr, set); } +int sigtimedwait(sigset_t const* set, siginfo_t* info, struct timespec const* timeout) +{ + int rc = syscall(Syscall::SC_sigtimedwait, set, info, timeout); + __RETURN_WITH_ERRNO(rc, rc, -1); +} + const char* sys_signame[] = { "INVAL", "HUP", diff --git a/Userland/Libraries/LibC/signal.h b/Userland/Libraries/LibC/signal.h index e11bf80b8d..f582a9e94c 100644 --- a/Userland/Libraries/LibC/signal.h +++ b/Userland/Libraries/LibC/signal.h @@ -27,6 +27,7 @@ int sigismember(const sigset_t*, int sig); int sigprocmask(int how, const sigset_t* set, sigset_t* old_set); int sigpending(sigset_t*); int sigsuspend(const sigset_t*); +int sigtimedwait(sigset_t const*, siginfo_t*, struct timespec const*); int raise(int sig); int getsignalbyname(const char*); const char* getsignalname(int);