diff --git a/CHANGES b/CHANGES index cf1af0b..f6766bd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Release 240220 +* [NEW] seq: Stone-simple implementation; needs some improvement yet. +* [NEW] watch: Complete, simple and stable implementation. +* [NEW] timeout: Complete implementation with extended payloads. +* readlink: Different help messages for both UCB and default variants. + Release 230503 * chroot: "docs: Update date at chroot.8 manual page." * chroot: "chore: Remove comment, will move to GitHub" diff --git a/makefile b/makefile index 0dc4fae..1b866ae 100644 --- a/makefile +++ b/makefile @@ -14,8 +14,9 @@ SUBDIRS = build libwchar libcommon libuxre _install \ paste pathchk pg pgrep pr printenv printf priocntl ps psrinfo pwd \ random readlink renice rm rmdir \ sdiff sed seq setpgrp shl sleep sort spell split stty su sum sync \ - tabs tail tapecntl tar tcopy tee test time touch tr true tsort tty \ - ul uname uniq units users watch wc what who whoami whodo xargs yes + tabs tail tapecntl tar tcopy tee test time timeout touch tr true \ + tsort tty ul uname uniq units users watch wc what who whoami whodo \ + xargs yes dummy: makefiles all diff --git a/timeout/Makefile.mk b/timeout/Makefile.mk new file mode 100644 index 0000000..6f502f3 --- /dev/null +++ b/timeout/Makefile.mk @@ -0,0 +1,18 @@ +all: timeout + +timeout: timeout.o sigtable.o + $(LD) $(LDFLAGS) timeout.o sigtable.o $(LCOMMON) $(LWCHAR) $(LIBS) -o timeout + +timeout.o: timeout.c + $(CC) -std=c99 -Wall $(CFLAGS) $(CPPFLAGS) $(XO5FL) $(IWCHAR) $(ICOMMON) -I. -g -c timeout.c + +sigtable.o: sigtable.c + $(CC) $(CPPFLAGS) -c sigtable.c + +install: all + $(UCBINST) -c timeout $(ROOT)$(DEFBIN)/timeout + $(STRIP) $(ROOT)$(DEFBIN)/timeout + $(MANINST) -c -m 644 timeout.1 $(ROOT)$(MANDIR)/man1/timeout.1 + +clean: + rm -f timeout timeout.o sigtable.o core log *~ diff --git a/timeout/debug.txt b/timeout/debug.txt new file mode 100644 index 0000000..e69de29 diff --git a/timeout/sigtable.h b/timeout/sigtable.h new file mode 100644 index 0000000..154ca48 --- /dev/null +++ b/timeout/sigtable.h @@ -0,0 +1,149 @@ +/* + * sigtable.h - signal map-like struct. + */ +/* + * Derived from kill/strsig.c + * + * Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany + * Copyright (C) 2023, 2024: Luiz Antônio (takusuman) + * + * SPDX-Licence-Identifier: CDDL-1.0 + */ + +#include + +struct sig_strlist { + int signum; + char *signame; +}; + +static const struct sig_strlist sig_strs[] = { + { 0, "EXIT" }, /* UNKNOWN SIGNAL */ + { SIGHUP, "HUP" }, /* Hangup */ + { SIGINT, "INT" }, /* Interrupt */ + { SIGQUIT, "QUIT" }, /* Quit */ + { SIGILL, "ILL" }, /* Illegal Instruction */ + { SIGTRAP, "TRAP" }, /* Trace/Breakpoint Trap */ + { SIGABRT, "ABRT" }, /* Abort */ +#ifdef SIGIOT + { SIGIOT, "IOT" }, /* Input/Output Trap */ +#endif +#ifdef SIGEMT + { SIGEMT, "EMT" }, /* Emulation Trap */ +#endif +#ifdef SIGFPE + { SIGFPE, "FPE" }, /* Arithmetic Exception */ +#endif +#ifdef SIGKILL + { SIGKILL, "KILL" }, /* Killed */ +#endif +#ifdef SIGBUS + { SIGBUS, "BUS" }, /* Bus Error */ +#endif +#ifdef SIGSEGV + { SIGSEGV, "SEGV" }, /* Segmentation Fault */ +#endif +#ifdef SIGSYS + { SIGSYS, "SYS" }, /* Bad System Call */ +#endif +#ifdef SIGPIPE + { SIGPIPE, "PIPE" }, /* Broken Pipe */ +#endif +#ifdef SIGALRM + { SIGALRM, "ALRM" }, /* Alarm Clock */ +#endif +#ifdef SIGTERM + { SIGTERM, "TERM" }, /* Terminated */ +#endif +#ifdef SIGUSR1 + { SIGUSR1, "USR1" }, /* User Signal 1 */ +#endif +#ifdef SIGUSR2 + { SIGUSR2, "USR2" }, /* User Signal 2 */ +#endif +#ifdef SIGCLD + { SIGCLD, "CLD" }, /* Child Status Changed */ +#endif +#ifdef SIGCHLD + { SIGCHLD, "CHLD" }, /* Child Status Changed */ +#endif +#ifdef SIGPWR + { SIGPWR, "PWR" }, /* Power-Fail/Restart */ +#endif +#ifdef SIGWINCH + { SIGWINCH, "WINCH" }, /* Window Size Change */ +#endif +#ifdef SIGURG + { SIGURG, "URG" }, /* Urgent Socket Condition */ +#endif +#ifdef SIGPOLL + { SIGPOLL, "POLL" }, /* Pollable Event */ +#endif +#ifdef SIGIO + { SIGIO, "IO" }, /* Input/Output Now Possible */ +#endif +#ifdef SIGSTOP + { SIGSTOP, "STOP" }, /* Stopped (signal) */ +#endif +#ifdef SIGTSTP + { SIGTSTP, "TSTP" }, /* Stopped (user) */ +#endif +#ifdef SIGCONT + { SIGCONT, "CONT" }, /* Continued */ +#endif +#ifdef SIGTTIN + { SIGTTIN, "TTIN" }, /* Stopped (tty input) */ +#endif +#ifdef SIGTTOU + { SIGTTOU, "TTOU" }, /* Stopped (tty output) */ +#endif +#ifdef SIGVTALRM + { SIGVTALRM, "VTALRM" }, /* Virtual Timer Expired */ +#endif +#ifdef SIGPROF + { SIGPROF, "PROF" }, /* Profiling Timer Expired */ +#endif +#ifdef SIGXCPU + { SIGXCPU, "XCPU" }, /* Cpu Limit Exceeded */ +#endif +#ifdef SIGXFSZ + { SIGXFSZ, "XFSZ" }, /* File Size Limit Exceeded */ +#endif +#ifdef SIGWAITING + { SIGWAITING, "WAITING" }, /* No runnable lwp */ +#endif +#ifdef SIGLWP + { SIGLWP, "LWP" }, /* Inter-lwp signal */ +#endif +#ifdef SIGFREEZE + { SIGFREEZE, "FREEZE" }, /* Checkpoint Freeze */ +#endif +#ifdef SIGTHAW + { SIGTHAW, "THAW" }, /* Checkpoint Thaw */ +#endif +#ifdef SIGCANCEL + { SIGCANCEL, "CANCEL" }, /* Thread Cancellation */ +#endif +#ifdef SIGLOST + { SIGLOST, "LOST" }, /* Resource Lost */ +#endif +#ifdef SIGSTKFLT + { SIGSTKFLT, "STKFLT" }, /* Stack Fault On Coprocessor */ +#endif +#ifdef SIGINFO + { SIGINFO, "INFO" }, /* Status Request From Keyboard */ +#endif +#ifdef SIG_2_STR_WITH_RT_SIGNALS + { SIGRTMIN, "RTMIN" }, /* First Realtime Signal */ + { SIGRTMIN+1, "RTMIN+1" }, /* Second Realtime Signal */ + { SIGRTMIN+2, "RTMIN+2" }, /* Third Realtime Signal */ + { SIGRTMIN+3, "RTMIN+3" }, /* Fourth Realtime Signal */ + { SIGRTMAX-3, "RTMAX-3" }, /* Fourth Last Realtime Signal */ + { SIGRTMAX-2, "RTMAX-2" }, /* Third Last Realtime Signal */ + { SIGRTMAX-1, "RTMAX-1" }, /* Second Last Realtime Signal */ + { SIGRTMAX, "RTMAX" }, /* Last Realtime Signal */ +#endif /* SIG_2_STR_WITH_RT_SIGNALS */ + + /* If this is removed bad things happen dont remove this */ + { -1, NULL } +}; diff --git a/timeout/takefive.sh b/timeout/takefive.sh new file mode 100755 index 0000000..cf73d38 --- /dev/null +++ b/timeout/takefive.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# takefive.sh: test script for timeout(1), takes +# seconds as argument. +# Copyright (c) 2023, 2024 Luiz Antônio Rangel. +# +# This script is public domain, no more, no less. + +main() { + for SIG in ABRT ALRM HUP INT TERM; do + eval "trap 'handle_signal $SIG' $SIG" + done + take=${1:-5} + printf 1>&2 'Sleeping %d seconds...\n' $take + while [ $take -gt 0 ]; do + take=$(($take - 1)) + printf '%d... ' $take + sleep 1 + done + sleep $take + return 3 +} + +handle_signal() { # function input: string signal + s=$1 + printf 'Caught %s\n' "$1" +} + +main $1 diff --git a/timeout/timeout.1 b/timeout/timeout.1 new file mode 100644 index 0000000..32011ba --- /dev/null +++ b/timeout/timeout.1 @@ -0,0 +1,158 @@ +.\" +.\" Copyright(C) 2024 Luiz Antônio Rangel. All rights reserved. +.\" +.\" SPDX-Licence-Identifier: Zlib +.\" +.TH TIMEOUT 1 "2/20/24" "Heirloom Toolchest" "User Commands" +.SH NAME +timeout \- execute a command with a time limit +.SH SYNOPSIS +\fBtimeout\fR [\fB\-k\fR \fItime\fR] +[\fB\-s\fR \fIsignal\fR] [\-fp] +\fItime\fR \fIcommand\fR [\fIargument\fR ...] +.SH DESCRIPTION +.I Timeout +executes a specified \fIcommand\fR and +attempts to terminate it after an +also specified \fItime\fR. +Per default, it sends signal 15 +(terminate, alias SIGTERM), but the +signal can be redefined (see option +'\fI\-s\fR'). +If the duration is a zero value, the +timer is disabled (see timer_settime(2)), +ergo there will not be any signal sent +to the specified \fIcommand\fR's process. +.PP +It accepts the following options: +.TP +.B \-k \fItime\fR +Sets a complementary time for tolerance +before sending a final signal 9 (SIGKILL) +to \fIcommand\fR if it's still executing. +.TP +.B \-s \fIsignal\fR +Sets a signal to be sent instead of +signal 15 (terminate). It's defined +per a decimal number or its symbolic +name (see kill(1) '\-l' option). +.br +If it's defined as 9 (SIGKILL), it +has an immediate effect and annuls +the '\-k' option. +.TP +.B \-f +Permits that \fIcommand\fR runs as +foreground and recieves signals +from the terminal; per using this +option, the children processes that +\fIcommand\fR may create will not be +terminated with it even in case of +the timer timing out. +.TP +.B \-p +Keep \fIcommand\fR's exit status +instead of using this program's +default. +.SH "DURATION FORMATTING" +\fItime\fR can be specified as a +fractional number by using a full +stop or a comma as the decimal +separator. +The default value are seconds +unless a time unit is specified per +its single-letter identifier; in +ascend order, the supported units +are: +.TP +.I s +seconds +.TP +.I m +minutes +.TP +.I h +hours +.TP +.I d +days +.SH DIAGNOSTICS +Exit status is 124 if the timer expired +and the \-p option was not set, 126 if +the command could be found, but could not +be executed and 127 if the command could +not be found. +.SH EXAMPLES +This program has different possible +applications in one's routine, here are some: +.br +Lets suppose you need to check if your system +is connected to the Internet and that it can +successfully connect to a server in less than +5 seconds, you can use timeout along with nc(1): +.LP +.RS +.sp +.nf +% timeout -p 5 nc -vz pindorama.dob.jp 80 +% echo $? +0 +.fi +.sp +.RE +In another example, you have a project and +start experiencing hangings at some part after +a contributor sent a patch, but you do not know +the part nor the file that was broken; since +you know that it should not take more than +half a minute to pass that part, you can use +timeout and visualize precisely where it hangs +per enabling debug at the shell interpreter: +.IP \& 2 +.BI "% timeout 0,5m /bin/ksh -x ./build.ksh" +.LP +.SH NOTES +Although this implementation being \fIsœur\fR +with BSDs' implementations since it shares some +parts of code with it, it does not support long +options as GNU (and BSDs') does, so some shell +scripts may not work unchanged with this +implementation. +.br +An abideable approach for solving this problem +could be an small check/wrapper for the +timeout command in case of utilizing the \-f or +the \-p options in its long format. +.RS +.sp +.nf +if test \`getconf HEIRLOOM_TOOLCHEST_VERSION\` \-ge 20240220 +then + timeout \-p \-k\fItime\fR \fItime\fR \fIcommand\fR +else + timeout \-\-preserve\-duration \-\-kill\-after \fItime\fR \fItime\fR \fIcommand\fR +fi +.fi +.sp +.RE +.PP +This implementation supports intervals using +both European or Anglo decimal separators, +GNU doesn't. +.PP +In contrast to OpenBSD's \(em and any other +implementation thoroughly based and/or ported +from FreeBSD 10.3R \(em, this implementation +supports nanoseconds as the interval instead +of microseconds. This can change in the future +depending on your platform, though. +.SH "SEE ALSO" +exec(3), fork(2), kill(1), signal(2), +timer_settime(2), itimerspec(3type), +wait(2) +.SH HISTORY +The timeout utility first appeared as a proposal +in BusyBox mailing lists in February 3, 2006, +but this version have payloads closer to the +timeout found at GNU Coreutils 7.0, +released in 05 October, 2008. diff --git a/timeout/timeout.c b/timeout/timeout.c new file mode 100644 index 0000000..30f16cf --- /dev/null +++ b/timeout/timeout.c @@ -0,0 +1,561 @@ +/* + * timeout.c - execute a command with a time limit + */ +/* + * Copyright (C) 2023, 2024: Luiz Antônio Rangel (takusuman) + * Arthur Bacci (arthurbacci)(atr) + * + * SPDX-Licence-Identifier: Zlib + * + * The 'parse_interval' function was adapted from suspicious-tools' + * watch(1) (available at + * https://github.com/arthurbacci/suspicious-tools.git) and licensed + * under the public domain. + * + * The part that makes the called command, in fact, sleep was + * throloughly based on OpenBSD's implementation of timeout. + * As per the copyright header of OpenBSD's "usr.bin/timeout/timeout.c": + * Copyright (c) 2021 Job Snijders + * Copyright (c) 2014 Baptiste Daroussin + * Copyright (c) 2014 Vsevolod Stakhov + * + * SPDX-Licence-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char *progname; + +int main(int argc, char *argv[]); +int validate_signal(char *signal_name); +struct TClock validate_duration(char *duration); +int parse_interval(const char *ss, struct TClock *interval); +void settimeout(struct TClock *duration); +void handle_signal(int signo); +void usage(void); + +struct TClock { + time_t sec; + long int nsec; +}; + +struct LSignal { + sig_atomic_t sig_alrm; + sig_atomic_t sig_chld; + sig_atomic_t sig_term; + sig_atomic_t sig_ign; +}; +static struct LSignal siglist; + +int main(int argc, char *argv[]) { + progname = argv[0]; + register int c = 0, + s = 0; + int option = 0, + fForeground = 0, + fOnemoretime = 0, + fPreserve_status = 0, + killer_sig = SIGTERM, /* Default killer signal is SIGTERM. */ + /* + * -1 can be replaced by another signal + * if the user sets the -s flag. + */ + sigsused[] = { -1, SIGHUP, SIGINT, SIGQUIT, + SIGTERM, SIGCHLD, SIGALRM }, + execerr = 0, /* Command execution error code. */ + ecmd = 0, /* Command exit code. */ + eprog = 0; /* Program exit code. */ + + char **commandv, + *fst_commandv; + /* + * atr note: it might be better to have other integers + * replaced by booleans, since it makes it a lot easier + * to tell what is being acomplished. + */ + bool timesout = false; + pid_t cmdpid = 0, + exec_pid = 0, + pgid = 0; + struct TClock first_interval = {0}, + second_interval = {0}; + struct sigaction sa = {0}; + + while ( (option = getopt(argc, argv, "fps:k:h")) != -1 ){ + switch (option) { + case 'f': + /* According to GNU's timeout(1) usage() + * function: + * "when not running timeout directly from a + * shell prompt, allow COMMAND to read from + * the TTY and get TTY signals; in this mode, + * children of COMMAND will not be timed out. + */ + fForeground = 1; + break; + case 'p': + /* Simplest option overall, it catches process' + * exit code and returns it instead of returning + * timeout's default "timed out" exit code (124). + * This is only valid for processes that did not + * exit as zero. + */ + fPreserve_status = 1; + break; + case 's': + killer_sig = validate_signal(optarg); + break; + case 'k': + fOnemoretime = 1; + second_interval = validate_duration(optarg); + break; + case 'h': + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + + /* Missing operand. */ + if ( argc < 2 ){ + usage(); + } + + /* + * atr note: if one wants to use malloc he must be sure that the last + * element of commandv is set to NULL + */ + /* Allocate commandv[], where argv[] will be copied to. */ + if ( (commandv = calloc((size_t)argc, sizeof(char *))) == NULL ) { + pfmt(stderr, MM_ERROR, "%s: could not allocate an array of " + "%d elements, each one being %lu " + "bytes large.\n", progname, + argc, sizeof(char *) + ); + exit(1); + } + + /* And then copy argv[] to commandv[]. */ + /* atr: note that the previous code was only copying the reference, I + * believe it's a mistake and replaced it by strdup. + */ + /* atr: it's shifted, the first element of commandv is the second of + * argv and so on. + */ + for (c = 1; c < argc; c++) { + commandv[c - 1] = strdup(argv[c]); + } + + /* atr: dummy string for funny mutations */ + fst_commandv = strdup(argv[0]); + first_interval = validate_duration(fst_commandv); + free(fst_commandv); + + if (! fForeground) { + /* + * setpgid(pid, pgid) + * If both the 'pid' and 'pgid' are equal to zero, setpgid(2) + * assumes that both the child process pid and pgid shall be the + * same as parent's, that is why it is used in this case, where + * we do not want to foreground the process. + */ + if ((pgid = setpgid(0, 0)) != 0) { + pfmt(stderr, MM_ERROR, + "%s: failed to set process group via setpgid" + "(0, 0): %s.\n", progname, strerror(errno)); + } + } + + /* + * The list of signals masked (blocked) during the execution of the + * handler (which is present at sigaction.sa_handler/sa_sigaction) + * must be initialized per excluding the current list at sa_mask. + */ + sigemptyset(&sa.sa_mask); + + /* + * Since these two signals can not be caught nor ignored (using + * sa_mask), its inclusion at the signal ignore array must avoided. + * It will not cause any error, though, just be ignored. + */ + if (killer_sig != SIGKILL && killer_sig != SIGSTOP) { + sigsused[0] = killer_sig; + } + + /* + * Iterate over the sigsused[] integer array and start adding + * each one to the sa_mask set again. + */ + for (s = 0; s < (int)(sizeof(sigsused)/sizeof(sigsused[0])); s++) { + sigaddset(&sa.sa_mask, sigsused[s]); + } + + sa.sa_handler = &handle_signal; + + /* + * Set SA_RESTART at sa_flags, this will make certain system calls + * restartable by signals. This is useful because, after handle_signal() + * returns, the system call is restarted instead of failing with EINTR, + * which means an error by interruption. + */ + sa.sa_flags = SA_RESTART; + + /* + * Iterate over the sigsused[] integer array again, but this time it + * will be using sigaction() for changing an action taken by the system + * when any of the signals of the array --- which also are present on the + * handle_signal() function and, as the name explicitaly say, handled by + * it --- are sent, so, when any of these signals are sent, it actually + * calls handle_signal(). + */ + for (s = 0; s < (int)(sizeof(sigsused)/sizeof(sigsused[0])); s++) { + if (sigsused[s] != -1 && sigsused[s] != 0 + && sigaction(sigsused[s], &sa, NULL) != 0) { + pfmt(stderr, MM_ERROR, + "%s: failed to change action for %s: %s.\n", + progname, strsignal(sigsused[s]), + strerror(errno)); + exit(1); + } + } + + /* + * This is meant to ignore stop signals that could come from the + * terminal on the parent process. + */ + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + + exec_pid = fork(); + + /* Get the bad news first. */ + if (exec_pid == -1) { + pfmt(stderr, MM_ERROR, "%s: failed to fork: %s.\n", + progname, strerror(errno)); + } else if (exec_pid == 0) { + /* + * On the child process, signals sent by the terminal mustn't + * be ignored anymore and that the default action --- without + * involving the handler --- have to be taken by the system. + */ + signal(SIGTTIN, SIG_DFL); + signal(SIGTTOU, SIG_DFL); + + /* Here we will be executing the child process. */ + if ( (execvp(commandv[0], commandv)) == -1 ) { + pfmt(stderr, MM_ERROR, "%s: failed to exec(): %s.\n", + progname, strerror(errno)); + } + + /* + * According to GNU's timeout(1) manual page: + * + * 126 if COMMAND is found but cannot be invoked + * 127 if COMMAND cannot be found + * + * ENOENT stands for "error no entity/entry", so it + * means, in this context, no such file or directory. + */ + execerr = (errno == ENOENT) ? 127 : 126; + _exit(execerr); + } + + /* The command string will not be necessary after exec(). */ + for (int i = 0; commandv[i]; i++) { + /* atr: frees the strduped strings */ + free(commandv[i]); + } + free(commandv); + + /* + * Add signals from the sa_mask set to the SIG_BLOCK set, + * blocking it after the execution of the commmand. + */ + if (sigprocmask(SIG_BLOCK, &sa.sa_mask, NULL) != 0) { + pfmt(stderr, MM_ERROR, "%s: failed to sigprocmask(): %s.\n", + progname, strerror(errno)); + exit(1); + } + + settimeout(&first_interval); + + for (;;) { + /* + * The sa_mask will be empty again, but we will + * also be using sigsuspend() to prevent the + * function assigned to sa_handler of + * interrupting this section. + */ + sigemptyset(&sa.sa_mask); + sigsuspend(&sa.sa_mask); + + if (siglist.sig_chld) { + siglist.sig_chld = 0; + + for (; ((cmdpid = wait(&ecmd)) < 0 && errno == EINTR);); + + if (cmdpid == exec_pid) { + eprog = ecmd; + break; + } + } else if (siglist.sig_alrm || siglist.sig_term) { + if (siglist.sig_alrm) { + siglist.sig_alrm = 0; + timesout = true; + } + + if (! fForeground) { + killpg(pgid, killer_sig); + } else { + kill(exec_pid, siglist.sig_term + ? (int)siglist.sig_term + : killer_sig); + } + + if (! fOnemoretime) { + break; + } else { + settimeout(&second_interval); + fOnemoretime = 0; + siglist.sig_ign = killer_sig; + killer_sig = SIGKILL; + } + } + } + + for (; (cmdpid != exec_pid && wait(&eprog) == -1);) { + if (errno != EINTR) { + pfmt(stderr, MM_ERROR, "%s: failed to wait(): %s.\n", + progname, strerror(errno)); + + exit(1); + } + } + + if (WEXITSTATUS(eprog)) { + eprog = WEXITSTATUS(eprog); + } else if (WIFSIGNALED(eprog)) { + eprog = (128 + WTERMSIG(eprog)); + } + + if (timesout && !fPreserve_status) { + eprog = 124; + } + + return eprog; +} + +void settimeout(struct TClock *duration) { + timer_t tid; + struct itimerspec its; + + /* + * Since the second value, sevp on the prototype of + * timer_create(2), is defined as NULL, every + * expiration of this timer will sent an SIGALRM. + * Just as expected. + */ + if (timer_create(CLOCK_REALTIME, NULL, &tid) != 0) { + pfmt(stderr, MM_ERROR, + "%s: failed to create timer: %s\n", + progname, strerror(errno)); + exit(1); + } + + /* Timer expiration. */ + its.it_value.tv_sec = duration->sec; + its.it_value.tv_nsec = duration->nsec; + /* + * Timer period betwixt expirations. + * taks note: This must be all zeroed, + * ergo option '-k' can work properly. + * itimerspec(3type) is incomplete + * on Linux and reads vague on NetBSD, + * so I'm doing pure Computer Alchemy here. + */ + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + + if (timer_settime(tid, 0, &its, NULL) != 0) { + pfmt(stderr, MM_ERROR, + "%s: failed to set timer: %s\n", + progname, strerror(errno)); + exit(1); + } +} + +int validate_signal(char *str) { + int i; + /* + * Check if the first character of the input string is a letter, so it + * can be parsed into a signal name. + */ + if (isalpha(str[0])) { + /* If it starts with "SIG" */ + if (strncmp(str, "SIG", 3) == 0) { + /* Skip 3 bytes (SIG) */ + str = &str[3]; + } + + for (i = 0; sig_strs[i].signame; ++i) { + if (strcmp(str, sig_strs[i].signame) == 0) + return sig_strs[i].signum; + } + + pfmt(stderr, MM_ERROR, "%s: invalid signal %s.\n", + progname, str); + exit(1); + } else { + i = atoi(str); + + if (i < 0 || i > SIGRTMAX) { + pfmt(stderr, MM_ERROR, "%s: invalid signal %d.\n", + progname, i); + exit(1); + } + return i; + } +} + +/* Boilerplate for parse_interval(). */ +struct TClock validate_duration(char *timestr) { + struct TClock mclock; + + if (parse_interval(timestr, &mclock) == -1) { + pfmt(stderr, MM_ERROR, + "the given interval couldn't be parsed.\n"); + exit(1); + } + + return mclock; +} + +/* + * Returns -1 if the given string is invalid, + * otherwise 0 is returned and the interval + * struct is set. + */ +int parse_interval(const char *ss, struct TClock *interval) { + char s[32]; + char *afterpoint = NULL, + *decsep = NULL, + *tunit = NULL; + /* 'double' for the converted time per unit. */ + double ftime = 0; + size_t i, afterpoint_len; + + /* + * Support both Anglo and European decimal separators. + * taks note: In varietate concordia. 🇧🇷🇪🇺🤝🇺🇸🇬🇧 + */ + decsep = strchr(ss, ','); + if (decsep) { + *decsep = '.'; + } + + ftime = strtod(ss, &tunit); + switch (tunit[0]) { + case '\0': + case 's': + break; + case 'm': + ftime *= 60; + break; + case 'h': + ftime *= (60 * 60); + break; + case 'd': + ftime *= ((60*60) * 24); + break; + default: + pfmt(stderr, MM_ERROR, + "%s: invalid time unit suffix '%c'.\n", + progname, tunit); + exit(1); + } + /* Copy the time value back to 'ss'. */ + sprintf(ss, "%g", ftime); + + strncpy(s, ss, 32); + s[31] = '\0'; + + for (i = 0; s[i]; i++) { + if (s[i] == '.') { + if (afterpoint == NULL) { + afterpoint = &s[i + 1]; + s[i] = '\0'; + } else { + return -1; + } + } else if (!isdigit(s[i])) { + return -1; + } + } + + interval->sec = strlen(s) ? atoi(s) : 0; + interval->nsec = afterpoint ? atoi(afterpoint) : 0; + + if (afterpoint == NULL) { + return 0; + } + + afterpoint_len = strlen(afterpoint); + if (afterpoint_len > 9) { + return -1; + } + for (i = afterpoint_len; i < 9; i++) { + interval->nsec *= 10; + } + + return 0; +} + +/* + * This is just a function that sets variables --- which + * indicates which signals where enabled or not ---, so it + * does not return anything. I wish these could be returned + * on something like a struct, like it was done for the + * interval, but the UNIX ABI does not give this opportunity. + */ +void handle_signal(int signo) { + if (siglist.sig_ign != 0 && signo == siglist.sig_ign) { + // Ignore this signal. + siglist.sig_ign = 0; + return; + } + + switch (signo) { + case SIGHUP: + case SIGINT: + case SIGQUIT: + case SIGTERM: + siglist.sig_term = signo; + break; + case SIGCHLD: + siglist.sig_chld = 1; + break; + case SIGALRM: + siglist.sig_alrm = 1; + break; + } +} + +void usage(void) { + pfmt(stderr, MM_NOSTD, "usage: %s: [-fp] [-s signal] [-k time] " + "time [command [args ...]]\n", progname); + exit(1); +} + diff --git a/watch/Makefile.mk b/watch/Makefile.mk index d100b9a..51bba50 100644 --- a/watch/Makefile.mk +++ b/watch/Makefile.mk @@ -12,4 +12,4 @@ install: all $(MANINST) -c -m 644 watch.1 $(ROOT)$(MANDIR)/man1/watch.1 clean: - rm -f watch watch.o core log *~ + rm -f watch watch.o core log *~ \ No newline at end of file