diff options
Diffstat (limited to 'timer.c')
| -rw-r--r-- | timer.c | 226 | 
1 files changed, 226 insertions, 0 deletions
| @@ -0,0 +1,226 @@ +// SPDX-FileCopyrightText: 2014-2017, 2025 Tomasz Kramkowski <tomasz@kramkow.ski> +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdnoreturn.h> +#include <string.h> +#include <sys/ioctl.h> +#include <time.h> +#include <unistd.h> + +static const unsigned long long SEC_SECOND = 1, SEC_MINUTE = 60, SEC_HOUR = 3600, +				SEC_DAY = 86400, SEC_MONTH = 2629800, +				SEC_YEAR = 31557600; +static const unsigned long INTERVAL_NSEC = 1000000000 / 4; + +volatile unsigned short term_width = 0; + +__attribute__((format(printf, 1, 2))) static noreturn void fatal(const char *fmt, ...) +{ +	va_list ap; +	int errno_save = errno; + +	assert(fmt != NULL); + +	fprintf(stderr, "fatal: "); + +	va_start(ap, fmt); +	vfprintf(stderr, fmt, ap); +	va_end(ap); + +	if (fmt[0] != '\0' && fmt[strlen(fmt) - 1] == ':') { +		fprintf(stderr, " %s", strerror(errno_save)); +	} + +	fputc('\n', stderr); + +	exit(EXIT_FAILURE); +} + +volatile sig_atomic_t got_sigwinch = 0; + +static void sigwinch(int sig) +{ +	if (sig != SIGWINCH) return; + +	got_sigwinch = 1; +} + +static void updatewidth(bool force) +{ +	struct winsize ws; + +	if (!got_sigwinch && !force) return; + +	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) +		fatal("Unable to get terminal width:"); + +	term_width = ws.ws_col; + +	got_sigwinch = 0; +} + +static void clear_line(void) +{ +	fputs("\r\x1b[K", stdout); + +	if (fflush(stdout) != 0) fatal("Unable to flush stdout:"); +} + +static void +format_dmy(char *dest, size_t size, const char *name, unsigned long long count) +{ +	if (count == 1) +		snprintf(dest, size, "%llu %s, ", count, name); +	else if (count > 1) +		snprintf(dest, size, "%llu %ss, ", count, name); +	else +		dest[0] = '\0'; +} + +static void print_time(unsigned long long total_sec, bool show_colon) +{ +	unsigned long long count, hours, minutes, seconds; +	char colon = show_colon ? ':' : ' ', syears[128], smonths[16], sdays[16]; + +	count = total_sec / SEC_YEAR; +	total_sec %= SEC_YEAR; +	format_dmy(syears, sizeof syears, "year", count); + +	count = total_sec / SEC_MONTH; +	total_sec %= SEC_MONTH; +	format_dmy(smonths, sizeof smonths, "month", count); + +	count = total_sec / SEC_DAY; +	total_sec %= SEC_DAY; +	format_dmy(sdays, sizeof sdays, "day", count); + +	hours = total_sec / SEC_HOUR; +	total_sec %= SEC_HOUR; + +	minutes = total_sec / SEC_MINUTE; +	total_sec %= SEC_MINUTE; + +	seconds = total_sec / SEC_SECOND; +	total_sec %= SEC_SECOND; + +	if (total_sec != 0) fatal("Unable to format time"); + +	printf(" %s%s%s%.2llu%c%.2llu%c%.2llu\r", syears, smonths, sdays, hours, +	       colon, minutes, colon, seconds); + +	if (fflush(stdout) != 0) fatal("Unable to flush stdout:"); +} + +static unsigned long long get_seconds(char *code) +{ +	unsigned long long val, mul; +	size_t len; +	char *end; + +	len = strlen(code); + +	if (len < 2) return 0; + +	switch (code[len - 1]) { +	case 's': mul = SEC_SECOND; break; +	case 'm': mul = SEC_MINUTE; break; +	case 'h': mul = SEC_HOUR; break; +	case 'D': mul = SEC_DAY; break; +	case 'M': mul = SEC_MONTH; break; +	case 'Y': mul = SEC_YEAR; break; +	default: return 0; +	} + +	errno = 0; +	val = strtoul(code, &end, 10); +	if (val == ULONG_MAX && errno != 0) +		fatal("Unable to convert time specifier %s:", code); +	if (strlen(end) != 1) fatal("Invalid time specifier %s", code); + +	// might wrap +	return val * mul; +} + +int main(int argc, char **argv) +{ +	int sig; +	sigset_t sigset; +	struct itimerspec its = {{0, INTERVAL_NSEC}, {0, INTERVAL_NSEC}}; +	struct sigaction sigact = {.sa_handler = sigwinch}; +	timer_t timerid; +	struct sigevent sev = { +		.sigev_notify = SIGEV_SIGNAL, +		.sigev_signo = SIGRTMIN, +		.sigev_value.sival_ptr = &timerid +	}; +	unsigned long long total_seconds = 0; + +	if (!isatty(STDOUT_FILENO)) fatal("stdout is not a tty"); + +	if (argc < 2) +		fatal("Not enough arguments.\nUsage:\n" +		      "\t%s <n>{s,m,h,D,M,Y} ...", +		      argv[0]); + +	for (int i = 1; i < argc; i++) total_seconds += get_seconds(argv[i]); + +	if (sigaction(SIGWINCH, &sigact, NULL) != 0) +		fatal("Unable to set SIGWINCH signal action:"); + +	if (timer_create(CLOCK_MONOTONIC, &sev, &timerid) != 0) +		fatal("Unable to create timer:"); + +	if (sigemptyset(&sigset) != 0) fatal("Unable to empty signal set:"); + +	if (sigaddset(&sigset, SIGRTMIN) != 0) +		fatal("Unable to add SIGRTMIN to signal set:"); + +	if (sigprocmask(SIG_BLOCK, &sigset, NULL) != 0) +		fatal("Unable to block SIGRTMIN:"); + +	if (timer_settime(timerid, 0, &its, NULL) != 0) +		fatal("Unable to set timer:"); + +	updatewidth(true); + +	for (unsigned long long i = 0; i < total_seconds; i++) +		for (int ii = 0; ii < 4; ii++) { +			if (sigwait(&sigset, &sig), sig != SIGRTMIN) +				fatal("sigwait returned unexpected signal %d", sig); + +			updatewidth(false); +			clear_line(); +			print_time(total_seconds - i, ii < 2); +		} + +	if (sigaddset(&sigset, SIGINT) != 0) +		fatal("Unable to add SIGINT to signal set:"); + +	while (true) { +		static bool blink = false; + +		blink = !blink; +		sigwait(&sigset, &sig); +		if (sig != SIGRTMIN) break; + +		clear_line(); + +		if (blink) printf(" -- BEEP --\a\r"); + +		if (fflush(stdout) != 0) fatal("Unable to flush stdout:"); +	} + +	timer_delete(timerid); + +	clear_line(); + +	return 0; +} | 
