logo

utils

~/.local/bin tools and git-hooks git clone https://hacktivis.me/git/utils.git
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:

Mcmd/sleep.16+++---
Mcmd/sleep.c134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mtest-cmd/Kyuafile2+-
Atest-cmd/sleep.t30++++++++++++++++++++++++++++++
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)