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