about summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
authorCedric Ware <cedric.ware__bml@normalesup.org>2020-04-20 12:10:01 -0500
committerdana <dana@dana.is>2020-04-20 12:10:01 -0500
commit25c9b61a6663f90bfb22fa73c1a7aa4fb9dee4ae (patch)
tree120e8f1e11ab3791b6fbe1d6302dda275d09c2ac /Src
parentf179dbf72a4f482c1ad513b7c58cd7f96c7cf7f3 (diff)
downloadzsh-25c9b61a6663f90bfb22fa73c1a7aa4fb9dee4ae.tar.gz
zsh-25c9b61a6663f90bfb22fa73c1a7aa4fb9dee4ae.tar.xz
zsh-25c9b61a6663f90bfb22fa73c1a7aa4fb9dee4ae.zip
45708: zsh/system: Enable sub-second timeout in zsystem flock
Diffstat (limited to 'Src')
-rw-r--r--Src/Modules/system.c79
-rw-r--r--Src/compat.c26
-rw-r--r--Src/utils.c36
3 files changed, 136 insertions, 5 deletions
diff --git a/Src/Modules/system.c b/Src/Modules/system.c
index fb3d80773..972aa0767 100644
--- a/Src/Modules/system.c
+++ b/Src/Modules/system.c
@@ -29,6 +29,7 @@
 
 #include "system.mdh"
 #include "system.pro"
+#include <math.h>
 
 #ifdef HAVE_POLL_H
 # include <poll.h>
@@ -531,7 +532,9 @@ static int
 bin_zsystem_flock(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
     int cloexec = 1, unlock = 0, readlock = 0;
-    zlong timeout = -1;
+    double timeout = -1;
+    long timeout_interval = 1e6;
+    mnumber timeout_param;
     char *fdvar = NULL;
 #ifdef HAVE_FCNTL_H
     struct flock lck;
@@ -583,7 +586,51 @@ bin_zsystem_flock(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 		} else {
 		    optarg = *args++;
 		}
-		timeout = mathevali(optarg);
+		timeout_param = matheval(optarg);
+		timeout = (timeout_param.type & MN_FLOAT) ?
+		    timeout_param.u.d : (double)timeout_param.u.l;
+
+		/*
+		 * timeout must not overflow time_t, but little is known
+		 * about this type's limits.  Conservatively limit to 2^30-1
+		 * (34 years).  Then it can only overflow if time_t is only
+		 * a 32-bit int and CLOCK_MONOTONIC is not supported, in which
+		 * case there is a Y2038 problem anyway.
+		 */
+		if (timeout < 1e-6 || timeout > 1073741823.) {
+		    zwarnnam(nam, "flock: invalid timeout value: '%s'",
+			     optarg);
+		    return 1;
+		}
+		break;
+
+	    case 'i':
+		/* retry interval in seconds */
+		if (optptr[1]) {
+		    optarg = optptr + 1;
+		    optptr += strlen(optarg) - 1;
+		} else if (!*args) {
+		    zwarnnam(nam,
+			     "flock: option %c requires "
+			     "a numeric retry interval",
+			     opt);
+		    return 1;
+		} else {
+		    optarg = *args++;
+		}
+		timeout_param = matheval(optarg);
+		if (!(timeout_param.type & MN_FLOAT)) {
+		    timeout_param.type = MN_FLOAT;
+		    timeout_param.u.d = (double)timeout_param.u.l;
+		}
+		timeout_param.u.d *= 1e6;
+		if (timeout_param.u.d < 1
+		    || timeout_param.u.d > 0.999 * LONG_MAX) {
+		    zwarnnam(nam, "flock: invalid interval value: '%s'",
+			     optarg);
+		    return 1;
+		}
+		timeout_interval = (long)timeout_param.u.d;
 		break;
 
 	    case 'u':
@@ -647,7 +694,24 @@ bin_zsystem_flock(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
     lck.l_len = 0;  /* lock the whole file */
 
     if (timeout > 0) {
-	time_t end = time(NULL) + (time_t)timeout;
+	/*
+	 * Get current time, calculate timeout time.
+	 * No need to check for overflow, already checked above.
+	 */
+	struct timespec now, end;
+	double timeout_s;
+	long remaining_us;
+	zgettime_monotonic_if_available(&now);
+	end.tv_sec = now.tv_sec;
+	end.tv_nsec = now.tv_nsec;
+	end.tv_nsec += modf(timeout, &timeout_s) * 1000000000L;
+	end.tv_sec += timeout_s;
+	if (end.tv_nsec >= 1000000000L) {
+	    end.tv_nsec -= 1000000000L;
+	    end.tv_sec += 1;
+	}
+
+	/* Try acquiring lock, loop until timeout. */
 	while (fcntl(flock_fd, F_SETLK, &lck) < 0) {
 	    if (errflag) {
                 zclose(flock_fd);
@@ -658,11 +722,16 @@ bin_zsystem_flock(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 		zwarnnam(nam, "failed to lock file %s: %e", args[0], errno);
 		return 1;
 	    }
-	    if (time(NULL) >= end) {
+	    zgettime_monotonic_if_available(&now);
+	    remaining_us = timespec_diff_us(&now, &end);
+	    if (remaining_us <= 0) {
                 zclose(flock_fd);
 		return 2;
             }
-	    sleep(1);
+	    if (remaining_us <= timeout_interval) {
+		timeout_interval = remaining_us;
+	    }
+	    zsleep(timeout_interval);
 	}
     } else {
 	while (fcntl(flock_fd, timeout == 0 ? F_SETLK : F_SETLKW, &lck) < 0) {
diff --git a/Src/compat.c b/Src/compat.c
index 74e426fba..817bb4aaf 100644
--- a/Src/compat.c
+++ b/Src/compat.c
@@ -126,6 +126,32 @@ zgettime(struct timespec *ts)
     return ret;
 }
 
+/* Likewise with CLOCK_MONOTONIC if available. */
+
+/**/
+mod_export int
+zgettime_monotonic_if_available(struct timespec *ts)
+{
+    int ret = -1;
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+    struct timespec dts;
+    if (clock_gettime(CLOCK_MONOTONIC, &dts) < 0) {
+	zwarn("unable to retrieve CLOCK_MONOTONIC time: %e", errno);
+	ret--;
+    } else {
+	ret++;
+	ts->tv_sec = (time_t) dts.tv_sec;
+	ts->tv_nsec = (long) dts.tv_nsec;
+    }
+#endif
+
+    if (ret) {
+	ret = zgettime(ts);
+    }
+    return ret;
+}
+
 
 /* compute the difference between two calendar times */
 
diff --git a/Src/utils.c b/Src/utils.c
index 69885fed3..e258ef836 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -2749,6 +2749,42 @@ read_poll(int fd, int *readchar, int polltty, zlong microseconds)
 }
 
 /*
+ * Return the difference between 2 times, given as struct timespec*,
+ * expressed in microseconds, as a long.  If the difference doesn't fit
+ * into a long, return LONG_MIN or LONG_MAX so that the times can still
+ * be compared.
+ *
+ * Note: returns a long rather than a zlong because zsleep() below
+ * takes a long.
+ */
+
+/**/
+long
+timespec_diff_us(const struct timespec *t1, const struct timespec *t2)
+{
+    int reverse = (t1->tv_sec > t2->tv_sec);
+    time_t diff_sec;
+    long diff_usec, max_margin, res;
+
+    /* Don't just subtract t2-t1 because time_t might be unsigned. */
+    diff_sec = (reverse ? t1->tv_sec - t2->tv_sec : t2->tv_sec - t1->tv_sec);
+    if (diff_sec > LONG_MAX / 1000000L) {
+	goto overflow;
+    }
+    res = diff_sec * 1000000L;
+    max_margin = LONG_MAX - res;
+    diff_usec = (reverse ?
+		 t1->tv_nsec - t2->tv_nsec : t2->tv_nsec - t1->tv_nsec
+		 ) / 1000;
+    if (diff_usec <= max_margin) {
+	res += diff_usec;
+	return (reverse ? -res : res);
+    }
+ overflow:
+    return (reverse ? LONG_MIN : LONG_MAX);
+}
+
+/*
  * Sleep for the given number of microseconds --- must be within
  * range of a long at the moment, but this is only used for
  * limited internal purposes.