aboutsummaryrefslogtreecommitdiffstats
path: root/timer.c
diff options
context:
space:
mode:
Diffstat (limited to 'timer.c')
-rw-r--r--timer.c226
1 files changed, 226 insertions, 0 deletions
diff --git a/timer.c b/timer.c
new file mode 100644
index 0000000..a303a71
--- /dev/null
+++ b/timer.c
@@ -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;
+}