[comp.unix.questions] Wait on an arbitrary process containing a pattern

montnaro@sprite.steinmetz.ge.com (Skip Montanaro) (02/10/88)

I would like to be able to wait on an arbitrary process, given a pattern
that occurs in its command string. For instance, I have a peripheral cleanup
task in a Makefile that compresses and archives some intermediate files. I
don't want the mainline build to be slowed down by this peripheral work, but
at the same time, the separate archiving commands all write to the same
archive file, so they have to wait for each other. Here's roughly how the
Makefile looks:

target.o : target.for
	... some initial stuff that generates target.f
	(compress target.f ; \
		ar /tmp/arch/intermed.a target.f; \
		rm target.f.Z)&
	... compile target.f into target.o

Clearly, that middle step must wait until it can get clear access to the
archive file. So, not knowing the process id of the previous ar command, I'd
like to wait for it's completion:

target.o : target.for
	... some initial stuff that generates target.f
	(waiton "ar /tmp" ; \
		compress target.f ; \
		ar /tmp/arch/intermed.a target.f; \
		rm target.f.Z)&
	... compile target.f into target.o

I wrote a relatively straightforward version of waiton that discovered the
highest numbered process whose command line contained the indicated pattern:

------------------------------------------------------------------------------
#!/bin/sh
# wait for the last (highest numbered) process to complete that contains the
# string in argv[1]

if [ $# = 0 ] ; then
  echo "usage: $0 pattern"
  exit 1
fi

s=$1

# The following pipeline is intended to grab the last process with $s in its
# command line, excluding this process, and any grep processes.  It should
# catch the command which we are waiting on and any other waiton processes.
# Clearly, this won't work if you want to "waiton grep".  It is also not
# going to work when the process numbering wraps around to zero.  It tacitly
# assumes that the last process in the list is waiting on the next to the
# last process on the list, and so on.

j=`ps -a|grep "$s"|grep -v ";"|grep -v "^ *$$"|grep -v grep|sort -n|tail -1`

# pid, if it exists, into $1
set X $j
shift

if [ $# -gt 0 ] ; then
  wait $1
fi
------------------------------------------------------------------------------

In principal, this would work okay, except that the wait command will
apparently only wait for child processes, even if you are running as root.

My current hacked solution replaces the wait with a sleep, throws the whole
thing in a "while true" loop, and lengthens the sleep time on each pass
through the loop. Can somebody tell me whether or not I can get wait, or
some variant thereof, to wait on completion of an arbitrary process?

Thanks,

Skip (montanaro@ge-crd.arpa, uunet!steinmetz!sprite!montanaro)

chris@trantor.umd.edu (Chris Torek) (02/11/88)

In article <9493@steinmetz.steinmetz.UUCP> montnaro@sprite.steinmetz.ge.com
(Skip Montanaro) writes:
>I would like to be able to wait on an arbitrary process....

No good.  There is, however, an alternate solution, given what
you want done:

>... I have a peripheral cleanup task in a Makefile that compresses
>and archives some intermediate files.  I don't want the mainline build
>to be slowed down by this peripheral work, but at the same time, the
>separate archiving commands all write to the same archive file, so
>they have to wait for each other.

Instead of having the programs wait for each other, have them
wait for exclusive access to the file(s) involved.  In this
case, a short C program using the system's locking primitives
(modern SysV and BSD systems have advisory file locking) and
then calling the shell will do the trick:

/* this code is utterly untested, but it probably works */
#include <sys/types.h>
#include <sys/file.h>
#include <stdio.h>
#include <sysexits.h>	/* BSD; else make up values for EX_xxx */

char	*progname;
char	*buildcmd(), *malloc();
/* #define index strchr */ /* if not BSD */
char	*index();

/*
 * Print this program's name and call perror, then optionally exit.
 * The constant saving and restoring of errno (which might be
 * clobbered by fprintf) would get tiresome otherwise.
 *
 * A routine like this, but with printf-style formats, really
 * should be part of the standard library (and is at U of MD CSD).
 */
void
gripe(quit, e, msg)
	int quit;
	int e;
	char *msg;
{
	extern int errno;

	if (e < 0)		/* default */
		e = errno;
	fprintf(stderr, "%s: ", progname);
	errno = e;
	perror(msg);
	if (quit)
		exit(quit);
}

main(argc, argv)
	int argc;
	char **argv;
{
	char *cmd;
	int fd, saverr, pid, status, w;

	progname = argv[0];
	if (argc < 3) {
		fprintf(stderr, "usage: %s file command...\n", progname);
		exit(EX_USAGE);
	}

	/*
	 * Lock the file, then execute the command in a fork.
	 * The shell re-parses the command, so we have to quote it
	 * (see buildcmd).
	 */
	fd = lockit(argv[1]);
	cmd = buildcmd(argc - 2, argv + 2);
	(void) fflush(stderr);	/* not supposed to be buffered, but... */
	switch (pid = fork()) {

	case -1:		/* failed */
		gripe(EX_OSERR, -1, "fork");
		/* NOTREACHED */

	case 0:			/* child */
		(void) close(fd);
		execlp("sh", "sh", "-c", cmd, (char *)0);
		execl("/bin/sh", "sh", "-c", cmd, (char *)0);
		gripe(0, -1, "exec(/bin/sh)");
		fflush(stderr);
		_exit(1);
		/* NOTREACHED */
	}

	/* THE FOLLOWING ASSUMES EXITING UNLOCKS */

	/* parent */
	while ((w = wait(&status)) != pid && w != -1)
		/* void */;
	if (w == -1)
		gripe(EX_OSERR, -1, "child vanished?! wait");

	/* this is somewhat gross */
	if (status & 0xff) {
		fprintf(stderr, "%s: died on signal %d%s\n", cmd,
			status & 0x7f, status & 0x80 ? " (core dumped)" : "");
		exit(EX_UNAVAILABLE);	/* ??? */
	}
	exit((status >> 8) & 0xff);
	/* NOTREACHED */
}

/*
 * Shell special characters, for buildcmd, cmdlen, and quotecmd.
 * Each of these except \n can be quoted by prepending a backslash;
 * \n requires "" or ''.
 */
static char specials[] = "'\"\\*?[$^&()`|<>{};= \t\n";

/* auxiliary for buildcmd: return the length of the quoted version of s */
static int
cmdlen(s)
	register char *s;
{
	register int l = 0, c;

	while ((c = *s++) != 0) {
		len++;			/* count it */
		if (index(specials, c))	/* and maybe \ or "" too */
			len += c == '\n' ? 2 : 1;
	}
	return (len);
}

/*
 * auxiliary for buildcmd: stuff into p the quoted version of s; return
 * the place after the last stuffed character.
 */
static char *
quotecmd(p, s)
	register char *p, *s;
{
	register int c;

	while ((c = *s++) != 0) {
		if (index(specials, c))
			*p++ = c == '\n' ? '"' : '\\';
		*p++ = c;
		if (c == '\n')
			*p++ = '"';
	}
	return (p);
}

/*
 * Build a quoted version of a command given an argument vector v of
 * length n.
 */
char *
buildcmd(n, v)
	int n;
	register char **v;
{
	register int sum = 0;	/* should be size_t, but no <stddef.h> */
	register char **p = v + n;
	register char *s;
	char *cmd;

	/* how much room for 'x y z\0'? */
	while (--p >= v)
		sum += cmdlen(*p) + 1;
	if ((cmd = malloc(sum)) == NULL)
		gripe(EX_OSERR, -1, "malloc");
	/* put them in */
	for (sum = n, s = cmd; sum > 0;) {
		s = quotecmd(s, *v++);
		*s++ = --sum > 0 ? ' ' : 0;
	}
	return (cmd);
}

/*
 * The next function is clearly O/S dependent.  I have only a vague
 * idea as to how to do this on SysV.
 *
 * Lock the named file, creating it first if necessary.
 */
int
lockit(fname)
	char *fname;
{
	int fd = open(fname, O_RDONLY | O_CREAT, 0666);

	if (fd < 0)
		gripe(EX_CANTCREAT, -1, fname);	/* or EX_NOINPUT? */

	/* BSD: get exclusive lock, waiting if necessary */
	if (flock(fd, LOCK_EX))
		gripe(EX_OSERR, -1, "flock");

	return (fd);
}
-- 
In-Real-Life: Chris Torek, Univ of MD Computer Science, +1 301 454 7163
(hiding out on trantor.umd.edu until mimsy is reassembled in its new home)
Domain: chris@mimsy.umd.edu		Path: not easily reachable