// oldtape.cpp - READ FILES FROM OLD BACKUP TAPES // // USAGE // oldtape tape-dev file-name // // tape-dev is the tape device name. // file-name is the name of the file to be extracted. // // MODULE INDEX // NAME CONTENTS // BigEndToLong Convert a big endian number to long // Pass Process a pass of the tape // main Main line // // MAINTENANCE HISTORY // DATE PROGRAMMER AND DETAILS // 08-11-02 JS Original // //----------------------------------------------------------------------------- #include // Standard input/output #include // String manipulation #include // Standard library #include // Error codes //----------------------------------------------------------------------------- // DEFINITIONS #define BLKSIZE 1024 // Tape block size #define FILENAMELEN 16 // File name length #define FSTDIRBLKNO 1 // First directory block number #define LSTDIRBLKNO 31 // Last directory block number #define DIRRECPERBLK (BLKSIZE/sizeof(DirRec)) // Directory records per block #define BLKNOPERBLK (BLKSIZE/sizeof(BigEnd)) // Block numbers per block #define MAXDATABLK (1 + BLKNOPERBLK + BLKNOPERBLK*BLKNOPERBLK) // Maximum data blocks #define MAXPNTBLK (1 + BLKNOPERBLK) // Maximum pointer blocks //----------------------------------------------------------------------------- // BIG ENDIAN NUMBER struct BigEnd { unsigned char bigEnd3; // Highest order byte unsigned char bigEnd2; unsigned char bigEnd1; unsigned char bigEnd0; // Lowest order byte }; //----------------------------------------------------------------------------- // DIRECTORY RECORD struct DirRec { char dirFileName[FILENAMELEN]; // File name BigEnd dirFileSize; // File size BigEnd dirFirstBlkNo; // First block number BigEnd dirPntBlkNo; // Pointer block number BigEnd dirPntPntBlkNo; // Pointer to pointer block numbers }; //----------------------------------------------------------------------------- // DIRECTORY BLOCK struct DirBlk { DirRec dirRecArr[DIRRECPERBLK]; // Directory record array }; //----------------------------------------------------------------------------- // POINTER BLOCK struct PntBlk { BigEnd pntBlkNoArr[BLKNOPERBLK]; // Block number array }; //----------------------------------------------------------------------------- // BLOCK STATES enum { BLKSTUNKNOWN, // Unknown block number BLKSTKNOWN, // Known block number BLKSTLOADED // Copied to the output file }; typedef char BlkSt; //----------------------------------------------------------------------------- // BLOCK CATEGORY enum { BLKCATSKIP, // Block not relevant to the current pass BLKCATPREV, // Previously processed BLKCATDATA, // Data block BLKCATPNT, // Pointer block BLKCATPNTPNT // Pointer-pointer block }; typedef char BlkCat; //----------------------------------------------------------------------------- // GLOBAL DATA DirRec selDirRec; // Selected directory record char selDirFnd; // Selected directory record found flag unsigned long dataBlkNoArr[MAXDATABLK]; // Data block numbers BlkSt dataBlkStArr[MAXDATABLK]; // Data block states unsigned long dataBlkCnt; // Data block count size_t lastDataBlkLen; // Last data block length unsigned long pntBlkNoArr[MAXPNTBLK]; // Pointer block numbers BlkSt pntBlkStArr[MAXPNTBLK]; // Pointer block states unsigned long pntBlkCnt; // Pointer block count unsigned long pntPntBlkNo; // Pointer-pointer block number BlkSt pntPntBlkSt; // Pointer-pointer block state unsigned long pntPntBlkCnt; // Pointer-pointer block count //----------------------------------------------------------------------------- // CONVERT A BIG ENDIAN NUMBER TO LONG unsigned long BigEndToLong ( BigEnd bigEnd) // Big endian number { unsigned long l; // Long number l = bigEnd.bigEnd3; l <<= 8; l |= bigEnd.bigEnd2; l <<= 8; l |= bigEnd.bigEnd1; l <<= 8; l |= bigEnd.bigEnd0; return l; } //----------------------------------------------------------------------------- // PROCESS A PASS OF THE TAPE void Pass ( FILE *tapeFp, // Tape file pointer unsigned long blkNo, // Current block number char *fileName, // Selected file name FILE *fileFp) // File file pointer { unsigned long i, j; // General purpose index size_t blkLen; // Current data block length char blkBuf[BLKSIZE]; // Block buffer PntBlk pntBlk; // Pointer block unsigned long pntCnt; // Pointer count unsigned long pntOfs; // Pointer offset BlkCat blkCat; // Block category // Skip boot and directory blocks while (blkNo <= LSTDIRBLKNO) { if (fread (blkBuf, BLKSIZE, 1, tapeFp) != 1) { fprintf (stderr, "Error: missing directory block\n"); exit (1); } blkNo ++ ; } // Process data blocks while (fread (blkBuf, BLKSIZE, 1, tapeFp) == 1) { // Determine the block category for ( i = 0; i < dataBlkCnt && ( dataBlkStArr[i] == BLKSTUNKNOWN || dataBlkNoArr[i] != blkNo ); i ++ ); if (i < dataBlkCnt) { if (dataBlkStArr[i] == BLKSTLOADED) blkCat = BLKCATPREV; else blkCat = BLKCATDATA; } else { for ( i = 0; i < pntBlkCnt && ( pntBlkStArr[i] == BLKSTUNKNOWN || pntBlkNoArr[i] != blkNo ); i ++ ); if (i < pntBlkCnt) { if (pntBlkStArr[i] == BLKSTLOADED) blkCat = BLKCATPREV; else blkCat = BLKCATPNT; } else { if ( pntPntBlkCnt > 0 && pntPntBlkSt != BLKSTUNKNOWN && pntPntBlkNo == blkNo ) { if (pntPntBlkSt == BLKSTLOADED) blkCat = BLKCATPREV; else blkCat = BLKCATPNTPNT; } else { blkCat = BLKCATSKIP; } } } // Process the block categories switch (blkCat) { // Unloaded data blocks case BLKCATDATA: if (fseek (fileFp, i*BLKSIZE, SEEK_SET) != 0) { fprintf (stderr, "Error: seek %s %lu: %s\n", fileName, i*BLKSIZE, strerror(errno)); exit (1); } blkLen = i < dataBlkCnt-1 ? BLKSIZE : lastDataBlkLen; if (fwrite (blkBuf, blkLen, 1, fileFp) != 1) { fprintf (stderr, "Error: write %s at %lu: %s\n", fileName, i*BLKSIZE, strerror(errno)); exit (1); } dataBlkStArr[i] = BLKSTLOADED; break; // Unloaded pointer blocks case BLKCATPNT: memcpy (&pntBlk, blkBuf, sizeof(pntBlk)); pntOfs = 1 + i*BLKNOPERBLK; pntCnt = i < pntBlkCnt-1 ? BLKNOPERBLK : (dataBlkCnt-1) % BLKNOPERBLK; for (j = 0; j < pntCnt; j++) { if (dataBlkStArr[pntOfs+j] == BLKSTUNKNOWN) { dataBlkNoArr[pntOfs+j] = BigEndToLong ( pntBlk.pntBlkNoArr[j] ); dataBlkStArr[pntOfs+j] = BLKSTKNOWN; } } pntBlkStArr[i] = BLKSTLOADED; break; // Unloaded pointer-pointer block case BLKCATPNTPNT: memcpy (&pntBlk, blkBuf, sizeof(pntBlk)); pntCnt = (pntBlkCnt-1) % BLKNOPERBLK; for (j = 0; j < pntCnt; j++) { if (pntBlkStArr[1+j] == BLKSTUNKNOWN) { pntBlkNoArr[1+j] = BigEndToLong ( pntBlk.pntBlkNoArr[j] ); pntBlkStArr[1+j]=BLKSTKNOWN; } } pntPntBlkSt = BLKSTLOADED; break; // Other states are not relevant default: break; } // Increment the block number blkNo ++ ; } } //----------------------------------------------------------------------------- // MAIN LINE int main ( int argc, // Argument count char *argv[]) // Argument value pointers { FILE *tapeFp; // Tape file pointer FILE *fileFp; // File file pointer char *fileName; // File name char blkBuf[BLKSIZE]; // Block buffer unsigned long blkNo; // Block number DirBlk dirBlk; // Directory block unsigned long i; // General purpose indexes unsigned long fileSize; // File size in bytes unsigned long pass; // Pass index // Validate the argument count if (argc != 3) { fprintf (stderr, "Usage: oldtape tape-dev file-name\n"); exit (1); } // Open the tape device if ((tapeFp = fopen (argv[1], "r")) == NULL) { fprintf (stderr, "Error: cannot open %s: %s\n", argv[1], strerror(errno)); exit (1); } // Load the file name pointer fileName = argv[2]; if (strlen(fileName) > FILENAMELEN) { fprintf (stderr, "Error: file name %s too long\n", fileName); exit (1); } // First pass - skip the boot block blkNo = 0; while (blkNo < FSTDIRBLKNO) { if (fread (blkBuf, BLKSIZE, 1, tapeFp) != 1) { fprintf (stderr, "Error: no boot block\n"); exit (1); } blkNo ++ ; } // Process directory blocks selDirFnd = 0; while (blkNo <= LSTDIRBLKNO) { if (fread (blkBuf, BLKSIZE, 1, tapeFp) != 1) { fprintf (stderr, "Error: missing directory block\n"); exit (1); } memcpy (&dirBlk, blkBuf, sizeof(dirBlk)); for ( i = 0; i < DIRRECPERBLK && strncmp ( dirBlk.dirRecArr[i].dirFileName, fileName, FILENAMELEN ) != 0; i ++ ); if (i < DIRRECPERBLK) { if (selDirFnd) { fprintf (stderr, "Error: duplicate direcory record\n"); exit (1); } selDirRec = dirBlk.dirRecArr[i]; selDirFnd = 1; } blkNo ++ ; } // Check that the directory record was found if ( ! selDirFnd) { fprintf (stderr, "Error: %s is not on the tape\n", fileName); exit (1); } // Create the output file if ((fileFp = fopen (fileName, "w")) == NULL) { fprintf (stderr, "Error: cannot create %s: %s\n", fileName, strerror(errno)); exit (1); } // Initialise the block tables fileSize = BigEndToLong (selDirRec.dirFileSize); dataBlkCnt = (fileSize + BLKSIZE - 1) / BLKSIZE; lastDataBlkLen = (size_t) (fileSize % BLKSIZE); for (i = 0; i < dataBlkCnt; i++) dataBlkStArr[i] = BLKSTUNKNOWN; if (dataBlkCnt > 0) { dataBlkNoArr[0] = BigEndToLong(selDirRec.dirFirstBlkNo); dataBlkStArr[0] = BLKSTKNOWN; } pntBlkCnt = (dataBlkCnt - 1 + BLKNOPERBLK - 1) / BLKNOPERBLK; for (i = 0; i < pntBlkCnt; i++) pntBlkStArr[i] = BLKSTUNKNOWN; if (pntBlkCnt > 0) { pntBlkNoArr[0] = BigEndToLong(selDirRec.dirPntBlkNo); pntBlkStArr[0] = BLKSTKNOWN; } pntPntBlkCnt = (pntBlkCnt - 1 + BLKNOPERBLK - 1) / BLKNOPERBLK; pntPntBlkSt = BLKSTUNKNOWN; if (pntPntBlkCnt > 0) { pntPntBlkNo = BigEndToLong(selDirRec.dirPntPntBlkNo); pntPntBlkSt = BLKSTKNOWN; } // Execute up to three passes of the tape in an effort to retrieve // all the data blocks. pass = 0; for (;;) { for (i=0; i= dataBlkCnt || pass >= 3) break; Pass (tapeFp, blkNo, fileName, fileFp); rewind (tapeFp); blkNo = 0; pass ++ ; } // If data blocks could not be retrieved, report the problem if (i < dataBlkCnt) { fprintf (stderr, "Error: data blocks off end of tape %lu\n", i); exit (1); } // Close the files and exit gracefully fclose (fileFp); fclose (tapeFp); return 0; }