coreutils

General Software Utilities
git clone http://git.omkov.net/coreutils
Log | Tree | Refs | README | LICENCE | Download

AuthorJamozed <[email protected]>
Date2022-02-28 14:04:16
Commit48e316bef1bc769c698b51ec0b987caf74d5a993
Parentd57b240f3cd7ad52e749855d70ee811abfeb0e48

timeout: Add timeout utility

Diffstat

M CMakeLists.txt | 85 ++++++++++++++++++++++++++++++++++++++++---------------------------------------
A src/timeout.c | 257 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

2 files changed, 300 insertions, 42 deletions

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4603db4..9eb8a66 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,47 +1,48 @@
-CMAKE_MINIMUM_REQUIRED(VERSION 3.12)
-PROJECT(coreutils LANGUAGES C)
+cmake_minimum_required(VERSION 3.12)
+project(coreutils LANGUAGES C)
 
-SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
-SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
-SET(CMAKE_STATIC_LIBRARY_PREFIX "")
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
+set(CMAKE_STATIC_LIBRARY_PREFIX "")
 
-FILE(GLOB SRC_UTIL ${PROJECT_SOURCE_DIR}/src/util/*)
+file(GLOB SRC_UTIL ${PROJECT_SOURCE_DIR}/src/util/*)
 
-ADD_LIBRARY(libutil STATIC ${SRC_UTIL})
+add_library(libutil STATIC ${SRC_UTIL})
 
-LINK_LIBRARIES(libutil)
+link_libraries(libutil)
 
-ADD_EXECUTABLE(base64   ${PROJECT_SOURCE_DIR}/src/base64.c)
-ADD_EXECUTABLE(basename ${PROJECT_SOURCE_DIR}/src/basename.c)
-ADD_EXECUTABLE(cat      ${PROJECT_SOURCE_DIR}/src/cat.c)
-ADD_EXECUTABLE(chmod    ${PROJECT_SOURCE_DIR}/src/chmod.c)
-ADD_EXECUTABLE(cksum    ${PROJECT_SOURCE_DIR}/src/cksum.c)
-ADD_EXECUTABLE(cp       ${PROJECT_SOURCE_DIR}/src/cp.c)
-ADD_EXECUTABLE(dirname  ${PROJECT_SOURCE_DIR}/src/dirname.c)
-ADD_EXECUTABLE(echo     ${PROJECT_SOURCE_DIR}/src/echo.c)
-ADD_EXECUTABLE(env      ${PROJECT_SOURCE_DIR}/src/env.c)
-ADD_EXECUTABLE(false    ${PROJECT_SOURCE_DIR}/src/false.c)
-ADD_EXECUTABLE(head     ${PROJECT_SOURCE_DIR}/src/head.c)
-ADD_EXECUTABLE(id       ${PROJECT_SOURCE_DIR}/src/id.c)
-ADD_EXECUTABLE(link     ${PROJECT_SOURCE_DIR}/src/link.c)
-ADD_EXECUTABLE(logname  ${PROJECT_SOURCE_DIR}/src/logname.c)
-ADD_EXECUTABLE(mkdir    ${PROJECT_SOURCE_DIR}/src/mkdir.c)
-ADD_EXECUTABLE(nice     ${PROJECT_SOURCE_DIR}/src/nice.c)
-ADD_EXECUTABLE(orphan   ${PROJECT_SOURCE_DIR}/src/orphan.c)
-ADD_EXECUTABLE(od       ${PROJECT_SOURCE_DIR}/src/od.c)
-ADD_EXECUTABLE(pwd      ${PROJECT_SOURCE_DIR}/src/pwd.c)
-ADD_EXECUTABLE(rand     ${PROJECT_SOURCE_DIR}/src/rand.c)
-ADD_EXECUTABLE(realpath ${PROJECT_SOURCE_DIR}/src/realpath.c)
-ADD_EXECUTABLE(rmdir    ${PROJECT_SOURCE_DIR}/src/rmdir.c)
-ADD_EXECUTABLE(sleep    ${PROJECT_SOURCE_DIR}/src/sleep.c)
-ADD_EXECUTABLE(sum      ${PROJECT_SOURCE_DIR}/src/sum.c)
-ADD_EXECUTABLE(sync     ${PROJECT_SOURCE_DIR}/src/sync.c)
-ADD_EXECUTABLE(tee      ${PROJECT_SOURCE_DIR}/src/tee.c)
-ADD_EXECUTABLE(time     ${PROJECT_SOURCE_DIR}/src/time.c)
-ADD_EXECUTABLE(touch    ${PROJECT_SOURCE_DIR}/src/touch.c)
-ADD_EXECUTABLE(true     ${PROJECT_SOURCE_DIR}/src/true.c)
-ADD_EXECUTABLE(tty      ${PROJECT_SOURCE_DIR}/src/tty.c)
-ADD_EXECUTABLE(uname    ${PROJECT_SOURCE_DIR}/src/uname.c)
-ADD_EXECUTABLE(unlink   ${PROJECT_SOURCE_DIR}/src/unlink.c)
-ADD_EXECUTABLE(wc       ${PROJECT_SOURCE_DIR}/src/wc.c)
-ADD_EXECUTABLE(yes      ${PROJECT_SOURCE_DIR}/src/yes.c)
+add_executable(base64   ${PROJECT_SOURCE_DIR}/src/base64.c)
+add_executable(basename ${PROJECT_SOURCE_DIR}/src/basename.c)
+add_executable(cat      ${PROJECT_SOURCE_DIR}/src/cat.c)
+add_executable(chmod    ${PROJECT_SOURCE_DIR}/src/chmod.c)
+add_executable(cksum    ${PROJECT_SOURCE_DIR}/src/cksum.c)
+add_executable(cp       ${PROJECT_SOURCE_DIR}/src/cp.c)
+add_executable(dirname  ${PROJECT_SOURCE_DIR}/src/dirname.c)
+add_executable(echo     ${PROJECT_SOURCE_DIR}/src/echo.c)
+add_executable(env      ${PROJECT_SOURCE_DIR}/src/env.c)
+add_executable(false    ${PROJECT_SOURCE_DIR}/src/false.c)
+add_executable(head     ${PROJECT_SOURCE_DIR}/src/head.c)
+add_executable(id       ${PROJECT_SOURCE_DIR}/src/id.c)
+add_executable(link     ${PROJECT_SOURCE_DIR}/src/link.c)
+add_executable(logname  ${PROJECT_SOURCE_DIR}/src/logname.c)
+add_executable(mkdir    ${PROJECT_SOURCE_DIR}/src/mkdir.c)
+add_executable(nice     ${PROJECT_SOURCE_DIR}/src/nice.c)
+add_executable(orphan   ${PROJECT_SOURCE_DIR}/src/orphan.c)
+add_executable(od       ${PROJECT_SOURCE_DIR}/src/od.c)
+add_executable(pwd      ${PROJECT_SOURCE_DIR}/src/pwd.c)
+add_executable(rand     ${PROJECT_SOURCE_DIR}/src/rand.c)
+add_executable(realpath ${PROJECT_SOURCE_DIR}/src/realpath.c)
+add_executable(rmdir    ${PROJECT_SOURCE_DIR}/src/rmdir.c)
+add_executable(sleep    ${PROJECT_SOURCE_DIR}/src/sleep.c)
+add_executable(sum      ${PROJECT_SOURCE_DIR}/src/sum.c)
+add_executable(sync     ${PROJECT_SOURCE_DIR}/src/sync.c)
+add_executable(tee      ${PROJECT_SOURCE_DIR}/src/tee.c)
+add_executable(time     ${PROJECT_SOURCE_DIR}/src/time.c)
+add_executable(timeout  ${PROJECT_SOURCE_DIR}/src/timeout.c)
+add_executable(touch    ${PROJECT_SOURCE_DIR}/src/touch.c)
+add_executable(true     ${PROJECT_SOURCE_DIR}/src/true.c)
+add_executable(tty      ${PROJECT_SOURCE_DIR}/src/tty.c)
+add_executable(uname    ${PROJECT_SOURCE_DIR}/src/uname.c)
+add_executable(unlink   ${PROJECT_SOURCE_DIR}/src/unlink.c)
+add_executable(wc       ${PROJECT_SOURCE_DIR}/src/wc.c)
+add_executable(yes      ${PROJECT_SOURCE_DIR}/src/yes.c)
diff --git a/src/timeout.c b/src/timeout.c
new file mode 100644
index 0000000..3ab1a00
--- /dev/null
+++ b/src/timeout.c
@@ -0,0 +1,257 @@
+// timeout.c
+// OMKOV coreutils timeout
+// Copyright (C) 2022, Jakob Wakeling
+// All rights reserved.
+
+#define VERSION "0.1.0"
+
+/*
+OMKOV Permissive Licence, version 1.0
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal with
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimers.
+* Redistributions in binary form must reproduce the above copyright notice, this
+  list of conditions and the following disclaimers in the documentation and/or
+  other materials provided with the distribution.
+* Neither the names of the copyright holders, nor the names of its contributors
+  may be used to endorse or promote products derived from this Software without
+  specific prior written permission.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT
+HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
+*/
+
+#include "util/error.h"
+#include "util/optget.h"
+#include "util/util.h"
+
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+static struct lop lops[] = {
+	{ "help",    ARG_NUL, 256 },
+	{ "version", ARG_NUL, 257 },
+	{ NULL, 0, 0 }
+};
+
+static f64 itime = 0, ktime = 0;
+static int tosig = SIGTERM;
+
+static pid_t pid = 0;
+static bool tout = false;
+
+static f64 time_parse(const char *s);
+static int timeout(char *av[]);
+
+static void signal_install(int sig);
+static void signal_block(int sig, sigset_t *oset);
+static void handle_sigalrm(int sig);
+static void handle_sigchld(int sig);
+static int signal_send(pid_t pid, int sig);
+
+static void timer(f64 duration);
+static struct timespec f64totimespec(f64 seconds);
+
+static void hlp(void);
+static void ver(void);
+
+int main(int ac, char *av[]) { A0 = av[0];
+	struct opt opt = OPTGET_INIT; opt.str = "k:s:"; opt.lops = lops;
+	for (int o; (o = optget(&opt, av, 1)) != -1;) switch (o) {
+	case 'k': { ktime = time_parse(opt.arg); } break;
+	case 's': { /* TODO */ return 1; } break;
+	case 256: { hlp(); } return 0;
+	case 257: { ver(); } return 0;
+	default: {} return 125;
+	}
+	
+	if ((ac - opt.ind) < 2) { error(125, "missing operand(s)"); }
+	
+	itime = time_parse(av[opt.ind]);
+	return timeout(&av[opt.ind + 1]);
+}
+
+/* Parse a duration string. */
+static inline f64 time_parse(const char *s) {
+	char *ep = NULL; errno = 0; f64 time = strtod(s, &ep);
+	if (errno != 0) { error(1, "%s: %s", s, SERR); }
+	if (time < 0) { error(1, "%s: duration cannot be negative", s); }
+	
+	/* Check for an apply any valid suffix */
+	if (ep != NULL && ep[0] != '\0') {
+		switch (ep[0]) {
+		case 'D': { time *= 86400; } break;
+		case 'h': { time *= 3600;  } break;
+		case 'm': { time *= 60;    } break;
+		case 's': { time *= 1;     } break;
+		default: { error(1, "%s: invalid suffix", s); } break;
+		}
+		
+		if (ep[1] != '\0') { error(1, "%s: invalid suffix", s); }
+	}
+	
+	return time;
+}
+
+static int timeout(char *av[]) {
+	signal_install(tosig);
+	signal(SIGTTIN, SIG_IGN);
+	signal(SIGTTOU, SIG_IGN);
+	
+	switch ((pid = fork())) {
+	case -1: { error(125, "%s", SERR); }
+	case  0: { /* Child */
+		signal(SIGTTIN, SIG_DFL);
+		signal(SIGTTOU, SIG_DFL);
+		
+		execvp(av[0], av);
+		
+		error((errno = ENOENT) ? 127 : 126, "%s: %s", av[0], SERR);
+	} break;
+	default: { /* Parent */
+		int status; pid_t wpid; sigset_t sset;
+		
+		/* Unblock and start the timeout timer */
+		/* TODO unblock */ timer(itime);
+		
+		signal_block(tosig, &sset);
+		
+		/* Wait for the timer to expire, or the child to die */
+		for (; (wpid = waitpid(pid, &status, WNOHANG)) == 0;) {
+			sigsuspend(&sset);
+		}
+		
+		if (wpid == -1) { error(125, "%s", SERR); }
+		
+		/* If the child process exited normally */
+		if (WIFEXITED(status)) { status = WEXITSTATUS(status); }
+		/* If the child process exited abnormally */
+		else if (WIFSIGNALED(status)) {
+			int sig = WTERMSIG(status);
+			
+			status = 128 + sig;
+		}
+		else { error(125, "%s", SERR); }
+		
+		return tout ? 124 : status;
+	} break;
+	}
+}
+
+/* Configure SIGALRM handling. */
+static void signal_install(int sig) {
+	struct sigaction sa0 = { .sa_handler = handle_sigalrm, .sa_flags = SA_RESTART };
+	struct sigaction sa1 = { .sa_handler = handle_sigchld, .sa_flags = SA_RESTART };
+	
+	sigemptyset(&sa0.sa_mask);
+	sigaction(SIGALRM, &sa0, NULL); /* Alarm signal, from the timeout timer */
+	sigaction(SIGHUP,  &sa0, NULL); /* Hangup signal, i.e. upon tty closing */
+	sigaction(SIGINT,  &sa0, NULL); /* Interrupt signal, i.e. from Ctrl+C */
+	sigaction(SIGQUIT, &sa0, NULL); /* Quit signal, i.e. from Ctrl+\ */
+	sigaction(SIGTERM, &sa0, NULL); /* Terminate signal, i.e. upon death */
+	sigaction(sig,     &sa0, NULL); /* User specified signal */
+	
+	sigemptyset(&sa1.sa_mask);
+	sigaction(SIGCHLD, &sa1, NULL); /* Child signal, i.e. upon child death */
+}
+
+/* Block all appropriate signals, as per signal_install(). */
+static void signal_block(int sig, sigset_t *oset) {
+	sigset_t set; sigemptyset(&set);
+	sigaddset(&set, SIGALRM);
+	sigaddset(&set, SIGHUP);
+	sigaddset(&set, SIGINT);
+	sigaddset(&set, SIGQUIT);
+	sigaddset(&set, SIGTERM);
+	sigaddset(&set, sig);
+	
+	sigaddset(&set, SIGCHLD);
+	
+	if (sigprocmask(SIG_BLOCK, &set, oset) == -1) { warn("%s", SERR); }
+}
+
+/* Handle SIGALRM. */
+static void handle_sigalrm(int sig) {
+	if (sig == SIGALRM) { tout = true; sig = tosig; }
+	
+	/* If we are the child, or the child is not yet born, then exit */
+	if (pid == 0) { exit(128 + sig); }
+	
+	if (ktime) {
+		/* Start a new timeout, after which SIGKILL will be sent */
+		int ern = errno; tosig = SIGKILL; timer(ktime); ktime = 0; errno = ern;
+	}
+	
+	signal_send(pid, sig);
+}
+
+/* Handle SIGCHLD. */
+static void handle_sigchld(int sig) {}
+
+/* Send a signal to a process, ignoring signals in the case of a group. */
+static int signal_send(pid_t pid, int sig) {
+	if (pid == 0) { signal(sig, SIG_IGN); }
+	return kill(pid, sig);
+}
+
+/* Start the timeout timer, after which we'll recieve a SIGALRM. */
+static void timer(f64 duration) {
+	struct timespec ts = f64totimespec(duration);
+	struct itimerspec its = { { 0, 0 }, ts }; timer_t tid;
+	
+	/* Create and set a timer with timer_create and timer_settime */
+	if (timer_create(CLOCK_REALTIME, NULL, &tid) == 0) {
+		if (timer_settime(tid, 0, &its, NULL) == 0) { return; }
+		else { warn("timer_settime: %s", SERR); timer_delete(tid); }
+	}
+	else { warn("timer_create: %s", SERR); }
+	
+	/* Fallback to alarm() with single second precision upon failure */
+	if (duration >= (f64)U32_MAX) { alarm(U32_MAX); }
+	else { alarm((u32)duration + ((u32)duration < duration)); }
+}
+
+/* Convert an f64 value to a timespec struct. */
+static struct timespec f64totimespec(f64 seconds) {
+	struct timespec ts; seconds += 0.5e-9;
+	ts.tv_sec = (time_t)seconds;
+	ts.tv_nsec = (seconds - ts.tv_sec) * 1.0e+9;
+	return ts;
+}
+
+/* Print help information. */
+static void hlp(void) {
+	puts("timeout - Invoke a command with a timelimit\n");
+	puts("Usage:");
+	puts("  timeout [-ks] duration command [argument...]\n");
+	puts("Options:");
+	puts("  -k duration  Send a KILL signal after the specified duration");
+	puts("  -s signal    Send the specified signal instead of SIGINT");
+	puts("  --help       Display help information");
+	puts("  --version    Display version information");
+}
+
+/* Print version information. */
+static void ver(void) {
+	puts("OMKOV cryptutils timeout, version " VERSION);
+	puts("Copyright (C) 2022, Jakob Wakeling");
+	puts("All rights reserved.");
+	puts("OMKOV Permissive Licence (https://www.omkov.net/OLPE)");
+}