dosread: remove DOS formatting
authorAlan Cox <alan@linux.intel.com>
Wed, 15 Apr 2015 11:42:26 +0000 (12:42 +0100)
committerAlan Cox <alan@linux.intel.com>
Wed, 15 Apr 2015 11:43:38 +0000 (12:43 +0100)
(and correct missing const on main argv[] via Tormod Volden)

Applications/util/dosread.c

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