mirror of
https://github.com/bobbimanners/ProDOS-Utils.git
synced 2025-04-30 19:37:20 +00:00
2078 lines
52 KiB
C
2078 lines
52 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
//#include <sys/stat.h>
|
|
//#include <orca.h>
|
|
//#include <gsos.h>
|
|
//#include <prodos.h>
|
|
#include <apple2enh.h>
|
|
#include <dio.h>
|
|
|
|
#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<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 *p, uchar len, uchar *minvers, uchar *vers) {
|
|
*vers = 0x00;
|
|
*minvers = 0x00;
|
|
}
|
|
|
|
/*
|
|
* Convert filename pointed to by p into to have first letter capitalized
|
|
* (which is recorded as a bitmap in the vers and minvers fields.
|
|
* If mode = 0 then just uppercase the initial char ("Read.me")
|
|
* otherwise camel-case the name ("Read.Me")
|
|
*/
|
|
void initialcase(uchar mode, char *p, uchar len, uchar *minvers, uchar *vers) {
|
|
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, "Error opening dir %s", dirname);
|
|
goto ret;
|
|
}
|
|
|
|
len = read(fp, buf, BLKSZ);
|
|
if (len != BLKSZ) {
|
|
err(FATAL, "Error reading first blk of dir %s", 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;
|
|
|
|
*device = *lastdev;
|
|
slot = (*lastdev & 0x70) >> 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<flsize; ++i) {
|
|
markused(flblk);
|
|
if (readdiskblock(device, flblk++, p) == -1) {
|
|
err(NONFATAL, "Error reading free list");
|
|
return -1;
|
|
}
|
|
p += BLKSZ;
|
|
}
|
|
flloaded = 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Determine if block blk is free or not
|
|
*/
|
|
int isfree(uint blk) {
|
|
uint idx = blk / 8;
|
|
uint bit = blk % 8;
|
|
return (freelist[idx] << bit) & 0x80 ? 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* Mark a block as free
|
|
*/
|
|
void markfree(uint blk) {
|
|
uint idx = blk / 8;
|
|
uint bit = blk % 8;
|
|
freelist[idx] |= (0x80 >> 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; i<NMLEN+1; ++i)
|
|
filelist[numfiles].name[i] = '\0';
|
|
for (i=0; i<(ent->typ_len & 0x0f); ++i)
|
|
filelist[numfiles].name[i] = namebuf[i];
|
|
filelist[numfiles].type = ent->type;
|
|
filelist[numfiles].blockidx = blkcnt;
|
|
filelist[numfiles].entrynum = blkentries;
|
|
filelist[numfiles].blocks = blks;
|
|
filelist[numfiles].eof = eof;
|
|
|
|
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; 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 'd':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_datetime_asc);
|
|
break;
|
|
case 'D':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_datetime_desc);
|
|
break;
|
|
case 't':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_type_asc);
|
|
break;
|
|
case 'T':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_type_desc);
|
|
break;
|
|
case 'f':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_dir_beg);
|
|
break;
|
|
case 'F':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_dir_end);
|
|
break;
|
|
case 'b':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_blocks_asc);
|
|
break;
|
|
case 'B':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_blocks_desc);
|
|
break;
|
|
case 'e':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_eof_asc);
|
|
break;
|
|
case 'E':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_eof_desc);
|
|
break;
|
|
case '.':
|
|
qsort(filelist, numfiles, sizeof(struct fileent),
|
|
cmp_noop);
|
|
break;
|
|
default:
|
|
err(FATALBADARG, "Invalid sort option");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Print the file info stored in filelist[]
|
|
*/
|
|
void printlist(void) {
|
|
uint i, j;
|
|
hline();
|
|
fputs("Dirblk Entry Type : Name : Blocks EOF ", stdout);
|
|
if (do_ctime)
|
|
puts("Created");
|
|
else
|
|
puts("Modified");
|
|
for (i=0; i<numfiles; ++i) {
|
|
printf(" %03u %02u %02x : %s",
|
|
filelist[i].blockidx,
|
|
filelist[i].entrynum,
|
|
filelist[i].type,
|
|
filelist[i].name);
|
|
for (j=0; j<(16-strlen(filelist[i].name)); ++j)
|
|
putchar(' ');
|
|
printf(": %5u %8lu", filelist[i].blocks, filelist[i].eof);
|
|
printf(" %s\n", filelist[i].datetime);
|
|
}
|
|
hline();
|
|
}
|
|
|
|
/*
|
|
* Convert block index to block number
|
|
* Block index is 1-based (1,2,3 ...)
|
|
*/
|
|
uint blockidxtoblocknum(uint idx) {
|
|
uint i;
|
|
struct block *p = blocks;
|
|
for (i=1; i<idx; ++i)
|
|
if (p)
|
|
p = p->next;
|
|
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; i<numfiles; ++i) {
|
|
if (dodebug)
|
|
puts(filelist[i].name);
|
|
copyent(filelist[i].blockidx, filelist[i].entrynum,
|
|
destblk, destentry, device);
|
|
if (destentry++ == entperblk) {
|
|
++destblk;
|
|
destentry = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write out the sorted directory
|
|
*/
|
|
int writedir(uchar device) {
|
|
struct block *i = blocks;
|
|
while (i) {
|
|
if(writediskblock(device, i->blocknum, i->sorteddata) == -1) {
|
|
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<strlen(sortopts); ++i) {
|
|
if (doverbose)
|
|
printf("[%c] ", sortopts[i]);
|
|
sortlist(sortopts[i]);
|
|
}
|
|
if (doverbose)
|
|
putchar('\n');
|
|
#ifdef SORT
|
|
sortblocks(device);
|
|
#endif
|
|
if (doverbose) {
|
|
printlist();
|
|
}
|
|
if (dowrite) {
|
|
puts("Writing dir ...");
|
|
errs = writedir(device);
|
|
} else
|
|
puts("** NOT writing dir");
|
|
}
|
|
done:
|
|
freeblocks();
|
|
}
|
|
|
|
/*
|
|
* Iterate through freelist[] and usedlist[] and see if all is well.
|
|
* If we have visited all files and directories on the volume, every
|
|
* block should either be marked free or marked used.
|
|
*/
|
|
void checkfreeandused(uchar device) {
|
|
#ifdef FREELIST
|
|
uint i, freeblks = 0;
|
|
printf("Total blks\t%u\n", totblks);
|
|
for (i=0; i<totblks; ++i)
|
|
if (isfree(i))
|
|
++freeblks;
|
|
printf("Free blks\t%u\n", freeblks);
|
|
// printf("Percentage full\t\t%.1f\n",
|
|
// 100.0 * (float)(totblks - freeblks) / totblks);
|
|
for (i=0; i<totblks; ++i) {
|
|
uint idx = i / 8;
|
|
if (!(freelist[idx] ^ usedlist[i])) /* Speed-up */
|
|
continue;
|
|
if (isfree(i)) {
|
|
if (isused(i)) {
|
|
err(NONFATAL,
|
|
"Blk %u used, marked free", i);
|
|
if (askfix() == 1)
|
|
marknotfree(i);
|
|
}
|
|
} else {
|
|
if (!isused(i)) {
|
|
err(NONFATAL,
|
|
"Blk %u unused, not marked free", i);
|
|
if (askfix() == 1)
|
|
markfree(i);
|
|
}
|
|
}
|
|
}
|
|
if (dozero)
|
|
zerofreeblocks(device, freeblks);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Zero block blocknum
|
|
*/
|
|
#ifdef FREELIST
|
|
void zeroblock(uchar device, uint blocknum) {
|
|
bzero(buf, BLKSZ);
|
|
// 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");
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Zero all free blocks on the volume
|
|
*/
|
|
#ifdef FREELIST
|
|
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
|
|
|
|
#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;
|
|
}
|
|
}
|
|
|
|
//int main(int argc, char *argv[]) {
|
|
int main() {
|
|
int opt;
|
|
uchar dev;
|
|
uint blk;
|
|
// int argc = 5;
|
|
// char *argv[] = {"sortdir", "-r", "-v", "-snf", "/gno"}; // TODO: HARDCODED FOR NOW
|
|
|
|
// uint *p;
|
|
// p = (uint*)0x36; // CSW
|
|
// *p = 0xc307; // BASICOUT
|
|
// p = (uint*)0x38; // KSW
|
|
// *p = 0xc305; // BASICIN
|
|
|
|
uchar *pp;
|
|
pp = (uchar*)0xbf98;
|
|
if (!(*pp & 0x02))
|
|
err(FATAL, "Need 80 cols");
|
|
if ((*pp & 0x30) != 0x30)
|
|
err(FATAL, "Need 128K");
|
|
|
|
// 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());
|
|
|
|
parseargs();
|
|
|
|
if (argc == 1)
|
|
interactive();
|
|
|
|
else {
|
|
if (argc < 2)
|
|
usage();
|
|
while ((opt = getopt(argc, argv, "cDrwvVzs:n:f:d:h")) != -1) {
|
|
switch (opt) {
|
|
case 'c':
|
|
do_ctime = 1;
|
|
break;
|
|
case 'D':
|
|
dowholedisk = 1;
|
|
dorecurse = 1;
|
|
break;
|
|
case 'r':
|
|
dorecurse = 1;
|
|
break;
|
|
case 'w':
|
|
dowrite = 1;
|
|
break;
|
|
case 'v':
|
|
doverbose = 1;
|
|
break;
|
|
case 'V':
|
|
dodebug = 1;
|
|
break;
|
|
case '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();
|
|
}
|
|
|
|
if (((strlen(caseopts) > 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
|
|
}
|
|
|