diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:26:05 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:26:05 +0300 |
| commit | 997dd21c2f45e9a586e9ef459381de1d540a9cfb (patch) | |
| tree | 5648d5924a91841c15ae95b9e7b56a9b07a9b284 /corebinutils/kill | |
| parent | 3d884ae8958e9e060e76d6e8165eb33ae86829d4 (diff) | |
| parent | 4888cfe1b3ad8e83935dc7b061bdbdade5d3ebfd (diff) | |
| download | Project-Tick-997dd21c2f45e9a586e9ef459381de1d540a9cfb.tar.gz Project-Tick-997dd21c2f45e9a586e9ef459381de1d540a9cfb.zip | |
Add 'corebinutils/kill/' from commit '4888cfe1b3ad8e83935dc7b061bdbdade5d3ebfd'
git-subtree-dir: corebinutils/kill
git-subtree-mainline: 3d884ae8958e9e060e76d6e8165eb33ae86829d4
git-subtree-split: 4888cfe1b3ad8e83935dc7b061bdbdade5d3ebfd
Diffstat (limited to 'corebinutils/kill')
| -rw-r--r-- | corebinutils/kill/.gitignore | 25 | ||||
| -rw-r--r-- | corebinutils/kill/GNUmakefile | 36 | ||||
| -rw-r--r-- | corebinutils/kill/LICENSE | 32 | ||||
| -rw-r--r-- | corebinutils/kill/LICENSES/BSD-3-Clause.txt | 11 | ||||
| -rw-r--r-- | corebinutils/kill/README.md | 31 | ||||
| -rw-r--r-- | corebinutils/kill/kill.1 | 163 | ||||
| -rw-r--r-- | corebinutils/kill/kill.c | 575 | ||||
| -rw-r--r-- | corebinutils/kill/tests/test.sh | 322 |
8 files changed, 1195 insertions, 0 deletions
diff --git a/corebinutils/kill/.gitignore b/corebinutils/kill/.gitignore new file mode 100644 index 0000000000..a74d30b48c --- /dev/null +++ b/corebinutils/kill/.gitignore @@ -0,0 +1,25 @@ +*.a +*.core +*.lo +*.nossppico +*.o +*.orig +*.pico +*.pieo +*.po +*.rej +*.so +*.so.[0-9]* +*.sw[nop] +*~ +.*DS_Store +.cache +.clangd +.ccls-cache +.depend* +compile_commands.json +compile_commands.events.json +tags +build/ +out/ +.linux-obj/ diff --git a/corebinutils/kill/GNUmakefile b/corebinutils/kill/GNUmakefile new file mode 100644 index 0000000000..5a4aa4ebed --- /dev/null +++ b/corebinutils/kill/GNUmakefile @@ -0,0 +1,36 @@ +.DEFAULT_GOAL := all + +CC ?= cc +CPPFLAGS ?= +CPPFLAGS += -D_POSIX_C_SOURCE=200809L +CFLAGS ?= -O2 +CFLAGS += -std=c17 -g -Wall -Wextra -Werror +LDFLAGS ?= +LDLIBS ?= + +OBJDIR := $(CURDIR)/build +OUTDIR := $(CURDIR)/out +TARGET := $(OUTDIR)/kill +OBJS := $(OBJDIR)/kill.o + +.PHONY: all clean dirs status test + +all: $(TARGET) + +dirs: + @mkdir -p "$(OBJDIR)" "$(OUTDIR)" + +$(TARGET): $(OBJS) | dirs + $(CC) $(LDFLAGS) -o "$@" $(OBJS) $(LDLIBS) + +$(OBJDIR)/kill.o: $(CURDIR)/kill.c | dirs + $(CC) $(CPPFLAGS) $(CFLAGS) -c "$(CURDIR)/kill.c" -o "$@" + +test: $(TARGET) + CC="$(CC)" KILL_BIN="$(TARGET)" sh "$(CURDIR)/tests/test.sh" + +status: + @printf '%s\n' "$(TARGET)" + +clean: + @rm -rf "$(OBJDIR)" "$(OUTDIR)" diff --git a/corebinutils/kill/LICENSE b/corebinutils/kill/LICENSE new file mode 100644 index 0000000000..9df35c2a9e --- /dev/null +++ b/corebinutils/kill/LICENSE @@ -0,0 +1,32 @@ +Copyright (c) 1988, 1993, 1994 + The Regents of the University of California. All rights reserved. + +Copyright (c) 2026 + Project Tick. All rights reserved. + +This code is derived from software contributed to Berkeley by +the Institute of Electrical and Electronics Engineers, Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/corebinutils/kill/LICENSES/BSD-3-Clause.txt b/corebinutils/kill/LICENSES/BSD-3-Clause.txt new file mode 100644 index 0000000000..ea890afbc7 --- /dev/null +++ b/corebinutils/kill/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,11 @@ +Copyright (c) <year> <owner>. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/corebinutils/kill/README.md b/corebinutils/kill/README.md new file mode 100644 index 0000000000..f8b4313bc6 --- /dev/null +++ b/corebinutils/kill/README.md @@ -0,0 +1,31 @@ +# kill + +Standalone Linux-native port of FreeBSD `kill` for Project Tick BSD/Linux Distribution. + +## Build + +```sh +gmake -f GNUmakefile +gmake -f GNUmakefile CC=musl-gcc +``` + +## Test + +```sh +gmake -f GNUmakefile test +gmake -f GNUmakefile test CC=musl-gcc +``` + +## Port Strategy + +- Process signaling is mapped directly to Linux `kill(2)`. +- Signal name parsing and `kill -l` output are implemented locally from Linux `<signal.h>` constants instead of FreeBSD `str2sig(3)`, `sig2str(3)`, `sys_signame`, or `sys_nsig`. +- Real-time signals are exposed through Linux `SIGRTMIN`/`SIGRTMAX` as `RTMIN`, `RTMIN+N`, and `RTMAX-N`. +- Negative process-group targets are supported through the native Linux `kill(2)` PID semantics; callers must use `--` before a negative PID to avoid ambiguity with signal options. + +## Supported / Unsupported Semantics + +- Supported: `kill pid ...`, `kill -s signal_name pid ...`, `kill -signal_name pid ...`, `kill -signal_number pid ...`, `kill -l`, `kill -l exit_status`, signal names with optional `SIG` prefix, and signal `0`. +- Unsupported by design: shell job-control operands such as `%1`. Those require a shell builtin with job table state, so this standalone binary exits with a clear error instead of guessing. +- Unsupported by design: GNU reverse lookup forms such as `kill -l TERM` and numeric `kill -s 9`. This port keeps the BSD/POSIX interface strict rather than adding GNU-only parsing. +- Unsupported on Linux: BSD-only signals that do not exist in Linux `<signal.h>` such as `INFO`. They fail explicitly as unknown signals. diff --git a/corebinutils/kill/kill.1 b/corebinutils/kill/kill.1 new file mode 100644 index 0000000000..52f67591a9 --- /dev/null +++ b/corebinutils/kill/kill.1 @@ -0,0 +1,163 @@ +.\"- +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Copyright (c) 2026 +.\" Project Tick. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd October 3, 2016 +.Dt KILL 1 +.Os +.Sh NAME +.Nm kill +.Nd terminate or signal a process +.Sh SYNOPSIS +.Nm +.Op Fl s Ar signal_name +.Ar pid ... +.Nm +.Fl l +.Op Ar exit_status +.Nm +.Fl Ar signal_name +.Ar pid ... +.Nm +.Fl Ar signal_number +.Ar pid ... +.Sh DESCRIPTION +The +.Nm +utility sends a signal to the processes specified by the +.Ar pid +operands. +.Pp +Only the super-user may send signals to other users' processes. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl s Ar signal_name +A symbolic signal name specifying the signal to be sent instead of the +default +.Dv TERM . +.It Fl l Op Ar exit_status +If no operand is given, list the signal names; otherwise, write +the signal name corresponding to +.Ar exit_status . +.It Fl Ar signal_name +A symbolic signal name specifying the signal to be sent instead of the +default +.Dv TERM . +.It Fl Ar signal_number +A non-negative decimal integer, specifying the signal to be sent instead +of the default +.Dv TERM . +.El +.Pp +The following PIDs have special meanings: +.Bl -tag -width indent +.It 0 +The signal is sent to all processes whose group ID is equal to the process +group ID of the sender, and for which the process has permission. +.It -1 +If superuser, broadcast the signal to all processes; otherwise broadcast +to all processes belonging to the user. +.It - Ns Ar PGID +The signal is sent to all processes that belong to the specified +process group ID (PGID). +.El +.Pp +Some of the more commonly used signals: +.Pp +.Bl -tag -width indent -compact +.It 1 +HUP (hang up) +.It 2 +INT (interrupt) +.It 3 +QUIT (quit) +.It 6 +ABRT (abort) +.It 9 +KILL (non-catchable, non-ignorable kill) +.It 14 +ALRM (alarm clock) +.It 15 +TERM (software termination signal) +.El +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Terminate +the processes with PIDs 142 and 157: +.Pp +.Dl "kill 142 157" +.Pp +Send the hangup signal +.Pq Dv SIGHUP +to the process with PID 507: +.Pp +.Dl "kill -s HUP 507" +.Pp +Terminate the process group with PGID 117: +.Pp +.Dl "kill -- -117" +.Sh SEE ALSO +.Xr builtin 1 , +.Xr csh 1 , +.Xr killall 1 , +.Xr ps 1 , +.Xr sh 1 , +.Xr kill 2 , +.Xr sigaction 2 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Sh HISTORY +A +.Nm +command appeared in +.At v3 +in section 8 of the manual. +.Sh BUGS +A replacement for the command +.Dq Li kill 0 +for +.Xr csh 1 +users should be provided. diff --git a/corebinutils/kill/kill.c b/corebinutils/kill/kill.c new file mode 100644 index 0000000000..82d8324365 --- /dev/null +++ b/corebinutils/kill/kill.c @@ -0,0 +1,575 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2026 + * Project Tick. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define _POSIX_C_SOURCE 200809L + +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/types.h> +#include <unistd.h> + +#ifdef SHELL +#define main killcmd +#include "bltin/bltin.h" +#include "error.h" +#endif + +struct signal_entry { + const char *name; + int number; +}; + +#define SIGNAL_ENTRY(name) { #name, SIG##name } +#define SIGNAL_ALIAS(name, signal) { name, signal } + +static const struct signal_entry canonical_signals[] = { +#ifdef SIGHUP + SIGNAL_ENTRY(HUP), +#endif +#ifdef SIGINT + SIGNAL_ENTRY(INT), +#endif +#ifdef SIGQUIT + SIGNAL_ENTRY(QUIT), +#endif +#ifdef SIGILL + SIGNAL_ENTRY(ILL), +#endif +#ifdef SIGTRAP + SIGNAL_ENTRY(TRAP), +#endif +#ifdef SIGABRT + SIGNAL_ENTRY(ABRT), +#endif +#ifdef SIGBUS + SIGNAL_ENTRY(BUS), +#endif +#ifdef SIGFPE + SIGNAL_ENTRY(FPE), +#endif +#ifdef SIGKILL + SIGNAL_ENTRY(KILL), +#endif +#ifdef SIGUSR1 + SIGNAL_ENTRY(USR1), +#endif +#ifdef SIGSEGV + SIGNAL_ENTRY(SEGV), +#endif +#ifdef SIGUSR2 + SIGNAL_ENTRY(USR2), +#endif +#ifdef SIGPIPE + SIGNAL_ENTRY(PIPE), +#endif +#ifdef SIGALRM + SIGNAL_ENTRY(ALRM), +#endif +#ifdef SIGTERM + SIGNAL_ENTRY(TERM), +#endif +#ifdef SIGSTKFLT + SIGNAL_ENTRY(STKFLT), +#endif +#ifdef SIGCHLD + SIGNAL_ENTRY(CHLD), +#endif +#ifdef SIGCONT + SIGNAL_ENTRY(CONT), +#endif +#ifdef SIGSTOP + SIGNAL_ENTRY(STOP), +#endif +#ifdef SIGTSTP + SIGNAL_ENTRY(TSTP), +#endif +#ifdef SIGTTIN + SIGNAL_ENTRY(TTIN), +#endif +#ifdef SIGTTOU + SIGNAL_ENTRY(TTOU), +#endif +#ifdef SIGURG + SIGNAL_ENTRY(URG), +#endif +#ifdef SIGXCPU + SIGNAL_ENTRY(XCPU), +#endif +#ifdef SIGXFSZ + SIGNAL_ENTRY(XFSZ), +#endif +#ifdef SIGVTALRM + SIGNAL_ENTRY(VTALRM), +#endif +#ifdef SIGPROF + SIGNAL_ENTRY(PROF), +#endif +#ifdef SIGWINCH + SIGNAL_ENTRY(WINCH), +#endif +#ifdef SIGIO + SIGNAL_ENTRY(IO), +#endif +#ifdef SIGPWR + SIGNAL_ENTRY(PWR), +#endif +#ifdef SIGSYS + SIGNAL_ENTRY(SYS), +#endif +}; + +static const struct signal_entry signal_aliases[] = { +#ifdef SIGIOT + SIGNAL_ALIAS("IOT", SIGIOT), +#endif +#ifdef SIGCLD + SIGNAL_ALIAS("CLD", SIGCLD), +#endif +#ifdef SIGPOLL + SIGNAL_ALIAS("POLL", SIGPOLL), +#endif +#ifdef SIGUNUSED + SIGNAL_ALIAS("UNUSED", SIGUNUSED), +#endif +}; + +static void usage(void); +static void die(int status, const char *fmt, ...); +static char *normalize_signal_name(const char *token); +static bool parse_pid_argument(const char *text, pid_t *pid); +static bool parse_nonnegative_number(const char *text, int *value); +static bool parse_signal_name(const char *token, int *signum); +static bool parse_signal_option_token(const char *token, int *signum); +static bool parse_signal_for_dash_s(const char *token, int *signum); +static bool signal_name_for_number(int signum, char *buffer, size_t buffer_size); +static void printsignals(FILE *fp); +static void unknown_signal(const char *token); +static int max_signal_number(void); + +static void +usage(void) +{ +#ifdef SHELL + error("usage: kill [-s signal_name] pid ..."); +#else + fprintf(stderr, + "usage: kill [-s signal_name] pid ...\n" + " kill -l [exit_status]\n" + " kill -signal_name pid ...\n" + " kill -signal_number pid ...\n"); + exit(2); +#endif +} + +static void +die(int status, const char *fmt, ...) +{ + va_list ap; + +#ifdef SHELL + char buffer[256]; + + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + errorwithstatus(status, "%s", buffer); +#else + fprintf(stderr, "kill: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + exit(status); +#endif +} + +static char * +normalize_signal_name(const char *token) +{ + size_t i, length, offset; + char *name; + + length = strlen(token); + name = malloc(length + 1); + if (name == NULL) + die(2, "out of memory"); + + for (i = 0; i < length; i++) + name[i] = (char)toupper((unsigned char)token[i]); + name[length] = '\0'; + + offset = 0; + if (length > 3 && name[0] == 'S' && name[1] == 'I' && name[2] == 'G') + offset = 3; + if (offset != 0) + memmove(name, name + offset, length - offset + 1); + + return (name); +} + +static bool +parse_pid_argument(const char *text, pid_t *pid) +{ + intmax_t value; + pid_t converted; + char *end; + + if (text[0] == '\0') + return (false); + + errno = 0; + value = strtoimax(text, &end, 10); + if (errno == ERANGE || *end != '\0') + return (false); + + converted = (pid_t)value; + if ((intmax_t)converted != value) + return (false); + + *pid = converted; + return (true); +} + +static bool +parse_nonnegative_number(const char *text, int *value) +{ + intmax_t parsed; + char *end; + + if (text[0] == '\0') + return (false); + + errno = 0; + parsed = strtoimax(text, &end, 10); + if (errno == ERANGE || *end != '\0' || parsed < 0 || parsed > INT_MAX) + return (false); + + *value = (int)parsed; + return (true); +} + +static bool +parse_signal_name(const char *token, int *signum) +{ + char *name; + char *end; + int offset; + size_t i; + + if (strcmp(token, "0") == 0) { + *signum = 0; + return (true); + } + + name = normalize_signal_name(token); + for (i = 0; i < sizeof(canonical_signals) / sizeof(canonical_signals[0]); i++) { + if (strcmp(name, canonical_signals[i].name) == 0) { + *signum = canonical_signals[i].number; + free(name); + return (true); + } + } + + for (i = 0; i < sizeof(signal_aliases) / sizeof(signal_aliases[0]); i++) { + if (strcmp(name, signal_aliases[i].name) == 0) { + *signum = signal_aliases[i].number; + free(name); + return (true); + } + } + +#ifdef SIGRTMIN +#ifdef SIGRTMAX + if (strcmp(name, "RTMIN") == 0) { + *signum = SIGRTMIN; + free(name); + return (true); + } + if (strncmp(name, "RTMIN+", 6) == 0) { + errno = 0; + offset = (int)strtol(name + 6, &end, 10); + if (errno == 0 && *end == '\0' && offset >= 0 && + SIGRTMIN + offset <= SIGRTMAX) { + *signum = SIGRTMIN + offset; + free(name); + return (true); + } + } + if (strcmp(name, "RTMAX") == 0) { + *signum = SIGRTMAX; + free(name); + return (true); + } + if (strncmp(name, "RTMAX-", 6) == 0) { + errno = 0; + offset = (int)strtol(name + 6, &end, 10); + if (errno == 0 && *end == '\0' && offset >= 0 && + SIGRTMAX - offset >= SIGRTMIN) { + *signum = SIGRTMAX - offset; + free(name); + return (true); + } + } +#endif +#endif + + free(name); + return (false); +} + +static bool +parse_signal_option_token(const char *token, int *signum) +{ + if (parse_nonnegative_number(token, signum)) + return (true); + return (parse_signal_name(token, signum)); +} + +static bool +parse_signal_for_dash_s(const char *token, int *signum) +{ + if (parse_nonnegative_number(token, signum)) + return (true); + return (parse_signal_name(token, signum)); +} + +static bool +signal_name_for_number(int signum, char *buffer, size_t buffer_size) +{ + size_t i; + + if (signum == 0) { + if (buffer != NULL && buffer_size > 0) { + buffer[0] = '0'; + if (buffer_size > 1) + buffer[1] = '\0'; + } + return (true); + } + + for (i = 0; i < sizeof(canonical_signals) / sizeof(canonical_signals[0]); i++) { + if (canonical_signals[i].number != signum) + continue; + if (buffer != NULL && buffer_size > 0) { + if (snprintf(buffer, buffer_size, "%s", + canonical_signals[i].name) >= (int)buffer_size) + die(2, "internal signal name buffer overflow"); + } + return (true); + } + +#ifdef SIGRTMIN +#ifdef SIGRTMAX + if (signum == SIGRTMIN) { + if (buffer != NULL && buffer_size > 0) + snprintf(buffer, buffer_size, "RTMIN"); + return (true); + } + if (signum == SIGRTMAX) { + if (buffer != NULL && buffer_size > 0) + snprintf(buffer, buffer_size, "RTMAX"); + return (true); + } + if (signum > SIGRTMIN && signum < SIGRTMAX) { + if (buffer != NULL && buffer_size > 0) { + if (snprintf(buffer, buffer_size, "RTMIN+%d", + signum - SIGRTMIN) >= (int)buffer_size) + die(2, "internal signal name buffer overflow"); + } + return (true); + } +#endif +#endif + + return (false); +} + +static void +printsignals(FILE *fp) +{ + char signame[32]; + int line_length, maxsig, signum; + + line_length = 0; + maxsig = max_signal_number(); + for (signum = 1; signum <= maxsig; signum++) { + size_t name_length; + + if (!signal_name_for_number(signum, signame, sizeof(signame))) + continue; + + name_length = strlen(signame); + if (line_length != 0) { + if (line_length + 1 + (int)name_length > 72) { + putc('\n', fp); + line_length = 0; + } else { + putc(' ', fp); + line_length++; + } + } + fputs(signame, fp); + line_length += (int)name_length; + } + putc('\n', fp); +} + +static void +unknown_signal(const char *token) +{ + fprintf(stderr, "kill: unknown signal %s; valid signals:\n", token); + printsignals(stderr); + exit(2); +} + +static int +max_signal_number(void) +{ + size_t i; + int maxsig; + + maxsig = 0; + for (i = 0; i < sizeof(canonical_signals) / sizeof(canonical_signals[0]); i++) { + if (canonical_signals[i].number > maxsig) + maxsig = canonical_signals[i].number; + } +#ifdef SIGRTMAX + if (SIGRTMAX > maxsig) + maxsig = SIGRTMAX; +#endif + return (maxsig); +} + +int +main(int argc, char *argv[]) +{ + char signame[32]; + pid_t pid; + int errors, signum, status_value; + + if (argc < 2) + usage(); + + signum = SIGTERM; + argc--; + argv++; + + if (strcmp(argv[0], "-l") == 0) { + argc--; + argv++; + if (argc > 1) + usage(); + if (argc == 0) { + printsignals(stdout); + return (0); + } + + if (parse_nonnegative_number(argv[0], &status_value)) { + if (status_value >= 128) + status_value -= 128; + if (status_value == 0) { + printf("0\n"); + return (0); + } + if (!signal_name_for_number(status_value, signame, sizeof(signame))) + unknown_signal(argv[0]); + printf("%s\n", signame); + return (0); + } + if (!parse_signal_name(argv[0], &status_value)) + unknown_signal(argv[0]); + printf("%d\n", status_value); + return (0); + } + + if (strcmp(argv[0], "-s") == 0) { + argc--; + argv++; + if (argc == 0) { + fprintf(stderr, "kill: option requires an argument -- s\n"); + usage(); + } + if (!parse_signal_for_dash_s(argv[0], &signum)) + die(2, "option -s requires a signal name, number, or 0: %s", + argv[0]); + argc--; + argv++; + } else if (argv[0][0] == '-' && argv[0][1] != '\0' && argv[0][1] != '-') { + if (!parse_signal_option_token(argv[0] + 1, &signum)) + unknown_signal(argv[0] + 1); + argc--; + argv++; + } + + if (argc > 0 && strcmp(argv[0], "--") == 0) { + argc--; + argv++; + } + + if (argc == 0) + usage(); + + errors = 0; + for (; argc > 0; argc--, argv++) { + if (argv[0][0] == '%') { +#ifdef SHELL + if (killjob(argv[0], signum) != 0) { + fprintf(stderr, "kill: %s: %s\n", argv[0], + strerror(errno)); + errors = 1; + } + continue; +#else + die(2, "job control process specifications require a shell builtin: %s", + argv[0]); +#endif + } + if (!parse_pid_argument(argv[0], &pid)) + die(2, "illegal process id: %s", argv[0]); + if (kill(pid, signum) != 0) { + fprintf(stderr, "kill: %s: %s\n", argv[0], strerror(errno)); + errors = 1; + } + } + + return (errors); +} diff --git a/corebinutils/kill/tests/test.sh b/corebinutils/kill/tests/test.sh new file mode 100644 index 0000000000..c8ea94d941 --- /dev/null +++ b/corebinutils/kill/tests/test.sh @@ -0,0 +1,322 @@ +#!/bin/sh +set -eu + +ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +KILL_BIN=${KILL_BIN:-"$ROOT/out/kill"} +CC=${CC:-cc} +TMPDIR=${TMPDIR:-/tmp} +WORKDIR=$(mktemp -d "$TMPDIR/kill-test.XXXXXX") +HELPER_C="$WORKDIR/signal-helper.c" +HELPER_BIN="$WORKDIR/signal-helper" +STDOUT_FILE="$WORKDIR/stdout" +STDERR_FILE="$WORKDIR/stderr" +ACTIVE_PID= +ACTIVE_LOG= +ACTIVE_READY= +trap ' + if [ -n "${ACTIVE_PID}" ]; then + kill -KILL "$ACTIVE_PID" 2>/dev/null || true + wait "$ACTIVE_PID" 2>/dev/null || true + fi + rm -rf "$WORKDIR" +' EXIT INT TERM + +export LC_ALL=C + +USAGE_OUTPUT=$(cat <<'EOF' +usage: kill [-s signal_name] pid ... + kill -l [exit_status] + kill -signal_name pid ... + kill -signal_number pid ... +EOF +) + +fail() { + printf '%s\n' "FAIL: $1" >&2 + exit 1 +} + +assert_eq() { + name=$1 + expected=$2 + actual=$3 + if [ "$expected" != "$actual" ]; then + printf '%s\n' "FAIL: $name" >&2 + printf '%s\n' "--- expected ---" >&2 + printf '%s' "$expected" >&2 + printf '\n%s\n' "--- actual ---" >&2 + printf '%s' "$actual" >&2 + printf '\n' >&2 + exit 1 + fi +} + +assert_contains() { + name=$1 + text=$2 + pattern=$3 + case $text in + *"$pattern"*) ;; + *) fail "$name" ;; + esac +} + +assert_empty() { + name=$1 + text=$2 + if [ -n "$text" ]; then + printf '%s\n' "FAIL: $name" >&2 + printf '%s\n' "--- expected empty ---" >&2 + printf '%s\n' "--- actual ---" >&2 + printf '%s' "$text" >&2 + printf '\n' >&2 + exit 1 + fi +} + +assert_status() { + name=$1 + expected=$2 + actual=$3 + if [ "$expected" -ne "$actual" ]; then + printf '%s\n' "FAIL: $name" >&2 + printf '%s\n' "expected status: $expected" >&2 + printf '%s\n' "actual status: $actual" >&2 + exit 1 + fi +} + +run_capture() { + if "$@" >"$STDOUT_FILE" 2>"$STDERR_FILE"; then + LAST_STATUS=0 + else + LAST_STATUS=$? + fi + + LAST_STDOUT=$(cat "$STDOUT_FILE") + LAST_STDERR=$(cat "$STDERR_FILE") +} + +build_helper() { +cat >"$HELPER_C" <<'EOF' +#define _POSIX_C_SOURCE 200809L + +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static volatile sig_atomic_t received_signal; + +static void +handle_signal(int signo) +{ + received_signal = signo; +} + +static const char * +signal_name(int signo) +{ + switch (signo) { + case SIGTERM: + return "TERM"; + case SIGUSR1: + return "USR1"; + default: + return "UNKNOWN"; + } +} + +int +main(int argc, char *argv[]) +{ + struct sigaction sa; + FILE *fp; + + if (argc != 2) + return (2); + + if (setpgid(0, 0) != 0) + return (3); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handle_signal; + if (sigemptyset(&sa.sa_mask) != 0) + return (4); + if (sigaction(SIGTERM, &sa, NULL) != 0) + return (5); + if (sigaction(SIGUSR1, &sa, NULL) != 0) + return (6); + + if (printf("%ld %ld\n", (long)getpid(), (long)getpgrp()) < 0) + return (7); + if (fflush(stdout) != 0) + return (8); + + while (received_signal == 0) + pause(); + + fp = fopen(argv[1], "w"); + if (fp == NULL) + return (9); + if (fprintf(fp, "%s\n", signal_name(received_signal)) < 0) { + fclose(fp); + return (10); + } + if (fclose(fp) != 0) + return (11); + + return (0); +} +EOF + + "$CC" -D_POSIX_C_SOURCE=200809L -O2 -std=c17 -Wall -Wextra -Werror \ + "$HELPER_C" -o "$HELPER_BIN" \ + || fail "failed to build helper with $CC" +} + +start_helper() { + name=$1 + ACTIVE_LOG="$WORKDIR/$name.log" + ACTIVE_READY="$WORKDIR/$name.ready" + rm -f "$ACTIVE_LOG" "$ACTIVE_READY" + mkfifo "$ACTIVE_READY" + + "$HELPER_BIN" "$ACTIVE_LOG" >"$ACTIVE_READY" & + ACTIVE_PID=$! + + if ! IFS=' ' read -r HELPER_PID HELPER_PGID <"$ACTIVE_READY"; then + fail "failed to read helper readiness" + fi + rm -f "$ACTIVE_READY" + ACTIVE_READY= + + [ -n "$HELPER_PID" ] || fail "helper pid missing" + [ -n "$HELPER_PGID" ] || fail "helper pgid missing" +} + +wait_helper() { + wait "$ACTIVE_PID" || fail "helper exited with failure" + ACTIVE_PID= +} + +wait_helper_signaled() { + if wait "$ACTIVE_PID"; then + fail "helper was expected to terminate by signal" + fi + ACTIVE_PID= +} + +[ -x "$KILL_BIN" ] || fail "missing binary: $KILL_BIN" +build_helper + +run_capture "$KILL_BIN" +assert_status "usage without args status" 2 "$LAST_STATUS" +assert_empty "usage without args stdout" "$LAST_STDOUT" +assert_eq "usage without args stderr" "$USAGE_OUTPUT" "$LAST_STDERR" + +run_capture "$KILL_BIN" -l +assert_status "list signals status" 0 "$LAST_STATUS" +assert_empty "list signals stderr" "$LAST_STDERR" +assert_contains "list signals contains HUP" "$LAST_STDOUT" "HUP" +assert_contains "list signals contains KILL" "$LAST_STDOUT" "KILL" +assert_contains "list signals contains TERM" "$LAST_STDOUT" "TERM" + +run_capture "$KILL_BIN" -l 15 +assert_status "list signal 15 status" 0 "$LAST_STATUS" +assert_eq "list signal 15 stdout" "TERM" "$LAST_STDOUT" +assert_empty "list signal 15 stderr" "$LAST_STDERR" + +run_capture "$KILL_BIN" -l 143 +assert_status "list signal 143 status" 0 "$LAST_STATUS" +assert_eq "list signal 143 stdout" "TERM" "$LAST_STDOUT" +assert_empty "list signal 143 stderr" "$LAST_STDERR" + +run_capture "$KILL_BIN" -l 0 +assert_status "list signal 0 status" 0 "$LAST_STATUS" +assert_eq "list signal 0 stdout" "0" "$LAST_STDOUT" +assert_empty "list signal 0 stderr" "$LAST_STDERR" + +run_capture "$KILL_BIN" -l TERM +assert_status "list TERM status" 0 "$LAST_STATUS" +assert_eq "list TERM stdout" "15" "$LAST_STDOUT" +assert_empty "list TERM stderr" "$LAST_STDERR" + +run_capture "$KILL_BIN" -s +assert_status "missing -s argument status" 2 "$LAST_STATUS" +assert_empty "missing -s argument stdout" "$LAST_STDOUT" +assert_contains "missing -s argument stderr" "$LAST_STDERR" \ + "kill: option requires an argument -- s" + +run_capture "$KILL_BIN" -NOPE 1 +assert_status "unknown option signal status" 2 "$LAST_STATUS" +assert_empty "unknown option signal stdout" "$LAST_STDOUT" +assert_contains "unknown option signal stderr" "$LAST_STDERR" \ + "kill: unknown signal NOPE" + +run_capture "$KILL_BIN" abc +assert_status "invalid pid status" 2 "$LAST_STATUS" +assert_empty "invalid pid stdout" "$LAST_STDOUT" +assert_eq "invalid pid stderr" "kill: illegal process id: abc" "$LAST_STDERR" + +run_capture "$KILL_BIN" %1 +assert_status "job spec status" 2 "$LAST_STATUS" +assert_empty "job spec stdout" "$LAST_STDOUT" +assert_eq "job spec stderr" \ + "kill: job control process specifications require a shell builtin: %1" \ + "$LAST_STDERR" + +start_helper probe + +run_capture "$KILL_BIN" -0 "$HELPER_PID" +assert_status "signal zero short form status" 0 "$LAST_STATUS" +assert_empty "signal zero short form stdout" "$LAST_STDOUT" +assert_empty "signal zero short form stderr" "$LAST_STDERR" + +run_capture "$KILL_BIN" -s 0 "$HELPER_PID" +assert_status "signal zero -s status" 0 "$LAST_STATUS" +assert_empty "signal zero -s stdout" "$LAST_STDOUT" +assert_empty "signal zero -s stderr" "$LAST_STDERR" + +run_capture "$KILL_BIN" -9 "$HELPER_PID" +assert_status "signal 9 short form status" 0 "$LAST_STATUS" +assert_empty "signal 9 short form stdout" "$LAST_STDOUT" +assert_empty "signal 9 short form stderr" "$LAST_STDERR" +wait_helper_signaled +[ ! -e "$ACTIVE_LOG" ] || fail "signal 9 helper log should not exist" + +start_helper numeric_s +run_capture "$KILL_BIN" -s 9 "$HELPER_PID" +assert_status "signal 9 with -s status" 0 "$LAST_STATUS" +assert_empty "signal 9 with -s stdout" "$LAST_STDOUT" +assert_empty "signal 9 with -s stderr" "$LAST_STDERR" +wait_helper_signaled +[ ! -e "$ACTIVE_LOG" ] || fail "signal 9 with -s helper log should not exist" + +start_helper probe +run_capture "$KILL_BIN" -s SIGUSR1 "$HELPER_PID" +assert_status "send SIGUSR1 with -s status" 0 "$LAST_STATUS" +assert_empty "send SIGUSR1 with -s stdout" "$LAST_STDOUT" +assert_empty "send SIGUSR1 with -s stderr" "$LAST_STDERR" +wait_helper +assert_eq "SIGUSR1 helper log" "USR1" "$(cat "$ACTIVE_LOG")" + +start_helper default_term +run_capture "$KILL_BIN" "$HELPER_PID" +assert_status "default TERM status" 0 "$LAST_STATUS" +assert_empty "default TERM stdout" "$LAST_STDOUT" +assert_empty "default TERM stderr" "$LAST_STDERR" +wait_helper +assert_eq "default TERM helper log" "TERM" "$(cat "$ACTIVE_LOG")" + +start_helper group_term +run_capture "$KILL_BIN" -TERM -- "-$HELPER_PGID" +assert_status "group TERM status" 0 "$LAST_STATUS" +assert_empty "group TERM stdout" "$LAST_STDOUT" +assert_empty "group TERM stderr" "$LAST_STDERR" +wait_helper +assert_eq "group TERM helper log" "TERM" "$(cat "$ACTIVE_LOG")" + +printf '%s\n' "PASS" |
