mirror of
https://github.com/bobbimanners/GNO-Extras.git
synced 2024-12-22 17:30:13 +00:00
1479 lines
38 KiB
Plaintext
1479 lines
38 KiB
Plaintext
/*
|
|
* Bobbi January-February 2020
|
|
*
|
|
* TODO: Error counting & fix error return codes
|
|
* TODO: Error log file option
|
|
* TODO: Fix mode
|
|
* 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: 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;
|
|
typedef unsigned long ulong;
|
|
|
|
#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 */
|
|
#define FLSZ 8192 /* Bytes required for 64K block free-list */
|
|
|
|
/*
|
|
* 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 */
|
|
char datetime[20]; /* Date/time as a yyyy-mm-dd hh:mm string */
|
|
uchar type; /* ProDOS file type */
|
|
uint blocks; /* Size in blocks */
|
|
ulong eof; /* EOF position in bytes */
|
|
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;
|
|
};
|
|
|
|
/*
|
|
* Represents a date and time
|
|
*/
|
|
struct datetime {
|
|
uint year;
|
|
uchar month;
|
|
uchar day;
|
|
uchar hour;
|
|
uchar minute;
|
|
uchar ispd25format;
|
|
};
|
|
|
|
/* Globals */
|
|
static uint totblks; /* Total # blocks on volume */
|
|
static uchar *freelist; /* Free-list bitmap */
|
|
static uchar *usedlist; /* Bit map of used blocks */
|
|
static uchar flloaded = 0; /* 1 if free-list has been loaded */
|
|
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 dowholedisk = 0; /* -D whole-disk option */
|
|
static uchar dorecurse = 0; /* -r recurse option */
|
|
static uchar dowrite = 0; /* -w write option */
|
|
static uchar doverbose = 0; /* -v verbose option */
|
|
static uchar dodebug = 0; /* -V very verbose option */
|
|
static uchar do_ctime = 0; /* -k ctime option */
|
|
static char sortopts[5] = ""; /* -s:abc list of sort options */
|
|
static char caseopts[2] = ""; /* -c:x case conversion option */
|
|
|
|
/* Prototypes */
|
|
void prerr(char *s);
|
|
void hline(void);
|
|
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);
|
|
void lowercase(char *p, uchar len, uchar *minvers, uchar *vers);
|
|
void uppercase(char *p, uchar len, uchar *minvers, uchar *vers);
|
|
void initialcase(uchar mode, char *p, uchar len, uchar *minvers, uchar *vers);
|
|
void convertdatetime(uchar time[4], struct datetime *dt);
|
|
int readfreelist(uchar device);
|
|
int isfree(uint blk);
|
|
int isused(uint blk);
|
|
void markused(uint blk);
|
|
void checkblock(uint blk, char *msg);
|
|
int seedlingblocks(uchar device, uint keyblk, uint *blkcnt);
|
|
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_datetime_asc(const void *a, const void *b);
|
|
int cmp_datetime_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);
|
|
int cmp_blocks_asc(const void *a, const void *b);
|
|
int cmp_blocks_desc(const void *a, const void *b);
|
|
int cmp_eof_asc(const void *a, const void *b);
|
|
int cmp_eof_desc(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);
|
|
void checkfreeandused(void);
|
|
|
|
/* Print error string to stderr */
|
|
void prerr(char *s) {
|
|
fputs(s, stderr);
|
|
fputs("\n", stderr);
|
|
}
|
|
|
|
/* Horizontal line */
|
|
void hline(void) {
|
|
for (uint i=0; i<80; ++i)
|
|
putchar('-');
|
|
}
|
|
|
|
/*
|
|
* 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';
|
|
}
|
|
|
|
/*
|
|
* Convert filename pointed to by p into lower case (which is recorded
|
|
* as a bitmap in the vers and minvers fields.
|
|
*/
|
|
void lowercase(char *p, uchar len, uchar *minvers, uchar *vers) {
|
|
uchar idx = 0;
|
|
//printf("vers 0x%2x minvers 0x%2x\n", *vers, *minvers);
|
|
*vers = 0x01;
|
|
*minvers = 0x00;
|
|
for (uint i=0; i<7; ++i) {
|
|
*vers <<= 1;
|
|
if ((idx < len) && isalpha(p[idx++]))
|
|
*vers |= 0x01;
|
|
}
|
|
for (uint i=0; i<8; ++i) {
|
|
*minvers <<= 1;
|
|
if ((idx < len) && isalpha(p[idx++]))
|
|
*minvers |= 0x01;
|
|
}
|
|
//printf("vers 0x%2x minvers 0x%2x\n", *vers, *minvers);
|
|
}
|
|
|
|
/*
|
|
* Convert filename pointed to by p into upper case (which is recorded
|
|
* as a bitmap in the vers and minvers fields.
|
|
*/
|
|
void uppercase(char *p, uchar len, uchar *minvers, uchar *vers) {
|
|
*vers = 0x00;
|
|
*minvers = 0x00;
|
|
}
|
|
|
|
/*
|
|
* Convert filename pointed to by p into to have first letter capitalized
|
|
* (which is recorded as a bitmap in the vers and minvers fields.
|
|
* If mode = 0 then just uppercase the initial char ("Read.me")
|
|
* otherwise camel-case the name ("Read.Me")
|
|
*/
|
|
void initialcase(uchar mode, char *p, uchar len, uchar *minvers, uchar *vers) {
|
|
uchar idx = 0;
|
|
uchar capsflag = 1;
|
|
//printf("vers 0x%2x minvers 0x%2x\n", *vers, *minvers);
|
|
*vers = 0x01;
|
|
*minvers = 0x00;
|
|
for (uint i=0; i<7; ++i) {
|
|
*vers <<= 1;
|
|
if ((idx < len) && isalpha(p[idx++]))
|
|
if (!capsflag)
|
|
*vers |= 0x01;
|
|
if ((mode == 1) && !isalpha(p[idx-1]))
|
|
capsflag = 1;
|
|
else
|
|
capsflag = 0;
|
|
}
|
|
for (uint i=0; i<8; ++i) {
|
|
*minvers <<= 1;
|
|
if ((idx < len) && isalpha(p[idx++]))
|
|
if (!capsflag)
|
|
*minvers |= 0x01;
|
|
if ((mode == 1) && !isalpha(p[idx-1]))
|
|
capsflag = 1;
|
|
else
|
|
capsflag = 0;
|
|
}
|
|
//printf("vers 0x%2x minvers 0x%2x\n", *vers, *minvers);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
segment "extra";
|
|
|
|
/*
|
|
* Parse mtime or ctime fields and populate the fields of the datetime struct
|
|
* Supports the legacy ProDOS date/time format as used by ProDOS 1.0->2.4.0
|
|
* and also the new format introduced with ProDOS 2.5.
|
|
*/
|
|
void convertdatetime(uchar time[4], struct datetime *dt) {
|
|
uint d = time[0] + 256U * time[1];
|
|
uint t = time[2] + 256U * time[3];
|
|
if (!(t & 0xe000)) {
|
|
/* ProDOS 1.0 to 2.4.2 date format */
|
|
dt->year = (d & 0xfe00) >> 9;
|
|
dt->month = (d & 0x01e0) >> 5;
|
|
dt->day = d & 0x001f;
|
|
dt->hour = (t & 0x1f00) >> 8;
|
|
dt->minute = t & 0x003f;
|
|
dt->ispd25format = 0;
|
|
if (dt->year < 40) /* See ProDOS-8 Tech Note 48 */
|
|
dt->year += 2000;
|
|
else
|
|
dt->year += 1900;
|
|
} else {
|
|
/* ProDOS 2.5.0+ */
|
|
dt->year = t & 0x0fff;
|
|
dt->month = ((t & 0xf000) >> 12) - 1;
|
|
dt->day = (d & 0xf800) >> 11;
|
|
dt->hour = (d & 0x07c0) >> 6;
|
|
dt->minute = d & 0x003f;
|
|
dt->ispd25format = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read the free list
|
|
*/
|
|
int readfreelist(uchar device) {
|
|
freelist = (uchar*)malloc(FLSZ);
|
|
if (!freelist) {
|
|
puts("** Unable to allocate memory **");
|
|
exit(3);
|
|
}
|
|
bzero(freelist, FLSZ);
|
|
usedlist = (uchar*)malloc(FLSZ);
|
|
if (!usedlist) {
|
|
puts("** Unable to allocate memory **");
|
|
exit(3);
|
|
}
|
|
bzero(usedlist, FLSZ);
|
|
markused(0); /* Boot block */
|
|
markused(1); /* SOS boot block */
|
|
if (readdiskblock(device, 2, buf) == -1) {
|
|
puts("Error reading volume dir");
|
|
return -1;
|
|
}
|
|
uint flblk = buf[0x27] + 256U * buf[0x28];
|
|
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) {
|
|
markused(flblk);
|
|
if (readdiskblock(device, flblk++, p) == -1) {
|
|
puts("Error reading free list");
|
|
return -1;
|
|
}
|
|
p += BLKSZ;
|
|
}
|
|
flloaded = 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Determine if block blk is free or not
|
|
*/
|
|
int isfree(uint blk) {
|
|
uint idx = blk / 8;
|
|
uint bit = blk % 8;
|
|
return (freelist[idx] << bit) & 0x80 ? 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* Determine if block blk is used or not
|
|
*/
|
|
int isused(uint blk) {
|
|
uint idx = blk / 8;
|
|
uint bit = blk % 8;
|
|
return (usedlist[idx] << bit) & 0x80 ? 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* Mark a block as used
|
|
*/
|
|
void markused(uint blk) {
|
|
//printf("markused(%u)\n", blk);
|
|
uint idx = blk / 8;
|
|
uint bit = blk % 8;
|
|
usedlist[idx] |= (0x80 >> bit);
|
|
}
|
|
|
|
/*
|
|
* Perform all the operations to check a block which is used by
|
|
* a directory or file. Complains if the block is on the free-list
|
|
* and also if we have encountered this block in a previous file or dir.
|
|
*/
|
|
void checkblock(uint blk, char *msg) {
|
|
if (isfree(blk))
|
|
printf(" %s block %u is marked free!\n", msg, blk);
|
|
if (isused(blk))
|
|
printf(" %s block %u is already used!\n", msg, blk);
|
|
markused(blk);
|
|
}
|
|
|
|
/*
|
|
* Count the blocks in a seedling file
|
|
*/
|
|
int seedlingblocks(uchar device, uint keyblk, uint *blkcnt) {
|
|
checkblock(keyblk, "Data");
|
|
*blkcnt = 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Count the blocks in a sapling file
|
|
*/
|
|
int saplingblocks(uchar device, uint keyblk, uint *blkcnt) {
|
|
checkblock(keyblk, "Data");
|
|
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) {
|
|
checkblock(p, "Data");
|
|
++(*blkcnt);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Count the blocks in a tree file
|
|
*/
|
|
int treeblocks(uchar device, uint keyblk, uint *blkcnt) {
|
|
checkblock(keyblk, "Tree index");
|
|
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) {
|
|
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) {
|
|
checkblock(keyblk, "Fork key");
|
|
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];
|
|
switch (type) {
|
|
case 0x1:
|
|
/* Seedling */
|
|
seedlingblocks(device, keyblk, &count);
|
|
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];
|
|
switch (type) {
|
|
case 0x1:
|
|
/* Seedling */
|
|
seedlingblocks(device, keyblk, &count);
|
|
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 (!dorecurse)
|
|
checkblock(keyblk, "Directory");
|
|
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 (!dorecurse)
|
|
checkblock(blocknum, "Directory");
|
|
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;
|
|
|
|
checkblock(blocknum, "Directory");
|
|
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);
|
|
|
|
hline();
|
|
printf("Directory %s (%d entries)\n", namebuf, filecount);
|
|
hline();
|
|
|
|
/* 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) {
|
|
|
|
if (strlen(caseopts) > 0) {
|
|
switch (caseopts[0]) {
|
|
case 'u':
|
|
uppercase(ent->name,
|
|
ent->typ_len & 0x0f,
|
|
&(ent->vers),
|
|
&(ent->minvers));
|
|
break;
|
|
case 'l':
|
|
lowercase(ent->name,
|
|
ent->typ_len & 0x0f,
|
|
&(ent->vers),
|
|
&(ent->minvers));
|
|
break;
|
|
case 'i':
|
|
initialcase(0,
|
|
ent->name,
|
|
ent->typ_len & 0x0f,
|
|
&(ent->vers),
|
|
&(ent->minvers));
|
|
break;
|
|
case 'c':
|
|
initialcase(1,
|
|
ent->name,
|
|
ent->typ_len & 0x0f,
|
|
&(ent->vers),
|
|
&(ent->minvers));
|
|
break;
|
|
}
|
|
}
|
|
|
|
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
|
|
uint blks = ent->blksused[0] + 256U * ent->blksused[1];
|
|
ulong eof = ent->eof[0] + 256L * ent->eof[1] +
|
|
65536L * ent->eof[2];
|
|
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;
|
|
filelist[numfiles].blocks = blks;
|
|
filelist[numfiles].eof = eof;
|
|
|
|
struct datetime dt;
|
|
convertdatetime(do_ctime ? ent->ctime : ent->mtime,
|
|
&dt);
|
|
sprintf(filelist[numfiles].datetime,
|
|
"%04d-%02d-%02d %02d:%02d %s",
|
|
dt.year, dt.month, dt.day, dt.hour, dt.minute,
|
|
(dt.ispd25format ? "*" : " "));
|
|
|
|
#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);
|
|
}
|
|
uint count;
|
|
switch (ent->typ_len & 0xf0) {
|
|
case 0xd0:
|
|
/* Subdirectory */
|
|
enqueuesubdir(keyblk, subdirs++);
|
|
subdirblocks(device, keyblk, ent,
|
|
blocknum, blkentries, &count);
|
|
break;
|
|
case 0x10:
|
|
/* Seedling */
|
|
seedlingblocks(device, keyblk, &count);
|
|
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;
|
|
checkblock(blocknum, "Directory");
|
|
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 - date/time sort in ascending order
|
|
*/
|
|
int cmp_datetime_asc(const void *a, const void *b) {
|
|
return strncmp(((struct fileent*)a)->datetime,
|
|
((struct fileent*)b)->datetime, 16);
|
|
}
|
|
|
|
/*
|
|
* Compare - date/time sort in descending order
|
|
*/
|
|
int cmp_datetime_desc(const void *a, const void *b) {
|
|
return strncmp(((struct fileent*)b)->datetime,
|
|
((struct fileent*)a)->datetime, 16);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* Compare - sort in increasing order of blocks used
|
|
*/
|
|
int cmp_blocks_asc(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
int rc = aa->blocks - bb->blocks;
|
|
return rc != 0 ? rc : aa->order - bb->order;
|
|
}
|
|
|
|
/*
|
|
* Compare - sort in decreasing order of blocks used
|
|
*/
|
|
int cmp_blocks_desc(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
int rc = bb->blocks - aa->blocks;
|
|
return rc != 0 ? rc : aa->order - bb->order;
|
|
}
|
|
|
|
/*
|
|
* Compare - sort in increasing order of EOF position
|
|
*/
|
|
int cmp_eof_asc(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
long diff = aa->eof - bb->eof;
|
|
if (diff == 0)
|
|
return aa->order - bb->order;
|
|
if (diff > 0)
|
|
return 1;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Compare - sort in decreasing order of EOF position
|
|
*/
|
|
int cmp_eof_desc(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
long diff = bb->eof - aa->eof;
|
|
if (diff == 0)
|
|
return aa->order - bb->order;
|
|
if (diff > 0)
|
|
return 1;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* 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 'd':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_datetime_asc);
|
|
break;
|
|
case 'D':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_datetime_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 'f':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_dir_beg);
|
|
break;
|
|
case 'F':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_dir_end);
|
|
break;
|
|
case 'b':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_blocks_asc);
|
|
break;
|
|
case 'B':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_blocks_desc);
|
|
break;
|
|
case 'e':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_eof_asc);
|
|
break;
|
|
case 'E':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_eof_desc);
|
|
break;
|
|
default:
|
|
puts("Invalid sort option");
|
|
exit(2);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print the file info stored in filelist[]
|
|
*/
|
|
void printlist(void) {
|
|
hline();
|
|
printf("numfiles=%u\n", numfiles);
|
|
fputs("Dirblk Entry Type : Name : Blocks EOF ", stdout);
|
|
if (do_ctime)
|
|
puts("Created");
|
|
else
|
|
puts("Modified");
|
|
for (uint i=0; i<numfiles; ++i) {
|
|
printf(" %03u %02u %02x : %s",
|
|
filelist[i].blockidx,
|
|
filelist[i].entrynum,
|
|
filelist[i].type,
|
|
filelist[i].name);
|
|
for (uint j=0; j<(16-strlen(filelist[i].name)); ++j)
|
|
putchar(' ');
|
|
printf(": %5u %8lu", filelist[i].blocks, filelist[i].eof);
|
|
printf(" %s\n", filelist[i].datetime);
|
|
}
|
|
hline();
|
|
}
|
|
|
|
/*
|
|
* 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] [-n x] [-rDwcvVh] path\n");
|
|
prerr(" Options: -s xxx Directory sort options");
|
|
prerr(" -n x Filename upper/lower case options");
|
|
prerr(" -r Recursive descent");
|
|
prerr(" -D Whole-disk mode (implies -r)");
|
|
prerr(" -w Enable writing to disk");
|
|
prerr(" -c Use creation time rather than modification");
|
|
prerr(" -v Verbose output");
|
|
prerr(" -V Verbose debugging output");
|
|
prerr(" -h This help");
|
|
prerr("");
|
|
prerr("Upper/lower case option x:");
|
|
prerr(" l convert filenames to lower case eg: read.me");
|
|
prerr(" u convert filenames to upper case eg: READ.ME");
|
|
prerr(" i convert filenames to initial upper case eg: Read.me");
|
|
prerr(" c convert filenames to camel case eg: Read.Me");
|
|
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(" d sort by modification (or creation [-c]) date ascending");
|
|
prerr(" D sort by modification (or creation [-c]) date descending");
|
|
prerr(" t sort by type ascending");
|
|
prerr(" T sort by type descending");
|
|
prerr(" f sort folders (directories) to top");
|
|
prerr(" F sort folders (directories) to bottom");
|
|
prerr(" b sort by blocks used ascending");
|
|
prerr(" B sort by blocks used descending");
|
|
prerr(" e sort by EOF position ascending");
|
|
prerr(" E sort by EOF position descending");
|
|
prerr("");
|
|
prerr("e.g.: sortdir -w -s nf .");
|
|
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();
|
|
}
|
|
|
|
/*
|
|
* Iterate through freelist[] and usedlist[] and see if all is well.
|
|
* If we have visited all files and directories on the volume, every
|
|
* block should either be marked free or marked used.
|
|
*/
|
|
void checkfreeandused(void) {
|
|
printf("Total blocks on volume %u\n", totblks);
|
|
uint freeblks = 0;
|
|
for (uint i=0; i<totblks; ++i)
|
|
if (isfree(i))
|
|
++freeblks;
|
|
printf("Free blocks on volume %u\n", freeblks);
|
|
for (uint i=0; i<totblks; ++i) {
|
|
uint idx = i / 8;
|
|
if (!(freelist[idx] ^ usedlist[i])) /* Speed-up */
|
|
continue;
|
|
if (isfree(i)) {
|
|
if (isused(i))
|
|
printf("Blk %u used, marked free\n", i);
|
|
} else {
|
|
if (!isused(i))
|
|
printf("Blk %u unused, not marked free\n", i);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
if (argc < 2) {
|
|
usage();
|
|
exit(1);
|
|
}
|
|
int opt;
|
|
while ((opt = getopt(argc, argv, "cDrwvVs:n:h")) != -1) {
|
|
switch (opt) {
|
|
case 'c':
|
|
do_ctime = 1;
|
|
break;
|
|
case 'D':
|
|
dowholedisk = 1;
|
|
dorecurse = 1;
|
|
break;
|
|
case 'r':
|
|
dorecurse = 1;
|
|
break;
|
|
case 'w':
|
|
dowrite = 1;
|
|
break;
|
|
case 'v':
|
|
doverbose = 1;
|
|
break;
|
|
case 'V':
|
|
dodebug = 1;
|
|
break;
|
|
case 's':
|
|
strncpy(sortopts, optarg, 5);
|
|
break;
|
|
case 'n':
|
|
strncpy(caseopts, optarg, 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);
|
|
if (dowholedisk)
|
|
processdir(dev, 2);
|
|
else
|
|
processdir(dev, blk);
|
|
if (dorecurse) {
|
|
while (dirs) {
|
|
struct dirblk *d = dirs;
|
|
blk = dirs->blocknum;
|
|
dirs = d->next;
|
|
free(d);
|
|
processdir(dev, blk);
|
|
}
|
|
}
|
|
if (dowholedisk)
|
|
checkfreeandused();
|
|
free(freelist);
|
|
free(usedlist);
|
|
return 0;
|
|
}
|
|
|