1 /* HETGET.C     (c) Copyright Leland Lucius, 2000-2009               */
2 /*              Extract files from an HET file                       */
3 
4 /*
5 || ----------------------------------------------------------------------------
6 ||
7 || HETGET.C     (c) Copyright Leland Lucius, 2000-2009
8 ||              Released under terms of the Q Public License.
9 ||
10 || Extract files from an HET file
11 ||
12 || ----------------------------------------------------------------------------
13 */
14 
15 #include "hstdinc.h"
16 
17 #include "hercules.h"
18 #include "hetlib.h"
19 #include "sllib.h"
20 #include "herc_getopt.h"
21 
22 /*
23 || Local volatile data
24 */
25 #define O_NL        0x80
26 #define O_ASCII     0x40
27 #define O_STRIP     0x20
28 #define O_UNBLOCK   0x10
29 struct
30 {
31     char *ifile;
32     char *ofile;
33     int fileno;
34     int lrecl;
35     int blksize;
36     unsigned char flags;
37     unsigned char recfm;
38 }
39 opts =
40 {
41     NULL,
42     NULL,
43     0,
44     0,
45     0,
46     0,
47     0,
48 };
49 
50 /*
51 || Local constant data
52 */
53 static const char help[] =
54     "%s - Extract files from an HET file\n\n"
55     "Usage: %s [options] hetfile outfile fileno [recfm lrecl blksize]\n\n"
56     "Options:\n"
57     "  -a  convert to ASCII (implies -u)\n"
58     "  -h  display usage summary\n"
59     "  -n  file is an NL (or BLP like) tape\n"
60     "  -u  unblock (removes BDWs and RDWs if RECFM=V)\n"
61     "  -s  strip trailing blanks (requires -a)\n";
62 
63 /*
64 || Valid record formats
65 */
66 #define O_UNDEFINED 0x80
67 #define O_FIXED     0x40
68 #define O_VARIABLE  0x20
69 #define O_BLOCKED   0x08
70 #define O_SPANNED   0x04
71 
72 static const struct
73 {
74     char *recfm;
75     int fmt;
76 }
77 valfm[] =
78 {
79     { "U",      O_UNDEFINED | 0         | 0         },
80     { "UA",     O_UNDEFINED | 0         | 0         },
81     { "UM",     O_UNDEFINED | 0         | 0         },
82     { "F",      O_FIXED     | 0         | 0         },
83     { "FA",     O_FIXED     | 0         | 0         },
84     { "FM",     O_FIXED     | 0         | 0         },
85     { "FB",     O_FIXED     | O_BLOCKED | 0         },
86     { "FBA",    O_FIXED     | O_BLOCKED | 0         },
87     { "FBM",    O_FIXED     | O_BLOCKED | 0         },
88     { "FS",     O_FIXED     | O_BLOCKED | 0         },
89     { "FSA",    O_FIXED     | O_BLOCKED | 0         },
90     { "FSM",    O_FIXED     | O_BLOCKED | 0         },
91     { "FBS",    O_FIXED     | O_BLOCKED | 0         },
92     { "FBSA",   O_FIXED     | O_BLOCKED | 0         },
93     { "FBSM",   O_FIXED     | O_BLOCKED | 0         },
94     { "V",      O_VARIABLE  | 0         | 0         },
95     { "VA",     O_VARIABLE  | 0         | 0         },
96     { "VM",     O_VARIABLE  | 0         | 0         },
97     { "VB",     O_VARIABLE  | O_BLOCKED | 0         },
98     { "VBA",    O_VARIABLE  | O_BLOCKED | 0         },
99     { "VBM",    O_VARIABLE  | O_BLOCKED | 0         },
100     { "VS",     O_VARIABLE  | 0         | O_SPANNED },
101     { "VSA",    O_VARIABLE  | 0         | O_SPANNED },
102     { "VSM",    O_VARIABLE  | 0         | O_SPANNED },
103     { "VBS",    O_VARIABLE  | O_BLOCKED | O_SPANNED },
104     { "VBSA",   O_VARIABLE  | O_BLOCKED | O_SPANNED },
105     { "VBSM",   O_VARIABLE  | O_BLOCKED | O_SPANNED },
106 };
107 #define VALFMCNT ( sizeof( valfm ) / sizeof( valfm[ 0 ] ) )
108 
109 /*
110 || Block and record management
111 */
112 unsigned char *blkptr = NULL;
113 int blkidx = 0;
114 int blklen = 0;
115 unsigned char *recptr = NULL;
116 int recidx = 0;
117 int reclen = 0;
118 
119 #ifdef EXTERNALGUI
120 /*
121 || Previously reported file position
122 */
123 static off_t prevpos = 0;
124 /*
125 || Report progress every this many bytes
126 */
127 #define PROGRESS_MASK (~0x3FFFF /* 256K */)
128 #endif /*EXTERNALGUI*/
129 
130 /*
131 || Merge DCB information from HDR2 label
132 */
133 void
merge(SLLABEL * lab)134 merge( SLLABEL *lab )
135 {
136     SLFMT fmt;
137     int i;
138 
139     /*
140     || Make the label more managable
141     */
142     sl_fmtlab( &fmt, lab );
143 
144     /*
145     || Merge the record format;
146     */
147     if( opts.recfm == 0 )
148     {
149         opts.recfm = O_UNDEFINED;
150         for( i = 0 ; i < (int)VALFMCNT ; i++ )
151         {
152             if( strcasecmp( fmt.slds2.recfm, valfm[ i ].recfm ) == 0 )
153             {
154                 opts.recfm = valfm[ i ].fmt;
155                 break;
156             }
157         }
158     }
159 
160     /*
161     || Merge in the record length
162     */
163     if( opts.lrecl == 0 )
164     {
165         opts.lrecl = atoi( fmt.slds2.lrecl );
166     }
167 
168     /*
169     || Merge in the block size
170     */
171     if( opts.blksize == 0 )
172     {
173         /*
174         || Try the blksize field first
175         */
176         opts.blksize = atoi( fmt.slds2.blksize );
177         if( opts.blksize == 0 )
178         {
179             /*
180             || Still zero, so try the lblkln field
181             */
182             opts.blksize = atoi( fmt.slds2.lblkln );
183         }
184     }
185 
186     /*
187     || Locate final RECFM string
188     */
189     for( i = 0 ; i < (int)VALFMCNT ; i++ )
190     {
191         if( strcasecmp( fmt.slds2.recfm, valfm[ i ].recfm ) == 0 )
192         {
193             break;
194         }
195     }
196 
197     /*
198     || Print DCB attributes
199     */
200     printf( "DCB Attributes used:\n" );
201     printf( "  RECFM=%-4.4s  LRECL=%-5.5d  BLKSIZE=%d\n",
202         valfm[ i ].recfm,
203         opts.lrecl,
204         opts.blksize );
205 
206     return;
207 }
208 
209 /*
210 || Return block length from BDW
211 */
212 int
bdw_length(const unsigned char * ptr)213 bdw_length( const unsigned char *ptr  )
214 {
215     unsigned int len;
216 
217     /*
218     || Extended format BDW?
219     */
220     if( ptr[ 0 ] & 0x80 )
221     {
222         /*
223         || Length is 31 bits
224         */
225         len  = ptr[ 0 ] << 24;
226         len += ptr[ 1 ] << 16;
227         len += ptr[ 2 ] << 8;
228         len += ptr[ 3 ];
229         len &= 0x7fffffff;
230     }
231     else
232     {
233         /*
234         || Length is 15 bits
235         */
236         len  = ptr[ 0 ] << 8;
237         len += ptr[ 1 ];
238     }
239 
240     return( len );
241 }
242 
243 /*
244 || Return record length from RDW
245 */
246 int
rdw_length(const unsigned char * ptr)247 rdw_length( const unsigned char *ptr )
248 {
249     unsigned int len;
250 
251     /*
252     || Get the record length
253     */
254     len  = ptr[ 0 ] << 8;
255     len += ptr[ 1 ];
256 
257     return( len );
258 }
259 
260 /*
261 || Retrieves a block from the tape file and resets variables
262 */
263 int
getblock(HETB * hetb)264 getblock( HETB *hetb )
265 {
266     int rc;
267 
268     /*
269     || Read a block from the tape
270     */
271     rc = het_read( hetb, blkptr );
272     if( rc < 0 )
273     {
274         return( rc );
275     }
276 
277     /*
278     || Save the block length (should we use BDW for RECFM=V files???)
279     */
280     blklen = rc;
281 
282     return( rc );
283 }
284 
285 /*
286 || Retrieve logical records from the tape - doesn't handle SPANNED records
287 */
288 int
getrecord(HETB * hetb)289 getrecord( HETB *hetb )
290 {
291     int rc;
292 
293     /*
294     || Won't be null if we've been here before
295     */
296     if( recptr != NULL )
297     {
298         recidx += reclen;
299     }
300 
301     /*
302     || Need a new block first time through or we've exhausted current block
303     */
304     if( ( recptr == NULL ) || ( recidx >= blklen ) )
305     {
306         /*
307         || Go get another block
308         */
309         rc = getblock( hetb );
310         if( rc < 0 )
311         {
312             return( rc );
313         }
314 
315         /*
316         || For RECFM=V, bump index past BDW
317         */
318         recidx = 0;
319         if( opts.recfm & O_VARIABLE )
320         {
321             /* protect against a corrupt (short) block */
322             if ( rc < 8 )
323             {
324                 return ( -1 );
325             }
326             recidx = 4;
327         }
328     }
329 
330     /*
331     || Set the new record pointer
332     */
333     recptr = &blkptr[ recidx ];
334 
335     /*
336     || Set the record length depending on record type
337     */
338     if( opts.recfm & O_FIXED )
339     {
340         reclen = opts.lrecl;
341     }
342     else if( opts.recfm & O_VARIABLE )
343     {
344         reclen = rdw_length( recptr );
345 
346         /* protect against corrupt (short) block */
347         if ( reclen + recidx > blklen )
348         {
349             return (-1);
350         }
351         /* protect against a corrupt (less than 4) RDW */
352         if ( reclen < 4 )
353         {
354             return (-1);
355         }
356     }
357     else
358     {
359         reclen = blklen;
360     }
361 
362     return( reclen );
363 }
364 
365 /*
366 || Retrieve and validate a standard label
367 */
368 int
get_sl(HETB * hetb,SLLABEL * lab)369 get_sl( HETB *hetb, SLLABEL *lab )
370 {
371     int rc;
372 
373     /*
374     || Read a block
375     */
376     rc = het_read( hetb, blkptr );
377     if( rc >= 0 )
378     {
379         /*
380         || Does is look like a standard label?
381         */
382         if( sl_islabel( lab, blkptr, rc ) == TRUE )
383         {
384             return( 0 );
385         }
386     }
387     else
388     {
389         printf( "%s while reading block\n", het_error( rc ) );
390     }
391 
392     return( -1 );
393 }
394 
395 /*
396 || Extract the file from the tape
397 */
398 int
getfile(HETB * hetb,FILE * outf)399 getfile( HETB *hetb, FILE *outf )
400 {
401     SLFMT fmt;
402     SLLABEL lab;
403     unsigned char *ptr;
404     int fileno;
405     int rc;
406 
407     /*
408     || Skip to the desired file
409     */
410     if( opts.flags & O_NL )
411     {
412         /*
413         || For NL tapes, just use the specified file number
414         */
415         fileno = opts.fileno;
416 
417         /*
418         || Start skipping
419         */
420         while( --fileno )
421         {
422             /*
423             || Forward space to beginning of next file
424             */
425             rc = het_fsf( hetb );
426             if( rc < 0 )
427             {
428                 printf( "%s while positioning to file #%d\n",
429                     het_error( rc ),
430                     opts.fileno );
431                 return( rc );
432             }
433         }
434     }
435     else
436     {
437         /*
438         || First block should be a VOL1 record
439         */
440         rc = get_sl( hetb, &lab );
441         if( rc < 0 || !sl_isvol( &lab, 1 ) )
442         {
443             printf( "Expected VOL1 label\n" );
444             return( -1 );
445         }
446 
447         /*
448         || For SL, adjust the file # so we end up on the label before the data
449         */
450         fileno = ( opts.fileno * 3 ) - 2;
451 
452         /*
453         || Start skipping
454         */
455         while( --fileno )
456         {
457             /*
458             || Forward space to beginning of next file
459             */
460             rc = het_fsf( hetb );
461             if( rc < 0 )
462             {
463                 printf( "%s while positioning to file #%d\n",
464                     het_error( rc ),
465                     opts.fileno );
466                 return( rc );
467             }
468         }
469 
470         /*
471         || Get the HDR1 label.
472         */
473         rc = get_sl( hetb, &lab );
474         if( rc < 0 || !sl_ishdr( &lab, 1 ) )
475         {
476             printf( "Expected HDR1 label\n" );
477             return( -1 );
478         }
479 
480         /*
481         || Make the label more managable
482         */
483         sl_fmtlab( &fmt, &lab );
484         printf("File Info:\n  DSN=%-17.17s\n", fmt.slds1.dsid );
485 
486         /*
487         || Get the HDR2 label.
488         */
489         rc = get_sl( hetb, &lab );
490         if( rc < 0 || !sl_ishdr( &lab, 2 ) )
491         {
492             printf( "Expected HDR2 label\n" );
493             return( -1 );
494         }
495 
496         /*
497         || Merge the DCB information
498         */
499         merge( &lab );
500 
501         /*
502         || Hop over the tapemark
503         */
504         rc = het_fsf( hetb );
505         if( rc < 0 )
506         {
507             printf( "%s while spacing to start of data\n",
508                 het_error( rc ) );
509             return( rc );
510         }
511     }
512 
513     /*
514     || Different processing when converting to ASCII
515     */
516     if( opts.flags & ( O_ASCII | O_UNBLOCK ) )
517     {
518         /*
519         || Get a record
520         */
521         while( ( rc = getrecord( hetb ) ) >= 0 )
522         {
523 #ifdef EXTERNALGUI
524             if( extgui )
525             {
526                 /* Report progress every nnnK */
527                 off_t curpos = ftell( hetb->fd );
528                 if( ( curpos & PROGRESS_MASK ) != ( prevpos & PROGRESS_MASK ) )
529                 {
530                     prevpos = curpos;
531                     fprintf( stderr, "IPOS=%" I64_FMT "d\n", (U64)curpos );
532                 }
533             }
534 #endif /*EXTERNALGUI*/
535             /*
536             || Get working copy of record ptr
537             */
538             ptr = recptr;
539 
540             /*
541             || Only want data portion for RECFM=V records
542             */
543             if( opts.recfm & O_VARIABLE )
544             {
545                 ptr += 4;
546                 rc -= 4;
547             }
548 
549             /*
550             || Convert record to ASCII
551             */
552             if( opts.flags & O_ASCII )
553             {
554                 sl_etoa( NULL, ptr, rc );
555             }
556 
557             /*
558             || Strip trailing blanks
559             */
560             if( opts.flags & O_STRIP )
561             {
562                 while( rc > 0 && ptr[ rc - 1 ] == ' ' )
563                 {
564                     rc--;
565                 }
566             }
567 
568             /*
569             || Write the record out
570             */
571             fwrite( ptr, rc, 1, outf );
572 
573             /*
574             || Put out a linefeed when converting
575             */
576             if( opts.flags & O_ASCII )
577             {
578                 fwrite( "\n", 1, 1, outf );
579             }
580         }
581     }
582     else
583     {
584         /*
585         || Get a record
586         */
587         while( ( rc = getblock( hetb ) ) >= 0 )
588         {
589 #ifdef EXTERNALGUI
590             if( extgui )
591             {
592                 /* Report progress every nnnK */
593                 off_t curpos = ftell( hetb->fd );
594                 if( ( curpos & PROGRESS_MASK ) != ( prevpos & PROGRESS_MASK ) )
595                 {
596                     prevpos = curpos;
597                     fprintf( stderr, "IPOS=%" I64_FMT "d\n", (U64)curpos );
598                 }
599             }
600 #endif /*EXTERNALGUI*/
601             /*
602             || Write the record out
603             */
604             fwrite( blkptr, blklen, 1, outf );
605         }
606     }
607 
608     return( rc );
609 }
610 
611 /*
612 || Prints usage information
613 */
614 void
usage(char * name)615 usage( char *name )
616 {
617     printf( help, name, name );
618 }
619 
620 /*
621 || Standard main
622 */
623 int
main(int argc,char * argv[])624 main( int argc, char *argv[] )
625 {
626     HETB *hetb;
627     FILE *outf;
628     int rc;
629     int i;
630 
631     INITIALIZE_UTILITY("hetget");
632 
633     /* Display the program identification message */
634     display_version (stderr, "Hercules HET extract files program ", FALSE);
635 
636     /*
637     || Process option switches
638     */
639     while( TRUE )
640     {
641         rc = getopt( argc, argv, "abhnsu" );
642         if( rc == -1 )
643         {
644             break;
645         }
646 
647         switch( rc )
648         {
649             case 'a':
650                 opts.flags |= O_ASCII;
651             break;
652 
653             case 'h':
654                 usage( argv[ 0 ] );
655                 exit( 1 );
656             break;
657 
658             case 'n':
659                 opts.flags |= O_NL;
660             break;
661 
662             case 's':
663                 opts.flags |= O_STRIP;
664             break;
665 
666             case 'u':
667                 opts.flags |= O_UNBLOCK;
668             break;
669 
670             default:
671                 usage( argv[ 0 ] );
672                 exit( 1 );
673             break;
674         }
675     }
676 
677     /*
678     || Calc number of non-switch arguments
679     */
680     argc -= optind;
681 
682     /*
683     || We must have at least the first 3 parms
684     */
685     if( argc < 3 )
686     {
687         printf( "Must specify input tape, output file, and file #\n" );
688         printf( "Use -h option for more help\n" );
689         exit( 1 );
690     }
691 
692     opts.ifile = argv[ optind ];
693     opts.ofile = argv[ optind + 1 ];
694     opts.fileno = atoi( argv[ optind + 2 ] );
695     if( opts.fileno == 0 || opts.fileno > 9999 )
696     {
697         printf( "File number must be within 1-9999\n" );
698         exit( 1 );
699     }
700 
701     /*
702     || If NL tape, then we require the DCB attributes
703     */
704     if( opts.flags & O_NL )
705     {
706         if( argc != 6 )
707         {
708             printf( "DCB attributes required for NL tapes\n" );
709             exit( 1 );
710         }
711     }
712 
713     /*
714     || If specified, get the DCB attributes
715     */
716     if( argc > 3 )
717     {
718         /*
719         || Must have only three
720         */
721         if( argc != 6 )
722         {
723             usage( argv[ 0 ] );
724             exit( 1 );
725         }
726 
727         /*
728         || Lookup the specified RECFM in our table
729         */
730         opts.recfm = 0;
731         for( i = 0 ; i < (int)VALFMCNT ; i++ )
732         {
733             if( strcasecmp( argv[ optind + 3 ], valfm[ i ].recfm ) == 0 )
734             {
735                 opts.recfm = valfm[ i ].fmt;
736                 break;
737             }
738         }
739 
740         /*
741         || If we didn't find a match, show the user what the valid ones are
742         */
743         if( opts.recfm == 0)
744         {
745             /*
746             || Dump out the valid RECFMs
747             */
748             printf( "Valid record formats are:\n" );
749             for( i = 0 ; i < (int)VALFMCNT ; i++ )
750             {
751                 printf( "  %-4.4s", valfm[ i ].recfm );
752                 if( ( ( i + 1 ) % 3 ) == 0 )
753                 {
754                     printf( "\n" );
755                 }
756             }
757             exit( 1 );
758         }
759 
760         /*
761         || Get the record length
762         */
763         opts.lrecl = atoi( argv[ optind + 4 ] );
764 
765         /*
766         || Get and validate the blksize
767         */
768         opts.blksize = atoi( argv[ optind + 5 ] );
769         if( opts.blksize == 0 )
770         {
771             printf( "Block size can't be zero\n" );
772             exit( 1 );
773         }
774     }
775 
776     /*
777     || Open the tape file
778     */
779     rc = het_open( &hetb, opts.ifile, 0 );
780     if( rc >= 0 )
781     {
782         /*
783         || Get memory for the tape buffer
784         */
785         blkptr = malloc( HETMAX_BLOCKSIZE );
786         if( blkptr != NULL )
787         {
788             /*
789             || Open the output file
790             */
791             char pathname[MAX_PATH];
792             hostpath(pathname, opts.ofile, sizeof(pathname));
793             outf = fopen( pathname, "wb" );
794             if( outf != NULL )
795             {
796                 /*
797                 || Go extract the file from the tape
798                 */
799                 rc = getfile( hetb, outf );
800 
801                 /*
802                 || Close the output file
803                 */
804                 fclose( outf );
805             }
806 
807             /*
808             || Free the buffer memory
809             */
810             free( blkptr );
811         }
812     }
813     else
814     {
815         printf( "het_open() returned %s\n", het_error( rc ) );
816     }
817 
818     /*
819     || Close the tape file
820     */
821     het_close( &hetb );
822 
823     return 0;
824 }
825