/* * Convert a file containing a raw dump of a Tektronix fbr floppy image to a tar archive * with an embedded content report record in "TarXc" format * * options * -b file use for lot information * -i ignore checksum errors in directory entries * -l generate table of contents to stdout instead of a tarball * * 1.0 20120521 aek */ #include #include #include #include #include //#include "md5.h" #define UID_NOBODY 32767 #define GID_NOBODY 32767 /* * Tek fbr disks are either single or double sided. The first track on side 0 * has 26 sectors of 128 bytes. The rest of the disk is 26 sectors of 256 bytes * The first four 128 byte sectors are ignored. * * fbr directory entry * * The first directory entry is a label entry for the entire archive * path A comment about this archive. * mode Set by fbr to a file readable/writeable/executable by all. Other- * wise unused. * uid The userID of the user who created this archive. * gid The groupID of the same. * size The total number of bytes in the data area of the archive.This * field in combination with the fblk field, records the size of * both the directory area and the entire archive. * acct The date-time that this archive was created - not its access * time. * modt The date-time that this archive was last altered. * fblk The block number of the first archive block following the direc- * tory area. */ typedef struct { // originally 128 bytes long char path[106]; // pathname (or label comment) unsigned short mode; // unix file mode short uid; // unix UID short gid; // unix GID unsigned long fsize; // file size time_t access_t; // time last accessed (or when archive was created) time_t mod_t; // time last modified unsigned short fblk; // file starting block char unused; char cksum; // fbr header checksum, byte initialized to -1 // extra fields added by this program char ccksum; // computed checksum char valid; // checksum was valid char *f_data; // pointer to the contents of the file int f_len; // real length padded to 512 byte boundary int nam_len; // length of the file path char md5[33]; // MD5 hash of the contents of the file long offset; // offset to the tar file header in the tarball } fbrdir; fbrdir d_tbl[0x3f * 4]; // maximum number of directory entires on DS/DD disk fbrdir d_lab; // the fbr label block /* * A POSIX tar file header. Currently the program does not deal with long pathnames */ typedef struct { // a V7 tar header, 512 bytes long char path[100]; // file path, currently limited to original V7 length char mode[8]; // all numeric data represented as octal char uid[8]; // null-terminated strings with leading zeros char gid[8]; char size[12]; char mtime[12]; char cksum[7]; // checksum over all 512 bytes initialized to ' ' char ftyp[2]; // POSIX file type char link[100]; char ustar[8]; // POSIX identifier "ustar " char unam[32]; // POSIX UID name string char gnam[32]; // POSIX GID name string char unused[180]; } tarlbl; tarlbl t_lab; long bytecount = 0; struct tm time_s; char time_str[50]; FILE *toc_file; /* * fetch a byte from stdin. if we've reached EOF, exit with an error */ unsigned char get_stdin(){ if(feof(stdin)){ fprintf(stderr,"Error: EOF on stdin after byte %d\n", bytecount); exit(EXIT_FAILURE); } else{ bytecount++; return(getchar()); } } /* * skip n bytes from stdin. If we've reached EOF, exit with an error */ void skip_stdin(int count) { while(count--){ if(feof(stdin)){ fprintf(stderr,"Error: EOF on stdin after byte %d\n", bytecount); exit(EXIT_FAILURE); } else{ bytecount++; getchar(); } } } /* * little-endian byte munging with checksum accumulation */ unsigned int get_16(char *cksum){ unsigned int i; unsigned char c; c = get_stdin(); *cksum += c; i = c; c = get_stdin(); *cksum += c; i |= (c << 8); return(i); } unsigned long get_32(char *cksum){ unsigned long l; unsigned char c; c = get_stdin(); *cksum += c; l = c << 16; c = get_stdin(); *cksum += c; l |= c << 24; c = get_stdin(); *cksum += c; l |= c; c = get_stdin(); *cksum += c; l |= c << 8; return(l); } /* * create fbr label record in the TOC */ print_label(fbrdir *p){ fprintf(toc_file,"| Label: | Mode: %06o | UID: %-02d | GID: %-02d | Sz: %-6d | Sbk: %-4X | Cks: %02X ", p->mode, p->uid, p->gid, p->fsize, p->fblk, (unsigned char)p->cksum, p->ccksum, p->path); time_s = *gmtime(&p->access_t); strftime(time_str, sizeof(time_str), "%\"%Y-%m-%d %H:%M:%S%\"", &time_s); fprintf(toc_file,"| Cre: %s ", time_str); time_s = *gmtime(&p->mod_t); strftime(time_str, sizeof(time_str), "%\"%Y-%m-%d %H:%M:%S%\"", &time_s); fprintf(toc_file,"| Mod: %s ", time_str); fprintf(toc_file,"| Nam: %\"%s%\"\n", p->path); } /* * create a file information record in the TOC */ print_dir(int fnum, fbrdir *p){ if(p->mode & 0040000) fprintf(toc_file, "| Dir: "); if(p->mode & 0100000) fprintf(toc_file, "| File: "); fprintf(toc_file, "%-3d | Offs: %-7d | Mode: %06o | UID: %-02d | GID: %-02d | Sz: %-6d | Sbk: %-4X | Cks: %02X ", fnum, p->offset, p->mode, p->uid, p->gid, p->fsize, p->fblk, (unsigned char)p->cksum, p->ccksum, p->path); time_s = *gmtime(&p->access_t); strftime(time_str, sizeof(time_str), "%\"%Y-%m-%d %H:%M:%S%\"", &time_s); fprintf(toc_file,"| Acc: %s ", time_str); time_s = *gmtime(&p->mod_t); strftime(time_str, sizeof(time_str), "%\"%Y-%m-%d %H:%M:%S%\"", &time_s); fprintf(toc_file,"| Mod: %s ", time_str); fprintf(toc_file,"| MD5: %s ", p->md5); fprintf(toc_file,"| Path: %\"%s%\"\n",p->path); } /* * Analyze all the raw bytes of a fbr disk image, then use the collected * information to generate a tarball with an additional file on the front containing * a summary of the fbr contents */ main(int argc, char *argv[]){ unsigned int i, j, k; unsigned char c; fbrdir *p; char *cp; unsigned int cksum; int max_fblks; int actual_fblks = 0; int longest_path = 0; char *prefix_string; char *null_hash = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; int toc_filesize; struct timeval rawtime; struct stat statbuf; char buf[100]; /* * create a temporary file for the TOC to live in */ gettimeofday(&rawtime); time_s = *gmtime(&rawtime.tv_sec); if((toc_file = tmpfile()) == 0){ fprintf(stderr,"Error: could not open temp file\n"); exit(EXIT_FAILURE); } fprintf(toc_file,"TarXc_1.0\n"); strftime(buf, sizeof(buf), "%\"%Y-%m-%d %X %Z\"", &time_s); fprintf(toc_file,"| Created: %s\n", buf); fprintf(toc_file,"| Format: %\"Tektronix fbr%\"\n"); skip_stdin(512); // first 512 bytes unused /* * fetch the label block */ p = &d_lab; p->ccksum = -1; for(i=0; i<106; i++){ c = get_stdin(); p->ccksum += c; p->path[i] = c; } p->mode = get_16(&p->ccksum); p->uid = get_16(&p->ccksum); p->gid = get_16(&p->ccksum); p->fsize = get_32(&p->ccksum); p->access_t = get_32(&p->ccksum); p->mod_t = get_32(&p->ccksum); p->fblk = get_16(&p->ccksum); p->unused = get_stdin(); p->ccksum += p->unused; p->cksum = get_stdin(); if(p->cksum == p->ccksum) p->valid = 1; else{ fprintf(stderr,"Error: Label block cksum failed %x %x\n", p->cksum, p->ccksum); exit(EXIT_FAILURE); } print_label(p); // generate TOC label entry /* * create a path prefix string based on the current time */ asprintf(&prefix_string, "./FBR_%06X%04X/",rawtime.tv_sec,rawtime.((tv_usec>>10) & 0xffff)); switch(p->fblk){ case 0x20: max_fblks = (0x20 * 4) - 5; break; case 0x3f: max_fblks = (0x3f * 4) - 5; break; default: fprintf(stderr,"Error: first data block not at 0x20 or 0x3f\n"); exit(EXIT_FAILURE); } for(i = 0, p = d_tbl; i < max_fblks; i++, p++){ p->ccksum = -1; for(j = 0; j < 106; j++){ c = get_stdin(); if(c) p->f_len++; p->ccksum += c; if((c < ' ')&&(c != 0)) c = 'x'; // convert non-printing char in path p->path[j] = c; } if(longest_path < p->f_len) longest_path = p->f_len; p->mode = get_16(&p->ccksum); p->uid = get_16(&p->ccksum); p->gid = get_16(&p->ccksum); p->fsize = get_32(&p->ccksum); p->access_t = get_32(&p->ccksum); p->mod_t = get_32(&p->ccksum); p->fblk = get_16(&p->ccksum); p->unused = get_stdin(); p->ccksum += p->unused; p->cksum = get_stdin(); strcpy(p->md5, null_hash); if(p->cksum == p->ccksum) p->valid = 1; else{ fprintf(stderr,"Error: directory block %d cksum failed %x %x Offset %x\n",j , p->cksum, p->ccksum, bytecount); exit(EXIT_FAILURE); } if(p->fblk != 0) actual_fblks++; } for(i = 0, p = d_tbl; i < actual_fblks; i++, p++){ if(p->fblk){ print_dir(i+1, p); } } /* * We have all the directories, walk the rest of the file and create content records. * It would be easy, except that the starting blocks skip around on the disk, so we have * to find the directory entry that matches how far we've progressed in the image file */ for(i = 0; i < actual_fblks; i++ ){ // walk the directory table for(j = 0, p = d_tbl; j < actual_fblks; j++, p++){ if((p->fblk << 9) == bytecount) break; } if(j >= actual_fblks){ fprintf(stderr,"Error: input offset %d (blk %x) doesn't match a file starting block number\n", bytecount, bytecount >> 9); exit(EXIT_FAILURE); } if(p->fsize % 512) // if file length isn't a multiple of 512 j = p->fsize + (512 - (p->fsize %512)); // pad file size to 512 byte boundary else j = p->fsize; if(j == 0) // empty files take up one block j = 512; p->f_len = j; // remember how much memory is malloc'ed p->f_data = malloc(j); // wire the buffer to the directory entry for(j = 0; j < p->fsize; j++){ // copy the data p->f_data[j] = get_stdin(); } while(j < p->f_len){ get_stdin(); // ignore trailing stuff from the disk image and p->f_data[j++] = 0; // zero fill local buffer to block boundary } /* * calculate md5sum of actual contents of the file */ } // printf("byte count %d, longest pathname %d\n", bytecount, longest_path); if(longest_path >= (100 - strlen(prefix_string))){ fprintf(stderr,"Error: longest path %d, too long for V7 tar\n", longest_path); exit(EXIT_FAILURE); } /* * now build the tarball, starting with the accumulated file information * pathnames have ./{uniqueid}/ prepended to them. */ toc_filesize = ftell(toc_file); // calculate size of TOC file for(i = 512 - (toc_filesize % 512); i; i--) // pad TOC to 512 byte boundary fputc('\0', toc_file); rewind(toc_file); // TOC file ready to go /* * create the tar header for the TOC file */ memset(&t_lab,0 , sizeof(tarlbl)); sprintf(t_lab.path,"%s__TarXc_TOC.txt",prefix_string); sprintf(t_lab.mode,"%07o", 0444); sprintf(t_lab.uid,"%07o", UID_NOBODY); sprintf(t_lab.gid,"%07o", GID_NOBODY); sprintf(t_lab.size,"%011o", toc_filesize); sprintf(t_lab.mtime, "%011o", rawtime.tv_sec); memset(&t_lab.cksum, 0x20, sizeof(t_lab.cksum)); // ENTIRE field set to 0x20 sprintf(t_lab.ftyp, "%2d", 0); sprintf(t_lab.ustar, "ustar "); sprintf(t_lab.unam, "nobody"); sprintf(t_lab.gnam, "nobody"); cp = &t_lab.path[0]; cksum = 0; for(i=0; i<512; i++) cksum += *cp++; sprintf(t_lab.cksum, "%06o", cksum); cp = &t_lab.path[0]; for(i=0; i<512; i++) putchar(*cp++); // push out the TOC header for(i = toc_filesize + (512 - (toc_filesize % 512)); i; i--) putchar(fgetc(toc_file)); // push out the TOC contents /* * walk the file list, creating all the file entries */ p = d_tbl; for(i = 0, p=d_tbl; i < actual_fblks; i++, p++ ){ memset(&t_lab,0 , sizeof(tarlbl)); sprintf(t_lab.path,"%s%s",prefix_string,p->path); sprintf(t_lab.mode,"%07o", p->mode); sprintf(t_lab.uid,"%07o", p->uid); sprintf(t_lab.gid,"%07o", p->gid); sprintf(t_lab.size,"%011o", p->fsize); sprintf(t_lab.mtime, "%011o", p->mod_t); memset(&t_lab.cksum, 0x20, sizeof(t_lab.cksum)); // ENTIRE field set to 0x20 if(p->mode & 0100000) sprintf(t_lab.ftyp, "%2d", 0); if(p->mode & 0040000) sprintf(t_lab.ftyp, "%2d", 5); sprintf(t_lab.ustar, "ustar "); // sprintf(t_lab.unam, ""); // sprintf(t_lab.gnam, ""); cp = &t_lab.path[0]; cksum = 0; for(j=0; j<512; j++) cksum += *cp++; sprintf(t_lab.cksum, "%06o", cksum); cp = &t_lab.path[0]; for(j=0; j<512; j++) putchar(*cp++); // push out the file contents if(p->fsize){ cp = p->f_data; for(j = p->f_len; j; j--) putchar(*cp++); } } }