/* Write a TAR format tape or file , from files in VMS directories */ /* May compile as a version with "./" prefix */ /* Copyright 1986,Sid Penstone, * Department of Electrical Engineering, * Queen's University, * Kingston, Ontario, Canada K7L3N6 * (613)-545-2925 * BITNET: PENSTONE@QUCDNEE1 * Version 1.11, May 25,1987 * * 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 * 1.9 - don't output file with negative bytecounts * 1.10 - simplified some code in scan_name() * - report creation time instead of modification time * - added conditionals for prefix (for some strange UNIX systems e.g. * Metheus) * 1.11 - Do not bother to rewrite stream_lf files to count the bytes */ /* 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 this only if necessary: */ /* #define useprefix */ #define ISDIRE 1 #define ISFILE 0 #define FIXED FAB$C_FIX #define STREAM FAB$C_STMLF #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 */ #ifdef useprefix int prefix; #endif /* 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; #ifdef useprefix prefix=0; #endif } 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; #ifdef useprefix case 'p': prefix=1; break; #endif 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 >0 || 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 || vmsrfm == STREAM) { 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_ctime; /* was 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 trailing '.', suppress it , unless the "d" option * is set * If the prefix bit is set ("p" option), then put the prefix "./" ahead * of all pathnames on the tape */ strcpy(temp,strrchr(line,':')+1); /* Start with the whole name */ strcpy(dire, ""); /* And an empty directory */ #ifdef useprefix if(prefix) strcpy(dire,"./"); #endif /* 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 { if(*end2 != ']' || *end1 != '.') /* Is this a subdirectory? */ end1 = strchr(temp,'['); /* no root, use the full name */ end1++; /* Get past the starting . or [ */ } strcat(dire,end1); if(strlen(dire) && (end2=strchr(dire,']'))!= NULL) strcpy(end2,"/"); /* get rid of the directory marks */ strcpy(temp,strchr(line,']')+1); /* Now get the file name */ if((end1=strindex(temp,".dir;1"))!=0) /* Is this a directory? */ { strcpy(end1,"/"); /* Terminate directory name */ strcat(dire,temp); /* Add it to the pathname */ strcpy(fname,""); /* No file name */ } else { strcpy(fname,temp); strcpy(strchr(fname,';'),""); /* no version numbers */ } /* Now rewrite the directory name, starting after the first character */ if(strlen(dire) != 0) 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); }