xref: /openbsd/usr.bin/rdist/docmd.c (revision d485f761)
1 /*	$OpenBSD: docmd.c,v 1.9 2001/06/23 23:08:16 millert Exp $	*/
2 
3 /*
4  * Copyright (c) 1983 Regents of the University of California.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #ifndef lint
37 #if 0
38 static char RCSid[] =
39 "$From: docmd.c,v 6.86 1996/01/30 02:29:43 mcooper Exp $";
40 #else
41 static char RCSid[] =
42 "$OpenBSD: docmd.c,v 1.9 2001/06/23 23:08:16 millert Exp $";
43 #endif
44 
45 static char sccsid[] = "@(#)docmd.c	5.1 (Berkeley) 6/6/85";
46 
47 static char copyright[] =
48 "@(#) Copyright (c) 1983 Regents of the University of California.\n\
49  All rights reserved.\n";
50 #endif /* not lint */
51 
52 /*
53  * Functions for rdist that do command (cmd) related activities.
54  */
55 
56 #include "defs.h"
57 #include "y.tab.h"
58 #include <sys/socket.h>
59 #include <netdb.h>
60 
61 struct subcmd	       *subcmds;		/* list of sub-commands for
62 						   current cmd */
63 struct namelist	       *filelist;		/* list of source files */
64 extern struct cmd      *cmds;			/* Initialized by yyparse() */
65 time_t			lastmod;		/* Last modify time */
66 
67 extern char 		target[];
68 extern char 	       *ptarget;
69 extern int		activechildren;
70 extern int		maxchildren;
71 extern int		amchild;
72 extern char	       *path_rdistd;
73 
74 static void cmptime();
75 
76 /*
77  * Signal end of connection.
78  */
79 static void closeconn()
80 {
81 	debugmsg(DM_CALL, "closeconn() called\n");
82 
83 	if (rem_w >= 0) {
84 		/* We don't care if the connection is still good or not */
85 		signal(SIGPIPE, SIG_IGN);
86 
87 		(void) sendcmd(C_FERRMSG, NULL);
88 		(void) close(rem_w);
89 		(void) close(rem_r); /* This can't hurt */
90 		rem_w = -1;
91 		rem_r = -1;
92 	}
93 }
94 
95 /*
96  * Notify the list of people the changes that were made.
97  * rhost == NULL if we are mailing a list of changes compared to at time
98  * stamp file.
99  */
100 static void notify(rhost, to, lmod)
101 	char *rhost;
102 	register struct namelist *to;
103 	time_t lmod;
104 {
105 	register int fd, len;
106 	FILE *pf, *popen();
107 	struct stat stb;
108 	static char buf[BUFSIZ];
109 	char *file;
110 
111 	if (IS_ON(options, DO_VERIFY) || to == NULL)
112 		return;
113 
114 	if ((file = getnotifyfile()) == NULL)
115 		return;
116 
117 	if (!IS_ON(options, DO_QUIET)) {
118 		message(MT_INFO, "notify %s%s %s",
119 			(rhost) ? "@" : "",
120 			(rhost) ? rhost : "", getnlstr(to));
121 	}
122 
123 	if (nflag)
124 		return;
125 
126 	debugmsg(DM_MISC, "notify() temp file = '%s'", file);
127 
128 	if ((fd = open(file, O_RDONLY)) < 0) {
129 		error("%s: open for reading failed: %s", file, SYSERR);
130 		return;
131 	}
132 	if (fstat(fd, &stb) < 0) {
133 		error("%s: fstat failed: %s", file, SYSERR);
134 		(void) close(fd);
135 		return;
136 	}
137 	if (stb.st_size == 0) {
138 		(void) close(fd);
139 		return;
140 	}
141 	/*
142 	 * Create a pipe to mailling program.
143 	 * Set IFS to avoid possible security problem with users
144 	 * setting "IFS=/".
145 	 */
146 	(void) sprintf(buf, "IFS=\" \t\"; export IFS; %s -oi -t",
147 		       _PATH_SENDMAIL);
148 	pf = popen(buf, "w");
149 	if (pf == NULL) {
150 		error("notify: \"%s\" failed\n", _PATH_SENDMAIL);
151 		(void) unlink(file);
152 		(void) close(fd);
153 		return;
154 	}
155 	/*
156 	 * Output the proper header information.
157 	 */
158 	(void) fprintf(pf, "From: rdist (Remote distribution program)\n");
159 	(void) fprintf(pf, "To:");
160 	if (!any('@', to->n_name) && rhost != NULL)
161 		(void) fprintf(pf, " %s@%s", to->n_name, rhost);
162 	else
163 		(void) fprintf(pf, " %s", to->n_name);
164 	to = to->n_next;
165 	while (to != NULL) {
166 		if (!any('@', to->n_name) && rhost != NULL)
167 			(void) fprintf(pf, ", %s@%s", to->n_name, rhost);
168 		else
169 			(void) fprintf(pf, ", %s", to->n_name);
170 		to = to->n_next;
171 	}
172 	(void) putc('\n', pf);
173 	if (rhost != NULL)
174 		(void) fprintf(pf,
175 			     "Subject: files updated by rdist from %s to %s\n",
176 			       host, rhost);
177 	else
178 		(void) fprintf(pf, "Subject: files updated after %s\n",
179 			       ctime(&lmod));
180 	(void) putc('\n', pf);
181 	(void) putc('\n', pf);
182 
183 	while ((len = read(fd, buf, sizeof(buf))) > 0)
184 		(void) fwrite(buf, 1, len, pf);
185 
186 	(void) pclose(pf);
187 	(void) close(fd);
188 	(void) unlink(file);
189 }
190 
191 /*
192  * XXX Hack for NFS.  If a hostname from the distfile
193  * ends with a '+', then the normal restriction of
194  * skipping files that are on an NFS filesystem is
195  * bypassed.  We always strip '+' to be consistent.
196  */
197 static void checkcmd(cmd)
198 	struct cmd *cmd;
199 {
200 	int l;
201 
202 	if (!cmd || !(cmd->c_name)) {
203 		debugmsg(DM_MISC, "checkcmd() NULL cmd parameter");
204 		return;
205 	}
206 
207 	l = strlen(cmd->c_name);
208 	if (l <= 0)
209 		return;
210 	if (cmd->c_name[l-1] == '+') {
211 		cmd->c_flags |= CMD_NOCHKNFS;
212 		cmd->c_name[l-1] = CNULL;
213 	}
214 }
215 
216 /*
217  * Mark all other entries for this command (cmd)
218  * as assigned.
219  */
220 extern void markassigned(cmd, cmdlist)
221 	struct cmd *cmd;
222 	struct cmd *cmdlist;
223 {
224 	register struct cmd *pcmd;
225 
226 	for (pcmd = cmdlist; pcmd; pcmd = pcmd->c_next) {
227 		checkcmd(pcmd);
228 		if (pcmd->c_type == cmd->c_type &&
229 		    strcmp(pcmd->c_name, cmd->c_name)==0)
230 			pcmd->c_flags |= CMD_ASSIGNED;
231 	}
232 }
233 
234 /*
235  * Mark the command "cmd" as failed for all commands in list cmdlist.
236  */
237 static void markfailed(cmd, cmdlist)
238 	struct cmd *cmd;
239 	struct cmd *cmdlist;
240 {
241 	register struct cmd *pc;
242 
243 	if (!cmd) {
244 		debugmsg(DM_MISC, "markfailed() NULL cmd parameter");
245 		return;
246 	}
247 
248 	checkcmd(cmd);
249 	cmd->c_flags |= CMD_CONNFAILED;
250 	for (pc = cmdlist; pc; pc = pc->c_next) {
251 		checkcmd(pc);
252 		if (pc->c_type == cmd->c_type &&
253 		    strcmp(pc->c_name, cmd->c_name)==0)
254 			pc->c_flags |= CMD_CONNFAILED;
255 	}
256 }
257 
258 static int remotecmd(rhost, luser, ruser, cmd)
259 	char *rhost;
260 	char *luser, *ruser;
261 	char *cmd;
262 {
263 	int desc;
264 #if	defined(DIRECT_RCMD)
265 	static int port = -1;
266 #endif	/* DIRECT_RCMD */
267 
268 	debugmsg(DM_MISC, "local user = %s remote user = %s\n", luser, ruser);
269 	debugmsg(DM_MISC, "Remote command = '%s'\n", cmd);
270 
271 	(void) fflush(stdout);
272 	(void) fflush(stderr);
273 	(void) signal(SIGALRM, sighandler);
274 	(void) alarm(RTIMEOUT);
275 
276 #if	defined(DIRECT_RCMD)
277 	(void) signal(SIGPIPE, sighandler);
278 
279 	if (port < 0) {
280 		struct servent *sp;
281 
282 		if ((sp = getservbyname("shell", "tcp")) == NULL)
283 				fatalerr("shell/tcp: unknown service");
284 		port = sp->s_port;
285 	}
286 
287 	if (becomeroot() != 0)
288 		exit(1);
289 	desc = rcmd(&rhost, port, luser, ruser, cmd, 0);
290 	if (becomeuser() != 0)
291 		exit(1);
292 #else	/* !DIRECT_RCMD */
293 	debugmsg(DM_MISC, "Remote shell command = '%s'\n",
294 	    path_remsh ? path_remsh : "default");
295 	(void) signal(SIGPIPE, SIG_IGN);
296 	desc = rcmdsh(&rhost, -1, luser, ruser, cmd, path_remsh);
297 	if (desc > 0)
298 		(void) signal(SIGPIPE, sighandler);
299 #endif	/* DIRECT_RCMD */
300 
301 	(void) alarm(0);
302 
303 	return(desc);
304 }
305 
306 /*
307  * Create a connection to the rdist server on the machine rhost.
308  * Return 0 if the connection fails or 1 if it succeeds.
309  */
310 static int makeconn(rhost)
311 	char *rhost;
312 {
313 	register char *ruser, *cp;
314 	static char *cur_host = NULL;
315 	extern char *locuser;
316 	extern long min_freefiles, min_freespace;
317 	extern char *remotemsglist;
318 	char tuser[BUFSIZ], buf[BUFSIZ];
319 	u_char respbuff[BUFSIZ] = "";
320 	int n;
321 
322 	debugmsg(DM_CALL, "makeconn(%s)", rhost);
323 
324 	/*
325 	 * See if we're already connected to this host
326 	 */
327 	if (cur_host != NULL && rem_w >= 0) {
328 		if (strcmp(cur_host, rhost) == 0)
329 			return(1);
330 		closeconn();
331 	}
332 
333 	/*
334 	 * Determine remote user and current host names
335 	 */
336 	cur_host = rhost;
337 	cp = strchr(rhost, '@');
338 
339 	if (cp != NULL) {
340 		char c = *cp;
341 
342 		*cp = CNULL;
343 		(void) strncpy((char *)tuser, rhost, sizeof(tuser)-1);
344 		*cp = c;
345 		rhost = cp + 1;
346 		ruser = tuser;
347 		if (*ruser == CNULL)
348 			ruser = locuser;
349 		else if (!okname(ruser))
350 			return(0);
351 	} else
352 		ruser = locuser;
353 
354 	if (!IS_ON(options, DO_QUIET))
355 		message(MT_VERBOSE, "updating host %s", rhost);
356 
357 	(void) sprintf(buf, "%.*s -S", sizeof(buf)-5, path_rdistd);
358 
359 	if ((rem_r = rem_w = remotecmd(rhost, locuser, ruser, buf)) < 0)
360 		return(0);
361 
362 	/*
363 	 * First thing received should be S_VERSION
364 	 */
365 	n = remline(respbuff, sizeof(respbuff), TRUE);
366 	if (n <= 0 || respbuff[0] != S_VERSION) {
367 		error("Unexpected input from server: \"%s\".", respbuff);
368 		closeconn();
369 		return(0);
370 	}
371 
372 	/*
373 	 * For future compatibility we check to see if the server
374 	 * sent it's version number to us.  If it did, we use it,
375 	 * otherwise, we send our version number to the server and let
376 	 * it decide if it can handle our protocol version.
377 	 */
378 	if (respbuff[1] == CNULL) {
379 		/*
380 		 * The server wants us to send it our version number
381 		 */
382 		(void) sendcmd(S_VERSION, "%d", VERSION);
383 		if (response() < 0)
384 			return(0);
385 	} else {
386 		/*
387 		 * The server sent it's version number to us
388 		 */
389 		proto_version = atoi(&respbuff[1]);
390 		if (proto_version != VERSION) {
391 			fatalerr(
392 		  "Server version (%d) is not the same as local version (%d).",
393 			      proto_version, VERSION);
394 			return(0);
395 		}
396 	}
397 
398 	/*
399 	 * Send config commands
400 	 */
401 	if (host[0]) {
402 		(void) sendcmd(C_SETCONFIG, "%c%s", SC_HOSTNAME, host);
403 		if (response() < 0)
404 			return(0);
405 	}
406 	if (min_freespace) {
407 		(void) sendcmd(C_SETCONFIG, "%c%d", SC_FREESPACE,
408 			       min_freespace);
409 		if (response() < 0)
410 			return(0);
411 	}
412 	if (min_freefiles) {
413 		(void) sendcmd(C_SETCONFIG, "%c%d", SC_FREEFILES,
414 			       min_freefiles);
415 		if (response() < 0)
416 			return(0);
417 	}
418 	if (remotemsglist) {
419 		(void) sendcmd(C_SETCONFIG, "%c%s", SC_LOGGING, remotemsglist);
420 		if (response() < 0)
421 			return(0);
422 	}
423 
424 	return(1);
425 }
426 
427 /*
428  * Process commands for sending files to other machines.
429  */
430 static void doarrow(cmd, filev)
431 	struct cmd *cmd;
432 	char **filev;
433 {
434 	register struct namelist *f;
435 	register struct subcmd *sc;
436 	register char **cpp;
437 	int n, ddir, destdir;
438 	struct namelist *files;
439 	struct subcmd *sbcmds;
440 	char *rhost;
441 	volatile int opts = options;
442 	volatile int didupdate = 0;
443 
444         if (setjmp_ok) {
445 		error("reentrant call to doarrow");
446 		abort();
447 	}
448 
449 	if (!cmd) {
450 		debugmsg(DM_MISC, "doarrow() NULL cmd parameter");
451 		return;
452 	}
453 
454 	files = cmd->c_files;
455 	sbcmds = cmd->c_cmds;
456 	rhost = cmd->c_name;
457 
458 	if (files == NULL) {
459 		error("No files to be updated on %s for target \"%s\"",
460 		      rhost, cmd->c_label);
461 		return;
462 	}
463 
464 	debugmsg(DM_CALL, "doarrow(%x, %s, %x) start",
465 		 files, A(rhost), sbcmds);
466 
467 	if (nflag)
468 		(void) printf("updating host %s\n", rhost);
469 	else {
470 		if (cmd->c_flags & CMD_CONNFAILED) {
471 			debugmsg(DM_MISC,
472 				 "makeconn %s failed before; skipping\n",
473 				 rhost);
474 			return;
475 		}
476 
477 		if (setjmp(finish_jmpbuf)) {
478 			setjmp_ok = FALSE;
479 			debugmsg(DM_MISC, "setjmp to finish_jmpbuf");
480 			markfailed(cmd, cmds);
481 			return;
482 		}
483 		setjmp_ok = TRUE;
484 
485 		if (!makeconn(rhost)) {
486 			setjmp_ok = FALSE;
487 			markfailed(cmd, cmds);
488 			return;
489 		}
490 	}
491 
492 	subcmds = sbcmds;
493 	filelist = files;
494 
495 	n = 0;
496 	for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
497 		if (sc->sc_type != INSTALL)
498 			continue;
499 		n++;
500 	/*
501 	 * destination is a directory if one of the following is true:
502 	 * a) more than one name specified on left side of -> directive
503 	 * b) basename of destination in "install" directive is "."
504 	 *    (e.g. install /tmp/.;)
505 	 * c) name on left side of -> directive is a directory on local system.
506  	 *
507  	 * We need 2 destdir flags (destdir and ddir) because single directory
508  	 * source is handled differently.  In this case, ddir is 0 (which
509  	 * tells install() not to send DIRTARGET directive to remote rdistd)
510  	 * and destdir is 1 (which tells remfilename() how to build the FILE
511  	 * variables correctly).  In every other case, destdir and ddir will
512  	 * have the same value.
513 	 */
514   	ddir = files->n_next != NULL;	/* destination is a directory */
515 	if (!ddir) {
516 		struct stat s;
517  		int isadir = 0;
518 
519 		if (lstat(files->n_name, &s) == 0)
520  			isadir = S_ISDIR(s.st_mode);
521  		if (!isadir && sc->sc_name && *sc->sc_name)
522  			ddir = !strcmp(xbasename(sc->sc_name),".");
523  		destdir = isadir | ddir;
524  	} else
525  		destdir = ddir;
526 
527 	debugmsg(DM_MISC,
528 		 "Debug files->n_next= %d, destdir=%d, ddir=%d",
529 		 files->n_next, destdir, ddir);
530 
531 	if (!sc->sc_name || !*sc->sc_name) {
532 		destdir = 0;
533 		ddir = 0;
534 	}
535 
536 	debugmsg(DM_MISC,
537 		 "Debug sc->sc_name=%x, destdir=%d, ddir=%d",
538 		 sc->sc_name, destdir, ddir);
539 
540 	for (f = files; f != NULL; f = f->n_next) {
541 		if (filev) {
542 			for (cpp = filev; *cpp; cpp++)
543 				if (strcmp(f->n_name, *cpp) == 0)
544 					goto found;
545 			continue;
546 		}
547 	found:
548 		if (install(f->n_name, sc->sc_name, ddir, destdir,
549 				sc->sc_options) > 0)
550 			++didupdate;
551 		opts = sc->sc_options;
552 	}
553 
554 	} /* end loop for each INSTALL command */
555 
556 	/* if no INSTALL commands present, do default install */
557 	if (!n) {
558 		for (f = files; f != NULL; f = f->n_next) {
559 			if (filev) {
560 				for (cpp = filev; *cpp; cpp++)
561 					if (strcmp(f->n_name, *cpp) == 0)
562 						goto found2;
563 				continue;
564 			}
565 		found2:
566 			/* ddir & destdir set to zero for default install */
567 			if (install(f->n_name, NULL, 0, 0, options) > 0)
568 				++didupdate;
569 		}
570 	}
571 
572 done:
573 	/*
574 	 * Run any commands for the entire cmd
575 	 */
576 	if (didupdate > 0) {
577 		runcmdspecial(cmd, filev, opts);
578 		didupdate = 0;
579 	}
580 
581 	if (!nflag)
582 		(void) signal(SIGPIPE, cleanup);
583 
584 	for (sc = sbcmds; sc != NULL; sc = sc->sc_next)
585 		if (sc->sc_type == NOTIFY)
586 			notify(rhost, sc->sc_args, (time_t) 0);
587 
588 	if (!nflag) {
589 		register struct linkbuf *nextl, *l;
590 
591 		for (l = ihead; l != NULL; freelinkinfo(l), l = nextl) {
592 			nextl = l->nextp;
593 			if (contimedout || IS_ON(opts, DO_IGNLNKS) ||
594 			    l->count == 0)
595 				continue;
596 			message(MT_WARNING, "%s: Warning: %d %s link%s",
597 				l->pathname, abs(l->count),
598 				(l->count > 0) ? "missing" : "extra",
599 				(l->count == 1) ? "" : "s");
600 		}
601 		ihead = NULL;
602 	}
603 	setjmp_ok = FALSE;
604 }
605 
606 int
607 okname(name)
608 	register char *name;
609 {
610 	register char *cp = name;
611 	register int c, isbad;
612 
613 	for (isbad = FALSE; *cp && !isbad; ++cp) {
614 		c = *cp;
615 		if (c & 0200)
616 			isbad = TRUE;
617 		if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-')
618 			isbad = TRUE;
619 	}
620 
621 	if (isbad) {
622 		error("Invalid user name \"%s\"\n", name);
623 		return(0);
624 	}
625 	return(1);
626 }
627 
628 static void rcmptime(st, sbcmds, env)
629 	struct stat *st;
630 	struct subcmd *sbcmds;
631 	char **env;
632 {
633 	register DIR *d;
634 	register DIRENTRY *dp;
635 	register char *cp;
636 	char *optarget;
637 	int len;
638 
639 	debugmsg(DM_CALL, "rcmptime(%x) start", st);
640 
641 	if ((d = opendir((char *) target)) == NULL) {
642 		error("%s: open directory failed: %s", target, SYSERR);
643 		return;
644 	}
645 	optarget = ptarget;
646 	len = ptarget - target;
647 	while ((dp = readdir(d))) {
648 		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
649 			continue;
650 		if (len + 1 + (int)strlen(dp->d_name) >= BUFSIZ - 1) {
651 			error("%s/%s: Name too long\n", target, dp->d_name);
652 			continue;
653 		}
654 		ptarget = optarget;
655 		*ptarget++ = '/';
656 		cp = dp->d_name;
657 		while ((*ptarget++ = *cp++))
658 			;
659 		ptarget--;
660 		cmptime(target, sbcmds, env);
661 	}
662 	(void) closedir((DIR *) d);
663 	ptarget = optarget;
664 	*ptarget = '\0';
665 }
666 
667 /*
668  * Compare the mtime of file to the list of time stamps.
669  */
670 static void cmptime(name, sbcmds, env)
671 	char *name;
672 	struct subcmd *sbcmds;
673 	char **env;
674 {
675 	struct subcmd *sc;
676 	struct stat stb;
677 	int inlist();
678 
679 	debugmsg(DM_CALL, "cmptime(%s)", name);
680 
681 	if (except(name))
682 		return;
683 
684 	if (nflag) {
685 		(void) printf("comparing dates: %s\n", name);
686 		return;
687 	}
688 
689 	/*
690 	 * first time cmptime() is called?
691 	 */
692 	if (ptarget == NULL) {
693 		if (exptilde(target, name) == NULL)
694 			return;
695 		ptarget = name = target;
696 		while (*ptarget)
697 			ptarget++;
698 	}
699 	if (access(name, R_OK) < 0 || stat(name, &stb) < 0) {
700 		error("%s: cannot access file: %s", name, SYSERR);
701 		return;
702 	}
703 
704 	if (S_ISDIR(stb.st_mode)) {
705 		rcmptime(&stb, sbcmds, env);
706 		return;
707 	} else if (!S_ISREG(stb.st_mode)) {
708 		error("%s: not a plain file", name);
709 		return;
710 	}
711 
712 	if (stb.st_mtime > lastmod) {
713 		message(MT_INFO, "%s: file is newer", name);
714 		for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
715 			char buf[BUFSIZ];
716 			if (sc->sc_type != SPECIAL)
717 				continue;
718 			if (sc->sc_args != NULL && !inlist(sc->sc_args, name))
719 				continue;
720 			(void) sprintf(buf, "%s=%s;%s",
721 				       E_LOCFILE, name, sc->sc_name);
722 			message(MT_CHANGE, "special \"%s\"", buf);
723 			if (*env) {
724 				int len = strlen(*env);
725 				*env = (char *) xrealloc(*env, len +
726 							 strlen(name) + 2);
727 				*env[len] = CNULL;
728 				(void) strcat(*env, name);
729 				(void) strcat(*env, ":");
730 			}
731 			if (IS_ON(options, DO_VERIFY))
732 				continue;
733 
734 			runcommand(buf);
735 		}
736 	}
737 }
738 
739 /*
740  * Process commands for comparing files to time stamp files.
741  */
742 static void dodcolon(cmd, filev)
743 	struct cmd *cmd;
744 	char **filev;
745 {
746 	register struct subcmd *sc;
747 	register struct namelist *f;
748 	register char *cp, **cpp;
749 	struct stat stb;
750 	struct namelist *files = cmd->c_files;
751 	struct subcmd *sbcmds = cmd->c_cmds;
752 	char *env, *stamp = cmd->c_name;
753 
754 	debugmsg(DM_CALL, "dodcolon()");
755 
756 	if (files == NULL) {
757 		error("No files to be updated for target \"%s\"",
758 		      cmd->c_label);
759 		return;
760 	}
761 	if (stat(stamp, &stb) < 0) {
762 		error("%s: stat failed: %s", stamp, SYSERR);
763 		return;
764 	}
765 
766 	debugmsg(DM_MISC, "%s: mtime %d\n", stamp, stb.st_mtime);
767 
768 	env = NULL;
769 	for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
770 		if (sc->sc_type == CMDSPECIAL) {
771 			env = (char *) xmalloc(sizeof(E_FILES) + 3);
772 			(void) sprintf(env, "%s='", E_FILES);
773 			break;
774 		}
775 	}
776 
777 	subcmds = sbcmds;
778 	filelist = files;
779 
780 	lastmod = stb.st_mtime;
781 	if (!nflag && !IS_ON(options, DO_VERIFY))
782 		/*
783 		 * Set atime and mtime to current time
784 		 */
785 		(void) setfiletime(stamp, (time_t) 0, (time_t) 0);
786 
787 	for (f = files; f != NULL; f = f->n_next) {
788 		if (filev) {
789 			for (cpp = filev; *cpp; cpp++)
790 				if (strcmp(f->n_name, *cpp) == 0)
791 					goto found;
792 			continue;
793 		}
794 	found:
795 		ptarget = NULL;
796 		cmptime(f->n_name, sbcmds, &env);
797 	}
798 
799 	for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
800 		if (sc->sc_type == NOTIFY)
801 			notify(NULL, sc->sc_args, (time_t)lastmod);
802 		else if (sc->sc_type == CMDSPECIAL && env) {
803 			char *p;
804 			int len = strlen(env);
805 
806 			env = xrealloc(env,
807 				       len + strlen(sc->sc_name) + 2);
808 			env[len] = CNULL;
809 			if (*(p = &env[len - 1]) == ':')
810 				*p = CNULL;
811 			(void) strcat(env, "';");
812 			(void) strcat(env, sc->sc_name);
813 			message(MT_CHANGE, "cmdspecial \"%s\"", env);
814 			if (!nflag && IS_OFF(options, DO_VERIFY))
815 				runcommand(env);
816 			(void) free(env);
817 			env = NULL;	/* so cmdspecial is only called once */
818 		}
819 	}
820 	if (!nflag && !IS_ON(options, DO_VERIFY) && (cp = getnotifyfile()))
821 		(void) unlink(cp);
822 }
823 
824 /*
825  * Return TRUE if file is in the exception list.
826  */
827 extern int except(file)
828 	char *file;
829 {
830 	register struct	subcmd *sc;
831 	register struct	namelist *nl;
832 
833 	debugmsg(DM_CALL, "except(%s)", file);
834 
835 	for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
836 		if (sc->sc_type == EXCEPT) {
837 			for (nl = sc->sc_args; nl != NULL; nl = nl->n_next)
838 				if (strcmp(file, nl->n_name) == 0)
839 					return(1);
840   			continue;
841 		}
842 		if (sc->sc_type == PATTERN) {
843 			for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) {
844 				char *cp, *re_comp();
845 
846 				if ((cp = re_comp(nl->n_name)) != NULL) {
847 					error("Regex error \"%s\" for \"%s\".",
848 					      cp, nl->n_name);
849 					return(0);
850 				}
851 				if (re_exec(file) > 0)
852   					return(1);
853 			}
854 		}
855 	}
856 	return(0);
857 }
858 
859 /*
860  * Do a specific command for a specific host
861  */
862 static void docmdhost(cmd, filev)
863 	struct cmd *cmd;
864 	char **filev;
865 {
866 	checkcmd(cmd);
867 
868 	/*
869 	 * If we're multi-threaded and we're the parent, spawn a
870 	 * new child process.
871 	 */
872 	if (do_fork && !amchild) {
873 		int pid;
874 
875 		/*
876 		 * If we're at maxchildren, wait for number of active
877 		 * children to fall below max number of children.
878 		 */
879 		while (activechildren >= maxchildren)
880 			waitup();
881 
882 		pid = spawn(cmd, cmds);
883 		if (pid == 0)
884 			/* Child */
885 			amchild = 1;
886 		else
887 			/* Parent */
888 			return;
889 	}
890 
891 	/*
892 	 * Disable NFS checks
893 	 */
894 	if (cmd->c_flags & CMD_NOCHKNFS)
895 		FLAG_OFF(options, DO_CHKNFS);
896 
897 	if (!nflag) {
898 		currenthost = (cmd->c_name) ? cmd->c_name : "<unknown>";
899 #if	defined(SETARGS) || defined(HAVE_SETPROCTITLE)
900 		setproctitle("update %s", currenthost);
901 #endif 	/* SETARGS || HAVE_SETPROCTITLE */
902 	}
903 
904 	switch (cmd->c_type) {
905 	case ARROW:
906 		doarrow(cmd, filev);
907 		break;
908 	case DCOLON:
909 		dodcolon(cmd, filev);
910 		break;
911 	default:
912 		fatalerr("illegal command type %d", cmd->c_type);
913 	}
914 }
915 
916 /*
917  * Do a specific command (cmd)
918  */
919 static void docmd(cmd, argc, argv)
920 	struct cmd *cmd;
921 	int argc;
922 	char **argv;
923 {
924 	register struct namelist *f;
925 	register int i;
926 
927 	if (argc) {
928 		for (i = 0; i < argc; i++) {
929 			if (cmd->c_label != NULL &&
930 			    strcmp(cmd->c_label, argv[i]) == 0) {
931 				docmdhost(cmd, NULL);
932 				return;
933 			}
934 			for (f = cmd->c_files; f != NULL; f = f->n_next)
935 				if (strcmp(f->n_name, argv[i]) == 0) {
936 					docmdhost(cmd, &argv[i]);
937 					return;
938 				}
939 		}
940 	} else
941 		docmdhost(cmd, NULL);
942 }
943 
944 /*
945  *
946  * Multiple hosts are updated at once via a "ring" of at most
947  * maxchildren rdist processes.  The parent rdist fork()'s a child
948  * for a given host.  That child will update the given target files
949  * and then continue scanning through the remaining targets looking
950  * for more work for a given host.  Meanwhile, the parent gets the
951  * next target command and makes sure that it hasn't encountered
952  * that host yet since the children are responsible for everything
953  * for that host.  If no children have done this host, then check
954  * to see if the number of active proc's is less than maxchildren.
955  * If so, then spawn a new child for that host.  Otherwise, wait
956  * for a child to finish.
957  *
958  */
959 
960 /*
961  * Do the commands in cmds (initialized by yyparse).
962  */
963 extern void docmds(hostlist, argc, argv)
964 	struct namelist *hostlist;
965 	int argc;
966 	char **argv;
967 {
968 	register struct cmd *c;
969 	register char *cp;
970 	register int i;
971 
972 	(void) signal(SIGHUP, sighandler);
973 	(void) signal(SIGINT, sighandler);
974 	(void) signal(SIGQUIT, sighandler);
975 	(void) signal(SIGTERM, sighandler);
976 
977 	if (!nflag)
978 		mysetlinebuf(stdout);	/* Make output (mostly) clean */
979 
980 #if	defined(USE_STATDB)
981 	if (!nflag && (dostatdb || juststatdb)) {
982 		extern long reccount;
983 		message(MT_INFO, "Making stat database [%s] ... \n",
984 			       gettimestr());
985 		if (mkstatdb() < 0)
986 			error("Warning: Make stat database failed.");
987 		message(MT_INFO,
988 			      "Stat database created: %d files stored [%s].\n",
989 			       reccount, gettimestr());
990 		if (juststatdb)
991 			return;
992 	}
993 #endif	/* USE_STATDB */
994 
995 	/*
996 	 * Print errors for any command line targets we didn't find.
997 	 * If any errors are found, return to main() which will then exit.
998 	 */
999 	for (i = 0; i < argc; i++) {
1000 		int found;
1001 
1002 		for (found = FALSE, c = cmds; c != NULL; c = c->c_next) {
1003 			if (c->c_label && argv[i] &&
1004 			    strcmp(c->c_label, argv[i]) == 0) {
1005 				found = TRUE;
1006 				break;
1007 			}
1008 		}
1009 		if (!found)
1010 			error("Label \"%s\" is not defined in the distfile.",
1011 			      argv[i]);
1012 	}
1013 	if (nerrs)
1014 		return;
1015 
1016 	/*
1017 	 * Main command loop.  Loop through all the commands.
1018 	 */
1019 	for (c = cmds; c != NULL; c = c->c_next) {
1020 		checkcmd(c);
1021 		if (do_fork) {
1022 			/*
1023 			 * Let the children take care of their assigned host
1024 			 */
1025 			if (amchild) {
1026 				if (strcmp(c->c_name, currenthost) != 0)
1027 					continue;
1028 			} else if (c->c_flags & CMD_ASSIGNED) {
1029 				/* This cmd has been previously assigned */
1030 				debugmsg(DM_MISC, "prev assigned: %s\n",
1031 					 c->c_name);
1032 				continue;
1033 			}
1034 		}
1035 
1036 		if (hostlist) {
1037 			/* Do specific hosts as specified on command line */
1038 			register struct namelist *nlptr;
1039 
1040 			for (nlptr = hostlist; nlptr; nlptr = nlptr->n_next)
1041 				/*
1042 				 * Try an exact match and then a match
1043 				 * without '@' (if present).
1044 				 */
1045 				if ((strcmp(c->c_name, nlptr->n_name) == 0) ||
1046 				    ((cp = strchr(c->c_name, '@')) &&
1047 				     strcmp(++cp, nlptr->n_name) == 0))
1048 					docmd(c, argc, argv);
1049 			continue;
1050 		} else
1051 			/* Do all of the command */
1052 			docmd(c, argc, argv);
1053 	}
1054 
1055 	if (do_fork) {
1056 		/*
1057 		 * We're multi-threaded, so do appropriate shutdown
1058 		 * actions based on whether we're the parent or a child.
1059 		 */
1060 		if (amchild) {
1061 			if (!IS_ON(options, DO_QUIET))
1062 				message(MT_VERBOSE, "updating of %s finished",
1063 					currenthost);
1064 			closeconn();
1065 			cleanup();
1066 			exit(nerrs);
1067 		}
1068 
1069 		/*
1070 		 * Wait for all remaining active children to finish
1071 		 */
1072 		while (activechildren > 0) {
1073 			debugmsg(DM_MISC,
1074 				 "Waiting for %d children to finish.\n",
1075 				 activechildren);
1076 			waitup();
1077 		}
1078 	} else if (!nflag) {
1079 		/*
1080 		 * We're single-threaded so close down current connection
1081 		 */
1082 		closeconn();
1083 		cleanup();
1084 	}
1085 }
1086