From 60a72bd5defa0a6b19cbf4d1c75d74af22425157 Mon Sep 17 00:00:00 2001 From: Bobbi Webber-Manners Date: Thu, 30 Jul 2020 17:47:27 -0400 Subject: [PATCH] Fixed parsing of dateopts, caseopts, fixopts. --- sortdir.c | 5139 ++++++++++++++++++++++++++-------------------------- sortdir.po | Bin 143360 -> 143360 bytes 2 files changed, 2572 insertions(+), 2567 deletions(-) diff --git a/sortdir.c b/sortdir.c index 7a76bab..3c8f179 100644 --- a/sortdir.c +++ b/sortdir.c @@ -1,2567 +1,2572 @@ -/* - * SORTDIR - for Apple II (ProDOS) - * - * Bobbi January-June 2020 - * - * TODO: Find out why free(usedlist) at end -> crash. Memory corruption? - * TODO: EOF validation / fix: - * 1) Check this in readir() taking account of sparse files - * 2) When trimming a directory, need to update EOF for parent entry - * TODO: Print indication when a file is sparse - blocks in inverse video? - * TODO: Get both ProDOS-8 and GNO versions to build from this source - * - * Revision History - * v0.50 Initial alpha release on GitHub. Ported from GNO/ME version. - * v0.51 Made buf[] and buf2[] dynamic. - * v0.52 Support for aux memory. - * v0.53 Auto-sizing of filelist[] to fit available memory. - * v0.54 Make command line argument handling a compile time option. - * v0.55 Can use *all* of largest heap block for filelist[]. - * v0.56 Minor improvements to conditional compilation. - * v0.57 Fixed bugs in aux memory allocation, memory zeroing bug. - * v0.58 Fixed more bugs. Now working properly using aux memory. - * v0.59 Moved creation of filelist[] into buildsorttable(). More bugfix. - * v0.60 Modified fileent to be a union. Build it for each subsort. Saves RAM. - * v0.61 Squeezed fileent to be a few bytes smaller. Fixed folder sort. - * v0.62 Modified buildsorttable() to update existing filelist[]. - * v0.63 Made code work properly with #undef CHECK. - * v0.64 Fixed overflow in file count (entries). Added check to auxalloc(). - * v0.65 Fixed length passed to AUXMOVE in copyaux(). - * v0.66 Modified to build sorted blocks on the fly rather than in aux memory. - * v0.67 Fixed bug in v0.66 where garbage was written to end of directory. - * v0.68 Cleaned up error msgs. - * v0.69 Fixed support for drive number >2. (cc65 needs to be fixed too!) - * v0.70 Changed sort options to support mtime & ctime. Improved UI a bit. - * v0.71 Added support for allocating aux LC memory. - * v0.72 Initial support for freelist and usedlist in aux mem. (Slow!) - * v0.73 Speedup to checkfreeandused(); - * v0.74 Eliminate no-op sort. - * v0.75 Fix bug - crash when too many files to sort. - * v0.76 Fix bug - checkfreeandused() not traversing all freelist. - * v0.77 Implemented zeroblock() for ProDOS-8. - * v0.78 Improved error handling when too many files to sort. - * v0.79 Trim unused directory blocks after sorting. Write freelist to disk. - * v0.80 Reinstated no-op sort (useful for compacting dir without reordering). - * v0.81 Do not trim volume directory to <4 blocks. - * v0.82 Minor fix to TRIMDIR conditional compilation. - * v0.83 Print additional info on each file. - * v0.84 Minor fixup for builds without CHECK and FREELIST defined. - * v0.85 Only write free list if it has been changed. - * v0.86 Show 'invisible' access bit. - * v0.87 Change the fix options so '-' is ask, 'y'/'n' are always/never. - * v0.88 Show ProDOS 2.5 dates in inverse video (saves two columns!) - * v0.89 Commented out free(usedlist) which was crashing for some reason. - */ - -//#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 -#include // For revers() and clrscr() - -#define CHECK /* Perform additional integrity checking */ -#define SORT /* Enable sorting code */ -#define FREELIST /* Checking of free list */ -#define AUXMEM /* Auxiliary memory support on //e and up */ -#undef CMDLINE /* Command line option parsing */ -#undef TRIMDIR /* Enable trimming of directory blocks */ - -#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 */ - -/* - * 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 ENTSZ 0x27 /* Normal ProDOS directory entry size */ -#define ENTPERBLK 0x0d /* Normal ProDOS dirents per block */ -#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 - * Directory block is stored in data[] - */ -struct block { -#ifdef AUXMEM - char *data; /* Contents of block (pointer to auxmem) */ -#else - char data[BLKSZ]; /* Contents of block */ -#endif - uint blocknum; /* Block number on disk */ - struct block *next; -}; - -/* - * Entry for array of filenames used by qsort() - */ -struct fileent { - uchar blockidx; /* Index of dir block (1,2,3 ...) */ - uchar entrynum; /* Entry within the block */ - union { - char name[NMLEN-2]; /* Name converted to upper/lower case */ - char datetime[12+1];/* Date/time as a yyyymmddhhmm string */ - uchar type; /* ProDOS file type */ - uint blocks; /* Size in blocks */ - ulong eof; /* EOF position in bytes */ - }; - /* NOTE: Because name is unique we do not need the order field to make - * the sort stable, so we can let the name buffer overflow by 2 bytes - */ - uint order; /* Hack to make qsort() stable */ -}; - -/* - * 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; - uchar nodatetime; -}; - -/* - * Globals - */ -#ifdef AUXMEM -#define STARTAUX1 0x0800 // 46K main aux block -#define ENDAUX1 0xbfff -#define STARTAUX2 0xd000 // 12K block in aux LC -#define ENDAUX2 0xffff -static char *auxp = (char*)STARTAUX1; /* For allocating aux main */ -static char *auxp2 = (char*)STARTAUX2; /* For allocating aux LC */ -static char *auxlockp = (char*)STARTAUX1; /* Aux mem protection */ -#endif -#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 */ -static uchar flchanged = 0; /* 1 if free-list has been changed */ -static uint flsize; /* Size of free-list in blocks */ -static uint flblk; /* Block num for start of freelist */ -#endif -static char currdir[NMLEN+1]; /* Name of current directory */ -static struct block *blocks = NULL; /* List of directory disk blocks */ -static struct dirblk *dirs = NULL; /* List of key blocks of subdirs */ -static uint numfiles; /* Number of files in current dir */ -static uint maxfiles; /* Size of filelist[] */ -static uchar entsz; /* Bytes per file entry */ -static uchar entperblk; /* Number of entries per block */ -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 */ -#ifdef FREELIST -static uchar dozero = 0; /* -z zero free blocks option */ -#endif -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 */ - -// Allocated dynamically in main() -static char *buf; /* General purpose scratch buffer */ -static char *buf2; /* General purpose scratch buffer */ -static char *dirblkbuf; /* Used for reading directory blocks */ -static struct fileent *filelist; /* Used for qsort() */ - -/* Error messages */ -static const char err_nomem[] = "No memory!"; -static const char err_noaux[] = "No aux mem!"; -static const char err_rdblk1[] = "Can't read blk %u"; -static const char err_rdblk2[] = "Can't read blk %u ($%2x)"; -static const char err_wtblk1[] = "Can't write blk %u"; -static const char err_wtblk2[] = "Can't write blk %u ($%2x)"; -#ifdef CHECK -static const char err_stype2[] = "Bad storage type $%2x for %s"; -#endif -static const char err_odir1[] = "Can't open dir %s"; -static const char err_rddir1[] = "Can't read dir %s"; -static const char err_rdpar[] = "Can't read parent dir"; -#ifdef CHECK -static const char err_sdname[] = "Bad subdir name"; -static const char err_entsz2[] = "Bad entry size %u, should be %u"; -static const char err_entblk2[] = "Bad entries/blk %u, should be %u"; -static const char err_parblk3[] = "Bad parent %s %u, should be %u"; -static const char err_hdrblk2[] = "Bad hdr blk %u, should be %u"; -static const char err_access[] = "Bad access"; -static const char err_forksz3[] = "%s fork size %u is wrong, should be %u"; -static const char err_used2[] = "Blks used %u is wrong, should be %u"; -#endif -static const char err_many[] = "Too many files to sort"; -static const char err_count2[] = "Filecount %u wrong, should be %u"; -static const char err_nosort[] = "Not sorting due to errors"; -#ifdef FREELIST -static const char err_rdfl[] = "Can't read free list"; -static const char err_blfree1[] = "In use blk %u is marked free"; -static const char err_blfree2[] = "%s blk %u marked free"; -static const char err_blused1[] = "Unused blk %u not marked free"; -static const char err_blused2[] = "%s blk %u used elsewhere"; -#endif -static const char err_updsdir1[] = "Can't update subdir entry (%s)"; -static const char err_invopt[] = "Invalid %s option"; -#ifdef CMDLINE -static const char err_usage[] = "Usage error"; -#endif -static const char err_80col[] = "Need 80 cols"; -static const char err_128K[] = "Need 128K"; - - -/* Prototypes */ -#ifdef AUXMEM -void copyaux(char *src, char *dst, uint len, uchar dir); -char *auxalloc(uint bytes); -char *auxalloc2(uint bytes); -void lockaux(void); -void freeallaux(void); -#endif -void hline(void); -void hlinechar(char c); -void confirm(void); -void err(enum errtype severity, const 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(struct datetime *dt, uchar time[4]); -void printdatetime(struct datetime *dt); -uint askfix(void); -#ifdef FREELIST -int readfreelist(uchar device); -int isfree(uint blk); -int isused(uint blk); -void markused(uint blk); -void trimdirblock(uint blk); -void checkblock(uint blk, char *msg); -#endif -#ifdef CHECK -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); -#endif -void enqueuesubdir(uint blocknum, uint subdiridx); -int readdir(uint device, uint blocknum); -#ifdef SORT -uchar buildsorttable(char s, uchar callidx); -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); -#endif -void printlist(void); -uint blockidxtoblocknum(uint idx); -void copydirblkptrs(uint blkidx); -void copydirent(uint srcblk, uint srcent, uint dstblk, uint dstent, uint device); -uchar sortblock(uint device, uint dstblk); -uchar writedir(uchar device); -uchar writefreelist(uchar device); -void freeblocks(void); -void subtitle(char *s); -void interactive(void); -void processdir(uint device, uint blocknum); -#ifdef FREELIST -void checkfreeandused(uchar device); -void zeroblock(uchar device, uint blocknum); -void zerofreeblocks(uchar device, uint freeblks); -#endif -#ifdef CMDLINE -void usage(void); -void parseargs(void); -#endif - -enum errtype {WARN, NONFATAL, FATAL, FATALALLOC, FATALBADARG, FINISHED}; - -#ifdef AUXMEM - -/* Aux memory copy routine */ -#define FROMAUX 0 -#define TOAUX 1 -void copyaux(char *src, char *dst, uint len, uchar dir) { - char **a1 = (char**)0x3c; - char **a2 = (char**)0x3e; - char **a4 = (char**)0x42; - *a1 = src; - *a2 = src + len - 1; // AUXMOVE moves length+1 bytes!! - *a4 = dst; - if (dir == TOAUX) { - __asm__("sec"); // Copy main->aux - __asm__("jsr $c311"); // AUXMOVE - } else { - __asm__("clc"); // Copy aux->main - __asm__("jsr $c311"); // AUXMOVE - } -} - -/* Extremely simple aux memory allocator */ -char *auxalloc(uint bytes) { - char *p = auxp; - auxp += bytes; - if (auxp > (char*)ENDAUX1) - return auxalloc2(bytes); - return p; -} - -/* Extremely simple aux memory allocator */ -char *auxalloc2(uint bytes) { - char *p = auxp2; - auxp2 += bytes; - if (auxp2 < p) // ie: wrap around $ffff - err(FATAL, err_noaux); - return p; -} - -/* Lock aux memory below address provided - * Must be in main bank - */ -void lockaux(void) { - auxlockp = auxp; -} - -/* Free all aux memory above lock address */ -void freeallaux() { - auxp = (char*)auxlockp; - auxp2 = (char*)STARTAUX2; -} - -#endif - -/* Horizontal line */ -void hline(void) { - hlinechar('-'); -} - -void hlinechar(char c) { - uint i; - for (i = 0; i < 80; ++i) - putchar(c); -} - -void confirm() { - puts("[Press any key to restart system]"); - getchar(); -} - - -/****************************************************************************/ -/* LANGUAGE CARD BANK 2 0xd400-x0dfff 3KB */ -/****************************************************************************/ -#pragma code-name (push, "LC") - - -/* - * Display error message - */ -void err(enum errtype severity, const char *fmt, ...) { - va_list v; - uint rv = 0; - putchar('\n'); - 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); - va_end(v); - if (rv > 0) { - printf("\nStopping 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 CHECK -#ifdef FREELIST - if (flloaded) - if (isfree(blocknum)) - err(NONFATAL, err_blfree1, blocknum); -#endif -#endif -// BlockRec br; -// br.blockDevNum = device; -// br.blockDataBuffer = buf; -// br.blockNum = blocknum; -// READ_BLOCK(&br); -// int rc = toolerror(); -// if (rc) { -// err(FATAL, "Blk read failed, err=%x", rc); -// return -1; -// } - rc = dio_read(dio_hdl, blocknum, buf); - if (rc) - err(FATAL, err_rdblk2, blocknum, 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; - if ((strcmp(currdir, "LIB") == 0) || - (strcmp(currdir, "LIBRARIES") == 0)) { - printf("Not writing lib dir %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, err_wtblk2, blocknum, 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 < NMLEN; ++idx) - out[idx] = in[idx]; - out[len] = '\0'; - return; - } - vers <<= 1; - for (i = 0; i < 7; ++i) { - out[idx] = ((vers & 0x80) ? tolower(in[idx]) : in[idx]); - ++idx; - vers <<= 1; - } - for (i = 0; i < 8; ++i) { - out[idx] = ((minvers & 0x80) ? tolower(in[idx]) : in[idx]); - ++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) { - uint i; - uchar idx = 0; - *vers = 0x01; - *minvers = 0x00; - for (i = 0; i < 7; ++i) { - *vers <<= 1; - if ((idx < len) && isalpha(p[idx++])) - *vers |= 0x01; - } - for (i = 0; i < 8; ++i) { - *minvers <<= 1; - if ((idx < len) && isalpha(p[idx++])) - *minvers |= 0x01; - } -} - -/* - * Convert filename pointed to by p into upper case (which is recorded - * as a bitmap in the vers and minvers fields. - */ -void uppercase(char*, uchar, 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) { - uint i; - uchar idx = 0; - uchar capsflag = 1; - *vers = 0x01; - *minvers = 0x00; - for (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 (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; - } -} - -//segment "extra"; - -/* - * Read the first block of a directory and deduce the device ID and block - * number of the first block of the directory. - */ -void firstblk(char *dirname, uchar *device, uint *block) { - struct pd_dirhdr *hdr; - struct pd_dirent *ent; - int fp; - uint len; - uint parentblk, parententry, parententlen; - uchar slot, drive; - uchar *lastdev = (uchar*)0xbf30; /* Last device accessed by ProDOS */ - - fp = open(dirname, O_RDONLY); - if (!fp) { - err(FATAL, err_odir1, dirname); - goto ret; - } - - len = read(fp, buf, BLKSZ); - if (len != BLKSZ) { - err(FATAL, err_rddir1, dirname); - goto ret; - } - -// struct stat st; -// if (stat(dirname, &st) == -1) -// err(FATAL, "Can't stat %s", dirname); -// -// if (!S_ISDIR(st.st_mode)) -// err(FATAL, "%s is not a directory", dirname); -// -// *device = st.st_dev; - - /* - * lastdev is in the following format: - * ProDOS 2.5+ DSSS00DD (supports drives 1-8 for each slot) - * ProDOS 2.x DSSS0000 (supports drives 1-2 for each slot) - */ - *device = *lastdev; - slot = (*lastdev & 0x70) >> 4; - drive = ((*lastdev & 0x80) >> 7) + ((*lastdev & 0x03) << 1) + 1; - clrscr(); - printf("[Slot %u, Drive %u]\n", slot, drive); - *device = slot + (drive - 1) * 8; - dio_hdl = dio_open(*device); - - 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, err_stype2, hdr->typ_len & 0xf0, "dir"); - 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, err_rdpar); - - 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 ((d == 0) && (t == 0)) { - dt->nodatetime = 1; - return; - } - dt->nodatetime = 0; - 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[]. Supports both legacy format - * (ProDOS 1.0-2.4.2) and the new date and time format introduced - * with ProDOS 2.5 - */ -void writedatetime(struct datetime *dt, uchar time[4]) { - uint d, t; - if (dt->nodatetime == 1) { - time[0] = time[1] = time[2] = time[3] = 0; - return; - } - if (dt->ispd25format == 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; -} - -/* - * Print date/time value for directory listing - */ -void printdatetime(struct datetime *dt) { - if (dt->nodatetime) - fputs("-------- --:--", stderr); - else { - if (dt->ispd25format) - revers(1); - printf("%02d%02d%02d %02d:%02d", - dt->year, dt->month, dt->day, dt->hour, dt->minute); - revers(0); - } -} - -/* - * 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; - fputs(": Fix (y/n)? ", stdout); - switch (fixopts[0]) { - case '-': - if (tolower(getchar()) == 'y') - return 1; - return 0; - case 'y': - fputs("y", stdout); - return 1; - default: - fputs("n", stdout); - return 0; - } -} - -#ifdef FREELIST - -/* - * Read the free list - */ -int readfreelist(uchar device) { - uint i, f; - char *p; -#ifdef AUXMEM - bzero(buf, BLKSZ); - for (i = 0; i < 16; ++i) { - copyaux(buf, freelist + i * BLKSZ, BLKSZ, TOAUX); - copyaux(buf, usedlist + i * BLKSZ, BLKSZ, TOAUX); - } -#else - bzero(freelist, FLSZ); - bzero(usedlist, FLSZ); -#endif - markused(0); /* Boot block */ - markused(1); /* SOS boot block */ - if (readdiskblock(device, 2, buf) == -1) { - err(NONFATAL, err_rdblk1, 2); - return -1; - } - flblk = f = buf[0x27] + 256U * buf[0x28]; - totblks = buf[0x29] + 256U * buf[0x2a]; - if (doverbose) - printf("Volume has %u blocks\n", totblks); - flsize = totblks / 4096U; - if ((totblks % 4096) > 0) - ++flsize; - p = (char*)freelist; - for (i = 0; i < flsize; ++i) { - markused(f); -#ifdef AUXMEM - if (readdiskblock(device, f++, buf) == -1) { -#else - if (readdiskblock(device, f++, p) == -1) { -#endif - err(NONFATAL, err_rdfl); - return -1; - } -#ifdef AUXMEM - copyaux(buf, p, BLKSZ, TOAUX); -#endif - p += BLKSZ; - } - flloaded = 1; - return 0; -} - -/* - * Determine if block blk is free or not - */ -int isfree(uint blk) { - uchar temp; - uint idx = blk / 8; - uint bit = blk % 8; -#ifdef AUXMEM - copyaux(freelist + idx, &temp, 1, FROMAUX); - return (temp << bit) & 0x80 ? 1 : 0; -#else - return (freelist[idx] << bit) & 0x80 ? 1 : 0; -#endif -} - -/* - * Determine if block blk is used or not - */ -int isused(uint blk) { - uchar temp; - uint idx = blk / 8; - uint bit = blk % 8; -#ifdef AUXMEM - copyaux(usedlist + idx, &temp, 1, FROMAUX); - return (temp << bit) & 0x80 ? 1 : 0; -#else - return (usedlist[idx] << bit) & 0x80 ? 1 : 0; -#endif -} - -/* - * Mark a block as used - */ -void markused(uint blk) { - uchar temp; - uint idx = blk / 8; - uint bit = blk % 8; -#ifdef AUXMEM - copyaux(usedlist + idx, &temp, 1, FROMAUX); - temp |= (0x80 >> bit); - copyaux(&temp, usedlist + idx, 1, TOAUX); -#else - usedlist[idx] |= (0x80 >> bit); -#endif -} - -/* - * Mark a block as not used and add it to freelist - */ -void trimdirblock(uint blk) { - uchar temp; - uint idx = blk / 8; - uint bit = blk % 8; -#ifdef AUXMEM - copyaux(usedlist + idx, &temp, 1, FROMAUX); - temp &= ~(0x80 >> bit); - copyaux(&temp, usedlist + idx, 1, TOAUX); - copyaux(freelist + idx, &temp, 1, FROMAUX); - temp |= (0x80 >> bit); - copyaux(&temp, freelist + idx, 1, TOAUX); -#else - usedlist[idx] &= ~(0x80 >> bit); - freelist[idx] |= (0x80 >> bit); -#endif - flchanged = 1; -} - -/* - * 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, err_blfree2, msg, blk); - if (isused(blk)) - err(WARN, err_blused2, msg, blk); - markused(blk); -} - -#endif - -#ifdef CHECK - -/* - * Count the blocks in a seedling file - */ -#ifdef FREELIST -int seedlingblocks(uchar, uint keyblk, uint *blkcnt) { - checkblock(keyblk, "Data"); -#else -int seedlingblocks(uchar, uint, uint *blkcnt) { -#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, err_rdblk1, 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, err_rdblk1, 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, err_rdblk1, 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, err_stype2, d_type, "data fork"); - count = 0; - break; - } - if (d_blks != count) { - if (count != 0) { - err(NONFATAL, err_forksz3, "Data", 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, err_stype2, r_type, "res fork"); - count = 0; - break; - } - if (r_blks != count) { - if (count != 0) { - err(NONFATAL, err_forksz3, "Res", 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, err_rdblk1, 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, err_parblk3, "blk", parblk, blocknum); - if (askfix() == 1) { - hdr->parptr[0] = blocknum & 0xff; - hdr->parptr[1] = (blocknum >> 8) & 0xff; - } - } - - if (parentry != blkentries) { - err(NONFATAL, err_parblk3, "entry", parentry, blkentries); - if (askfix() == 1) { - hdr->parentry = blkentries; - } - } - if (parentlen != ENTSZ) { - err(NONFATAL, err_parblk3, "entry size", parentlen, ENTSZ); - if (askfix() == 1) { - hdr->parentlen = ENTSZ; - } - } - dirname = buf + 0x05; - if (strncmp(dirname, ent->name, NMLEN)) { - err(NONFATAL, err_sdname); - } - - blocknum = buf[0x02] + 256U * buf[0x03]; - while (blocknum) { -#ifdef FREELIST - if (!dorecurse) - checkblock(blocknum, "Directory"); -#endif - if (readdiskblock(device, blocknum, buf) == -1) { - err(NONFATAL, err_rdblk1, blocknum); - return -1; - } - ++(*blkcnt); - blocknum = buf[0x02] + 256U * buf[0x03]; - } - return 0; -} - -#endif - -/* - * 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)); - if (!p) - err(FATALALLOC, err_nomem); - 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. - * 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, entries, auxtype; -#ifdef CHECK - uint count; -#endif - uchar blkentries, i; - uint errsbefore = errcount; - uint blkcnt = 1; - uint hdrblknum = blocknum; - - numfiles = 0; - - blocks = (struct block*)malloc(sizeof(struct block)); - if (!blocks) - err(FATALALLOC, err_nomem); - curblk = blocks; - curblk->next = NULL; - curblk->blocknum = blocknum; - -#ifdef AUXMEM - curblk->data = auxalloc(BLKSZ); -#endif - -#ifdef FREELIST - checkblock(blocknum, "Directory"); -#endif - if (readdiskblock(device, blocknum, dirblkbuf) == -1) { - err(NONFATAL, err_rdblk1, blocknum); - goto done; - } - - hdr = (struct pd_dirhdr*)(dirblkbuf + 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); - - hlinechar('='); - printf("Directory %s (%u", currdir, filecount); - printf(" %s)\n", filecount == 1 ? "entry" : "entries"); - hline(); - fputs(" Name Blk EOF Typ Aux Perm Modified Created OK", stdout); - -#ifdef CHECK - if (entsz != ENTSZ) { - err(NONFATAL, err_entsz2, entsz, ENTSZ); - goto done; - } - if (entperblk != ENTPERBLK) { - err(NONFATAL, err_entblk2, entperblk, ENTPERBLK); - goto done; - } -#endif - idx = entsz + PTRSZ; /* Skip header */ - blkentries = 2; - entries = 0; - subdirs = 0; - - while (1) { - uint errsbeforeent = errcount; - struct pd_dirent *ent = (struct pd_dirent*)(dirblkbuf + 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, err_invopt, "case"); - } - } - - if (strlen(dateopts) > 0) { - struct datetime ctime, mtime; - readdatetime(ent->ctime, &ctime); - readdatetime(ent->mtime, &mtime); - switch (dateopts[0]) { - case 'n': - ctime.ispd25format = 1; - mtime.ispd25format = 1; - break; - case 'o': - ctime.ispd25format = 0; - mtime.ispd25format = 0; - break; - default: - err(FATALBADARG, err_invopt, "date"); - } - writedatetime(&ctime, ent->ctime); - writedatetime(&mtime, ent->mtime); - } - - fixcase(ent->name, namebuf, - ent->vers, ent->minvers, ent->typ_len & 0x0f); - - switch (ent->typ_len & 0xf0) { - case 0x10: - fputs("s ", stdout); - break; - case 0x20: - fputs("S ", stdout); - break; - case 0x30: - fputs("T ", stdout); - break; - case 0x40: - fputs("P ", stdout); - break; - case 0x50: - fputs("F ", stdout); - break; - case 0xd0: - fputs("D ", stdout); - break; - default: - fputs("? ", stdout); - break; - } - fputs(namebuf, stdout); - for (i = 0; i < 16 - strlen(namebuf); ++i) - putchar(' '); - - blks = ent->blksused[0] + 256U * ent->blksused[1]; - eof = ent->eof[0] + 256L * ent->eof[1] + 65536L * ent->eof[2]; - auxtype = ent->auxtype[0] + 256L * ent->auxtype[1]; - printf("%4d %8ld %02x %04x %c%c%c%c%c%c", - blks, eof, ent->type,auxtype, - (ent->access & 0x80) ? 'D' : '-', - (ent->access & 0x40) ? 'R' : '-', - (ent->access & 0x20) ? 'B' : '-', - (ent->access & 0x04) ? 'I' : '-', - (ent->access & 0x02) ? 'w' : '-', - (ent->access & 0x01) ? 'r' : '-'); - - readdatetime(ent->ctime, &dt); - putchar(' '); - printdatetime(&dt); - readdatetime(ent->mtime, &dt); - putchar(' '); - printdatetime(&dt); - putchar(' '); - - keyblk = ent->keyptr[0] + 256U * ent->keyptr[1]; - hdrblk = ent->hdrptr[0] + 256U * ent->hdrptr[1]; -#ifdef CHECK - if (ent->access & 0x18) { - err(NONFATAL, err_access); - if (askfix() == 1) - ent->access &= 0xe7; - } - if (hdrblk != hdrblknum) { - err(NONFATAL, err_hdrblk2, 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, err_stype2, ent->typ_len & 0xf0, "entry"); - count = 0; -#endif - } -#ifdef CHECK - if (blks != count) { - if (count != 0) { - err(NONFATAL, err_used2, blks, count); - if (askfix() == 1) { - ent->blksused[0] = count & 0xff; - ent->blksused[1] = (count >> 8) & 0xff; - } - } - } -#endif - ++numfiles; - if (errcount == errsbeforeent) { -#ifdef CHECK - puts(" *"); -#else - putchar('\n'); -#endif - } else - putchar('\n'); - ++entries; - } - if (blkentries == entperblk) { - blocknum = dirblkbuf[0x02] + 256U * dirblkbuf[0x03]; -#ifdef AUXMEM - copyaux(dirblkbuf, curblk->data, BLKSZ, TOAUX); -#else - memcpy(curblk->data, dirblkbuf, BLKSZ); -#endif - if (blocknum == 0) { - break; - } - curblk->next = (struct block*)malloc(sizeof(struct block)); - if (!curblk->next) - err(FATALALLOC, err_nomem); - curblk = curblk->next; - curblk->next = NULL; - curblk->blocknum = blocknum; - ++blkcnt; - -#ifdef AUXMEM - curblk->data = auxalloc(BLKSZ); -#endif - -#ifdef FREELIST - checkblock(blocknum, "Directory"); -#endif - if (readdiskblock(device, blocknum, dirblkbuf) == -1) { - err(NONFATAL, err_rdblk1, blocknum); - goto done; - } - - blkentries = 1; - idx = PTRSZ; - } else { - ++blkentries; - idx += entsz; - } - } - if (filecount != entries) { - err(NONFATAL, err_count2, filecount, entries); - if (askfix() == 1) { - hdr->filecnt[0] = entries & 0xff; - hdr->filecnt[1] = (entries >> 8) & 0xff; - } - } -#ifdef AUXMEM - copyaux(dirblkbuf, curblk->data, BLKSZ, TOAUX); -#else - memcpy(curblk->data, dirblkbuf, BLKSZ); -#endif - -done: - return errcount - errsbefore; -} - -#ifdef SORT - -/* - * Build filelist[], the table used by the sorting algorithm. - * s - character representing the sorting mode - * callidx - if 0, the routine populates the table, otherwise it updates - * and existing table - * Returns 1 on error, 0 if OK. - */ -uchar buildsorttable(char s, uchar callidx) { - static char namebuf[NMLEN+1]; - uint off; - uchar entry, i; - struct datetime dt; - struct pd_dirent *ent; - uint idx = 0; - struct block *b = blocks; - uchar firstent = 2; /* Skip first entry of first block */ - uchar blkidx = 1; - - while (b) { -#ifdef AUXMEM - copyaux(b->data, dirblkbuf, BLKSZ, FROMAUX); -#else - memcpy(dirblkbuf, b->data, BLKSZ); -#endif - for (entry = firstent; entry <= ENTPERBLK; ++entry) { - - off = PTRSZ + (entry - 1) * entsz; - ent = (struct pd_dirent*)(dirblkbuf + off); - - if (ent->typ_len != 0) { - - if (callidx == 0) { - /* Build filelist[] on first call for each dir */ - filelist[idx].blockidx = blkidx; - filelist[idx].entrynum = entry; - } else { - /* On subsequent calls Find existing entry in list and update it */ - for (idx = 0; idx < numfiles; ++idx) - if ((filelist[idx].blockidx == blkidx) && - (filelist[idx].entrynum == entry)) - break; - } - switch (tolower(s)) { - case 'n': - case 'i': - fixcase(ent->name, namebuf, - ent->vers, ent->minvers, ent->typ_len & 0x0f); - bzero(filelist[idx].name, NMLEN); - for (i = 0; i < (ent->typ_len & 0x0f); ++i) - filelist[idx].name[i] = namebuf[i]; - break; - case 'd': - case 't': - filelist[idx].type = ent->type; - break; - case 'b': - filelist[idx].blocks = - ent->blksused[0] + 256U * ent->blksused[1]; - break; - case 'e': - filelist[idx].eof = - ent->eof[0] + 256L * ent->eof[1] + 65536L * ent->eof[2]; - break; - case 'c': - readdatetime(ent->ctime, &dt); - case 'm': - readdatetime(ent->mtime, &dt); - sprintf(filelist[idx].datetime, "%04d%02d%02d%02d%02d", - dt.year, dt.month, dt.day, dt.hour, dt.minute); - break; - } - if (++idx == maxfiles) { - err(NONFATAL, err_many); - return 1; - } - } - } - b = b->next; - ++blkidx; - firstent = 1; - } - if (callidx == 0) - numfiles = idx; - - return 0; -} - -/* - * Compare - filename sort in ascending order - */ -int cmp_name_asc(const void *a, const void *b) { - return strncmp(((struct fileent*)a)->name, - ((struct fileent*)b)->name, NMLEN); -} - -/* - * Compare - filename sort in descending order - */ -int cmp_name_desc(const void *a, const void *b) { - return strncmp(((struct fileent*)b)->name, - ((struct fileent*)a)->name, NMLEN); -} - -/* - * Compare - 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; -} - -/* - * Compare - no-op compare which leaves order unchanged - */ -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; -} - -/* - * Sort filelist[] - * s defines the field to sort on - */ -void sortlist(char s) { - uint i; - char ss = tolower(s); - - /* - * We only populate the order field when NOT sorting by name. - * This lets us save two bytes by overflowing the name field into the - * order field. - */ - if ((ss != 'n') && (ss != 'i')) { - for (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 'i': - qsort(filelist, numfiles, sizeof(struct fileent), cmp_name_asc_ci); - break; - case 'I': - qsort(filelist, numfiles, sizeof(struct fileent), cmp_name_desc_ci); - break; - case 'c': - case 'm': - qsort(filelist, numfiles, sizeof(struct fileent), cmp_datetime_asc); - break; - case 'C': - case 'M': - 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 'd': - qsort(filelist, numfiles, sizeof(struct fileent), cmp_dir_beg); - break; - case 'D': - 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; - case '.': - qsort(filelist, numfiles, sizeof(struct fileent), cmp_noop); - break; - default: - err(FATALBADARG, err_invopt, "sort"); - } -} - -#endif - -/* - * Convert block index to block number - * Block index is 1-based (1,2,3 ...) - */ -uint blockidxtoblocknum(uint idx) { - uint i; - struct block *p = blocks; - for (i = 1; i < idx; ++i) - p = p->next; - return p->blocknum; -} - -/* - * Copy the 4 bytes of pointers from the directory block with index idx - * to the start of dirblkbuf[]; zeroes the rest of dirblkbuf[]. - */ -void copydirblkptrs(uint idx) { - uint i; - struct block *p = blocks; - for (i = 1; i < idx; ++i) - p = p->next; - bzero(dirblkbuf, BLKSZ); -#ifdef AUXMEM - copyaux(p->data, dirblkbuf, PTRSZ, FROMAUX); -#else - memcpy(dirblkbuf, p->data, PTRSZ); -#endif -} - -/* - * Copy a file entry from one srcblk, srcent to dstblk, dstent - * All indices are 1-based. - * dstblk is written to dirblkbuf[] - */ -void copydirent(uint srcblk, uint srcent, uint dstblk, uint dstent, uint device) { - struct block *source = 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); - } - - while (--srcblk > 0) - source = source->next; - - srcptr = source->data + PTRSZ + (srcent-1) * entsz; - dstptr = dirblkbuf + PTRSZ + (dstent-1) * entsz; - -#ifdef AUXMEM - copyaux(srcptr, dstptr, entsz, FROMAUX); -#else - memcpy(dstptr, srcptr, entsz); -#endif - - /* 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(NONFATAL, err_updsdir1, "read"); - hdr = (struct pd_dirhdr*)(buf + PTRSZ); - parentblk = blockidxtoblocknum(dstblk); - hdr->parptr[0] = parentblk & 0xff; - hdr->parptr[1] = (parentblk >> 8) & 0xff; - hdr->parentry = dstent; - if (dowrite) { - if (writediskblock(device, block, buf) == -1) - err(NONFATAL, err_updsdir1, "write"); - } - } -} - -/* - * Build sorted directory block dstblk (1,2,3...) using the sorted list in - * filelist[]. Note that the block and entry numbers are 1-based indices. - * Returns 1 if last block of directory, 0 otherwise. - */ -uchar sortblock(uint device, uint dstblk) { - uint i, firstlistent, lastlistent; - uchar destentry, rc = 0; - copydirblkptrs(dstblk); - if (dstblk == 1) { - copydirent(1, 1, 1, 1, device); /* Copy directory header */ - destentry = 2; /* Skip header on first block */ - firstlistent = 0; - lastlistent = entperblk - 2; - } else { - destentry = 1; - firstlistent = (dstblk - 1) * entperblk - 1; - lastlistent = firstlistent + entperblk - 1; - } - - if (lastlistent > numfiles - 1) { - - lastlistent = numfiles - 1; - -#ifdef TRIMDIR -// TODO: Fix EOF for directory -#ifdef AUXMEM - dirblkbuf[2] = dirblkbuf[3] = 0; /* Set next ptr to NULL */ -#else - p->data[2] = p->data[3] = 0; /* Set next ptr to NULL */ -#endif - rc = 1; -#else - rc = 0; -#endif - } - - for (i = firstlistent; i <= lastlistent; ++i) { - copydirent(filelist[i].blockidx, filelist[i].entrynum, - dstblk, destentry++, device); - } - return rc; -} - -/* - * Build each sorted directory block in turn, then write them - * out to disk. - */ -uchar writedir(uchar device) { - uint dstblk = 1; - uchar finished = 0; - struct block *b = blocks; - while (b) { - if (!finished) { - finished = sortblock(device, dstblk++); - if (writediskblock(device, b->blocknum, dirblkbuf) == -1) { - err(NONFATAL, err_wtblk1, b->blocknum); - return 1; - } -#ifdef FREELIST - } else { - /* Standard volume directory is blocks 2-5 (4 blocks) - * We will not trim volume directory to less than 4 blocks - */ - if (b->blocknum > 5) { - puts("Trimming dir blk"); - trimdirblock(b->blocknum); - } - } -#else - } -#endif - b = b->next; - } - return 0; -} - -#ifdef FREELIST - -/* - * Write the freelist back to disk. - */ -uchar writefreelist(uchar device) { - uchar b; - puts("Writing freelist ..."); - for (b = 0; b < flsize; ++b) { -#ifdef AUXMEM - copyaux(freelist + b * BLKSZ, dirblkbuf, BLKSZ, FROMAUX); -#else - memcpy(dirblkbuf, freelist + b * BLKSZ, BLKSZ); -#endif - if (writediskblock(device, flblk, dirblkbuf) == -1) { - err(NONFATAL, err_wtblk1, flblk); - return 1; - } - ++flblk; - } - return 0; -} - -#endif - -/* - * 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 subtitle(char *s) { - uchar i; - putchar('\n'); - hlinechar('_'); - revers(1); - fputs(s, stdout); - revers(0); - for (i = strlen(s); i < 79; ++i) - putchar(' '); - putchar('|'); -} - -void interactive(void) { - char w, l, d, f, wrt; -#ifdef FREELIST - char z; -#endif - uchar level; - - doverbose = 1; - - revers(1); - hlinechar(' '); - fputs("S O R T D I R v0.89 alpha Use ^ to return to previous question", stdout); - hlinechar(' '); - revers(0); - -q1: - putchar('\n'); - revers(1); - fputs("Enter start path>", stdout); - revers(0); - putchar(' '); - scanf("%s", buf); - getchar(); // Eat the carriage return - -q2: - dowholedisk = dorecurse = 0; - subtitle("What to process"); - do { - fputs("| [-] Directory | [t] Tree | [v] Volume | |", stdout); - 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: - subtitle("Multi-level directory sort"); - // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 - fputs("| Lower case option ascending order, upper case option descending order |", stdout); - fputs("| [nN] Name | [iI] Name (case-insens) | [tT] Type | [dD] Directories |", stdout); - fputs("| [cC] Creation | [mM] Modification | [bB] Blocks | [eE] EOF Position |", stdout); - fputs("| [-] Done | [.] Just compact dir | | |", stdout); - for (level = 0; level < NLEVELS; ++level) { - do { - printf("\nLevel %d > ", level+1); - sortopts[level] = getchar(); - } while (strchr("-.nNiItTdDcCmMbBeE^", sortopts[level]) == NULL); - if (sortopts[level] == '-') { - sortopts[level] = '\0'; - break; - } - if (sortopts[level] == '^') - goto q2; - } - sortopts[NLEVELS] = '\0'; - -q4: - subtitle("Filename case conversion"); - do { - // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 - fputs("| [-] No change | | | |", stdout); - fputs("| [l] Lowercase | [u] Uppercase | [i] Initial | [c] Camelcase |", stdout); - l = getchar(); - } while (strchr("-luic^", l) == NULL); - if (l == '^') - goto q3; - if (l != '-') - caseopts[0] = l; - -q5: - subtitle("Date format conversion"); - do { - fputs("| [-] No change | [n] 'New' ProDOS 2.5+ | [o] 'Old' legacy format |",stdout); - d = getchar(); - } while (strchr("-no^", d) == NULL); - if (d == '^') - goto q4; - if (d != '-') - dateopts[0] = d; - -q6: - subtitle("Attempt to fix errors?"); - do { - fputs("| [-] Always ask | [y] Always fix | [n] Never fix |", stderr); - f = getchar(); - } while (strchr("-yn^", f) == NULL); - if (f == '^') - goto q5; - fixopts[0] = ((f == '-') ? 'n' : f); - -#ifdef FREELIST - if (w == 'v') { - subtitle("Zero free space?"); - do { - fputs("| [-] No | [z] Zero free blocks | |", stderr); - z = getchar(); - } while (strchr("-z^", z) == NULL); - if (z == '^') - goto q6; - if (z == 'z') - dozero = 1; - } -#endif - - subtitle("Confirm write to disk"); - do { - fputs("| [-] No | [w] Write to disk | |", stderr); - wrt = getchar(); - } while (strchr("-w^", wrt) == NULL); - if (wrt == '^') - goto q6; - if (wrt == 'w') - dowrite = 1; -} - - -/* - * 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(); - if (readdir(device, blocknum) != 0) { - err(NONFATAL, err_nosort); - putchar('\n'); - goto done; - } -#ifdef SORT - if (strlen(sortopts) > 0) { - if (doverbose) - fputs("Sorting: ", stdout); - for (i = 0; i < strlen(sortopts); ++i) { - if (doverbose) - printf("[%c] ", sortopts[i]); - if (buildsorttable(sortopts[i], i) != 0) { - err(NONFATAL, err_nosort); - putchar('\n'); - goto done; - } - sortlist(sortopts[i]); - } - if (doverbose) - putchar('\n'); - if (dowrite) { - puts("Writing dir ..."); - errs = writedir(device); - } else { - revers(1); - fputs("Not writing dir", stdout); - revers(0); - putchar('\n'); - } - } -#endif -done: - freeblocks(); -#ifdef AUXMEM - freeallaux(); -#endif -} - -#ifdef FREELIST - -/* - * 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(uchar device) { - uchar fl, ul, bit; - uint byte, blk = 0, blkcnt = 0; - printf("Total blks %u", totblks); - for (byte = 0; byte < flsize * BLKSZ; ++byte) { -#ifdef AUXMEM - copyaux(freelist + byte, &fl, 1, FROMAUX); - copyaux(usedlist + byte, &ul, 1, FROMAUX); -#else - fl = freelist[byte]; - ul = usedlist[byte]; -#endif - for (bit = 0; bit < 8; ++bit) { - if (blk >= totblks) - break; - if ((fl << bit) & 0x80) { - /* Free */ - if ((ul << bit) & 0x80) { - /* ... and used */ - err(NONFATAL, err_blfree1, blk); - if (askfix() == 1) { - ++blkcnt; - fl &= ~(0x80 >> bit); -#ifdef AUXMEM - copyaux(&fl, freelist + byte, 1, TOAUX); -#else - freelist[byte] = fl; -#endif - flchanged = 1; - } - } - } else { - /* Not free */ - ++blkcnt; - if (!((ul << bit) & 0x80)) { - /* ... and not used */ - err(NONFATAL, err_blused1, blk); - if (askfix() == 1) { - --blkcnt; - fl |= (0x80 >> bit); -#ifdef AUXMEM - copyaux(&fl, freelist + byte, 1, TOAUX); -#else - freelist[byte] = fl; -#endif - flchanged = 1; - } - } - } - ++blk; - } - } - printf("\nFree blks %u\n", totblks - blkcnt); - - if (dozero) - zerofreeblocks(device, totblks - blkcnt); -} - -/* - * Zero block blocknum - */ -void zeroblock(uchar device, uint blocknum) { - bzero(buf, BLKSZ); - if (writediskblock(device, blocknum, buf) == -1) - err(FATAL, err_wtblk1, blocknum); -// 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, "Block write failed"); -} - -/* - * Zero all free blocks on the volume - */ -void zerofreeblocks(uchar device, uint freeblks) { - uint i, step = freeblks / 60, ctr = 0; - puts("Zeroing free blocks ..."); - for (i = 0; i < totblks; ++i) - if (isfree(i)) { - zeroblock(device, i); - ++ctr; - if (ctr == step) { - putchar('='); - fflush(stdout); - ctr = 0; - } - } - puts("\nDone zeroing!"); -} - -#endif - -#ifdef CMDLINE - -void usage(void) { - printf("usage: sortdir [-s xxx] [-n x] [-rDwvVh] path\n\n"); - 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(" -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(" c sort by create date/time ascending\n"); - printf(" C sort by create date/time descending\n"); - printf(" m sort by modify date/time ascending\n"); - printf(" M sort by modify date/time descending\n"); - printf(" t sort by type ascending\n"); - printf(" T sort by type descending\n"); - printf(" d sort directories to top\n"); - printf(" D sort 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"); - err(FATAL, err_usage); -} - -#define MAXNUMARGS 10 - -int argc; -char *argv[MAXNUMARGS]; - -void parseargs() { - char *p; - char i = 0, s = 0, prev = ' '; - argc = 1; - for (p = (char*)0x200; p <= (char*)0x27f; ++p) { - *p &= 0x7f; - if ((*p == 0) || (*p == 0x0d)) { - argv[argc - 1] = buf + s; - break; - } - if (*p == ' ') { - if (prev != ' ') { - buf[i++] = '\0'; - argv[argc - 1] = buf + s; - s = i; - ++argc; - } - } else { - buf[i++] = *p; - } - prev = *p; - } -} - -#endif - -//int main(int argc, char *argv[]) { -int main() { -#ifdef CMDLINE - int opt; -#endif - uchar dev; - uint blk; - - uchar *pp; - pp = (uchar*)0xbf98; - if (!(*pp & 0x02)) - err(FATAL, err_80col); -#ifdef AUXMEM - if ((*pp & 0x30) != 0x30) - err(FATAL, err_128K); -#endif - - // Clear system bit map - for (pp = (uchar*)0xbf58; pp <= (uchar*)0xbf6f; ++pp) - *pp = 0; - - videomode(VIDEOMODE_80COL); - - _heapadd((void*)0x0800, 0x1800); - //printf("\nHeap: %u %u\n", _heapmemavail(), _heapmaxavail()); - -#ifdef FREELIST - -#ifdef AUXMEM - freelist = (uchar*)auxalloc(FLSZ); -#else - freelist = (uchar*)malloc(FLSZ); -#endif - if (!freelist) - err(FATALALLOC, err_nomem); -#ifdef AUXMEM - usedlist = (uchar*)auxalloc(FLSZ); -#else - usedlist = (uchar*)malloc(FLSZ); -#endif - if (!usedlist) - err(FATALALLOC, err_nomem); - -#endif - -#ifdef AUXMEM - lockaux(); // Protect free list and used list -#endif - - buf = (char*)malloc(sizeof(char) * BLKSZ); - buf2 = (char*)malloc(sizeof(char) * BLKSZ); - dirblkbuf = (char*)malloc(sizeof(char) * BLKSZ); - //printf("\nHeap: %u %u\n", _heapmemavail(), _heapmaxavail()); - maxfiles = _heapmaxavail() / sizeof(struct fileent); - printf("[%u]\n", maxfiles); - filelist = (struct fileent*)malloc(sizeof(struct fileent) * maxfiles); - -#ifdef CMDLINE - parseargs(); -#endif - -#ifdef CMDLINE - if (argc == 1) - interactive(); - else { - if (argc < 2) - usage(); - while ((opt = getopt(argc, argv, "DrwvVzs:n:f:d:h")) != -1) { - switch (opt) { - 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 'z': - dozero = 1; - dowholedisk = 1; - dorecurse = 1; - break; - case 's': - strncpy(sortopts, optarg, NLEVELS); - break; - case 'n': - strncpy(caseopts, optarg, 1); - break; - case 'f': - strncpy(fixopts, optarg, 1); - break; - case 'd': - strncpy(dateopts, optarg, 1); - break; - case 'h': - default: - usage(); - } - } - - if (optind != argc - 1) - usage(); - } -#else - - interactive(); - -#endif - -#ifdef CMDLINE - firstblk(((argc == 1) ? buf : argv[optind]), &dev, &blk); -#else - firstblk(buf, &dev, &blk); -#endif - -#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); - } - } -#ifdef FREELIST - if (dowholedisk) { - checkfreeandused(dev); - if (dowrite && flchanged) - writefreelist(dev); - } - - free(freelist); -// free(usedlist); /// TODO This is crashing ATM -#endif - err(FINISHED, ""); - return 0; // Just to shut up warning -} - +/* + * SORTDIR - for Apple II (ProDOS) + * + * Bobbi January-June 2020 + * + * TODO: Find out why free(usedlist) at end -> crash. Memory corruption? + * TODO: EOF validation / fix: + * 1) Check this in readir() taking account of sparse files + * 2) When trimming a directory, need to update EOF for parent entry + * TODO: Print indication when a file is sparse - blocks in inverse video? + * TODO: Get both ProDOS-8 and GNO versions to build from this source + * + * Revision History + * v0.50 Initial alpha release on GitHub. Ported from GNO/ME version. + * v0.51 Made buf[] and buf2[] dynamic. + * v0.52 Support for aux memory. + * v0.53 Auto-sizing of filelist[] to fit available memory. + * v0.54 Make command line argument handling a compile time option. + * v0.55 Can use *all* of largest heap block for filelist[]. + * v0.56 Minor improvements to conditional compilation. + * v0.57 Fixed bugs in aux memory allocation, memory zeroing bug. + * v0.58 Fixed more bugs. Now working properly using aux memory. + * v0.59 Moved creation of filelist[] into buildsorttable(). More bugfix. + * v0.60 Modified fileent to be a union. Build it for each subsort. Saves RAM. + * v0.61 Squeezed fileent to be a few bytes smaller. Fixed folder sort. + * v0.62 Modified buildsorttable() to update existing filelist[]. + * v0.63 Made code work properly with #undef CHECK. + * v0.64 Fixed overflow in file count (entries). Added check to auxalloc(). + * v0.65 Fixed length passed to AUXMOVE in copyaux(). + * v0.66 Modified to build sorted blocks on the fly rather than in aux memory. + * v0.67 Fixed bug in v0.66 where garbage was written to end of directory. + * v0.68 Cleaned up error msgs. + * v0.69 Fixed support for drive number >2. (cc65 needs to be fixed too!) + * v0.70 Changed sort options to support mtime & ctime. Improved UI a bit. + * v0.71 Added support for allocating aux LC memory. + * v0.72 Initial support for freelist and usedlist in aux mem. (Slow!) + * v0.73 Speedup to checkfreeandused(); + * v0.74 Eliminate no-op sort. + * v0.75 Fix bug - crash when too many files to sort. + * v0.76 Fix bug - checkfreeandused() not traversing all freelist. + * v0.77 Implemented zeroblock() for ProDOS-8. + * v0.78 Improved error handling when too many files to sort. + * v0.79 Trim unused directory blocks after sorting. Write freelist to disk. + * v0.80 Reinstated no-op sort (useful for compacting dir without reordering). + * v0.81 Do not trim volume directory to <4 blocks. + * v0.82 Minor fix to TRIMDIR conditional compilation. + * v0.83 Print additional info on each file. + * v0.84 Minor fixup for builds without CHECK and FREELIST defined. + * v0.85 Only write free list if it has been changed. + * v0.86 Show 'invisible' access bit. + * v0.87 Change the fix options so '-' is ask, 'y'/'n' are always/never. + * v0.88 Show ProDOS 2.5 dates in inverse video (saves two columns!) + * v0.89 Commented out free(usedlist) which was crashing for some reason. + * v0.90 Fixed parsing of dateopts[], caseopts[], fixopts[] + */ + +//#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 +#include // For revers() and clrscr() + +#define CHECK /* Perform additional integrity checking */ +#define SORT /* Enable sorting code */ +#define FREELIST /* Checking of free list */ +#define AUXMEM /* Auxiliary memory support on //e and up */ +#undef CMDLINE /* Command line option parsing */ +#undef TRIMDIR /* Enable trimming of directory blocks */ + +#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 */ + +/* + * 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 ENTSZ 0x27 /* Normal ProDOS directory entry size */ +#define ENTPERBLK 0x0d /* Normal ProDOS dirents per block */ +#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 + * Directory block is stored in data[] + */ +struct block { +#ifdef AUXMEM + char *data; /* Contents of block (pointer to auxmem) */ +#else + char data[BLKSZ]; /* Contents of block */ +#endif + uint blocknum; /* Block number on disk */ + struct block *next; +}; + +/* + * Entry for array of filenames used by qsort() + */ +struct fileent { + uchar blockidx; /* Index of dir block (1,2,3 ...) */ + uchar entrynum; /* Entry within the block */ + union { + char name[NMLEN-2]; /* Name converted to upper/lower case */ + char datetime[12+1];/* Date/time as a yyyymmddhhmm string */ + uchar type; /* ProDOS file type */ + uint blocks; /* Size in blocks */ + ulong eof; /* EOF position in bytes */ + }; + /* NOTE: Because name is unique we do not need the order field to make + * the sort stable, so we can let the name buffer overflow by 2 bytes + */ + uint order; /* Hack to make qsort() stable */ +}; + +/* + * 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; + uchar nodatetime; +}; + +/* + * Globals + */ +#ifdef AUXMEM +#define STARTAUX1 0x0800 // 46K main aux block +#define ENDAUX1 0xbfff +#define STARTAUX2 0xd000 // 12K block in aux LC +#define ENDAUX2 0xffff +static char *auxp = (char*)STARTAUX1; /* For allocating aux main */ +static char *auxp2 = (char*)STARTAUX2; /* For allocating aux LC */ +static char *auxlockp = (char*)STARTAUX1; /* Aux mem protection */ +#endif +#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 */ +static uchar flchanged = 0; /* 1 if free-list has been changed */ +static uint flsize; /* Size of free-list in blocks */ +static uint flblk; /* Block num for start of freelist */ +#endif +static char currdir[NMLEN+1]; /* Name of current directory */ +static struct block *blocks = NULL; /* List of directory disk blocks */ +static struct dirblk *dirs = NULL; /* List of key blocks of subdirs */ +static uint numfiles; /* Number of files in current dir */ +static uint maxfiles; /* Size of filelist[] */ +static uchar entsz; /* Bytes per file entry */ +static uchar entperblk; /* Number of entries per block */ +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 */ +#ifdef FREELIST +static uchar dozero = 0; /* -z zero free blocks option */ +#endif +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 */ + +// Allocated dynamically in main() +static char *buf; /* General purpose scratch buffer */ +static char *buf2; /* General purpose scratch buffer */ +static char *dirblkbuf; /* Used for reading directory blocks */ +static struct fileent *filelist; /* Used for qsort() */ + +/* Error messages */ +static const char err_nomem[] = "No memory!"; +static const char err_noaux[] = "No aux mem!"; +static const char err_rdblk1[] = "Can't read blk %u"; +static const char err_rdblk2[] = "Can't read blk %u ($%2x)"; +static const char err_wtblk1[] = "Can't write blk %u"; +static const char err_wtblk2[] = "Can't write blk %u ($%2x)"; +#ifdef CHECK +static const char err_stype2[] = "Bad storage type $%2x for %s"; +#endif +static const char err_odir1[] = "Can't open dir %s"; +static const char err_rddir1[] = "Can't read dir %s"; +static const char err_rdpar[] = "Can't read parent dir"; +#ifdef CHECK +static const char err_sdname[] = "Bad subdir name"; +static const char err_entsz2[] = "Bad entry size %u, should be %u"; +static const char err_entblk2[] = "Bad entries/blk %u, should be %u"; +static const char err_parblk3[] = "Bad parent %s %u, should be %u"; +static const char err_hdrblk2[] = "Bad hdr blk %u, should be %u"; +static const char err_access[] = "Bad access"; +static const char err_forksz3[] = "%s fork size %u is wrong, should be %u"; +static const char err_used2[] = "Blks used %u is wrong, should be %u"; +#endif +static const char err_many[] = "Too many files to sort"; +static const char err_count2[] = "Filecount %u wrong, should be %u"; +static const char err_nosort[] = "Not sorting due to errors"; +#ifdef FREELIST +static const char err_rdfl[] = "Can't read free list"; +static const char err_blfree1[] = "In use blk %u is marked free"; +static const char err_blfree2[] = "%s blk %u marked free"; +static const char err_blused1[] = "Unused blk %u not marked free"; +static const char err_blused2[] = "%s blk %u used elsewhere"; +#endif +static const char err_updsdir1[] = "Can't update subdir entry (%s)"; +static const char err_invopt[] = "Invalid %s option"; +#ifdef CMDLINE +static const char err_usage[] = "Usage error"; +#endif +static const char err_80col[] = "Need 80 cols"; +static const char err_128K[] = "Need 128K"; + + +/* Prototypes */ +#ifdef AUXMEM +void copyaux(char *src, char *dst, uint len, uchar dir); +char *auxalloc(uint bytes); +char *auxalloc2(uint bytes); +void lockaux(void); +void freeallaux(void); +#endif +void hline(void); +void hlinechar(char c); +void confirm(void); +void err(enum errtype severity, const 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(struct datetime *dt, uchar time[4]); +void printdatetime(struct datetime *dt); +uint askfix(void); +#ifdef FREELIST +int readfreelist(uchar device); +int isfree(uint blk); +int isused(uint blk); +void markused(uint blk); +void trimdirblock(uint blk); +void checkblock(uint blk, char *msg); +#endif +#ifdef CHECK +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); +#endif +void enqueuesubdir(uint blocknum, uint subdiridx); +int readdir(uint device, uint blocknum); +#ifdef SORT +uchar buildsorttable(char s, uchar callidx); +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); +#endif +void printlist(void); +uint blockidxtoblocknum(uint idx); +void copydirblkptrs(uint blkidx); +void copydirent(uint srcblk, uint srcent, uint dstblk, uint dstent, uint device); +uchar sortblock(uint device, uint dstblk); +uchar writedir(uchar device); +uchar writefreelist(uchar device); +void freeblocks(void); +void subtitle(char *s); +void interactive(void); +void processdir(uint device, uint blocknum); +#ifdef FREELIST +void checkfreeandused(uchar device); +void zeroblock(uchar device, uint blocknum); +void zerofreeblocks(uchar device, uint freeblks); +#endif +#ifdef CMDLINE +void usage(void); +void parseargs(void); +#endif + +enum errtype {WARN, NONFATAL, FATAL, FATALALLOC, FATALBADARG, FINISHED}; + +#ifdef AUXMEM + +/* Aux memory copy routine */ +#define FROMAUX 0 +#define TOAUX 1 +void copyaux(char *src, char *dst, uint len, uchar dir) { + char **a1 = (char**)0x3c; + char **a2 = (char**)0x3e; + char **a4 = (char**)0x42; + *a1 = src; + *a2 = src + len - 1; // AUXMOVE moves length+1 bytes!! + *a4 = dst; + if (dir == TOAUX) { + __asm__("sec"); // Copy main->aux + __asm__("jsr $c311"); // AUXMOVE + } else { + __asm__("clc"); // Copy aux->main + __asm__("jsr $c311"); // AUXMOVE + } +} + +/* Extremely simple aux memory allocator */ +char *auxalloc(uint bytes) { + char *p = auxp; + auxp += bytes; + if (auxp > (char*)ENDAUX1) + return auxalloc2(bytes); + return p; +} + +/* Extremely simple aux memory allocator */ +char *auxalloc2(uint bytes) { + char *p = auxp2; + auxp2 += bytes; + if (auxp2 < p) // ie: wrap around $ffff + err(FATAL, err_noaux); + return p; +} + +/* Lock aux memory below address provided + * Must be in main bank + */ +void lockaux(void) { + auxlockp = auxp; +} + +/* Free all aux memory above lock address */ +void freeallaux() { + auxp = (char*)auxlockp; + auxp2 = (char*)STARTAUX2; +} + +#endif + +/* Horizontal line */ +void hline(void) { + hlinechar('-'); +} + +void hlinechar(char c) { + uint i; + for (i = 0; i < 80; ++i) + putchar(c); +} + +void confirm() { + puts("[Press any key to restart system]"); + getchar(); +} + + +/****************************************************************************/ +/* LANGUAGE CARD BANK 2 0xd400-x0dfff 3KB */ +/****************************************************************************/ +#pragma code-name (push, "LC") + + +/* + * Display error message + */ +void err(enum errtype severity, const char *fmt, ...) { + va_list v; + uint rv = 0; + putchar('\n'); + 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); + va_end(v); + if (rv > 0) { + printf("\nStopping 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 CHECK +#ifdef FREELIST + if (flloaded) + if (isfree(blocknum)) + err(NONFATAL, err_blfree1, blocknum); +#endif +#endif +// BlockRec br; +// br.blockDevNum = device; +// br.blockDataBuffer = buf; +// br.blockNum = blocknum; +// READ_BLOCK(&br); +// int rc = toolerror(); +// if (rc) { +// err(FATAL, "Blk read failed, err=%x", rc); +// return -1; +// } + rc = dio_read(dio_hdl, blocknum, buf); + if (rc) + err(FATAL, err_rdblk2, blocknum, 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; + if ((strcmp(currdir, "LIB") == 0) || + (strcmp(currdir, "LIBRARIES") == 0)) { + printf("Not writing lib dir %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, err_wtblk2, blocknum, 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 < NMLEN; ++idx) + out[idx] = in[idx]; + out[len] = '\0'; + return; + } + vers <<= 1; + for (i = 0; i < 7; ++i) { + out[idx] = ((vers & 0x80) ? tolower(in[idx]) : in[idx]); + ++idx; + vers <<= 1; + } + for (i = 0; i < 8; ++i) { + out[idx] = ((minvers & 0x80) ? tolower(in[idx]) : in[idx]); + ++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) { + uint i; + uchar idx = 0; + *vers = 0x01; + *minvers = 0x00; + for (i = 0; i < 7; ++i) { + *vers <<= 1; + if ((idx < len) && isalpha(p[idx++])) + *vers |= 0x01; + } + for (i = 0; i < 8; ++i) { + *minvers <<= 1; + if ((idx < len) && isalpha(p[idx++])) + *minvers |= 0x01; + } +} + +/* + * Convert filename pointed to by p into upper case (which is recorded + * as a bitmap in the vers and minvers fields. + */ +void uppercase(char*, uchar, 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) { + uint i; + uchar idx = 0; + uchar capsflag = 1; + *vers = 0x01; + *minvers = 0x00; + for (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 (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; + } +} + +//segment "extra"; + +/* + * Read the first block of a directory and deduce the device ID and block + * number of the first block of the directory. + */ +void firstblk(char *dirname, uchar *device, uint *block) { + struct pd_dirhdr *hdr; + struct pd_dirent *ent; + int fp; + uint len; + uint parentblk, parententry, parententlen; + uchar slot, drive; + uchar *lastdev = (uchar*)0xbf30; /* Last device accessed by ProDOS */ + + fp = open(dirname, O_RDONLY); + if (!fp) { + err(FATAL, err_odir1, dirname); + goto ret; + } + + len = read(fp, buf, BLKSZ); + if (len != BLKSZ) { + err(FATAL, err_rddir1, dirname); + goto ret; + } + +// struct stat st; +// if (stat(dirname, &st) == -1) +// err(FATAL, "Can't stat %s", dirname); +// +// if (!S_ISDIR(st.st_mode)) +// err(FATAL, "%s is not a directory", dirname); +// +// *device = st.st_dev; + + /* + * lastdev is in the following format: + * ProDOS 2.5+ DSSS00DD (supports drives 1-8 for each slot) + * ProDOS 2.x DSSS0000 (supports drives 1-2 for each slot) + */ + *device = *lastdev; + slot = (*lastdev & 0x70) >> 4; + drive = ((*lastdev & 0x80) >> 7) + ((*lastdev & 0x03) << 1) + 1; + clrscr(); + printf("[Slot %u, Drive %u]\n", slot, drive); + *device = slot + (drive - 1) * 8; + dio_hdl = dio_open(*device); + + 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, err_stype2, hdr->typ_len & 0xf0, "dir"); + 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, err_rdpar); + + 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 ((d == 0) && (t == 0)) { + dt->nodatetime = 1; + return; + } + dt->nodatetime = 0; + 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[]. Supports both legacy format + * (ProDOS 1.0-2.4.2) and the new date and time format introduced + * with ProDOS 2.5 + */ +void writedatetime(struct datetime *dt, uchar time[4]) { + uint d, t; + if (dt->nodatetime == 1) { + time[0] = time[1] = time[2] = time[3] = 0; + return; + } + if (dt->ispd25format == 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; +} + +/* + * Print date/time value for directory listing + */ +void printdatetime(struct datetime *dt) { + if (dt->nodatetime) + fputs("-------- --:--", stderr); + else { + if (dt->ispd25format) + revers(1); + printf("%02d%02d%02d %02d:%02d", + dt->year, dt->month, dt->day, dt->hour, dt->minute); + revers(0); + } +} + +/* + * 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; + fputs(": Fix (y/n)? ", stdout); + switch (fixopts[0]) { + case '-': + if (tolower(getchar()) == 'y') + return 1; + return 0; + case 'y': + fputs("y", stdout); + return 1; + default: + fputs("n", stdout); + return 0; + } +} + +#ifdef FREELIST + +/* + * Read the free list + */ +int readfreelist(uchar device) { + uint i, f; + char *p; +#ifdef AUXMEM + bzero(buf, BLKSZ); + for (i = 0; i < 16; ++i) { + copyaux(buf, freelist + i * BLKSZ, BLKSZ, TOAUX); + copyaux(buf, usedlist + i * BLKSZ, BLKSZ, TOAUX); + } +#else + bzero(freelist, FLSZ); + bzero(usedlist, FLSZ); +#endif + markused(0); /* Boot block */ + markused(1); /* SOS boot block */ + if (readdiskblock(device, 2, buf) == -1) { + err(NONFATAL, err_rdblk1, 2); + return -1; + } + flblk = f = buf[0x27] + 256U * buf[0x28]; + totblks = buf[0x29] + 256U * buf[0x2a]; + if (doverbose) + printf("Volume has %u blocks\n", totblks); + flsize = totblks / 4096U; + if ((totblks % 4096) > 0) + ++flsize; + p = (char*)freelist; + for (i = 0; i < flsize; ++i) { + markused(f); +#ifdef AUXMEM + if (readdiskblock(device, f++, buf) == -1) { +#else + if (readdiskblock(device, f++, p) == -1) { +#endif + err(NONFATAL, err_rdfl); + return -1; + } +#ifdef AUXMEM + copyaux(buf, p, BLKSZ, TOAUX); +#endif + p += BLKSZ; + } + flloaded = 1; + return 0; +} + +/* + * Determine if block blk is free or not + */ +int isfree(uint blk) { + uchar temp; + uint idx = blk / 8; + uint bit = blk % 8; +#ifdef AUXMEM + copyaux(freelist + idx, &temp, 1, FROMAUX); + return (temp << bit) & 0x80 ? 1 : 0; +#else + return (freelist[idx] << bit) & 0x80 ? 1 : 0; +#endif +} + +/* + * Determine if block blk is used or not + */ +int isused(uint blk) { + uchar temp; + uint idx = blk / 8; + uint bit = blk % 8; +#ifdef AUXMEM + copyaux(usedlist + idx, &temp, 1, FROMAUX); + return (temp << bit) & 0x80 ? 1 : 0; +#else + return (usedlist[idx] << bit) & 0x80 ? 1 : 0; +#endif +} + +/* + * Mark a block as used + */ +void markused(uint blk) { + uchar temp; + uint idx = blk / 8; + uint bit = blk % 8; +#ifdef AUXMEM + copyaux(usedlist + idx, &temp, 1, FROMAUX); + temp |= (0x80 >> bit); + copyaux(&temp, usedlist + idx, 1, TOAUX); +#else + usedlist[idx] |= (0x80 >> bit); +#endif +} + +/* + * Mark a block as not used and add it to freelist + */ +void trimdirblock(uint blk) { + uchar temp; + uint idx = blk / 8; + uint bit = blk % 8; +#ifdef AUXMEM + copyaux(usedlist + idx, &temp, 1, FROMAUX); + temp &= ~(0x80 >> bit); + copyaux(&temp, usedlist + idx, 1, TOAUX); + copyaux(freelist + idx, &temp, 1, FROMAUX); + temp |= (0x80 >> bit); + copyaux(&temp, freelist + idx, 1, TOAUX); +#else + usedlist[idx] &= ~(0x80 >> bit); + freelist[idx] |= (0x80 >> bit); +#endif + flchanged = 1; +} + +/* + * 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, err_blfree2, msg, blk); + if (isused(blk)) + err(WARN, err_blused2, msg, blk); + markused(blk); +} + +#endif + +#ifdef CHECK + +/* + * Count the blocks in a seedling file + */ +#ifdef FREELIST +int seedlingblocks(uchar, uint keyblk, uint *blkcnt) { + checkblock(keyblk, "Data"); +#else +int seedlingblocks(uchar, uint, uint *blkcnt) { +#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, err_rdblk1, 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, err_rdblk1, 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, err_rdblk1, 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, err_stype2, d_type, "data fork"); + count = 0; + break; + } + if (d_blks != count) { + if (count != 0) { + err(NONFATAL, err_forksz3, "Data", 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, err_stype2, r_type, "res fork"); + count = 0; + break; + } + if (r_blks != count) { + if (count != 0) { + err(NONFATAL, err_forksz3, "Res", 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, err_rdblk1, 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, err_parblk3, "blk", parblk, blocknum); + if (askfix() == 1) { + hdr->parptr[0] = blocknum & 0xff; + hdr->parptr[1] = (blocknum >> 8) & 0xff; + } + } + + if (parentry != blkentries) { + err(NONFATAL, err_parblk3, "entry", parentry, blkentries); + if (askfix() == 1) { + hdr->parentry = blkentries; + } + } + if (parentlen != ENTSZ) { + err(NONFATAL, err_parblk3, "entry size", parentlen, ENTSZ); + if (askfix() == 1) { + hdr->parentlen = ENTSZ; + } + } + dirname = buf + 0x05; + if (strncmp(dirname, ent->name, NMLEN)) { + err(NONFATAL, err_sdname); + } + + blocknum = buf[0x02] + 256U * buf[0x03]; + while (blocknum) { +#ifdef FREELIST + if (!dorecurse) + checkblock(blocknum, "Directory"); +#endif + if (readdiskblock(device, blocknum, buf) == -1) { + err(NONFATAL, err_rdblk1, blocknum); + return -1; + } + ++(*blkcnt); + blocknum = buf[0x02] + 256U * buf[0x03]; + } + return 0; +} + +#endif + +/* + * 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)); + if (!p) + err(FATALALLOC, err_nomem); + 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. + * 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, entries, auxtype; +#ifdef CHECK + uint count; +#endif + uchar blkentries, i; + uint errsbefore = errcount; + uint blkcnt = 1; + uint hdrblknum = blocknum; + + numfiles = 0; + + blocks = (struct block*)malloc(sizeof(struct block)); + if (!blocks) + err(FATALALLOC, err_nomem); + curblk = blocks; + curblk->next = NULL; + curblk->blocknum = blocknum; + +#ifdef AUXMEM + curblk->data = auxalloc(BLKSZ); +#endif + +#ifdef FREELIST + checkblock(blocknum, "Directory"); +#endif + if (readdiskblock(device, blocknum, dirblkbuf) == -1) { + err(NONFATAL, err_rdblk1, blocknum); + goto done; + } + + hdr = (struct pd_dirhdr*)(dirblkbuf + 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); + + hlinechar('='); + printf("Directory %s (%u", currdir, filecount); + printf(" %s)\n", filecount == 1 ? "entry" : "entries"); + hline(); + fputs(" Name Blk EOF Typ Aux Perm Modified Created OK", stdout); + +#ifdef CHECK + if (entsz != ENTSZ) { + err(NONFATAL, err_entsz2, entsz, ENTSZ); + goto done; + } + if (entperblk != ENTPERBLK) { + err(NONFATAL, err_entblk2, entperblk, ENTPERBLK); + goto done; + } +#endif + idx = entsz + PTRSZ; /* Skip header */ + blkentries = 2; + entries = 0; + subdirs = 0; + + while (1) { + uint errsbeforeent = errcount; + struct pd_dirent *ent = (struct pd_dirent*)(dirblkbuf + 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, err_invopt, "case"); + } + } + + if (strlen(dateopts) > 0) { + struct datetime ctime, mtime; + readdatetime(ent->ctime, &ctime); + readdatetime(ent->mtime, &mtime); + switch (dateopts[0]) { + case 'n': + ctime.ispd25format = 1; + mtime.ispd25format = 1; + break; + case 'o': + ctime.ispd25format = 0; + mtime.ispd25format = 0; + break; + default: + err(FATALBADARG, err_invopt, "date"); + } + writedatetime(&ctime, ent->ctime); + writedatetime(&mtime, ent->mtime); + } + + fixcase(ent->name, namebuf, + ent->vers, ent->minvers, ent->typ_len & 0x0f); + + switch (ent->typ_len & 0xf0) { + case 0x10: + fputs("s ", stdout); + break; + case 0x20: + fputs("S ", stdout); + break; + case 0x30: + fputs("T ", stdout); + break; + case 0x40: + fputs("P ", stdout); + break; + case 0x50: + fputs("F ", stdout); + break; + case 0xd0: + fputs("D ", stdout); + break; + default: + fputs("? ", stdout); + break; + } + fputs(namebuf, stdout); + for (i = 0; i < 16 - strlen(namebuf); ++i) + putchar(' '); + + blks = ent->blksused[0] + 256U * ent->blksused[1]; + eof = ent->eof[0] + 256L * ent->eof[1] + 65536L * ent->eof[2]; + auxtype = ent->auxtype[0] + 256L * ent->auxtype[1]; + printf("%4d %8ld %02x %04x %c%c%c%c%c%c", + blks, eof, ent->type,auxtype, + (ent->access & 0x80) ? 'D' : '-', + (ent->access & 0x40) ? 'R' : '-', + (ent->access & 0x20) ? 'B' : '-', + (ent->access & 0x04) ? 'I' : '-', + (ent->access & 0x02) ? 'w' : '-', + (ent->access & 0x01) ? 'r' : '-'); + + readdatetime(ent->ctime, &dt); + putchar(' '); + printdatetime(&dt); + readdatetime(ent->mtime, &dt); + putchar(' '); + printdatetime(&dt); + putchar(' '); + + keyblk = ent->keyptr[0] + 256U * ent->keyptr[1]; + hdrblk = ent->hdrptr[0] + 256U * ent->hdrptr[1]; +#ifdef CHECK + if (ent->access & 0x18) { + err(NONFATAL, err_access); + if (askfix() == 1) + ent->access &= 0xe7; + } + if (hdrblk != hdrblknum) { + err(NONFATAL, err_hdrblk2, 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, err_stype2, ent->typ_len & 0xf0, "entry"); + count = 0; +#endif + } +#ifdef CHECK + if (blks != count) { + if (count != 0) { + err(NONFATAL, err_used2, blks, count); + if (askfix() == 1) { + ent->blksused[0] = count & 0xff; + ent->blksused[1] = (count >> 8) & 0xff; + } + } + } +#endif + ++numfiles; + if (errcount == errsbeforeent) { +#ifdef CHECK + puts(" *"); +#else + putchar('\n'); +#endif + } else + putchar('\n'); + ++entries; + } + if (blkentries == entperblk) { + blocknum = dirblkbuf[0x02] + 256U * dirblkbuf[0x03]; +#ifdef AUXMEM + copyaux(dirblkbuf, curblk->data, BLKSZ, TOAUX); +#else + memcpy(curblk->data, dirblkbuf, BLKSZ); +#endif + if (blocknum == 0) { + break; + } + curblk->next = (struct block*)malloc(sizeof(struct block)); + if (!curblk->next) + err(FATALALLOC, err_nomem); + curblk = curblk->next; + curblk->next = NULL; + curblk->blocknum = blocknum; + ++blkcnt; + +#ifdef AUXMEM + curblk->data = auxalloc(BLKSZ); +#endif + +#ifdef FREELIST + checkblock(blocknum, "Directory"); +#endif + if (readdiskblock(device, blocknum, dirblkbuf) == -1) { + err(NONFATAL, err_rdblk1, blocknum); + goto done; + } + + blkentries = 1; + idx = PTRSZ; + } else { + ++blkentries; + idx += entsz; + } + } + if (filecount != entries) { + err(NONFATAL, err_count2, filecount, entries); + if (askfix() == 1) { + hdr->filecnt[0] = entries & 0xff; + hdr->filecnt[1] = (entries >> 8) & 0xff; + } + } +#ifdef AUXMEM + copyaux(dirblkbuf, curblk->data, BLKSZ, TOAUX); +#else + memcpy(curblk->data, dirblkbuf, BLKSZ); +#endif + +done: + return errcount - errsbefore; +} + +#ifdef SORT + +/* + * Build filelist[], the table used by the sorting algorithm. + * s - character representing the sorting mode + * callidx - if 0, the routine populates the table, otherwise it updates + * and existing table + * Returns 1 on error, 0 if OK. + */ +uchar buildsorttable(char s, uchar callidx) { + static char namebuf[NMLEN+1]; + uint off; + uchar entry, i; + struct datetime dt; + struct pd_dirent *ent; + uint idx = 0; + struct block *b = blocks; + uchar firstent = 2; /* Skip first entry of first block */ + uchar blkidx = 1; + + while (b) { +#ifdef AUXMEM + copyaux(b->data, dirblkbuf, BLKSZ, FROMAUX); +#else + memcpy(dirblkbuf, b->data, BLKSZ); +#endif + for (entry = firstent; entry <= ENTPERBLK; ++entry) { + + off = PTRSZ + (entry - 1) * entsz; + ent = (struct pd_dirent*)(dirblkbuf + off); + + if (ent->typ_len != 0) { + + if (callidx == 0) { + /* Build filelist[] on first call for each dir */ + filelist[idx].blockidx = blkidx; + filelist[idx].entrynum = entry; + } else { + /* On subsequent calls Find existing entry in list and update it */ + for (idx = 0; idx < numfiles; ++idx) + if ((filelist[idx].blockidx == blkidx) && + (filelist[idx].entrynum == entry)) + break; + } + switch (tolower(s)) { + case 'n': + case 'i': + fixcase(ent->name, namebuf, + ent->vers, ent->minvers, ent->typ_len & 0x0f); + bzero(filelist[idx].name, NMLEN); + for (i = 0; i < (ent->typ_len & 0x0f); ++i) + filelist[idx].name[i] = namebuf[i]; + break; + case 'd': + case 't': + filelist[idx].type = ent->type; + break; + case 'b': + filelist[idx].blocks = + ent->blksused[0] + 256U * ent->blksused[1]; + break; + case 'e': + filelist[idx].eof = + ent->eof[0] + 256L * ent->eof[1] + 65536L * ent->eof[2]; + break; + case 'c': + readdatetime(ent->ctime, &dt); + case 'm': + readdatetime(ent->mtime, &dt); + sprintf(filelist[idx].datetime, "%04d%02d%02d%02d%02d", + dt.year, dt.month, dt.day, dt.hour, dt.minute); + break; + } + if (++idx == maxfiles) { + err(NONFATAL, err_many); + return 1; + } + } + } + b = b->next; + ++blkidx; + firstent = 1; + } + if (callidx == 0) + numfiles = idx; + + return 0; +} + +/* + * Compare - filename sort in ascending order + */ +int cmp_name_asc(const void *a, const void *b) { + return strncmp(((struct fileent*)a)->name, + ((struct fileent*)b)->name, NMLEN); +} + +/* + * Compare - filename sort in descending order + */ +int cmp_name_desc(const void *a, const void *b) { + return strncmp(((struct fileent*)b)->name, + ((struct fileent*)a)->name, NMLEN); +} + +/* + * Compare - 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; +} + +/* + * Compare - no-op compare which leaves order unchanged + */ +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; +} + +/* + * Sort filelist[] + * s defines the field to sort on + */ +void sortlist(char s) { + uint i; + char ss = tolower(s); + + /* + * We only populate the order field when NOT sorting by name. + * This lets us save two bytes by overflowing the name field into the + * order field. + */ + if ((ss != 'n') && (ss != 'i')) { + for (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 'i': + qsort(filelist, numfiles, sizeof(struct fileent), cmp_name_asc_ci); + break; + case 'I': + qsort(filelist, numfiles, sizeof(struct fileent), cmp_name_desc_ci); + break; + case 'c': + case 'm': + qsort(filelist, numfiles, sizeof(struct fileent), cmp_datetime_asc); + break; + case 'C': + case 'M': + 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 'd': + qsort(filelist, numfiles, sizeof(struct fileent), cmp_dir_beg); + break; + case 'D': + 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; + case '.': + qsort(filelist, numfiles, sizeof(struct fileent), cmp_noop); + break; + default: + err(FATALBADARG, err_invopt, "sort"); + } +} + +#endif + +/* + * Convert block index to block number + * Block index is 1-based (1,2,3 ...) + */ +uint blockidxtoblocknum(uint idx) { + uint i; + struct block *p = blocks; + for (i = 1; i < idx; ++i) + p = p->next; + return p->blocknum; +} + +/* + * Copy the 4 bytes of pointers from the directory block with index idx + * to the start of dirblkbuf[]; zeroes the rest of dirblkbuf[]. + */ +void copydirblkptrs(uint idx) { + uint i; + struct block *p = blocks; + for (i = 1; i < idx; ++i) + p = p->next; + bzero(dirblkbuf, BLKSZ); +#ifdef AUXMEM + copyaux(p->data, dirblkbuf, PTRSZ, FROMAUX); +#else + memcpy(dirblkbuf, p->data, PTRSZ); +#endif +} + +/* + * Copy a file entry from one srcblk, srcent to dstblk, dstent + * All indices are 1-based. + * dstblk is written to dirblkbuf[] + */ +void copydirent(uint srcblk, uint srcent, uint dstblk, uint dstent, uint device) { + struct block *source = 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); + } + + while (--srcblk > 0) + source = source->next; + + srcptr = source->data + PTRSZ + (srcent-1) * entsz; + dstptr = dirblkbuf + PTRSZ + (dstent-1) * entsz; + +#ifdef AUXMEM + copyaux(srcptr, dstptr, entsz, FROMAUX); +#else + memcpy(dstptr, srcptr, entsz); +#endif + + /* 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(NONFATAL, err_updsdir1, "read"); + hdr = (struct pd_dirhdr*)(buf + PTRSZ); + parentblk = blockidxtoblocknum(dstblk); + hdr->parptr[0] = parentblk & 0xff; + hdr->parptr[1] = (parentblk >> 8) & 0xff; + hdr->parentry = dstent; + if (dowrite) { + if (writediskblock(device, block, buf) == -1) + err(NONFATAL, err_updsdir1, "write"); + } + } +} + +/* + * Build sorted directory block dstblk (1,2,3...) using the sorted list in + * filelist[]. Note that the block and entry numbers are 1-based indices. + * Returns 1 if last block of directory, 0 otherwise. + */ +uchar sortblock(uint device, uint dstblk) { + uint i, firstlistent, lastlistent; + uchar destentry, rc = 0; + copydirblkptrs(dstblk); + if (dstblk == 1) { + copydirent(1, 1, 1, 1, device); /* Copy directory header */ + destentry = 2; /* Skip header on first block */ + firstlistent = 0; + lastlistent = entperblk - 2; + } else { + destentry = 1; + firstlistent = (dstblk - 1) * entperblk - 1; + lastlistent = firstlistent + entperblk - 1; + } + + if (lastlistent > numfiles - 1) { + + lastlistent = numfiles - 1; + +#ifdef TRIMDIR +// TODO: Fix EOF for directory +#ifdef AUXMEM + dirblkbuf[2] = dirblkbuf[3] = 0; /* Set next ptr to NULL */ +#else + p->data[2] = p->data[3] = 0; /* Set next ptr to NULL */ +#endif + rc = 1; +#else + rc = 0; +#endif + } + + for (i = firstlistent; i <= lastlistent; ++i) { + copydirent(filelist[i].blockidx, filelist[i].entrynum, + dstblk, destentry++, device); + } + return rc; +} + +/* + * Build each sorted directory block in turn, then write them + * out to disk. + */ +uchar writedir(uchar device) { + uint dstblk = 1; + uchar finished = 0; + struct block *b = blocks; + while (b) { + if (!finished) { + finished = sortblock(device, dstblk++); + if (writediskblock(device, b->blocknum, dirblkbuf) == -1) { + err(NONFATAL, err_wtblk1, b->blocknum); + return 1; + } +#ifdef FREELIST + } else { + /* Standard volume directory is blocks 2-5 (4 blocks) + * We will not trim volume directory to less than 4 blocks + */ + if (b->blocknum > 5) { + puts("Trimming dir blk"); + trimdirblock(b->blocknum); + } + } +#else + } +#endif + b = b->next; + } + return 0; +} + +#ifdef FREELIST + +/* + * Write the freelist back to disk. + */ +uchar writefreelist(uchar device) { + uchar b; + puts("Writing freelist ..."); + for (b = 0; b < flsize; ++b) { +#ifdef AUXMEM + copyaux(freelist + b * BLKSZ, dirblkbuf, BLKSZ, FROMAUX); +#else + memcpy(dirblkbuf, freelist + b * BLKSZ, BLKSZ); +#endif + if (writediskblock(device, flblk, dirblkbuf) == -1) { + err(NONFATAL, err_wtblk1, flblk); + return 1; + } + ++flblk; + } + return 0; +} + +#endif + +/* + * 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 subtitle(char *s) { + uchar i; + putchar('\n'); + hlinechar('_'); + revers(1); + fputs(s, stdout); + revers(0); + for (i = strlen(s); i < 79; ++i) + putchar(' '); + putchar('|'); +} + +void interactive(void) { + char w, l, d, f, wrt; +#ifdef FREELIST + char z; +#endif + uchar level; + + doverbose = 1; + + revers(1); + hlinechar(' '); + fputs("S O R T D I R v0.90 alpha Use ^ to return to previous question", stdout); + hlinechar(' '); + revers(0); + +q1: + putchar('\n'); + revers(1); + fputs("Enter start path>", stdout); + revers(0); + putchar(' '); + scanf("%s", buf); + getchar(); // Eat the carriage return + +q2: + dowholedisk = dorecurse = 0; + subtitle("What to process"); + do { + fputs("| [-] Directory | [t] Tree | [v] Volume | |", stdout); + 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: + subtitle("Multi-level directory sort"); + // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + fputs("| Lower case option ascending order, upper case option descending order |", stdout); + fputs("| [nN] Name | [iI] Name (case-insens) | [tT] Type | [dD] Directories |", stdout); + fputs("| [cC] Creation | [mM] Modification | [bB] Blocks | [eE] EOF Position |", stdout); + fputs("| [-] Done | [.] Just compact dir | | |", stdout); + for (level = 0; level < NLEVELS; ++level) { + do { + printf("\nLevel %d > ", level+1); + sortopts[level] = getchar(); + } while (strchr("-.nNiItTdDcCmMbBeE^", sortopts[level]) == NULL); + if (sortopts[level] == '-') { + sortopts[level] = '\0'; + break; + } + if (sortopts[level] == '^') + goto q2; + } + sortopts[NLEVELS] = '\0'; + +q4: + subtitle("Filename case conversion"); + do { + // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + fputs("| [-] No change | | | |", stdout); + fputs("| [l] Lowercase | [u] Uppercase | [i] Initial | [c] Camelcase |", stdout); + l = getchar(); + } while (strchr("-luic^", l) == NULL); + if (l == '^') + goto q3; + if (l == '-') + caseopts[0] = '\0'; + else + caseopts[0] = l; + +q5: + subtitle("Date format conversion"); + do { + fputs("| [-] No change | [n] 'New' ProDOS 2.5+ | [o] 'Old' legacy format |",stdout); + d = getchar(); + } while (strchr("-no^", d) == NULL); + if (d == '^') + goto q4; + if (d == '-') + dateopts[0] = '\0'; + else + dateopts[0] = d; + +q6: + subtitle("Attempt to fix errors?"); + do { + fputs("| [-] Always ask | [y] Always fix | [n] Never fix |", stderr); + f = getchar(); + } while (strchr("-yn^", f) == NULL); + if (f == '^') + goto q5; + fixopts[0] = f; + +#ifdef FREELIST + if (w == 'v') { + subtitle("Zero free space?"); + do { + fputs("| [-] No | [z] Zero free blocks | |", stderr); + z = getchar(); + } while (strchr("-z^", z) == NULL); + if (z == '^') + goto q6; + if (z == 'z') + dozero = 1; + } +#endif + + subtitle("Confirm write to disk"); + do { + fputs("| [-] No | [w] Write to disk | |", stderr); + wrt = getchar(); + } while (strchr("-w^", wrt) == NULL); + if (wrt == '^') + goto q6; + if (wrt == 'w') + dowrite = 1; +} + + +/* + * 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(); + if (readdir(device, blocknum) != 0) { + err(NONFATAL, err_nosort); + putchar('\n'); + goto done; + } +#ifdef SORT + if (strlen(sortopts) > 0) { + if (doverbose) + fputs("Sorting: ", stdout); + for (i = 0; i < strlen(sortopts); ++i) { + if (doverbose) + printf("[%c] ", sortopts[i]); + if (buildsorttable(sortopts[i], i) != 0) { + err(NONFATAL, err_nosort); + putchar('\n'); + goto done; + } + sortlist(sortopts[i]); + } + if (doverbose) + putchar('\n'); + if (dowrite) { + puts("Writing dir ..."); + errs = writedir(device); + } else { + revers(1); + fputs("Not writing dir", stdout); + revers(0); + putchar('\n'); + } + } +#endif +done: + freeblocks(); +#ifdef AUXMEM + freeallaux(); +#endif +} + +#ifdef FREELIST + +/* + * 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(uchar device) { + uchar fl, ul, bit; + uint byte, blk = 0, blkcnt = 0; + printf("Total blks %u", totblks); + for (byte = 0; byte < flsize * BLKSZ; ++byte) { +#ifdef AUXMEM + copyaux(freelist + byte, &fl, 1, FROMAUX); + copyaux(usedlist + byte, &ul, 1, FROMAUX); +#else + fl = freelist[byte]; + ul = usedlist[byte]; +#endif + for (bit = 0; bit < 8; ++bit) { + if (blk >= totblks) + break; + if ((fl << bit) & 0x80) { + /* Free */ + if ((ul << bit) & 0x80) { + /* ... and used */ + err(NONFATAL, err_blfree1, blk); + if (askfix() == 1) { + ++blkcnt; + fl &= ~(0x80 >> bit); +#ifdef AUXMEM + copyaux(&fl, freelist + byte, 1, TOAUX); +#else + freelist[byte] = fl; +#endif + flchanged = 1; + } + } + } else { + /* Not free */ + ++blkcnt; + if (!((ul << bit) & 0x80)) { + /* ... and not used */ + err(NONFATAL, err_blused1, blk); + if (askfix() == 1) { + --blkcnt; + fl |= (0x80 >> bit); +#ifdef AUXMEM + copyaux(&fl, freelist + byte, 1, TOAUX); +#else + freelist[byte] = fl; +#endif + flchanged = 1; + } + } + } + ++blk; + } + } + printf("\nFree blks %u\n", totblks - blkcnt); + + if (dozero) + zerofreeblocks(device, totblks - blkcnt); +} + +/* + * Zero block blocknum + */ +void zeroblock(uchar device, uint blocknum) { + bzero(buf, BLKSZ); + if (writediskblock(device, blocknum, buf) == -1) + err(FATAL, err_wtblk1, blocknum); +// 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, "Block write failed"); +} + +/* + * Zero all free blocks on the volume + */ +void zerofreeblocks(uchar device, uint freeblks) { + uint i, step = freeblks / 60, ctr = 0; + puts("Zeroing free blocks ..."); + for (i = 0; i < totblks; ++i) + if (isfree(i)) { + zeroblock(device, i); + ++ctr; + if (ctr == step) { + putchar('='); + fflush(stdout); + ctr = 0; + } + } + puts("\nDone zeroing!"); +} + +#endif + +#ifdef CMDLINE + +void usage(void) { + printf("usage: sortdir [-s xxx] [-n x] [-rDwvVh] path\n\n"); + 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(" -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(" c sort by create date/time ascending\n"); + printf(" C sort by create date/time descending\n"); + printf(" m sort by modify date/time ascending\n"); + printf(" M sort by modify date/time descending\n"); + printf(" t sort by type ascending\n"); + printf(" T sort by type descending\n"); + printf(" d sort directories to top\n"); + printf(" D sort 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"); + err(FATAL, err_usage); +} + +#define MAXNUMARGS 10 + +int argc; +char *argv[MAXNUMARGS]; + +void parseargs() { + char *p; + char i = 0, s = 0, prev = ' '; + argc = 1; + for (p = (char*)0x200; p <= (char*)0x27f; ++p) { + *p &= 0x7f; + if ((*p == 0) || (*p == 0x0d)) { + argv[argc - 1] = buf + s; + break; + } + if (*p == ' ') { + if (prev != ' ') { + buf[i++] = '\0'; + argv[argc - 1] = buf + s; + s = i; + ++argc; + } + } else { + buf[i++] = *p; + } + prev = *p; + } +} + +#endif + +//int main(int argc, char *argv[]) { +int main() { +#ifdef CMDLINE + int opt; +#endif + uchar dev; + uint blk; + + uchar *pp; + pp = (uchar*)0xbf98; + if (!(*pp & 0x02)) + err(FATAL, err_80col); +#ifdef AUXMEM + if ((*pp & 0x30) != 0x30) + err(FATAL, err_128K); +#endif + + // Clear system bit map + for (pp = (uchar*)0xbf58; pp <= (uchar*)0xbf6f; ++pp) + *pp = 0; + + videomode(VIDEOMODE_80COL); + + _heapadd((void*)0x0800, 0x1800); + //printf("\nHeap: %u %u\n", _heapmemavail(), _heapmaxavail()); + +#ifdef FREELIST + +#ifdef AUXMEM + freelist = (uchar*)auxalloc(FLSZ); +#else + freelist = (uchar*)malloc(FLSZ); +#endif + if (!freelist) + err(FATALALLOC, err_nomem); +#ifdef AUXMEM + usedlist = (uchar*)auxalloc(FLSZ); +#else + usedlist = (uchar*)malloc(FLSZ); +#endif + if (!usedlist) + err(FATALALLOC, err_nomem); + +#endif + +#ifdef AUXMEM + lockaux(); // Protect free list and used list +#endif + + buf = (char*)malloc(sizeof(char) * BLKSZ); + buf2 = (char*)malloc(sizeof(char) * BLKSZ); + dirblkbuf = (char*)malloc(sizeof(char) * BLKSZ); + //printf("\nHeap: %u %u\n", _heapmemavail(), _heapmaxavail()); + maxfiles = _heapmaxavail() / sizeof(struct fileent); + printf("[%u]\n", maxfiles); + filelist = (struct fileent*)malloc(sizeof(struct fileent) * maxfiles); + +#ifdef CMDLINE + parseargs(); +#endif + +#ifdef CMDLINE + if (argc == 1) + interactive(); + else { + if (argc < 2) + usage(); + while ((opt = getopt(argc, argv, "DrwvVzs:n:f:d:h")) != -1) { + switch (opt) { + 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 'z': + dozero = 1; + dowholedisk = 1; + dorecurse = 1; + break; + case 's': + strncpy(sortopts, optarg, NLEVELS); + break; + case 'n': + strncpy(caseopts, optarg, 1); + break; + case 'f': + strncpy(fixopts, optarg, 1); + break; + case 'd': + strncpy(dateopts, optarg, 1); + break; + case 'h': + default: + usage(); + } + } + + if (optind != argc - 1) + usage(); + } +#else + + interactive(); + +#endif + +#ifdef CMDLINE + firstblk(((argc == 1) ? buf : argv[optind]), &dev, &blk); +#else + firstblk(buf, &dev, &blk); +#endif + +#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); + } + } +#ifdef FREELIST + if (dowholedisk) { + checkfreeandused(dev); + if (dowrite && flchanged) + writefreelist(dev); + } + + free(freelist); +// free(usedlist); /// TODO This is crashing ATM +#endif + err(FINISHED, ""); + return 0; // Just to shut up warning +} + diff --git a/sortdir.po b/sortdir.po index 9445c272ea60c7cb5b48d34d4e5634da3d0579cd..ecff41c0e50bc2cf4b966bcee9f3d889f2de2626 100644 GIT binary patch delta 26806 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 delta 26736 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