From: HENRY::IN%"[email-masked]" 7-JAN-1987 11:40
To: [email-masked]

Glenn:
        Here's the source code for the programs. I have included
both sources in one message, I hope they don't get truncated by the net..
Look for the string "VMS2TAR" to separate
the files. I will probably submit the code to DECUS now, since I really
got swamped with requests this time around !

-Sid.

                                        October 6/1986.
Greetings:

        Here is the program to extract files from a Tar format tape or
file into a VMS system. This is a major update on  version 1.xx
that I distributed earlier. The major changes:

(1) It reads from either a tape or a file image of a tar tape
(2) It transfers files as is, does NOT translate UNIX stream files to
        the format that the VMS editor likes, but this is not a
        problem unless you intend to edit the files. See below.
        (The old "s" option is now the default).
(3) It can usually leave the tape positioned ready to read another
        Tar file from the tape. NOTE: there is a SET MAGTAPE/
        option in VMS 4.x that will skip files to start reading in
        the middle of a tape. The TAR2VMS program does not do any rewinds.
(4) UNIX "linked" files are handled.

        I will be glad to hear of your experiences with the code, since
often when we get a Tar tape from a different system, we find something
that I hadn't anticipated. The program was developed by looking at DUMP
output from various tar format tapes on a VMS system, so there are
undoubtedly variations that I haven't seen. ( I am not a UNIX expert..)
We looked at UNIX manuals as well, but found some discrepancies in real
tapes, so went with what seemed to work.

        I also have a partially debugged version of a Tar writer; ask
for it if you are willing to be a guinea pig, or need to transfer some
files to a UNIX system from VMS.

Sid Penstone,
VLSI Group,
Department of Electrical Engineering,
Queen's University,
Kingston, Ontario, Canada, K7L3N6
phone (613) 545-2925

BITNET:         PENSTONE@QUCDNEE1       (Preferred)
                PENSTONE@QUCDN  (If the other doesn't work)

NOTE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
NOTE!!! The program must be separated at the "** CUT HERE **" lines
NOTE!!! below before compiling.
NOTE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Introduction:

        The program TAR2VMS.C reads from a /foreign tape,or a file
which is an image of a Tar format tape, and extracts the files into
a directory tree starting in the current default directory.
Directories will be created automatically.
For example, if your default is [JONES.MAIN], then the file
"electric/sub/test.c" will be written into [JONES.MAIN.ELECTRIC.SUB]TEST.C .
        For the simplest operation, just  RUN it after defining the
logical "TAPE" to be the the Tar source and doing a
MOUNT/FOREIGN/BLOCK=10240/RECORD=512 (if it is a tape); this
does the extraction automatically, reporting on stdout as it does.

        The extracted files will be written to your system as C stream files.
The VMS editor EDT will complain about the format, but EVE will not.

        To convert a text file to standard VMS format:
(Thanks to Eric Gisin at U. of Waterloo:)
        You can use the VMS CONVERT utility by first creating a xxxx.FDL file
containing:

FILE
        ORGANIZATION            sequential
RECORD
        BLOCK_SPAN              yes
        CARRIAGE_CONTROL        carriage_return
        FORMAT                  variable

Then run Convert with the regular DCL:
CONVERT/FDL=xxxx  input-file  output-file
-------------------------------------------------------------------------

Usage:

        -Define the logical variable "TAPE" to be the desired tape
         unit or the file containing the Tar data.

        -Mount the tape /FOREIGN/BLOCK=10240/RECORD=512

        -Set the default directory below which the tree will be created
         before calling the program. Thus: if your default directory
         is SYS$SYSDEVICE:[MYDIRE], the file "electric/dbase.c" will
         be written in SYS$DEVICE:[MYDIRE.ELECTRIC]DBASE.C

       -Then run TAR2VMS: (either by RUN or as a command)
         if you use RUN, the operation is (extract, verbose)
         otherwise:
'command' [options]
        where the options are:
                "t" (listing only)       Listing of files on tape
                "x" (extract)            Extract files from the tape
                "v" (verbose):           Report activity
                "w" (wait)               ...Not implemented
    -to use the options, the program must be executed using the
     foreign command feature of VMS, by defining a command:
command :== $path:tar2vms      !where path=complete location of tar2vms.exe

        Example:
If the command is 'tar2vms', then:
tar2vms t                       will list the contents of the tar file
tar2vms xv                      will extract all of the files, reporting

The listing reports the UNIX protection (in octal), the file size in bytes,
the modification date on the tape, and the file name.

ERRORS: Most probable causes:
        "Can't open input file" - likely forgot to define TAPE, or mount
                                        the tape.
        "Error reading input"   - unexpected Tar format, or bad file.
        "Error writing output " - directory access error
        "Creation Error...   "  - illegal directory or file name

--------------------------------------------------------------------------
Important assumptions for this version (2.1):

    The defaults are (no options):
    -Extract all of the files, reporting as each file is created

    -Read from a tape, mounted foreign, or a fixed record length
     file,with block size of 10240 bytes or less . If larger block
     size, change the  definition of SIZE.

    -Stop on the special (blank header) block written by UNIX.
     (Note, like UNIX Tar, it does not move to the file mark at
     the end of the file. Answer "y" to the prompt to get to another
     tar file on the same tape.)THIS MIGHT NOT WORK...

    -All files are assumed to be UNIX stream and are
     just copied. The vms file-attribute will be "stream". This may
     have to be changed in certain cases with CONVERT, or by writing
     a little C program to read the file and rewrite it.

    -When a UNIX linked file is encountered, there will be another
     file on the tape that contains the actual data. It may not be an
     earlier file that was the original file, since the files
     are written on in alphabetical order. The program will create a
     file under the name of the linked file, but it will contain only
     a text messsage reporting the name of the file that contains the
     data.

    This version converts the path names to VMS format before creating
the files. Some special cases:

 (1) File names with more than one period (".") in the name have those
        after the first converted to underscores (e.g. "mults.s.c" is
        written out under the name "mults.s_c"
 (2) It is assumed that all of the pathnames on the Tar tape have
        complete directory info, with no device parts.
 (3) Commas and hyphens in file or directory names are converted to
        underscores. If this causes any problems, you can modify the
        code in vms_cleanup().Any such translations are reported in
        "verbose" mode.
 (4) Files are created with the user's default protection. The mode on
        the tape is ignored.
 (5) The dates of files extracted are the date of the extraction, not the
        date of the Tar version.
 (6) UNIX files that have no extension, and end in a letter will have
        a "." added to the name automatically. This can cause some
        mysteries with large programs that look for other files with
        an exact string search. Also, if the files are written out
        to UNIX as a TAR tape from VMS, the "." goes along with the file
        name.

------------------------------------------------------------------------
Known Bugs:

    - Can't always read another file on the same tape. The program
      only works correctly when the tape had regular EOF marks on it,
      which apparently is not always true.
    - Can't selectively extract files from the tape. The "wait" option
      could be useful, but haven't got around to it..

/**************************** CUT HERE ****************************** */
/* Read a TAR format tape or file , move files into VMS directories */
/* Copyright 1986, Sid Penstone,
*  Department of Electrical Engineering,
*  Queen's University,
*  Kingston, Ontario, Canada K7L3N6
* (613)-545-5925
* BITNET:          PENSTONE@QUCDNEE1  (Preferred)
*       or  PENSTONE@QUCDN
*
* Version 2.2, Oct.21,1986
* mods: - corrected header size (thanks to Eric Gisin, U .of Waterloo)
*       - No more of the dreaded QIO's ( "  "  " )
*       - tried to sort out link flag format
*       - uses a tape or a file as input
*       - NOTE: default is NO conversion to vms standard text format (cr)
* 2.1   - trapped commas in file names, converted to '_'
* 2.2   - reported translations of names
*       - continued after error in opening output file
*       - exit correctly on cannot open input file
*/

/* The input data is in record format, length 512, blocks of 10240 bytes;
 */

#include stdio
#include time
#include ssdef
#include iodef
#include descrip
#include ctype

#define ERROR1 -1
#define BUFFSIZE 512
#define ISDIRE 1
#define ISFILE 0
#define NAMSIZE 100
#define SIZE 10240              /* Block size */
#define DSIZE 512               /* Data block size */

struct                  /* A Tar header */
    {
    char title[NAMSIZE];
    char protection[8];
    char field1[8];             /* this is the user id */
    char field2[8];             /*  this is the group id */
    char count[12];             /*  was 11 in error */
    char time[12];              /* UNIX format date  */
    char chksum[8];             /* Header Checksum (ignored) */
    char linkcount;             /* hope this is right */
    char linkname[NAMSIZE]; /* Space for the name of the link */
    char dummy[255];    /* and the rest */
    } header;

static char buffer[DSIZE];      /* BUFFER for a record */

/* Function flags, options:*/
int extract,            /* x option (default) */
    list,                       /* t option : list tape contents */
    verbose,            /* v option, report actions */
    wait;

/* Miscellaneous globals, etc. */

char *tarfile = "tape", /* Input file name  */
    pathname[NAMSIZE],  /* File name as found on tape (UNIX) */
    directory[NAMSIZE], /* Current directory */
    new_directory[NAMSIZE],     /* Directory of current file */
    top[NAMSIZE],               /* Top level or root */
    newfile[NAMSIZE],   /* VMS format of file name */
    outfile[NAMSIZE],   /* Complete output file specification */
    temp[256],          /* Scratch */
    creation[NAMSIZE],  /* Date as extracted from the TAR file */
    *ctime(),           /* System function */
    linkname[NAMSIZE];  /* Linked file name  */

int bytecount,  mode, uic1, uic2, linktype;/* Data from header */
int tarfd;                      /* The input file descriptor */

main(argc,argv)
int argc;
char *argv[];
{
int isterm,status,file_type,j,c, flag;
char *make_directory(), *cp;

/* Decode the options and parameters: */

    if(argc ==1)
        {
        extract = 1;            /* Default for now */
        verbose = 1;
        wait = 0;               /* Don't wait for prompt */
        }
    while(--argc > 0)
        {
        cp = argv[1];
        while(c = *cp++)
            {
            switch(c)
            {
            case 't':
                list=1;
                break;
            case 'x':
                extract=1;
                break;
            case 'v':
                verbose=1;
                break;
            case 'w':
                wait=1;
                break;
            default:
                printf("Option '%c' not recognized.\n",c);
            }
       }
   }

/* Find if this is a terminal */
    isterm = isatty(0);

/* Set up directory names */
    strcpy(top,getenv("PATH"));

/* Start with the default as the top */
    strcpy(directory,top);

/* open the file for reading */
    if((tarfd = opentar()) <= 0)
        {
        printf("Error opening the Tar tape\n");
        exit(2);
        }
/* Now keep reading headers from this file, and decode the names, etc. */

    while((status=hdr_read(&header))==DSIZE)    /* 0 on end of file */
        {
        if(strlen(header.title)!=0)     /* Valid header */
            {
            decode_header();
            if(extract)
                {
                file_type=scan_title(pathname,new_directory,newfile);
                if( make_new(new_directory)!=0)
                    printf("Error creating %s\n",new_directory);
                if(file_type == ISDIRE)
                    {}
                if(file_type == ISFILE)
/*  Now move the data into the output file */
                    if(bytecount>0)
                        {
                        strcpy(outfile,new_directory);
                        strcat(outfile,newfile);
                        if((j=copyfile(outfile,bytecount))<0)
                            printf("Error writing file %s\n",outfile);
                        }
                }
            else                        /* listing only */
                {
                printf("%o %6d %s %s\n",
                    mode,bytecount,creation+4,pathname);
                if(linktype == 0)
                    tarskip(bytecount);
                else
                    printf("     *****( Linked to file: %s)\n",linkname);
                }
            }
        else                    /* Empty header means the end!!! */
            {
            status = 1;
            printf("End of Tar file found.\n");
            break;
            }

        }       /* end while  */
    if(status == 1)                     /* Empty header */
        {
        printf("Do you wish to move past the EOF mark ? y/n\n");
        gets(temp);
        if(tolower(temp[0]) == 'y')
            while((status=hdr_read(&header)) >0);
        else
            exit(SS$_NORMAL);
        }
    if(status==0)                       /* End of tar file  */
        {
        printf("End of file encountered\n");
        exit(SS$_NORMAL);
        }
    if(status<0)                        /* An error  */
        {
        printf("Error reading input.\n");
        exit(2);
        }
}

/* This function simply copies the file to the output, no conversion */

int copyfile(outfile,nbytes)
char outfile[]; /* name of output version */
int nbytes;

{
int inbytes, fil;
/*  Open the output file */
    if((fil=creat(outfile,0)) == ERROR1)
        {
        printf(" Creation error in opening %s \n",outfile);
        tarskip(bytecount);
        return(-2);
        }
    if(linktype !=0)
        {
        sprintf(buffer,"This file is linked to %s\n",linkname);
        write(fil,buffer,strlen(temp));
        }
    else
        {
        while(nbytes>0)
            {
            if((inbytes=read(tarfd,buffer,DSIZE)) > 0)
                {
                write(fil,buffer,(nbytes > DSIZE)? DSIZE:nbytes);
                nbytes -= inbytes;
                }
            else
                {
                printf("End of input file detected\n");
                close(fil);
                return(-1);
                }
            }
        }
/* Close the file */
    close(fil);
    if(verbose)
        {
        printf("CREATED: %s\n",outfile);
        if(linktype!=0)
            printf(" *** REAL DATA IS IN: %s\n",linkname);
        }
    return(0);
}

/* Decode a file name into the directory, and the 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.
*/

int scan_title(line,dire,fname)
char line[],dire[],fname[];
{
char temp[NAMSIZE],*end1;
int len,len2,i,ind;
/* 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.
*/
    strcpy(dire,top);                   /* Start with the top level */
    if(strncmp(line,"./",2)==0)
        strcpy(line,line+2);            /* ignore "./" */
    strcpy(temp,line);                  /* Start in local buffer */
    ind=vms_cleanup(temp);              /* Remove illegal vms characters */
    if((end1=strrchr(temp,'/'))==0)     /* No directory at all  ? */
        strcpy(fname,temp);             /* Only a file name */
    else
        {                               /* End of directory name is '/' */
        *end1 = 0;                      /* Terminate directory name */
        strcpy(fname,end1+1);           /* File name without directory */
        for (i=0;temp[i];i++)           /* Change '/' to '.' in directory */
            if(temp[i]=='/')
                temp[i]='.';
        dire[strlen(dire)-1] = (temp[0]=='.')?0:'.' ;
                 /* "." to indicate a subdirectory (unless already there )*/
        strcat(dire,temp);      /* Add on the new directory  */
        strcat(dire,"]") ;              /* And close with ']' */
        }
    if(strlen(fname)==0)        /* Could this cause problems ? */
        {
        return(ISDIRE);
        }
    else
        for(i=0,end1=fname;*end1;end1++) /* Replace multiple . */
            if(*end1 == '.')
                if(i++)*end1 = '_'; /* After the first */
        if((i>1||ind)&& verbose )       /* Any translations ? */
            printf("****RENAMED: %s \n         TO: %s\n",line,fname);
    return(ISFILE);
}

/* Create a new directory, finding out any higher levels that are missing */

/* We will parse the directory name into the next higher directory, and the
* desired directory as "desired.dir".
* Thus: "DEV:[top.sub1.sub2]" is made into "DEV:[top.sub1]sub2.dir" . If
* the directory does not exist , then create the original directory. There
* may be higher levels missing, so we can recurse until we reach the top
* level directory, then work our way back, creating directories at each
* successive level.
*/

int make_new(want)
char want[];
{
int i,len;
char a[NAMSIZE],parent[NAMSIZE],*end,name[NAMSIZE];
    strcpy(parent,want);
    len = strlen(parent);
    parent[len-1] =0 ;          /* Get rid of the "]" */
    end = strrchr(parent,'.');  /* Find the last '.' */
    if(end != NULL)
        {
        strcpy(a,end+1);        /* Get the last parent */
        strcat(a,".dir");       /* Add the extension */
        *end++ = ']' ;          /* Reduce the directory parent */
        *end = 0;               /* Terminate the directory */
        strcpy(name,parent);
        strcat(name,a);
        }

    if(access(name,0) <0)       /* Does the directory exist ? */
        {
            if(strcmp(parent,top)!=0) /* No, are we at the top? */
                if(make_new(parent))   /*  No, look again */
                    return(-1); /* recurse */
            if(mkdir(want,0755,0,0,0)) /* make it */
                return(-1);             /* Leave on error */
            else
                if(verbose)
                    printf("CREATED: %s\n",want);
            return(0);
        }
    return(0);
}

 /* Function to open and get data from the blocked input file */
int opentar()
{
int fd;
    fd = open(tarfile, 0, "rfm = fix","mrs = 512");
    if(fd < 0)
        {
        printf("Can't open input file \n");
        return(0);
        }
    return(fd);
}

/* Get the next file header from the input file buffer. We will always
* move to the next 512 byte boundary.
*/
int hdr_read(buffer)
char *buffer;
{
int stat;
    stat = read(tarfd,buffer,DSIZE);    /* read the header */
    return(stat);                               /* Catch them next read ? */
}

/* This is supposed to skip over data to get to the desired position */
/* Position is the number of bytes to skip. We should never have to use
* this during data transfers; just during listings. */
int tarskip(bytes)
int bytes;
{
int i=0;
    while(bytes > 0)
        {
        if((i=read(tarfd,buffer,DSIZE)) == 0)
            {
            printf("End of file encountered while skipping.\n");
            return(-1);
            }
        bytes -= i;
        }
    return(0);
}

/* Decode the fields of the header */

int decode_header()
{
int idate, *bintim;
char ll;
bintim = &idate;
    linktype=0; strcpy(linkname,"");
    strcpy(pathname,header.title);
    sscanf(header.time,"%o",bintim);
    strcpy(creation,ctime(bintim));     /* Work on this! */
    creation[24]=0;
    sscanf(header.count,"%o",&bytecount);
    sscanf(header.protection,"%o",&mode);
    sscanf(header.field1,"%o",&uic1);
    sscanf(header.field2,"%o",&uic2);
    /* We may have the link written as binary or as character:  */
    linktype = isdigit(header.linkcount)?
            (header.linkcount - '0'):header.linkcount;
    if(linktype != 0)
        sscanf(header.linkname,"%s",linkname);
    return(0);
}

/* remove illegal characters from directory and file names; replace
* hyphens and commas with underscores.Returns number of translations
* that were made.
*/
vms_cleanup(string)
char string[];
{
int i,flag=0;
char c;
    for(i=0;c=string[i];i++)
        {
        switch (c)
            {
            case '-':           /* No hyphens in file names */
            case ',':           /* No commas in file names  */
                string[i]= '_';
                flag++;         /* Record if any changes were made */
                break;
            default:
                break;
            }
        }
    return(flag);
}

/********************************** VMS2TAR **********************/
Greetings:                                      Dec.23,1986

        Here is the current version (1.8) of a program to write Tar
format tapes on a VMS system. Comments and reports on bugs will be much
appreciated. It has been tried with a SUN system, and files were read
successfully.

        Before compiling, separate the program, at the "*** CUT HERE ***"
line below.

        The file-spec in the command string can use the usual VMS wild cards,
in fact anything that you would use in a DIRECTORY command. Successive
file-specs must be separated by commas, WITHOUT BLANK SPACES, or the
command handler separates them as separate arguments, and only the data
ahead of the blanks is used.

Example:   vms2tar cv [john.new...]*.c   will copy all of the .c files in
the subdirectories to the Tar file.

        Directory specifications are propagated to successive file
specifications, if there is no explicit directory specification. A
test with the "t" (list) option will help to show how it works.

        Note that UNIX does not permit two files of the same name in
the same directory, so the program does not assume the wild card for
the version number, and you should not use it in the input spec, or
only the last version written on the tape (the oldest) will survive
when extracted on the UNIX system. The program as written copies the
highest version number by default. The version number is NOT included
in the file name on the Tar tape.

        If you wish to output different version numbers to Tar, then
the code would be modified something like this in scan_name:
If there is a ";*" string in the searchname[] string, then convert
the ';' in the fname[] into an appropriate UNIX symbol, e.g. '.',,
so it will come out as "fname.c.34" , for example.

        Incidentally, .EXE programs from vms can be moved around on a tar tape,
and when re-extracted with the Tar reader, they run on vms again. (Note that
the file attributes are wrong, but the binary is correct...).

        The present program has trouble with indexed files.They should be
copied as fixed-format files.

Sid Penstone,
VLSI Group,
Dept. of Electrical Engineering,
Queen's University,
Kingston, Ontario, Canada K7L 3N6

 Usage:
-------------------------------------------------------------------------

    -Define the logical variable "TAPE" to be the desired tape
     unit, or the output file.

    -Mount the tape /FOREIGN/BLOCK=10240/RECORD=512
     Existing data on the tape will be written over, unless the
     tape is moved to an end-of-file with the SET MAGTAPE/ commands.

    -Set the default directory to the top of the tree (if applicable).
     This gives pathnames on the tape that start at the current
     default. i.e. if your default is [MYDIRE.MAIN], the file
     [MYDIRE.MAIN]START.C will be copied as "start.c" ; the file
     [MYDIRE.MAIN.TESTS]RUNIT.C will be copied as "tests/runit.c" .
     Files that are not in the current default tree will be copied
     with a full pathname; e.g. "mydire/main/tests/runit.c"

   -Then run VMS2TAR: (either by RUN or as a command)
     if you use RUN, the operation is "(create, verbose) [...]*.*"
     As a command:
vms2tar [options] [file-spec,file-spec,,,]
     where the options are:
            "t" (listing of the files that will be copied (no copy done))
            "c" (create copies of the files on the output tape, starting
                        at the current position on the tape.)
            "v" (verbose: report activities on stdout)
            "w" (wait)  wait for 'y' or 'n' for action
            "d" (dot) Keep a trailing period in a file name
    -To use the options:
     the program must be executed using the foreign command
     feature of VMS by defining a command:
command :== $path:vms2tar      !where path= full location of vms2tar.exe

     - if no file-spec is given, the highest version number of all of
     the files in the current default directory and its subdirectories
     will be written out.

ERRORS:
    - "couldn't open Tar file":  likely forgot to MOUNT the tape
    - "error getting status" :  protection error

--------------------------------------------------------------------------
Important assumptions for this version:

    The defaults are (no options):
    -Extract the highest numbered version of all of the files in the
     current directory tree,reporting  as each file is created.
     Pathnames on the tape start below the current default directory; for
     example, if you are in [SID.MAIN], the files in [SID.MAIN.SUB]
     will be written as sub/filename .

    -Write to a tape, mounted foreign, with block size of 10240
     bytes, or a file of fixed record length. If a different block
     size is desired, change the  definition of BLKSIZE.

    -Close with the special (blank header) block written by UNIX.
     It will be followed by an end-of-file or end-of tape.

    -All files with record attributes are assumed to be text files
     and are converted into  UNIX stream files. This includes regular
     VMS text files, printer files, stream files, or Fortran output.

    -Files with fixed record length and no record attributes are
     copied in fixed block format. This includes .EXE files, and
     other specials, but not .OBJ files.

    -Does not copy files with variable-length, no record attributes;
     ex. .OBJ files. They are reported as not copied.

    -Does not copy savesets correctly.

 This version converts the directory names to UNIX format before creating
 the files. Some special cases:
 (1) Directories are written with a trailing "/".
 (2) Directories are created in  the order that they are encountered
        by the F$SEARCH() operation of VMS

--------------------------------------------------------------------------
Known Bugs:
    (1) Writes extra empty trailer blocks into a file as though it
     were a tape.Could be fixed by finding out if the output is a file.
    (2) The method used to remove the higher level directory spec.
     from the pathname may be a little flaky. Watch to see if any files
     end up in the wrong tree.
    (3) Can't change the default directory within the same run.
    (4) Cannot emulate the "r" or "u" functions of UNIX Tar. Separate
     files must be used.
    (5) There is a problem with indexed files (e.g .MAI files) that
        will be looked at shortly.
    (6) Files with no file extension will be written on the TAR file
        without a trailing "." ,unless the "d" option is set. This is
        to avoid having some files apparently vanish on the UNIX system
        that reads the tape, since "makefile" would otherwise be called
        "makefile.", etc. since VMS always includes the '.' in filenames.
    (7) There are similar problems with files that were copied from a
        UNIX Tar tape that had extra "." or "," in the file name; these will
        have been translated to "_" if my Tar-to-VMS reader was used, and
        will be written out with the '_', so the name will not be the
        original UNIX name.

/********************************* CUT HERE ***********************/
/* Write a TAR format tape or file , from files in VMS directories */
/* Copyright 1986,Sid Penstone,
*  Department of Electrical Engineering,
*  Queen's University,
*  Kingston, Ontario, Canada K7L3N6
* (613)-545-2925
* BITNET:   PENSTONE@QUCDNEE1
* Version 1.8, Dec.23,1986
*
* Modifications:
*       - used the C call stat() to get file information
*       - now propagates directory specs to subsequent file-specs
* 1.4   - starts pathnames at current default directory
* 1.5   - corrected error in scan_name for  directories outside tree
*       - did immediate close of file-descriptor version in out_file
*         to get around overflow of file operations ???
* 1.6   - added cleanup_dire() to correctly handle rooted directory specs
* 1.6b  - corrected typing error in scan_name "-" should be "="
*       - looked past nodename in getting rid of device in scan_name()
*       - reject access to other nodes by trapping "::" in initsearch()
* 1.7   - set resultant string size to 0 in search(), because of problems
*         with rooted directories
* 1.8   - added "d" option, with default to suppress trailing "." in
*         output file names
*/

/* The input data is in VMS format,. The output will be
*  blocks of 10240 bytes, with 512 byte internal blocks of header
* and data. Unfilled 512 blocks will be packed with garbage.
* The output will be padded out to a full block of size BLKSIZE at
* the end of the Tar file.
 */

#include stdio
#include time
#include ssdef
#include descrip
#include ctype
#include rms
#include stsdef
#include file
#include stat
#include types

#define ISDIRE 1
#define ISFILE 0
#define FIXED           FAB$C_FIX
#define NAMSIZE 100
#define BLKSIZE 10240           /* Block size on tape*/
#define DSIZE 512               /* data block */

static struct tarhdr                    /* A Tar header */
    {
    char title[NAMSIZE];
    char protection[8];
    char uid[8];
    char gid[8];
    char count[12];
    char time[12];
    char chksum[8];
    char linkmode;      /* hope this is right!! */
    char linkname[NAMSIZE];     /* Space for the name of the link */
    char dummy[255];    /* and the rest */
    } header;

struct tarhdr *empty;                   /* buffer of nulls  */
char *dbuffer;                          /* for buffering output data  */

static int bufferpointer;               /* Position of next byte in the block */
static struct FAB fblock,fblock2;       /* File attribute block */
static struct NAM nblock,nblock2;       /* Name attribute block for rms */
struct stat *sblock;                    /*  for stat() call  */

/* Function flags, options:*/
int create,                     /* c operation (default) */
    list,                       /* t operation */
    verbose,            /* v option, report actions */
    wait,
    dot;                /* Include trailing dots  */

/* Miscellaneous globals, etc. */

char temp[NAMSIZE];     /* scratch  */

char *strindex();       /* String search function */

char *tarfile = "tape" ,        /* Output file */
    operation = ' ',    /* Current operation c,t  */
    pathname[NAMSIZE],  /* File name as written on tape (UNIX) */
    directory[NAMSIZE], /* The current root directory */
    new_directory[NAMSIZE],     /* Directory of current file */
    newfile[NAMSIZE],   /* VMS format of file name */
    outfile[NAMSIZE],   /* stripped format of file name */
    filetime[32],               /* Modification date as from vms */
    searchname[NAMSIZE];        /* for the NAM block  */

/* Global file characteristics */
FILE *vmsfile;
int vmsfd, outfd, temfd;
unsigned vmsmrs, vmstime;               /* maximum record size */
int vmsrat,vmsorg,vmsrfm; /* Other format (as integers) */

int bytecount,  mode, uid , gid , links;/* Data in the header */
default_name = "*.*";   /* Only get the most recent version */
int i;

/* ******************** start here *************************** */
main(argc,argv)
int argc;
char *argv[];
{
int status,file_type,j,c,len;
char  *cp, *indx, names[NAMSIZE]= "", string[NAMSIZE]= "",
        ans[10];
FILE *temfile;

    initialize();

/* Decode the options and parameters: */
    if(argc ==1)
        {
        create = 1;             /* Default for now */
        operation = 'c';
        list = 0;
        verbose = 1;
        wait = 0;               /* Don't wait for prompt */
        dot = 0;
        }
    if(--argc > 0)
        {
        cp = argv[1];
        while(c = *cp++)
            {
            switch(c)
                {
                case 'c':               /*  c and t exclusive  */
                    create = 1;
                    operation = 'c';
                    list = 0;
                    break;
                case 't':
                    create = 0;
                    list = 1;
                    operation = 't';
                    verbose = 1;        /* t means report  */
                    break;
                case 'v':
                    verbose=1;
                    break;
                case 'w':
                    wait=1;
                    break;
                case 'd':
                    dot=1;
                    break;
                case '-':
                    break;
                default:
                    printf("Option '%c' not recognized.\n",c);
                }
            }
        }

/* Use the parse operation to fill in missing specifications, and
* set up for repeated searching. The multiple file search described
* in the RMS manual does not seem to work, so we will have to sort out
* the items separated by commas.
 */
    if(--argc > 0)
        strcpy(names,argv[2]);          /* Assume file name is next */
    else
        {
        strcpy(names,"[...]*.*");
/******** COMMENTED OUT.. otherwise asks the user for file names
    printf(" File name(s)?");
    gets(names);
************ */
        }
    if(create)  /* Open the output and scratch files  */
        {
        outfd = creat(tarfile,0600,"rfm=fix","mrs=512");
        temfile = tmpfile();
        temfd = fileno(temfile);
        if(outfd < 0 || temfd < 0 )
            {
            printf(
            "Couldn't open output tar or scratch file. Bye,bye.\n");
            exit(2);
            }
        }
    /* Now we will extract search strings from the names string. Assume
    * they are separated by commas.
    */
    while(strlen(names)!=0)     /* Get the file name argument */
        {
        if(indx=strchr(names,','))
                *indx = 0;      /* Replace , by terminator  */
        if(strchr(names,']'))   /* Always include a directory spec */
            strcpy(string,names);       /* Get the new part  */
        else                            /* If no directory, keep the old one */
            if((cp = strchr(string,']')))
                strcpy(cp+1,names);
            else
                strcpy(string,names);
        if(initsearch(string) <= 0)
            printf("***** No files found while searching for:%s\n",string);
        else
        while(search(newfile,100)!=0)
            {
            chgcase(newfile);   /* all lower case, please */
            cleanup_dire(newfile);
            file_type = scan_name(newfile,new_directory,outfile);
            strcpy(pathname,new_directory);
            strcat(pathname,outfile);
            get_attributes(newfile);
            if(wait)
                {
                strcpy(temp,ctime(&vmstime));
                if((cp=strchr(temp,'\n'))!= NULL)
                     *cp = 0;           /* Avoid newline  */
                printf("%c: %-40s %-18s [y/n] ?",
                       operation,newfile,temp+4);
                scanf("%s",ans);
                    if ( ans[0] != 'y')
                        continue;
                }
            if(create)
                {
                if(file_type == ISDIRE)
                    {
                    bytecount =  0;
                    mode = 0755;
                    fill_header(pathname);
                    write_header(outfd);
                    }
                if(file_type == ISFILE)
                    {
                    mode = 0644;
                    if(addfile(newfile, pathname) < 0)
                        printf("ERROR: %s NOT COPIED\n",newfile);
                    }
                }
            if(verbose || list)
                {
                if(bytecount || file_type == ISDIRE)
                    printf("%c: %-40s %6d  %26s",
                       operation,pathname,bytecount,ctime(&vmstime));
                else
                   printf("IGNORED:  %s\n",pathname);
                }
            }
        if(indx)                        /* any more in the string ? */
            strcpy(names,indx+1);       /* Yes, work on it  */
        else
            break;
        }
    if(create)
        {
        write_trailer(outfd);
        close(outfd);
        }
    exit(SS$_NORMAL);
}

/* Copy the vms file to the output file.
*
*/

int addfile(vmsname,unixname)
char vmsname[],unixname[];
{
int ind;
    if(bytecount == 0)          /* We don't output null files  */
        return(0);
    if((ind=out_file(vmsname,bytecount,outfd)) < 0)
        return(ind);
    bufferpointer = bufferpointer%BLKSIZE;
    return(1);
}

/* Write out the file.
* move nbytes of data from "fdin" to "fdout";
* Always pad the output to a full DSIZE
* If it a vms text file, it may be various formats, so we will
* write into a temporary file first, then copy to the output
* so that we get the correct byte count.
* We set the bytecount=0 if this is funny file.
*/
int out_file(filename,nbytes,fdout)
char filename[];
int fdout, nbytes;
{
int i, n, pos, fdin;
FILE *filein;
    if(vmsrfm == FIXED)
        {
        if((fdin=open(filename,0)) <=0)
                {
                printf("Error opening input file %s\n",filename);
                return(-1);
                }
        fill_header(pathname);          /* We have all of the information */
        write_header(outfd);            /* So write to the output */
        while(nbytes > 0)
            {
            n = read(fdin,dbuffer,nbytes>DSIZE? DSIZE:nbytes);
            if(n<0)
                {
                close(fdin);
                printf("Read error on input file\n");
                return(-1);
                }
            nbytes -= n;
            write(fdout,dbuffer,DSIZE);
            bufferpointer += DSIZE;             /* Count the position */
            }
        close(fdin);
        return(0);
        }
    else if(vmsrat != 0)                /* must be a text file  */
        {                               /* Write out to standard stream */
        if((filein = fopen(filename,"r")) == NULL)
                {
                printf("ERROR OPENING %s\n",filename);
                return(-1);
                }
        nbytes = 0;
        lseek(temfd,0,0);               /* Back to the beginning  */
        while((i = fgets(dbuffer,DSIZE,filein)) !=  NULL)
            {
            n = strlen(dbuffer);
            nbytes += n;
            write(temfd,dbuffer,n);
            }
        fclose(filein);                 /* All done with the input */
        lseek(temfd,0,0);               /* Back to the beginning  */
        bytecount = nbytes;             /* Use the real count  */
        fill_header(pathname);          /* Compute the header */
        write_header(outfd);            /* Write it  */
        while(nbytes > 0)               /* Now copy to the output */
            {
            n = read(temfd,dbuffer,nbytes>DSIZE? DSIZE:nbytes);
            nbytes -= n;
            write(fdout,dbuffer,DSIZE);
            bufferpointer += DSIZE;             /* Count the position */
            }
        return(0);
        }
                        /* Other formats e.g. .OBJ are not done  */
        bytecount = 0;
        return(0);
}

/* Copy the header to the output file  */
int write_header(fd)
int fd;
{
int n;
    if((n=write(fd,&header,DSIZE))!=DSIZE)
        {
        printf("Error writing header in output file.\n");
        exit(1);
        }
    bufferpointer += DSIZE;
    return(n);
}

/*  get the file attributes via stat()  */

int get_attributes(fname)
char fname[];
{
    if(stat(fname,sblock))
    {
        printf("Error getting file status:%s\n",fname);
        vmstime = 0;                    /* Prevent garbage printoout */
        bytecount = 0;                  /* of inaccessible files  */
        return(-1);
    }
/* now get the file attributes, we don't use them all */
    bytecount = sblock->st_size;
    vmsrat = sblock->st_fab_rat;
    vmsmrs = sblock->st_fab_mrs;
    vmsrfm = sblock->st_fab_rfm;
    vmstime = sblock->st_mtime;
}

/* Write the two blank blocks on the output file, and pad the output
* to a full blocksize if needed. */
write_trailer(fdout)
int fdout;
{
int rem;
    header = *empty;
    write_header(fdout);
    write_header(fdout);
    bufferpointer = bufferpointer%BLKSIZE;
    while (bufferpointer < BLKSIZE)
        write_header(fdout);
    return(1);
}

/* Decode a file name into the directory, and the name, and convert
* to a valid UNIX pathname. 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.
*/

int scan_name(line,dire,fname)
char line[],dire[],fname[];
{
char temp[NAMSIZE],*end1,*end2;
int len,len2,i;
/* The format will be VMS at input, so we have to scan for the
* VMS device separator ':', and also the VMS directory separators
*  '[' and ']'.
* If the name ends with '.dir;1' then it is actually a directory name.
* The outputs dire and  fname will be a complete file spec, based on the default
* directory.
* It may be a rooted directory, in which case there will be a "][" string
* remove it..
* Strip out colons from the right, in case there is a node name (should not be!)
* If the filename ends in a triling '.', suppressit , unless the "d" option
* is set
*/

    strcpy(temp,strrchr(line,':')+1);           /* Start with the whole name */
/* Get rid of default directory part of the name  */
    for(end1=temp,end2=strrchr(directory,':')+1;*end2 && (*end1 == *end2);
         end1++,end2++);
    if(*end2 == 0)
        *end1 = 0;              /* Perfect match, no directory spec  */
    else
        {
        switch(*end1)
        {
        case '.':
        case '[':               /* Legal beginnings or ends  */
            break;
        case ']':               /* We are above the default, use full name */
            end1 = strchr(temp,'[');    /* Fixed this from 1.5  */
            break;
        default:                /* Something strange, back up  */
            while(*end1 != '.' && *end1 != '[' && end1 >= temp)
                end1--;
            break;
        }
        end1++;                 /* Get past the starting . or [ */
    }
    strcpy(dire,end1);
    if(strlen(dire))
        strcpy(strchr(dire,']'),"/");   /* get rid of the directory marks  */
    strcpy(temp,strchr(line,']')+1); /* Now get the file name */
    if((end1=strindex(temp,".dir;1"))!=0)
        {
        strcpy(end1,"/");                       /* Terminate directory name */
        strcat(dire,temp);
        strcpy(fname,"");
        }
    else
        {
        strcpy(fname,temp);
        strcpy(strchr(fname,';'),"");           /* no version numbers */
        }
    /* Now rewrite the directory name  */
    for (i=1;dire[i];i++)       /* Change '.' to '/'  */
        if(dire[i]=='.')
            dire[i]='/';
    if((len=strlen(fname))==0)
        {
        return(ISDIRE);
        }
    else
        if(fname[--len] == '.')
           if (dot == 0)
                fname[len] = 0; /* No trailing dots  */
        return(ISFILE);
}

/* To start looking for file names to satisfy the requested input,
* use the sys$parse routine to create a wild-card name block. When
* it returns, we can then use the resultant FAB and NAM blocks on
* successive calls to sys$search() until there are no more files
* that match
*/

int initsearch(string)
char string[];
{
int status;

    if(strindex(string,"::")!=NULL)
        {
        printf("***** Access across nodes is not supported.\n");
        return(-1);
        }
    fblock = cc$rms_fab;
    nblock = cc$rms_nam;
    fblock.fab$l_dna = default_name;
    fblock.fab$b_dns = strlen(default_name);
    fblock.fab$l_fna = string;
    fblock.fab$b_fns = strlen(string);
    fblock.fab$l_nam = &nblock;
    nblock.nam$l_esa = searchname;
    nblock.nam$b_ess = sizeof(searchname);
#ifdef debug
    printf("searching on: %s\n",string);
#endif
    status = sys$parse(&fblock);
    if(status != RMS$_NORMAL)
        {
        if(status == RMS$_DNF)
            printf("Directory not found:%s\n",searchname);
        else
            printf("Error in sys$parse()\n");
        return (-1);
        }
    searchname[nblock.nam$b_esl] = 0;   /* Terminate the string  */
    /* Now reset for searching, pointing to the parsed name block */
    fblock = cc$rms_fab;
    fblock.fab$l_nam = &nblock;
    return(nblock.nam$b_esl); /* return the length of the string  */
}

/* Get the next file name that matches the namblock that was set
* up by the sys$search() function.
*/
int search(buff,maxlen)
char buff[];
int maxlen;
{
int status;

    nblock.nam$l_rsa = buff;
    nblock.nam$b_rss = maxlen;
    nblock.nam$b_rsl = 0;       /* For next time around  */
    while( (status = sys$search(&fblock)) != RMS$_NMF)
    {
        buff[nblock.nam$b_rsl] = 0;
        if(status == RMS$_NORMAL)
            {
            return(nblock.nam$b_rsl);
            }
        else
            {
            if( status == RMS$_PRV)
                printf("%s : No privilege for access.\n",buff);
            else if (status == RMS$_FNF)
                printf("%s : File not found.\n",buff);
            else
                {
                printf(" Error in f$search for :%s\n", buff);
                return (0);
                }
            }
    }
    return (0);
}
/* 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. */

int fill_header(name)
char name[];
{
int i,chksum;
char *ptr,tem[15];
    header = *empty;            /* Clear the header  */
    if(strlen(name)!=0)         /* only fill if there is a file */
        {
        sprintf(header.title,"%s",name);        /* write file name */
        sprintf(header.protection,"%6o ",mode); /* all written with */
        sprintf(header.uid,"%6o ",uid);         /* a trailing space */
        sprintf(header.gid,"%6o ",gid);
        sprintf(tem,"%11o ",bytecount);         /* except the count */
        strncpy(header.count,tem,12);           /* and the time, which */
        sprintf(tem,"%11o ",vmstime);           /* except the count */
        strncpy(header.time,tem,12);            /* have no null */
        strncpy(header.chksum,"        ",8);    /* all blanks for sum*/
/* I know that the next two are already zero, but do them */
        header.linkmode = 0;                    /* always zero */
        sprintf(header.linkname,"%s","");       /* always blank */
        for(chksum=0, ptr = &header;ptr < &header.linkmode;ptr++)
                 chksum += *ptr;                /* make the checksum */
        sprintf(header.chksum,"%6o",chksum);    /* This is how it looks */
        }                                       /* on UNIX tapes... */
    return(0);
}

/* Initialize various fields, get some standard values */

initialize()
{
int i;
    dbuffer = malloc(DSIZE+1);
    sblock = malloc(sizeof(struct stat));
    empty = malloc(DSIZE);
    for (i=0;i < DSIZE;i++)
        empty->title[i] = 0;    /* all zeroes, even if malloc() did it  */
    header = *empty;
    bufferpointer = 0;
    gid = getgid();     /* for now, use the user's uic */
    uid = getuid(); /* and group uic */
    mode = 0644;                /* This get changed elsewhere, anyhow */
    strcpy(directory,getenv("PATH"));   /* Default directory */
    return;
}

/* Search for string2 in string1; return address pointer. */
char *strindex(string1,string2)
char *string1,*string2;
{
char *c1, *c2, *cp;
    for(c1 = string1; *c1 !=0; c1++)
        {
        cp = c1;        /* save the start address */
        for(c2=string2; *c2 !=0 && *c1 == *c2; c1++,c2++);
           if(*c2 == 0)
                return(cp);
        }
    return(NULL);
}

/* function to change a string to lower case */
int chgcase(string)
char string[];
{
int i;
        for(i=0;string[i]=tolower(string[i]);i++);
        return (--i);           /* return string length */
}

/* Routine to get rid of rooted directory problems,
* and any others that turn up
*/

int cleanup_dire(string)
char string[];
{
char *ptr, temp[NAMSIZE];
        if((ptr=strindex(string,"][")) == NULL)
                return(0);
                /* Just collapse around the string */
        strcpy(ptr,ptr+2);
        return(1);
}