rsalz@bbn.com (Rich Salz) (02/18/88)
Submitted-by: Rob McMahon <daisy.warwick.ac.uk!cudcv> Posting-number: Volume 13, Issue 48 Archive-name: with With will reserve exclusive use of a device by creating a lock file, changing ownership of the device to your current user id, setting the device to mode 600 (rw by you, nothing to others), and giving you a new shell. When the shell terminates, the device is released. In the event that a device is in use by another user you will be notified of this, and with can wait until it is available. For example: with -n tape "Rob's tape, to read, please" with tape "..." -c "tar xvp ; mt offl" > tar.out & ---- #!/bin/sh echo 'Start of pack.out, part 01 of 01:' echo 'x - Makefile' sed 's/^X//' > Makefile << '/' X# makefile for with X X# DESTDIR is the destination directory (in which the compiled source lives) X# CFLAGS contains the arguments passed to the C compiler X# SEPID whether to be compiled with -i or -n (not used on the VAX) X# R prefix to destination names (ie. an optional root) X# HACKS - local flags to the C compiler to define/undefine symbols X XDESTDIR=/usr/local XHACKS= XSEPID= XCFLAGS=-O $(SEPID) $(HACKS) XR= X X.SUFFIXES: .c,v X Xall with: with.o X $(CC) $(CFLAGS) with.o -o with X Xcp install: with X install -s -o root -g general -m 4710 with $R$(DESTDIR) X Xcmp: with X -cmp with $R$(DESTDIR)/with X rm -f with *.o X Xclean: X rm -f with *.o X X.c,v.o: X co -q $*.c X $(CC) $(CFLAGS) -c $*.c X rm -f $*.c X X# entry points are (at least): X# all, with - compile with and leave it where it is X# cp, install - compile and install with X# cmp - compile and compare with, remove dross X# clean - removes any garbage X# default action is to compile and leave the binary in situ X X X# there are problems if the command name is the same as an entry point. X# there are problems moving mv to /usr/ucb/mv (text in use) X# or cping cp to /bin/cp X# X# clean does not remove .c files when the source is in sccs s. ... .c format / echo 'x - README' sed 's/^X//' > README << '/' XWith is based on a set of programs, written by Dave Butterfield at the XUCLA Dept. of Mathamatics, called get & rls. These programs had a Xnumber of problems in our environment, the worst being that you couldn't Xuse them to run a job in the background. Out of this grew with. X XWith uses a lockfile (LOCKSFILE, "/etc/locks/lockfiles" by default), Xwith lines consisting of: name of file to create and flock, name of Xresources as known to user, and a list of up to MAXPDEV (16) devices Xwhich are chown'ed to the user, and given mode OWNERONLY (0600, X-rw-------), before a subshell is started. When the shell exits, the Xdevices are chown'ed back to Rootuid, Rootgid (0,3), and the flock is Xremoved by with exiting. (The lockfile is not removed, as someone else Xcould have an flock pending on it.) X XA second argument can be given to with, and if it is not an empty string Xit is a request which is sent to the operators via syslog, and with then Xwaits for a signal back from the operators to say whether the request Xhas been fulfilled or denied (SIGEMT or SIGTERM). Any extra arguments Xare passed to the subshell. X XA pseudo-resource '-' is available, which allows a request to the Xoperators without actually getting any resource. This is useful for Xe.g. loading the second tape. X Xe.g. X Xwith -n tape "Rob's tape, to read, please" X... Xmt offl Xwith - "second tape, please" X... Xmt offl X^D X XThe -n tells with not to wait if someone else is already using the tape Xdeck. We recommend the 'mt offl's so that your tape is unavailable to Xanyone else using with after you release the tape deck. X XIn an 'at' job: X Xwith tape "..." << EOF X... Xmt offl XEOF X XIn the background: X Xwith tape "..." -c "tar xvp ; mt offl" > tar.out & X X--- XUUCP: ...!mcvax!ukc!warwick!cudcv PHONE: +44 203 523037 XJANET: cudcv@uk.ac.warwick.daisy ARPA: cudcv@daisy.warwick.ac.uk XRob McMahon, Computing Services, Warwick University, Coventry CV4 7AL, England / echo 'x - with.1' sed 's/^X//' > with.1 << '/' X.TH with 1 local X.SH NAME Xwith \- a program to reserve exclusive use of a device X.SH SYNOPSIS X.I X.B with X[ X.B \-sn X] X.I dev X[ X.I request X[ X.I shell parameters X] X] X.SH DESCRIPTION X.I With Xwill reserve exclusive use of a device by creating a lock file, changing Xownership of the device to your current user id, setting the device to Xmode 600 (rw by you, nothing to others), and giving you a new shell. When the Xshell terminates, the device is released. XIn the event that a device is in Xuse by another user you will be notified of this, and X.I with Xwill wait until it is available, or if the X.B \-n Xflag is given will return immediately with an indication of failure. XThe X.B \-s Xswitch causes X.I with Xto perform its work silently. X.I With Xreturns a nonzero return code to indicate failure. X.PP XIf a request is specified, the operators are asked to fulfil the request once Xthe device is available. E.g. X.PP Xwith tape "cudcv's tape to read, please" X.PP XA request can be made without actually locking a device by specifying the Xdevice as `-'. In this case no shell is started, but with waits until the Xrequest is fulfilled, and then exits. X.PP XArguments after the X.I request Xare handed to the shell. A null request can be specified as `-', to grab a Xdevice and execute a command without making a request. X.PP XCurrently known devices are: X.TP 8 Xtape XThe magtape; all 16 logical devices 800 & 1600 BPI: ({,n}{,r}mt{0,4,8,12}). X.SH FILES X.TP 30 X/etc/locks/lockfiles Xfor device names, lock file names, and /dev/minordev names. X.SH ENVIRONMENT X.TP 20 XSHELL Xfor user's default shell X.SH DIAGNOSTICS XA non-zero return code indicates one of the devices was not available, or with Xwas interrupted. X.nf Xdev is yours. Xdev is in use ... waiting Xdev is in use - try again later. Xdev released. XI don't know how to get dev. X.fi X.SH BUGS / echo 'x - with.c' sed 's/^X//' > with.c << '/' X/* X * with --- a program to do simple device locking. X */ X/* X * format of working file (lockfiles) X * full_name_of_lock_file name_of_device minor0 minor1 ... X * full name of files must include path (in great detail /.../.../...) X * there may be as many as MAXPDEV minor devices associated with name_of_device X * note that name_of_device need not be the same as any minor device X * example X * /etc/tape0.lock tape0 /dev/mt0 /dev/mt4 /dev/rmt0 /dev/rmt4 X * /usr/spool/uucp/LCK..cul0 cul0 /dev/cul0 X * X * $Log: with.c,v $ X * Revision 2.0 87/10/26 09:32:15 cudcv X * "Stable" X * X * Revision 1.11 87/10/26 09:19:37 cudcv X * Cleanup comments, lint X * X * Revision 1.10 86/09/17 09:01:42 cudcv X * 'waiting for confirmation' message wasn't getting out if stderr buffered. X * X * Revision 1.9 86/08/06 10:34:33 cudcv X * Make handling of null resource cleaner, allow - as null request. X * X * Revision 1.8 86/06/17 13:14:44 cudcv X * Allow further arguments to be passed to the shell X * Return status from shell X * X * Revision 1.7 86/06/17 12:28:45 cudcv X * Slip of the editor X * X * Revision 1.6 86/01/24 15:01:36 cudcv X * Restart shell when with is restarted X * X * Revision 1.5 86/01/24 12:09:59 cudcv X * With would hang if shell was stopped X * X * Revision 1.4 86/01/23 09:38:23 cudcv X * Was ignoring last device X * X * Revision 1.3 86/01/23 09:02:20 cudcv X * Make more portable for Gould X * X * Revision 1.2 86/01/21 15:04:35 cudcv X * Allow second argument with request to operators X * Record usercode of requester X * With no arguments lists current locks X * X * Revision 1.1 85/10/07 10:31:25 cudcv X * Initial revision X * X */ Xstatic char RCSid[] = "$Header: with.c,v 2.0 87/10/26 09:32:15 cudcv Exp $"; X#include <ctype.h> X#include <pwd.h> X#include <signal.h> X#include <stdio.h> X#include <strings.h> X#include <syslog.h> X#include <sys/time.h> X#include <sys/types.h> X#include <sys/dir.h> X#include <sys/file.h> X#include <sys/resource.h> X#include <sys/stat.h> X#include <sys/wait.h> X X#ifndef PW_NAMEL X#define PW_NAMEL 8 X#endif X X#ifndef LOCKSFILE X#define LOCKSDIR "/etc/locks" X#define LOCKSFILE "/etc/locks/lockfiles" X#endif X#define MAXSTLEN 40 /*much more than necessary*/ X#define NLOCKS 10 /* " " " " */ X#define MAXPDEV 16 /* max # phys. devices associate with a name*/ X#define OWNERONLY 0600 /*protection mode for the devices*/ X#define LOGLEVEL LOG_CRIT /* level for syslog's */ X X#define subdev(LP,n) (& ( (LP)->physdev[n][0])) /*all dave's fault*/ X#define mask(i) (1 << (i-1)) /* for signals */ X XFILE *fopen(); X X#ifdef RLIMIT_INTACT Xstruct rlimit intact = {1, 1}; /* make ourselves interactive */ X#endif X Xtypedef struct { X char lok[MAXSTLEN], /* lock (flock) */ X dev[MAXSTLEN], /* what the user calls it (with dev) */ X physdev[MAXPDEV][MAXSTLEN]; /* /dev/dev */ X int locked; /*got lock on this*/ X} dev_lk; X Xdev_lk locks[NLOCKS]; Xdev_lk nolok; X Xchar *progname; Xextern int optind; Xchar *malloc(), *sprintf(); Xstruct passwd *getpwuid(); Xextern char *sys_siglist[]; Xextern char *sys_errlist[]; Xextern int errno; X Xint silent = 0; Xint nowait = 0; /* don't wait until available */ Xint nerrs = 0; Xint nlock = 0; Xint Rootuid = 0, Rootgid = 3; /* magic numbers */ Xint uuid, ugid; Xchar *resource; Xint needrequest = 0; /* got resource, need extra request */ Xint requested = 0; /* have warned of need, while waiting */ Xint fulfilled = 0; /* request fulfilled */ Xstruct passwd *pw; Xchar buf[BUFSIZ]; Xchar **shellargs; X Xvoid print_locks(); X Xmain(argc,argv) Xint argc; Xchar *argv[]; X{ X register int i, pdev; X register dev_lk *lkp; X char *request = NULL; X int c, lockfile, omask; X int mypid = getpid(); X int cleanup(), fulfil(); X X uuid = getuid(); X ugid = getgid(); X if ((pw = getpwuid(uuid)) == (struct passwd *)NULL) { X Printf("Who are you ?\n"); X exit(1); X } X (void) endpwent(); X progname = argv[0]; X while ((c = getopt(argc, argv, "sn")) != EOF) X switch (c) { X case 's': silent++; break; X case 'n': nowait++; break; X case '?': nerrs++; break; X } X if (nerrs){ X Printf("usage: %s [-sn] device [request [parameters]]\n", X progname); X Printf("eg: %s tape \"cudcv's tape for read, please\"", X progname); X Printf(" -c dd if=/dev/rmt8 ...\n"); X exit(1); X } X if((nlock = read_loks(LOCKSFILE, locks)) < 0){ X Printf("%s:%s unopenable or messed up.\n",progname,LOCKSFILE); X exit(1); X } X /* locks have been read-in */ X X if (argc == optind) { X print_locks(); X exit(0); X } X X /*can't be interrupted once we start linking things so ...*/ X omask = sigsetmask((int)0x7fffffff); X for(i = 1; i < NSIG; i++) { X /* allow stopping */ X if (i == SIGTSTP || i == SIGTTIN || i == SIGTTOU) continue; X /* don't catch those normally ignored */ X if (i == SIGURG || i == SIGCONT || X i == SIGCHLD || i == SIGIO X#ifdef SIGENQ X || i == SIGENQ X#endif X ) continue; X /* don't catch those currently ignored */ X if (signal(i, cleanup) == SIG_IGN) X (void) signal(i, SIG_IGN); X } X (void) sigsetmask(omask); X /* don't want to get clobbered by XCPU either */ X#ifdef RLIMIT_INTACT X if (setrlimit(RLIMIT_INTACT, &intact)) { X perror("setrlimit"); X exit(1); X } X#endif X /*now get resource*/ X resource = argv[optind++]; X if (argc > optind) { X request = argv[optind++]; X if (!*request || !strcmp(request, "-")) X request = NULL; X (void) sprintf(buf, "(%s) with", pw->pw_name); X openlog(buf, LOG_PID); X } X shellargs = &argv[optind-1]; X if (!strcmp(resource, "-")) { X resource = NULL; X lkp = &nolok; X (void) sprintf(lkp->lok, "%s/%d", LOCKSDIR, getpid()); X (void) strcpy(lkp->dev, "request"); X } else { X for (i = 0;i < nlock; i++) X if (strcmp(locks[i].dev, resource) == 0) break; X if(i >= nlock){ X Printf("I don't know how to get %s.\n", resource); X nerrs++; X cleanup(0); X } X lkp = &locks[i]; X } X X if ((lockfile = open(lkp->lok, O_WRONLY|O_CREAT, 0644)) < 0) { X Printf("%s : cannot open %s : %s\n", X progname, lkp->lok, sys_errlist[errno]); X nerrs++; X cleanup(0); X } X if (flock(lockfile, LOCK_EX|LOCK_NB) < 0) { X if (!resource) { X Printf("%s: cannot lock %s : %s\n", X progname, lkp->lok, sys_errlist[errno]); X nerrs++; X cleanup(0); X } X Printf("%s is in use", lkp->dev); X if (nowait) { X Printf(" - try again later.\n"); X nerrs++; X cleanup(0); X } else { X Printf(" ... waiting"); X (void) fflush(stderr); X if (request) X syslog(LOGLEVEL, "will need %s, when %s is available", X request, resource); X requested++; X if (flock(lockfile, LOCK_EX) < 0) { X Printf("\n%s : cannot lock : %s\n", X progname, sys_errlist[errno]); X nerrs++; X cleanup(0); X } else X Printf("\n"); X } X } X (void) write(lockfile, (char *)&mypid, sizeof(int)); X (void) write(lockfile, pw->pw_name, PW_NAMEL); X (void) ftruncate(lockfile, sizeof(int) + PW_NAMEL); X lkp->locked++; X for(pdev = 0;pdev < MAXPDEV && (*subdev(lkp,pdev));pdev++){ X (void) chown( subdev(lkp,pdev),uuid,ugid ); X (void) chmod( subdev(lkp,pdev),OWNERONLY); X } X if (resource) X Printf("%s is yours", lkp->dev); X if (request) { X if (resource) X Printf(" - "); X Printf("waiting for confirmation of request"); X (void) fflush(stderr); X omask = sigblock(SIGEMT); X (void) signal(SIGEMT, fulfil); X needrequest++; requested = 0; X (void) write(lockfile, request, strlen(request)); X if (resource) X syslog(LOGLEVEL, "have %s, need %s", resource, request); X else X syslog(LOGLEVEL, "need %s", request); X syslog(LOGLEVEL, "`kill -EMT %d' or `kill -TERM %d'", X getpid(), getpid()); X do { X sigpause(omask & ~mask(SIGEMT)); X } while (!fulfilled); X (void) ftruncate(lockfile, sizeof(int) + PW_NAMEL); X Printf(" - going"); X syslog(LOGLEVEL, "going"); X } X Printf("\n"); X X if (resource) shell(); X X cleanup(0); X} X X Xint Xread_loks(lf, lks) Xchar *lf; Xregister dev_lk lks[]; X{ X register FILE *lfp; X register dev_lk *dp; X register int i; X int n; X char line[BUFSIZ]; X X lfp = fopen(lf, "r"); X if(lfp == NULL) X return -1; /*error*/ X X /*now read in the entries*/ X for(dp = lks,i=0; i < NLOCKS ;dp++, i++){ X if( fgets(line,sizeof(line),lfp) == NULL) /* if EOF*/ X break; X /*read at most 2 + MAXPDEV entries from line*/ X n = sscanf(line,"%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", X dp->lok, dp->dev, X subdev(dp,0),subdev(dp,1), X subdev(dp,2),subdev(dp,3), X subdev(dp,4),subdev(dp,5), X subdev(dp,6),subdev(dp,7), X subdev(dp,8),subdev(dp,9), X subdev(dp,10),subdev(dp,11), X subdev(dp,12),subdev(dp,13), X subdev(dp,14),subdev(dp,15)); X if(n < 3){ /*if line was bad format or blank ...*/ X dp--;i--; /*try again*/ X continue;} X if(n < MAXPDEV + 2) X *subdev(dp, n - 2) = '\0'; /*no string*/ X dp->locked = 0; X } X (void) fclose(lfp); X return i; /*'i' is the # of lines read successfully*/ X} X Xvoid Xprint_locks() X{ X register dev_lk *lkp; X int nent; X struct direct **dp, **names; X int pids(), numsort(); X X for (lkp = &locks[0]; lkp < &locks[nlock]; lkp++) { X print_lock(lkp->lok, lkp->dev); X } X if (chdir(LOCKSDIR)) { X perror(LOCKSDIR); X return; X } X if ((nent = scandir(".", &names, pids, numsort)) < 0) { X perror("scandir"); X return; X } X for (dp = &names[0]; dp < &names[nent]; dp++) { X print_lock((*dp)->d_name, (char *)NULL); X } X} X Xprint_lock(lok, dev) X char *lok, *dev; X{ X int i, lockfile, pid; X X if ((lockfile = open(lok, O_RDONLY)) < 0) X return; X if (flock(lockfile, LOCK_SH|LOCK_NB) < 0) { X (void) read(lockfile, (char *)&pid, sizeof(int)); X (void) read(lockfile, buf, PW_NAMEL); X buf[PW_NAMEL] = '\0'; X if (dev) X Printf("%s in use by ", dev); X Printf("%u (%s)", pid, buf); X if ((i = read(lockfile, buf, BUFSIZ)) > 0) { X buf[i] = '\0'; X Printf(": needs %s", buf); X } X Printf("\n"); X } X (void) close(lockfile); X} X Xint Xpids(dp) X struct direct *dp; X{ X register char *p = dp->d_name; X while (*p) X if (!isdigit(*p++)) X return(0); X return(1); X} X Xint Xnumsort(d1, d2) X struct direct **d1, **d2; X{ X int p1, p2; X X p1 = atoi((*d1)->d_name); X p2 = atoi((*d2)->d_name); X return p1 - p2; X} X Xstatic int child; X Xsigcont() X{ X (void) kill(child, SIGCONT); X} X Xshell() X{ X register i; X char *myshell, *shellt; X union wait status; X char *getenv(), *rindex(); X int omask; X X if (!(myshell = getenv("SHELL"))) X myshell = "/bin/sh"; X if (shellt = rindex(myshell, '/')) X shellt++; X else X shellt = myshell; X X /* ignore keyboard generated signals now - could manipulate process X * groups I suppose, but I'm lazy X * First block them over the fork X */ X omask = sigblock(mask(SIGINT)|mask(SIGQUIT)); X (void) fflush(stderr); X if ((child = vfork()) == 0) { X /* normal keyboard signals in shell */ X (void) sigsetmask(omask); X child = getpid(); X (void) setuid(uuid); X (void) setgid(ugid); X for(i = getdtablesize(); i > 3; ) X (void) close(--i); X shellargs[0] = shellt; X execv(myshell, shellargs); X Printf("%s : couldn't execute %s : %s\n", X progname, myshell, sys_errlist[errno]); X _exit(1); X } else if (child < 0) { X Printf("%s : couldn't fork : %s\n", X progname, sys_errlist[errno]); X } else { X /* ignore keyboard signals in parent */ X (void) signal(SIGINT, SIG_IGN); X (void) signal(SIGQUIT, SIG_IGN); X (void) signal(SIGCONT, sigcont); X (void) sigsetmask(omask); X do { X while((i = wait3(&status, X WUNTRACED, X (struct rusage *)NULL)) > 0 X && i != child); X if (status.w_stopval == WSTOPPED) X (void) kill(0, (int)status.w_stopsig); X else X nerrs = status.w_retcode; X } while (status.w_stopval == WSTOPPED); X } X} X Xrestore(lkp) /*restores ownership and protections for resources unlocked*/ Xregister dev_lk *lkp; X{ X register int pdev; X X for(pdev = 0;pdev < MAXPDEV && (*subdev(lkp,pdev));pdev++){ X (void) chown( subdev(lkp,pdev),Rootuid,Rootgid ); X (void) chmod( subdev(lkp,pdev),OWNERONLY); X } X} X X/* VARARGS1 */ XPrintf(f, a, b, c, d) char *f; X{ X if (!silent) (void) fprintf(stderr, f, a, b, c, d); X} X Xfulfil() { fulfilled = 1; } X Xcleanup(sig) X{ X register dev_lk *lkp; X X /* wait for child to finish before we go ourselves */ X if (!nerrs || sig) Printf("\n"); X if (sig) Printf("%s\n", sys_siglist[sig]); X if (requested) syslog(LOGLEVEL, "request withdrawn"); X if (needrequest) { X if (sig) { X Printf("Request denied\n"); X syslog(LOGLEVEL, "request denied"); X } else if (resource) { X syslog(LOGLEVEL, "done with %s", resource); X } X } X /* check if there's anything to wait for */ X if (!wait3((union wait *)NULL, WNOHANG, (struct rusage *)NULL)) X Printf("waiting for shell to exit\n"); X (void) fflush(stderr); X while (wait((union wait *)NULL) > 0); X for (lkp = &locks[0]; lkp < &locks[nlock]; lkp++) X if (lkp->locked) { X restore(lkp); X Printf("%s released.\n", lkp->dev); X } X if (!resource) X (void) unlink(nolok.lok); X exit(sig ? -1 : nerrs); X} / echo 'Part 01 of pack.out complete.' exit ---- UUCP: ...!mcvax!ukc!warwick!cudcv PHONE: +44 203 523037 JANET: cudcv@uk.ac.warwick.daisy ARPA: cudcv@daisy.warwick.ac.uk Rob McMahon, Computing Services, Warwick University, Coventry CV4 7AL, England -- For comp.sources.unix stuff, mail to sources@uunet.uu.net.