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