[net.decus] Reading TAR from VMS

ramin@rtgvax.UUCP (Pantagruel) (08/26/86)

Dammit dammit dammit!

I should've asked for it while it was flying past... I did see the
request a few months back for a program to read TAR format tapes from
VMS, but thought "oh well... who needs that." 

Well if anyone has one, I sure would appreciate getting a copy of
it... Our unix tape drive is about to be shipped to the funny farm and
the vms machines are getting all the new model ones... I looked in
mod.sources listings but alas... I'll also check the decus tapes, but
I doubt I'll have much luck.

Thanks much...

ramin

-- 
=--------------------------------------=-------------------------------------=
: alias: ramin firoozye'               :   USps: Systems Control Inc.        :
: uucp: ...!shasta \                   :         1801 Page Mill Road         :
:       ...!lll-lcc \                  :         Palo Alto, CA  94303        :
:       ...!ihnp4    \...!rtgvax!ramin :   ^G:   (415) 494-1165 x-1777       :
=--------------------------------------=-------------------------------------=

bhoward@funvax.UUCP (Bruce Howard) (08/28/86)

> Dammit dammit dammit!
> 
> I should've asked for it while it was flying past... I did see the
> request a few months back for a program to read TAR format tapes from
> VMS, but thought "oh well... who needs that." 
> 
> Well if anyone has one, I sure would appreciate getting a copy of
> it... Our unix tape drive is about to be shipped to the funny farm and
> the vms machines are getting all the new model ones... I looked in
> mod.sources listings but alas... I'll also check the decus tapes, but
> I doubt I'll have much luck.

Gosh!  And I thought we were the only people in the world
with a screwball set up -- we have a network of VAXen that
speak Internet and DECnet to each other.  The only available
tape drive unfortunately lies on the downstairs VAX 8600.
In the long term, the BEST solution will be to obtain a
TK50 or TU80? (something like that) tape drive.  Anyways,
please credit most of the following solution to Chris Lent
at ...philabs!phri!cooper!chris.

This method has been successfully used to read in our
Berkeley distribution tapes.  Additionally, I have managed
to write what appear to be UNIX format tapes though I have
not been able to test them on a tape drive attached to a UNIX
host.

First you must have an fdl file created that describes the
file we are pulling off the UNIX format tape.  I have not 
had the time nor the desire to fully understand its format
but it seems to simply say that whatever it is we pull off
the tape should be placed in a sequential file with no strange
attributes:

--- cut here ---
IDENT	"16-NOV-1985 09:34:31	VAX/VMS ANALYZE/RMS_FILE Utility"

SYSTEM
	SOURCE                  VAX/VMS

FILE
	ALLOCATION              13
	BEST_TRY_CONTIGUOUS     no
	CLUSTER_SIZE            1
	CONTIGUOUS              no
	EXTENSION               0
	GLOBAL_BUFFER_COUNT     0
	NAME                    "dua2:[bhoward.vms]tar.exe"
	ORGANIZATION            sequential
	OWNER                   [647,24]        
	PROTECTION              (system:RWED, owner:RWED, group:RE, world:RE)

RECORD
	BLOCK_SPAN              yes
	CARRIAGE_CONTROL        none
	FORMAT                  fixed
	SIZE                    10240
--- cut here ---
The owner field should be defined to be the UIC of the account
which happens to be reading in the file.  I don't think that
which is defined in the NAME field is important, but you can
experiment or look it up in the VMS docs.  For purposes of
this explanation, I shall assume you have typed this into a
file named tarcvt.fdl

Once you have this fdl typed in, the following steps will read
a file off of the UNIX tape into a VMS file which can then be
transported to UNIX via ethernet or kermit.  

$ mount mua0: /foreign/block=10240
(mua0: happens to be the name of our primary tape drive.
 10240 is the block size on our berkeley tapes.  Set as
 is appropriate for your tape.)
$ convert /fdl=tarcvt.fdl mua0: nameoftarfile
(this reads in the first file on the tape into a file on
 vms named nameoftarfile)
$ dismount mua0:

Sometimes, as in the case of the berkeley distribution, you
have multiple files on a tape.  One of ours had three sections.
You can use the set mag command to move between files.  For
example, we wanted to pull off the ingress database which I
believe was the third file of one tape.  To do this we typed:

$ mount mua0: /foreign/block=10240
$ set mag /skip=files:2
$ convert /fdl=tarcvt.fdl mua0: ingres.tar
$ dismount mua0:

Also useful, is set mag /rewind which rewinds without dismounting
the tape.

To write a tape file is almost the same as reading.  You just
switch the order around on the files, that is:

$ mount mua0: /foreign/block=10240
$ convert /fdl=tarcvt.fdl usrbackup.tar mua0:
$ convert /fdl=tarcvt.fdl srcbackup.dump mua0:
$ dismount mua0:

which places two files onto the tape.  I have NOT confirmed
that these tapes are readable by UNIX hosts.

Here are a few tricks I've developed to read tar files across
DECnet.  For example, to pull off a directory name ./etc/getty
from a tar file that has been placed onto VMS, from UNIX type:

% dcp -i mother/bhoward::'[.archive]src.tar' - | tar -xvf - ./etc/getty
Password for mother/bhoward::? (type password)
(watch it verbosely untar)

This can also be done with the cpio and dump commands though
I don't believe it works with dump in interactive mode.

I think this is slightly incomplete, but it should get you started. 
I plan of writing a more complete paper on these and other techniques
our unique (silly) hardware situation has forced us to develop. 
Please feel free to ask me questions via mail.

			Cheers!
			Bruce

-- 

		 ---------------------------------
...decvax!ittatc!funvax!bhoward  Bruce Howard @ Fairfield University
...ihnp4!itivax!funvax!bhoward   Located in Scenic Fairfield, CT 06430
		 ---------------------------------

ramin@rtgvax.UUCP (09/07/86)

Well... my recent inquiry about TAR programs in VMS garnished me
the following responses... The last two have actual programs
that were enclosed and I have included them verbatim (though
it seems they are closely related... but I thought I'd throw them
both in anyway...) I also have another FORTRAN one here that someone at
a sister company wrote. I haven't included it since I don't know
if he wants it distributed...

I have not fully tested any of them. But since this machine is
shortly due to go off the net I figured I should send it out
before they pull the plug...(:-( Barring circumstances I should be
back on the net via another system in about a month and I might
fix up the programs to allow subdirectory creations, etc...)

Again, thanks to all who responded.

P.S. John Gilmore (hoptoad!gnu) has also offered a copy of a C TAR program
he has... Hopefully I'll get it soon enough... If someone needs to
try it out they could contact him directly if I'm not around...

ramin

***************************************************************************

From: H}vard Eidnes <lll-lcc!caip!seismo!mcvax!vax.runit.unit.uninett!H_Eidnes>

I saw your recent request for some TAR program on VMS. It just
happens that a friend of mine recently wrote such a program.
The program is written in VMS Pascal. It has mainly been used
to read TAR tapes down to TAR files on disk to be transferred
to a Unix system to be unpacked. We've used Kermit to transfer
from our VMS computer to a MicroVAX II (without 1/2" tape), and
that has worked, but is slow, eg. it took 12hrs transferring
TeX, but it worked...

The Pascal program is capable of just extracting a part of a tape
by giving it a starting and ending filename prefix. It also has
routines to do actual extraction on VMS, but we haven't used these
routines much. NB: the program only handles TAR files blocked 20.

I will be happy to send you the program if you want it.

***************************************************************************

From: lll-lcc!caip!uw-beaver!uw-june!gordon (Gordon Davisson)

Here's a program that does what you want.  It did get into a recent decus
tape (VAX85C?), but that version happens to not work.  Use this instead.

-- 
Human:    Gordon Davisson
ARPA:     gordon@uw-june.ARPA
UUCP:     {ihnp4,decvax,tektronix}!uw-beaver!uw-june!gordon
Bitnet:   gordon@uwaphast
ATT:      (206) 527-0832
USnail:   5008 12th NE, Seattle, WA, 98105

--------------- cut here, then run the file (with an @) ---------------
$!
$ write sys$output "creating CVT.FOR"
$ create CVT.FOR
$ deck
c
c this subroutine converts a complete filespec (directory+file) name from
c unix format to VMS
c
      subroutine cvt_dir_uv( unix, vms, vlen )

      parameter reserved = 10
      character*(*) unix, vms
      integer*2 vlen, i, j

      vms( 1:1 ) = '['
      vlen = 1
      i = 1
      if ( unix( 1:1 ) .eq. '/' ) i = 2
      j = index( unix( i: ), '/' )
      do while ( j .ne. 0 )
         vms( vlen+1:vlen+1 ) = '.'
         call cvt_string_uv( unix( i : i+j-2 ), vms( vlen+2: ), k )
         i = i + j
         j = index( unix( i: ), '/' )
         vlen = vlen + k + 1
         if ( vlen + reserved .gt. len( vms )) then
            vlen = len( vms ) - reserved
            if ( vms( vlen:vlen ) .eq. '.' ) vlen = vlen - 1
            do while ( j .ne. 0 )
               i = i + j
               j = index( unix( i: ), '/' )
            end do
         end if
      end do

      if ( vlen .eq. 1 ) then
         vlen = 0
      else
         vlen = vlen + 1
         vms( vlen:vlen ) = ']'
      end if

      call cvt_file_uv( unix( i: ), vms( vlen+1: ), k )
      vlen = vlen + k
      return

      end      

c
c this subroutine converts an individual file name from unix format to VMS
c
      subroutine cvt_file_uv( unix, vms, vlen )

      parameter mlen1 = 64, mlen2 = 64
c     parameter mlen1 = 9, mlen2 = 3       ! for version 3 and before
      character*(*) unix, vms
      integer*2 vlen, i, j

      i = index( unix, '.' )
      if ( i .eq. 0 ) i = len( unix ) + 1

      call cvt_string_uv( unix( :i-1 ), vms, j )
      if ( j .gt. mlen1 ) j = mlen1
      vlen = j + 1
      if ( vlen .gt. len( vms )) vlen = len( vms )
      vms( vlen:vlen ) = '.'

      if ( i .ge. len( unix )) return

      call cvt_string_uv( unix( i+1: ), vms( vlen+1: ), j )
      if ( j .gt. mlen2 ) j = mlen2
      vlen = vlen + j
      return

      end

c
c this subroutine converts a string to characters that can appear in
c VMS filenames
c
c if you're using a version 3 or pervious VMS system, you'll have to
c rewrite this to avoid _ and $.
c
      subroutine cvt_string_uv( unix, vms, vlen )

      character*(*) unix, vms, c*1
      integer*2 vlen, i

      vms = unix
      vlen = min( len( unix ), len( vms ))
      do i = 1, vlen
         c = vms( i:i )
         if ( 'A' .le. c .and. c .le. 'Z' .or.
     -        '0' .le. c .and. c .le. '9' .or.
     -        c .eq. '_' .or. c .eq. '$'  ) then
            continue
         else if ( 'a' .le. c .and. c .le. 'z' ) then
            vms( i:i ) = char( ichar( c ) - 32 )
         else if ( c .eq. '-' ) then
            vms( i:i ) = '_'
         else
            vms( i:i ) = '$'
         end if
      end do

      return

      end

c
c this souroutine converts an octal digit to a 3-character protection mask
c
      subroutine cvt_prot( c, out )

      character c*1, out*3, mask( 8 )*3
      data mask / '---', '--x', '-w-', '-wx',
     -            'r--', 'r-x', 'rw-', 'rwx' /

      out = mask( ichar( c ) - ichar( '0' ) + 1 )
      return

      end

$ eod
$!
$ write sys$output "creating TAPEIO.DCK"
$ create TAPEIO.DCK
$ deck
c
c parameters:
c   blocklen is the size of the units tar works with
c   saveblocks is the number of blocks into the file saved for a second chance
c   maxrecl is the maximum length of record a text can have
c   maxblockfactor is the maximum blocking factor this program can deal with
c
      parameter blocklen = 512, saveblocks = 5, maxrecl = 512,
     -    maxblockfactor = 20

c secondary parameters calculated from those above
      parameter recblocks = 2 + maxrecl/blocklen,
     -   maxblocks = maxblockfactor + saveblocks + recblocks,
     -   maxlen = blocklen*maxblocks

c these are numbers for fortran units to be used for various files
      parameter inunit = 1, outunit = 2, listunit = 3, nameunit = 4

c
c variables:
c
c i/o control stuff
      integer*2 channel
      logical*1 tape_file

c this is the buffer records get read into
      character buffer*( maxlen ), block( maxblocks )*( blocklen )
      equivalence ( buffer, block )

c control info for the buffer
      integer*2 using, using2, curr
      logical*1 eof


      common /commonbuf/ buffer
      common /commonints/ using, using2, curr, eof, tape_file, channel
$ eod
$!
$ write sys$output "creating TAPEIO.FOR"
$ create TAPEIO.FOR
$ deck
c
c this routine accesses, checks, and rewinds the specified tape (or file,
c if tape_file is true.
c
      subroutine open_tape( name )

      include 'tapeio.dck/list'
      include '($iodef)/nolist'
      include '($dvidef)/nolist'
      include '($devdef)/nolist'
      character*(*) name

      integer*2 iosb( 2 ), devreq_w( 2 )
      integer*4 stat, devreq( 4 ), devchar, sys$assign, sys$qiow,
     -    sys$getdvi

      equivalence ( devreq, devreq_w )
      data devreq_w, devreq( 3 ), devreq( 4 )
     -    / 4, dvi$_devchar, 0, 0 /

      if ( tape_file ) then
         stat = sys$assign( name, channel,, )
         if ( .not. stat ) goto 900
         devreq( 2 ) = %loc( devchar )
         stat = sys$getdvi( , %val( channel ),, devreq, iosb,,, )
         if ( .not. stat ) goto 900
         stat = iosb( 1 )
         if ( .not. stat ) goto 900
         if ( .not. btest( devchar, dev$v_avl )) goto 910
         if ( .not. btest( devchar, dev$v_for )) goto 920
         stat = sys$qiow( , %val( channel ), %val( io$_rewind ),
     -       iosb,,,,,,,, )
         if ( .not. stat ) goto 930
         stat = iosb( 1 )
         if ( .not. stat ) goto 930
      else
         open( inunit, err=950, name=name,
     -       type='old', readonly )
      end if

      return

c
c fatal errors
c
  900 type *, 'Error accessing tape, ', name
      call exit( stat )
  910 type *, 'Tape offline or not available.'
      call exit
  920 type *, 'Tape must be mounted /foreign.'
      call exit
  930 type *, 'Error rewinding tape.'
      call exit( stat )

  950 type *, 'Error opening input file, ', name
      call exit

      end

c
c this routine makes sure the next block is available, reading it from
c tape if necessary.
c
      subroutine next_block
      include 'tapeio.dck'
      include '($iodef)/nolist'
      integer*2 blen, u, i, iosb( 4 )
      integer*4 stat, sys$qiow
      data curr, blen / 0, 0 /

      eof = .false.
      curr = curr + 1
      if ( curr .le. blen / blocklen ) return

      if ( using2 .le. 0 ) then
         u = using
      else if ( using .le. 0 ) then
         u = using2
      else
         u = min( using, using2 )
      end if

      if ( u .gt. blen / blocklen ) then
         type *, 'Internal error.  Call the debugger.'
         call exit
      else if ( u .gt. 1 ) then
         buffer( 1 : blen - blocklen*(u-1)) =
     -       buffer( 1+blocklen*(u-1) : blen )
         blen = blen - blocklen*(u-1)
         curr = curr - u + 1
         using = using - u + 1
         using2 = using2 - u + 1
      else if ( u .lt. 1 ) then
         blen = 0
         curr = 1
      end if

      do while ( curr .gt. blen / blocklen )
         if ( tape_file ) then
            stat = sys$qiow( , %val( channel ), %val( io$_readlblk ),
     -          iosb,,, %ref( buffer( blen+1: )), %val( maxlen-blen )
     -          ,,,, )
            if ( .not. stat ) then
               type *, 'Error reading from tape'
               call exit( stat )
            else if ( .not. iosb( 1 )) then
               type *, 'Error reading from tape'
               call exit( iosb( 1 ))
            else if ( iosb( 2 ) .eq. 0 ) then
               goto 99
            endif
            blen = blen + iosb( 2 )
         else
            read( inunit, 10, end=99 ) i, buffer( blen+1: )
   10       format( q, a )
            blen = blen + i
         end if
         if ( blen .gt. maxlen ) then
            type *, 'Blocking factor too large.'
            call exit
         end if
      end do
      return

99    curr = curr - 1
      eof = .true.
      return
      end
$ eod
$!
$ write sys$output "creating TAR.CLD"
$ create TAR.CLD
$ deck
!**************************************TAR**************************************
define verb TAR
   image drc0:[gordon.decus.tar]TAR
   parameter P1 , label=TAPE , prompt="Tape drive"
      value (required,type=$infile)
   qualifier FILE
   qualifier EXTRACT
   qualifier BINARY
   qualifier INQUIRE
   qualifier SECOND_CHANCE
      default
   qualifier NAMES
      value (default="sys$output:",type=$outfile)
   qualifier LIST
      value (default="sys$output:",type=$outfile)
   qualifier VERBOSE
   qualifier FLATTEN
      default
$ eod
$!
$ write sys$output "creating TAR.FOR"
$ create TAR.FOR
$ deck
c
c This is tar for VMS, by Gordon Davisson (gordon@uw-june).  It is
c not based on any liscenced software, and is completely in the
c public domain.
c
c Version 1.0, Gordon Davisson, July 24 1985
c revised by G.D. July 29 1985 to use io$_rewind right and not abuse rms$_eof
c revised by G.D. Oct 17 1985 to make sure tape is mounted
c
      program tar

      parameter bell = char( 7 ), lf = char( 10 )
      external cli$_present, cli$_negated, cli$_locpres, cli$_locneg,
     -    cli$_absent, cli$_defaulted, cli$_normal,
     -    cli$_comma, cli$_concat
      include 'tapeio.dck/list'
      character fname*100, out*128, pstr*9, prot*3, link*1, tmp*1
      integer*2 i, j, start, finish, flen, olen, files,
     -    stuff1, stuff2, stuff3, iosb( 2 )
      integer*4 size, blocks, time, stat,
     -    cli$get_value, cli$present, sys$assign, sys$qiow
      logical*1 listing, naming, extracting, binary, inquiring,
     -    verbose, second_chance, flatten, absent

      absent( stat ) = ( stat .eq. %loc( cli$_absent ) .or.
     -                   stat .eq. %loc( cli$_negated ))
c
c parse command line
c
c file -- read from normal file, not a tape
      stat = cli$present( 'file' )
      if ( absent( stat )) then
         tape_file = .true.
      else if ( .not. stat ) then
         goto 900
      else
        tape_file = .false.
      end if

c p1 -- tape drive name
      stat = cli$get_value( 'tape', fname, flen )
      if ( .not. stat ) goto 900
      call open_tape( fname( :flen ))

c p2 -- files to extract/list/whatever.  NOT IMPLEMENTED
c     files = 0
c     stat = cli$get_value( 'match', fname, flen )
c     do while ( .not. absent( stat ))
c        if ( .not. stat ) goto 900
c        type *, 'File selector: ', fname( :flen )
c        files = files + 1
c        stat = cli$get_value( 'match', fname, flen )
c     end do

c extract -- copy files to disk
      stat = cli$present( 'extract' )
      if ( absent( stat )) then
         extracting = .false.
      else if ( .not. stat ) then
         goto 900
      else
        extracting = .true.
      end if

c binary -- copy to disk in block mode
      stat = cli$present( 'binary' )
      if ( absent( stat )) then
         binary = .false.
      else if ( .not. stat ) then
         goto 900
      else
        binary = .true.
      end if

c inquire -- ask what to do to each file
      stat = cli$present( 'inquire' )
      if ( absent( stat )) then
         inquiring = .false.
      else if ( .not. stat ) then
         goto 900
      else
        inquiring = .true.
      end if

c second_chance -- try to recognize binary files and save them as such
      stat = cli$present( 'second_chance' )
      if ( absent( stat )) then
         second_chance = .false.
      else if ( .not. stat ) then
         goto 900
      else
        second_chance = .true.
      end if

c list -- list files on tape
      stat = cli$get_value( 'list', fname, flen )     
      if ( absent( stat )) then
         listing = .false.
      else if ( .not. stat ) then
         goto 900
      else
         open( listunit, err=920, name=fname( :flen ), type='new',
     -       defaultfile='tar.lis', carriagecontrol='list' )
         listing = .true.
      end if

c verbose -- make a verbose list
      stat = cli$present( 'verbose' )
      if ( absent( stat )) then
         verbose = .false.
      else if ( .not. stat ) then
         goto 900
      else
        verbose = .true.
      end if

c flatten -- extract all files to the current directory
      stat = cli$present( 'flatten' )
      if ( absent( stat )) then
         flatten = .false.
      else if ( .not. stat ) then
         goto 900
      else
        flatten = .true.
      end if

c names -- make a list of what unix filenames mapped to what VMS filenames
      stat = cli$get_value( 'names', fname, flen )     
      if ( absent( stat )) then
         naming = .false.
      else if ( .not. stat ) then
         goto 900
      else
         open( nameunit, err=930, name=fname( :flen ), type='new',
     -       defaultfile='tar.nam', carriagecontrol='list' )
         naming = .true.
      end if

c
c file loop: executed for each file in the archive
c
      do while ( .true. )
   10    using = 0
         using2 = 0
         call next_block
         if ( eof .or. block( curr ) (1:1) .eq. char( 0 )) goto 899

c start parsing out a file entry -- parse the header
         read( block( curr ), 1001 ) fname, prot, stuff1, stuff2,
     -      size, time, stuff3, link
 1001    format( a100, 3x, a3, 2x, 2(o6,2x), 2(o11,1x), o6, 2x, a1 )
         blocks = ( size + blocklen - 1 ) / blocklen
         flen = index( fname, char( 0 )) - 1
         if ( flen .lt. 0 ) flen = len( fname )

c add it to the list
         if ( listing .and. verbose ) then
            call cvt_prot( prot( 1:1 ), pstr( 1:3 ))
            call cvt_prot( prot( 2:2 ), pstr( 4:6 ))
            call cvt_prot( prot( 3:3 ), pstr( 7:9 ))
            write( listunit, 2001 ) pstr, stuff1, stuff2, size,
     -          time, fname( :flen )
 2001       format( a9, i3, '/', i3, i7, i11, 1x, a )
         else if ( listing ) then
            write( listunit, 2002 ) fname( :flen )
 2002       format( a )
         end if

c ignore links
         if ( link .eq. '1' ) goto 10

c skip if not extracting
         if ( .not. extracting ) goto 40

c figure out what to do with the file
         if ( inquiring ) then
            call lib$get_input( tmp, fname( :flen ) // ': [ynbtq]' )
            do while ( index( ' yYnNbBtTqQ', tmp ) .eq. 0 )
               call lib$get_input( tmp,
     -             bell // fname( :flen ) // ': [ynbtq]' )
            end do
            call str$upcase( tmp, tmp )
            if ( tmp .eq. 'Q' ) goto 899
         else
            tmp = 'Y'
         end if

         if ( tmp .eq. 'Y' .or. tmp .eq. ' ' ) then
            tmp = 'T'
            if ( binary ) tmp = 'B'
         else if ( tmp .eq. 'N' ) then
            goto 40
         end if

c parse file name
         call cvt_dir_uv( fname( :flen ), out, olen )
         if ( flatten ) then
            i = index( out( :olen ), ']' )
            out = out( i+1:olen )
            olen = olen - i
         end if
         if ( tmp .eq. 'B' ) goto 30

c create a text file
   20    open( outunit, name=out( :olen ), type='new', recl=maxrecl,
     -       defaultfile='.', carriagecontrol='list', err=38 )
         if ( size .le. 0 ) then
            close( outunit )
            goto 99
         end if

c copy the file to disk
         if ( second_chance ) using2 = curr
         call next_block
         blocks = blocks - 1
         start = 1
         do while ( size .gt. 0 )
            using = curr
            finish = index( block( curr ) ( start: ), lf )
            do while ( finish .eq. 0 .and. blocks .gt. 0 .and.
     -          curr - using + 1 .lt. recblocks )
               call next_block
               blocks = blocks - 1
               if ( eof ) goto 990
               finish = index( block( curr ), lf )
            end do
            if ( finish .eq. 0 ) finish = 2 * blocklen + 1
            if ( using .eq. curr ) finish = finish + start - 1
            i = start + blocklen * (using-1)
            j = finish + blocklen * (curr-1)
            start = finish + 1
            size = size - j + i - 1
            if ( size .lt. 0 ) then
               j = j + size
               size = 0
            end if
            if ( using2 .ne. 0 .and. j - i .gt. maxrecl ) then
               type *, 'Giving ', fname( :flen ), ' a second chance...'
               blocks = blocks + curr - using2
               curr = using2
               using = 0
               using2 = 0
               close( outunit, dispose='delete' )
               goto 30
            end if
            write( outunit, 2005, err=39, iostat=stat )
     -          buffer( i : j-1 )
2005        format( a )
            if ( curr - using2 .ge. saveblocks ) using2 = 0
         end do

         close( outunit )
         goto 99

c create a binary file
   30    using = 0
         using2 = 0
         open( outunit, name=out( :olen ), type='new',
     -       recl=blocklen, recordtype='fixed', defaultfile='.',
     -       carriagecontrol='none', err=39 )

         do while ( blocks .gt. 0 )
            call next_block
            blocks = blocks - 1
            write( outunit, 3001, err=39, iostat=stat ) block( curr )
 3001       format( a )
         end do
         close( outunit )
         goto 99

c got an error creating the file: skip it.
   38    type *, 'Error creating ', out( :olen ), ' skipping...'
         goto 40

c got an error writing the file: skip the rest of it.
   39    type *, 'Error writing ', out( :olen ), ' skipping...'
         close( outunit, dispose='delete' )

c skip the file
   40    using = 0
         using2 = 0
         do i = 1, blocks
            call next_block
            if ( eof ) goto 990
         end do
         goto 10

c successfully copied file to disk: enter it in the names file
   99    if ( naming ) write( nameunit, 2000 ) out( :olen ),
     -       fname( :flen )
 2000    format( a, ' -> ', a )

      end do

c end of tape: close it and exit!
  899 close( inunit )
      call exit

c
c fatal errors
c
  900 type *, 'Error parsing command line'
      call exit

  920 type *, 'Error opening listing file, ', fname( :flen )
      call exit

  930 type *, 'Error opening names file, ', fname( :flen )
      call exit

  990 type *, 'Premature end of tape while reading ', fname( :flen )
      call exit

      end
$ eod
$!
$ write sys$output "creating TAR.HLP"
$ create TAR.HLP
$ deck
1 TAR
  Invokes the tape archive reader to read unix-format tapes.

  Format:

    TAR tape-name[:]
2 Parameter

 tape-name[:]

  Specifies the device name of the tape drive the archive is mounted on.
  The archive must be mounted foreign.

  If the /FILE qualifier is specified, this parameter is interpreted as
  the file name for the archive.
2 Command_Qualifiers

/BINARY

  Specifies that the files extracted should be put into fixed-length-512-
  byte-record files and that no interpretation should be preformed on
  the contents.

/EXTRACT

  Specifies that the files in the archive should be copied into the
  directory, or subdirectories (depending on the /FLATTEN qualifier).

/FILE

  Specifies that, instead of a tape, the archive is contained in a normal
  file.

/FLATTEN (D)

  Specifies that files extracted from the archive should be put in the
  default directory even when the files would normally be put in
  subdirectories.

  This qualifier is on by default because the program is incapable of
  creating subdirectories to put the extracted files in, so unless they
  exist already, /NOFLATTEN in a pure loss.

/INQUIRE

  Tells the program to ask the user what to do with each file it has been
  told to extract.  The program prompts with the filename followed by a
  list of options.  The options are:

     y - extract the file normally
     n - skip the file
     t - extract the file in text (not binary) mode
     b - extract the file in binary mode
     q - exit the program

  The y option is equivalent t or b depending on whether the /BINARY
  qualifier was given.

/LIST

 /LIST=filename (default = sys$output:)

  Tells the program to create a list of all of the files on the tape.
  If the /VERBOSE qualifier is also specified, the list contains more
  than just the file names.

/NAMES

 /NAMES=filename (default = sys$output:)

  If files are extracted, the program creates a file giving the names
  of the files on the tape and the VMS filenames they were mapped into
  when extracted.

/SECOND_CHANCE (D)

  This specifies that if a file is being extracted in text mode, and
  a line longer then 512 bytes in encountered sufficiently near the
  beginning of the file, it should be re-extracted in binary mode.

  If negated, files with long lines are discarded in text mode.

/VERBOSE

  This specifies that lists should contain more information than just
  the filename.

2 Bugs
  Here's a list of some of the more noticable bugs and deficiencies:
     - It can't write tar tapes.
     - It can't operate on only some of the files on a tape.
     - Verbose listings contain the date in seconds since 1970 or