GNO-Extras/bobbi/sortdir.c#b00008
2020-02-13 17:22:26 -05:00

1092 lines
27 KiB
Plaintext

/*
* Bobbi January-February 2020
*
* TODO: Error counting & fix error return codes
* TODO: Error log file option
* TODO: Whole volume mode - check all unused blocks are free
* TODO: Check no disk blocks are used more than once
* TODO: Fix mode
* TODO: Sort by mtime, ctime, size ...
* TODO: Tool for lower/upper/camel casing filenames
* TODO: Tool for 'extending' volume dir to more than 4 blocks
* TODO: Legacy/extended date format conversion
* TODO: Trimming unused directory blocks
* TODO: Improve output and user interface
* TODO: Use segment to make more memory available
* TODO: Needs a manpage
* TODO: Maybe make a version that doesn't need GNO
*/
#pragma debug 25 /* Enable stack checking */
#pragma lint -1
#pragma stacksize 16384
#pragma memorymodel 0
#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 */
};
/*
* Entry for list of directory keyblocks to check
*/
struct dirblk {
uint blocknum;
struct dirblk *next;
};
/* Globals */
static uchar freelist[8096]; /* 1 bit for each of 64K blocks */
static uchar flloaded = 0;
static struct block *blocks = NULL;
static struct dirblk *dirs = NULL;
static struct fileent filelist[MAXFILES];
static uint numfiles;
static uchar entsz; /* Bytes per file entry */
static uchar entperblk; /* Number of entries per block */
static char buf[BLKSZ]; /* General purpose scratch buffer */
static char buf2[BLKSZ]; /* General purpose scratch buffer */
static char buf3[BLKSZ]; /* General purpose scratch buffer */
static uchar dorecurse = 0;
static uchar dowrite = 0;
static uchar doverbose = 0;
static uchar dodebug = 0;
static char sortopts[5] = "";
/* Prototypes */
void prerr(char *s);
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, uchar len);
int readfreelist(uchar device);
int isfree(uint blk);
int saplingblocks(uchar device, uint keyblk, uint *blkcnt);
int treeblocks(uchar device, uint keyblk, uint *blkcnt);
int forkblocks(uchar device, uint keyblk, uint *blkcnt);
int subdirblocks(uchar device, uint keyblk, struct pd_dirent *ent,
uint blocknum, uint blkentries, uint *blkcnt);
void enqueuesubdir(uint blocknum, uint subdiridx);
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);
uint blockidxtoblocknum(uint idx);
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 processdir(uint device, uint blocknum);
/* Print error string to stderr */
void prerr(char *s) {
fputs(s, stderr);
fputs("\n", stderr);
}
/*
* 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
printf("Reading dev %2u block %5u\n", device, blocknum);
#endif
#ifdef CHECK
if (flloaded)
if (isfree(blocknum))
printf(" Block %u is marked free!\n", blocknum);
#endif
BlockRec br;
br.blockDevNum = device;
br.blockDataBuffer = buf;
br.blockNum = blocknum;
READ_BLOCK(&br);
int rc = toolerror();
if (rc) {
printf(" 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
printf("Writing dev %2u block %5u\n", device, blocknum);
#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 len) {
uchar idx = 0;
if (!(vers & 0x80)) {
for (idx=0; idx<NMLEN; ++idx)
out[idx] = in[idx];
out[len] = '\0';
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;
}
out[len] = '\0';
}
/*
* 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) {
printf("Error opening dir %s\n", dirname);
rv = 1;
goto ret;
}
ssize_t len = read(fp, buf, BLKSZ);
if (len != BLKSZ) {
printf("Error reading first block of dir %s", dirname);
rv = 1;
goto ret;
}
struct stat st;
if (stat(dirname, &st) == -1) {
printf("Can't stat %s\n", dirname);
exit(1);
}
if (!S_ISDIR(st.st_mode)) {
printf("%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) {
puts("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) {
printf("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 the free list
*/
int readfreelist(uchar device) {
bzero(freelist, 8096);
if (readdiskblock(device, 2, buf) == -1) {
puts("Error reading volume dir");
return -1;
}
uint flblk = buf[0x27] + 256U * buf[0x28];
uint totblks = buf[0x29] + 256U * buf[0x2a];
if (doverbose)
printf("Volume has %u blocks\n", totblks);
uint flsize = totblks / 4096U;
if ((flsize % 4096) >0)
++flsize;
char *p = (char*)freelist;
for (uint i=0; i<flsize; ++i) {
if (readdiskblock(device, flblk++, p) == -1) {
puts("Error reading free list");
return -1;
}
p += BLKSZ;
}
#if 0
for (uint i=0; i<8096; ++i) {
pr_uint(freelist[i]);
putchar(' ');
}
#endif
flloaded = 1;
return 0;
}
/*
* Determine if block blk is free or not
*/
int isfree(uint blk) {
uint idx = blk / 8;
uint bit = blk % 8;
//printf("freelist[%u]=%u\n",idx,freelist[idx]);
return (freelist[idx] << bit) & 0x80 ? 1 : 0;
}
/*
* Count the blocks in a sapling file
*/
int saplingblocks(uchar device, uint keyblk, uint *blkcnt) {
if (readdiskblock(device, keyblk, buf) == -1) {
printf(" Error reading blk %u\n", keyblk);
return -1;
}
*blkcnt = 1;
for (uint i=0; i<256; ++i) {
uint p = buf[i] + 256U * buf[i+256];
if (p) {
if (isfree(p))
printf(" Data block %u is marked free!\n", p);
++(*blkcnt);
}
}
return 0;
}
/*
* Count the blocks in a tree file
*/
int treeblocks(uchar device, uint keyblk, uint *blkcnt) {
if (readdiskblock(device, keyblk, buf2) == -1) {
printf(" Error reading blk %u\n", keyblk);
return -1;
}
*blkcnt = 1;
for (uint i=0; i<256; ++i) {
uint p = buf2[i] + 256U * buf2[i+256];
if (p) {
if (isfree(p))
printf(" Idx block %u is marked free!\n", p);
uint b;
if (saplingblocks(device, p, &b) == 0)
*blkcnt += b;
else
return -1;
}
}
return 0;
}
/*
* Count the blocks in a GSOS fork file
* See http://1000bit.it/support/manual/apple/technotes/pdos/tn.pdos.25.html
*/
int forkblocks(uchar device, uint keyblk, uint *blkcnt) {
if (readdiskblock(device, keyblk, buf3) == -1) {
printf(" Error reading blk %u\n", keyblk);
return -1;
}
*blkcnt = 1;
uint count;
/* Data fork */
uchar type = buf3[0x00];
keyblk = buf3[0x01] + 256U * buf3[0x02];
uint blks = buf3[0x03] + 256U * buf3[0x04];
if (isfree(keyblk))
printf(" Data fork keyblock %u is marked free!\n", keyblk);
switch (type) {
case 0x1:
/* Seedling */
count = 1;
break;
case 0x2:
/* Sapling */
saplingblocks(device, keyblk, &count);
break;
case 0x3:
/* Tree */
treeblocks(device, keyblk, &count);
break;
default:
puts(" Invalid storage type for data fork");
count = 0;
break;
}
if (blks != count) {
if (count != 0) {
printf(" Data fork size %u is incorrect", blks);
printf(", should be %u\n", count);
}
}
*blkcnt += count;
/* Data fork */
type = buf3[0x100];
keyblk = buf3[0x101] + 256U * buf3[0x102];
blks = buf3[0x103] + 256U * buf3[0x104];
if (isfree(keyblk))
printf(" Resource fork keyblock %u is marked free!\n", keyblk);
switch (type) {
case 0x1:
/* Seedling */
count = 1;
break;
case 0x2:
/* Sapling */
saplingblocks(device, keyblk, &count);
break;
case 0x3:
/* Tree */
treeblocks(device, keyblk, &count);
break;
default:
puts(" Invalid storage type for resource fork");
count = 0;
break;
}
if (blks != count) {
if (count != 0) {
printf(" Resource fork size %u is incorrect", blks);
printf(", should be %u\n", count);
}
}
*blkcnt += count;
return 0;
}
/*
* Count the blocks in a subdirectory
*/
int subdirblocks(uchar device, uint keyblk, struct pd_dirent *ent,
uint blocknum, uint blkentries, uint *blkcnt) {
if (readdiskblock(device, keyblk, buf) == -1) {
printf(" Error reading keyblock %u\n", keyblk);
return -1;
}
*blkcnt = 1;
struct pd_dirhdr *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) {
printf(" Bad parent block %u", parblk);
printf(", should be %u\n", blocknum);
}
if (parentry != blkentries) {
printf(" Bad parent block entry %u", parentry);
printf(", should be %u\n", blkentries);
}
if (parentlen != 0x27)
puts(" Bad parent entry length");
char *dirname = buf + 0x05;
if (strncmp(dirname, ent->name, NMLEN))
puts(" Subdirectory name mismatch");
blocknum = buf[0x02] + 256U * buf[0x03];
while (blocknum) {
if (readdiskblock(device, blocknum, buf) == -1) {
printf(" Error reading dir block %u\n", blocknum);
return -1;
}
++(*blkcnt);
blocknum = buf[0x02] + 256U * buf[0x03];
}
return 0;
}
/*
* Record the keyblock of a subdirectory to be processed subsequently
* blocknum is the block number of the subdirectory keyblock
* subdiridx is a sequential counter of the subdirs in the current directory
*/
void enqueuesubdir(uint blocknum, uint subdiridx) {
struct dirblk *p = (struct dirblk*)malloc(sizeof(struct dirblk));
if (!p) {
puts("** Unable to allocate memory **");
exit(3);
}
p->blocknum = blocknum;
static struct dirblk *prev;
if (subdiridx == 0) { /* First subdir is inserted at head of list */
p->next = dirs;
dirs = p;
} else { /* Subsequent subdirs follow the previous */
p->next = prev->next;
prev->next = p;
}
prev = p;
}
/*
* 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) {
uint blkcnt = 1;
uint hdrblknum = blocknum;
numfiles = 0;
blocks = (struct block*)malloc(sizeof(struct block));
if (!blocks) {
puts("** Unable to allocate memory **");
exit(3);
}
struct block *curblk = blocks;
curblk->next = NULL;
curblk->blocknum = blocknum;
if (readdiskblock(device, blocknum, curblk->data) == -1) {
printf("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];
static char namebuf[NMLEN+1];
fixcase(hdr->name, namebuf,
hdr->vers, hdr->minvers, hdr->typ_len & 0x0f);
puts("---------------------------------------------------------");
printf("Directory %s (%d entries)\n", namebuf, filecount);
puts("---------------------------------------------------------");
/* Copy pointers and header to sorteddata[], zero the rest */
bzero(curblk->sorteddata, BLKSZ);
memcpy(curblk->sorteddata, curblk->data, PTRSZ + entsz);
#ifdef CHECK
if (entsz != 0x27) {
puts("Error - bad entry size");
return 1;
}
if (entperblk != 0x0d) {
puts("Error - bad entries/block");
return 1;
}
#endif
uint idx = entsz + PTRSZ; /* Skip header */
uchar blkentries = 2;
uchar entries = 0;
uint subdirs = 0;
while (1) {
struct pd_dirent *ent = (struct pd_dirent*)(curblk->data + idx);
if (ent->typ_len != 0) {
fixcase(ent->name, namebuf,
ent->vers, ent->minvers, ent->typ_len & 0x0f);
#ifdef CHECK
switch (ent->typ_len & 0xf0) {
case 0x10:
fputs("Seed ", stdout);
break;
case 0x20:
fputs("Sapl ", stdout);
break;
case 0x30:
fputs("Tree ", stdout);
break;
case 0x40:
fputs("Pasc ", stdout);
break;
case 0x50:
fputs("Fork ", stdout);
break;
case 0xd0:
fputs("Dir ", stdout);
break;
default:
fputs("???? ", stdout);
break;
}
puts(namebuf);
#endif
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) {
printf(" Header ptr %u should be %u\n",
hdrblk, hdrblknum);
}
if (isfree(keyblk))
printf(" Keyblock %u is marked free!\n",
keyblk);
uint blks = ent->blksused[0] + 256U * ent->blksused[1];
uint count;
switch (ent->typ_len & 0xf0) {
case 0xd0:
/* Subdirectory */
enqueuesubdir(keyblk, subdirs++);
subdirblocks(device, keyblk, ent,
blocknum, blkentries, &count);
break;
case 0x10:
/* Seedling */
count = 1;
break;
case 0x20:
/* Sapling */
saplingblocks(device, keyblk, &count);
break;
case 0x30:
/* Tree */
treeblocks(device, keyblk, &count);
break;
case 0x40:
/* Pascal area */
puts(" Pascal area!!");
//
// TODO: Check name is PASCAL.AREA type 0xef
//
count = 0;
break;
case 0x50:
/* File with resource fork */
forkblocks(device, keyblk, &count);
break;
default:
printf(" %s: unexpected storage type 0x%x\n",
namebuf, ent->typ_len & 0xf0);
count = 0;
}
if (blks != count) {
if (count != 0) {
printf(" Size %u is incorrect", blks);
printf(", should be %u\n", count);
}
}
#endif
++numfiles;
if (numfiles == MAXFILES) {
puts("** Too many files! **");
return 1;
}
++entries;
}
if (blkentries == entperblk) {
blocknum = curblk->data[0x02] + 256U*curblk->data[0x03];
if (blocknum == 0) {
break;
}
curblk->next =
(struct block*)malloc(sizeof(struct block));
if (!curblk->next) {
puts("** Unable to allocate memory **");
exit(3);
}
curblk = curblk->next;
curblk->next = NULL;
curblk->blocknum = blocknum;
++blkcnt;
if (
readdiskblock(device, blocknum, curblk->data) == -1) {
printf(" 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;
}
}
if (filecount != entries)
printf("Filecount %u wrong, should be %u\n", filecount, entries);
return 0; // TODO: THIS SHOULD BE NUMBER OF ERRORS
}
/*
* 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:
puts("Invalid sort option");
exit(2);
}
}
/*
* Print the file info stored in filelist[]
*/
void printlist(void) {
puts("---------------------------------------------------------");
printf("numfiles=%u\n", numfiles);
puts("Dirblk Entry Type : Name");
for(uint i=0; i<numfiles; ++i)
printf(" %03u %02u %02x : %s\n",
filelist[i].blockidx,
filelist[i].entrynum,
filelist[i].type,
filelist[i].name);
puts("---------------------------------------------------------");
}
/*
* Convert block index to block number
* Block index is 1-based (1,2,3 ...)
*/
uint blockidxtoblocknum(uint idx) {
struct block *p = blocks;
for (uint i=1; i<idx; ++i)
if (p)
p = p->next;
else {
puts("** Internal error **");
exit(3);
}
if (!p) {
puts("** Internal error **");
exit(3);
}
return p->blocknum;
}
/*
* 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 (dodebug) {
printf(" from dirblk %03u entry %02u\n", srcblk, srcent);
printf(" to dirblk %03u entry %02u\n", dstblk, dstent);
}
uint parentblk = blockidxtoblocknum(dstblk);
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) {
puts("Can't read subdirectory");
exit(1);
}
struct pd_dirhdr *hdr = (struct pd_dirhdr*)(buf + PTRSZ);
hdr->parptr[0] = parentblk & 0xff;
hdr->parptr[1] = (parentblk >> 8) & 0xff;
hdr->parentry = dstent;
if (dowrite) {
if (writediskblock(device, block, buf) == -1) {
puts("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) {
if (dodebug)
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) {
printf("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;
}
blocks = NULL;
}
void usage(void) {
prerr("usage: sortdir [-s xxx] [-rwv] path\n");
prerr(" Options: -s xxx Directory sort options");
prerr(" -r Recursive descent");
prerr(" -w Enable writing to disk");
prerr(" -v Verbose output");
prerr(" -V Verbose debugging 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);
}
/*
* Performs all actions for a single directory
* blocknum is the keyblock of the directory to process
*/
void processdir(uint device, uint blocknum) {
uchar err = readdir(device, blocknum);
if (doverbose) {
printlist();
}
if (err) {
puts("Error scanning directory, will not sort");
goto done;
}
if (strlen(sortopts) > 0) {
for (uchar i=0; i<strlen(sortopts); ++i) {
if (doverbose) {
printf("Sorting - order '%c' ...\n",
sortopts[i]);
}
sortlist(sortopts[i]);
}
sortblocks(device);
if (doverbose) {
puts("After sorting ...");
printlist();
}
if (dowrite)
err = writedir(device);
else if (doverbose)
puts("Not writing to disk without -w");
}
done:
freeblocks();
}
int main(int argc, char *argv[]) {
if (argc < 2) {
usage();
exit(1);
}
int opt;
while ((opt = getopt(argc, argv, "rhwvVs:")) != -1) {
switch (opt) {
case 'r':
dorecurse = 1;
break;
case 's':
strncpy(sortopts, optarg, 5);
break;
case 'w':
dowrite = 1;
break;
case 'v':
doverbose = 1;
break;
case 'V':
dodebug = 1;
break;
case 'h':
default:
usage();
}
}
if (optind != argc - 1)
usage();
uchar dev;
uint blk;
if (firstblk(argv[optind], &dev, &blk) != 0) {
exit(1);
}
readfreelist(dev);
processdir(dev, blk);
if (dorecurse) {
while (dirs) {
struct dirblk *d = dirs;
blk = dirs->blocknum;
dirs = d->next;
free(d);
processdir(dev, blk);
}
}
return 0;
}