Nexoniarz is the sole author and SchOS was never published under Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text, every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE, README.md, docs/licensing.md and the site say so. projects/schfs-linux follows the rest of the tree. The GPL gives copyleft: a fork has to ship its source. It also lifts the old "Linux drivers, never" rule, since GPL-2.0-only code combines with GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still has to pick one version, so docs/licensing.md now keeps Apache-2.0 and GPLv3 code out of the kernel to keep Linux code possible there. BSD 4-clause code goes from "avoid" to "no", because the advertising clause is GPL-incompatible. LDLS still ships no .ko. That boundary is now project policy rather than a legal requirement, and ldls.md, devices.md and licensing.md section 6 say so. refmod declares license=GPL, and ldls-check passes (27 checks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
5.1 KiB
C
149 lines
5.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// sigtest -- the SIGCHLD/sigsuspend contract every shell is built on.
|
|
//
|
|
// This is not one shell's quirk. It is what POSIX says to do and what zsh,
|
|
// mksh, bash and dash all do to wait for a child:
|
|
//
|
|
// sigprocmask(SIG_BLOCK, {SIGCHLD}, &old); // close the window
|
|
// pid = fork(); // child may exit at once
|
|
// while (!flag) // flag set by the handler
|
|
// sigsuspend(&old_without_SIGCHLD); // unblock and wait, atomically
|
|
// sigprocmask(SIG_SETMASK, &old, 0);
|
|
//
|
|
// Blocking first is the whole point: the child can exit *before* the parent
|
|
// reaches sigsuspend. Blocked, the signal stays pending; sigsuspend then
|
|
// unblocks and waits in one step, so a pending signal is delivered at once and
|
|
// the wait returns immediately. Get that wrong in either direction and the
|
|
// parent waits for a signal that has already happened -- which is a hang, in
|
|
// every shell, on every `$(command substitution)`.
|
|
//
|
|
// Written against the raw syscalls rather than a libc, because the thing under
|
|
// test is the kernel's half of the contract. **The user-facing sigset has
|
|
// signal N at bit N-1** (Linux's layout); the kernel converts at the boundary.
|
|
// Getting that backwards is a real bug this tree has had, so the masks below
|
|
// are spelled out rather than built by a macro.
|
|
|
|
#include "types.h"
|
|
#include "user.h"
|
|
#include "posix.h"
|
|
|
|
#define U_SIGCHLD_BIT (1ULL << (SIGCHLD - 1))
|
|
|
|
// rt_sigaction takes a pointer to Linux's struct sigaction, not a function
|
|
// pointer: the handler is at offset 0 and the restorer at 144. The kernel
|
|
// reads exactly those two words (SA_HANDLER_OFF / SA_RESTORER_OFF in
|
|
// kernel/sys/sys_sig.c), so a 152-byte zeroed block with the handler written
|
|
// into the first word is the whole of what it needs.
|
|
#define SA_SIZE 152
|
|
|
|
static int failures;
|
|
static volatile int handler_runs;
|
|
|
|
static void
|
|
puts_(const char *s)
|
|
{
|
|
int n = 0;
|
|
while (s[n]) n++;
|
|
write(1, (void *)s, n);
|
|
}
|
|
|
|
static void
|
|
check(const char *what, int ok)
|
|
{
|
|
puts_(ok ? "[ok] " : "[FAIL] ");
|
|
puts_(what);
|
|
puts_("\n");
|
|
if (!ok) failures++;
|
|
}
|
|
|
|
static void
|
|
on_chld(int sig)
|
|
{
|
|
(void)sig;
|
|
handler_runs++;
|
|
}
|
|
|
|
// A bounded wait. A wrong answer here is a hang, so nothing may wait for ever:
|
|
// sigsuspend returns -EINTR each time a signal is delivered, and a kernel that
|
|
// never delivers spins out instead of stopping the test dead.
|
|
#define SPINS 300
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
uint64 blocked, unblocked, old = 0;
|
|
int pid, status, i;
|
|
|
|
puts_("sigtest: the SIGCHLD/sigsuspend contract\n\n");
|
|
|
|
{
|
|
static char act[SA_SIZE];
|
|
*(uint64 *)act = (uint64)(void *)on_chld;
|
|
rt_sigaction(SIGCHLD, act, 0, 8);
|
|
}
|
|
|
|
// --- 1. blocking actually blocks --------------------------------------
|
|
blocked = U_SIGCHLD_BIT;
|
|
sigprocmask(SIG_BLOCK, (const sigset_t *)&blocked, (sigset_t *)&old);
|
|
|
|
handler_runs = 0;
|
|
pid = fork();
|
|
if (pid == 0)
|
|
exit_group(0);
|
|
|
|
// Every chance for the child to exit and for the kernel to deliver
|
|
// wrongly. If the mask is honoured, the handler has not run.
|
|
for (i = 0; i < 50; i++)
|
|
getpid();
|
|
sleep(10);
|
|
check("SIGCHLD stays pending while blocked", handler_runs == 0);
|
|
|
|
// --- 2. sigsuspend delivers what is already pending --------------------
|
|
// The case that matters, and the one that hangs: the child is already
|
|
// gone, so nothing new will ever arrive. sigsuspend has to deliver what is
|
|
// *pending*, not wait for the next one.
|
|
unblocked = ~U_SIGCHLD_BIT;
|
|
for (i = 0; i < SPINS && handler_runs == 0; i++)
|
|
rt_sigsuspend(&unblocked);
|
|
|
|
check("sigsuspend delivers a signal that was already pending",
|
|
handler_runs > 0);
|
|
check("and it did not have to spin to get it", i <= 1);
|
|
check("the handler ran exactly once", handler_runs == 1);
|
|
|
|
// --- 3. the mask survives ----------------------------------------------
|
|
{
|
|
uint64 now = 0;
|
|
sigprocmask(SIG_BLOCK, 0, (sigset_t *)&now);
|
|
check("sigsuspend restores the mask it was called with",
|
|
(now & U_SIGCHLD_BIT) != 0);
|
|
}
|
|
|
|
sigprocmask(SIG_SETMASK, (const sigset_t *)&old, 0);
|
|
|
|
// --- 4. the child is still reapable ------------------------------------
|
|
status = -1;
|
|
check("the child can still be reaped after the handler ran",
|
|
waitpid(pid, &status, 0) == pid);
|
|
check("and its status says it exited cleanly", status == 0);
|
|
|
|
// --- 5. the other order: the signal arrives while waiting ---------------
|
|
handler_runs = 0;
|
|
sigprocmask(SIG_BLOCK, (const sigset_t *)&blocked, (sigset_t *)&old);
|
|
pid = fork();
|
|
if (pid == 0) {
|
|
sleep(30); // let the parent reach sigsuspend first
|
|
exit_group(0);
|
|
}
|
|
for (i = 0; i < SPINS && handler_runs == 0; i++)
|
|
rt_sigsuspend(&unblocked);
|
|
check("sigsuspend wakes for a signal that arrives while it waits",
|
|
handler_runs > 0);
|
|
sigprocmask(SIG_SETMASK, (const sigset_t *)&old, 0);
|
|
waitpid(pid, &status, 0);
|
|
|
|
puts_("\n");
|
|
puts_(failures ? "sigtest: FAILED\n" : "sigtest: all checks passed\n");
|
|
return failures ? 1 : 0;
|
|
}
|