Skip to content

Commit cb7c210

Browse files
committed
timeout: don’t sleep less than requested
Also, change sleep and tail to not sleep less than requested. * bootstrap.conf (gnulib_modules): Add dtimespec-bound. * gl/lib/dtimespec-bound.c, gl/lib/dtimespec-bound.h: * gl/modules/dtimespec-bound: New files. * src/sleep.c, src/tail.c, src/timeout.c: Include dtimespec-bound.h. * src/sleep.c, src/tail.c: Don’t include xstrtod.h. * src/sleep.c (apply_suffix, main): * src/tail.c (parse_options): * src/timeout.c (apply_time_suffix): Don’t sleep less than the true number of seconds. * src/timeout.c: Don’t include ctype.h. (is_negative): Remove; no longer needed. (parse_duration): Use a slightly looser bound on the timeout, one that doesn’t need -lm on GNU/Linux. Clear errno before calling cl_strtod.
1 parent 0311d45 commit cb7c210

8 files changed

Lines changed: 129 additions & 32 deletions

File tree

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ GNU coreutils NEWS -*- outline -*-
2727
For example `timeout 1e-5000 sleep inf` would never timeout.
2828
[bug introduced with timeout in coreutils-7.0]
2929

30+
sleep, tail, and timeout would sometimes sleep for slightly less
31+
time than requested.
32+
[bug introduced in coreutils-5.0]
33+
3034
'who -m' now outputs entries for remote logins. Previously login
3135
entries prefixed with the service (like "sshd") were not matched.
3236
[bug introduced in coreutils-9.4]

bootstrap.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ gnulib_modules="
7878
dirfd
7979
dirname
8080
do-release-commit-and-tag
81+
dtimespec-bound
8182
dtoastr
8283
dup2
8384
endian

gl/lib/dtimespec-bound.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include <config.h>
2+
#define DTIMESPEC_BOUND_INLINE _GL_EXTERN_INLINE
3+
#include "dtimespec-bound.h"

gl/lib/dtimespec-bound.h

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* Compute a timespec-related bound for floating point.
2+
3+
Copyright 2025 Free Software Foundation, Inc.
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <https://www.gnu.org/licenses/>. */
17+
18+
/* written by Paul Eggert */
19+
20+
#ifndef DTIMESPEC_BOUND_H
21+
#define DTIMESPEC_BOUND_H 1
22+
23+
/* This file uses _GL_INLINE_HEADER_BEGIN, _GL_INLINE. */
24+
#if !_GL_CONFIG_H_INCLUDED
25+
#error "Please include config.h first."
26+
#endif
27+
28+
#include <errno.h>
29+
#include <float.h>
30+
#include <math.h>
31+
32+
_GL_INLINE_HEADER_BEGIN
33+
#ifndef DTIMESPEC_BOUND_INLINE
34+
# define DTIMESPEC_BOUND_INLINE _GL_INLINE
35+
#endif
36+
37+
/* If C is positive and finite, return the least floating point value
38+
greater than C. However, if 0 < C < (2 * DBL_TRUE_MIN) / (DBL_EPSILON**2),
39+
return a positive value less than 1e-9.
40+
41+
If C is +0.0, return a positive value < 1e-9 if ERR == ERANGE, C otherwise.
42+
If C is +Inf, return C.
43+
If C is negative, return -timespec_roundup(-C).
44+
If C is a NaN, return a NaN.
45+
46+
Assume round-to-even.
47+
48+
This function can be useful if some floating point operation
49+
rounded to C but we want a nearby bound on the true value, where
50+
the bound can be converted to struct timespec. If the operation
51+
underflowed to zero, ERR should be ERANGE a la strtod. */
52+
53+
DTIMESPEC_BOUND_INLINE double
54+
dtimespec_bound (double c, int err)
55+
{
56+
/* Do not use copysign or nextafter, as they link to -lm in GNU/Linux. */
57+
58+
/* Use DBL_TRUE_MIN for the special case of underflowing to zero;
59+
any positive value less than 1e-9 will do. */
60+
if (err == ERANGE && c == 0)
61+
return signbit (c) ? -DBL_TRUE_MIN : DBL_TRUE_MIN;
62+
63+
/* This is the first part of Algorithm 2 of:
64+
Rump SM, Zimmermann P, Boldo S, Melquiond G.
65+
Computing predecessor and successor in rounding to nearest.
66+
BIT Numer Math. 2009;49(419-431).
67+
<https://doi.org/10.1007/s10543-009-0218-z>
68+
The rest of Algorithm 2 is not needed because numbers less than
69+
the predecessor of 1e-9 merely need to stay less than 1e-9. */
70+
double phi = DBL_EPSILON / 2 * (1 + DBL_EPSILON);
71+
return c + phi * c;
72+
}
73+
74+
_GL_INLINE_HEADER_END
75+
76+
#endif

gl/modules/dtimespec-bound

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Description:
2+
Adjust a double to provide a timespec bound.
3+
4+
Files:
5+
lib/dtimespec-bound.c
6+
lib/dtimespec-bound.h
7+
8+
Depends-on:
9+
float-h
10+
signbit
11+
12+
configure.ac:
13+
14+
Makefile.am:
15+
lib_SOURCES += dtimespec-bound.c
16+
17+
Include:
18+
"dtimespec-bound.h"
19+
20+
License:
21+
GPL
22+
23+
Maintainer:
24+
all

src/sleep.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020

2121
#include "system.h"
2222
#include "cl-strtod.h"
23+
#include "dtimespec-bound.h"
2324
#include "long-options.h"
2425
#include "quote.h"
2526
#include "xnanosleep.h"
26-
#include "xstrtod.h"
2727

2828
/* The official name of this program (e.g., no 'g' prefix). */
2929
#define PROGRAM_NAME "sleep"
@@ -85,7 +85,7 @@ apply_suffix (double *x, char suffix_char)
8585
return false;
8686
}
8787

88-
*x *= multiplier;
88+
*x = dtimespec_bound (*x * multiplier, 0);
8989

9090
return true;
9191
}
@@ -116,9 +116,11 @@ main (int argc, char **argv)
116116

117117
for (int i = optind; i < argc; i++)
118118
{
119-
double s;
120-
char const *p;
121-
if (! (xstrtod (argv[i], &p, &s, cl_strtod) || errno == ERANGE)
119+
char *p;
120+
errno = 0;
121+
double duration = cl_strtod (argv[i], &p);
122+
double s = dtimespec_bound (duration, errno);
123+
if (argv[i] == p
122124
/* Nonnegative interval. */
123125
|| ! (0 <= s)
124126
/* No extra chars after the number and an optional s,m,h,d char. */
@@ -130,7 +132,7 @@ main (int argc, char **argv)
130132
ok = false;
131133
}
132134

133-
seconds += s;
135+
seconds = dtimespec_bound (seconds + s, 0);
134136
}
135137

136138
if (!ok)

src/tail.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "assure.h"
3636
#include "c-ctype.h"
3737
#include "cl-strtod.h"
38+
#include "dtimespec-bound.h"
3839
#include "fcntl--.h"
3940
#include "iopoll.h"
4041
#include "isapipe.h"
@@ -47,7 +48,6 @@
4748
#include "xdectoint.h"
4849
#include "xnanosleep.h"
4950
#include "xstrtol.h"
50-
#include "xstrtod.h"
5151

5252
#if HAVE_INOTIFY
5353
# include "hash.h"
@@ -2276,11 +2276,13 @@ parse_options (int argc, char **argv,
22762276

22772277
case 's':
22782278
{
2279-
double s;
2280-
if (! (xstrtod (optarg, nullptr, &s, cl_strtod) && 0 <= s))
2279+
char *ep;
2280+
errno = 0;
2281+
double s = cl_strtod (optarg, &ep);
2282+
if (optarg == ep || *ep || ! (0 <= s))
22812283
error (EXIT_FAILURE, 0,
22822284
_("invalid number of seconds: %s"), quote (optarg));
2283-
*sleep_interval = s;
2285+
*sleep_interval = dtimespec_bound (s, errno);
22842286
}
22852287
break;
22862288

src/timeout.c

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
Written by Pádraig Brady. */
4646

4747
#include <config.h>
48-
#include <ctype.h>
4948
#include <getopt.h>
5049
#include <stdio.h>
5150
#include <sys/types.h>
@@ -57,6 +56,7 @@
5756

5857
#include "system.h"
5958
#include "cl-strtod.h"
59+
#include "dtimespec-bound.h"
6060
#include "sig2str.h"
6161
#include "operand2sig.h"
6262
#include "quote.h"
@@ -348,47 +348,32 @@ apply_time_suffix (double *x, char suffix_char)
348348
return false;
349349
}
350350

351-
*x *= multiplier;
351+
*x = dtimespec_bound (*x * multiplier, 0);
352352

353353
return true;
354354
}
355355

356-
ATTRIBUTE_PURE static bool
357-
is_negative (char const *num)
358-
{
359-
while (*num && isspace (to_uchar (*num)))
360-
num++;
361-
return *num == '-';
362-
}
363-
364356
static double
365357
parse_duration (char const *str)
366358
{
367359
char *ep;
360+
errno = 0;
368361
double duration = cl_strtod (str, &ep);
362+
double s = dtimespec_bound (duration, errno);
369363

370364
if (ep == str
371365
/* Nonnegative interval. */
372-
|| ! (0 <= duration)
373-
/* The interval did not underflow to -0. */
374-
|| (errno == ERANGE && is_negative (str))
366+
|| ! (0 <= s)
375367
/* No extra chars after the number and an optional s,m,h,d char. */
376368
|| (*ep && *(ep + 1))
377369
/* Check any suffix char and update timeout based on the suffix. */
378-
|| !apply_time_suffix (&duration, *ep))
370+
|| !apply_time_suffix (&s, *ep))
379371
{
380372
error (0, 0, _("invalid time interval %s"), quote (str));
381373
usage (EXIT_CANCELED);
382374
}
383375

384-
/* Do not let the duration underflow to 0, as 0 disables the timeout.
385-
Use 2**-30 instead of 0; settimeout will round it up to 1 ns,
386-
whereas 1e-9 might double-round to 2 ns. */
387-
388-
if (duration == 0 && errno == ERANGE)
389-
duration = 9.313225746154785e-10;
390-
391-
return duration;
376+
return s;
392377
}
393378

394379
static void

0 commit comments

Comments
 (0)