version 1.1, 1995/10/18 08:45:58 |
version 1.2, 1996/02/03 12:12:22 |
|
|
/* |
/* |
* Copyright (c) 1983, 1993 |
* Copyright (c) 1983 Regents of the University of California. |
* The Regents of the University of California. All rights reserved. |
* All rights reserved. |
* |
* |
* Redistribution and use in source and binary forms, with or without |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* modification, are permitted provided that the following conditions |
|
|
*/ |
*/ |
|
|
#ifndef lint |
#ifndef lint |
/* from: static char sccsid[] = "@(#)docmd.c 8.1 (Berkeley) 6/9/93"; */ |
static char RCSid[] = |
static char *rcsid = "$Id$"; |
"$Id$"; |
|
|
|
static char sccsid[] = "@(#)docmd.c 5.1 (Berkeley) 6/6/85"; |
|
|
|
static char copyright[] = |
|
"@(#) Copyright (c) 1983 Regents of the University of California.\n\ |
|
All rights reserved.\n"; |
#endif /* not lint */ |
#endif /* not lint */ |
|
|
|
/* |
|
* Functions for rdist that do command (cmd) related activities. |
|
*/ |
|
|
#include "defs.h" |
#include "defs.h" |
#include <setjmp.h> |
#include "y.tab.h" |
|
#include <sys/socket.h> |
#include <netdb.h> |
#include <netdb.h> |
|
|
FILE *lfp; /* log file for recording files updated */ |
struct subcmd *subcmds; /* list of sub-commands for |
struct subcmd *subcmds; /* list of sub-commands for current cmd */ |
current cmd */ |
jmp_buf env; |
struct namelist *filelist; /* list of source files */ |
|
extern struct cmd *cmds; /* Initialized by yyparse() */ |
|
time_t lastmod; /* Last modify time */ |
|
|
static int makeconn __P((char *)); |
extern char target[]; |
static int okname __P((char *)); |
extern char *ptarget; |
static void closeconn __P((void)); |
extern int activechildren; |
static void cmptime __P((char *)); |
extern int maxchildren; |
static void doarrow __P((char **, |
extern int amchild; |
struct namelist *, char *, struct subcmd *)); |
extern char *path_rdistd; |
static void dodcolon __P((char **, |
|
struct namelist *, char *, struct subcmd *)); |
|
static void notify __P((char *, char *, struct namelist *, time_t)); |
|
static void rcmptime __P((struct stat *)); |
|
|
|
|
static void cmptime(); |
|
|
/* |
/* |
* Do the commands in cmds (initialized by yyparse). |
* Signal end of connection. |
*/ |
*/ |
void |
static void closeconn() |
docmds(dhosts, argc, argv) |
|
char **dhosts; |
|
int argc; |
|
char **argv; |
|
{ |
{ |
register struct cmd *c; |
debugmsg(DM_CALL, "closeconn() called\n"); |
register struct namelist *f; |
|
register char **cpp; |
|
extern struct cmd *cmds; |
|
|
|
signal(SIGHUP, cleanup); |
if (rem_w >= 0) { |
signal(SIGINT, cleanup); |
/* We don't care if the connection is still good or not */ |
signal(SIGQUIT, cleanup); |
signal(SIGPIPE, SIG_IGN); |
signal(SIGTERM, cleanup); |
|
|
|
for (c = cmds; c != NULL; c = c->c_next) { |
(void) sendcmd(C_FERRMSG, NULL); |
if (dhosts != NULL && *dhosts != NULL) { |
(void) close(rem_w); |
for (cpp = dhosts; *cpp; cpp++) |
(void) close(rem_r); /* This can't hurt */ |
if (strcmp(c->c_name, *cpp) == 0) |
rem_w = -1; |
goto fndhost; |
rem_r = -1; |
continue; |
|
} |
|
fndhost: |
|
if (argc) { |
|
for (cpp = argv; *cpp; cpp++) { |
|
if (c->c_label != NULL && |
|
strcmp(c->c_label, *cpp) == 0) { |
|
cpp = NULL; |
|
goto found; |
|
} |
|
for (f = c->c_files; f != NULL; f = f->n_next) |
|
if (strcmp(f->n_name, *cpp) == 0) |
|
goto found; |
|
} |
|
continue; |
|
} else |
|
cpp = NULL; |
|
found: |
|
switch (c->c_type) { |
|
case ARROW: |
|
doarrow(cpp, c->c_files, c->c_name, c->c_cmds); |
|
break; |
|
case DCOLON: |
|
dodcolon(cpp, c->c_files, c->c_name, c->c_cmds); |
|
break; |
|
default: |
|
fatal("illegal command type %d\n", c->c_type); |
|
} |
|
} |
} |
closeconn(); |
|
} |
} |
|
|
/* |
/* |
* Process commands for sending files to other machines. |
* 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 |
static void notify(rhost, to, lmod) |
doarrow(filev, files, rhost, cmds) |
|
char **filev; |
|
struct namelist *files; |
|
char *rhost; |
char *rhost; |
struct subcmd *cmds; |
register struct namelist *to; |
|
time_t lmod; |
{ |
{ |
register struct namelist *f; |
register int fd, len; |
register struct subcmd *sc; |
FILE *pf, *popen(); |
register char **cpp; |
struct stat stb; |
int n, ddir, opts = options; |
static char buf[BUFSIZ]; |
|
char *file; |
|
|
if (debug) |
if (IS_ON(options, DO_VERIFY) || to == NULL) |
printf("doarrow(%x, %s, %x)\n", files, rhost, cmds); |
return; |
|
|
if (files == NULL) { |
if ((file = getnotifyfile()) == NULL) |
error("no files to be updated\n"); |
|
return; |
return; |
|
|
|
if (!IS_ON(options, DO_QUIET)) { |
|
message(MT_INFO, "notify %s%s %s", |
|
(rhost) ? "@" : "", |
|
(rhost) ? rhost : "", getnlstr(to)); |
} |
} |
|
|
subcmds = cmds; |
|
ddir = files->n_next != NULL; /* destination is a directory */ |
|
if (nflag) |
if (nflag) |
printf("updating host %s\n", rhost); |
return; |
else { |
|
if (setjmp(env)) |
debugmsg(DM_MISC, "notify() temp file = '%s'", file); |
goto done; |
|
signal(SIGPIPE, lostconn); |
if ((fd = open(file, O_RDONLY)) < 0) { |
if (!makeconn(rhost)) |
error("%s: open for reading failed: %s", file, SYSERR); |
return; |
return; |
if ((lfp = fopen(tempfile, "w")) == NULL) { |
|
fatal("cannot open %s\n", tempfile); |
|
exit(1); |
|
} |
|
} |
} |
for (f = files; f != NULL; f = f->n_next) { |
if (fstat(fd, &stb) < 0) { |
if (filev) { |
error("%s: fstat failed: %s", file, SYSERR); |
for (cpp = filev; *cpp; cpp++) |
(void) close(fd); |
if (strcmp(f->n_name, *cpp) == 0) |
return; |
goto found; |
|
if (!nflag) |
|
(void) fclose(lfp); |
|
continue; |
|
} |
|
found: |
|
n = 0; |
|
for (sc = cmds; sc != NULL; sc = sc->sc_next) { |
|
if (sc->sc_type != INSTALL) |
|
continue; |
|
n++; |
|
install(f->n_name, sc->sc_name, |
|
sc->sc_name == NULL ? 0 : ddir, sc->sc_options); |
|
opts = sc->sc_options; |
|
} |
|
if (n == 0) |
|
install(f->n_name, NULL, 0, options); |
|
} |
} |
done: |
if (stb.st_size == 0) { |
if (!nflag) { |
(void) close(fd); |
(void) signal(SIGPIPE, cleanup); |
return; |
(void) fclose(lfp); |
|
lfp = NULL; |
|
} |
} |
for (sc = cmds; sc != NULL; sc = sc->sc_next) |
/* |
if (sc->sc_type == NOTIFY) |
* Create a pipe to mailling program. |
notify(tempfile, rhost, sc->sc_args, 0); |
* Set IFS to avoid possible security problem with users |
if (!nflag) { |
* setting "IFS=/". |
(void) unlink(tempfile); |
*/ |
for (; ihead != NULL; ihead = ihead->nextp) { |
(void) sprintf(buf, "IFS=\" \t\"; export IFS; %s -oi -t", |
free(ihead); |
_PATH_SENDMAIL); |
if ((opts & IGNLNKS) || ihead->count == 0) |
pf = popen(buf, "w"); |
continue; |
if (pf == NULL) { |
log(lfp, "%s: Warning: missing links\n", |
error("notify: \"%s\" failed\n", _PATH_SENDMAIL); |
ihead->pathname); |
(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. |
* Create a connection to the rdist server on the machine rhost. |
|
* Return 0 if the connection fails or 1 if it succeeds. |
*/ |
*/ |
static int |
static int makeconn(rhost) |
makeconn(rhost) |
|
char *rhost; |
char *rhost; |
{ |
{ |
register char *ruser, *cp; |
register char *ruser, *cp; |
static char *cur_host = NULL; |
static char *cur_host = NULL; |
static int port = -1; |
extern char *locuser; |
char tuser[20]; |
extern long min_freefiles, min_freespace; |
|
extern char *remotemsglist; |
|
char tuser[BUFSIZ], buf[BUFSIZ]; |
|
u_char respbuff[BUFSIZ]; |
int n; |
int n; |
extern char user[]; |
|
extern int userid; |
|
|
|
if (debug) |
debugmsg(DM_CALL, "makeconn(%s)", rhost); |
printf("makeconn(%s)\n", rhost); |
|
|
|
if (cur_host != NULL && rem >= 0) { |
/* |
|
* See if we're already connected to this host |
|
*/ |
|
if (cur_host != NULL && rem_w >= 0) { |
if (strcmp(cur_host, rhost) == 0) |
if (strcmp(cur_host, rhost) == 0) |
return(1); |
return(1); |
closeconn(); |
closeconn(); |
} |
} |
|
|
|
/* |
|
* Determine remote user and current host names |
|
*/ |
cur_host = rhost; |
cur_host = rhost; |
cp = index(rhost, '@'); |
cp = strchr(rhost, '@'); |
|
|
if (cp != NULL) { |
if (cp != NULL) { |
char c = *cp; |
char c = *cp; |
|
|
*cp = '\0'; |
*cp = CNULL; |
strncpy(tuser, rhost, sizeof(tuser)-1); |
(void) strncpy((char *)tuser, rhost, sizeof(tuser)-1); |
*cp = c; |
*cp = c; |
rhost = cp + 1; |
rhost = cp + 1; |
ruser = tuser; |
ruser = tuser; |
if (*ruser == '\0') |
if (*ruser == CNULL) |
ruser = user; |
ruser = locuser; |
else if (!okname(ruser)) |
else if (!okname(ruser)) |
return(0); |
return(0); |
} else |
} else |
ruser = user; |
ruser = locuser; |
if (!qflag) |
|
printf("updating host %s\n", rhost); |
|
(void) sprintf(buf, "%s -Server%s", _PATH_RDIST, qflag ? " -q" : ""); |
|
if (port < 0) { |
|
struct servent *sp; |
|
|
|
if ((sp = getservbyname("shell", "tcp")) == NULL) |
if (!IS_ON(options, DO_QUIET)) |
fatal("shell/tcp: unknown service"); |
message(MT_VERBOSE, "updating host %s", rhost); |
port = sp->s_port; |
|
} |
|
|
|
if (debug) { |
(void) sprintf(buf, "%.*s -S", sizeof(buf)-5, path_rdistd); |
printf("port = %d, luser = %s, ruser = %s\n", ntohs(port), user, ruser); |
|
printf("buf = %s\n", buf); |
if ((rem_r = rem_w = remotecmd(rhost, locuser, ruser, buf)) < 0) |
} |
return(0); |
|
|
fflush(stdout); |
/* |
seteuid(0); |
* First thing received should be S_VERSION |
rem = rcmd(&rhost, port, user, ruser, buf, 0); |
*/ |
seteuid(userid); |
n = remline(respbuff, sizeof(respbuff), TRUE); |
if (rem < 0) |
if (n <= 0 || respbuff[0] != S_VERSION) { |
|
error("Unexpected input from server: \"%s\".", respbuff); |
|
closeconn(); |
return(0); |
return(0); |
cp = buf; |
} |
if (read(rem, cp, 1) != 1) |
|
lostconn(0); |
/* |
if (*cp == 'V') { |
* For future compatibility we check to see if the server |
do { |
* sent it's version number to us. If it did, we use it, |
if (read(rem, cp, 1) != 1) |
* otherwise, we send our version number to the server and let |
lostconn(0); |
* it decide if it can handle our protocol version. |
} while (*cp++ != '\n' && cp < &buf[BUFSIZ]); |
*/ |
*--cp = '\0'; |
if (respbuff[1] == CNULL) { |
cp = buf; |
/* |
n = 0; |
* The server wants us to send it our version number |
while (*cp >= '0' && *cp <= '9') |
*/ |
n = (n * 10) + (*cp++ - '0'); |
(void) sendcmd(S_VERSION, "%d", VERSION); |
if (*cp == '\0' && n == VERSION) |
if (response() < 0) |
return(1); |
return(0); |
error("connection failed: version numbers don't match (local %d, remote %d)\n", VERSION, n); |
|
} else { |
} else { |
error("connection failed: version numbers don't match\n"); |
/* |
error("got unexpected input:"); |
* The server sent it's version number to us |
do { |
*/ |
error("%c", *cp); |
proto_version = atoi(&respbuff[1]); |
} while (*cp != '\n' && read(rem, cp, 1) == 1); |
if (proto_version != VERSION) { |
|
fatalerr( |
|
"Server version (%d) is not the same as local version (%d).", |
|
proto_version, VERSION); |
|
return(0); |
|
} |
} |
} |
closeconn(); |
|
return(0); |
|
} |
|
|
|
/* |
/* |
* Signal end of previous connection. |
* Send config commands |
*/ |
*/ |
static void |
if (host[0]) { |
closeconn() |
(void) sendcmd(C_SETCONFIG, "%c%s", SC_HOSTNAME, host); |
{ |
if (response() < 0) |
if (debug) |
return(0); |
printf("closeconn()\n"); |
|
|
|
if (rem >= 0) { |
|
(void) write(rem, "\2\n", 2); |
|
(void) close(rem); |
|
rem = -1; |
|
} |
} |
} |
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); |
|
} |
|
|
void |
|
lostconn(signo) |
|
int signo; |
|
{ |
|
if (iamremote) |
|
cleanup(0); |
|
log(lfp, "rdist: lost connection\n"); |
|
longjmp(env, 1); |
|
} |
|
|
|
static int |
|
okname(name) |
|
register char *name; |
|
{ |
|
register char *cp = name; |
|
register int c; |
|
|
|
do { |
|
c = *cp; |
|
if (c & 0200) |
|
goto bad; |
|
if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-') |
|
goto bad; |
|
cp++; |
|
} while (*cp); |
|
return(1); |
return(1); |
bad: |
|
error("invalid user name %s\n", name); |
|
return(0); |
|
} |
} |
|
|
time_t lastmod; |
|
FILE *tfp; |
|
extern char target[], *tp; |
|
|
|
/* |
/* |
* Process commands for comparing files to time stamp files. |
* Process commands for sending files to other machines. |
*/ |
*/ |
static void |
static void doarrow(cmd, filev) |
dodcolon(filev, files, stamp, cmds) |
struct cmd *cmd; |
char **filev; |
char **filev; |
struct namelist *files; |
|
char *stamp; |
|
struct subcmd *cmds; |
|
{ |
{ |
register struct subcmd *sc; |
|
register struct namelist *f; |
register struct namelist *f; |
|
register struct subcmd *sc; |
register char **cpp; |
register char **cpp; |
struct timeval tv[2]; |
int n, ddir, destdir, opts = options; |
struct timezone tz; |
struct namelist *files; |
struct stat stb; |
struct subcmd *sbcmds; |
|
char *rhost; |
|
int didupdate = 0; |
|
|
if (debug) |
if (!cmd) { |
printf("dodcolon()\n"); |
debugmsg(DM_MISC, "doarrow() NULL cmd parameter"); |
|
return; |
|
} |
|
|
|
files = cmd->c_files; |
|
sbcmds = cmd->c_cmds; |
|
rhost = cmd->c_name; |
|
|
if (files == NULL) { |
if (files == NULL) { |
error("no files to be updated\n"); |
error("No files to be updated on %s for target \"%s\"", |
|
rhost, cmd->c_label); |
return; |
return; |
} |
} |
if (stat(stamp, &stb) < 0) { |
|
error("%s: %s\n", stamp, strerror(errno)); |
|
return; |
|
} |
|
if (debug) |
|
printf("%s: %ld\n", stamp, stb.st_mtime); |
|
|
|
subcmds = cmds; |
debugmsg(DM_CALL, "doarrow(%x, %s, %x) start", |
lastmod = stb.st_mtime; |
files, A(rhost), sbcmds); |
if (nflag || (options & VERIFY)) |
|
tfp = NULL; |
if (nflag) |
|
(void) printf("updating host %s\n", rhost); |
else { |
else { |
if ((tfp = fopen(tempfile, "w")) == NULL) { |
if (cmd->c_flags & CMD_CONNFAILED) { |
error("%s: %s\n", stamp, strerror(errno)); |
debugmsg(DM_MISC, |
|
"makeconn %s failed before; skipping\n", |
|
rhost); |
return; |
return; |
} |
} |
(void) gettimeofday(&tv[0], &tz); |
|
tv[1] = tv[0]; |
if (setjmp(finish_jmpbuf)) { |
(void) utimes(stamp, tv); |
debugmsg(DM_MISC, "setjmp to finish_jmpbuf"); |
|
markfailed(cmd, cmds); |
|
return; |
|
} |
|
|
|
if (!makeconn(rhost)) { |
|
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) { |
for (f = files; f != NULL; f = f->n_next) { |
if (filev) { |
if (filev) { |
for (cpp = filev; *cpp; cpp++) |
for (cpp = filev; *cpp; cpp++) |
|
|
continue; |
continue; |
} |
} |
found: |
found: |
tp = NULL; |
if (install(f->n_name, sc->sc_name, ddir, destdir, |
cmptime(f->n_name); |
sc->sc_options) > 0) |
|
++didupdate; |
|
opts = sc->sc_options; |
} |
} |
|
|
if (tfp != NULL) |
} /* end loop for each INSTALL command */ |
(void) fclose(tfp); |
|
for (sc = cmds; sc != NULL; sc = sc->sc_next) |
|
if (sc->sc_type == NOTIFY) |
|
notify(tempfile, NULL, sc->sc_args, lastmod); |
|
if (!nflag && !(options & VERIFY)) |
|
(void) unlink(tempfile); |
|
} |
|
|
|
/* |
/* if no INSTALL commands present, do default install */ |
* Compare the mtime of file to the list of time stamps. |
if (!n) { |
*/ |
for (f = files; f != NULL; f = f->n_next) { |
static void |
if (filev) { |
cmptime(name) |
for (cpp = filev; *cpp; cpp++) |
char *name; |
if (strcmp(f->n_name, *cpp) == 0) |
{ |
goto found2; |
struct stat stb; |
continue; |
|
} |
if (debug) |
found2: |
printf("cmptime(%s)\n", name); |
/* ddir & destdir set to zero for default install */ |
|
if (install(f->n_name, NULL, 0, 0, options) > 0) |
if (except(name)) |
++didupdate; |
return; |
} |
|
|
if (nflag) { |
|
printf("comparing dates: %s\n", name); |
|
return; |
|
} |
} |
|
|
|
done: |
/* |
/* |
* first time cmptime() is called? |
* Run any commands for the entire cmd |
*/ |
*/ |
if (tp == NULL) { |
if (didupdate > 0) { |
if (exptilde(target, name) == NULL) |
runcmdspecial(cmd, filev, opts); |
return; |
didupdate = 0; |
tp = name = target; |
|
while (*tp) |
|
tp++; |
|
} |
} |
if (access(name, 4) < 0 || stat(name, &stb) < 0) { |
|
error("%s: %s\n", name, strerror(errno)); |
|
return; |
|
} |
|
|
|
switch (stb.st_mode & S_IFMT) { |
if (!nflag) |
case S_IFREG: |
(void) signal(SIGPIPE, cleanup); |
break; |
|
|
|
case S_IFDIR: |
for (sc = sbcmds; sc != NULL; sc = sc->sc_next) |
rcmptime(&stb); |
if (sc->sc_type == NOTIFY) |
return; |
notify(rhost, sc->sc_args, (time_t) 0); |
|
|
default: |
if (!nflag) { |
error("%s: not a plain file\n", name); |
register struct linkbuf *nextl, *l; |
return; |
|
|
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; |
} |
} |
|
} |
|
|
if (stb.st_mtime > lastmod) |
okname(name) |
log(tfp, "new: %s\n", 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 |
static void rcmptime(st, sbcmds, env) |
rcmptime(st) |
|
struct stat *st; |
struct stat *st; |
|
struct subcmd *sbcmds; |
|
char **env; |
{ |
{ |
register DIR *d; |
register DIR *d; |
register struct direct *dp; |
register DIRENTRY *dp; |
register char *cp; |
register char *cp; |
char *otp; |
char *optarget; |
int len; |
int len; |
|
|
if (debug) |
debugmsg(DM_CALL, "rcmptime(%x) start", st); |
printf("rcmptime(%x)\n", st); |
|
|
|
if ((d = opendir(target)) == NULL) { |
if ((d = opendir((char *) target)) == NULL) { |
error("%s: %s\n", target, strerror(errno)); |
error("%s: open directory failed: %s", target, SYSERR); |
return; |
return; |
} |
} |
otp = tp; |
optarget = ptarget; |
len = tp - target; |
len = ptarget - target; |
while (dp = readdir(d)) { |
while (dp = readdir(d)) { |
if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) |
if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) |
continue; |
continue; |
if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { |
if (len + 1 + (int)strlen(dp->d_name) >= BUFSIZ - 1) { |
error("%s/%s: Name too long\n", target, dp->d_name); |
error("%s/%s: Name too long\n", target, dp->d_name); |
continue; |
continue; |
} |
} |
tp = otp; |
ptarget = optarget; |
*tp++ = '/'; |
*ptarget++ = '/'; |
cp = dp->d_name; |
cp = dp->d_name; |
while (*tp++ = *cp++) |
while (*ptarget++ = *cp++) |
; |
; |
tp--; |
ptarget--; |
cmptime(target); |
cmptime(target, sbcmds, env); |
} |
} |
closedir(d); |
(void) closedir((DIR *) d); |
tp = otp; |
ptarget = optarget; |
*tp = '\0'; |
*ptarget = '\0'; |
} |
} |
|
|
/* |
/* |
* Notify the list of people the changes that were made. |
* Compare the mtime of file to the list of time stamps. |
* rhost == NULL if we are mailing a list of changes compared to at time |
|
* stamp file. |
|
*/ |
*/ |
static void |
static void cmptime(name, sbcmds, env) |
notify(file, rhost, to, lmod) |
char *name; |
char *file, *rhost; |
struct subcmd *sbcmds; |
register struct namelist *to; |
char **env; |
time_t lmod; |
|
{ |
{ |
register int fd, len; |
struct subcmd *sc; |
struct stat stb; |
struct stat stb; |
FILE *pf; |
int inlist(); |
|
|
if ((options & VERIFY) || to == NULL) |
debugmsg(DM_CALL, "cmptime(%s)", name); |
|
|
|
if (except(name)) |
return; |
return; |
if (!qflag) { |
|
printf("notify "); |
|
if (rhost) |
|
printf("@%s ", rhost); |
|
prnames(to); |
|
} |
|
if (nflag) |
|
return; |
|
|
|
if ((fd = open(file, 0)) < 0) { |
if (nflag) { |
error("%s: %s\n", file, strerror(errno)); |
(void) printf("comparing dates: %s\n", name); |
return; |
return; |
} |
} |
if (fstat(fd, &stb) < 0) { |
|
error("%s: %s\n", file, strerror(errno)); |
/* |
(void) close(fd); |
* first time cmptime() is called? |
return; |
*/ |
|
if (ptarget == NULL) { |
|
if (exptilde(target, name) == NULL) |
|
return; |
|
ptarget = name = target; |
|
while (*ptarget) |
|
ptarget++; |
} |
} |
if (stb.st_size == 0) { |
if (access(name, R_OK) < 0 || stat(name, &stb) < 0) { |
(void) close(fd); |
error("%s: cannot access file: %s", name, SYSERR); |
return; |
return; |
} |
} |
/* |
|
* Create a pipe to mailling program. |
if (S_ISDIR(stb.st_mode)) { |
*/ |
rcmptime(&stb, sbcmds, env); |
(void)sprintf(buf, "%s -oi -t", _PATH_SENDMAIL); |
|
pf = popen(buf, "w"); |
|
if (pf == NULL) { |
|
error("notify: \"%s\" failed\n", _PATH_SENDMAIL); |
|
(void) close(fd); |
|
return; |
return; |
|
} else if (!S_ISREG(stb.st_mode)) { |
|
error("%s: not a plain file", name); |
|
return; |
} |
} |
/* |
|
* Output the proper header information. |
|
*/ |
|
fprintf(pf, "From: rdist (Remote distribution program)\n"); |
|
fprintf(pf, "To:"); |
|
if (!any('@', to->n_name) && rhost != NULL) |
|
fprintf(pf, " %s@%s", to->n_name, rhost); |
|
else |
|
fprintf(pf, " %s", to->n_name); |
|
to = to->n_next; |
|
while (to != NULL) { |
|
if (!any('@', to->n_name) && rhost != NULL) |
|
fprintf(pf, ", %s@%s", to->n_name, rhost); |
|
else |
|
fprintf(pf, ", %s", to->n_name); |
|
to = to->n_next; |
|
} |
|
putc('\n', pf); |
|
if (rhost != NULL) |
|
fprintf(pf, "Subject: files updated by rdist from %s to %s\n", |
|
host, rhost); |
|
else |
|
fprintf(pf, "Subject: files updated after %s\n", ctime(&lmod)); |
|
putc('\n', pf); |
|
|
|
while ((len = read(fd, buf, BUFSIZ)) > 0) |
if (stb.st_mtime > lastmod) { |
(void) fwrite(buf, 1, len, pf); |
message(MT_INFO, "%s: file is newer", name); |
(void) close(fd); |
for (sc = sbcmds; sc != NULL; sc = sc->sc_next) { |
(void) pclose(pf); |
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); |
|
} |
|
} |
} |
} |
|
|
/* |
/* |
* Return true if name is in the list. |
* Process commands for comparing files to time stamp files. |
*/ |
*/ |
int |
static void dodcolon(cmd, filev) |
inlist(list, file) |
struct cmd *cmd; |
struct namelist *list; |
char **filev; |
char *file; |
|
{ |
{ |
register struct namelist *nl; |
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; |
|
|
for (nl = list; nl != NULL; nl = nl->n_next) |
debugmsg(DM_CALL, "dodcolon()"); |
if (!strcmp(file, nl->n_name)) |
|
return(1); |
if (files == NULL) { |
return(0); |
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. |
* Return TRUE if file is in the exception list. |
*/ |
*/ |
int |
extern int except(file) |
except(file) |
|
char *file; |
char *file; |
{ |
{ |
register struct subcmd *sc; |
register struct subcmd *sc; |
register struct namelist *nl; |
register struct namelist *nl; |
|
|
if (debug) |
debugmsg(DM_CALL, "except(%s)", file); |
printf("except(%s)\n", file); |
|
|
|
for (sc = subcmds; sc != NULL; sc = sc->sc_next) { |
for (sc = subcmds; sc != NULL; sc = sc->sc_next) { |
if (sc->sc_type != EXCEPT && sc->sc_type != PATTERN) |
if (sc->sc_type == EXCEPT) { |
continue; |
for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) |
for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) { |
if (strcmp(file, nl->n_name) == 0) |
if (sc->sc_type == EXCEPT) { |
|
if (!strcmp(file, nl->n_name)) |
|
return(1); |
return(1); |
continue; |
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); |
} |
} |
re_comp(nl->n_name); |
|
if (re_exec(file) > 0) |
|
return(1); |
|
} |
} |
} |
} |
return(0); |
return(0); |
} |
} |
|
|
char * |
/* |
colon(cp) |
* Do a specific command for a specific host |
register char *cp; |
*/ |
|
static void docmdhost(cmd, filev) |
|
struct cmd *cmd; |
|
char **filev; |
{ |
{ |
|
checkcmd(cmd); |
|
|
while (*cp) { |
/* |
if (*cp == ':') |
* If we're multi-threaded and we're the parent, spawn a |
return(cp); |
* new child process. |
if (*cp == '/') |
*/ |
return(0); |
if (do_fork && !amchild) { |
cp++; |
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; |
} |
} |
return(0); |
|
|
/* |
|
* 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(); |
|
} |
} |
} |