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