diff options
Diffstat (limited to 'sleep.c')
| -rw-r--r-- | sleep.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/sleep.c b/sleep.c new file mode 100644 index 0000000000..d56845b4fe --- /dev/null +++ b/sleep.c @@ -0,0 +1,269 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Copyright (c) 2026 + * Project Tick. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdarg.h> +#include <stdbool.h> +#include <errno.h> +#include <limits.h> +#include <math.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#ifndef SIGINFO +#define SLEEP_INFO_SIGNAL SIGUSR1 +#else +#define SLEEP_INFO_SIGNAL SIGINFO +#endif + +#define NSECS_PER_SEC 1000000000L + +static volatile sig_atomic_t report_requested; + +static void usage(void) __attribute__((noreturn)); +static void die(const char *fmt, ...) __attribute__((noreturn, format(printf, 1, 2))); +static void die_errno(const char *context) __attribute__((noreturn)); +static void install_info_handler(void); +static long double seconds_from_timespec(const struct timespec *ts); +static long double maximum_time_t_seconds(void); +static void report_remaining(const struct timespec *remaining, long double original); +static long double scale_interval(long double value, int multiplier, const char *arg); +static long double parse_interval(const char *arg); +static struct timespec seconds_to_timespec(long double seconds); + +static void +report_request(int signo) +{ + (void)signo; + report_requested = 1; +} + +static void +usage(void) +{ + fprintf(stderr, "usage: sleep number[unit] [...]\n" + "Unit can be 's' (seconds, the default), " + "m (minutes), h (hours), or d (days).\n"); + exit(1); +} + +static void +die(const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "sleep: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +static void +die_errno(const char *context) +{ + die("%s: %s", context, strerror(errno)); +} + +static void +install_info_handler(void) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = report_request; + sigemptyset(&sa.sa_mask); + if (sigaction(SLEEP_INFO_SIGNAL, &sa, NULL) != 0) + die_errno("sigaction"); +} + +static long double +seconds_from_timespec(const struct timespec *ts) +{ + return ((long double)ts->tv_sec + + (long double)ts->tv_nsec / (long double)NSECS_PER_SEC); +} + +static long double +maximum_time_t_seconds(void) +{ + int bits; + + bits = (int)(sizeof(time_t) * CHAR_BIT); + if ((time_t)-1 < (time_t)0) + return (ldexpl(1.0L, bits - 1) - 1.0L); + return (ldexpl(1.0L, bits) - 1.0L); +} + +static void +report_remaining(const struct timespec *remaining, long double original) +{ + if (printf("about %.9Lf second(s) left out of the original %.9Lf\n", + seconds_from_timespec(remaining), original) < 0) + die_errno("stdout"); + if (fflush(stdout) != 0) + die_errno("stdout"); +} + +static long double +scale_interval(long double value, int multiplier, const char *arg) +{ + long double result; + + result = value * (long double)multiplier; + if (!isfinite(result)) + die("time interval out of range: %s", arg); + return (result); +} + +static long double +parse_interval(const char *arg) +{ + long double value; + char *end; + + errno = 0; + value = strtold(arg, &end); + if (end == arg) + die("invalid time interval: %s", arg); + if (errno == ERANGE) + die("time interval out of range: %s", arg); + if (!isfinite(value)) + die("non-finite time interval is not supported on Linux: %s", arg); + if (*end == '\0') + return (value); + if (end[1] != '\0') + die("invalid time interval: %s", arg); + + switch (*end) { + case 's': + return (value); + case 'm': + return (scale_interval(value, 60, arg)); + case 'h': + return (scale_interval(value, 60 * 60, arg)); + case 'd': + return (scale_interval(value, 24 * 60 * 60, arg)); + default: + die("unsupported time unit in interval '%s': '%c' (supported: s, m, h, d)", + arg, *end); + } +} + +static struct timespec +seconds_to_timespec(long double seconds) +{ + struct timespec ts; + long double whole; + long double fractional; + long double nsec; + long double max_seconds; + + if (seconds <= 0.0L) { + ts.tv_sec = 0; + ts.tv_nsec = 0; + return (ts); + } + + max_seconds = maximum_time_t_seconds(); + if (seconds > max_seconds + 1.0L) + die("requested interval is too large for Linux sleep APIs"); + + fractional = modfl(seconds, &whole); + if (whole > max_seconds) + die("requested interval is too large for Linux sleep APIs"); + + ts.tv_sec = (time_t)whole; + nsec = ceill(fractional * (long double)NSECS_PER_SEC); + if (nsec >= (long double)NSECS_PER_SEC) { + if ((long double)ts.tv_sec >= max_seconds) + die("requested interval is too large for Linux sleep APIs"); + ts.tv_sec += 1; + ts.tv_nsec = 0; + return (ts); + } + if (nsec <= 0.0L) + nsec = 1.0L; + ts.tv_nsec = (long)nsec; + return (ts); +} + +int +main(int argc, char *argv[]) +{ + struct timespec time_to_sleep; + long double original; + long double seconds; + int i; + + if (argc > 1 && strcmp(argv[1], "--") == 0) { + argv++; + argc--; + } + if (argc < 2) + usage(); + + seconds = 0.0L; + for (i = 1; i < argc; i++) { + long double interval; + long double total; + + interval = parse_interval(argv[i]); + total = seconds + interval; + if (!isfinite(total)) + die("time interval out of range after adding: %s", argv[i]); + seconds = total; + } + if (seconds <= 0.0L) + exit(0); + + time_to_sleep = seconds_to_timespec(seconds); + original = seconds_from_timespec(&time_to_sleep); + install_info_handler(); + + while (nanosleep(&time_to_sleep, &time_to_sleep) != 0) { + if (errno != EINTR) + die_errno("nanosleep"); + if (report_requested) { + report_remaining(&time_to_sleep, original); + report_requested = 0; + } + } + + exit(0); +} |
