/* * SORTDIR - for Apple II (ProDOS) * * Bobbi January-March 2020 * * TODO: Aux memory support for ProDOS-8 * TODO: Enable free list functionality on ProDOS-8 * TODO: Get both ProDOS-8 and GNO versions to build from this source * TODO: Trimming unused directory blocks */ //#pragma debug 9 //#pragma lint -1 //#pragma stacksize 16384 //#pragma memorymodel 0 //#pragma optimize -1 /* Disable stack repair code */ #include #include #include #include #include #include #include //#include //#include //#include //#include #include #include #undef DEBUG /* Enable additional debug printout */ #define CHECK /* Perform additional integrity checking */ #define SORT /* Enable sorting code */ #undef FREELIST /* Checking of free list */ #define NLEVELS 4 /* Number of nested sorts permitted */ typedef unsigned char uchar; typedef unsigned int uint; typedef unsigned long ulong; #define NMLEN 15 /* Length of filename */ #define MAXFILES 200 /* Max files per directory */ /* * ProDOS directory header * See ProDOS-8 Tech Ref pp. 152 */ struct pd_dirhdr { uchar typ_len; char name[NMLEN]; char reserved[8]; uchar ctime[4]; uchar vers; uchar minvers; uchar access; uchar entlen; uchar entperblk; uchar filecnt[2]; uchar parptr[2]; /* Bitmap pointer in volume dir */ uchar parentry; /* Total blocks LSB in volume dir */ uchar parentlen; /* Total blocks MSB in volume dir */ }; /* * ProDOS file entry * See ProDOS-8 Tech Ref pp. 155 */ struct pd_dirent { uchar typ_len; char name[NMLEN]; uchar type; uchar keyptr[2]; uchar blksused[2]; uchar eof[3]; uchar ctime[4]; uchar vers; uchar minvers; uchar access; uchar auxtype[2]; uchar mtime[4]; uchar hdrptr[2]; }; #define BLKSZ 512 /* 512 byte blocks */ #define PTRSZ 4 /* 4 bytes of pointers at beginning of each blk */ #define FLSZ 8192 /* Bytes required for 64K block free-list */ /* Exit codes */ #define EXIT_SUCCESS 0 #define EXIT_BAD_ARG 1 #define EXIT_ALLOC_ERR 2 #define EXIT_FATAL_ERR 3 /* * Linked list of directory blocks read from disk * Original directory block is stored in data[] * Directory block for sorted directory is stored in sorteddata[] */ struct block { char data[BLKSZ]; /* Original contents of block */ char sorteddata[BLKSZ]; /* Content block for sorted dir */ uint blocknum; /* Block number on disk */ struct block *next; }; /* * Entry for array of filenames used by qsort() */ struct fileent { char name[NMLEN+1]; /* Name converted to upper/lower case */ char datetime[20]; /* Date/time as a yyyy-mm-dd hh:mm string */ uchar type; /* ProDOS file type */ uint blocks; /* Size in blocks */ ulong eof; /* EOF position in bytes */ uint order; /* Hack to make qsort() stable */ uchar blockidx; /* Index of dir block (1,2,3 ...) */ uchar entrynum; /* Entry within the block */ }; /* * Entry for list of directory keyblocks to check */ struct dirblk { uint blocknum; struct dirblk *next; }; /* * Represents a date and time */ struct datetime { uint year; uchar month; uchar day; uchar hour; uchar minute; uchar ispd25format; }; /* Globals */ #ifdef FREELIST static uint totblks; /* Total # blocks on volume */ static uchar *freelist; /* Free-list bitmap */ static uchar *usedlist; /* Bit map of used blocks */ static uchar flloaded = 0; /* 1 if free-list has been loaded */ #endif static char currdir[NMLEN+1]; /* Name of directory currently processed */ static struct block *blocks = NULL; static struct dirblk *dirs = NULL; static struct fileent filelist[MAXFILES]; static uint numfiles; static uchar entsz; /* Bytes per file entry */ static uchar entperblk; /* Number of entries per block */ static char buf[BLKSZ]; /* General purpose scratch buffer */ static char buf2[BLKSZ]; /* General purpose scratch buffer */ static uint errcount = 0; /* Error counter */ static dhandle_t dio_hdl; /* cc64 direct I/O handle */ static uchar dowholedisk = 0; /* -D whole-disk option */ static uchar dorecurse = 0; /* -r recurse option */ static uchar dowrite = 0; /* -w write option */ static uchar doverbose = 0; /* -v verbose option */ static uchar dodebug = 0; /* -V very verbose option */ static uchar do_ctime = 0; /* -k ctime option */ static uchar dozero = 0; /* -z zero free blocks option */ static char sortopts[NLEVELS+1] = ""; /* -s:abc list of sort options */ static char caseopts[2] = ""; /* -c:x case conversion option */ static char fixopts[2] = ""; /* -f:x fix mode option */ static char dateopts[2] = ""; /* -d:x date conversion option */ /* Prototypes */ void hline(void); void confirm(void); void err(enum errtype severity, char *fmt, ...); void flushall(void); int readdiskblock(uchar device, uint blocknum, char *buf); int writediskblock(uchar device, uint blocknum, char *buf); void fixcase(char *in, char *out, uchar minvers, uchar vers, uchar len); void lowercase(char *p, uchar len, uchar *minvers, uchar *vers); void uppercase(char *p, uchar len, uchar *minvers, uchar *vers); void initialcase(uchar mode, char *p, uchar len, uchar *minvers, uchar *vers); void firstblk(char *dirname, uchar *device, uint *block); void readdatetime(uchar time[4], struct datetime *dt); void writedatetime(uchar pd25, struct datetime *dt, uchar time[4]); uint askfix(void); #ifdef FREELIST int readfreelist(uchar device); int isfree(uint blk); void markfree(uint blk); void marknotfree(uint blk); int isused(uint blk); void markused(uint blk); void checkblock(uint blk, char *msg); #endif int seedlingblocks(uchar device, uint keyblk, uint *blkcnt); int saplingblocks(uchar device, uint keyblk, uint *blkcnt); int treeblocks(uchar device, uint keyblk, uint *blkcnt); int forkblocks(uchar device, uint keyblk, uint *blkcnt); int subdirblocks(uchar device, uint keyblk, struct pd_dirent *ent, uint blocknum, uint blkentries, uint *blkcnt); void enqueuesubdir(uint blocknum, uint subdiridx); int readdir(uint device, uint blocknum); int cmp_name_asc(const void *a, const void *b); int cmp_name_desc(const void *a, const void *b); int cmp_name_asc_ci(const void *a, const void *b); int cmp_name_desc_ci(const void *a, const void *b); int cmp_datetime_asc(const void *a, const void *b); int cmp_datetime_desc(const void *a, const void *b); int cmp_type_asc(const void *a, const void *b); int cmp_type_desc(const void *a, const void *b); int cmp_dir_beg(const void *a, const void *b); int cmp_dir_end(const void *a, const void *b); int cmp_blocks_asc(const void *a, const void *b); int cmp_blocks_desc(const void *a, const void *b); int cmp_eof_asc(const void *a, const void *b); int cmp_eof_desc(const void *a, const void *b); int cmp_noop(const void *a, const void *b); void sortlist(char s); void printlist(void); uint blockidxtoblocknum(uint idx); void copyent(uint srcblk, uint srcent, uint dstblk, uint dstent, uint device); void sortblocks(uint device); int writedir(uchar device); void freeblocks(void); void interactive(void); void usage(void); void processdir(uint device, uint blocknum); void checkfreeandused(uchar device); void zeroblock(uchar device, uint blocknum); void zerofreeblocks(uchar device, uint freeblks); void parseargs(void); /* Horizontal line */ void hline(void) { uint i; for (i=0; i<80; ++i) putchar('-'); } void hline2(void) { uint i; for (i=0; i<80; ++i) putchar('='); } enum errtype {WARN, NONFATAL, FATAL, FATALALLOC, FATALBADARG, FINISHED}; /****************************************************************************/ /* LANGUAGE CARD BANK 2 0xd400-x0dfff 3KB */ /****************************************************************************/ #pragma code-name (push, "LC") void confirm() { puts("[Press any key to restart system]"); getchar(); } /* * Display error message */ void err(enum errtype severity, char *fmt, ...) { va_list v; uint rv = 0; rebootafterexit(); // Necessary if we were called from BASIC if (severity == FINISHED) { hline(); if (errcount == 0) printf("DONE - no errors found.\n"); else printf("DONE - %u errors\n", errcount); hline(); confirm(); exit(EXIT_SUCCESS); } ++errcount; switch (severity) { case FATAL: rv = EXIT_FATAL_ERR; case FATALALLOC: rv = EXIT_ALLOC_ERR; case FATALBADARG: rv = EXIT_BAD_ARG; } fputs(((rv > 0) ? " ** " : " "), stdout); va_start(v, fmt); vprintf(fmt, v); putchar('\n'); va_end(v); if (rv > 0) { printf("Stopping after %u errors\n", errcount); confirm(); exit(rv); } } /* * Disable GSOS block cache and flush any unwritten changes */ void flushall(void) { // short ff[2]; // ResetCacheGS(0); /* Disable block caching */ // ff[0] = 1; // ff[1] = 0; // FlushGS(ff); } /* * Read block from disk using ProDOS call * buf must point to buffer with at least 512 bytes */ int readdiskblock(uchar device, uint blocknum, char *buf) { int rc; #ifdef DEBUG printf("Reading dev %2u block %5u\n", device, blocknum); #endif #ifdef CHECK #ifdef FREELIST if (flloaded) if (isfree(blocknum)) err(NONFATAL, "Blk %u is marked free!", blocknum); #endif #endif // BlockRec br; // br.blockDevNum = device; // br.blockDataBuffer = buf; // br.blockNum = blocknum; // READ_BLOCK(&br); // int rc = toolerror(); // if (rc) { // err(FATAL, "Block read failed, err=%x", rc); // return -1; // } rc = dio_read(dio_hdl, blocknum, buf); if (rc) err(FATAL, "Blk read failed, err=%x", rc); return 0; } /* * Write block from disk using ProDOS call * buf must point to buffer with at least 512 bytes */ int writediskblock(uchar device, uint blocknum, char *buf) { int rc; #ifdef DEBUG printf("Writing dev %2u blk %5u\n", device, blocknum); #endif if ((strcmp(currdir, "LIB") == 0) || (strcmp(currdir, "LIBRARIES") == 0)) { printf("Not writing library directory %s\n", currdir); return 0; } flushall(); // DIORecGS dr; // dr.pCount = 6; // dr.devNum = device; // dr.buffer = buf; // dr.requestCount = BLKSZ; // dr.startingBlock = blocknum; // dr.blockSize = BLKSZ; // DWriteGS(&dr); // if (dr.transferCount != BLKSZ) { // err(FATAL, "Blk write failed"); // return -1; // } rc = dio_write(dio_hdl, blocknum, buf); if (rc) err(FATAL, "Blk write failed, err=%x", rc); return 0; } /* * Uses the vers and minvers fields of the directory entry * as a bitmap representing which characters are upper and which are * lowercase */ void fixcase(char *in, char *out, uchar minvers, uchar vers, uchar len) { uint i; uchar idx = 0; if (!(vers & 0x80)) { for (idx=0; idx> 4; drive = ((*lastdev & 0x80) >> 7) + (*lastdev & 0x02) + 1; printf("[Slot %u, Drive %u]\n", slot, drive); *device = slot + (drive - 1) * 8; dio_hdl = dio_open(*device); // TODO should dio_close on exit hdr = (struct pd_dirhdr*)(buf + PTRSZ); /* Detect & handle volume directory */ if ((hdr->typ_len & 0xf0) == 0xf0) { *block = 2; goto ret; } #ifdef CHECK if ((hdr->typ_len & 0xf0) != 0xe0) { err(NONFATAL, "Bad storage type"); goto ret; } #endif /* Handle subdirectory */ parentblk = hdr->parptr[0] + 256U * hdr->parptr[1]; parententry = hdr->parentry; parententlen = hdr->parentlen; /* Read parent directory block */ if (readdiskblock(*device, parentblk, buf) == -1) err(FATAL, "Can't read parent directory for %s", dirname); ent = (struct pd_dirent *)(buf + PTRSZ + (parententry-1) * parententlen); *block = ent->keyptr[0] + 256U * ent->keyptr[1]; ret: if (fp) close(fp); } /****************************************************************************/ /* END OF LANGUAGE CARD BANK 2 0xd400-x0dfff 3KB SEGMENT */ /****************************************************************************/ #pragma code-name (pop) /* * Parse mtime or ctime fields and populate the fields of the datetime struct * Supports the legacy ProDOS date/time format as used by ProDOS 1.0->2.4.0 * and also the new format introduced with ProDOS 2.5. */ void readdatetime(uchar time[4], struct datetime *dt) { uint d = time[0] + 256U * time[1]; uint t = time[2] + 256U * time[3]; if (!(t & 0xe000)) { /* ProDOS 1.0 to 2.4.2 date format */ dt->year = (d & 0xfe00) >> 9; dt->month = (d & 0x01e0) >> 5; dt->day = d & 0x001f; dt->hour = (t & 0x1f00) >> 8; dt->minute = t & 0x003f; dt->ispd25format = 0; if (dt->year < 40) /* See ProDOS-8 Tech Note 48 */ dt->year += 2000; else dt->year += 1900; } else { /* ProDOS 2.5.0+ */ dt->year = t & 0x0fff; dt->month = ((t & 0xf000) >> 12) - 1; dt->day = (d & 0xf800) >> 11; dt->hour = (d & 0x07c0) >> 6; dt->minute = d & 0x003f; dt->ispd25format = 1; } } /* * Write the date and time stored in struct datetime in ProDOS on disk format, * storing the bytes in array time[]. If pd25 is 0 then use legacy format * (ProDOS 1.0-2.4.2) otherwise use the new date and time format introduced * with ProDOS 2.5 */ void writedatetime(uchar pd25, struct datetime *dt, uchar time[4]) { uint d, t; if (pd25 == 0) { /* ProDOS 1.0 to 2.4.2 date format */ uint year = dt->year; if (year > 2039) /* 2039 is last year */ year = 2039; if (year < 1940) /* 1940 is first year */ year = 1940; if (year >= 2000) year -= 2000; if (year >= 1900) year -= 1900; d = (year << 9) | (dt->month << 5) | dt->day; t = (dt->hour << 8) | dt->minute; } else { /* ProDOS 2.5.0+ */ t = ((dt->month + 1) << 12) | dt->year; d = (dt->day << 11) | (dt->hour << 6) | dt->minute; } time[0] = d & 0xff; time[1] = (d >> 8) & 0xff; time[2] = t & 0xff; time[3] = (t >> 8) & 0xff; } /* * Determine whether or not to perform a fix * Return 0 not to perform fix, 1 to perform fix */ uint askfix(void) { if (strlen(fixopts) == 0) return 0; switch (fixopts[0]) { case '?': fputs("Fix (y/n)? ", stdout); if (tolower(getchar()) == 'y') return 1; return 0; case 'y': return 1; default: return 0; } } #ifdef FREELIST /* * Read the free list */ int readfreelist(uchar device) { uint i, flblk, flsize; char *p; freelist = (uchar*)malloc(FLSZ); if (!freelist) err(FATALALLOC, "No memory!"); bzero(freelist, FLSZ); usedlist = (uchar*)malloc(FLSZ); if (!usedlist) err(FATALALLOC, "No memory!"); bzero(usedlist, FLSZ); markused(0); /* Boot block */ markused(1); /* SOS boot block */ if (readdiskblock(device, 2, buf) == -1) { err(NONFATAL, "Error reading volume dir"); return -1; } flblk = buf[0x27] + 256U * buf[0x28]; totblks = buf[0x29] + 256U * buf[0x2a]; if (doverbose) printf("Volume has %u blocks\n", totblks); flsize = totblks / 4096U; if ((flsize % 4096) >0) ++flsize; p = (char*)freelist; for (i=0; i> bit); } /* * Mark a block as not free */ void marknotfree(uint blk) { uint idx = blk / 8; uint bit = blk % 8; freelist[idx] &= ~(0x80 >> bit); } /* * Determine if block blk is used or not */ int isused(uint blk) { uint idx = blk / 8; uint bit = blk % 8; return (usedlist[idx] << bit) & 0x80 ? 1 : 0; } /* * Mark a block as used */ void markused(uint blk) { uint idx = blk / 8; uint bit = blk % 8; usedlist[idx] |= (0x80 >> bit); } /* * Perform all the operations to check a block which is used by * a directory or file. Complains if the block is on the free-list * and also if we have encountered this block in a previous file or dir. */ void checkblock(uint blk, char *msg) { if (isfree(blk)) err(WARN, "%s blk %u is marked free!", msg, blk); if (isused(blk)) err(WARN, "%s blk %u is already used!", msg, blk); markused(blk); } #endif /* * Count the blocks in a seedling file */ int seedlingblocks(uchar device, uint keyblk, uint *blkcnt) { #ifdef FREELIST checkblock(keyblk, "Data"); #endif *blkcnt = 1; return 0; } /* * Count the blocks in a sapling file */ int saplingblocks(uchar device, uint keyblk, uint *blkcnt) { uint i, p; #ifdef FREELIST checkblock(keyblk, "Data"); #endif if (readdiskblock(device, keyblk, buf) == -1) { err(NONFATAL, "Error reading blk %u", keyblk); return -1; } *blkcnt = 1; for (i=0; i<256; ++i) { p = buf[i] + 256U * buf[i+256]; if (p) { #ifdef FREELIST checkblock(p, "Data"); #endif ++(*blkcnt); } } return 0; } /* * Count the blocks in a tree file */ int treeblocks(uchar device, uint keyblk, uint *blkcnt) { uint i, p, b; #ifdef FREELIST checkblock(keyblk, "Tree index"); #endif if (readdiskblock(device, keyblk, buf2) == -1) { err(NONFATAL, "Error reading blk %u", keyblk); return -1; } *blkcnt = 1; for (i=0; i<256; ++i) { p = buf2[i] + 256U * buf2[i+256]; if (p) { if (saplingblocks(device, p, &b) == 0) *blkcnt += b; else return -1; } } return 0; } /* * Count the blocks in a GSOS fork file * See http://1000bit.it/support/manual/apple/technotes/pdos/tn.pdos.25.html */ int forkblocks(uchar device, uint keyblk, uint *blkcnt) { uint count, d_blks, r_blks, d_keyblk, r_keyblk; uchar d_type, r_type; #ifdef FREELIST checkblock(keyblk, "Fork key"); #endif if (readdiskblock(device, keyblk, buf) == -1) { err(NONFATAL, "Error reading blk %u", keyblk); return -1; } *blkcnt = 1; d_type = buf[0x00]; d_keyblk = buf[0x01] + 256U * buf[0x02]; d_blks = buf[0x03] + 256U * buf[0x04]; r_type = buf[0x100]; r_keyblk = buf[0x101] + 256U * buf[0x102]; r_blks = buf[0x103] + 256U * buf[0x104]; /* Data fork */ switch (d_type) { case 0x1: /* Seedling */ seedlingblocks(device, d_keyblk, &count); break; case 0x2: /* Sapling */ saplingblocks(device, d_keyblk, &count); break; case 0x3: /* Tree */ treeblocks(device, d_keyblk, &count); break; default: err(NONFATAL, "Invalid storage type for data fork"); count = 0; break; } if (d_blks != count) { if (count != 0) { err(NONFATAL, "Data fork size %u is incorrect, should be %u", d_blks, count); // TODO: Need to rethink the fix mode here ... it was buggy anyhow // if (askfix() == 1) { // buf[0x03] = count & 0xff; // buf[0x04] = (count >> 8) & 0xff; // } } } *blkcnt += count; /* Resource fork */ switch (r_type) { case 0x1: /* Seedling */ seedlingblocks(device, r_keyblk, &count); break; case 0x2: /* Sapling */ saplingblocks(device, r_keyblk, &count); break; case 0x3: /* Tree */ treeblocks(device, r_keyblk, &count); break; default: err(NONFATAL, "Invalid storage type for resource fork"); count = 0; break; } if (r_blks != count) { if (count != 0) { err(NONFATAL, "Res fork size %u is incorrect, should be %u", r_blks, count); if (askfix() == 1) { // TODO: Need to rethink the fix mode here ... it was buggy anyhow // buf[0x103] = count & 0xff; // buf[0x104] = (count >> 8) & 0xff; } } } *blkcnt += count; return 0; } /* * Count the blocks in a subdirectory */ int subdirblocks(uchar device, uint keyblk, struct pd_dirent *ent, uint blocknum, uint blkentries, uint *blkcnt) { struct pd_dirhdr *hdr; uchar parentry, parentlen; uint parblk; char *dirname; #ifdef FREELIST if (!dorecurse) checkblock(keyblk, "Directory"); #endif if (readdiskblock(device, keyblk, buf) == -1) { err(NONFATAL, "Error reading keyblock %u", keyblk); return -1; } *blkcnt = 1; hdr = (struct pd_dirhdr*)(buf + PTRSZ); parentry = hdr->parentry; parentlen = hdr->parentlen; parblk = hdr->parptr[0] + 256U * hdr->parptr[1]; if (parblk != blocknum) { err(NONFATAL, "Bad parent blk %u, should be %u", parblk, blocknum); if (askfix() == 1) { hdr->parptr[0] = blocknum & 0xff; hdr->parptr[1] = (blocknum >> 8) & 0xff; } } if (parentry != blkentries) { err(NONFATAL, "Bad parent blk entry %u, should be %u", parentry, blkentries); if (askfix() == 1) { hdr->parentry = blkentries; } } if (parentlen != 0x27) { err(NONFATAL, "Bad parent entry length"); if (askfix() == 1) { hdr->parentlen = 0x27; } } dirname = buf + 0x05; if (strncmp(dirname, ent->name, NMLEN)) { err(NONFATAL, "Subdir name mismatch"); } blocknum = buf[0x02] + 256U * buf[0x03]; while (blocknum) { #ifdef FREELIST if (!dorecurse) checkblock(blocknum, "Directory"); #endif if (readdiskblock(device, blocknum, buf) == -1) { err(NONFATAL, "Error reading dir blk %u", blocknum); return -1; } ++(*blkcnt); blocknum = buf[0x02] + 256U * buf[0x03]; } return 0; } /* * Record the keyblock of a subdirectory to be processed subsequently * blocknum is the block number of the subdirectory keyblock * subdiridx is a sequential counter of the subdirs in the current directory */ void enqueuesubdir(uint blocknum, uint subdiridx) { static struct dirblk *prev; struct dirblk *p = (struct dirblk*)malloc(sizeof(struct dirblk)); //printf("ALLOC %p\n", p); if (!p) err(FATALALLOC, "No memory!"); p->blocknum = blocknum; if (subdiridx == 0) { /* First subdir is inserted at head of list */ p->next = dirs; dirs = p; } else { /* Subsequent subdirs follow the previous */ p->next = prev->next; prev->next = p; } prev = p; } /* * Read a directory, store the raw directory blocks in a linked list * and build filelist[] in preparation for sorting. * device is the device number containing the directory * blocknum is the block number of the first block of the directory */ int readdir(uint device, uint blocknum) { static char namebuf[NMLEN+1]; struct pd_dirhdr *hdr; struct block *curblk; struct datetime dt; ulong eof; uint filecount, idx, subdirs, blks, keyblk, hdrblk, count; uchar blkentries, entries, pd25, i; uint errsbefore = errcount; uint blkcnt = 1; uint hdrblknum = blocknum; numfiles = 0; blocks = (struct block*)malloc(sizeof(struct block)); //printf("ALLOC %p\n", blocks); if (!blocks) err(FATALALLOC, "No memory!"); curblk = blocks; curblk->next = NULL; curblk->blocknum = blocknum; #ifdef FREELIST checkblock(blocknum, "Directory"); #endif if (readdiskblock(device, blocknum, curblk->data) == -1) { err(NONFATAL, "Error reading dir blk %d", blkcnt); return 1; } hdr = (struct pd_dirhdr*)(curblk->data + PTRSZ); entsz = hdr->entlen; entperblk = hdr->entperblk; filecount = hdr->filecnt[0] + 256U * hdr->filecnt[1]; fixcase(hdr->name, currdir, hdr->vers, hdr->minvers, hdr->typ_len & 0x0f); hline2(); printf("Directory %s (%u", currdir, filecount); printf(" %s)\n", filecount == 1 ? "entry" : "entries"); hline(); /* Copy pointers to sorteddata[], zero the rest */ bzero(curblk->sorteddata, BLKSZ); memcpy(curblk->sorteddata, curblk->data, PTRSZ); #ifdef CHECK if (entsz != 0x27) { err(NONFATAL, "Error - bad entry size"); return 1; } if (entperblk != 0x0d) { err(NONFATAL, "Error - bad entries/block"); return 1; } #endif idx = entsz + PTRSZ; /* Skip header */ blkentries = 2; entries = 0; subdirs = 0; while (1) { uint errsbeforeent = errcount; struct pd_dirent *ent = (struct pd_dirent*)(curblk->data + idx); if (ent->typ_len != 0) { if (strlen(caseopts) > 0) { switch (caseopts[0]) { case 'u': uppercase(ent->name, ent->typ_len & 0x0f, &(ent->vers), &(ent->minvers)); break; case 'l': lowercase(ent->name, ent->typ_len & 0x0f, &(ent->vers), &(ent->minvers)); break; case 'i': initialcase(0, ent->name, ent->typ_len & 0x0f, &(ent->vers), &(ent->minvers)); break; case 'c': initialcase(1, ent->name, ent->typ_len & 0x0f, &(ent->vers), &(ent->minvers)); break; default: err(FATALBADARG, "Invalid case option"); } } if (strlen(dateopts) > 0) { struct datetime ctime, mtime; readdatetime(ent->ctime, &ctime); readdatetime(ent->mtime, &mtime); switch (dateopts[0]) { case 'n': pd25 = 1; break; case 'o': pd25 = 0; break; default: err(FATALBADARG, "Invalid date option"); } writedatetime(pd25, &ctime, ent->ctime); writedatetime(pd25, &mtime, ent->mtime); } fixcase(ent->name, namebuf, ent->vers, ent->minvers, ent->typ_len & 0x0f); #ifdef CHECK switch (ent->typ_len & 0xf0) { case 0x10: fputs("Seed ", stdout); break; case 0x20: fputs("Sapl ", stdout); break; case 0x30: fputs("Tree ", stdout); break; case 0x40: fputs("Pasc ", stdout); break; case 0x50: fputs("Fork ", stdout); break; case 0xd0: fputs("Dir ", stdout); break; default: fputs("???? ", stdout); break; } fputs(namebuf, stdout); #endif blks = ent->blksused[0] + 256U * ent->blksused[1]; eof = ent->eof[0] + 256L * ent->eof[1] + 65536L * ent->eof[2]; for (i=0; ityp_len & 0x0f); ++i) filelist[numfiles].name[i] = namebuf[i]; filelist[numfiles].type = ent->type; filelist[numfiles].blockidx = blkcnt; filelist[numfiles].entrynum = blkentries; filelist[numfiles].blocks = blks; filelist[numfiles].eof = eof; readdatetime(do_ctime ? ent->ctime : ent->mtime, &dt); sprintf(filelist[numfiles].datetime, "%04d-%02d-%02d %02d:%02d %s", dt.year, dt.month, dt.day, dt.hour, dt.minute, (dt.ispd25format ? "*" : " ")); keyblk = ent->keyptr[0] + 256U * ent->keyptr[1]; hdrblk = ent->hdrptr[0] + 256U * ent->hdrptr[1]; #ifdef CHECK if (hdrblk != hdrblknum) { err(NONFATAL, "Header ptr %u, should be %u", hdrblk, hdrblknum); if (askfix() == 1) { ent->hdrptr[0] = hdrblknum & 0xff; ent->hdrptr[1] = (hdrblknum >> 8)&0xff; } } #endif switch (ent->typ_len & 0xf0) { case 0xd0: /* Subdirectory */ enqueuesubdir(keyblk, subdirs++); #ifdef CHECK subdirblocks(device, keyblk, ent, blocknum, blkentries, &count); #endif break; #ifdef CHECK case 0x10: /* Seedling */ seedlingblocks(device, keyblk, &count); break; case 0x20: /* Sapling */ saplingblocks(device, keyblk, &count); break; case 0x30: /* Tree */ treeblocks(device, keyblk, &count); break; case 0x40: /* Pascal area */ puts(" Pascal area!!"); // TODO: Check name is PASCAL.AREA type 0xef count = 0; break; case 0x50: /* File with resource fork */ forkblocks(device, keyblk, &count); break; default: err(NONFATAL, "%s: unexpected storage type 0x%x", namebuf, ent->typ_len & 0xf0); count = 0; #endif } #ifdef CHECK if (blks != count) { if (count != 0) { err(NONFATAL, "Blks used %u is incorrect, " "should be %u", blks, count); if (askfix() == 1) { ent->blksused[0] = count&0xff; ent->blksused[1] = (count >> 8) & 0xff; } } } #endif ++numfiles; if (numfiles == MAXFILES) { err(NONFATAL, "Too many files!"); return 1; } if (errcount == errsbeforeent) { for (i = 0; i < 53 - strlen(namebuf); ++i) putchar(' '); printf("%5u blocks [ OK ]", blks); } else putchar('\n'); ++entries; } if (blkentries == entperblk) { blocknum = curblk->data[0x02] + 256U*curblk->data[0x03]; if (blocknum == 0) { break; } curblk->next = (struct block*)malloc(sizeof(struct block)); //printf("ALLOC %p\n", curblk->next); if (!curblk->next) err(FATALALLOC, "No memory!"); curblk = curblk->next; curblk->next = NULL; curblk->blocknum = blocknum; ++blkcnt; #ifdef FREELIST checkblock(blocknum, "Directory"); #endif if ( readdiskblock(device, blocknum, curblk->data) == -1) { err(NONFATAL,"Error reading dir blk %d", blkcnt); return 1; } /* Copy ptrs to sorteddata[], zero the rest */ bzero(curblk->sorteddata, BLKSZ); memcpy(curblk->sorteddata, curblk->data, PTRSZ); blkentries = 1; idx = PTRSZ; } else { ++blkentries; idx += entsz; } } if (filecount != entries) { err(NONFATAL, "Filecount %u wrong, should be %u", filecount, entries); if (askfix() == 1) { hdr->filecnt[0] = entries & 0xff; hdr->filecnt[1] = (entries >> 8) & 0xff; } } return errcount - errsbefore; } #ifdef SORT /* * Compare - filename sort in ascending order */ int cmp_name_asc(const void *a, const void *b) { return strncmp(((struct fileent*)a)->name, ((struct fileent*)b)->name, NMLEN); } /* * Compare - filename sort in descending order */ int cmp_name_desc(const void *a, const void *b) { return strncmp(((struct fileent*)b)->name, ((struct fileent*)a)->name, NMLEN); } /* * Compare - filename sort in ascending order - case insensitive */ int cmp_name_asc_ci(const void *a, const void *b) { return strncasecmp(((struct fileent*)a)->name, ((struct fileent*)b)->name, NMLEN); } /* * Compare - filename sort in descending order - case insensitive */ int cmp_name_desc_ci(const void *a, const void *b) { return strncasecmp(((struct fileent*)b)->name, ((struct fileent*)a)->name, NMLEN); } /* * Compare - date/time sort in ascending order */ int cmp_datetime_asc(const void *a, const void *b) { return strncmp(((struct fileent*)a)->datetime, ((struct fileent*)b)->datetime, 16); } /* * Compare - date/time sort in descending order */ int cmp_datetime_desc(const void *a, const void *b) { return strncmp(((struct fileent*)b)->datetime, ((struct fileent*)a)->datetime, 16); } /* * Compare - type sort in ascending order * Uses the order field to make qsort() stable */ int cmp_type_asc(const void *a, const void *b) { struct fileent *aa = (struct fileent*)a; struct fileent *bb = (struct fileent*)b; int rc = aa->type - bb->type; return rc != 0 ? rc : aa->order - bb->order; } /* * Compare - type sort in descending order * Uses the order field to make qsort() stable */ int cmp_type_desc(const void *a, const void *b) { struct fileent *aa = (struct fileent*)a; struct fileent *bb = (struct fileent*)b; int rc = bb->type - aa->type; return rc != 0 ? rc : aa->order - bb->order; } /* * Compare - sort with directories at the beginning * Uses the order field to make qsort() stable */ int cmp_dir_beg(const void *a, const void *b) { struct fileent *aa = (struct fileent*)a; struct fileent *bb = (struct fileent*)b; if ((aa->type == 0x0f) && (bb->type != 0x0f)) return -1; if ((bb->type == 0x0f) && (aa->type != 0x0f)) return 1; return aa->order - bb->order; } /* * Compare - sort with directories at the end * Uses the order field to make qsort() stable */ int cmp_dir_end(const void *a, const void *b) { struct fileent *aa = (struct fileent*)a; struct fileent *bb = (struct fileent*)b; if ((aa->type == 0x0f) && (bb->type != 0x0f)) return 1; if ((bb->type == 0x0f) && (aa->type != 0x0f)) return -1; return aa->order - bb->order; } /* * Compare - sort in increasing order of blocks used */ int cmp_blocks_asc(const void *a, const void *b) { struct fileent *aa = (struct fileent*)a; struct fileent *bb = (struct fileent*)b; int rc = aa->blocks - bb->blocks; return rc != 0 ? rc : aa->order - bb->order; } /* * Compare - sort in decreasing order of blocks used */ int cmp_blocks_desc(const void *a, const void *b) { struct fileent *aa = (struct fileent*)a; struct fileent *bb = (struct fileent*)b; int rc = bb->blocks - aa->blocks; return rc != 0 ? rc : aa->order - bb->order; } /* * Compare - sort in increasing order of EOF position */ int cmp_eof_asc(const void *a, const void *b) { struct fileent *aa = (struct fileent*)a; struct fileent *bb = (struct fileent*)b; long diff = aa->eof - bb->eof; if (diff == 0) return aa->order - bb->order; if (diff > 0) return 1; else return -1; } /* * Compare - sort in decreasing order of EOF position */ int cmp_eof_desc(const void *a, const void *b) { struct fileent *aa = (struct fileent*)a; struct fileent *bb = (struct fileent*)b; long diff = bb->eof - aa->eof; if (diff == 0) return aa->order - bb->order; if (diff > 0) return 1; else return -1; } /* * No-op sort which just keeps items in the same order */ int cmp_noop(const void *a, const void *b) { struct fileent *aa = (struct fileent*)a; struct fileent *bb = (struct fileent*)b; return aa->order - bb->order; } #endif /* * Sort filelist[] * s defines the field to sort on */ void sortlist(char s) { uint i; #ifndef SORT return; #else for (i=0; inext; else err(FATAL, "Int error"); if (!p) err(FATAL, "Int error"); return p->blocknum; } /* * Copy a file entry from one srcblk, srcent to dstblk, dstent * All indices are 1-based. */ void copyent(uint srcblk, uint srcent, uint dstblk, uint dstent, uint device) { struct block *source = blocks, *dest = blocks; struct pd_dirent *ent; struct pd_dirhdr *hdr; char *srcptr, *dstptr; uint parentblk; if (dodebug) { printf(" from dirblk %03u entry %02u", srcblk, srcent); printf(" to dirblk %03u entry %02u\n", dstblk, dstent); } parentblk = blockidxtoblocknum(dstblk); while (--srcblk > 0) source = source->next; while (--dstblk > 0) dest = dest->next; srcptr = source->data + PTRSZ + (srcent-1) * entsz; dstptr = dest->sorteddata + PTRSZ + (dstent-1) * entsz; memcpy(dstptr, srcptr, entsz); /* For directories, update the parent dir entry number */ ent = (struct pd_dirent*)dstptr; if ((ent->typ_len & 0xf0) == 0xd0) { uint block = ent->keyptr[0] + 256U * ent->keyptr[1]; if (readdiskblock(device, block, buf) == -1) err(FATAL, "Can't read subdir"); hdr = (struct pd_dirhdr*)(buf + PTRSZ); hdr->parptr[0] = parentblk & 0xff; hdr->parptr[1] = (parentblk >> 8) & 0xff; hdr->parentry = dstent; if (dowrite) { if (writediskblock(device, block, buf) == -1) err(FATAL, "Can't write subdir"); } } } /* * Use the sorted list in filelist[] to create a sorted set of directory * blocks. Note that the block and entry numbers are 1-based indices. */ void sortblocks(uint device) { uint i; uchar destblk = 1; uchar destentry = 2; /* Skip header on first block */ copyent(1, 1, 1, 1, device); /* Copy directory header */ for(i=0; iblocknum, i->sorteddata) == -1) { err(NONFATAL, "Can't write block %u", i->blocknum); return 1; } i = i->next; } return 0; } /* * Walk through the linked list freeing memory */ void freeblocks(void) { struct block *i = blocks, *j; while (i) { j = i->next; free(i); i = j; } blocks = NULL; } void interactive(void) { char w, l, d, f, z, wrt; uchar level; doverbose = 1; puts("S O R T D I R v0.5 alpha Use ^ to return to previous question"); q1: fputs("\nEnter path (e.g.: /H1) of starting directory> ", stdout); scanf("%s", buf); getchar(); // Eat the carriage return q2: dowholedisk = dorecurse = 0; puts("\nWhat to process ..."); do { puts("[-] Single directory [t] Tree [v] whole volume"); w = getchar(); } while (strchr("-tv^", w) == NULL); if (w == '^') goto q1; switch (w) { case 't': dorecurse = 1; break; case 'v': dorecurse = 1; dowholedisk = 1; } q3: puts("\nMulti-level directory sort ..."); puts(" Lower case option ascending order, upper case option descending order"); puts(" [nN] Name [iI] Name (case-insens) [dD] Date/Time [tT] Type"); puts(" [fF] Folders (dirs) [bB] Blks used [eE] EOF (file size)"); fputs(" [-] Done with sorting", stdout); for (level = 0; level < NLEVELS; ++level) { do { printf("\nLevel %d > ", level+1); sortopts[level] = getchar(); } while (strchr("-nNiIdDtTfFbBeE^", sortopts[level]) == NULL); if (sortopts[level] == '-') { sortopts[level] = '\0'; break; } if (sortopts[level] == '^') goto q2; } sortopts[NLEVELS] = '\0'; q4: puts("\nFilename case conversion ..."); do { puts("[-] No change [l] Lower case [u] Upper case [i] Initial case [c] Camel case"); l = getchar(); } while (strchr("-luic^", l) == NULL); if (l == '^') goto q3; if (l != '-') caseopts[0] = l; q5: puts("\nOn-disk date format conversion ..."); do { puts("[-] No change [n] -> New ProDOS 2.5 format [o] -> Old legacy ProDOS format"); d = getchar(); } while (strchr("-no^", d) == NULL); if (d == '^') goto q4; if (d != '-') dateopts[0] = d; q6: puts("\nAttempt to fix errors? ..."); do { puts("[-] Never fix [?] Ask before fixing [a] Always fix"); f = getchar(); } while (strchr("-?a^", f) == NULL); if (f == '^') goto q5; fixopts[0] = ((f == '-') ? 'n' : f); q7: if (w == 'v') { puts("\nZero free space? ..."); do { puts("[-] No, don't zero [z] Yes, zero free blocks"); z = getchar(); } while (strchr("-z^", z) == NULL); if (z == '^') goto q6; if (z == 'z') dozero = 1; } q8: puts("\nAllow writing to disk? ..."); do { puts("[-] No, don't write (Dry run) [w] Yes, commit changes to disk"); wrt = getchar(); } while (strchr("-w^", wrt) == NULL); if (wrt == '^') goto q7; if (wrt == 'w') dowrite = 1; //do_ctime = 0; /* -k ctime option */ } void usage(void) { printf("usage: sortdir [-s xxx] [-n x] [-rDwcvVh] path\n\n"); #if 0 printf(" Options: -s xxx Directory sort options\n"); printf(" -n x Filename upper/lower case options\n"); printf(" -d x Date format conversion options\n"); printf(" -f x Fix mode\n"); printf(" -r Recursive descent\n"); printf(" -D Whole-disk mode (implies -r)\n"); printf(" -w Enable writing to disk\n"); printf(" -c Use create time rather than modify time\n"); printf(" -z Zero free space\n"); printf(" -v Verbose output\n"); printf(" -V Verbose debugging output\n"); printf(" -h This help\n"); printf("\n"); printf("-nx: Upper/lower case filenames, where x is:\n"); printf(" l convert filenames to lower case eg: read.me\n"); printf(" u convert filenames to upper case eg: READ.ME\n"); printf(" i convert filenames to initial upper case eg: Read.me\n"); printf(" c convert filenames to camel case eg: Read.Me\n"); printf("\n"); printf("-dx: Date/time on-disk format conversion, where x is:\n"); printf(" n convert to new (ProDOS 2.5+) format\n"); printf(" o convert to old (ProDOS 1.0-2.4.2, GSOS) format\n"); printf("\n"); printf("-sxxx: Dir sort, where xxx is a list of fields to sort\n"); printf("on. The sort options are processed left-to-right.\n"); printf(" n sort by filename ascending\n"); printf(" N sort by filename descending\n"); printf(" i sort by filename ascending - case insensitive\n"); printf(" I sort by filename descending - case insensitive\n"); printf(" d sort by modify (or create [-c]) date ascending\n"); printf(" D sort by modify (or create [-c]) date descending\n"); printf(" t sort by type ascending\n"); printf(" T sort by type descending\n"); printf(" f sort folders (directories) to top\n"); printf(" F sort folders (directories) to bottom\n"); printf(" b sort by blocks used ascending\n"); printf(" B sort by blocks used descending\n"); printf(" e sort by EOF position ascending\n"); printf(" E sort by EOF position descending\n"); printf("\n"); printf("-fx: Fix mode, where x is:\n"); printf(" ? prompt for each fix\n"); printf(" n never fix\n"); printf(" y always fix (be careful!)\n"); printf("\n"); printf("e.g.: sortdir -w -s nf .\n"); printf("Will sort the current directory first by name (ascending),\n"); printf("then sort directories to the top, and will write the\n"); printf("sorted directory to disk.\n"); #endif err(FATAL, "Usage error"); } /* * Performs all actions for a single directory * blocknum is the keyblock of the directory to process */ void processdir(uint device, uint blocknum) { uchar i, errs; flushall(); errs = readdir(device, blocknum); // if (doverbose) { // printlist(); // } if ((strlen(fixopts) == 0) && errs) { err(NONFATAL, "Error scanning directory, will not sort\n"); goto done; } if (strlen(sortopts) > 0) { if (doverbose) fputs("Sorting: ", stdout); for (i=0; i 0) || (strlen(fixopts) > 0) || (strlen(dateopts) > 0)) && (strlen(sortopts) == 0)) strncpy(sortopts, ".", 1); firstblk(((argc == 1) ? buf : argv[optind]), &dev, &blk); #ifdef FREELIST readfreelist(dev); #endif if (dowholedisk) processdir(dev, 2); else processdir(dev, blk); if (dorecurse) { while (dirs) { struct dirblk *d = dirs; blk = dirs->blocknum; dirs = d->next; free(d); processdir(dev, blk); } } if (dowholedisk) checkfreeandused(dev); #ifdef FREELIST free(freelist); free(usedlist); #endif err(FINISHED, ""); return 0; // Just to shut up warning }