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