root/usr.bin/rdist/docmd.c
/*      $OpenBSD: docmd.c,v 1.36 2024/04/23 13:34:50 jsg Exp $  */

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

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "client.h"
#include "gram.h"

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

struct subcmd          *subcmds;                /* list of sub-commands for 
                                                   current cmd */
struct namelist        *filelist;               /* list of source files */
time_t                  lastmod;                /* Last modify time */

static void closeconn(void);
static void notify(char *, struct namelist *, time_t);
static void checkcmd(struct cmd *);
static void markfailed(struct cmd *, struct cmd *);
static int remotecmd(char *, char *, char *, char *);
static int makeconn(char *);
static void doarrow(struct cmd *, char **);
static void rcmptime(struct stat *, struct subcmd *, char **);
static void cmptime(char *, struct subcmd *, char **);
static void dodcolon(struct cmd *, char **);
static void docmdhost(struct cmd *, char **);
static void docmd(struct cmd *, int, char **);

/*
 * Signal end of connection.
 */
static void
closeconn(void)
{
        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(char *rhost, struct namelist *to, time_t lmod)
{
        int fd;
        ssize_t len;
        FILE *pf;
        struct stat stb;
        static char buf[BUFSIZ];
        char *file, *user;

        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)) == -1) {
                error("%s: open for reading failed: %s", file, SYSERR);
                return;
        }
        if (fstat(fd, &stb) == -1) {
                error("%s: fstat failed: %s", file, SYSERR);
                (void) close(fd);
                return;
        }
        if (stb.st_size == 0) {
                (void) close(fd);
                return;
        }
        /*
         * Create a pipe to mailing program.
         * Set IFS to avoid possible security problem with users
         * setting "IFS=/".
         */
        (void) snprintf(buf, sizeof(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, "Auto-Submitted: auto-generated\n");
        (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 ((user = getlogin()) == NULL)
                user = locuser;

        if (rhost != NULL)
                (void) fprintf(pf, 
                         "Subject: files updated by %s from %s to %s\n",
                         locuser, host, rhost);
        else
                (void) fprintf(pf, "Subject: files updated after %s\n", 
                               ctime(&lmod));
        (void) putc('\n', pf);
        (void) putc('\n', pf);
        (void) fprintf(pf, "Options: %s\n\n", getondistoptlist(options));

        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(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.
 */
void
markassigned(struct cmd *cmd, struct cmd *cmdlist)
{
        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(struct cmd *cmd, struct cmd *cmdlist)
{
        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(char *rhost, char *luser, char *ruser, char *cmd)
{
        int desc;

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

        debugmsg(DM_MISC, "Remote shell command = '%s'\n",
            path_remsh ? path_remsh : "default");
        (void) signal(SIGPIPE, SIG_IGN);
        desc = rcmdsh(&rhost, -1, luser, ruser, cmd, path_remsh);
        if (desc > 0)
                (void) signal(SIGPIPE, sighandler);

        (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(char *rhost)
{
        char *ruser, *cp;
        static char *cur_host = NULL;
        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) strlcpy((char *)tuser, rhost, sizeof(tuser));
                *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) snprintf(buf, sizeof(buf), "%.*s -S",
                        (int)(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
         */
        respbuff[0] = '\0';
        n = remline(respbuff, sizeof(respbuff), TRUE);
        if (n <= 0 || respbuff[0] != S_VERSION) {
                if (n > 0)
                    error("Unexpected input from server: \"%s\".", respbuff);
                else
                    error("No input from server.");
                closeconn();
                return(0);
        }

        /*
         * For future compatibility we check to see if the server
         * sent its 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 its version number to us
                 */
                int 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%lld", SC_FREESPACE, 
                               min_freespace);
                if (response() < 0)
                        return(0);
        }
        if (min_freefiles) {
                (void) sendcmd(C_SETCONFIG, "%c%lld", 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);
        }
        if (strcmp(defowner, "bin") != 0) {
                (void) sendcmd(C_SETCONFIG, "%c%s", SC_DEFOWNER, defowner);
                if (response() < 0)
                        return(0);
        }
        if (strcmp(defgroup, "bin") != 0) {
                (void) sendcmd(C_SETCONFIG, "%c%s", SC_DEFGROUP, defgroup);
                if (response() < 0)
                        return(0);
        }

        return(1);
}

/*
 * Process commands for sending files to other machines.
 */
static void
doarrow(struct cmd *cmd, char **filev)
{
        struct namelist *f;
        struct subcmd *sc;
        char **cpp;
        int n, ddir, destdir;
        volatile opt_t opts = options;
        struct namelist *files;
        struct subcmd *sbcmds;
        char *rhost;
        volatile 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(%p, %s, %p) 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= %p, 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=%p, 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;
                }
        }

        /*
         * Run any commands for the entire cmd
         */
        if (didupdate > 0) {
                runcmdspecial(cmd, 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) {
                struct linkbuf *nextl, *l;

                for (l = ihead; l != NULL; freelinkinfo(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;
}

int
okname(char *name)
{
        char *cp = name;
        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(struct stat *st, struct subcmd *sbcmds, char **env)
{
        DIR *d;
        struct dirent *dp;
        char *cp;
        char *optarget;
        int len;

        debugmsg(DM_CALL, "rcmptime(%p) 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)) != NULL) {
                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++) != '\0')
                        ;
                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(char *name, struct subcmd *sbcmds, char **env)
{
        struct subcmd *sc;
        struct stat stb;

        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, sizeof(target)) == NULL)
                        return;
                ptarget = name = target;
                while (*ptarget)
                        ptarget++;
        }
        if (access(name, R_OK) == -1 || stat(name, &stb) == -1) {
                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) snprintf(buf, sizeof(buf), "%s=%s;%s", 
                                        E_LOCFILE, name, sc->sc_name);
                        message(MT_CHANGE, "special \"%s\"", buf);
                        if (*env) {
                                size_t len = strlen(*env) + strlen(name) + 2;
                                *env = xrealloc(*env, len);
                                (void) strlcat(*env, name, len);
                                (void) strlcat(*env, ":", len);
                        }
                        if (IS_ON(options, DO_VERIFY))
                                continue;

                        runcommand(buf);
                }
        }
}

/*
 * Process commands for comparing files to time stamp files.
 */
static void
dodcolon(struct cmd *cmd, char **filev)
{
        struct subcmd *sc;
        struct namelist *f;
        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) == -1) {
                error("%s: stat failed: %s", stamp, SYSERR);
                return;
        }

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

        env = NULL;
        for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
                if (sc->sc_type == CMDSPECIAL) {
                        env = xmalloc(sizeof(E_FILES) + 3);
                        (void) snprintf(env, sizeof(E_FILES) + 3,
                                        "%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(NULL, sc->sc_args, (time_t)lastmod);
                else if (sc->sc_type == CMDSPECIAL && env) {
                        size_t len = strlen(env);
                        if (env[len - 1] == ':')
                                env[--len] = CNULL;
                        len += 2 + strlen(sc->sc_name) + 1;
                        env = xrealloc(env, len);
                        (void) strlcat(env, "';", len);
                        (void) strlcat(env, sc->sc_name, len);
                        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.
 */
int
except(char *file)
{
        struct  subcmd *sc;
        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 ebuf[BUFSIZ];
                                int ecode = 0;

                                /* allocate and compile n_regex as needed */
                                if (nl->n_regex == NULL) {
                                        nl->n_regex = xmalloc(sizeof(regex_t));
                                        ecode = regcomp(nl->n_regex, nl->n_name,
                                                        REG_NOSUB);
                                }
                                if (ecode == 0) {
                                        ecode = regexec(nl->n_regex, file, 0,
                                            NULL, 0);
                                }
                                switch (ecode) {
                                case REG_NOMATCH:
                                        break;
                                case 0:
                                        return(1);      /* match! */
                                default:
                                        regerror(ecode, nl->n_regex, ebuf,
                                                 sizeof(ebuf));
                                        error("Regex error \"%s\" for \"%s\".",
                                              ebuf, nl->n_name);
                                        return(0);
                                }
                        }
                }
        }
        return(0);
}

/*
 * Do a specific command for a specific host
 */
static void
docmdhost(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) {
                pid_t 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>";
                setproctitle("update %s", currenthost);
        }

        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(struct cmd *cmd, int argc, char **argv)
{
        struct namelist *f;
        int i;

        if (argc) {
                for (i = 0; i < argc; i++) {
                        if (cmd->c_label != NULL &&
                            strcmp(cmd->c_label, argv[i]) == 0) {
                                docmdhost(cmd, 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, 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).
 */
void
docmds(struct namelist *hostlist, int argc, char **argv)
{
        struct cmd *c;
        char *cp;
        int i;

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

        if (!nflag)
                setvbuf(stdout, NULL, _IOLBF, 0);

        /*
         * 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 */
                        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(0);
                        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(0);
        }
}