1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2025-10-23 04:30:24 +02:00

27754 plus NEWS change: add "zsystem flock"

This commit is contained in:
Peter Stephenson 2010-02-24 21:37:24 +00:00
parent 48315b019b
commit bec3de98df
7 changed files with 284 additions and 6 deletions

View file

@ -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 $
*****************************************************

View file

@ -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)

5
NEWS
View file

@ -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
-----------------------------------------

View file

@ -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)
};

View file

@ -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;

View file

@ -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

View file

@ -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 */