// SPDX-FileCopyrightText: 2014-2017, 2025 Tomasz Kramkowski // SPDX-License-Identifier: GPL-3.0-or-later #include #include #include #include #include #include #include #include #include #include #include #include #include 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 {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; }