/******************************** Program "interp-tape-labels.c" ******************************** To interpret OS type tape labels, either directly from a tape, or from the contents of a disk file, as previously obtained from a tape. This program will automatically adapt to the data being either in EBCDIC (as with a 3480 cartridge created under VM, MVS, or VPS), or in ASCII, as in the case of labels captured in a disk file perhaps gotten via FTP and thus pre-translated to ASCII. INVOCATION: 'interp-tape-labels -f pathname' where: pathname would typically be the full path name of the tape drive, such as "/dev/rmt83"; or could be the name of a disk file containing an image of previously obtained labels. NOTES: - This program will detect labels contained in a disk file and will automatically "deblock" them for proper, individual interpretation. - From the read() man page: Note: When reading tapes, the read subroutines consume a physical tape block on each call to the subroutine. If the physical data block size is larger than specified by the Nbytes parameter, only bytes of the data block is put into the buffer. The next call to the read subroutines skips the extra data and moves to the next block of data, without informing the user about the skipped data. To avoid losing any data due to unknown blocking sizes on tapes, set the NByte parameter to a very large value (such as 32K). HISTORY: 1994/01/13 Written by Richard Sims 2000/07/23 Overhauled to greatly extend internal documentation and support the reporting of additional label types. RBS *************************************************************************************************/ #define _ALL_SOURCE /* For ANSI C compile. */ #include #include #include #include #include #include /*********************************** Global variables *******************************/ char *Pgm_Name; /* The name of this program, obtained by * main(), and made available to all functions * for message identification. */ /************************ ebctoasc(string) *************************** Convert a character string from EBCDIC to ASCII. Returns the same pointer value that it received, for the convenience of functional usage. (It might be desirable to translate the input into a different area and return its address, but the mechanics of trying to reserve space for a string whose size could be of any size makes such an approach impractical.) ***********************************************************************/ char * ebctoasc ( char *ebcdic_string /* The EBCDIC string to translate to ASCII. */ ) { /*____________________________Local storage____________________________*/ char *char_ptr; /*______________TABLE FOR TRANSLATING FROM EBCDIC TO ASCII_____________*/ /* Handling of EBCDIC codes with no ASCII equivalent: * Cents sign (X'4A'): translate to Dollar sign (ASCII X'24') */ char ebctoasc_table[] = { 0x00, 0x01, 0x02, 0x03, 0x1A, 0x09, 0x1A, 0x7F, /* X'00'-X'07' */ 0x1A, 0x1A, 0x1A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /* X'08'-X'0F' */ 0x10, 0x11, 0x12, 0x13, 0x1A, 0x1A, 0x08, 0x1A, /* X'10'-X'17' */ 0x18, 0x19, 0x1A, 0x1A, 0x1C, 0x1D, 0x1E, 0x1F, /* X'18'-X'1F' */ 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x0A, 0x17, 0x1B, /* X'20'-X'27' */ 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x05, 0x06, 0x07, /* X'28'-X'2F' */ 0x1A, 0x1A, 0x16, 0x1A, 0x1A, 0x1A, 0x1A, 0x04, /* X'30'-X'37' */ 0x1A, 0x1A, 0x1A, 0x1A, 0x14, 0x15, 0x1A, 0x1A, /* X'38'-X'3F' */ 0x20, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'40'-X'47' */ 0x1A, 0x1A, 0x24, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, /* X'48'-X'4F' */ 0x26, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'50'-X'57' */ 0x1A, 0x1A, 0x5D, 0x24, 0x2A, 0x29, 0x3B, 0x5E, /* X'58'-X'5F' */ 0x2D, 0x2F, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'60'-X'67' */ 0x1A, 0x1A, 0x7C, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, /* X'68'-X'6F' */ 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'70'-X'77' */ 0x1A, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, /* X'78'-X'7F' */ 0x1A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, /* X'80'-X'87' */ 0x68, 0x69, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'88'-X'8F' */ 0x1A, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, /* X'90'-X'97' */ 0x71, 0x72, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'98'-X'9F' */ 0x1A, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, /* X'A0'-X'A7' */ 0x79, 0x7A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'A8'-X'AF' */ 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'B0'-X'B7' */ 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'B8'-X'BF' */ 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, /* X'C0'-X'C7' */ 0x48, 0x49, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'C8'-X'CF' */ 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, /* X'D0'-X'D7' */ 0x51, 0x52, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'D8'-X'DF' */ 0x5C, 0x1A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, /* X'E0'-X'E7' */ 0x59, 0x5A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, /* X'E8'-X'EF' */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, /* X'F0'-X'F7' */ 0x38, 0x39, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A /* X'F8'-X'FF' */ }; char_ptr = ebcdic_string; while (*char_ptr != '\0') { *char_ptr = ebctoasc_table[*char_ptr]; char_ptr++; } return ebcdic_string; } /* End of ebctoasc(). */ /**************************** ctod() ********************************* Convert a character numeral to an integer. Returns 0-9 value if successful; -1 if not. ***********************************************************************/ int ctod(char char_value) { if (! isdigit(char_value)) return -1; else return (int)char_value & 0x7; } /* End of ctod(). */ /************************************ interp_tape_label() ************************************** Interpret the passed tape label record, emitting the results to Stdout. INVOCATION: rc = interp_tape_label(label_string); where: label_string is an 80-character label string with no embedded newline. RETURNS: Success: 0 Label successfully interpreted. -1 Label not recognized. Volume Label The IBM standard volume label (VOL1) appears at the beginning of each tape. The volume label identifies the volume and its owner and is used to verify that the correct volume is mounted. This label is created by either a utility program or the user's program when the tape is first received at an installation. Data Set Header Labels The data set header label group consists of IBM standard data set label 1 (HDR1) and IBM standard data set label 2 (HDR2). These are used to identify and describe the data set and to protect it from unauthorized use. HDR1 contains operating system and device-dependent data that relates to the data set. HDR2 contains additional data set characteristics. User Header Labels Optionally, a maximum of 8 user header labels (UHL1 - UHL8) can appear on the tape immediately following the data set header labels (HDR1, HDR2). These labels contain user-specified data that can be made available to the user program for processing. Data Set Trailer Labels The data set trailer group consists of IBM standard data set label 1 (EOV1 or EOF1) and IBM standard data set label 2 (EOV2 or EOF2). These labels duplicate the IBM data set header labels so that the tae can be read backward. The trailer labels are identical to the header labels, except that: - The identifier is EOV or EOF instead of HDR; - A block count is recorded in the first trailer label (EOV1 or EOF1) and is used on input to verify that all blocks of the data set are processed. The block count on the HDR1 label contains zeros (in EBCDIC or BCD). These labels are created automatically by data management when the data set is recorded on tape. User Trailer Labels Optionally, a maximum of 8 user trailer labels (UTL1 - UTL8) can immediately follow the data set trailer labels. These labels contain user-specified data that can be made available to your program for processing. Tapemarks Each data set and each data set label grou to be processed by data management must be followed by a tapemark... - There is no tapemark between the volume label and the first header label group on the volume; - The tapemark that marks the end of the header label group also indicates the beginning of the data set to be processed. - The tapemark that follows the data set also indicates the beginning of the trailer label group. - A tapemark marks the end of the trailer label group. A second tapemark follows the trailer label group of the last data set on the volume, provided that the data set does not continue on another volume. Positioning When the volume is mounted, the tape is initially positioned to before the VOL1 label. The operating system then verifies that label against a runtime specification to assure that the volume is the correct one and, if so, will position past the VOL1 to the header label group. Usually, there is only one data set on the reel, and the header label group immediately follows the volume label. To retrieve a data set when more than one data set is on a single reel, tapemark delineation allows positioning to the one of interest. *************************************************************************************************/ int interp_tape_label( char *label_string, /* The label string to be interpreted. */ int record_number /* The record number, for reporting. */ ) { int i, n; char *str_ptr, *char_ptr, *density; struct basic_label { char label_id[3]; /* Label identifier. E.g., "VOL", "HDR".*/ char label_number[1]; /* Label identifier. E.g., "1". */ char remainder[76]; /* The rest of the record. */ } basic_label, *basic_label_ptr = (struct basic_label *)label_string; struct vol1_label { char label_id[3]; /* 01-03 Label identifier. "VOL". */ char label_number[1]; /* 04 Label identifier. "1". */ char volser[6]; /* 05-10 Volume serial "number". May be any characters. * If fewer than 6, the string must be left-justified * and padded on the right with blanks. */ char reserved1[1]; /* 11 Reserved. */ char vtoc_ptr[10]; /* 12-21 VTOC pointer. DASD only. On tapes, should be blanks. */ char reserved2[16]; /* 22-41 Reserved. */ char owner[14]; /* 42-51 Owner name and address, left-justified, padded on right * with blanks. */ char reserved3[29]; /* 52-80 Reserved. */ } vol1_label, *vol1_label_ptr = (struct vol1_label *)label_string; struct hdr1_label /* Common format for HDR1, EOV1, EOF1. */ { char label_id[3]; /* 01-03 Label identifier. "HDR". */ char label_number[1]; /* 04 Label identifier. "1". */ char dataset_id[17]; /* 05-21 Last 17 chars of dataset name. If shorter, left-justified, * and right-padded with blanks. */ char dataset_ser[6]; /* 22-27 Dataset serial number: the volume serial of the first or * only tape housing this data set. */ char vol_seq_num[4]; /* 28-31 Volume sequence number, for when there are multiple volumes * housing the data set: A number, 0001 - 9999, specifying * which volume this one is when the data set is contained on * multiple volumes. Is simply 0001 for a single-volume data set. */ char dataset_seq_num[4]; /* 32-35 Dataset sequence number, for when there are multiple data sets * on a single volume: A number, 0001 - 9999, specifying the * relative number of this data set amongst a collection of data * sets. Is simply 0001 for the usual case of a single data set * on a single reel. */ char generation[4]; /* 36-39 Generation number, for Generation Data Groups: A number, * 0001-9999, identifying the absolute generation number. * For non-GDGs, this field is blank. */ char version[2]; /* 40-41 Version number, for Generation Data Groups: A number, * 01 - 99, identifying the version number of the generation. */ char creation_date[6]; /* 42-47 The CYYDDD Century-Julian creation date, where C is blank * for 1900, 0 for 2000, 1 for 2001, etc. */ char expiration_date[6]; /* 48-53 The CYYDDD Century-Julian expiration date, per the RETPD * retention period specification when the data set was created. */ char dataset_security[1]; /* 54 Security status: a code number indicating the security status * of the dataset: * 0 - None * 1 - Password protection for reading, writing, deleting * 3 - Password protection for writing, deleting */ char block_count[6]; /* 55-60 Number of blocks in dataset. 000000 in HDR1, but actual count * in trailer label. */ char system_code[13]; /* 61-73 Name of IBM system, such as "IBM OS/VS 370". */ char reserved[7]; /* 74-80 Reserved. */ } /* Logically continued in HDR2 label. */ hdr1_label, *hdr1_label_ptr = (struct hdr1_label *)label_string; struct hdr2_label /* Common format for HDR2, EOV2, EOF2. */ { char label_id[3]; /* 01-03 Label identifier. "HDR". */ char label_number[1]; /* 04 Label identifier. "2". */ char recfm[1]; /* 05 Record format: F, V, or U. See also Block Attribute. */ char blksize[5]; /* 06-10 Block size: a number up to 32760. * For RECFM F: is block length, a multiple of LRECL. * For RECFM V: the maximum block length, including 4-byte * Block Descriptor Word. * For RECFM U: the maximum block length. */ char lrecl[5]; /* 11-15 Logical record length: a number <= blksize. * For RECFM F: the common length of all records. * For RECFM V: the maximum record length, including 4-byte * Record Descriptor Word. * For RECFM U: is 00000. */ char den[1]; /* 16 Tape recording density: * 0 7-track, 200 bpi * 1 7-track, 556 bpi * 2 7-track or 9-track, 800 bpi * 3 9-track, 1600 bpi * 4 9-track, 6250 bpi */ char dataset_pos[1]; /* 17 Dataset position: a code indicating whether a volume * switch has occurred: 0 = no, 1 = yes. */ char job_step[17]; /* 18-34 Job/jobstep identification, in that format. * For EOF2 with DISP=MOD processing, reflects the name * of the job/step which extended it. */ char recording_tech[2]; /* 35-36 Tape recording technique (FL2TRTCH), for 7-track tapes: * Tb Odd parity with translation * Cb Odd parity with conversion * Eb Even parity with no translation * ET Even parity with translation * bb Odd parity with no translation or conversion * Pb Compacted (3480,3490 IDRC) */ char control_char[1]; /* 37 Printer control char type indicating whether a control * character set was used to create the data set and the * type of control characters used: * A ISO/ANSI/FPS control characters (alpha) * M Machine control characters (binary) * b No control characters */ char reserved1[1]; /* 38 Reserved. */ char block_attr[1]; /* 39 Block Attribute (blocking/blksize method): * B Blocked records * S Spanned records, if RECFM=V * S Standard records, if RECFM=F * R Blocked and spanned records, if RECFM=V * R Blocked and standard records, if RECFM=F * b Records that are not blocked and not spanned, * or records that are not blocked and not standard */ char reserved2[8]; /* 40-47 For 2400 and 3410 tape drive, reserved. * 3420: First 3 bytes reserved, 1-byte model number, * 4 bytes for last 4 digits of drive serial number. * 3480: First 3 bytes reserved, 4 bytes for last 4 digits * of control unit serial number, 1-byte device addr. */ char ckpt_dataset_id[1]; /* 48 Checkpoint dataset identifier: C if a secure checkpoint * data set, else blank. */ char reserved3[32]; /* 49-80 Reserved. */ } hdr2_label, *hdr2_label_ptr = (struct hdr2_label *)label_string; /*___________________________________Display the raw data_______________________________________*/ printf("\n____________________________________Record %d____________________________________\n", record_number); puts("Raw label data:\n" " 1 2 3 4 5 6 7 8\n" "12345678901234567890123456789012345678901234567890123456789012345678901234567890"); printf("%s\n", label_string); /*_____________________________________Process label by type________________________________________*/ if ((strncmp(basic_label_ptr->label_id,"VOL",3) == 0) && (*basic_label_ptr->label_number == '1')) { /*_____________________________________It's a VOL1___________________________________________*/ /* Refer instead to the VOL1 mapping of the label string. */ puts("\nVOL1 label contents, by field position:"); printf("05-10: Volser: '%-*.*s'\n", sizeof(vol1_label.volser), sizeof(vol1_label.volser), vol1_label_ptr->volser); puts(" 11: (reserved)"); puts("12-21: (VTOC pointer - DASD only)"); puts("22-41: (reserved)"); printf("42-51: Owner: '%-*.*s'\n", sizeof(vol1_label.owner), sizeof(vol1_label.owner), vol1_label_ptr->owner); puts("52-80: (reserved)"); } else if (((strncmp(basic_label_ptr->label_id,"HDR",3) == 0) || (strncmp(basic_label_ptr->label_id,"EOV",3) == 0) || (strncmp(basic_label_ptr->label_id,"EOF",3) == 0)) && (*basic_label_ptr->label_number == '1')) { /*____________________________It's an IBM Standard Data Set Label 1 (HDR1/EOV1/EOF1)________________________________*/ printf("\n%.*s%.*s label contents, by field position:\n", sizeof(basic_label_ptr->label_id, basic_label_ptr->label_id, sizeof(basic_label_ptr->label_number, basic_label_ptr->label_number); printf("05-21: Dataset Id: '%-*.*s' (Last 17 chars of dataset name)\n", sizeof(hdr1_label.dataset_id), sizeof(hdr1_label.dataset_id), hdr1_label_ptr->dataset_id); printf("22-27: Dataset Serial Number: '%-*.*s'\n", sizeof(hdr1_label.dataset_ser), sizeof(hdr1_label.dataset_ser), hdr1_label_ptr->dataset_ser); printf("28-31: Volume Sequence Number: '%-*.*s'\n", sizeof(hdr1_label.vol_seq_num), sizeof(hdr1_label.vol_seq_num), hdr1_label_ptr->vol_seq_num); printf("32-35: Dataset Sequence Number: '%-*.*s'\n", sizeof(hdr1_label.dataset_seq_num), sizeof(hdr1_label.dataset_seq_num), hdr1_label_ptr->dataset_seq_num); printf("36-39: Generation Number: '%-*.*s'\n", sizeof(hdr1_label.generation), sizeof(hdr1_label.generation), hdr1_label_ptr->generation); printf("40-41: Version Number: '%-*.*s'\n", sizeof(hdr1_label.version), sizeof(hdr1_label.version), hdr1_label_ptr->version); i = hdr1_label_ptr->creation_date[0]; if (i == ' ') { n = 19; } else { n = 20 + ctod(i); } printf("42-47: Creation Date: '%-*.*s' (%d%-*.*s)\n", sizeof(hdr1_label.creation_date), sizeof(hdr1_label.creation_date), hdr1_label_ptr->creation_date, n, sizeof(hdr1_label.creation_date)-1, sizeof(hdr1_label.creation_date)-1, hdr1_label_ptr->creation_date+1); i = hdr1_label_ptr->creation_date[0]; if (i == ' ') { n = 19; } else { n = 20 + ctod(i); } printf("48-53: Expiration Date: '%-*.*s' (%d%-*.*s)\n", sizeof(hdr1_label.expiration_date), sizeof(hdr1_label.expiration_date), hdr1_label_ptr->expiration_date, n, sizeof(hdr1_label.expiration_date)-1, sizeof(hdr1_label.expiration_date)-1, hdr1_label_ptr->expiration_date+1); str_ptr = hdr1_label_ptr->dataset_security; /* For brevity. */ if (strncmp(str_ptr,"0",1) == 0) { char_ptr = "No password protection"; } else if (strncmp(str_ptr,"1",1) == 0) { char_ptr = "R/W/Del password protection"; } else if (strncmp(str_ptr,"3",1) == 0) { char_ptr = "Write/Del password protection"; } else { char_ptr = "unrecognized"; } printf("54-54: Dataset Security: '%-*.*s' (%s)\n", sizeof(hdr1_label.dataset_security), sizeof(hdr1_label.dataset_security), hdr1_label_ptr->dataset_security,char_ptr); printf("55-60: Block Count: '%-*.*s' (always 0 in hdr; actual value in trailer)\n", sizeof(hdr1_label.block_count), sizeof(hdr1_label.block_count), hdr1_label_ptr->block_count); printf("61-73: System Code: '%-*.*s'\n", sizeof(hdr1_label.system_code), sizeof(hdr1_label.system_code), hdr1_label_ptr->system_code); puts("74-80: (reserved)"); } else if (((strncmp(basic_label_ptr->label_id,"HDR",3) == 0) || (strncmp(basic_label_ptr->label_id,"EOV",3) == 0) || (strncmp(basic_label_ptr->label_id,"EOF",3) == 0)) && (*basic_label_ptr->label_number == '2')) { /*____________________________It's an IBM Standard Data Set Label 2 (HDR2/EOV2/EOF2)________________________________*/ printf("\n%.*s%.*s label contents, by field position:\n", sizeof(basic_label_ptr->label_id, basic_label_ptr->label_id, sizeof(basic_label_ptr->label_number, basic_label_ptr->label_number); printf("05-05: Record Format: '%-*.*s'\n", sizeof(hdr2_label.recfm),sizeof(hdr2_label.recfm), hdr2_label_ptr->recfm); printf("06-10: Block Size: '%-*.*s'\n", sizeof(hdr2_label.blksize),sizeof(hdr2_label.blksize), hdr2_label_ptr->blksize); printf("11-15: Logical Record Length: '%-*.*s'\n", sizeof(hdr2_label.lrecl),sizeof(hdr2_label.lrecl), hdr2_label_ptr->lrecl); if (hdr2_label_ptr->den[0] == '0') { density = "3480: Not applicable; 3420: 200 bpi (7-Track)"; } else if (hdr2_label_ptr->den[0] == '1') { density = "3480: Not applicable; 3420: 556 bpi (7-Track)"; } else if (hdr2_label_ptr->den[0] == '2') { density = "3480: Not applicable; 3420: 800 bpi (7/9-Track)"; } else if (hdr2_label_ptr->den[0] == '3') { density = "3480: Not applicable; 3420: 1600 bpi (9-Track)"; } else if (hdr2_label_ptr->den[0] == '4') { density = "3480: Not applicable; 3420: 6250 bpi (9-Track)"; } else { density = "unrecognized value"; } printf("16-16: Tape Density: '%-*.*s' (%s)\n", sizeof(hdr2_label.den), sizeof(hdr2_label.den), hdr2_label_ptr->den, density); printf("17-17: Dataset Position: '%-*.*s'\n", sizeof(hdr2_label.dataset_pos), sizeof(hdr2_label.dataset_pos), hdr2_label_ptr->dataset_pos); printf("18-34: Job/Job Step Id: '%-*.*s'\n", sizeof(hdr2_label.job_step), sizeof(hdr2_label.job_step), hdr2_label_ptr->job_step); str_ptr = hdr2_label_ptr->recording_tech; /* For brevity. */ if (strncmp(str_ptr,"T ",2) == 0) { char_ptr = "Odd parity with translation"; } else if (strncmp(str_ptr,"C ",2) == 0) { char_ptr = "Odd parity with conversion"; } else if (strncmp(str_ptr,"E ",2) == 0) { char_ptr = "Even parity with no translation"; } else if (strncmp(str_ptr,"ET",2) == 0) { char_ptr = "Even parity with translation"; } else if (strncmp(str_ptr," ",2) == 0) { char_ptr = "Odd parity with no translation or conversion"; } else if (strncmp(str_ptr,"P ",2) == 0) { char_ptr = "Compacted (IDRC on, 3480/3490)"; } else { char_ptr = "unrecognized"; } printf("35-36: Tape Recording Technique (TRTCH): '%-*.*s'\n (%s)\n", sizeof(hdr2_label.recording_tech), sizeof(hdr2_label.recording_tech), hdr2_label_ptr->recording_tech, char_ptr); str_ptr = hdr2_label_ptr->control_char; /* For brevity. */ if (strncmp(str_ptr,"A",1) == 0) { char_ptr = "ANSI type"; } else if (strncmp(str_ptr,"M",1) == 0) { char_ptr = "Machine type"; } else if (strncmp(str_ptr," ",1) == 0) { char_ptr = "none"; } else { char_ptr = "unrecognized"; } printf("37-37: Print Control Type: '%-*.*s' (%s)\n", sizeof(hdr2_label.control_char),sizeof(hdr2_label.control_char), hdr2_label_ptr->control_char,char_ptr); puts("38-38: (reserved)"); str_ptr = hdr2_label_ptr->block_attr; /* For brevity. */ if (strncmp(str_ptr,"B",1) == 0) { char_ptr = "Blocked records"; } else if (strncmp(str_ptr,"S",1) == 0) { if (strncmp(hdr2_label_ptr->recfm,"V",1) == 0) char_ptr = "Spanned"; else if (strncmp(hdr2_label_ptr->recfm,"F",1) == 0) char_ptr = "Standard"; } else if (strncmp(str_ptr,"R",1) == 0) { if (strncmp(hdr2_label_ptr->recfm,"V",1) == 0) char_ptr = "Blocked and spanned"; else if (strncmp(hdr2_label_ptr->recfm,"F",1) == 0) char_ptr = "Blocked and standard"; } else if (strncmp(str_ptr," ",1) == 0) char_ptr = "Not blocked and not spanned/standard"; else { char_ptr = "unrecognized"; } printf("Cols 39-39: Block Attribute: '%-*.*s' (%s)\n", sizeof(hdr2_label.block_attr),sizeof(hdr2_label.block_attr), hdr2_label_ptr->block_attr,char_ptr); printf("Cols 40-47: '%-*.*s' (reserved); but for 3420, the 1-byte model number" " and 4 bytes for last 4 digits of drive serial number;" " for 3480, 4 bytes for last 4 digits of control unit serial" " number, 1-byte device addr.\n"); sizeof(hdr2_label.reserved2), sizeof(hdr2_label.reserved2), hdr2_label_ptr->reserved2); printf("Cols 48-48: Checkpoint Dataset Id: '%-*.*s'\n", sizeof(hdr2_label.ckpt_dataset_id), sizeof(hdr2_label.ckpt_dataset_id), hdr2_label_ptr->ckpt_dataset_id); puts("Cols 49-80: (reserved)"); } else if (strncmp(basic_label_ptr->label_id,"UHL",3) == 0) { /*_______________________It's a User Header Label_____________________*/ puts("User Header Label: content is user-defined - not interpretable."); } else { /*____________________Don't recognize it_________________________*/ puts("I do not recognize this label data."); return -1; } return 0; } /* End of interp_tape_label(). */ /************************************************ main() ****************************************************/ main(int argc, /* Number of arguments passed. */ char * argv[], /* An array of pointers to the character strings which comprise the arguments. */ char * envp[] /* An array of pointers to the character strings which comprise the environmental * parameters. */ ) { /*____________________________________________Local storage________________________________________________*/ int i; int save_errno; int bytes_read; /* Bytes read by read() function. */ char *char_ptr, *string_ptr; char *input_path; /* Cmdline-given -f path name. */ int input_blk_ct = 0; /* Input block count. */ int input_rec_ct = 0; /* Input record count. (May exceed block count * if there are multiple newline-delimited * records in a block.) */ char *invocation = "tapelabl -f pathname"; char input_buffer[32768]; /* I/O buffer, big enough to contain the largest * OS record. */ char label_data[81]; /* To accommodate 80 characters of label data, * plus a null. */ int is_ebcdic = 0; /* Is set if data is determined to be EBCDIC. */ int ifd = 0; /* Input file descriptor, from open(). Init 0 to know if open. */ int exit_status = 0; /* Main() exits with whatever value is stored * here. Logic will alter value if errors. */ /*__________________________________PROCESS INVOCATION PARAMETERS__________________________________*/ if (argc == 1) /* Only command name present in parameters. */ { printf("No invocation arguments - cannot proceed.\n"); printf(" Invoke as: '%s'.\n",invocation); exit(EXIT_FAILURE); } /* Process all command line arguments. (Element 0 is the command name, * so elements 1 to argc-1 are all the arguments). */ Pgm_Name = argv[0]; printf("%s program, version 19940114.c, R. Sims.\n\n",Pgm_Name); for (i = 1; i < argc; i++) { if (argv[i][0] == '-') /* If argument begins '-' ... */ { /* ...should be the start of an option. */ if (strlen(argv[i]) == 1) { fprintf(stderr,"Command line operand %d ('%s') is an option with no value.\n", i, argv[i]); printf(" Invoke as: '%s'.\n", invocation); exit(EXIT_FAILURE); } /* Okay, the operand is an option; determine which. */ if (strcmp(argv[i]+1,"f") == 0) /* Option which identifies a file/device name. */ { if (i == argc) { fprintf(stderr,"%s: No file/device name follows '-f' option.\n", Pgm_Name); exit(EXIT_FAILURE); } else { /* Capture the path name and advance past that cmdline element. */ input_path = argv[i+1]; i++; } } else /* It's not an option that I recognize: */ { fprintf(stderr,"%s: Command line option '%s' not recognized.\n", Pgm_Name, argv[i]); printf(" Invoke as: '%s'.\n", invocation); exit(EXIT_FAILURE); } } /* end of analyzing an option */ else { fprintf(stderr,"%s: Command line element '%s' is not prefaced by an option.\n", Pgm_Name, argv[i]); printf(" Invoke as: '%s'.\n", invocation); exit(EXIT_FAILURE); } } /* end of arguments processing loop */ printf("Proceeding with path name '%s'...\n", input_path); /*_________________________INITIAL PROCESSING__________________________*/ if ((ifd = open(input_path, O_RDONLY)) == -1) { save_errno = errno; fprintf(stderr,"%s: Open failed on input file '%s', errno = %d\n" "\tError reason: %s\n", Pgm_Name, input_path, errno, strerror(save_errno)); exit(save_errno); } /*________________________MAINLINE PROCESSING__________________________*/ while (input_rec_ct < 12) /* A limiter on the number of records read, * to prevent runaways while still allowing * for VOL1, HDR1, HDR2, and UHL1-8. */ { /* The read() function will read a physical block at a time. * Each OS tape label is written as a separate, 80-character block, * with tape mark after the labels marking end of file for that section. */ if ((bytes_read = read(ifd, input_buffer, sizeof(input_buffer))) == 0) { /* Nothing was read: should return number of bytes read. */ /* At end of file. */ puts("\nEnd of data apparently reached."); break /* out of while-loop */; } else { /* Your data is ready, sir. */ input_blk_ct++; /* Increment the block count. */ input_rec_ct++; /* Increment the record count. */ input_buffer[bytes_read] = '\0'; /* Set end of string. */ /*________________Invoke interpeter on this record_______________*/ /* If the data seems to be EBCDIC, translate to ASCII. */ if (!is_ebcdic) { /* Need to determine if EBCDIC: */ for (char_ptr = input_buffer, i = 0; i < bytes_read; char_ptr++, i++) { if (*char_ptr > 127) { is_ebcdic = 1; /* Put out what is a one-time header, advising the invoker: */ puts("\nThe data seems to be in EBCDIC character code. Will translate."); break; /* Exit the for-loop. */ } } /* end of for-loop to check data type. */ } if (is_ebcdic) /* If we believe the data to be EBCDIC (now). */ { char_ptr = ebctoasc(input_buffer); /* Translate from EBCDIC to ASCII. * (The assignment is just perfunctory.) */ } /* Even if we miss detection of the data actually being EBCDIC, the interpreter logic * will find out soon enough whether it can recognize the stuff. */ if (bytes_read > 80) { /* printf("Block: '%s'\n", input_buffer); */ /*_________________________________Pursue deblocking__________________________________*/ /* Labels are always 80 chars, but we may be looking at a disk-based capture of a set * of tape labels, in a single file. If so, each will be demarcated by a newline * (including the last), so we have to effectively "deblock" and interpret each in turn. * string_ptr will keep track of the start of each string, and char_ptr will be used * to step through it, seeking the newline at the end of the string. */ for (string_ptr = char_ptr = input_buffer, i = 0; i < bytes_read; char_ptr++, i++) { if (*char_ptr == '\n') { /* We found a newline in the string, so this seems to be a composite. */ *char_ptr = '\0'; /* Make that its own string. */ strcpy(label_data,string_ptr); /* Copy to work area. */ /* Pad the record out to 80 with blanks, if necessary. */ while (strlen(label_data) < 80) { strcat(label_data," "); /* A simple method. */ } label_data[80] = '\0'; /* Set end of string. */ if (interp_tape_label(label_data, input_rec_ct) != 0) { fprintf(stderr,"%s: Quitting due to label unrecognized.\n", Pgm_Name); exit_status = 1; goto CLOSEOUT; } string_ptr = char_ptr + 1; /* Advance to start of next string. */ input_rec_ct++; /* Increment the record count. */ } /* end of processing a record within a block */ } /* end of for-loop to check data type. */ if (string_ptr == input_buffer) { /* We didn't find a newline in the long data. */ fprintf(stderr,"%s: Record %d length (%d) exceeds 80; this can't be a label." "Quitting.\n", Pgm_Name, input_rec_ct, bytes_read); /* printf(" Oversized record contains:\n '%.80s'...\n", input_buffer); */ exit_status = 1; break; /* Break out of while-loop to exit cleanly. */ } input_rec_ct--; /* Adjust record count to account for one-over * value. */ } /* end of processing a physical block longer than 80 bytes */ else /* Record length is 80 or less. */ { if (bytes_read < 80) { /* This should not happen in AIX, but just in case... */ /* The CMS IBM C compiler library strips trailing blanks, even * from fixed-length records. (See p. 9-2 of its C Language * manual.) For proper processing, we have to restore them. * Remember that fgets() leaves a newline in the buffer, * at the end of the string, so we have to remove that as * well. */ for (char_ptr = &input_buffer[strlen(input_buffer)-1], i = strlen(input_buffer)-1; i < 80; char_ptr++, i++) { *char_ptr = ' '; } input_buffer[80] = '\0'; /* Set end of string. */ } /* We now have a record of exactly 80 characters for the interpreter * to work on. */ /* printf("Input record length = %d\n", strlen(input_buffer)); */ /* printf("Input record:\n'%s'", input_buffer); */ if (interp_tape_label(input_buffer, input_rec_ct) != 0) { fprintf(stderr,"%s: Quitting due to label unrecognized.\n", Pgm_Name); exit_status = 1; break; /* Break out of while-loop to exit cleanly. */ } } } /* end of processing a record. */ } /* end of while-loop to examine a limited number of tape records */ CLOSEOUT: if (ifd) close(ifd); /* If non-zero, had been opened, so close now. */ printf("Processed %d records in %d physical blocks.\n", input_rec_ct, input_blk_ct); exit(exit_status); } /* end of main() */