/* * Bobbi January 2020 * * TODO: Fix bugs!!!! * TODO: Sort by type, combinations etc. * TODO: Improve checking code */ #pragma debug 25 /* Enable stack checking */ #pragma lint -1 #include #include #include #include #include #include #include #include #include #include #define DEBUG /* Enable additional debug printout */ #define CHECK /* Perform additional integrity checking */ typedef unsigned char uchar; typedef unsigned int uint; #define NMLEN 15 /* Length of filename */ #define MAXFILES 1000 /* Max files per directory */ /* * ProDOS directory header * See ProDOS-8 Tech Ref pp. 152 */ struct pd_dirhdr { uchar typ_len; char name[NMLEN]; char reserved[8]; uchar ctime[4]; uchar vers; uchar minvers; uchar access; uchar entlen; uchar entperblk; uchar filecnt[2]; uchar parptr[2]; /* Bitmap pointer in volume dir */ uchar parentry; /* Total blocks LSB in volume dir */ uchar parentlen; /* Total blocks MSB in volume dir */ }; /* * ProDOS file entry * See ProDOS-8 Tech Ref pp. 155 */ struct pd_dirent { uchar typ_len; char name[NMLEN]; uchar type; uchar keyptr[2]; uchar blksused[2]; uchar eof[3]; uchar ctime[4]; uchar vers; uchar minvers; uchar access; uchar auxtype[2]; uchar mtime[4]; uchar hdrptr[2]; }; #define BLKSZ 512 /* 512 byte blocks */ #define PTRSZ 4 /* 4 bytes of pointers at beginning of each blk */ /* * Linked list of directory blocks read from disk * Original directory block is stored in data[] * Directory block for sorted directory is stored in sorteddata[] */ struct block { char data[BLKSZ]; /* Original contents of block */ char sorteddata[BLKSZ]; /* Content block for sorted dir */ uint blocknum; /* Block number on disk */ struct block *next; }; struct block *blocks = NULL; /* * Entry for array of filenames used by qsort() */ struct fileent { char name[NMLEN+1]; /* Name converted to upper/lower case */ uchar blockidx; /* Index of dir block (1,2,3 ...) */ uchar entrynum; /* Entry within the block */ }; /* Globals */ static struct fileent filelist[MAXFILES]; static uint numfiles = 0; static uchar entsz; /* Bytes per file entry */ static uchar entperblk; /* Number of entries per block */ static char buf[BLKSZ]; /* General purpose scratch buffer */ static uchar dowrite = 0; static uchar doreverse = 0; static uint stack; // DEBUG //#define STACK(msg) asm {tsc; sta stack }; fputs(msg, stdout); fputs(" ", stdout); pr_int(stack); putchar('\n'); /* Prototypes */ void prerr(char *s); void reverse(char *s); void itoa(int n, char s[]); void uitoa(uint n, char s[]); void pr_int(int a); void pr_uint(uint a); void print_uint(uint i); int readdiskblock(uchar device, uint blocknum, char *buf); int writediskblock(uchar device, uint blocknum, char *buf); void fixcase(char *in, char *out, uchar minvers, uchar vers); int readdir(uint device, uint blocknum); int compare(const void *a, const void *b); void sortlist(void); void printlist(void); void copyent(uint srcblk, uint srcent, uint dstblk, uint dstent, uint device); void sortblocks(uint device); int writedir(uchar device); void freeblocks(void); void usage(void); void prerr(char *s) { fputs(s, stderr); fputs("\n", stderr); } /* Reverse the characters of the string s */ void reverse(char *s) { char buf[10]; int l = strlen(s); for (int i=0; i 0); if (sign < 0) s[i++] = '-'; s[i] = '\0'; reverse(s); } /* Unsigned verson of itoa() */ void uitoa(uint n, char s[]) { int i; i = 0; do { s[i++] = n % 10 + '0'; } while ((n /= 10) > 0); s[i] = '\0'; reverse(s); } /* Print an integer */ void pr_int(int a) { char buf[10]; itoa(a, buf); fputs(buf, stdout); } /* Print an unsigned integer */ void pr_uint(unsigned int a) { char buf[10]; uitoa(a, buf); fputs(buf, stdout); } /* * Read block from disk using ProDOS call * buf must point to buffer with at least 512 bytes */ int readdiskblock(uchar device, uint blocknum, char *buf) { #ifdef DEBUG fputs("Reading dev ", stdout); pr_int(device); fputs(" block ", stdout); pr_uint(blocknum); putchar('\n'); #endif BlockRec br; br.blockDevNum = device; br.blockDataBuffer = buf; br.blockNum = blocknum; READ_BLOCK(&br); int rc = toolerror(); if (rc) { fprintf(stderr, "READ_BLOCK failed, err=%x\n", rc); return -1; } return 0; } /* * Write block from disk using ProDOS call * buf must point to buffer with at least 512 bytes */ int writediskblock(uchar device, uint blocknum, char *buf) { #ifdef DEBUG fputs("Writing dev ", stdout); pr_int(device); fputs(" block ", stdout); pr_uint(blocknum); putchar('\n'); #endif DIORecGS dr; dr.pCount = 6; dr.devNum = device; dr.buffer = buf; dr.requestCount = BLKSZ; dr.startingBlock = blocknum; dr.blockSize = BLKSZ; DWriteGS(&dr); if (dr.transferCount != BLKSZ) return -1; return 0; } /* * Uses the vers and minvers fields of the directory entry * as a bitmap representing which characters are upper and which are * lowercase */ void fixcase(char *in, char *out, uchar minvers, uchar vers) { uchar idx = 0; if (!(vers&0x80)) { for (idx=0; idxtyp_len & 0xf0) == 0xf0) { *block = 2; goto ret; } #ifdef CHECK if ((hdr->typ_len & 0xf0) != 0xe0) { prerr("Bad storage type"); rv = 1; goto ret; } #endif /* Handle subdirectory */ uint parentblk = hdr->parptr[0] + 256U * hdr->parptr[1]; uint parententry = hdr->parentry; uint parententlen = hdr->parentlen; /* Read parent directory block */ if (readdiskblock(*device, parentblk, buf) == -1) { fprintf(stderr, "Can't read parent directory for %s", dirname); rv = 1; goto ret; } struct pd_dirent *ent = (struct pd_dirent *)(buf + PTRSZ + (parententry-1) * parententlen); *block = ent->keyptr[0] + 256U * ent->keyptr[1]; ret: if (fp) close(fp); return rv; } /* * Read a directory, store the raw directory blocks in a linked list * and build filelist[] in preparation for sorting. * device is the device number containing the directory * blocknum is the block number of the first block of the directory */ int readdir(uint device, uint blocknum) { uchar blkcnt = 1; uint hdrblknum = blocknum; blocks = (struct block*)malloc(BLKSZ); if (!blocks) { prerr("Unable to allocate memory"); return 1; } struct block *curblk = blocks; curblk->next = NULL; curblk->blocknum = blocknum; if (readdiskblock(device, blocknum, curblk->data) == -1) { fprintf(stderr, "Error reading dir block %d\n", blkcnt); return 1; } struct pd_dirhdr *hdr = (struct pd_dirhdr*)(curblk->data + PTRSZ); entsz = hdr->entlen; entperblk = hdr->entperblk; uint filecount = hdr->filecnt[0] + 256U * hdr->filecnt[1]; /* Copy pointers and header to sorteddata[], zero the rest */ bzero(curblk->sorteddata, BLKSZ); memcpy(curblk->sorteddata, curblk->data, PTRSZ + entsz); #ifdef DEBUG fputs("entsz=", stdout); pr_uint(entsz); putchar('\n'); fputs("entperblk=", stdout); pr_uint(entperblk); putchar('\n'); fputs("filecount=", stdout); pr_uint(filecount); putchar('\n'); #endif // TODO: Add a check that the filecount is correct #ifdef CHECK if (entsz != 0x27) { prerr("Error - bad entry size"); return 1; } if (entperblk != 0x0d) { prerr("Error - bad entries/block"); return 1; } #endif uint idx = entsz + PTRSZ; /* Skip header */ uchar blkentries = 2; uchar entries = 0; static char namebuf[NMLEN]; while (entries < filecount) { struct pd_dirent *ent = (struct pd_dirent*)(curblk->data + idx); if (ent->typ_len != 0) { fixcase(ent->name, namebuf, ent->vers, ent->minvers); for (uchar i=0; ityp_len & 0x0f); ++i) filelist[numfiles].name[i] = namebuf[i]; filelist[numfiles].blockidx = blkcnt; filelist[numfiles].entrynum = blkentries; #ifdef CHECK uint keyblk = ent->keyptr[0] + 256U * ent->keyptr[1]; uint hdrblk = ent->hdrptr[0] + 256U * ent->hdrptr[1]; if (hdrblk != hdrblknum) { fprintf(stderr, "%s: Header ptr %u should be %u\n", namebuf, hdrblk, hdrblknum); } if ((ent->typ_len & 0xf0) == 0xd0) { if (readdiskblock(device, keyblk, buf) == -1) { fprintf(stderr, "Error reading blk %u\n", keyblk); return 1; } hdr = (struct pd_dirhdr*)(buf + PTRSZ); uchar parentry = hdr->parentry; uchar parentlen = hdr->parentlen; uint parblk = hdr->parptr[0] + 256U * hdr->parptr[1]; if (parblk != blocknum) { prerr("Bad parent block number"); return 1; } if (parentry != blkentries) { prerr("Bad parent block entry num"); return 1; } if (parentlen != 0x27) { prerr("Bad parent entry length"); return 1; } char *dirname = buf + 0x05; if (strncmp(dirname, ent->name, NMLEN)) { prerr("Subdirectory name mismatch"); return 1; } } #endif ++numfiles; if (numfiles == MAXFILES) { prerr("Too many files"); return 1; } ++entries; } if (entries < filecount) { if (blkentries == entperblk) { blocknum = curblk->data[0x02] + 256U*curblk->data[0x03]; curblk->next = (struct block*)malloc(BLKSZ); if (!curblk->next) { prerr("Unable to allocate memory"); return 1; } curblk = curblk->next; curblk->next = NULL; curblk->blocknum = blocknum; ++blkcnt; if (readdiskblock(device, blocknum, curblk->data) == -1) { fprintf(stderr, "Error reading dir block %d\n", blkcnt); return 1; } /* Copy ptrs to sorteddata[], zero the rest */ bzero(curblk->sorteddata, BLKSZ); memcpy(curblk->sorteddata, curblk->data, PTRSZ); blkentries = 1; idx = PTRSZ; } else { ++blkentries; idx += entsz; } } } return 0; } /* * Compare two filenames */ int compare(const void *a, const void *b) { int rc; rc = strncmp(((struct fileent*)a)->name, ((struct fileent*)b)->name, 15); return (doreverse ? !rc : rc); } /* * Sort filelist[] */ void sortlist(void) { qsort(filelist, numfiles, sizeof(struct fileent), compare); } /* * Print the file info stored in filelist[] */ void printlist(void) { fputs("numfiles=", stdout); pr_int(numfiles); putchar('\n'); for(uint i=0; i 0) source = source->next; while (--dstblk > 0) dest = dest->next; char *dstptr = dest->sorteddata + PTRSZ + (dstent-1) * entsz; memcpy(dstptr, source->data + PTRSZ + (srcent-1) * entsz, entsz); /* For directories, update the parent dir entry number */ if ((*dstptr & 0xf0) == 0xd0) { struct pd_dirent *ent = (struct pd_dirent *)dstptr; uint block = ent->keyptr[0] + 256U * ent->keyptr[1]; if (readdiskblock(device, block, buf) == -1) { prerr("Can't read subdirectory"); exit(1); } struct pd_dirhdr *hdr = (struct pd_dirhdr*)(buf + PTRSZ); hdr->parentry = dstent; if (dowrite) { if (writediskblock(device, block, buf) == -1) { prerr("Can't write subdirectory"); exit(1); } } else { puts("Not writing updated subdir to disk"); } } } /* * Use the sorted list in filelist[] to create a sorted set of directory * blocks. Note that the block and entry numbers are 1-based indices. */ void sortblocks(uint device) { uchar destblk = 1; uchar destentry = 2; /* Skip header on first block */ for(uint i=0; iblocknum, i->sorteddata) == -1) { fprintf(stderr, "Can't write block %u\n", i->blocknum); return 1; } i = i->next; } return 0; } /* * Walk through the linked list freeing memory */ void freeblocks(void) { struct block *i = blocks, *j; while (i) { j = i->next; free(i); i = j; } } void usage(void) { prerr("usage: sortdir [-w] [-r] path\n"); prerr(" Flags: -w Enable writing to disk"); prerr(" -r Reverse sort order\n"); exit(2); } int main(int argc, char *argv[]) { if (argc < 2) { usage(); exit(1); } int opt; while ((opt = getopt(argc, argv, "rw")) != -1) { switch (opt) { case 'r': doreverse = 1; break; case 'w': dowrite = 1; break; default: usage(); } } if (optind >= argc) usage(); uchar dev; uint blk; if (firstblk(argv[optind], &dev, &blk) != 0) { exit(1); } uchar err = readdir(dev, blk); if (!err) { printlist(); sortlist(); sortblocks(dev); if (dowrite) err = writedir(dev); else puts("Not writing to disk"); } freeblocks(); return err; }