git: 2295cae7e606 - main - sleep: Overhaul.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Mon, 25 Mar 2024 15:59:24 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=2295cae7e606d75016575927c3d8729745abb17a

commit 2295cae7e606d75016575927c3d8729745abb17a
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2024-03-25 15:58:31 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2024-03-25 15:59:02 +0000

    sleep: Overhaul.
    
    Program:
    
    * Add a dummy getopt(3) loop to handle `--`.
    * Move interval parsing out into a separate function.
    * Print a diagnostic for every invalid interval.
    * Check for NaN and infinity.
    * Improve bounds checks.
    
    Manual page:
    
    * Miscellaneous markup fixes.
    * Reword DESCRIPTION section.
    * Move text about GNU compatibility to STANDARDS section.
    * Convert examples from csh to sh.
    
    Sponsored by:   Klara, Inc.
    Reviewed by:    kevans
    Differential Revision:  https://reviews.freebsd.org/D44471
---
 bin/sleep/sleep.1 |  77 ++++++++++++++++++-------------------
 bin/sleep/sleep.c | 111 +++++++++++++++++++++++++++++-------------------------
 2 files changed, 98 insertions(+), 90 deletions(-)

diff --git a/bin/sleep/sleep.1 b/bin/sleep/sleep.1
index b6af3a648a97..c9069a015706 100644
--- a/bin/sleep/sleep.1
+++ b/bin/sleep/sleep.1
@@ -29,7 +29,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd May 25, 2022
+.Dd March 22, 2024
 .Dt SLEEP 1
 .Os
 .Sh NAME
@@ -38,21 +38,26 @@
 .Sh SYNOPSIS
 .Nm
 .Ar number Ns Op Ar unit
-.Ar ...
+.Op ...
 .Sh DESCRIPTION
 The
 .Nm
 command suspends execution for a minimum of
 .Ar number
 seconds (the default, or unit
-.Cm s ) ,
+.Li s ) ,
 minutes (unit
-.Cm m ) ,
+.Li m ) ,
 hours (unit
-.Cm h ) ,
+.Li h ) ,
 or days (unit
-.Cm d ) .
-If multiple arguments are passed, the delay will be the sum of all values.
+.Li d ) .
+Intervals can be written in any form allowed by
+.Xr strtod 3 .
+If multiple intervals are given, they are added together.
+If the final sum is zero or negative,
+.Nm
+exits immediately.
 .Pp
 If the
 .Nm
@@ -65,57 +70,49 @@ sleep is printed on the standard output.
 The
 .Dv SIGALRM
 signal is not handled specially by this implementation.
-.Pp
-The
-.Nm
-command supports other time units than seconds,
-honors a non-integer number of time units to sleep in any form acceptable by
-.Xr strtod 3 ,
-and accepts more than one delay value.
-These are non-portable extensions, but they have also been implemented
-in GNU sh-utils since version 2.0a (released in 2002).
 .Sh EXIT STATUS
 .Ex -std
 .Sh EXAMPLES
-To schedule the execution of a command for
-.Va x
-number seconds later (with
-.Xr csh 1 ) :
+To run a command after half an hour:
 .Pp
-.Dl (sleep 1800; sh command_file >& errors)&
+.Dl (sleep 0.5h; sh command_file >out 2>err)&
 .Pp
-This incantation would wait a half hour before
-running the script command_file.
-(See the
+This incantation would wait half an hour before
+running the script
+.Pa command_file .
+See the
 .Xr at 1
-utility.)
+utility for another way to do this.
 .Pp
-To reiteratively run a command (with the
-.Xr csh 1 ) :
+To reiteratively run a command:
 .Pp
 .Bd -literal -offset indent -compact
-while (1)
-	if (! -r zzz.rawdata) then
-		sleep 300
+while :; do
+	if ! [ -r zzz.rawdata ] ; then
+		sleep 5m
 	else
-		foreach i (`ls *.rawdata`)
+		for i in *.rawdata ; do
 			sleep 70
-			awk -f collapse_data $i >> results
-		end
+			awk -f collapse_data "$i"
+		done >results
 		break
-	endif
-end
+	fi
+done
 .Ed
 .Pp
 The scenario for a script such as this might be: a program currently
 running is taking longer than expected to process a series of
 files, and it would be nice to have
 another program start processing the files created by the first
-program as soon as it is finished (when zzz.rawdata is created).
-The script checks every five minutes for the file zzz.rawdata,
+program as soon as it is finished (when
+.Pa zzz.rawdata
+is created).
+The script checks every five minutes for the file
+.Pa zzz.rawdata ,
 when the file is found, then another portion processing
 is done courteously by sleeping for 70 seconds in between each
-awk job.
+.Xr awk 1
+job.
 .Sh SEE ALSO
 .Xr nanosleep 2 ,
 .Xr sleep 3
@@ -125,6 +122,10 @@ The
 command is expected to be
 .St -p1003.2
 compatible.
+.Pp
+Support for non-integer intervals, units other than seconds, and
+multiple intervals which are added together are non-portable
+extensions first introduced in GNU sh-utils 2.0a (released in 2002).
 .Sh HISTORY
 A
 .Nm
diff --git a/bin/sleep/sleep.c b/bin/sleep/sleep.c
index 20e525ce093c..34d335cf4736 100644
--- a/bin/sleep/sleep.c
+++ b/bin/sleep/sleep.c
@@ -31,93 +31,100 @@
 #include <err.h>
 #include <errno.h>
 #include <limits.h>
+#include <math.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <time.h>
-
-static void usage(void) __dead2;
+#include <unistd.h>
 
 static volatile sig_atomic_t report_requested;
+
 static void
 report_request(int signo __unused)
 {
-
 	report_requested = 1;
 }
 
+static void __dead2
+usage(void)
+{
+	fprintf(stderr, "usage: sleep number[unit] [...]\n"
+	    "Unit can be 's' (seconds, the default), "
+	    "m (minutes), h (hours), or d (days).\n");
+	exit(1);
+}
+
+static double
+parse_interval(const char *arg)
+{
+	double num;
+	char unit, extra;
+
+	switch (sscanf(arg, "%lf%c%c", &num, &unit, &extra)) {
+	case 2:
+		switch (unit) {
+		case 'd':
+			num *= 24;
+			/* FALLTHROUGH */
+		case 'h':
+			num *= 60;
+			/* FALLTHROUGH */
+		case 'm':
+			num *= 60;
+			/* FALLTHROUGH */
+		case 's':
+			if (!isnan(num))
+				return (num);
+		}
+		break;
+	case 1:
+		if (!isnan(num))
+			return (num);
+	}
+	warnx("invalid time interval: %s", arg);
+	return (INFINITY);
+}
+
 int
 main(int argc, char *argv[])
 {
 	struct timespec time_to_sleep;
-	double d, seconds;
+	double seconds;
 	time_t original;
-	char unit;
-	char buf[2];
-	int i, matches;
 
 	if (caph_limit_stdio() < 0 || caph_enter() < 0)
 		err(1, "capsicum");
 
-	if (argc < 2)
+	while (getopt(argc, argv, "") != -1)
+		usage();
+	argc -= optind;
+	argv += optind;
+	if (argc < 1)
 		usage();
 
 	seconds = 0;
-	for (i = 1; i < argc; i++) {
-		matches = sscanf(argv[i], "%lf%c%1s", &d, &unit, buf);
-		if (matches == 2)
-			switch(unit) {
-			case 'd':
-				d *= 24;
-				/* FALLTHROUGH */
-			case 'h':
-				d *= 60;
-				/* FALLTHROUGH */
-			case 'm':
-				d *= 60;
-				/* FALLTHROUGH */
-			case 's':
-				break;
-			default:
-				usage();
-			}
-		else
-			if (matches != 1)
-				usage();
-		seconds += d;
-	}
+	while (argc--)
+		seconds += parse_interval(*argv++);
 	if (seconds > INT_MAX)
 		usage();
-	if (seconds <= 0)
-		return (0);
+	if (seconds < 1e-9)
+		exit(0);
 	original = time_to_sleep.tv_sec = (time_t)seconds;
 	time_to_sleep.tv_nsec = 1e9 * (seconds - time_to_sleep.tv_sec);
 
 	signal(SIGINFO, report_request);
 
-	/*
-	 * Note: [EINTR] is supposed to happen only when a signal was handled
-	 * but the kernel also returns it when a ptrace-based debugger
-	 * attaches. This is a bug but it is hard to fix.
-	 */
 	while (nanosleep(&time_to_sleep, &time_to_sleep) != 0) {
+		if (errno != EINTR)
+			err(1, "nanosleep");
 		if (report_requested) {
 			/* Reporting does not bother with nanoseconds. */
-			warnx("about %d second(s) left out of the original %d",
-			    (int)time_to_sleep.tv_sec, (int)original);
+			warnx("about %ld second(s) left out of the original %ld",
+			    (long)time_to_sleep.tv_sec, (long)original);
 			report_requested = 0;
-		} else if (errno != EINTR)
-			err(1, "nanosleep");
+		}
 	}
-	return (0);
-}
 
-static void
-usage(void)
-{
-
-	fprintf(stderr, "usage: sleep number[unit] ...\n");
-	fprintf(stderr, "Unit can be 's' (seconds, the default), "
-			"m (minutes), h (hours), or d (days).\n");
-	exit(1);
+	exit(0);
 }