/* * aar.c a program to read and write alto file systems L. Stewart 12-3-92 */ /* * Modifications L. Stewart 1/18/93 add z switch to read compressed files */ /* * endian issues: for big endian machines, reverse all 16-bit shorts in the * file read in, then do not reverse 16-bit shorts in the strings */ #include /*#include */ /*#include */ #include #include #include #include #include #include #define NPAGES 4872 typedef unsigned short word; typedef unsigned char byte; struct PAGE { word pagenum; word header[2]; word label[8]; word data[256]; }; struct LABEL { word nextRDA; word prevRDA; word unused1; /* always 0 ? */ word nbytes; /* up to 512 */ word filepage; /* from 0 */ word fid[3]; /* see below */ }; struct TIME { word altotime[2]; }; struct SN { word sn[2]; }; struct FP { struct SN serialNumber; word version; word blank; word leaderVDA; }; struct FA { word vda; word pageNumber; word charPos; }; struct LEADER { struct TIME created; struct TIME written; struct TIME read; char filename[40]; word leaderProps[210]; word spare[10]; /* for 256 word pages */ byte proplength; /* 1 word */ byte propbegin; byte changeSN; /* 1 word */ byte consecutive; struct FP dirFpHint; /* 5 words */ struct FA lastPageHint; /* 3 words */ }; /* fid[0] is 1 for all used files, ffff for free pages */ /* fid[1] is 8000 for a directory, 0 for regular, ffff for free */ /* fid[2] is the fileid */ struct DV { word typelength; struct FP fileptr; char filename[40]; /* not all used */ }; /* header for the DiskDescriptor file */ struct KDH { word nDisks; /* how many disks in the fs */ word nTracks; /* how big is each disk */ word nHeads; /* how many heads */ word nSectors; /* how many sectors */ struct SN lastSN; /* last SN used on disk */ word blank; /* formerly bitTableChanged */ word diskBTsize; /* number valid words in the bit table */ word defVersionsKept; /* 0 implies no multiple versions */ word freePages; /* pages left */ word blank1[6]; }; /* storage for disk allocation datastructures */ struct KDH kdh; /* disk descriptor */ word *bitTable; /* pages allocated */ /* storage for the disk image for dp0 and dp1 */ struct PAGE disk[NPAGES * 2]; extern void dump_headers (); extern void dump_directory (); extern void extract_file (int leader_page_number); extern void extract_files (int argc, char *argv[]); extern void table_files (int argc, char *argv[]); extern int find_file (char *name); extern void dump_leader_pages (); extern int Verify_Headers (); extern word RDAtoVDA (word); extern word VDAtoRDA (word); extern void copystring (char *to, char *from, int count, int lower); extern void swabit (char *data, int count); extern int getword (struct FA *fa); extern int ValidateDiskDescriptor (); extern void FixDiskDescriptor (); extern int getBT (int page); /* get bit from free page bit table */ extern void setBT (int page, int new); /* set bit in bit table */ extern void delete_file (int leader_page_VDA); extern void extract_all_files (); extern void print_file_times (int leader_page_VDA); extern void table_file (int leader_page_VDA, struct DV *dv); extern void table_all_files (); extern void print_alto_time (); extern void ReadDiskFile (char *name); extern void ReadSingleDisk (char *name, struct PAGE *diskp); /* general utilities */ int Assert (int book, char *errmsg,...); /* printf style */ void AssertOrDie (int book, char *errmsg,...); /* printf style */ /* actual procedures */ struct LEADER * pageLeader (int vda) { return ((struct LEADER *) &disk[vda].data[0]); } struct LABEL * pageLabel (int vda) { return ((struct LABEL *) &disk[vda].label[0]); } short int little = 1; /* endian test */ int lflag = 0; /* dump leader pages */ int xflag = 0; /* extract files */ int tflag = 0; /* list files */ int vflag = 0; /* verbose mode */ int bflag = 0; /* extract binary file */ int zflag = 0; /* work from compressed disk image */ int doubledisk = 0; /* double disk system */ main (int argc, char *argv[]) { int got, totalgot; FILE *infile; int i; /* process arguments */ /* aar [xt][v] diskimage [file...] */ /* new flag 'b' for binary, which applies to extract */ char *flags; if (argc < 3) { printf ("Usage: aar [xt][v] diskimage [file...] \n"); exit (1); } flags = argv[1]; while (*flags) { switch (*flags) { case 'l': lflag++; break; case 't': tflag++; break; case 'x': xflag++; break; case 'v': vflag++; break; case 'b': bflag++; break; case 'z': zflag++; break; default: AssertOrDie (0, "Unknown flag %c\n", *flags); break; } flags++; } AssertOrDie (!(tflag && xflag), "Illegal flag combination\n"); ReadDiskFile (argv[2]); if ((*(char *) &little) == 0) swabit ((char *) disk, sizeof (disk)); /* AssertOrDie (Verify_Headers (), "Disk Scrambled, header verify failed\n"); */ Assert(Verify_Headers (), "Disk Scrambled, header verify failed\n"); /* ValidateDiskDescriptor (); */ if (lflag) dump_leader_pages (); if (tflag) table_files (argc, argv); if (xflag) extract_files (argc, argv); } /******************************/ /* Main work-doing procedures */ /******************************/ void ReadDiskFile (char *name) { char *dp0name = NULL; char *dp1name = NULL; dp0name = name; dp1name = strchr (name, ','); if (dp1name != NULL) { *dp1name = 0; dp1name += 1; } ReadSingleDisk (dp0name, &disk[0]); doubledisk = dp1name != NULL; if (doubledisk) ReadSingleDisk (dp1name, &disk[4872]); } void ReadSingleDisk (char *name, struct PAGE *diskp) { FILE *infile; int bytes; int totalbytes = 0; int total = NPAGES * sizeof (struct PAGE); char *dp = (char *) diskp; /* * We conclude the disk image is compressed if either the zflag is set or * if the name ends with .Z */ if (zflag || (strstr (name, ".Z") == (name + strlen (name) - 2))) { char *cmd; cmd = malloc (strlen (name) + 10); sprintf (cmd, "zcat %s", name); infile = popen (cmd, "r"); AssertOrDie (infile != NULL, "popen failed on zcat %s\n", name); free (cmd); } else { infile = fopen (name, "rb"); AssertOrDie (infile != NULL, "open failed on Alto disk image file %s\n", name); } while (totalbytes < total) { bytes = fread (dp, sizeof (char), total - totalbytes, infile); dp += bytes; totalbytes += bytes; Assert (!(ferror (infile) || feof (infile)), "disk read failed: %d bytes read instead of %d\n", totalbytes, total); } } void dump_disk_block (int page) { int row, col; word d; char str[17], c; for (row = 0; row < 16; row += 1) { printf ("%04x:", row * 8); for (col = 0; col < 8; col += 1) { printf (" %04x", disk[page].data[(row * 8) + col]); } for (col = 0; col < 8; col += 1) { d = disk[page].data[(row * 8) + col]; c = (d >> 8) & 0x7f; str[(col * 2)] = (isprint (c)) ? c : ' '; c = (d) & 0x7f; str[(col * 2) + 1] = (isprint (c)) ? c : ' '; } str[16] = 0; printf (" %16s\n", str); } } void dump_leader_pages () { int i, j, bad, length, last; char fn[42]; struct LABEL *l; struct LEADER *lp; bad = 0; last = (doubledisk) ? NPAGES * 2 : NPAGES; for (i = 0; i < last; i += 1) { l = pageLabel (i); lp = pageLeader (i); if ((l->filepage == 0) && (l->fid[0] == 1)) { copystring (fn, lp->filename, 40, 0); length = fn[0]; if (length > 39) length = 39; length -= 1; /* erase closing '.' */ fn[length + 1] = 0; if (!vflag) printf ("%s\n", &fn[1]); else { /* time conversion here */ printf ("%s ", &fn[1]); print_file_times (i); printf ("\n"); /* dump_disk_block (i); */ } } } } void dump_headers () { int i, j, last; last = (doubledisk) ? NPAGES * 2 : NPAGES; for (i = 0; i < last; i += 1) { printf ("%04x-%04x %04x", disk[i].pagenum, disk[i].header[0], disk[i].header[1]); for (j = 0; j < 8; j += 1) printf ("-%04x", disk[i].label[j]); printf ("\n"); } } char *monthnames[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; void print_alto_time (struct TIME t) { time_t time; struct tm *ltm; time = t.altotime[1] + (t.altotime[0] << 16); time += 2117503696; /* magic value to convert to Unix epoch */ ltm = localtime (&time); /* like 4-Jun-80 17:14:36 */ printf ("%02d-%s-%02d %2d:%02d:%02d", ltm->tm_mday, monthnames[ltm->tm_mon], ltm->tm_year, ltm->tm_hour, ltm->tm_min, ltm->tm_sec); } void print_file_times (int leader_page_VDA) { struct LABEL *l; struct LEADER *lp; l = pageLabel (leader_page_VDA); lp = pageLeader (leader_page_VDA); printf ("cr: "); print_alto_time (lp->created); printf (" wr: "); print_alto_time (lp->written); printf (" rd: "); print_alto_time (lp->read); } void dump_directory () { int i, w, length, j, valid; struct LABEL *l; struct FA fa; struct DV *dv; char fn[42]; word dvspace[sizeof (struct DV) / 2]; l = pageLabel (1); fa.vda = RDAtoVDA (l->nextRDA); fa.pageNumber = 1; fa.charPos = 0; dv = (struct DV *) &dvspace[0]; for (;;) { w = getword (&fa); if (w < 0) return; /* EOF on directory */ dvspace[0] = w; valid = (((dv->typelength >> 10) & 0x3f) == 1); for (i = 1; i < (dvspace[0] & 0x3ff); i += 1) { w = getword (&fa); AssertOrDie (w >= 0, "unexpected EOF on directory!\n"); if (valid && (i < (sizeof (struct DV) / 2))) dvspace[i] = w; } if (valid) { copystring (fn, dv->filename, 40, 0); length = fn[0]; if (length > 39) length = 39; length -= 1; /* erase final . */ fn[length + 1] = 0; if (!vflag) printf ("%s\n", &fn[1]); else { printf ("%20s ", &fn[1]); print_file_times (dv->fileptr.leaderVDA); printf ("\n"); } } } } int file_length (int leader_page_VDA) { int length = 0; int filepage; struct LABEL *label; filepage = leader_page_VDA; while (filepage != 0) { label = pageLabel (filepage); length = length + label->nbytes; if (label->nbytes < 512) break; filepage = RDAtoVDA (label->nextRDA); } return (length); } void name_from_dv (struct DV *dv, char *fn) { char myfn[42]; int length; copystring (myfn, dv->filename, 40, 0); length = myfn[0]; if (length > 39) length = 39; length -= 1; /* erase final . */ myfn[length + 1] = 0; strcpy (fn, &myfn[1]); } void name_from_leader (struct LEADER *lp, char *fn) { char myfn[42]; int length; copystring (myfn, lp->filename, 40, 0); length = myfn[0]; if (length > 39) length = 39; length -= 1; /* erase final . */ myfn[length + 1] = 0; strcpy (fn, &myfn[1]); } void table_file (int leader_page_VDA, struct DV *dv) { struct LABEL *l; struct LEADER *lp; int length; char fn[42]; if (dv) leader_page_VDA = dv->fileptr.leaderVDA; lp = pageLeader (leader_page_VDA); if (vflag) { length = file_length (leader_page_VDA); printf ("%8d ", length); /* printf("create: "); */ print_alto_time (lp->created); /* * printf(" written: "); print_alto_time(lp->written); printf(" read: "); * print_alto_time(lp->read); */ } if (dv) name_from_dv (dv, fn); /* print file name from dv */ else name_from_leader (lp, fn); /* print file name from leader page */ printf (" %s\n", fn); } void extract_files (int argc, char *argv[]) { int i, lp; if (argc == 3) extract_all_files (); else { for (i = 3; i < argc; i += 1) { lp = find_file (argv[i]); extract_file (lp); } } } void table_files (int argc, char *argv[]) { int i, lp; if (argc == 3) table_all_files (); else { for (i = 3; i < argc; i += 1) { lp = find_file (argv[i]); /* table_file(lp); */ } } } void delete_files (int argc, char *argv[]) { int i, lp; AssertOrDie (argc > 3, "delete command must specify files\n"); for (i = 3; i < argc; i += 1) { lp = find_file (argv[i]); delete_file (lp); } } /**********************************/ /* general disk untility routines */ /**********************************/ word RDAtoVDA (word rda) { word vda, head, sector, cylinder; sector = ((rda >> 12) & 0xf); head = ((rda >> 2) & 1); cylinder = ((rda >> 3) & 0x1ff); vda = (cylinder * 24) + (head * 12) + sector; if(rda & 2) vda += NPAGES; return (vda); } word VDAtoRDA (word vda) { int rda; int head, sector, cylinder; sector = vda % 12; head = (vda / 12) & 1; cylinder = vda / 24; rda = (cylinder << 3) + (head << 2) + (sector << 12); if(vda >= NPAGES) rda |= 2; return (rda); } int find_file (char *name) { /* search directory for file and return leader page VDA */ int i, j, bad, length, last; char fn[42], *s; struct LABEL *l; struct LEADER *lp; /* use linear search ! */ last = (doubledisk) ? NPAGES * 2 : NPAGES; for (i = 0; i < last; i += 1) { l = pageLabel (i); lp = pageLeader (i); if ((l->filepage == 0) && (l->fid[0] == 1)) { copystring (fn, lp->filename, 40, 1); length = fn[0]; if (length > 39) length = 39; length -= 1; /* erase closing '.' */ fn[length + 1] = 0; s = name; /* while (*s) { *s = tolower (*s); s++; } if (strcmp (name, &fn[1]) == 0) return (i); */ } } /* AssertOrDie(0, "file %s not found\n", name); */ return (-1); } void delete_file (int leader_page_VDA) { } void extract_all_files () { int i, last; struct LABEL *l; last = (doubledisk) ? NPAGES * 2 : NPAGES; for (i = 0; i < last; i += 1) { l = (struct LABEL *) &disk[i].label[0]; if ((l->filepage == 0) && (l->fid[0] == 1)) extract_file (i); } } void table_all_files () { int i, last; struct LABEL *l; last = (doubledisk) ? NPAGES * 2 : NPAGES; for (i = 0; i < last; i += 1) { l = (struct LABEL *) &disk[i].label[0]; if ((l->filepage == 0) && (l->fid[0] == 1)) { printf("%8d ", i); table_file(i, NULL); } } } void extract_file (int leader_page_VDA) { int j, length; int ofd; char fn[42]; struct LABEL *l; struct LEADER *lp; int filepage, bytes; l = pageLabel (leader_page_VDA); lp = pageLeader (leader_page_VDA); AssertOrDie (l->filepage == 0, "extract_file, page %d is not a leader page!\n", leader_page_VDA); copystring (fn, lp->filename, 40, 0); length = fn[0]; if (length > 39) length = 39; length -= 1; /* erase final . */ fn[length + 1] = 0; if (vflag) printf ("x %s\n", &fn[1]); ofd = open (&fn[1], O_WRONLY | O_CREAT | O_TRUNC, 0666); AssertOrDie (ofd >= 0, "open for write failed on %s\n", &fn[1]); while (l->nextRDA != 0) { filepage = RDAtoVDA (l->nextRDA); l = pageLabel (filepage); bytes = write (ofd, (char*)&disk[filepage].data[0], l->nbytes); AssertOrDie (bytes == l->nbytes, "write to %s failed!\n", &fn[1]); } close (ofd); } int altotometotime (struct TIME at) { } int getword (struct FA *fa) { struct LABEL *l; int w; l = pageLabel (fa->vda); AssertOrDie ((fa->charPos & 1) == 0, "getword called on odd byte boundary\n"); if (fa->charPos >= l->nbytes) { if ((l->nextRDA == 0) || (l->nbytes < 512)) return (-1); fa->vda = RDAtoVDA (l->nextRDA); l = pageLabel (fa->vda); fa->pageNumber += 1; fa->charPos = 0; } AssertOrDie (fa->pageNumber == l->filepage, "disk corruption - expected vda %d to be filepage %d\n", fa->vda, l->filepage); w = disk[fa->vda].data[fa->charPos >> 1]; fa->charPos += 2; return (w); } /* don't think we need this routine anyway */ void putword (struct FA *fa, word w) { struct LABEL *l; l = pageLabel (fa->vda); AssertOrDie ((fa->charPos & 1) == 0, "putword called on odd byte boundary\n"); /* * case 1: writing in the middle of an existing file, on a page with more * bytes than the one we're at case 2: extending the last page of a file, * changing nbytes as we go case 3: extending past the last page, need to * allocate a new one */ /* case 1, existing page, in the middle */ } /**********************************************/ /* Disk page allocation, DiskDescriptor, etc. */ /**********************************************/ int getBT (int page) { int bit; /* * the bit table is big endian, so page 0 is in bit 15, page 1 is in bit * 15, and page 15 is in bit 0 */ bit = 15 - (page % 16); return ((bitTable[page / 16] >> bit) & 1); } void setBT (int page, int new) { int w, bit; w = page / 16; bit = 15 - (page % 16); bitTable[w] &= ~(1 << bit); bitTable[w] |= (new != 0) << bit; } int pagefree (int page) { struct LABEL *l; l = pageLabel (page); return ((l->fid[0] == 0xffff) && (l->fid[1] == 0xffff) && (l->fid[2] == 0xffff)); } /* Sanity Checking */ /* make sure that each page header refers to itself */ int Verify_Headers () { int i, ok, last; ok = 1; return(ok); last = (doubledisk) ? NPAGES * 2 : NPAGES; for (i = 0; i < last; i += 1) ok &= Assert (disk[i].pagenum == RDAtoVDA (disk[i].header[1]), "page %04x header doesn't match: %04x %04x\n", disk[i].pagenum, disk[i].header[0], disk[i].header[1]); return (ok); } int ValidateDiskDescriptor () { /* * check numdisks * */ int ddlp, i, page, bit, free, ok, last; struct LEADER *lp; struct LABEL *l; struct FA fa; /* locate DiskDescriptor and copy it into the global data structure */ ddlp = find_file ("DiskDescriptor"); ok = Assert (ddlp != -1, "Can't find DiskDescriptor\n"); if (!ok) return (ok); lp = pageLeader (ddlp); l = pageLabel (ddlp); fa.vda = RDAtoVDA (l->nextRDA); bcopy (&disk[fa.vda].data[0], &kdh, sizeof (kdh)); bitTable = (word *) malloc (kdh.diskBTsize * sizeof (word)); /* now copy the bit table from the disk into bitTable */ fa.pageNumber = 1; fa.charPos = sizeof (kdh); for (i = 0; i < kdh.diskBTsize; i += 1) bitTable[i] = getword (&fa); /* for single disk systems, (only one supported now) */ ok &= Assert (kdh.nDisks == 1, "only support single disk systems\n"); ok &= Assert (kdh.nTracks == 203, "KDH tracks != 203\n"); ok &= Assert (kdh.nHeads == 2, "KDH heads != 2\n"); ok &= Assert (kdh.nSectors == 12, "KDH sectors != 12\n"); ok &= Assert (kdh.defVersionsKept == 0, "defaultVersions != 0\n"); /* count free pages in bit table */ free = 0; last = (doubledisk) ? NPAGES * 2 : NPAGES; for (page = 0; page < last; page += 1) free += (getBT (page) == 0); ok &= Assert (free == kdh.freePages, "bit table count %d doesn't match KDH free pages %d\n", free, kdh.freePages); /* count free pages in actual image */ free = 0; for (page = 0; page < last; page += 1) free += pagefree (page); ok &= Assert ((free == kdh.freePages), "actual free page count %d doesn't match KDH value %d\n", free, kdh.freePages); return (ok); } void FixDiskDescriptor () { int page, free, t, last; /* rebuild bit table and free page count from labels */ free = 0; last = (doubledisk) ? NPAGES * 2 : NPAGES; for (page = 0; page < last; page += 1) { t = pagefree (page); free += t; setBT (page, t ^ 1); } kdh.freePages = free; } /****************************/ /* general support routines */ /****************************/ int Assert (int bool, char *errmsg, ...) { va_list ap; if (!bool) { va_start (ap, errmsg); vprintf (errmsg, ap); va_end (ap); } return (bool); } void AssertOrDie (int bool, char *errmsg, ...) { va_list ap; if (!bool) { va_start (ap, errmsg); vprintf (errmsg, ap); va_end (ap); exit (1); } } void swabit (char *data, int count) { word junk, *d; AssertOrDie (((count & 1) == 0) && (((long) data & 1) == 0), "swab called with unaligned values\n"); count >>= 1; d = (word *) data; while (count--) { junk = *d; junk = ((junk >> 8) & 0xff) | (junk << 8); *d++ = junk; } } void copystring (char *to, char *from, int length, int lower) { int i; char c; for (i = 0; i < length; i += 1) { if (*(char *) &little) c = from[i ^ 1]; else c = from[i]; if (lower) c = tolower (c); to[i] = c; } }