/********************************* 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);
}
