From: simons/G=Colin/I=CB/S=Sewell/O=H.A.Simons.Ltd/OU=CORP@mhs.attmail.com
Newsgroups: comp.os.vms
Subject: Re: tar for vms
Date: 11 Dec 92 22:09:19 GMT
Distribution: world
Organization: The Internet

#pragma nostandard
#module VMSTAR "V2.0"
#pragma builtins

/*
**      VMSTAR.C - a Un*x-like tar reader/writer for VMS
**                 based on TAR2VMS and VMS2TAR
**
** Usage:
**      tar x|t|c[vwbd][f tarfile] [file [file...]]
**      x - extract from tarfile
**      t - type contents of tarfile
**      c - create tarfile, archive VMS files
**      v - verbose
**      w - wait for confirmation before extracting/archiving
**      b - binary mode extract, create (rfm=fixed, rat=none, mrs=512) files
**      z - automatic mode, try to guess if files are text or binary, extract
**          text files as (rfm=stream-LF, rat=cr) binary files as
**          (rfm=fixed, rat=none, mrs=512)
**      d - keep trailing dots in file names
**      o - restore original owner
**      f - specify tarfile name, default is $TAPE
**      file - space-separated list of file names, can include VMS-style
**             string wildcards on extract, can be any VMS file name
**             specification (except DECnet) on create archive.
**
** Original author of the VMS2TAR and TAR2VMS programs:
** Copyright 1986, Sid Penstone,
**  Department of Electrical Engineering,
**  Queen's University,
**  Kingston, Ontario, Canada K7L3N6
** (613)-545-2925
** BITNET:   PENSTONE@QUCDNEE1
**
** Also deeply modified by:
** Colin Sewell
** H. A. Simons Ltd.
** Vancouver, BC
**
** Deeply modified by:
** Alain Fauconnet
** System Manager
** SIM - Public Health Research Laboratories
** 91 Boulevard de l'Hopital
** 75634 PARIS CEDEX 13 - FRANCE
** Bitnet: FAUCONNE@FRSIM51
**
** PROBLEMS SHOULD BE REPORTED TO ME. PLEASE DON'T BOTHER SID PENSTONE
** WITH MY OWN BUGS !
**
** Version 2.0 21-NOV-1992
** Based on TAR2VMS V2.2 21-OCT-1986 and VMS2TAR V1.8 23-DEC-1986
**
** Sid Penstone did not include any copyright information in his program so
** this only applies if Sid Penstone agrees: you may use VMSTAR, distribute it,
** modify it freely provided you don't use it for commercial
** or military purposes. Please include the two above author names in the
** source file of any modified version of VMSTAR.
**
** Modification history:
**
** 2.0   - complete rewrite
** 1.6-0 - added 'z' option for automatic determination of file type on
**         extraction
**       - reworked copyfile() to close files and exit on fatal error -
**         error status not returned, they weren't checked anyway
**       - some code cleanup (much, much more to do...)
** 1.5-3 - removed duplicate error message
** 1.5-2 - removed duplicate #include iodef
**       - added write status checking in copyfile and cleaned up error
**         handling (avoids duplicate messages)
** 1.5-1 - fix by Mark Parisi <MPARISI@RIPPLE.JPL.NASA.GOV>
**         fixed bug in out_file: if the length of a text file was an
**         exact multiple of DSIZE, flushout was called an additional
**         time.
**       - added some code for empty files handling.
** 1.5  - when archiving a non-text file with rfm=stream_lf, rat=cr
**        attributes, VMSTAR truncated the file. Modified out_file
**        to more cleanly handle various RMS file formats: only
**        variable and stream_cr record formats need two passes to compute
**        the actual file size and need to be read record by record.
**        All other formats should by read buffer by buffer and written
**        as-is in the tar archive, thus out_file now fopens the file
**        in binary mode and freads buffers.
**        In the case of a stream_cr file, if a single record cannot fit in
**        our buffers (probably because the file is non-text and has
**        incorrect RMS attributes) out_file now error exits.
** 1.4  - fixed a bug in scan_title that caused VMSTAR to fail on
**        extraction of absolute tarfiles (thanks to Tom Allebrandi
**        for this one)
**      - added some code in scan_title to correctly handle dots in
**        directory names found in tarfile (replaced by "_")
** 1.3  - changed tar2vms to use standard IO calls (fopen, fread) to
**        read input tarfile in binary mode i.e. no translation of
**        RMS record attributes done by C RTL. This fixes problem reading
**        tarfiles created with rfm=fix, rat=cr
**      - more room for file size in output formats
** 1.2  - fixed bug in out_file not closing input VMS file, limited
**        maximum number of files archived to FILLM quota
**      - added mapping to underscores of some more characters found
**        in Un*x file names illegal in VMS file names
** 1.1  - reworked handling of current VMS default
**      - will now create *relative* tarfiles (i.e. files known
**        as "./..." in tar archive directory) except when
**        device name is specified or wilcard search gives filenames
**        located above current default (tar cvf foo.tar [-...]*.* will
**        lead to this kind of situation)
**      - attempt to handle more than 8 levels of directories upon
**        extract : .../d7/d8/d9 will be mapped to [...D7.D8$D9]
**      - greatly simplified make_new() because mkdir() creates
**        intermediate levels of directories if missing
** 1.0  Original version from Sid Penstone's code
**      - merged VMS2TAR & TAR2VMS into a single source file
**      - code reworked, messages cleaned up
**      - added support for 'f tarfile' option, changed default to $TAPE
**      - added support for VMS style wildcard file names on extract
**      - added support for 'b' (binary file extract) option
**      - suppressed usage of intermediate scratch file for create operation
**      - file list on create should now be space separated (removed difficult
**        support of comma-separated list with context "a la BACKUP")
**      - global code simplification attempt, may have broken some
**        odd case handling
**      - added some error handling in tarfile write operations
**      - probably millions of bugs introduced... sorry.
*/


/****************************************************************************/
/*  declarations                                                            */
/****************************************************************************/

#include stdlib
#include stdio
#include time
#include ssdef
#include iodef
#include dvidef
#include dcdef
#include descrip
#include ctype
#include strdef
#include rms
#include stsdef
#include file
#include stat
#include types
#include string
#include errno
#include hlpdef

#define ISFILE      0           /* is a file                            */
#define ISDIRE      1           /* is a directory                       */
#define BLKSIZE     20          /* Block size                           */
#define DSIZE       512         /* Data block size                      */
#define EOS         '\0'        /* End of string character              */

#define NAMSIZE     100
#define MODESIZE    8
#define UIDSIZE     8
#define GIDSIZE     8
#define COUNTSIZE   12
#define TIMESIZE    12
#define CHKSUMSIZE  8

struct tarhdr                   /* A tar header                         */
    {
    char title[NAMSIZE];        /* name of entry                        */
    char mode[MODESIZE];        /* file mode                            */
    char uid[UIDSIZE];          /* this is the user id                  */
    char gid[GIDSIZE];          /* this is the group id                 */
    char count[COUNTSIZE];      /* was 11 in error                      */
    char time[TIMESIZE];        /* UNIX format date                     */
    char chksum[CHKSUMSIZE];    /* header checksum                      */
    char linkcount;             /* hope this is right                   */
    char linkname[NAMSIZE];     /* Space for the name of the link       */
    char dummy[255];            /* and the rest                         */
    };


/****************************************************************************/
/*  Function flags, options                                                 */
/****************************************************************************/

static int extract  = 0;        /* x option, extract                        */
static int list     = 0;        /* t option, list tape contents             */
static int create   = 0;        /* c option, create                         */
static int verbose  = 0;        /* v option, report actions                 */
static int wait     = 0;        /* w option, prompt                         */
static int dot      = 0;        /* d option, suppress dots                  */
static int binmode  = 0;        /* b option, binary mode                    */
static int automode = 0;        /* z option, automatic mode                 */
static int foption  = 0;        /* f option, specify tarfile                */
static int debug    = 0;        /* D option, debug                          */
static int owner    = 0;
static int help     = 0;

static int devclass = 0;        /* Tar file device class                    */

/****************************************************************************/
/*  Miscellaneous globals, etc.                                             */
/****************************************************************************/

static char tarfile[NAM$C_MAXRSS] = "$TAPE";/* Tarfile name                 */
static char curdir[NAM$C_MAXRSS];           /* Current directory            */
static FILE *tarfp;                         /* tar file pointer             */


/****************************************************************************/
/*  Global file characteristics                                             */
/****************************************************************************/

/* Chars found in Un*x file names, illegal in VMS */

static const char   badchars[] = { ",+~`@#%^*?|\&[]{}" };       

/* only get the most recent version */

static const char   default_name[] = { "*.*;" };


/****************************************************************************/
/*  Forward definitions/prototypes                                          */
/****************************************************************************/
char    *vmserr( int );                     /* return ptr to vms error text */
char    *tar_format_mode( int, char * );    /* format file mode             */
char    *tar_format_time( int );            /* format time                  */



/****************************************************************************/
/*                                                                          */
/*  main program -- parses options, dispatches to tar2vms and vms2tar       */
/*                                                                          */
/****************************************************************************/
static int vmstar( int argc, char *argv[] ) MAIN_PROGRAM

{

    register char   *cp;
    register char   c;


    /************************************************************************/
    /*  decode the options and parameters:                                  */
    /************************************************************************/

    if (argc == 1) tar_usage();


    cp = argv[1];

    while (c = *cp++)
        {
        switch(c)
            {
            case 't':
                list = 1;
                break;

            case 'x':
                extract = 1;
                break;

            case 'c':
                create = 1;
                break;

            case 'v':
                verbose = 1;
                break;

            case 'w':
                wait = 1;
                break;

            case 'd':
                dot = 1;
                break;

            case 'b':
                binmode = 1;
                break;

            case 'z':
                automode = 1;
                break;

            case 'D':
                debug = 1;
                break;

            case 'o':
                owner = 1;
                break;

            case 'h':
                help = 1;
                break;

            case 'f':
                if (*cp != '\0' || argc < 3)
                    tar_usage();
                else
                    foption = 1;
                    strcpy( tarfile, argv[2] );
                break;

            case '-':
                break;

            default:
                fprintf( stderr, "tar: option '%c' not recognized.\n",c );
                tar_usage();
            }
        }



    if (help)
        {
        int     status;
        int     lib$put_output();
        int     lib$get_input();
        $DESCRIPTOR( vmstardsc,     "VMSTAR" );
        $DESCRIPTOR( helplibdsc,    "SOE$UTIL:VMSTAR" );
        status = lbr$output_help( &lib$put_output, 0, &vmstardsc, &helplibdsc,
                                    &HLP$M_PROMPT, &lib$get_input );
        return status;
        }



    /************************************************************************/
    /*  check options are coherent                                          */
    /************************************************************************/

    if (extract + list + create == 0)
        {
        fprintf( stderr, "tar: no action specified.\n" );
        exit(1);
        }

    if (extract + create == 2)
        {
        fprintf( stderr, "tar: incompatible options 'x' and 'c' specified.\n" );
        exit(1);
        }

    if (binmode + automode == 2)
        fprintf( stderr, "tar: incompatible options 'b' and 'z' specified.\n" );


    /************************************************************************/
    /*  get current directory                                               */
    /************************************************************************/

    strcpy( curdir, getenv("PATH") );

    cp = strchr( curdir, ':' );         /* extract directory                */
    strcpy( curdir, cp+1 );
    tar_clean_dir( curdir );

    if (debug)
        printf( "curdir = %s\n", curdir );

    if (create == 0)
        tar2vms( argc, argv );
    else
        vms2tar( argc, argv );

    return SS$_NORMAL;

}



/****************************************************************************/
/*  tar2vms -- handles extract and list options                             */
/****************************************************************************/
static tar2vms( int argc, char **argv )

{

    int                     status;
    int                     ftype;
    char                    *ptr;
    char                    pathname[NAMSIZE+1];
    char                    linkname[NAMSIZE+1];
    char                    filespec[NAM$C_MAXRSS+1];
    char                    dirnam[NAM$C_MAXRSS];
    char                    filnam[NAM$C_MAXRSS];
    struct stat             sblock;
    struct tarhdr           header;
    struct dsc$descriptor   pattern     = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };
    struct dsc$descriptor   candidate   = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };



    /************************************************************************/
    /* open the file for reading                                            */
    /************************************************************************/

    if ((tarfp = fopen( tarfile, "rb" )) == NULL)
        {
        fprintf( stderr, "tar: error opening tarfile.\n" );
        perror( "tar" );
        exit( 1 );
        }

    fgetname( tarfp, tarfile );
    lowercase( tarfile );

    candidate.dsc$w_length  = strlen( tarfile );
    candidate.dsc$a_pointer = tarfile;

    status = lib$getdvi( &DVI$_DEVCLASS, 0, &candidate, &devclass );
    if (!(status & 1))
        {
        fprintf( stderr, "tar: error getting device information for %s\n", tarfile );
        fprintf( stderr, "tar: %s\n", vmserr( status ) );
        exit( SS$_NORMAL );
        }


    /************************************************************************/
    /* Now keep reading headers from this file, and decode the names, etc.  */
    /************************************************************************/


    while ((status = fread( &header, 1, DSIZE, tarfp )) == DSIZE)    /* 0 on end of file */
        {

        /********************************************************************/
        /*  exit on empty header                                            */
        /********************************************************************/

        if (!strlen(header.title)) exit( SS$_NORMAL );



        /********************************************************************/
        /*  decode the header info                                          */
        /********************************************************************/

        tar_decode_hdr( &header, pathname, linkname, &sblock );



        /********************************************************************/
        /*  Now if file names were specified on the command line, check     */
        /*  if they match the current one                                   */
        /********************************************************************/

        if ((foption == 0 && argc > 2) || argc > 3)
            {

            register int    argi = foption ? 3 : 2;

            candidate.dsc$w_length = strlen( pathname );
            candidate.dsc$a_pointer = pathname;

            while (argi < argc)
                {
                pattern.dsc$w_length = strlen( argv[argi] );
                pattern.dsc$a_pointer = argv[argi];

                ++argi;

                if ((status = STR$MATCH_WILD( &candidate, &pattern )) == STR$_MATCH)
                    break;
                }

            if (status != STR$_MATCH)
                {
                if (!sblock.st_nlink) tar_skip( sblock.st_size );
                continue;
                }

            }


        /********************************************************************/
        /*  listing only                                                    */
        /********************************************************************/

        if (list)
            {
            tar_display_hdr( pathname, linkname, &sblock );
            if (!sblock.st_nlink) tar_skip( sblock.st_size );
            continue;
            }


        /********************************************************************/
        /*  prompt to process file                                          */
        /********************************************************************/

        if (wait)
            {
            char    temp[4] = EOS;

            while (*temp != 'y' && *temp != 'n')
                {
                printf( "x %s: ", pathname );
                scanf( "%s", temp );
                *temp = _tolower( *temp );
                }

            if (*temp != 'y')
                {
                tar_skip( sblock.st_size );
                continue;
                }
            }


        ftype = tar_cvt_unixname_to_vmsname( pathname, dirnam, filnam );

        tar_clean_dir( dirnam );

        if (tar_create_dir( dirnam ))
            {
            fprintf( stderr, "tar: error creating %s\n", dirnam );
            perror( "tar" );
            if (!sblock.st_nlink) tar_skip( sblock.st_size );
            continue;
            }


        if (ftype == ISFILE)
            {
            strcpy( filespec, dirnam );
            strcat( filespec, filnam );
            tar_extract_file( filespec, linkname, &sblock );
            }

        }


    if (!status)            /* End of tar file  */
        {
        fprintf( stderr, "tar: EOF hit on tarfile.\n" );
        exit( SS$_NORMAL );
        }


    if (status < 0)         /* An error  */
        {
        fprintf( stderr, "tar: error reading tarfile.\n" );
        perror( "tar" );
        exit( SS$_NORMAL );
        }

}




/****************************************************************************/
/*  This function simply copies the file to the output, no conversion       */
/****************************************************************************/
static tar_extract_file
    (   char        outfile[],  /* name of output file                      */
        char        linkname[],
        struct stat *sblock )

{

    char    buffer[DSIZE];      /* buffer for a tarfile record              */
    int     nbytes      = sblock->st_size;
    int     inbytes     = 0;
    int     outbytes    = 0;
    int     outfd;
    int     binfile     = binmode;


    /************************************************************************/
    /*  Read the first block of the tarred file                             */
    /************************************************************************/

    if (sblock->st_nlink == 0 && nbytes)
        {

        if ((inbytes = fread( buffer, 1, DSIZE, tarfp )) < 0)
            {
            fprintf( stderr, "tar: error reading tar file.\n" );
            perror( "tar" );
            tar_skip( nbytes );
            return;
            }


        /********************************************************************/
        /*  If automatic mode is set, then try to figure out what kind      */
        /*  of file this is.                                                */
        /********************************************************************/

        if (automode && inbytes)
            {

            int     i;
            int     ctlchars        = 0;
            int     eightbitchars   = 0;
            int     nchars          = nbytes < inbytes ? nbytes : inbytes;
            char    c;

            /****************************************************************/
            /*  Scan the buffer, counting chars with msb set and control    */
            /*  chars not CR, LF or FF                                      */
            /****************************************************************/

            for (i = 0; i < nchars; ++i)
                {

                c = buffer[i];

                if (c < ' ' && c != 0x0a && c != 0x0c && c != 0x0d && c != 0x09)
                    ctlchars++;

                if (c > 127)
                    eightbitchars++;

                }

            /****************************************************************/
            /* Apply some heuristics to determine file is text or binary    */
            /****************************************************************/

            ctlchars = ctlchars * 100 / nchars;

            eightbitchars = eightbitchars * 100 / nchars;

            if  (ctlchars > 10 || eightbitchars > 30 ||
                (ctlchars > 5 && eightbitchars > 20))
                binfile = 1;
            
            }
        }




    /************************************************************************/
    /*  Open the output file                                                */
    /************************************************************************/

    if (binfile)
        outfd = creat( outfile, sblock->st_mode, "rfm=fix", "mrs=512", "alq=5" );
    else
        outfd = creat( outfile, sblock->st_mode, "rfm=stmlf", "rat=cr" );


    if (outfd < 0)
        {
        fprintf( stderr, "tar: error creating %s \n", outfile );
        perror( "tar" );
        if (!sblock->st_nlink) tar_skip( nbytes - inbytes );
        return;
        }


    getname( outfd, outfile );
    lowercase( outfile );

    if (sblock->st_nlink)
        {
        sprintf( buffer, "*** This file is a link to %s\n", linkname );
        outbytes = write( outfd, buffer, strlen( buffer ) );
        }
    else
        {
        while (nbytes > 0 && inbytes >= 0 && outbytes >= 0)
            {
            outbytes = write( outfd, buffer, inbytes <= nbytes ? inbytes : nbytes );
            if (outbytes > 0)
                {
                nbytes -= inbytes;
                if (nbytes > 0) inbytes = fread( buffer, 1, DSIZE, tarfp );
                }
            }
        }


    if (!inbytes && inbytes)
        {
        fprintf( stderr, "tar: unexpected EOF on tar file.\n" );
        perror("tar");
        close( outfd );
        return;
        }

    if (inbytes < 0)
        {
        fprintf( stderr, "tar: error reading tar file.\n" );
        perror("tar");
        close( outfd );
        return;
        }

    if (outbytes < 0)
        {
        fprintf( stderr, "tar: error writing file %s\n", outfile );
        perror( "tar" );
        close( outfd );
        tar_skip( nbytes );
        return;
        }


    close( outfd );

    if (owner)
        chown( outfile, sblock->st_uid, sblock->st_gid );

    if (verbose)
        {
        printf( "x %c%s%s%s, %d byte%s\n",
                    binfile ? '*' : ' ',
                    outfile,
                    sblock->st_nlink ? " -> " : "",
                    sblock->st_nlink ? linkname : "",
                    sblock->st_size,
                    sblock->st_size != 1 ? "s" : "" );
        }

    return;

}



/****************************************************************************/
/*  tar_cvt_unixname_to_vmsname                                             */
/*                                                                          */
/*      decode a Un*x file name into the directory and name                 */
/*                                                                          */
/*  Return a value to indicate if this is a directory name, or another file */
/*  We return the extracted directory string in "dire", and the             */
/*  filename (if it exists) in "fname". The full title is in "line"         */
/*  at input.                                                               */
/****************************************************************************/
static int tar_cvt_unixname_to_vmsname
    (   char    pathname[],
        char    dirnam[],
        char    filnam[] )

{

    char            temp[NAM$C_MAXRSS+1];
    register char   *cp;
    register char   *cp1;


    *dirnam = *filnam = EOS;

    /************************************************************************/
    /*  The format will be UNIX at input, so we have to scan for the        */
    /*  UNIX directory separator '/'                                        */
    /*  If the name ends with '/' then it is actually a directory name.     */
    /*  If the directory consists only of '.', then don't add a             */
    /*  subdirectory. The output directory will be a complete file spec,    */
    /*  based on the default directory.                                     */
    /************************************************************************/

    if (!strncmp( pathname, "./", 2))
        strcpy( pathname, pathname + 2 );   /* ignore "./"                  */

    strcpy( temp, pathname );               /* work on a copy               */

    tar_clean_filespec( temp );             /* Remove illegal vms characters*/


    if (cp = strrchr( temp, '/' ))
        {
        if (cp != &temp[strlen(temp) - 1])  /* no trailing "/"              */
            strcpy( filnam, cp + 1 );

        *cp = EOS;

        while (cp = strchr( temp, '.' )) *cp = '_';
        while (cp = strchr( temp, '/' )) *cp = '.';

        if (*temp == '.')                   /* absolute path                */
            {
            strcpy( dirnam, "[" );
            strcat( dirnam, temp );
            }
        else                                /* relative path                */
            {
            strcpy( dirnam, curdir );
            dirnam[ strlen(dirnam) - 1 ] = '.';
            strcat( dirnam, temp );
            }

        strcat( dirnam, "]" );

        }
    else
        {
        strcpy( filnam, temp );             /* no "/", a file               */
        strcpy( dirnam, curdir );
        }
    


    /************************************************************************/
    /*  fix up filename; replaces all '.' after first '.' with '_'          */
    /************************************************************************/

    if (cp = strchr( filnam, '.' ))
        while (cp1 = strchr( cp+1, '.' )) *cp1 = '_';


    if (*filnam == EOS)
        return ISDIRE;
    else
        return ISFILE;

}




/****************************************************************************/
/*  tar_create_dir -- create a new directory                                */
/****************************************************************************/
static int tar_create_dir( char want[] )

{

    int         status;
    int         created     = 1;
    char        *dotp;


    status = mkdir( want, 0 );      /* mkdir in VAX C creates all missing levels */

    if (status)
        {

        if (errno == EEXIST)        /* exists                               */
            return 0;

        if (errno != EINVAL)
            return -1;              /* unknown error, simply return         */

                                    /* maybe too many levels of directories */
                                    /* change "[...FOO.BAR]" to "[...FOO$BAR]" */

        for (dotp = &want[strlen(want) - 1]; dotp > want && status != 0; )
            if (*--dotp == '.')
                {
                *dotp = '$';
                status = mkdir( want, 0 );
                if (status && (errno == EEXIST))
                    {
                    status = created = 0;
                    break;
                    }
                }
        }

    if (status)
        return -1;

    if (verbose && created)
        printf( "x %s%\n", want );

    return 0;

}




/****************************************************************************/
/*  This is supposed to skip over data to get to the desired position       */
/*  Position is the number of bytes to skip.                                */
/****************************************************************************/
static int tar_skip( int bytes )

{

    register int    i;

    if (bytes <= 0) return 0;

    if (devclass != DC$_TAPE)
        {

        int skipsize = (DSIZE * (bytes / DSIZE)) + ((bytes % DSIZE) ? DSIZE : 0);

        if ((i = fseek( tarfp, skipsize, 1 )) == EOF)
            {
            fprintf( stderr, "tar: EOF hit on tar file while skipping.\n" );
            return -1;
            }

        }
    else
        {

        int skipsize = (bytes / DSIZE) + ((bytes % DSIZE) ? 1 : 0);
        char buffer[DSIZE];
        int j;

        for (j = 0; j < skipsize; j++)
            {
            if (( i = fread( buffer, 1, DSIZE, tarfp )) == 0)
                {
                fprintf( stderr, "tar: EOF hit on tar file while skipping.\n" );
                return -1;
                }
            }

        }

    return 0;

}



/****************************************************************************/
/*  Display the header                                                      */
/****************************************************************************/
static int tar_display_hdr
    (   char        *pathname,
        char        *linkname,
        struct stat *sblock )


{
    int     link = (sblock->st_nlink == 1 || sblock->st_nlink == 2);

    if (verbose)
        {

        char    uidstr[9];
        char    gidstr[9];
        int     status;
        short   namlen;
        $DESCRIPTOR( uiddsc, uidstr );

        switch (sblock->st_uid)
            {
            case 0:
                strcpy( uidstr, "root" );
                break;
            case 1:
                strcpy( uidstr, "system" );
                break;
            default:
                if ((status = sys$idtoasc( sblock->st_uid, &namlen, &uiddsc, 0, 0, 0 )) & 1)
                    {
                    uidstr[namlen] = EOS;
                    lowercase( uidstr );
                    }
                else
                    sprintf( uidstr, "%-8d", sblock->st_uid );
            }


        switch (sblock->st_gid)
            {
            case 0:
                strcpy( gidstr, "root" );
                break;
            case 1:
                strcpy( gidstr, "system" );
                break;
            default:
                sprintf( gidstr, "%-8d", sblock->st_gid );
                break;
            }

        printf( "%s   1 %-8s %-8s %8d  %s %s%s%s\n",
                    tar_format_mode( sblock->st_mode, pathname ),
                    uidstr,
                    gidstr,
                    sblock->st_size,
                    tar_format_time( sblock->st_mtime ),
                    pathname,
                    link ? " -> " : "",
                    link ? linkname : "" );
        }
    else
        printf( "%s%s%s\n", pathname, link ? " -> " : "", link ? linkname : "" );

}




/****************************************************************************/
/*  Decode the fields of the header                                         */
/****************************************************************************/
static int tar_decode_hdr
    (   struct tarhdr   *header,
        char            *pathname,
        char            *linkname,
        struct stat     *sblock )

{

    register int    i;
    int             chksum;
    int             value;
    char            *ptr;
    int             filetype;



    if (debug)
        {
        printf( "title = %.*s\n", NAMSIZE,      header->title );
        printf( " mode = %.*s\n", MODESIZE,     header->mode );
        printf( "  uid = %.*s\n", UIDSIZE,      header->uid );
        printf( "  gid = %.*s\n", GIDSIZE,      header->gid );
        printf( "count = %.*s\n", COUNTSIZE,    header->count );
        printf( " time = %.*s\n", TIMESIZE,     header->time );
        printf( "cksum = %.*s\n", CHKSUMSIZE,   header->chksum );
        printf( " lcnt = %d\n",                 header->linkcount );
        printf( " lnam = %.*s\n", NAMSIZE,      header->linkname );
        printf( " junk = %s\n",                 header->dummy );
        }


    sblock->st_nlink = 0;
    sscanf( header->title,  "%100s",pathname );
    sscanf( header->mode,   "%8o",  &sblock->st_mode );
    sscanf( header->uid,    "%8o",  &sblock->st_uid );
    sscanf( header->gid,    "%8o",  &sblock->st_gid );
    sscanf( header->count,  "%12o", &sblock->st_size );
    sscanf( header->time,   "%12o", &sblock->st_mtime );
    sscanf( header->chksum, "%8o",  &chksum );
    *linkname = EOS;


    /************************************************************************/
    /*  Verify checksum                                                     */
    /************************************************************************/

    for (value = 0, ptr = header; ptr < &header->chksum; ptr++)
        value += *ptr;                  /* make the checksum */

    for (ptr = &header->linkcount; ptr < &header->dummy[255]; ptr++)
        value += *ptr;

    value += (8 * ' ');                 /* checksum considered as all spaces */

    if (chksum != value)
        {                               /* abort if incorrect */
        fprintf( stderr, "tar: directory checksum error for %s\n", pathname );
/*      exit(1);*/
        }


    /************************************************************************/
    /*  We may have the link written as binary or as character:             */
    /************************************************************************/

    sblock->st_nlink = isdigit( header->linkcount ) ? (header->linkcount - '0') : header->linkcount;

    if (sblock->st_nlink)
        sscanf( header->linkname, "%100s", linkname );

    return 0;

}



/****************************************************************************/
/*  tar_format_time                                                         */
/*                                                                          */
/*      format file time into readable string                               */
/*                                                                          */
/****************************************************************************/
static char *tar_format_time
    (   int     idate )

{

    time_t          now = time( NULL );
    struct tm       *tim;
    int             curyear;
    int             filyear;
    static char     strtim[26];

    strcpy( strtim, ctime( &idate ) );
    strcpy( strtim, &strtim[4] );
    strtim[12] = EOS;


    tim  = localtime( &now );
    curyear = tim->tm_year;

    tim     = localtime( &idate );
    filyear = tim->tm_year;

    if (curyear != filyear)
        {
        strtim[7] = ' ';
        sprintf( &strtim[8], "%4d", filyear + 1900 );
        }

    return strtim;

}




/****************************************************************************/
/*  tar_format_mode                                                         */
/*                                                                          */
/*      format file mode into readable string                               */
/*                                                                          */
/****************************************************************************/
static char *tar_format_mode
    (   int     mode,
        char    pathname[] )

{
    static char     modestr[10];
    register int    filetype;
    register int    i;


    strcpy( modestr, "----------" );

    filetype = (mode & S_IFMT);

    if (pathname[ strlen(pathname) - 1 ] != '/')
        {
        switch (filetype & S_IFMPB)
            {
            case 0:
                break;
            case S_IFCHR:
                modestr[0] = 'l';
                break;
            default:
                modestr[0] = 'x';
                break;
            }
        }
    else
        {
        switch (filetype)
            {
            case 0:
            case S_IFDIR:
                modestr[0] = 'd';
                break;
            case S_IFCHR:
            case S_IFBLK:
                modestr[0] = 'b';
                break;
            default:
                modestr[0] = 'x';
                break;
            }
        }

    for (i = 1; i < 8; i += 3)
        {
        if (mode & (S_IREAD  >> (i-1))) modestr[i]      = 'r';
        if (mode & (S_IWRITE >> (i-1))) modestr[i+1]    = 'w';
        if (mode & (S_IEXEC  >> (i-1))) modestr[i+2]    = 'x';
        }

    if (mode & S_ISUID)
        modestr[3] = (modestr[3] == 'x') ? 's' : 'S';

    if (mode & S_ISGID)
        modestr[6] = (modestr[6] == 'x') ? 's' : 'S';

    if (mode & S_ISVTX)
        modestr[9] = (modestr[9] == 'x') ? 't' : 'T';


    return modestr;

}





/****************************************************************************/
/*  tar_clean_filespec -- removes illegal characters from directory and     */
/*  file names. Replaces hyphens and commas with underscores.               */
/****************************************************************************/
static tar_clean_filespec( char string[] )

{
    register char   *c;

    for ( c = string; *c; c++ )
        if (strchr( badchars, *c))
            *c = '_';               /* Replace illegal characters by underscores    */

}



/****************************************************************************/
/*                                                                          */
/*  vms2tar -- handles create function                                      */
/*                                                                          */
/****************************************************************************/
static vms2tar( int argc, char **argv )

{
    int                     status;
    struct dsc$descriptor   fsdsc   = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };
    char                    rs[NAM$C_MAXRSS+1];
    struct dsc$descriptor   rsdsc   = { sizeof(rs) - 1, DSC$K_DTYPE_T, DSC$K_CLASS_S, rs };
    struct dsc$descriptor   dsdsc   = { strlen(default_name), DSC$K_DTYPE_T, DSC$K_CLASS_S, default_name };
    struct FAB              *fab    = NULL;
    struct NAM              *nam    = NULL;
    int                     argi;
    int                     tarfd;      /* File descriptor of output tarfile*/
    int                     ftype;
    int                     absolute = 0;
    char                    pathname[NAMSIZE];
    char                    dirnam[NAMSIZE];
    char                    filnam[NAMSIZE];
    char                    filtyp[NAMSIZE];
    struct tarhdr           header;     /* A tar header                     */
    struct stat             sblock;     /* structure returned from stat()   */
    struct dsc$descriptor   tarfdsc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, tarfile };


    if (argc < 3 || (foption && argc < 4)) tar_usage();

    rs[sizeof(rs)-1]    = EOS;




    /************************************************************************/
    /*  Open the output file                                                */
    /************************************************************************/

    tarfd = creat( tarfile, 0600, "rfm=fix", "mrs=512" );

    if (tarfd < 0)
        {
        fprintf( stderr, "tar: error opening output tarfile %s\n", tarfile );
        perror( "tar" );
        exit( SS$_NORMAL );
        }

    getname( tarfd, tarfile );
    lowercase( tarfile );

    tarfdsc.dsc$w_length  = strlen( tarfile );

    status = lib$getdvi( &DVI$_DEVCLASS, 0, &tarfdsc, &devclass );
    if (!(status & 1))
        {
        fprintf( stderr, "tar: error getting device information for %s\n", tarfile );
        fprintf( stderr, "tar: %s\n", vmserr( status ) );
        exit( SS$_NORMAL );
        }



    /************************************************************************/
    /*  For all files on the command line ...                               */
    /************************************************************************/

    for (argi = foption ? 3 : 2; argi < argc; ++argi)
        {

        fsdsc.dsc$w_length  = strlen( argv[argi] );
        fsdsc.dsc$a_pointer = argv[argi];

        /********************************************************************/
        /*  start the search                                                */
        /********************************************************************/

        status = lib$find_file( &fsdsc, &rsdsc, &fab, &dsdsc );

        nam = fab->fab$l_nam;


        /********************************************************************/
        /*  check for network file access                                   */
        /********************************************************************/

        if (nam->nam$b_node)
            {
            fprintf( stderr, "tar: network file access is not supported.\n" );
            lib$find_file_end( &fab );
            continue;
            }


        /********************************************************************/
        /*  store absolute UNIX pathnames if explicit device or directory   */
        /********************************************************************/

        if (nam->nam$v_exp_dev)
            absolute = 1;
        else
            if (nam->nam$v_exp_dir)
                if  (!strstr( argv[argi], "[-" ) &&
                     !strstr( argv[argi], "[." ) &&
                     !strstr( argv[argi], "<-" ) &&
                     !strstr( argv[argi], "<." ))
                    absolute = 1;


        do
            {

            tar_trim( rs );

            lowercase( rs );

            if (!(status & 1))
                {
                switch (status)
                    {
                    case RMS$_DNF:
                        fprintf( stderr, "tar: directory not found %s\n", rs );
                        break;
                    case RMS$_FNF:
                        fprintf( stderr, "tar: file not found %s\n", rs );
                        break;
                    case RMS$_PRV:
                        fprintf( stderr, "tar: no privilege to access %s\n", rs );
                        break;
                    default:
                        fprintf( stderr, "tar: error searching for %s\n", rs );
                        fprintf( stderr, "tar: %s.\n", vmserr( status ) );
                        break;
                    }
                continue;
                }


            /****************************************************************/
            /*  split filespec into directory, name, and type+version       */
            /****************************************************************/


            strncpy( dirnam, nam->nam$l_dir,  nam->nam$b_dir );
            dirnam[nam->nam$b_dir] = EOS;

            strncpy( filnam, nam->nam$l_name, nam->nam$b_name );
            filnam[nam->nam$b_name] = EOS;

            strncpy( filtyp, nam->nam$l_type, nam->nam$b_type + nam->nam$b_ver );
            filtyp[nam->nam$b_type + nam->nam$b_ver] = EOS;


            /****************************************************************/
            /*  convert vms filespec to UNIX                                */
            /****************************************************************/

            ftype = tar_cvt_vmsname_to_unixname( dirnam, filnam, filtyp, absolute );

            strcpy( pathname, dirnam );
            strcat( pathname, filnam );
            strcat( pathname, filtyp );

            if (tar_get_fattr( rs, &sblock )) continue;

            /****************************************************************/
            /* prompt to process file                                       */
            /****************************************************************/

            if (wait)
                {
                char    temp[4] = EOS;
                while (*temp != 'y' && *temp != 'n')
                    {
                    printf( "a %s: ", rs );
                    scanf( "%s", temp );
                    *temp = _tolower( *temp );
                    }
                if (*temp != 'y') continue;
                }


            switch (ftype)
                {

                case ISDIRE:
                    sblock.st_size  =  0;
                    tar_fill_hdr( pathname, &sblock, &header );
                    tar_write_hdr( tarfd, &header );
                    break;

                case ISFILE:

                    if (tar_insert_file( rs, pathname, &sblock, tarfd ) < 0)
                        continue;
                    break;

                }

            if (verbose)
                {
                int blocks = (sblock.st_size / DSIZE) + ((sblock.st_size % DSIZE) ? 1 : 0);
                printf( "a %s %d block%s\n", pathname, blocks, (blocks != 1 ? "s" : "") );
                }

            }
        while ( rsdsc.dsc$w_length = sizeof(rs) - 1,
                (status = lib$find_file( &fsdsc, &rsdsc, &fab, &dsdsc )) != RMS$_NMF );

        }


    tar_write_trailer( tarfd );
    close( tarfd );

}



/****************************************************************************/
/*  tar_insert_file - insert file into tar file.                            */
/*                                                                          */
/*  Move data from input file to tar file.                                  */
/*  Always pad the output to a full DSIZE                                   */
/****************************************************************************/
static int tar_insert_file
    (   char        filename[],
        char        pathname[],
        struct stat *sblock,
        int         tarfd )

{

    int             nbytes  = sblock->st_size;
    char            buffer[DSIZE];
    int             inbytes;
    FILE            *infd;
    struct tarhdr   header;
    int             tarhdrpos;


    tarhdrpos = lseek( tarfd, 0, SEEK_CUR );



    if (sblock->st_fab_rfm == FAB$C_FIX ||
        sblock->st_fab_rfm == FAB$C_STM ||
        sblock->st_fab_rfm == FAB$C_STMLF)
        {

        if ((infd = fopen( filename, "rb" )) == NULL)
            {
            fprintf( stderr, "tar: error opening input file %s\n", filename );
            perror( "tar" );
            return -1;
            }

        tar_fill_hdr( pathname, sblock, &header );  /* We have all of the information   */
        tar_write_hdr( tarfd, &header );            /* So write to the output           */

        while (nbytes > 0)
            {

            inbytes = fread( buffer, 1, nbytes > DSIZE ? DSIZE : nbytes, infd );

            if (!inbytes)
                {
                fprintf( stderr, "tar: error reading input file %s\n", filename );
                perror( "tar" );
                fclose( infd );

                if (devclass != DC$_TAPE)
                    {
                    lseek( tarfd, tarhdrpos, SEEK_SET );
                    return -1;
                    }
                else
                    exit( SS$_NORMAL );

                }
            nbytes -= inbytes;
            tar_flush( tarfd, buffer );
            }

        fclose( infd );

        }
    else
        {

        char    *bufp   = buffer;
        char    *linep;
        char    dbuffer[DSIZE];

        if ((infd = fopen( filename, "r" )) == NULL)
            {
            fprintf( stderr, "tar: error opening input file %s\n", filename );
            perror( "tar" );
            return -1;
            }

        sblock->st_size = 0;

        if (devclass == DC$_TAPE)
            {
            if ((sblock->st_size = tar_find_filesize( infd, filename )) < 0)
                {
                fclose( infd );
                return -1;
                }
            }

        tar_fill_hdr( pathname, sblock, &header );  /* Compute the header   */
        tar_write_hdr( tarfd, &header );            /* Write it             */

        nbytes = 0;

        while (fgets( dbuffer, DSIZE, infd ) !=  NULL)
            {

            nbytes += strlen( dbuffer );

            linep = dbuffer;

            while (*linep != EOS)   /* read source file line by line*/
                {
                if (bufp >= &buffer[DSIZE])
                    {
                    bufp = buffer;
                    tar_flush( tarfd, buffer ); /* if buffer full, flush it */
                    }                   /* copy in fixed size output buffer */
                *bufp++ = *linep++;
                }
            }

        tar_flush( tarfd, buffer );

        fclose( infd );

        if (devclass != DC$_TAPE)
            if (nbytes)
                {
                sblock->st_size = nbytes;
                tar_rewrite_hdr( tarfd, tarhdrpos, nbytes );
                }
            else
                lseek( tarfd, tarhdrpos, SEEK_SET );

        }

    return 0;

}


/****************************************************************************/
/*  tar_find_filesize - calc bytes inf file by reading entire file          */
/****************************************************************************/
static tar_find_filesize
    (   FILE    *infd,
        char    filename[] )

{

    register int    bytes   = 0;
    char            buffer[DSIZE];


    while (fgets( buffer, DSIZE, infd ) !=  NULL)
        bytes += strlen( buffer );

    if (!feof(infd))
        {
        fprintf( stderr, "tar: error reading input file %s\n", filename );
        fprintf( stderr, "tar: record too large or incorrect RMS attributes\n" );
        return -1;
        }

    rewind( infd );                         /* Back to the beginning        */

    return bytes;

}



/****************************************************************************/
/*  tar_flush - write a fixed size block in output tarfile                  */
/****************************************************************************/
static tar_flush
    (   int     tarfd,
        char    buffer[] )

{

    if (write( tarfd, buffer, DSIZE) != DSIZE)
        {
        fprintf( stderr, "tar: error writing tarfile.\n" );
        perror( "tar" );
        exit( SS$_NORMAL );
        }

}



/****************************************************************************/
/*  tar_write_hdr - copy the header to the output file                      */
/****************************************************************************/
static int tar_write_hdr
    (   int             tarfd,
        struct tarhdr   *header )

{

    register int    n;

    if ((n = write( tarfd, header, DSIZE )) != DSIZE)
        {
        fprintf( stderr, "tar: error writing header in tarfile.\n" );
        perror( "tar" );
        exit( SS$_NORMAL );
        }

    return n;

}



/****************************************************************************/
/*  tar_rewrite_hdr - rewrite header with proper count and checksum         */
/****************************************************************************/
static int tar_rewrite_hdr
    (   int     tarfd,
        int     hdrpos,
        int     bytecount )

{

    register int    status;
    int             chksum;
    char            *ptr;
    int             curpos;
    struct tarhdr   header;
    char            tmp[15];


    /************************************************************************/
    /*  save current position in file                                       */
    /************************************************************************/

    curpos = lseek( tarfd, 0, SEEK_CUR );


    /************************************************************************/
    /*  position to header                                                  */
    /************************************************************************/

    status = lseek( tarfd, hdrpos, SEEK_SET );


    /************************************************************************/
    /*  read the old header                                                 */
    /************************************************************************/

    status = read( tarfd, &header, DSIZE );


    /************************************************************************/
    /*  store new count and checksum                                        */
    /************************************************************************/

    sprintf( tmp, "%11o ", bytecount );
    strncpy( header.count, tmp, 12 );

    strncpy( header.chksum, "        ", 8);

    for (chksum = 0, ptr = &header; ptr < &header.linkcount; ptr++)
        chksum += *ptr;                         /* make the checksum */

    sprintf( header.chksum, "%6o", chksum );


    /************************************************************************/
    /*  rewrite the header                                                  */
    /************************************************************************/

    status = lseek( tarfd, hdrpos, SEEK_SET );
    status = write( tarfd, &header, DSIZE );

    /************************************************************************/
    /*  back to our original position                                       */
    /************************************************************************/

    status = lseek( tarfd, curpos, SEEK_SET );

}



/****************************************************************************/
/*  tar_get_fattr - get the file attributes via stat()                      */
/****************************************************************************/
static int tar_get_fattr
    (   char        filespec[],
        struct stat *sblock )

{

    if (stat( filespec, sblock ))
        {
        fprintf( stderr, "tar: can't get file status on %s\n", filespec );
        perror( "tar" );
        return -1;
        }
    else
        return 0;

}




/****************************************************************************/
/*  tar_write_trailer - write the two blank blocks on the output file       */
/*  pad the output to a full blocksize if needed.                           */
/****************************************************************************/
static tar_write_trailer( int tarfd )

{
    register int    i;
    struct tarhdr   header;
    register int    tarfilepos;

    memset( &header, 0, DSIZE );

    tar_write_hdr( tarfd, &header );
    tar_write_hdr( tarfd, &header );

    tarfilepos = (lseek( tarfd, 0, SEEK_CUR ) / DSIZE) % BLKSIZE;

    for ( i = tarfilepos; i < BLKSIZE; i++ )
        tar_write_hdr( tarfd, &header );

    return 1;

}


/****************************************************************************/
/*  tar_cvt_vmsname_to_unixname                                             */
/*                                                                          */
/*  Convert a VMS filename to UNIX format. Return a value to indicate       */
/*  if this is a directory name, or another file.                           */
/****************************************************************************/
static int tar_cvt_vmsname_to_unixname
    (   char    dirnam[],
        char    filnam[],
        char    filtyp[],
        int     absolute )

{
    register char   *cp;


    /************************************************************************/
    /*  fix up directory name, getting rid of rooted directories and mfd    */
    /************************************************************************/

    tar_clean_dir( dirnam );


    /************************************************************************/
    /*  lowercase the lot of em                                             */
    /************************************************************************/

    lowercase( dirnam );
    lowercase( filnam );
    lowercase( filtyp );


    if (!absolute)
        {

        char            tmp1[NAM$C_MAXRSS];
        char            tmp2[NAM$C_MAXRSS];
        register char   *cp1    = tmp1;
        register char   *cp2    = tmp2;
        register char   *dotp1;
        register char   *dotp2;

        strcpy( tmp1, dirnam );
        tmp1[strlen(tmp1) - 1] = '.';

        strcpy( tmp2, curdir );
        tmp2[strlen(tmp2) - 1] = '.';

        while ( (dotp1 = strchr( cp1, '.' )) && (dotp2 = strchr( cp2, '.' )) )
            {
            *dotp1 = *dotp2 = EOS;
            if (strcmp( cp1, cp2 )) break;
            cp1 = ++dotp1;
            cp2 = ++dotp2;
            }

        if (dotp1) *dotp1 = '.';
        strcpy( dirnam, cp1 );
        }


    if (*dirnam == '[') *dirnam = '/';

    while (cp = dirnam, cp = strchr(cp, '.'))
        *cp = '/';

    while (cp = dirnam, cp = strchr(cp, ']'))
        *cp = '/';

    if (!strcmp( filtyp, ".dir;1" ))
        {
        strcat( dirnam, filnam );
        strcat( dirnam, "/" );
        *filnam = *filtyp = EOS;
        return ISDIRE;
        }

    if (cp = strchr( filtyp, ';' )) *cp = EOS;  /* strip version number     */

    if (!strcmp( filtyp, "." ) && !dot)         /* strip trailing dot maybe */
        *filtyp = EOS;

    return ISFILE;

}




/****************************************************************************/
/*  tar_fill_hdr - fill the fields of the header                            */
/*                                                                          */
/*  enter with the file name, if the file name is empty,                    */
/*  then this is a trailer, and we should fill it with zeroes.              */
/****************************************************************************/
static int tar_fill_hdr
    (   char            path[],
        struct stat     *sblock,
        struct tarhdr   *header )

{

    int         chksum;
    char        *ptr;
    char        tmp[15];


    /************************************************************************/
    /*  Fill the header with zeroes                                         */
    /************************************************************************/

    memset( header, 0, DSIZE );

    if (strlen( path ))                 /* only fill if there is a file     */
        {
        sprintf( header->title,         "%s",    path );
        sprintf( header->mode,  "%6o ",  sblock->st_mode );
        sprintf( header->uid,           "%6o ",  sblock->st_uid );
        sprintf( header->gid,           "%6o ",  sblock->st_gid );
        sprintf( tmp,                   "%11o ", sblock->st_size );
        strncpy( header->count, tmp, 12 );
        sprintf( tmp,                   "%11o ", sblock->st_mtime );
        strncpy( header->time, tmp, 12 );
        strncpy( header->chksum, "        ", 8);

        for (chksum = 0, ptr = header; ptr < &header->linkcount; ptr++)
            chksum += *ptr;                             /* make the checksum */

        sprintf( header->chksum, "%6o", chksum );
        }

    return 0;

}



/****************************************************************************/
/*  lowercase - function to change a string to lower case                   */
/****************************************************************************/
static int lowercase( char string[] )

{
    int i;

    for (i=0; string[i] = _tolower( string[i] ); i++ );

    return --i;             /* return string length */

}




/****************************************************************************/
/*  tar_trim - trim string of trailing spaces/tabs                          */
/****************************************************************************/
static tar_trim( char *string )

{
    int     i;

    for ( i = strlen(string) - 1; string[i] == ' ' || string[i] == '\t'; i-- );

    string[i+1] = EOS;

}



/****************************************************************************/
/*  tar_clean_dir - routine to get rid of rooted directory problems         */
/*  and any others that turn up                                             */
/****************************************************************************/
static int tar_clean_dir( char string[] )

{
    register char   *cp;



    if (cp = strstr( string, "][" ))
        strcpy( cp, cp+2 );             /* Just collapse around the string  */

    if (cp = strstr( string, "[000000." ))
        strcpy( cp + 1, cp + 8 );       /* remove "000000."                 */

    if (cp = strchr( string, '<' ))
        *cp = '[';

    if (cp = strchr( string, '>' ))
        *cp = ']';

}



/****************************************************************************/
/*  Syntax error exit                                                       */
/****************************************************************************/
static tar_usage()
{
    printf( "usage: tar x|c|t[vwbdz][f tarfile] [file [file...]]\n" );
    exit( 1 );
}



/************************************************************************/
/*  return pointer to C-string vms error message                        */
/************************************************************************/
static char *vmserr
    ( int   status )

{
    static char     errmsg[133];
    $DESCRIPTOR( errmsgdsc, errmsg );
    long            i;

    sys$getmsg( status, &errmsgdsc.dsc$w_length, &errmsgdsc, 15, 0 );
    errmsg[errmsgdsc.dsc$w_length] = EOS;
    for (i = 0; errmsg[i]; i++)
        errmsg[i] = _tolower(errmsg[i]);

    return errmsg;
}