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