GNO-Extras/bobbi/sortdir.c#b00008
2020-02-06 20:21:35 -05:00

796 lines
19 KiB
Plaintext

/*
* Bobbi January 2020
*
* TODO: Improve filesystem checking code
* TODO: Improve output
*/
#pragma debug 25 /* Enable stack checking */
#pragma lint -1
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <orca.h>
#include <gsos.h>
#include <prodos.h>
#undef 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;
};
/*
* Entry for array of filenames used by qsort()
*/
struct fileent {
char name[NMLEN+1];/* Name converted to upper/lower case */
uchar type; /* ProDOS file type */
uint order; /* Hack to make qsort() stable */
uchar blockidx; /* Index of dir block (1,2,3 ...) */
uchar entrynum; /* Entry within the block */
};
/* Globals */
static struct block *blocks = NULL;
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 doverbose = 0;
static char sortopts[5] = "n";
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 cmp_name_asc(const void *a, const void *b);
int cmp_name_desc(const void *a, const void *b);
int cmp_type_asc(const void *a, const void *b);
int cmp_type_desc(const void *a, const void *b);
int cmp_dir_beg(const void *a, const void *b);
int cmp_dir_end(const void *a, const void *b);
void sortlist(char s);
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);
/* Print error string to stderr */
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<l; ++i)
buf[i] = s[i];
for (int i=0; i<l; ++i)
s[i] = buf[l-i-1];
}
/* Stolen from K&R 2nd Ed. pp 64 */
void itoa(int n, char s[]) {
int i, sign;
if ((sign = n) < 0)
n = -1;
i = 0;
do {
s[i++] = n % 10 + '0';
} while ((n /= 10) > 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; idx<NMLEN; ++idx)
out[idx] = in[idx];
return;
}
vers <<= 1;
for (int i=0; i<7; ++i) {
out[idx++] = ((vers&0x80) ? tolower(in[idx]) : in[idx]);
vers <<= 1;
}
for (int i=0; i<8; ++i) {
out[idx++] = ((minvers&0x80) ? tolower(in[idx]) : in[idx]);
minvers <<= 1;
}
}
/*
* Read the first block of a directory and deduce the device ID and block
* number of the first block of the directory.
*/
uchar firstblk(char *dirname, uchar *device, uint *block) {
int fp;
uchar rv = 0;
fp = open(dirname, O_RDONLY);
if (!fp) {
fprintf(stderr, "Error opening dir %s\n", dirname);
rv = 1;
goto ret;
}
ssize_t len = read(fp, buf, BLKSZ);
if (len != BLKSZ) {
fprintf(stderr, "Error reading first block of dir %s", dirname);
rv = 1;
goto ret;
}
struct stat st;
if (stat(dirname, &st) == -1) {
fprintf(stderr, "Can't stat %s\n", dirname);
exit(1);
}
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "%s is not a directory\n", dirname);
exit(1);
}
*device = st.st_dev;
struct pd_dirhdr *hdr = (struct pd_dirhdr*)(buf + PTRSZ);
/* Detect & handle volume directory */
if ((hdr->typ_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(sizeof(struct block));
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; i<NMLEN+1; ++i)
filelist[numfiles].name[i] = '\0';
for (uchar i=0; i<(ent->typ_len & 0x0f); ++i)
filelist[numfiles].name[i] = namebuf[i];
filelist[numfiles].type = ent->type;
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(sizeof(struct block));
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 - filename sort in ascending order
*/
int cmp_name_asc(const void *a, const void *b) {
return strncmp(((struct fileent*)a)->name,
((struct fileent*)b)->name, NMLEN);
}
/*
* Compare - filename sort in descending order
*/
int cmp_name_desc(const void *a, const void *b) {
return strncmp(((struct fileent*)b)->name,
((struct fileent*)a)->name, NMLEN);
}
/*
* Compare - type sort in ascending order
* Uses the order field to make qsort() stable
*/
int cmp_type_asc(const void *a, const void *b) {
struct fileent *aa = (struct fileent*)a;
struct fileent *bb = (struct fileent*)b;
int rc = aa->type - bb->type;
return rc != 0 ? rc : aa->order - bb->order;
}
/*
* Compare - type sort in descending order
* Uses the order field to make qsort() stable
*/
int cmp_type_desc(const void *a, const void *b) {
struct fileent *aa = (struct fileent*)a;
struct fileent *bb = (struct fileent*)b;
int rc = bb->type - aa->type;
return rc != 0 ? rc : aa->order - bb->order;
}
/*
* Compare - sort with directories at the beginning
* Uses the order field to make qsort() stable
*/
int cmp_dir_beg(const void *a, const void *b) {
struct fileent *aa = (struct fileent*)a;
struct fileent *bb = (struct fileent*)b;
if ((aa->type == 0x0f) && (bb->type != 0x0f))
return -1;
if ((bb->type == 0x0f) && (aa->type != 0x0f))
return 1;
return aa->order - bb->order;
}
/*
* Compare - sort with directories at the end
* Uses the order field to make qsort() stable
*/
int cmp_dir_end(const void *a, const void *b) {
struct fileent *aa = (struct fileent*)a;
struct fileent *bb = (struct fileent*)b;
if ((aa->type == 0x0f) && (bb->type != 0x0f))
return 1;
if ((bb->type == 0x0f) && (aa->type != 0x0f))
return -1;
return aa->order - bb->order;
}
/*
* Sort filelist[]
* s defines the field to sort on
*/
void sortlist(char s) {
for(uint i=0; i<numfiles; ++i) {
filelist[i].order = i;
}
switch (s) {
case 'n':
qsort(filelist, numfiles, sizeof(struct fileent),
cmp_name_asc);
break;
case 'N':
qsort(filelist, numfiles, sizeof(struct fileent),
cmp_name_desc);
break;
case 't':
qsort(filelist, numfiles, sizeof(struct fileent),
cmp_type_asc);
break;
case 'T':
qsort(filelist, numfiles, sizeof(struct fileent),
cmp_type_desc);
break;
case 'd':
qsort(filelist, numfiles, sizeof(struct fileent),
cmp_dir_beg);
break;
case 'D':
qsort(filelist, numfiles, sizeof(struct fileent),
cmp_dir_end);
break;
default:
prerr("Invalid sort option");
exit(2);
}
}
/*
* Print the file info stored in filelist[]
*/
void printlist(void) {
fputs("----------------------------------------------------\n", stdout);
fputs("numfiles=", stdout);
pr_int(numfiles);
putchar('\n');
for(uint i=0; i<numfiles; ++i) {
fputs("blk=", stdout);
pr_int(filelist[i].blockidx);
fputs(" ent=", stdout);
pr_int(filelist[i].entrynum);
putchar('\t');
fputs(filelist[i].name, stdout);
putchar('\n');
}
fputs("----------------------------------------------------\n", stdout);
}
/*
* Copy a file entry from one srcblk, srcent to dstblk, dstent
* All indices are 1-based.
*/
void copyent(uint srcblk, uint srcent, uint dstblk, uint dstent, uint device) {
if (doverbose) {
fputs(" from blk ", stdout);
pr_uint(srcblk);
fputs(" entry ", stdout);
pr_uint(srcent);
putchar('\n');
fputs(" to blk ", stdout);
pr_uint(dstblk);
fputs(" entry ", stdout);
pr_uint(dstent);
putchar('\n');
}
struct block *source = blocks;
struct block *dest = blocks;
while (--srcblk > 0)
source = source->next;
while (--dstblk > 0)
dest = dest->next;
char *srcptr = source->data + PTRSZ + (srcent-1) * entsz;
char *dstptr = dest->sorteddata + PTRSZ + (dstent-1) * entsz;
memcpy(dstptr, srcptr, entsz);
/* For directories, update the parent dir entry number */
struct pd_dirent *ent = (struct pd_dirent*)dstptr;
if ((ent->typ_len & 0xf0) == 0xd0) {
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 {
if (doverbose)
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; i<numfiles; ++i) {
puts(filelist[i].name);
copyent(filelist[i].blockidx, filelist[i].entrynum,
destblk, destentry, device);
if (destentry++ == entperblk) {
++destblk;
destentry = 1;
}
}
}
/*
* Write out the sorted directory
*/
int writedir(uchar device) {
struct block *i = blocks;
while (i) {
if(writediskblock(device, i->blocknum, 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 [-s xxx] [-rwv] path\n");
prerr(" Options: -s xxx Directory sort options");
prerr(" -w Enable writing to disk");
prerr(" -v Verbose output");
prerr(" -h This help");
prerr("");
prerr("Directory sort options xxx, is a list of fields on which to");
prerr("sort. The sort options are processed left-to-right.");
prerr(" n sort by filename ascending");
prerr(" N sort by filename descending");
prerr(" t sort by type ascending");
prerr(" T sort by type descending");
prerr(" d sort directories to top");
prerr(" D sort directories to bottom");
prerr("");
prerr("e.g.: sortdir -w -s nd .");
prerr("Will sort the current directory first by name (ascending),");
prerr("then sort directories to the top, and will write the sorted");
prerr("directory to disk.");
exit(2);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
usage();
exit(1);
}
int opt;
while ((opt = getopt(argc, argv, "hwvs:")) != -1) {
switch (opt) {
case 's':
strncpy(sortopts, optarg, 5);
break;
case 'w':
dowrite = 1;
break;
case 'v':
doverbose = 1;
break;
case 'h':
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) {
if (doverbose)
printlist();
for (uchar i=0; i<strlen(sortopts); ++i)
sortlist(sortopts[i]);
sortblocks(dev);
if (doverbose)
printlist();
if (dowrite)
err = writedir(dev);
else
if (doverbose)
puts("Not writing to disk");
}
freeblocks();
return err;
}