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