mirror of
https://github.com/bobbimanners/ProDOS-Utils.git
synced 2025-02-19 07:30:25 +00:00
2732 lines
68 KiB
C
2732 lines
68 KiB
C
/*
|
|
* SORTDIR - for Apple II (ProDOS)
|
|
*
|
|
* Bobbi January-June 2020
|
|
*
|
|
* TODO: Find out why free(usedlist) at end -> crash. Memory corruption?
|
|
* TODO: EOF validation / fix:
|
|
* 1) Check this in readir() taking account of sparse files
|
|
* 2) When trimming a directory, need to update EOF for parent entry
|
|
* TODO: Print indication when a file is sparse - blocks in inverse video?
|
|
* TODO: Get both ProDOS-8 and GNO versions to build from this source
|
|
*
|
|
* Revision History
|
|
* v0.50 Initial alpha release on GitHub. Ported from GNO/ME version.
|
|
* v0.51 Made buf[] and buf2[] dynamic.
|
|
* v0.52 Support for aux memory.
|
|
* v0.53 Auto-sizing of filelist[] to fit available memory.
|
|
* v0.54 Make command line argument handling a compile time option.
|
|
* v0.55 Can use *all* of largest heap block for filelist[].
|
|
* v0.56 Minor improvements to conditional compilation.
|
|
* v0.57 Fixed bugs in aux memory allocation, memory zeroing bug.
|
|
* v0.58 Fixed more bugs. Now working properly using aux memory.
|
|
* v0.59 Moved creation of filelist[] into buildsorttable(). More bugfix.
|
|
* v0.60 Modified fileent to be a union. Build it for each subsort. Saves RAM.
|
|
* v0.61 Squeezed fileent to be a few bytes smaller. Fixed folder sort.
|
|
* v0.62 Modified buildsorttable() to update existing filelist[].
|
|
* v0.63 Made code work properly with #undef CHECK.
|
|
* v0.64 Fixed overflow in file count (entries). Added check to auxalloc().
|
|
* v0.65 Fixed length passed to AUXMOVE in copyaux().
|
|
* v0.66 Modified to build sorted blocks on the fly rather than in aux memory.
|
|
* v0.67 Fixed bug in v0.66 where garbage was written to end of directory.
|
|
* v0.68 Cleaned up error msgs.
|
|
* v0.69 Fixed support for drive number >2. (cc65 needs to be fixed too!)
|
|
* v0.70 Changed sort options to support mtime & ctime. Improved UI a bit.
|
|
* v0.71 Added support for allocating aux LC memory.
|
|
* v0.72 Initial support for freelist and usedlist in aux mem. (Slow!)
|
|
* v0.73 Speedup to checkfreeandused();
|
|
* v0.74 Eliminate no-op sort.
|
|
* v0.75 Fix bug - crash when too many files to sort.
|
|
* v0.76 Fix bug - checkfreeandused() not traversing all freelist.
|
|
* v0.77 Implemented zeroblock() for ProDOS-8.
|
|
* v0.78 Improved error handling when too many files to sort.
|
|
* v0.79 Trim unused directory blocks after sorting. Write freelist to disk.
|
|
* v0.80 Reinstated no-op sort (useful for compacting dir without reordering).
|
|
* v0.81 Do not trim volume directory to <4 blocks.
|
|
* v0.82 Minor fix to TRIMDIR conditional compilation.
|
|
* v0.83 Print additional info on each file.
|
|
* v0.84 Minor fixup for builds without CHECK and FREELIST defined.
|
|
* v0.85 Only write free list if it has been changed.
|
|
* v0.86 Show 'invisible' access bit.
|
|
* v0.87 Change the fix options so '-' is ask, 'y'/'n' are always/never.
|
|
* v0.88 Show ProDOS 2.5 dates in inverse video (saves two columns!)
|
|
* v0.89 Commented out free(usedlist) which was crashing for some reason.
|
|
* v0.90 Fixed parsing of dateopts[], caseopts[], fixopts[]
|
|
* v0.91 Added disconnect_ramdisk()
|
|
* v0.92 Copied RAMdisk disconnection/reconnection code from EDIT.SYSTEM
|
|
*/
|
|
|
|
//#pragma debug 9
|
|
//#pragma lint -1
|
|
//#pragma stacksize 16384
|
|
//#pragma memorymodel 0
|
|
//#pragma optimize -1 /* Disable stack repair code */
|
|
|
|
#include <apple2enh.h>
|
|
#include <conio.h>
|
|
#include <ctype.h>
|
|
#include <dio.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
//#include <sys/stat.h>
|
|
//#include <orca.h>
|
|
//#include <gsos.h>
|
|
//#include <prodos.h>
|
|
|
|
#define CHECK /* Perform additional integrity checking */
|
|
#define SORT /* Enable sorting code */
|
|
#define FREELIST /* Checking of free list */
|
|
#define AUXMEM /* Auxiliary memory support on //e and up */
|
|
#undef CMDLINE /* Command line option parsing */
|
|
#undef TRIMDIR /* Enable trimming of directory blocks */
|
|
|
|
#define NLEVELS 4 /* Number of nested sorts permitted */
|
|
|
|
typedef unsigned char uchar;
|
|
typedef unsigned int uint;
|
|
typedef unsigned long ulong;
|
|
|
|
#define NMLEN 15 /* Length of filename */
|
|
|
|
/*
|
|
* ProDOS directory header
|
|
* See ProDOS-8 Tech Ref pp. 152
|
|
*/
|
|
struct pd_dirhdr {
|
|
uchar typ_len;
|
|
char name[NMLEN];
|
|
char reserved[8];
|
|
uchar ctime[4];
|
|
uchar vers;
|
|
uchar minvers;
|
|
uchar access;
|
|
uchar entlen;
|
|
uchar entperblk;
|
|
uchar filecnt[2];
|
|
uchar parptr[2]; /* Bitmap pointer in volume dir */
|
|
uchar parentry; /* Total blocks LSB in volume dir */
|
|
uchar parentlen; /* Total blocks MSB in volume dir */
|
|
};
|
|
|
|
/*
|
|
* ProDOS file entry
|
|
* See ProDOS-8 Tech Ref pp. 155
|
|
*/
|
|
struct pd_dirent {
|
|
uchar typ_len;
|
|
char name[NMLEN];
|
|
uchar type;
|
|
uchar keyptr[2];
|
|
uchar blksused[2];
|
|
uchar eof[3];
|
|
uchar ctime[4];
|
|
uchar vers;
|
|
uchar minvers;
|
|
uchar access;
|
|
uchar auxtype[2];
|
|
uchar mtime[4];
|
|
uchar hdrptr[2];
|
|
};
|
|
|
|
#define BLKSZ 512 /* 512 byte blocks */
|
|
#define PTRSZ 4 /* 4 bytes of pointers at beginning of each blk */
|
|
#define ENTSZ 0x27 /* Normal ProDOS directory entry size */
|
|
#define ENTPERBLK 0x0d /* Normal ProDOS dirents per block */
|
|
#define FLSZ 8192 /* Bytes required for 64K block free-list */
|
|
|
|
/* Exit codes */
|
|
#define EXIT_SUCCESS 0
|
|
#define EXIT_BAD_ARG 1
|
|
#define EXIT_ALLOC_ERR 2
|
|
#define EXIT_FATAL_ERR 3
|
|
|
|
/*
|
|
* Linked list of directory blocks read from disk
|
|
* Directory block is stored in data[]
|
|
*/
|
|
struct block {
|
|
#ifdef AUXMEM
|
|
char *data; /* Contents of block (pointer to auxmem) */
|
|
#else
|
|
char data[BLKSZ]; /* Contents of block */
|
|
#endif
|
|
uint blocknum; /* Block number on disk */
|
|
struct block *next;
|
|
};
|
|
|
|
/*
|
|
* Entry for array of filenames used by qsort()
|
|
*/
|
|
struct fileent {
|
|
uchar blockidx; /* Index of dir block (1,2,3 ...) */
|
|
uchar entrynum; /* Entry within the block */
|
|
union {
|
|
char name[NMLEN-2]; /* Name converted to upper/lower case */
|
|
char datetime[12+1];/* Date/time as a yyyymmddhhmm string */
|
|
uchar type; /* ProDOS file type */
|
|
uint blocks; /* Size in blocks */
|
|
ulong eof; /* EOF position in bytes */
|
|
};
|
|
/* NOTE: Because name is unique we do not need the order field to make
|
|
* the sort stable, so we can let the name buffer overflow by 2 bytes
|
|
*/
|
|
uint order; /* Hack to make qsort() stable */
|
|
};
|
|
|
|
/*
|
|
* Entry for list of directory keyblocks to check
|
|
*/
|
|
struct dirblk {
|
|
uint blocknum;
|
|
struct dirblk *next;
|
|
};
|
|
|
|
/*
|
|
* Represents a date and time
|
|
*/
|
|
struct datetime {
|
|
uint year;
|
|
uchar month;
|
|
uchar day;
|
|
uchar hour;
|
|
uchar minute;
|
|
uchar ispd25format;
|
|
uchar nodatetime;
|
|
};
|
|
|
|
/*
|
|
* Globals
|
|
*/
|
|
#ifdef AUXMEM
|
|
#define STARTAUX1 0x0800 // 46K main aux block
|
|
#define ENDAUX1 0xbfff
|
|
#define STARTAUX2 0xd000 // 12K block in aux LC
|
|
#define ENDAUX2 0xffff
|
|
static char *auxp = (char*)STARTAUX1; /* For allocating aux main */
|
|
static char *auxp2 = (char*)STARTAUX2; /* For allocating aux LC */
|
|
static char *auxlockp = (char*)STARTAUX1; /* Aux mem protection */
|
|
#endif
|
|
#ifdef FREELIST
|
|
static uint totblks; /* Total # blocks on volume */
|
|
static uchar *freelist; /* Free-list bitmap */
|
|
static uchar *usedlist; /* Bit map of used blocks */
|
|
static uchar flloaded = 0; /* 1 if free-list has been loaded */
|
|
static uchar flchanged = 0; /* 1 if free-list has been changed */
|
|
static uint flsize; /* Size of free-list in blocks */
|
|
static uint flblk; /* Block num for start of freelist */
|
|
#endif
|
|
static char currdir[NMLEN+1]; /* Name of current directory */
|
|
static struct block *blocks = NULL; /* List of directory disk blocks */
|
|
static struct dirblk *dirs = NULL; /* List of key blocks of subdirs */
|
|
static uint numfiles; /* Number of files in current dir */
|
|
static uint maxfiles; /* Size of filelist[] */
|
|
static uchar entsz; /* Bytes per file entry */
|
|
static uchar entperblk; /* Number of entries per block */
|
|
static uint errcount = 0; /* Error counter */
|
|
static dhandle_t dio_hdl; /* cc64 direct I/O handle */
|
|
static uchar dowholedisk = 0; /* -D whole-disk option */
|
|
static uchar dorecurse = 0; /* -r recurse option */
|
|
static uchar dowrite = 0; /* -w write option */
|
|
static uchar doverbose = 0; /* -v verbose option */
|
|
static uchar dodebug = 0; /* -V very verbose option */
|
|
#ifdef FREELIST
|
|
static uchar dozero = 0; /* -z zero free blocks option */
|
|
#endif
|
|
static char sortopts[NLEVELS+1] = ""; /* -s:abc list of sort options */
|
|
static char caseopts[2] = ""; /* -c:x case conversion option */
|
|
static char fixopts[2] = ""; /* -f:x fix mode option */
|
|
static char dateopts[2] = ""; /* -d:x date conversion option */
|
|
|
|
// Allocated dynamically in main()
|
|
static char *buf; /* General purpose scratch buffer */
|
|
static char *buf2; /* General purpose scratch buffer */
|
|
static char *dirblkbuf; /* Used for reading directory blocks */
|
|
static struct fileent *filelist; /* Used for qsort() */
|
|
|
|
/* Error messages */
|
|
static const char err_nomem[] = "No memory!";
|
|
static const char err_noaux[] = "No aux mem!";
|
|
static const char err_rdblk1[] = "Can't read blk %u";
|
|
static const char err_rdblk2[] = "Can't read blk %u ($%2x)";
|
|
static const char err_wtblk1[] = "Can't write blk %u";
|
|
static const char err_wtblk2[] = "Can't write blk %u ($%2x)";
|
|
#ifdef CHECK
|
|
static const char err_stype2[] = "Bad storage type $%2x for %s";
|
|
#endif
|
|
static const char err_odir1[] = "Can't open dir %s";
|
|
static const char err_rddir1[] = "Can't read dir %s";
|
|
static const char err_rdpar[] = "Can't read parent dir";
|
|
#ifdef CHECK
|
|
static const char err_sdname[] = "Bad subdir name";
|
|
static const char err_entsz2[] = "Bad entry size %u, should be %u";
|
|
static const char err_entblk2[] = "Bad entries/blk %u, should be %u";
|
|
static const char err_parblk3[] = "Bad parent %s %u, should be %u";
|
|
static const char err_hdrblk2[] = "Bad hdr blk %u, should be %u";
|
|
static const char err_access[] = "Bad access";
|
|
static const char err_forksz3[] = "%s fork size %u is wrong, should be %u";
|
|
static const char err_used2[] = "Blks used %u is wrong, should be %u";
|
|
#endif
|
|
static const char err_many[] = "Too many files to sort";
|
|
static const char err_count2[] = "Filecount %u wrong, should be %u";
|
|
static const char err_nosort[] = "Not sorting due to errors";
|
|
#ifdef FREELIST
|
|
static const char err_rdfl[] = "Can't read free list";
|
|
static const char err_blfree1[] = "In use blk %u is marked free";
|
|
static const char err_blfree2[] = "%s blk %u marked free";
|
|
static const char err_blused1[] = "Unused blk %u not marked free";
|
|
static const char err_blused2[] = "%s blk %u used elsewhere";
|
|
#endif
|
|
static const char err_updsdir1[] = "Can't update subdir entry (%s)";
|
|
static const char err_invopt[] = "Invalid %s option";
|
|
#ifdef CMDLINE
|
|
static const char err_usage[] = "Usage error";
|
|
#endif
|
|
static const char err_80col[] = "Need 80 cols";
|
|
static const char err_128K[] = "Need 128K";
|
|
|
|
// The following are used for reconnecting /RAM and /RAM3 on exit
|
|
uint16_t s3d1vec;
|
|
uint16_t s3d2vec;
|
|
uint8_t s3d1dev;
|
|
uint8_t s3d2dev;
|
|
|
|
/* Prototypes */
|
|
#ifdef AUXMEM
|
|
void copyaux(char *src, char *dst, uint len, uchar dir);
|
|
char *auxalloc(uint bytes);
|
|
char *auxalloc2(uint bytes);
|
|
void lockaux(void);
|
|
void freeallaux(void);
|
|
#endif
|
|
void hline(void);
|
|
void hlinechar(char c);
|
|
void confirm(void);
|
|
void err(enum errtype severity, const char *fmt, ...);
|
|
void flushall(void);
|
|
int readdiskblock(uchar device, uint blocknum, char *buf);
|
|
int writediskblock(uchar device, uint blocknum, char *buf);
|
|
void fixcase(char *in, char *out, uchar vers, uchar minvers, uchar len);
|
|
void lowercase(char *p, uchar len, uchar *vers, uchar *minvers);
|
|
void uppercase(char *p, uchar len, uchar *vers, uchar *minvers);
|
|
void initialcase(uchar mode, char *p, uchar len, uchar *vers, uchar *minvers);
|
|
void firstblk(char *dirname, uchar *device, uint *block);
|
|
void readdatetime(uchar time[4], struct datetime *dt);
|
|
void writedatetime(struct datetime *dt, uchar time[4]);
|
|
void printdatetime(struct datetime *dt);
|
|
uint askfix(void);
|
|
#ifdef FREELIST
|
|
int readfreelist(uchar device);
|
|
int isfree(uint blk);
|
|
int isused(uint blk);
|
|
void markused(uint blk);
|
|
void trimdirblock(uint blk);
|
|
void checkblock(uint blk, char *msg);
|
|
#endif
|
|
#ifdef CHECK
|
|
int seedlingblocks(uchar device, uint keyblk, uint *blkcnt);
|
|
int saplingblocks(uchar device, uint keyblk, uint *blkcnt);
|
|
int treeblocks(uchar device, uint keyblk, uint *blkcnt);
|
|
int forkblocks(uchar device, uint keyblk, uint *blkcnt);
|
|
int subdirblocks(uchar device, uint keyblk, struct pd_dirent *ent,
|
|
uint blocknum, uint blkentries, uint *blkcnt);
|
|
#endif
|
|
void enqueuesubdir(uint blocknum, uint subdiridx);
|
|
int readdir(uint device, uint blocknum);
|
|
#ifdef SORT
|
|
uchar buildsorttable(char s, uchar callidx);
|
|
int cmp_name_asc(const void *a, const void *b);
|
|
int cmp_name_desc(const void *a, const void *b);
|
|
int cmp_name_asc_ci(const void *a, const void *b);
|
|
int cmp_name_desc_ci(const void *a, const void *b);
|
|
int cmp_datetime_asc(const void *a, const void *b);
|
|
int cmp_datetime_desc(const void *a, const void *b);
|
|
int cmp_type_asc(const void *a, const void *b);
|
|
int cmp_type_desc(const void *a, const void *b);
|
|
int cmp_dir_beg(const void *a, const void *b);
|
|
int cmp_dir_end(const void *a, const void *b);
|
|
int cmp_blocks_asc(const void *a, const void *b);
|
|
int cmp_blocks_desc(const void *a, const void *b);
|
|
int cmp_eof_asc(const void *a, const void *b);
|
|
int cmp_eof_desc(const void *a, const void *b);
|
|
int cmp_noop(const void *a, const void *b);
|
|
void sortlist(char s);
|
|
#endif
|
|
void printlist(void);
|
|
uint blockidxtoblocknum(uint idx);
|
|
void copydirblkptrs(uint blkidx);
|
|
void copydirent(uint srcblk, uint srcent, uint dstblk, uint dstent, uint device);
|
|
uchar sortblock(uint device, uint dstblk);
|
|
uchar writedir(uchar device);
|
|
uchar writefreelist(uchar device);
|
|
void freeblocks(void);
|
|
void subtitle(char *s);
|
|
void interactive(void);
|
|
void processdir(uint device, uint blocknum);
|
|
#ifdef FREELIST
|
|
void checkfreeandused(uchar device);
|
|
void zeroblock(uchar device, uint blocknum);
|
|
void zerofreeblocks(uchar device, uint freeblks);
|
|
#endif
|
|
#ifdef CMDLINE
|
|
void usage(void);
|
|
void parseargs(void);
|
|
#endif
|
|
|
|
enum errtype {WARN, NONFATAL, FATAL, FATALALLOC, FATALBADARG, FINISHED};
|
|
|
|
#ifdef AUXMEM
|
|
|
|
/* Aux memory copy routine */
|
|
#define FROMAUX 0
|
|
#define TOAUX 1
|
|
void copyaux(char *src, char *dst, uint len, uchar dir) {
|
|
char **a1 = (char**)0x3c;
|
|
char **a2 = (char**)0x3e;
|
|
char **a4 = (char**)0x42;
|
|
*a1 = src;
|
|
*a2 = src + len - 1; // AUXMOVE moves length+1 bytes!!
|
|
*a4 = dst;
|
|
if (dir == TOAUX) {
|
|
__asm__("sec"); // Copy main->aux
|
|
__asm__("jsr $c311"); // AUXMOVE
|
|
} else {
|
|
__asm__("clc"); // Copy aux->main
|
|
__asm__("jsr $c311"); // AUXMOVE
|
|
}
|
|
}
|
|
|
|
/* Extremely simple aux memory allocator */
|
|
char *auxalloc(uint bytes) {
|
|
char *p = auxp;
|
|
auxp += bytes;
|
|
if (auxp > (char*)ENDAUX1)
|
|
return auxalloc2(bytes);
|
|
return p;
|
|
}
|
|
|
|
/* Extremely simple aux memory allocator */
|
|
char *auxalloc2(uint bytes) {
|
|
char *p = auxp2;
|
|
auxp2 += bytes;
|
|
if (auxp2 < p) // ie: wrap around $ffff
|
|
err(FATAL, err_noaux);
|
|
return p;
|
|
}
|
|
|
|
/* Lock aux memory below address provided
|
|
* Must be in main bank
|
|
*/
|
|
void lockaux(void) {
|
|
auxlockp = auxp;
|
|
}
|
|
|
|
/* Free all aux memory above lock address */
|
|
void freeallaux() {
|
|
auxp = (char*)auxlockp;
|
|
auxp2 = (char*)STARTAUX2;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* Horizontal line */
|
|
void hline(void) {
|
|
hlinechar('-');
|
|
}
|
|
|
|
void hlinechar(char c) {
|
|
uint i;
|
|
for (i = 0; i < 80; ++i)
|
|
putchar(c);
|
|
}
|
|
|
|
void confirm() {
|
|
puts("[Press Any Key]");
|
|
getchar();
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
/* LANGUAGE CARD BANK 2 0xd400-x0dfff 3KB */
|
|
/****************************************************************************/
|
|
#pragma code-name (push, "LC")
|
|
|
|
|
|
/*
|
|
* Display error message
|
|
*/
|
|
void err(enum errtype severity, const char *fmt, ...) {
|
|
va_list v;
|
|
uint rv = 0;
|
|
putchar('\n');
|
|
if (severity == FINISHED) {
|
|
hline();
|
|
if (errcount == 0)
|
|
printf("DONE - no errors found.\n");
|
|
else
|
|
printf("DONE - %u errors\n", errcount);
|
|
hline();
|
|
confirm();
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
++errcount;
|
|
|
|
switch (severity) {
|
|
case FATAL:
|
|
rv = EXIT_FATAL_ERR;
|
|
case FATALALLOC:
|
|
rv = EXIT_ALLOC_ERR;
|
|
case FATALBADARG:
|
|
rv = EXIT_BAD_ARG;
|
|
}
|
|
|
|
fputs(((rv > 0) ? " ** " : " "), stdout);
|
|
va_start(v, fmt);
|
|
vprintf(fmt, v);
|
|
va_end(v);
|
|
if (rv > 0) {
|
|
printf("\nStopping after %u errors\n", errcount);
|
|
confirm();
|
|
exit(rv);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Disable GSOS block cache and flush any unwritten changes
|
|
*/
|
|
void flushall(void) {
|
|
// short ff[2];
|
|
// ResetCacheGS(0); /* Disable block caching */
|
|
// ff[0] = 1;
|
|
// ff[1] = 0;
|
|
// FlushGS(ff);
|
|
}
|
|
|
|
/*
|
|
* Read block from disk using ProDOS call
|
|
* buf must point to buffer with at least 512 bytes
|
|
*/
|
|
int readdiskblock(uchar device, uint blocknum, char *buf) {
|
|
int rc;
|
|
#ifdef CHECK
|
|
#ifdef FREELIST
|
|
if (flloaded)
|
|
if (isfree(blocknum))
|
|
err(NONFATAL, err_blfree1, blocknum);
|
|
#endif
|
|
#endif
|
|
// BlockRec br;
|
|
// br.blockDevNum = device;
|
|
// br.blockDataBuffer = buf;
|
|
// br.blockNum = blocknum;
|
|
// READ_BLOCK(&br);
|
|
// int rc = toolerror();
|
|
// if (rc) {
|
|
// err(FATAL, "Blk read failed, err=%x", rc);
|
|
// return -1;
|
|
// }
|
|
rc = dio_read(dio_hdl, blocknum, buf);
|
|
if (rc)
|
|
err(FATAL, err_rdblk2, blocknum, rc);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Write block from disk using ProDOS call
|
|
* buf must point to buffer with at least 512 bytes
|
|
*/
|
|
int writediskblock(uchar device, uint blocknum, char *buf) {
|
|
int rc;
|
|
if ((strcmp(currdir, "LIB") == 0) ||
|
|
(strcmp(currdir, "LIBRARIES") == 0)) {
|
|
printf("Not writing lib dir %s\n", currdir);
|
|
return 0;
|
|
}
|
|
flushall();
|
|
// DIORecGS dr;
|
|
// dr.pCount = 6;
|
|
// dr.devNum = device;
|
|
// dr.buffer = buf;
|
|
// dr.requestCount = BLKSZ;
|
|
// dr.startingBlock = blocknum;
|
|
// dr.blockSize = BLKSZ;
|
|
// DWriteGS(&dr);
|
|
// if (dr.transferCount != BLKSZ) {
|
|
// err(FATAL, "Blk write failed");
|
|
// return -1;
|
|
// }
|
|
rc = dio_write(dio_hdl, blocknum, buf);
|
|
if (rc)
|
|
err(FATAL, err_wtblk2, blocknum, rc);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Uses the vers and minvers fields of the directory entry
|
|
* as a bitmap representing which characters are upper and which are
|
|
* lowercase
|
|
*/
|
|
void fixcase(char *in, char *out, uchar vers, uchar minvers, uchar len) {
|
|
uint i;
|
|
uchar idx = 0;
|
|
if (!(minvers & 0x80)) {
|
|
for (idx = 0; idx < NMLEN; ++idx)
|
|
out[idx] = in[idx];
|
|
out[len] = '\0';
|
|
return;
|
|
}
|
|
minvers <<= 1;
|
|
for (i = 0; i < 7; ++i) {
|
|
out[idx] = ((minvers & 0x80) ? tolower(in[idx]) : in[idx]);
|
|
++idx;
|
|
minvers <<= 1;
|
|
}
|
|
for (i = 0; i < 8; ++i) {
|
|
out[idx] = ((vers & 0x80) ? tolower(in[idx]) : in[idx]);
|
|
++idx;
|
|
vers <<= 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 *vers, uchar *minvers) {
|
|
uint i;
|
|
uchar idx = 0;
|
|
*vers = 0x00;
|
|
*minvers = 0x01;
|
|
for (i = 0; i < 7; ++i) {
|
|
*minvers <<= 1;
|
|
if ((idx < len) && isalpha(p[idx++]))
|
|
*minvers |= 0x01;
|
|
}
|
|
for (i = 0; i < 8; ++i) {
|
|
*vers <<= 1;
|
|
if ((idx < len) && isalpha(p[idx++]))
|
|
*vers |= 0x01;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convert filename pointed to by p into upper case (which is recorded
|
|
* as a bitmap in the vers and minvers fields.
|
|
*/
|
|
void uppercase(char*, uchar, uchar *vers, uchar *minvers) {
|
|
*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 *vers, uchar *minvers) {
|
|
uint i;
|
|
uchar idx = 0;
|
|
uchar capsflag = 1;
|
|
*vers = 0x00;
|
|
*minvers = 0x01;
|
|
for (i = 0; i < 7; ++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;
|
|
}
|
|
for (i = 0; i < 8; ++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;
|
|
}
|
|
}
|
|
|
|
//segment "extra";
|
|
|
|
/*
|
|
* Read the first block of a directory and deduce the device ID and block
|
|
* number of the first block of the directory.
|
|
*/
|
|
void firstblk(char *dirname, uchar *device, uint *block) {
|
|
struct pd_dirhdr *hdr;
|
|
struct pd_dirent *ent;
|
|
int fp;
|
|
uint len;
|
|
uint parentblk, parententry, parententlen;
|
|
uchar slot, drive;
|
|
uchar *lastdev = (uchar*)0xbf30; /* Last device accessed by ProDOS */
|
|
|
|
fp = open(dirname, O_RDONLY);
|
|
if (!fp) {
|
|
err(FATAL, err_odir1, dirname);
|
|
goto ret;
|
|
}
|
|
|
|
len = read(fp, buf, BLKSZ);
|
|
if (len != BLKSZ) {
|
|
err(FATAL, err_rddir1, dirname);
|
|
goto ret;
|
|
}
|
|
|
|
// struct stat st;
|
|
// if (stat(dirname, &st) == -1)
|
|
// err(FATAL, "Can't stat %s", dirname);
|
|
//
|
|
// if (!S_ISDIR(st.st_mode))
|
|
// err(FATAL, "%s is not a directory", dirname);
|
|
//
|
|
// *device = st.st_dev;
|
|
|
|
/*
|
|
* lastdev is in the following format:
|
|
* ProDOS 2.5+ DSSS00DD (supports drives 1-8 for each slot)
|
|
* ProDOS 2.x DSSS0000 (supports drives 1-2 for each slot)
|
|
*/
|
|
*device = *lastdev;
|
|
slot = (*lastdev & 0x70) >> 4;
|
|
drive = ((*lastdev & 0x80) >> 7) + ((*lastdev & 0x03) << 1) + 1;
|
|
clrscr();
|
|
printf("[Slot %u, Drive %u]\n", slot, drive);
|
|
*device = slot + (drive - 1) * 8;
|
|
dio_hdl = dio_open(*device);
|
|
|
|
hdr = (struct pd_dirhdr*)(buf + PTRSZ);
|
|
|
|
/* Detect & handle volume directory */
|
|
if ((hdr->typ_len & 0xf0) == 0xf0) {
|
|
*block = 2;
|
|
goto ret;
|
|
}
|
|
|
|
#ifdef CHECK
|
|
if ((hdr->typ_len & 0xf0) != 0xe0) {
|
|
err(NONFATAL, err_stype2, hdr->typ_len & 0xf0, "dir");
|
|
goto ret;
|
|
}
|
|
#endif
|
|
|
|
/* Handle subdirectory */
|
|
parentblk = hdr->parptr[0] + 256U * hdr->parptr[1];
|
|
parententry = hdr->parentry;
|
|
parententlen = hdr->parentlen;
|
|
|
|
/* Read parent directory block */
|
|
if (readdiskblock(*device, parentblk, buf) == -1)
|
|
err(FATAL, err_rdpar);
|
|
|
|
ent = (struct pd_dirent *)(buf + PTRSZ + (parententry-1) * parententlen);
|
|
|
|
*block = ent->keyptr[0] + 256U * ent->keyptr[1];
|
|
|
|
ret:
|
|
if (fp)
|
|
close(fp);
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* END OF LANGUAGE CARD BANK 2 0xd400-x0dfff 3KB SEGMENT */
|
|
/****************************************************************************/
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Parse mtime or ctime fields and populate the fields of the datetime struct
|
|
* Supports the legacy ProDOS date/time format as used by ProDOS 1.0->2.4.0
|
|
* and also the new format introduced with ProDOS 2.5.
|
|
*/
|
|
void readdatetime(uchar time[4], struct datetime *dt) {
|
|
uint d = time[0] + 256U * time[1];
|
|
uint t = time[2] + 256U * time[3];
|
|
if ((d == 0) && (t == 0)) {
|
|
dt->nodatetime = 1;
|
|
return;
|
|
}
|
|
dt->nodatetime = 0;
|
|
if (!(t & 0xe000)) {
|
|
/* ProDOS 1.0 to 2.4.2 date format */
|
|
dt->year = (d & 0xfe00) >> 9;
|
|
dt->month = (d & 0x01e0) >> 5;
|
|
dt->day = d & 0x001f;
|
|
dt->hour = (t & 0x1f00) >> 8;
|
|
dt->minute = t & 0x003f;
|
|
dt->ispd25format = 0;
|
|
if (dt->year < 40) /* See ProDOS-8 Tech Note 48 */
|
|
dt->year += 2000;
|
|
else
|
|
dt->year += 1900;
|
|
} else {
|
|
/* ProDOS 2.5.0+ */
|
|
dt->year = t & 0x0fff;
|
|
dt->month = ((t & 0xf000) >> 12) - 1;
|
|
dt->day = (d & 0xf800) >> 11;
|
|
dt->hour = (d & 0x07c0) >> 6;
|
|
dt->minute = d & 0x003f;
|
|
dt->ispd25format = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write the date and time stored in struct datetime in ProDOS on disk format,
|
|
* storing the bytes in array time[]. Supports both legacy format
|
|
* (ProDOS 1.0-2.4.2) and the new date and time format introduced
|
|
* with ProDOS 2.5
|
|
*/
|
|
void writedatetime(struct datetime *dt, uchar time[4]) {
|
|
uint d, t;
|
|
if (dt->nodatetime == 1) {
|
|
time[0] = time[1] = time[2] = time[3] = 0;
|
|
return;
|
|
}
|
|
if (dt->ispd25format == 0) {
|
|
/* ProDOS 1.0 to 2.4.2 date format */
|
|
uint year = dt->year;
|
|
if (year > 2039) /* 2039 is last year */
|
|
year = 2039;
|
|
if (year < 1940) /* 1940 is first year */
|
|
year = 1940;
|
|
if (year >= 2000)
|
|
year -= 2000;
|
|
if (year >= 1900)
|
|
year -= 1900;
|
|
d = (year << 9) | (dt->month << 5) | dt->day;
|
|
t = (dt->hour << 8) | dt->minute;
|
|
} else {
|
|
/* ProDOS 2.5.0+ */
|
|
t = ((dt->month + 1) << 12) | dt->year;
|
|
d = (dt->day << 11) | (dt->hour << 6) | dt->minute;
|
|
}
|
|
time[0] = d & 0xff;
|
|
time[1] = (d >> 8) & 0xff;
|
|
time[2] = t & 0xff;
|
|
time[3] = (t >> 8) & 0xff;
|
|
}
|
|
|
|
/*
|
|
* Print date/time value for directory listing
|
|
*/
|
|
void printdatetime(struct datetime *dt) {
|
|
if (dt->nodatetime)
|
|
fputs("-------- --:--", stderr);
|
|
else {
|
|
if (dt->ispd25format)
|
|
revers(1);
|
|
printf("%02d%02d%02d %02d:%02d",
|
|
dt->year, dt->month, dt->day, dt->hour, dt->minute);
|
|
revers(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Determine whether or not to perform a fix
|
|
* Return 0 not to perform fix, 1 to perform fix
|
|
*/
|
|
uint askfix(void) {
|
|
if (strlen(fixopts) == 0)
|
|
return 0;
|
|
fputs(": Fix (y/n)? ", stdout);
|
|
switch (fixopts[0]) {
|
|
case '-':
|
|
if (tolower(getchar()) == 'y')
|
|
return 1;
|
|
return 0;
|
|
case 'y':
|
|
fputs("y", stdout);
|
|
return 1;
|
|
default:
|
|
fputs("n", stdout);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#ifdef FREELIST
|
|
|
|
/*
|
|
* Read the free list
|
|
*/
|
|
int readfreelist(uchar device) {
|
|
uint i, f;
|
|
char *p;
|
|
#ifdef AUXMEM
|
|
bzero(buf, BLKSZ);
|
|
for (i = 0; i < 16; ++i) {
|
|
copyaux(buf, freelist + i * BLKSZ, BLKSZ, TOAUX);
|
|
copyaux(buf, usedlist + i * BLKSZ, BLKSZ, TOAUX);
|
|
}
|
|
#else
|
|
bzero(freelist, FLSZ);
|
|
bzero(usedlist, FLSZ);
|
|
#endif
|
|
markused(0); /* Boot block */
|
|
markused(1); /* SOS boot block */
|
|
if (readdiskblock(device, 2, buf) == -1) {
|
|
err(NONFATAL, err_rdblk1, 2);
|
|
return -1;
|
|
}
|
|
flblk = f = buf[0x27] + 256U * buf[0x28];
|
|
totblks = buf[0x29] + 256U * buf[0x2a];
|
|
if (doverbose)
|
|
printf("Volume has %u blocks\n", totblks);
|
|
flsize = totblks / 4096U;
|
|
if ((totblks % 4096) > 0)
|
|
++flsize;
|
|
p = (char*)freelist;
|
|
for (i = 0; i < flsize; ++i) {
|
|
markused(f);
|
|
#ifdef AUXMEM
|
|
if (readdiskblock(device, f++, buf) == -1) {
|
|
#else
|
|
if (readdiskblock(device, f++, p) == -1) {
|
|
#endif
|
|
err(NONFATAL, err_rdfl);
|
|
return -1;
|
|
}
|
|
#ifdef AUXMEM
|
|
copyaux(buf, p, BLKSZ, TOAUX);
|
|
#endif
|
|
p += BLKSZ;
|
|
}
|
|
flloaded = 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Determine if block blk is free or not
|
|
*/
|
|
int isfree(uint blk) {
|
|
uchar temp;
|
|
uint idx = blk / 8;
|
|
uint bit = blk % 8;
|
|
#ifdef AUXMEM
|
|
copyaux(freelist + idx, &temp, 1, FROMAUX);
|
|
return (temp << bit) & 0x80 ? 1 : 0;
|
|
#else
|
|
return (freelist[idx] << bit) & 0x80 ? 1 : 0;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Determine if block blk is used or not
|
|
*/
|
|
int isused(uint blk) {
|
|
uchar temp;
|
|
uint idx = blk / 8;
|
|
uint bit = blk % 8;
|
|
#ifdef AUXMEM
|
|
copyaux(usedlist + idx, &temp, 1, FROMAUX);
|
|
return (temp << bit) & 0x80 ? 1 : 0;
|
|
#else
|
|
return (usedlist[idx] << bit) & 0x80 ? 1 : 0;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Mark a block as used
|
|
*/
|
|
void markused(uint blk) {
|
|
uchar temp;
|
|
uint idx = blk / 8;
|
|
uint bit = blk % 8;
|
|
#ifdef AUXMEM
|
|
copyaux(usedlist + idx, &temp, 1, FROMAUX);
|
|
temp |= (0x80 >> bit);
|
|
copyaux(&temp, usedlist + idx, 1, TOAUX);
|
|
#else
|
|
usedlist[idx] |= (0x80 >> bit);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Mark a block as not used and add it to freelist
|
|
*/
|
|
void trimdirblock(uint blk) {
|
|
uchar temp;
|
|
uint idx = blk / 8;
|
|
uint bit = blk % 8;
|
|
#ifdef AUXMEM
|
|
copyaux(usedlist + idx, &temp, 1, FROMAUX);
|
|
temp &= ~(0x80 >> bit);
|
|
copyaux(&temp, usedlist + idx, 1, TOAUX);
|
|
copyaux(freelist + idx, &temp, 1, FROMAUX);
|
|
temp |= (0x80 >> bit);
|
|
copyaux(&temp, freelist + idx, 1, TOAUX);
|
|
#else
|
|
usedlist[idx] &= ~(0x80 >> bit);
|
|
freelist[idx] |= (0x80 >> bit);
|
|
#endif
|
|
flchanged = 1;
|
|
}
|
|
|
|
/*
|
|
* Perform all the operations to check a block which is used by
|
|
* a directory or file. Complains if the block is on the free-list
|
|
* and also if we have encountered this block in a previous file or dir.
|
|
*/
|
|
void checkblock(uint blk, char *msg) {
|
|
if (isfree(blk))
|
|
err(WARN, err_blfree2, msg, blk);
|
|
if (isused(blk))
|
|
err(WARN, err_blused2, msg, blk);
|
|
markused(blk);
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CHECK
|
|
|
|
/*
|
|
* Count the blocks in a seedling file
|
|
*/
|
|
#ifdef FREELIST
|
|
int seedlingblocks(uchar, uint keyblk, uint *blkcnt) {
|
|
checkblock(keyblk, "Data");
|
|
#else
|
|
int seedlingblocks(uchar, uint, uint *blkcnt) {
|
|
#endif
|
|
*blkcnt = 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Count the blocks in a sapling file
|
|
*/
|
|
int saplingblocks(uchar device, uint keyblk, uint *blkcnt) {
|
|
uint i, p;
|
|
#ifdef FREELIST
|
|
checkblock(keyblk, "Data");
|
|
#endif
|
|
if (readdiskblock(device, keyblk, buf) == -1) {
|
|
err(NONFATAL, err_rdblk1, keyblk);
|
|
return -1;
|
|
}
|
|
*blkcnt = 1;
|
|
for (i = 0; i < 256; ++i) {
|
|
p = buf[i] + 256U * buf[i+256];
|
|
if (p) {
|
|
#ifdef FREELIST
|
|
checkblock(p, "Data");
|
|
#endif
|
|
++(*blkcnt);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Count the blocks in a tree file
|
|
*/
|
|
int treeblocks(uchar device, uint keyblk, uint *blkcnt) {
|
|
uint i, p, b;
|
|
#ifdef FREELIST
|
|
checkblock(keyblk, "Tree index");
|
|
#endif
|
|
if (readdiskblock(device, keyblk, buf2) == -1) {
|
|
err(NONFATAL, err_rdblk1, keyblk);
|
|
return -1;
|
|
}
|
|
*blkcnt = 1;
|
|
for (i = 0; i < 256; ++i) {
|
|
p = buf2[i] + 256U * buf2[i+256];
|
|
if (p) {
|
|
if (saplingblocks(device, p, &b) == 0)
|
|
*blkcnt += b;
|
|
else
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Count the blocks in a GSOS fork file
|
|
* See http://1000bit.it/support/manual/apple/technotes/pdos/tn.pdos.25.html
|
|
*/
|
|
int forkblocks(uchar device, uint keyblk, uint *blkcnt) {
|
|
uint count, d_blks, r_blks, d_keyblk, r_keyblk;
|
|
uchar d_type, r_type;
|
|
#ifdef FREELIST
|
|
checkblock(keyblk, "Fork key");
|
|
#endif
|
|
if (readdiskblock(device, keyblk, buf) == -1) {
|
|
err(NONFATAL, err_rdblk1, keyblk);
|
|
return -1;
|
|
}
|
|
*blkcnt = 1;
|
|
|
|
d_type = buf[0x00];
|
|
d_keyblk = buf[0x01] + 256U * buf[0x02];
|
|
d_blks = buf[0x03] + 256U * buf[0x04];
|
|
r_type = buf[0x100];
|
|
r_keyblk = buf[0x101] + 256U * buf[0x102];
|
|
r_blks = buf[0x103] + 256U * buf[0x104];
|
|
|
|
/* Data fork */
|
|
switch (d_type) {
|
|
case 0x1:
|
|
/* Seedling */
|
|
seedlingblocks(device, d_keyblk, &count);
|
|
break;
|
|
case 0x2:
|
|
/* Sapling */
|
|
saplingblocks(device, d_keyblk, &count);
|
|
break;
|
|
case 0x3:
|
|
/* Tree */
|
|
treeblocks(device, d_keyblk, &count);
|
|
break;
|
|
default:
|
|
err(NONFATAL, err_stype2, d_type, "data fork");
|
|
count = 0;
|
|
break;
|
|
}
|
|
if (d_blks != count) {
|
|
if (count != 0) {
|
|
err(NONFATAL, err_forksz3, "Data", d_blks, count);
|
|
// TODO: Need to rethink the fix mode here ... it was buggy anyhow
|
|
// if (askfix() == 1) {
|
|
// buf[0x03] = count & 0xff;
|
|
// buf[0x04] = (count >> 8) & 0xff;
|
|
// }
|
|
}
|
|
}
|
|
*blkcnt += count;
|
|
|
|
/* Resource fork */
|
|
switch (r_type) {
|
|
case 0x1:
|
|
/* Seedling */
|
|
seedlingblocks(device, r_keyblk, &count);
|
|
break;
|
|
case 0x2:
|
|
/* Sapling */
|
|
saplingblocks(device, r_keyblk, &count);
|
|
break;
|
|
case 0x3:
|
|
/* Tree */
|
|
treeblocks(device, r_keyblk, &count);
|
|
break;
|
|
default:
|
|
err(NONFATAL, err_stype2, r_type, "res fork");
|
|
count = 0;
|
|
break;
|
|
}
|
|
if (r_blks != count) {
|
|
if (count != 0) {
|
|
err(NONFATAL, err_forksz3, "Res", r_blks, count);
|
|
if (askfix() == 1) {
|
|
// TODO: Need to rethink the fix mode here ... it was buggy anyhow
|
|
// buf[0x103] = count & 0xff;
|
|
// buf[0x104] = (count >> 8) & 0xff;
|
|
}
|
|
}
|
|
}
|
|
*blkcnt += count;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Count the blocks in a subdirectory
|
|
*/
|
|
int subdirblocks(uchar device, uint keyblk, struct pd_dirent *ent,
|
|
uint blocknum, uint blkentries, uint *blkcnt) {
|
|
|
|
struct pd_dirhdr *hdr;
|
|
uchar parentry, parentlen;
|
|
uint parblk;
|
|
char *dirname;
|
|
|
|
#ifdef FREELIST
|
|
if (!dorecurse)
|
|
checkblock(keyblk, "Directory");
|
|
#endif
|
|
if (readdiskblock(device, keyblk, buf) == -1) {
|
|
err(NONFATAL, err_rdblk1, keyblk);
|
|
return -1;
|
|
}
|
|
*blkcnt = 1;
|
|
hdr = (struct pd_dirhdr*)(buf + PTRSZ);
|
|
parentry = hdr->parentry;
|
|
parentlen = hdr->parentlen;
|
|
parblk = hdr->parptr[0] + 256U * hdr->parptr[1];
|
|
|
|
if (parblk != blocknum) {
|
|
err(NONFATAL, err_parblk3, "blk", parblk, blocknum);
|
|
if (askfix() == 1) {
|
|
hdr->parptr[0] = blocknum & 0xff;
|
|
hdr->parptr[1] = (blocknum >> 8) & 0xff;
|
|
}
|
|
}
|
|
|
|
if (parentry != blkentries) {
|
|
err(NONFATAL, err_parblk3, "entry", parentry, blkentries);
|
|
if (askfix() == 1) {
|
|
hdr->parentry = blkentries;
|
|
}
|
|
}
|
|
if (parentlen != ENTSZ) {
|
|
err(NONFATAL, err_parblk3, "entry size", parentlen, ENTSZ);
|
|
if (askfix() == 1) {
|
|
hdr->parentlen = ENTSZ;
|
|
}
|
|
}
|
|
dirname = buf + 0x05;
|
|
if (strncmp(dirname, ent->name, NMLEN)) {
|
|
err(NONFATAL, err_sdname);
|
|
}
|
|
|
|
blocknum = buf[0x02] + 256U * buf[0x03];
|
|
while (blocknum) {
|
|
#ifdef FREELIST
|
|
if (!dorecurse)
|
|
checkblock(blocknum, "Directory");
|
|
#endif
|
|
if (readdiskblock(device, blocknum, buf) == -1) {
|
|
err(NONFATAL, err_rdblk1, blocknum);
|
|
return -1;
|
|
}
|
|
++(*blkcnt);
|
|
blocknum = buf[0x02] + 256U * buf[0x03];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Record the keyblock of a subdirectory to be processed subsequently
|
|
* blocknum is the block number of the subdirectory keyblock
|
|
* subdiridx is a sequential counter of the subdirs in the current directory
|
|
*/
|
|
void enqueuesubdir(uint blocknum, uint subdiridx) {
|
|
static struct dirblk *prev;
|
|
struct dirblk *p = (struct dirblk*)malloc(sizeof(struct dirblk));
|
|
if (!p)
|
|
err(FATALALLOC, err_nomem);
|
|
p->blocknum = blocknum;
|
|
if (subdiridx == 0) { /* First subdir is inserted at head of list */
|
|
p->next = dirs;
|
|
dirs = p;
|
|
} else { /* Subsequent subdirs follow the previous */
|
|
p->next = prev->next;
|
|
prev->next = p;
|
|
}
|
|
prev = p;
|
|
}
|
|
|
|
/*
|
|
* Read a directory, store the raw directory blocks in a linked list.
|
|
* device is the device number containing the directory
|
|
* blocknum is the block number of the first block of the directory
|
|
*/
|
|
int readdir(uint device, uint blocknum) {
|
|
static char namebuf[NMLEN+1];
|
|
struct pd_dirhdr *hdr;
|
|
struct block *curblk;
|
|
struct datetime dt;
|
|
ulong eof;
|
|
uint filecount, idx, subdirs, blks, keyblk, hdrblk, entries, auxtype;
|
|
#ifdef CHECK
|
|
uint count;
|
|
#endif
|
|
uchar blkentries, i;
|
|
uint errsbefore = errcount;
|
|
uint blkcnt = 1;
|
|
uint hdrblknum = blocknum;
|
|
|
|
numfiles = 0;
|
|
|
|
blocks = (struct block*)malloc(sizeof(struct block));
|
|
if (!blocks)
|
|
err(FATALALLOC, err_nomem);
|
|
curblk = blocks;
|
|
curblk->next = NULL;
|
|
curblk->blocknum = blocknum;
|
|
|
|
#ifdef AUXMEM
|
|
curblk->data = auxalloc(BLKSZ);
|
|
#endif
|
|
|
|
#ifdef FREELIST
|
|
checkblock(blocknum, "Directory");
|
|
#endif
|
|
if (readdiskblock(device, blocknum, dirblkbuf) == -1) {
|
|
err(NONFATAL, err_rdblk1, blocknum);
|
|
goto done;
|
|
}
|
|
|
|
hdr = (struct pd_dirhdr*)(dirblkbuf + PTRSZ);
|
|
|
|
entsz = hdr->entlen;
|
|
entperblk = hdr->entperblk;
|
|
filecount = hdr->filecnt[0] + 256U * hdr->filecnt[1];
|
|
|
|
fixcase(hdr->name, currdir,
|
|
hdr->vers, hdr->minvers, hdr->typ_len & 0x0f);
|
|
|
|
hlinechar('=');
|
|
printf("Directory %s (%u", currdir, filecount);
|
|
printf(" %s)\n", filecount == 1 ? "entry" : "entries");
|
|
hline();
|
|
fputs(" Name Blk EOF Typ Aux Perm Modified Created OK", stdout);
|
|
|
|
#ifdef CHECK
|
|
if (entsz != ENTSZ) {
|
|
err(NONFATAL, err_entsz2, entsz, ENTSZ);
|
|
goto done;
|
|
}
|
|
if (entperblk != ENTPERBLK) {
|
|
err(NONFATAL, err_entblk2, entperblk, ENTPERBLK);
|
|
goto done;
|
|
}
|
|
#endif
|
|
idx = entsz + PTRSZ; /* Skip header */
|
|
blkentries = 2;
|
|
entries = 0;
|
|
subdirs = 0;
|
|
|
|
while (1) {
|
|
uint errsbeforeent = errcount;
|
|
struct pd_dirent *ent = (struct pd_dirent*)(dirblkbuf + idx);
|
|
|
|
if (ent->typ_len != 0) {
|
|
|
|
if (strlen(caseopts) > 0) {
|
|
switch (caseopts[0]) {
|
|
case 'u':
|
|
uppercase(ent->name,
|
|
ent->typ_len & 0x0f,
|
|
&(ent->vers),
|
|
&(ent->minvers));
|
|
break;
|
|
case 'l':
|
|
lowercase(ent->name,
|
|
ent->typ_len & 0x0f,
|
|
&(ent->vers),
|
|
&(ent->minvers));
|
|
break;
|
|
case 'i':
|
|
initialcase(0,
|
|
ent->name,
|
|
ent->typ_len & 0x0f,
|
|
&(ent->vers),
|
|
&(ent->minvers));
|
|
break;
|
|
case 'c':
|
|
initialcase(1,
|
|
ent->name,
|
|
ent->typ_len & 0x0f,
|
|
&(ent->vers),
|
|
&(ent->minvers));
|
|
break;
|
|
default:
|
|
err(FATALBADARG, err_invopt, "case");
|
|
}
|
|
}
|
|
|
|
if (strlen(dateopts) > 0) {
|
|
struct datetime ctime, mtime;
|
|
readdatetime(ent->ctime, &ctime);
|
|
readdatetime(ent->mtime, &mtime);
|
|
switch (dateopts[0]) {
|
|
case 'n':
|
|
ctime.ispd25format = 1;
|
|
mtime.ispd25format = 1;
|
|
break;
|
|
case 'o':
|
|
ctime.ispd25format = 0;
|
|
mtime.ispd25format = 0;
|
|
break;
|
|
default:
|
|
err(FATALBADARG, err_invopt, "date");
|
|
}
|
|
writedatetime(&ctime, ent->ctime);
|
|
writedatetime(&mtime, ent->mtime);
|
|
}
|
|
|
|
fixcase(ent->name, namebuf,
|
|
ent->vers, ent->minvers, ent->typ_len & 0x0f);
|
|
|
|
switch (ent->typ_len & 0xf0) {
|
|
case 0x10:
|
|
fputs("s ", stdout);
|
|
break;
|
|
case 0x20:
|
|
fputs("S ", stdout);
|
|
break;
|
|
case 0x30:
|
|
fputs("T ", stdout);
|
|
break;
|
|
case 0x40:
|
|
fputs("P ", stdout);
|
|
break;
|
|
case 0x50:
|
|
fputs("F ", stdout);
|
|
break;
|
|
case 0xd0:
|
|
fputs("D ", stdout);
|
|
break;
|
|
default:
|
|
fputs("? ", stdout);
|
|
break;
|
|
}
|
|
fputs(namebuf, stdout);
|
|
for (i = 0; i < 16 - strlen(namebuf); ++i)
|
|
putchar(' ');
|
|
|
|
blks = ent->blksused[0] + 256U * ent->blksused[1];
|
|
eof = ent->eof[0] + 256L * ent->eof[1] + 65536L * ent->eof[2];
|
|
auxtype = ent->auxtype[0] + 256L * ent->auxtype[1];
|
|
printf("%4d %8ld %02x %04x %c%c%c%c%c%c",
|
|
blks, eof, ent->type,auxtype,
|
|
(ent->access & 0x80) ? 'D' : '-',
|
|
(ent->access & 0x40) ? 'R' : '-',
|
|
(ent->access & 0x20) ? 'B' : '-',
|
|
(ent->access & 0x04) ? 'I' : '-',
|
|
(ent->access & 0x02) ? 'w' : '-',
|
|
(ent->access & 0x01) ? 'r' : '-');
|
|
|
|
readdatetime(ent->ctime, &dt);
|
|
putchar(' ');
|
|
printdatetime(&dt);
|
|
readdatetime(ent->mtime, &dt);
|
|
putchar(' ');
|
|
printdatetime(&dt);
|
|
putchar(' ');
|
|
|
|
keyblk = ent->keyptr[0] + 256U * ent->keyptr[1];
|
|
hdrblk = ent->hdrptr[0] + 256U * ent->hdrptr[1];
|
|
#ifdef CHECK
|
|
if (ent->access & 0x18) {
|
|
err(NONFATAL, err_access);
|
|
if (askfix() == 1)
|
|
ent->access &= 0xe7;
|
|
}
|
|
if (hdrblk != hdrblknum) {
|
|
err(NONFATAL, err_hdrblk2, hdrblk, hdrblknum);
|
|
if (askfix() == 1) {
|
|
ent->hdrptr[0] = hdrblknum & 0xff;
|
|
ent->hdrptr[1] = (hdrblknum >> 8)&0xff;
|
|
}
|
|
}
|
|
#endif
|
|
switch (ent->typ_len & 0xf0) {
|
|
case 0xd0:
|
|
/* Subdirectory */
|
|
enqueuesubdir(keyblk, subdirs++);
|
|
#ifdef CHECK
|
|
subdirblocks(device, keyblk, ent,
|
|
blocknum, blkentries, &count);
|
|
#endif
|
|
break;
|
|
#ifdef CHECK
|
|
case 0x10:
|
|
/* Seedling */
|
|
seedlingblocks(device, keyblk, &count);
|
|
break;
|
|
case 0x20:
|
|
/* Sapling */
|
|
saplingblocks(device, keyblk, &count);
|
|
break;
|
|
case 0x30:
|
|
/* Tree */
|
|
treeblocks(device, keyblk, &count);
|
|
break;
|
|
case 0x40:
|
|
/* Pascal area */
|
|
puts(" Pascal area!!");
|
|
// TODO: Check name is PASCAL.AREA type 0xef
|
|
count = 0;
|
|
break;
|
|
case 0x50:
|
|
/* File with resource fork */
|
|
forkblocks(device, keyblk, &count);
|
|
break;
|
|
default:
|
|
err(NONFATAL, err_stype2, ent->typ_len & 0xf0, "entry");
|
|
count = 0;
|
|
#endif
|
|
}
|
|
#ifdef CHECK
|
|
if (blks != count) {
|
|
if (count != 0) {
|
|
err(NONFATAL, err_used2, blks, count);
|
|
if (askfix() == 1) {
|
|
ent->blksused[0] = count & 0xff;
|
|
ent->blksused[1] = (count >> 8) & 0xff;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
++numfiles;
|
|
if (errcount == errsbeforeent) {
|
|
#ifdef CHECK
|
|
puts(" *");
|
|
#else
|
|
putchar('\n');
|
|
#endif
|
|
} else
|
|
putchar('\n');
|
|
++entries;
|
|
}
|
|
if (blkentries == entperblk) {
|
|
blocknum = dirblkbuf[0x02] + 256U * dirblkbuf[0x03];
|
|
#ifdef AUXMEM
|
|
copyaux(dirblkbuf, curblk->data, BLKSZ, TOAUX);
|
|
#else
|
|
memcpy(curblk->data, dirblkbuf, BLKSZ);
|
|
#endif
|
|
if (blocknum == 0) {
|
|
break;
|
|
}
|
|
curblk->next = (struct block*)malloc(sizeof(struct block));
|
|
if (!curblk->next)
|
|
err(FATALALLOC, err_nomem);
|
|
curblk = curblk->next;
|
|
curblk->next = NULL;
|
|
curblk->blocknum = blocknum;
|
|
++blkcnt;
|
|
|
|
#ifdef AUXMEM
|
|
curblk->data = auxalloc(BLKSZ);
|
|
#endif
|
|
|
|
#ifdef FREELIST
|
|
checkblock(blocknum, "Directory");
|
|
#endif
|
|
if (readdiskblock(device, blocknum, dirblkbuf) == -1) {
|
|
err(NONFATAL, err_rdblk1, blocknum);
|
|
goto done;
|
|
}
|
|
|
|
blkentries = 1;
|
|
idx = PTRSZ;
|
|
} else {
|
|
++blkentries;
|
|
idx += entsz;
|
|
}
|
|
}
|
|
if (filecount != entries) {
|
|
err(NONFATAL, err_count2, filecount, entries);
|
|
if (askfix() == 1) {
|
|
hdr->filecnt[0] = entries & 0xff;
|
|
hdr->filecnt[1] = (entries >> 8) & 0xff;
|
|
}
|
|
}
|
|
#ifdef AUXMEM
|
|
copyaux(dirblkbuf, curblk->data, BLKSZ, TOAUX);
|
|
#else
|
|
memcpy(curblk->data, dirblkbuf, BLKSZ);
|
|
#endif
|
|
|
|
done:
|
|
return errcount - errsbefore;
|
|
}
|
|
|
|
#ifdef SORT
|
|
|
|
/*
|
|
* Build filelist[], the table used by the sorting algorithm.
|
|
* s - character representing the sorting mode
|
|
* callidx - if 0, the routine populates the table, otherwise it updates
|
|
* and existing table
|
|
* Returns 1 on error, 0 if OK.
|
|
*/
|
|
uchar buildsorttable(char s, uchar callidx) {
|
|
static char namebuf[NMLEN+1];
|
|
uint off;
|
|
uchar entry, i;
|
|
struct datetime dt;
|
|
struct pd_dirent *ent;
|
|
uint idx = 0;
|
|
struct block *b = blocks;
|
|
uchar firstent = 2; /* Skip first entry of first block */
|
|
uchar blkidx = 1;
|
|
|
|
while (b) {
|
|
#ifdef AUXMEM
|
|
copyaux(b->data, dirblkbuf, BLKSZ, FROMAUX);
|
|
#else
|
|
memcpy(dirblkbuf, b->data, BLKSZ);
|
|
#endif
|
|
for (entry = firstent; entry <= ENTPERBLK; ++entry) {
|
|
|
|
off = PTRSZ + (entry - 1) * entsz;
|
|
ent = (struct pd_dirent*)(dirblkbuf + off);
|
|
|
|
if (ent->typ_len != 0) {
|
|
|
|
if (callidx == 0) {
|
|
/* Build filelist[] on first call for each dir */
|
|
filelist[idx].blockidx = blkidx;
|
|
filelist[idx].entrynum = entry;
|
|
} else {
|
|
/* On subsequent calls Find existing entry in list and update it */
|
|
for (idx = 0; idx < numfiles; ++idx)
|
|
if ((filelist[idx].blockidx == blkidx) &&
|
|
(filelist[idx].entrynum == entry))
|
|
break;
|
|
}
|
|
switch (tolower(s)) {
|
|
case 'n':
|
|
case 'i':
|
|
fixcase(ent->name, namebuf,
|
|
ent->vers, ent->minvers, ent->typ_len & 0x0f);
|
|
bzero(filelist[idx].name, NMLEN);
|
|
for (i = 0; i < (ent->typ_len & 0x0f); ++i)
|
|
filelist[idx].name[i] = namebuf[i];
|
|
break;
|
|
case 'd':
|
|
case 't':
|
|
filelist[idx].type = ent->type;
|
|
break;
|
|
case 'b':
|
|
filelist[idx].blocks =
|
|
ent->blksused[0] + 256U * ent->blksused[1];
|
|
break;
|
|
case 'e':
|
|
filelist[idx].eof =
|
|
ent->eof[0] + 256L * ent->eof[1] + 65536L * ent->eof[2];
|
|
break;
|
|
case 'c':
|
|
readdatetime(ent->ctime, &dt);
|
|
case 'm':
|
|
readdatetime(ent->mtime, &dt);
|
|
sprintf(filelist[idx].datetime, "%04d%02d%02d%02d%02d",
|
|
dt.year, dt.month, dt.day, dt.hour, dt.minute);
|
|
break;
|
|
}
|
|
if (++idx == maxfiles) {
|
|
err(NONFATAL, err_many);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
b = b->next;
|
|
++blkidx;
|
|
firstent = 1;
|
|
}
|
|
if (callidx == 0)
|
|
numfiles = idx;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Compare - filename sort in ascending order
|
|
*/
|
|
int cmp_name_asc(const void *a, const void *b) {
|
|
return strncmp(((struct fileent*)a)->name,
|
|
((struct fileent*)b)->name, NMLEN);
|
|
}
|
|
|
|
/*
|
|
* Compare - filename sort in descending order
|
|
*/
|
|
int cmp_name_desc(const void *a, const void *b) {
|
|
return strncmp(((struct fileent*)b)->name,
|
|
((struct fileent*)a)->name, NMLEN);
|
|
}
|
|
|
|
/*
|
|
* Compare - filename sort in ascending order - case insensitive
|
|
*/
|
|
int cmp_name_asc_ci(const void *a, const void *b) {
|
|
return strncasecmp(((struct fileent*)a)->name,
|
|
((struct fileent*)b)->name, NMLEN);
|
|
}
|
|
|
|
/*
|
|
* Compare - filename sort in descending order - case insensitive
|
|
*/
|
|
int cmp_name_desc_ci(const void *a, const void *b) {
|
|
return strncasecmp(((struct fileent*)b)->name,
|
|
((struct fileent*)a)->name, NMLEN);
|
|
}
|
|
|
|
/*
|
|
* Compare - date/time sort in ascending order
|
|
*/
|
|
int cmp_datetime_asc(const void *a, const void *b) {
|
|
return strncmp(((struct fileent*)a)->datetime,
|
|
((struct fileent*)b)->datetime, 16);
|
|
}
|
|
|
|
/*
|
|
* Compare - date/time sort in descending order
|
|
*/
|
|
int cmp_datetime_desc(const void *a, const void *b) {
|
|
return strncmp(((struct fileent*)b)->datetime,
|
|
((struct fileent*)a)->datetime, 16);
|
|
}
|
|
|
|
/*
|
|
* Compare - type sort in ascending order
|
|
* Uses the order field to make qsort() stable
|
|
*/
|
|
int cmp_type_asc(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
int rc = aa->type - bb->type;
|
|
return rc != 0 ? rc : aa->order - bb->order;
|
|
}
|
|
/*
|
|
* Compare - type sort in descending order
|
|
* Uses the order field to make qsort() stable
|
|
*/
|
|
int cmp_type_desc(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
int rc = bb->type - aa->type;
|
|
return rc != 0 ? rc : aa->order - bb->order;
|
|
}
|
|
|
|
/*
|
|
* Compare - sort with directories at the beginning
|
|
* Uses the order field to make qsort() stable
|
|
*/
|
|
int cmp_dir_beg(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
if ((aa->type == 0x0f) && (bb->type != 0x0f))
|
|
return -1;
|
|
if ((bb->type == 0x0f) && (aa->type != 0x0f))
|
|
return 1;
|
|
return aa->order - bb->order;
|
|
}
|
|
|
|
/*
|
|
* Compare - sort with directories at the end
|
|
* Uses the order field to make qsort() stable
|
|
*/
|
|
int cmp_dir_end(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
if ((aa->type == 0x0f) && (bb->type != 0x0f))
|
|
return 1;
|
|
if ((bb->type == 0x0f) && (aa->type != 0x0f))
|
|
return -1;
|
|
return aa->order - bb->order;
|
|
}
|
|
|
|
/*
|
|
* Compare - sort in increasing order of blocks used
|
|
*/
|
|
int cmp_blocks_asc(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
int rc = aa->blocks - bb->blocks;
|
|
return rc != 0 ? rc : aa->order - bb->order;
|
|
}
|
|
|
|
/*
|
|
* Compare - sort in decreasing order of blocks used
|
|
*/
|
|
int cmp_blocks_desc(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
int rc = bb->blocks - aa->blocks;
|
|
return rc != 0 ? rc : aa->order - bb->order;
|
|
}
|
|
|
|
/*
|
|
* Compare - sort in increasing order of EOF position
|
|
*/
|
|
int cmp_eof_asc(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
long diff = aa->eof - bb->eof;
|
|
if (diff == 0)
|
|
return aa->order - bb->order;
|
|
if (diff > 0)
|
|
return 1;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Compare - sort in decreasing order of EOF position
|
|
*/
|
|
int cmp_eof_desc(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
long diff = bb->eof - aa->eof;
|
|
if (diff == 0)
|
|
return aa->order - bb->order;
|
|
if (diff > 0)
|
|
return 1;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Compare - no-op compare which leaves order unchanged
|
|
*/
|
|
int cmp_noop(const void *a, const void *b) {
|
|
struct fileent *aa = (struct fileent*)a;
|
|
struct fileent *bb = (struct fileent*)b;
|
|
return aa->order - bb->order;
|
|
}
|
|
|
|
/*
|
|
* Sort filelist[]
|
|
* s defines the field to sort on
|
|
*/
|
|
void sortlist(char s) {
|
|
uint i;
|
|
char ss = tolower(s);
|
|
|
|
/*
|
|
* We only populate the order field when NOT sorting by name.
|
|
* This lets us save two bytes by overflowing the name field into the
|
|
* order field.
|
|
*/
|
|
if ((ss != 'n') && (ss != 'i')) {
|
|
for (i = 0; i < numfiles; ++i) {
|
|
filelist[i].order = i;
|
|
}
|
|
}
|
|
|
|
switch (s) {
|
|
case 'n':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_name_asc);
|
|
break;
|
|
case 'N':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_name_desc);
|
|
break;
|
|
case 'i':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_name_asc_ci);
|
|
break;
|
|
case 'I':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_name_desc_ci);
|
|
break;
|
|
case 'c':
|
|
case 'm':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_datetime_asc);
|
|
break;
|
|
case 'C':
|
|
case 'M':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_datetime_desc);
|
|
break;
|
|
case 't':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_type_asc);
|
|
break;
|
|
case 'T':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_type_desc);
|
|
break;
|
|
case 'd':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_dir_beg);
|
|
break;
|
|
case 'D':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_dir_end);
|
|
break;
|
|
case 'b':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_blocks_asc);
|
|
break;
|
|
case 'B':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_blocks_desc);
|
|
break;
|
|
case 'e':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_eof_asc);
|
|
break;
|
|
case 'E':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_eof_desc);
|
|
break;
|
|
case '.':
|
|
qsort(filelist, numfiles, sizeof(struct fileent), cmp_noop);
|
|
break;
|
|
default:
|
|
err(FATALBADARG, err_invopt, "sort");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Convert block index to block number
|
|
* Block index is 1-based (1,2,3 ...)
|
|
*/
|
|
uint blockidxtoblocknum(uint idx) {
|
|
uint i;
|
|
struct block *p = blocks;
|
|
for (i = 1; i < idx; ++i)
|
|
p = p->next;
|
|
return p->blocknum;
|
|
}
|
|
|
|
/*
|
|
* Copy the 4 bytes of pointers from the directory block with index idx
|
|
* to the start of dirblkbuf[]; zeroes the rest of dirblkbuf[].
|
|
*/
|
|
void copydirblkptrs(uint idx) {
|
|
uint i;
|
|
struct block *p = blocks;
|
|
for (i = 1; i < idx; ++i)
|
|
p = p->next;
|
|
bzero(dirblkbuf, BLKSZ);
|
|
#ifdef AUXMEM
|
|
copyaux(p->data, dirblkbuf, PTRSZ, FROMAUX);
|
|
#else
|
|
memcpy(dirblkbuf, p->data, PTRSZ);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Copy a file entry from one srcblk, srcent to dstblk, dstent
|
|
* All indices are 1-based.
|
|
* dstblk is written to dirblkbuf[]
|
|
*/
|
|
void copydirent(uint srcblk, uint srcent, uint dstblk, uint dstent, uint device) {
|
|
struct block *source = blocks;
|
|
struct pd_dirent *ent;
|
|
struct pd_dirhdr *hdr;
|
|
char *srcptr, *dstptr;
|
|
uint parentblk;
|
|
|
|
if (dodebug) {
|
|
printf(" from dirblk %03u entry %02u", srcblk, srcent);
|
|
printf(" to dirblk %03u entry %02u\n", dstblk, dstent);
|
|
}
|
|
|
|
while (--srcblk > 0)
|
|
source = source->next;
|
|
|
|
srcptr = source->data + PTRSZ + (srcent-1) * entsz;
|
|
dstptr = dirblkbuf + PTRSZ + (dstent-1) * entsz;
|
|
|
|
#ifdef AUXMEM
|
|
copyaux(srcptr, dstptr, entsz, FROMAUX);
|
|
#else
|
|
memcpy(dstptr, srcptr, entsz);
|
|
#endif
|
|
|
|
/* For directories, update the parent dir entry number */
|
|
ent = (struct pd_dirent*)dstptr;
|
|
if ((ent->typ_len & 0xf0) == 0xd0) {
|
|
uint block = ent->keyptr[0] + 256U * ent->keyptr[1];
|
|
if (readdiskblock(device, block, buf) == -1)
|
|
err(NONFATAL, err_updsdir1, "read");
|
|
hdr = (struct pd_dirhdr*)(buf + PTRSZ);
|
|
parentblk = blockidxtoblocknum(dstblk);
|
|
hdr->parptr[0] = parentblk & 0xff;
|
|
hdr->parptr[1] = (parentblk >> 8) & 0xff;
|
|
hdr->parentry = dstent;
|
|
if (dowrite) {
|
|
if (writediskblock(device, block, buf) == -1)
|
|
err(NONFATAL, err_updsdir1, "write");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Build sorted directory block dstblk (1,2,3...) using the sorted list in
|
|
* filelist[]. Note that the block and entry numbers are 1-based indices.
|
|
* Returns 1 if last block of directory, 0 otherwise.
|
|
*/
|
|
uchar sortblock(uint device, uint dstblk) {
|
|
uint i, firstlistent, lastlistent;
|
|
uchar destentry, rc = 0;
|
|
copydirblkptrs(dstblk);
|
|
if (dstblk == 1) {
|
|
copydirent(1, 1, 1, 1, device); /* Copy directory header */
|
|
destentry = 2; /* Skip header on first block */
|
|
firstlistent = 0;
|
|
lastlistent = entperblk - 2;
|
|
} else {
|
|
destentry = 1;
|
|
firstlistent = (dstblk - 1) * entperblk - 1;
|
|
lastlistent = firstlistent + entperblk - 1;
|
|
}
|
|
|
|
if (lastlistent > numfiles - 1) {
|
|
|
|
lastlistent = numfiles - 1;
|
|
|
|
#ifdef TRIMDIR
|
|
// TODO: Fix EOF for directory
|
|
#ifdef AUXMEM
|
|
dirblkbuf[2] = dirblkbuf[3] = 0; /* Set next ptr to NULL */
|
|
#else
|
|
p->data[2] = p->data[3] = 0; /* Set next ptr to NULL */
|
|
#endif
|
|
rc = 1;
|
|
#else
|
|
rc = 0;
|
|
#endif
|
|
}
|
|
|
|
for (i = firstlistent; i <= lastlistent; ++i) {
|
|
copydirent(filelist[i].blockidx, filelist[i].entrynum,
|
|
dstblk, destentry++, device);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Build each sorted directory block in turn, then write them
|
|
* out to disk.
|
|
*/
|
|
uchar writedir(uchar device) {
|
|
uint dstblk = 1;
|
|
uchar finished = 0;
|
|
struct block *b = blocks;
|
|
while (b) {
|
|
if (!finished) {
|
|
finished = sortblock(device, dstblk++);
|
|
if (writediskblock(device, b->blocknum, dirblkbuf) == -1) {
|
|
err(NONFATAL, err_wtblk1, b->blocknum);
|
|
return 1;
|
|
}
|
|
#ifdef FREELIST
|
|
} else {
|
|
/* Standard volume directory is blocks 2-5 (4 blocks)
|
|
* We will not trim volume directory to less than 4 blocks
|
|
*/
|
|
if (b->blocknum > 5) {
|
|
puts("Trimming dir blk");
|
|
trimdirblock(b->blocknum);
|
|
}
|
|
}
|
|
#else
|
|
}
|
|
#endif
|
|
b = b->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef FREELIST
|
|
|
|
/*
|
|
* Write the freelist back to disk.
|
|
*/
|
|
uchar writefreelist(uchar device) {
|
|
uchar b;
|
|
puts("Writing freelist ...");
|
|
for (b = 0; b < flsize; ++b) {
|
|
#ifdef AUXMEM
|
|
copyaux(freelist + b * BLKSZ, dirblkbuf, BLKSZ, FROMAUX);
|
|
#else
|
|
memcpy(dirblkbuf, freelist + b * BLKSZ, BLKSZ);
|
|
#endif
|
|
if (writediskblock(device, flblk, dirblkbuf) == -1) {
|
|
err(NONFATAL, err_wtblk1, flblk);
|
|
return 1;
|
|
}
|
|
++flblk;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Walk through the linked list freeing memory
|
|
*/
|
|
void freeblocks(void) {
|
|
struct block *i = blocks, *j;
|
|
while (i) {
|
|
j = i->next;
|
|
free(i);
|
|
i = j;
|
|
}
|
|
blocks = NULL;
|
|
}
|
|
|
|
void subtitle(char *s) {
|
|
uchar i;
|
|
putchar('\n');
|
|
hlinechar('_');
|
|
revers(1);
|
|
fputs(s, stdout);
|
|
revers(0);
|
|
for (i = strlen(s); i < 79; ++i)
|
|
putchar(' ');
|
|
putchar('|');
|
|
}
|
|
|
|
void interactive(void) {
|
|
char w, l, d, f, wrt;
|
|
#ifdef FREELIST
|
|
char z;
|
|
#endif
|
|
uchar level;
|
|
|
|
doverbose = 1;
|
|
|
|
revers(1);
|
|
hlinechar(' ');
|
|
fputs("S O R T D I R v0.92 alpha Use ^ to return to previous question", stdout);
|
|
hlinechar(' ');
|
|
revers(0);
|
|
|
|
q1:
|
|
putchar('\n');
|
|
revers(1);
|
|
fputs("Enter start path>", stdout);
|
|
revers(0);
|
|
putchar(' ');
|
|
scanf("%s", buf);
|
|
getchar(); // Eat the carriage return
|
|
|
|
q2:
|
|
dowholedisk = dorecurse = 0;
|
|
subtitle("What to process");
|
|
do {
|
|
fputs("| [-] Directory | [t] Tree | [v] Volume | |", stdout);
|
|
w = getchar();
|
|
} while (strchr("-tv^", w) == NULL);
|
|
if (w == '^')
|
|
goto q1;
|
|
switch (w) {
|
|
case 't':
|
|
dorecurse = 1;
|
|
break;
|
|
case 'v':
|
|
dorecurse = 1;
|
|
dowholedisk = 1;
|
|
}
|
|
|
|
q3:
|
|
subtitle("Multi-level directory sort");
|
|
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
|
fputs("| Lower case option ascending order, upper case option descending order |", stdout);
|
|
fputs("| [nN] Name | [iI] Name (case-insens) | [tT] Type | [dD] Directories |", stdout);
|
|
fputs("| [cC] Creation | [mM] Modification | [bB] Blocks | [eE] EOF Position |", stdout);
|
|
fputs("| [-] Done | [.] Just compact dir | | |", stdout);
|
|
for (level = 0; level < NLEVELS; ++level) {
|
|
do {
|
|
printf("\nLevel %d > ", level+1);
|
|
sortopts[level] = getchar();
|
|
} while (strchr("-.nNiItTdDcCmMbBeE^", sortopts[level]) == NULL);
|
|
if (sortopts[level] == '-') {
|
|
sortopts[level] = '\0';
|
|
break;
|
|
}
|
|
if (sortopts[level] == '^')
|
|
goto q2;
|
|
}
|
|
sortopts[NLEVELS] = '\0';
|
|
|
|
q4:
|
|
subtitle("Filename case conversion");
|
|
do {
|
|
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
|
fputs("| [-] No change | | | |", stdout);
|
|
fputs("| [l] Lowercase | [u] Uppercase | [i] Initial | [c] Camelcase |", stdout);
|
|
l = getchar();
|
|
} while (strchr("-luic^", l) == NULL);
|
|
if (l == '^')
|
|
goto q3;
|
|
if (l == '-')
|
|
caseopts[0] = '\0';
|
|
else
|
|
caseopts[0] = l;
|
|
|
|
q5:
|
|
subtitle("Date format conversion");
|
|
do {
|
|
fputs("| [-] No change | [n] 'New' ProDOS 2.5+ | [o] 'Old' legacy format |",stdout);
|
|
d = getchar();
|
|
} while (strchr("-no^", d) == NULL);
|
|
if (d == '^')
|
|
goto q4;
|
|
if (d == '-')
|
|
dateopts[0] = '\0';
|
|
else
|
|
dateopts[0] = d;
|
|
|
|
q6:
|
|
subtitle("Attempt to fix errors?");
|
|
do {
|
|
fputs("| [-] Always ask | [y] Always fix | [n] Never fix |", stderr);
|
|
f = getchar();
|
|
} while (strchr("-yn^", f) == NULL);
|
|
if (f == '^')
|
|
goto q5;
|
|
fixopts[0] = f;
|
|
|
|
#ifdef FREELIST
|
|
if (w == 'v') {
|
|
subtitle("Zero free space?");
|
|
do {
|
|
fputs("| [-] No | [z] Zero free blocks | |", stderr);
|
|
z = getchar();
|
|
} while (strchr("-z^", z) == NULL);
|
|
if (z == '^')
|
|
goto q6;
|
|
if (z == 'z')
|
|
dozero = 1;
|
|
}
|
|
#endif
|
|
|
|
subtitle("Confirm write to disk");
|
|
do {
|
|
fputs("| [-] No | [w] Write to disk | |", stderr);
|
|
wrt = getchar();
|
|
} while (strchr("-w^", wrt) == NULL);
|
|
if (wrt == '^')
|
|
goto q6;
|
|
if (wrt == 'w')
|
|
dowrite = 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Performs all actions for a single directory
|
|
* blocknum is the keyblock of the directory to process
|
|
*/
|
|
void processdir(uint device, uint blocknum) {
|
|
uchar i, errs;
|
|
flushall();
|
|
if (readdir(device, blocknum) != 0) {
|
|
err(NONFATAL, err_nosort);
|
|
putchar('\n');
|
|
goto done;
|
|
}
|
|
#ifdef SORT
|
|
if (strlen(sortopts) > 0) {
|
|
if (doverbose)
|
|
fputs("Sorting: ", stdout);
|
|
for (i = 0; i < strlen(sortopts); ++i) {
|
|
if (doverbose)
|
|
printf("[%c] ", sortopts[i]);
|
|
if (buildsorttable(sortopts[i], i) != 0) {
|
|
err(NONFATAL, err_nosort);
|
|
putchar('\n');
|
|
goto done;
|
|
}
|
|
sortlist(sortopts[i]);
|
|
}
|
|
if (doverbose)
|
|
putchar('\n');
|
|
if (dowrite) {
|
|
puts("Writing dir ...");
|
|
errs = writedir(device);
|
|
} else {
|
|
revers(1);
|
|
fputs("Not writing dir", stdout);
|
|
revers(0);
|
|
putchar('\n');
|
|
}
|
|
}
|
|
#endif
|
|
done:
|
|
freeblocks();
|
|
#ifdef AUXMEM
|
|
freeallaux();
|
|
#endif
|
|
}
|
|
|
|
#ifdef FREELIST
|
|
|
|
/*
|
|
* Iterate through freelist[] and usedlist[] and see if all is well.
|
|
* If we have visited all files and directories on the volume, every
|
|
* block should either be marked free or marked used.
|
|
*/
|
|
void checkfreeandused(uchar device) {
|
|
uchar fl, ul, bit;
|
|
uint byte, blk = 0, blkcnt = 0;
|
|
printf("Total blks %u", totblks);
|
|
for (byte = 0; byte < flsize * BLKSZ; ++byte) {
|
|
#ifdef AUXMEM
|
|
copyaux(freelist + byte, &fl, 1, FROMAUX);
|
|
copyaux(usedlist + byte, &ul, 1, FROMAUX);
|
|
#else
|
|
fl = freelist[byte];
|
|
ul = usedlist[byte];
|
|
#endif
|
|
for (bit = 0; bit < 8; ++bit) {
|
|
if (blk >= totblks)
|
|
break;
|
|
if ((fl << bit) & 0x80) {
|
|
/* Free */
|
|
if ((ul << bit) & 0x80) {
|
|
/* ... and used */
|
|
err(NONFATAL, err_blfree1, blk);
|
|
if (askfix() == 1) {
|
|
++blkcnt;
|
|
fl &= ~(0x80 >> bit);
|
|
#ifdef AUXMEM
|
|
copyaux(&fl, freelist + byte, 1, TOAUX);
|
|
#else
|
|
freelist[byte] = fl;
|
|
#endif
|
|
flchanged = 1;
|
|
}
|
|
}
|
|
} else {
|
|
/* Not free */
|
|
++blkcnt;
|
|
if (!((ul << bit) & 0x80)) {
|
|
/* ... and not used */
|
|
err(NONFATAL, err_blused1, blk);
|
|
if (askfix() == 1) {
|
|
--blkcnt;
|
|
fl |= (0x80 >> bit);
|
|
#ifdef AUXMEM
|
|
copyaux(&fl, freelist + byte, 1, TOAUX);
|
|
#else
|
|
freelist[byte] = fl;
|
|
#endif
|
|
flchanged = 1;
|
|
}
|
|
}
|
|
}
|
|
++blk;
|
|
}
|
|
}
|
|
printf("\nFree blks %u\n", totblks - blkcnt);
|
|
|
|
if (dozero)
|
|
zerofreeblocks(device, totblks - blkcnt);
|
|
}
|
|
|
|
/*
|
|
* Zero block blocknum
|
|
*/
|
|
void zeroblock(uchar device, uint blocknum) {
|
|
bzero(buf, BLKSZ);
|
|
if (writediskblock(device, blocknum, buf) == -1)
|
|
err(FATAL, err_wtblk1, blocknum);
|
|
// DIORecGS dr;
|
|
// dr.pCount = 6;
|
|
// dr.devNum = device;
|
|
// dr.buffer = buf;
|
|
// dr.requestCount = BLKSZ;
|
|
// dr.startingBlock = blocknum;
|
|
// dr.blockSize = BLKSZ;
|
|
// DWriteGS(&dr);
|
|
// if (dr.transferCount != BLKSZ)
|
|
// err(FATAL, "Block write failed");
|
|
}
|
|
|
|
/*
|
|
* Zero all free blocks on the volume
|
|
*/
|
|
void zerofreeblocks(uchar device, uint freeblks) {
|
|
uint i, step = freeblks / 60, ctr = 0;
|
|
puts("Zeroing free blocks ...");
|
|
for (i = 0; i < totblks; ++i)
|
|
if (isfree(i)) {
|
|
zeroblock(device, i);
|
|
++ctr;
|
|
if (ctr == step) {
|
|
putchar('=');
|
|
fflush(stdout);
|
|
ctr = 0;
|
|
}
|
|
}
|
|
puts("\nDone zeroing!");
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CMDLINE
|
|
|
|
void usage(void) {
|
|
printf("usage: sortdir [-s xxx] [-n x] [-rDwvVh] path\n\n");
|
|
printf(" Options: -s xxx Directory sort options\n");
|
|
printf(" -n x Filename upper/lower case options\n");
|
|
printf(" -d x Date format conversion options\n");
|
|
printf(" -f x Fix mode\n");
|
|
printf(" -r Recursive descent\n");
|
|
printf(" -D Whole-disk mode (implies -r)\n");
|
|
printf(" -w Enable writing to disk\n");
|
|
printf(" -z Zero free space\n");
|
|
printf(" -v Verbose output\n");
|
|
printf(" -V Verbose debugging output\n");
|
|
printf(" -h This help\n");
|
|
printf("\n");
|
|
printf("-nx: Upper/lower case filenames, where x is:\n");
|
|
printf(" l convert filenames to lower case eg: read.me\n");
|
|
printf(" u convert filenames to upper case eg: READ.ME\n");
|
|
printf(" i convert filenames to initial upper case eg: Read.me\n");
|
|
printf(" c convert filenames to camel case eg: Read.Me\n");
|
|
printf("\n");
|
|
printf("-dx: Date/time on-disk format conversion, where x is:\n");
|
|
printf(" n convert to new (ProDOS 2.5+) format\n");
|
|
printf(" o convert to old (ProDOS 1.0-2.4.2, GSOS) format\n");
|
|
printf("\n");
|
|
printf("-sxxx: Dir sort, where xxx is a list of fields to sort\n");
|
|
printf("on. The sort options are processed left-to-right.\n");
|
|
printf(" n sort by filename ascending\n");
|
|
printf(" N sort by filename descending\n");
|
|
printf(" i sort by filename ascending - case insensitive\n");
|
|
printf(" I sort by filename descending - case insensitive\n");
|
|
printf(" c sort by create date/time ascending\n");
|
|
printf(" C sort by create date/time descending\n");
|
|
printf(" m sort by modify date/time ascending\n");
|
|
printf(" M sort by modify date/time descending\n");
|
|
printf(" t sort by type ascending\n");
|
|
printf(" T sort by type descending\n");
|
|
printf(" d sort directories to top\n");
|
|
printf(" D sort directories to bottom\n");
|
|
printf(" b sort by blocks used ascending\n");
|
|
printf(" B sort by blocks used descending\n");
|
|
printf(" e sort by EOF position ascending\n");
|
|
printf(" E sort by EOF position descending\n");
|
|
printf("\n");
|
|
printf("-fx: Fix mode, where x is:\n");
|
|
printf(" - prompt for each fix\n");
|
|
printf(" n never fix\n");
|
|
printf(" y always fix (be careful!)\n");
|
|
printf("\n");
|
|
printf("e.g.: sortdir -w -s nf .\n");
|
|
printf("Will sort the current directory first by name (ascending),\n");
|
|
printf("then sort directories to the top, and will write the\n");
|
|
printf("sorted directory to disk.\n");
|
|
err(FATAL, err_usage);
|
|
}
|
|
|
|
#define MAXNUMARGS 10
|
|
|
|
int argc;
|
|
char *argv[MAXNUMARGS];
|
|
|
|
void parseargs() {
|
|
char *p;
|
|
char i = 0, s = 0, prev = ' ';
|
|
argc = 1;
|
|
for (p = (char*)0x200; p <= (char*)0x27f; ++p) {
|
|
*p &= 0x7f;
|
|
if ((*p == 0) || (*p == 0x0d)) {
|
|
argv[argc - 1] = buf + s;
|
|
break;
|
|
}
|
|
if (*p == ' ') {
|
|
if (prev != ' ') {
|
|
buf[i++] = '\0';
|
|
argv[argc - 1] = buf + s;
|
|
s = i;
|
|
++argc;
|
|
}
|
|
} else {
|
|
buf[i++] = *p;
|
|
}
|
|
prev = *p;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Check if there are files on a RAM disk
|
|
* dev - Device number of RAM disk
|
|
*/
|
|
void check_ramdisk(uint8_t dev) {
|
|
dhandle_t dio_hdl;
|
|
uint8_t c;
|
|
dio_hdl = dio_open(dev);
|
|
dio_read(dio_hdl, 2, buf);
|
|
dio_close(dio_hdl);
|
|
c = buf[0x25] + 256 * buf[0x26]; // File count
|
|
if (c > 0) {
|
|
fputs("\n/",stdout);
|
|
for (c = 0; c < (buf[0x04] & 0x0f); ++c)
|
|
putchar(buf[0x05 + c]);
|
|
printf(" is not empty.\n");
|
|
printf("[Q] to quit, and preserve RAMDisk\n");
|
|
putchar(0x07); // BELL
|
|
c = cgetc();
|
|
if ((c == 'Q') || (c == 'q'))
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Disconnect RAM disks /RAM and/or /RAM3
|
|
* Note that both /RAM and /RAM3 may be active at the same time!!
|
|
*/
|
|
void disconnect_ramdisk(void) {
|
|
uint8_t i, j;
|
|
uint8_t *devcnt = (uint8_t*)0xbf31; // Number of devices
|
|
uint8_t *devlst = (uint8_t*)0xbf32; // Disk device numbers
|
|
uint16_t *s0d1 = (uint16_t*)0xbf10; // s0d1 driver vector
|
|
uint16_t *s3d1 = (uint16_t*)0xbf16; // s3d1 driver vector
|
|
uint16_t *s3d2 = (uint16_t*)0xbf26; // s3d2 driver vector
|
|
if (*s0d1 != *s3d2)
|
|
check_ramdisk(3 + (2 - 1) * 8); // s3d2
|
|
if (*s0d1 != *s3d1)
|
|
check_ramdisk(3 + (1 - 1) * 8); // s3d1
|
|
if (*s0d1 == *s3d2) {
|
|
s3d2dev = 0;
|
|
goto s3d1; // No /RAM
|
|
}
|
|
for (i = 0; i <= *devcnt; ++i)
|
|
if ((devlst[i] & 0xf0) == 0xb0) {
|
|
s3d2dev = devlst[i];
|
|
for (j = i; j <= *devcnt; ++j)
|
|
devlst[j] = devlst[j + 1];
|
|
break;
|
|
}
|
|
s3d2vec = *s3d2;
|
|
*s3d2 = *s0d1;
|
|
--(*devcnt);
|
|
s3d1:
|
|
if (*s0d1 == *s3d1) {
|
|
s3d1dev = 0;
|
|
return; // No /RAM3
|
|
}
|
|
for (i = 0; i <= *devcnt; ++i)
|
|
if ((devlst[i] & 0xf0) == 0x30) {
|
|
s3d1dev = devlst[i];
|
|
for (j = i; j <= *devcnt; ++j)
|
|
devlst[j] = devlst[j + 1];
|
|
break;
|
|
}
|
|
s3d1vec = *s3d1;
|
|
*s3d1 = *s0d1;
|
|
--(*devcnt);
|
|
}
|
|
|
|
//
|
|
// No point in reconnecting the RAMdisk(s) since we reboot on exit
|
|
//
|
|
|
|
/*
|
|
* Stub to invoke the slot 3 drive 1 device driver
|
|
*/
|
|
void s3d1driver(void) {
|
|
__asm__("jmp (%v)", s3d1vec);
|
|
}
|
|
|
|
/*
|
|
* Stub to invoke the slot 3 drive 2 device driver
|
|
*/
|
|
void s3d2driver(void) {
|
|
__asm__("jmp (%v)", s3d2vec);
|
|
}
|
|
|
|
/*
|
|
* Reconnect RAMdisk on exit
|
|
*/
|
|
#pragma optimize (off)
|
|
void reconnect_ramdisk(void) {
|
|
uint8_t *devcnt = (uint8_t*)0xbf31; // Number of devices
|
|
uint8_t *devlst = (uint8_t*)0xbf32; // Disk device numbers
|
|
uint16_t *s3d1 = (uint16_t*)0xbf16; // s3d1 driver vector
|
|
uint16_t *s3d2 = (uint16_t*)0xbf26; // s3d2 driver vector
|
|
if (s3d2dev) {
|
|
*s3d2 = s3d2vec;
|
|
++(*devcnt);
|
|
devlst[*devcnt] = s3d2dev;
|
|
__asm__("lda #$03"); // FORMAT request
|
|
__asm__("sta $42");
|
|
__asm__("lda #$b0"); // Unit number (s3d2)
|
|
__asm__("sta $43");
|
|
__asm__("lda #$00"); // LSB of buffer pointer
|
|
__asm__("sta $44");
|
|
__asm__("lda #$20"); // MSB of buffer pointer
|
|
__asm__("sta $45");
|
|
__asm__("lda $c08b"); // R/W enable LC, bank 1 on
|
|
__asm__("lda $c08b");
|
|
__asm__("jsr %v", s3d2driver); // Call driver
|
|
__asm__("bit $c082"); // ROM back online
|
|
__asm__("bcc %g", s3d1); // If no error ...
|
|
putchar(0x07); // BELL
|
|
printf("Unable to reconnect S3D2");
|
|
}
|
|
s3d1:
|
|
if (s3d1dev) {
|
|
*s3d1 = s3d1vec;
|
|
++(*devcnt);
|
|
devlst[*devcnt] = s3d1dev;
|
|
__asm__("lda #$03"); // FORMAT request
|
|
__asm__("sta $42");
|
|
__asm__("lda #$30"); // Unit number (s3d1)
|
|
__asm__("sta $43");
|
|
__asm__("lda #$00"); // LSB of buffer pointer
|
|
__asm__("sta $44");
|
|
__asm__("lda #$20"); // MSB of buffer pointer
|
|
__asm__("sta $45");
|
|
__asm__("lda $c08b"); // R/W enable LC, bank 1 on
|
|
__asm__("lda $c08b");
|
|
__asm__("jsr %v", s3d1driver); // Call driver
|
|
__asm__("bit $c082"); // ROM back online
|
|
__asm__("bcc %g", done); // If no error ...
|
|
putchar(0x07); // BELL
|
|
printf("Unable to reconnect S3D1");
|
|
}
|
|
done:
|
|
return;
|
|
}
|
|
#pragma optimize (on)
|
|
|
|
//int main(int argc, char *argv[]) {
|
|
int main() {
|
|
#ifdef CMDLINE
|
|
int opt;
|
|
#endif
|
|
uchar dev;
|
|
uint blk;
|
|
|
|
uchar *pp;
|
|
pp = (uchar*)0xbf98;
|
|
if (!(*pp & 0x02))
|
|
err(FATAL, err_80col);
|
|
#ifdef AUXMEM
|
|
if ((*pp & 0x30) != 0x30)
|
|
err(FATAL, err_128K);
|
|
#endif
|
|
|
|
// Clear system bit map
|
|
for (pp = (uchar*)0xbf58; pp <= (uchar*)0xbf6f; ++pp)
|
|
*pp = 0;
|
|
|
|
videomode(VIDEOMODE_80COL);
|
|
|
|
_heapadd((void*)0x0800, 0x1800);
|
|
//printf("\nHeap: %u %u\n", _heapmemavail(), _heapmaxavail());
|
|
|
|
#ifdef FREELIST
|
|
|
|
#ifdef AUXMEM
|
|
freelist = (uchar*)auxalloc(FLSZ);
|
|
#else
|
|
freelist = (uchar*)malloc(FLSZ);
|
|
#endif
|
|
if (!freelist)
|
|
err(FATALALLOC, err_nomem);
|
|
#ifdef AUXMEM
|
|
usedlist = (uchar*)auxalloc(FLSZ);
|
|
#else
|
|
usedlist = (uchar*)malloc(FLSZ);
|
|
#endif
|
|
if (!usedlist)
|
|
err(FATALALLOC, err_nomem);
|
|
|
|
#endif
|
|
|
|
#ifdef AUXMEM
|
|
lockaux(); // Protect free list and used list
|
|
#endif
|
|
|
|
buf = (char*)malloc(sizeof(char) * BLKSZ);
|
|
buf2 = (char*)malloc(sizeof(char) * BLKSZ);
|
|
dirblkbuf = (char*)malloc(sizeof(char) * BLKSZ);
|
|
//printf("\nHeap: %u %u\n", _heapmemavail(), _heapmaxavail());
|
|
maxfiles = _heapmaxavail() / sizeof(struct fileent);
|
|
|
|
#ifdef AUXMEM
|
|
disconnect_ramdisk();
|
|
#endif
|
|
|
|
//rebootafterexit(); // Necessary if we were called from BASIC
|
|
|
|
clrscr();
|
|
printf("[%u]\n", maxfiles);
|
|
filelist = (struct fileent*)malloc(sizeof(struct fileent) * maxfiles);
|
|
|
|
#ifdef CMDLINE
|
|
parseargs();
|
|
#endif
|
|
|
|
#ifdef CMDLINE
|
|
if (argc == 1)
|
|
interactive();
|
|
else {
|
|
if (argc < 2)
|
|
usage();
|
|
while ((opt = getopt(argc, argv, "DrwvVzs:n:f:d:h")) != -1) {
|
|
switch (opt) {
|
|
case 'D':
|
|
dowholedisk = 1;
|
|
dorecurse = 1;
|
|
break;
|
|
case 'r':
|
|