logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/
commit: 02de891996ed3af39c5f91cddc5aef92bc8485a3
parent c0c9b9ce4654fb31b4ce6f2fe812147f2957150a
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Wed, 18 Jun 2025 13:45:07 +0200

Add support for concatenated durations (say 1d5m)

Diffstat:

Mcmd/sleep.15+++--
Mcmd/timeout.17++++---
Mlib/strtodur.c149+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mtest-lib/t_strtodur.c30+++++++++++++++++++-----------
4 files changed, 111 insertions(+), 80 deletions(-)

diff --git a/cmd/sleep.1 b/cmd/sleep.1 @@ -1,7 +1,7 @@ .\" utils-std: Collection of commonly available Unix tools .\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> .\" SPDX-License-Identifier: MPL-2.0 -.Dd July 25, 2024 +.Dd June 18, 2025 .Dt SLEEP 1 .Os .Sh NAME @@ -18,7 +18,8 @@ utily shall suspends execution for the total of each argument. .Pp .Ar duration -is a non-negative decimal number including floats, optionally followed by a suffix: s for seconds (default), m for minutes, h for hours. +is a string containing non-negative decimal numbers including floats, terminated by a suffix: s for seconds, m for minutes, h for hours, d for days. +If the final number doesn't have a suffix it is assumed to be seconds. Longer durations are taken as out of scope. .Sh EXIT STATUS .Ex -std diff --git a/cmd/timeout.1 b/cmd/timeout.1 @@ -1,7 +1,7 @@ .\" utils-std: Collection of commonly available Unix tools .\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> .\" SPDX-License-Identifier: MPL-2.0 -.Dd July 22, 2024 +.Dd June 18, 2025 .Dt TIMEOUT 1 .Os .Sh NAME @@ -24,8 +24,9 @@ and terminates it, if still running after .Ar duration . .Pp .Ar duration -is a non-negative decimal number including floats, -optionally followed by a suffix: s for seconds (default), m for minutes, h for hours. +is a string containing non-negative decimal numbers including floats, terminated by a suffix: s for seconds, m for minutes, h for hours, d for days. +If the final number doesn't have a suffix it is assumed to be seconds. +Longer durations are taken as out of scope. .Sh OPTIONS .Bl -tag -width __ .It Fl f diff --git a/lib/strtodur.c b/lib/strtodur.c @@ -6,6 +6,7 @@ #include "strtodur.h" #include <assert.h> +#include <ctype.h> #include <errno.h> // errno #include <stdio.h> // fprintf, perror, sscanf #include <string.h> // strerror @@ -15,95 +16,115 @@ const char *errstr_nan = "Not a number"; int strtodur(char *s, struct timespec *dur) { - if(s == 0 || s[0] == '\0') return 0; + if(s == 0) return 0; assert(dur); - float in = 0.0; + float total = 0.0; - if(s[0] != '.' && s[0] != ',') + while(*s != '\0') { - int parsed = 0; + float in = 0.0; - errno = 0; - if(sscanf(s, "%10f%n", &in, &parsed) < 1) + if(s[0] != '.' && s[0] != ',') { - const char *errstr = errstr_nan; - if((errno = !0)) + int parsed = 0; + float res = 0.0; + + errno = 0; + if(sscanf(s, "%10f%n", &res, &parsed) < 1) { - errstr = strerror(errno); - errno = 0; + const char *errstr = errstr_nan; + if(errno != 0) + { + errstr = strerror(errno); + errno = 0; + } + + fprintf( + stderr, "%s: error: strtodur failed scanning '%s' as a number: %s\n", argv0, s, errstr); + return -1; } - fprintf( - stderr, "%s: error: strtodur failed scanning '%s' as a number: %s\n", argv0, s, errstr); - return -1; + in += res; + s += parsed; } - s += parsed; - } + if((s[0] == '.' || s[0] == ',')) + { + if(s[1] == '\0') + { + s++; + goto dot_skip; + } - if((s[0] == '.' || s[0] == ',') && s[1] != '\0') - { - float fraction = 0.0; - if(s[0] == ',') s[0] = '.'; + float fraction = 0.0; + if(s[0] == ',') s[0] = '.'; - int parsed = 0; + int parsed = 0; - errno = 0; - if(sscanf(s, "%10f%n", &fraction, &parsed) < 1) - { - const char *errstr = errstr_nan; - if((errno = !0)) + errno = 0; + if(sscanf(s, "%10f%n", &fraction, &parsed) < 1) { - errstr = strerror(errno); - errno = 0; + const char *errstr = errstr_nan; + if(errno != 0) + { + errstr = strerror(errno); + errno = 0; + } + + fprintf(stderr, + "%s: error: strtodur failed scanning decimal part '%s' as a number: %s\n", + argv0, + s, + errstr); + return -1; } - fprintf(stderr, - "%s: error: strtodur failed scanning decimal part '%s' as a number: %s\n", - argv0, - s, - errstr); - return -1; - } - - in += fraction; - s += parsed; - } + if(parsed == 0) parsed++; - if(s[0] != '\0' && s[0] != ',' && s[0] != '.') - { - if(s[1] != '\0') - { - fprintf(stderr, - "%s: error: duration suffix '%s' is too long, should be only one character\n", - argv0, - s); - return -1; + in += fraction; + s += parsed; } - switch(s[0]) + if(s[0] != '\0' && s[0] != ',' && s[0] != '.') { - case 's': // seconds - break; - case 'm': // minutes - in *= 60; - break; - case 'h': // hours - in *= 60 * 60; - break; - case 'd': // days - in *= 24 * 60 * 60; - break; - default: - fprintf(stderr, "%s: error: Unknown duration suffix '%c'\n", argv0, s[0]); - return -1; + if(s[1] != '\0' && !isdigit(s[1])) + { + fprintf(stderr, + "%s: error: duration suffix '%s' is too long, should be only one character\n", + argv0, + s); + return -1; + } + + switch(s[0]) + { + case 's': // seconds + break; + case 'm': // minutes + in *= 60; + break; + case 'h': // hours + in *= 60 * 60; + break; + case 'd': // days + in *= 24 * 60 * 60; + break; + default: + fprintf(stderr, "%s: error: Unknown duration suffix '%c'\n", argv0, s[0]); + return -1; + } + + s++; } + +dot_skip: + total += in; } - dur->tv_sec = in; - dur->tv_nsec = (in - dur->tv_sec) * 1000000000; + dur->tv_sec = total; + dur->tv_nsec = (total - dur->tv_sec) * 1000000000; return 0; } diff --git a/test-lib/t_strtodur.c b/test-lib/t_strtodur.c @@ -44,11 +44,16 @@ t_strtodur(char *str, time_t ex_sec, long ex_nsec) int main(void) { - int plan = 16; + int plan = 18; printf("1..%d\n", plan); // TODO: Capture errors, say with open_memstream(3) +#define T_NSEC 1000000000 +#define T_MIN 60 +#define T_HOUR 60 * T_MIN +#define T_DAY 24 * T_HOUR + t_strtodur(NULL, 0, 0); t_strtodur((char *)"", 0, 0); t_strtodur((char *)",", 0, 0); @@ -57,18 +62,21 @@ main(void) t_strtodur((char *)"1.", 1, 0); t_strtodur((char *)"1,", 1, 0); - t_strtodur((char *)".1", 0, 1000000000 * 0.1); - t_strtodur((char *)"0.1", 0, 1000000000 * 0.1); + t_strtodur((char *)".1", 0, T_NSEC * 0.1); + t_strtodur((char *)"0.1", 0, T_NSEC * 0.1); t_strtodur((char *)"1s", 1, 0); - t_strtodur((char *)"1m", 60, 0); - t_strtodur((char *)"1h", 60 * 60, 0); - t_strtodur((char *)"1d", 60 * 60 * 24, 0); - - t_strtodur((char *)"1.5s", 1, 1000000000 * 0.5); - t_strtodur((char *)"1.5m", 1.5 * 60, 0); - t_strtodur((char *)"1.5h", 1.5 * 60 * 60, 0); - t_strtodur((char *)"1.5d", 1.5 * 60 * 60 * 24, 0); + t_strtodur((char *)"1m", T_MIN, 0); + t_strtodur((char *)"1h", 1 * T_HOUR, 0); + t_strtodur((char *)"1d", 1 * T_DAY, 0); + t_strtodur((char *)"1d1h1m1s", (1 * T_DAY) + (1 * T_HOUR) + (1 * T_MIN) + 1, 0); + + t_strtodur((char *)"1.5s", 1, T_NSEC * 0.5); + t_strtodur((char *)"1.5m", 1.5 * T_MIN, 0); + t_strtodur((char *)"1.5h", 1.5 * T_HOUR, 0); + t_strtodur((char *)"1.5d", 1.5 * T_DAY, 0); + t_strtodur( + (char *)"1.5d1.5h1.5m1.5s", (1.5 * T_DAY) + (1.5 * T_HOUR) + (1.5 * T_MIN) + 1, T_NSEC * 0.5); assert(counter == plan); return err;