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