commit 02f5e354e79f38e6a9663f6e938850739b54efad
parent 94877581739a14b335f498a8e9ec31eceedf99ed
Author: Sebastian <sebastian@sebsite.pw>
Date: Wed, 10 May 2023 17:04:36 -0400
unix::signal: implement on FreeBSD
Implements: https://todo.sr.ht/~sircmpwn/hare/174
Signed-off-by: Sebastian <sebastian@sebsite.pw>
Diffstat:
3 files changed, 461 insertions(+), 1 deletion(-)
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -1474,6 +1474,11 @@ unix_signal() {
types.ha \
+linux.ha
gen_ssa -plinux unix::signal io errors rt
+
+ gen_srcs -pfreebsd unix::signal \
+ types.ha \
+ +freebsd.ha
+ gen_ssa -pfreebsd unix::signal io errors rt
}
unix_tty() {
@@ -1603,7 +1608,7 @@ unix::hosts linux freebsd
unix::passwd
unix::poll linux freebsd
unix::resolvconf linux freebsd
-unix::signal linux
+unix::signal linux freebsd
unix::tty linux freebsd
uuid"
stdlib() {
diff --git a/stdlib.mk b/stdlib.mk
@@ -740,6 +740,10 @@ stdlib_deps_freebsd += $(stdlib_unix_resolvconf_freebsd)
stdlib_unix_signal_linux = $(HARECACHE)/unix/signal/unix_signal-linux.o
stdlib_deps_linux += $(stdlib_unix_signal_linux)
+# gen_lib unix::signal (freebsd)
+stdlib_unix_signal_freebsd = $(HARECACHE)/unix/signal/unix_signal-freebsd.o
+stdlib_deps_freebsd += $(stdlib_unix_signal_freebsd)
+
# gen_lib unix::tty (linux)
stdlib_unix_tty_linux = $(HARECACHE)/unix/tty/unix_tty-linux.o
stdlib_deps_linux += $(stdlib_unix_tty_linux)
@@ -2195,6 +2199,17 @@ $(HARECACHE)/unix/signal/unix_signal-linux.ssa: $(stdlib_unix_signal_linux_srcs)
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nunix::signal \
-t$(HARECACHE)/unix/signal/unix_signal.td $(stdlib_unix_signal_linux_srcs)
+# unix::signal (+freebsd)
+stdlib_unix_signal_freebsd_srcs = \
+ $(STDLIB)/unix/signal/types.ha \
+ $(STDLIB)/unix/signal/+freebsd.ha
+
+$(HARECACHE)/unix/signal/unix_signal-freebsd.ssa: $(stdlib_unix_signal_freebsd_srcs) $(stdlib_rt) $(stdlib_io_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_rt_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/unix/signal
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nunix::signal \
+ -t$(HARECACHE)/unix/signal/unix_signal.td $(stdlib_unix_signal_freebsd_srcs)
+
# unix::tty (+linux)
stdlib_unix_tty_linux_srcs = \
$(STDLIB)/unix/tty/types.ha \
@@ -2985,6 +3000,10 @@ testlib_deps_freebsd += $(testlib_unix_resolvconf_freebsd)
testlib_unix_signal_linux = $(TESTCACHE)/unix/signal/unix_signal-linux.o
testlib_deps_linux += $(testlib_unix_signal_linux)
+# gen_lib unix::signal (freebsd)
+testlib_unix_signal_freebsd = $(TESTCACHE)/unix/signal/unix_signal-freebsd.o
+testlib_deps_freebsd += $(testlib_unix_signal_freebsd)
+
# gen_lib unix::tty (linux)
testlib_unix_tty_linux = $(TESTCACHE)/unix/tty/unix_tty-linux.o
testlib_deps_linux += $(testlib_unix_tty_linux)
@@ -4502,6 +4521,17 @@ $(TESTCACHE)/unix/signal/unix_signal-linux.ssa: $(testlib_unix_signal_linux_srcs
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nunix::signal \
-t$(TESTCACHE)/unix/signal/unix_signal.td $(testlib_unix_signal_linux_srcs)
+# unix::signal (+freebsd)
+testlib_unix_signal_freebsd_srcs = \
+ $(STDLIB)/unix/signal/types.ha \
+ $(STDLIB)/unix/signal/+freebsd.ha
+
+$(TESTCACHE)/unix/signal/unix_signal-freebsd.ssa: $(testlib_unix_signal_freebsd_srcs) $(testlib_rt) $(testlib_io_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_rt_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/unix/signal
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nunix::signal \
+ -t$(TESTCACHE)/unix/signal/unix_signal.td $(testlib_unix_signal_freebsd_srcs)
+
# unix::tty (+linux)
testlib_unix_tty_linux_srcs = \
$(STDLIB)/unix/tty/types.ha \
diff --git a/unix/signal/+freebsd.ha b/unix/signal/+freebsd.ha
@@ -0,0 +1,425 @@
+use errors;
+use io;
+use rt;
+
+// Configures a new signal handler, returning the old details (which can be
+// passed to [[restore]] to restore its behavior).
+//
+// The variadic parameters specify either [[flags]] to enable or a signal mask
+// to use via [[sigset]]; if the latter is provided no more than one may be
+// used.
+export fn handle(
+ signum: signal,
+ handler: *handler,
+ opt: (flags | sigset)...
+) sigaction = {
+ let sa_mask = newsigset();
+
+ let sa_flags = 0, nmask = 0;
+ for (let i = 0z; i < len(opt); i += 1) {
+ match (opt[i]) {
+ case let flag: flags =>
+ sa_flags |= flag: int;
+ case let mask: sigset =>
+ assert(nmask == 0, "Multiple signal masks provided to signal::handle");
+ nmask += 1;
+ sa_mask = mask;
+ };
+ };
+
+ let new = rt::sigact {
+ sa_sigaction = handler: *fn(_: int, _: *rt::siginfo, _: *void) void,
+ sa_mask = sa_mask,
+ sa_flags = sa_flags,
+ };
+ let old = rt::sigact {
+ sa_sigaction = null: *fn(_: int, _: *rt::siginfo, _: *void) void,
+ ...
+ };
+ match (rt::sigaction(signum, &new, &old)) {
+ case int =>
+ yield;
+ case rt::errno =>
+ abort("sigaction failed (invalid signal?)");
+ };
+ return old;
+};
+
+// Restores previous signal behavior following [[handle]].
+export fn restore(signum: signal, action: *sigaction) void = {
+ match (rt::sigaction(signum, action, null)) {
+ case int =>
+ yield;
+ case rt::errno =>
+ abort("sigaction failed (invalid signal?)");
+ };
+};
+
+// Unregisters signal handlers for the specified signal.
+export fn reset(signum: signal) void = {
+ handle(signum, rt::SIG_DFL: *handler);
+};
+
+// Unregisters all signal handlers.
+export fn resetall() void = {
+ // SIGKILL and SIGSTOP deliberately omitted; see sigaction(2)
+ reset(SIGHUP);
+ reset(SIGINT);
+ reset(SIGQUIT);
+ reset(SIGILL);
+ reset(SIGTRAP);
+ reset(SIGABRT);
+ reset(SIGEMT);
+ reset(SIGFPE);
+ reset(SIGBUS);
+ reset(SIGSEGV);
+ reset(SIGSYS);
+ reset(SIGPIPE);
+ reset(SIGALRM);
+ reset(SIGTERM);
+ reset(SIGURG);
+ reset(SIGTSTP);
+ reset(SIGCONT);
+ reset(SIGCHLD);
+ reset(SIGTTIN);
+ reset(SIGTTOU);
+ reset(SIGIO);
+ reset(SIGXCPU);
+ reset(SIGXFSZ);
+ reset(SIGVTALRM);
+ reset(SIGPROF);
+ reset(SIGWINCH);
+ reset(SIGINFO);
+ reset(SIGUSR1);
+ reset(SIGUSR2);
+ reset(SIGTHR);
+ reset(SIGLIBRT);
+};
+
+// Prevents given signal from arriving to the current process.
+// One common use case is to ignore SIGCHLD to avoid zombie child processes.
+export fn ignore(signum: signal) void = {
+ handle(signum, rt::SIG_IGN: *handler);
+};
+
+// Adds the given list of signals to the process's current signal mask,
+// returning the old signal mask. This is a convenience function around
+// [[setprocmask]].
+export fn block(signals: signal...) sigset = {
+ let new = newsigset(signals...);
+ return setprocmask(how::BLOCK, &new);
+};
+
+// Removes the given list of signals from the process's current signal mask,
+// returning the old signal mask. This is a convenience function around
+// [[setprocmask]].
+export fn unblock(signals: signal...) sigset = {
+ let new = newsigset(signals...);
+ return setprocmask(how::UNBLOCK, &new);
+};
+
+// Sets the process's signal mask, returning the previous mask.
+export fn setprocmask(how: how, mask: *sigset) sigset = {
+ let old = sigset { ... };
+ rt::sigprocmask(how, mask, &old)!;
+ return old;
+};
+
+// Gets the current process's signal mask.
+export fn getprocmask() sigset = {
+ let old = sigset { ... };
+ rt::sigprocmask(how::SETMASK, null, &old)!;
+ return old;
+};
+
+// Defines the modes of operation for [[setprocmask]].
+export type how = enum int {
+ // Adds the given set of signals to the current mask.
+ BLOCK = rt::SIG_BLOCK,
+ // Removes the given set of signals from the current mask.
+ UNBLOCK = rt::SIG_UNBLOCK,
+ // Sets the process mask to the given set.
+ SETMASK = rt::SIG_SETMASK,
+};
+
+export type sigaction = rt::sigact;
+
+export type sigset = rt::sigset;
+
+// Creates a new signal set filled in with the provided signals (or empty if
+// none are provided).
+export fn newsigset(items: signal...) sigset = {
+ let set = sigset { ... };
+ rt::sigemptyset(&set);
+ sigset_add(&set, items...);
+ return set;
+};
+
+// Sets a [[sigset]] to empty.
+export fn sigset_empty(set: *sigset) void = {
+ rt::sigemptyset(set);
+};
+
+// Adds signals to a [[sigset]].
+export fn sigset_add(set: *sigset, items: signal...) void = {
+ for (let i = 0z; i < len(items); i += 1) {
+ rt::sigaddset(set, items[i])!;
+ };
+};
+
+// Removes signals from a [[sigset]].
+export fn sigset_del(set: *sigset, items: signal...) void = {
+ for (let i = 0z; i < len(items); i += 1) {
+ rt::sigdelset(set, items[i])!;
+ };
+};
+
+// Returns true if the given signal is a member of this [[sigset]].
+export fn sigset_member(set: *sigset, item: signal) bool = {
+ return rt::sigismember(set, item)!;
+};
+
+// Provides additional information about signal deliveries. Only the members
+// defined by POSIX are available here; cast to [[rt::siginfo]] to access
+// non-portable members.
+//
+// TODO: Expand this with more portable options
+export type siginfo = union {
+ struct {
+ // The signal number being delivered.
+ signo: signal,
+ // The errno, if any, associated with this signal. See
+ // [[errors::errno]] to convert to a Hare-native error.
+ errno: rt::errno,
+ // The signal code, if any.
+ code: code,
+ },
+ // Pads the structure out to the length used by the kernel; do not use.
+ _si_pad: [128 - 3 * size(int)]u8,
+};
+
+// A code indicating why a signal was sent.
+export type code = enum int {
+ USER = 0, // sent by userspace program (kill)
+ KERNEL = 128, // sent by kernel
+ QUEUE = -1, // sent by sigqueue
+ TIMER = -2, // generated by expiration of a timer
+ MESQ = -3, // generated by arrival of a message on an empty queue
+ ASYNCIO = -4, // generated by completion of an asynchronous I/O request
+ SIGIO = -5,
+ TKILL = -6, // sent by userspace program (tkill, tgkill)
+ ASYNCNL = -60,
+
+ ILLOPC = 1, // SIGILL: illegal opcode
+ ILLOPN = 2, // SIGILL: illegal operand
+ ILLADR = 3, // SIGILL: illegal addressing mode
+ ILLTRP = 4, // SIGILL: illegal trap
+ PRVOPC = 5, // SIGILL: privileged opcode
+ PRVREG = 6, // SIGILL: privileged register
+ COPROC = 7, // SIGILL: coprocessor error
+ BADSTK = 8, // SIGILL: internal stack error
+
+ INTOVF = 1, // SIGFPE: integer overflow
+ INTDIV = 2, // SIGFPE: integer divide by zero
+ FLTDIV = 3, // SIGFPE: floating-point divide by zero
+ FLTOVF = 4, // SIGFPE: floating-point overflow
+ FLTUND = 5, // SIGFPE: floating-point underflow
+ FLTRES = 6, // SIGFPE: floating-point inexact result
+ FLTINV = 7, // SIGFPE: invalid floating-point operation
+ FLTSUB = 8, // SIGFPE: subscript out of range
+
+ MAPERR = 1, // SIGSEGV: address not mapped to object
+ ACCERR = 2, // SIGSEGV: invalid permissions for mapped object
+ PKUERR = 100, // SIGSEGV: access was denied by memory protection keys (x86_64)
+
+ ADRALN = 1, // SIGBUS: invalid address alignment
+ ADRERR = 2, // SIGBUS: nonexistent physical address
+ OBJERR = 3, // SIGBUS: object-specific hardware error
+ OOMERR = 100, // SIGBUS: out of memory
+
+ BRKPT = 1, // SIGTRAP: process breakpoint
+ TRACE = 2, // SIGTRAP: process trace trap
+ DTRACE = 3, // SIGTRAP: DTrace induced trap
+ CAP = 4, // SIGTRAP: capabilities protection trap
+
+ EXITED = 1, // SIGCHLD: child exited
+ KILLED = 2, // SIGCHLD: child terminated abnormally without a core file
+ DUMPED = 3, // SIGCHLD: child terminated abnormally with a core file
+ TRAPPED = 4, // SIGCHLD: traced child has trapped
+ STOPPED = 5, // SIGCHLD: child has stopped
+ CONTINUED = 6, // SIGCHLD: stopped child has continued
+
+ IN = 1, // SIGPOLL: data input available
+ OUT = 2, // SIGPOLL: output buffers available
+ MSG = 3, // SIGPOLL: input message available
+ ERR = 4, // SIGPOLL: I/O error
+ PRI = 5, // SIGPOLL: high priority input available
+ HUP = 6, // SIGPOLL: device disconnected
+};
+
+// Flags used to configure the behavior of a signal handler.
+export type flags = enum int {
+ // For use with [[SIGCHLD]]. Prevents notifications when child processes
+ // stop (e.g. via [[SIGSTOP]]) or resume (i.e. [[SIGCONT]]).
+ NOCLDSTOP = rt::SA_NOCLDSTOP: int,
+ // For use with [[SIGCHLD]]. Do not transform children into zombies when
+ // they terminate. Note that POSIX leaves the delivery of [[SIGCHLD]]
+ // unspecified when this flag is present; some systems will still
+ // deliver a signal and others may not.
+ NOCLDWAIT = rt::SA_NOCLDWAIT: int,
+ // Uses an alternate stack when handling this signal. See
+ // [[setaltstack]] and [[getaltstack]] for details.
+ ONSTACK = rt::SA_ONSTACK: int,
+ // Do not add the signal to the signal mask while executing the signal
+ // handler. This can cause the same signal to be delivered again during
+ // the execution of the signal handler.
+ NODEFER = rt::SA_NODEFER: int,
+ // Restore the signal handler to the default behavior upon entering the
+ // signal handler.
+ RESETHAND = rt::SA_RESETHAND: int,
+ // Makes certain system calls restartable across signals. See signal(7)
+ // or similar documentation for your local system for details.
+ RESTART = rt::SA_RESTART: int,
+};
+
+export type signal = int;
+
+// Hangup.
+export def SIGHUP: signal = rt::SIGHUP;
+// Terminal interrupt signal.
+export def SIGINT: signal = rt::SIGINT;
+// Terminal quit signal.
+export def SIGQUIT: signal = rt::SIGQUIT;
+// Illegal instruction.
+export def SIGILL: signal = rt::SIGILL;
+// Trace/breakposignal trap.
+export def SIGTRAP: signal = rt::SIGTRAP;
+// Process abort signal.
+export def SIGABRT: signal = rt::SIGABRT;
+// Emulate instruction executed.
+export def SIGEMT: signal = rt::SIGEMT;
+// Erroneous arithmetic operation.
+export def SIGFPE: signal = rt::SIGFPE;
+// Kill (cannot be caught or ignored).
+export def SIGKILL: signal = rt::SIGKILL;
+// Access to an undefined portion of a memory object.
+export def SIGBUS: signal = rt::SIGBUS;
+// Invalid memory reference.
+export def SIGSEGV: signal = rt::SIGSEGV;
+// Bad system call.
+export def SIGSYS: signal = rt::SIGSYS;
+// Write on a pipe with no one to read it.
+export def SIGPIPE: signal = rt::SIGPIPE;
+// Alarm clock.
+export def SIGALRM: signal = rt::SIGALRM;
+// Termination signal.
+export def SIGTERM: signal = rt::SIGTERM;
+// High bandwidth data is available at a socket.
+export def SIGURG: signal = rt::SIGURG;
+// Stop executing (cannot be caught or ignored).
+export def SIGSTOP: signal = rt::SIGSTOP;
+// Terminal stop signal.
+export def SIGTSTP: signal = rt::SIGTSTP;
+// Continue executing, if stopped.
+export def SIGCONT: signal = rt::SIGCONT;
+// Child process terminated, stopped, or continued.
+export def SIGCHLD: signal = rt::SIGCHLD;
+// Background process attempting read.
+export def SIGTTIN: signal = rt::SIGTTIN;
+// Background process attempting write.
+export def SIGTTOU: signal = rt::SIGTTOU;
+// I/O now possible.
+export def SIGIO: signal = rt::SIGIO;
+// CPU time limit exceeded.
+export def SIGXCPU: signal = rt::SIGXCPU;
+// File size limit exceeded.
+export def SIGXFSZ: signal = rt::SIGXFSZ;
+// Virtual timer expired.
+export def SIGVTALRM: signal = rt::SIGVTALRM;
+// Profiling timer expired.
+export def SIGPROF: signal = rt::SIGPROF;
+// Window resize signal.
+export def SIGWINCH: signal = rt::SIGWINCH;
+// Status request from keyboard.
+export def SIGINFO: signal = rt::SIGINFO;
+// User-defined signal 1.
+export def SIGUSR1: signal = rt::SIGUSR1;
+// User-defined signal 2.
+export def SIGUSR2: signal = rt::SIGUSR2;
+// Thread interrupt.
+export def SIGTHR: signal = rt::SIGTHR;
+// Real-time library interrupt.
+export def SIGLIBRT: signal = rt::SIGLIBRT;
+
+// Returns the human friendly name of a given signal.
+export fn signame(sig: signal) const str = {
+ switch (sig) {
+ case SIGHUP =>
+ return "SIGHUP";
+ case SIGINT =>
+ return "SIGINT";
+ case SIGQUIT =>
+ return "SIGQUIT";
+ case SIGILL =>
+ return "SIGILL";
+ case SIGTRAP =>
+ return "SIGTRAP";
+ case SIGABRT =>
+ return "SIGABRT";
+ case SIGEMT =>
+ return "SIGEMT";
+ case SIGFPE =>
+ return "SIGFPE";
+ case SIGKILL =>
+ return "SIGKILL";
+ case SIGBUS =>
+ return "SIGBUS";
+ case SIGSEGV =>
+ return "SIGSEGV";
+ case SIGSYS =>
+ return "SIGSYS";
+ case SIGPIPE =>
+ return "SIGPIPE";
+ case SIGALRM =>
+ return "SIGALRM";
+ case SIGTERM =>
+ return "SIGTERM";
+ case SIGURG =>
+ return "SIGURG";
+ case SIGSTOP =>
+ return "SIGSTOP";
+ case SIGTSTP =>
+ return "SIGTSTP";
+ case SIGCONT =>
+ return "SIGCONT";
+ case SIGCHLD =>
+ return "SIGCHLD";
+ case SIGTTIN =>
+ return "SIGTTIN";
+ case SIGTTOU =>
+ return "SIGTTOU";
+ case SIGIO =>
+ return "SIGIO";
+ case SIGXCPU =>
+ return "SIGXCPU";
+ case SIGXFSZ =>
+ return "SIGXFSZ";
+ case SIGVTALRM =>
+ return "SIGVTALRM";
+ case SIGPROF =>
+ return "SIGPROF";
+ case SIGWINCH =>
+ return "SIGWINCH";
+ case SIGINFO =>
+ return "SIGINFO";
+ case SIGUSR1 =>
+ return "SIGUSR1";
+ case SIGUSR2 =>
+ return "SIGUSR2";
+ case SIGTHR =>
+ return "SIGTHR";
+ case SIGLIBRT =>
+ return "SIGLIBRT";
+ case => abort(); // unreachable
+ };
+};