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