/*
 * Copyright (c) 1992-1998 Michael A. Cooper.
 * This software may be freely used and distributed provided it is not
 * sold for profit or used in part or in whole for commercial gain
 * without prior written agreement, and the author is credited
 * appropriately.
 */
/*
 * Copyright (c) 1983 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static char RCSid[] = 
"$Id: docmd.c,v 6.87 1998/11/10 04:08:32 mcooper Exp $";

static char sccsid[] = "@(#)docmd.c	5.1 (Berkeley) 6/6/85";

static char copyright[] =
"Copyright (c) 1992-1998 Michael A. Cooper.\n\
@(#) Copyright (c) 1983 Regents of the University of California.\n\
 All rights reserved.\n";
#endif /* not lint */

/*
 * Functions for rdist that do command (cmd) related activities.
 */

#include "defs.h"
#include <sys/socket.h>
#include <netdb.h>

struct subcmd	       *subcmds;		/* list of sub-commands for 
						   current cmd */
struct namelist	       *filelist;		/* list of source files */
extern struct cmd      *cmds;			/* Initialized by yyparse() */
time_t			lastmod;		/* Last modify time */

extern char 		target[];
extern char 	       *ptarget;
extern int		activechildren;
extern int		maxchildren;
extern int		amchild;
extern char	       *path_rdistd;

static void cmptime();

/*
 * Signal end of connection.
 */
static void closeconn()
{
	debugmsg(DM_CALL, "closeconn() called\n");

	if (rem_w >= 0) {
		/* We don't care if the connection is still good or not */
		signal(SIGPIPE, SIG_IGN);	

		(void) sendcmd(C_FERRMSG, NULL);
		(void) close(rem_w);
		(void) close(rem_r); /* This can't hurt */
		rem_w = -1;
		rem_r = -1;
	}
}

/*
 * Notify the list of people the changes that were made.
 * rhost == NULL if we are mailing a list of changes compared to at time
 * stamp file.
 */
static void notify(rhost, to, lmod)
	char *rhost;
	register struct namelist *to;
	time_t lmod;
{
	register int fd, len;
	FILE *pf, *popen();
	struct stat stb;
	static char buf[BUFSIZ];
	char *file;

	if (IS_ON(options, DO_VERIFY) || to == NULL)
		return;

	if ((file = getnotifyfile()) == NULL)
		return;

	if (!IS_ON(options, DO_QUIET)) {
		message(MT_INFO, "notify %s%s %s", 
			(rhost) ? "@" : "",
			(rhost) ? rhost : "", getnlstr(to));
	}

	if (nflag)
		return;

	debugmsg(DM_MISC, "notify() temp file = '%s'", file);

	if ((fd = open(file, O_RDONLY)) < 0) {
		error("%s: open for reading failed: %s", file, SYSERR);
		return;
	}
	if (fstat(fd, &stb) < 0) {
		error("%s: fstat failed: %s", file, SYSERR);
		(void) close(fd);
		return;
	}
	if (stb.st_size == 0) {
		(void) close(fd);
		return;
	}
	/*
	 * Create a pipe to mailling program.
	 * Set IFS to avoid possible security problem with users
	 * setting "IFS=/".
	 */
	(void) sprintf(buf, "IFS=\" \t\"; export IFS; %s -oi -t", 
		       _PATH_SENDMAIL);
	pf = popen(buf, "w");
	if (pf == NULL) {
		error("notify: \"%s\" failed\n", _PATH_SENDMAIL);
		(void) unlink(file);
		(void) close(fd);
		return;
	}
	/*
	 * Output the proper header information.
	 */
	(void) fprintf(pf, "From: rdist (Remote distribution program)\n");
	(void) fprintf(pf, "To:");
	if (!any('@', to->n_name) && rhost != NULL)
		(void) fprintf(pf, " %s@%s", to->n_name, rhost);
	else
		(void) fprintf(pf, " %s", to->n_name);
	to = to->n_next;
	while (to != NULL) {
		if (!any('@', to->n_name) && rhost != NULL)
			(void) fprintf(pf, ", %s@%s", to->n_name, rhost);
		else
			(void) fprintf(pf, ", %s", to->n_name);
		to = to->n_next;
	}
	(void) putc('\n', pf);
	if (rhost != NULL)
		(void) fprintf(pf, 
			     "Subject: files updated by rdist from %s to %s\n",
			       host, rhost);
	else
		(void) fprintf(pf, "Subject: files updated after %s\n", 
			       ctime(&lmod));
	(void) putc('\n', pf);
	(void) putc('\n', pf);

	while ((len = read(fd, buf, sizeof(buf))) > 0)
		(void) fwrite(buf, 1, len, pf);

	(void) pclose(pf);
	(void) close(fd);
	(void) unlink(file);
}

/* 
 * XXX Hack for NFS.  If a hostname from the distfile
 * ends with a '+', then the normal restriction of
 * skipping files that are on an NFS filesystem is
 * bypassed.  We always strip '+' to be consistent.
 */
static void checkcmd(cmd)
	struct cmd *cmd;
{
	int l;

	if (!cmd || !(cmd->c_name)) {
		debugmsg(DM_MISC, "checkcmd() NULL cmd parameter");
		return;
	}

	l = strlen(cmd->c_name);
	if (l <= 0)
		return;
	if (cmd->c_name[l-1] == '+') {
		cmd->c_flags |= CMD_NOCHKNFS;
		cmd->c_name[l-1] = CNULL;
	}
}

/*
 * Mark all other entries for this command (cmd)
 * as assigned.
 */
extern void markassigned(cmd, cmdlist)
	struct cmd *cmd;
	struct cmd *cmdlist;
{
	register struct cmd *pcmd;
	
	for (pcmd = cmdlist; pcmd; pcmd = pcmd->c_next) {
		checkcmd(pcmd);
		if (pcmd->c_type == cmd->c_type &&
		    strcmp(pcmd->c_name, cmd->c_name)==0)
			pcmd->c_flags |= CMD_ASSIGNED;
	}
}

/*
 * Mark the command "cmd" as failed for all commands in list cmdlist.
 */
static void markfailed(cmd, cmdlist)
	struct cmd *cmd;
	struct cmd *cmdlist;
{
	register struct cmd *pc;

	if (!cmd) {
		debugmsg(DM_MISC, "markfailed() NULL cmd parameter");
		return;
	}

	checkcmd(cmd);
	cmd->c_flags |= CMD_CONNFAILED;
	for (pc = cmdlist; pc; pc = pc->c_next) {
		checkcmd(pc);
		if (pc->c_type == cmd->c_type &&
		    strcmp(pc->c_name, cmd->c_name)==0)
			pc->c_flags |= CMD_CONNFAILED;
	}
}

static int remotecmd(rhost, luser, ruser, cmd)
	char *rhost;
	char *luser, *ruser;
	char *cmd;
{
	int desc;
#if	defined(DIRECT_RCMD)
	static int port = -1;
#endif	/* DIRECT_RCMD */

	debugmsg(DM_MISC, "local user = %s remote user = %s\n", luser, ruser);
	debugmsg(DM_MISC, "Remote command = '%s'\n", cmd);

	(void) fflush(stdout);
	(void) fflush(stderr);
	(void) signal(SIGALRM, sighandler);
	(void) alarm(RTIMEOUT);

#if	defined(DIRECT_RCMD)
	(void) signal(SIGPIPE, sighandler);

	if (port < 0) {
		struct servent *sp;
		
		if ((sp = getservbyname("shell", "tcp")) == NULL)
				fatalerr("shell/tcp: unknown service");
		port = sp->s_port;
	}

	if (becomeroot() != 0)
		exit(1);
	desc = rcmd(&rhost, port, luser, ruser, cmd, 0);
	if (becomeuser() != 0)
		exit(1);
#else	/* !DIRECT_RCMD */
	debugmsg(DM_MISC, "Remote shell command = '%s'\n", path_remsh);
	(void) signal(SIGPIPE, SIG_IGN);
	desc = rshrcmd(&rhost, -1, luser, ruser, cmd, 0);
	if (desc > 0)
		(void) signal(SIGPIPE, sighandler);
#endif	/* DIRECT_RCMD */

	(void) alarm(0);

	return(desc);
}

/*
 * Create a connection to the rdist server on the machine rhost.
 * Return 0 if the connection fails or 1 if it succeeds.
 */
static int makeconn(rhost)
	char *rhost;
{
	register char *ruser, *cp;
	static char *cur_host = NULL;
	extern char *locuser;
	extern long min_freefiles, min_freespace;
	extern char *remotemsglist;
	char tuser[BUFSIZ], buf[BUFSIZ];
	u_char respbuff[BUFSIZ];
	int n;

	debugmsg(DM_CALL, "makeconn(%s)", rhost);

	/*
	 * See if we're already connected to this host
	 */
	if (cur_host != NULL && rem_w >= 0) {
		if (strcmp(cur_host, rhost) == 0)
			return(1);
		closeconn();
	}

	/*
	 * Determine remote user and current host names
	 */
	cur_host = rhost;
	cp = strchr(rhost, '@');

	if (cp != NULL) {
		char c = *cp;

		*cp = CNULL;
		(void) strncpy((char *)tuser, rhost, sizeof(tuser)-1);
		*cp = c;
		rhost = cp + 1;
		ruser = tuser;
		if (*ruser == CNULL)
			ruser = locuser;
		else if (!okname(ruser))
			return(0);
	} else
		ruser = locuser;

	if (!IS_ON(options, DO_QUIET))
		message(MT_VERBOSE, "updating host %s", rhost);

	(void) sprintf(buf, "%.*s -S", sizeof(buf)-5, path_rdistd);
		
	if ((rem_r = rem_w = remotecmd(rhost, locuser, ruser, buf)) < 0)
		return(0);

	/*
	 * First thing received should be S_VERSION
	 */
	n = remline(respbuff, sizeof(respbuff), TRUE);
	if (n <= 0 || respbuff[0] != S_VERSION) {
		error("Unexpected input from server: \"%s\".", respbuff);
		closeconn();
		return(0);
	}

	/*
	 * For future compatibility we check to see if the server
	 * sent it's version number to us.  If it did, we use it,
	 * otherwise, we send our version number to the server and let
	 * it decide if it can handle our protocol version.
	 */
	if (respbuff[1] == CNULL) {
		/*
		 * The server wants us to send it our version number
		 */
		(void) sendcmd(S_VERSION, "%d", VERSION);
		if (response() < 0) 
			return(0);
	} else {
		/*
		 * The server sent it's version number to us
		 */
		proto_version = atoi(&respbuff[1]);
		if (proto_version != VERSION) {
			fatalerr(
		  "Server version (%d) is not the same as local version (%d).",
			      proto_version, VERSION);
			return(0);
		}
	}

	/*
	 * Send config commands
	 */
	if (host[0]) {
		(void) sendcmd(C_SETCONFIG, "%c%s", SC_HOSTNAME, host);
		if (response() < 0)
			return(0);
	}
	if (min_freespace) {
		(void) sendcmd(C_SETCONFIG, "%c%d", SC_FREESPACE, 
			       min_freespace);
		if (response() < 0)
			return(0);
	}
	if (min_freefiles) {
		(void) sendcmd(C_SETCONFIG, "%c%d", SC_FREEFILES, 
			       min_freefiles);
		if (response() < 0)
			return(0);
	}
	if (remotemsglist) {
		(void) sendcmd(C_SETCONFIG, "%c%s", SC_LOGGING, remotemsglist);
		if (response() < 0)
			return(0);
	}

	return(1);
}

/*
 * Process commands for sending files to other machines.
 */
static void doarrow(cmd, filev)
	struct cmd *cmd;
	char **filev;
{
	register struct namelist *f;
	register struct subcmd *sc;
	register char **cpp;
	int n, ddir, destdir, opts = options;
	struct namelist *files;
	struct subcmd *sbcmds;
	char *rhost;
	int didupdate = 0;

        if (setjmp_ok) {
		error("reentrant call to doarrow");
		abort();
	}

	if (!cmd) {
		debugmsg(DM_MISC, "doarrow() NULL cmd parameter");
		return;
	}

	files = cmd->c_files;
	sbcmds = cmd->c_cmds;
	rhost = cmd->c_name;

	if (files == NULL) {
		error("No files to be updated on %s for target \"%s\"", 
		      rhost, cmd->c_label);
		return;
	}

	debugmsg(DM_CALL, "doarrow(%x, %s, %x) start", 
		 files, A(rhost), sbcmds);

	if (nflag)
		(void) printf("updating host %s\n", rhost);
	else {
		if (cmd->c_flags & CMD_CONNFAILED) {
			debugmsg(DM_MISC,
				 "makeconn %s failed before; skipping\n",
				 rhost);
			return;
		}

		if (setjmp(finish_jmpbuf)) {
			setjmp_ok = FALSE;
			debugmsg(DM_MISC, "setjmp to finish_jmpbuf");
			markfailed(cmd, cmds);
			return;
		}
		setjmp_ok = TRUE;

		if (!makeconn(rhost)) {
			setjmp_ok = FALSE;
			markfailed(cmd, cmds);
			return;
		}
	}

	subcmds = sbcmds;
	filelist = files;

	n = 0;
	for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
		if (sc->sc_type != INSTALL)
			continue;
		n++;
	/*
	 * destination is a directory if one of the following is true:
	 * a) more than one name specified on left side of -> directive
	 * b) basename of destination in "install" directive is "."
	 *    (e.g. install /tmp/.;)
	 * c) name on left side of -> directive is a directory on local system.
 	 *
 	 * We need 2 destdir flags (destdir and ddir) because single directory
 	 * source is handled differently.  In this case, ddir is 0 (which
 	 * tells install() not to send DIRTARGET directive to remote rdistd)
 	 * and destdir is 1 (which tells remfilename() how to build the FILE
 	 * variables correctly).  In every other case, destdir and ddir will
 	 * have the same value.
	 */
  	ddir = files->n_next != NULL;	/* destination is a directory */
	if (!ddir) {
		struct stat s;
 		int isadir = 0;

		if (lstat(files->n_name, &s) == 0)
 			isadir = S_ISDIR(s.st_mode);
 		if (!isadir && sc->sc_name && *sc->sc_name)
 			ddir = !strcmp(xbasename(sc->sc_name),".");
 		destdir = isadir | ddir;
 	} else
 		destdir = ddir;

	debugmsg(DM_MISC,
		 "Debug files->n_next= %d, destdir=%d, ddir=%d",
		 files->n_next, destdir, ddir);
 
	if (!sc->sc_name || !*sc->sc_name) {
		destdir = 0;
		ddir = 0;
	}

	debugmsg(DM_MISC,
		 "Debug sc->sc_name=%x, destdir=%d, ddir=%d",
		 sc->sc_name, destdir, ddir);

	for (f = files; f != NULL; f = f->n_next) {
		if (filev) {
			for (cpp = filev; *cpp; cpp++)
				if (strcmp(f->n_name, *cpp) == 0)
					goto found;
			continue;
		}
	found:
		if (install(f->n_name, sc->sc_name, ddir, destdir,
				sc->sc_options) > 0)
			++didupdate;
		opts = sc->sc_options;
	}

	} /* end loop for each INSTALL command */

	/* if no INSTALL commands present, do default install */
	if (!n) {
		for (f = files; f != NULL; f = f->n_next) {
			if (filev) {
				for (cpp = filev; *cpp; cpp++)
					if (strcmp(f->n_name, *cpp) == 0)
						goto found2;
				continue;
			}
		found2:
			/* ddir & destdir set to zero for default install */
			if (install(f->n_name, NULL, 0, 0, options) > 0)
				++didupdate;
		}
	}

done:
	/*
	 * Run any commands for the entire cmd
	 */
	if (didupdate > 0) {
		runcmdspecial(cmd, filev, opts);
		didupdate = 0;
	}

	if (!nflag)
		(void) signal(SIGPIPE, cleanup);

	for (sc = sbcmds; sc != NULL; sc = sc->sc_next)
		if (sc->sc_type == NOTIFY)
			notify(rhost, sc->sc_args, (time_t) 0);

	if (!nflag) {
		register struct linkbuf *nextl, *l;

		for (l = ihead; l != NULL; free((char *)l), l = nextl) {
			nextl = l->nextp;
			if (contimedout || IS_ON(opts, DO_IGNLNKS) || 
			    l->count == 0)
				continue;
			message(MT_WARNING, "%s: Warning: %d %s link%s",
				l->pathname, abs(l->count),	
				(l->count > 0) ? "missing" : "extra",
				(l->count == 1) ? "" : "s");
		}
		ihead = NULL;
	}
	setjmp_ok = FALSE;
}

okname(name)
	register char *name;
{
	register char *cp = name;
	register int c, isbad;

	for (isbad = FALSE; *cp && !isbad; ++cp) {
		c = *cp;
		if (c & 0200)
			isbad = TRUE;
		if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-')
			isbad = TRUE;
	}

	if (isbad) {
		error("Invalid user name \"%s\"\n", name);
		return(0);
	}
	return(1);
}

static void rcmptime(st, sbcmds, env)
	struct stat *st;
	struct subcmd *sbcmds;
	char **env;
{
	register DIR *d;
	register DIRENTRY *dp;
	register char *cp;
	char *optarget;
	int len;

	debugmsg(DM_CALL, "rcmptime(%x) start", st);

	if ((d = opendir((char *) target)) == NULL) {
		error("%s: open directory failed: %s", target, SYSERR);
		return;
	}
	optarget = ptarget;
	len = ptarget - target;
	while (dp = readdir(d)) {
		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
			continue;
		if (len + 1 + (int)strlen(dp->d_name) >= BUFSIZ - 1) {
			error("%s/%s: Name too long\n", target, dp->d_name);
			continue;
		}
		ptarget = optarget;
		*ptarget++ = '/';
		cp = dp->d_name;
		while (*ptarget++ = *cp++)
			;
		ptarget--;
		cmptime(target, sbcmds, env);
	}
	(void) closedir((DIR *) d);
	ptarget = optarget;
	*ptarget = '\0';
}

/*
 * Compare the mtime of file to the list of time stamps.
 */
static void cmptime(name, sbcmds, env)
	char *name;
	struct subcmd *sbcmds;
	char **env;
{
	struct subcmd *sc;
	struct stat stb;
	int inlist();

	debugmsg(DM_CALL, "cmptime(%s)", name);

	if (except(name))
		return;

	if (nflag) {
		(void) printf("comparing dates: %s\n", name);
		return;
	}

	/*
	 * first time cmptime() is called?
	 */
	if (ptarget == NULL) {
		if (exptilde(target, name) == NULL)
			return;
		ptarget = name = target;
		while (*ptarget)
			ptarget++;
	}
	if (access(name, R_OK) < 0 || stat(name, &stb) < 0) {
		error("%s: cannot access file: %s", name, SYSERR);
		return;
	}

	if (S_ISDIR(stb.st_mode)) {
		rcmptime(&stb, sbcmds, env);
		return;
	} else if (!S_ISREG(stb.st_mode)) {
		error("%s: not a plain file", name);
		return;
	}

	if (stb.st_mtime > lastmod) {
		message(MT_INFO, "%s: file is newer", name);
		for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
			char buf[BUFSIZ];
			if (sc->sc_type != SPECIAL)
				continue;
			if (sc->sc_args != NULL && !inlist(sc->sc_args, name))
				continue;
			(void) sprintf(buf, "%s=%s;%s", 
				       E_LOCFILE, name, sc->sc_name);
			message(MT_CHANGE, "special \"%s\"", buf);
			if (*env) {
				int len = strlen(*env);
				*env = (char *) xrealloc(*env, len +
							 strlen(name) + 2);
				*env[len] = CNULL;
				(void) strcat(*env, name);
				(void) strcat(*env, ":");
			}
			if (IS_ON(options, DO_VERIFY))
				continue;

			runcommand(buf);
		}
	}
}

/*
 * Process commands for comparing files to time stamp files.
 */
static void dodcolon(cmd, filev)
	struct cmd *cmd;
	char **filev;
{
	register struct subcmd *sc;
	register struct namelist *f;
	register char *cp, **cpp;
	struct stat stb;
	struct namelist *files = cmd->c_files;
	struct subcmd *sbcmds = cmd->c_cmds;
	char *env, *stamp = cmd->c_name;

	debugmsg(DM_CALL, "dodcolon()");

	if (files == NULL) {
		error("No files to be updated for target \"%s\"", 
		      cmd->c_label);
		return;
	}
	if (stat(stamp, &stb) < 0) {
		error("%s: stat failed: %s", stamp, SYSERR);
		return;
	}

	debugmsg(DM_MISC, "%s: mtime %d\n", stamp, stb.st_mtime);

	env = NULL;
	for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
		if (sc->sc_type == CMDSPECIAL) {
			env = (char *) xmalloc(sizeof(E_FILES) + 3);
			(void) sprintf(env, "%s='", E_FILES);
			break;
		}
	}

	subcmds = sbcmds;
	filelist = files;

	lastmod = stb.st_mtime;
	if (!nflag && !IS_ON(options, DO_VERIFY))
		/*
		 * Set atime and mtime to current time
		 */
		(void) setfiletime(stamp, (time_t) 0, (time_t) 0);

	for (f = files; f != NULL; f = f->n_next) {
		if (filev) {
			for (cpp = filev; *cpp; cpp++)
				if (strcmp(f->n_name, *cpp) == 0)
					goto found;
			continue;
		}
	found:
		ptarget = NULL;
		cmptime(f->n_name, sbcmds, &env);
	}

	for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
		if (sc->sc_type == NOTIFY)
			notify((char *)NULL, sc->sc_args, (time_t)lastmod);
		else if (sc->sc_type == CMDSPECIAL && env) {
			char *p;
			int len = strlen(env);

			env = xrealloc(env, 
				       len + strlen(sc->sc_name) + 2);
			env[len] = CNULL;
			if (*(p = &env[len - 1]) == ':')
				*p = CNULL;
			(void) strcat(env, "';");
			(void) strcat(env, sc->sc_name);
			message(MT_CHANGE, "cmdspecial \"%s\"", env);
			if (!nflag && IS_OFF(options, DO_VERIFY))
				runcommand(env);
			(void) free(env);
			env = NULL;	/* so cmdspecial is only called once */
		}
	}
	if (!nflag && !IS_ON(options, DO_VERIFY) && (cp = getnotifyfile()))
		(void) unlink(cp);
}

/*
 * Return TRUE if file is in the exception list.
 */
extern int except(file)
	char *file;
{
	register struct	subcmd *sc;
	register struct	namelist *nl;

	debugmsg(DM_CALL, "except(%s)", file);

	for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
		if (sc->sc_type == EXCEPT) {
			for (nl = sc->sc_args; nl != NULL; nl = nl->n_next)
				if (strcmp(file, nl->n_name) == 0)
					return(1);
  			continue;
		}
		if (sc->sc_type == PATTERN) {
			for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) {
				char *cp, *re_comp();

				if ((cp = re_comp(nl->n_name)) != NULL) {
					error("Regex error \"%s\" for \"%s\".",
					      cp, nl->n_name);
					return(0);
				}
				if (re_exec(file) > 0)
  					return(1);
			}
		}
	}
	return(0);
}

/*
 * Do a specific command for a specific host
 */
static void docmdhost(cmd, filev)
	struct cmd *cmd;
	char **filev;
{
	checkcmd(cmd);

	/*
	 * If we're multi-threaded and we're the parent, spawn a 
	 * new child process.
	 */
	if (do_fork && !amchild) {
		int pid;

		/*
		 * If we're at maxchildren, wait for number of active
		 * children to fall below max number of children.
		 */
		while (activechildren >= maxchildren)
			waitup();

		pid = spawn(cmd, cmds);
		if (pid == 0)
			/* Child */
			amchild = 1;
		else
			/* Parent */
			return;
	}

	/*
	 * Disable NFS checks
	 */
	if (cmd->c_flags & CMD_NOCHKNFS)
		FLAG_OFF(options, DO_CHKNFS);

	if (!nflag) {
		currenthost = (cmd->c_name) ? cmd->c_name : "<unknown>";
#if	defined(SETARGS)
		setproctitle("update %s", currenthost);
#endif 	/* SETARGS */
	}

	switch (cmd->c_type) {
	case ARROW:
		doarrow(cmd, filev);
		break;
	case DCOLON:
		dodcolon(cmd, filev);
		break;
	default:
		fatalerr("illegal command type %d", cmd->c_type);
	}
}

/*
 * Do a specific command (cmd)
 */
static void docmd(cmd, argc, argv)
	struct cmd *cmd;
	int argc;
	char **argv;
{
	register struct namelist *f;
	register int i;

	if (argc) {
		for (i = 0; i < argc; i++) {
			if (cmd->c_label != NULL &&
			    strcmp(cmd->c_label, argv[i]) == 0) {
				docmdhost(cmd, (char **) NULL);
				return;
			}
			for (f = cmd->c_files; f != NULL; f = f->n_next)
				if (strcmp(f->n_name, argv[i]) == 0) {
					docmdhost(cmd, &argv[i]);
					return;
				}
		}
	} else
		docmdhost(cmd, (char **) NULL);
}

/*
 *
 * Multiple hosts are updated at once via a "ring" of at most
 * maxchildren rdist processes.  The parent rdist fork()'s a child
 * for a given host.  That child will update the given target files
 * and then continue scanning through the remaining targets looking
 * for more work for a given host.  Meanwhile, the parent gets the
 * next target command and makes sure that it hasn't encountered
 * that host yet since the children are responsible for everything
 * for that host.  If no children have done this host, then check
 * to see if the number of active proc's is less than maxchildren.
 * If so, then spawn a new child for that host.  Otherwise, wait
 * for a child to finish.
 *
 */

/*
 * Do the commands in cmds (initialized by yyparse).
 */
extern void docmds(hostlist, argc, argv)
	struct namelist *hostlist;
	int argc;
	char **argv;
{
	register struct cmd *c;
	register char *cp;
	register int i;

	(void) signal(SIGHUP, sighandler);
	(void) signal(SIGINT, sighandler);
	(void) signal(SIGQUIT, sighandler);
	(void) signal(SIGTERM, sighandler);

	if (!nflag)
		mysetlinebuf(stdout);	/* Make output (mostly) clean */

#if	defined(USE_STATDB)
	if (!nflag && (dostatdb || juststatdb)) {
		extern long reccount;
		message(MT_INFO, "Making stat database [%s] ... \n", 
			       gettimestr());
		if (mkstatdb() < 0)
			error("Warning: Make stat database failed.");
		message(MT_INFO,
			      "Stat database created: %d files stored [%s].\n",
			       reccount, gettimestr());
		if (juststatdb)
			return;
	}
#endif	/* USE_STATDB */

	/*
	 * Print errors for any command line targets we didn't find.
	 * If any errors are found, return to main() which will then exit.
	 */
	for (i = 0; i < argc; i++) {
		int found;

		for (found = FALSE, c = cmds; c != NULL; c = c->c_next) {
			if (c->c_label && argv[i] && 
			    strcmp(c->c_label, argv[i]) == 0) {
				found = TRUE;
				break;
			}
		}
		if (!found)
			error("Label \"%s\" is not defined in the distfile.", 
			      argv[i]);
	}
	if (nerrs)
		return;

	/*
	 * Main command loop.  Loop through all the commands.
	 */
	for (c = cmds; c != NULL; c = c->c_next) {
		checkcmd(c);
		if (do_fork) {
			/*
			 * Let the children take care of their assigned host
			 */
			if (amchild) {
				if (strcmp(c->c_name, currenthost) != 0)
					continue;
			} else if (c->c_flags & CMD_ASSIGNED) {
				/* This cmd has been previously assigned */
				debugmsg(DM_MISC, "prev assigned: %s\n",
					 c->c_name);
				continue;
			}
		}

		if (hostlist) {
			/* Do specific hosts as specified on command line */
			register struct namelist *nlptr;

			for (nlptr = hostlist; nlptr; nlptr = nlptr->n_next)
				/*
				 * Try an exact match and then a match
				 * without '@' (if present).
				 */
				if ((strcmp(c->c_name, nlptr->n_name) == 0) ||
				    ((cp = strchr(c->c_name, '@')) &&
				     strcmp(++cp, nlptr->n_name) == 0))
					docmd(c, argc, argv);
			continue;
		} else
			/* Do all of the command */
			docmd(c, argc, argv);
	}

	if (do_fork) {
		/*
		 * We're multi-threaded, so do appropriate shutdown
		 * actions based on whether we're the parent or a child.
		 */
		if (amchild) {
			if (!IS_ON(options, DO_QUIET))
				message(MT_VERBOSE, "updating of %s finished", 
					currenthost);
			closeconn();
			cleanup();
			exit(nerrs);
		}

		/*
		 * Wait for all remaining active children to finish
		 */
		while (activechildren > 0) {
			debugmsg(DM_MISC, 
				 "Waiting for %d children to finish.\n",
				 activechildren);
			waitup();
		}
	} else if (!nflag) {
		/*
		 * We're single-threaded so close down current connection
		 */
		closeconn();
		cleanup();
	}
}
