|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1983 Regents of the University of California. ! 3: * All rights reserved. The Berkeley software License Agreement ! 4: * specifies the terms and conditions for redistribution. ! 5: */ ! 6: ! 7: #ifndef lint ! 8: char copyright[] = ! 9: "@(#) Copyright (c) 1983 Regents of the University of California.\n\ ! 10: All rights reserved.\n"; ! 11: #endif not lint ! 12: ! 13: #ifndef lint ! 14: static char sccsid[] = "@(#)rcp.c 5.9 (Berkeley) 10/22/87"; ! 15: #endif not lint ! 16: ! 17: /* ! 18: * rcp ! 19: */ ! 20: #include <sys/param.h> ! 21: #include <sys/file.h> ! 22: #include <sys/stat.h> ! 23: #include <sys/time.h> ! 24: #include <sys/ioctl.h> ! 25: ! 26: #include <netinet/in.h> ! 27: ! 28: #include <stdio.h> ! 29: #include <signal.h> ! 30: #include <pwd.h> ! 31: #include <ctype.h> ! 32: #include <netdb.h> ! 33: #include <errno.h> ! 34: ! 35: int rem; ! 36: char *colon(), *index(), *rindex(), *malloc(), *strcpy(); ! 37: int errs; ! 38: int lostconn(); ! 39: int errno; ! 40: char *sys_errlist[]; ! 41: int iamremote, targetshouldbedirectory; ! 42: int iamrecursive; ! 43: int pflag; ! 44: struct passwd *pwd; ! 45: struct passwd *getpwuid(); ! 46: int userid; ! 47: int port; ! 48: ! 49: struct buffer { ! 50: int cnt; ! 51: char *buf; ! 52: } *allocbuf(); ! 53: ! 54: /*VARARGS*/ ! 55: int error(); ! 56: ! 57: #define ga() (void) write(rem, "", 1) ! 58: ! 59: main(argc, argv) ! 60: int argc; ! 61: char **argv; ! 62: { ! 63: char *targ, *host, *src; ! 64: char *suser, *tuser, *thost; ! 65: int i; ! 66: char buf[BUFSIZ], cmd[16]; ! 67: struct servent *sp; ! 68: ! 69: sp = getservbyname("shell", "tcp"); ! 70: if (sp == NULL) { ! 71: fprintf(stderr, "rcp: shell/tcp: unknown service\n"); ! 72: exit(1); ! 73: } ! 74: port = sp->s_port; ! 75: pwd = getpwuid(userid = getuid()); ! 76: if (pwd == 0) { ! 77: fprintf(stderr, "who are you?\n"); ! 78: exit(1); ! 79: } ! 80: ! 81: for (argc--, argv++; argc > 0 && **argv == '-'; argc--, argv++) { ! 82: (*argv)++; ! 83: while (**argv) switch (*(*argv)++) { ! 84: ! 85: case 'r': ! 86: iamrecursive++; ! 87: break; ! 88: ! 89: case 'p': /* preserve mtimes and atimes */ ! 90: pflag++; ! 91: break; ! 92: ! 93: /* The rest of these are not for users. */ ! 94: case 'd': ! 95: targetshouldbedirectory = 1; ! 96: break; ! 97: ! 98: case 'f': /* "from" */ ! 99: iamremote = 1; ! 100: (void) response(); ! 101: (void) setuid(userid); ! 102: source(--argc, ++argv); ! 103: exit(errs); ! 104: ! 105: case 't': /* "to" */ ! 106: iamremote = 1; ! 107: (void) setuid(userid); ! 108: sink(--argc, ++argv); ! 109: exit(errs); ! 110: ! 111: default: ! 112: usage(); ! 113: } ! 114: } ! 115: if (argc < 2) ! 116: usage(); ! 117: if (argc > 2) ! 118: targetshouldbedirectory = 1; ! 119: rem = -1; ! 120: (void) sprintf(cmd, "rcp%s%s%s", ! 121: iamrecursive ? " -r" : "", pflag ? " -p" : "", ! 122: targetshouldbedirectory ? " -d" : ""); ! 123: (void) signal(SIGPIPE, lostconn); ! 124: targ = colon(argv[argc - 1]); ! 125: if (targ) { /* ... to remote */ ! 126: *targ++ = 0; ! 127: if (*targ == 0) ! 128: targ = "."; ! 129: thost = index(argv[argc - 1], '@'); ! 130: if (thost) { ! 131: *thost++ = 0; ! 132: tuser = argv[argc - 1]; ! 133: if (*tuser == '\0') ! 134: tuser = NULL; ! 135: else if (!okname(tuser)) ! 136: exit(1); ! 137: } else { ! 138: thost = argv[argc - 1]; ! 139: tuser = NULL; ! 140: } ! 141: for (i = 0; i < argc - 1; i++) { ! 142: src = colon(argv[i]); ! 143: if (src) { /* remote to remote */ ! 144: *src++ = 0; ! 145: if (*src == 0) ! 146: src = "."; ! 147: host = index(argv[i], '@'); ! 148: if (host) { ! 149: *host++ = 0; ! 150: suser = argv[i]; ! 151: if (*suser == '\0') ! 152: suser = pwd->pw_name; ! 153: else if (!okname(suser)) ! 154: continue; ! 155: (void) sprintf(buf, "/usr/ucb/rsh %s -l %s -n %s %s '%s%s%s:%s'", ! 156: host, suser, cmd, src, ! 157: tuser ? tuser : "", ! 158: tuser ? "@" : "", ! 159: thost, targ); ! 160: } else ! 161: (void) sprintf(buf, "/usr/ucb/rsh %s -n %s %s '%s%s%s:%s'", ! 162: argv[i], cmd, src, ! 163: tuser ? tuser : "", ! 164: tuser ? "@" : "", ! 165: thost, targ); ! 166: (void) susystem(buf); ! 167: } else { /* local to remote */ ! 168: if (rem == -1) { ! 169: (void) sprintf(buf, "%s -t %s", ! 170: cmd, targ); ! 171: host = thost; ! 172: rem = rcmd(&host, port, pwd->pw_name, ! 173: tuser ? tuser : pwd->pw_name, ! 174: buf, 0); ! 175: if (rem < 0) ! 176: exit(1); ! 177: if (response() < 0) ! 178: exit(1); ! 179: (void) setuid(userid); ! 180: } ! 181: source(1, argv+i); ! 182: } ! 183: } ! 184: } else { /* ... to local */ ! 185: if (targetshouldbedirectory) ! 186: verifydir(argv[argc - 1]); ! 187: for (i = 0; i < argc - 1; i++) { ! 188: src = colon(argv[i]); ! 189: if (src == 0) { /* local to local */ ! 190: (void) sprintf(buf, "/bin/cp%s%s %s %s", ! 191: iamrecursive ? " -r" : "", ! 192: pflag ? " -p" : "", ! 193: argv[i], argv[argc - 1]); ! 194: (void) susystem(buf); ! 195: } else { /* remote to local */ ! 196: *src++ = 0; ! 197: if (*src == 0) ! 198: src = "."; ! 199: host = index(argv[i], '@'); ! 200: if (host) { ! 201: *host++ = 0; ! 202: suser = argv[i]; ! 203: if (*suser == '\0') ! 204: suser = pwd->pw_name; ! 205: else if (!okname(suser)) ! 206: continue; ! 207: } else { ! 208: host = argv[i]; ! 209: suser = pwd->pw_name; ! 210: } ! 211: (void) sprintf(buf, "%s -f %s", cmd, src); ! 212: rem = rcmd(&host, port, pwd->pw_name, suser, ! 213: buf, 0); ! 214: if (rem < 0) ! 215: continue; ! 216: (void) setreuid(0, userid); ! 217: sink(1, argv+argc-1); ! 218: (void) setreuid(userid, 0); ! 219: (void) close(rem); ! 220: rem = -1; ! 221: } ! 222: } ! 223: } ! 224: exit(errs); ! 225: } ! 226: ! 227: verifydir(cp) ! 228: char *cp; ! 229: { ! 230: struct stat stb; ! 231: ! 232: if (stat(cp, &stb) >= 0) { ! 233: if ((stb.st_mode & S_IFMT) == S_IFDIR) ! 234: return; ! 235: errno = ENOTDIR; ! 236: } ! 237: error("rcp: %s: %s.\n", cp, sys_errlist[errno]); ! 238: exit(1); ! 239: } ! 240: ! 241: char * ! 242: colon(cp) ! 243: char *cp; ! 244: { ! 245: ! 246: while (*cp) { ! 247: if (*cp == ':') ! 248: return (cp); ! 249: if (*cp == '/') ! 250: return (0); ! 251: cp++; ! 252: } ! 253: return (0); ! 254: } ! 255: ! 256: okname(cp0) ! 257: char *cp0; ! 258: { ! 259: register char *cp = cp0; ! 260: register int c; ! 261: ! 262: do { ! 263: c = *cp; ! 264: if (c & 0200) ! 265: goto bad; ! 266: if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-') ! 267: goto bad; ! 268: cp++; ! 269: } while (*cp); ! 270: return (1); ! 271: bad: ! 272: fprintf(stderr, "rcp: invalid user name %s\n", cp0); ! 273: return (0); ! 274: } ! 275: ! 276: susystem(s) ! 277: char *s; ! 278: { ! 279: int status, pid, w; ! 280: register int (*istat)(), (*qstat)(); ! 281: ! 282: if ((pid = vfork()) == 0) { ! 283: (void) setuid(userid); ! 284: execl("/bin/sh", "sh", "-c", s, (char *)0); ! 285: _exit(127); ! 286: } ! 287: istat = signal(SIGINT, SIG_IGN); ! 288: qstat = signal(SIGQUIT, SIG_IGN); ! 289: while ((w = wait(&status)) != pid && w != -1) ! 290: ; ! 291: if (w == -1) ! 292: status = -1; ! 293: (void) signal(SIGINT, istat); ! 294: (void) signal(SIGQUIT, qstat); ! 295: return (status); ! 296: } ! 297: ! 298: source(argc, argv) ! 299: int argc; ! 300: char **argv; ! 301: { ! 302: char *last, *name; ! 303: struct stat stb; ! 304: static struct buffer buffer; ! 305: struct buffer *bp; ! 306: int x, readerr, f, amt; ! 307: off_t i; ! 308: char buf[BUFSIZ]; ! 309: ! 310: for (x = 0; x < argc; x++) { ! 311: name = argv[x]; ! 312: if ((f = open(name, 0)) < 0) { ! 313: error("rcp: %s: %s\n", name, sys_errlist[errno]); ! 314: continue; ! 315: } ! 316: if (fstat(f, &stb) < 0) ! 317: goto notreg; ! 318: switch (stb.st_mode&S_IFMT) { ! 319: ! 320: case S_IFREG: ! 321: break; ! 322: ! 323: case S_IFDIR: ! 324: if (iamrecursive) { ! 325: (void) close(f); ! 326: rsource(name, &stb); ! 327: continue; ! 328: } ! 329: /* fall into ... */ ! 330: default: ! 331: notreg: ! 332: (void) close(f); ! 333: error("rcp: %s: not a plain file\n", name); ! 334: continue; ! 335: } ! 336: last = rindex(name, '/'); ! 337: if (last == 0) ! 338: last = name; ! 339: else ! 340: last++; ! 341: if (pflag) { ! 342: /* ! 343: * Make it compatible with possible future ! 344: * versions expecting microseconds. ! 345: */ ! 346: (void) sprintf(buf, "T%ld 0 %ld 0\n", ! 347: stb.st_mtime, stb.st_atime); ! 348: (void) write(rem, buf, strlen(buf)); ! 349: if (response() < 0) { ! 350: (void) close(f); ! 351: continue; ! 352: } ! 353: } ! 354: (void) sprintf(buf, "C%04o %ld %s\n", ! 355: stb.st_mode&07777, stb.st_size, last); ! 356: (void) write(rem, buf, strlen(buf)); ! 357: if (response() < 0) { ! 358: (void) close(f); ! 359: continue; ! 360: } ! 361: if ((bp = allocbuf(&buffer, f, BUFSIZ)) < 0) { ! 362: (void) close(f); ! 363: continue; ! 364: } ! 365: readerr = 0; ! 366: for (i = 0; i < stb.st_size; i += bp->cnt) { ! 367: amt = bp->cnt; ! 368: if (i + amt > stb.st_size) ! 369: amt = stb.st_size - i; ! 370: if (readerr == 0 && read(f, bp->buf, amt) != amt) ! 371: readerr = errno; ! 372: (void) write(rem, bp->buf, amt); ! 373: } ! 374: (void) close(f); ! 375: if (readerr == 0) ! 376: ga(); ! 377: else ! 378: error("rcp: %s: %s\n", name, sys_errlist[readerr]); ! 379: (void) response(); ! 380: } ! 381: } ! 382: ! 383: #include <sys/dir.h> ! 384: ! 385: rsource(name, statp) ! 386: char *name; ! 387: struct stat *statp; ! 388: { ! 389: DIR *d = opendir(name); ! 390: char *last; ! 391: struct direct *dp; ! 392: char buf[BUFSIZ]; ! 393: char *bufv[1]; ! 394: ! 395: if (d == 0) { ! 396: error("rcp: %s: %s\n", name, sys_errlist[errno]); ! 397: return; ! 398: } ! 399: last = rindex(name, '/'); ! 400: if (last == 0) ! 401: last = name; ! 402: else ! 403: last++; ! 404: if (pflag) { ! 405: (void) sprintf(buf, "T%ld 0 %ld 0\n", ! 406: statp->st_mtime, statp->st_atime); ! 407: (void) write(rem, buf, strlen(buf)); ! 408: if (response() < 0) { ! 409: closedir(d); ! 410: return; ! 411: } ! 412: } ! 413: (void) sprintf(buf, "D%04o %d %s\n", statp->st_mode&07777, 0, last); ! 414: (void) write(rem, buf, strlen(buf)); ! 415: if (response() < 0) { ! 416: closedir(d); ! 417: return; ! 418: } ! 419: while (dp = readdir(d)) { ! 420: if (dp->d_ino == 0) ! 421: continue; ! 422: if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) ! 423: continue; ! 424: if (strlen(name) + 1 + strlen(dp->d_name) >= BUFSIZ - 1) { ! 425: error("%s/%s: Name too long.\n", name, dp->d_name); ! 426: continue; ! 427: } ! 428: (void) sprintf(buf, "%s/%s", name, dp->d_name); ! 429: bufv[0] = buf; ! 430: source(1, bufv); ! 431: } ! 432: closedir(d); ! 433: (void) write(rem, "E\n", 2); ! 434: (void) response(); ! 435: } ! 436: ! 437: response() ! 438: { ! 439: char resp, c, rbuf[BUFSIZ], *cp = rbuf; ! 440: ! 441: if (read(rem, &resp, 1) != 1) ! 442: lostconn(); ! 443: switch (resp) { ! 444: ! 445: case 0: /* ok */ ! 446: return (0); ! 447: ! 448: default: ! 449: *cp++ = resp; ! 450: /* fall into... */ ! 451: case 1: /* error, followed by err msg */ ! 452: case 2: /* fatal error, "" */ ! 453: do { ! 454: if (read(rem, &c, 1) != 1) ! 455: lostconn(); ! 456: *cp++ = c; ! 457: } while (cp < &rbuf[BUFSIZ] && c != '\n'); ! 458: if (iamremote == 0) ! 459: (void) write(2, rbuf, cp - rbuf); ! 460: errs++; ! 461: if (resp == 1) ! 462: return (-1); ! 463: exit(1); ! 464: } ! 465: /*NOTREACHED*/ ! 466: } ! 467: ! 468: lostconn() ! 469: { ! 470: ! 471: if (iamremote == 0) ! 472: fprintf(stderr, "rcp: lost connection\n"); ! 473: exit(1); ! 474: } ! 475: ! 476: sink(argc, argv) ! 477: int argc; ! 478: char **argv; ! 479: { ! 480: off_t i, j; ! 481: char *targ, *whopp, *cp; ! 482: int of, mode, wrerr, exists, first, count, amt, size; ! 483: struct buffer *bp; ! 484: static struct buffer buffer; ! 485: struct stat stb; ! 486: int targisdir = 0; ! 487: int mask = umask(0); ! 488: char *myargv[1]; ! 489: char cmdbuf[BUFSIZ], nambuf[BUFSIZ]; ! 490: int setimes = 0; ! 491: struct timeval tv[2]; ! 492: #define atime tv[0] ! 493: #define mtime tv[1] ! 494: #define SCREWUP(str) { whopp = str; goto screwup; } ! 495: ! 496: if (!pflag) ! 497: (void) umask(mask); ! 498: if (argc != 1) { ! 499: error("rcp: ambiguous target\n"); ! 500: exit(1); ! 501: } ! 502: targ = *argv; ! 503: if (targetshouldbedirectory) ! 504: verifydir(targ); ! 505: ga(); ! 506: if (stat(targ, &stb) == 0 && (stb.st_mode & S_IFMT) == S_IFDIR) ! 507: targisdir = 1; ! 508: for (first = 1; ; first = 0) { ! 509: cp = cmdbuf; ! 510: if (read(rem, cp, 1) <= 0) ! 511: return; ! 512: if (*cp++ == '\n') ! 513: SCREWUP("unexpected '\\n'"); ! 514: do { ! 515: if (read(rem, cp, 1) != 1) ! 516: SCREWUP("lost connection"); ! 517: } while (*cp++ != '\n'); ! 518: *cp = 0; ! 519: if (cmdbuf[0] == '\01' || cmdbuf[0] == '\02') { ! 520: if (iamremote == 0) ! 521: (void) write(2, cmdbuf+1, strlen(cmdbuf+1)); ! 522: if (cmdbuf[0] == '\02') ! 523: exit(1); ! 524: errs++; ! 525: continue; ! 526: } ! 527: *--cp = 0; ! 528: cp = cmdbuf; ! 529: if (*cp == 'E') { ! 530: ga(); ! 531: return; ! 532: } ! 533: ! 534: #define getnum(t) (t) = 0; while (isdigit(*cp)) (t) = (t) * 10 + (*cp++ - '0'); ! 535: if (*cp == 'T') { ! 536: setimes++; ! 537: cp++; ! 538: getnum(mtime.tv_sec); ! 539: if (*cp++ != ' ') ! 540: SCREWUP("mtime.sec not delimited"); ! 541: getnum(mtime.tv_usec); ! 542: if (*cp++ != ' ') ! 543: SCREWUP("mtime.usec not delimited"); ! 544: getnum(atime.tv_sec); ! 545: if (*cp++ != ' ') ! 546: SCREWUP("atime.sec not delimited"); ! 547: getnum(atime.tv_usec); ! 548: if (*cp++ != '\0') ! 549: SCREWUP("atime.usec not delimited"); ! 550: ga(); ! 551: continue; ! 552: } ! 553: if (*cp != 'C' && *cp != 'D') { ! 554: /* ! 555: * Check for the case "rcp remote:foo\* local:bar". ! 556: * In this case, the line "No match." can be returned ! 557: * by the shell before the rcp command on the remote is ! 558: * executed so the ^Aerror_message convention isn't ! 559: * followed. ! 560: */ ! 561: if (first) { ! 562: error("%s\n", cp); ! 563: exit(1); ! 564: } ! 565: SCREWUP("expected control record"); ! 566: } ! 567: cp++; ! 568: mode = 0; ! 569: for (; cp < cmdbuf+5; cp++) { ! 570: if (*cp < '0' || *cp > '7') ! 571: SCREWUP("bad mode"); ! 572: mode = (mode << 3) | (*cp - '0'); ! 573: } ! 574: if (*cp++ != ' ') ! 575: SCREWUP("mode not delimited"); ! 576: size = 0; ! 577: while (isdigit(*cp)) ! 578: size = size * 10 + (*cp++ - '0'); ! 579: if (*cp++ != ' ') ! 580: SCREWUP("size not delimited"); ! 581: if (targisdir) ! 582: (void) sprintf(nambuf, "%s%s%s", targ, ! 583: *targ ? "/" : "", cp); ! 584: else ! 585: (void) strcpy(nambuf, targ); ! 586: exists = stat(nambuf, &stb) == 0; ! 587: if (cmdbuf[0] == 'D') { ! 588: if (exists) { ! 589: if ((stb.st_mode&S_IFMT) != S_IFDIR) { ! 590: errno = ENOTDIR; ! 591: goto bad; ! 592: } ! 593: if (pflag) ! 594: (void) chmod(nambuf, mode); ! 595: } else if (mkdir(nambuf, mode) < 0) ! 596: goto bad; ! 597: myargv[0] = nambuf; ! 598: sink(1, myargv); ! 599: if (setimes) { ! 600: setimes = 0; ! 601: if (utimes(nambuf, tv) < 0) ! 602: error("rcp: can't set times on %s: %s\n", ! 603: nambuf, sys_errlist[errno]); ! 604: } ! 605: continue; ! 606: } ! 607: if ((of = open(nambuf, O_WRONLY|O_CREAT, mode)) < 0) { ! 608: bad: ! 609: error("rcp: %s: %s\n", nambuf, sys_errlist[errno]); ! 610: continue; ! 611: } ! 612: if (exists && pflag) ! 613: (void) fchmod(of, mode); ! 614: ga(); ! 615: if ((bp = allocbuf(&buffer, of, BUFSIZ)) < 0) { ! 616: (void) close(of); ! 617: continue; ! 618: } ! 619: cp = bp->buf; ! 620: count = 0; ! 621: wrerr = 0; ! 622: for (i = 0; i < size; i += BUFSIZ) { ! 623: amt = BUFSIZ; ! 624: if (i + amt > size) ! 625: amt = size - i; ! 626: count += amt; ! 627: do { ! 628: j = read(rem, cp, amt); ! 629: if (j <= 0) { ! 630: if (j == 0) ! 631: error("rcp: dropped connection"); ! 632: else ! 633: error("rcp: %s\n", ! 634: sys_errlist[errno]); ! 635: exit(1); ! 636: } ! 637: amt -= j; ! 638: cp += j; ! 639: } while (amt > 0); ! 640: if (count == bp->cnt) { ! 641: if (wrerr == 0 && ! 642: write(of, bp->buf, count) != count) ! 643: wrerr++; ! 644: count = 0; ! 645: cp = bp->buf; ! 646: } ! 647: } ! 648: if (count != 0 && wrerr == 0 && ! 649: write(of, bp->buf, count) != count) ! 650: wrerr++; ! 651: if (ftruncate(of, size)) ! 652: error("rcp: can't truncate %s: %s\n", ! 653: nambuf, sys_errlist[errno]); ! 654: (void) close(of); ! 655: (void) response(); ! 656: if (setimes) { ! 657: setimes = 0; ! 658: if (utimes(nambuf, tv) < 0) ! 659: error("rcp: can't set times on %s: %s\n", ! 660: nambuf, sys_errlist[errno]); ! 661: } ! 662: if (wrerr) ! 663: error("rcp: %s: %s\n", nambuf, sys_errlist[errno]); ! 664: else ! 665: ga(); ! 666: } ! 667: screwup: ! 668: error("rcp: protocol screwup: %s\n", whopp); ! 669: exit(1); ! 670: } ! 671: ! 672: struct buffer * ! 673: allocbuf(bp, fd, blksize) ! 674: struct buffer *bp; ! 675: int fd, blksize; ! 676: { ! 677: struct stat stb; ! 678: int size; ! 679: ! 680: if (fstat(fd, &stb) < 0) { ! 681: error("rcp: fstat: %s\n", sys_errlist[errno]); ! 682: return ((struct buffer *)-1); ! 683: } ! 684: size = roundup(stb.st_blksize, blksize); ! 685: if (size == 0) ! 686: size = blksize; ! 687: if (bp->cnt < size) { ! 688: if (bp->buf != 0) ! 689: free(bp->buf); ! 690: bp->buf = (char *)malloc((unsigned) size); ! 691: if (bp->buf == 0) { ! 692: error("rcp: malloc: out of memory\n"); ! 693: return ((struct buffer *)-1); ! 694: } ! 695: } ! 696: bp->cnt = size; ! 697: return (bp); ! 698: } ! 699: ! 700: /*VARARGS1*/ ! 701: error(fmt, a1, a2, a3, a4, a5) ! 702: char *fmt; ! 703: int a1, a2, a3, a4, a5; ! 704: { ! 705: char buf[BUFSIZ], *cp = buf; ! 706: ! 707: errs++; ! 708: *cp++ = 1; ! 709: (void) sprintf(cp, fmt, a1, a2, a3, a4, a5); ! 710: (void) write(rem, buf, strlen(buf)); ! 711: if (iamremote == 0) ! 712: (void) write(2, buf+1, strlen(buf+1)); ! 713: } ! 714: ! 715: usage() ! 716: { ! 717: fputs("usage: rcp [-p] f1 f2; or: rcp [-rp] f1 ... fn d2\n", stderr); ! 718: exit(1); ! 719: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.