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