--- /dev/null
+/* dos{dir|read|write|del} - {list|read|write|del} MS-DOS disks Author: M. Huisjes */\r
+\r
+/* dosdir - list MS-DOS directories to stdout\r
+ * doswrite - write stdin/file to DOS-file\r
+ * dosread - read DOS-file to stdout/file\r
+ * dosdel - delete DOS-file\r
+ *\r
+ * Author: Michiel Huisjes.\r
+ *\r
+ * Usage: dos... [-lra] drive [ms-dos file/dir] [file]\r
+ * l: Give long listing.\r
+ * r: List recursively.\r
+ * a: Set ASCII bit.\r
+ */\r
+\r
+/* no assertions - let's save memory! */\r
+#define NDEBUG\r
+#undef CACHE_ROOT\r
+\r
+#include <assert.h>\r
+#include <ctype.h>\r
+#include <errno.h>\r
+#include <limits.h>\r
+#include <types.h>\r
+#include <sys/stat.h>\r
+#include <fcntl.h>\r
+#include <stdlib.h>\r
+#include <stdio.h>\r
+#include <string.h>\r
+#include <time.h>\r
+#include <unistd.h>\r
+\r
+#ifdef MSX_UZIX_TARGET\r
+/* these are true numbers, but you can increase if you have memory */\r
+#define MAX_CLUSTER_SIZE 8192\r
+#define MAX_ROOT_ENTRIES 256\r
+#else\r
+#ifdef PC_UZIX_TARGET\r
+/* due to limitation of memory... */\r
+#define MAX_CLUSTER_SIZE 2048\r
+#define MAX_ROOT_ENTRIES 256\r
+#else\r
+#define MAX_CLUSTER_SIZE 4096\r
+#define MAX_ROOT_ENTRIES 512\r
+#endif\r
+#endif\r
+#define FAT_START 512L /* After bootsector */\r
+#define ROOTADDR (FAT_START + 2L * fat_size)\r
+#define clus_add(cl_no) ((long) (((long) cl_no - 2L) \\r
+ * (long) cluster_size \\r
+ + data_start \\r
+ ))\r
+struct dir_entry {\r
+ unsigned char d_name[8];\r
+ unsigned char d_ext[3];\r
+ unsigned char d_attribute;\r
+ unsigned char d_reserved[10];\r
+ unsigned short d_time;\r
+ unsigned short d_date;\r
+ unsigned short d_cluster;\r
+ unsigned long d_size;\r
+};\r
+\r
+typedef struct dir_entry DIRECTORY;\r
+\r
+#define NOT_USED 0x00\r
+#define ERASED 0xE5\r
+#define DIR 0x2E\r
+#define DIR_SIZE (sizeof (struct dir_entry))\r
+#define SUB_DIR 0x10\r
+#define NIL_DIR ((DIRECTORY *) 0)\r
+\r
+#define LAST_CLUSTER12 0xFFF\r
+#define LAST_CLUSTER 0xFFFF\r
+#define FREE 0x000\r
+#define BAD 0xFF0\r
+#define BAD16 0xFFF0\r
+\r
+#define FCLOSE if (fdo != stdout) fclose(fdo)\r
+#define FABORT(x) FCLOSE; exit(x)\r
+\r
+typedef int BOOL;\r
+\r
+#define TRUE 1\r
+#define FALSE 0\r
+#define NIL_PTR ((char *) 0)\r
+\r
+#define DOS_TIME 315532800L /* 1970 - 1980 */\r
+\r
+#define READ 0\r
+#define WRITE 1\r
+\r
+#define FIND 3\r
+#define LABEL 4\r
+#define ENTRY 5\r
+#define find_entry(d, e, p) directory(d, e, FIND, p)\r
+#define list_dir(d, e, f) (void) directory(d, e, f, NIL_PTR)\r
+#define label() directory(root, root_entries, LABEL, NIL_PTR)\r
+#define new_entry(d, e) directory(d, e, ENTRY, NIL_PTR)\r
+\r
+#define is_dir(d) ((d)->d_attribute & SUB_DIR)\r
+\r
+#define STD_OUT 1\r
+\r
+char *cmnd;\r
+\r
+static int disk; /* File descriptor for disk I/O */\r
+\r
+#ifdef CACHE_ROOT\r
+static DIRECTORY root[MAX_ROOT_ENTRIES];\r
+#else\r
+static DIRECTORY *root = NULL;\r
+#endif\r
+static DIRECTORY save_entry;\r
+static char drive[] = "/dev/dosX";\r
+static char drive2[] = "/dev/fdX";\r
+#define DRIVE_NR (sizeof (drive) - 2)\r
+static char buffer[MAX_CLUSTER_SIZE], *device = drive, path[128];\r
+static long data_start;\r
+static long mark; /* offset of directory entry to be written */\r
+static unsigned short total_clusters, cluster_size, root_entries, sub_entries;\r
+static unsigned long fat_size;\r
+static char *unixfile;\r
+FILE *fdo;\r
+\r
+static BOOL Rflag, Lflag, Aflag, dos_read, dos_write, dos_dir, dos_del, fat_16 = 0;\r
+static BOOL big_endian;\r
+\r
+/* maximum size of a cooked 12bit FAT. Also Size of 16bit FAT cache\r
+ * if not enough memory for whole FAT\r
+ */\r
+#ifdef MSX_UZIX_TARGET\r
+#define COOKED_SIZE 512\r
+#else\r
+#define COOKED_SIZE 8192\r
+#endif\r
+/* raw FAT. Only used for 12bit FAT to make conversion easier \r
+ */\r
+static unsigned char *raw_fat;\r
+/* Cooked FAT. May be only part of the FAT for 16 bit FATs\r
+ */\r
+static unsigned short *cooked_fat;\r
+/* lowest and highest entry in fat cache\r
+ */\r
+static unsigned short fat_low = USHRT_MAX,\r
+ fat_high = 0;\r
+static BOOL fat_dirty = FALSE;\r
+static unsigned int cache_size;\r
+static unsigned long rawfat_size;\r
+\r
+\r
+/* Prototypes. */\r
+void usage(const char *prog_name);\r
+unsigned c2u2(const unsigned char *ucarray);\r
+unsigned long c4u4(const unsigned char *ucarray);\r
+void determine(void);\r
+int main(int argc, char *argv []);\r
+DIRECTORY *directory(DIRECTORY *dir, int entries, BOOL function, char *pathname);\r
+void extract(DIRECTORY *entry);\r
+void delete(DIRECTORY *entry);\r
+void make_file(DIRECTORY *dir_ptr, int entries, char *name);\r
+void fill_date(DIRECTORY *entry);\r
+char *make_name(register DIRECTORY *dir_ptr, short dir_fl);\r
+int fill(char *buff, size_t size);\r
+void xmodes(int mode);\r
+void show(DIRECTORY *dir_ptr, char *name);\r
+void free_blocks(void);\r
+DIRECTORY *read_cluster(unsigned int cluster);\r
+unsigned short free_cluster(BOOL leave_fl);\r
+void link_fat(unsigned int cl_1, unsigned int cl_2);\r
+unsigned short next_cluster(unsigned int cl_no);\r
+unsigned short clear_cluster(unsigned int cl_no);\r
+char *slash(char *str);\r
+void add_path(char *file, BOOL slash_fl);\r
+void disk_io(BOOL op, unsigned long seek, void *address, unsigned bytes);\r
+void flush_fat(void);\r
+void read_fat(unsigned int cl_no);\r
+BOOL free_range(unsigned short *first, unsigned short *last);\r
+long lmin(long a, long b);\r
+\r
+void usage(const char *prog_name)\r
+{\r
+ fprintf (stderr, "usage: %s ", prog_name);\r
+ if (dos_dir) fprintf(stderr, "[-lr] drive [dir]\n");\r
+ if (dos_read || dos_write) fprintf(stderr, "[-a] drive dosfile [file]\n");\r
+ if (dos_del) fprintf(stderr, "drive dosfile\n");\r
+ exit(1);\r
+}\r
+\r
+unsigned c2u2(const unsigned char *ucarray)\r
+{\r
+ return ucarray[0] + (ucarray[1] << 8); /* parens vital */\r
+}\r
+\r
+unsigned long c4u4(const unsigned char *ucarray)\r
+{\r
+ return ucarray[0] + ((unsigned long) ucarray[1] << 8) +\r
+ ((unsigned long) ucarray[2] << 16) +\r
+ ((unsigned long) ucarray[3] << 24);\r
+}\r
+\r
+void determine(void)\r
+{\r
+ struct dosboot {\r
+ unsigned char cjump[2]; /* unsigneds avoid bugs */\r
+ unsigned char nop;\r
+ unsigned char name[8];\r
+ unsigned char cbytepers[2]; /* don't use shorts, etc */\r
+ unsigned char secpclus; /* to avoid struct member */\r
+ unsigned char creservsec[2]; /* alignment and byte */\r
+ unsigned char fats; /* order bugs */\r
+ unsigned char cdirents[2];\r
+ unsigned char ctotsec[2];\r
+ unsigned char media;\r
+ unsigned char csecpfat[2];\r
+ unsigned char csecptrack[2];\r
+ unsigned char cheads[2];\r
+ unsigned char chiddensec[2];\r
+ unsigned char dos4hidd2[2];\r
+ unsigned char dos4totsec[4];\r
+ /* Char fill[476]; */\r
+ } boot;\r
+ unsigned short boot_magic; /* last of boot block */\r
+ unsigned bytepers, reservsec, dirents;\r
+ unsigned secpfat, secptrack, heads, hiddensec;\r
+ unsigned long totsec;\r
+ unsigned char fat_info, fat_check;\r
+ unsigned short endiantest = 1;\r
+ int errcount = 0;\r
+\r
+ big_endian = !(*(unsigned char *)&endiantest);\r
+\r
+ /* Read Bios-Parameterblock */\r
+ disk_io(READ, 0L, &boot, sizeof boot);\r
+ disk_io(READ, 0x1FEL, &boot_magic, sizeof boot_magic);\r
+\r
+ /* Convert some arrays */\r
+ bytepers = c2u2(boot.cbytepers);\r
+ reservsec = c2u2(boot.creservsec);\r
+ dirents = c2u2(boot.cdirents);\r
+ totsec = c2u2(boot.ctotsec);\r
+ if (totsec == 0) totsec = c4u4(boot.dos4totsec);\r
+ secpfat = c2u2(boot.csecpfat);\r
+ secptrack = c2u2(boot.csecptrack);\r
+ heads = c2u2(boot.cheads);\r
+\r
+ /* The `hidden sectors' are the sectors before the partition.\r
+ * The calculation here is probably wrong (I think the dos4hidd2\r
+ * bytes are the msbs), but that doesn't matter, since the\r
+ * value isn't used anyway\r
+ */\r
+ hiddensec = c2u2(boot.chiddensec);\r
+ if (hiddensec == 0) hiddensec = c2u2 (boot.dos4hidd2);\r
+\r
+#ifdef PC_UZIX_TARGET\r
+ /* Safety checking */\r
+ if (boot_magic != 0xAA55) {\r
+ fprintf (stderr, "%s: magic != 0xAA55\n", cmnd);\r
+ ++errcount;\r
+ }\r
+#endif\r
+ \r
+ /* Check sectors per track instead of inadequate media byte */\r
+ if (secptrack < 15 && /* assume > 15 hard disk & wini OK */\r
+#ifdef SECT10 /* BIOS modified for 10 sec/track */\r
+ secptrack != 10 &&\r
+#endif\r
+#ifdef SECT8 /* BIOS modified for 8 sec/track */\r
+ secptrack != 8 &&\r
+#endif\r
+ secptrack != 9) {\r
+ fprintf (stderr, "%s: %d sectors per track not supported\n", cmnd, secptrack);\r
+ ++errcount;\r
+ }\r
+ if (bytepers == 0) {\r
+ fprintf (stderr, "%s: bytes per sector == 0\n", cmnd);\r
+ ++errcount;\r
+ }\r
+ if (boot.secpclus == 0) {\r
+ fprintf (stderr, "%s: sectors per cluster == 0\n", cmnd);\r
+ ++errcount;\r
+ }\r
+ if (boot.fats != 2 && (dos_write || dos_del)) {\r
+ fprintf (stderr, "%s: fats != 2\n", cmnd);\r
+ ++errcount;\r
+ }\r
+ if (reservsec != 1) {\r
+ fprintf (stderr, "%s: reserved != 1\n", cmnd);\r
+ ++errcount;\r
+ }\r
+ if (errcount != 0) {\r
+ fprintf (stderr, "%s: Can't handle disk\n", cmnd);\r
+ exit(2);\r
+ }\r
+\r
+ /* Calculate everything. */\r
+ if (boot.secpclus == 0) boot.secpclus = 1;\r
+ total_clusters =\r
+ (totsec - boot.fats * secpfat - reservsec -\r
+ dirents * 32L / bytepers ) / boot.secpclus + 2;\r
+ /* first 2 entries in FAT aren't used */\r
+ cluster_size = bytepers * boot.secpclus;\r
+ fat_size = (unsigned long) secpfat * (unsigned long) bytepers;\r
+ data_start = (long) bytepers + (long) boot.fats * fat_size\r
+ + (long) dirents *32L;\r
+ root_entries = dirents;\r
+ sub_entries = boot.secpclus * bytepers / 32;\r
+ if (total_clusters > 4096) fat_16 = 1;\r
+\r
+ /* Further safety checking */\r
+ if (cluster_size > MAX_CLUSTER_SIZE) {\r
+ fprintf (stderr, "%s: cluster size too big\n", cmnd);\r
+ ++errcount;\r
+ }\r
+#ifndef CACHE_ROOT\r
+ if (dirents * DIR_SIZE > MAX_CLUSTER_SIZE) {\r
+ fprintf(stderr, "%s: root directory too big\n", cmnd);\r
+ ++errcount;\r
+ }\r
+#endif\r
+ \r
+ disk_io(READ, FAT_START, &fat_info, 1);\r
+ disk_io(READ, FAT_START + fat_size, &fat_check, 1);\r
+ if (fat_check != fat_info) {\r
+ fprintf (stderr, "%s: Disk type in FAT copy differs from disk type in FAT original.\n", cmnd);\r
+ ++errcount;\r
+ }\r
+ if (errcount != 0) {\r
+ fprintf (stderr, "%s: Can't handle disk\n", cmnd);\r
+ exit(2);\r
+ }\r
+}\r
+\r
+int main(int argc, const char *argv[])\r
+{\r
+ register char *arg_ptr = slash(argv[0]);\r
+ DIRECTORY *entry;\r
+ short idx = 1;\r
+ char dev_nr = '0';\r
+\r
+ fdo = stdout;\r
+ cmnd = arg_ptr; /* needed for error messages */\r
+ if (!strcmp(arg_ptr, "dosdir"))\r
+ dos_dir = TRUE;\r
+ else if (!strcmp(arg_ptr, "dosread"))\r
+ dos_read = TRUE;\r
+ else if (!strcmp(arg_ptr, "doswrite"))\r
+ dos_write = TRUE;\r
+ else if (!strcmp(arg_ptr, "dosdel"))\r
+ dos_del = TRUE;\r
+ else {\r
+ fprintf (stderr, "%s: Program should be named dosread, doswrite, dosdel or dosdir.\n", cmnd);\r
+ exit(1);\r
+ }\r
+\r
+ if (argc == 1) usage(argv[0]);\r
+\r
+ if (argv[1][0] == '-') {\r
+ for (arg_ptr = &argv[1][1]; *arg_ptr; arg_ptr++) {\r
+ if (*arg_ptr == 'l' && dos_dir) {\r
+ Lflag = TRUE;\r
+ } else if (*arg_ptr == 'r' && dos_dir) {\r
+ Rflag = TRUE;\r
+ } else if (*arg_ptr == 'a' && !dos_dir && !dos_del) {\r
+ assert ('\n' == 10);\r
+ assert ('\r' == 13);\r
+ Aflag = TRUE;\r
+ } else {\r
+ usage(argv[0]);\r
+ }\r
+ }\r
+ idx++;\r
+ }\r
+ if (idx == argc) usage(argv[0]);\r
+\r
+ if (strlen(argv[idx]) > 1) {\r
+ if (*(argv[idx]+1) == ':') {\r
+ if ((dev_nr = toupper (*argv[idx])) < 'A' || dev_nr > 'Z')\r
+ usage(argv[0]);\r
+ dev_nr = dev_nr - 'A' + '0';\r
+ device = drive2;\r
+ device[7] = dev_nr;\r
+ if (*(argv[idx]+2) == '\0')\r
+ idx++;\r
+ else\r
+ argv[idx] = (argv[idx] + 2);\r
+ } else {\r
+ device = argv[idx++];\r
+\r
+ /* If the device does not contain a / we assume that it\r
+ * is the name of a device in /dev. Instead of prepending\r
+ * /dev/ we try to chdir there.\r
+ */\r
+ if (strchr(device, '/') == NULL && chdir("/dev") < 0) {\r
+ perror("/dev");\r
+ exit(1);\r
+ }\r
+ dev_nr = device[strlen(device)-1];\r
+ }\r
+ } else {\r
+ if ((dev_nr = toupper (*argv[idx++])) < 'A' || dev_nr > 'Z')\r
+ usage(argv[0]);\r
+\r
+ device[DRIVE_NR] = dev_nr;\r
+ }\r
+ if ((disk = open(device, (dos_write || dos_del) ? O_RDWR : O_RDONLY)) < 0) {\r
+ fprintf (stderr, "%s: cannot open %s: %s\n",\r
+ cmnd, device, strerror (errno));\r
+ exit(1);\r
+ }\r
+ determine();\r
+\r
+#ifdef CACHE_ROOT\r
+ disk_io(READ, ROOTADDR, root, \r
+ root_entries > MAX_ROOT_ENTRIES ? \r
+ DIR_SIZE * MAX_ROOT_ENTRIES : \r
+ DIR_SIZE * root_entries);\r
+ if (root_entries > MAX_ROOT_ENTRIES) {\r
+ fprintf (stderr, "%s: root dir limited to %d files\n", cmnd, MAX_ROOT_ENTRIES);\r
+ }\r
+#endif\r
+\r
+ if (dos_read || dos_write) {\r
+ unixfile = argv[idx+1];\r
+ if (unixfile == NIL_PTR) {\r
+ fdo = stdout;\r
+ if (dos_write) fdo = stdin;\r
+ } else {\r
+ if ((fdo = fopen(unixfile, dos_read ? "wb" : "rb")) == NULL) {\r
+ perror(argv[0]);\r
+ exit(1);\r
+ }\r
+ }\r
+ }\r
+\r
+ if (dos_dir && Lflag) {\r
+ entry = label();\r
+ fprintf (fdo, "Volume in drive %c ", dev_nr - '0' + 'A');\r
+ if (entry == NIL_DIR)\r
+ fprintf(fdo, "has no label.\n\n");\r
+ else\r
+ fprintf (fdo, "is %.11s\n\n", entry->d_name);\r
+ }\r
+ if (argv[idx] == NIL_PTR) {\r
+ if (!dos_dir) usage(argv[0]);\r
+ if (Lflag) fprintf (fdo, "Root directory:\n");\r
+ list_dir(root, root_entries, FALSE);\r
+ if (Lflag) free_blocks();\r
+ fflush (fdo);\r
+ FABORT(0);\r
+ }\r
+ for (arg_ptr = argv[idx]; *arg_ptr; arg_ptr++)\r
+ if (*arg_ptr == '\\') *arg_ptr = '/';\r
+ else *arg_ptr = toupper (*arg_ptr);\r
+ if (*--arg_ptr == '/') *arg_ptr = '\0'; /* skip trailing '/' */\r
+\r
+ add_path(argv[idx], FALSE);\r
+ add_path("/", FALSE);\r
+\r
+ if (dos_dir && Lflag) fprintf (fdo, "Directory %s:\n", path);\r
+\r
+ entry = find_entry(root, root_entries, argv[idx]);\r
+\r
+ if (dos_dir) {\r
+ list_dir(entry, sub_entries, FALSE);\r
+ if (Lflag) free_blocks();\r
+ } else if (dos_read)\r
+ extract(entry);\r
+ else if (dos_del)\r
+ delete(entry);\r
+ else {\r
+ if (entry != NIL_DIR) {\r
+ fflush (fdo);\r
+ if (is_dir(entry))\r
+ fprintf (stderr, "%s: %s is a directory.\n", cmnd, path);\r
+ else\r
+ fprintf (stderr, "%s: %s already exists.\n", cmnd, argv[idx]);\r
+ exit(1);\r
+ }\r
+ add_path(NIL_PTR, TRUE);\r
+\r
+ if (*path) make_file(find_entry(root, root_entries, path),\r
+ sub_entries, slash(argv[idx]));\r
+ else\r
+ make_file(root, root_entries, argv[idx]);\r
+ }\r
+\r
+ (void) close(disk);\r
+ fflush (fdo);\r
+ if (fdo != stdout) fclose(fdo);\r
+ return(0);\r
+}\r
+\r
+\r
+/* General directory search routine.\r
+ * \r
+ * dir:\r
+ * Points to one or more directory entries\r
+ * if dir == root, when ROOT_CACHE is defined, dir points to the \r
+ * entire root directory; if ROOT_CACHE is not defined, root dir\r
+ * will be read from disk when needed. If dir != root, it points\r
+ * to a single directory entry describing the directory to be\r
+ * searched.\r
+ *\r
+ * entries:\r
+ * number of entries\r
+ * \r
+ * function:\r
+ * FIND ... find pathname relative to directory dir.\r
+ * LABEL ... find first label entry in dir.\r
+ * ENTRY ... create a new empty entry.\r
+ * FALSE ... list directory\r
+ *\r
+ * pathname:\r
+ * name of the file to be found or directory to be listed.\r
+ * must be in upper case, pathname components must be\r
+ * separated by slashes, but can be longer than than \r
+ * 8+3 characters (The rest is ignored).\r
+ */\r
+DIRECTORY *directory(DIRECTORY *dir, int entries, int function, char *pathname)\r
+{\r
+ register DIRECTORY *dir_ptr = dir;\r
+ DIRECTORY *mem = NIL_DIR;\r
+ unsigned short cl_no = dir->d_cluster;\r
+ unsigned short type, last = 0;\r
+ char file_name[14];\r
+ char dir_bkp[DIR_SIZE];\r
+ char *name;\r
+ int i = 0;\r
+\r
+ if (dir != NULL) {\r
+ memcpy((char *)dir_bkp, (char *)dir, DIR_SIZE);\r
+ dir = (void *)dir_bkp;\r
+ }\r
+ if (function == FIND) {\r
+ while (*pathname == '/') *pathname++;\r
+ while (*pathname != '/' && *pathname != '.' && *pathname &&\r
+ i < 8) {\r
+ file_name[i++] = *pathname++;\r
+ }\r
+ if (*pathname == '.') {\r
+ int j = 0;\r
+ file_name[i++] = *pathname++;\r
+ while (*pathname != '/' && *pathname != '.' && *pathname &&\r
+ j++ < 3) {\r
+ file_name[i++] = *pathname++;\r
+ }\r
+ }\r
+ while (*pathname != '/' && *pathname) pathname++;\r
+ file_name[i] = '\0';\r
+ }\r
+ do {\r
+ if (dir != root) {\r
+ mem = dir_ptr = read_cluster(cl_no);\r
+ last = cl_no;\r
+ cl_no = next_cluster(cl_no);\r
+ }\r
+#ifndef CACHE_ROOT\r
+ else {\r
+ disk_io(READ, ROOTADDR, buffer, root_entries * DIR_SIZE);\r
+ dir_ptr = (void *)buffer;\r
+ cl_no = dir_ptr->d_cluster; \r
+ }\r
+#endif \r
+ for (i = 0; i < entries; i++, dir_ptr++) {\r
+ type = dir_ptr->d_name[0] & 0x0FF;\r
+ if (!mem)\r
+ mark = ROOTADDR + (long) i *(long) DIR_SIZE;\r
+ else\r
+ mark = clus_add(last) + (long) i *(long) DIR_SIZE;\r
+ if (function == ENTRY) {\r
+ if (type == NOT_USED || type == ERASED) return dir_ptr;\r
+ continue;\r
+ }\r
+ if (type == NOT_USED) break;\r
+ if (dir_ptr->d_attribute & 0x08) {\r
+ if (function == LABEL) return dir_ptr;\r
+ continue;\r
+ }\r
+ if (type == DIR || type == ERASED || function == LABEL)\r
+ continue;\r
+ type = is_dir(dir_ptr);\r
+ name = make_name(dir_ptr,\r
+ (function == FIND) ? FALSE : type);\r
+ if (function == FIND) {\r
+ if (strcmp(file_name, name) != 0) continue;\r
+ if (!type) {\r
+ if (dos_dir || *pathname) {\r
+ fflush (stdout);\r
+ fprintf (stderr, "%s: Not a directory: %s\n", cmnd, file_name);\r
+ exit(1);\r
+ }\r
+ } else if (*pathname == '\0' && dos_read) {\r
+ fflush (stdout);\r
+ fprintf (stderr, "%s: %s is a directory.\n", cmnd, path);\r
+ exit(1);\r
+ }\r
+ if (*pathname) {\r
+ dir_ptr = find_entry(dir_ptr,\r
+ sub_entries, pathname + 1);\r
+ }\r
+ if (mem) {\r
+ if (dir_ptr) {\r
+ memcpy((char *)&save_entry, (char *)dir_ptr, DIR_SIZE);\r
+ dir_ptr = &save_entry;\r
+ }\r
+ }\r
+ return dir_ptr;\r
+ } else {\r
+ if (function == FALSE) {\r
+ show(dir_ptr, name);\r
+ } else if (type) { /* Recursive */\r
+ printf ( "Directory %s%s:\n", path, name);\r
+ add_path(name, FALSE);\r
+ list_dir(dir_ptr, sub_entries, FALSE);\r
+ add_path(NIL_PTR, FALSE);\r
+#ifndef CACHE_ROOT\r
+ /* Re-read directory from disk */\r
+ mem = dir_ptr = read_cluster(cl_no);\r
+#endif\r
+ }\r
+ }\r
+ }\r
+ } while (cl_no != LAST_CLUSTER && mem);\r
+\r
+ switch (function) {\r
+ case FIND:\r
+ if (dos_write && *pathname == '\0') return NIL_DIR;\r
+ fflush (stdout);\r
+ fprintf (stderr, "%s: Cannot find `%s'.\n", cmnd, file_name);\r
+ exit(1);\r
+ case LABEL:\r
+ return NIL_DIR;\r
+ case ENTRY:\r
+ if (!mem) {\r
+ fflush (stdout);\r
+ fprintf (stderr, "%s: No entries left in root directory.\n", cmnd);\r
+ exit(1);\r
+ }\r
+ cl_no = free_cluster(TRUE);\r
+ link_fat(last, cl_no);\r
+ link_fat(cl_no, LAST_CLUSTER);\r
+ memset(buffer, 0, cluster_size);\r
+ disk_io(WRITE, clus_add(cl_no), buffer, cluster_size);\r
+\r
+ return new_entry(dir, entries);\r
+ case FALSE:\r
+ if (Rflag) {\r
+ printf ("\n");\r
+ list_dir(dir, entries, TRUE);\r
+ }\r
+ }\r
+ return NULL;\r
+}\r
+\r
+void delete(DIRECTORY *entry)\r
+{\r
+ register unsigned short cl_no = entry->d_cluster;\r
+ \r
+ entry->d_name[0] = 0xe5;\r
+ disk_io(WRITE, mark, entry, DIR_SIZE);\r
+ while (cl_no != LAST_CLUSTER) cl_no = clear_cluster(cl_no);\r
+ if (fat_dirty) flush_fat ();\r
+}\r
+\r
+void extract(DIRECTORY *entry)\r
+{\r
+ register unsigned short cl_no = entry->d_cluster;\r
+ int rest, i;\r
+ long size = entry->d_size;\r
+\r
+ if (size == 0) /* Empty file */\r
+ return;\r
+\r
+ do {\r
+ disk_io(READ, clus_add(cl_no), buffer, cluster_size);\r
+ rest = (size > (long) cluster_size) ? cluster_size : (short)size;\r
+\r
+ if (Aflag) {\r
+ for (i = 0; i < rest; i ++) {\r
+ if (buffer [i] != '\r') fputc (buffer [i], fdo);\r
+ }\r
+ if (ferror (stdout)) {\r
+ fprintf (stderr, "%s: cannot write file: %s\n",\r
+ cmnd, strerror (errno));\r
+ FABORT(1);\r
+ }\r
+ } else {\r
+ if (fwrite (buffer, 1, rest, fdo) != rest) {\r
+ fprintf (stderr, "%s: cannot write file: %s\n",\r
+ cmnd, strerror (errno));\r
+ FABORT(1);\r
+ }\r
+ }\r
+ size -= (long) rest;\r
+ cl_no = next_cluster(cl_no);\r
+ if (cl_no == BAD16) {\r
+ fflush (stdout);\r
+ fprintf (stderr, "%s: reserved cluster value %x encountered.\n",\r
+ cmnd, cl_no);\r
+ FABORT(1);\r
+ }\r
+ } while (size && cl_no != LAST_CLUSTER);\r
+\r
+ if (cl_no != LAST_CLUSTER)\r
+ fprintf (stderr, "%s: Too many clusters allocated for file.\n", cmnd);\r
+ else if (size != 0)\r
+ fprintf (stderr, "%s: Premature EOF: %ld bytes left.\n", cmnd,\r
+ entry->d_size);\r
+}\r
+\r
+\r
+/* Minimum of two long values\r
+ */\r
+long lmin (long a, long b)\r
+{\r
+ if (a < b) return a;\r
+ else return b;\r
+}\r
+\r
+\r
+void make_file(DIRECTORY *dir_ptr, int entries, char *name)\r
+{\r
+ register DIRECTORY *entry = new_entry(dir_ptr, entries);\r
+ register char *ptr;\r
+ unsigned short cl_no = 0;\r
+ int i, r;\r
+ long size = 0L;\r
+ unsigned short first_cluster, last_cluster;\r
+ long chunk;\r
+ char dir_bkp[DIR_SIZE];\r
+\r
+ memcpy((char *)dir_bkp, (char *)entry, DIR_SIZE);\r
+ entry = (DIRECTORY *)dir_bkp;\r
+ memset (&entry->d_name[0], ' ', 11); /* clear entry */\r
+ for (i = 0, ptr = name; i < 8 && *ptr != '.' && *ptr; i++)\r
+ entry->d_name[i] = *ptr++;\r
+ while (*ptr != '.' && *ptr) ptr++;\r
+ if (*ptr == '.') ptr++;\r
+ for (i = 0; i < 3 && *ptr != '.' && *ptr; i++) entry->d_ext[i] = *ptr++;\r
+\r
+ for (i = 0; i < 10; i++) entry->d_reserved[i] = '\0';\r
+ entry->d_attribute = '\0';\r
+\r
+ entry->d_cluster = 0;\r
+\r
+ while (free_range (&first_cluster, &last_cluster)) {\r
+ do {\r
+ unsigned short nr_clus;\r
+\r
+ chunk = lmin ((long) (last_cluster - first_cluster + 1) *\r
+ cluster_size,\r
+ (long) MAX_CLUSTER_SIZE);\r
+ r = fill(buffer, chunk);\r
+ if (r == 0) goto done;\r
+ nr_clus = (r + cluster_size - 1) / cluster_size;\r
+ disk_io(WRITE, clus_add(first_cluster), buffer, r);\r
+\r
+ for (i = 0; i < nr_clus; i ++) {\r
+ if (entry->d_cluster == 0)\r
+ cl_no = entry->d_cluster = first_cluster;\r
+ else {\r
+ link_fat(cl_no, first_cluster);\r
+ cl_no = first_cluster;\r
+ }\r
+ first_cluster ++;\r
+ }\r
+\r
+ size += r;\r
+ } while (first_cluster <= last_cluster);\r
+ }\r
+ fprintf (stderr, "%s: disk full. File truncated\n", cmnd);\r
+done:\r
+ if (entry->d_cluster != 0) link_fat(cl_no, LAST_CLUSTER);\r
+ entry->d_size = size;\r
+ fill_date(entry);\r
+ disk_io(WRITE, mark, entry, DIR_SIZE);\r
+\r
+ if (fat_dirty) flush_fat ();\r
+}\r
+\r
+\r
+#define SEC_MIN 60L\r
+#define SEC_HOUR (60L * SEC_MIN)\r
+#define SEC_DAY (24L * SEC_HOUR)\r
+#define SEC_YEAR (365L * SEC_DAY)\r
+#define SEC_LYEAR (366L * SEC_DAY)\r
+\r
+unsigned short mon_len[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};\r
+\r
+void fill_date(DIRECTORY *entry)\r
+{\r
+ time_t atime;\r
+ unsigned long cur_time;\r
+ unsigned short year = 0, month = 1, day, hour, minutes, seconds;\r
+ int i;\r
+ long tmp;\r
+\r
+ time(&atime);\r
+ cur_time = atime - DOS_TIME;\r
+ for (;;) {\r
+ tmp = (year % 4 == 0) ? SEC_LYEAR : SEC_YEAR;\r
+ if (cur_time < tmp) break;\r
+ cur_time -= tmp;\r
+ year++;\r
+ }\r
+ day = (unsigned short) (cur_time / SEC_DAY);\r
+ cur_time -= (long) day *SEC_DAY;\r
+\r
+ hour = (unsigned short) (cur_time / SEC_HOUR);\r
+ cur_time -= (long) hour *SEC_HOUR;\r
+\r
+ minutes = (unsigned short) (cur_time / SEC_MIN);\r
+ cur_time -= (long) minutes *SEC_MIN;\r
+\r
+ seconds = (unsigned short) cur_time;\r
+\r
+ mon_len[1] = (year % 4 == 0) ? 29 : 28;\r
+ i = 0;\r
+ while (day >= mon_len[i]) {\r
+ month++;\r
+ day -= mon_len[i++];\r
+ }\r
+ day++;\r
+\r
+ entry->d_date = (year << 9) | (month << 5) | day;\r
+ entry->d_time = (hour << 11) | (minutes << 5) | seconds;\r
+}\r
+\r
+char *make_name(DIRECTORY *dir_ptr, short dir_fl)\r
+{\r
+ static char name_buf[14];\r
+ register char *ptr = name_buf;\r
+ short i;\r
+\r
+ for (i = 0; i < 8; i++) *ptr++ = dir_ptr->d_name[i];\r
+\r
+ while (*--ptr == ' ');\r
+ assert (ptr >= name_buf);\r
+\r
+ ptr++;\r
+ if (dir_ptr->d_ext[0] != ' ') {\r
+ *ptr++ = '.';\r
+ for (i = 0; i < 3; i++) *ptr++ = dir_ptr->d_ext[i];\r
+ while (*--ptr == ' ');\r
+ ptr++;\r
+ }\r
+ if (dir_fl) *ptr++ = '/';\r
+ *ptr = '\0';\r
+\r
+ return name_buf;\r
+}\r
+\r
+\r
+int fill(char *buff, size_t size)\r
+{\r
+ static BOOL nl_mark = FALSE;\r
+ char *last = &buff[size];\r
+ char *begin = buff;\r
+ register int c;\r
+\r
+ while (buff < last) {\r
+ if (nl_mark) {\r
+ *buff ++ = '\n';\r
+ nl_mark = FALSE;\r
+ } else {\r
+ c = fgetc(fdo);\r
+ if (c == EOF) break;\r
+ if (Aflag && c == '\n') {\r
+ *buff ++ = '\r';\r
+ nl_mark = TRUE;\r
+ } else {\r
+ *buff++ = c;\r
+ }\r
+ }\r
+ }\r
+\r
+ return (buff - begin);\r
+}\r
+\r
+#define HOUR 0xF800 /* Upper 5 bits */\r
+#define MIN 0x07E0 /* Middle 6 bits */\r
+#define YEAR 0xFE00 /* Upper 7 bits */\r
+#define MONTH 0x01E0 /* Mid 4 bits */\r
+#define DAY 0x01F /* Lowest 5 bits */\r
+\r
+char *month[] = {\r
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",\r
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"\r
+};\r
+\r
+void xmodes(int mode)\r
+{\r
+ printf ( "\t%c%c%c%c%c", (mode & SUB_DIR) ? 'd' : '-',\r
+ (mode & 02) ? 'h' : '-', (mode & 04) ? 's' : '-',\r
+ (mode & 01) ? '-' : 'w', (mode & 0x20) ? 'a' : '-');\r
+}\r
+\r
+void show(DIRECTORY *dir_ptr, char *name)\r
+{\r
+ register unsigned short e_date = dir_ptr->d_date;\r
+ register unsigned short e_time = dir_ptr->d_time;\r
+ unsigned short next;\r
+ char bname[20];\r
+ short i = 0;\r
+\r
+ while (*name && *name != '/') bname[i++] = *name++;\r
+ bname[i] = '\0';\r
+ if (!Lflag) {\r
+ fprintf (fdo, "%s\n", bname);\r
+ return;\r
+ }\r
+ xmodes( (int) dir_ptr->d_attribute);\r
+ fprintf (fdo, "\t%s%s", bname, strlen(bname) < 8 ? "\t\t" : "\t");\r
+ i = 1;\r
+ if (is_dir(dir_ptr)) {\r
+ next = dir_ptr->d_cluster;\r
+ while ((next = next_cluster(next)) != LAST_CLUSTER) i++;\r
+ fprintf (fdo, "%8ld", (long) i * (long) cluster_size);\r
+ } else\r
+ fprintf (fdo, "%8ld", dir_ptr->d_size);\r
+ fprintf (fdo, " %02d:%02d %2d %s %d\n", ((e_time & HOUR) >> 11),\r
+ ((e_time & MIN) >> 5), (e_date & DAY),\r
+ month[((e_date & MONTH) >> 5) - 1], ((e_date & YEAR) >> 9) + 1980);\r
+}\r
+\r
+void free_blocks(void)\r
+{\r
+ register unsigned short cl_no;\r
+ long nr_free = 0;\r
+ long nr_bad = 0;\r
+\r
+ for (cl_no = 2; cl_no < total_clusters; cl_no++) {\r
+ switch (next_cluster(cl_no)) {\r
+ case FREE: nr_free++; break;\r
+ case BAD16: nr_bad++; break;\r
+ }\r
+ }\r
+\r
+ fprintf (fdo, "Free space: %ld bytes.\n", nr_free * (long) cluster_size);\r
+ if (nr_bad != 0)\r
+ fprintf (fdo, "Bad sectors: %ld bytes.\n", nr_bad * (long) cluster_size);\r
+}\r
+\r
+\r
+DIRECTORY *read_cluster(unsigned int cluster)\r
+{\r
+ disk_io(READ, clus_add(cluster), buffer, cluster_size);\r
+\r
+ return (DIRECTORY *)buffer;\r
+}\r
+\r
+static unsigned short cl_index = 2;\r
+\r
+/* find a range of consecutive free clusters. Return TRUE if found\r
+ * and return the first and last cluster in the |*first| and |*last|.\r
+ * If no free clusters are left, return FALSE.\r
+ *\r
+ * Warning: Assumes that all of the range is used before the next call\r
+ * to free_range or free_cluster.\r
+ */\r
+BOOL free_range (unsigned short *first, unsigned short *last)\r
+{\r
+ while (cl_index < total_clusters && next_cluster(cl_index) != FREE)\r
+ cl_index++;\r
+ if (cl_index >= total_clusters) return FALSE;\r
+ *first = cl_index;\r
+ while (cl_index < total_clusters && next_cluster(cl_index) == FREE)\r
+ cl_index++;\r
+ *last = cl_index - 1;\r
+ return TRUE;\r
+}\r
+\r
+\r
+/* find a free cluster.\r
+ * Return the number of the free cluster or a number > |total_clusters|\r
+ * if none is found.\r
+ * If |leave_fl| is TRUE, the the program will be terminated if \r
+ * no free cluster can be found\r
+ *\r
+ * Warning: Assumes that the cluster is used before the next call\r
+ * to free_range or free_cluster.\r
+ */\r
+unsigned short free_cluster(BOOL leave_fl)\r
+{\r
+ while (cl_index < total_clusters && next_cluster(cl_index) != FREE)\r
+ cl_index++;\r
+\r
+ if (leave_fl && cl_index >= total_clusters) {\r
+ fprintf (stderr, "%s: Diskette full. File not added.\n", cmnd);\r
+ exit(1);\r
+ }\r
+ return cl_index++;\r
+}\r
+\r
+/* read a portion of the fat containing |cl_no| into the cache\r
+ */\r
+void read_fat (unsigned int cl_no)\r
+{\r
+\r
+ if (!cooked_fat) {\r
+ /* Read the fat for the first time. We have to allocate all the\r
+ * buffers\r
+ */\r
+ if (fat_16) {\r
+ /* FAT consists of little endian shorts. Easy to convert\r
+ */\r
+ if ((cooked_fat = malloc (fat_size)) == NULL) {\r
+ /* Oops, FAT doesn't fit into memory, just read\r
+ * a chunk\r
+ */\r
+ if ((cooked_fat = malloc (COOKED_SIZE)) == NULL) {\r
+ fprintf (stderr, "%s: not enough memory for FAT cache.\n",\r
+ cmnd);\r
+ exit (1);\r
+ }\r
+ cache_size = COOKED_SIZE / 2;\r
+ } else {\r
+ cache_size = fat_size / 2;\r
+ }\r
+ } else {\r
+ /* 12 bit FAT. Difficult encoding, but small. Keep\r
+ * both raw FAT and cooked version in memory if possible.\r
+ */\r
+ cooked_fat = malloc (total_clusters * sizeof (short));\r
+ raw_fat = malloc (fat_size);\r
+ if (cooked_fat == NULL || raw_fat == NULL) {\r
+ if (cooked_fat != NULL) free(cooked_fat);\r
+ if (raw_fat != NULL) free(raw_fat);\r
+ /* Oops, FAT doesn't fit into memory, just read\r
+ * a chunk\r
+ */\r
+ cooked_fat = malloc (COOKED_SIZE);\r
+ raw_fat = malloc (COOKED_SIZE/sizeof (short)/2*3);\r
+ if (cooked_fat == NULL || raw_fat == NULL) {\r
+ fprintf (stderr, "%s: not enough memory for FAT cache.\n",\r
+ cmnd);\r
+ exit (1);\r
+ }\r
+ cache_size = COOKED_SIZE / sizeof(short);\r
+ rawfat_size = COOKED_SIZE / sizeof(short)/2*3;\r
+ } else {\r
+ cache_size = total_clusters;\r
+ rawfat_size = fat_size;\r
+ }\r
+ }\r
+ }\r
+ fat_low = cl_no; /* / cache_size * cache_size; ??? */\r
+\r
+ /* for FAT 12, round fat_low to a multiple of 2, so, when reading FAT from \r
+ * disk, we start at this cluster or previous one */\r
+ if (!fat_16) if (fat_low & 1 != 0) fat_low--;\r
+\r
+ fat_high = fat_low + cache_size - 1;\r
+\r
+ if (!fat_16) {\r
+ unsigned short *cp;\r
+ unsigned char *rp;\r
+ unsigned short i;\r
+\r
+ disk_io (READ, FAT_START + fat_low * 3 / 2, raw_fat, rawfat_size);\r
+ for (rp = raw_fat, cp = cooked_fat, i = 0;\r
+ i < cache_size;\r
+ rp += 3, i += 2) {\r
+ *cp = *rp + ((*(rp + 1) & 0x0f) << 8);\r
+ if (*cp == BAD) *cp = BAD16;\r
+ else if (*cp == LAST_CLUSTER12) *cp = LAST_CLUSTER;\r
+ cp ++;\r
+ *cp = ((*(rp + 1) & 0xf0) >> 4) + (*(rp + 2) << 4);\r
+ if (*cp == BAD) *cp = BAD16;\r
+ else if (*cp == LAST_CLUSTER12) *cp = LAST_CLUSTER;\r
+ cp ++;\r
+ }\r
+ } else {\r
+ assert (sizeof (short) == 2);\r
+ assert (CHAR_BIT == 8); /* just in case */\r
+\r
+ disk_io (READ, FAT_START + fat_low * 2, (void *)cooked_fat, cache_size * 2);\r
+ if (big_endian) {\r
+ unsigned short *cp;\r
+ unsigned char *rp;\r
+ unsigned short i;\r
+\r
+ for (i = 0, rp = (unsigned char *)cooked_fat /* sic */, cp = cooked_fat;\r
+ i < cache_size;\r
+ rp += 2, cp ++, i ++) {\r
+ *cp = c2u2 (rp);\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+/* flush the fat cache out to disk\r
+ */\r
+void flush_fat(void)\r
+{\r
+ if (fat_16) {\r
+ if (big_endian) {\r
+ unsigned short *cp;\r
+ unsigned char *rp;\r
+ unsigned short i;\r
+\r
+ for (i = 0, rp = (unsigned char *)cooked_fat /* sic */, cp = cooked_fat;\r
+ i < cache_size;\r
+ rp += 2, cp ++, i ++) {\r
+ *rp = *cp;\r
+ *(rp + 1) = *cp >> 8;\r
+ }\r
+ }\r
+ disk_io (WRITE, FAT_START + fat_low * 2, (void *)cooked_fat, cache_size * 2);\r
+ disk_io (WRITE, FAT_START + fat_size + fat_low * 2, (void *)cooked_fat, cache_size * 2);\r
+ } else {\r
+ unsigned short *cp;\r
+ unsigned char *rp;\r
+ unsigned short i;\r
+\r
+ for (rp = raw_fat, cp = cooked_fat, i = 0;\r
+ i < cache_size;\r
+ rp += 3, cp += 2, i += 2) {\r
+ *rp = *cp;\r
+ *(rp + 1) = ((*cp & 0xf00) >> 8) |\r
+ ((*(cp + 1) & 0x00f) << 4);\r
+ *(rp + 2) = ((*(cp + 1) & 0xff0) >> 4);\r
+ }\r
+ disk_io (WRITE, FAT_START + fat_low * 3 / 2, raw_fat, rawfat_size);\r
+ disk_io (WRITE, FAT_START + fat_size + fat_low * 3 / 2, raw_fat, rawfat_size);\r
+ }\r
+}\r
+\r
+/* make cl_2 the successor of cl_1\r
+ */\r
+void link_fat(unsigned int cl_1, unsigned int cl_2)\r
+{\r
+ if (cl_1 < fat_low || cl_1 > fat_high) {\r
+ if (fat_dirty) flush_fat ();\r
+ read_fat (cl_1);\r
+ }\r
+ cooked_fat [cl_1 - fat_low] = cl_2;\r
+ fat_dirty = TRUE;\r
+}\r
+\r
+\r
+unsigned short next_cluster(unsigned int cl_no)\r
+{\r
+ if (cl_no < fat_low || cl_no > fat_high) {\r
+ if (fat_dirty) flush_fat ();\r
+ read_fat (cl_no);\r
+ }\r
+ return cooked_fat [cl_no - fat_low];\r
+}\r
+\r
+/* free cluster cl_no in FAT and return its sucessor */\r
+unsigned short clear_cluster(unsigned int cl_no)\r
+{\r
+ unsigned short old;\r
+ \r
+ if (cl_no < fat_low || cl_no > fat_high) {\r
+ if (fat_dirty) flush_fat ();\r
+ read_fat (cl_no);\r
+ }\r
+ old = cooked_fat[cl_no - fat_low];\r
+ cooked_fat[cl_no - fat_low] = 0;\r
+ fat_dirty = TRUE;\r
+ return old;\r
+}\r
+\r
+char *slash(char *str)\r
+{\r
+ register char *result = str;\r
+\r
+ while (*str)\r
+ if (*str++ == '/') result = str;\r
+\r
+ return result;\r
+}\r
+\r
+void add_path(char *file, BOOL slash_fl)\r
+{\r
+ register char *ptr = path;\r
+\r
+ while (*ptr) ptr++;\r
+\r
+ if (file == NIL_PTR) {\r
+ if (ptr != path) ptr--;\r
+ if (ptr != path) do {\r
+ ptr--;\r
+ } while (*ptr != '/' && ptr != path);\r
+ if (ptr != path && !slash_fl) *ptr++ = '/';\r
+ *ptr = '\0';\r
+ } else\r
+ strcpy (ptr, file);\r
+}\r
+\r
+\r
+void disk_io(BOOL op, unsigned long seek, void *address, unsigned bytes)\r
+{\r
+ unsigned int r;\r
+\r
+ if (lseek(disk, seek, SEEK_SET) < 0L) {\r
+ fflush (stdout);\r
+ fprintf (stderr, "%s: Bad lseek: %s\n", cmnd, strerror (errno));\r
+ exit(1);\r
+ }\r
+ if (op == READ)\r
+ r = read(disk, (char *) address, bytes);\r
+ else {\r
+ r = write(disk, (char *) address, bytes);\r
+ };\r
+\r
+ if (r != bytes) {\r
+ fprintf (stderr, "%s: %s error: %s\n", op == READ ? "read" : "write", cmnd, strerror (errno));\r
+ exit (1);\r
+ }\r
+}\r
+\r
+char dosread_c_rcs_id [] = \r
+ "$Id: dosread.c,v 2.0 2001/03/11 14:30:43 adrcunha Rel $";\r
+\r
+/* $Log: dosread.c,v $\r
+ * Revision 2.0 2001/03/11 14:30:43 adrcunha@dcc.unicamp.br\r
+ * Fixed some bugs, added FAT12 cache, decreased memory usage, changed\r
+ * drive number to letter on dir header, introduced some sanity check for\r
+ * memory, added compilation directive CACHE_ROOT (with this, root dir is\r
+ * loaded on memory at the beginning; without this, root dir is allways\r
+ * read from disk when needed - it's slow, but saves memory).\r
+ *\r
+ * Revision 1.9 1999/11/28 14:32:37 adrcunha@dcc.unicamp.br\r
+ * Dosread and doswrite can read/write to/from a file, not only stdin/stdout.\r
+ * Added one more personality to the schizophrenic dosread: dosdel\r
+ * Drives can be referenced as A:, B:, etc for /dev/fd0, /dev/fd1, etc\r
+ * Dosfiles can be referenced after drive if drive is 'X:' (e.g., A:FOO.BAR)\r
+ *\r
+ * Revision 1.8 1994/05/14 21:53:08 hjp\r
+ * filenames with more than 3 characters and extension work again.\r
+ * removed debugging stuff and b_copy.\r
+ *\r
+ * Revision 1.7 1994/04/09 03:09:01 hjp\r
+ * (posted to comp.os.minix)\r
+ * merged branch 1.5.387 with stem.\r
+ * changed treatment of drive parameter\r
+ *\r
+ * Revision 1.5.387.9 1994/04/09 02:07:51 hjp\r
+ * Disk full no longer produces lost clusters but a truncated file.\r
+ * Truncated file names to 8+3 before comparisons to avoid duplicate\r
+ * files and filenames containing dots in the extension.\r
+ * Replaced sbrk and brk by malloc and free (mixing brk and malloc causes\r
+ * heap corruption which sometimes lead to core dumps. It may also have\r
+ * been the cause of data corruption Kees reported).\r
+ * Made global variables static and removed some unused ones.\r
+ * Error messages now contain program name.\r
+ *\r
+ * Revision 1.5.387.8 1993/11/13 00:38:45 hjp\r
+ * Posted to comp.os.minix and included in Minix-386vm 1.6.25.1.\r
+ * Speed optimizations for 1.44 MB disks.\r
+ * Replaced lowlevel I/O by stdio.\r
+ * Simplified -a: Now only removes resp. adds CRs\r
+ * Cleaned up.\r
+ * \r
+ * Revision 1.5.387.1 1993/01/15 19:32:29 ast\r
+ * Released with 1.6.24b\r
+ */\r