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