src/usr.bin/rdist/docmd.c

1050 lines
24 KiB
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);
}
}