386BSD 0.1 development
authorWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Mon, 10 Feb 1992 19:36:51 +0000 (11:36 -0800)
committerWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Mon, 10 Feb 1992 19:36:51 +0000 (11:36 -0800)
Work on file usr/src/usr.bin/rdist/gram.y
Work on file usr/src/usr.bin/rdist/Makefile
Work on file usr/src/usr.bin/rdist/docmd.c

Co-Authored-By: Lynne Greer Jolitz <ljolitz@cardio.ucsf.edu>
Synthesized-from: 386BSD-0.1

usr/src/usr.bin/rdist/Makefile [new file with mode: 0644]
usr/src/usr.bin/rdist/docmd.c [new file with mode: 0644]
usr/src/usr.bin/rdist/gram.y [new file with mode: 0644]

diff --git a/usr/src/usr.bin/rdist/Makefile b/usr/src/usr.bin/rdist/Makefile
new file mode 100644 (file)
index 0000000..28a9945
--- /dev/null
@@ -0,0 +1,11 @@
+#      @(#)Makefile    5.11 (Berkeley) 3/12/91
+
+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
+
+.include <bsd.prog.mk>
diff --git a/usr/src/usr.bin/rdist/docmd.c b/usr/src/usr.bin/rdist/docmd.c
new file mode 100644 (file)
index 0000000..b99d126
--- /dev/null
@@ -0,0 +1,602 @@
+/*
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)docmd.c    5.8 (Berkeley) 3/1/91";
+#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;
+
+void cleanup(), lostconn();
+
+/*
+ * Do the commands in cmds (initialized by yyparse).
+ */
+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.
+ */
+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.
+ */
+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);
+       setreuid(userid, 0);
+       rem = rcmd(&rhost, port, user, ruser, buf, 0);
+       setreuid(0, userid);
+       if (rem < 0)
+               return(0);
+       cp = buf;
+       if (read(rem, cp, 1) != 1)
+               lostconn();
+       if (*cp == 'V') {
+               do {
+                       if (read(rem, cp, 1) != 1)
+                               lostconn();
+               } 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");
+       closeconn();
+       return(0);
+}
+
+/*
+ * Signal end of previous connection.
+ */
+closeconn()
+{
+       if (debug)
+               printf("closeconn()\n");
+
+       if (rem >= 0) {
+               (void) write(rem, "\2\n", 2);
+               (void) close(rem);
+               rem = -1;
+       }
+}
+
+void
+lostconn()
+{
+       if (iamremote)
+               cleanup();
+       log(lfp, "rdist: lost connection\n");
+       longjmp(env, 1);
+}
+
+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.
+ */
+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: %d\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.
+ */
+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);
+}
+
+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.
+ */
+notify(file, rhost, to, lmod)
+       char *file, *rhost;
+       register struct namelist *to;
+       time_t lmod;
+{
+       register int fd, len;
+       FILE *pf, *popen();
+       struct stat stb;
+
+       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.
+ */
+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.
+ */
+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;
+                       }
+                       if (regexec(file, regcomp(nl->n_name)) > 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/src/usr.bin/rdist/gram.y b/usr/src/usr.bin/rdist/gram.y
new file mode 100644 (file)
index 0000000..e9c87a1
--- /dev/null
@@ -0,0 +1,507 @@
+%{
+/*
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)gram.y     5.6 (Berkeley) 6/1/90";
+#endif /* not lint */
+
+#include "defs.h"
+
+struct cmd *cmds = NULL;
+struct cmd *last_cmd;
+struct namelist *last_n;
+struct subcmd *last_sc;
+
+%}
+
+%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;
+#ifdef nope
+                       char *cp, *re_comp();
+
+                       for (nl = $2; nl != NULL; nl = nl->n_next)
+                               if ((cp = re_comp(nl->n_name)) != NULL)
+                                       yyerror(cp);
+#endif
+                       $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;
+
+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);
+}
+
+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.
+ */
+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.
+ */
+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.
+ */
+yyerror(s)
+       char *s;
+{
+       extern int yychar;
+
+       nerrs++;
+       fflush(stdout);
+       fprintf(stderr, "rdist: line %d: %s\n", yylineno, s);
+}
+
+/*
+ * Return a copy of the string.
+ */
+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, name)
+       int type;
+       register char *name;
+{
+       register char *cp;
+       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);
+}