From: Paul Eggert Date: Sat, 5 Apr 2025 07:58:22 +0000 (-0700) Subject: timeout: round timeouts up X-Git-Tag: v9.7~14 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a3b862ece2aa04d5c0b5e93fec8e66e45b9b3560;p=thirdparty%2Fcoreutils.git timeout: round timeouts up This handles timeouts like 16777216.000000001 correctly; formerly the subsecond part of that timeout was ignored. * bootstrap.conf (gnulib_modules): Add fenv-rounding, signbit. * src/local.mk (src_timeout_LDADD): Append $(FENV_ROUNDING_LIBM). * src/timeout.c: Include fenv.h, math.h. Don’t include xstrtod.h, as xstrtod’s checking now gets in the way. (parse_duration): Round up when calling cl_strtod. Check for -1e-1000. Don’t double-round 1e-9. * tests/timeout/timeout-parameters.sh: Test for -0.1, -1e-1000, 1e-1000. --- diff --git a/bootstrap.conf b/bootstrap.conf index c99838e95a..3c133ef714 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -99,6 +99,7 @@ gnulib_modules=" fdatasync fdopen fdutimensat + fenv-rounding file-has-acl file-type fileblocks @@ -242,6 +243,7 @@ gnulib_modules=" settime sig2str sigaction + signbit skipchars smack ssize_t diff --git a/src/local.mk b/src/local.mk index fd9dc81c27..0a4e3af432 100644 --- a/src/local.mk +++ b/src/local.mk @@ -257,6 +257,9 @@ src_stat_LDADD += $(LIB_SELINUX) # for nvlist_lookup_uint64_array src_stat_LDADD += $(LIB_NVPAIR) +# for fegetround, fesetround +src_timeout_LDADD += $(FENV_ROUNDING_LIBM) + # for gettime, settime, tempname, utimecmp, utimens copy_ldadd += $(CLOCK_TIME_LIB) src_date_LDADD += $(CLOCK_TIME_LIB) diff --git a/src/timeout.c b/src/timeout.c index 6756cd8881..5947c19421 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -45,7 +45,9 @@ Written by Pádraig Brady. */ #include +#include #include +#include #include #include #include @@ -56,7 +58,6 @@ #include "system.h" #include "cl-strtod.h" -#include "xstrtod.h" #include "sig2str.h" #include "operand2sig.h" #include "quote.h" @@ -356,12 +357,29 @@ apply_time_suffix (double *x, char suffix_char) static double parse_duration (char const *str) { - double duration; - char const *ep; + /* If possible tell strtod to round up, so that we always wait at + least as long as STR specifies. Although Standard C requires + FENV_ACCESS ON, don't bother if using GCC as it warns. */ +#ifdef FE_UPWARD +# if !defined __GNUC__ && 199901 <= __STDC_VERSION__ +# pragma STDC FENV_ACCESS ON +# endif + int round = fegetround (); + fesetround (FE_UPWARD); +#endif + + char *ep; + double duration = cl_strtod (str, &ep); + +#ifdef FE_UPWARD + fesetround (round); +#endif - if (! (xstrtod (str, &ep, &duration, cl_strtod) || errno == ERANGE) + if (ep == str /* Nonnegative interval. */ || ! (0 <= duration) + /* The interval did not underflow to -0. */ + || (signbit (duration) && errno == ERANGE) /* No extra chars after the number and an optional s,m,h,d char. */ || (*ep && *(ep + 1)) /* Check any suffix char and update timeout based on the suffix. */ @@ -371,9 +389,18 @@ parse_duration (char const *str) usage (EXIT_CANCELED); } - /* Clamp underflow to 1ns, as 0 disables the timeout. */ + /* Do not let the duration underflow to 0, as 0 disables the timeout. + Use 2**-30 instead of 0; settimeout will round it up to 1 ns, + whereas 1e-9 might double-round to 2 ns. + + If FE_UPWARD is defined, this code is not needed on glibc as its + strtod rounds upward correctly. Although this code might not be + needed on non-glibc platforms too, it's too much trouble to + worry about that. */ +#if !defined FE_UPWARD || !defined __GLIBC__ if (duration == 0 && errno == ERANGE) - duration = 1e-9; + duration = 9.313225746154785e-10; +#endif return duration; } diff --git a/tests/timeout/timeout-parameters.sh b/tests/timeout/timeout-parameters.sh index 84b450e907..4088462622 100755 --- a/tests/timeout/timeout-parameters.sh +++ b/tests/timeout/timeout-parameters.sh @@ -23,8 +23,10 @@ getlimits_ # internal errors are 125, distinct from execution failure -# invalid timeout +# invalid timeouts returns_ 125 timeout invalid sleep 0 || fail=1 +returns_ 125 timeout ' -0.1' sleep 0 || fail=1 +returns_ 125 timeout ' -1e-10000' sleep 0 || fail=1 # invalid kill delay returns_ 125 timeout --kill-after=invalid 1 sleep 0 || fail=1 @@ -38,6 +40,9 @@ timeout 10.34 sleep 0 || fail=1 # nanoseconds potentially supported timeout 9.999999999 sleep 0 || fail=1 +# round underflow up to 1 ns +returns_ 124 timeout 1e-10000 sleep 1 || fail + # invalid signal spec returns_ 125 timeout --signal=invalid 1 sleep 0 || fail=1