root/usr/src/cmd/sleep/sleep.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2019 Robert Mustacchi
 */

#include <locale.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <err.h>
#include <errno.h>
#include <math.h>
#include <limits.h>
#include <time.h>
#include <libintl.h>

/*
 * This implements the sleep(1) command. It allows for a number of extensions
 * that match both the GNU implementation and parts of what ksh93 used to
 * provide. Mainly:
 *
 *  o Fractional seconds
 *  o Suffixes that change the amount of time
 */

typedef struct {
        char            sm_char;
        uint64_t        sm_adj;
} sleep_map_t;

static const sleep_map_t sleep_map[] = {
        { 's', 1 },
        { 'm', 60 },
        { 'h', 60 * 60 },
        { 'd', 60 * 60 * 24 },
        { 'w', 60 * 60 * 24 * 7 },
        { 'y', 60 * 60 * 24 * 365 },
        { '\0', 0 }
};

static void
sleep_sigalrm(int sig)
{
        /*
         * Note, the normal exit(2) function is not Async-Signal-Safe.
         */
        _exit(0);
}

int
main(int argc, char *argv[])
{
        int c;
        long double d, sec, frac;
        char *eptr;

        (void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
        (void) textdomain(TEXT_DOMAIN);

        (void) signal(SIGALRM, sleep_sigalrm);

        while ((c = getopt(argc, argv, ":")) != -1) {
                switch (c) {
                case '?':
                        warnx(gettext("illegal option -- %c"), optopt);
                        (void) fprintf(stderr,
                            gettext("Usage: sleep time[suffix]\n"));
                        exit(EXIT_FAILURE);
                }
        }

        argc -= optind;
        argv += optind;

        if (argc != 1) {
                warnx(gettext("only one operand is supported"));
                (void) fprintf(stderr, gettext("Usage: sleep time[suffix]\n"));
                exit(EXIT_FAILURE);
        }

        errno = 0;
        d = strtold(argv[0], &eptr);
        if (errno != 0 || (eptr[0] != '\0' && eptr[1] != '\0') ||
            eptr == argv[0] || d == NAN) {
                errx(EXIT_FAILURE, gettext("failed to parse time '%s'"),
                    argv[0]);
        }

        if (d < 0.0) {
                errx(EXIT_FAILURE,
                    gettext("time interval '%s', cannot be negative"), argv[0]);
        }

        if (eptr[0] != '\0') {
                int i;
                for (i = 0; sleep_map[i].sm_char != '\0'; i++) {
                        if (sleep_map[i].sm_char == eptr[0]) {
                                d *= sleep_map[i].sm_adj;
                                break;
                        }
                }

                if (sleep_map[i].sm_char == '\0') {
                        errx(EXIT_FAILURE, gettext("failed to parse time %s"),
                            argv[0]);
                }
        }

        /*
         * If we have no time, then we're done. Short circuit.
         */
        if (d == 0) {
                exit(EXIT_SUCCESS);
        }

        /*
         * Split this apart into the fractional and seconds parts to make it
         * easier to work with.
         */
        frac = modfl(d, &sec);

        /*
         * We may have a rather large double value. Chop it up in units of
         * INT_MAX.
         */
        while (sec > 0 || frac != 0) {
                struct timespec ts;

                if (frac != 0) {
                        frac *= NANOSEC;
                        ts.tv_nsec = (long)frac;
                        frac = 0;
                } else {
                        ts.tv_nsec = 0;
                }

                /*
                 * We have a floating point number of fractional seconds. We
                 * need to convert that to nanoseconds.
                 */
                if (sec > (float)INT_MAX) {
                        ts.tv_sec = INT_MAX;
                } else {
                        ts.tv_sec = (time_t)sec;
                }
                sec -= ts.tv_sec;

                if (nanosleep(&ts, NULL) != 0) {
                        err(EXIT_FAILURE, gettext("nanosleep failed"));
                }
        }

        return (0);
}