commit: d632a0f3a2e8f5f8ebfa141fb48acd7539dc18e6
parent 281599280ffac7e38e6b53b11f493aad214769e9
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Sun, 13 Aug 2023 18:49:20 +0200
cmd/sleep: Allow decimal numbers / floats
Diffstat:
4 files changed, 131 insertions(+), 41 deletions(-)
diff --git a/cmd/sleep.1 b/cmd/sleep.1
@@ -1,5 +1,5 @@
.\" Collection of Unix tools, comparable to coreutils
-.\" Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" Copyright 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
.\" SPDX-License-Identifier: MPL-2.0
.Dd 2022-11-19
.Dt SLEEP 1
@@ -18,13 +18,13 @@ utily shell suspends execution for the total of each
argument.
.Pp
.Ar duration
-is a non-negative decimal integer optionally followed by a suffix: s for seconds (default), m for minutes, h for hours.
+is a non-negative decimal number including floats, optionally followed by a suffix: s for seconds (default), m for minutes, h for hours.
Longer durations are taken as out of scope.
.Sh EXIT STATUS
.Ex -std
.Sh SEE ALSO
.Xr at 1 ,
.Xr crontab 1 ,
-.Xr sleep 3
+.Xr nanosleep 3
.Sh AUTHORS
.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me
diff --git a/cmd/sleep.c b/cmd/sleep.c
@@ -3,47 +3,83 @@
// SPDX-License-Identifier: MPL-2.0
#define _POSIX_C_SOURCE 200809L
-#include <errno.h> // ENOTSUP
-#include <inttypes.h> // strtou
-#include <limits.h> // UINT_MAX, _POSIX_ARG_MAX
-#include <stdio.h> // fprintf, perror
-#include <stdlib.h> // exit
-#include <string.h> // strnlen
-#include <unistd.h> // sleep
-
-uintmax_t
-argtonum(char *arg)
+#include <errno.h> // errno
+#include <stdio.h> // fprintf, perror, sscanf
+#include <stdlib.h> // exit
+#include <time.h> // nanosleep, timespec
+
+// Maybe should be moved in ./lib with iso_parse
+static struct timespec
+strtodur(char *s)
{
- char *end;
- errno = 0;
- unsigned long dur = strtoul(arg, &end, 10);
- if(errno != 0)
+ struct timespec dur = {.tv_sec = 0, .tv_nsec = 0};
+
+ if(s[0] == 0)
{
- perror("sleep: strtoul");
- exit(1);
+ fprintf(stderr, "sleep: Got an empty string as duration\n");
+ return dur;
}
- if(end[0] != 0)
+
+ int parsed = 0;
+ if(s[0] != '.' && s[0] != ',')
{
- size_t end_len = strnlen(end, _POSIX_ARG_MAX);
- if(end_len != 1)
+ errno = 0;
+ if(sscanf(s, "%10ld%n", &dur.tv_sec, &parsed) < 1)
{
- fprintf(stderr, "sleep: suffix '%s' is too long, should be only one character\n", end);
- exit(1);
+ if(errno == 0)
+ {
+ fprintf(stderr, "sleep: Not a number: %s\n", s);
+ }
+ else
+ {
+ perror("sleep: sscanf");
+ }
+ exit(EXIT_FAILURE);
}
- switch(end[0])
+
+ s += parsed;
+
+ if(s[0] == 0) return dur;
+ }
+
+ if(s[0] == '.' || s[0] == ',')
+ {
+ double fraction = 0.0;
+ if(s[0] == ',') s[0] = '.';
+
+ parsed = 0;
+ errno = 0;
+ if(sscanf(s, "%10lf%n", &fraction, &parsed) < 1)
{
- case 's': // seconds
- break;
- case 'm': // minutes
- dur *= 60;
- break;
- case 'h': // hours
- dur *= 60 * 60;
- break;
- default:
- fprintf(stderr, "sleep: Unknown suffix %c\n", end[0]);
- exit(1);
+ perror("sleep: sscanf");
+ exit(EXIT_FAILURE);
}
+
+ dur.tv_nsec = fraction * 1000000000;
+ s += parsed;
+ }
+
+ if(s[0] == 0) return dur;
+
+ if(s[1] != 0)
+ {
+ fprintf(stderr, "sleep: suffix '%s' is too long, should be only one character\n", s);
+ exit(1);
+ }
+
+ switch(s[0])
+ {
+ case 's': // seconds
+ break;
+ case 'm': // minutes
+ dur.tv_sec *= 60;
+ break;
+ case 'h': // hours
+ dur.tv_sec *= 60 * 60;
+ break;
+ default:
+ fprintf(stderr, "sleep: Unknown suffix %c\n", s[0]);
+ exit(1);
}
return dur;
@@ -52,18 +88,42 @@ argtonum(char *arg)
int
main(int argc, char *argv[])
{
- unsigned int dur = 0;
+ struct timespec dur = {.tv_sec = 0, .tv_nsec = 0};
for(int i = 1; i < argc; i++)
{
- dur += argtonum(argv[i]);
+ struct timespec arg_dur = strtodur(argv[i]);
+
+ dur.tv_sec += arg_dur.tv_sec;
+ dur.tv_nsec += arg_dur.tv_nsec;
+ if(dur.tv_nsec > 999999999)
+ {
+ dur.tv_nsec = 0;
+ dur.tv_sec += 1;
+ }
}
- if(dur == 0)
+ if(dur.tv_sec == 0 && dur.tv_nsec == 0)
{
fprintf(stderr, "sleep: Got a duration of 0\n");
return 1;
}
- sleep(dur);
+ //fprintf(stderr, "sleep: going to sleep for %ld.%ld seconds\n", dur.tv_sec, dur.tv_nsec);
+
+ errno = 0;
+ if(nanosleep(&dur, &dur) < 0)
+ {
+ if(errno == EINTR)
+ {
+ fprintf(stderr,
+ "sleep: Interrupted during sleep, still had %ld.%ld seconds remaining\n",
+ dur.tv_sec,
+ dur.tv_nsec);
+ }
+ else
+ {
+ perror("sleep: nanosleep");
+ }
+ }
return 0;
}
diff --git a/test-cmd/Kyuafile b/test-cmd/Kyuafile
@@ -9,8 +9,8 @@ basedir = fs.dirname(fs.dirname(current_kyuafile()))
-- 9,$|LC_ALL=C.UTF-8 sort
-- atf_test_program{name="pat", required_files=basedir.."/cmd/pat", timeout=1}
atf_test_program{name="args", required_files=basedir.."/cmd/args", timeout=1}
-atf_test_program{name="basename", required_files=basedir.."/cmd/basename", timeout=1}
atf_test_program{name="base64", required_files=basedir.."/cmd/base64", timeout=1}
+atf_test_program{name="basename", required_files=basedir.."/cmd/basename", timeout=1}
atf_test_program{name="cat", required_files=basedir.."/cmd/cat", timeout=1}
atf_test_program{name="date", required_files=basedir.."/cmd/date", timeout=1}
atf_test_program{name="del", required_files=basedir.."/cmd/del", timeout=1}
diff --git a/test-cmd/sleep.t b/test-cmd/sleep.t
@@ -0,0 +1,30 @@
+#!/usr/bin/env cram
+# SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+ $ cd $TESTDIR/../cmd
+
+ $ ./sleep
+ sleep: Got a duration of 0
+ [1]
+
+ $ ./sleep -f
+ sleep: Not a number: -f
+ [1]
+
+ $ ./time ./sleep 0
+ sleep: Got a duration of 0
+ real 0.0* (re)
+ user 0.0* (re)
+ sys 0.0* (re)
+ [1]
+
+ $ ./time ./sleep 1
+ real 1.0* (re)
+ user 0.0* (re)
+ sys 0.0* (re)
+
+ $ ./time ./sleep .1
+ real 0.10* (re)
+ user 0.0* (re)
+ sys 0.0* (re)