1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2019 Robert Mustacchi
14 */
15
16 #include <locale.h>
17 #include <stdlib.h>
18 #include <signal.h>
19 #include <unistd.h>
20 #include <stdio.h>
21 #include <err.h>
22 #include <errno.h>
23 #include <math.h>
24 #include <limits.h>
25 #include <time.h>
26 #include <libintl.h>
27
28 /*
29 * This implements the sleep(1) command. It allows for a number of extensions
30 * that match both the GNU implementation and parts of what ksh93 used to
31 * provide. Mainly:
32 *
33 * o Fractional seconds
34 * o Suffixes that change the amount of time
35 */
36
37 typedef struct {
38 char sm_char;
39 uint64_t sm_adj;
40 } sleep_map_t;
41
42 static const sleep_map_t sleep_map[] = {
43 { 's', 1 },
44 { 'm', 60 },
45 { 'h', 60 * 60 },
46 { 'd', 60 * 60 * 24 },
47 { 'w', 60 * 60 * 24 * 7 },
48 { 'y', 60 * 60 * 24 * 365 },
49 { '\0', 0 }
50 };
51
52 static void
sleep_sigalrm(int sig)53 sleep_sigalrm(int sig)
54 {
55 /*
56 * Note, the normal exit(2) function is not Async-Signal-Safe.
57 */
58 _exit(0);
59 }
60
61 int
main(int argc,char * argv[])62 main(int argc, char *argv[])
63 {
64 int c;
65 long double d, sec, frac;
66 char *eptr;
67
68 (void) setlocale(LC_ALL, "");
69 #if !defined(TEXT_DOMAIN)
70 #define TEXT_DOMAIN "SYS_TEST"
71 #endif
72 (void) textdomain(TEXT_DOMAIN);
73
74 (void) signal(SIGALRM, sleep_sigalrm);
75
76 while ((c = getopt(argc, argv, ":")) != -1) {
77 switch (c) {
78 case '?':
79 warnx(gettext("illegal option -- %c"), optopt);
80 (void) fprintf(stderr,
81 gettext("Usage: sleep time[suffix]\n"));
82 exit(EXIT_FAILURE);
83 }
84 }
85
86 argc -= optind;
87 argv += optind;
88
89 if (argc != 1) {
90 warnx(gettext("only one operand is supported"));
91 (void) fprintf(stderr, gettext("Usage: sleep time[suffix]\n"));
92 exit(EXIT_FAILURE);
93 }
94
95 errno = 0;
96 d = strtold(argv[0], &eptr);
97 if (errno != 0 || (eptr[0] != '\0' && eptr[1] != '\0') ||
98 eptr == argv[0] || d == NAN) {
99 errx(EXIT_FAILURE, gettext("failed to parse time '%s'"),
100 argv[0]);
101 }
102
103 if (d < 0.0) {
104 errx(EXIT_FAILURE,
105 gettext("time interval '%s', cannot be negative"), argv[0]);
106 }
107
108 if (eptr[0] != '\0') {
109 int i;
110 for (i = 0; sleep_map[i].sm_char != '\0'; i++) {
111 if (sleep_map[i].sm_char == eptr[0]) {
112 d *= sleep_map[i].sm_adj;
113 break;
114 }
115 }
116
117 if (sleep_map[i].sm_char == '\0') {
118 errx(EXIT_FAILURE, gettext("failed to parse time %s"),
119 argv[0]);
120 }
121 }
122
123 /*
124 * If we have no time, then we're done. Short circuit.
125 */
126 if (d == 0) {
127 exit(EXIT_SUCCESS);
128 }
129
130 /*
131 * Split this apart into the fractional and seconds parts to make it
132 * easier to work with.
133 */
134 frac = modfl(d, &sec);
135
136 /*
137 * We may have a rather large double value. Chop it up in units of
138 * INT_MAX.
139 */
140 while (sec > 0 || frac != 0) {
141 struct timespec ts;
142
143 if (frac != 0) {
144 frac *= NANOSEC;
145 ts.tv_nsec = (long)frac;
146 frac = 0;
147 } else {
148 ts.tv_nsec = 0;
149 }
150
151 /*
152 * We have a floating point number of fractional seconds. We
153 * need to convert that to nanoseconds.
154 */
155 if (sec > (float)INT_MAX) {
156 ts.tv_sec = INT_MAX;
157 } else {
158 ts.tv_sec = (time_t)sec;
159 }
160 sec -= ts.tv_sec;
161
162 if (nanosleep(&ts, NULL) != 0) {
163 err(EXIT_FAILURE, gettext("nanosleep failed"));
164 }
165 }
166
167 return (0);
168 }
169