diff options
Diffstat (limited to 'usr.bin/rdist')
-rw-r--r-- | usr.bin/rdist/Makefile | 15 | ||||
-rw-r--r-- | usr.bin/rdist/cron.entry | 1 | ||||
-rw-r--r-- | usr.bin/rdist/defs.h | 181 | ||||
-rw-r--r-- | usr.bin/rdist/docmd.c | 630 | ||||
-rw-r--r-- | usr.bin/rdist/expand.c | 667 | ||||
-rw-r--r-- | usr.bin/rdist/gram.y | 509 | ||||
-rw-r--r-- | usr.bin/rdist/lookup.c | 167 | ||||
-rw-r--r-- | usr.bin/rdist/main.c | 328 | ||||
-rw-r--r-- | usr.bin/rdist/pathnames.h | 39 | ||||
-rw-r--r-- | usr.bin/rdist/rdist.1 | 413 | ||||
-rw-r--r-- | usr.bin/rdist/server.c | 1586 |
11 files changed, 4536 insertions, 0 deletions
diff --git a/usr.bin/rdist/Makefile b/usr.bin/rdist/Makefile new file mode 100644 index 00000000000..f3f5de8532f --- /dev/null +++ b/usr.bin/rdist/Makefile @@ -0,0 +1,15 @@ +# from: @(#)Makefile 5.11 (Berkeley) 3/12/91 +# $Id: Makefile,v 1.1 1995/10/18 08:45:58 deraadt Exp $ + +PROG= rdist +CFLAGS+=-I${.CURDIR} +SRCS= docmd.c expand.c lookup.c main.c server.c +OBJS+= gram.o +BINOWN= root +BINMODE=4555 +CLEANFILES=y.tab.h + +LDADD= -lcompat +DPADD= ${LIBCOMPAT} + +.include <bsd.prog.mk> diff --git a/usr.bin/rdist/cron.entry b/usr.bin/rdist/cron.entry new file mode 100644 index 00000000000..90968cf3faa --- /dev/null +++ b/usr.bin/rdist/cron.entry @@ -0,0 +1 @@ +30 3 * * * /usr/bin/rdist -f /etc/Distfile >& /var/log/rdist/rdist.err 2>&1 diff --git a/usr.bin/rdist/defs.h b/usr.bin/rdist/defs.h new file mode 100644 index 00000000000..731f43427b4 --- /dev/null +++ b/usr.bin/rdist/defs.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)defs.h 8.1 (Berkeley) 6/9/93 + * $Id: defs.h,v 1.1 1995/10/18 08:45:58 deraadt Exp $ + */ + +#include <sys/param.h> +#include <sys/dir.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/file.h> + +#include <netinet/in.h> + +#include <errno.h> +#include <pwd.h> +#include <grp.h> +#include <stdio.h> +#include <ctype.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include "pathnames.h" + +/* + * The version number should be changed whenever the protocol changes. + */ +#define VERSION 3 + + /* defines for yacc */ +#define EQUAL 1 +#define LP 2 +#define RP 3 +#define SM 4 +#define ARROW 5 +#define COLON 6 +#define DCOLON 7 +#define NAME 8 +#define STRING 9 +#define INSTALL 10 +#define NOTIFY 11 +#define EXCEPT 12 +#define PATTERN 13 +#define SPECIAL 14 +#define OPTION 15 + + /* lexical definitions */ +#define QUOTE 0200 /* used internally for quoted characters */ +#define TRIM 0177 /* Mask to strip quote bit */ + + /* table sizes */ +#define HASHSIZE 1021 +#define INMAX 3500 + + /* option flags */ +#define VERIFY 0x1 +#define WHOLE 0x2 +#define YOUNGER 0x4 +#define COMPARE 0x8 +#define REMOVE 0x10 +#define FOLLOW 0x20 +#define IGNLNKS 0x40 + + /* expand type definitions */ +#define E_VARS 0x1 +#define E_SHELL 0x2 +#define E_TILDE 0x4 +#define E_ALL 0x7 + + /* actions for lookup() */ +#define LOOKUP 0 +#define INSERT 1 +#define REPLACE 2 + +#define ISDIR(m) (((m) & S_IFMT) == S_IFDIR) + +#define ALLOC(x) (struct x *) malloc(sizeof(struct x)) + +struct namelist { /* for making lists of strings */ + char *n_name; + struct namelist *n_next; +}; + +struct subcmd { + short sc_type; /* type - INSTALL,NOTIFY,EXCEPT,SPECIAL */ + short sc_options; + char *sc_name; + struct namelist *sc_args; + struct subcmd *sc_next; +}; + +struct cmd { + int c_type; /* type - ARROW,DCOLON */ + char *c_name; /* hostname or time stamp file name */ + char *c_label; /* label for partial update */ + struct namelist *c_files; + struct subcmd *c_cmds; + struct cmd *c_next; +}; + +struct linkbuf { + ino_t inum; + dev_t devnum; + int count; + char pathname[BUFSIZ]; + char target[BUFSIZ]; + struct linkbuf *nextp; +}; + +extern int debug; /* debugging flag */ +extern int nflag; /* NOP flag, don't execute commands */ +extern int qflag; /* Quiet. don't print messages */ +extern int options; /* global options */ + +extern int nerrs; /* number of errors seen */ +extern int rem; /* remote file descriptor */ +extern int iamremote; /* acting as remote server */ +extern char tempfile[]; /* file name for logging changes */ +extern struct linkbuf *ihead; /* list of files with more than one link */ +extern struct passwd *pw; /* pointer to static area used by getpwent */ +extern struct group *gr; /* pointer to static area used by getgrent */ +extern char host[]; /* host name of master copy */ +extern char buf[]; /* general purpose buffer */ + +int any __P((int, char *)); +char *colon __P((char *)); +void cleanup __P((int)); +void define __P((char *)); +void docmds __P((char **, int, char **)); +void error __P((const char *, ...)); +int except __P((char *)); +struct namelist * + expand __P((struct namelist *, int)); +char *exptilde __P((char [], char *)); +void fatal __P((const char *, ...)); +int inlist __P((struct namelist *, char *)); +void insert __P((char *, + struct namelist *, struct namelist *, struct subcmd *)); +void install __P((char *, char *, int, int)); +void log __P((FILE *, const char *, ...)); +struct namelist * + lookup __P((char *, int, struct namelist *)); +void lostconn __P((int)); +struct namelist * + makenl __P((char *)); +struct subcmd * + makesubcmd __P((int)); +void prnames __P((struct namelist *)); +void server __P((void)); +void yyerror __P((char *)); +int yyparse __P((void)); diff --git a/usr.bin/rdist/docmd.c b/usr.bin/rdist/docmd.c new file mode 100644 index 00000000000..4f71389e5ad --- /dev/null +++ b/usr.bin/rdist/docmd.c @@ -0,0 +1,630 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +/* from: static char sccsid[] = "@(#)docmd.c 8.1 (Berkeley) 6/9/93"; */ +static char *rcsid = "$Id: docmd.c,v 1.1 1995/10/18 08:45:58 deraadt Exp $"; +#endif /* not lint */ + +#include "defs.h" +#include <setjmp.h> +#include <netdb.h> + +FILE *lfp; /* log file for recording files updated */ +struct subcmd *subcmds; /* list of sub-commands for current cmd */ +jmp_buf env; + +static int makeconn __P((char *)); +static int okname __P((char *)); +static void closeconn __P((void)); +static void cmptime __P((char *)); +static void doarrow __P((char **, + struct namelist *, char *, struct subcmd *)); +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 *)); + +/* + * Do the commands in cmds (initialized by yyparse). + */ +void +docmds(dhosts, argc, argv) + char **dhosts; + int argc; + char **argv; +{ + register struct cmd *c; + register struct namelist *f; + register char **cpp; + extern struct cmd *cmds; + + signal(SIGHUP, cleanup); + signal(SIGINT, cleanup); + signal(SIGQUIT, cleanup); + signal(SIGTERM, cleanup); + + for (c = cmds; c != NULL; c = c->c_next) { + if (dhosts != NULL && *dhosts != NULL) { + for (cpp = dhosts; *cpp; cpp++) + if (strcmp(c->c_name, *cpp) == 0) + goto fndhost; + 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. + */ +static void +doarrow(filev, files, rhost, cmds) + char **filev; + struct namelist *files; + char *rhost; + struct subcmd *cmds; +{ + register struct namelist *f; + register struct subcmd *sc; + register char **cpp; + int n, ddir, opts = options; + + if (debug) + printf("doarrow(%x, %s, %x)\n", files, rhost, cmds); + + if (files == NULL) { + error("no files to be updated\n"); + return; + } + + subcmds = cmds; + ddir = files->n_next != NULL; /* destination is a directory */ + if (nflag) + printf("updating host %s\n", rhost); + else { + if (setjmp(env)) + goto done; + signal(SIGPIPE, lostconn); + if (!makeconn(rhost)) + 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 (filev) { + for (cpp = filev; *cpp; cpp++) + if (strcmp(f->n_name, *cpp) == 0) + 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 (!nflag) { + (void) signal(SIGPIPE, cleanup); + (void) fclose(lfp); + lfp = NULL; + } + for (sc = cmds; sc != NULL; sc = sc->sc_next) + if (sc->sc_type == NOTIFY) + notify(tempfile, rhost, sc->sc_args, 0); + if (!nflag) { + (void) unlink(tempfile); + for (; ihead != NULL; ihead = ihead->nextp) { + free(ihead); + if ((opts & IGNLNKS) || ihead->count == 0) + continue; + log(lfp, "%s: Warning: missing links\n", + ihead->pathname); + } + } +} + +/* + * Create a connection to the rdist server on the machine rhost. + */ +static int +makeconn(rhost) + char *rhost; +{ + register char *ruser, *cp; + static char *cur_host = NULL; + static int port = -1; + char tuser[20]; + int n; + extern char user[]; + extern int userid; + + if (debug) + printf("makeconn(%s)\n", rhost); + + if (cur_host != NULL && rem >= 0) { + if (strcmp(cur_host, rhost) == 0) + return(1); + closeconn(); + } + cur_host = rhost; + cp = index(rhost, '@'); + if (cp != NULL) { + char c = *cp; + + *cp = '\0'; + strncpy(tuser, rhost, sizeof(tuser)-1); + *cp = c; + rhost = cp + 1; + ruser = tuser; + if (*ruser == '\0') + ruser = user; + else if (!okname(ruser)) + return(0); + } else + ruser = user; + 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) + fatal("shell/tcp: unknown service"); + port = sp->s_port; + } + + if (debug) { + printf("port = %d, luser = %s, ruser = %s\n", ntohs(port), user, ruser); + printf("buf = %s\n", buf); + } + + fflush(stdout); + seteuid(0); + rem = rcmd(&rhost, port, user, ruser, buf, 0); + seteuid(userid); + if (rem < 0) + return(0); + cp = buf; + if (read(rem, cp, 1) != 1) + lostconn(0); + if (*cp == 'V') { + do { + if (read(rem, cp, 1) != 1) + lostconn(0); + } while (*cp++ != '\n' && cp < &buf[BUFSIZ]); + *--cp = '\0'; + cp = buf; + n = 0; + while (*cp >= '0' && *cp <= '9') + n = (n * 10) + (*cp++ - '0'); + if (*cp == '\0' && n == VERSION) + return(1); + error("connection failed: version numbers don't match (local %d, remote %d)\n", VERSION, n); + } else { + error("connection failed: version numbers don't match\n"); + error("got unexpected input:"); + do { + error("%c", *cp); + } while (*cp != '\n' && read(rem, cp, 1) == 1); + } + closeconn(); + return(0); +} + +/* + * Signal end of previous connection. + */ +static void +closeconn() +{ + if (debug) + printf("closeconn()\n"); + + if (rem >= 0) { + (void) write(rem, "\2\n", 2); + (void) close(rem); + rem = -1; + } +} + +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); +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. + */ +static void +dodcolon(filev, files, stamp, cmds) + char **filev; + struct namelist *files; + char *stamp; + struct subcmd *cmds; +{ + register struct subcmd *sc; + register struct namelist *f; + register char **cpp; + struct timeval tv[2]; + struct timezone tz; + struct stat stb; + + if (debug) + printf("dodcolon()\n"); + + if (files == NULL) { + error("no files to be updated\n"); + 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; + lastmod = stb.st_mtime; + if (nflag || (options & VERIFY)) + tfp = NULL; + else { + if ((tfp = fopen(tempfile, "w")) == NULL) { + error("%s: %s\n", stamp, strerror(errno)); + return; + } + (void) gettimeofday(&tv[0], &tz); + tv[1] = tv[0]; + (void) utimes(stamp, tv); + } + + 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: + tp = NULL; + cmptime(f->n_name); + } + + if (tfp != NULL) + (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); +} + +/* + * Compare the mtime of file to the list of time stamps. + */ +static void +cmptime(name) + char *name; +{ + struct stat stb; + + if (debug) + printf("cmptime(%s)\n", name); + + if (except(name)) + return; + + if (nflag) { + printf("comparing dates: %s\n", name); + return; + } + + /* + * first time cmptime() is called? + */ + if (tp == NULL) { + if (exptilde(target, name) == NULL) + return; + 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) { + case S_IFREG: + break; + + case S_IFDIR: + rcmptime(&stb); + return; + + default: + error("%s: not a plain file\n", name); + return; + } + + if (stb.st_mtime > lastmod) + log(tfp, "new: %s\n", name); +} + +static void +rcmptime(st) + struct stat *st; +{ + register DIR *d; + register struct direct *dp; + register char *cp; + char *otp; + int len; + + if (debug) + printf("rcmptime(%x)\n", st); + + if ((d = opendir(target)) == NULL) { + error("%s: %s\n", target, strerror(errno)); + return; + } + otp = tp; + len = tp - target; + while (dp = readdir(d)) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { + error("%s/%s: Name too long\n", target, dp->d_name); + continue; + } + tp = otp; + *tp++ = '/'; + cp = dp->d_name; + while (*tp++ = *cp++) + ; + tp--; + cmptime(target); + } + closedir(d); + tp = otp; + *tp = '\0'; +} + +/* + * 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(file, rhost, to, lmod) + char *file, *rhost; + register struct namelist *to; + time_t lmod; +{ + register int fd, len; + struct stat stb; + FILE *pf; + + if ((options & VERIFY) || to == NULL) + return; + if (!qflag) { + printf("notify "); + if (rhost) + printf("@%s ", rhost); + prnames(to); + } + if (nflag) + return; + + if ((fd = open(file, 0)) < 0) { + error("%s: %s\n", file, strerror(errno)); + return; + } + if (fstat(fd, &stb) < 0) { + error("%s: %s\n", file, strerror(errno)); + (void) close(fd); + return; + } + if (stb.st_size == 0) { + (void) close(fd); + return; + } + /* + * Create a pipe to mailling program. + */ + (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; + } + /* + * 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) + (void) fwrite(buf, 1, len, pf); + (void) close(fd); + (void) pclose(pf); +} + +/* + * Return true if name is in the list. + */ +int +inlist(list, file) + struct namelist *list; + char *file; +{ + register struct namelist *nl; + + for (nl = list; nl != NULL; nl = nl->n_next) + if (!strcmp(file, nl->n_name)) + return(1); + return(0); +} + +/* + * Return TRUE if file is in the exception list. + */ +int +except(file) + char *file; +{ + register struct subcmd *sc; + register struct namelist *nl; + + if (debug) + printf("except(%s)\n", file); + + for (sc = subcmds; sc != NULL; sc = sc->sc_next) { + if (sc->sc_type != EXCEPT && sc->sc_type != PATTERN) + continue; + for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) { + if (sc->sc_type == EXCEPT) { + if (!strcmp(file, nl->n_name)) + return(1); + continue; + } + re_comp(nl->n_name); + if (re_exec(file) > 0) + return(1); + } + } + return(0); +} + +char * +colon(cp) + register char *cp; +{ + + while (*cp) { + if (*cp == ':') + return(cp); + if (*cp == '/') + return(0); + cp++; + } + return(0); +} diff --git a/usr.bin/rdist/expand.c b/usr.bin/rdist/expand.c new file mode 100644 index 00000000000..6b917331e3c --- /dev/null +++ b/usr.bin/rdist/expand.c @@ -0,0 +1,667 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +/* from: static char sccsid[] = "@(#)expand.c 8.1 (Berkeley) 6/9/93"; */ +static char *rcsid = "$Id: expand.c,v 1.1 1995/10/18 08:45:59 deraadt Exp $"; +#endif /* not lint */ + +#include "defs.h" + +#define GAVSIZ NCARGS / 6 +#define LC '{' +#define RC '}' + +static char shchars[] = "${[*?"; + +int which; /* bit mask of types to expand */ +int eargc; /* expanded arg count */ +char **eargv; /* expanded arg vectors */ +char *path; +char *pathp; +char *lastpathp; +char *tilde; /* "~user" if not expanding tilde, else "" */ +char *tpathp; +int nleft; + +int expany; /* any expansions done? */ +char *entp; +char **sortbase; + +#define sort() qsort((char *)sortbase, &eargv[eargc] - sortbase, \ + sizeof(*sortbase), argcmp), sortbase = &eargv[eargc] + +static void Cat __P((char *, char *)); +static void addpath __P((int)); +static int amatch __P((char *, char *)); +static int argcmp __P((const void *, const void *)); +static int execbrc __P((char *, char *)); +static void expsh __P((char *)); +static void expstr __P((char *)); +static int match __P((char *, char *)); +static void matchdir __P((char *)); +static int smatch __P((char *, char *)); + +/* + * Take a list of names and expand any macros, etc. + * wh = E_VARS if expanding variables. + * wh = E_SHELL if expanding shell characters. + * wh = E_TILDE if expanding `~'. + * or any of these or'ed together. + * + * Major portions of this were snarfed from csh/sh.glob.c. + */ +struct namelist * +expand(list, wh) + struct namelist *list; + int wh; +{ + register struct namelist *nl, *prev; + register int n; + char pathbuf[BUFSIZ]; + char *argvbuf[GAVSIZ]; + + if (debug) { + printf("expand(%x, %d)\nlist = ", list, wh); + prnames(list); + } + + if (wh == 0) { + register char *cp; + + for (nl = list; nl != NULL; nl = nl->n_next) + for (cp = nl->n_name; *cp; cp++) + *cp = *cp & TRIM; + return(list); + } + + which = wh; + path = tpathp = pathp = pathbuf; + *pathp = '\0'; + lastpathp = &path[sizeof pathbuf - 2]; + tilde = ""; + eargc = 0; + eargv = sortbase = argvbuf; + *eargv = 0; + nleft = NCARGS - 4; + /* + * Walk the name list and expand names into eargv[]; + */ + for (nl = list; nl != NULL; nl = nl->n_next) + expstr(nl->n_name); + /* + * Take expanded list of names from eargv[] and build a new list. + */ + list = prev = NULL; + for (n = 0; n < eargc; n++) { + nl = makenl(NULL); + nl->n_name = eargv[n]; + if (prev == NULL) + list = prev = nl; + else { + prev->n_next = nl; + prev = nl; + } + } + if (debug) { + printf("expanded list = "); + prnames(list); + } + return(list); +} + +static void +expstr(s) + char *s; +{ + register char *cp, *cp1; + register struct namelist *tp; + char *tail; + char buf[BUFSIZ]; + int savec, oeargc; + extern char homedir[]; + + if (s == NULL || *s == '\0') + return; + + if ((which & E_VARS) && (cp = index(s, '$')) != NULL) { + *cp++ = '\0'; + if (*cp == '\0') { + yyerror("no variable name after '$'"); + return; + } + if (*cp == LC) { + cp++; + if ((tail = index(cp, RC)) == NULL) { + yyerror("unmatched '{'"); + return; + } + *tail++ = savec = '\0'; + if (*cp == '\0') { + yyerror("no variable name after '$'"); + return; + } + } else { + tail = cp + 1; + savec = *tail; + *tail = '\0'; + } + tp = lookup(cp, NULL, 0); + if (savec != '\0') + *tail = savec; + if (tp != NULL) { + for (; tp != NULL; tp = tp->n_next) { + sprintf(buf, "%s%s%s", s, tp->n_name, tail); + expstr(buf); + } + return; + } + sprintf(buf, "%s%s", s, tail); + expstr(buf); + return; + } + if ((which & ~E_VARS) == 0 || !strcmp(s, "{") || !strcmp(s, "{}")) { + Cat(s, ""); + sort(); + return; + } + if (*s == '~') { + cp = ++s; + if (*cp == '\0' || *cp == '/') { + tilde = "~"; + cp1 = homedir; + } else { + tilde = cp1 = buf; + *cp1++ = '~'; + do + *cp1++ = *cp++; + while (*cp && *cp != '/'); + *cp1 = '\0'; + if (pw == NULL || strcmp(pw->pw_name, buf+1) != 0) { + if ((pw = getpwnam(buf+1)) == NULL) { + strcat(buf, ": unknown user name"); + yyerror(buf+1); + return; + } + } + cp1 = pw->pw_dir; + s = cp; + } + for (cp = path; *cp++ = *cp1++; ) + ; + tpathp = pathp = cp - 1; + } else { + tpathp = pathp = path; + tilde = ""; + } + *pathp = '\0'; + if (!(which & E_SHELL)) { + if (which & E_TILDE) + Cat(path, s); + else + Cat(tilde, s); + sort(); + return; + } + oeargc = eargc; + expany = 0; + expsh(s); + if (eargc == oeargc) + Cat(s, ""); /* "nonomatch" is set */ + sort(); +} + +static int +argcmp(a1, a2) + const void *a1, *a2; +{ + + return (strcmp(*(char **)a1, *(char **)a2)); +} + +/* + * If there are any Shell meta characters in the name, + * expand into a list, after searching directory + */ +static void +expsh(s) + char *s; +{ + register char *cp; + register char *spathp, *oldcp; + struct stat stb; + + spathp = pathp; + cp = s; + while (!any(*cp, shchars)) { + if (*cp == '\0') { + if (!expany || stat(path, &stb) >= 0) { + if (which & E_TILDE) + Cat(path, ""); + else + Cat(tilde, tpathp); + } + goto endit; + } + addpath(*cp++); + } + oldcp = cp; + while (cp > s && *cp != '/') + cp--, pathp--; + if (*cp == '/') + cp++, pathp++; + *pathp = '\0'; + if (*oldcp == '{') { + execbrc(cp, NULL); + return; + } + matchdir(cp); +endit: + pathp = spathp; + *pathp = '\0'; +} + +static void +matchdir(pattern) + char *pattern; +{ + struct stat stb; + register struct direct *dp; + DIR *dirp; + + dirp = opendir(path); + if (dirp == NULL) { + if (expany) + return; + goto patherr2; + } + if (fstat(dirp->dd_fd, &stb) < 0) + goto patherr1; + if (!ISDIR(stb.st_mode)) { + errno = ENOTDIR; + goto patherr1; + } + while ((dp = readdir(dirp)) != NULL) + if (match(dp->d_name, pattern)) { + if (which & E_TILDE) + Cat(path, dp->d_name); + else { + strcpy(pathp, dp->d_name); + Cat(tilde, tpathp); + *pathp = '\0'; + } + } + closedir(dirp); + return; + +patherr1: + closedir(dirp); +patherr2: + strcat(path, ": "); + strcat(path, strerror(errno)); + yyerror(path); +} + +static int +execbrc(p, s) + char *p, *s; +{ + char restbuf[BUFSIZ + 2]; + register char *pe, *pm, *pl; + int brclev = 0; + char *lm, savec, *spathp; + + for (lm = restbuf; *p != '{'; *lm++ = *p++) + continue; + for (pe = ++p; *pe; pe++) + switch (*pe) { + + case '{': + brclev++; + continue; + + case '}': + if (brclev == 0) + goto pend; + brclev--; + continue; + + case '[': + for (pe++; *pe && *pe != ']'; pe++) + continue; + if (!*pe) + yyerror("Missing ']'"); + continue; + } +pend: + if (brclev || !*pe) { + yyerror("Missing '}'"); + return (0); + } + for (pl = pm = p; pm <= pe; pm++) + switch (*pm & (QUOTE|TRIM)) { + + case '{': + brclev++; + continue; + + case '}': + if (brclev) { + brclev--; + continue; + } + goto doit; + + case ',': + if (brclev) + continue; +doit: + savec = *pm; + *pm = 0; + strcpy(lm, pl); + strcat(restbuf, pe + 1); + *pm = savec; + if (s == 0) { + spathp = pathp; + expsh(restbuf); + pathp = spathp; + *pathp = 0; + } else if (amatch(s, restbuf)) + return (1); + sort(); + pl = pm + 1; + continue; + + case '[': + for (pm++; *pm && *pm != ']'; pm++) + continue; + if (!*pm) + yyerror("Missing ']'"); + continue; + } + return (0); +} + +static int +match(s, p) + char *s, *p; +{ + register int c; + register char *sentp; + char sexpany = expany; + + if (*s == '.' && *p != '.') + return (0); + sentp = entp; + entp = s; + c = amatch(s, p); + entp = sentp; + expany = sexpany; + return (c); +} + +static int +amatch(s, p) + register char *s, *p; +{ + register int scc; + int ok, lc; + char *spathp; + struct stat stb; + int c, cc; + + expany = 1; + for (;;) { + scc = *s++ & TRIM; + switch (c = *p++) { + + case '{': + return (execbrc(p - 1, s - 1)); + + case '[': + ok = 0; + lc = 077777; + while (cc = *p++) { + if (cc == ']') { + if (ok) + break; + return (0); + } + if (cc == '-') { + if (lc <= scc && scc <= *p++) + ok++; + } else + if (scc == (lc = cc)) + ok++; + } + if (cc == 0) { + yyerror("Missing ']'"); + return (0); + } + continue; + + case '*': + if (!*p) + return (1); + if (*p == '/') { + p++; + goto slash; + } + for (s--; *s; s++) + if (amatch(s, p)) + return (1); + return (0); + + case '\0': + return (scc == '\0'); + + default: + if ((c & TRIM) != scc) + return (0); + continue; + + case '?': + if (scc == '\0') + return (0); + continue; + + case '/': + if (scc) + return (0); +slash: + s = entp; + spathp = pathp; + while (*s) + addpath(*s++); + addpath('/'); + if (stat(path, &stb) == 0 && ISDIR(stb.st_mode)) + if (*p == '\0') { + if (which & E_TILDE) + Cat(path, ""); + else + Cat(tilde, tpathp); + } else + expsh(p); + pathp = spathp; + *pathp = '\0'; + return (0); + } + } +} + +static int +smatch(s, p) + register char *s, *p; +{ + register int scc; + int ok, lc; + int c, cc; + + for (;;) { + scc = *s++ & TRIM; + switch (c = *p++) { + + case '[': + ok = 0; + lc = 077777; + while (cc = *p++) { + if (cc == ']') { + if (ok) + break; + return (0); + } + if (cc == '-') { + if (lc <= scc && scc <= *p++) + ok++; + } else + if (scc == (lc = cc)) + ok++; + } + if (cc == 0) { + yyerror("Missing ']'"); + return (0); + } + continue; + + case '*': + if (!*p) + return (1); + for (s--; *s; s++) + if (smatch(s, p)) + return (1); + return (0); + + case '\0': + return (scc == '\0'); + + default: + if ((c & TRIM) != scc) + return (0); + continue; + + case '?': + if (scc == 0) + return (0); + continue; + + } + } +} + +static void +Cat(s1, s2) + register char *s1, *s2; +{ + int len = strlen(s1) + strlen(s2) + 1; + register char *s; + + nleft -= len; + if (nleft <= 0 || ++eargc >= GAVSIZ) + yyerror("Arguments too long"); + eargv[eargc] = 0; + eargv[eargc - 1] = s = malloc(len); + if (s == NULL) + fatal("ran out of memory\n"); + while (*s++ = *s1++ & TRIM) + ; + s--; + while (*s++ = *s2++ & TRIM) + ; +} + +static void +addpath(c) + int c; +{ + + if (pathp >= lastpathp) + yyerror("Pathname too long"); + else { + *pathp++ = c & TRIM; + *pathp = '\0'; + } +} + +/* + * Expand file names beginning with `~' into the + * user's home directory path name. Return a pointer in buf to the + * part corresponding to `file'. + */ +char * +exptilde(buf, file) + char buf[]; + register char *file; +{ + register char *s1, *s2, *s3; + extern char homedir[]; + + if (*file != '~') { + strcpy(buf, file); + return(buf); + } + if (*++file == '\0') { + s2 = homedir; + s3 = NULL; + } else if (*file == '/') { + s2 = homedir; + s3 = file; + } else { + s3 = file; + while (*s3 && *s3 != '/') + s3++; + if (*s3 == '/') + *s3 = '\0'; + else + s3 = NULL; + if (pw == NULL || strcmp(pw->pw_name, file) != 0) { + if ((pw = getpwnam(file)) == NULL) { + error("%s: unknown user name\n", file); + if (s3 != NULL) + *s3 = '/'; + return(NULL); + } + } + if (s3 != NULL) + *s3 = '/'; + s2 = pw->pw_dir; + } + for (s1 = buf; *s1++ = *s2++; ) + ; + s2 = --s1; + if (s3 != NULL) { + s2++; + while (*s1++ = *s3++) + ; + } + return(s2); +} diff --git a/usr.bin/rdist/gram.y b/usr.bin/rdist/gram.y new file mode 100644 index 00000000000..1eb190f6a4b --- /dev/null +++ b/usr.bin/rdist/gram.y @@ -0,0 +1,509 @@ +%{ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +/* from: static char sccsid[] = "@(#)gram.y 8.1 (Berkeley) 6/9/93"; */ +static char *rcsid = "$Id: gram.y,v 1.1 1995/10/18 08:45:59 deraadt Exp $"; +#endif /* not lint */ + +#include "defs.h" + +struct cmd *cmds = NULL; +struct cmd *last_cmd; +struct namelist *last_n; +struct subcmd *last_sc; + +static char *makestr __P((char *)); + +%} + +%term EQUAL 1 +%term LP 2 +%term RP 3 +%term SM 4 +%term ARROW 5 +%term COLON 6 +%term DCOLON 7 +%term NAME 8 +%term STRING 9 +%term INSTALL 10 +%term NOTIFY 11 +%term EXCEPT 12 +%term PATTERN 13 +%term SPECIAL 14 +%term OPTION 15 + +%union { + int intval; + char *string; + struct subcmd *subcmd; + struct namelist *namel; +} + +%type <intval> OPTION, options +%type <string> NAME, STRING +%type <subcmd> INSTALL, NOTIFY, EXCEPT, PATTERN, SPECIAL, cmdlist, cmd +%type <namel> namelist, names, opt_namelist + +%% + +file: /* VOID */ + | file command + ; + +command: NAME EQUAL namelist = { + (void) lookup($1, INSERT, $3); + } + | namelist ARROW namelist cmdlist = { + insert(NULL, $1, $3, $4); + } + | NAME COLON namelist ARROW namelist cmdlist = { + insert($1, $3, $5, $6); + } + | namelist DCOLON NAME cmdlist = { + append(NULL, $1, $3, $4); + } + | NAME COLON namelist DCOLON NAME cmdlist = { + append($1, $3, $5, $6); + } + | error + ; + +namelist: NAME = { + $$ = makenl($1); + } + | LP names RP = { + $$ = $2; + } + ; + +names: /* VOID */ { + $$ = last_n = NULL; + } + | names NAME = { + if (last_n == NULL) + $$ = last_n = makenl($2); + else { + last_n->n_next = makenl($2); + last_n = last_n->n_next; + $$ = $1; + } + } + ; + +cmdlist: /* VOID */ { + $$ = last_sc = NULL; + } + | cmdlist cmd = { + if (last_sc == NULL) + $$ = last_sc = $2; + else { + last_sc->sc_next = $2; + last_sc = $2; + $$ = $1; + } + } + ; + +cmd: INSTALL options opt_namelist SM = { + register struct namelist *nl; + + $1->sc_options = $2 | options; + if ($3 != NULL) { + nl = expand($3, E_VARS); + if (nl) { + if (nl->n_next != NULL) + yyerror("only one name allowed\n"); + $1->sc_name = nl->n_name; + free(nl); + } else + $1->sc_name = NULL; + } + $$ = $1; + } + | NOTIFY namelist SM = { + if ($2 != NULL) + $1->sc_args = expand($2, E_VARS); + $$ = $1; + } + | EXCEPT namelist SM = { + if ($2 != NULL) + $1->sc_args = expand($2, E_ALL); + $$ = $1; + } + | PATTERN namelist SM = { + struct namelist *nl; + char *cp, *re_comp(); + + for (nl = $2; nl != NULL; nl = nl->n_next) + if ((cp = re_comp(nl->n_name)) != NULL) + yyerror(cp); + $1->sc_args = expand($2, E_VARS); + $$ = $1; + } + | SPECIAL opt_namelist STRING SM = { + if ($2 != NULL) + $1->sc_args = expand($2, E_ALL); + $1->sc_name = $3; + $$ = $1; + } + ; + +options: /* VOID */ = { + $$ = 0; + } + | options OPTION = { + $$ |= $2; + } + ; + +opt_namelist: /* VOID */ = { + $$ = NULL; + } + | namelist = { + $$ = $1; + } + ; + +%% + +int yylineno = 1; +extern FILE *fin; + +int +yylex() +{ + static char yytext[INMAX]; + register int c; + register char *cp1, *cp2; + static char quotechars[] = "[]{}*?$"; + +again: + switch (c = getc(fin)) { + case EOF: /* end of file */ + return(0); + + case '#': /* start of comment */ + while ((c = getc(fin)) != EOF && c != '\n') + ; + if (c == EOF) + return(0); + case '\n': + yylineno++; + case ' ': + case '\t': /* skip blanks */ + goto again; + + case '=': /* EQUAL */ + return(EQUAL); + + case '(': /* LP */ + return(LP); + + case ')': /* RP */ + return(RP); + + case ';': /* SM */ + return(SM); + + case '-': /* -> */ + if ((c = getc(fin)) == '>') + return(ARROW); + ungetc(c, fin); + c = '-'; + break; + + case '"': /* STRING */ + cp1 = yytext; + cp2 = &yytext[INMAX - 1]; + for (;;) { + if (cp1 >= cp2) { + yyerror("command string too long\n"); + break; + } + c = getc(fin); + if (c == EOF || c == '"') + break; + if (c == '\\') { + if ((c = getc(fin)) == EOF) { + *cp1++ = '\\'; + break; + } + } + if (c == '\n') { + yylineno++; + c = ' '; /* can't send '\n' */ + } + *cp1++ = c; + } + if (c != '"') + yyerror("missing closing '\"'\n"); + *cp1 = '\0'; + yylval.string = makestr(yytext); + return(STRING); + + case ':': /* : or :: */ + if ((c = getc(fin)) == ':') + return(DCOLON); + ungetc(c, fin); + return(COLON); + } + cp1 = yytext; + cp2 = &yytext[INMAX - 1]; + for (;;) { + if (cp1 >= cp2) { + yyerror("input line too long\n"); + break; + } + if (c == '\\') { + if ((c = getc(fin)) != EOF) { + if (any(c, quotechars)) + c |= QUOTE; + } else { + *cp1++ = '\\'; + break; + } + } + *cp1++ = c; + c = getc(fin); + if (c == EOF || any(c, " \"'\t()=;:\n")) { + ungetc(c, fin); + break; + } + } + *cp1 = '\0'; + if (yytext[0] == '-' && yytext[2] == '\0') { + switch (yytext[1]) { + case 'b': + yylval.intval = COMPARE; + return(OPTION); + + case 'R': + yylval.intval = REMOVE; + return(OPTION); + + case 'v': + yylval.intval = VERIFY; + return(OPTION); + + case 'w': + yylval.intval = WHOLE; + return(OPTION); + + case 'y': + yylval.intval = YOUNGER; + return(OPTION); + + case 'h': + yylval.intval = FOLLOW; + return(OPTION); + + case 'i': + yylval.intval = IGNLNKS; + return(OPTION); + } + } + if (!strcmp(yytext, "install")) + c = INSTALL; + else if (!strcmp(yytext, "notify")) + c = NOTIFY; + else if (!strcmp(yytext, "except")) + c = EXCEPT; + else if (!strcmp(yytext, "except_pat")) + c = PATTERN; + else if (!strcmp(yytext, "special")) + c = SPECIAL; + else { + yylval.string = makestr(yytext); + return(NAME); + } + yylval.subcmd = makesubcmd(c); + return(c); +} + +int +any(c, str) + register int c; + register char *str; +{ + while (*str) + if (c == *str++) + return(1); + return(0); +} + +/* + * Insert or append ARROW command to list of hosts to be updated. + */ +void +insert(label, files, hosts, subcmds) + char *label; + struct namelist *files, *hosts; + struct subcmd *subcmds; +{ + register struct cmd *c, *prev, *nc; + register struct namelist *h; + + files = expand(files, E_VARS|E_SHELL); + hosts = expand(hosts, E_ALL); + for (h = hosts; h != NULL; free(h), h = h->n_next) { + /* + * Search command list for an update to the same host. + */ + for (prev = NULL, c = cmds; c!=NULL; prev = c, c = c->c_next) { + if (strcmp(c->c_name, h->n_name) == 0) { + do { + prev = c; + c = c->c_next; + } while (c != NULL && + strcmp(c->c_name, h->n_name) == 0); + break; + } + } + /* + * Insert new command to update host. + */ + nc = ALLOC(cmd); + if (nc == NULL) + fatal("ran out of memory\n"); + nc->c_type = ARROW; + nc->c_name = h->n_name; + nc->c_label = label; + nc->c_files = files; + nc->c_cmds = subcmds; + nc->c_next = c; + if (prev == NULL) + cmds = nc; + else + prev->c_next = nc; + /* update last_cmd if appending nc to cmds */ + if (c == NULL) + last_cmd = nc; + } +} + +/* + * Append DCOLON command to the end of the command list since these are always + * executed in the order they appear in the distfile. + */ +void +append(label, files, stamp, subcmds) + char *label; + struct namelist *files; + char *stamp; + struct subcmd *subcmds; +{ + register struct cmd *c; + + c = ALLOC(cmd); + if (c == NULL) + fatal("ran out of memory\n"); + c->c_type = DCOLON; + c->c_name = stamp; + c->c_label = label; + c->c_files = expand(files, E_ALL); + c->c_cmds = subcmds; + c->c_next = NULL; + if (cmds == NULL) + cmds = last_cmd = c; + else { + last_cmd->c_next = c; + last_cmd = c; + } +} + +/* + * Error printing routine in parser. + */ +void +yyerror(s) + char *s; +{ + ++nerrs; + fflush(stdout); + fprintf(stderr, "rdist: line %d: %s\n", yylineno, s); +} + +/* + * Return a copy of the string. + */ +static char * +makestr(str) + char *str; +{ + register char *cp, *s; + + str = cp = malloc(strlen(s = str) + 1); + if (cp == NULL) + fatal("ran out of memory\n"); + while (*cp++ = *s++) + ; + return(str); +} + +/* + * Allocate a namelist structure. + */ +struct namelist * +makenl(name) + char *name; +{ + register struct namelist *nl; + + nl = ALLOC(namelist); + if (nl == NULL) + fatal("ran out of memory\n"); + nl->n_name = name; + nl->n_next = NULL; + return(nl); +} + +/* + * Make a sub command for lists of variables, commands, etc. + */ +struct subcmd * +makesubcmd(type) + int type; +{ + register struct subcmd *sc; + + sc = ALLOC(subcmd); + if (sc == NULL) + fatal("ran out of memory\n"); + sc->sc_type = type; + sc->sc_args = NULL; + sc->sc_next = NULL; + sc->sc_name = NULL; + return(sc); +} diff --git a/usr.bin/rdist/lookup.c b/usr.bin/rdist/lookup.c new file mode 100644 index 00000000000..69e15dd6304 --- /dev/null +++ b/usr.bin/rdist/lookup.c @@ -0,0 +1,167 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +/* from: static char sccsid[] = "@(#)lookup.c 8.1 (Berkeley) 6/9/93"; */ +static char *rcsid = "$Id: lookup.c,v 1.1 1995/10/18 08:45:59 deraadt Exp $"; +#endif /* not lint */ + +#include "defs.h" + + /* symbol types */ +#define VAR 1 +#define CONST 2 + +struct syment { + int s_type; + char *s_name; + struct namelist *s_value; + struct syment *s_next; +}; + +static struct syment *hashtab[HASHSIZE]; + +/* + * Define a variable from a command line argument. + */ +void +define(name) + char *name; +{ + register char *cp, *s; + register struct namelist *nl; + struct namelist *value; + + if (debug) + printf("define(%s)\n", name); + + cp = index(name, '='); + if (cp == NULL) + value = NULL; + else if (cp[1] == '\0') { + *cp = '\0'; + value = NULL; + } else if (cp[1] != '(') { + *cp++ = '\0'; + value = makenl(cp); + } else { + nl = NULL; + *cp++ = '\0'; + do + cp++; + while (*cp == ' ' || *cp == '\t'); + for (s = cp; ; s++) { + switch (*s) { + case ')': + *s = '\0'; + case '\0': + break; + case ' ': + case '\t': + *s++ = '\0'; + while (*s == ' ' || *s == '\t') + s++; + if (*s == ')') + *s = '\0'; + break; + default: + continue; + } + if (nl == NULL) + value = nl = makenl(cp); + else { + nl->n_next = makenl(cp); + nl = nl->n_next; + } + if (*s == '\0') + break; + cp = s; + } + } + (void) lookup(name, REPLACE, value); +} + +/* + * Lookup name in the table and return a pointer to it. + * LOOKUP - just do lookup, return NULL if not found. + * INSERT - insert name with value, error if already defined. + * REPLACE - insert or replace name with value. + */ + +struct namelist * +lookup(name, action, value) + char *name; + int action; + struct namelist *value; +{ + register unsigned n; + register char *cp; + register struct syment *s; + char buf[256]; + + if (debug) + printf("lookup(%s, %d, %x)\n", name, action, value); + + n = 0; + for (cp = name; *cp; ) + n += *cp++; + n %= HASHSIZE; + + for (s = hashtab[n]; s != NULL; s = s->s_next) { + if (strcmp(name, s->s_name)) + continue; + if (action != LOOKUP) { + if (action != INSERT || s->s_type != CONST) { + (void)sprintf(buf, "%s redefined", name); + yyerror(buf); + } + } + return(s->s_value); + } + + if (action == LOOKUP) { + (void)sprintf(buf, "%s undefined", name); + yyerror(buf); + return(NULL); + } + + s = ALLOC(syment); + if (s == NULL) + fatal("ran out of memory\n"); + s->s_next = hashtab[n]; + hashtab[n] = s; + s->s_type = action == INSERT ? VAR : CONST; + s->s_name = name; + s->s_value = value; + return(value); +} diff --git a/usr.bin/rdist/main.c b/usr.bin/rdist/main.c new file mode 100644 index 00000000000..037836000bd --- /dev/null +++ b/usr.bin/rdist/main.c @@ -0,0 +1,328 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static char copyright[] = +"@(#) Copyright (c) 1983, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +/* from: static char sccsid[] = "@(#)main.c 8.1 (Berkeley) 6/9/93"; */ +static char *rcsid = "$Id: main.c,v 1.1 1995/10/18 08:45:59 deraadt Exp $"; +#endif /* not lint */ + +#include "defs.h" + +#define NHOSTS 100 + +/* + * Remote distribution program. + */ + +char *distfile = NULL; +#define _RDIST_TMP "/rdistXXXXXX" +char tempfile[sizeof _PATH_TMP + sizeof _RDIST_TMP + 1]; +char *tempname; + +int debug; /* debugging flag */ +int nflag; /* NOP flag, just print commands without executing */ +int qflag; /* Quiet. Don't print messages */ +int options; /* global options */ +int iamremote; /* act as remote server for transfering files */ + +FILE *fin = NULL; /* input file pointer */ +int rem = -1; /* file descriptor to remote source/sink process */ +char host[32]; /* host name */ +int nerrs; /* number of errors while sending/receiving */ +char user[10]; /* user's name */ +char homedir[128]; /* user's home directory */ +int userid; /* user's user ID */ +int groupid; /* user's group ID */ + +struct passwd *pw; /* pointer to static area used by getpwent */ +struct group *gr; /* pointer to static area used by getgrent */ + +static void usage __P((void)); +static void docmdargs __P((int, char *[])); + +int +main(argc, argv) + int argc; + char *argv[]; +{ + register char *arg; + int cmdargs = 0; + char *dhosts[NHOSTS], **hp = dhosts; + + pw = getpwuid(userid = getuid()); + if (pw == NULL) { + fprintf(stderr, "%s: Who are you?\n", argv[0]); + exit(1); + } + strcpy(user, pw->pw_name); + strcpy(homedir, pw->pw_dir); + groupid = pw->pw_gid; + gethostname(host, sizeof(host)); + strcpy(tempfile, _PATH_TMP); + strcat(tempfile, _RDIST_TMP); + if ((tempname = rindex(tempfile, '/')) != 0) + tempname++; + else + tempname = tempfile; + + while (--argc > 0) { + if ((arg = *++argv)[0] != '-') + break; + if (!strcmp(arg, "-Server")) + iamremote++; + else while (*++arg) + switch (*arg) { + case 'f': + if (--argc <= 0) + usage(); + distfile = *++argv; + if (distfile[0] == '-' && distfile[1] == '\0') + fin = stdin; + break; + + case 'm': + if (--argc <= 0) + usage(); + if (hp >= &dhosts[NHOSTS-2]) { + fprintf(stderr, "rdist: too many destination hosts\n"); + exit(1); + } + *hp++ = *++argv; + break; + + case 'd': + if (--argc <= 0) + usage(); + define(*++argv); + break; + + case 'D': + debug++; + break; + + case 'c': + cmdargs++; + break; + + case 'n': + if (options & VERIFY) { + printf("rdist: -n overrides -v\n"); + options &= ~VERIFY; + } + nflag++; + break; + + case 'q': + qflag++; + break; + + case 'b': + options |= COMPARE; + break; + + case 'R': + options |= REMOVE; + break; + + case 'v': + if (nflag) { + printf("rdist: -n overrides -v\n"); + break; + } + options |= VERIFY; + break; + + case 'w': + options |= WHOLE; + break; + + case 'y': + options |= YOUNGER; + break; + + case 'h': + options |= FOLLOW; + break; + + case 'i': + options |= IGNLNKS; + break; + + default: + usage(); + } + } + *hp = NULL; + + seteuid(userid); + mktemp(tempfile); + + if (iamremote) { + server(); + exit(nerrs != 0); + } + + if (cmdargs) + docmdargs(argc, argv); + else { + if (fin == NULL) { + if(distfile == NULL) { + if((fin = fopen("distfile","r")) == NULL) + fin = fopen("Distfile", "r"); + } else + fin = fopen(distfile, "r"); + if(fin == NULL) { + perror(distfile ? distfile : "distfile"); + exit(1); + } + } + yyparse(); + if (nerrs == 0) + docmds(dhosts, argc, argv); + } + + exit(nerrs != 0); +} + +static void +usage() +{ + printf("Usage: rdist [-nqbhirvwyD] [-f distfile] [-d var=value] [-m host] [file ...]\n"); + printf("or: rdist [-nqbhirvwyD] -c source [...] machine[:dest]\n"); + exit(1); +} + +/* + * rcp like interface for distributing files. + */ +static void +docmdargs(nargs, args) + int nargs; + char *args[]; +{ + register struct namelist *nl, *prev; + register char *cp; + struct namelist *files, *hosts; + struct subcmd *cmds; + char *dest; + static struct namelist tnl = { NULL, NULL }; + int i; + + if (nargs < 2) + usage(); + + prev = NULL; + for (i = 0; i < nargs - 1; i++) { + nl = makenl(args[i]); + if (prev == NULL) + files = prev = nl; + else { + prev->n_next = nl; + prev = nl; + } + } + + cp = args[i]; + if ((dest = index(cp, ':')) != NULL) + *dest++ = '\0'; + tnl.n_name = cp; + hosts = expand(&tnl, E_ALL); + if (nerrs) + exit(1); + + if (dest == NULL || *dest == '\0') + cmds = NULL; + else { + cmds = makesubcmd(INSTALL); + cmds->sc_options = options; + cmds->sc_name = dest; + } + + if (debug) { + printf("docmdargs()\nfiles = "); + prnames(files); + printf("hosts = "); + prnames(hosts); + } + insert(NULL, files, hosts, cmds); + docmds(NULL, 0, NULL); +} + +/* + * Print a list of NAME blocks (mostly for debugging). + */ +void +prnames(nl) + register struct namelist *nl; +{ + printf("( "); + while (nl != NULL) { + printf("%s ", nl->n_name); + nl = nl->n_next; + } + printf(")\n"); +} + +#if __STDC__ +#include <stdarg.h> +#else +#include <varargs.h> +#endif + +void +#if __STDC__ +warn(const char *fmt, ...) +#else +warn(fmt, va_alist) + char *fmt; + va_dcl +#endif +{ + extern int yylineno; + va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + (void)fprintf(stderr, "rdist: line %d: Warning: ", yylineno); + (void)vfprintf(stderr, fmt, ap); + (void)fprintf(stderr, "\n"); + va_end(ap); +} diff --git a/usr.bin/rdist/pathnames.h b/usr.bin/rdist/pathnames.h new file mode 100644 index 00000000000..c81b7d5fa62 --- /dev/null +++ b/usr.bin/rdist/pathnames.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)pathnames.h 8.1 (Berkeley) 6/9/93 + * $Id: pathnames.h,v 1.1 1995/10/18 08:45:59 deraadt Exp $ + */ + +#include <paths.h> + +#define _PATH_RDIST "rdist" diff --git a/usr.bin/rdist/rdist.1 b/usr.bin/rdist/rdist.1 new file mode 100644 index 00000000000..3a0287ddeee --- /dev/null +++ b/usr.bin/rdist/rdist.1 @@ -0,0 +1,413 @@ +.\" Copyright (c) 1985, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)rdist.1 8.2 (Berkeley) 12/30/93 +.\" $Id: rdist.1,v 1.1 1995/10/18 08:45:59 deraadt Exp $ +.\" +.Dd December 30, 1993 +.Dt RDIST 1 +.Os BSD 4.3 +.Sh NAME +.Nm rdist +.Nd remote file distribution program +.Sh SYNOPSIS +.Nm rdist +.Op Fl nqbRhivwy +.Op Fl f Ar distfile +.Op Fl d Ar var=value +.Op Fl m Ar host +.Op Ar name ... +.Nm rdist +.Op Fl nqbRhivwy +.Fl c +.Ar name ... +.Oo login@ Oc Ns Ar host Ns Op :dest +.Sh DESCRIPTION +.Nm Rdist +is a program to maintain identical copies of files over multiple hosts. +It preserves the owner, group, mode, and mtime of files if possible and +can update programs that are executing. +.Nm Rdist +reads commands from +.Ar distfile +to direct the updating of files and/or directories. +.Pp +Options specific to the first SYNOPSIS form: +.Pp +.Bl -tag -width indent +.It Fl +If +.Ar distfile +is +.Sq Fl , +the standard input is used. +.It Fl f Ar distfile +Use the specified +.Ar distfile. +.El +.Pp +If either the +.Fl f +or +.Sq Fl +option is not specified, the program looks first for +.Dq Pa distfile , +then +.Dq Pa Distfile +to use as the input. +If no names are specified on the command line, +.Nm rdist +will update all of the files and directories listed in +.Ar distfile . +Otherwise, the argument is taken to be the name of a file to be updated +or the label of a command to execute. If label and file names conflict, +it is assumed to be a label. +These may be used together to update specific files +using specific commands. +.Pp +Options specific to the second SYNOPSIS form: +.Pp +.Bl -tag -width Fl c +.It Fl c +Forces +.Nm rdist +to interpret the remaining arguments as a small +.Ar distfile . +.Pp +The equivalent distfile is as follows. +.Pp +.Bd -filled -offset indent -compact +.Pq Ar name ... +.Li -> +.Op Ar login@ +.Ar host +.Bd -filled -offset indent -compact +.Li install +.Op Ar dest ; +.Ed +.Ed +.El +.Pp +Options common to both forms: +.Pp +.Bl -tag -width Ic +.It Fl b +Binary comparison. Perform a binary comparison and update files if they differ +rather than comparing dates and sizes. +.It Fl d Ar var=value +Define +.Ar var +to have +.Ar value . +The +.Fl d +option is used to define or override variable definitions in the +.Ar distfile . +.Ar Value +can be the empty string, one name, or a list of names surrounded by +parentheses and separated by tabs and/or spaces. +.It Fl h +Follow symbolic links. Copy the file that the link points to rather than the +link itself. +.It Fl i +Ignore unresolved links. +.Nm Rdist +will normally try to maintain the link structure of files being transferred +and warn the user if all the links cannot be found. +.It Fl m Ar host +Limit which machines are to be updated. Multiple +.Fl m +arguments can be given to limit updates to a subset of the hosts listed in the +.Ar distfile . +.It Fl n +Print the commands without executing them. This option is +useful for debugging +.Ar distfile . +.It Fl q +Quiet mode. Files that are being modified are normally +printed on standard output. The +.Fl q +option suppresses this. +.It Fl R +Remove extraneous files. If a directory is being updated, any files that exist +on the remote host that do not exist in the master directory are removed. +This is useful for maintaining truly identical copies of directories. +.It Fl v +Verify that the files are up to date on all the hosts. Any files +that are out of date will be displayed but no files will be changed +nor any mail sent. +.It Fl w +Whole mode. The whole file name is appended to the destination directory +name. Normally, only the last component of a name is used when renaming files. +This will preserve the directory structure of the files being +copied instead of flattening the directory structure. For example, +renaming a list of files such as ( dir1/f1 dir2/f2 ) to dir3 would create +files dir3/dir1/f1 and dir3/dir2/f2 instead of dir3/f1 and dir3/f2. +.It Fl y +Younger mode. Files are normally updated if their +.Ar mtime +and +.Ar size +(see +.Xr stat 2 ) +disagree. The +.Fl y +option causes +.Nm rdist +not to update files that are younger than the master copy. +This can be used +to prevent newer copies on other hosts from being replaced. +A warning message is printed for files which are newer than the master copy. +.El +.Pp +.Ar Distfile +contains a sequence of entries that specify the files +to be copied, the destination hosts, and what operations to perform +to do the updating. Each entry has one of the following formats. +.Pp +.Bd -literal -offset indent -compact +<variable name> `=' <name list> +[label:]<source list> `\->' <destination list> <command list> +[label:]<source list> `::' <time_stamp file> <command list> +.Ed +.Pp +The first format is used for defining variables. +The second format is used for distributing files to other hosts. +The third format is used for making lists of files that have been changed +since some given date. +The +.Ar source list +specifies a +list of files and/or directories on the local host which are to be used +as the master copy for distribution. +The +.Ar destination list +is the list of hosts to which these files are to be +copied. Each file in the source list is added to a list of changes +if the file is out of date on the host which is being updated (second format) or +the file is newer than the time stamp file (third format). +.Pp +Labels are optional. They are used to identify a command for partial updates. +.Pp +Newlines, tabs, and blanks are only used as separators and are +otherwise ignored. Comments begin with `#' and end with a newline. +.Pp +Variables to be expanded begin with `$' followed by one character or +a name enclosed in curly braces (see the examples at the end). +.Pp +The source and destination lists have the following format: +.Bd -literal -offset indent +<name> +.Ed +or +.Bd -literal -offset indent -compact +`(' <zero or more names separated by white-space> `)' +.Ed +.Pp +The shell meta-characters `[', `]', `{', `}', `*', and `?' +are recognized and expanded (on the local host only) in the same way as +.Xr csh 1 . +They can be escaped with a backslash. +The `~' character is also expanded in the same way as +.Xr csh 1 +but is expanded separately on the local and destination hosts. +When the +.Fl w +option is used with a file name that begins with `~', everything except the +home directory is appended to the destination name. +File names which do not begin with `/' or `~' use the destination user's +home directory as the root directory for the rest of the file name. +.Pp +The command list consists of zero or more commands of the following +format. +.Bd -ragged -offset indent -compact +.Bl -column except_patx pattern\ listx +.It `install' <options> opt_dest_name `;' +.It `notify' <name list> `;' +.It `except' <name list> `;' +.It `except_pat' <pattern list> `;' +.It `special' <name list> string `;' +.El +.Ed +.Pp +The +.Ic install +command is used to copy out of date files and/or directories. +Each source file is copied to each host in the destination list. +Directories are recursively copied in the same way. +.Ar Opt_dest_name +is an optional parameter to rename files. +If no +.Ic install +command appears in the command list or +the destination name is not specified, +the source file name is used. +Directories in the path name will be created if they +do not exist on the remote host. +To help prevent disasters, a non-empty directory on a target host will +never be replaced with a regular file or a symbolic link. +However, under the `\-R' option a non-empty directory will be removed +if the corresponding filename is completely absent on the master host. +The +.Ar options +are `\-R', `\-h', `\-i', `\-v', `\-w', `\-y', and `\-b' +and have the same semantics as +options on the command line except they only apply to the files +in the source list. +The login name used on the destination host is the same as the local host +unless the destination name is of the format ``login@host". +.Pp +The +.Ic notify +command is used to mail the list of files updated (and any errors +that may have occurred) to the listed names. +If no `@' appears in the name, the destination host is appended to +the name +(e.g., name1@host, name2@host, ...). +.Pp +The +.Ic except +command is used to update all of the files in the source list +.Ic except +for the files listed in +.Ar name list . +This is usually used to copy everything in a directory except certain files. +.Pp +The +.Ic except_pat +command is like the +.Ic except +command except that +.Ar pattern list +is a list of regular expressions +(see +.Xr ed 1 +for details). +If one of the patterns matches some string within a file name, that file will +be ignored. +Note that since `\e' is a quote character, it must be doubled to become +part of the regular expression. Variables are expanded in +.Ar pattern list +but not shell file pattern matching characters. To include a `$', it +must be escaped with `\e'. +.Pp +The +.Ic special +command is used to specify +.Xr sh 1 +commands that are to be executed on the +remote host after the file in +.Ar name list +is updated or installed. +If the +.Ar name list +is omitted then the shell commands will be executed +for every file updated or installed. The shell variable `FILE' is set +to the current filename before executing the commands in +.Ar string . +.Ar String +starts and ends with `"' and can cross multiple lines in +.Ar distfile . +Multiple commands to the shell should be separated by `;'. +Commands are executed in the user's home directory on the host +being updated. +The +.Ar special +command can be used to rebuild private databases, etc. +after a program has been updated. +.Pp +The following is a small example: +.Bd -literal -offset indent +HOSTS = ( matisse root@arpa ) + +FILES = ( /bin /lib /usr/bin /usr/games +\t/usr/include/{*.h,{stand,sys,vax*,pascal,machine}/*.h} +\t/usr/lib /usr/man/man? /usr/ucb /usr/local/rdist ) + +EXLIB = ( Mail.rc aliases aliases.dir aliases.pag crontab dshrc +\tsendmail.cf sendmail.fc sendmail.hf sendmail.st uucp vfont ) + +${FILES} -> ${HOSTS} +\tinstall -R ; +\texcept /usr/lib/${EXLIB} ; +\texcept /usr/games/lib ; +\tspecial /usr/lib/sendmail "/usr/lib/sendmail -bz" ; + +srcs: +/usr/src/bin -> arpa +\texcept_pat ( \e\e.o\e$ /SCCS\e$ ) ; + +IMAGEN = (ips dviimp catdvi) + +imagen: +/usr/local/${IMAGEN} -> arpa +\tinstall /usr/local/lib ; +\tnotify ralph ; + +${FILES} :: stamp.cory +\tnotify root@cory ; +.Ed +.Sh FILES +.Bl -tag -width /tmp/rdist* -compact +.It Pa distfile +input command file +.It Pa /tmp/rdist* +temporary file for update lists +.El +.Sh SEE ALSO +.Xr sh 1 , +.Xr csh 1 , +.Xr stat 2 +.Sh HISTORY +The +.Nm rdist +command appeared in +.Bx 4.3 . +.Sh DIAGNOSTICS +A complaint about mismatch of rdist version numbers may really stem +from some problem with starting your shell, e.g., you are in too many groups. +.Sh BUGS +Source files must reside on the local host where +.Nm rdist +is executed. +.Pp +There is no easy way to have a special command executed after all files +in a directory have been updated. +.Pp +Variable expansion only works for name lists; there should be a general macro +facility. +.Pp +.Nm Rdist +aborts on files which have a negative mtime (before Jan 1, 1970). +.Pp +There should be a `force' option to allow replacement of non-empty directories +by regular files or symlinks. A means of updating file modes and owners +of otherwise identical files is also needed. diff --git a/usr.bin/rdist/server.c b/usr.bin/rdist/server.c new file mode 100644 index 00000000000..450c8ec8e83 --- /dev/null +++ b/usr.bin/rdist/server.c @@ -0,0 +1,1586 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +/* from: static char sccsid[] = "@(#)server.c 8.1 (Berkeley) 6/9/93"; */ +static char *rcsid = "$Id: server.c,v 1.1 1995/10/18 08:45:59 deraadt Exp $"; +#endif /* not lint */ + +#include <sys/wait.h> +#include "defs.h" + +#define ack() (void) write(rem, "\0\n", 2) +#define err() (void) write(rem, "\1\n", 2) + +struct linkbuf *ihead; /* list of files with more than one link */ +char buf[BUFSIZ]; /* general purpose buffer */ +char target[BUFSIZ]; /* target/source directory name */ +char *tp; /* pointer to end of target name */ +char *Tdest; /* pointer to last T dest*/ +int catname; /* cat name to target name */ +char *stp[32]; /* stack of saved tp's for directories */ +int oumask; /* old umask for creating files */ + +extern FILE *lfp; /* log file for mailing changes */ + +static int chkparent __P((char *)); +static void clean __P((char *)); +static void comment __P((char *)); +static void dospecial __P((char *)); +static int fchog __P((int, char *, char *, char *, int)); +static void hardlink __P((char *)); +static void note __P((const char *, ...)); +static void query __P((char *)); +static void recvf __P((char *, int)); +static void removeit __P((struct stat *)); +static int response __P((void)); +static void rmchk __P((int)); +static struct linkbuf * + savelink __P((struct stat *)); +static void sendf __P((char *, int)); +static int update __P((char *, int, struct stat *)); + +/* + * Server routine to read requests and process them. + * Commands are: + * Tname - Transmit file if out of date + * Vname - Verify if file out of date or not + * Qname - Query if file exists. Return mtime & size if it does. + */ +void +server() +{ + char cmdbuf[BUFSIZ]; + register char *cp; + + signal(SIGHUP, cleanup); + signal(SIGINT, cleanup); + signal(SIGQUIT, cleanup); + signal(SIGTERM, cleanup); + signal(SIGPIPE, cleanup); + + rem = 0; + oumask = umask(0); + (void) sprintf(buf, "V%d\n", VERSION); + (void) write(rem, buf, strlen(buf)); + + for (;;) { + cp = cmdbuf; + if (read(rem, cp, 1) <= 0) + return; + if (*cp++ == '\n') { + error("server: expected control record\n"); + continue; + } + do { + if (read(rem, cp, 1) != 1) + cleanup(0); + } while (*cp++ != '\n' && cp < &cmdbuf[BUFSIZ]); + *--cp = '\0'; + cp = cmdbuf; + switch (*cp++) { + case 'T': /* init target file/directory name */ + catname = 1; /* target should be directory */ + goto dotarget; + + case 't': /* init target file/directory name */ + catname = 0; + dotarget: + if (exptilde(target, cp) == NULL) + continue; + tp = target; + while (*tp) + tp++; + ack(); + continue; + + case 'R': /* Transfer a regular file. */ + recvf(cp, S_IFREG); + continue; + + case 'D': /* Transfer a directory. */ + recvf(cp, S_IFDIR); + continue; + + case 'K': /* Transfer symbolic link. */ + recvf(cp, S_IFLNK); + continue; + + case 'k': /* Transfer hard link. */ + hardlink(cp); + continue; + + case 'E': /* End. (of directory) */ + *tp = '\0'; + if (catname <= 0) { + error("server: too many 'E's\n"); + continue; + } + tp = stp[--catname]; + *tp = '\0'; + ack(); + continue; + + case 'C': /* Clean. Cleanup a directory */ + clean(cp); + continue; + + case 'Q': /* Query. Does the file/directory exist? */ + query(cp); + continue; + + case 'S': /* Special. Execute commands */ + dospecial(cp); + continue; + +#ifdef notdef + /* + * These entries are reserved but not currently used. + * The intent is to allow remote hosts to have master copies. + * Currently, only the host rdist runs on can have masters. + */ + case 'X': /* start a new list of files to exclude */ + except = bp = NULL; + case 'x': /* add name to list of files to exclude */ + if (*cp == '\0') { + ack(); + continue; + } + if (*cp == '~') { + if (exptilde(buf, cp) == NULL) + continue; + cp = buf; + } + if (bp == NULL) + except = bp = expand(makeblock(NAME, cp), E_VARS); + else + bp->b_next = expand(makeblock(NAME, cp), E_VARS); + while (bp->b_next != NULL) + bp = bp->b_next; + ack(); + continue; + + case 'I': /* Install. Transfer file if out of date. */ + opts = 0; + while (*cp >= '0' && *cp <= '7') + opts = (opts << 3) | (*cp++ - '0'); + if (*cp++ != ' ') { + error("server: options not delimited\n"); + return; + } + install(cp, opts); + continue; + + case 'L': /* Log. save message in log file */ + log(lfp, cp); + continue; +#endif + + case '\1': + nerrs++; + continue; + + case '\2': + return; + + default: + error("server: unknown command '%s'\n", cp); + case '\0': + continue; + } + } +} + +/* + * Update the file(s) if they are different. + * destdir = 1 if destination should be a directory + * (i.e., more than one source is being copied to the same destination). + */ +void +install(src, dest, destdir, opts) + char *src, *dest; + int destdir, opts; +{ + char *rname; + char destcopy[BUFSIZ]; + + if (dest == NULL) { + opts &= ~WHOLE; /* WHOLE mode only useful if renaming */ + dest = src; + } + + if (nflag || debug) { + printf("%s%s%s%s%s %s %s\n", opts & VERIFY ? "verify":"install", + opts & WHOLE ? " -w" : "", + opts & YOUNGER ? " -y" : "", + opts & COMPARE ? " -b" : "", + opts & REMOVE ? " -R" : "", src, dest); + if (nflag) + return; + } + + rname = exptilde(target, src); + if (rname == NULL) + return; + tp = target; + while (*tp) + tp++; + /* + * If we are renaming a directory and we want to preserve + * the directory heirarchy (-w), we must strip off the leading + * directory name and preserve the rest. + */ + if (opts & WHOLE) { + while (*rname == '/') + rname++; + destdir = 1; + } else { + rname = rindex(target, '/'); + if (rname == NULL) + rname = target; + else + rname++; + } + if (debug) + printf("target = %s, rname = %s\n", target, rname); + /* + * Pass the destination file/directory name to remote. + */ + (void) sprintf(buf, "%c%s\n", destdir ? 'T' : 't', dest); + if (debug) + printf("buf = %s", buf); + (void) write(rem, buf, strlen(buf)); + if (response() < 0) + return; + + if (destdir) { + strcpy(destcopy, dest); + Tdest = destcopy; + } + sendf(rname, opts); + Tdest = 0; +} + +#define protoname() (pw ? pw->pw_name : user) +#define protogroup() (gr ? gr->gr_name : group) +/* + * Transfer the file or directory in target[]. + * rname is the name of the file on the remote host. + */ +static void +sendf(rname, opts) + char *rname; + int opts; +{ + register struct subcmd *sc; + struct stat stb; + int sizerr, f, u, len; + off_t i; + DIR *d; + struct direct *dp; + char *otp, *cp; + extern struct subcmd *subcmds; + static char user[15], group[15]; + + if (debug) + printf("sendf(%s, %x)\n", rname, opts); + + if (except(target)) + return; + if ((opts & FOLLOW ? stat(target, &stb) : lstat(target, &stb)) < 0) { + error("%s: %s\n", target, strerror(errno)); + return; + } + if ((u = update(rname, opts, &stb)) == 0) { + if ((stb.st_mode & S_IFMT) == S_IFREG && stb.st_nlink > 1) + (void) savelink(&stb); + return; + } + + if (pw == NULL || pw->pw_uid != stb.st_uid) + if ((pw = getpwuid(stb.st_uid)) == NULL) { + log(lfp, "%s: no password entry for uid %d \n", + target, stb.st_uid); + pw = NULL; + (void)sprintf(user, ":%lu", stb.st_uid); + } + if (gr == NULL || gr->gr_gid != stb.st_gid) + if ((gr = getgrgid(stb.st_gid)) == NULL) { + log(lfp, "%s: no name for group %d\n", + target, stb.st_gid); + gr = NULL; + (void)sprintf(group, ":%lu", stb.st_gid); + } + if (u == 1) { + if (opts & VERIFY) { + log(lfp, "need to install: %s\n", target); + goto dospecial; + } + log(lfp, "installing: %s\n", target); + opts &= ~(COMPARE|REMOVE); + } + + switch (stb.st_mode & S_IFMT) { + case S_IFDIR: + if ((d = opendir(target)) == NULL) { + error("%s: %s\n", target, strerror(errno)); + return; + } + (void) sprintf(buf, "D%o %04o 0 0 %s %s %s\n", opts, + stb.st_mode & 07777, protoname(), protogroup(), rname); + if (debug) + printf("buf = %s", buf); + (void) write(rem, buf, strlen(buf)); + if (response() < 0) { + closedir(d); + return; + } + + if (opts & REMOVE) + rmchk(opts); + + otp = tp; + len = tp - target; + while (dp = readdir(d)) { + if (!strcmp(dp->d_name, ".") || + !strcmp(dp->d_name, "..")) + continue; + if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { + error("%s/%s: Name too long\n", target, + dp->d_name); + continue; + } + tp = otp; + *tp++ = '/'; + cp = dp->d_name; + while (*tp++ = *cp++) + ; + tp--; + sendf(dp->d_name, opts); + } + closedir(d); + (void) write(rem, "E\n", 2); + (void) response(); + tp = otp; + *tp = '\0'; + return; + + case S_IFLNK: + if (u != 1) + opts |= COMPARE; + if (stb.st_nlink > 1) { + struct linkbuf *lp; + + if ((lp = savelink(&stb)) != NULL) { + /* install link */ + if (*lp->target == 0) + (void) sprintf(buf, "k%o %s %s\n", opts, + lp->pathname, rname); + else + (void) sprintf(buf, "k%o %s/%s %s\n", opts, + lp->target, lp->pathname, rname); + if (debug) + printf("buf = %s", buf); + (void) write(rem, buf, strlen(buf)); + (void) response(); + return; + } + } + (void) sprintf(buf, "K%o %o %qd %ld %s %s %s\n", opts, + stb.st_mode & 07777, stb.st_size, stb.st_mtime, + protoname(), protogroup(), rname); + if (debug) + printf("buf = %s", buf); + (void) write(rem, buf, strlen(buf)); + if (response() < 0) + return; + sizerr = (readlink(target, buf, BUFSIZ) != stb.st_size); + (void) write(rem, buf, stb.st_size); + if (debug) + printf("readlink = %.*s\n", (int)stb.st_size, buf); + goto done; + + case S_IFREG: + break; + + default: + error("%s: not a file or directory\n", target); + return; + } + + if (u == 2) { + if (opts & VERIFY) { + log(lfp, "need to update: %s\n", target); + goto dospecial; + } + log(lfp, "updating: %s\n", target); + } + + if (stb.st_nlink > 1) { + struct linkbuf *lp; + + if ((lp = savelink(&stb)) != NULL) { + /* install link */ + if (*lp->target == 0) + (void) sprintf(buf, "k%o %s %s\n", opts, + lp->pathname, rname); + else + (void) sprintf(buf, "k%o %s/%s %s\n", opts, + lp->target, lp->pathname, rname); + if (debug) + printf("buf = %s", buf); + (void) write(rem, buf, strlen(buf)); + (void) response(); + return; + } + } + + if ((f = open(target, O_RDONLY, 0)) < 0) { + error("%s: %s\n", target, strerror(errno)); + return; + } + (void) sprintf(buf, "R%o %o %qd %ld %s %s %s\n", opts, + stb.st_mode & 07777, stb.st_size, stb.st_mtime, + protoname(), protogroup(), rname); + if (debug) + printf("buf = %s", buf); + (void) write(rem, buf, strlen(buf)); + if (response() < 0) { + (void) close(f); + return; + } + sizerr = 0; + for (i = 0; i < stb.st_size; i += BUFSIZ) { + int amt = BUFSIZ; + if (i + amt > stb.st_size) + amt = stb.st_size - i; + if (sizerr == 0 && read(f, buf, amt) != amt) + sizerr = 1; + (void) write(rem, buf, amt); + } + (void) close(f); +done: + if (sizerr) { + error("%s: file changed size\n", target); + err(); + } else + ack(); + f = response(); + if (f < 0 || f == 0 && (opts & COMPARE)) + return; +dospecial: + for (sc = subcmds; sc != NULL; sc = sc->sc_next) { + if (sc->sc_type != SPECIAL) + continue; + if (sc->sc_args != NULL && !inlist(sc->sc_args, target)) + continue; + log(lfp, "special \"%s\"\n", sc->sc_name); + if (opts & VERIFY) + continue; + (void) sprintf(buf, "SFILE=%s;%s\n", target, sc->sc_name); + if (debug) + printf("buf = %s", buf); + (void) write(rem, buf, strlen(buf)); + while (response() > 0) + ; + } +} + +static struct linkbuf * +savelink(stp) + struct stat *stp; +{ + struct linkbuf *lp; + + for (lp = ihead; lp != NULL; lp = lp->nextp) + if (lp->inum == stp->st_ino && lp->devnum == stp->st_dev) { + lp->count--; + return(lp); + } + lp = (struct linkbuf *) malloc(sizeof(*lp)); + if (lp == NULL) + log(lfp, "out of memory, link information lost\n"); + else { + lp->nextp = ihead; + ihead = lp; + lp->inum = stp->st_ino; + lp->devnum = stp->st_dev; + lp->count = stp->st_nlink - 1; + strcpy(lp->pathname, target); + if (Tdest) + strcpy(lp->target, Tdest); + else + *lp->target = 0; + } + return(NULL); +} + +/* + * Check to see if file needs to be updated on the remote machine. + * Returns 0 if no update, 1 if remote doesn't exist, 2 if out of date + * and 3 if comparing binaries to determine if out of date. + */ +static int +update(rname, opts, stp) + char *rname; + int opts; + struct stat *stp; +{ + register char *cp, *s; + register off_t size; + register time_t mtime; + + if (debug) + printf("update(%s, %x, %x)\n", rname, opts, stp); + + /* + * Check to see if the file exists on the remote machine. + */ + (void) sprintf(buf, "Q%s\n", rname); + if (debug) + printf("buf = %s", buf); + (void) write(rem, buf, strlen(buf)); +again: + cp = s = buf; + do { + if (read(rem, cp, 1) != 1) + lostconn(0); + } while (*cp++ != '\n' && cp < &buf[BUFSIZ]); + + switch (*s++) { + case 'Y': + break; + + case 'N': /* file doesn't exist so install it */ + return(1); + + case '\1': + nerrs++; + if (*s != '\n') { + if (!iamremote) { + fflush(stdout); + (void) write(2, s, cp - s); + } + if (lfp != NULL) + (void) fwrite(s, 1, cp - s, lfp); + } + return(0); + + case '\3': + *--cp = '\0'; + if (lfp != NULL) + log(lfp, "update: note: %s\n", s); + goto again; + + default: + *--cp = '\0'; + error("update: unexpected response '%s'\n", s); + return(0); + } + + if (*s == '\n') + return(2); + + if (opts & COMPARE) + return(3); + + size = 0; + while (isdigit(*s)) + size = size * 10 + (*s++ - '0'); + if (*s++ != ' ') { + error("update: size not delimited\n"); + return(0); + } + mtime = 0; + while (isdigit(*s)) + mtime = mtime * 10 + (*s++ - '0'); + if (*s != '\n') { + error("update: mtime not delimited\n"); + return(0); + } + /* + * File needs to be updated? + */ + if (opts & YOUNGER) { + if (stp->st_mtime == mtime) + return(0); + if (stp->st_mtime < mtime) { + log(lfp, "Warning: %s: remote copy is newer\n", target); + return(0); + } + } else if (stp->st_mtime == mtime && stp->st_size == size) + return(0); + return(2); +} + +/* + * Query. Check to see if file exists. Return one of the following: + * N\n - doesn't exist + * Ysize mtime\n - exists and its a regular file (size & mtime of file) + * Y\n - exists and its a directory or symbolic link + * ^Aerror message\n + */ +static void +query(name) + char *name; +{ + struct stat stb; + + if (catname) + (void) sprintf(tp, "/%s", name); + + if (lstat(target, &stb) < 0) { + if (errno == ENOENT) + (void) write(rem, "N\n", 2); + else + error("%s:%s: %s\n", host, target, strerror(errno)); + *tp = '\0'; + return; + } + + switch (stb.st_mode & S_IFMT) { + case S_IFREG: + (void) sprintf(buf, "Y%qd %ld\n", stb.st_size, + stb.st_mtime); + (void) write(rem, buf, strlen(buf)); + break; + + case S_IFLNK: + case S_IFDIR: + (void) write(rem, "Y\n", 2); + break; + + default: + error("%s: not a file or directory\n", name); + break; + } + *tp = '\0'; +} + +static void +recvf(cmd, type) + char *cmd; + int type; +{ + register char *cp; + int f, mode, opts, wrerr, olderrno; + off_t i, size; + time_t mtime; + struct stat stb; + struct timeval tvp[2]; + char *owner, *group; + char new[BUFSIZ]; + extern char *tempname; + + cp = cmd; + opts = 0; + while (*cp >= '0' && *cp <= '7') + opts = (opts << 3) | (*cp++ - '0'); + if (*cp++ != ' ') { + error("recvf: options not delimited\n"); + return; + } + mode = 0; + while (*cp >= '0' && *cp <= '7') + mode = (mode << 3) | (*cp++ - '0'); + if (*cp++ != ' ') { + error("recvf: mode not delimited\n"); + return; + } + size = 0; + while (isdigit(*cp)) + size = size * 10 + (*cp++ - '0'); + if (*cp++ != ' ') { + error("recvf: size not delimited\n"); + return; + } + mtime = 0; + while (isdigit(*cp)) + mtime = mtime * 10 + (*cp++ - '0'); + if (*cp++ != ' ') { + error("recvf: mtime not delimited\n"); + return; + } + owner = cp; + while (*cp && *cp != ' ') + cp++; + if (*cp != ' ') { + error("recvf: owner name not delimited\n"); + return; + } + *cp++ = '\0'; + group = cp; + while (*cp && *cp != ' ') + cp++; + if (*cp != ' ') { + error("recvf: group name not delimited\n"); + return; + } + *cp++ = '\0'; + + if (type == S_IFDIR) { + if (catname >= sizeof(stp)) { + error("%s:%s: too many directory levels\n", + host, target); + return; + } + stp[catname] = tp; + if (catname++) { + *tp++ = '/'; + while (*tp++ = *cp++) + ; + tp--; + } + if (opts & VERIFY) { + ack(); + return; + } + if (lstat(target, &stb) == 0) { + if (ISDIR(stb.st_mode)) { + if ((stb.st_mode & 07777) == mode) { + ack(); + return; + } + buf[0] = '\0'; + (void) sprintf(buf + 1, + "%s: Warning: remote mode %o != local mode %o\n", + target, stb.st_mode & 07777, mode); + (void) write(rem, buf, strlen(buf + 1) + 1); + return; + } + errno = ENOTDIR; + } else if (errno == ENOENT && (mkdir(target, mode) == 0 || + chkparent(target) == 0 && mkdir(target, mode) == 0)) { + if (fchog(-1, target, owner, group, mode) == 0) + ack(); + return; + } + error("%s:%s: %s\n", host, target, strerror(errno)); + tp = stp[--catname]; + *tp = '\0'; + return; + } + + if (catname) + (void) sprintf(tp, "/%s", cp); + cp = rindex(target, '/'); + if (cp == NULL) + strcpy(new, tempname); + else if (cp == target) + (void) sprintf(new, "/%s", tempname); + else { + *cp = '\0'; + (void) sprintf(new, "%s/%s", target, tempname); + *cp = '/'; + } + + if (type == S_IFLNK) { + int j; + + ack(); + cp = buf; + for (i = 0; i < size; i += j) { + if ((j = read(rem, cp, size - i)) <= 0) + cleanup(0); + cp += j; + } + *cp = '\0'; + if (response() < 0) { + err(); + return; + } + if (symlink(buf, new) < 0) { + if (errno != ENOENT || chkparent(new) < 0 || + symlink(buf, new) < 0) + goto badnew1; + } + mode &= 0777; + if (opts & COMPARE) { + char tbuf[BUFSIZ]; + + if ((i = readlink(target, tbuf, BUFSIZ)) >= 0 && + i == size && strncmp(buf, tbuf, size) == 0) { + (void) unlink(new); + ack(); + return; + } + if (opts & VERIFY) + goto differ; + } + goto fixup; + } + + if ((f = creat(new, mode)) < 0) { + if (errno != ENOENT || chkparent(new) < 0 || + (f = creat(new, mode)) < 0) + goto badnew1; + } + + ack(); + wrerr = 0; + for (i = 0; i < size; i += BUFSIZ) { + int amt = BUFSIZ; + + cp = buf; + if (i + amt > size) + amt = size - i; + do { + int j = read(rem, cp, amt); + + if (j <= 0) { + (void) close(f); + (void) unlink(new); + cleanup(0); + } + amt -= j; + cp += j; + } while (amt > 0); + amt = BUFSIZ; + if (i + amt > size) + amt = size - i; + if (wrerr == 0 && write(f, buf, amt) != amt) { + olderrno = errno; + wrerr++; + } + } + if (response() < 0) { + err(); + goto badnew2; + } + if (wrerr) + goto badnew1; + if (opts & COMPARE) { + FILE *f1, *f2; + int c; + + if ((f1 = fopen(target, "r")) == NULL) + goto badtarget; + if ((f2 = fopen(new, "r")) == NULL) { +badnew1: error("%s:%s: %s\n", host, new, strerror(errno)); + goto badnew2; + } + while ((c = getc(f1)) == getc(f2)) + if (c == EOF) { + (void) fclose(f1); + (void) fclose(f2); + ack(); + goto badnew2; + } + (void) fclose(f1); + (void) fclose(f2); + if (opts & VERIFY) { +differ: buf[0] = '\0'; + (void) sprintf(buf + 1, "need to update: %s\n",target); + (void) write(rem, buf, strlen(buf + 1) + 1); + goto badnew2; + } + } + + /* + * Set last modified time + */ + tvp[0].tv_sec = time(0); + tvp[0].tv_usec = 0; + tvp[1].tv_sec = mtime; + tvp[1].tv_usec = 0; + if (utimes(new, tvp) < 0) + note("%s: utimes failed %s: %s\n", host, new, strerror(errno)); + + if (fchog(f, new, owner, group, mode) < 0) { +badnew2: (void) close(f); + (void) unlink(new); + return; + } + (void) close(f); + +fixup: if (rename(new, target) < 0) { +badtarget: error("%s:%s: %s\n", host, target, strerror(errno)); + (void) unlink(new); + return; + } + + if (opts & COMPARE) { + buf[0] = '\0'; + (void) sprintf(buf + 1, "updated %s\n", target); + (void) write(rem, buf, strlen(buf + 1) + 1); + } else + ack(); +} + +/* + * Creat a hard link to existing file. + */ +static void +hardlink(cmd) + char *cmd; +{ + register char *cp; + struct stat stb; + char *oldname; + int opts, exists = 0; + + cp = cmd; + opts = 0; + while (*cp >= '0' && *cp <= '7') + opts = (opts << 3) | (*cp++ - '0'); + if (*cp++ != ' ') { + error("hardlink: options not delimited\n"); + return; + } + oldname = cp; + while (*cp && *cp != ' ') + cp++; + if (*cp != ' ') { + error("hardlink: oldname name not delimited\n"); + return; + } + *cp++ = '\0'; + + if (catname) { + (void) sprintf(tp, "/%s", cp); + } + if (lstat(target, &stb) == 0) { + int mode = stb.st_mode & S_IFMT; + if (mode != S_IFREG && mode != S_IFLNK) { + error("%s:%s: not a regular file\n", host, target); + return; + } + exists = 1; + } + if (chkparent(target) < 0 ) { + error("%s:%s: %s (no parent)\n", + host, target, strerror(errno)); + return; + } + if (exists && (unlink(target) < 0)) { + error("%s:%s: %s (unlink)\n", + host, target, strerror(errno)); + return; + } + if (link(oldname, target) < 0) { + error("%s:can't link %s to %s\n", + host, target, oldname); + return; + } + ack(); +} + +/* + * Check to see if parent directory exists and create one if not. + */ +static int +chkparent(name) + char *name; +{ + register char *cp; + struct stat stb; + + cp = rindex(name, '/'); + if (cp == NULL || cp == name) + return(0); + *cp = '\0'; + if (lstat(name, &stb) < 0) { + if (errno == ENOENT && chkparent(name) >= 0 && + mkdir(name, 0777 & ~oumask) >= 0) { + *cp = '/'; + return(0); + } + } else if (ISDIR(stb.st_mode)) { + *cp = '/'; + return(0); + } + *cp = '/'; + return(-1); +} + +/* + * Change owner, group and mode of file. + */ +static int +fchog(fd, file, owner, group, mode) + int fd; + char *file, *owner, *group; + int mode; +{ + register int i; + int uid, gid; + extern char user[]; + extern int userid; + + uid = userid; + if (userid == 0) { + if (*owner == ':') { + uid = atoi(owner + 1); + } else if (pw == NULL || strcmp(owner, pw->pw_name) != 0) { + if ((pw = getpwnam(owner)) == NULL) { + if (mode & 04000) { + note("%s:%s: unknown login name, clearing setuid", + host, owner); + mode &= ~04000; + uid = 0; + } + } else + uid = pw->pw_uid; + } else + uid = pw->pw_uid; + if (*group == ':') { + gid = atoi(group + 1); + goto ok; + } + } else if ((mode & 04000) && strcmp(user, owner) != 0) + mode &= ~04000; + gid = -1; + if (gr == NULL || strcmp(group, gr->gr_name) != 0) { + if ((*group == ':' && (getgrgid(gid = atoi(group + 1)) == NULL)) + || ((gr = getgrnam(group)) == NULL)) { + if (mode & 02000) { + note("%s:%s: unknown group", host, group); + mode &= ~02000; + } + } else + gid = gr->gr_gid; + } else + gid = gr->gr_gid; + if (userid && gid >= 0) { + if (gr) for (i = 0; gr->gr_mem[i] != NULL; i++) + if (!(strcmp(user, gr->gr_mem[i]))) + goto ok; + mode &= ~02000; + gid = -1; + } +ok: if (fd != -1 && fchown(fd, uid, gid) < 0 || chown(file, uid, gid) < 0) + note("%s: %s chown: %s", host, file, strerror(errno)); + else if (mode & 07000 && + (fd != -1 && fchmod(fd, mode) < 0 || chmod(file, mode) < 0)) + note("%s: %s chmod: %s", host, file, strerror(errno)); + return(0); +} + +/* + * Check for files on the machine being updated that are not on the master + * machine and remove them. + */ +static void +rmchk(opts) + int opts; +{ + register char *cp, *s; + struct stat stb; + + if (debug) + printf("rmchk()\n"); + + /* + * Tell the remote to clean the files from the last directory sent. + */ + (void) sprintf(buf, "C%o\n", opts & VERIFY); + if (debug) + printf("buf = %s", buf); + (void) write(rem, buf, strlen(buf)); + if (response() < 0) + return; + for (;;) { + cp = s = buf; + do { + if (read(rem, cp, 1) != 1) + lostconn(0); + } while (*cp++ != '\n' && cp < &buf[BUFSIZ]); + + switch (*s++) { + case 'Q': /* Query if file should be removed */ + /* + * Return the following codes to remove query. + * N\n -- file exists - DON'T remove. + * Y\n -- file doesn't exist - REMOVE. + */ + *--cp = '\0'; + (void) sprintf(tp, "/%s", s); + if (debug) + printf("check %s\n", target); + if (except(target)) + (void) write(rem, "N\n", 2); + else if (lstat(target, &stb) < 0) + (void) write(rem, "Y\n", 2); + else + (void) write(rem, "N\n", 2); + break; + + case '\0': + *--cp = '\0'; + if (*s != '\0') + log(lfp, "%s\n", s); + break; + + case 'E': + *tp = '\0'; + ack(); + return; + + case '\1': + case '\2': + nerrs++; + if (*s != '\n') { + if (!iamremote) { + fflush(stdout); + (void) write(2, s, cp - s); + } + if (lfp != NULL) + (void) fwrite(s, 1, cp - s, lfp); + } + if (buf[0] == '\2') + lostconn(0); + break; + + default: + error("rmchk: unexpected response '%s'\n", buf); + err(); + } + } +} + +/* + * Check the current directory (initialized by the 'T' command to server()) + * for extraneous files and remove them. + */ +static void +clean(cp) + register char *cp; +{ + DIR *d; + register struct direct *dp; + struct stat stb; + char *otp; + int len, opts; + + opts = 0; + while (*cp >= '0' && *cp <= '7') + opts = (opts << 3) | (*cp++ - '0'); + if (*cp != '\0') { + error("clean: options not delimited\n"); + return; + } + if ((d = opendir(target)) == NULL) { + error("%s:%s: %s\n", host, target, strerror(errno)); + return; + } + ack(); + + otp = tp; + len = tp - target; + while (dp = readdir(d)) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { + error("%s:%s/%s: Name too long\n", + host, target, dp->d_name); + continue; + } + tp = otp; + *tp++ = '/'; + cp = dp->d_name;; + while (*tp++ = *cp++) + ; + tp--; + if (lstat(target, &stb) < 0) { + error("%s:%s: %s\n", host, target, strerror(errno)); + continue; + } + (void) sprintf(buf, "Q%s\n", dp->d_name); + (void) write(rem, buf, strlen(buf)); + cp = buf; + do { + if (read(rem, cp, 1) != 1) + cleanup(0); + } while (*cp++ != '\n' && cp < &buf[BUFSIZ]); + *--cp = '\0'; + cp = buf; + if (*cp != 'Y') + continue; + if (opts & VERIFY) { + cp = buf; + *cp++ = '\0'; + (void) sprintf(cp, "need to remove: %s\n", target); + (void) write(rem, buf, strlen(cp) + 1); + } else + removeit(&stb); + } + closedir(d); + (void) write(rem, "E\n", 2); + (void) response(); + tp = otp; + *tp = '\0'; +} + +/* + * Remove a file or directory (recursively) and send back an acknowledge + * or an error message. + */ +static void +removeit(stp) + struct stat *stp; +{ + DIR *d; + struct direct *dp; + register char *cp; + struct stat stb; + char *otp; + int len; + + switch (stp->st_mode & S_IFMT) { + case S_IFREG: + case S_IFLNK: + if (unlink(target) < 0) + goto bad; + goto removed; + + case S_IFDIR: + break; + + default: + error("%s:%s: not a plain file\n", host, target); + return; + } + + if ((d = opendir(target)) == NULL) + goto bad; + + otp = tp; + len = tp - target; + while (dp = readdir(d)) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { + error("%s:%s/%s: Name too long\n", + host, target, dp->d_name); + continue; + } + tp = otp; + *tp++ = '/'; + cp = dp->d_name;; + while (*tp++ = *cp++) + ; + tp--; + if (lstat(target, &stb) < 0) { + error("%s:%s: %s\n", host, target, strerror(errno)); + continue; + } + removeit(&stb); + } + closedir(d); + tp = otp; + *tp = '\0'; + if (rmdir(target) < 0) { +bad: + error("%s:%s: %s\n", host, target, strerror(errno)); + return; + } +removed: + cp = buf; + *cp++ = '\0'; + (void) sprintf(cp, "removed %s\n", target); + (void) write(rem, buf, strlen(cp) + 1); +} + +/* + * Execute a shell command to handle special cases. + */ +static void +dospecial(cmd) + char *cmd; +{ + int fd[2], status, pid, i; + register char *cp, *s; + char sbuf[BUFSIZ]; + extern int userid, groupid; + + if (pipe(fd) < 0) { + error("%s\n", strerror(errno)); + return; + } + if ((pid = fork()) == 0) { + /* + * Return everything the shell commands print. + */ + (void) close(0); + (void) close(1); + (void) close(2); + (void) open(_PATH_DEVNULL, O_RDONLY); + (void) dup(fd[1]); + (void) dup(fd[1]); + (void) close(fd[0]); + (void) close(fd[1]); + setgid(groupid); + setuid(userid); + execl(_PATH_BSHELL, "sh", "-c", cmd, 0); + _exit(127); + } + (void) close(fd[1]); + s = sbuf; + *s++ = '\0'; + while ((i = read(fd[0], buf, sizeof(buf))) > 0) { + cp = buf; + do { + *s++ = *cp++; + if (cp[-1] != '\n') { + if (s < &sbuf[sizeof(sbuf)-1]) + continue; + *s++ = '\n'; + } + /* + * Throw away blank lines. + */ + if (s == &sbuf[2]) { + s--; + continue; + } + (void) write(rem, sbuf, s - sbuf); + s = &sbuf[1]; + } while (--i); + } + if (s > &sbuf[1]) { + *s++ = '\n'; + (void) write(rem, sbuf, s - sbuf); + } + while ((i = wait(&status)) != pid && i != -1) + ; + if (i == -1) + status = -1; + (void) close(fd[0]); + if (status) + error("shell returned %d\n", status); + else + ack(); +} + +#if __STDC__ +#include <stdarg.h> +#else +#include <varargs.h> +#endif + +void +#if __STDC__ +log(FILE *fp, const char *fmt, ...) +#else +log(fp, fmt, va_alist) + FILE *fp; + char *fmt; + va_dcl +#endif +{ + va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + /* Print changes locally if not quiet mode */ + if (!qflag) + (void)vprintf(fmt, ap); + + /* Save changes (for mailing) if really updating files */ + if (!(options & VERIFY) && fp != NULL) + (void)vfprintf(fp, fmt, ap); + va_end(ap); +} + +void +#if __STDC__ +error(const char *fmt, ...) +#else +error(fmt, va_alist) + char *fmt; + va_dcl +#endif +{ + static FILE *fp; + va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + + ++nerrs; + if (!fp && !(fp = fdopen(rem, "w"))) + return; + if (iamremote) { + (void)fprintf(fp, "%crdist: ", 0x01); + (void)vfprintf(fp, fmt, ap); + fflush(fp); + } + else { + fflush(stdout); + (void)fprintf(stderr, "rdist: "); + (void)vfprintf(stderr, fmt, ap); + fflush(stderr); + } + if (lfp != NULL) { + (void)fprintf(lfp, "rdist: "); + (void)vfprintf(lfp, fmt, ap); + fflush(lfp); + } + va_end(ap); +} + +void +#if __STDC__ +fatal(const char *fmt, ...) +#else +fatal(fmt, va_alist) + char *fmt; + va_dcl +#endif +{ + static FILE *fp; + va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + + ++nerrs; + if (!fp && !(fp = fdopen(rem, "w"))) + return; + if (iamremote) { + (void)fprintf(fp, "%crdist: ", 0x02); + (void)vfprintf(fp, fmt, ap); + fflush(fp); + } + else { + fflush(stdout); + (void)fprintf(stderr, "rdist: "); + (void)vfprintf(stderr, fmt, ap); + fflush(stderr); + } + if (lfp != NULL) { + (void)fprintf(lfp, "rdist: "); + (void)vfprintf(lfp, fmt, ap); + fflush(lfp); + } + cleanup(0); +} + +static int +response() +{ + char *cp, *s; + char resp[BUFSIZ]; + + if (debug) + printf("response()\n"); + + cp = s = resp; + do { + if (read(rem, cp, 1) != 1) + lostconn(0); + } while (*cp++ != '\n' && cp < &resp[BUFSIZ]); + + switch (*s++) { + case '\0': + *--cp = '\0'; + if (*s != '\0') { + log(lfp, "%s\n", s); + return(1); + } + return(0); + case '\3': + *--cp = '\0'; + log(lfp, "Note: %s\n",s); + return(response()); + + default: + s--; + /* fall into... */ + case '\1': + case '\2': + nerrs++; + if (*s != '\n') { + if (!iamremote) { + fflush(stdout); + (void) write(2, s, cp - s); + } + if (lfp != NULL) + (void) fwrite(s, 1, cp - s, lfp); + } + if (resp[0] == '\2') + lostconn(0); + return(-1); + } +} + +/* + * Remove temporary files and do any cleanup operations before exiting. + */ +void +cleanup(signo) + int signo; +{ + (void) unlink(tempfile); + exit(1); +} + +static void +#if __STDC__ +note(const char *fmt, ...) +#else +note(fmt, va_alist) + char *fmt; + va_dcl +#endif +{ + static char buf[BUFSIZ]; + va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + (void)vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + comment(buf); +} + +static void +comment(s) + char *s; +{ + char c; + + c = '\3'; + write(rem, &c, 1); + write(rem, s, strlen(s)); + c = '\n'; + write(rem, &c, 1); +} |