diff options
author | Cedric Ware <cedric.ware__bml@normalesup.org> | 2020-04-20 12:10:01 -0500 |
---|---|---|
committer | dana <dana@dana.is> | 2020-04-20 12:10:01 -0500 |
commit | 25c9b61a6663f90bfb22fa73c1a7aa4fb9dee4ae (patch) | |
tree | 120e8f1e11ab3791b6fbe1d6302dda275d09c2ac /Src | |
parent | f179dbf72a4f482c1ad513b7c58cd7f96c7cf7f3 (diff) | |
download | zsh-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.c | 79 | ||||
-rw-r--r-- | Src/compat.c | 26 | ||||
-rw-r--r-- | Src/utils.c | 36 |
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. |