summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog8
-rw-r--r--Doc/Zsh/mod_system.yo43
-rw-r--r--NEWS5
-rw-r--r--Src/Modules/system.c166
-rw-r--r--Src/exec.c9
-rw-r--r--Src/utils.c48
-rw-r--r--Src/zsh.h11
7 files changed, 284 insertions, 6 deletions
diff --git a/ChangeLog b/ChangeLog
index 633a474d2..77ab063ad 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2010-02-24  Peter Stephenson  <p.w.stephenson@ntlworld.com>
+
+	* 27754: NEWS (unposted), Doc/Zsh/mod_system.yo, Src/exec.c,
+	Src/utils.c, Src/zsh.h, Src/Modules/system.c: add
+	"zsystem flock" subcommand to zsh/system module.
+
 2010-02-22  Peter Stephenson  <pws@csr.com>
 
 	* unposted: Src/utils.c: Add a debug test for trapping bad uses of
@@ -12813,5 +12819,5 @@
 
 *****************************************************
 * This is used by the shell to define $ZSH_PATCHLEVEL
-* $Revision: 1.4913 $
+* $Revision: 1.4914 $
 *****************************************************
diff --git a/Doc/Zsh/mod_system.yo b/Doc/Zsh/mod_system.yo
index 0df10e0b2..29047bfe4 100644
--- a/Doc/Zsh/mod_system.yo
+++ b/Doc/Zsh/mod_system.yo
@@ -1,8 +1,8 @@
 COMMENT(!MOD!zsh/system
 A builtin interface to various low-level system features.
 !MOD!)
-The tt(zsh/system) module makes available three builtin commands and
-two parameters.
+The tt(zsh/system) module makes available various builtin commands and
+parameters.
 
 subsect(Builtins)
 
@@ -109,6 +109,45 @@ to the command, or 2 for an error on the write; no error message is
 printed in the last case, but the parameter tt(ERRNO) will reflect
 the error that occurred.
 )
+xitem(tt(zsystem flock [ -t) var(timeout) tt(] [ -f) var(var) tt(] [-er]) var(file))
+item(tt(zsystem flock -u) var(fd_expr))(
+The builtin tt(zsystem)'s subcommand tt(flock) performs advisory file
+locking (via the manref(fcntl)(2) system call) over the entire contents
+of the given file.  This form of locking requires the processes
+accessing the file to cooperate; its most obvious use is between two
+instances of the shell itself.
+
+In the first form the named var(file), which must already exist, is
+locked by opening a file descriptor to the file and applying a lock to
+the file descriptor.  The lock terminates when the shell process that
+created the lock exits; it is therefore often convenient to create file
+locks within subshells, since the lock is automatically released when
+the subshell exits.  Status 0 is returned if the lock succeeds, else
+status 1.
+
+In the second form the file descriptor given by the arithmetic
+expression tt(fd_expr) is closed, releasing a lock.  The file descriptor
+can be queried by using the `tt(-f) var(var)' form during the lock;
+on a successful lock, the shell variable var(var) is set to the file
+descriptor used for locking.  The lock will be released if the
+file descriptor is closed by any other means, for example using
+`tt(exec {)var(var)tt(}>&-)'; however, the form described here performs
+a safety check that the file descriptor is in use for file locking.
+
+By default the shell waits indefinitely for the lock to succeed.
+The option tt(-t) var(timeout) specifies a timeout for the lock in
+seconds; currently this must be an integer.  The shell will attempt
+to lock the file once a second during this period.  If the attempt
+times out, status 2 is returned.
+
+If the option tt(-e) is given, the file descriptor for the lock is
+preserved when the shell uses tt(exec) to start a new process;
+otherwise it is closed at that point and the lock released.
+
+If the option tt(-r) is given, the lock is only for reading, otherwise
+it is for reading and writing.  The file descriptor is opened
+accordingly.
+)
 enditem()
 
 subsect(Parameters)
diff --git a/NEWS b/NEWS
index 7e7ebadc0..a2f86722c 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,11 @@ The glob qualifier P can be used to add a separate word before each
 match.  For example, *(P:-f:) produces the command line
 `-f file1 -f file2 ...'.
 
+The module zsh/system has a new "zsystem" builtin whose subcommands
+perform system level tasks.  Currently "zsystem flock" performs
+advisory file locking.  This is a particularly convenient way
+of locking files for the length of a subshell.
+
 Changes between versions 4.3.9 and 4.3.10
 -----------------------------------------
 
diff --git a/Src/Modules/system.c b/Src/Modules/system.c
index 4af464db0..ef1ebb0b0 100644
--- a/Src/Modules/system.c
+++ b/Src/Modules/system.c
@@ -173,7 +173,7 @@ bin_sysread(char *nam, char **args, Options ops, UNUSED(int func))
 	    select_tv.tv_usec = 0;
 	}
 
-	while ((ret = select(infd+1, (SELECT_ARG_2_T) &fds, 
+	while ((ret = select(infd+1, (SELECT_ARG_2_T) &fds,
 			     NULL, NULL,&select_tv)) < 1) {
 	    if (errno != EINTR || errflag || retflag || breaks || contflag)
 		break;
@@ -340,10 +340,174 @@ bin_syserror(char *nam, char **args, Options ops, UNUSED(int func))
     return 0;
 }
 
+/**/
+static int
+bin_zsystem_flock(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
+{
+    int cloexec = 1, unlock = 0, readlock = 0;
+    time_t timeout = 0;
+    char *fdvar = NULL;
+#ifdef HAVE_FCNTL_H
+    struct flock lck;
+    int flock_fd, flags;
+#endif
+
+    while (*args && **args == '-') {
+	int opt;
+	char *optptr = *args + 1, *optarg;
+	args++;
+	if (!*optptr || !strcmp(optptr, "-"))
+	    break;
+	while ((opt = *optptr)) {
+	    switch (opt) {
+	    case 'e':
+		/* keep lock on "exec" */
+		cloexec = 0;
+		break;
+
+	    case 'f':
+		/* variable for fd */
+		if (optptr[1]) {
+		    fdvar = optptr + 1;
+		    optptr += strlen(fdvar) - 1;
+		} else if (*args) {
+		    fdvar = *args++;
+		}
+		if (fdvar == NULL || !isident(fdvar)) {
+		    zwarnnam(nam, "flock: option %c requires a variable name",
+			     opt);
+		    return 1;
+		}
+		break;
+
+	    case 'r':
+		/* read lock rather than read-write lock */
+		readlock = 1;
+		break;
+
+	    case 't':
+		/* timeout in seconds */
+		if (optptr[1]) {
+		    optarg = optptr + 1;
+		    optptr += strlen(optarg) - 1;
+		} else if (!*args) {
+		    zwarnnam(nam, "flock: option %c requires a numeric timeout",
+			     opt);
+		    return 1;
+		} else {
+		    optarg = *args++;
+		}
+		timeout = (time_t)mathevali(optarg);
+		break;
+
+	    case 'u':
+		/* unlock: argument is fd */
+		unlock = 1;
+		break;
+
+	    default:
+		zwarnnam(nam, "flock: unknown option: %c", *optptr);
+		return 1;
+	    }
+	    optptr++;
+	}
+    }
+
+
+    if (!args[0]) {
+	zwarnnam(nam, "flock: not enough arguments");
+	return 1;
+    }
+    if (args[1]) {
+	zwarnnam(nam, "flock: too many arguments");
+	return 1;
+    }
+
+#ifdef HAVE_FCNTL_H
+    if (unlock) {
+	flock_fd = (int)mathevali(args[0]);
+	if (zcloselockfd(flock_fd) < 0) {
+	    zwarnnam(nam, "flock: file descriptor %d not in use for locking",
+		     flock_fd);
+	    return 1;
+	}
+	return 0;
+    }
+
+    if (readlock)
+	flags = O_RDONLY | O_NOCTTY;
+    else
+	flags = O_RDWR | O_NOCTTY;
+    if ((flock_fd = open(unmeta(args[0]), flags)) < 0) {
+	zwarnnam(nam, "failed to open %s for writing: %e", args[0], errno);
+	return 1;
+    }
+    flock_fd = movefd(flock_fd);
+    if (flock_fd == -1)
+	return 1;
+#ifdef FD_CLOEXEC
+    if (cloexec)
+    {
+	long fdflags = fcntl(flock_fd, F_GETFD, 0);
+	if (fdflags != (long)-1)
+	    fcntl(flock_fd, F_SETFD, fdflags | FD_CLOEXEC);
+    }
+#endif
+    addlockfd(flock_fd, cloexec);
+
+    lck.l_type = readlock ? F_RDLCK : F_WRLCK;
+    lck.l_whence = SEEK_SET;
+    lck.l_start = 0;
+    lck.l_len = 0;  /* lock the whole file */
+
+    if (timeout > 0) {
+	time_t end = time(NULL) + (time_t)timeout;
+	while (fcntl(flock_fd, F_SETLK, &lck) < 0) {
+	    if (errno != EINTR && errno != EACCES && errno != EAGAIN) {
+		zwarnnam(nam, "failed to lock file %s: %e", args[0], errno);
+		return 1;
+	    }
+	    if (time(NULL) >= end)
+		return 2;
+	    sleep(1);
+	}
+    } else {
+	while (fcntl(flock_fd, F_SETLKW, &lck) < 0) {
+	    if (errno == EINTR)
+		continue;
+	    zwarnnam(nam, "failed to lock file %s: %e", args[0], errno);
+	    return 1;
+	}
+    }
+
+    if (fdvar)
+	setiparam(fdvar, flock_fd);
+
+    return 0;
+#else /* HAVE_FCNTL_H */
+    zwarnnam(nam, "flock: not implemented on this system");
+    return 255;
+#endif /* HAVE_FCNTL_H */
+}
+
+
+/**/
+static int
+bin_zsystem(char *nam, char **args, Options ops, int func)
+{
+    /* If more commands are implemented, this can be more sophisticated */
+    if (!strcmp(*args, "flock")) {
+	return bin_zsystem_flock(nam, args+1, ops, func);
+    }
+    zwarnnam(nam, "unknown subcommand: %s", *args);
+    return 1;
+}
+
 static struct builtin bintab[] = {
     BUILTIN("syserror", 0, bin_syserror, 0, 1, 0, "e:p:", NULL),
     BUILTIN("sysread", 0, bin_sysread, 0, 1, 0, "c:i:o:s:t:", NULL),
     BUILTIN("syswrite", 0, bin_syswrite, 1, 1, 0, "c:o:", NULL),
+    BUILTIN("zsystem", 0, bin_zsystem, 1, -1, 0, NULL, NULL)
 };
 
 
diff --git a/Src/exec.c b/Src/exec.c
index 7ba80d986..952d118c1 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -136,6 +136,12 @@ mod_export int coprocin;
 /**/
 mod_export int coprocout;
 
+/* count of file locks recorded in fdtable */
+
+/**/
+int fdtable_flocks;
+
+
 /* != 0 if the line editor is active */
 
 /**/
@@ -2716,7 +2722,8 @@ execcmd(Estate state, int input, int output, int how, int last1)
     if ((how & Z_ASYNC) ||
 	(!do_exec &&
 	 (((is_builtin || is_shfunc) && output) ||
-	  (!is_cursh && (last1 != 1 || nsigtrapped || havefiles()))))) {
+	  (!is_cursh && (last1 != 1 || nsigtrapped || havefiles() ||
+			 fdtable_flocks))))) {
 
 	pid_t pid;
 	int synch[2], flags;
diff --git a/Src/utils.c b/Src/utils.c
index f488d9de6..b5cdc4613 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -1691,13 +1691,43 @@ redup(int x, int y)
 	} else {
 	    check_fd_table(y);
 	    fdtable[y] = fdtable[x];
+	    if (fdtable[y] == FDT_FLOCK || fdtable[y] == FDT_FLOCK_EXEC)
+		fdtable[y] = FDT_INTERNAL;
 	}
+	/*
+	 * Closing any fd to the locked file releases the lock.
+	 * This isn't expected to happen, it's here for completeness.
+	 */
+	if (fdtable[x] == FDT_FLOCK)
+	    fdtable_flocks--;
 	zclose(x);
     }
 
     return ret;
 }
 
+/*
+ * Indicate that an fd has a file lock; if cloexec is 1 it will be closed
+ * on exec.
+ * The fd should already be known to fdtable (e.g. by movefd).
+ * Note the fdtable code doesn't care what sort of lock
+ * is used; this simply prevents the main shell exiting prematurely
+ * when it holds a lock.
+ */
+
+/**/
+mod_export void
+addlockfd(int fd, int cloexec)
+{
+    if (cloexec) {
+	if (fdtable[fd] != FDT_FLOCK)
+	    fdtable_flocks++;
+	fdtable[fd] = FDT_FLOCK;
+    } else {
+	fdtable[fd] = FDT_FLOCK_EXEC;
+    }
+}
+
 /* Close the given fd, and clear it from fdtable. */
 
 /**/
@@ -1713,6 +1743,8 @@ zclose(int fd)
 	 */
 	DPUTS2(fd > max_zsh_fd && fdtable[fd] != FDT_UNUSED,
 	       "BUG: fd is %d, max_zsh_fd is %d", fd, max_zsh_fd);
+	if (fdtable[fd] == FDT_FLOCK)
+	    fdtable_flocks--;
 	fdtable[fd] = FDT_UNUSED;
 	while (max_zsh_fd > 0 && fdtable[max_zsh_fd] == FDT_UNUSED)
 	    max_zsh_fd--;
@@ -1725,6 +1757,22 @@ zclose(int fd)
     return -1;
 }
 
+/*
+ * Close an fd returning 0 if used for locking; return -1 if it isn't.
+ */
+
+/**/
+mod_export int
+zcloselockfd(int fd)
+{
+    if (fd > max_zsh_fd)
+	return -1;
+    if (fdtable[fd] != FDT_FLOCK && fdtable[fd] != FDT_FLOCK_EXEC)
+	return -1;
+    zclose(fd);
+    return 0;
+}
+
 #ifdef HAVE__MKTEMP
 extern char *_mktemp(char *);
 #endif
diff --git a/Src/zsh.h b/Src/zsh.h
index f2bab06fd..c918f7863 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -350,6 +350,15 @@ enum {
  * Entry used by output from the XTRACE option.
  */
 #define FDT_XTRACE		3
+/*
+ * Entry used for file locking.
+ */
+#define FDT_FLOCK		4
+/*
+ * As above, but the fd is not marked for closing on exec,
+ * so the shell can still exec the last process.
+ */
+#define FDT_FLOCK_EXEC		5
 #ifdef PATH_DEV_FD
 /*
  * Entry used by a process substition.
@@ -357,7 +366,7 @@ enum {
  * decremented on exit; we don't close entries greater than
  * FDT_PROC_SUBST except when closing everything.
  */
-#define FDT_PROC_SUBST		4
+#define FDT_PROC_SUBST		6
 #endif
 
 /* Flags for input stack */