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