diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2d01034 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +###################################################################### +# Makefile for Sortdir, using cc65 +# Bobbi, 2020 +# GPL v3+ +###################################################################### + +# Adjust these to match your site installation +CC65DIR = ~/Personal/Development/cc65 +CC65BINDIR = $(CC65DIR)/bin +CC65LIBDIR = $(CC65DIR)/lib +CC65INCDIR = $(CC65DIR)/include +CA65INCDIR = $(CC65DIR)/asminc +APPLECMDR = ~/Personal/Historic\ Computing/Micros/Apple2/AppleCommander-1.3.5.jar + +all: sortdir.system\#ff0000 + +clean: + rm -f *.s *.o *.map sortdir + +sortdir.o: sortdir.c + $(CC65BINDIR)/cc65 -I $(CC65INCDIR) -t apple2enh -D A2E -o sortdir.s sortdir.c + $(CC65BINDIR)/ca65 -I $(CA65INCDIR) -t apple2enh sortdir.s + +sortdir.system\#ff0000: sortdir.o + $(CC65BINDIR)/ld65 -m sortdir.map -o sortdir.system\#ff0000 -C apple2enh-system.cfg sortdir.o $(CC65LIBDIR)/apple2enh.lib + +#sortdir.bin: sortdir.o +# $(CC65BINDIR)/ld65 -m sortdir.map -o sortdir.bin -C apple2enh.cfg -D __HIMEM__=0xbf00 sortdir.o $(CC65LIBDIR)/apple2enh.lib + diff --git a/apple2enh-system.cfg b/apple2enh-system.cfg new file mode 100644 index 0000000..f4684d9 --- /dev/null +++ b/apple2enh-system.cfg @@ -0,0 +1,40 @@ +# Configuration for ProDOS 8 system programs (without the header) + +SYMBOLS { + __STACKSIZE__: type = weak, value = $0800; # 2k stack + __LCADDR__: type = weak, value = $D400; # Behind quit code + __LCSIZE__: type = weak, value = $0C00; # Rest of bank two +} +MEMORY { + ZP: file = "", define = yes, start = $0080, size = $001A; + MAIN: file = %O, start = $2000, size = $BF00 - $2000; + BSS: file = "", start = __ONCE_RUN__, size = $BF00 - __STACKSIZE__ - __ONCE_RUN__; + LC: file = "", define = yes, start = __LCADDR__, size = __LCSIZE__; +} +SEGMENTS { + ZEROPAGE: load = ZP, type = zp; + STARTUP: load = MAIN, type = ro; + LOWCODE: load = MAIN, type = ro, optional = yes; + CODE: load = MAIN, type = ro; + RODATA: load = MAIN, type = ro; + DATA: load = MAIN, type = rw; + INIT: load = MAIN, type = rw; + ONCE: load = MAIN, type = ro, define = yes; + LC: load = MAIN, run = LC, type = ro, optional = yes; + BSS: load = BSS, type = bss, define = yes; +} +FEATURES { + CONDES: type = constructor, + label = __CONSTRUCTOR_TABLE__, + count = __CONSTRUCTOR_COUNT__, + segment = ONCE; + CONDES: type = destructor, + label = __DESTRUCTOR_TABLE__, + count = __DESTRUCTOR_COUNT__, + segment = RODATA; + CONDES: type = interruptor, + label = __INTERRUPTOR_TABLE__, + count = __INTERRUPTOR_COUNT__, + segment = RODATA, + import = __CALLIRQ__; +} diff --git a/sortdir.c b/sortdir.c new file mode 100644 index 0000000..b01682d --- /dev/null +++ b/sortdir.c @@ -0,0 +1,2077 @@ +/* + * SORTDIR - for Apple II (ProDOS) + * + * Bobbi January-March 2020 + * + * TODO: Aux memory support for ProDOS-8 + * TODO: Enable free list functionality on ProDOS-8 + * TODO: Get both ProDOS-8 and GNO versions to build from this source + * TODO: Trimming unused directory blocks + */ + +//#pragma debug 9 +//#pragma lint -1 +//#pragma stacksize 16384 +//#pragma memorymodel 0 +//#pragma optimize -1 /* Disable stack repair code */ + +#include +#include +#include +#include +#include +#include +#include +//#include +//#include +//#include +//#include +#include +#include + +#undef DEBUG /* Enable additional debug printout */ +#define CHECK /* Perform additional integrity checking */ +#define SORT /* Enable sorting code */ +#undef FREELIST /* Checking of free list */ + +#define NLEVELS 4 /* Number of nested sorts permitted */ + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; + +#define NMLEN 15 /* Length of filename */ +#define MAXFILES 200 /* 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 */ + +/* Exit codes */ +#define EXIT_SUCCESS 0 +#define EXIT_BAD_ARG 1 +#define EXIT_ALLOC_ERR 2 +#define EXIT_FATAL_ERR 3 + +/* + * 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 */ +#ifdef FREELIST +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 */ +#endif +static char currdir[NMLEN+1]; /* Name of directory currently processed */ +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 uint errcount = 0; /* Error counter */ +static dhandle_t dio_hdl; /* cc64 direct I/O handle */ +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 uchar dozero = 0; /* -z zero free blocks option */ +static char sortopts[NLEVELS+1] = ""; /* -s:abc list of sort options */ +static char caseopts[2] = ""; /* -c:x case conversion option */ +static char fixopts[2] = ""; /* -f:x fix mode option */ +static char dateopts[2] = ""; /* -d:x date conversion option */ + +/* Prototypes */ +void hline(void); +void confirm(void); +void err(enum errtype severity, char *fmt, ...); +void flushall(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 firstblk(char *dirname, uchar *device, uint *block); +void readdatetime(uchar time[4], struct datetime *dt); +void writedatetime(uchar pd25, struct datetime *dt, uchar time[4]); +uint askfix(void); +#ifdef FREELIST +int readfreelist(uchar device); +int isfree(uint blk); +void markfree(uint blk); +void marknotfree(uint blk); +int isused(uint blk); +void markused(uint blk); +void checkblock(uint blk, char *msg); +#endif +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_name_asc_ci(const void *a, const void *b); +int cmp_name_desc_ci(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); +int cmp_noop(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 interactive(void); +void usage(void); +void processdir(uint device, uint blocknum); +void checkfreeandused(uchar device); +void zeroblock(uchar device, uint blocknum); +void zerofreeblocks(uchar device, uint freeblks); +void parseargs(void); + +/* Horizontal line */ +void hline(void) { + uint i; + for (i=0; i<80; ++i) + putchar('-'); +} + +void hline2(void) { + uint i; + for (i=0; i<80; ++i) + putchar('='); +} + +enum errtype {WARN, NONFATAL, FATAL, FATALALLOC, FATALBADARG, FINISHED}; + + +/****************************************************************************/ +/* LANGUAGE CARD BANK 2 0xd400-x0dfff 3KB */ +/****************************************************************************/ +#pragma code-name (push, "LC") + +void confirm() { + puts("[Press any key to restart system]"); + getchar(); +} + + +/* + * Display error message + */ +void err(enum errtype severity, char *fmt, ...) { + va_list v; + uint rv = 0; + rebootafterexit(); // Necessary if we were called from BASIC + if (severity == FINISHED) { + hline(); + if (errcount == 0) + printf("DONE - no errors found.\n"); + else + printf("DONE - %u errors\n", errcount); + hline(); + confirm(); + exit(EXIT_SUCCESS); + } + ++errcount; + + switch (severity) { + case FATAL: + rv = EXIT_FATAL_ERR; + case FATALALLOC: + rv = EXIT_ALLOC_ERR; + case FATALBADARG: + rv = EXIT_BAD_ARG; + } + + fputs(((rv > 0) ? " ** " : " "), stdout); + va_start(v, fmt); + vprintf(fmt, v); + putchar('\n'); + va_end(v); + if (rv > 0) { + printf("Stopping after %u errors\n", errcount); + confirm(); + exit(rv); + } +} + +/* + * Disable GSOS block cache and flush any unwritten changes + */ +void flushall(void) { +// short ff[2]; +// ResetCacheGS(0); /* Disable block caching */ +// ff[0] = 1; +// ff[1] = 0; +// FlushGS(ff); +} + +/* + * 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) { + int rc; +#ifdef DEBUG + printf("Reading dev %2u block %5u\n", device, blocknum); +#endif +#ifdef CHECK +#ifdef FREELIST + if (flloaded) + if (isfree(blocknum)) + err(NONFATAL, "Blk %u is marked free!", blocknum); +#endif +#endif +// BlockRec br; +// br.blockDevNum = device; +// br.blockDataBuffer = buf; +// br.blockNum = blocknum; +// READ_BLOCK(&br); +// int rc = toolerror(); +// if (rc) { +// err(FATAL, "Block read failed, err=%x", rc); +// return -1; +// } + rc = dio_read(dio_hdl, blocknum, buf); + if (rc) + err(FATAL, "Blk read failed, err=%x", rc); + 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) { + int rc; +#ifdef DEBUG + printf("Writing dev %2u blk %5u\n", device, blocknum); +#endif + if ((strcmp(currdir, "LIB") == 0) || + (strcmp(currdir, "LIBRARIES") == 0)) { + printf("Not writing library directory %s\n", currdir); + return 0; + } + flushall(); +// 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) { +// err(FATAL, "Blk write failed"); +// return -1; +// } + rc = dio_write(dio_hdl, blocknum, buf); + if (rc) + err(FATAL, "Blk write failed, err=%x", rc); + 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) { + uint i; + uchar idx = 0; + if (!(vers & 0x80)) { + for (idx=0; idx> 4; + drive = ((*lastdev & 0x80) >> 7) + (*lastdev & 0x02) + 1; + printf("[Slot %u, Drive %u]\n", slot, drive); + *device = slot + (drive - 1) * 8; + dio_hdl = dio_open(*device); // TODO should dio_close on exit + + 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) { + err(NONFATAL, "Bad storage type"); + goto ret; + } +#endif + + /* Handle subdirectory */ + parentblk = hdr->parptr[0] + 256U * hdr->parptr[1]; + parententry = hdr->parentry; + parententlen = hdr->parentlen; + + /* Read parent directory block */ + if (readdiskblock(*device, parentblk, buf) == -1) + err(FATAL, "Can't read parent directory for %s", dirname); + + ent = (struct pd_dirent *)(buf + PTRSZ + (parententry-1) * parententlen); + + *block = ent->keyptr[0] + 256U * ent->keyptr[1]; + +ret: + if (fp) + close(fp); +} + +/****************************************************************************/ +/* END OF LANGUAGE CARD BANK 2 0xd400-x0dfff 3KB SEGMENT */ +/****************************************************************************/ +#pragma code-name (pop) + +/* + * 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 readdatetime(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; + } +} + +/* + * Write the date and time stored in struct datetime in ProDOS on disk format, + * storing the bytes in array time[]. If pd25 is 0 then use legacy format + * (ProDOS 1.0-2.4.2) otherwise use the new date and time format introduced + * with ProDOS 2.5 + */ +void writedatetime(uchar pd25, struct datetime *dt, uchar time[4]) { + uint d, t; + if (pd25 == 0) { + /* ProDOS 1.0 to 2.4.2 date format */ + uint year = dt->year; + if (year > 2039) /* 2039 is last year */ + year = 2039; + if (year < 1940) /* 1940 is first year */ + year = 1940; + if (year >= 2000) + year -= 2000; + if (year >= 1900) + year -= 1900; + d = (year << 9) | (dt->month << 5) | dt->day; + t = (dt->hour << 8) | dt->minute; + } else { + /* ProDOS 2.5.0+ */ + t = ((dt->month + 1) << 12) | dt->year; + d = (dt->day << 11) | (dt->hour << 6) | dt->minute; + } + time[0] = d & 0xff; + time[1] = (d >> 8) & 0xff; + time[2] = t & 0xff; + time[3] = (t >> 8) & 0xff; +} + +/* + * Determine whether or not to perform a fix + * Return 0 not to perform fix, 1 to perform fix + */ +uint askfix(void) { + if (strlen(fixopts) == 0) + return 0; + switch (fixopts[0]) { + case '?': + fputs("Fix (y/n)? ", stdout); + if (tolower(getchar()) == 'y') + return 1; + return 0; + case 'y': + return 1; + default: + return 0; + } +} + +#ifdef FREELIST + +/* + * Read the free list + */ +int readfreelist(uchar device) { + uint i, flblk, flsize; + char *p; + freelist = (uchar*)malloc(FLSZ); + if (!freelist) + err(FATALALLOC, "No memory!"); + bzero(freelist, FLSZ); + usedlist = (uchar*)malloc(FLSZ); + if (!usedlist) + err(FATALALLOC, "No memory!"); + bzero(usedlist, FLSZ); + markused(0); /* Boot block */ + markused(1); /* SOS boot block */ + if (readdiskblock(device, 2, buf) == -1) { + err(NONFATAL, "Error reading volume dir"); + return -1; + } + flblk = buf[0x27] + 256U * buf[0x28]; + totblks = buf[0x29] + 256U * buf[0x2a]; + if (doverbose) + printf("Volume has %u blocks\n", totblks); + flsize = totblks / 4096U; + if ((flsize % 4096) >0) + ++flsize; + p = (char*)freelist; + for (i=0; i> bit); +} + +/* + * Mark a block as not free + */ +void marknotfree(uint blk) { + uint idx = blk / 8; + uint bit = blk % 8; + freelist[idx] &= ~(0x80 >> bit); +} + +/* + * 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) { + 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)) + err(WARN, "%s blk %u is marked free!", msg, blk); + if (isused(blk)) + err(WARN, "%s blk %u is already used!", msg, blk); + markused(blk); +} + +#endif + +/* + * Count the blocks in a seedling file + */ +int seedlingblocks(uchar device, uint keyblk, uint *blkcnt) { +#ifdef FREELIST + checkblock(keyblk, "Data"); +#endif + *blkcnt = 1; + return 0; +} + +/* + * Count the blocks in a sapling file + */ +int saplingblocks(uchar device, uint keyblk, uint *blkcnt) { + uint i, p; +#ifdef FREELIST + checkblock(keyblk, "Data"); +#endif + if (readdiskblock(device, keyblk, buf) == -1) { + err(NONFATAL, "Error reading blk %u", keyblk); + return -1; + } + *blkcnt = 1; + for (i=0; i<256; ++i) { + p = buf[i] + 256U * buf[i+256]; + if (p) { +#ifdef FREELIST + checkblock(p, "Data"); +#endif + ++(*blkcnt); + } + } + return 0; +} + +/* + * Count the blocks in a tree file + */ +int treeblocks(uchar device, uint keyblk, uint *blkcnt) { + uint i, p, b; +#ifdef FREELIST + checkblock(keyblk, "Tree index"); +#endif + if (readdiskblock(device, keyblk, buf2) == -1) { + err(NONFATAL, "Error reading blk %u", keyblk); + return -1; + } + *blkcnt = 1; + for (i=0; i<256; ++i) { + p = buf2[i] + 256U * buf2[i+256]; + if (p) { + 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) { + uint count, d_blks, r_blks, d_keyblk, r_keyblk; + uchar d_type, r_type; +#ifdef FREELIST + checkblock(keyblk, "Fork key"); +#endif + if (readdiskblock(device, keyblk, buf) == -1) { + err(NONFATAL, "Error reading blk %u", keyblk); + return -1; + } + *blkcnt = 1; + + d_type = buf[0x00]; + d_keyblk = buf[0x01] + 256U * buf[0x02]; + d_blks = buf[0x03] + 256U * buf[0x04]; + r_type = buf[0x100]; + r_keyblk = buf[0x101] + 256U * buf[0x102]; + r_blks = buf[0x103] + 256U * buf[0x104]; + + /* Data fork */ + switch (d_type) { + case 0x1: + /* Seedling */ + seedlingblocks(device, d_keyblk, &count); + break; + case 0x2: + /* Sapling */ + saplingblocks(device, d_keyblk, &count); + break; + case 0x3: + /* Tree */ + treeblocks(device, d_keyblk, &count); + break; + default: + err(NONFATAL, "Invalid storage type for data fork"); + count = 0; + break; + } + if (d_blks != count) { + if (count != 0) { + err(NONFATAL, + "Data fork size %u is incorrect, should be %u", + d_blks, count); +// TODO: Need to rethink the fix mode here ... it was buggy anyhow +// if (askfix() == 1) { +// buf[0x03] = count & 0xff; +// buf[0x04] = (count >> 8) & 0xff; +// } + } + } + *blkcnt += count; + + /* Resource fork */ + switch (r_type) { + case 0x1: + /* Seedling */ + seedlingblocks(device, r_keyblk, &count); + break; + case 0x2: + /* Sapling */ + saplingblocks(device, r_keyblk, &count); + break; + case 0x3: + /* Tree */ + treeblocks(device, r_keyblk, &count); + break; + default: + err(NONFATAL, "Invalid storage type for resource fork"); + count = 0; + break; + } + if (r_blks != count) { + if (count != 0) { + err(NONFATAL, + "Res fork size %u is incorrect, should be %u", + r_blks, count); + if (askfix() == 1) { +// TODO: Need to rethink the fix mode here ... it was buggy anyhow +// buf[0x103] = count & 0xff; +// buf[0x104] = (count >> 8) & 0xff; + } + } + } + *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) { + + struct pd_dirhdr *hdr; + uchar parentry, parentlen; + uint parblk; + char *dirname; + +#ifdef FREELIST + if (!dorecurse) + checkblock(keyblk, "Directory"); +#endif + if (readdiskblock(device, keyblk, buf) == -1) { + err(NONFATAL, "Error reading keyblock %u", keyblk); + return -1; + } + *blkcnt = 1; + hdr = (struct pd_dirhdr*)(buf + PTRSZ); + parentry = hdr->parentry; + parentlen = hdr->parentlen; + parblk = hdr->parptr[0] + 256U * hdr->parptr[1]; + + if (parblk != blocknum) { + err(NONFATAL, "Bad parent blk %u, should be %u", + parblk, blocknum); + if (askfix() == 1) { + hdr->parptr[0] = blocknum & 0xff; + hdr->parptr[1] = (blocknum >> 8) & 0xff; + } + } + + if (parentry != blkentries) { + err(NONFATAL, "Bad parent blk entry %u, should be %u", + parentry, blkentries); + if (askfix() == 1) { + hdr->parentry = blkentries; + } + } + if (parentlen != 0x27) { + err(NONFATAL, "Bad parent entry length"); + if (askfix() == 1) { + hdr->parentlen = 0x27; + } + } + dirname = buf + 0x05; + if (strncmp(dirname, ent->name, NMLEN)) { + err(NONFATAL, "Subdir name mismatch"); + } + + blocknum = buf[0x02] + 256U * buf[0x03]; + while (blocknum) { +#ifdef FREELIST + if (!dorecurse) + checkblock(blocknum, "Directory"); +#endif + if (readdiskblock(device, blocknum, buf) == -1) { + err(NONFATAL, "Error reading dir blk %u", 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) { + static struct dirblk *prev; + struct dirblk *p = (struct dirblk*)malloc(sizeof(struct dirblk)); +//printf("ALLOC %p\n", p); + if (!p) + err(FATALALLOC, "No memory!"); + p->blocknum = blocknum; + 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) { + static char namebuf[NMLEN+1]; + struct pd_dirhdr *hdr; + struct block *curblk; + struct datetime dt; + ulong eof; + uint filecount, idx, subdirs, blks, keyblk, hdrblk, count; + uchar blkentries, entries, pd25, i; + uint errsbefore = errcount; + uint blkcnt = 1; + uint hdrblknum = blocknum; + + numfiles = 0; + + blocks = (struct block*)malloc(sizeof(struct block)); +//printf("ALLOC %p\n", blocks); + if (!blocks) + err(FATALALLOC, "No memory!"); + curblk = blocks; + curblk->next = NULL; + curblk->blocknum = blocknum; + +#ifdef FREELIST + checkblock(blocknum, "Directory"); +#endif + if (readdiskblock(device, blocknum, curblk->data) == -1) { + err(NONFATAL, "Error reading dir blk %d", blkcnt); + return 1; + } + + hdr = (struct pd_dirhdr*)(curblk->data + PTRSZ); + + entsz = hdr->entlen; + entperblk = hdr->entperblk; + filecount = hdr->filecnt[0] + 256U * hdr->filecnt[1]; + + fixcase(hdr->name, currdir, + hdr->vers, hdr->minvers, hdr->typ_len & 0x0f); + + hline2(); + printf("Directory %s (%u", currdir, filecount); + printf(" %s)\n", filecount == 1 ? "entry" : "entries"); + hline(); + + /* Copy pointers to sorteddata[], zero the rest */ + bzero(curblk->sorteddata, BLKSZ); + memcpy(curblk->sorteddata, curblk->data, PTRSZ); + +#ifdef CHECK + if (entsz != 0x27) { + err(NONFATAL, "Error - bad entry size"); + return 1; + } + if (entperblk != 0x0d) { + err(NONFATAL, "Error - bad entries/block"); + return 1; + } +#endif + idx = entsz + PTRSZ; /* Skip header */ + blkentries = 2; + entries = 0; + subdirs = 0; + + while (1) { + uint errsbeforeent = errcount; + 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; + default: + err(FATALBADARG, "Invalid case option"); + } + } + + if (strlen(dateopts) > 0) { + struct datetime ctime, mtime; + readdatetime(ent->ctime, &ctime); + readdatetime(ent->mtime, &mtime); + switch (dateopts[0]) { + case 'n': + pd25 = 1; + break; + case 'o': + pd25 = 0; + break; + default: + err(FATALBADARG, "Invalid date option"); + } + writedatetime(pd25, &ctime, ent->ctime); + writedatetime(pd25, &mtime, ent->mtime); + } + + 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; + } + fputs(namebuf, stdout); +#endif + blks = ent->blksused[0] + 256U * ent->blksused[1]; + eof = ent->eof[0] + 256L * ent->eof[1] + 65536L * ent->eof[2]; + for (i=0; ityp_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; + + readdatetime(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 ? "*" : " ")); + + keyblk = ent->keyptr[0] + 256U * ent->keyptr[1]; + hdrblk = ent->hdrptr[0] + 256U * ent->hdrptr[1]; +#ifdef CHECK + if (hdrblk != hdrblknum) { + err(NONFATAL, "Header ptr %u, should be %u", + hdrblk, hdrblknum); + if (askfix() == 1) { + ent->hdrptr[0] = hdrblknum & 0xff; + ent->hdrptr[1] = (hdrblknum >> 8)&0xff; + } + } +#endif + switch (ent->typ_len & 0xf0) { + case 0xd0: + /* Subdirectory */ + enqueuesubdir(keyblk, subdirs++); +#ifdef CHECK + subdirblocks(device, keyblk, ent, + blocknum, blkentries, &count); +#endif + break; +#ifdef CHECK + 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: + err(NONFATAL, + "%s: unexpected storage type 0x%x", + namebuf, ent->typ_len & 0xf0); + count = 0; +#endif + } +#ifdef CHECK + if (blks != count) { + if (count != 0) { + err(NONFATAL, + "Blks used %u is incorrect, " + "should be %u", blks, count); + if (askfix() == 1) { + ent->blksused[0] = count&0xff; + ent->blksused[1] = + (count >> 8) & 0xff; + } + } + } +#endif + ++numfiles; + if (numfiles == MAXFILES) { + err(NONFATAL, "Too many files!"); + return 1; + } + if (errcount == errsbeforeent) { + for (i = 0; i < 53 - strlen(namebuf); ++i) + putchar(' '); + printf("%5u blocks [ OK ]", blks); + } else + putchar('\n'); + ++entries; + } + if (blkentries == entperblk) { + blocknum = curblk->data[0x02] + 256U*curblk->data[0x03]; + if (blocknum == 0) { + break; + } + curblk->next = + (struct block*)malloc(sizeof(struct block)); +//printf("ALLOC %p\n", curblk->next); + if (!curblk->next) + err(FATALALLOC, "No memory!"); + curblk = curblk->next; + curblk->next = NULL; + curblk->blocknum = blocknum; + ++blkcnt; +#ifdef FREELIST + checkblock(blocknum, "Directory"); +#endif + if ( readdiskblock(device, blocknum, + curblk->data) == -1) { + err(NONFATAL,"Error reading dir blk %d", + 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) { + err(NONFATAL, "Filecount %u wrong, should be %u", + filecount, entries); + if (askfix() == 1) { + hdr->filecnt[0] = entries & 0xff; + hdr->filecnt[1] = (entries >> 8) & 0xff; + } + } + return errcount - errsbefore; +} + +#ifdef SORT +/* + * 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 - filename sort in ascending order - case insensitive + */ +int cmp_name_asc_ci(const void *a, const void *b) { + return strncasecmp(((struct fileent*)a)->name, + ((struct fileent*)b)->name, NMLEN); +} + +/* + * Compare - filename sort in descending order - case insensitive + */ +int cmp_name_desc_ci(const void *a, const void *b) { + return strncasecmp(((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; +} + +/* + * No-op sort which just keeps items in the same order + */ +int cmp_noop(const void *a, const void *b) { + struct fileent *aa = (struct fileent*)a; + struct fileent *bb = (struct fileent*)b; + return aa->order - bb->order; +} +#endif + +/* + * Sort filelist[] + * s defines the field to sort on + */ +void sortlist(char s) { + uint i; + +#ifndef SORT + return; +#else + for (i=0; inext; + else + err(FATAL, "Int error"); + if (!p) + err(FATAL, "Int error"); + 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) { + struct block *source = blocks, *dest = blocks; + struct pd_dirent *ent; + struct pd_dirhdr *hdr; + char *srcptr, *dstptr; + uint parentblk; + + if (dodebug) { + printf(" from dirblk %03u entry %02u", srcblk, srcent); + printf(" to dirblk %03u entry %02u\n", dstblk, dstent); + } + + parentblk = blockidxtoblocknum(dstblk); + + while (--srcblk > 0) + source = source->next; + while (--dstblk > 0) + dest = dest->next; + srcptr = source->data + PTRSZ + (srcent-1) * entsz; + dstptr = dest->sorteddata + PTRSZ + (dstent-1) * entsz; + memcpy(dstptr, srcptr, entsz); + + /* For directories, update the parent dir entry number */ + 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) + err(FATAL, "Can't read subdir"); + 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) + err(FATAL, "Can't write subdir"); + } + } +} + +/* + * 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) { + uint i; + uchar destblk = 1; + uchar destentry = 2; /* Skip header on first block */ + copyent(1, 1, 1, 1, device); /* Copy directory header */ + for(i=0; iblocknum, i->sorteddata) == -1) { + err(NONFATAL, "Can't write block %u", 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 interactive(void) { + char w, l, d, f, z, wrt; + uchar level; + + doverbose = 1; + + puts(" Use ^ to return to previous question"); + +q1: + fputs("\nEnter path (e.g.: /H1) of starting directory> ", stdout); + scanf("%s", buf); + getchar(); // Eat the carriage return + +q2: + dowholedisk = dorecurse = 0; + puts("\nWhat to process ..."); + do { + puts("[-] Single directory [t] Tree [v] whole volume"); + w = getchar(); + } while (strchr("-tv^", w) == NULL); + if (w == '^') + goto q1; + switch (w) { + case 't': + dorecurse = 1; + break; + case 'v': + dorecurse = 1; + dowholedisk = 1; + } + +q3: + puts("\nMulti-level directory sort ..."); + puts(" Lower case option ascending order, upper case option descending order"); + puts(" [nN] Name [iI] Name (case-insens) [dD] Date/Time [tT] Type"); + puts(" [fF] Folders (dirs) [bB] Blks used [eE] EOF (file size)"); + fputs(" [-] Done with sorting", stdout); + for (level = 0; level < NLEVELS; ++level) { + do { + printf("\nLevel %d > ", level+1); + sortopts[level] = getchar(); + } while (strchr("-nNiIdDtTfFbBeE^", sortopts[level]) == NULL); + if (sortopts[level] == '-') { + sortopts[level] = '\0'; + break; + } + if (sortopts[level] == '^') + goto q2; + } + sortopts[NLEVELS] = '\0'; + +q4: + puts("\nFilename case conversion ..."); + do { + puts("[-] No change [l] Lower case [u] Upper case [i] Initial case [c] Camel case"); + l = getchar(); + } while (strchr("-luic^", l) == NULL); + if (l == '^') + goto q3; + if (l != '-') + caseopts[0] = l; + +q5: + puts("\nOn-disk date format conversion ..."); + do { + puts("[-] No change [n] -> New ProDOS 2.5 format [o] -> Old legacy ProDOS format"); + d = getchar(); + } while (strchr("-no^", d) == NULL); + if (d == '^') + goto q4; + if (d != '-') + dateopts[0] = d; + +q6: + puts("\nAttempt to fix errors? ..."); + do { + puts("[-] Never fix [?] Ask before fixing [a] Always fix"); + f = getchar(); + } while (strchr("-?a^", f) == NULL); + if (f == '^') + goto q5; + fixopts[0] = ((f == '-') ? 'n' : f); + +q7: + if (w == 'v') { + puts("\nZero free space? ..."); + do { + puts("[-] No, don't zero [z] Yes, zero free blocks"); + z = getchar(); + } while (strchr("-z^", z) == NULL); + if (z == '^') + goto q6; + if (z == 'z') + dozero = 1; + } + +q8: + puts("\nAllow writing to disk? ..."); + do { + puts("[-] No, don't write (Dry run) [w] Yes, commit changes to disk"); + wrt = getchar(); + } while (strchr("-w^", wrt) == NULL); + if (wrt == '^') + goto q7; + if (wrt == 'w') + dowrite = 1; + +//do_ctime = 0; /* -k ctime option */ +} + +void usage(void) { + printf("usage: sortdir [-s xxx] [-n x] [-rDwcvVh] path\n\n"); +#if 0 + printf(" Options: -s xxx Directory sort options\n"); + printf(" -n x Filename upper/lower case options\n"); + printf(" -d x Date format conversion options\n"); + printf(" -f x Fix mode\n"); + printf(" -r Recursive descent\n"); + printf(" -D Whole-disk mode (implies -r)\n"); + printf(" -w Enable writing to disk\n"); + printf(" -c Use create time rather than modify time\n"); + printf(" -z Zero free space\n"); + printf(" -v Verbose output\n"); + printf(" -V Verbose debugging output\n"); + printf(" -h This help\n"); + printf("\n"); + printf("-nx: Upper/lower case filenames, where x is:\n"); + printf(" l convert filenames to lower case eg: read.me\n"); + printf(" u convert filenames to upper case eg: READ.ME\n"); + printf(" i convert filenames to initial upper case eg: Read.me\n"); + printf(" c convert filenames to camel case eg: Read.Me\n"); + printf("\n"); + printf("-dx: Date/time on-disk format conversion, where x is:\n"); + printf(" n convert to new (ProDOS 2.5+) format\n"); + printf(" o convert to old (ProDOS 1.0-2.4.2, GSOS) format\n"); + printf("\n"); + printf("-sxxx: Dir sort, where xxx is a list of fields to sort\n"); + printf("on. The sort options are processed left-to-right.\n"); + printf(" n sort by filename ascending\n"); + printf(" N sort by filename descending\n"); + printf(" i sort by filename ascending - case insensitive\n"); + printf(" I sort by filename descending - case insensitive\n"); + printf(" d sort by modify (or create [-c]) date ascending\n"); + printf(" D sort by modify (or create [-c]) date descending\n"); + printf(" t sort by type ascending\n"); + printf(" T sort by type descending\n"); + printf(" f sort folders (directories) to top\n"); + printf(" F sort folders (directories) to bottom\n"); + printf(" b sort by blocks used ascending\n"); + printf(" B sort by blocks used descending\n"); + printf(" e sort by EOF position ascending\n"); + printf(" E sort by EOF position descending\n"); + printf("\n"); + printf("-fx: Fix mode, where x is:\n"); + printf(" ? prompt for each fix\n"); + printf(" n never fix\n"); + printf(" y always fix (be careful!)\n"); + printf("\n"); + printf("e.g.: sortdir -w -s nf .\n"); + printf("Will sort the current directory first by name (ascending),\n"); + printf("then sort directories to the top, and will write the\n"); + printf("sorted directory to disk.\n"); +#endif + err(FATAL, "Usage error"); +} + +/* + * Performs all actions for a single directory + * blocknum is the keyblock of the directory to process + */ +void processdir(uint device, uint blocknum) { + uchar i, errs; + flushall(); + errs = readdir(device, blocknum); +// if (doverbose) { +// printlist(); +// } + if ((strlen(fixopts) == 0) && errs) { + err(NONFATAL, "Error scanning directory, will not sort\n"); + goto done; + } + if (strlen(sortopts) > 0) { + if (doverbose) + fputs("Sorting: ", stdout); + for (i=0; i 0) || (strlen(fixopts) > 0) || + (strlen(dateopts) > 0)) && (strlen(sortopts) == 0)) + strncpy(sortopts, ".", 1); + + firstblk(((argc == 1) ? buf : argv[optind]), &dev, &blk); +#ifdef FREELIST + readfreelist(dev); +#endif + 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(dev); + +#ifdef FREELIST + free(freelist); + free(usedlist); +#endif + err(FINISHED, ""); + return 0; // Just to shut up warning +} +