aar.c: a program to read and write alto file systems

AttachmentSize
Package icon ALTO AAR.C.zip6.7 KB
/*
 * 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 <fcntl.h>
/*#include <sys/stat.h>*/
/*#include <sys/types.h>*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <time.h>

#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 <name> 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;
}
}