1 /* $NetBSD: server.c,v 1.23 2002/06/14 01:18:55 wiz 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.23 2002/06/14 01:18:55 wiz 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 log(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 log(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 log(lfp, "need to install: %s\n", target); 364 goto dospecial; 365 } 366 log(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 log(lfp, "need to update: %s\n", target); 474 goto dospecial; 475 } 476 log(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 log(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 log(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 log(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 log(lfp, "Warning: %s: remote copy is newer\n", target); 672 return(0); 673 } 674 } else if (stp->st_mtime == mtime && stp->st_size == size) 675 return(0); 676 return(2); 677 } 678 679 /* 680 * Query. Check to see if file exists. Return one of the following: 681 * N\n - doesn't exist 682 * Ysize mtime\n - exists and its a regular file (size & mtime of file) 683 * Y\n - exists and its a directory or symbolic link 684 * ^Aerror message\n 685 */ 686 static void 687 query(char *name) 688 { 689 struct stat stb; 690 691 if (catname) 692 (void) snprintf(tp, sizeof(target) - (tp - target), 693 "/%s", name); 694 695 if (lstat(target, &stb) < 0) { 696 if (errno == ENOENT) { 697 if (write(rem, "N\n", 2) < 0) 698 error("write to remote failed: %s\n", 699 strerror(errno)); 700 } else 701 error("%s:%s: %s\n", host, target, strerror(errno)); 702 *tp = '\0'; 703 return; 704 } 705 706 switch (stb.st_mode & S_IFMT) { 707 case S_IFREG: 708 (void)snprintf(buf, sizeof(buf), "Y%lld %ld\n", 709 (unsigned long long)stb.st_size, (u_long)stb.st_mtime); 710 if (write(rem, buf, strlen(buf)) < 0) 711 error("write to remote failed: %s\n", strerror(errno)); 712 break; 713 714 case S_IFLNK: 715 case S_IFDIR: 716 if (write(rem, "Y\n", 2) < 0) 717 error("write to remote failed: %s\n", strerror(errno)); 718 break; 719 720 default: 721 error("%s: not a file or directory\n", name); 722 break; 723 } 724 *tp = '\0'; 725 } 726 727 static void 728 recvf(char *cmd, int type) 729 { 730 char *cp = cmd; 731 int f = -1, opts = 0, wrerr, olderrno; 732 mode_t mode; 733 off_t i, size; 734 time_t mtime; 735 struct stat stb; 736 char *owner, *group; 737 char new[BUFSIZ]; 738 extern char *tempname; 739 740 while (*cp >= '0' && *cp <= '7') 741 opts = (opts << 3) | (*cp++ - '0'); 742 if (*cp++ != ' ') { 743 error("recvf: options not delimited\n"); 744 return; 745 } 746 mode = 0; 747 while (*cp >= '0' && *cp <= '7') 748 mode = (mode << 3) | (*cp++ - '0'); 749 if (*cp++ != ' ') { 750 error("recvf: mode not delimited\n"); 751 return; 752 } 753 size = 0; 754 while (isdigit((unsigned char)*cp)) 755 size = size * 10 + (*cp++ - '0'); 756 if (*cp++ != ' ') { 757 error("recvf: size not delimited\n"); 758 return; 759 } 760 mtime = 0; 761 while (isdigit((unsigned char)*cp)) 762 mtime = mtime * 10 + (*cp++ - '0'); 763 if (*cp++ != ' ') { 764 error("recvf: mtime not delimited\n"); 765 return; 766 } 767 owner = cp; 768 while (*cp && *cp != ' ') 769 cp++; 770 if (*cp != ' ') { 771 error("recvf: owner name not delimited\n"); 772 return; 773 } 774 *cp++ = '\0'; 775 group = cp; 776 while (*cp && *cp != ' ') 777 cp++; 778 if (*cp != ' ') { 779 error("recvf: group name not delimited\n"); 780 return; 781 } 782 *cp++ = '\0'; 783 784 if (type == S_IFDIR) { 785 if (catname >= sizeof(stp)) { 786 error("%s:%s: too many directory levels\n", 787 host, target); 788 return; 789 } 790 stp[catname] = tp; 791 if (catname++) { 792 *tp++ = '/'; 793 while ((*tp++ = *cp++) != 0) 794 ; 795 tp--; 796 } 797 if (opts & VERIFY) { 798 ack(); 799 return; 800 } 801 if (lstat(target, &stb) == 0) { 802 if (S_ISDIR(stb.st_mode)) { 803 if ((stb.st_mode & 07777) == mode) { 804 ack(); 805 return; 806 } 807 buf[0] = '\0'; 808 (void) snprintf(buf + 1, sizeof(buf) - 1, 809 "%s: Warning: remote mode %o != local mode %o\n", 810 target, stb.st_mode & 07777, mode); 811 if (write(rem, buf, strlen(buf + 1) + 1) < 0) 812 error("write to remote failed: %s\n", 813 strerror(errno)); 814 return; 815 } 816 errno = ENOTDIR; 817 } else if ((errno == ENOENT && mkdir(target, mode) == 0) || 818 (chkparent(target) == 0 && mkdir(target, mode) == 0)) { 819 if (fchtogm(-1, target, mtime, owner, group, mode) == 0) 820 ack(); 821 return; 822 } 823 error("%s:%s: %s\n", host, target, strerror(errno)); 824 tp = stp[--catname]; 825 *tp = '\0'; 826 return; 827 } 828 829 if (catname) 830 (void) snprintf(tp, sizeof(target) - (tp - target), "/%s", cp); 831 cp = strrchr(target, '/'); 832 if (cp == NULL) 833 strcpy(new, tempname); 834 else if (cp == target) 835 (void) snprintf(new, sizeof(new), "/%s", tempname); 836 else { 837 *cp = '\0'; 838 (void) snprintf(new, sizeof(new), "%s/%s", target, tempname); 839 *cp = '/'; 840 } 841 842 if (type == S_IFLNK) { 843 int j; 844 845 ack(); 846 cp = buf; 847 for (i = 0; i < size; i += j) { 848 if ((j = read(rem, cp, size - i)) <= 0) 849 cleanup(0); 850 cp += j; 851 } 852 *cp = '\0'; 853 if (response() < 0) { 854 err(); 855 return; 856 } 857 if (symlink(buf, new) < 0) { 858 if (errno != ENOENT || chkparent(new) < 0 || 859 symlink(buf, new) < 0) 860 goto badnew1; 861 } 862 mode &= 0777; 863 if (opts & COMPARE) { 864 char tbuf[BUFSIZ]; 865 866 if ((i = readlink(target, tbuf, BUFSIZ)) >= 0 && 867 i == size && strncmp(buf, tbuf, size) == 0) { 868 (void) unlink(new); 869 ack(); 870 return; 871 } 872 if (opts & VERIFY) 873 goto differ; 874 } 875 goto fixup; 876 } 877 878 if ((f = creat(new, mode)) < 0) { 879 if (errno != ENOENT || chkparent(new) < 0 || 880 (f = creat(new, mode)) < 0) 881 goto badnew1; 882 } 883 884 ack(); 885 wrerr = 0; 886 for (i = 0; i < size; i += BUFSIZ) { 887 int amt = BUFSIZ; 888 889 cp = buf; 890 if (i + amt > size) 891 amt = size - i; 892 do { 893 int j = read(rem, cp, amt); 894 895 if (j <= 0) { 896 (void) close(f); 897 (void) unlink(new); 898 cleanup(0); 899 } 900 amt -= j; 901 cp += j; 902 } while (amt > 0); 903 amt = BUFSIZ; 904 if (i + amt > size) 905 amt = size - i; 906 if (wrerr == 0 && write(f, buf, amt) != amt) { 907 olderrno = errno; 908 wrerr++; 909 } 910 } 911 if (response() < 0) { 912 err(); 913 goto badnew2; 914 } 915 if (wrerr) 916 goto badnew1; 917 if (opts & COMPARE) { 918 FILE *f1, *f2; 919 int c; 920 921 if ((f1 = fopen(target, "r")) == NULL) 922 goto badtarget; 923 if ((f2 = fopen(new, "r")) == NULL) { 924 badnew1: error("%s:%s: %s\n", host, new, strerror(errno)); 925 goto badnew2; 926 } 927 while ((c = getc(f1)) == getc(f2)) 928 if (c == EOF) { 929 (void) fclose(f1); 930 (void) fclose(f2); 931 ack(); 932 goto badnew2; 933 } 934 (void) fclose(f1); 935 (void) fclose(f2); 936 if (opts & VERIFY) { 937 differ: buf[0] = '\0'; 938 (void)snprintf(buf + 1, sizeof(buf) - 1, 939 "need to update: %s\n",target); 940 (void) write(rem, buf, strlen(buf + 1) + 1); 941 goto badnew2; 942 } 943 } 944 945 if (fchtogm(f, new, mtime, owner, group, mode) < 0) { 946 badnew2: 947 if (f != -1) 948 (void) close(f); 949 (void) unlink(new); 950 return; 951 } 952 (void) close(f); 953 954 fixup: if (rename(new, target) < 0) { 955 badtarget: error("%s:%s: %s\n", host, target, strerror(errno)); 956 (void) unlink(new); 957 return; 958 } 959 960 if (opts & COMPARE) { 961 buf[0] = '\0'; 962 (void) snprintf(buf + 1, sizeof(buf) - 1, 963 "updated %s\n", target); 964 (void) write(rem, buf, strlen(buf + 1) + 1); 965 } else 966 ack(); 967 } 968 969 /* 970 * Creat a hard link to existing file. 971 */ 972 static void 973 hardlink(char *cmd) 974 { 975 char *cp; 976 struct stat stb; 977 char *oldname; 978 int opts, exists = 0; 979 980 cp = cmd; 981 opts = 0; 982 while (*cp >= '0' && *cp <= '7') 983 opts = (opts << 3) | (*cp++ - '0'); 984 if (*cp++ != ' ') { 985 error("hardlink: options not delimited\n"); 986 return; 987 } 988 oldname = cp; 989 while (*cp && *cp != ' ') 990 cp++; 991 if (*cp != ' ') { 992 error("hardlink: oldname name not delimited\n"); 993 return; 994 } 995 *cp++ = '\0'; 996 997 if (catname) { 998 (void) snprintf(tp, sizeof(target) - (tp - target), "/%s", cp); 999 } 1000 if (lstat(target, &stb) == 0) { 1001 if (!S_ISREG(stb.st_mode) && !S_ISLNK(stb.st_mode)) { 1002 error("%s: %s: not a regular file\n", host, target); 1003 return; 1004 } 1005 exists = 1; 1006 } 1007 if (chkparent(target) < 0 ) { 1008 error("%s:%s: %s (no parent)\n", 1009 host, target, strerror(errno)); 1010 return; 1011 } 1012 if (exists && (unlink(target) < 0)) { 1013 error("%s:%s: %s (unlink)\n", 1014 host, target, strerror(errno)); 1015 return; 1016 } 1017 if (link(oldname, target) < 0) { 1018 error("%s:can't link %s to %s\n", 1019 host, target, oldname); 1020 return; 1021 } 1022 ack(); 1023 } 1024 1025 /* 1026 * Check to see if parent directory exists and create one if not. 1027 */ 1028 static int 1029 chkparent(char *name) 1030 { 1031 char *cp; 1032 struct stat stb; 1033 1034 cp = strrchr(name, '/'); 1035 if (cp == NULL || cp == name) 1036 return(0); 1037 *cp = '\0'; 1038 if (lstat(name, &stb) < 0) { 1039 if (errno == ENOENT && chkparent(name) >= 0 && 1040 mkdir(name, 0777 & ~oumask) >= 0) { 1041 *cp = '/'; 1042 return(0); 1043 } 1044 } else if (S_ISDIR(stb.st_mode)) { 1045 *cp = '/'; 1046 return(0); 1047 } 1048 *cp = '/'; 1049 return(-1); 1050 } 1051 1052 /* 1053 * Change owner, group and mode of file. 1054 */ 1055 static int 1056 fchtogm(int fd, char *file, time_t mtime, char *owner, char *group, __mode_t mode) 1057 { 1058 int i; 1059 struct timeval tv[2]; 1060 uid_t uid; 1061 gid_t gid; 1062 extern char user[]; 1063 1064 uid = userid; 1065 if (userid == 0) { 1066 if (*owner == ':') { 1067 uid = atoi(owner + 1); 1068 } else if (pw == NULL || strcmp(owner, pw->pw_name) != 0) { 1069 if ((pw = getpwnam(owner)) == NULL) { 1070 if (mode & 04000) { 1071 note("%s:%s: unknown login name, clearing setuid", 1072 host, owner); 1073 mode &= ~04000; 1074 uid = 0; 1075 } 1076 } else 1077 uid = pw->pw_uid; 1078 } else 1079 uid = pw->pw_uid; 1080 if (*group == ':') { 1081 gid = atoi(group + 1); 1082 goto ok; 1083 } 1084 } else if ((mode & 04000) && strcmp(user, owner) != 0) 1085 mode &= ~04000; 1086 gid = -1; 1087 if (gr == NULL || strcmp(group, gr->gr_name) != 0) { 1088 if ((*group == ':' && (getgrgid(gid = atoi(group + 1)) == NULL)) 1089 || ((gr = getgrnam(group)) == NULL)) { 1090 if (mode & 02000) { 1091 note("%s:%s: unknown group", host, group); 1092 mode &= ~02000; 1093 } 1094 } else 1095 gid = gr->gr_gid; 1096 } else 1097 gid = gr->gr_gid; 1098 if (userid && gid >= 0) { 1099 if (gr) for (i = 0; gr->gr_mem[i] != NULL; i++) 1100 if (!(strcmp(user, gr->gr_mem[i]))) 1101 goto ok; 1102 mode &= ~02000; 1103 gid = -1; 1104 } 1105 ok: 1106 (void) gettimeofday(&tv[0], (struct timezone *)0); 1107 tv[1].tv_sec = mtime; 1108 tv[1].tv_usec = 0; 1109 if (fd != -1 ? futimes(fd, tv) < 0 : utimes(file, tv) < 0) 1110 note("%s: %s utimes: %s", host, file, strerror(errno)); 1111 if (fd != -1 ? fchown(fd, uid, gid) < 0 : chown(file, uid, gid) < 0) 1112 note("%s: %s chown: %s", host, file, strerror(errno)); 1113 else if (mode & 07000 && 1114 (fd != -1 ? fchmod(fd, mode) < 0 : chmod(file, mode) < 0)) 1115 note("%s: %s chmod: %s", host, file, strerror(errno)); 1116 return(0); 1117 } 1118 1119 /* 1120 * Check for files on the machine being updated that are not on the master 1121 * machine and remove them. 1122 */ 1123 static void 1124 rmchk(int opts) 1125 { 1126 char *cp, *s; 1127 struct stat stb; 1128 1129 if (debug) 1130 printf("rmchk()\n"); 1131 1132 /* 1133 * Tell the remote to clean the files from the last directory sent. 1134 */ 1135 (void) snprintf(buf, sizeof(buf), "C%o\n", opts & VERIFY); 1136 if (debug) 1137 printf("buf = %s", buf); 1138 (void) write(rem, buf, strlen(buf)); 1139 if (response() < 0) 1140 return; 1141 for (;;) { 1142 cp = s = buf; 1143 do { 1144 if (read(rem, cp, 1) != 1) 1145 lostconn(0); 1146 } while (*cp++ != '\n' && cp < &buf[BUFSIZ]); 1147 1148 switch (*s++) { 1149 case 'Q': /* Query if file should be removed */ 1150 /* 1151 * Return the following codes to remove query. 1152 * N\n -- file exists - DON'T remove. 1153 * Y\n -- file doesn't exist - REMOVE. 1154 */ 1155 *--cp = '\0'; 1156 (void) snprintf(tp, sizeof(target) - (tp - target), 1157 "/%s", s); 1158 if (debug) 1159 printf("check %s\n", target); 1160 if (except(target)) 1161 (void) write(rem, "N\n", 2); 1162 else if (lstat(target, &stb) < 0) 1163 (void) write(rem, "Y\n", 2); 1164 else 1165 (void) write(rem, "N\n", 2); 1166 break; 1167 1168 case '\0': 1169 *--cp = '\0'; 1170 if (*s != '\0') 1171 log(lfp, "%s\n", s); 1172 break; 1173 1174 case 'E': 1175 *tp = '\0'; 1176 ack(); 1177 return; 1178 1179 case '\1': 1180 case '\2': 1181 nerrs++; 1182 if (*s != '\n') { 1183 if (!iamremote) { 1184 fflush(stdout); 1185 (void) write(2, s, cp - s); 1186 } 1187 if (lfp != NULL) 1188 (void) fwrite(s, 1, cp - s, lfp); 1189 } 1190 if (buf[0] == '\2') 1191 lostconn(0); 1192 break; 1193 1194 default: 1195 error("rmchk: unexpected response '%s'\n", buf); 1196 err(); 1197 } 1198 } 1199 } 1200 1201 /* 1202 * Check the current directory (initialized by the 'T' command to server()) 1203 * for extraneous files and remove them. 1204 */ 1205 static void 1206 clean(char *cp) 1207 { 1208 DIR *d; 1209 struct dirent *dp; 1210 struct stat stb; 1211 char *otp; 1212 int len, opts; 1213 1214 opts = 0; 1215 while (*cp >= '0' && *cp <= '7') 1216 opts = (opts << 3) | (*cp++ - '0'); 1217 if (*cp != '\0') { 1218 error("clean: options not delimited\n"); 1219 return; 1220 } 1221 if ((d = opendir(target)) == NULL) { 1222 error("%s:%s: %s\n", host, target, strerror(errno)); 1223 return; 1224 } 1225 ack(); 1226 1227 otp = tp; 1228 len = tp - target; 1229 while ((dp = readdir(d)) != NULL) { 1230 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 1231 continue; 1232 if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { 1233 error("%s:%s/%s: Name too long\n", 1234 host, target, dp->d_name); 1235 continue; 1236 } 1237 tp = otp; 1238 *tp++ = '/'; 1239 cp = dp->d_name;; 1240 while ((*tp++ = *cp++) != 0) 1241 ; 1242 tp--; 1243 if (lstat(target, &stb) < 0) { 1244 error("%s:%s: %s\n", host, target, strerror(errno)); 1245 continue; 1246 } 1247 (void) snprintf(buf, sizeof(buf), "Q%s\n", dp->d_name); 1248 (void) write(rem, buf, strlen(buf)); 1249 cp = buf; 1250 do { 1251 if (read(rem, cp, 1) != 1) 1252 cleanup(0); 1253 } while (*cp++ != '\n' && cp < &buf[BUFSIZ]); 1254 *--cp = '\0'; 1255 cp = buf; 1256 if (*cp != 'Y') 1257 continue; 1258 if (opts & VERIFY) { 1259 cp = buf; 1260 *cp++ = '\0'; 1261 (void) snprintf(cp, sizeof(buf) - 1, 1262 "need to remove: %s\n", target); 1263 (void) write(rem, buf, strlen(cp) + 1); 1264 } else 1265 removeit(&stb); 1266 } 1267 closedir(d); 1268 (void) write(rem, "E\n", 2); 1269 (void) response(); 1270 tp = otp; 1271 *tp = '\0'; 1272 } 1273 1274 /* 1275 * Remove a file or directory (recursively) and send back an acknowledge 1276 * or an error message. 1277 */ 1278 static void 1279 removeit(struct stat *stp) 1280 { 1281 DIR *d; 1282 struct dirent *dp; 1283 char *cp; 1284 struct stat stb; 1285 char *otp; 1286 int len; 1287 1288 switch (stp->st_mode & S_IFMT) { 1289 case S_IFREG: 1290 case S_IFLNK: 1291 if (unlink(target) < 0) 1292 goto bad; 1293 goto removed; 1294 1295 case S_IFDIR: 1296 break; 1297 1298 default: 1299 error("%s:%s: not a plain file\n", host, target); 1300 return; 1301 } 1302 1303 if ((d = opendir(target)) == NULL) 1304 goto bad; 1305 1306 otp = tp; 1307 len = tp - target; 1308 while ((dp = readdir(d)) != NULL) { 1309 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 1310 continue; 1311 if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { 1312 error("%s:%s/%s: Name too long\n", 1313 host, target, dp->d_name); 1314 continue; 1315 } 1316 tp = otp; 1317 *tp++ = '/'; 1318 cp = dp->d_name;; 1319 while ((*tp++ = *cp++) != 0) 1320 ; 1321 tp--; 1322 if (lstat(target, &stb) < 0) { 1323 error("%s:%s: %s\n", host, target, strerror(errno)); 1324 continue; 1325 } 1326 removeit(&stb); 1327 } 1328 closedir(d); 1329 tp = otp; 1330 *tp = '\0'; 1331 if (rmdir(target) < 0) { 1332 bad: 1333 error("%s:%s: %s\n", host, target, strerror(errno)); 1334 return; 1335 } 1336 removed: 1337 cp = buf; 1338 *cp++ = '\0'; 1339 (void) snprintf(cp, sizeof(buf) - 1, "removed %s\n", target); 1340 (void) write(rem, buf, strlen(cp) + 1); 1341 } 1342 1343 /* 1344 * Execute a shell command to handle special cases. 1345 */ 1346 static void 1347 dospecial(char *cmd) 1348 { 1349 int fd[2], status, pid, i; 1350 char *cp, *s; 1351 char sbuf[BUFSIZ]; 1352 1353 if (pipe(fd) < 0) { 1354 error("%s\n", strerror(errno)); 1355 return; 1356 } 1357 if ((pid = fork()) == 0) { 1358 /* 1359 * Return everything the shell commands print. 1360 */ 1361 (void) close(0); 1362 (void) close(1); 1363 (void) close(2); 1364 (void) open(_PATH_DEVNULL, O_RDONLY); 1365 (void) dup(fd[1]); 1366 (void) dup(fd[1]); 1367 (void) close(fd[0]); 1368 (void) close(fd[1]); 1369 setgid(groupid); 1370 setuid(userid); 1371 execl(_PATH_BSHELL, "sh", "-c", cmd, 0); 1372 _exit(127); 1373 } 1374 (void) close(fd[1]); 1375 s = sbuf; 1376 *s++ = '\0'; 1377 while ((i = read(fd[0], buf, sizeof(buf))) > 0) { 1378 cp = buf; 1379 do { 1380 *s++ = *cp++; 1381 if (cp[-1] != '\n') { 1382 if (s < &sbuf[sizeof(sbuf)-1]) 1383 continue; 1384 *s++ = '\n'; 1385 } 1386 /* 1387 * Throw away blank lines. 1388 */ 1389 if (s == &sbuf[2]) { 1390 s--; 1391 continue; 1392 } 1393 (void) write(rem, sbuf, s - sbuf); 1394 s = &sbuf[1]; 1395 } while (--i); 1396 } 1397 if (s > &sbuf[1]) { 1398 *s++ = '\n'; 1399 (void) write(rem, sbuf, s - sbuf); 1400 } 1401 while ((i = wait(&status)) != pid && i != -1) 1402 ; 1403 if (i == -1) 1404 status = -1; 1405 (void) close(fd[0]); 1406 if (status) 1407 error("shell returned %d\n", status); 1408 else 1409 ack(); 1410 } 1411 1412 1413 void 1414 log(FILE *fp, const char *fmt, ...) 1415 { 1416 va_list ap; 1417 1418 /* Print changes locally if not quiet mode */ 1419 if (!qflag) { 1420 va_start(ap, fmt); 1421 (void)vprintf(fmt, ap); 1422 va_end(ap); 1423 } 1424 1425 /* Save changes (for mailing) if really updating files */ 1426 if (!(options & VERIFY) && fp != NULL) { 1427 va_start(ap, fmt); 1428 (void)vfprintf(fp, fmt, ap); 1429 va_end(ap); 1430 } 1431 } 1432 1433 void 1434 error(const char *fmt, ...) 1435 { 1436 static FILE *fp; 1437 va_list ap; 1438 1439 ++nerrs; 1440 if (!fp && !(fp = fdopen(rem, "w"))) 1441 return; 1442 va_start(ap, fmt); 1443 if (iamremote) { 1444 (void)fprintf(fp, "%crdist: ", 0x01); 1445 (void)vfprintf(fp, fmt, ap); 1446 fflush(fp); 1447 } 1448 else { 1449 fflush(stdout); 1450 (void)fprintf(stderr, "rdist: "); 1451 (void)vfprintf(stderr, fmt, ap); 1452 fflush(stderr); 1453 } 1454 va_end(ap); 1455 if (lfp != NULL) { 1456 (void)fprintf(lfp, "rdist: "); 1457 va_start(ap, fmt); 1458 (void)vfprintf(lfp, fmt, ap); 1459 va_end(ap); 1460 fflush(lfp); 1461 } 1462 } 1463 1464 void 1465 fatal(const char *fmt, ...) 1466 { 1467 static FILE *fp; 1468 va_list ap; 1469 1470 ++nerrs; 1471 if (!fp && !(fp = fdopen(rem, "w"))) 1472 return; 1473 va_start(ap, fmt); 1474 if (iamremote) { 1475 (void)fprintf(fp, "%crdist: ", 0x02); 1476 (void)vfprintf(fp, fmt, ap); 1477 fflush(fp); 1478 } 1479 else { 1480 fflush(stdout); 1481 (void)fprintf(stderr, "rdist: "); 1482 (void)vfprintf(stderr, fmt, ap); 1483 fflush(stderr); 1484 } 1485 va_end(ap); 1486 if (lfp != NULL) { 1487 (void)fprintf(lfp, "rdist: "); 1488 va_start(ap, fmt); 1489 (void)vfprintf(lfp, fmt, ap); 1490 va_end(ap); 1491 fflush(lfp); 1492 } 1493 cleanup(0); 1494 } 1495 1496 static int 1497 response(void) 1498 { 1499 char *cp, *s; 1500 char resp[BUFSIZ]; 1501 1502 if (debug) 1503 printf("response()\n"); 1504 1505 cp = s = resp; 1506 do { 1507 if (read(rem, cp, 1) != 1) 1508 lostconn(0); 1509 } while (*cp++ != '\n' && cp < &resp[BUFSIZ]); 1510 1511 switch (*s++) { 1512 case '\0': 1513 *--cp = '\0'; 1514 if (*s != '\0') { 1515 log(lfp, "%s\n", s); 1516 return(1); 1517 } 1518 return(0); 1519 case '\3': 1520 *--cp = '\0'; 1521 log(lfp, "Note: %s\n",s); 1522 return(response()); 1523 1524 default: 1525 s--; 1526 /* fall into... */ 1527 case '\1': 1528 case '\2': 1529 nerrs++; 1530 if (*s != '\n') { 1531 if (!iamremote) { 1532 fflush(stdout); 1533 (void) write(2, s, cp - s); 1534 } 1535 if (lfp != NULL) 1536 (void) fwrite(s, 1, cp - s, lfp); 1537 } 1538 if (resp[0] == '\2') 1539 lostconn(0); 1540 return(-1); 1541 } 1542 } 1543 1544 /* 1545 * Remove temporary files and do any cleanup operations before exiting. 1546 */ 1547 void 1548 cleanup(int signo) 1549 { 1550 (void) unlink(tempfile); 1551 exit(1); 1552 } 1553 1554 static void 1555 note(const char *fmt, ...) 1556 { 1557 static char buf[BUFSIZ]; 1558 va_list ap; 1559 1560 va_start(ap, fmt); 1561 (void)vsnprintf(buf, sizeof(buf), fmt, ap); 1562 va_end(ap); 1563 comment(buf); 1564 } 1565 1566 static void 1567 comment(char *s) 1568 { 1569 char c; 1570 1571 c = '\3'; 1572 write(rem, &c, 1); 1573 write(rem, s, strlen(s)); 1574 c = '\n'; 1575 write(rem, &c, 1); 1576 } 1577