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