home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume1 / 8712 / 1 next >
Encoding:
Text File  |  1990-07-13  |  30.2 KB  |  1,502 lines

  1. Path: uunet!husc6!necntc!ncoast!allbery
  2. From: david@elroy.jpl.nasa.gov (David Robinson)
  3. Newsgroups: comp.sources.misc
  4. Subject: Copytape with remote tapes
  5. Message-ID: <6320@ncoast.UUCP>
  6. Date: 2 Dec 87 05:55:21 GMT
  7. Sender: allbery@ncoast.UUCP
  8. Lines: 1490
  9. Approved: allbery@ncoast.UUCP
  10. X-Archive: comp.sources.misc/8712/1
  11.  
  12. Here is the copytape version that I mentioned earlier that works
  13. with the rmtlib routines to allow you to copy tapes across
  14. a network.
  15.  
  16. The syntax is the same as normal copytape but allows the input
  17. and output files to be given in the remote file format (ala rcp).
  18.  
  19. Example:
  20.     % copytape /dev/rmt0 foobar:/dev/rmt8
  21.  
  22.     -David Robinson
  23.     david@elroy.jpl.nasa.gov
  24.  
  25. # -----------CUT HERE---------------------
  26. # This is a shell archive.  Remove anything before this line,
  27. # then unpack it by saving it in a file and typing "sh file".
  28. #
  29. # Wrapped by elroy!david on Sun Nov 29 06:40:35 PST 1987
  30. # Contents:  Makefile README.network copytape.1 copytape.5 copytape.c rmt.h
  31. #    rmtlib.c
  32.  
  33. echo x - Makefile
  34. sed 's/^@//' > "Makefile" <<'@//E*O*F Makefile//'
  35. CFLAGS= -O
  36. MAN1 =    /usr/man/man1
  37. MAN5 =    /usr/man/man5
  38. BIN =    /usr/local
  39.  
  40. copytape:    copytape.o rmtlib.o
  41.     cc -O -o copytape copytape.o rmtlib.o
  42.  
  43. install:    copytape
  44.     install -s -m 0511 copytape ${BIN}
  45.  
  46. clean: 
  47.     rm -f copytape copytape.o rmtlib.o
  48.  
  49. man:    man1 man5
  50.  
  51. man1:    ${MAN1}/copytape.1
  52.     cp copytape.1 ${MAN1}
  53.  
  54. man5:    ${MAN5}/copytape.5
  55.     cp copytape.5 ${MAN5}
  56. @//E*O*F Makefile//
  57. chmod u=rw,g=r,o=r Makefile
  58.  
  59. echo x - README.network
  60. sed 's/^@//' > "README.network" <<'@//E*O*F README.network//'
  61.  
  62.  
  63. This is a version of the public domain copytape program that
  64. works with a slightly modified version of the public domain
  65. rmtlib remote tape library.
  66.  
  67. The version of copytape is not the most uptodate version but
  68. it works.  It should not be difficult to upgrade it if you
  69. have the most recent version.
  70.  
  71. The rmtlib was slightly modified to get some (not all) of the
  72. ioctl calls to work.  In particular the MTIOCGET ioctl does
  73. not work correctly with a remote tape so it is fake by always
  74. returning zero (0).  This assumes that the user will never try to
  75. copy a tape to a remote file instead of device.
  76.  
  77. Bugs probably do exist if so forward them on to me.
  78.  
  79.     11/29/87
  80.     David Robinson
  81.     MS 168-522
  82.     Jet Propulsion Lab
  83.     4800 Oak Grove
  84.     Pasadena CA 91109
  85.     david@elroy.jpl.nasa.gov
  86. @//E*O*F README.network//
  87. chmod u=rw,g=r,o=r README.network
  88.  
  89. echo x - copytape.1
  90. sed 's/^@//' > "copytape.1" <<'@//E*O*F copytape.1//'
  91. @.TH COPYTAPE 1 "25 June 1986"
  92. @.\"@(#)copytape.1 1.0 86/07/08 AICenter; by David S. Hayes
  93. @.SH NAME
  94. copytape \- duplicate magtapes
  95. @.SH SYNOPSIS
  96. @.B copytape
  97. \[\-f\]
  98. \[\-t\]
  99. \[\-s\fInnn\fP\]
  100. \[\-l\fInnn\fP\]
  101. \[\-v\]
  102. @.I
  103. \[input \[output\]\]
  104. @.SH DESCRIPTION
  105. @.LP
  106. @.I copytape
  107. duplicates magtapes.  It is intended for duplication of
  108. bootable or other non-file-structured (non-tar-structured)
  109. magtapes on systems with only one tape drive.
  110. @.I copytape
  111. is blissfully ignorant of tape formats.  It merely makes
  112. a bit-for-bit copy of its input.
  113. @.PP
  114. In normal use,
  115. @.I copytape
  116. would be run twice.  First, a boot tape is copied to an
  117. intermediate disk file.  The file is in a special format that
  118. preserves the record boundaries and tape marks.  On the second
  119. run, 
  120. @.I copytape
  121. reads this file and generates a new tape.  The second step
  122. may be repeated if multiple copies are required.  The typical
  123. process would look like this:
  124. @.sp
  125. @.RS +.5i
  126. tutorial% copytape /dev/rmt8 tape.tmp
  127. @.br
  128. tutorial% copytape tape.tmp /dev/rmt8
  129. @.br
  130. tutorial% rm tape.tmp
  131. @.RE
  132. @.PP
  133. @.I copytape
  134. copies from the standard input to the standard output, unless
  135. input and output arguments are provided.  It will automatically
  136. determine whether its input and output are physical tapes, or
  137. data files.  Data files are encoded in a special (human-readable)
  138. format.
  139. @.PP
  140. Since
  141. @.I copytape
  142. will automatically determine what sort of thing its input
  143. and output are, a twin-drive system can duplicate a tape in
  144. one pass.  The command would be
  145. @.RS +.5i
  146. tutorial% copytape /dev/rmt8 /dev/rmt9
  147. @.RE
  148. @.PP
  149. @.I copytape
  150. will also take as input and/or output the name of a
  151. remote tape drive in the format hostname[.username]:drive.
  152. If for example you are inputing from local rmt8 and wish
  153. to copy to rmt9 on machine fred, the command would be
  154. @.RS +.5i
  155. tutorial% copytape /dev/rmt8 fred:/dev/rmt9
  156. @.RE
  157. @.SH OPTIONS
  158. @.TP 3
  159. @.RI \-s nnn
  160. Skip tape marks.  The specified number of tape marks are skipped
  161. on the input tape, before the copy begins.  By default, nothing is
  162. skipped, resulting in a copy of the complete input tape.  Multiple
  163. tar(1) and dump(1) archives on a single tape are normally
  164. separated by a single tape mark.  On ANSI or IBM labelled tapes,
  165. each file has three associated tape marks.  Count carefully.
  166. @.TP 3
  167. @.RI \-l nnn
  168. Limit.  Only nnn files (data followed by a tape mark), at most,
  169. are copied.  This can be used to terminate a copy early.  If the
  170. skip option is also specified, the files skipped do not count
  171. against the limit.
  172. @.TP 3
  173. \-f
  174. @From tape.  The input is treated as though it were a physical
  175. tape, even if it is a data file.  This option can be used
  176. to copy block-structured device files other than magtapes.
  177. @.TP 3
  178. \-t
  179. To tape.  The output is treated as though it were a physical
  180. tape, even if it is a data file.  Normally, data files mark
  181. physical tape blocks with a (human\-readable) header describing
  182. the block.  If the \-t option is used when the output is
  183. actually a disk file, these headers will not be written.
  184. This will extract all the information from the tape, but
  185. @.I copytape
  186. will not be able to duplicate the original tape based on
  187. the resulting data file.
  188. @.TP 3
  189. \-v
  190. Verbose.
  191. @.I copytape
  192. does not normally produce any output on the control terminal.
  193. The verbose option will identify the input and output files,
  194. tell whether they are physical tapes or data files, and
  195. announce the size of each block copied.  This can produce
  196. a lot of output on even relatively short tapes.  It is
  197. intended mostly for diagnostic work.
  198. @.SH FILES
  199. /dev/rmt*
  200. @.SH "SEE ALSO"
  201. ansitape(1), dd(1), tar(1), mtio(4), copytape(5)
  202. @.SH AUTHOR
  203. David S. Hayes, Site Manager, US Army Artificial Intelligence Center.
  204. Originally developed September 1984 at Rensselaer Polytechnic Institute,
  205. Troy, New York.
  206. Revised July 1986.  This software is in the public domain.
  207. @.SH BUGS
  208. @.LP
  209. @.I copytape
  210. treats two successive file marks as logical end-of-tape.
  211. @.LP
  212. The intermediate data file can consume huge amounts of
  213. disk space.  A 2400-foot reel at 6250-bpi can burn 140 megabytes.
  214. This is not strictly speaking a bug, but users should
  215. be aware of the possibility.  Check disk space with
  216. @.I df(1)
  217. before starting
  218. @.IR copytape .
  219. Caveat Emptor!
  220. @.LP
  221. A 256K buffer is used internally.  This limits the maximum block
  222. size of the input tape.
  223. @//E*O*F copytape.1//
  224. chmod u=rw,g=r,o=r copytape.1
  225.  
  226. echo x - copytape.5
  227. sed 's/^@//' > "copytape.5" <<'@//E*O*F copytape.5//'
  228. @.TH COPYTAPE 5  "8 August 1986"
  229. @.SH NAME
  230. copytape \- copytape intermediate data file format
  231. @.SH DESCRIPTION
  232. @.I copytape
  233. duplicates magtapes on single\-tape systems by making
  234. an intermediate copy of the tape in a disk file.
  235. This disk file has a special format that preserves
  236. the block boundaries and tape marks of the original
  237. physical tape.
  238. @.PP
  239. Each block is preceded by a header identifying what
  240. sort of block it is.  In the case of data blocks,
  241. the length of the data is also given.  Each header is
  242. on a separate text line, followed by a newline character.
  243. @.sp
  244. @.TP 3
  245. CPTP:BLK \fInnnnnn\fP
  246. @.ti -3
  247. \fIdata\fP\\n
  248. @.sp
  249. A data block is identified by the keyword
  250. @.IR BLK .
  251. The length of the block is given in a six\-character
  252. numeric field.  The field is zero\-padded on the left if
  253. less than six characters are needed.  The header is
  254. followed by a newline character.
  255. The original data follows.  The data may have any characters
  256. in it, since
  257. @.I copytape
  258. uses a read(2) to extract it.
  259. The data is followed by a newline, to make the file easy
  260. to view with an editor.
  261. @.TP 3
  262. CPTP:MRK
  263. A tape mark was encountered in the original tape.
  264. @.TP 3
  265. CPTP:EOT
  266. When two consecutive tape marks are encountered,
  267. @.I copytape
  268. treats the second as a logical end\-of\-tape.  On
  269. output, both MRK and EOT generate
  270. a physical tape mark.
  271. @.I copytape
  272. stops processing after copying an EOT.
  273. @.SH "SEE ALSO"
  274. mtio(4)
  275. @.SH BUGS
  276. Some weird tapes may not use two consecutive tape marks
  277. as logical end\-of\-tape.
  278. @//E*O*F copytape.5//
  279. chmod u=rw,g=r,o=r copytape.5
  280.  
  281. echo x - copytape.c
  282. sed 's/^@//' > "copytape.c" <<'@//E*O*F copytape.c//'
  283.  
  284. /*
  285.  * COPYTAPE.C
  286.  *
  287.  * This program duplicates magnetic tapes, preserving the
  288.  * blocking structure and placement of tape marks.
  289.  *
  290.  * This program was updated at
  291.  *
  292.  *    U.S. Army Artificial Intelligence Center
  293.  *    HQDA (Attn: DACS-DMA)
  294.  *    Pentagon
  295.  *    Washington, DC  20310-0200
  296.  *
  297.  *    Phone: (202) 694-6900
  298.  *
  299.  **************************************************
  300.  *
  301.  *    THIS PROGRAM IS IN THE PUBLIC DOMAIN
  302.  *
  303.  **************************************************
  304.  *
  305.  * July 1986        David S. Hayes
  306.  *        Made data file format human-readable.
  307.  *
  308.  * April 1985        David S. Hayes
  309.  *        Original Version.
  310.  */
  311.  
  312.  
  313. #include <stdio.h>
  314. #include <sys/types.h>
  315. #include "rmt.h"
  316. #include <sys/ioctl.h>
  317. #include <sys/mtio.h>
  318. #include <sys/file.h>
  319.  
  320. extern int      errno;
  321.  
  322. #define BUFLEN        262144    /* max tape block size */
  323. #define TAPE_MARK    -100    /* return record length if we read a
  324.                  * tape mark */
  325. #define END_OF_TAPE    -101    /* 2 consecutive tape marks */
  326. #define FORMAT_ERROR    -102    /* data file munged */
  327.  
  328. int             totape = 0,    /* treat destination as a tape drive */
  329.                 fromtape = 0;    /* treat source as a tape drive */
  330.  
  331. int             verbose = 0;    /* tell what we're up to */
  332.  
  333. char           *source = "stdin",
  334.                *dest = "stdout";
  335.  
  336. char            tapebuf[BUFLEN];
  337.  
  338. main(argc, argv)
  339.     int             argc;
  340.     char           *argv[];
  341. {
  342.     int             from = 0,
  343.                     to = 1;
  344.     int             len;    /* number of bytes in record */
  345.     int             skip = 0;    /* number of files to skip before
  346.                  * copying */
  347.     unsigned int    limit = 0xffffffff;
  348.     int             i;
  349.     struct mtget    status;
  350.  
  351.     for (i = 1; i < argc && argv[i][0] == '-'; i++) {
  352.     switch (argv[i][1]) {
  353.       case 's':        /* skip option */
  354.         skip = atoi(&argv[i][2]);
  355.         break;
  356.  
  357.       case 'l':
  358.         limit = atoi(&argv[i][2]);
  359.         break;
  360.  
  361.       case 'f':        /* from tape option */
  362.         fromtape = 1;
  363.         break;
  364.  
  365.       case 't':        /* to tape option */
  366.         totape = 1;
  367.         break;
  368.  
  369.       case 'v':        /* be wordy */
  370.         verbose = 1;
  371.         break;
  372.  
  373.       default:
  374.         fprintf(stderr, "usage: copytape [-f] [-t] [-lnn] [-snn] [-v] from to\n");
  375.         exit(-1);
  376.     }
  377.     }
  378.  
  379.     if (i < argc) {
  380.     from = open(argv[i], O_RDONLY);
  381.     source = argv[i];
  382.     if (from == -1) {
  383.         perror("copytape: input open failed");
  384.         exit(-1);
  385.     }
  386.     i++;;
  387.     }
  388.     if (i < argc) {
  389.     to = open(argv[i], O_WRONLY | O_CREAT | O_TRUNC, 0666);
  390.     dest = argv[i];
  391.     if (to == -1) {
  392.         perror("copytape: output open failed");
  393.         exit(-1);
  394.     }
  395.     i++;
  396.     }
  397.     if (i < argc)
  398.     perror("copytape: extra arguments ignored");
  399.  
  400.     /*
  401.      * Determine if source and/or destination is a tape device. Try to
  402.      * issue a magtape ioctl to it.  If it doesn't error, then it was a
  403.      * magtape. 
  404.      */
  405.  
  406.     errno = 0;
  407.     ioctl(from, MTIOCGET, &status);
  408.     fromtape |= errno == 0;
  409.     errno = 0;
  410.     ioctl(to, MTIOCGET, &status);
  411.     totape |= errno == 0;
  412.     errno = 0;
  413.  
  414.     if (verbose) {
  415.     fprintf(stderr, "copytape: from %s (%s)\n",
  416.         source, fromtape ? "tape" : "data");
  417.     fprintf(stderr, "          to %s (%s)\n",
  418.         dest, totape ? "tape" : "data");
  419.     }
  420.  
  421.     /*
  422.      * Skip number of files, specified by -snnn, given on the command
  423.      * line. This is used to copy second and subsequent files on the
  424.      * tape. 
  425.      */
  426.  
  427.     if (verbose && skip) {
  428.     fprintf(stderr, "copytape: skipping %d input files\n", skip);
  429.     }
  430.     for (i = 0; i < skip; i++) {
  431.     do {
  432.         len = input(from);
  433.     } while (len > 0);
  434.     if (len == FORMAT_ERROR) {
  435.         perror(stderr, "copytape: format error on skip");
  436.         exit(-1);
  437.     };
  438.     if (len == END_OF_TAPE) {
  439.         fprintf(stderr, "copytape: only %d files in input\n", i);
  440.         exit(-1);
  441.     };
  442.     };
  443.  
  444.     /*
  445.      * Do the copy. 
  446.      */
  447.  
  448.     len = 0;
  449.     while (limit && !(len == END_OF_TAPE || len == FORMAT_ERROR)) {
  450.     do {
  451.         do {
  452.         len = input(from);
  453.         if (len == FORMAT_ERROR)
  454.             perror("copytape: data format error - block ignored");
  455.         } while (len == FORMAT_ERROR);
  456.  
  457.         output(to, len);
  458.  
  459.         if (verbose) {
  460.         switch (len) {
  461.           case TAPE_MARK:
  462.             fprintf(stderr, "  copied MRK\n");
  463.             break;
  464.  
  465.           case END_OF_TAPE:
  466.             fprintf(stderr, "  copied EOT\n");
  467.             break;
  468.  
  469.           default:
  470.             fprintf(stderr, "  copied %d bytes\n", len);
  471.         };
  472.         };
  473.     } while (len > 0);
  474.     limit--;
  475.     }
  476.     exit(0);
  477. }
  478.  
  479.  
  480. /*
  481.  * Input up to 256K from a file or tape. If input file is a tape, then
  482.  * do markcount stuff.  Input record length will be supplied by the
  483.  * operating system. 
  484.  */
  485.  
  486. input(fd)
  487.     int             fd;
  488. {
  489.     static          markcount = 0;    /* number of consecutive tape
  490.                      * marks */
  491.     int             len,
  492.                     l2,
  493.                     c;
  494.     char            header[40];
  495.  
  496.     if (fromtape) {
  497.     len = read(fd, tapebuf, BUFLEN);
  498.     switch (len) {
  499.       case -1:
  500.         perror("copytape: can't read input");
  501.         return END_OF_TAPE;
  502.  
  503.       case 0:
  504.         if (++markcount == 2)
  505.         return END_OF_TAPE;
  506.         else
  507.         return TAPE_MARK;
  508.  
  509.       default:
  510.         markcount = 0;        /* reset tape mark count */
  511.         return len;
  512.     };
  513.     }
  514.     /* Input is really a data file. */
  515.     l2 = read(fd, header, 5);
  516.     if (l2 != 5 || strncmp(header, "CPTP:", 5) != 0)
  517.     return FORMAT_ERROR;
  518.  
  519.     l2 = read(fd, header, 4);
  520.     if (strncmp(header, "BLK ", 4) == 0) {
  521.     l2 = read(fd, header, 7);
  522.     if (l2 != 7)
  523.         return FORMAT_ERROR;
  524.     header[6] = '\0';
  525.     len = atoi(header);
  526.     l2 = read(fd, tapebuf, len);
  527.     if (l2 != len)
  528.         return FORMAT_ERROR;
  529.     read(fd, header, 1);    /* skip trailing newline */
  530.     } else if (strncmp(header, "MRK\n", 4) == 0)
  531.     return TAPE_MARK;
  532.     else if (strncmp(header, "EOT\n", 4) == 0)
  533.     return END_OF_TAPE;
  534.     else
  535.     return FORMAT_ERROR;
  536.  
  537.     return len;
  538. }
  539.  
  540.  
  541. /*
  542.  * Copy a buffer out to a file or tape. 
  543.  *
  544.  * If output is a tape, write the record.  A length of zero indicates that
  545.  * a tapemark should be written. 
  546.  *
  547.  * If not a tape, write len to the output file, then the buffer.  
  548.  */
  549.  
  550. output(fd, len)
  551.     int             fd,
  552.                     len;
  553. {
  554.     struct mtop     op;
  555.     char            header[20];
  556.  
  557.     if (totape && (len == TAPE_MARK || len == END_OF_TAPE)) {
  558.     op.mt_op = MTWEOF;
  559.     op.mt_count = 1;
  560.     ioctl(fd, MTIOCTOP, &op);
  561.     return;
  562.     }
  563.     if (!totape) {
  564.     switch (len) {
  565.       case TAPE_MARK:
  566.         write(fd, "CPTP:MRK\n", 9);
  567.         break;
  568.  
  569.       case END_OF_TAPE:
  570.         write(fd, "CPTP:EOT\n", 9);
  571.         break;
  572.  
  573.       case FORMAT_ERROR:
  574.         break;
  575.  
  576.       default:
  577.         sprintf(header, "CPTP:BLK %06d\n", len);
  578.         write(fd, header, strlen(header));
  579.         write(fd, tapebuf, len);
  580.         write(fd, "\n", 1);
  581.     }
  582.     } else
  583.     write(fd, tapebuf, len);
  584. }
  585. @//E*O*F copytape.c//
  586. chmod u=rw,g=r,o=r copytape.c
  587.  
  588. echo x - rmt.h
  589. sed 's/^@//' > "rmt.h" <<'@//E*O*F rmt.h//'
  590. /*
  591.  *    rmt.h
  592.  *
  593.  *    Added routines to replace open(), close(), lseek(), ioctl(), etc.
  594.  *    The preprocessor can be used to remap these the rmtopen(), etc
  595.  *    thus minimizing source changes.
  596.  *
  597.  *    This file must be included before <sys/stat.h>, since it redefines
  598.  *    stat to be rmtstat, so that struct stat xyzzy; declarations work
  599.  *    properly.
  600.  *
  601.  *    -- Fred Fish (w/some changes by Arnold Robbins)
  602.  */
  603.  
  604.  
  605. #ifndef access        /* avoid multiple redefinition */
  606. #ifndef lint        /* in this case what lint doesn't know won't hurt it */
  607. #define access rmtaccess
  608. #define close rmtclose
  609. #define creat rmtcreat
  610. #define dup rmtdup
  611. #define fcntl rmtfcntl
  612. #define fstat rmtfstat
  613. #define ioctl rmtioctl
  614. #define isatty rmtisatty
  615. #define lseek rmtlseek
  616. #define lstat rmtlstat
  617. #define open rmtopen
  618. #define read rmtread
  619. #define stat rmtstat
  620. #define write rmtwrite
  621.  
  622. extern long rmtlseek ();    /* all the rest are int's */
  623. #endif
  624. #endif
  625. @//E*O*F rmt.h//
  626. chmod u=rw,g=r,o=r rmt.h
  627.  
  628. echo x - rmtlib.c
  629. sed 's/^@//' > "rmtlib.c" <<'@//E*O*F rmtlib.c//'
  630. /*
  631.  *    rmt --- remote tape emulator subroutines
  632.  *
  633.  *    Originally written by Jeff Lee, modified some by Arnold Robbins
  634.  *
  635.  *    WARNING:  The man page rmt(8) for /etc/rmt documents the remote mag
  636.  *    tape protocol which rdump and rrestore use.  Unfortunately, the man
  637.  *    page is *WRONG*.  The author of the routines I'm including originally
  638.  *    wrote his code just based on the man page, and it didn't work, so he
  639.  *    went to the rdump source to figure out why.  The only thing he had to
  640.  *    change was to check for the 'F' return code in addition to the 'E',
  641.  *    and to separate the various arguments with \n instead of a space.  I
  642.  *    personally don't think that this is much of a problem, but I wanted to
  643.  *    point it out.
  644.  *    -- Arnold Robbins
  645.  *
  646.  *    Redone as a library that can replace open, read, write, etc, by
  647.  *    Fred Fish, with some additional work by Arnold Robbins.
  648.  */
  649.  
  650. /*
  651.  *    MAXUNIT --- Maximum number of remote tape file units
  652.  *
  653.  *    READ --- Return the number of the read side file descriptor
  654.  *    WRITE --- Return the number of the write side file descriptor
  655.  */
  656.  
  657. #define RMTIOCTL    1
  658.  
  659. #include <stdio.h>
  660. #include <signal.h>
  661. #include <sys/types.h>
  662.  
  663. #ifdef RMTIOCTL
  664. #include <sys/ioctl.h>
  665. #include <sys/mtio.h>
  666. #endif
  667.  
  668. #include <errno.h>
  669. #include <setjmp.h>
  670. #include <sys/stat.h>
  671.  
  672. #define BUFMAGIC    64    /* a magic number for buffer sizes */
  673. #define MAXUNIT    4
  674.  
  675. #define READ(fd)    (Ctp[fd][0])
  676. #define WRITE(fd)    (Ptc[fd][1])
  677.  
  678. static int Ctp[MAXUNIT][2] = { -1, -1, -1, -1, -1, -1, -1, -1 };
  679. static int Ptc[MAXUNIT][2] = { -1, -1, -1, -1, -1, -1, -1, -1 };
  680.  
  681. static jmp_buf Jmpbuf;
  682. extern int errno;
  683.  
  684. /*
  685.  *    abort --- close off a remote tape connection
  686.  */
  687.  
  688. static void abort(fildes)
  689. int fildes;
  690. {
  691.     close(READ(fildes));
  692.     close(WRITE(fildes));
  693.     READ(fildes) = -1;
  694.     WRITE(fildes) = -1;
  695. }
  696.  
  697.  
  698.  
  699. /*
  700.  *    command --- attempt to perform a remote tape command
  701.  */
  702.  
  703. static int command(fildes, buf)
  704. int fildes;
  705. char *buf;
  706. {
  707.     register int blen;
  708.     int (*pstat)();
  709.  
  710. /*
  711.  *    save current pipe status and try to make the request
  712.  */
  713.  
  714.     blen = strlen(buf);
  715.     pstat = signal(SIGPIPE, SIG_IGN);
  716.     if (write(WRITE(fildes), buf, blen) == blen)
  717.     {
  718.         signal(SIGPIPE, pstat);
  719.         return(0);
  720.     }
  721.  
  722. /*
  723.  *    something went wrong. close down and go home
  724.  */
  725.  
  726.     signal(SIGPIPE, pstat);
  727.     abort(fildes);
  728.  
  729.     errno = EIO;
  730.     return(-1);
  731. }
  732.  
  733.  
  734.  
  735. /*
  736.  *    status --- retrieve the status from the pipe
  737.  */
  738.  
  739. static int status(fildes)
  740. int fildes;
  741. {
  742.     int i;
  743.     char c, *cp;
  744.     char buffer[BUFMAGIC];
  745.         int st;
  746.  
  747. /*
  748.  *    read the reply command line
  749.  */
  750.  
  751.     for (i = 0, cp = buffer; i < BUFMAGIC; i++, cp++)
  752.     {
  753.         if ((st = read(READ(fildes), cp, 1)) != 1)
  754.         {
  755.             abort(fildes);
  756.             errno = EIO;
  757.             return(-1);
  758.         }
  759.         if (*cp == '\n')
  760.         {
  761.             *cp = 0;
  762.             break;
  763.         }
  764.     }
  765.  
  766.     if (i == BUFMAGIC)
  767.     {
  768.         abort(fildes);
  769.         errno = EIO;
  770.         return(-1);
  771.     }
  772.  
  773. /*
  774.  *    check the return status
  775.  */
  776.  
  777.     for (cp = buffer; *cp; cp++)
  778.         if (*cp != ' ')
  779.             break;
  780.  
  781.     if (*cp == 'E' || *cp == 'F')
  782.     {
  783.         errno = atoi(cp + 1);
  784.         while (read(READ(fildes), &c, 1) == 1)
  785.             if (c == '\n')
  786.                 break;
  787.  
  788.         if (*cp == 'F')
  789.             abort(fildes);
  790.  
  791.         return(-1);
  792.     }
  793.  
  794. /*
  795.  *    check for mis-synced pipes
  796.  */
  797.  
  798.     if (*cp != 'A')
  799.     {
  800.         abort(fildes);
  801.         errno = EIO;
  802.         return(-1);
  803.     }
  804.  
  805.     return(atoi(cp + 1));
  806. }
  807.  
  808.  
  809.  
  810. /*
  811.  *    _rmt_open --- open a magtape device on system specified, as given user
  812.  *
  813.  *    file name has the form system[.user]:/dev/????
  814.  */
  815.  
  816. #define MAXHOSTLEN    257    /* BSD allows very long host names... */
  817.  
  818. static int _rmt_open (path, oflag, mode)
  819. char *path;
  820. int oflag;
  821. int mode;
  822. {
  823.     int i, rc;
  824.     char buffer[BUFMAGIC];
  825.     char system[MAXHOSTLEN];
  826.     char device[BUFMAGIC];
  827.     char login[BUFMAGIC];
  828.     char *sys, *dev, *user;
  829.  
  830.     sys = system;
  831.     dev = device;
  832.     user = login;
  833.  
  834. /*
  835.  *    first, find an open pair of file descriptors
  836.  */
  837.  
  838.     for (i = 0; i < MAXUNIT; i++)
  839.         if (READ(i) == -1 && WRITE(i) == -1)
  840.             break;
  841.  
  842.     if (i == MAXUNIT)
  843.     {
  844.         errno = EMFILE;
  845.         return(-1);
  846.     }
  847.  
  848. /*
  849.  *    pull apart system and device, and optional user
  850.  *    don't munge original string
  851.  */
  852.     while (*path != '.' && *path != ':') {
  853.         *sys++ = *path++;
  854.     }
  855.     *sys = '\0';
  856.     path++;
  857.  
  858.     if (*(path - 1) == '.')
  859.     {
  860.         while (*path != ':') {
  861.             *user++ = *path++;
  862.         }
  863.         *user = '\0';
  864.         path++;
  865.     }
  866.     else
  867.         *user = '\0';
  868.  
  869.     while (*path) {
  870.         *dev++ = *path++;
  871.     }
  872.     *dev = '\0';
  873.  
  874. /*
  875.  *    setup the pipes for the 'rsh' command and fork
  876.  */
  877.  
  878.     if (pipe(Ptc[i]) == -1 || pipe(Ctp[i]) == -1)
  879.         return(-1);
  880.  
  881.     if ((rc = fork()) == -1)
  882.         return(-1);
  883.  
  884.     if (rc == 0)
  885.     {
  886.         close(0);
  887.         dup(Ptc[i][0]);
  888.         close(Ptc[i][0]); close(Ptc[i][1]);
  889.         close(1);
  890.         dup(Ctp[i][1]);
  891.         close(Ctp[i][0]); close(Ctp[i][1]);
  892.         (void) setuid (getuid ());
  893.         (void) setgid (getgid ());
  894.         if (*user)
  895.         {
  896.             execl("/usr/ucb/rsh", "rsh", system, "-l", login,
  897.                 "/etc/rmt", (char *) 0);
  898.             execl("/usr/bin/remsh", "remsh", system, "-l", login,
  899.                 "/etc/rmt", (char *) 0);
  900.         }
  901.         else
  902.         {
  903.             execl("/usr/ucb/rsh", "rsh", system,
  904.                 "/etc/rmt", (char *) 0);
  905.             execl("/usr/bin/remsh", "remsh", system,
  906.                 "/etc/rmt", (char *) 0);
  907.         }
  908.  
  909. /*
  910.  *    bad problems if we get here
  911.  */
  912.  
  913.         perror("exec");
  914.         exit(1);
  915.     }
  916.  
  917.     close(Ptc[i][0]); close(Ctp[i][1]);
  918.  
  919. /*
  920.  *    now attempt to open the tape device
  921.  */
  922.  
  923.     sprintf(buffer, "O%s\n%d\n", device, oflag);
  924.     if (command(i, buffer) == -1 || status(i) == -1)
  925.         return(-1);
  926.  
  927.     return(i);
  928. }
  929.  
  930.  
  931.  
  932. /*
  933.  *    _rmt_close --- close a remote magtape unit and shut down
  934.  */
  935.  
  936. static int _rmt_close(fildes)
  937. int fildes;
  938. {
  939.     int rc;
  940.  
  941.     if (command(fildes, "C\n") != -1)
  942.     {
  943.         rc = status(fildes);
  944.  
  945.         abort(fildes);
  946.         return(rc);
  947.     }
  948.  
  949.     return(-1);
  950. }
  951.  
  952.  
  953.  
  954. /*
  955.  *    _rmt_read --- read a buffer from a remote tape
  956.  */
  957.  
  958. static int _rmt_read(fildes, buf, nbyte)
  959. int fildes;
  960. char *buf;
  961. unsigned int nbyte;
  962. {
  963.     int rc, i;
  964.     char buffer[BUFMAGIC];
  965.  
  966.     sprintf(buffer, "R%d\n", nbyte);
  967.     if (command(fildes, buffer) == -1 || (rc = status(fildes)) == -1)
  968.         return(-1);
  969.  
  970.     for (i = 0; i < rc; i += nbyte, buf += nbyte)
  971.     {
  972.         nbyte = read(READ(fildes), buf, rc);
  973.         if (nbyte <= 0)
  974.         {
  975.             abort(fildes);
  976.             errno = EIO;
  977.             return(-1);
  978.         }
  979.     }
  980.  
  981.     return(rc);
  982. }
  983.  
  984.  
  985.  
  986. /*
  987.  *    _rmt_write --- write a buffer to the remote tape
  988.  */
  989.  
  990. static int _rmt_write(fildes, buf, nbyte)
  991. int fildes;
  992. char *buf;
  993. unsigned int nbyte;
  994. {
  995.     int rc;
  996.     char buffer[BUFMAGIC];
  997.     int (*pstat)();
  998.  
  999.     sprintf(buffer, "W%d\n", nbyte);
  1000.     if (command(fildes, buffer) == -1)
  1001.         return(-1);
  1002.  
  1003.     pstat = signal(SIGPIPE, SIG_IGN);
  1004.     if (write(WRITE(fildes), buf, nbyte) == nbyte)
  1005.     {
  1006.         signal (SIGPIPE, pstat);
  1007.         return(status(fildes));
  1008.     }
  1009.  
  1010.     signal (SIGPIPE, pstat);
  1011.     abort(fildes);
  1012.     errno = EIO;
  1013.     return(-1);
  1014. }
  1015.  
  1016.  
  1017.  
  1018. /*
  1019.  *    _rmt_lseek --- perform an imitation lseek operation remotely
  1020.  */
  1021.  
  1022. static long _rmt_lseek(fildes, offset, whence)
  1023. int fildes;
  1024. long offset;
  1025. int whence;
  1026. {
  1027.     char buffer[BUFMAGIC];
  1028.  
  1029.     sprintf(buffer, "L%d\n%d\n", offset, whence);
  1030.     if (command(fildes, buffer) == -1)
  1031.         return(-1);
  1032.  
  1033.     return(status(fildes));
  1034. }
  1035.  
  1036.  
  1037. /*
  1038.  *    _rmt_ioctl --- perform raw tape operations remotely
  1039.  */
  1040.  
  1041. #ifdef RMTIOCTL
  1042. static _rmt_ioctl(fildes, op, arg)
  1043. int fildes, op;
  1044. char *arg;
  1045. {
  1046.     char c;
  1047.     int rc, cnt;
  1048.     char buffer[BUFMAGIC];
  1049.  
  1050. /*
  1051.  *    MTIOCOP is the easy one. nothing is transfered in binary
  1052.  */
  1053.  
  1054.     if (op == MTIOCTOP)
  1055.     {
  1056.         sprintf(buffer, "I%d\n%d\n", ((struct mtop *) arg)->mt_op,
  1057.             ((struct mtop *) arg)->mt_count);
  1058.         if (command(fildes, buffer) == -1)
  1059.             return(-1);
  1060.         return(status(fildes));
  1061.     }
  1062.  
  1063. /*
  1064.  *    we can only handle 2 ops, if not the other one, punt
  1065.  */
  1066.  
  1067.     if (op != MTIOCGET)
  1068.     {
  1069.         errno = EINVAL;
  1070.         return(-1);
  1071.     }
  1072.  
  1073. /*
  1074.  *    grab the status and read it directly into the structure
  1075.  *    this assumes that the status buffer is (hopefully) not
  1076.  *    padded and that 2 shorts fit in a long without any word
  1077.  *    alignment problems, ie - the whole struct is contiguous
  1078.  *    NOTE - this is probably NOT a good assumption.
  1079.  */
  1080. /*
  1081.  * This doesn't work right so punt!
  1082.  */
  1083.     errno = 0;
  1084.     return(0);
  1085.  
  1086. #ifdef notdef
  1087.     if (command(fildes, "S\n") == -1 || (rc = status(fildes)) == -1)
  1088.         return(-1);
  1089.  
  1090.     for (; rc > 0; rc -= cnt, arg += cnt)
  1091.     {
  1092.         cnt = read(READ(fildes), arg, rc);
  1093.         if (cnt <= 0)
  1094.         {
  1095.             abort(fildes);
  1096.             errno = EIO;
  1097.             return(-1);
  1098.         }
  1099.     }
  1100.  
  1101. /*
  1102.  *    now we check for byte position. mt_type is a small integer field
  1103.  *    (normally) so we will check its magnitude. if it is larger than
  1104.  *    256, we will assume that the bytes are swapped and go through
  1105.  *    and reverse all the bytes
  1106.  */
  1107.  
  1108.     if (((struct mtget *) arg)->mt_type < 256)
  1109.         return(0);
  1110.  
  1111.     for (cnt = 0; cnt < rc; cnt += 2)
  1112.     {
  1113.         c = arg[cnt];
  1114.         arg[cnt] = arg[cnt+1];
  1115.         arg[cnt+1] = c;
  1116.     }
  1117.  
  1118.     return(0);
  1119. #endif
  1120.   }
  1121. #endif /* RMTIOCTL */
  1122.  
  1123. /*
  1124.  *    Added routines to replace open(), close(), lseek(), ioctl(), etc.
  1125.  *    The preprocessor can be used to remap these the rmtopen(), etc
  1126.  *    thus minimizing source changes:
  1127.  *
  1128.  *        #ifdef <something>
  1129.  *        #  define access rmtaccess
  1130.  *        #  define close rmtclose
  1131.  *        #  define creat rmtcreat
  1132.  *        #  define dup rmtdup
  1133.  *        #  define fcntl rmtfcntl
  1134.  *        #  define fstat rmtfstat
  1135.  *        #  define ioctl rmtioctl
  1136.  *        #  define isatty rmtisatty
  1137.  *        #  define lseek rmtlseek
  1138.  *        #  define lstat rmtlstat
  1139.  *        #  define open rmtopen
  1140.  *        #  define read rmtread
  1141.  *        #  define stat rmtstat
  1142.  *        #  define write rmtwrite
  1143.  *        #  define access rmtaccess
  1144.  *        #  define close rmtclose
  1145.  *        #  define creat rmtcreat
  1146.  *        #  define dup rmtdup
  1147.  *        #  define fcntl rmtfcntl
  1148.  *        #  define fstat rmtfstat
  1149.  *        #  define ioctl rmtioctl
  1150.  *        #  define lseek rmtlseek
  1151.  *        #  define open rmtopen
  1152.  *        #  define read rmtread
  1153.  *        #  define stat rmtstat
  1154.  *        #  define write rmtwrite
  1155.  *        #endif
  1156.  *
  1157.  *    -- Fred Fish
  1158.  *
  1159.  *    ADR --- I set up a <rmt.h> include file for this
  1160.  *
  1161.  */
  1162.  
  1163. /*
  1164.  *    Note that local vs remote file descriptors are distinquished
  1165.  *    by adding a bias to the remote descriptors.  This is a quick
  1166.  *    and dirty trick that may not be portable to some systems.
  1167.  */
  1168.  
  1169. #define REM_BIAS 128
  1170.  
  1171.  
  1172. /*
  1173.  *    Test pathname to see if it is local or remote.  A remote device
  1174.  *    is any string that contains ":/dev/".  Returns 1 if remote,
  1175.  *    0 otherwise.
  1176.  */
  1177.  
  1178. static int remdev (path)
  1179. register char *path;
  1180. {
  1181. #define strchr    index
  1182.     extern char *strchr ();
  1183.  
  1184.     if ((path = strchr (path, ':')) != NULL)
  1185.     {
  1186.         if (strncmp (path + 1, "/dev/", 5) == 0)
  1187.         {
  1188.             return (1);
  1189.         }
  1190.     }
  1191.     return (0);
  1192. }
  1193.  
  1194.  
  1195. /*
  1196.  *    Open a local or remote file.  Looks just like open(2) to
  1197.  *    caller.
  1198.  */
  1199.  
  1200. int rmtopen (path, oflag, mode)
  1201. char *path;
  1202. int oflag;
  1203. int mode;
  1204. {
  1205.     if (remdev (path))
  1206.     {
  1207.         return (_rmt_open (path, oflag, mode) + REM_BIAS);
  1208.     }
  1209.     else
  1210.     {
  1211.         return (open (path, oflag, mode));
  1212.     }
  1213. }
  1214.  
  1215. /*
  1216.  *    Test pathname for specified access.  Looks just like access(2)
  1217.  *    to caller.
  1218.  */
  1219.  
  1220. int rmtaccess (path, amode)
  1221. char *path;
  1222. int amode;
  1223. {
  1224.     if (remdev (path))
  1225.     {
  1226.         return (0);        /* Let /etc/rmt find out */
  1227.     }
  1228.     else
  1229.     {
  1230.         return (access (path, amode));
  1231.     }
  1232. }
  1233.  
  1234.  
  1235. /*
  1236.  *    Read from stream.  Looks just like read(2) to caller.
  1237.  */
  1238.   
  1239. int rmtread (fildes, buf, nbyte)
  1240. int fildes;
  1241. char *buf;
  1242. unsigned int nbyte;
  1243. {
  1244.     if (isrmt (fildes))
  1245.     {
  1246.         return (_rmt_read (fildes - REM_BIAS, buf, nbyte));
  1247.     }
  1248.     else
  1249.     {
  1250.         return (read (fildes, buf, nbyte));
  1251.     }
  1252. }
  1253.  
  1254.  
  1255. /*
  1256.  *    Write to stream.  Looks just like write(2) to caller.
  1257.  */
  1258.  
  1259. int rmtwrite (fildes, buf, nbyte)
  1260. int fildes;
  1261. char *buf;
  1262. unsigned int nbyte;
  1263. {
  1264.     if (isrmt (fildes))
  1265.     {
  1266.         return (_rmt_write (fildes - REM_BIAS, buf, nbyte));
  1267.     }
  1268.     else
  1269.     {
  1270.         return (write (fildes, buf, nbyte));
  1271.     }
  1272. }
  1273.  
  1274. /*
  1275.  *    Perform lseek on file.  Looks just like lseek(2) to caller.
  1276.  */
  1277.  
  1278. long rmtlseek (fildes, offset, whence)
  1279. int fildes;
  1280. long offset;
  1281. int whence;
  1282. {
  1283.     if (isrmt (fildes))
  1284.     {
  1285.         return (_rmt_lseek (fildes - REM_BIAS, offset, whence));
  1286.     }
  1287.     else
  1288.     {
  1289.         return (lseek (fildes, offset, whence));
  1290.     }
  1291. }
  1292.  
  1293.  
  1294. /*
  1295.  *    Close a file.  Looks just like close(2) to caller.
  1296.  */
  1297.  
  1298. int rmtclose (fildes)
  1299. int fildes;
  1300. {
  1301.     if (isrmt (fildes))
  1302.     {
  1303.         return (_rmt_close (fildes - REM_BIAS));
  1304.     }
  1305.     else
  1306.     {
  1307.         return (close (fildes));
  1308.     }
  1309. }
  1310.  
  1311. /*
  1312.  *    Do ioctl on file.  Looks just like ioctl(2) to caller.
  1313.  */
  1314.  
  1315. int rmtioctl (fildes, request, arg)
  1316. int fildes, request, arg;
  1317. {
  1318.     if (isrmt (fildes))
  1319.     {
  1320. #ifdef RMTIOCTL
  1321.         return (_rmt_ioctl (fildes - REM_BIAS, request, arg)); 
  1322. #else
  1323.         errno = EOPNOTSUPP;
  1324.         return (-1);        /* For now  (fnf) */
  1325. #endif
  1326.     }
  1327.     else
  1328.     {
  1329.         return (ioctl (fildes, request, arg));
  1330.     }
  1331. }
  1332.  
  1333.  
  1334. /*
  1335.  *    Duplicate an open file descriptor.  Looks just like dup(2)
  1336.  *    to caller.
  1337.  */
  1338.  
  1339. int rmtdup (fildes)
  1340. int fildes;
  1341. {
  1342.     if (isrmt (fildes))
  1343.     {
  1344.         errno = EOPNOTSUPP;
  1345.         return (-1);        /* For now (fnf) */
  1346.     }
  1347.     else
  1348.     {
  1349.         return (dup (fildes));
  1350.     }
  1351. }
  1352.  
  1353. /*
  1354.  *    Get file status.  Looks just like fstat(2) to caller.
  1355.  */
  1356.  
  1357. int rmtfstat (fildes, buf)
  1358. int fildes;
  1359. struct stat *buf;
  1360. {
  1361.     if (isrmt (fildes))
  1362.     {
  1363.         errno = EOPNOTSUPP;
  1364.         return (-1);        /* For now (fnf) */
  1365.     }
  1366.     else
  1367.     {
  1368.         return (fstat (fildes, buf));
  1369.     }
  1370. }
  1371.  
  1372.  
  1373. /*
  1374.  *    Get file status.  Looks just like stat(2) to caller.
  1375.  */
  1376.  
  1377. int rmtstat (path, buf)
  1378. char *path;
  1379. struct stat *buf;
  1380. {
  1381.     if (remdev (path))
  1382.     {
  1383.         errno = EOPNOTSUPP;
  1384.         return (-1);        /* For now (fnf) */
  1385.     }
  1386.     else
  1387.     {
  1388.         return (stat (path, buf));
  1389.     }
  1390. }
  1391.  
  1392.  
  1393.  
  1394. /*
  1395.  *    Create a file from scratch.  Looks just like creat(2) to the caller.
  1396.  */
  1397.  
  1398. #include <sys/file.h>        /* BSD DEPENDANT!!! */
  1399. /* #include <fcntl.h>        /* use this one for S5 with remote stuff */
  1400.  
  1401. int rmtcreat (path, mode)
  1402. char *path;
  1403. int mode;
  1404. {
  1405.     if (remdev (path))
  1406.     {
  1407.         return (rmtopen (path, 1 | O_CREAT, mode));
  1408.     }
  1409.     else
  1410.     {
  1411.         return (creat (path, mode));
  1412.     }
  1413. }
  1414.  
  1415. /*
  1416.  *    Isrmt. Let a programmer know he has a remote device.
  1417.  */
  1418.  
  1419. int isrmt (fd)
  1420. int fd;
  1421. {
  1422.     return (fd >= REM_BIAS);
  1423. }
  1424.  
  1425. /*
  1426.  *    Rmtfcntl. Do a remote fcntl operation.
  1427.  */
  1428.  
  1429. int rmtfcntl (fd, cmd, arg)
  1430. int fd, cmd, arg;
  1431. {
  1432.     if (isrmt (fd))
  1433.     {
  1434.         errno = EOPNOTSUPP;
  1435.         return (-1);
  1436.     }
  1437.     else
  1438.     {
  1439.         return (fcntl (fd, cmd, arg));
  1440.     }
  1441. }
  1442.  
  1443. /*
  1444.  *    Rmtisatty.  Do the isatty function.
  1445.  */
  1446.  
  1447. int rmtisatty (fd)
  1448. int fd;
  1449. {
  1450.     if (isrmt (fd))
  1451.         return (0);
  1452.     else
  1453.         return (isatty (fd));
  1454. }
  1455.  
  1456.  
  1457. /*
  1458.  *    Get file status, even if symlink.  Looks just like lstat(2) to caller.
  1459.  */
  1460.  
  1461. int rmtlstat (path, buf)
  1462. char *path;
  1463. struct stat *buf;
  1464. {
  1465.     if (remdev (path))
  1466.     {
  1467.         errno = EOPNOTSUPP;
  1468.         return (-1);        /* For now (fnf) */
  1469.     }
  1470.     else
  1471.     {
  1472.         return (lstat (path, buf));
  1473.     }
  1474. }
  1475. @//E*O*F rmtlib.c//
  1476. chmod u=rw,g=r,o=r rmtlib.c
  1477.  
  1478. echo Inspecting for damage in transit...
  1479. temp=/tmp/shar$$; dtemp=/tmp/.shar$$
  1480. trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
  1481. cat > $temp <<\!!!
  1482.       21      47     354 Makefile
  1483.       25     136     774 README.network
  1484.      132     711    4256 copytape.1
  1485.       50     259    1472 copytape.5
  1486.      302     951    6549 copytape.c
  1487.       35     144     904 rmt.h
  1488.      845    2292   13807 rmtlib.c
  1489.     1410    4540   28116 total
  1490. !!!
  1491. wc  Makefile README.network copytape.1 copytape.5 copytape.c rmt.h rmtlib.c | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
  1492. if [ -s $dtemp ]
  1493. then echo "Ouch [diff of wc output]:" ; cat $dtemp
  1494. else echo "No problems found."
  1495. fi
  1496. exit 0
  1497. -- 
  1498.     David Robinson        elroy!david@csvax.caltech.edu     ARPA
  1499.                 david@elroy.jpl.nasa.gov      ARPA
  1500.                 {cit-vax,ames}!elroy!david      UUCP
  1501. Disclaimer: No one listens to me anyway!
  1502.