1 /* $NetBSD: server.c,v 1.24 2002/12/06 03:09:28 thorpej Exp $ */ 2 3 /* 4 * Copyright (c) 1983, 1993 5 * The Regents of the University of California. 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 #include <sys/cdefs.h> 37 #ifndef lint 38 #if 0 39 static char sccsid[] = "@(#)server.c 8.1 (Berkeley) 6/9/93"; 40 #else 41 __RCSID("$NetBSD: server.c,v 1.24 2002/12/06 03:09:28 thorpej Exp $"); 42 #endif 43 #endif /* not lint */ 44 45 #include <sys/types.h> 46 #include <sys/wait.h> 47 48 #include <errno.h> 49 #include <fcntl.h> 50 #include <grp.h> 51 #include <pwd.h> 52 #include <stdarg.h> 53 54 #include "defs.h" 55 56 #define ack() do { if (write(rem, "\0\n", 2) < 0) error("ack failed: %s\n", strerror(errno)); } while (0) 57 #define err() do { if (write(rem, "\1\n", 2) < 0) error("err failed: %s\n", strerror(errno)); } while (0) 58 59 struct linkbuf *ihead; /* list of files with more than one link */ 60 char buf[BUFSIZ]; /* general purpose buffer */ 61 char target[BUFSIZ]; /* target/source directory name */ 62 char *tp; /* pointer to end of target name */ 63 char *Tdest; /* pointer to last T dest*/ 64 int catname; /* cat name to target name */ 65 char *stp[32]; /* stack of saved tp's for directories */ 66 int oumask; /* old umask for creating files */ 67 68 extern FILE *lfp; /* log file for mailing changes */ 69 70 static int chkparent(char *); 71 static void clean(char *); 72 static void comment(char *); 73 static void dospecial(char *); 74 static int fchtogm(int, char *, time_t, char *, char *, mode_t); 75 static void hardlink(char *); 76 static void note(const char *, ...) 77 __attribute__((__format__(__printf__, 1, 2))); 78 static void query(char *); 79 static void recvf(char *, int); 80 static void removeit(struct stat *); 81 static int response(void); 82 static void rmchk(int); 83 static struct linkbuf * 84 savelink(struct stat *); 85 static void sendf(char *, int); 86 static int update(char *, int, struct stat *); 87 88 /* 89 * Server routine to read requests and process them. 90 * Commands are: 91 * Tname - Transmit file if out of date 92 * Vname - Verify if file out of date or not 93 * Qname - Query if file exists. Return mtime & size if it does. 94 */ 95 void 96 server(void) 97 { 98 char cmdbuf[BUFSIZ]; 99 char *cp; 100 101 signal(SIGHUP, cleanup); 102 signal(SIGINT, cleanup); 103 signal(SIGQUIT, cleanup); 104 signal(SIGTERM, cleanup); 105 signal(SIGPIPE, cleanup); 106 107 rem = 0; 108 oumask = umask(0); 109 (void) snprintf(buf, sizeof(buf), "V%d\n", VERSION); 110 if (write(rem, buf, strlen(buf)) < 0) 111 error("server: could not write remote end: %s\n", 112 strerror(errno)); 113 114 for (;;) { 115 cp = cmdbuf; 116 if (read(rem, cp, 1) <= 0) 117 return; 118 if (*cp++ == '\n') { 119 error("server: expected control record\n"); 120 continue; 121 } 122 do { 123 if (read(rem, cp, 1) != 1) 124 cleanup(0); 125 } while (*cp++ != '\n' && cp < &cmdbuf[BUFSIZ]); 126 *--cp = '\0'; 127 cp = cmdbuf; 128 switch (*cp++) { 129 case 'T': /* init target file/directory name */ 130 catname = 1; /* target should be directory */ 131 goto dotarget; 132 133 case 't': /* init target file/directory name */ 134 catname = 0; 135 dotarget: 136 if (exptilde(target, cp) == NULL) 137 continue; 138 tp = target; 139 while (*tp) 140 tp++; 141 ack(); 142 continue; 143 144 case 'R': /* Transfer a regular file. */ 145 recvf(cp, S_IFREG); 146 continue; 147 148 case 'D': /* Transfer a directory. */ 149 recvf(cp, S_IFDIR); 150 continue; 151 152 case 'K': /* Transfer symbolic link. */ 153 recvf(cp, S_IFLNK); 154 continue; 155 156 case 'k': /* Transfer hard link. */ 157 hardlink(cp); 158 continue; 159 160 case 'E': /* End. (of directory) */ 161 *tp = '\0'; 162 if (catname <= 0) { 163 error("server: too many 'E's\n"); 164 continue; 165 } 166 tp = stp[--catname]; 167 *tp = '\0'; 168 ack(); 169 continue; 170 171 case 'C': /* Clean. Cleanup a directory */ 172 clean(cp); 173 continue; 174 175 case 'Q': /* Query. Does the file/directory exist? */ 176 query(cp); 177 continue; 178 179 case 'S': /* Special. Execute commands */ 180 dospecial(cp); 181 continue; 182 183 #ifdef notdef 184 /* 185 * These entries are reserved but not currently used. 186 * The intent is to allow remote hosts to have master copies. 187 * Currently, only the host rdist runs on can have masters. 188 */ 189 case 'X': /* start a new list of files to exclude */ 190 except = bp = NULL; 191 case 'x': /* add name to list of files to exclude */ 192 if (*cp == '\0') { 193 ack(); 194 continue; 195 } 196 if (*cp == '~') { 197 if (exptilde(buf, cp) == NULL) 198 continue; 199 cp = buf; 200 } 201 if (bp == NULL) 202 except = bp = expand(makeblock(NAME, cp), E_VARS); 203 else 204 bp->b_next = expand(makeblock(NAME, cp), E_VARS); 205 while (bp->b_next != NULL) 206 bp = bp->b_next; 207 ack(); 208 continue; 209 210 case 'I': /* Install. Transfer file if out of date. */ 211 opts = 0; 212 while (*cp >= '0' && *cp <= '7') 213 opts = (opts << 3) | (*cp++ - '0'); 214 if (*cp++ != ' ') { 215 error("server: options not delimited\n"); 216 return; 217 } 218 install(cp, opts); 219 continue; 220 221 case 'L': /* Log. save message in log file */ 222 log(lfp, cp); 223 continue; 224 #endif 225 226 case '\1': 227 nerrs++; 228 continue; 229 230 case '\2': 231 return; 232 233 default: 234 error("server: unknown command '%s'\n", cp); 235 case '\0': 236 continue; 237 } 238 } 239 } 240 241 /* 242 * Update the file(s) if they are different. 243 * destdir = 1 if destination should be a directory 244 * (i.e., more than one source is being copied to the same destination). 245 */ 246 void 247 install(char *src, char *dest, int destdir, int opts) 248 { 249 char *rname; 250 char destcopy[BUFSIZ]; 251 252 if (dest == NULL) { 253 opts &= ~WHOLE; /* WHOLE mode only useful if renaming */ 254 dest = src; 255 } 256 257 if (nflag || debug) { 258 printf("%s%s%s%s%s %s %s\n", opts & VERIFY ? "verify":"install", 259 opts & WHOLE ? " -w" : "", 260 opts & YOUNGER ? " -y" : "", 261 opts & COMPARE ? " -b" : "", 262 opts & REMOVE ? " -R" : "", src, dest); 263 if (nflag) 264 return; 265 } 266 267 rname = exptilde(target, src); 268 if (rname == NULL) 269 return; 270 tp = target; 271 while (*tp) 272 tp++; 273 /* 274 * If we are renaming a directory and we want to preserve 275 * the directory hierarchy (-w), we must strip off the leading 276 * directory name and preserve the rest. 277 */ 278 if (opts & WHOLE) { 279 while (*rname == '/') 280 rname++; 281 destdir = 1; 282 } else { 283 rname = strrchr(target, '/'); 284 if (rname == NULL) 285 rname = target; 286 else 287 rname++; 288 } 289 if (debug) 290 printf("target = %s, rname = %s\n", target, rname); 291 /* 292 * Pass the destination file/directory name to remote. 293 */ 294 (void) snprintf(buf, sizeof(buf), "%c%s\n", destdir ? 'T' : 't', dest); 295 if (debug) 296 printf("buf = %s", buf); 297 if (write(rem, buf, strlen(buf)) < 0) 298 error("could not pass filename to remote: %s\n", 299 strerror(errno)); 300 if (response() < 0) 301 return; 302 303 if (destdir) { 304 strcpy(destcopy, dest); 305 Tdest = destcopy; 306 } 307 sendf(rname, opts); 308 Tdest = 0; 309 } 310 311 #define protoname() (pw ? pw->pw_name : user) 312 #define protogroup() (gr ? gr->gr_name : group) 313 /* 314 * Transfer the file or directory in target[]. 315 * rname is the name of the file on the remote host. 316 */ 317 static void 318 sendf(char *rname, int opts) 319 { 320 struct subcmd *sc; 321 struct stat stb; 322 int sizerr, f, u, len; 323 off_t i; 324 DIR *d; 325 struct dirent *dp; 326 char *otp, *cp; 327 extern struct subcmd *subcmds; 328 static char user[15], group[15]; 329 330 if (debug) 331 printf("sendf(%s, %x)\n", rname, opts); 332 333 if (except(target)) 334 return; 335 if ((opts & FOLLOW ? stat(target, &stb) : lstat(target, &stb)) < 0) { 336 error("%s: %s\n", target, strerror(errno)); 337 return; 338 } 339 if ((u = update(rname, opts, &stb)) == 0) { 340 if (S_ISREG(stb.st_mode) && stb.st_nlink > 1) 341 (void) savelink(&stb); 342 return; 343 } 344 345 if (pw == NULL || pw->pw_uid != stb.st_uid) 346 if ((pw = getpwuid(stb.st_uid)) == NULL) { 347 dolog(lfp, "%s: no password entry for uid %d \n", 348 target, stb.st_uid); 349 pw = NULL; 350 (void)snprintf(user, sizeof(user), ":%lu", 351 (u_long)stb.st_uid); 352 } 353 if (gr == NULL || gr->gr_gid != stb.st_gid) 354 if ((gr = getgrgid(stb.st_gid)) == NULL) { 355 dolog(lfp, "%s: no name for group %d\n", 356 target, stb.st_gid); 357 gr = NULL; 358 (void)snprintf(group, sizeof(group), ":%lu", 359 (u_long)stb.st_gid); 360 } 361 if (u == 1) { 362 if (opts & VERIFY) { 363 dolog(lfp, "need to install: %s\n", target); 364 goto dospecial; 365 } 366 dolog(lfp, "installing: %s\n", target); 367 opts &= ~(COMPARE|REMOVE); 368 } 369 370 switch (stb.st_mode & S_IFMT) { 371 case S_IFDIR: 372 if ((d = opendir(target)) == NULL) { 373 error("%s: %s\n", target, strerror(errno)); 374 return; 375 } 376 (void) snprintf(buf, sizeof(buf), "D%o %04o 0 0 %s %s %s\n", 377 opts, stb.st_mode & 07777, protoname(), protogroup(), 378 rname); 379 if (debug) 380 printf("buf = %s", buf); 381 if (write(rem, buf, strlen(buf)) < 0) 382 error("can not write dir spec to remote: %s\n", 383 strerror(errno)); 384 385 if (response() < 0) { 386 closedir(d); 387 return; 388 } 389 390 if (opts & REMOVE) 391 rmchk(opts); 392 393 otp = tp; 394 len = tp - target; 395 while ((dp = readdir(d)) != NULL) { 396 if (!strcmp(dp->d_name, ".") || 397 !strcmp(dp->d_name, "..")) 398 continue; 399 if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { 400 error("%s/%s: Name too long\n", target, 401 dp->d_name); 402 continue; 403 } 404 tp = otp; 405 *tp++ = '/'; 406 cp = dp->d_name; 407 while ((*tp++ = *cp++) != 0) 408 ; 409 tp--; 410 sendf(dp->d_name, opts); 411 } 412 closedir(d); 413 if (write(rem, "E\n", 2) < 0) 414 error("can not write E to remote: %s\n", 415 strerror(errno)); 416 (void) response(); 417 tp = otp; 418 *tp = '\0'; 419 return; 420 421 case S_IFLNK: 422 if (u != 1) 423 opts |= COMPARE; 424 if (stb.st_nlink > 1) { 425 struct linkbuf *lp; 426 427 if ((lp = savelink(&stb)) != NULL) { 428 /* install link */ 429 if (*lp->target == 0) 430 (void) snprintf(buf, sizeof(buf), 431 "k%o %s %s\n", opts, lp->pathname, rname); 432 else 433 (void) snprintf(buf, sizeof(buf), 434 "k%o %s/%s %s\n", opts, lp->target, 435 lp->pathname, rname); 436 if (debug) 437 printf("buf = %s", buf); 438 if (write(rem, buf, strlen(buf)) < 0) 439 error("can not write link spec to remote: %s\n", 440 strerror(errno)); 441 (void) response(); 442 return; 443 } 444 } 445 (void) snprintf(buf, sizeof(buf), "K%o %o %lld %ld %s %s %s\n", 446 opts, stb.st_mode & 07777, (unsigned long long)stb.st_size, 447 (u_long)stb.st_mtime, protoname(), protogroup(), rname); 448 if (debug) 449 printf("buf = %s", buf); 450 if (write(rem, buf, strlen(buf)) < 0) 451 error("can not write link spec to remote: %s\n", 452 strerror(errno)); 453 if (response() < 0) 454 return; 455 sizerr = (readlink(target, buf, BUFSIZ) != stb.st_size); 456 if (write(rem, buf, stb.st_size) < 0) 457 error("can not write link name to remote: %s\n", 458 strerror(errno)); 459 if (debug) 460 printf("readlink = %.*s\n", (int)stb.st_size, buf); 461 goto done; 462 463 case S_IFREG: 464 break; 465 466 default: 467 error("%s: not a file or directory\n", target); 468 return; 469 } 470 471 if (u == 2) { 472 if (opts & VERIFY) { 473 dolog(lfp, "need to update: %s\n", target); 474 goto dospecial; 475 } 476 dolog(lfp, "updating: %s\n", target); 477 } 478 479 if (stb.st_nlink > 1) { 480 struct linkbuf *lp; 481 482 if ((lp = savelink(&stb)) != NULL) { 483 /* install link */ 484 if (*lp->target == 0) 485 (void) snprintf(buf, sizeof(buf), "k%o %s %s\n", opts, 486 lp->pathname, rname); 487 else 488 (void) snprintf(buf, sizeof(buf), "k%o %s/%s %s\n", 489 opts, lp->target, lp->pathname, rname); 490 if (debug) 491 printf("buf = %s", buf); 492 if (write(rem, buf, strlen(buf)) <0) 493 error("write of file name failed: %s\n", 494 strerror(errno)); 495 (void) response(); 496 return; 497 } 498 } 499 500 if ((f = open(target, O_RDONLY, 0)) < 0) { 501 error("%s: %s\n", target, strerror(errno)); 502 return; 503 } 504 (void)snprintf(buf, sizeof(buf), "R%o %o %lld %lu %s %s %s\n", opts, 505 stb.st_mode & 07777, (unsigned long long)stb.st_size, 506 (u_long)stb.st_mtime, protoname(), protogroup(), rname); 507 if (debug) 508 printf("buf = %s", buf); 509 if (write(rem, buf, strlen(buf)) < 0) 510 error("write of file name failed: %s\n", strerror(errno)); 511 if (response() < 0) { 512 (void) close(f); 513 return; 514 } 515 sizerr = 0; 516 for (i = 0; i < stb.st_size; i += BUFSIZ) { 517 int amt = BUFSIZ; 518 if (i + amt > stb.st_size) 519 amt = stb.st_size - i; 520 if (sizerr == 0 && read(f, buf, amt) != amt) 521 sizerr = 1; 522 if (write(rem, buf, amt) < 0) 523 error("write of file data failed: %s\n", strerror(errno)); 524 } 525 (void) close(f); 526 done: 527 if (sizerr) { 528 error("%s: file changed size\n", target); 529 err(); 530 } else 531 ack(); 532 f = response(); 533 if (f < 0 || (f == 0 && (opts & COMPARE))) 534 return; 535 dospecial: 536 for (sc = subcmds; sc != NULL; sc = sc->sc_next) { 537 if (sc->sc_type != SPECIAL) 538 continue; 539 if (sc->sc_args != NULL && !inlist(sc->sc_args, target)) 540 continue; 541 dolog(lfp, "special \"%s\"\n", sc->sc_name); 542 if (opts & VERIFY) 543 continue; 544 (void) snprintf(buf, sizeof(buf), "SFILE=%s;%s\n", target, 545 sc->sc_name); 546 if (debug) 547 printf("buf = %s", buf); 548 if (write(rem, buf, strlen(buf)) < 0) 549 error("write of special failed: %s\n", strerror(errno)); 550 while (response() > 0) 551 ; 552 } 553 } 554 555 static struct linkbuf * 556 savelink(struct stat *stp) 557 { 558 struct linkbuf *lp; 559 560 for (lp = ihead; lp != NULL; lp = lp->nextp) 561 if (lp->inum == stp->st_ino && lp->devnum == stp->st_dev) { 562 lp->count--; 563 return(lp); 564 } 565 lp = (struct linkbuf *) malloc(sizeof(*lp)); 566 if (lp == NULL) 567 dolog(lfp, "out of memory, link information lost\n"); 568 else { 569 lp->nextp = ihead; 570 ihead = lp; 571 lp->inum = stp->st_ino; 572 lp->devnum = stp->st_dev; 573 lp->count = stp->st_nlink - 1; 574 strcpy(lp->pathname, target); 575 if (Tdest) 576 strcpy(lp->target, Tdest); 577 else 578 *lp->target = 0; 579 } 580 return(NULL); 581 } 582 583 /* 584 * Check to see if file needs to be updated on the remote machine. 585 * Returns 0 if no update, 1 if remote doesn't exist, 2 if out of date 586 * and 3 if comparing binaries to determine if out of date. 587 */ 588 static int 589 update(char *rname, int opts, struct stat *stp) 590 { 591 char *cp, *s; 592 off_t size; 593 time_t mtime; 594 595 if (debug) 596 printf("update(%s, %lx, %lx)\n", rname, (long)opts, (long)stp); 597 598 /* 599 * Check to see if the file exists on the remote machine. 600 */ 601 (void) snprintf(buf, sizeof(buf), "Q%s\n", rname); 602 if (debug) 603 printf("buf = %s", buf); 604 if (write(rem, buf, strlen(buf)) < 0) 605 error("write to remote failed: %s\n", strerror(errno)); 606 again: 607 cp = s = buf; 608 do { 609 if (read(rem, cp, 1) != 1) 610 lostconn(0); 611 } while (*cp++ != '\n' && cp < &buf[BUFSIZ]); 612 613 switch (*s++) { 614 case 'Y': 615 break; 616 617 case 'N': /* file doesn't exist so install it */ 618 return(1); 619 620 case '\1': 621 nerrs++; 622 if (*s != '\n') { 623 if (!iamremote) { 624 fflush(stdout); 625 (void) write(2, s, cp - s); 626 } 627 if (lfp != NULL) 628 (void) fwrite(s, 1, cp - s, lfp); 629 } 630 return(0); 631 632 case '\3': 633 *--cp = '\0'; 634 if (lfp != NULL) 635 dolog(lfp, "update: note: %s\n", s); 636 goto again; 637 638 default: 639 *--cp = '\0'; 640 error("update: unexpected response '%s'\n", s); 641 return(0); 642 } 643 644 if (*s == '\n') 645 return(2); 646 647 if (opts & COMPARE) 648 return(3); 649 650 size = 0; 651 while (isdigit((unsigned char)*s)) 652 size = size * 10 + (*s++ - '0'); 653 if (*s++ != ' ') { 654 error("update: size not delimited\n"); 655 return(0); 656 } 657 mtime = 0; 658 while (isdigit((unsigned char)*s)) 659 mtime = mtime * 10 + (*s++ - '0'); 660 if (*s != '\n') { 661 error("update: mtime not delimited\n"); 662 return(0); 663 } 664 /* 665 * File needs to be updated? 666 */ 667 if (opts & YOUNGER) { 668 if (stp->st_mtime == mtime) 669 return(0); 670 if (stp->st_mtime < mtime) { 671 dolog(lfp, "Warning: %s: remote copy is newer\n", 672 target); 673 return(0); 674 } 675 } else if (stp->st_mtime == mtime && stp->st_size == size) 676 return(0); 677 return(2); 678 } 679 680 /* 681 * Query. Check to see if file exists. Return one of the following: 682 * N\n - doesn't exist 683 * Ysize mtime\n - exists and its a regular file (size & mtime of file) 684 * Y\n - exists and its a directory or symbolic link 685 * ^Aerror message\n 686 */ 687 static void 688 query(char *name) 689 { 690 struct stat stb; 691 692 if (catname) 693 (void) snprintf(tp, sizeof(target) - (tp - target), 694 "/%s", name); 695 696 if (lstat(target, &stb) < 0) { 697 if (errno == ENOENT) { 698 if (write(rem, "N\n", 2) < 0) 699 error("write to remote failed: %s\n", 700 strerror(errno)); 701 } else 702 error("%s:%s: %s\n", host, target, strerror(errno)); 703 *tp = '\0'; 704 return; 705 } 706 707 switch (stb.st_mode & S_IFMT) { 708 case S_IFREG: 709 (void)snprintf(buf, sizeof(buf), "Y%lld %ld\n", 710 (unsigned long long)stb.st_size, (u_long)stb.st_mtime); 711 if (write(rem, buf, strlen(buf)) < 0) 712 error("write to remote failed: %s\n", strerror(errno)); 713 break; 714 715 case S_IFLNK: 716 case S_IFDIR: 717 if (write(rem, "Y\n", 2) < 0) 718 error("write to remote failed: %s\n", strerror(errno)); 719 break; 720 721 default: 722 error("%s: not a file or directory\n", name); 723 break; 724 } 725 *tp = '\0'; 726 } 727 728 static void 729 recvf(char *cmd, int type) 730 { 731 char *cp = cmd; 732 int f = -1, opts = 0, wrerr, olderrno; 733 mode_t mode; 734 off_t i, size; 735 time_t mtime; 736 struct stat stb; 737 char *owner, *group; 738 char new[BUFSIZ]; 739 extern char *tempname; 740 741 while (*cp >= '0' && *cp <= '7') 742 opts = (opts << 3) | (*cp++ - '0'); 743 if (*cp++ != ' ') { 744 error("recvf: options not delimited\n"); 745 return; 746 } 747 mode = 0; 748 while (*cp >= '0' && *cp <= '7') 749 mode = (mode << 3) | (*cp++ - '0'); 750 if (*cp++ != ' ') { 751 error("recvf: mode not delimited\n"); 752 return; 753 } 754 size = 0; 755 while (isdigit((unsigned char)*cp)) 756 size = size * 10 + (*cp++ - '0'); 757 if (*cp++ != ' ') { 758 error("recvf: size not delimited\n"); 759 return; 760 } 761 mtime = 0; 762 while (isdigit((unsigned char)*cp)) 763 mtime = mtime * 10 + (*cp++ - '0'); 764 if (*cp++ != ' ') { 765 error("recvf: mtime not delimited\n"); 766 return; 767 } 768 owner = cp; 769 while (*cp && *cp != ' ') 770 cp++; 771 if (*cp != ' ') { 772 error("recvf: owner name not delimited\n"); 773 return; 774 } 775 *cp++ = '\0'; 776 group = cp; 777 while (*cp && *cp != ' ') 778 cp++; 779 if (*cp != ' ') { 780 error("recvf: group name not delimited\n"); 781 return; 782 } 783 *cp++ = '\0'; 784 785 if (type == S_IFDIR) { 786 if (catname >= sizeof(stp)) { 787 error("%s:%s: too many directory levels\n", 788 host, target); 789 return; 790 } 791 stp[catname] = tp; 792 if (catname++) { 793 *tp++ = '/'; 794 while ((*tp++ = *cp++) != 0) 795 ; 796 tp--; 797 } 798 if (opts & VERIFY) { 799 ack(); 800 return; 801 } 802 if (lstat(target, &stb) == 0) { 803 if (S_ISDIR(stb.st_mode)) { 804 if ((stb.st_mode & 07777) == mode) { 805 ack(); 806 return; 807 } 808 buf[0] = '\0'; 809 (void) snprintf(buf + 1, sizeof(buf) - 1, 810 "%s: Warning: remote mode %o != local mode %o\n", 811 target, stb.st_mode & 07777, mode); 812 if (write(rem, buf, strlen(buf + 1) + 1) < 0) 813 error("write to remote failed: %s\n", 814 strerror(errno)); 815 return; 816 } 817 errno = ENOTDIR; 818 } else if ((errno == ENOENT && mkdir(target, mode) == 0) || 819 (chkparent(target) == 0 && mkdir(target, mode) == 0)) { 820 if (fchtogm(-1, target, mtime, owner, group, mode) == 0) 821 ack(); 822 return; 823 } 824 error("%s:%s: %s\n", host, target, strerror(errno)); 825 tp = stp[--catname]; 826 *tp = '\0'; 827 return; 828 } 829 830 if (catname) 831 (void) snprintf(tp, sizeof(target) - (tp - target), "/%s", cp); 832 cp = strrchr(target, '/'); 833 if (cp == NULL) 834 strcpy(new, tempname); 835 else if (cp == target) 836 (void) snprintf(new, sizeof(new), "/%s", tempname); 837 else { 838 *cp = '\0'; 839 (void) snprintf(new, sizeof(new), "%s/%s", target, tempname); 840 *cp = '/'; 841 } 842 843 if (type == S_IFLNK) { 844 int j; 845 846 ack(); 847 cp = buf; 848 for (i = 0; i < size; i += j) { 849 if ((j = read(rem, cp, size - i)) <= 0) 850 cleanup(0); 851 cp += j; 852 } 853 *cp = '\0'; 854 if (response() < 0) { 855 err(); 856 return; 857 } 858 if (symlink(buf, new) < 0) { 859 if (errno != ENOENT || chkparent(new) < 0 || 860 symlink(buf, new) < 0) 861 goto badnew1; 862 } 863 mode &= 0777; 864 if (opts & COMPARE) { 865 char tbuf[BUFSIZ]; 866 867 if ((i = readlink(target, tbuf, BUFSIZ)) >= 0 && 868 i == size && strncmp(buf, tbuf, size) == 0) { 869 (void) unlink(new); 870 ack(); 871 return; 872 } 873 if (opts & VERIFY) 874 goto differ; 875 } 876 goto fixup; 877 } 878 879 if ((f = creat(new, mode)) < 0) { 880 if (errno != ENOENT || chkparent(new) < 0 || 881 (f = creat(new, mode)) < 0) 882 goto badnew1; 883 } 884 885 ack(); 886 wrerr = 0; 887 for (i = 0; i < size; i += BUFSIZ) { 888 int amt = BUFSIZ; 889 890 cp = buf; 891 if (i + amt > size) 892 amt = size - i; 893 do { 894 int j = read(rem, cp, amt); 895 896 if (j <= 0) { 897 (void) close(f); 898 (void) unlink(new); 899 cleanup(0); 900 } 901 amt -= j; 902 cp += j; 903 } while (amt > 0); 904 amt = BUFSIZ; 905 if (i + amt > size) 906 amt = size - i; 907 if (wrerr == 0 && write(f, buf, amt) != amt) { 908 olderrno = errno; 909 wrerr++; 910 } 911 } 912 if (response() < 0) { 913 err(); 914 goto badnew2; 915 } 916 if (wrerr) 917 goto badnew1; 918 if (opts & COMPARE) { 919 FILE *f1, *f2; 920 int c; 921 922 if ((f1 = fopen(target, "r")) == NULL) 923 goto badtarget; 924 if ((f2 = fopen(new, "r")) == NULL) { 925 badnew1: error("%s:%s: %s\n", host, new, strerror(errno)); 926 goto badnew2; 927 } 928 while ((c = getc(f1)) == getc(f2)) 929 if (c == EOF) { 930 (void) fclose(f1); 931 (void) fclose(f2); 932 ack(); 933 goto badnew2; 934 } 935 (void) fclose(f1); 936 (void) fclose(f2); 937 if (opts & VERIFY) { 938 differ: buf[0] = '\0'; 939 (void)snprintf(buf + 1, sizeof(buf) - 1, 940 "need to update: %s\n",target); 941 (void) write(rem, buf, strlen(buf + 1) + 1); 942 goto badnew2; 943 } 944 } 945 946 if (fchtogm(f, new, mtime, owner, group, mode) < 0) { 947 badnew2: 948 if (f != -1) 949 (void) close(f); 950 (void) unlink(new); 951 return; 952 } 953 (void) close(f); 954 955 fixup: if (rename(new, target) < 0) { 956 badtarget: error("%s:%s: %s\n", host, target, strerror(errno)); 957 (void) unlink(new); 958 return; 959 } 960 961 if (opts & COMPARE) { 962 buf[0] = '\0'; 963 (void) snprintf(buf + 1, sizeof(buf) - 1, 964 "updated %s\n", target); 965 (void) write(rem, buf, strlen(buf + 1) + 1); 966 } else 967 ack(); 968 } 969 970 /* 971 * Creat a hard link to existing file. 972 */ 973 static void 974 hardlink(char *cmd) 975 { 976 char *cp; 977 struct stat stb; 978 char *oldname; 979 int opts, exists = 0; 980 981 cp = cmd; 982 opts = 0; 983 while (*cp >= '0' && *cp <= '7') 984 opts = (opts << 3) | (*cp++ - '0'); 985 if (*cp++ != ' ') { 986 error("hardlink: options not delimited\n"); 987 return; 988 } 989 oldname = cp; 990 while (*cp && *cp != ' ') 991 cp++; 992 if (*cp != ' ') { 993 error("hardlink: oldname name not delimited\n"); 994 return; 995 } 996 *cp++ = '\0'; 997 998 if (catname) { 999 (void) snprintf(tp, sizeof(target) - (tp - target), "/%s", cp); 1000 } 1001 if (lstat(target, &stb) == 0) { 1002 if (!S_ISREG(stb.st_mode) && !S_ISLNK(stb.st_mode)) { 1003 error("%s: %s: not a regular file\n", host, target); 1004 return; 1005 } 1006 exists = 1; 1007 } 1008 if (chkparent(target) < 0 ) { 1009 error("%s:%s: %s (no parent)\n", 1010 host, target, strerror(errno)); 1011 return; 1012 } 1013 if (exists && (unlink(target) < 0)) { 1014 error("%s:%s: %s (unlink)\n", 1015 host, target, strerror(errno)); 1016 return; 1017 } 1018 if (link(oldname, target) < 0) { 1019 error("%s:can't link %s to %s\n", 1020 host, target, oldname); 1021 return; 1022 } 1023 ack(); 1024 } 1025 1026 /* 1027 * Check to see if parent directory exists and create one if not. 1028 */ 1029 static int 1030 chkparent(char *name) 1031 { 1032 char *cp; 1033 struct stat stb; 1034 1035 cp = strrchr(name, '/'); 1036 if (cp == NULL || cp == name) 1037 return(0); 1038 *cp = '\0'; 1039 if (lstat(name, &stb) < 0) { 1040 if (errno == ENOENT && chkparent(name) >= 0 && 1041 mkdir(name, 0777 & ~oumask) >= 0) { 1042 *cp = '/'; 1043 return(0); 1044 } 1045 } else if (S_ISDIR(stb.st_mode)) { 1046 *cp = '/'; 1047 return(0); 1048 } 1049 *cp = '/'; 1050 return(-1); 1051 } 1052 1053 /* 1054 * Change owner, group and mode of file. 1055 */ 1056 static int 1057 fchtogm(int fd, char *file, time_t mtime, char *owner, char *group, __mode_t mode) 1058 { 1059 int i; 1060 struct timeval tv[2]; 1061 uid_t uid; 1062 gid_t gid; 1063 extern char user[]; 1064 1065 uid = userid; 1066 if (userid == 0) { 1067 if (*owner == ':') { 1068 uid = atoi(owner + 1); 1069 } else if (pw == NULL || strcmp(owner, pw->pw_name) != 0) { 1070 if ((pw = getpwnam(owner)) == NULL) { 1071 if (mode & 04000) { 1072 note("%s:%s: unknown login name, clearing setuid", 1073 host, owner); 1074 mode &= ~04000; 1075 uid = 0; 1076 } 1077 } else 1078 uid = pw->pw_uid; 1079 } else 1080 uid = pw->pw_uid; 1081 if (*group == ':') { 1082 gid = atoi(group + 1); 1083 goto ok; 1084 } 1085 } else if ((mode & 04000) && strcmp(user, owner) != 0) 1086 mode &= ~04000; 1087 gid = -1; 1088 if (gr == NULL || strcmp(group, gr->gr_name) != 0) { 1089 if ((*group == ':' && (getgrgid(gid = atoi(group + 1)) == NULL)) 1090 || ((gr = getgrnam(group)) == NULL)) { 1091 if (mode & 02000) { 1092 note("%s:%s: unknown group", host, group); 1093 mode &= ~02000; 1094 } 1095 } else 1096 gid = gr->gr_gid; 1097 } else 1098 gid = gr->gr_gid; 1099 if (userid && gid >= 0) { 1100 if (gr) for (i = 0; gr->gr_mem[i] != NULL; i++) 1101 if (!(strcmp(user, gr->gr_mem[i]))) 1102 goto ok; 1103 mode &= ~02000; 1104 gid = -1; 1105 } 1106 ok: 1107 (void) gettimeofday(&tv[0], (struct timezone *)0); 1108 tv[1].tv_sec = mtime; 1109 tv[1].tv_usec = 0; 1110 if (fd != -1 ? futimes(fd, tv) < 0 : utimes(file, tv) < 0) 1111 note("%s: %s utimes: %s", host, file, strerror(errno)); 1112 if (fd != -1 ? fchown(fd, uid, gid) < 0 : chown(file, uid, gid) < 0) 1113 note("%s: %s chown: %s", host, file, strerror(errno)); 1114 else if (mode & 07000 && 1115 (fd != -1 ? fchmod(fd, mode) < 0 : chmod(file, mode) < 0)) 1116 note("%s: %s chmod: %s", host, file, strerror(errno)); 1117 return(0); 1118 } 1119 1120 /* 1121 * Check for files on the machine being updated that are not on the master 1122 * machine and remove them. 1123 */ 1124 static void 1125 rmchk(int opts) 1126 { 1127 char *cp, *s; 1128 struct stat stb; 1129 1130 if (debug) 1131 printf("rmchk()\n"); 1132 1133 /* 1134 * Tell the remote to clean the files from the last directory sent. 1135 */ 1136 (void) snprintf(buf, sizeof(buf), "C%o\n", opts & VERIFY); 1137 if (debug) 1138 printf("buf = %s", buf); 1139 (void) write(rem, buf, strlen(buf)); 1140 if (response() < 0) 1141 return; 1142 for (;;) { 1143 cp = s = buf; 1144 do { 1145 if (read(rem, cp, 1) != 1) 1146 lostconn(0); 1147 } while (*cp++ != '\n' && cp < &buf[BUFSIZ]); 1148 1149 switch (*s++) { 1150 case 'Q': /* Query if file should be removed */ 1151 /* 1152 * Return the following codes to remove query. 1153 * N\n -- file exists - DON'T remove. 1154 * Y\n -- file doesn't exist - REMOVE. 1155 */ 1156 *--cp = '\0'; 1157 (void) snprintf(tp, sizeof(target) - (tp - target), 1158 "/%s", s); 1159 if (debug) 1160 printf("check %s\n", target); 1161 if (except(target)) 1162 (void) write(rem, "N\n", 2); 1163 else if (lstat(target, &stb) < 0) 1164 (void) write(rem, "Y\n", 2); 1165 else 1166 (void) write(rem, "N\n", 2); 1167 break; 1168 1169 case '\0': 1170 *--cp = '\0'; 1171 if (*s != '\0') 1172 dolog(lfp, "%s\n", s); 1173 break; 1174 1175 case 'E': 1176 *tp = '\0'; 1177 ack(); 1178 return; 1179 1180 case '\1': 1181 case '\2': 1182 nerrs++; 1183 if (*s != '\n') { 1184 if (!iamremote) { 1185 fflush(stdout); 1186 (void) write(2, s, cp - s); 1187 } 1188 if (lfp != NULL) 1189 (void) fwrite(s, 1, cp - s, lfp); 1190 } 1191 if (buf[0] == '\2') 1192 lostconn(0); 1193 break; 1194 1195 default: 1196 error("rmchk: unexpected response '%s'\n", buf); 1197 err(); 1198 } 1199 } 1200 } 1201 1202 /* 1203 * Check the current directory (initialized by the 'T' command to server()) 1204 * for extraneous files and remove them. 1205 */ 1206 static void 1207 clean(char *cp) 1208 { 1209 DIR *d; 1210 struct dirent *dp; 1211 struct stat stb; 1212 char *otp; 1213 int len, opts; 1214 1215 opts = 0; 1216 while (*cp >= '0' && *cp <= '7') 1217 opts = (opts << 3) | (*cp++ - '0'); 1218 if (*cp != '\0') { 1219 error("clean: options not delimited\n"); 1220 return; 1221 } 1222 if ((d = opendir(target)) == NULL) { 1223 error("%s:%s: %s\n", host, target, strerror(errno)); 1224 return; 1225 } 1226 ack(); 1227 1228 otp = tp; 1229 len = tp - target; 1230 while ((dp = readdir(d)) != NULL) { 1231 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 1232 continue; 1233 if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { 1234 error("%s:%s/%s: Name too long\n", 1235 host, target, dp->d_name); 1236 continue; 1237 } 1238 tp = otp; 1239 *tp++ = '/'; 1240 cp = dp->d_name;; 1241 while ((*tp++ = *cp++) != 0) 1242 ; 1243 tp--; 1244 if (lstat(target, &stb) < 0) { 1245 error("%s:%s: %s\n", host, target, strerror(errno)); 1246 continue; 1247 } 1248 (void) snprintf(buf, sizeof(buf), "Q%s\n", dp->d_name); 1249 (void) write(rem, buf, strlen(buf)); 1250 cp = buf; 1251 do { 1252 if (read(rem, cp, 1) != 1) 1253 cleanup(0); 1254 } while (*cp++ != '\n' && cp < &buf[BUFSIZ]); 1255 *--cp = '\0'; 1256 cp = buf; 1257 if (*cp != 'Y') 1258 continue; 1259 if (opts & VERIFY) { 1260 cp = buf; 1261 *cp++ = '\0'; 1262 (void) snprintf(cp, sizeof(buf) - 1, 1263 "need to remove: %s\n", target); 1264 (void) write(rem, buf, strlen(cp) + 1); 1265 } else 1266 removeit(&stb); 1267 } 1268 closedir(d); 1269 (void) write(rem, "E\n", 2); 1270 (void) response(); 1271 tp = otp; 1272 *tp = '\0'; 1273 } 1274 1275 /* 1276 * Remove a file or directory (recursively) and send back an acknowledge 1277 * or an error message. 1278 */ 1279 static void 1280 removeit(struct stat *stp) 1281 { 1282 DIR *d; 1283 struct dirent *dp; 1284 char *cp; 1285 struct stat stb; 1286 char *otp; 1287 int len; 1288 1289 switch (stp->st_mode & S_IFMT) { 1290 case S_IFREG: 1291 case S_IFLNK: 1292 if (unlink(target) < 0) 1293 goto bad; 1294 goto removed; 1295 1296 case S_IFDIR: 1297 break; 1298 1299 default: 1300 error("%s:%s: not a plain file\n", host, target); 1301 return; 1302 } 1303 1304 if ((d = opendir(target)) == NULL) 1305 goto bad; 1306 1307 otp = tp; 1308 len = tp - target; 1309 while ((dp = readdir(d)) != NULL) { 1310 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 1311 continue; 1312 if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { 1313 error("%s:%s/%s: Name too long\n", 1314 host, target, dp->d_name); 1315 continue; 1316 } 1317 tp = otp; 1318 *tp++ = '/'; 1319 cp = dp->d_name;; 1320 while ((*tp++ = *cp++) != 0) 1321 ; 1322 tp--; 1323 if (lstat(target, &stb) < 0) { 1324 error("%s:%s: %s\n", host, target, strerror(errno)); 1325 continue; 1326 } 1327 removeit(&stb); 1328 } 1329 closedir(d); 1330 tp = otp; 1331 *tp = '\0'; 1332 if (rmdir(target) < 0) { 1333 bad: 1334 error("%s:%s: %s\n", host, target, strerror(errno)); 1335 return; 1336 } 1337 removed: 1338 cp = buf; 1339 *cp++ = '\0'; 1340 (void) snprintf(cp, sizeof(buf) - 1, "removed %s\n", target); 1341 (void) write(rem, buf, strlen(cp) + 1); 1342 } 1343 1344 /* 1345 * Execute a shell command to handle special cases. 1346 */ 1347 static void 1348 dospecial(char *cmd) 1349 { 1350 int fd[2], status, pid, i; 1351 char *cp, *s; 1352 char sbuf[BUFSIZ]; 1353 1354 if (pipe(fd) < 0) { 1355 error("%s\n", strerror(errno)); 1356 return; 1357 } 1358 if ((pid = fork()) == 0) { 1359 /* 1360 * Return everything the shell commands print. 1361 */ 1362 (void) close(0); 1363 (void) close(1); 1364 (void) close(2); 1365 (void) open(_PATH_DEVNULL, O_RDONLY); 1366 (void) dup(fd[1]); 1367 (void) dup(fd[1]); 1368 (void) close(fd[0]); 1369 (void) close(fd[1]); 1370 setgid(groupid); 1371 setuid(userid); 1372 execl(_PATH_BSHELL, "sh", "-c", cmd, 0); 1373 _exit(127); 1374 } 1375 (void) close(fd[1]); 1376 s = sbuf; 1377 *s++ = '\0'; 1378 while ((i = read(fd[0], buf, sizeof(buf))) > 0) { 1379 cp = buf; 1380 do { 1381 *s++ = *cp++; 1382 if (cp[-1] != '\n') { 1383 if (s < &sbuf[sizeof(sbuf)-1]) 1384 continue; 1385 *s++ = '\n'; 1386 } 1387 /* 1388 * Throw away blank lines. 1389 */ 1390 if (s == &sbuf[2]) { 1391 s--; 1392 continue; 1393 } 1394 (void) write(rem, sbuf, s - sbuf); 1395 s = &sbuf[1]; 1396 } while (--i); 1397 } 1398 if (s > &sbuf[1]) { 1399 *s++ = '\n'; 1400 (void) write(rem, sbuf, s - sbuf); 1401 } 1402 while ((i = wait(&status)) != pid && i != -1) 1403 ; 1404 if (i == -1) 1405 status = -1; 1406 (void) close(fd[0]); 1407 if (status) 1408 error("shell returned %d\n", status); 1409 else 1410 ack(); 1411 } 1412 1413 1414 void 1415 dolog(FILE *fp, const char *fmt, ...) 1416 { 1417 va_list ap; 1418 1419 /* Print changes locally if not quiet mode */ 1420 if (!qflag) { 1421 va_start(ap, fmt); 1422 (void)vprintf(fmt, ap); 1423 va_end(ap); 1424 } 1425 1426 /* Save changes (for mailing) if really updating files */ 1427 if (!(options & VERIFY) && fp != NULL) { 1428 va_start(ap, fmt); 1429 (void)vfprintf(fp, fmt, ap); 1430 va_end(ap); 1431 } 1432 } 1433 1434 void 1435 error(const char *fmt, ...) 1436 { 1437 static FILE *fp; 1438 va_list ap; 1439 1440 ++nerrs; 1441 if (!fp && !(fp = fdopen(rem, "w"))) 1442 return; 1443 va_start(ap, fmt); 1444 if (iamremote) { 1445 (void)fprintf(fp, "%crdist: ", 0x01); 1446 (void)vfprintf(fp, fmt, ap); 1447 fflush(fp); 1448 } 1449 else { 1450 fflush(stdout); 1451 (void)fprintf(stderr, "rdist: "); 1452 (void)vfprintf(stderr, fmt, ap); 1453 fflush(stderr); 1454 } 1455 va_end(ap); 1456 if (lfp != NULL) { 1457 (void)fprintf(lfp, "rdist: "); 1458 va_start(ap, fmt); 1459 (void)vfprintf(lfp, fmt, ap); 1460 va_end(ap); 1461 fflush(lfp); 1462 } 1463 } 1464 1465 void 1466 fatal(const char *fmt, ...) 1467 { 1468 static FILE *fp; 1469 va_list ap; 1470 1471 ++nerrs; 1472 if (!fp && !(fp = fdopen(rem, "w"))) 1473 return; 1474 va_start(ap, fmt); 1475 if (iamremote) { 1476 (void)fprintf(fp, "%crdist: ", 0x02); 1477 (void)vfprintf(fp, fmt, ap); 1478 fflush(fp); 1479 } 1480 else { 1481 fflush(stdout); 1482 (void)fprintf(stderr, "rdist: "); 1483 (void)vfprintf(stderr, fmt, ap); 1484 fflush(stderr); 1485 } 1486 va_end(ap); 1487 if (lfp != NULL) { 1488 (void)fprintf(lfp, "rdist: "); 1489 va_start(ap, fmt); 1490 (void)vfprintf(lfp, fmt, ap); 1491 va_end(ap); 1492 fflush(lfp); 1493 } 1494 cleanup(0); 1495 } 1496 1497 static int 1498 response(void) 1499 { 1500 char *cp, *s; 1501 char resp[BUFSIZ]; 1502 1503 if (debug) 1504 printf("response()\n"); 1505 1506 cp = s = resp; 1507 do { 1508 if (read(rem, cp, 1) != 1) 1509 lostconn(0); 1510 } while (*cp++ != '\n' && cp < &resp[BUFSIZ]); 1511 1512 switch (*s++) { 1513 case '\0': 1514 *--cp = '\0'; 1515 if (*s != '\0') { 1516 dolog(lfp, "%s\n", s); 1517 return(1); 1518 } 1519 return(0); 1520 case '\3': 1521 *--cp = '\0'; 1522 dolog(lfp, "Note: %s\n",s); 1523 return(response()); 1524 1525 default: 1526 s--; 1527 /* fall into... */ 1528 case '\1': 1529 case '\2': 1530 nerrs++; 1531 if (*s != '\n') { 1532 if (!iamremote) { 1533 fflush(stdout); 1534 (void) write(2, s, cp - s); 1535 } 1536 if (lfp != NULL) 1537 (void) fwrite(s, 1, cp - s, lfp); 1538 } 1539 if (resp[0] == '\2') 1540 lostconn(0); 1541 return(-1); 1542 } 1543 } 1544 1545 /* 1546 * Remove temporary files and do any cleanup operations before exiting. 1547 */ 1548 void 1549 cleanup(int signo) 1550 { 1551 (void) unlink(tempfile); 1552 exit(1); 1553 } 1554 1555 static void 1556 note(const char *fmt, ...) 1557 { 1558 static char buf[BUFSIZ]; 1559 va_list ap; 1560 1561 va_start(ap, fmt); 1562 (void)vsnprintf(buf, sizeof(buf), fmt, ap); 1563 va_end(ap); 1564 comment(buf); 1565 } 1566 1567 static void 1568 comment(char *s) 1569 { 1570 char c; 1571 1572 c = '\3'; 1573 write(rem, &c, 1); 1574 write(rem, s, strlen(s)); 1575 c = '\n'; 1576 write(rem, &c, 1); 1577 } 1578