New server
authorRobert Pengelly <robertapengelly@hotmail.com>
Mon, 3 Jun 2024 03:32:25 +0000 (04:32 +0100)
committerRobert Pengelly <robertapengelly@hotmail.com>
Mon, 3 Jun 2024 03:32:25 +0000 (04:32 +0100)
22 files changed:
LICENSE [new file with mode: 0644]
Makefile.pdw [new file with mode: 0644]
Makefile.std [new file with mode: 0644]
Makefile.unix [new file with mode: 0644]
Makefile.w32 [new file with mode: 0644]
README.md [new file with mode: 0644]
common.c [new file with mode: 0644]
common.h [new file with mode: 0644]
lib.c [new file with mode: 0644]
lib.h [new file with mode: 0644]
mcopy.c [new file with mode: 0644]
mcopy.h [new file with mode: 0644]
mkfs.c [new file with mode: 0644]
mkfs.h [new file with mode: 0644]
mls.c [new file with mode: 0644]
mmd.c [new file with mode: 0644]
mmd.h [new file with mode: 0644]
msdos.h [new file with mode: 0644]
report.c [new file with mode: 0644]
report.h [new file with mode: 0644]
write7x.c [new file with mode: 0644]
write7x.h [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..fdddb29
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <https://unlicense.org>
diff --git a/Makefile.pdw b/Makefile.pdw
new file mode 100644 (file)
index 0000000..07da597
--- /dev/null
@@ -0,0 +1,34 @@
+#******************************************************************************
+# @file             Makefile.pdw
+#******************************************************************************
+AS=aswin
+CC=gccwin
+LD=ldwin
+
+COPTS=-S -O2 -fno-common -ansi -I. -I../pdos/pdpclib -D__WIN32__ -D__NOBIVA__ -D__PDOS__
+COBJ=common.o report.o write7x.o
+
+all: clean mkdosfs.exe mcopy.exe mmd.exe mls.exe
+
+mkdosfs.exe: lib.o mkfs.o $(COBJ)
+  $(LD) -s -o mkdosfs.exe ../pdos/pdpclib/w32start.o lib.o mkfs.o $(COBJ) ../pdos/pdpclib/msvcrt.a
+
+mcopy.exe: mcopy.o $(COBJ)
+  $(LD) -s -o mcopy.exe ../pdos/pdpclib/w32start.o mcopy.o $(COBJ) ../pdos/pdpclib/msvcrt.a
+
+mmd.exe: mmd.o $(COBJ)
+  $(LD) -s -o mmd.exe ../pdos/pdpclib/w32start.o mmd.o $(COBJ) ../pdos/pdpclib/msvcrt.a
+
+mls.exe: mls.o $(COBJ)
+  $(LD) -s -o mls.exe ../pdos/pdpclib/w32start.o mls.o $(COBJ) ../pdos/pdpclib/msvcrt.a
+
+.c.o:
+  $(CC) $(COPTS) $<
+  $(AS) -o $@ $*.s
+  rm -f $*.s
+
+clean:
+  rm -f *.o mkdosfs.exe
+  rm -f *.o mcopy.exe
+  rm -f *.o mmd.exe
+  rm -f *.o mls.exe
diff --git a/Makefile.std b/Makefile.std
new file mode 100644 (file)
index 0000000..5d4980e
--- /dev/null
@@ -0,0 +1,35 @@
+#******************************************************************************\r
+# @file             Makefile.std\r
+#******************************************************************************\r
+AS=pdas --oformat coff\r
+CC=gccwin\r
+LD=pdld --no-insert-timestamp\r
+\r
+COPTS=-S -O2 -fno-common -ansi -I. -I../pdos/pdpclib -D__WIN32__ -D__NOBIVA__ -D__PDOS__\r
+COBJ=common.obj report.obj write7x.obj\r
+\r
+all: clean mkdosfs.exe mcopy.exe mmd.exe mls.exe\r
+\r
+mkdosfs.exe: lib.obj mkfs.obj $(COBJ)\r
+  $(LD) -s -o mkdosfs.exe ../pdos/pdpclib/w32start.obj lib.obj mkfs.obj $(COBJ) ../pdos/pdpclib/msvcrt.lib\r
+\r
+mcopy.exe: mcopy.obj $(COBJ)\r
+  $(LD) -s -o mcopy.exe ../pdos/pdpclib/w32start.obj mcopy.obj $(COBJ) ../pdos/pdpclib/msvcrt.lib\r
+\r
+mmd.exe: mmd.obj $(COBJ)\r
+  $(LD) -s -o mmd.exe ../pdos/pdpclib/w32start.obj mmd.obj $(COBJ) ../pdos/pdpclib/msvcrt.lib\r
+\r
+mls.exe: mls.obj $(COBJ)\r
+  $(LD) -s -o mls.exe ../pdos/pdpclib/w32start.obj mls.obj $(COBJ) ../pdos/pdpclib/msvcrt.lib\r
+\r
+.c.obj:\r
+  $(CC) $(COPTS) $<\r
+  $(AS) -o $@ $*.s\r
+  rm -f $*.s\r
+\r
+clean:\r
+  rm -f *.obj\r
+  rm -f mkdosfs.exe\r
+  rm -f mcopy.exe\r
+  rm -f mmd.exe\r
+  rm -f mls.exe\r
diff --git a/Makefile.unix b/Makefile.unix
new file mode 100644 (file)
index 0000000..55b2bde
--- /dev/null
@@ -0,0 +1,53 @@
+#******************************************************************************
+# @file             Makefile.unix
+#******************************************************************************
+SRCDIR              ?=  $(CURDIR)
+VPATH               :=  $(SRCDIR)
+
+CC                  :=  gcc
+CFLAGS              :=  -D_FILE_OFFSET_BITS=64 -Wall -Werror -Wextra -std=c90
+
+CSRC                :=  common.c report.c write7x.c
+
+ifeq ($(OS), Windows_NT)
+all: mkdosfs.exe mcopy.exe mmd.exe mls.exe
+
+mkdosfs.exe: lib.c mkfs.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+
+mcopy.exe: mcopy.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+
+mmd.exe: mmd.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+
+mls.exe: mls.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+else
+all: mkdosfs mcopy mmd mls
+
+mkdosfs: lib.c mkfs.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+
+mcopy: mcopy.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+
+mmd: mmd.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+
+mls: mls.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+endif
+
+clean:
+       if [ -f mkdosfs.exe ]; then rm -rf mkdosfs.exe; fi
+       if [ -f mkdosfs ]; then rm -rf mkdosfs; fi
+       
+       if [ -f mcopy.exe ]; then rm -rf mcopy.exe; fi
+       if [ -f mcopy ]; then rm -rf mcopy; fi
+       
+       if [ -f mmd.exe ]; then rm -rf mmd.exe; fi
+       if [ -f mmd ]; then rm -rf mmd; fi
+       
+       if [ -f mls.exe ]; then rm -rf mls.exe; fi
+       if [ -f mls ]; then rm -rf mls; fi
diff --git a/Makefile.w32 b/Makefile.w32
new file mode 100644 (file)
index 0000000..b1ac3ba
--- /dev/null
@@ -0,0 +1,37 @@
+#******************************************************************************
+# @file             Makefile.w32
+#******************************************************************************
+SRCDIR              ?=  $(CURDIR)
+VPATH               :=  $(SRCDIR)
+
+CC                  :=  gcc
+CFLAGS              :=  -D_FILE_OFFSET_BITS=64 -Wall -Werror -Wextra -std=c90
+
+CSRC                :=  common.c report.c write7x.c
+
+all: mkdosfs.exe mcopy.exe mmd.exe mls.exe
+
+clean:
+       if exist mkdosfs.exe ( del /q mkdosfs.exe )
+       if exist mkdosfs ( del /q mkdosfs )
+       
+       if exist mcopy.exe ( del /q mcopy.exe )
+       if exist mcopy ( del /q mcopy )
+       
+       if exist mmd.exe ( del /q mmd.exe )
+       if exist mmd ( del /q mmd )
+       
+       if exist mls.exe ( del /q mls.exe )
+       if exist mls ( del /q mls )
+
+mkdosfs.exe: lib.c mkfs.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+
+mcopy.exe: mcopy.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+
+mmd.exe: mmd.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+
+mls.exe: mls.c $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..ed1e864
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+All source code is Public Domain.
+
+## Obtain the source code
+
+    git clone https://git.candlhat.org/dosfstools.git
+
+## Building
+
+    BSD:
+    
+        Make sure you have gcc and gmake installed then run gmake -f Makefile.unix.
+    
+    Linux:
+    
+        Make sure you have gcc and make installed then run make -f Makefile.unix.
+    
+    macOS:
+    
+        Make sure you have xcode command line tools installed then run make -f Makefile.unix.
+    
+    Windows:
+    
+        Make sure you have mingw installed and the location within your PATH variable then run mingw32-make.exe -f Makefile.w32.
diff --git a/common.c b/common.c
new file mode 100644 (file)
index 0000000..35241d6
--- /dev/null
+++ b/common.c
@@ -0,0 +1,84 @@
+/******************************************************************************
+ * @file            common.c
+ *****************************************************************************/
+#include    <errno.h>
+#include    <stdarg.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+#include    <time.h>
+
+#ifndef     __PDOS__
+# if     defined (__GNUC__)
+#  include  <sys/time.h>
+#  include  <unistd.h>
+# else
+#  include  <io.h>
+# endif
+#endif
+
+#include    "common.h"
+
+unsigned short generate_datestamp (void) {
+
+#if     defined (__GNUC__) && !defined (__PDOS__)
+    struct timeval create_timeval;
+#else
+    time_t create_time;
+#endif
+
+    struct tm *ctime = NULL;
+    
+#if     defined (__GNUC__) && !defined (__PDOS__)
+    
+    if (gettimeofday (&create_timeval, 0) == 0 && create_timeval.tv_sec != (time_t) -1) {
+        ctime = localtime ((time_t *) &create_timeval.tv_sec);
+    }
+
+#else
+
+    if (time (&create_time) != 0 && create_time != 0) {
+        ctime = localtime (&create_time);
+    }
+
+#endif
+    
+    if (ctime != NULL && ctime->tm_year >= 80 && ctime->tm_year <= 207) {
+        return (unsigned short) (ctime->tm_mday + ((ctime->tm_mon + 1) << 5) + ((ctime->tm_year - 80) << 9));
+    }
+    
+    return 1 + (1 << 5);
+
+}
+
+unsigned short generate_timestamp (void) {
+
+#if     defined (__GNUC__) && !defined (__PDOS__)
+    struct timeval create_timeval;
+#else
+    time_t create_time;
+#endif
+
+    struct tm *ctime = NULL;
+    
+#if     defined (__GNUC__) && !defined (__PDOS__)
+    
+    if (gettimeofday (&create_timeval, 0) == 0 && create_timeval.tv_sec != (time_t) -1) {
+        ctime = localtime ((time_t *) &create_timeval.tv_sec);
+    }
+
+#else
+
+    if (time (&create_time) != 0 && create_time != 0) {
+        ctime = localtime (&create_time);
+    }
+
+#endif
+    
+    if (ctime != NULL && ctime->tm_year >= 80 && ctime->tm_year <= 207) {
+        return (unsigned short) ((ctime->tm_sec >> 1) + (ctime->tm_min << 5) + (ctime->tm_hour << 11));
+    }
+    
+    return 0;
+
+}
diff --git a/common.h b/common.h
new file mode 100644 (file)
index 0000000..830c2c3
--- /dev/null
+++ b/common.h
@@ -0,0 +1,10 @@
+/******************************************************************************
+ * @file            common.h
+ *****************************************************************************/
+#ifndef     _COMMON_H
+#define     _COMMON_H
+
+extern unsigned short generate_datestamp (void);
+extern unsigned short generate_timestamp (void);
+
+#endif      /* _COMMON_H */
diff --git a/lib.c b/lib.c
new file mode 100644 (file)
index 0000000..50f7dd1
--- /dev/null
+++ b/lib.c
@@ -0,0 +1,471 @@
+/******************************************************************************
+ * @file            lib.c
+ *****************************************************************************/
+#include    <ctype.h>
+#include    <errno.h>
+#include    <limits.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    "common.h"
+#include    "lib.h"
+#include    "mkfs.h"
+#include    "report.h"
+
+struct option {
+
+    const char *name;
+    int index, flags;
+
+};
+
+#define     OPTION_NO_ARG               0x0001
+#define     OPTION_HAS_ARG              0x0002
+
+enum options {
+
+    OPTION_IGNORED = 0,
+    OPTION_ARCA,
+    OPTION_BLOCKS,
+    OPTION_BOOT,
+    OPTION_FAT,
+    OPTION_HELP,
+    OPTION_NAME,
+    OPTION_OFFSET,
+    OPTION_SECTORS,
+    OPTION_VERBOSE
+
+};
+
+static struct option opts[] = {
+
+    { "F",          OPTION_FAT,         OPTION_HAS_ARG  },
+    { "n",          OPTION_NAME,        OPTION_HAS_ARG  },
+    { "s",          OPTION_SECTORS,     OPTION_HAS_ARG  },
+    { "v",          OPTION_VERBOSE,     OPTION_NO_ARG   },
+    
+    { "-arca",      OPTION_ARCA,        OPTION_NO_ARG   },
+    { "-boot",      OPTION_BOOT,        OPTION_HAS_ARG  },
+    { "-help",      OPTION_HELP,        OPTION_NO_ARG   },
+    { "-offset",    OPTION_OFFSET,      OPTION_HAS_ARG  },
+    { "-sectors",   OPTION_BLOCKS,      OPTION_HAS_ARG  },
+    
+    { 0,            0,                  0               }
+
+};
+
+static int strstart (const char *val, const char **str) {
+
+    const char *p = val;
+    const char *q = *str;
+    
+    while (*p != '\0') {
+    
+        if (*p != *q) {
+            return 0;
+        }
+        
+        ++p;
+        ++q;
+    
+    }
+    
+    *str = q;
+    return 1;
+
+}
+
+static void print_help (void) {
+
+    if (!program_name) {
+        goto _exit;
+    }
+    
+    fprintf (stderr, "Usage: %s [options] file\n\n", program_name);
+    fprintf (stderr, "Options:\n\n");
+    
+    fprintf (stderr, "    Short options:\n\n");
+    fprintf (stderr, "        -F BITS           Select FAT size BITS (12, 16, or 32).\n");
+    fprintf (stderr, "        -n LABEL          Set volume label as LABEL (max 11 characters).\n");
+    fprintf (stderr, "        -v                Verbose execution.\n");
+    
+    fprintf (stderr, "\n");
+    
+    fprintf (stderr, "    Long options:\n\n");
+    fprintf (stderr, "        --arca            Use CHS-alignment.\n");
+    fprintf (stderr, "        --boot FILE       Use FILE as the boot sector.\n");
+    fprintf (stderr, "        --help            Show this help information then exit.\n");
+    fprintf (stderr, "        --offset SECTOR   Write the filesystem starting at SECTOR.\n");
+    fprintf (stderr, "        --sectors COUNT   Make the filesystem the size of COUNT * 512.\n");
+    
+_exit:
+    
+    exit (EXIT_SUCCESS);
+
+}
+
+static int is_label_char (int ch) {
+    return ((ch & 0x80) || (ch == 0x20) || isalnum (ch) || strchr ("!#$%'-@_{}~", ch));
+}
+
+char *xstrdup (const char *str) {
+
+    char *ptr = xmalloc (strlen (str) + 1);
+    strcpy (ptr, str);
+    
+    return ptr;
+
+}
+
+int xstrcasecmp (const char *s1, const char *s2) {
+
+    const unsigned char *p1;
+    const unsigned char *p2;
+    
+    p1 = (const unsigned char *) s1;
+    p2 = (const unsigned char *) s2;
+    
+    while (*p1 != '\0') {
+    
+        if (toupper (*p1) < toupper (*p2)) {
+            return (-1);
+        } else if (toupper (*p1) > toupper (*p2)) {
+            return (1);
+        }
+        
+        p1++;
+        p2++;
+    
+    }
+    
+    if (*p2 == '\0') {
+        return (0);
+    } else {
+        return (-1);
+    }
+
+}
+
+void *xmalloc (unsigned long size) {
+
+    void *ptr = malloc (size);
+    
+    if (ptr == NULL && size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "memory full (malloc)");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    memset (ptr, 0, size);
+    return ptr;
+
+}
+
+void *xrealloc (void *ptr, unsigned long size) {
+
+    void *new_ptr = realloc (ptr, size);
+    
+    if (new_ptr == NULL && size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "memory full (realloc)");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    return new_ptr;
+
+}
+
+void dynarray_add (void *ptab, long *nb_ptr, void *data) {
+
+    int nb, nb_alloc;
+    void **pp;
+    
+    nb = *nb_ptr;
+    pp = *(void ***) ptab;
+    
+    if ((nb & (nb - 1)) == 0) {
+    
+        if (!nb) {
+            nb_alloc = 1;
+        } else {
+            nb_alloc = nb * 2;
+        }
+        
+        pp = xrealloc (pp, nb_alloc * sizeof (void *));
+        *(void ***) ptab = pp;
+    
+    }
+    
+    pp[nb++] = data;
+    *nb_ptr = nb;
+
+}
+
+void parse_args (int *pargc, char ***pargv, int optind) {
+
+    char **argv = *pargv;
+    int argc = *pargc;
+    
+    struct option *popt;
+    const char *optarg, *r;
+    
+    if (argc == optind) {
+        print_help ();
+    }
+    
+    memset (state->label, ' ', 11);
+    
+    while (optind < argc) {
+    
+        r = argv[optind++];
+        
+        if (r[0] != '-' || r[1] == '\0') {
+        
+            if (state->outfile) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "multiple output files provided");
+                exit (EXIT_FAILURE);
+            
+            }
+            
+            state->outfile = xstrdup (r);
+            continue;
+        
+        }
+        
+        for (popt = opts; popt; ++popt) {
+        
+            const char *p1 = popt->name;
+            const char *r1 = (r + 1);
+            
+            if (!p1) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "invalid option -- '%s'", r);
+                exit (EXIT_FAILURE);
+            
+            }
+            
+            if (!strstart (p1, &r1)) {
+                continue;
+            }
+            
+            optarg = r1;
+            
+            if (popt->flags & OPTION_HAS_ARG) {
+            
+                if (*optarg == '\0') {
+                
+                    if (optind >= argc) {
+                    
+                        report_at (program_name, 0, REPORT_ERROR, "argument to '%s' is missing", r);
+                        exit (EXIT_FAILURE);
+                    
+                    }
+                    
+                    optarg = argv[optind++];
+                
+                }
+            
+            } else if (*optarg != '\0') {
+                continue;
+            }
+            
+            break;
+        
+        }
+        
+        switch (popt->index) {
+        
+            case OPTION_ARCA: {
+            
+                state->chs_align = 1;
+                break;
+            
+            }
+            
+            case OPTION_BLOCKS: {
+            
+                long conversion;
+                char *temp;
+                
+                errno = 0;
+                conversion = strtol (optarg, &temp, 0);
+                
+                if (!*optarg || isspace ((int) *optarg) || errno || *temp) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "bad number for sectors count");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                if (conversion < 0) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "sectors count must be greater than zero");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                state->sectors = conversion;
+                break;
+            
+            }
+            
+            case OPTION_BOOT: {
+            
+                state->boot = xstrdup (optarg);
+                break;
+            
+            }
+            
+            case OPTION_FAT: {
+            
+                long conversion;
+                char *temp;
+                
+                errno = 0;
+                conversion = strtol (optarg, &temp, 0);
+                
+                if (!*optarg || isspace ((int) *optarg) || errno || *temp) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "bad number for fat size");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                switch (conversion) {
+                
+                    case 12:
+                    case 16:
+                    case 32:
+                    
+                        break;
+                    
+                    default:
+                    
+                        report_at (program_name, 0, REPORT_ERROR, "fat size can either be 12, 16 or 32 bits");
+                        exit (EXIT_FAILURE);
+                
+                }
+                
+                state->size_fat = conversion;
+                state->size_fat_by_user = 1;
+                
+                break;
+            
+            }
+            
+            case OPTION_HELP: {
+            
+                print_help ();
+                break;
+            
+            }
+            
+            case OPTION_NAME: {
+            
+                int n;
+                
+                if (strlen (optarg) > 11) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "volume label cannot exceed 11 characters");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                for (n = 0; optarg[n] != '\0'; n++) {
+                
+                    if (!is_label_char (optarg[n])) {
+                    
+                        report_at (program_name, 0, REPORT_ERROR, "volume label contains an invalid character");
+                        exit (EXIT_FAILURE);
+                    
+                    }
+                    
+                    state->label[n] = toupper (optarg[n]);
+                
+                }
+                
+                break;
+            
+            }
+            
+            case OPTION_OFFSET: {
+            
+                long conversion;
+                char *temp;
+                
+                errno = 0;
+                conversion = strtol (optarg, &temp, 0);
+                
+                if (!*optarg || isspace ((int) *optarg) || *temp || errno) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "bad number for offset");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                if (conversion < 0 || (unsigned long) conversion > UINT_MAX) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "offset must be between 0 and %u", UINT_MAX);
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                state->offset = conversion;
+                break;
+            
+            }
+            
+            case OPTION_SECTORS: {
+            
+                long conversion;
+                char *temp;
+                
+                errno = 0;
+                conversion = strtol (optarg, &temp, 0);
+                
+                if (!*optarg || isspace ((int) *optarg) || *temp || errno) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "bad number for sectors per cluster");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                if (conversion != 1 && conversion != 2 && conversion != 4 && conversion != 8 && conversion != 16 && conversion != 32 && conversion != 64 && conversion != 128) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "bad number for sectors per cluster");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                state->sectors_per_cluster = (unsigned char) conversion;
+                break;
+            
+            }
+            
+            case OPTION_VERBOSE: {
+            
+                state->verbose++;
+                break;
+            
+            }
+            
+            default: {
+            
+                report_at (program_name, 0, REPORT_ERROR, "unsupported option '%s'", r);
+                exit (EXIT_FAILURE);
+            
+            }
+        
+        }
+    
+    }
+    
+    if (memcmp (state->label, "           ", 11) == 0) {
+        memcpy (state->label, "NO NAME    ", 11);
+    }
+
+}
diff --git a/lib.h b/lib.h
new file mode 100644 (file)
index 0000000..980675d
--- /dev/null
+++ b/lib.h
@@ -0,0 +1,16 @@
+/******************************************************************************
+ * @file            lib.h
+ *****************************************************************************/
+#ifndef     _LIB_H
+#define     _LIB_H
+
+char *xstrdup (const char *str);
+int xstrcasecmp (const char *s1, const char *s2);
+
+void *xmalloc (unsigned long size);
+void *xrealloc (void *ptr, unsigned long size);
+
+void dynarray_add (void *ptab, long *nb_ptr, void *data);
+void parse_args (int *pargc, char ***pargv, int optind);
+
+#endif      /* _LIB_H */
diff --git a/mcopy.c b/mcopy.c
new file mode 100644 (file)
index 0000000..9db14f0
--- /dev/null
+++ b/mcopy.c
@@ -0,0 +1,2342 @@
+/******************************************************************************
+ * @file            mcopy.c
+ *****************************************************************************/
+#include    <ctype.h>
+#include    <errno.h>
+#include    <limits.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#ifndef     __PDOS__
+# if    defined (_WIN32)
+#  include  <windows.h>
+#  include  <winioctl.h>
+# elif defined (__GNUC__)
+#  include  <sys/ioctl.h>
+#  include  <termios.h>
+#  include  <unistd.h>
+#  if   defined (__CYGWIN__)
+#   include  <sys/socket.h>
+#  endif
+# endif
+#endif
+
+#include    "common.h"
+#include    "mcopy.h"
+#include    "msdos.h"
+#include    "report.h"
+#include    "write7x.h"
+
+#ifndef     PATH_MAX
+# define    PATH_MAX                    2048
+#endif
+
+static FILE *ofp;
+
+struct dir_info {
+
+    unsigned int current_cluster;
+    
+    unsigned char current_sector;
+    unsigned char current_entry;
+    unsigned char *scratch;
+    unsigned char flags;
+
+};
+
+struct file_info {
+
+    unsigned char dir_offset;
+    unsigned int dir_sector;
+    
+    unsigned char *scratch;
+    unsigned int filelen;
+    
+    unsigned int cluster;
+    unsigned int pointer;
+    
+    unsigned long bytes;
+
+};
+
+static struct mcopy_state *state = 0;
+static const char *program_name = 0;
+
+static struct msdos_boot_sector bs;
+static int size_fat = 0;
+
+static unsigned int cluster_count = 0;
+static unsigned int data_area = 0;
+static unsigned int info_sector = 0;
+static unsigned int number_of_fats = 0;
+static unsigned int reserved_sectors = 0;
+static unsigned int root_cluster = 0;
+static unsigned int root_dir = 0;
+static unsigned int root_entries = 0;
+static unsigned int sectors_per_cluster = 0;
+static unsigned int sectors_per_fat = 0;
+static unsigned int total_sectors = 0;
+
+struct option {
+
+    const char *name;
+    int index, flags;
+
+};
+
+#define     OPTION_NO_ARG               0x0001
+#define     OPTION_HAS_ARG              0x0002
+
+enum options {
+
+    OPTION_IGNORED = 1,
+    OPTION_ARCA,
+    OPTION_HELP,
+    OPTION_INPUT,
+    OPTION_OFFSET,
+    OPTION_STATUS
+
+};
+
+static struct option opts[] = {
+
+    { "i",          OPTION_INPUT,       OPTION_HAS_ARG  },
+    
+    { "-arca",      OPTION_ARCA,        OPTION_NO_ARG   },
+    { "-help",      OPTION_HELP,        OPTION_NO_ARG   },
+    { "-offset",    OPTION_OFFSET,      OPTION_HAS_ARG  },
+    { "-status",    OPTION_STATUS,      OPTION_NO_ARG   },
+    
+    { 0,            0,                  0               }
+
+};
+
+static int strstart (const char *val, const char **str) {
+
+    const char *p = val;
+    const char *q = *str;
+    
+    while (*p != '\0') {
+    
+        if (*p != *q) {
+            return 0;
+        }
+        
+        ++p;
+        ++q;
+    
+    }
+    
+    *str = q;
+    return 1;
+
+}
+
+static void print_help (int exitval) {
+
+    if (!program_name) {
+        goto _exit;
+    }
+    
+    fprintf (stderr, "Usage: %s [options] [::]source-file [::]target-file\n\n", program_name);
+    fprintf (stderr, "Options:\n\n");
+    
+    fprintf (stderr, "    Short options:\n\n");
+    fprintf (stderr, "        -i                Specify the input target.\n");
+    
+    fprintf (stderr, "\n");
+    
+    fprintf (stderr, "    Long options:\n\n");
+    fprintf (stderr, "        --arca            Use CHS-alignment (only works for VHD images).\n");
+    fprintf (stderr, "        --help            Show this help information then exit.\n");
+    fprintf (stderr, "        --offset SECTOR   Write the filesystem starting at SECTOR.\n");
+       
+_exit:
+    
+    exit (exitval);
+
+}
+
+static void *xmalloc (unsigned long size) {
+
+    void *ptr = malloc (size);
+    
+    if (ptr == NULL && size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "memory full (malloc)");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    memset (ptr, 0, size);
+    return ptr;
+
+}
+
+static void *xrealloc (void *ptr, unsigned long size) {
+
+    void *new_ptr = realloc (ptr, size);
+    
+    if (new_ptr == NULL && size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "memory full (realloc)");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    return new_ptr;
+
+}
+
+static char *xstrdup (const char *str) {
+
+    char *ptr = xmalloc (strlen (str) + 1);
+    strcpy (ptr, str);
+    
+    return ptr;
+
+}
+
+static void dynarray_add (void *ptab, long *nb_ptr, void *data) {
+
+    int nb, nb_alloc;
+    void **pp;
+    
+    nb = *nb_ptr;
+    pp = *(void ***) ptab;
+    
+    if ((nb & (nb - 1)) == 0) {
+    
+        if (!nb) {
+            nb_alloc = 1;
+        } else {
+            nb_alloc = nb * 2;
+        }
+        
+        pp = xrealloc (pp, nb_alloc * sizeof (void *));
+        *(void ***) ptab = pp;
+    
+    }
+    
+    pp[nb++] = data;
+    *nb_ptr = nb;
+
+}
+
+static void parse_args (int *pargc, char ***pargv, int optind) {
+
+    char **argv = *pargv;
+    int argc = *pargc;
+    
+    struct option *popt;
+    const char *optarg, *r;
+    
+    if (argc == optind) {
+        print_help (EXIT_SUCCESS);
+    }
+    
+    while (optind < argc) {
+    
+        r = argv[optind++];
+        
+        if (r[0] != '-' || r[1] == '\0') {
+        
+            dynarray_add (&state->files, &state->nb_files, xstrdup (r));
+            continue;
+        
+        }
+        
+        for (popt = opts; popt; ++popt) {
+        
+            const char *p1 = popt->name;
+            const char *r1 = (r + 1);
+            
+            if (!p1) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "invalid option -- '%s'", r);
+                exit (EXIT_FAILURE);
+            
+            }
+            
+            if (!strstart (p1, &r1)) {
+                continue;
+            }
+            
+            optarg = r1;
+            
+            if (popt->flags & OPTION_HAS_ARG) {
+            
+                if (*optarg == '\0') {
+                
+                    if (optind >= argc) {
+                    
+                        report_at (program_name, 0, REPORT_ERROR, "argument to '%s' is missing", r);
+                        exit (EXIT_FAILURE);
+                    
+                    }
+                    
+                    optarg = argv[optind++];
+                
+                }
+            
+            } else if (*optarg != '\0') {
+                continue;
+            }
+            
+            break;
+        
+        }
+        
+        switch (popt->index) {
+        
+            case OPTION_ARCA: {
+            
+                state->chs_align = 1;
+                break;
+            
+            }
+            
+            case OPTION_HELP: {
+            
+                print_help (EXIT_SUCCESS);
+                break;
+            
+            }
+            
+            case OPTION_INPUT: {
+            
+                if (state->outfile) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "multiple output files provided");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                state->outfile = xstrdup (optarg);
+                break;
+            
+            }
+            
+            case OPTION_OFFSET: {
+            
+                long conversion;
+                char *temp;
+                
+                errno = 0;
+                conversion = strtol (optarg, &temp, 0);
+                
+                if (!*optarg || isspace ((int) *optarg) || errno || *temp) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "bad number for offset (%s)", optarg);
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                if (conversion < 0 || (unsigned long) conversion > UINT_MAX) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "offset must be between 0 and %u", UINT_MAX);
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                state->offset = (unsigned long) conversion;
+                break;
+            
+            }
+            
+            case OPTION_STATUS: {
+            
+                state->status = 1;
+                break;
+            
+            }
+            
+            default: {
+            
+                report_at (program_name, 0, REPORT_ERROR, "unsupported option '%s'", r);
+                exit (EXIT_FAILURE);
+            
+            }
+        
+        }
+    
+    }
+
+}
+
+
+#ifndef     __PDOS__
+/** Get a single character from the terminal. */
+static char getch () {
+    
+#if     defined(_WIN32) && !defined (__PDOS__)
+
+    KEY_EVENT_RECORD keyevent;
+    INPUT_RECORD irec;
+    DWORD events;
+    
+    for (;;) {
+    
+        ReadConsoleInput (GetStdHandle (STD_INPUT_HANDLE), &irec, 1, &events);
+        
+        if (irec.EventType == KEY_EVENT && ((KEY_EVENT_RECORD) irec.Event.KeyEvent).bKeyDown) {
+        
+            const int ca = (int) keyevent.uChar.AsciiChar;
+            const int cv = (int) keyevent.wVirtualKeyCode;
+            const int key = ca == 0 ? -cv : ca + (ca > 0 ? 0 : 256);
+            
+            keyevent = (KEY_EVENT_RECORD) irec.Event.KeyEvent;
+            
+            switch (key) {
+            
+                case  -16: continue;    /* disable Shift */
+                case  -17: continue;    /* disable Ctrl / AltGr */
+                case  -18: continue;    /* disable Alt / AltGr */
+                case -220: continue;    /* disable first detection of "^" key (not "^" symbol) */
+                case -221: continue;    /* disable first detection of "'" key (not "'" symbol) */
+                case -191: continue;    /* disable AltGr + "#" */
+                case  -52: continue;    /* disable AltGr + "4" */
+                case  -53: continue;    /* disable AltGr + "5" */
+                case  -54: continue;    /* disable AltGr + "6" */
+                case  -12: continue;    /* disable num block 5 with num lock deactivated */
+                case   13: return  10;  /* enter */
+                case  -46: return 127;  /* delete */
+                case  -49: return 251;  /* Ã‚¹ */
+                case    0: continue;
+                case    1: continue;    /* disable Ctrl + a (selects all text) */
+                case    2: continue;    /* disable Ctrl + b */
+                case    3: continue;    /* disable Ctrl + c (terminates program) */
+                case    4: continue;    /* disable Ctrl + d */
+                case    5: continue;    /* disable Ctrl + e */
+                case    6: continue;    /* disable Ctrl + f (opens search) */
+                case    7: continue;    /* disable Ctrl + g */
+                case   10: continue;    /* disable Ctrl + j */
+                case   11: continue;    /* disable Ctrl + k */
+                case   12: continue;    /* disable Ctrl + l */
+                case   14: continue;    /* disable Ctrl + n */
+                case   15: continue;    /* disable Ctrl + o */
+                case   16: continue;    /* disable Ctrl + p */
+                case   17: continue;    /* disable Ctrl + q */
+                case   18: continue;    /* disable Ctrl + r */
+                case   19: continue;    /* disable Ctrl + s */
+                case   20: continue;    /* disable Ctrl + t */
+                case   21: continue;    /* disable Ctrl + u */
+                case   22: continue;    /* disable Ctrl + v (inserts clipboard) */
+                case   23: continue;    /* disable Ctrl + w */
+                case   24: continue;    /* disable Ctrl + x */
+                case   25: continue;    /* disable Ctrl + y */
+                case   26: continue;    /* disable Ctrl + z */
+                default: return key;    /* any other ASCII/virtual character */
+            
+            }
+        
+        }
+    
+    }
+
+#else
+
+    int key;
+    
+#if     defined (__PDOS__)
+
+    setvbuf (stdin, NULL, _IONBF, 0);
+    
+    for (;;) {
+
+#else
+
+    struct termios term;
+    int nbbytes;
+    
+    tcgetattr (0, &term);
+    
+    for (;;) {
+    
+        term.c_lflag &= ~(ICANON | ECHO);                   /* turn off line buffering and echoing */
+        tcsetattr (0, TCSANOW, &term);
+        
+        ioctl (0, FIONREAD, &nbbytes);                      /* 0 is STDIN */
+        
+        while (!nbbytes) {
+        
+            sleep (1);
+            fflush (stdout);
+            
+            ioctl (0, FIONREAD, &nbbytes);                  /* 0 is STDIN */
+        
+        }
+
+#endif
+        
+        key = (int) getchar ();
+        
+        if (key == 27 || key == 194 || key == 195) {        /* escape, 194/195 is escape for Ã‚°ÃŸÂ´Ã¤Ã¶Ã¼Ã„ÖÜ */
+        
+            key = (int) getchar ();
+            
+            if (key == 91) {                                /* [ following escape */
+            
+                key = (int) getchar ();                     /* get code of next char after \e[ */
+                
+                if (key == 49) {                            /* F5-F8 */
+                
+                    key = 62 + (int) getchar ();            /* 53, 55-57 */
+                    
+                    if (key == 115) {
+                        key++;                              /* F5 code is too low by 1 */
+                    }
+                    
+                    getchar ();                             /* take in following ~ (126), but discard code */
+                
+                } else if (key == 50) {                     /* insert or F9-F12 */
+                
+                    key = (int) getchar ();
+                    
+                    if (key == 126) {                       /* insert */
+                        key = 45;
+                    } else {                                /* F9-F12 */
+                    
+                        key += 71;                          /* 48, 49, 51, 52 */
+                        
+                        if (key < 121) {
+                            key++;                          /* F11 and F12 are too low by 1 */
+                        }
+                        
+                        getchar ();                         /* take in following ~ (126), but discard code */
+                    
+                    }
+                
+                } else if (key == 51 || key == 53 || key == 54) {               /* delete, page up/down */
+                    getchar ();                             /* take in following ~ (126), but discard code */
+                }
+            
+            } else if (key == 79) {                         /* F1-F4 */
+                key = 32 + (int) getchar ();                /* 80-83 */
+            }
+            
+            key = -key;                                     /* use negative numbers for escaped keys */
+        
+        }
+        
+#if     defined (__PDOS__)
+        setvbuf (stdin, NULL, _IOLBF, 0);
+#else
+
+        term.c_lflag |= (ICANON | ECHO);                    /* turn on line buffering and echoing */
+        tcsetattr (0, TCSANOW, &term);
+
+#endif
+        
+        switch (key) {
+        
+            case  127: return (char)   8;   /* backspace */
+            case  -27: return (char)  27;   /* escape */
+            case  -51: return (char) 127;   /* delete */
+            case -164: return (char) 132;   /* ÃƒÂ¤ */
+            case -182: return (char) 148;   /* ÃƒÂ¶ */
+            case -188: return (char) 129;   /* ÃƒÂ¼ */
+            case -132: return (char) 142;   /* Ãƒâ€ž */
+            case -150: return (char) 153;   /* Ãƒâ€“ */
+            case -156: return (char) 154;   /* ÃƒÅ“ */
+            case -159: return (char) 225;   /* ÃƒÅ¸ */
+            case -181: return (char) 230;   /* Ã‚µ */
+            case -167: return (char) 245;   /* Ã‚§ */
+            case -176: return (char) 248;   /* Ã‚° */
+            case -178: return (char) 253;   /* Ã‚² */
+            case -179: return (char) 252;   /* Ã‚³ */
+            case -180: return (char) 239;   /* Ã‚´ */
+            case  -65: return (char) -38;   /* up arrow */
+            case  -66: return (char) -40;   /* down arrow */
+            case  -68: return (char) -37;   /* left arrow */
+            case  -67: return (char) -39;   /* right arrow */
+            case  -53: return (char) -33;   /* page up */
+            case  -54: return (char) -34;   /* page down */
+            case  -72: return (char) -36;   /* pos1 */
+            case  -70: return (char) -35;   /* end */
+            case    0: continue;
+            case    1: continue;            /* disable Ctrl + a */
+            case    2: continue;            /* disable Ctrl + b */
+            case    3: continue;            /* disable Ctrl + c (terminates program) */
+            case    4: continue;            /* disable Ctrl + d */
+            case    5: continue;            /* disable Ctrl + e */
+            case    6: continue;            /* disable Ctrl + f */
+            case    7: continue;            /* disable Ctrl + g */
+            case    8: continue;            /* disable Ctrl + h */
+            case   11: continue;            /* disable Ctrl + k */
+            case   12: continue;            /* disable Ctrl + l */
+            case   13: continue;            /* disable Ctrl + m */
+            case   14: continue;            /* disable Ctrl + n */
+            case   15: continue;            /* disable Ctrl + o */
+            case   16: continue;            /* disable Ctrl + p */
+            case   17: continue;            /* disable Ctrl + q */
+            case   18: continue;            /* disable Ctrl + r */
+            case   19: continue;            /* disable Ctrl + s */
+            case   20: continue;            /* disable Ctrl + t */
+            case   21: continue;            /* disable Ctrl + u */
+            case   22: continue;            /* disable Ctrl + v */
+            case   23: continue;            /* disable Ctrl + w */
+            case   24: continue;            /* disable Ctrl + x */
+            case   25: continue;            /* disable Ctrl + y */
+            case   26: continue;            /* disable Ctrl + z (terminates program) */
+            default: return key;            /* any other ASCII character */
+        
+        }
+    
+    }
+
+#endif
+
+}
+#endif
+
+static int check_overwrite (const char *fn) {
+
+#if     defined (__PDOS__)
+
+    report_at (NULL, 0, REPORT_WARNING, "overwriting %s", fn);
+    return 1;
+
+#else
+
+    int ch;
+    
+    printf ("File %s already exists.\n", fn);
+    printf ("o)verwrite s)skip (os): ");
+    
+    while ((ch = tolower (getch ())) < 0) {}
+    
+    if (isspace (ch)) {
+        printf ("\n");
+    } else {
+        printf ("%c\n", ch);
+    }
+    
+    if (ch == 'o') {
+        return 1;
+    } else if (ch == 's') {
+        return 0;
+    }
+    
+    return check_overwrite (fn);
+
+#endif
+
+}
+
+
+static int seekto (long offset) {
+    return fseek (ofp, (state->offset * 512) + offset, SEEK_SET);
+}
+
+
+static int get_next_entry (struct dir_info *di, struct msdos_dirent *de);
+static int open_dir (const char *target, struct dir_info *di);
+
+static unsigned int get_fat_entry (unsigned char *scratch, unsigned int cluster);
+static int set_fat_entry (unsigned char *scratch, unsigned int cluster, unsigned int value);
+
+static unsigned int get_free_fat (unsigned char *scratch);
+
+static int canonical_to_dir (char *dest, const char *src) {
+
+    static const char invalid_chars[] = "\"*+,./:;<=>?[\\]|";
+    
+    int i, j;
+    int namelen = 0, dots = 0, extlen = 0;
+    
+    memset (dest, ' ', 11);
+    
+    if (*src == '\0' || *src == '.') {
+        return -1;
+    }
+    
+    for (j = i = 0; *src != '\0'; i++) {
+    
+        int c = (unsigned char) *src++;
+        
+        if (c == '/' || c == '\\') {
+            break;
+        }
+        
+        if (i >= 12) {
+            return -1;
+        }
+        
+        if (i == 0 && c == 0xE5) {
+        
+            /**
+             * 0xE5 in the first character of the name is a marker for delected files,
+             * it needs to be translated to 0x05.
+             */
+            c = 0x05;
+        
+        } else if (c == '.') {
+        
+            if (dots++) {
+                return -1;
+            }
+            
+            j = 8;
+            continue;
+        
+        }
+        
+        if (c <= 0x20 || strchr (invalid_chars, c)) {
+            return -1;
+        }
+        
+        if (dots) {
+        
+            if (++extlen > 3) {
+                return -1;
+            }
+        
+        } else {
+        
+            if (++namelen > 8) {
+                return -1;
+            }
+        
+        }
+        
+        if (c >= 'a' && c <= 'z') {
+            c -= 'a' - 'A';
+        }
+        
+        dest[j++] = c;
+    
+    }
+    
+    return 0;
+
+}
+
+static int copy_file (const char *source, struct file_info *fi, const char *fname) {
+
+    struct msdos_dirent de;
+    struct fat32_fsinfo *info;
+    
+    unsigned int free_clusters;
+    unsigned int next_cluster;
+    
+    FILE *ifp;
+    long bytes;
+    
+    unsigned char *buffer, *temp;
+    unsigned int i, j, sector;
+    
+    unsigned int clust_size = sectors_per_cluster * 512;
+    unsigned int start_cluster = 0, prev_cluster = 0, size = 0;
+    
+    unsigned long flen, copied = 0;
+    unsigned int num_clusters = 0, *cluster_chain;
+    
+    if ((ifp = fopen (source, "rb")) == NULL) {
+        return -1;
+    }
+    
+    fseek (ifp, 0, SEEK_END);
+    
+    if ((flen = ftell (ifp)) > UINT_MAX) {
+    
+        fclose (ifp);
+        return -1;
+    
+    }
+    
+    if (!(cluster_chain = malloc (cluster_count * sizeof (unsigned int)))) {
+    
+        fclose (ifp);
+        return -1;
+    
+    }
+    
+    num_clusters = (flen + clust_size -  1) / clust_size;
+    memset (cluster_chain, 0, cluster_count * sizeof (unsigned int));
+    
+    for (i = 2, j = 0; i < cluster_count && j < num_clusters; i++) {
+    
+        if (get_fat_entry (fi->scratch, i) == 0) {
+            cluster_chain[j++] = i;
+        }
+    
+    }
+    
+    if (j < num_clusters) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Not enough free space available");
+        free (cluster_chain);
+        
+        fclose (ifp);
+        return -1;
+    
+    }
+    
+    fseek (ifp, 0, SEEK_SET);
+    
+    if (!(buffer = (unsigned char *) malloc (clust_size))) {
+    
+        free (cluster_chain);
+        
+        fclose (ifp);
+        return -1;
+    
+    }
+    
+    memset (buffer, 0, clust_size);
+    j = 0;
+    
+    while ((bytes = fread (buffer, 1, clust_size, ifp)) > 0) {
+    
+        size += bytes;
+        i = cluster_chain[j++];
+        
+        if (i == cluster_count) {
+        
+            free (cluster_chain);
+            free (buffer);
+            
+            fclose (ifp);
+            return -1;
+        
+        }
+        
+        if (start_cluster == 0) {
+        
+            start_cluster = i;
+            
+            if (seekto ((unsigned long) fi->dir_sector * 512) || fread (fi->scratch, 512, 1, ofp) != 1) {
+            
+                free (cluster_chain);
+                free (buffer);
+                
+                fclose (ifp);
+                return -1;
+            
+            }
+            
+            de = (((struct msdos_dirent *) fi->scratch)[fi->dir_offset]);
+            
+            write721_to_byte_array (de.startlo, start_cluster);
+            write721_to_byte_array (de.starthi, start_cluster >> 16);
+            
+            memcpy (&(((struct msdos_dirent *) fi->scratch)[fi->dir_offset]), &de, sizeof (de));
+            
+            if (seekto ((unsigned long) fi->dir_sector * 512) || fwrite (fi->scratch, 512, 1, ofp) != 1) {
+                
+                free (cluster_chain);
+                free (buffer);
+                
+                fclose (ifp);
+                return -1;
+            
+            }
+        
+        } else {
+        
+            if (prev_cluster == 0) {
+                
+                free (cluster_chain);
+                free (buffer);
+                
+                fclose (ifp);
+                return -1;
+            
+            }
+            
+            if (set_fat_entry (fi->scratch, prev_cluster, i) < 0) {
+                
+                free (cluster_chain);
+                free (buffer);
+                
+                fclose (ifp);
+                return -1;
+            
+            }
+        
+        }
+        
+        sector = data_area + ((i - 2) * sectors_per_cluster);
+        
+        if (seekto ((unsigned long) sector * 512)) {
+        
+            free (cluster_chain);
+            free (buffer);
+            
+            fclose (ifp);
+            return -1;
+        
+        }
+        
+        temp = buffer;
+        
+        while (bytes > 0) {
+        
+            if (fwrite (temp, 512, 1, ofp) != 1) {
+                
+                free (cluster_chain);
+                free (buffer);
+                
+                fclose (ifp);
+                return -1;
+            
+            }
+            
+            if (bytes > 512) {
+                copied += 512;
+            } else {
+                copied += bytes;
+            }
+            
+            if (state->status) {
+            
+                double percent = (double) copied / (double) flen;
+                printf ("\rcopied %lu bytes (%.2f%%) to %s", copied, percent * 100, fname);
+            
+            }
+            
+            bytes -= 512;
+            temp += 512;
+        
+        }
+        
+        memset (buffer, 0, clust_size);
+        
+        if (set_fat_entry (fi->scratch, i, 0x0FFFFFF8) < 0) {
+        
+            free (cluster_chain);
+            free (buffer);
+            
+            fclose (ifp);
+            return -1;
+        
+        }
+        
+        prev_cluster = i;
+        if (feof (ifp)) { break; }
+    
+    }
+    
+    free (cluster_chain);
+    free (buffer);
+    
+    fclose (ifp);
+    
+    if (seekto ((unsigned long) fi->dir_sector * 512) || fread (fi->scratch, 512, 1, ofp) != 1) {
+        return -1;
+    }
+    
+    if (size_fat == 32) {
+    
+        if (seekto ((unsigned long) info_sector * 512) || fread (fi->scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+        
+        info = (struct fat32_fsinfo *) (fi->scratch + 0x1e0);
+        
+        free_clusters = (unsigned int) info->free_clusters[0] | (((unsigned int) info->free_clusters[1]) << 8) | (((unsigned int) info->free_clusters[2]) << 16) | (((unsigned int) info->free_clusters[3]) << 24);
+        next_cluster = (unsigned int) info->next_cluster[0] | (((unsigned int) info->next_cluster[1]) << 8) | (((unsigned int) info->next_cluster[2]) << 16) | (((unsigned int) info->next_cluster[3]) << 24);
+        
+        free_clusters -= num_clusters;
+        next_cluster += num_clusters;
+        
+        write741_to_byte_array (info->free_clusters, free_clusters);
+        write741_to_byte_array (info->next_cluster, next_cluster);
+        
+        if (seekto ((unsigned long) info_sector * 512) || fwrite (fi->scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+    
+    }
+    
+    if (seekto ((unsigned long) fi->dir_sector * 512) || fread (fi->scratch, 512, 1, ofp) != 1) {
+        return -1;
+    }
+    
+    de = (((struct msdos_dirent *) fi->scratch)[fi->dir_offset]);
+    
+    write741_to_byte_array (de.size, size);
+    memcpy (&(((struct msdos_dirent *) fi->scratch)[fi->dir_offset]), &de, sizeof (de));
+    
+    if (seekto ((unsigned long) fi->dir_sector * 512) || fwrite (fi->scratch, 512, 1, ofp) != 1) {
+        return -1;
+    }
+    
+    return 0;
+
+}
+
+static int get_free_dirent (const char *path, struct dir_info *di, struct msdos_dirent *de) {
+
+    int entry;
+    unsigned int i, tempclust;
+    
+    if (open_dir (path, di) < 0) {
+        return -1;
+    }
+    
+    entry = 0;
+    di->flags |= 0x01;
+    
+    do {
+    
+        entry = get_next_entry (di, de);
+        
+        /*if (entry == 0 && (!de->name[0] || de->name[0] == 0xE5 || (de->attr & ATTR_LONG_NAME) == ATTR_LONG_NAME)) {*/
+        if (entry == 0 && (!de->name[0] || de->name[0] == 0xE5)) {
+        
+            /*if (de->name[0] == 0xE5 || (de->attr & ATTR_LONG_NAME) == ATTR_LONG_NAME) {*/
+            if (de->name[0] == 0xE5) {
+                di->current_entry--;
+            }
+            
+            return 0;
+        
+        } else if (entry == -1) {
+            return -1;
+        } else if (entry == 1) {
+        
+            if ((tempclust = get_free_fat (di->scratch) == 0x0FFFFFF7)) {
+                return -1;
+            }
+            
+            memset (di->scratch, 0, 512);
+            
+            for (i = 0; i < sectors_per_cluster; i++) {
+            
+                unsigned long offset = (unsigned long) (data_area + ((tempclust - 2) * sectors_per_cluster) + i);
+                
+                if (seekto (offset * 512)) {
+                    return -1;
+                }
+                
+                if (fwrite (di->scratch, 512, 1, ofp) != 1) {
+                    return -1;
+                }
+            
+            }
+            
+            if (set_fat_entry (di->scratch, di->current_cluster, tempclust) < 0) {
+                return -1;
+            }
+            
+            di->current_cluster = tempclust;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            if (size_fat == 12) {
+                tempclust = 0x0FF8;
+            } else if (size_fat == 16) {
+                tempclust = 0xFFF8;
+            } else if (size_fat == 32) {
+            
+                struct fat32_fsinfo *info;
+                
+                unsigned int free_clusters;
+                unsigned int next_cluster;
+                
+                tempclust = 0x0ffffff8;
+                
+                if (seekto ((unsigned long) info_sector * 512) || fread (di->scratch, 512, 1, ofp) != 1) {
+                    return -1;
+                }
+                
+                info = (struct fat32_fsinfo *) (di->scratch + 0x1e0);
+                
+                free_clusters = (unsigned int) info->free_clusters[0] | (((unsigned int) info->free_clusters[1]) << 8) | (((unsigned int) info->free_clusters[2]) << 16) | (((unsigned int) info->free_clusters[3]) << 24);
+                next_cluster = (unsigned int) info->next_cluster[0] | (((unsigned int) info->next_cluster[1]) << 8) | (((unsigned int) info->next_cluster[2]) << 16) | (((unsigned int) info->next_cluster[3]) << 24);
+                
+                free_clusters--;
+                next_cluster++;
+                
+                write741_to_byte_array (info->free_clusters, free_clusters);
+                write741_to_byte_array (info->next_cluster, next_cluster);
+                
+                if (seekto ((unsigned long) info_sector * 512) || fwrite (di->scratch, 512, 1, ofp) != 1) {
+                    return -1;
+                }
+            
+            } else {
+                return -1;
+            }
+        
+        }
+    
+    } while (!entry);
+    
+    /* We should't get here! */
+    return -1;
+
+}
+
+static int get_next_entry (struct dir_info *di, struct msdos_dirent *de) {
+
+    unsigned int tempclust;
+    unsigned long offset;
+    
+    if (di->current_entry >= 512 / sizeof (*de)) {
+    
+        di->current_entry = 0;
+        di->current_sector++;
+        
+        if (di->current_cluster == 0) {
+        
+            unsigned long offset;
+            
+            if (di->current_sector * (512 / sizeof (*de)) >= root_entries) {
+                return -1;
+            }
+            
+            offset = (unsigned long) root_dir + di->current_sector;
+            
+            if (seekto (offset * 512) || fread (di->scratch, 512, 1, ofp) != 1) {
+                return -1;
+            }
+        
+        } else {
+        
+            if (di->current_sector >= sectors_per_cluster) {
+            
+                di->current_sector = 0;
+                
+                if ((size_fat == 12 && di->current_cluster >= 0x0FF7) || (size_fat == 16 && di->current_cluster >= 0xFFF7) || (size_fat == 32 && di->current_cluster >= 0x0FFFFFF7)) {
+                
+                    if (!(di->flags & 0x01)) {
+                        return -1;
+                    }
+                    
+                    return 1;
+                
+                }
+                
+                tempclust = get_fat_entry (di->scratch, di->current_cluster);
+                
+                if (size_fat == 12 && tempclust < 0x0FF8) {
+                    di->current_cluster = tempclust;
+                } else if (size_fat == 16 && tempclust < 0xFFF8) {
+                    di->current_cluster = tempclust;
+                } else if (size_fat == 32 && tempclust < 0x0FFFFFF8) {
+                    di->current_cluster = tempclust;
+                } else {
+                
+                    tempclust = get_free_fat (di->scratch);
+                    
+                    if (set_fat_entry (di->scratch, di->current_cluster, tempclust) < 0) {
+                        return -1;
+                    }
+                    
+                    di->current_cluster = tempclust;
+                    
+                    if (set_fat_entry (di->scratch, di->current_cluster, 0x0FFFFFF8) < 0) {
+                        return -1;
+                    }
+                
+                }
+            
+            }
+            
+            offset = (unsigned long) data_area;
+            offset += ((di->current_cluster - 2) * sectors_per_cluster);
+            offset += di->current_sector;
+            
+            if (seekto (offset * 512) || fread (di->scratch, 512, 1, ofp) != 1) {
+                return -1;
+            }
+        
+        }
+    
+    }
+    
+    memcpy (de, &(((struct msdos_dirent *) di->scratch)[di->current_entry]), sizeof (*de));
+    
+    if (de->name[0] == 0) {
+    
+        if (di->flags & 0x01) {
+            return 0;
+        }
+        
+        return -1;
+    
+    }
+    
+    if (de->name[0] == 0x05) {
+    
+        de->name[0] = 0xE5;
+        return 0;
+    
+    }
+    
+    di->current_entry++;
+    return 0;
+
+}
+
+static int open_dir (const char *target, struct dir_info *di) {
+
+    di->flags = 0;
+    
+    if (!strlen ((char *) target) || (strlen ((char *) target) == 1 && (target[0] == '/' || target[0] == '\\'))) {
+    
+        unsigned long offset;
+        
+        if (size_fat == 32) {
+        
+            di->current_cluster = root_cluster;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) data_area + ((di->current_cluster - 2) * sectors_per_cluster);
+        
+        } else {
+        
+            di->current_cluster = 0;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) root_dir;
+        
+        }
+        
+        if (seekto (offset * 512) || fread (di->scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+    
+    } else {
+    
+        unsigned char tmpfn[12];
+        unsigned char *ptr;
+        
+        struct msdos_dirent de;
+        unsigned int result;
+        
+        unsigned long offset;
+        
+        if (size_fat == 32) {
+        
+            di->current_cluster = root_cluster;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) data_area + ((di->current_cluster - 2) * sectors_per_cluster);
+        
+        } else {
+        
+            di->current_cluster = 0;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) root_dir;
+        
+        }
+        
+        if (seekto (offset * 512) || fread (di->scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+        
+        ptr = (unsigned char *) target;
+        
+        while ((*ptr == '/' || *ptr == '\\') && *ptr) {
+            ptr++;
+        }
+        
+        while (*ptr) {
+        
+            if (canonical_to_dir ((char *) tmpfn, (char *) ptr) < 0) {
+            
+                fprintf (stderr, "Failed to convert to 8:3\n");
+                return -1;
+            
+            }
+            
+            de.name[0] = 0;
+            
+            do {
+                result = get_next_entry (di, &de);
+            } while (!result && memcmp (de.name, tmpfn, 11));
+            
+            if (!memcmp (de.name, tmpfn, 11) && (de.attr & ATTR_DIR) == ATTR_DIR) {
+            
+                unsigned long offset;
+                
+                di->current_cluster = (unsigned int) de.startlo[0] | ((unsigned int) de.startlo[1]) << 8 | ((unsigned int) de.starthi[0]) << 16 | ((unsigned int) de.starthi[1]) << 24;
+                di->current_entry = 0;
+                di->current_sector = 0;
+                
+                offset = (unsigned long) (data_area + ((di->current_cluster - 2) * sectors_per_cluster));
+                
+                if (seekto (offset * 512)) {
+                    return -1;
+                }
+                
+                if (fread (di->scratch, 512, 1, ofp) != 1) {
+                    return -1;
+                }
+            
+            } else if (!memcmp (de.name, tmpfn, 11) && !(de.attr & ATTR_DIR)) {
+                return -1;
+            }
+            
+            while (*ptr != '/' && *ptr != '\\' && *ptr) {
+                ptr++;
+            }
+            
+            if (*ptr == '/' || *ptr == '\\') {
+                ptr++;
+            }
+        
+        }
+        
+        if (!di->current_cluster) {
+            return -1;
+        }
+    
+    }
+    
+    return 0;
+
+}
+
+static int open_file (const char *target, unsigned char *scratch, struct file_info *fi) {
+
+    unsigned char tmppath[PATH_MAX];
+    unsigned char filename[12];
+    unsigned char *p;
+    
+    struct dir_info di;
+    struct msdos_dirent de;
+    
+    unsigned short date;
+    unsigned short time;
+    
+    unsigned int tempclust;
+    
+    /* Zero out file structure. */
+    memset (fi, 0, sizeof (*fi));
+    
+    /* Get a local copy of the target.  If it's larger than PATH_MAX, abort. */
+    strncpy ((char *) tmppath, (char *) target, PATH_MAX);
+    tmppath[PATH_MAX - 1] = 0;
+    
+    if (strcmp ((char *) target, (char *) tmppath)) {
+        return -1;
+    }
+    
+    /* Strip leading seperators. */
+    while (tmppath[0] == '/' || tmppath[0] == '\\') {
+        strcpy ((char *) tmppath, (char *) tmppath + 1);
+    }
+    
+    /* Parse filename off the end of the suppiled target. */
+    p = tmppath;
+    
+    while (*(p++));
+    
+    p--;
+    
+    while (p > tmppath && *p != '/' && *p != '\\') {
+        p--;
+    }
+    
+    if (*p == '/' || *p == '\\') {
+        p++;
+    }
+    
+    if (canonical_to_dir ((char *) filename, (char *) p) < 0) {
+    
+        fprintf (stderr, "Failed to convert to 8:3\n");
+        return -1;
+    
+    }
+    
+    if (p > tmppath) {
+        p--;
+    }
+    
+    if (*p == '/' || *p == '\\' || p == tmppath) {
+        *p = 0;
+    }
+    
+    di.scratch = scratch;
+    
+    if (open_dir ((char *) tmppath, &di) < 0) {
+    
+        fprintf (stderr, "Failed to open directory\n");
+        return -1;
+    
+    }
+    
+    while (!get_next_entry (&di, &de)) {
+    
+        if (!memcmp (de.name, filename, 11)) {
+        
+            di.current_entry--;
+            
+            if (de.attr & ATTR_DIR) {
+                return -1;
+            }
+            
+            if (!check_overwrite ((char *) target)) {
+                return 0;
+            }
+            
+            fi->cluster = (unsigned int) de.startlo[0] | ((unsigned int) de.startlo[1]) << 8 | ((unsigned int) de.starthi[0]) << 16 | ((unsigned int) de.starthi[1]) << 24;
+            
+            for (;;) {
+            
+                if (size_fat == 12 && fi->cluster >= 0x0FF8) {
+                    break;
+                } else if (size_fat == 16 && fi->cluster >= 0xFFF8) {
+                    break;
+                } else if (size_fat == 32 && fi->cluster >= 0x0FFFFFF8) {
+                    break;
+                }
+                
+                tempclust = get_fat_entry (scratch, fi->cluster);
+                
+                if (set_fat_entry (scratch, fi->cluster, 0) < 0) {
+                    return -1;
+                }
+                
+                fi->cluster = tempclust;
+                
+                if (!fi->cluster || fi->cluster == 0x0FFFFFFF7) {
+                    break;
+                }
+            
+            }
+            
+            fi->cluster = 0;
+            
+            if (seekto ((unsigned long) fi->dir_sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+                return -1;
+            }
+            
+            date = generate_datestamp ();
+            time = generate_timestamp ();
+            
+            memset (&de, 0, sizeof (de));
+            memcpy (de.name, filename, 11);
+            
+            de.attr = ATTR_ARCHIVE;
+            
+            write721_to_byte_array (de.ctime, time);
+            write721_to_byte_array (de.cdate, date);
+            write721_to_byte_array (de.adate, date);
+            write721_to_byte_array (de.time, time);
+            write721_to_byte_array (de.date, date);
+            
+            fi->pointer = 0;
+            
+            if (di.current_cluster == 0) {
+                fi->dir_sector = root_dir + di.current_sector;
+            } else {
+                fi->dir_sector = data_area + ((di.current_cluster - 2) * sectors_per_cluster)  + di.current_sector;
+            }
+            
+            /*fi->dir_offset = di.current_entry - 1;*/
+            fi->dir_offset = di.current_entry;
+            
+            if (seekto ((unsigned long) fi->dir_sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+                return -1;
+            }
+            
+            memcpy (&(((struct msdos_dirent *) scratch)[fi->dir_offset]), &de, sizeof (de));
+            
+            if (seekto ((unsigned long) fi->dir_sector * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+                return -1;
+            }
+            
+            return 0;
+        
+        }
+    
+    }
+    
+    if (get_free_dirent ((char *) tmppath, &di, &de) < 0) {
+        return -1;
+    }
+    
+    date = generate_datestamp ();
+    time = generate_timestamp ();
+    
+    memset (&de, 0, sizeof (de));
+    memcpy (de.name, filename, 11);
+    
+    de.attr = ATTR_ARCHIVE;
+    
+    write721_to_byte_array (de.ctime, time);
+    write721_to_byte_array (de.cdate, date);
+    write721_to_byte_array (de.adate, date);
+    write721_to_byte_array (de.time, time);
+    write721_to_byte_array (de.date, date);
+    
+    fi->pointer = 0;
+    
+    if (di.current_cluster == 0) {
+        fi->dir_sector = root_dir + di.current_sector;
+    } else {
+        fi->dir_sector = data_area + ((di.current_cluster - 2) * sectors_per_cluster)  + di.current_sector;
+    }
+    
+    /*fi->dir_offset = di.current_entry - 1;*/
+    fi->dir_offset = di.current_entry;
+    
+    if (seekto ((unsigned long) fi->dir_sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+        return -1;
+    }
+    
+    memcpy (&(((struct msdos_dirent *) scratch)[fi->dir_offset]), &de, sizeof (de));
+    
+    if (seekto ((unsigned long) fi->dir_sector * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+        return -1;
+    }
+    
+    return 0;
+
+}
+
+static int set_fat_entry (unsigned char *scratch, unsigned int cluster, unsigned int value) {
+
+    unsigned int i, offset, sector;
+    
+    if (size_fat == 12) {
+    
+        offset = cluster + (cluster / 2);
+        value &= 0x0fff;
+    
+    } else if (size_fat == 16) {
+    
+        offset = cluster * 2;
+        value &= 0xffff;
+    
+    } else if (size_fat == 32) {
+    
+        offset = cluster * 4;
+        value &= 0x0fffffff;
+    
+    } else {
+        return -1;
+    }
+    
+    /**
+     * At this point, offset is the BYTE offset of the desired sector from the start
+     * of the FAT.  Calculate the physical sector containing this FAT entry.
+     */
+    sector = (offset / 512) + reserved_sectors;
+    
+    if (seekto (sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+        return -1;
+    }
+    
+    /**
+     * At this point, we "merely" need to extract the relevant entry.  This is
+     * easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry
+     * may span a sector boundary.  The normal way around this is always to
+     * read two FAT sectors, but luxary is (by design intent) unavailable.
+     */
+    offset %= 512;
+    
+    if (size_fat == 12) {
+    
+        if (offset == 511) {
+        
+            if (((cluster * 3) & 0x01) == 0) {
+                scratch[offset] = (unsigned char) (value & 0xFF);
+            } else {
+                scratch[offset] = (unsigned char) ((scratch[offset] & 0x0F) | ((value & 0x0F) << 4));
+            }
+            
+            for (i = 0; i < number_of_fats; i++) {
+            
+                unsigned long temp = sector + (i * sectors_per_fat);
+                
+                if (seekto (temp * 512) < 0 || fwrite (scratch, 512, 1, ofp) != 1) {
+                    return -1;
+                }
+            
+            }
+            
+            sector++;
+            
+            if (seekto (sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+                return -1;
+            }
+            
+            if (((cluster * 3) & 0x01) == 0) {
+                scratch[0] = (unsigned char) ((scratch[0] & 0xF0) | ((value & 0x0F00) >> 8));
+            } else {
+                scratch[0] = (unsigned char) ((value & 0x0FF0) >> 4);
+            }
+            
+            goto _write_fat;
+        
+        } else {
+        
+            if (((cluster * 3) & 0x01) == 0) {
+            
+                scratch[offset] = (unsigned char) (value & 0x00FF);
+                scratch[offset + 1] = (unsigned char) ((scratch[offset + 1] & 0x00F0) | ((value & 0x0F00) >> 8));
+            
+            } else {
+            
+                scratch[offset] = (unsigned char) ((scratch[offset] & 0x000F) | ((value & 0x000F) << 4));
+                scratch[offset + 1] = (unsigned char) ((value & 0x0FF0) >> 4);
+            
+            }
+            
+            goto _write_fat;
+        
+        }
+    
+    } else if (size_fat == 16) {
+    
+        scratch[offset] = (value & 0xFF);
+        scratch[offset + 1] = (value >> 8) & 0xFF;
+        
+        goto _write_fat;
+    
+    } else if (size_fat == 32) {
+    
+        scratch[offset] = (value & 0xFF);
+        scratch[offset + 1] = (value >> 8) & 0xFF;
+        scratch[offset + 2] = (value >> 16) & 0xFF;
+        scratch[offset + 3] = (scratch[offset + 3] & 0xF0) | ((value >> 24) & 0xFF);
+        
+        goto _write_fat;
+    
+    }
+    
+    return -1;
+
+_write_fat:
+
+    for (i = 0; i < number_of_fats; i++) {
+    
+        unsigned long temp = sector + (i * sectors_per_fat);
+        
+        if (seekto (temp * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+    
+    }
+    
+    return 0;
+
+}
+
+static unsigned int get_fat_entry (unsigned char *scratch, unsigned int cluster) {
+
+    unsigned int offset, sector, result;
+    
+    if (size_fat == 12) {
+        offset = cluster + (cluster / 2);
+    } else if (size_fat == 16) {
+        offset = cluster * 2;
+    } else if (size_fat == 32) {
+        offset = cluster * 4;
+    } else {
+        return 0x0FFFFFF7;
+    }
+    
+    sector = (offset / 512) + reserved_sectors;
+    
+    if (seekto ((unsigned long) sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+        return 0x0FFFFFF7;
+    }
+    
+    offset %= 512;
+    
+    if (size_fat == 12) {
+    
+        if (offset == 511) {
+        
+            result = (unsigned int) scratch[offset];
+            sector++;
+            
+            if (seekto ((unsigned long) sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+                return 0x0FFFFFF7;
+            }
+            
+            result |= ((unsigned int) scratch[0]) << 8;
+        
+        } else {
+            result = (unsigned int) scratch[offset] | ((unsigned int) scratch[offset + 1]) << 8;
+        }
+        
+        if (cluster & 1) {
+            result = result >> 4;
+        } else {
+            result = result & 0x0FFF;
+        }
+    
+    } else if (size_fat == 16) {
+        result = (unsigned int) scratch[offset] | ((unsigned int) scratch[offset + 1]) << 8;
+    } else if (size_fat == 32) {
+        result = ((unsigned int) scratch[offset] | ((unsigned int) scratch[offset + 1]) << 8 | ((unsigned int) scratch[offset + 2]) << 16 | ((unsigned int) scratch[offset + 3]) << 24) & 0x0FFFFFFF;
+    } else {
+        result = 0x0FFFFFF7;
+    }
+    
+    return result;
+
+}
+
+static unsigned int get_free_fat (unsigned char *scratch) {
+
+    unsigned int i, result;
+    
+    for (i = 2; i < cluster_count; i++) {
+    
+        if (!(result = get_fat_entry (scratch, i))) {
+            return i;
+        }
+    
+    }
+    
+    return 0x0FFFFFF7;
+
+}
+
+
+int get_file (const char *source, unsigned char *scratch, struct file_info *fi) {
+
+    unsigned char tmppath[PATH_MAX];
+    unsigned char filename[12];
+    unsigned char *p;
+    
+    struct dir_info di;
+    struct msdos_dirent de;
+    
+    /* Zero out file structure. */
+    memset (fi, 0, sizeof (*fi));
+    
+    /* Get a local copy of the target.  If it's larger than PATH_MAX, abort. */
+    strncpy ((char *) tmppath, (char *) source, PATH_MAX);
+    tmppath[PATH_MAX - 1] = 0;
+    
+    if (strcmp ((char *) source, (char *) tmppath)) {
+        return -1;
+    }
+    
+    /* Strip leading seperators. */
+    while (tmppath[0] == '/' || tmppath[0] == '\\') {
+        strcpy ((char *) tmppath, (char *) tmppath + 1);
+    }
+    
+    /* Parse filename off the end of the suppiled target. */
+    p = tmppath;
+    
+    while (*(p++));
+    
+    p--;
+    
+    while (p > tmppath && *p != '/' && *p != '\\') {
+        p--;
+    }
+    
+    if (*p == '/' || *p == '\\') {
+        p++;
+    }
+    
+    if (canonical_to_dir ((char *) filename, (char *) p) < 0) {
+    
+        fprintf (stderr, "Failed to convert to 8:3\n");
+        return -1;
+    
+    }
+    
+    if (p > tmppath) {
+        p--;
+    }
+    
+    if (*p == '/' || *p == '\\' || p == tmppath) {
+        *p = 0;
+    }
+    
+    di.scratch = scratch;
+    
+    if (open_dir ((char *) tmppath, &di) < 0) {
+    
+        fprintf (stderr, "Failed to open directory\n");
+        return -1;
+    
+    }
+    
+    while (!get_next_entry (&di, &de)) {
+    
+        if (!memcmp (de.name, filename, 11)) {
+        
+            di.current_entry--;
+            
+            if (de.attr & ATTR_DIR) {
+                return -1;
+            }
+            
+            fi->cluster = (unsigned int) de.startlo[0] | ((unsigned int) de.startlo[1]) << 8 | ((unsigned int) de.starthi[0]) << 16 | ((unsigned int) de.starthi[1]) << 24;
+            fi->bytes = (unsigned int) de.size[0] | ((unsigned int) de.size[1]) << 8 | ((unsigned int) de.size[2]) << 16 | ((unsigned int) de.size[3]) << 24;
+            
+            fi->scratch = scratch;
+            return 0;
+        
+        }
+    
+    }
+    
+    return -1;
+
+}
+
+int copy_from_image (const char *target, struct file_info *fi, const char *fname) {
+
+    unsigned long sector, i, copied = 0;
+    FILE *tfp;
+    
+    unsigned long flen = (unsigned long) fi->bytes;
+    
+    if ((tfp = fopen (target, "rb")) == NULL) {
+    
+        if ((tfp = fopen (target, "wb")) == NULL) {
+            return -1;
+        }
+    
+    } else {
+    
+        if (!check_overwrite ((char *) target)) {
+            return 0;
+        }
+        
+        fclose (tfp);
+        
+        if ((fopen (target, "wb")) == NULL) {
+            return -1;
+        }
+    
+    }
+    
+    for (;;) {
+    
+        if (size_fat == 12 && fi->cluster >= 0x0FF8) {
+            break;
+        } else if (size_fat == 16 && fi->cluster >= 0xFFF8) {
+            break;
+        } else if (size_fat == 32 && fi->cluster >= 0x0FFFFFF8) {
+            break;
+        }
+        
+        sector = data_area + ((fi->cluster - 2) * sectors_per_cluster);
+        
+        for (i = 0; i < sectors_per_cluster; ++i) {
+        
+            if (seekto (sector * 512) || fread (fi->scratch, 512, 1, ofp) != 1) {
+            
+                fclose (tfp);
+                remove (target);
+                
+                return -1;
+            
+            }
+            
+            if (fi->bytes > 512) {
+            
+                if (fwrite (fi->scratch, 512, 1, tfp) != 1) {
+                
+                    fclose (tfp);
+                    remove (target);
+                    
+                    return -1;
+                
+                }
+                
+                copied += 512;
+                
+                if (state->status) {
+                
+                    double percent = (double) copied / (double) flen;
+                    printf ("\rcopied %lu bytes (%.2f%%) to %s", copied, percent * 100, fname);
+                
+                }
+                
+                fi->bytes -= 512;
+            
+            } else {
+            
+                if (fwrite (fi->scratch, fi->bytes, 1, tfp) != 1) {
+                
+                    fclose (tfp);
+                    remove (target);
+                    
+                    return -1;
+                
+                }
+                
+                fclose (tfp);
+                return 0;
+            
+            }
+            
+            sector++;
+        
+        }
+        
+        fi->cluster = get_fat_entry (fi->scratch, fi->cluster);
+        
+        if (!fi->cluster || fi->cluster == 0x0FFFFFFF7) {
+            break;
+        }
+    
+    }
+    
+    fclose (tfp);
+    return 0;
+
+}
+
+struct vhd_footer {
+
+    unsigned char cookie[8];
+    unsigned char features[4];
+    
+    struct {
+    
+        unsigned char major[2];
+        unsigned char minor[2];
+    
+    } version;
+    
+    unsigned char next_offset[8];
+    unsigned char modified_time[4];
+    unsigned char creator_name[4];
+    
+    struct {
+    
+        unsigned char major[2];
+        unsigned char minor[2];
+    
+    } creator_version;
+    
+    unsigned char creator_host[4];
+    unsigned char disk_size[8];
+    unsigned char data_size[8];
+    
+    struct {
+    
+        unsigned char cylinders[2];
+        unsigned char heads_per_cyl;
+        unsigned char secs_per_track;
+    
+    } disk_geom;
+    
+    unsigned char disk_type[4];
+    unsigned char checksum[4];
+    unsigned char identifier[16];
+    unsigned char saved_state;
+    unsigned char reserved[427];
+
+};
+
+int main (int argc, char **argv) {
+
+    unsigned char *scratch;
+    char *target, tmppath[PATH_MAX], *source;
+    
+    struct vhd_footer footer;
+    long i;
+    
+    int copy_from = 1;
+    
+    if (argc && *argv) {
+    
+        char *p;
+        program_name = *argv;
+        
+        if ((p = strrchr (program_name, '/')) || (p = strrchr (program_name, '\\'))) {
+            program_name = (p + 1);
+        }
+    
+    }
+    
+    state = xmalloc (sizeof (*state));
+    parse_args (&argc, &argv, 1);
+    
+    if (!state->outfile || state->nb_files < 2) {
+        print_help (EXIT_FAILURE);
+    }
+    
+    target = state->files[state->nb_files - 1];
+    state->nb_files--;
+    
+    if (*target == ':') {
+    
+        ++target;
+        
+        if (*target == ':') {
+        
+            copy_from = 0;
+            ++target;
+        
+        }
+    
+    }
+    
+    if (!*target) {
+        target = "/";
+    }
+    
+    if ((ofp = fopen (state->outfile, "r+b")) == NULL) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "faild to open '%s' for writing", state->outfile);
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (state->chs_align) {
+    
+        long size = sizeof (footer), offset;
+        fseek (ofp, 0, SEEK_END);
+        
+        offset = ftell (ofp) - size;
+        
+        if (!fseek (ofp, offset, SEEK_SET)) {
+        
+            if (fread (&footer, size, 1, ofp) == 1) {
+            
+                if (footer.cookie[0] == 0x63 && footer.cookie[1] == 0x6F && footer.cookie[2] == 0x6E && footer.cookie[3] == 0x65 && footer.cookie[4] == 0x63 && footer.cookie[5] == 0x74 && footer.cookie[6] == 0x69 && footer.cookie[7] == 0x78) {
+                
+                    long old = state->offset, secs_per_track = footer.disk_geom.secs_per_track;
+                    
+                    if (state->offset % secs_per_track) {
+                    
+                        state->offset = ((state->offset / secs_per_track) + 1) * secs_per_track;
+                        report_at (program_name, 0, REPORT_WARNING, "offset changed from %lu to %lu", old, state->offset);
+                    
+                    }
+                
+                }
+            
+            }
+        
+        }
+    
+    }
+    
+    if (seekto ((unsigned long) 0) || fread (&bs, sizeof (bs), 1, ofp) != 1) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "failed whilst reading boot sector");
+        fclose (ofp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (bs.boot_jump[0] != 0xEB || bs.boot_jump[1] < 0x16 || bs.boot_jump[2] != 0x90) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+        fclose (ofp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    sectors_per_cluster = (unsigned int) bs.sectors_per_cluster;
+    reserved_sectors = (unsigned int) bs.reserved_sectors[0] | (((unsigned int) bs.reserved_sectors[1]) << 8);
+    number_of_fats = (unsigned int) bs.no_fats;
+    root_entries = (unsigned int) bs.root_entries[0] | (((unsigned int) bs.root_entries[1]) << 8);
+    total_sectors = (unsigned int) bs.total_sectors16[0] | (((unsigned int) bs.total_sectors16[1]) << 8);
+    sectors_per_fat = (unsigned int) bs.sectors_per_fat16[0] | (((unsigned int) bs.sectors_per_fat16[1]) << 8);
+    
+    if (!sectors_per_cluster || !reserved_sectors || !number_of_fats) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+        fclose (ofp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (!root_entries) {
+    
+        if (bs.boot_jump[1] < 0x58) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        root_cluster = (unsigned int) bs.fat32.root_cluster[0] | (((unsigned int) bs.fat32.root_cluster[1]) << 8) | (((unsigned int) bs.fat32.root_cluster[2]) << 16) | (((unsigned int) bs.fat32.root_cluster[3]) << 24);
+        
+        if (!root_cluster) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (!total_sectors) {
+    
+        if (bs.boot_jump[1] < 0x22) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        total_sectors = (unsigned int) bs.total_sectors32[0] | (((unsigned int) bs.total_sectors32[1]) << 8) | (((unsigned int) bs.total_sectors32[2]) << 16) | (((unsigned int) bs.total_sectors32[3]) << 24);
+        
+        if (!total_sectors) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (!sectors_per_fat) {
+    
+        if (bs.boot_jump[1] < 0x58) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        sectors_per_fat = (unsigned int) bs.fat32.sectors_per_fat32[0] | (((unsigned int) bs.fat32.sectors_per_fat32[1]) << 8) | (((unsigned int) bs.fat32.sectors_per_fat32[2]) << 16) | (((unsigned int) bs.fat32.sectors_per_fat32[3]) << 24);
+        
+        if (!sectors_per_fat) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (root_entries) {
+    
+        root_dir = reserved_sectors + (sectors_per_fat * 2);
+        data_area = root_dir + (((root_entries * 32) + (512 - 1)) / 512);
+    
+    } else {
+    
+        data_area = reserved_sectors + (sectors_per_fat * 2);
+        
+        /*root_dir = data_area + ((root_cluster - 2) * sectors_per_cluster);*/
+        /*root_dir = root_cluster;*/
+    
+    }
+    
+    cluster_count = (total_sectors - data_area) / sectors_per_cluster;
+    
+    if (bs.boot_jump[1] == 0x58) {
+    
+        info_sector = (unsigned int) bs.fat32.info_sector[0] | (((unsigned int) bs.fat32.info_sector[1]) << 8);
+        
+        if (!info_sector) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        size_fat = 32;
+    
+    } else if (cluster_count <= MAX_CLUST_12) {
+        size_fat = 12;
+    } else if (cluster_count >= MIN_CLUST_16 && cluster_count <= MAX_CLUST_16) {
+        size_fat = 16;
+    } else {
+    
+        report_at (program_name, 0, REPORT_ERROR, "FAT is not 12, 16 or 32 bits");
+        fclose (ofp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (target && target[strlen (target) - 1] != '/' && target[strlen (target) - 1] != '\\') {
+    
+        if (state->nb_files > 1) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "cannot copy multiple files to %s", target);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (!(scratch = (unsigned char *) malloc (512))) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Out of memory");
+        fclose (ofp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    for (i = 0; i < state->nb_files; ++i) {
+    
+        char *p, *ptr;
+        int need_fn = 0;
+        
+        unsigned long pathlen;
+        struct file_info fi;
+        
+        FILE *tmpfp;
+        source = state->files[i];
+        
+        if (copy_from) {
+        
+            if (memcmp (source, "::", 2)) {
+        
+                fclose (ofp);
+                remove (state->outfile);
+                
+                print_help (EXIT_FAILURE);
+            
+            }
+            
+            source += 2;
+        
+        }
+        
+        if (*target == '/' || *target == '\\') {
+            target++;
+        }
+        
+        pathlen = strlen (target);
+        
+        if (target[pathlen - 1] == '/' || target[pathlen - 1] == '\\') {
+        
+            need_fn = 1;
+            ptr = source;
+            
+            if ((p = strrchr (ptr, '/')) || (p = strrchr (ptr, '\\'))) {
+                ptr = (p + 1);
+            }
+            
+            pathlen += strlen (ptr);
+        
+        }
+        
+        if (pathlen > PATH_MAX) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "target too long");
+            free (scratch);
+            
+            fclose (ofp);
+            return EXIT_FAILURE;
+        
+        }
+        
+        memset (tmppath, 0, PATH_MAX);
+        strncpy (tmppath, target, strlen (target));
+        
+        if (need_fn) {
+            strncat (tmppath, ptr, strlen (ptr));
+        }
+        
+        if (copy_from) {
+        
+            if (get_file (source, scratch, &fi) < 0) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "file '%s' does not exist", source);
+                free (scratch);
+                
+                fclose (ofp);
+                return EXIT_FAILURE;
+            
+            }
+            
+            if (copy_from_image (target, &fi, tmppath) < 0) {
+                
+                if (state->status) {
+                    printf ("\n");
+                }
+                
+                report_at (program_name, 0, REPORT_ERROR, "failed to copy %s", source);
+                free (scratch);
+                
+                fclose (ofp);
+                return EXIT_FAILURE;
+            
+            }
+            
+            if (state->status) {
+                printf ("\n");
+            }
+            
+            continue;
+        
+        }
+        
+        if (!(tmpfp = fopen (source, "rb"))) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "file not found - %s", source);
+            continue;
+        
+        }
+        
+        fclose (tmpfp);
+        
+        if (open_file (tmppath, scratch, &fi) < 0) {
+        
+            if (!target || *target == '\0') {
+            
+                target = source;
+                
+                if ((p = strrchr (target, '/')) || (p = strrchr (target, '\\'))) {
+                    target = (p + 1);
+                }
+            
+            }
+            
+            report_at (program_name, 0, REPORT_ERROR, "failed to create %s", target);
+            continue;
+        
+        }
+        
+        fi.scratch = scratch;
+        
+        if (copy_file (source, &fi, tmppath) < 0) {
+        
+            if (state->status) {
+                printf ("\n");
+            }
+            
+            report_at (program_name, 0, REPORT_ERROR, "failed to copy %s", source);
+            continue;
+        
+        }
+        
+        if (state->status) {
+            printf ("\n");
+        }
+    
+    }
+    
+    free (scratch);
+    fclose (ofp);
+    
+    return (get_error_count () > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+
+}
diff --git a/mcopy.h b/mcopy.h
new file mode 100644 (file)
index 0000000..dee7b92
--- /dev/null
+++ b/mcopy.h
@@ -0,0 +1,21 @@
+/******************************************************************************
+ * @file            mcopy.h
+ *****************************************************************************/
+#ifndef     _MCOPY_H
+#define     _MCOPY_H
+
+struct mcopy_state {
+
+    char **files;
+    long nb_files;
+    
+    int status;
+    
+    const char *outfile;
+    size_t offset;
+    
+    int chs_align;
+
+};
+
+#endif      /* _MCOPY_H */
diff --git a/mkfs.c b/mkfs.c
new file mode 100644 (file)
index 0000000..3113607
--- /dev/null
+++ b/mkfs.c
@@ -0,0 +1,1361 @@
+/******************************************************************************
+ * @file            mkfs.c
+ *****************************************************************************/
+#include    <limits.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    "common.h"
+#include    "lib.h"
+#include    "mkfs.h"
+#include    "msdos.h"
+#include    "report.h"
+#include    "write7x.h"
+
+#ifndef     __PDOS__
+# if     defined (__GNUC__)
+#  include  <sys/time.h>
+#  include  <unistd.h>
+# else
+#  include  <io.h>
+# endif
+#endif
+
+static int align_structures = 1;
+
+static unsigned long image_size = 0;
+static FILE *ofp;
+
+static long total_sectors = 0;
+
+static int heads_per_cylinder = 255;
+static int sectors_per_track = 63;
+
+static unsigned int backup_boot = 0;
+static unsigned int cluster_count = 0;
+static unsigned int hidden_sectors = 0;
+static unsigned int info_sector = 0;
+static unsigned int media_descriptor = 0xf8;
+static unsigned int number_of_fats = 2;
+static unsigned int reserved_sectors = 0;
+static unsigned int root_cluster = 2;
+static unsigned int root_entries = 512;
+static unsigned int sectors_per_cluster = 4;
+static unsigned int sectors_per_fat = 0;
+
+struct mkfs_state *state = 0;
+const char *program_name = 0;
+
+static unsigned char dummy_boot_code[] =
+
+    "\x31\xC0"                                                                  /* xor ax, ax */
+    "\xFA"                                                                      /* cli */
+    "\x8E\xD0"                                                                  /* mov ss, ax */
+    "\xBC\x00\x7C"                                                              /* mov sp, 0x7c00 */
+    "\xFB"                                                                      /* sti */
+    "\x0E\x1F"                                                                  /* push cs, pop ds */
+    "\xEB\x19"                                                                  /* jmp XSTRING */
+    
+    "\x5E"                                                                      /* PRN: pop si */
+    "\xFC"                                                                      /* cld */
+    "\xAC"                                                                      /* XLOOP: lodsb */
+    "\x08\xC0"                                                                  /* or al, al */
+    "\x74\x09"                                                                  /* jz EOF */
+    "\xB4\x0E"                                                                  /* mov ah, 0x0e */
+    "\xBB\x07\x00"                                                              /* mov bx, 7 */
+    "\xCD\x10"                                                                  /* int 0x10 */
+    "\xEB\xF2"                                                                  /* jmp short XLOOP */
+    
+    "\x31\xC0"                                                                  /* EOF: xor ax, ax */
+    "\xCD\x16"                                                                  /* int 0x16 */
+    "\xCD\x19"                                                                  /* int 0x19 */
+    "\xF4"                                                                      /* HANG: hlt */
+    "\xEB\xFD"                                                                  /* jmp short HANG */
+    
+    "\xE8\xE4\xFF"                                                              /* XSTRING: call PRN */
+    
+    "Non-System disk or disk read error\r\n"
+    "Replace and strike any key when ready\r\n";
+
+static int cdiv (int a, int b) {
+    return (a + b - 1) / b;
+}
+
+static int seekto (long offset) {
+    return fseek (ofp, (state->offset * 512) + offset, SEEK_SET);
+}
+
+static int set_fat_entry (unsigned int cluster, unsigned int value) {
+
+    unsigned char *scratch;
+    unsigned int i, offset, sector;
+    
+    if (!(scratch = (unsigned char *) malloc (512))) {
+        return -1;
+    }
+    
+    if (state->size_fat == 12) {
+    
+        offset = cluster + (cluster / 2);
+        value &= 0x0fff;
+    
+    } else if (state->size_fat == 16) {
+    
+        offset = cluster * 2;
+        value &= 0xffff;
+    
+    } else if (state->size_fat == 32) {
+    
+        offset = cluster * 4;
+        value &= 0x0fffffff;
+    
+    } else {
+    
+        free (scratch);
+        return -1;
+    
+    }
+    
+    /**
+     * At this point, offset is the BYTE offset of the desired sector from the start
+     * of the FAT.  Calculate the physical sector containing this FAT entry.
+     */
+    sector = (offset / 512) + reserved_sectors;
+    
+    if (seekto (sector * 512)) {
+    
+        free (scratch);
+        
+        report_at (program_name, 0, REPORT_ERROR, "failed whilst seeking %s", state->outfile);
+        return -1;
+    
+    }
+    
+    if (fread (scratch, 512, 1, ofp) != 1) {
+    
+        free (scratch);
+        
+        report_at (program_name, 0, REPORT_ERROR, "failed whilst reading %s", state->outfile);
+        return -1;
+    
+    }
+    
+    /**
+     * At this point, we "merely" need to extract the relevant entry.  This is
+     * easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry
+     * may span a sector boundary.  The normal way around this is always to
+     * read two FAT sectors, but luxary is (by design intent) unavailable.
+     */
+    offset %= 512;
+    
+    if (state->size_fat == 12) {
+    
+        if (offset == 511) {
+        
+            if (((cluster * 3) & 0x01) == 0) {
+                scratch[offset] = (unsigned char) (value & 0xFF);
+            } else {
+                scratch[offset] = (unsigned char) ((scratch[offset] & 0x0F) | (value & 0xF0));
+            }
+            
+            for (i = 0; i < number_of_fats; i++) {
+            
+                long temp = sector + (i * sectors_per_fat);
+                
+                if (seekto (temp * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+                
+                    free (scratch);
+                    return -1;
+                
+                }
+            
+            }
+            
+            sector++;
+            
+            if (seekto (sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+            
+                free (scratch);
+                return -1;
+            
+            }
+            
+            if (((cluster * 3) & 0x01) == 0) {
+                scratch[0] = (unsigned char) ((scratch[0] & 0xF0) | (value & 0x0F));
+            } else {
+                scratch[0] = (unsigned char) (value & 0xFF00);
+            }
+            
+            goto _write_fat;
+        
+        } else {
+        
+            if (((cluster * 3) & 0x01) == 0) {
+            
+                scratch[offset] = (unsigned char) (value & 0x00FF);
+                scratch[offset + 1] = (unsigned char) ((scratch[offset + 1] & 0x00F0) | ((value & 0x0F00) >> 8));
+            
+            } else {
+            
+                scratch[offset] = (unsigned char) ((scratch[offset] & 0x000F) | ((value & 0x000F) << 4));
+                scratch[offset + 1] = (unsigned char) ((value & 0x0FF0) >> 4);
+            
+            }
+            
+            goto _write_fat;
+        
+        }
+    
+    } else if (state->size_fat == 16) {
+    
+        scratch[offset] = (value & 0xFF);
+        scratch[offset + 1] = (value >> 8) & 0xFF;
+        
+        goto _write_fat;
+    
+    } else if (state->size_fat == 32) {
+    
+        scratch[offset] = (value & 0xFF);
+        scratch[offset + 1] = (value >> 8) & 0xFF;
+        scratch[offset + 2] = (value >> 16) & 0xFF;
+        scratch[offset + 3] = (scratch[offset + 3] & 0xF0) | ((value >> 24) & 0xFF);
+        
+        goto _write_fat;
+    
+    }
+    
+    free (scratch);
+    return -1;
+
+_write_fat:
+
+    for (i = 0; i < number_of_fats; i++) {
+    
+        long temp = sector + (i * sectors_per_fat);
+        
+        if (seekto (temp * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+        
+            free (scratch);
+            return -1;
+        
+        }
+    
+    }
+    
+    free (scratch);
+    return 0;
+
+}
+
+static unsigned int align_object (unsigned int sectors, unsigned int clustsize) {
+
+    if (align_structures) {
+        return (sectors + clustsize - 1) & ~(clustsize - 1);
+    }
+    
+    return sectors;
+
+}
+
+static unsigned int generate_volume_id (void) {
+
+#if     defined (__PDOS__)
+
+    srand (time (NULL));
+    
+    /* rand() returns int from [0,RAND_MAX], therefor only 31-bits. */
+    return (((unsigned int) (rand () & 0xFFFF)) << 16) | ((unsigned int) (rand() & 0xFFFF));
+
+#elif   defined (__GNUC__)
+
+    struct timeval now;
+    
+    if (gettimeofday (&now, 0) != 0 || now.tv_sec == (time_t) -1 || now.tv_sec < 0) {
+    
+        srand (getpid ());
+        
+        /*- rand() returns int from [0,RAND_MAX], therefor only 31-bits. */
+        return (((unsigned int) (rand () & 0xFFFF)) << 16) | ((unsigned int) (rand() & 0xFFFF));
+    
+    }
+    
+    /* volume id = current time, fudged for more uniqueness. */
+    return ((unsigned int) now.tv_sec << 20) | (unsigned int) now.tv_usec;
+
+#elif   defined (__WATCOMC__)
+
+    srand (getpid ());
+    
+    /* rand() returns int from [0,RAND_MAX], therefor only 31-bits. */
+    return (((unsigned int) (rand () & 0xFFFF)) << 16) | ((unsigned int) (rand() & 0xFFFF));
+
+#endif
+
+}
+
+static void establish_bpb (void) {
+
+    unsigned int maxclustsize, root_dir_sectors;
+    
+    unsigned int clust12, clust16, clust32;
+    unsigned int fatdata1216, fatdata32;
+    unsigned int fatlength12, fatlength16, fatlength32;
+    unsigned int maxclust12, maxclust16, maxclust32;
+    
+    long cylinder_times_heads;
+    long sectors_per_cylinder;
+    
+    if ((unsigned long) total_sectors > UINT_MAX) {
+    
+        report_at (program_name, 0, REPORT_WARNING, "target too large, space at end will be left unused.\n");
+        total_sectors = UINT_MAX;
+    
+    }
+    
+    if (total_sectors > (long) 65535 * 16 * 63) {
+    
+        sectors_per_track = 63;
+        heads_per_cylinder = 255;
+        
+        cylinder_times_heads = total_sectors / sectors_per_track;
+    
+    } else {
+    
+        sectors_per_track = 17;
+        cylinder_times_heads = total_sectors / sectors_per_track;
+        
+        heads_per_cylinder = (cylinder_times_heads + 1023) >> 10;
+        
+        if (heads_per_cylinder < 4) {
+            heads_per_cylinder = 4;
+        }
+        
+        if (cylinder_times_heads >= heads_per_cylinder << 10 || heads_per_cylinder > 16) {
+        
+            sectors_per_track = 31;
+            heads_per_cylinder = 16;
+            
+            cylinder_times_heads = total_sectors / sectors_per_track;
+        
+        }
+        
+        if (cylinder_times_heads >= heads_per_cylinder << 10) {
+        
+            sectors_per_track = 63;
+            heads_per_cylinder = 16;
+            
+            cylinder_times_heads = total_sectors / sectors_per_track;
+        
+        }
+    
+    }
+    
+    sectors_per_cylinder = heads_per_cylinder * sectors_per_track;
+    
+    hidden_sectors = state->offset;
+    total_sectors = state->sectors;
+    
+    if (state->chs_align && hidden_sectors % sectors_per_track) {
+    
+        hidden_sectors = ((hidden_sectors / sectors_per_track) + 1) * sectors_per_track;
+        report_at (program_name, 0, REPORT_WARNING, "hidden sectors changed from %lu to %lu", state->offset, hidden_sectors);
+    
+    }
+    
+    if (state->chs_align) {
+    
+        long adjustment;
+        
+        if (total_sectors % sectors_per_cylinder) {
+            total_sectors = ((total_sectors / sectors_per_cylinder) + 1) * sectors_per_cylinder;
+        }
+        
+        if ((unsigned long) (hidden_sectors + total_sectors) > state->offset + state->sectors) {
+        
+            adjustment = (hidden_sectors + total_sectors) - (state->offset + state->sectors);
+            total_sectors -= adjustment;
+        
+        }
+        
+        if ((adjustment = (hidden_sectors + total_sectors) % sectors_per_cylinder)) {
+            total_sectors -= adjustment;
+        }
+        
+        if ((unsigned long) total_sectors != state->sectors) {
+            report_at (program_name, 0, REPORT_WARNING, "total sectors changed from %lu to %lu", state->sectors, total_sectors);
+        }
+    
+    }
+    
+    state->offset = hidden_sectors;
+    state->sectors = total_sectors;
+    
+    switch (total_sectors) {
+    
+        case 320:                                                               /* 160KB 5.25" */
+        
+            sectors_per_cluster = 2;
+            root_entries = 112;
+            media_descriptor = 0xfe;
+            sectors_per_track = 8;
+            heads_per_cylinder = 1;
+            break;
+        
+        case 360:                                                               /* 180KB 5.25" */
+        
+            sectors_per_cluster = 2;
+            root_entries = 112;
+            media_descriptor = 0xfc;
+            sectors_per_track = 9;
+            heads_per_cylinder = 1;
+            break;
+        
+        case 640:                                                               /* 320KB 5.25" */
+        
+            sectors_per_cluster = 2;
+            root_entries = 112;
+            media_descriptor = 0xff;
+            sectors_per_track = 8;
+            heads_per_cylinder = 2;
+            break;
+        
+        case 720:                                                               /* 360KB 5.25" */
+        
+            sectors_per_cluster = 2;
+            root_entries = 112;
+            media_descriptor = 0xfd;
+            sectors_per_track = 9;
+            heads_per_cylinder = 2;
+            break;
+        
+        case 1280:                                                              /* 640KB 5.25" / 3.5" */
+        
+            sectors_per_cluster = 2;
+            root_entries = 112;
+            media_descriptor = 0xfb;
+            sectors_per_track = 8;
+            heads_per_cylinder = 2;
+            break;
+        
+        case 1440:                                                              /* 720KB 5.25" / 3.5" */
+        
+            sectors_per_cluster = 2;
+            root_entries = 112;
+            media_descriptor = 0xf9;
+            sectors_per_track = 9;
+            heads_per_cylinder = 2;
+            break;
+        
+        case 1640:                                                              /* 820KB 3.5" */
+        
+            sectors_per_cluster = 2;
+            root_entries = 112;
+            media_descriptor = 0xf9;
+            sectors_per_track = 10;
+            heads_per_cylinder = 2;
+            break;
+        
+        case 2400:                                                              /* 1.20MB 5.25" / 3.5" */
+        
+            sectors_per_cluster = 1;
+            root_entries = 224;
+            media_descriptor = 0xf9;
+            sectors_per_track = 15;
+            heads_per_cylinder = 2;
+            break;
+        
+        case 2880:                                                              /* 1.44MB 3.5" */
+        
+            sectors_per_cluster = 1;
+            root_entries = 224;
+            media_descriptor = 0xf0;
+            sectors_per_track = 18;
+            heads_per_cylinder = 2;
+            break;
+        
+        case 3360:                                                              /* 1.68MB 3.5" */
+        
+            sectors_per_cluster = 1;
+            root_entries = 224;
+            media_descriptor = 0xf0;
+            sectors_per_track = 21;
+            heads_per_cylinder = 2;
+            break;
+        
+        case 3444:                                                              /* 1.72MB 3.5" */
+        
+            sectors_per_cluster = 1;
+            root_entries = 224;
+            media_descriptor = 0xf0;
+            sectors_per_track = 21;
+            heads_per_cylinder = 2;
+            break;
+        
+        case 5760:                                                              /* 2.88MB 3.5" */
+        
+            sectors_per_cluster = 2;
+            root_entries = 240;
+            media_descriptor = 0xf0;
+            sectors_per_track = 36;
+            heads_per_cylinder = 2;
+            break;
+    
+    }
+    
+    if (!state->size_fat && total_sectors >= 1048576) {
+        state->size_fat = 32;
+    }
+    
+    if (state->size_fat == 32) {
+    
+        root_entries = 0;
+        
+        /*
+         * For FAT32, try to do the same as M$'s format command
+         * (see http://www.win.tue.nl/~aeb/linux/fs/fat/fatgen103.pdf p. 20):
+         * 
+         * fs size <= 260M: 0.5k clusters
+         * fs size <=   8G:   4k clusters
+         * fs size <=  16G:   8k clusters
+         * fs size <=  32G:  16k clusters
+         * fs size >   32G:  32k clusters
+         */
+        sectors_per_cluster = (total_sectors > 32 * 1024 * 1024 * 2 ? 64 :
+                               total_sectors > 16 * 1024 * 1024 * 2 ? 32 :
+                               total_sectors >  8 * 1024 * 1024 * 2 ? 16 :
+                               total_sectors >       260 * 1024 * 2 ?  8 : 1);
+    
+    }
+    
+    if (state->sectors_per_cluster) {
+        sectors_per_cluster = state->sectors_per_cluster;
+    }
+    
+    if (!reserved_sectors) {
+        reserved_sectors = (state->size_fat == 32 ? 32 : 1);
+    }
+    
+    /*if (align_structures) {*/
+    
+        /** Align number of sectors to be multiple of sectors per track, needed by DOS and mtools. */
+        /*total_sectors = total_sectors / sectors_per_track * sectors_per_track;*/
+    
+    /*}*/
+    
+    if (total_sectors <= 8192) {
+    
+        if (align_structures && state->verbose) {
+            report_at (program_name, 0, REPORT_WARNING, "Disabling alignment due to tiny filsystem\n");
+        }
+        
+        align_structures = 0;
+    
+    }
+    
+    maxclustsize = 128;
+    root_dir_sectors = cdiv (root_entries * 32, 512);
+    
+    do {
+    
+        fatdata32 = total_sectors - align_object (reserved_sectors, sectors_per_cluster);
+        fatdata1216 = fatdata32 - align_object (root_dir_sectors, sectors_per_cluster);
+        
+        if (state->verbose) {
+            fprintf (stderr, "Trying with %d sectors/cluster:\n", sectors_per_cluster);
+        }
+        
+        /**
+         * The factor 2 below avoids cut-off errors for number_of_fats == 1.
+         * The "number_of_fats * 3" is for the reserved first two FAT entries.
+         */
+        clust12 = 2 * ((long) fatdata1216 * 512 + number_of_fats * 3) / (2 * (int) sectors_per_cluster * 512 + number_of_fats * 3);
+        fatlength12 = cdiv (((clust12 + 2) * 3 + 1) >> 1, 512);
+        fatlength12 = align_object (fatlength12, sectors_per_cluster);
+        
+        /**
+         * Need to recalculate number of clusters, since the unused parts of the
+         * FATs and data area together could make up space for an additional,
+         * not really present cluster.
+         */
+        clust12 = (fatdata1216 - number_of_fats * fatlength12) / sectors_per_cluster;
+        maxclust12 = (fatlength12 * 2 * 512) / 3;
+        
+        if (maxclust12 > MAX_CLUST_12) {
+            maxclust12 = MAX_CLUST_12;
+        }
+        
+        if (state->verbose && (state->size_fat == 0 || state->size_fat == 12)) {
+            fprintf (stderr, "Trying FAT12: #clu=%u, fatlen=%u, maxclu=%u, limit=%u\n", clust12, fatlength12, maxclust12, MAX_CLUST_12);
+        }
+        
+        if (clust12 > maxclust12) {
+        
+            clust12 = 0;
+            
+            if (state->verbose && (state->size_fat == 0 || state->size_fat == 12)) {
+                fprintf (stderr, "Trying FAT12: too many clusters\n");
+            }
+        
+        }
+        
+        clust16 = ((long) fatdata1216 * 512 + number_of_fats * 4) / ((int) sectors_per_cluster * 512 + number_of_fats * 2);
+        fatlength16 = cdiv ((clust16 + 2) * 2, 512);
+        fatlength16 = align_object (fatlength16, sectors_per_cluster);
+        
+        /**
+         * Need to recalculate number of clusters, since the unused parts of the
+         * FATs and data area together could make up space for an additional,
+         * not really present cluster.
+         */
+        clust16 = (fatdata1216 - number_of_fats * fatlength16) / sectors_per_cluster;
+        maxclust16 = (fatlength16 * 512) / 2;
+        
+        if (maxclust16 > MAX_CLUST_16) {
+            maxclust16 = MAX_CLUST_16;
+        }
+        
+        if (state->verbose && (state->size_fat == 0 || state->size_fat == 16)) {
+            fprintf (stderr, "Trying FAT16: #clu=%u, fatlen=%u, maxclu=%u, limit=%u/%u\n", clust16, fatlength16, maxclust16, MIN_CLUST_16, MAX_CLUST_16);
+        }
+        
+        if (clust16 > maxclust16) {
+        
+            clust16 = 0;
+            
+            if (state->verbose && (state->size_fat == 0 || state->size_fat == 16)) {
+                fprintf (stderr, "Trying FAT16: too many clusters\n");
+            }
+        
+        }
+        
+        /** This avoids that the filesystem will be misdetected as having a 12-bit FAT. */
+        if (clust16 && clust16 < MIN_CLUST_16) {
+        
+            clust16 = 0;
+            
+            if (state->verbose && (state->size_fat == 0 || state->size_fat == 16)) {
+                fprintf (stderr, "Trying FAT16: not enough clusters, would be misdected as FAT12\n");
+            }
+        
+        }
+        
+        clust32 = ((long) fatdata32 * 512 + number_of_fats * 8) / ((int) sectors_per_cluster * 512 + number_of_fats * 4);
+        fatlength32 = cdiv ((clust32 + 2) * 4, 512);
+        fatlength32 = align_object (fatlength32, sectors_per_cluster);
+        
+        /**
+         * Need to recalculate number of clusters, since the unused parts of the
+         * FATs and data area together could make up space for an additional,
+         * not really present cluster.
+         */
+        clust32 = (fatdata32 - number_of_fats * fatlength32) / sectors_per_cluster;
+        maxclust32 = (fatlength32 * 512) / 4;
+        
+        if (maxclust32 > MAX_CLUST_32) {
+            maxclust32 = MAX_CLUST_32;
+        }
+        
+        if (state->verbose && (state->size_fat == 0 || state->size_fat == 32)) {
+            fprintf (stderr, "Trying FAT32: #clu=%u, fatlen=%u, maxclu=%u, limit=%u/%u\n", clust32, fatlength32, maxclust32, MIN_CLUST_32, MAX_CLUST_32);
+        }
+        
+        if (clust32 > maxclust32) {
+        
+            clust32 = 0;
+            
+            if (state->verbose && (state->size_fat == 0 || state->size_fat == 32)) {
+                fprintf (stderr, "Trying FAT32: too many clusters\n");
+            }
+        
+        }
+        
+        if (clust32 && clust32 < MIN_CLUST_32 && !(state->size_fat_by_user && state->size_fat == 32)) {
+        
+            clust32 = 0;
+            
+            if (state->verbose && (state->size_fat == 0 || state->size_fat == 32)) {
+                fprintf (stderr, "Trying FAT32: not enough clusters\n");
+            }
+        
+        }
+        
+        if ((clust12 && (state->size_fat == 0 || state->size_fat == 12)) || (clust16 && (state->size_fat == 0 || state->size_fat == 16)) || (clust32 && state->size_fat == 32)) {
+            break;
+        }
+        
+        sectors_per_cluster <<= 1;
+    
+    } while (sectors_per_cluster && sectors_per_cluster <= maxclustsize);
+    
+    /** Use the optimal FAT size if not specified. */
+    if (!state->size_fat) {
+    
+        state->size_fat = (clust16 > clust12 ? 16 : 12);
+        
+        if (state->verbose) {
+            report_at (program_name, 0, REPORT_WARNING, "Choosing %d-bits for FAT\n", state->size_fat);
+        }
+    
+    }
+    
+    switch (state->size_fat) {
+    
+        case 12:
+        
+            cluster_count = clust12;
+            sectors_per_fat = fatlength12;
+            break;
+        
+        case 16:
+        
+            cluster_count = clust16;
+            sectors_per_fat = fatlength16;
+            break;
+        
+        case 32:
+        
+            cluster_count = clust32;
+            sectors_per_fat = fatlength32;
+            break;
+        
+        default:
+        
+            report_at (program_name, 0, REPORT_ERROR, "FAT not 12, 16 or 32 bits");
+            
+            fclose (ofp);
+            remove (state->outfile);
+            
+            exit (EXIT_FAILURE);
+    
+    }
+    
+    /** Adjust the reserved number of sectors for alignment. */
+    reserved_sectors = align_object (reserved_sectors, sectors_per_cluster);
+    
+    /** Adjust the number of root directory entries to help enforce alignment. */
+    if (align_structures) {
+        root_entries = align_object (root_dir_sectors, sectors_per_cluster) * (512 >> 5);
+    }
+    
+    if (state->size_fat == 32) {
+    
+        if (!info_sector) {
+            info_sector = 1;
+        }
+        
+        if (!backup_boot) {
+        
+            if (reserved_sectors >= 7 && info_sector != 6) {
+                backup_boot = 6;
+            } else if (reserved_sectors > 3 + info_sector && info_sector != reserved_sectors - 2 && info_sector != reserved_sectors - 1) {
+                backup_boot = reserved_sectors - 2;
+            } else if (reserved_sectors >= 3 && info_sector != reserved_sectors - 1) {
+                backup_boot = reserved_sectors - 1;
+            }
+        
+        }
+        
+        if (backup_boot) {
+        
+            if (backup_boot == info_sector) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "Backup boot sector must not be the same as the info sector (%d)", info_sector);
+                
+                fclose (ofp);
+                remove (state->outfile);
+                
+                exit (EXIT_FAILURE);
+            
+            } else if (backup_boot >= reserved_sectors) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "Backup boot sector must be a reserved sector");
+                
+                fclose (ofp);
+                remove (state->outfile);
+                
+                exit (EXIT_FAILURE);
+            
+            }
+        
+        }
+        
+        if (state->verbose) {
+            fprintf (stderr, "Using sector %d as backup boot sector (0 = none)\n", backup_boot);
+        }
+    
+    }
+    
+    if (!cluster_count) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Not enough clusters to make a viable filesystem");
+        
+        fclose (ofp);
+        remove (state->outfile);
+        
+        exit (EXIT_FAILURE);
+    
+    }
+
+}
+
+static void add_volume_label (void) {
+
+    struct msdos_dirent *de;
+    unsigned short date, time;
+    
+    unsigned char *scratch;
+    long offset = 0;
+    
+    if (!(scratch = (unsigned char *) malloc (512))) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Failed to allocate memory");
+        
+        fclose (ofp);
+        remove (state->outfile);
+        
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    if (state->size_fat == 32) {
+    
+        long temp = reserved_sectors + (sectors_per_fat * 2);
+        offset += temp + ((root_cluster - 2) * sectors_per_cluster);
+    
+    } else {
+        offset += reserved_sectors + (sectors_per_fat * 2);
+    }
+    
+    if (seekto (offset * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Failed whilst reading root directory");
+        free (scratch);
+        
+        fclose (ofp);
+        remove (state->outfile);
+        
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    de = (struct msdos_dirent *) scratch;
+    memset (de, 0, sizeof (*de));
+    
+    date = generate_datestamp ();
+    time = generate_timestamp ();
+    
+    memcpy (de->name, state->label, 11);
+    de->attr = ATTR_VOLUME_ID;
+    
+    write721_to_byte_array (de->ctime, time);
+    write721_to_byte_array (de->cdate, date);
+    write721_to_byte_array (de->adate, date);
+    write721_to_byte_array (de->time, time);
+    write721_to_byte_array (de->date, date);
+    
+    if (seekto (offset * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Failed whilst writing root directory");
+        free (scratch);
+        
+        fclose (ofp);
+        remove (state->outfile);
+        
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    free (scratch);
+
+}
+
+static void wipe_target (void) {
+
+    unsigned int i, sectors_to_wipe = 0;
+    void *blank;
+    
+    if (!(blank = malloc (512))) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Failed to allocate memory");
+        
+        fclose (ofp);
+        remove (state->outfile);
+        
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    memset (blank, 0, 512);
+    
+    sectors_to_wipe += reserved_sectors;
+    sectors_to_wipe += sectors_per_fat * 2;
+    
+    if (root_entries) {
+        sectors_to_wipe += (root_entries * 32) / 512;
+    } else {
+        sectors_to_wipe += 1;
+    }
+    
+    seekto (0);
+    
+    for (i = 0; i < sectors_to_wipe; i++) {
+    
+        if (fwrite (blank, 512, 1, ofp) != 1) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "Failed whilst writing blank sector");
+            free (blank);
+            
+            fclose (ofp);
+            remove (state->outfile);
+            
+            exit (EXIT_FAILURE);
+        
+        }
+    
+    }
+    
+    free (blank);
+
+}
+
+static void write_reserved (void) {
+
+    struct msdos_boot_sector bs;
+    struct msdos_volume_info *vi = (state->size_fat == 32 ? &bs.fstype._fat32.vi : &bs.fstype._oldfat.vi);
+    
+    memset (&bs, 0, sizeof (bs));
+    
+    if (state->boot) {
+    
+        FILE *ifp;
+        
+        if ((ifp = fopen (state->boot, "rb")) == NULL) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "unable to open %s", state->boot);
+            
+            fclose (ofp);
+            remove (state->outfile);
+            
+            exit (EXIT_FAILURE);
+        
+        }
+        
+        fseek (ifp, 0, SEEK_END);
+        
+        if (ftell (ifp) != sizeof (bs)) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "boot sector must be %lu bytes in size", (unsigned long) sizeof (bs));
+            fclose (ifp);
+            
+            fclose (ofp);
+            remove (state->outfile);
+            
+            exit (EXIT_FAILURE);
+        
+        }
+        
+        fseek (ifp, 0, SEEK_SET);
+        
+        if (fread (&bs, sizeof (bs), 1, ifp) != 1) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "failed to read %s", state->boot);
+            fclose (ifp);
+            
+            fclose (ofp);
+            remove (state->outfile);
+            
+            exit (EXIT_FAILURE);
+        
+        }
+        
+        fclose (ifp);
+    
+    } else {
+    
+        bs.boot_jump[0] = 0xEB;
+        bs.boot_jump[1] = ((state->size_fat == 32 ? (char *) &bs.fstype._fat32.boot_code : (char *) &bs.fstype._oldfat.boot_code) - (char *) &bs) - 2;
+        bs.boot_jump[2] = 0x90;
+        
+        if (state->size_fat == 32) {
+            memcpy (bs.fstype._fat32.boot_code, dummy_boot_code, sizeof (dummy_boot_code));
+        } else {
+            memcpy (bs.fstype._oldfat.boot_code, dummy_boot_code, sizeof (dummy_boot_code));
+        }
+        
+        bs.boot_sign[0] = 0x55;
+        bs.boot_sign[1] = 0xAA;
+    
+    }
+    
+    if (bs.boot_jump[0] != 0xEB || bs.boot_jump[1] < 0x16 || bs.boot_jump[2] != 0x90) {
+        goto _write_reserved;
+    }
+    
+    memcpy (bs.system_id, "MSWIN4.1", 8);
+    
+    bs.sectors_per_cluster = sectors_per_cluster;
+    bs.no_fats = number_of_fats;
+    bs.media_descriptor = media_descriptor;
+    
+    write721_to_byte_array (bs.bytes_per_sector, 512);
+    write721_to_byte_array (bs.reserved_sectors, reserved_sectors);
+    write721_to_byte_array (bs.root_entries, root_entries);
+    write721_to_byte_array (bs.total_sectors16, total_sectors);
+    write721_to_byte_array (bs.sectors_per_fat16, sectors_per_fat);
+    
+    if (bs.boot_jump[1] < 0x22) {
+        goto _write_reserved;
+    }
+    
+    write721_to_byte_array (bs.sectors_per_track, sectors_per_track);
+    write721_to_byte_array (bs.heads_per_cylinder, heads_per_cylinder);
+    write741_to_byte_array (bs.hidden_sectors, hidden_sectors);
+    
+    if (total_sectors > USHRT_MAX) {
+    
+        write721_to_byte_array (bs.total_sectors16, 0);
+        write741_to_byte_array (bs.total_sectors32, total_sectors);
+    
+    }
+    
+    if (state->size_fat == 32) {
+    
+        if (bs.boot_jump[1] < 0x58) {
+            goto _write_reserved;
+        }
+        
+        write721_to_byte_array (bs.sectors_per_fat16, 0);
+        write741_to_byte_array (bs.fstype._fat32.sectors_per_fat32, sectors_per_fat);
+        
+        write741_to_byte_array (bs.fstype._fat32.root_cluster, root_cluster);
+        write721_to_byte_array (bs.fstype._fat32.info_sector, info_sector);
+        write721_to_byte_array (bs.fstype._fat32.backup_boot, backup_boot);
+        
+        vi->drive_no = (media_descriptor == 0xF8 ? 0x80 : 0x00);
+        vi->ext_boot_sign = 0x29;
+        
+        write741_to_byte_array (vi->volume_id, generate_volume_id ());
+        memcpy (vi->volume_label, state->label, 11);
+        memcpy (vi->fs_type, "FAT32   ", 8);
+    
+    } else {
+    
+        if (bs.boot_jump[1] < 0x3C) {
+            goto _write_reserved;
+        }
+        
+        vi->drive_no = (media_descriptor == 0xF8 ? 0x80 : 0x00);
+        vi->ext_boot_sign = 0x29;
+        
+        write741_to_byte_array (vi->volume_id, generate_volume_id ());
+        memcpy (vi->volume_label, state->label, 11);
+        
+        if (state->size_fat == 12) {
+            memcpy (vi->fs_type, "FAT12   ", 8);
+        } else {
+            memcpy (vi->fs_type, "FAT16   ", 8);
+        }
+    
+    }
+
+_write_reserved:
+
+    seekto (0);
+    
+    if (fwrite (&bs, sizeof (bs), 1, ofp) != 1) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Failed whilst writing %s", state->outfile);
+        
+        fclose (ofp);
+        remove (state->outfile);
+        
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    if (state->size_fat == 32) {
+    
+        if (info_sector) {
+        
+            struct fat32_fsinfo *info;
+            unsigned char *buffer;
+            
+            if (seekto (info_sector * 512)) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "Failed whilst seeking %s", state->outfile);
+                
+                fclose (ofp);
+                remove (state->outfile);
+                
+                exit (EXIT_FAILURE);
+            
+            }
+            
+            if (!(buffer = (unsigned char *) malloc (512))) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "Failed to allocate memory");
+                
+                fclose (ofp);
+                remove (state->outfile);
+                
+                exit (EXIT_FAILURE);
+            
+            }
+            
+            memset (buffer, 0, 512);
+            
+            /** fsinfo structure is at offset 0x1e0 in info sector by observation. */
+            info = (struct fat32_fsinfo *) (buffer + 0x1e0);
+            
+            /** Info sector magic. */
+            buffer[0] = 'R';
+            buffer[1] = 'R';
+            buffer[2] = 'a';
+            buffer[3] = 'A';
+            
+            /** Magic for fsinfo structure. */
+            write741_to_byte_array (info->signature, 0x61417272);
+            
+            /** We've allocated cluster 2 for the root directory. */
+            write741_to_byte_array (info->free_clusters, cluster_count - 1);
+            write741_to_byte_array (info->next_cluster, 2);
+            
+            /** Info sector also must have boot sign. */
+            write721_to_byte_array (buffer + 0x1fe, 0xAA55);
+            
+            if (fwrite (buffer, 512, 1, ofp) != 1) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "Failed whilst writing info sector");
+                free (buffer);
+                
+                fclose (ofp);
+                remove (state->outfile);
+                
+                exit (EXIT_FAILURE);
+            
+            }
+            
+            free (buffer);
+        
+        }
+        
+        if (backup_boot) {
+        
+            if (seekto (backup_boot * 512) || fwrite (&bs, sizeof (bs), 1, ofp) != 1) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "Failed whilst writing info sector");
+                
+                fclose (ofp);
+                remove (state->outfile);
+                
+                exit (EXIT_FAILURE);
+            
+            }
+        
+        }
+    
+    }
+
+}
+
+struct vhd_footer {
+
+    unsigned char cookie[8];
+    unsigned char features[4];
+    
+    struct {
+    
+        unsigned char major[2];
+        unsigned char minor[2];
+    
+    } version;
+    
+    unsigned char next_offset[8];
+    unsigned char modified_time[4];
+    unsigned char creator_name[4];
+    
+    struct {
+    
+        unsigned char major[2];
+        unsigned char minor[2];
+    
+    } creator_version;
+    
+    unsigned char creator_host[4];
+    unsigned char disk_size[8];
+    unsigned char data_size[8];
+    
+    struct {
+    
+        unsigned char cylinders[2];
+        unsigned char heads_per_cyl;
+        unsigned char secs_per_track;
+    
+    } disk_geom;
+    
+    unsigned char disk_type[4];
+    unsigned char checksum[4];
+    unsigned char identifier[16];
+    unsigned char saved_state;
+    unsigned char reserved[427];
+
+};
+
+int main (int argc, char **argv) {
+
+    if (argc && *argv) {
+    
+        char *p;
+        program_name = *argv;
+        
+        if ((p = strrchr (program_name, '/')) || (p = strrchr (program_name, '\\'))) {
+            program_name = (p + 1);
+        }
+    
+    }
+    
+    state = xmalloc (sizeof (*state));
+    parse_args (&argc, &argv, 1);
+    
+    if (!state->outfile) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "no outfile file provided");
+        return EXIT_FAILURE;
+    
+    }
+    
+    image_size  = state->sectors * 512;
+    image_size += state->offset * 512;
+    
+    if ((ofp = fopen (state->outfile, "r+b")) == NULL) {
+    
+        unsigned long len;
+        void *zero;
+        
+        if ((ofp = fopen (state->outfile, "w+b")) == NULL) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "failed to open '%s' for writing", state->outfile);
+            return EXIT_FAILURE;
+        
+        }
+        
+        len = image_size;
+        zero = xmalloc (512);
+        
+        while (len > 0) {
+        
+            if (fwrite (zero, 512, 1, ofp) != 1) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "failed whilst writing '%s'", state->outfile);
+                fclose (ofp);
+                
+                free (zero);
+                remove (state->outfile);
+                
+                return EXIT_FAILURE;
+            
+            }
+            
+            len -= 512;
+        
+        }
+        
+        free (zero);
+    
+    } else {
+    
+        struct vhd_footer footer;
+        long size = sizeof (footer);
+        
+        fseek (ofp, 0, SEEK_END);
+        image_size = ftell (ofp);
+        
+        if (!fseek (ofp, ftell (ofp) - size, SEEK_SET)) {
+        
+            if (fread (&footer, size, 1, ofp) == 1) {
+            
+                if (footer.cookie[0] == 0x63 && footer.cookie[1] == 0x6F && footer.cookie[2] == 0x6E && footer.cookie[3] == 0x65 && footer.cookie[4] == 0x63 && footer.cookie[5] == 0x74 && footer.cookie[6] == 0x69 && footer.cookie[7] == 0x78) {
+                
+                    /* Okay, if we reach this we'll assume that we have a hard disk image so subtract the footer size and 512 for the MBR. */
+                    image_size -= size;
+                    image_size -= 512;
+                
+                }
+            
+            }
+        
+        }
+    
+    }
+    
+    total_sectors = image_size / 512;
+    establish_bpb ();
+    
+    seekto (0);
+    
+    if (state->offset * 512 > image_size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "size (%lu) of %s is less than the requested offset (%lu)", image_size, state->outfile, state->offset * 512);
+        
+        fclose (ofp);
+        remove (state->outfile);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    image_size -= state->offset * 512;
+    
+    if (state->sectors) {
+    
+        if (state->sectors * 512 > image_size) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "size (%lu) of %s is less than the requested size (%lu)", image_size, state->outfile, state->sectors * 512);
+            
+            fclose (ofp);
+            remove (state->outfile);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    wipe_target ();
+    write_reserved ();
+    
+    if (set_fat_entry (0, 0xFFFFFF00 | media_descriptor) < 0 || set_fat_entry (1, 0xFFFFFFFF) < 0) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Failed whilst setting FAT entry");
+        
+        fclose (ofp);
+        remove (state->outfile);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (state->size_fat == 32) {
+    
+        if (set_fat_entry (2, 0x0FFFFFF8) < 0) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "Failed whilst setting FAT entry");
+            
+            fclose (ofp);
+            remove (state->outfile);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (memcmp (state->label, "NO NAME    ", 11) != 0) {
+        add_volume_label ();
+    }
+    
+    fclose (ofp);
+    return EXIT_SUCCESS;
+
+}
diff --git a/mkfs.h b/mkfs.h
new file mode 100644 (file)
index 0000000..8f09e21
--- /dev/null
+++ b/mkfs.h
@@ -0,0 +1,23 @@
+/******************************************************************************
+ * @file            parted.h
+ *****************************************************************************/
+#ifndef     _PARTED_H
+#define     _PARTED_H
+
+struct mkfs_state {
+
+    const char *boot, *outfile;
+    char label[12];
+    
+    int create, size_fat, size_fat_by_user, verbose;
+    unsigned long sectors, offset;
+    
+    unsigned char sectors_per_cluster;
+    int chs_align;
+
+};
+
+extern struct mkfs_state *state;
+extern const char *program_name;
+
+#endif      /* _PARTED_H */
diff --git a/mls.c b/mls.c
new file mode 100644 (file)
index 0000000..5b1d9c6
--- /dev/null
+++ b/mls.c
@@ -0,0 +1,1068 @@
+/******************************************************************************
+ * @file            mls.c
+ *****************************************************************************/
+#include    <ctype.h>
+#include    <errno.h>
+#include    <limits.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    "common.h"
+#include    "msdos.h"
+#include    "report.h"
+
+#ifndef     PATH_MAX
+# define    PATH_MAX                    2048
+#endif
+
+struct mls_state {
+
+    char **dirs;
+    long nb_dirs;
+    
+    const char *outfile;
+    unsigned long offset;
+    
+    int chs_align;
+
+};
+
+static FILE *ifp;
+
+struct dir_info {
+
+    unsigned int current_cluster;
+    
+    unsigned char current_sector;
+    unsigned char current_entry;
+    unsigned char *scratch;
+    unsigned char flags;
+
+};
+
+static struct mls_state *state = 0;
+static const char *program_name = 0;
+
+static struct msdos_boot_sector bs;
+static int size_fat = 0;
+
+static unsigned int cluster_count = 0;
+static unsigned int data_area = 0;
+static unsigned int info_sector = 0;
+static unsigned int number_of_fats = 0;
+static unsigned int reserved_sectors = 0;
+static unsigned int root_cluster = 0;
+static unsigned int root_dir = 0;
+static unsigned int root_entries = 0;
+static unsigned int sectors_per_cluster = 0;
+static unsigned int sectors_per_fat = 0;
+static unsigned int total_sectors = 0;
+
+struct option {
+
+    const char *name;
+    int index, flags;
+
+};
+
+#define     OPTION_NO_ARG               0x0001
+#define     OPTION_HAS_ARG              0x0002
+
+enum options {
+
+    OPTION_IGNORED = 1,
+    OPTION_ARCA,
+    OPTION_HELP,
+    OPTION_INPUT,
+    OPTION_OFFSET
+
+};
+
+static struct option opts[] = {
+
+    { "i",          OPTION_INPUT,       OPTION_HAS_ARG  },
+    
+    { "-arca",      OPTION_ARCA,        OPTION_NO_ARG   },
+    { "-help",      OPTION_HELP,        OPTION_NO_ARG   },
+    { "-offset",    OPTION_OFFSET,      OPTION_HAS_ARG  },
+    
+    { 0,            0,                  0               }
+
+};
+
+static int strstart (const char *val, const char **str) {
+
+    const char *p = val;
+    const char *q = *str;
+    
+    while (*p != '\0') {
+    
+        if (*p != *q) {
+            return 0;
+        }
+        
+        ++p;
+        ++q;
+    
+    }
+    
+    *str = q;
+    return 1;
+
+}
+
+static void print_help (int exitval) {
+
+    if (!program_name) {
+        goto _exit;
+    }
+    
+    fprintf (stderr, "Usage: %s [options] dirname\n\n", program_name);
+    fprintf (stderr, "Options:\n\n");
+    
+    fprintf (stderr, "    Short options:\n\n");
+    fprintf (stderr, "        -i                Specify the input target.\n");
+    
+    fprintf (stderr, "\n");
+    
+    fprintf (stderr, "    Long options:\n\n");
+    fprintf (stderr, "        --arca            Use CHS-alignment (only works for VHD images).\n");
+    fprintf (stderr, "        --help            Show this help information then exit.\n");
+    fprintf (stderr, "        --offset SECTOR   Read the filesystem starting at SECTOR.\n");
+       
+_exit:
+    
+    exit (exitval);
+
+}
+
+static void *xmalloc (unsigned long size) {
+
+    void *ptr = malloc (size);
+    
+    if (ptr == NULL && size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "memory full (malloc)");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    memset (ptr, 0, size);
+    return ptr;
+
+}
+
+static void *xrealloc (void *ptr, unsigned long size) {
+
+    void *new_ptr = realloc (ptr, size);
+    
+    if (new_ptr == NULL && size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "memory full (realloc)");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    return new_ptr;
+
+}
+
+static char *xstrdup (const char *str) {
+
+    char *ptr = xmalloc (strlen (str) + 1);
+    strcpy (ptr, str);
+    
+    return ptr;
+
+}
+
+static void dynarray_add (void *ptab, long *nb_ptr, void *data) {
+
+    int nb, nb_alloc;
+    void **pp;
+    
+    nb = *nb_ptr;
+    pp = *(void ***) ptab;
+    
+    if ((nb & (nb - 1)) == 0) {
+    
+        if (!nb) {
+            nb_alloc = 1;
+        } else {
+            nb_alloc = nb * 2;
+        }
+        
+        pp = xrealloc (pp, nb_alloc * sizeof (void *));
+        *(void ***) ptab = pp;
+    
+    }
+    
+    pp[nb++] = data;
+    *nb_ptr = nb;
+
+}
+
+static void parse_args (int *pargc, char ***pargv, int optind) {
+
+    char **argv = *pargv;
+    int argc = *pargc;
+    
+    struct option *popt;
+    const char *optarg, *r;
+    
+    if (argc == optind) {
+        print_help (EXIT_SUCCESS);
+    }
+    
+    while (optind < argc) {
+    
+        r = argv[optind++];
+        
+        if (r[0] != '-' || r[1] == '\0') {
+        
+            dynarray_add (&state->dirs, &state->nb_dirs, xstrdup (r));
+            continue;
+        
+        }
+        
+        for (popt = opts; popt; ++popt) {
+        
+            const char *p1 = popt->name;
+            const char *r1 = (r + 1);
+            
+            if (!p1) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "invalid option -- '%s'", r);
+                exit (EXIT_FAILURE);
+            
+            }
+            
+            if (!strstart (p1, &r1)) {
+                continue;
+            }
+            
+            optarg = r1;
+            
+            if (popt->flags & OPTION_HAS_ARG) {
+            
+                if (*optarg == '\0') {
+                
+                    if (optind >= argc) {
+                    
+                        report_at (program_name, 0, REPORT_ERROR, "argument to '%s' is missing", r);
+                        exit (EXIT_FAILURE);
+                    
+                    }
+                    
+                    optarg = argv[optind++];
+                
+                }
+            
+            } else if (*optarg != '\0') {
+                continue;
+            }
+            
+            break;
+        
+        }
+        
+        switch (popt->index) {
+        
+            case OPTION_ARCA: {
+            
+                state->chs_align = 1;
+                break;
+            
+            }
+            
+            case OPTION_HELP: {
+            
+                print_help (EXIT_SUCCESS);
+                break;
+            
+            }
+            
+            case OPTION_INPUT: {
+            
+                if (state->outfile) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "multiple output files provided");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                state->outfile = xstrdup (optarg);
+                break;
+            
+            }
+            
+            case OPTION_OFFSET: {
+            
+                long conversion;
+                char *temp;
+                
+                errno = 0;
+                conversion = strtol (optarg, &temp, 0);
+                
+                if (!*optarg || isspace ((int) *optarg) || errno || *temp) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "bad number for offset (%s)", optarg);
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                if (conversion < 0 || (unsigned long) conversion > UINT_MAX) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "offset must be between 0 and %u", UINT_MAX);
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                state->offset = (unsigned long) conversion;
+                break;
+            
+            }
+            
+            default: {
+            
+                report_at (program_name, 0, REPORT_ERROR, "unsupported option '%s'", r);
+                exit (EXIT_FAILURE);
+            
+            }
+        
+        }
+    
+    }
+
+}
+
+
+static int seekto (long offset) {
+    return fseek (ifp, (state->offset * 512) + offset, SEEK_SET);
+}
+
+static int get_next_entry (struct dir_info *di, struct msdos_dirent *de);
+static int open_dir (const char *target, struct dir_info *di);
+
+static unsigned int get_fat_entry (unsigned char *scratch, unsigned int cluster);
+
+static int canonical_to_dir (char *dest, const char *src) {
+
+    static const char invalid_chars[] = "\"*+,./:;<=>?[\\]|";
+    
+    int i, j;
+    int namelen = 0, dots = 0, extlen = 0;
+    
+    memset (dest, ' ', 11);
+    
+    if (*src == '\0' || *src == '.') {
+        return -1;
+    }
+    
+    for (j = i = 0; *src != '\0'; i++) {
+    
+        int c = (unsigned char) *src++;
+        
+        if (c == '/' || c == '\\') {
+            break;
+        }
+        
+        if (i >= 12) {
+            return -1;
+        }
+        
+        if (i == 0 && c == 0xE5) {
+        
+            /**
+             * 0xE5 in the first character of the name is a marker for delected files,
+             * it needs to be translated to 0x05.
+             */
+            c = 0x05;
+        
+        } else if (c == '.') {
+        
+            if (dots++) {
+                return -1;
+            }
+            
+            j = 8;
+            continue;
+        
+        }
+        
+        if (c <= 0x20 || strchr (invalid_chars, c)) {
+            return -1;
+        }
+        
+        if (dots) {
+        
+            if (++extlen > 3) {
+                return -1;
+            }
+        
+        } else {
+        
+            if (++namelen > 8) {
+                return -1;
+            }
+        
+        }
+        
+        if (c >= 'a' && c <= 'z') {
+            c -= 'a' - 'A';
+        }
+        
+        dest[j++] = c;
+    
+    }
+    
+    return 0;
+
+}
+
+static int get_next_entry (struct dir_info *di, struct msdos_dirent *de) {
+
+    unsigned long offset;
+    
+    if (di->current_entry >= 512 / sizeof (*de)) {
+    
+        di->current_entry = 0;
+        di->current_sector++;
+        
+        if (di->current_cluster == 0) {
+        
+            unsigned long offset;
+            
+            if (di->current_sector * (512 / sizeof (*de)) >= root_entries) {
+                return -1;
+            }
+            
+            offset = (unsigned long) root_dir + di->current_sector;
+            
+            if (seekto (offset * 512) || fread (di->scratch, 512, 1, ifp) != 1) {
+                return -1;
+            }
+        
+        } else {
+        
+            if (di->current_sector >= sectors_per_cluster) {
+            
+                di->current_sector = 0;
+                
+                if ((size_fat == 12 && di->current_cluster >= 0x0FF7) || (size_fat == 16 && di->current_cluster >= 0xFFF7) || (size_fat == 32 && di->current_cluster >= 0x0FFFFFF7)) {
+                
+                    if (!(di->flags & 0x01)) {
+                        return -1;
+                    }
+                    
+                    return 1;
+                
+                }
+                
+                di->current_cluster = get_fat_entry (di->scratch, di->current_cluster);
+            
+            }
+            
+            offset = (unsigned long) data_area;
+            offset += ((di->current_cluster - 2) * sectors_per_cluster);
+            offset += di->current_sector;
+            
+            if (seekto (offset * 512) || fread (di->scratch, 512, 1, ifp) != 1) {
+                return -1;
+            }
+        
+        }
+    
+    }
+    
+    memcpy (de, &(((struct msdos_dirent *) di->scratch)[di->current_entry]), sizeof (*de));
+    
+    if (de->name[0] == 0) {
+    
+        if (di->flags & 0x01) {
+            return 0;
+        }
+        
+        return -1;
+    
+    }
+    
+    if (de->name[0] == 0x05) {
+    
+        de->name[0] = 0xE5;
+        return 0;
+    
+    }
+    
+    di->current_entry++;
+    return 0;
+
+}
+
+static int open_dir (const char *target, struct dir_info *di) {
+
+    di->flags = 0;
+    
+    if (!strlen ((char *) target) || (strlen ((char *) target) == 1 && (target[0] == '/' || target[0] == '\\'))) {
+    
+        unsigned long offset;
+        
+        if (size_fat == 32) {
+        
+            di->current_cluster = root_cluster;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) data_area + ((di->current_cluster - 2) * sectors_per_cluster);
+        
+        } else {
+        
+            di->current_cluster = 0;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) root_dir;
+        
+        }
+        
+        if (seekto (offset * 512) || fread (di->scratch, 512, 1, ifp) != 1) {
+            return -1;
+        }
+    
+    } else {
+    
+        unsigned char tmpfn[12];
+        unsigned char *ptr;
+        
+        struct msdos_dirent de;
+        unsigned int result;
+        
+        unsigned long offset;
+        
+        if (size_fat == 32) {
+        
+            di->current_cluster = root_cluster;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) data_area + ((di->current_cluster - 2) * sectors_per_cluster);
+        
+        } else {
+        
+            di->current_cluster = 0;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) root_dir;
+        
+        }
+        
+        if (seekto (offset * 512) || fread (di->scratch, 512, 1, ifp) != 1) {
+            return -1;
+        }
+        
+        ptr = (unsigned char *) target;
+        
+        while ((*ptr == '/' || *ptr == '\\') && *ptr) {
+            ptr++;
+        }
+        
+        while (*ptr) {
+        
+            if (canonical_to_dir ((char *) tmpfn, (char *) ptr) < 0) {
+            
+                fprintf (stderr, "Failed to convert to 8:3\n");
+                return -1;
+            
+            }
+            
+            de.name[0] = 0;
+            
+            do {
+                result = get_next_entry (di, &de);
+            } while (!result && memcmp (de.name, tmpfn, 11));
+            
+            if (!memcmp (de.name, tmpfn, 11) && (de.attr & ATTR_DIR) == ATTR_DIR) {
+            
+                unsigned long offset;
+                
+                di->current_cluster = (unsigned int) de.startlo[0] | ((unsigned int) de.startlo[1]) << 8 | ((unsigned int) de.starthi[0]) << 16 | ((unsigned int) de.starthi[1]) << 24;
+                di->current_entry = 0;
+                di->current_sector = 0;
+                
+                offset = (unsigned long) (data_area + ((di->current_cluster - 2) * sectors_per_cluster));
+                
+                if (seekto (offset * 512)) {
+                    return -1;
+                }
+                
+                if (fread (di->scratch, 512, 1, ifp) != 1) {
+                    return -1;
+                }
+            
+            } else if (!memcmp (de.name, tmpfn, 11) && !(de.attr & ATTR_DIR)) {
+                return -1;
+            }
+            
+            while (*ptr != '/' && *ptr != '\\' && *ptr) {
+                ptr++;
+            }
+            
+            if (*ptr == '/' || *ptr == '\\') {
+                ptr++;
+            }
+        
+        }
+        
+        if (!di->current_cluster) {
+            return -1;
+        }
+    
+    }
+    
+    return 0;
+
+}
+
+static unsigned int get_fat_entry (unsigned char *scratch, unsigned int cluster) {
+
+    unsigned int offset, sector, result;
+    
+    if (size_fat == 12) {
+        offset = cluster + (cluster / 2);
+    } else if (size_fat == 16) {
+        offset = cluster * 2;
+    } else if (size_fat == 32) {
+        offset = cluster * 4;
+    } else {
+        return 0x0FFFFFF7;
+    }
+    
+    sector = (offset / 512) + reserved_sectors;
+    
+    if (seekto ((unsigned long) sector * 512) || fread (scratch, 512, 1, ifp) != 1) {
+        return 0x0FFFFFF7;
+    }
+    
+    offset %= 512;
+    
+    if (size_fat == 12) {
+    
+        if (offset == 511) {
+        
+            result = (unsigned int) scratch[offset];
+            sector++;
+            
+            if (seekto ((unsigned long) sector * 512) || fread (scratch, 512, 1, ifp) != 1) {
+                return 0x0FFFFFF7;
+            }
+            
+            result |= ((unsigned int) scratch[0]) << 8;
+        
+        } else {
+            result = (unsigned int) scratch[offset] | ((unsigned int) scratch[offset + 1]) << 8;
+        }
+        
+        if (cluster & 1) {
+            result = result >> 4;
+        } else {
+            result = result & 0x0FFF;
+        }
+    
+    } else if (size_fat == 16) {
+        result = (unsigned int) scratch[offset] | ((unsigned int) scratch[offset + 1]) << 8;
+    } else if (size_fat == 32) {
+        result = ((unsigned int) scratch[offset] | ((unsigned int) scratch[offset + 1]) << 8 | ((unsigned int) scratch[offset + 2]) << 16 | ((unsigned int) scratch[offset + 3]) << 24) & 0x0FFFFFFF;
+    } else {
+        result = 0x0FFFFFF7;
+    }
+    
+    return result;
+
+}
+
+struct vhd_footer {
+
+    unsigned char cookie[8];
+    unsigned char features[4];
+    
+    struct {
+    
+        unsigned char major[2];
+        unsigned char minor[2];
+    
+    } version;
+    
+    unsigned char next_offset[8];
+    unsigned char modified_time[4];
+    unsigned char creator_name[4];
+    
+    struct {
+    
+        unsigned char major[2];
+        unsigned char minor[2];
+    
+    } creator_version;
+    
+    unsigned char creator_host[4];
+    unsigned char disk_size[8];
+    unsigned char data_size[8];
+    
+    struct {
+    
+        unsigned char cylinders[2];
+        unsigned char heads_per_cyl;
+        unsigned char secs_per_track;
+    
+    } disk_geom;
+    
+    unsigned char disk_type[4];
+    unsigned char checksum[4];
+    unsigned char identifier[16];
+    unsigned char saved_state;
+    unsigned char reserved[427];
+
+};
+
+int main (int argc, char **argv) {
+
+    unsigned char *scratch;
+    char *target, filename[13];
+    
+    struct dir_info di;
+    struct msdos_dirent de;
+    
+    unsigned int bytes, timestamp;
+    long i, j, k;
+    
+    struct vhd_footer footer;
+    
+    if (argc && *argv) {
+    
+        char *p;
+        program_name = *argv;
+        
+        if ((p = strrchr (program_name, '/')) || (p = strrchr (program_name, '\\'))) {
+            program_name = (p + 1);
+        }
+    
+    }
+    
+    state = xmalloc (sizeof (*state));
+    parse_args (&argc, &argv, 1);
+    
+    if (!state->outfile) {
+        print_help (EXIT_FAILURE);
+    }
+    
+    if (state->nb_dirs == 0) {
+        dynarray_add (&state->dirs, &state->nb_dirs, "/");
+    }
+    
+    if ((ifp = fopen (state->outfile, "rb")) == NULL) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "faild to open '%s' for reading", state->outfile);
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (state->chs_align) {
+    
+        long size = sizeof (footer), offset;
+        fseek (ifp, 0, SEEK_END);
+        
+        offset = ftell (ifp) - size;
+        
+        if (!fseek (ifp, offset, SEEK_SET)) {
+        
+            if (fread (&footer, size, 1, ifp) == 1) {
+            
+                if (footer.cookie[0] == 0x63 && footer.cookie[1] == 0x6F && footer.cookie[2] == 0x6E && footer.cookie[3] == 0x65 && footer.cookie[4] == 0x63 && footer.cookie[5] == 0x74 && footer.cookie[6] == 0x69 && footer.cookie[7] == 0x78) {
+                
+                    long old = state->offset, secs_per_track = footer.disk_geom.secs_per_track;
+                    
+                    if (state->offset % secs_per_track) {
+                    
+                        state->offset = ((state->offset / secs_per_track) + 1) * secs_per_track;
+                        report_at (program_name, 0, REPORT_WARNING, "offset changed from %lu to %lu", old, state->offset);
+                    
+                    }
+                
+                }
+            
+            }
+        
+        }
+    
+    }
+    
+    if (seekto ((unsigned long) 0) || fread (&bs, sizeof (bs), 1, ifp) != 1) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "failed whilst reading boot sector");
+        fclose (ifp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (bs.boot_jump[0] != 0xEB || bs.boot_jump[1] < 0x16 || bs.boot_jump[2] != 0x90) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+        fclose (ifp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    sectors_per_cluster = (unsigned int) bs.sectors_per_cluster;
+    reserved_sectors = (unsigned int) bs.reserved_sectors[0] | (((unsigned int) bs.reserved_sectors[1]) << 8);
+    number_of_fats = (unsigned int) bs.no_fats;
+    root_entries = (unsigned int) bs.root_entries[0] | (((unsigned int) bs.root_entries[1]) << 8);
+    total_sectors = (unsigned int) bs.total_sectors16[0] | (((unsigned int) bs.total_sectors16[1]) << 8);
+    sectors_per_fat = (unsigned int) bs.sectors_per_fat16[0] | (((unsigned int) bs.sectors_per_fat16[1]) << 8);
+    
+    if (!sectors_per_cluster || !reserved_sectors || !number_of_fats) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+        fclose (ifp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (!root_entries) {
+    
+        if (bs.boot_jump[1] < 0x58) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ifp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        root_cluster = (unsigned int) bs.fat32.root_cluster[0] | (((unsigned int) bs.fat32.root_cluster[1]) << 8) | (((unsigned int) bs.fat32.root_cluster[2]) << 16) | (((unsigned int) bs.fat32.root_cluster[3]) << 24);
+        
+        if (!root_cluster) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ifp);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (!total_sectors) {
+    
+        if (bs.boot_jump[1] < 0x22) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ifp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        total_sectors = (unsigned int) bs.total_sectors32[0] | (((unsigned int) bs.total_sectors32[1]) << 8) | (((unsigned int) bs.total_sectors32[2]) << 16) | (((unsigned int) bs.total_sectors32[3]) << 24);
+        
+        if (!total_sectors) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ifp);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (!sectors_per_fat) {
+    
+        if (bs.boot_jump[1] < 0x58) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ifp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        sectors_per_fat = (unsigned int) bs.fat32.sectors_per_fat32[0] | (((unsigned int) bs.fat32.sectors_per_fat32[1]) << 8) | (((unsigned int) bs.fat32.sectors_per_fat32[2]) << 16) | (((unsigned int) bs.fat32.sectors_per_fat32[3]) << 24);
+        
+        if (!sectors_per_fat) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ifp);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (root_entries) {
+    
+        root_dir = reserved_sectors + (sectors_per_fat * 2);
+        data_area = root_dir + (((root_entries * 32) + (512 - 1)) / 512);
+    
+    } else {
+    
+        data_area = reserved_sectors + (sectors_per_fat * 2);
+        
+        /*root_dir = data_area + ((root_cluster - 2) * sectors_per_cluster);*/
+        /*root_dir = root_cluster;*/
+    
+    }
+    
+    cluster_count = (total_sectors - data_area) / sectors_per_cluster;
+    
+    if (bs.boot_jump[1] == 0x58) {
+    
+        info_sector = (unsigned int) bs.fat32.info_sector[0] | (((unsigned int) bs.fat32.info_sector[1]) << 8);
+        
+        if (!info_sector) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ifp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        size_fat = 32;
+    
+    } else if (cluster_count <= MAX_CLUST_12) {
+        size_fat = 12;
+    } else if (cluster_count >= MIN_CLUST_16 && cluster_count <= MAX_CLUST_16) {
+        size_fat = 16;
+    } else {
+    
+        report_at (program_name, 0, REPORT_ERROR, "FAT is not 12, 16 or 32 bits");
+        fclose (ifp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (!(scratch = (unsigned char *) malloc (512))) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Out of memory");
+        fclose (ifp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    di.scratch = scratch;
+    
+    for (i = 0; i < state->nb_dirs; i++) {
+    
+        target = state->dirs[i];
+        
+        if (*target == '/' || *target == '\\') {
+            target++;
+        }
+        
+        if (open_dir ((char *) target, &di) < 0) {
+        
+            fprintf (stderr, "failed to open directory\n");
+            return -1;
+        
+        }
+        
+        if (state->nb_dirs > 1) {
+        
+            if (!*target) {
+                printf ("/\n\n");
+            } else {
+                printf ("%s:\n\n", target);
+            }
+        
+        }
+        
+        while (!get_next_entry (&di, &de)) {
+        
+            /*if (!de.name[0] || de.name[0] == 0xE5 || (de.attr & ATTR_LONG_NAME) == ATTR_LONG_NAME) {*/
+            if (!de.name[0] || de.name[0] == 0xE5) {
+                continue;
+            }
+            
+            if ((de.attr & ATTR_VOLUME_ID) == ATTR_VOLUME_ID) {
+                continue;
+            }
+            
+            memset (filename, 0, 13);
+            
+            for (j = 0, k = 0; j < 11; ++j) {
+            
+                if (j < 8 && de.name[j] == ' ') {
+                
+                    if ((de.attr & ATTR_DIR) != ATTR_DIR && k > 0 && filename[k - 1] != '.') {
+                        filename[k++] = '.';
+                    }
+                
+                } else {
+                
+                    if (j == 8 && (de.attr & ATTR_DIR) != ATTR_DIR && filename[k - 1] != '.') {
+                        filename[k++] = '.';
+                    }
+                    
+                    filename[k++] = de.name[j];
+                
+                }
+            
+            }
+            
+            if (state->nb_dirs > 1) {
+                printf ("    ");
+            }
+            
+            printf ("%s", filename);
+            
+            while (k < 12) {
+            
+                printf (" ");
+                k++;
+            
+            }
+            
+            if ((de.attr & ATTR_DIR) == ATTR_DIR) {
+                printf ("    <DIR>    "); 
+            } else {
+                printf ("             ");
+            }
+            
+            if ((de.attr & ATTR_DIR) == ATTR_DIR) {
+                printf ("          ");
+            } else {
+            
+                bytes = (unsigned int) de.size[0] | (((unsigned int) de.size[1]) << 8) | (((unsigned int) de.size[2]) << 16) | (((unsigned int) de.size[3]) << 24);
+                printf ("%10u", bytes);
+            
+            }
+            
+            timestamp = (unsigned short) de.date[0] | (((unsigned short) de.date[1]) << 8);
+            printf ("    %04d-%02d-%02d", ((timestamp >> 9) & 0x3f) + 1980, (timestamp >> 5) & 0x0f, timestamp & 0x1f);
+            
+            timestamp = (unsigned short) de.time[0] | (((unsigned short) de.time[1]) << 8);
+            printf ("    %02d:%02d:%02d", (timestamp >> 11) & 0x3f, (timestamp >> 5) & 0x3f, (timestamp & 0x1f) << 1);
+            
+            printf ("\n");
+        
+        }
+        
+        if (state->nb_dirs > 1 && i < state->nb_dirs - 1) {
+            printf ("\n");
+        }
+    
+    }
+    
+    free (scratch);
+    fclose (ifp);
+    
+    return EXIT_SUCCESS;
+
+}
diff --git a/mmd.c b/mmd.c
new file mode 100644 (file)
index 0000000..65780d4
--- /dev/null
+++ b/mmd.c
@@ -0,0 +1,1436 @@
+/******************************************************************************
+ * @file            mmd.c
+ *****************************************************************************/
+#include    <ctype.h>
+#include    <errno.h>
+#include    <limits.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    "common.h"
+#include    "mmd.h"
+#include    "msdos.h"
+#include    "report.h"
+#include    "write7x.h"
+
+#ifndef     PATH_MAX
+# define    PATH_MAX                    2048
+#endif
+
+static FILE *ofp;
+
+struct dir_info {
+
+    unsigned int current_cluster;
+    unsigned int root_cluster;
+    
+    unsigned char current_sector;
+    unsigned char current_entry;
+    unsigned char *scratch;
+    unsigned char flags;
+
+};
+
+static struct mmd_state *state = 0;
+static const char *program_name = 0;
+
+static struct msdos_boot_sector bs;
+static int size_fat = 0;
+
+static unsigned int cluster_count = 0;
+static unsigned int data_area = 0;
+static unsigned int info_sector = 0;
+static unsigned int number_of_fats = 0;
+static unsigned int reserved_sectors = 0;
+static unsigned int root_cluster = 0;
+static unsigned int root_dir = 0;
+static unsigned int root_entries = 0;
+static unsigned int sectors_per_cluster = 0;
+static unsigned int sectors_per_fat = 0;
+static unsigned int total_sectors = 0;
+
+struct option {
+
+    const char *name;
+    int index, flags;
+
+};
+
+#define     OPTION_NO_ARG               0x0001
+#define     OPTION_HAS_ARG              0x0002
+
+enum options {
+
+    OPTION_IGNORED = 1,
+    OPTION_ARCA,
+    OPTION_HELP,
+    OPTION_INPUT,
+    OPTION_OFFSET
+
+};
+
+static struct option opts[] = {
+
+    { "i",          OPTION_INPUT,       OPTION_HAS_ARG  },
+    
+    { "-arca",      OPTION_ARCA,        OPTION_NO_ARG   },
+    { "-help",      OPTION_HELP,        OPTION_NO_ARG   },
+    { "-offset",    OPTION_OFFSET,      OPTION_HAS_ARG  },
+    
+    { 0,            0,                  0               }
+
+};
+
+static int strstart (const char *val, const char **str) {
+
+    const char *p = val;
+    const char *q = *str;
+    
+    while (*p != '\0') {
+    
+        if (*p != *q) {
+            return 0;
+        }
+        
+        ++p;
+        ++q;
+    
+    }
+    
+    *str = q;
+    return 1;
+
+}
+
+static void print_help (int exitval) {
+
+    if (!program_name) {
+        goto _exit;
+    }
+    
+    fprintf (stderr, "Usage: %s [options] dirname\n\n", program_name);
+    fprintf (stderr, "Options:\n\n");
+    
+    fprintf (stderr, "    Short options:\n\n");
+    fprintf (stderr, "        -i                Specify the input target.\n");
+    
+    fprintf (stderr, "\n");
+    
+    fprintf (stderr, "    Long options:\n\n");
+    fprintf (stderr, "        --arca            Use CHS-alignment (only works for VHD images).\n");
+    fprintf (stderr, "        --help            Show this help information then exit.\n");
+    fprintf (stderr, "        --offset SECTOR   Write the filesystem starting at SECTOR.\n");
+       
+_exit:
+    
+    exit (exitval);
+
+}
+
+static void *xmalloc (unsigned long size) {
+
+    void *ptr = malloc (size);
+    
+    if (ptr == NULL && size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "memory full (malloc)");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    memset (ptr, 0, size);
+    return ptr;
+
+}
+
+static void *xrealloc (void *ptr, unsigned long size) {
+
+    void *new_ptr = realloc (ptr, size);
+    
+    if (new_ptr == NULL && size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "memory full (realloc)");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    return new_ptr;
+
+}
+
+static char *xstrdup (const char *str) {
+
+    char *ptr = xmalloc (strlen (str) + 1);
+    strcpy (ptr, str);
+    
+    return ptr;
+
+}
+
+static void dynarray_add (void *ptab, long *nb_ptr, void *data) {
+
+    int nb, nb_alloc;
+    void **pp;
+    
+    nb = *nb_ptr;
+    pp = *(void ***) ptab;
+    
+    if ((nb & (nb - 1)) == 0) {
+    
+        if (!nb) {
+            nb_alloc = 1;
+        } else {
+            nb_alloc = nb * 2;
+        }
+        
+        pp = xrealloc (pp, nb_alloc * sizeof (void *));
+        *(void ***) ptab = pp;
+    
+    }
+    
+    pp[nb++] = data;
+    *nb_ptr = nb;
+
+}
+
+static void parse_args (int *pargc, char ***pargv, int optind) {
+
+    char **argv = *pargv;
+    int argc = *pargc;
+    
+    struct option *popt;
+    const char *optarg, *r;
+    
+    if (argc == optind) {
+        print_help (EXIT_SUCCESS);
+    }
+    
+    while (optind < argc) {
+    
+        r = argv[optind++];
+        
+        if (r[0] != '-' || r[1] == '\0') {
+        
+            dynarray_add (&state->dirs, &state->nb_dirs, xstrdup (r));
+            continue;
+        
+        }
+        
+        for (popt = opts; popt; ++popt) {
+        
+            const char *p1 = popt->name;
+            const char *r1 = (r + 1);
+            
+            if (!p1) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "invalid option -- '%s'", r);
+                exit (EXIT_FAILURE);
+            
+            }
+            
+            if (!strstart (p1, &r1)) {
+                continue;
+            }
+            
+            optarg = r1;
+            
+            if (popt->flags & OPTION_HAS_ARG) {
+            
+                if (*optarg == '\0') {
+                
+                    if (optind >= argc) {
+                    
+                        report_at (program_name, 0, REPORT_ERROR, "argument to '%s' is missing", r);
+                        exit (EXIT_FAILURE);
+                    
+                    }
+                    
+                    optarg = argv[optind++];
+                
+                }
+            
+            } else if (*optarg != '\0') {
+                continue;
+            }
+            
+            break;
+        
+        }
+        
+        switch (popt->index) {
+        
+            case OPTION_ARCA: {
+            
+                state->chs_align = 1;
+                break;
+            
+            }
+            
+            case OPTION_HELP: {
+            
+                print_help (EXIT_SUCCESS);
+                break;
+            
+            }
+            
+            case OPTION_INPUT: {
+            
+                if (state->outfile) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "multiple output files provided");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                state->outfile = xstrdup (optarg);
+                break;
+            
+            }
+            
+            case OPTION_OFFSET: {
+            
+                long conversion;
+                char *temp;
+                
+                errno = 0;
+                conversion = strtol (optarg, &temp, 0);
+                
+                if (!*optarg || isspace ((int) *optarg) || errno || *temp) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "bad number for offset (%s)", optarg);
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                if (conversion < 0 || (unsigned long) conversion > UINT_MAX) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "offset must be between 0 and %u", UINT_MAX);
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                state->offset = (unsigned long) conversion;
+                break;
+            
+            }
+            
+            default: {
+            
+                report_at (program_name, 0, REPORT_ERROR, "unsupported option '%s'", r);
+                exit (EXIT_FAILURE);
+            
+            }
+        
+        }
+    
+    }
+
+}
+
+
+static int seekto (long offset) {
+    return fseek (ofp, (state->offset * 512) + offset, SEEK_SET);
+}
+
+
+static int create_dir (const char *target, unsigned char *scratch);
+static int get_next_entry (struct dir_info *di, struct msdos_dirent *de);
+static int get_free_dirent (const char *path, struct dir_info *di, struct msdos_dirent *de);
+static int open_dir (const char *target, struct dir_info *di);
+
+static unsigned int get_fat_entry (unsigned char *scratch, unsigned int cluster);
+static int set_fat_entry (unsigned char *scratch, unsigned int cluster, unsigned int value);
+
+static unsigned int get_free_fat (unsigned char *scratch);
+
+static int canonical_to_dir (char *dest, const char *src) {
+
+    static const char invalid_chars[] = "\"*+,./:;<=>?[\\]|";
+    
+    int i, j;
+    int namelen = 0, dots = 0, extlen = 0;
+    
+    memset (dest, ' ', 11);
+    
+    if (*src == '\0' || *src == '.') {
+        return -1;
+    }
+    
+    for (j = i = 0; *src != '\0'; i++) {
+    
+        int c = (unsigned char) *src++;
+        
+        if (c == '/' || c == '\\') {
+            break;
+        }
+        
+        if (i >= 12) {
+            return -1;
+        }
+        
+        if (i == 0 && c == 0xE5) {
+        
+            /**
+             * 0xE5 in the first character of the name is a marker for delected files,
+             * it needs to be translated to 0x05.
+             */
+            c = 0x05;
+        
+        } else if (c == '.') {
+        
+            if (dots++) {
+                return -1;
+            }
+            
+            j = 8;
+            continue;
+        
+        }
+        
+        if (c <= 0x20 || strchr (invalid_chars, c)) {
+            return -1;
+        }
+        
+        if (dots) {
+        
+            if (++extlen > 3) {
+                return -1;
+            }
+        
+        } else {
+        
+            if (++namelen > 8) {
+                return -1;
+            }
+        
+        }
+        
+        if (c >= 'a' && c <= 'z') {
+            c -= 'a' - 'A';
+        }
+        
+        dest[j++] = c;
+    
+    }
+    
+    return 0;
+
+}
+
+static int create_dir (const char *target, unsigned char *scratch) {
+
+    unsigned char tmppath[PATH_MAX];
+    unsigned char filename[12];
+    unsigned char *p;
+    
+    struct dir_info di;
+    struct msdos_dirent de;
+    
+    unsigned short date;
+    unsigned short time;
+    
+    unsigned int cluster, i;
+    unsigned int dir_sector;
+    unsigned int dir_offset;
+    
+    /* Get a local copy of the target.  If it's larger than PATH_MAX, abort. */
+    strncpy ((char *) tmppath, (char *) target, PATH_MAX);
+    tmppath[PATH_MAX - 1] = 0;
+    
+    if (strcmp ((char *) target, (char *) tmppath)) {
+        return -1;
+    }
+    
+    /* Strip leading seperators. */
+    while (tmppath[0] == '/' || tmppath[0] == '\\') {
+        strcpy ((char *) tmppath, (char *) tmppath + 1);
+    }
+    
+    /* Parse filename off the end of the suppiled target. */
+    p = tmppath;
+    
+    while (*(p++));
+    
+    p--;
+    
+    while (p > tmppath && *p != '/' && *p != '\\') {
+        p--;
+    }
+    
+    if (*p == '/' || *p == '\\') {
+        p++;
+    }
+    
+    if (canonical_to_dir ((char *) filename, (char *) p) < 0) {
+    
+        fprintf (stderr, "Failed to convert to 8:3\n");
+        return -1;
+    
+    }
+    
+    if (p > tmppath) {
+        p--;
+    }
+    
+    if (*p == '/' || *p == '\\' || p == tmppath) {
+        *p = 0;
+    }
+    
+    di.scratch = scratch;
+    
+    if (open_dir ((char *) tmppath, &di) < 0) {
+    
+        fprintf (stderr, "Failed to open directory\n");
+        return -1;
+    
+    }
+    
+    while (!get_next_entry (&di, &de)) {
+    
+        /*if (!de.name[0] || de.name[0] == 0xE5 || (de.attr & ATTR_LONG_NAME) == ATTR_LONG_NAME) {*/
+        if (!de.name[0] || de.name[0] == 0xE5) {
+            continue;
+        }
+        
+        if (!memcmp (de.name, filename, 11)) {
+        
+            fprintf (stderr, "%s already exists.\n", target);
+            return -1;
+        
+        }
+    
+    }
+    
+    if (get_free_dirent ((char *) tmppath, &di, &de) < 0) {
+        return -1;
+    }
+    
+    date = generate_datestamp ();
+    time = generate_timestamp ();
+    
+    memset (&de, 0, sizeof (de));
+    memcpy (de.name, filename, 11);
+    
+    cluster = get_free_fat (scratch);
+    
+    if (cluster == 0x0FFFFFF7 || set_fat_entry (scratch, cluster, 0x0FFFFFF8) < 0) {
+        return -1;
+    }
+    
+    write721_to_byte_array (de.startlo, cluster);
+    write721_to_byte_array (de.starthi, cluster >> 16);
+    
+    de.attr = ATTR_DIR;
+    
+    write721_to_byte_array (de.ctime, time);
+    write721_to_byte_array (de.cdate, date);
+    write721_to_byte_array (de.adate, date);
+    write721_to_byte_array (de.time, time);
+    write721_to_byte_array (de.date, date);
+    
+    if (di.current_cluster == 0) {
+        dir_sector = root_dir + di.current_sector;
+    } else {
+        dir_sector = data_area + ((di.current_cluster - 2) * sectors_per_cluster) + di.current_sector;
+    }
+    
+    /*fi->dir_offset = di.current_entry - 1;*/
+    dir_offset = di.current_entry;
+    
+    if (seekto ((unsigned long) dir_sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+        return -1;
+    }
+    
+    memcpy (&(((struct msdos_dirent *) scratch)[dir_offset]), &de, sizeof (de));
+    
+    if (seekto ((unsigned long) dir_sector * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+        return -1;
+    }
+    
+    memset (scratch, 0, 512);
+    
+    for (i = 0; i < sectors_per_cluster; i++) {
+    
+        unsigned long offset = (unsigned long) (data_area + ((cluster - 2) * sectors_per_cluster) + i);
+        
+        if (seekto (offset * 512)) {
+            return -1;
+        }
+        
+        if (fwrite (scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+    
+    }
+    
+    dir_offset = 0;
+    dir_sector = data_area + ((cluster - 2) * sectors_per_cluster);
+    
+    memset (de.name, ' ', 11);
+    de.name[0] = '.';
+    
+    write721_to_byte_array (de.startlo, cluster);
+    write721_to_byte_array (de.starthi, cluster >> 16);
+    
+    memcpy (&(((struct msdos_dirent *) scratch)[dir_offset]), &de, sizeof (de));
+    dir_offset++;
+    
+    de.name[1] = '.';
+    
+    if (di.current_cluster == root_cluster) {
+        di.current_cluster = 0;
+    }
+    
+    if (di.root_cluster == root_cluster) {
+        di.root_cluster = 0;
+    }
+    
+    write721_to_byte_array (de.startlo, di.root_cluster);
+    write721_to_byte_array (de.starthi, di.root_cluster >> 16);
+    
+    memcpy (&(((struct msdos_dirent *) scratch)[dir_offset]), &de, sizeof (de));
+    
+    if (seekto ((unsigned long) dir_sector * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+        return -1;
+    }
+    
+    if (size_fat == 32) {
+    
+        struct fat32_fsinfo *info;
+        
+        unsigned int free_clusters;
+        unsigned int next_cluster;
+        
+        if (seekto ((unsigned long) info_sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+        
+        info = (struct fat32_fsinfo *) (scratch + 0x1e0);
+        
+        free_clusters = (unsigned int) info->free_clusters[0] | (((unsigned int) info->free_clusters[1]) << 8) | (((unsigned int) info->free_clusters[2]) << 16) | (((unsigned int) info->free_clusters[3]) << 24);
+        next_cluster = (unsigned int) info->next_cluster[0] | (((unsigned int) info->next_cluster[1]) << 8) | (((unsigned int) info->next_cluster[2]) << 16) | (((unsigned int) info->next_cluster[3]) << 24);
+        
+        free_clusters--;
+        next_cluster++;
+        
+        write741_to_byte_array (info->free_clusters, free_clusters);
+        write741_to_byte_array (info->next_cluster, next_cluster);
+        
+        if (seekto ((unsigned long) info_sector * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+    
+    }
+    
+    return 0;
+
+}
+
+static int get_free_dirent (const char *path, struct dir_info *di, struct msdos_dirent *de) {
+
+    int entry;
+    unsigned int i, tempclust;
+    
+    if (open_dir (path, di) < 0) {
+        return -1;
+    }
+    
+    entry = 0;
+    di->flags |= 0x01;
+    
+    do {
+    
+        entry = get_next_entry (di, de);
+        
+        /*if (entry == 0 && (!de->name[0] || de->name[0] == 0xE5 || (de->attr & ATTR_LONG_NAME) == ATTR_LONG_NAME)) {*/
+        if (entry == 0 && (!de->name[0] || de->name[0] == 0xE5)) {
+        
+            /*if (de->name[0] == 0xE5 || (de->attr & ATTR_LONG_NAME) == ATTR_LONG_NAME) {*/
+            if (de->name[0] == 0xE5) {
+                di->current_entry--;
+            }
+            
+            return 0;
+        
+        } else if (entry == -1) {
+            return -1;
+        } else if (entry == 1) {
+        
+            if ((tempclust = get_free_fat (di->scratch) == 0x0FFFFFF7)) {
+                return -1;
+            }
+            
+            memset (di->scratch, 0, 512);
+            
+            for (i = 0; i < sectors_per_cluster; i++) {
+            
+                unsigned long offset = (unsigned long) (data_area + ((tempclust - 2) * sectors_per_cluster) + i);
+                
+                if (seekto (offset * 512)) {
+                    return -1;
+                }
+                
+                if (fwrite (di->scratch, 512, 1, ofp) != 1) {
+                    return -1;
+                }
+            
+            }
+            
+            i = 0;
+            
+            if (set_fat_entry (di->scratch, di->current_cluster, tempclust) < 0) {
+                return -1;
+            }
+            
+            di->current_cluster = tempclust;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            if (size_fat == 12) {
+                tempclust = 0x0FF8;
+            } else if (size_fat == 16) {
+                tempclust = 0xFFF8;
+            } else if (size_fat == 32) {
+            
+                struct fat32_fsinfo *info;
+                
+                unsigned int free_clusters;
+                unsigned int next_cluster;
+                
+                tempclust = 0x0ffffff8;
+                
+                if (seekto ((unsigned long) info_sector * 512) || fread (di->scratch, 512, 1, ofp) != 1) {
+                    return -1;
+                }
+                
+                info = (struct fat32_fsinfo *) (di->scratch + 0x1e0);
+                
+                free_clusters = (unsigned int) info->free_clusters[0] | (((unsigned int) info->free_clusters[1]) << 8) | (((unsigned int) info->free_clusters[2]) << 16) | (((unsigned int) info->free_clusters[3]) << 24);
+                next_cluster = (unsigned int) info->next_cluster[0] | (((unsigned int) info->next_cluster[1]) << 8) | (((unsigned int) info->next_cluster[2]) << 16) | (((unsigned int) info->next_cluster[3]) << 24);
+                
+                free_clusters--;
+                next_cluster++;
+                
+                write741_to_byte_array (info->free_clusters, free_clusters);
+                write741_to_byte_array (info->next_cluster, next_cluster);
+                
+                if (seekto ((unsigned long) info_sector * 512) || fwrite (di->scratch, 512, 1, ofp) != 1) {
+                    return -1;
+                }
+            
+            } else {
+                return -1;
+            }
+        
+        }
+    
+    } while (!entry);
+    
+    /* We should't get here! */
+    return -1;
+
+}
+
+static int get_next_entry (struct dir_info *di, struct msdos_dirent *de) {
+
+    unsigned long offset;
+    
+    if (di->current_entry >= 512 / sizeof (*de)) {
+    
+        di->current_entry = 0;
+        di->current_sector++;
+        
+        if (di->current_cluster == 0) {
+        
+            unsigned long offset;
+            
+            if (di->current_sector * (512 / sizeof (*de)) >= root_entries) {
+                return -1;
+            }
+            
+            offset = (unsigned long) root_dir + di->current_sector;
+            
+            if (seekto (offset * 512) || fread (di->scratch, 512, 1, ofp) != 1) {
+                return -1;
+            }
+        
+        } else {
+        
+            if (di->current_sector >= sectors_per_cluster) {
+            
+                di->current_sector = 0;
+                
+                if ((size_fat == 12 && di->current_cluster >= 0x0FF7) || (size_fat == 16 && di->current_cluster >= 0xFFF7) || (size_fat == 32 && di->current_cluster >= 0x0FFFFFF7)) {
+                
+                    if (!(di->flags & 0x01)) {
+                        return -1;
+                    }
+                    
+                    return 1;
+                
+                }
+                
+                di->current_cluster = get_fat_entry (di->scratch, di->current_cluster);
+            
+            }
+            
+            offset = (unsigned long) data_area;
+            offset += ((di->current_cluster - 2) * sectors_per_cluster);
+            offset += di->current_sector;
+            
+            if (seekto (offset * 512) || fread (di->scratch, 512, 1, ofp) != 1) {
+                return -1;
+            }
+        
+        }
+    
+    }
+    
+    memcpy (de, &(((struct msdos_dirent *) di->scratch)[di->current_entry]), sizeof (*de));
+    
+    if (de->name[0] == 0) {
+    
+        if (di->flags & 0x01) {
+            return 0;
+        }
+        
+        return -1;
+    
+    }
+    
+    if (de->name[0] == 0x05) {
+    
+        de->name[0] = 0xE5;
+        return 0;
+    
+    }
+    
+    di->current_entry++;
+    return 0;
+
+}
+
+static int open_dir (const char *target, struct dir_info *di) {
+
+    di->flags = 0;
+    
+    if (!strlen ((char *) target) || (strlen ((char *) target) == 1 && (target[0] == '/' || target[0] == '\\'))) {
+    
+        unsigned long offset;
+        
+        if (size_fat == 32) {
+        
+            di->current_cluster = root_cluster;
+            di->root_cluster = root_cluster;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) data_area + ((di->current_cluster - 2) * sectors_per_cluster);
+        
+        } else {
+        
+            di->current_cluster = 0;
+            di->root_cluster = 0;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) root_dir;
+        
+        }
+        
+        if (seekto (offset * 512) || fread (di->scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+    
+    } else {
+    
+        unsigned char tmpfn[12];
+        unsigned char *ptr;
+        
+        struct msdos_dirent de;
+        unsigned int result;
+        
+        unsigned long offset;
+        
+        if (size_fat == 32) {
+        
+            di->current_cluster = root_cluster;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) data_area + ((di->current_cluster - 2) * sectors_per_cluster);
+        
+        } else {
+        
+            di->current_cluster = 0;
+            di->current_entry = 0;
+            di->current_sector = 0;
+            
+            offset = (unsigned long) root_dir;
+        
+        }
+        
+        if (seekto (offset * 512) || fread (di->scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+        
+        ptr = (unsigned char *) target;
+        
+        while ((*ptr == '/' || *ptr == '\\') && *ptr) {
+            ptr++;
+        }
+        
+        while (*ptr) {
+        
+            if (canonical_to_dir ((char *) tmpfn, (char *) ptr) < 0) {
+            
+                fprintf (stderr, "Failed to convert to 8:3\n");
+                return -1;
+            
+            }
+            
+            de.name[0] = 0;
+            
+            do {
+                result = get_next_entry (di, &de);
+            } while (!result && memcmp (de.name, tmpfn, 11));
+            
+            if (!memcmp (de.name, tmpfn, 11) && (de.attr & ATTR_DIR) == ATTR_DIR) {
+            
+                unsigned long offset;
+                
+                di->current_cluster = (unsigned int) de.startlo[0] | ((unsigned int) de.startlo[1]) << 8 | ((unsigned int) de.starthi[0]) << 16 | ((unsigned int) de.starthi[1]) << 24;
+                di->root_cluster = di->current_cluster;
+                di->current_entry = 0;
+                di->current_sector = 0;
+                
+                offset = (unsigned long) (data_area + ((di->current_cluster - 2) * sectors_per_cluster));
+                
+                if (seekto (offset * 512)) {
+                    return -1;
+                }
+                
+                if (fread (di->scratch, 512, 1, ofp) != 1) {
+                    return -1;
+                }
+            
+            } else if (!memcmp (de.name, tmpfn, 11) && !(de.attr & ATTR_DIR)) {
+                return -1;
+            }
+            
+            while (*ptr != '/' && *ptr != '\\' && *ptr) {
+                ptr++;
+            }
+            
+            if (*ptr == '/' || *ptr == '\\') {
+                ptr++;
+            }
+        
+        }
+        
+        if (!di->current_cluster) {
+            return -1;
+        }
+    
+    }
+    
+    return 0;
+
+}
+
+static int set_fat_entry (unsigned char *scratch, unsigned int cluster, unsigned int value) {
+
+    unsigned int i, offset, sector;
+    
+    if (size_fat == 12) {
+    
+        offset = cluster + (cluster / 2);
+        value &= 0x0fff;
+    
+    } else if (size_fat == 16) {
+    
+        offset = cluster * 2;
+        value &= 0xffff;
+    
+    } else if (size_fat == 32) {
+    
+        offset = cluster * 4;
+        value &= 0x0fffffff;
+    
+    } else {
+        return -1;
+    }
+    
+    /**
+     * At this point, offset is the BYTE offset of the desired sector from the start
+     * of the FAT.  Calculate the physical sector containing this FAT entry.
+     */
+    sector = (offset / 512) + reserved_sectors;
+    
+    if (seekto (sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+        return -1;
+    }
+    
+    /**
+     * At this point, we "merely" need to extract the relevant entry.  This is
+     * easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry
+     * may span a sector boundary.  The normal way around this is always to
+     * read two FAT sectors, but luxary is (by design intent) unavailable.
+     */
+    offset %= 512;
+    
+    if (size_fat == 12) {
+    
+        if (offset == 511) {
+        
+            if (((cluster * 3) & 0x01) == 0) {
+                scratch[offset] = (unsigned char) (value & 0xFF);
+            } else {
+                scratch[offset] = (unsigned char) ((scratch[offset] & 0x0F) | (value & 0xF0));
+            }
+            
+            for (i = 0; i < number_of_fats; i++) {
+            
+                unsigned long temp = sector + (i * sectors_per_fat);
+                
+                if (seekto (temp * 512) < 0 || fwrite (scratch, 512, 1, ofp) != 1) {
+                    return -1;
+                }
+            
+            }
+            
+            sector++;
+            
+            if (seekto (sector) || fread (scratch, 512, 1, ofp) != 1) {
+                return -1;
+            }
+            
+            if (((cluster * 3) & 0x01) == 0) {
+                scratch[0] = (unsigned char) ((scratch[0] & 0xF0) | (value & 0x0F));
+            } else {
+                scratch[0] = (unsigned char) (value & 0xFF00);
+            }
+            
+            goto _write_fat;
+        
+        } else {
+        
+            if (((cluster * 3) & 0x01) == 0) {
+            
+                scratch[offset] = (unsigned char) (value & 0x00FF);
+                scratch[offset + 1] = (unsigned char) ((scratch[offset + 1] & 0x00F0) | ((value & 0x0F00) >> 8));
+            
+            } else {
+            
+                scratch[offset] = (unsigned char) ((scratch[offset] & 0x000F) | ((value & 0x000F) << 4));
+                scratch[offset + 1] = (unsigned char) ((value & 0x0FF0) >> 4);
+            
+            }
+            
+            goto _write_fat;
+        
+        }
+    
+    } else if (size_fat == 16) {
+    
+        scratch[offset] = (value & 0xFF);
+        scratch[offset + 1] = (value >> 8) & 0xFF;
+        
+        goto _write_fat;
+    
+    } else if (size_fat == 32) {
+    
+        scratch[offset] = (value & 0xFF);
+        scratch[offset + 1] = (value >> 8) & 0xFF;
+        scratch[offset + 2] = (value >> 16) & 0xFF;
+        scratch[offset + 3] = (scratch[offset + 3] & 0xF0) | ((value >> 24) & 0xFF);
+        
+        goto _write_fat;
+    
+    }
+    
+    return -1;
+
+_write_fat:
+
+    for (i = 0; i < number_of_fats; i++) {
+    
+        unsigned long temp = sector + (i * sectors_per_fat);
+        
+        if (seekto (temp * 512) || fwrite (scratch, 512, 1, ofp) != 1) {
+            return -1;
+        }
+    
+    }
+    
+    return 0;
+
+}
+
+static unsigned int get_fat_entry (unsigned char *scratch, unsigned int cluster) {
+
+    unsigned int offset, sector, result;
+    
+    if (size_fat == 12) {
+        offset = cluster + (cluster / 2);
+    } else if (size_fat == 16) {
+        offset = cluster * 2;
+    } else if (size_fat == 32) {
+        offset = cluster * 4;
+    } else {
+        return 0x0FFFFFF7;
+    }
+    
+    sector = (offset / 512) + reserved_sectors;
+    
+    if (seekto ((unsigned long) sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+        return 0x0FFFFFF7;
+    }
+    
+    offset %= 512;
+    
+    if (size_fat == 12) {
+    
+        if (offset == 511) {
+        
+            result = (unsigned int) scratch[offset];
+            sector++;
+            
+            if (seekto ((unsigned long) sector * 512) || fread (scratch, 512, 1, ofp) != 1) {
+                return 0x0FFFFFF7;
+            }
+            
+            result |= ((unsigned int) scratch[0]) << 8;
+        
+        } else {
+            result = (unsigned int) scratch[offset] | ((unsigned int) scratch[offset + 1]) << 8;
+        }
+        
+        if (cluster & 1) {
+            result = result >> 4;
+        } else {
+            result = result & 0x0FFF;
+        }
+    
+    } else if (size_fat == 16) {
+        result = (unsigned int) scratch[offset] | ((unsigned int) scratch[offset + 1]) << 8;
+    } else if (size_fat == 32) {
+        result = ((unsigned int) scratch[offset] | ((unsigned int) scratch[offset + 1]) << 8 | ((unsigned int) scratch[offset + 2]) << 16 | ((unsigned int) scratch[offset + 3]) << 24) & 0x0FFFFFFF;
+    } else {
+        result = 0x0FFFFFF7;
+    }
+    
+    return result;
+
+}
+
+static unsigned int get_free_fat (unsigned char *scratch) {
+
+    unsigned int i, result = 0xFFFFFFFF;
+    
+    for (i = 2; i < cluster_count; i++) {
+    
+        result = get_fat_entry (scratch, i);
+        
+        if (!result) {
+            return i;
+        }
+    
+    }
+    
+    return 0x0FFFFFF7;
+
+}
+
+struct vhd_footer {
+
+    unsigned char cookie[8];
+    unsigned char features[4];
+    
+    struct {
+    
+        unsigned char major[2];
+        unsigned char minor[2];
+    
+    } version;
+    
+    unsigned char next_offset[8];
+    unsigned char modified_time[4];
+    unsigned char creator_name[4];
+    
+    struct {
+    
+        unsigned char major[2];
+        unsigned char minor[2];
+    
+    } creator_version;
+    
+    unsigned char creator_host[4];
+    unsigned char disk_size[8];
+    unsigned char data_size[8];
+    
+    struct {
+    
+        unsigned char cylinders[2];
+        unsigned char heads_per_cyl;
+        unsigned char secs_per_track;
+    
+    } disk_geom;
+    
+    unsigned char disk_type[4];
+    unsigned char checksum[4];
+    unsigned char identifier[16];
+    unsigned char saved_state;
+    unsigned char reserved[427];
+
+};
+
+int main (int argc, char **argv) {
+
+    unsigned char *scratch;
+    char *target;
+    
+    struct vhd_footer footer;
+    long i;
+    
+    if (argc && *argv) {
+    
+        char *p;
+        program_name = *argv;
+        
+        if ((p = strrchr (program_name, '/')) || (p = strrchr (program_name, '\\'))) {
+            program_name = (p + 1);
+        }
+    
+    }
+    
+    state = xmalloc (sizeof (*state));
+    parse_args (&argc, &argv, 1);
+    
+    if (!state->outfile || state->nb_dirs == 0) {
+        print_help (EXIT_FAILURE);
+    }
+    
+    if ((ofp = fopen (state->outfile, "r+b")) == NULL) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "faild to open '%s' for writing", state->outfile);
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (state->chs_align) {
+    
+        long size = sizeof (footer), offset;
+        fseek (ofp, 0, SEEK_END);
+        
+        offset = ftell (ofp) - size;
+        
+        if (!fseek (ofp, offset, SEEK_SET)) {
+        
+            if (fread (&footer, size, 1, ofp) == 1) {
+            
+                if (footer.cookie[0] == 0x63 && footer.cookie[1] == 0x6F && footer.cookie[2] == 0x6E && footer.cookie[3] == 0x65 && footer.cookie[4] == 0x63 && footer.cookie[5] == 0x74 && footer.cookie[6] == 0x69 && footer.cookie[7] == 0x78) {
+                
+                    long old = state->offset, secs_per_track = footer.disk_geom.secs_per_track;
+                    
+                    if (state->offset % secs_per_track) {
+                    
+                        state->offset = ((state->offset / secs_per_track) + 1) * secs_per_track;
+                        report_at (program_name, 0, REPORT_WARNING, "offset changed from %lu to %lu", old, state->offset);
+                    
+                    }
+                
+                }
+            
+            }
+        
+        }
+    
+    }
+    
+    if (seekto ((unsigned long) 0) || fread (&bs, sizeof (bs), 1, ofp) != 1) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "failed whilst reading boot sector");
+        fclose (ofp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (bs.boot_jump[0] != 0xEB || bs.boot_jump[1] < 0x16 || bs.boot_jump[2] != 0x90) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+        fclose (ofp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    sectors_per_cluster = (unsigned int) bs.sectors_per_cluster;
+    reserved_sectors = (unsigned int) bs.reserved_sectors[0] | (((unsigned int) bs.reserved_sectors[1]) << 8);
+    number_of_fats = (unsigned int) bs.no_fats;
+    root_entries = (unsigned int) bs.root_entries[0] | (((unsigned int) bs.root_entries[1]) << 8);
+    total_sectors = (unsigned int) bs.total_sectors16[0] | (((unsigned int) bs.total_sectors16[1]) << 8);
+    sectors_per_fat = (unsigned int) bs.sectors_per_fat16[0] | (((unsigned int) bs.sectors_per_fat16[1]) << 8);
+    
+    if (!sectors_per_cluster || !reserved_sectors || !number_of_fats) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+        fclose (ofp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (!root_entries) {
+    
+        if (bs.boot_jump[1] < 0x58) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        root_cluster = (unsigned int) bs.fat32.root_cluster[0] | (((unsigned int) bs.fat32.root_cluster[1]) << 8) | (((unsigned int) bs.fat32.root_cluster[2]) << 16) | (((unsigned int) bs.fat32.root_cluster[3]) << 24);
+        
+        if (!root_cluster) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (!total_sectors) {
+    
+        if (bs.boot_jump[1] < 0x22) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        total_sectors = (unsigned int) bs.total_sectors32[0] | (((unsigned int) bs.total_sectors32[1]) << 8) | (((unsigned int) bs.total_sectors32[2]) << 16) | (((unsigned int) bs.total_sectors32[3]) << 24);
+        
+        if (!total_sectors) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (!sectors_per_fat) {
+    
+        if (bs.boot_jump[1] < 0x58) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        sectors_per_fat = (unsigned int) bs.fat32.sectors_per_fat32[0] | (((unsigned int) bs.fat32.sectors_per_fat32[1]) << 8) | (((unsigned int) bs.fat32.sectors_per_fat32[2]) << 16) | (((unsigned int) bs.fat32.sectors_per_fat32[3]) << 24);
+        
+        if (!sectors_per_fat) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    if (root_entries) {
+    
+        root_dir = reserved_sectors + (sectors_per_fat * 2);
+        data_area = root_dir + (((root_entries * 32) + (512 - 1)) / 512);
+    
+    } else {
+    
+        data_area = reserved_sectors + (sectors_per_fat * 2);
+        
+        /*root_dir = data_area + ((root_cluster - 2) * sectors_per_cluster);*/
+        /*root_dir = root_cluster;*/
+    
+    }
+    
+    cluster_count = (total_sectors - data_area) / sectors_per_cluster;
+    
+    if (bs.boot_jump[1] == 0x58) {
+    
+        info_sector = (unsigned int) bs.fat32.info_sector[0] | (((unsigned int) bs.fat32.info_sector[1]) << 8);
+        
+        if (!info_sector) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "%s does not have a valid FAT boot sector", state->outfile);
+            fclose (ofp);
+            
+            return EXIT_FAILURE;
+        
+        }
+        
+        size_fat = 32;
+    
+    } else if (cluster_count <= MAX_CLUST_12) {
+        size_fat = 12;
+    } else if (cluster_count >= MIN_CLUST_16 && cluster_count <= MAX_CLUST_16) {
+        size_fat = 16;
+    } else {
+    
+        report_at (program_name, 0, REPORT_ERROR, "FAT is not 12, 16 or 32 bits");
+        fclose (ofp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (!(scratch = (unsigned char *) malloc (512))) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "Out of memory");
+        fclose (ofp);
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    for (i = 0; i < state->nb_dirs; ++i) {
+    
+        target = state->dirs[i];
+        
+        if (*target == '/' || *target == '\\') {
+            target++;
+        }
+        
+        if (create_dir (target, scratch) < 0) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "failed to create %s", target);
+            free (scratch);
+            
+            fclose (ofp);
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    free (scratch);
+    fclose (ofp);
+    
+    return EXIT_SUCCESS;
+
+}
diff --git a/mmd.h b/mmd.h
new file mode 100644 (file)
index 0000000..0286400
--- /dev/null
+++ b/mmd.h
@@ -0,0 +1,19 @@
+/******************************************************************************
+ * @file            mmd.h
+ *****************************************************************************/
+#ifndef     _MMD_H
+#define     _MMD_H
+
+struct mmd_state {
+
+    char **dirs;
+    long nb_dirs;
+    
+    const char *outfile;
+    unsigned long offset;
+    
+    int chs_align;
+
+};
+
+#endif      /* _MMD_H */
diff --git a/msdos.h b/msdos.h
new file mode 100644 (file)
index 0000000..1b4464b
--- /dev/null
+++ b/msdos.h
@@ -0,0 +1,131 @@
+/******************************************************************************
+ * @file            msdos.h
+ *****************************************************************************/
+#ifndef     _MSDOS_H
+#define     _MSDOS_H
+
+/**
+ * According to Microsoft FAT specification (fatgen103.doc) disk with
+ * 4085 clusters (or more) is FAT16, but Microsoft Windows FAT driver
+ * fastfat.sys detects disk with less then 4087 clusters as FAT12.
+ * Linux FAT drivers msdos.ko and vfat.ko detect disk with at least
+ * 4085 clusters as FAT16, therefore for compatibility reasons with
+ * both systems disallow formatting disks to 4085 or 4086 clusters.
+ */
+#define     MAX_CLUST_12                4084
+#define     MIN_CLUST_16                4087
+
+/**
+ * According to Microsoft FAT specification (fatgen103.doc) disk with
+ * 65525 clusters (or more) is FAT32, but Microsoft Windows FAT driver
+ * fastfat.sys, Linux FAT drivers msdos.ko and vfat.ko detect disk as
+ * FAT32 when Sectors Per FAT (fat_length) is set to zero. And not by
+ * number of clusters. Still there is cluster upper limit for FAT16.
+ */
+#define     MAX_CLUST_16                65524
+#define     MIN_CLUST_32                65525
+
+/**
+ * M$ says the high 4 bits of a FAT32 FAT entry are reserved and don't belong
+ * to the cluster number. So the max. cluster# is based on 2^28
+ */
+#define     MAX_CLUST_32                268435446
+
+/** File Attributes. */
+#define     ATTR_NONE                   0x00
+#define     ATTR_READONLY               0x01
+#define     ATTR_HIDDEN                 0x02
+#define     ATTR_SYSTEM                 0x04
+#define     ATTR_VOLUME_ID              0x08
+#define     ATTR_DIR                    0x10
+#define     ATTR_ARCHIVE                0x20
+/*#define     ATTR_LONG_NAME              (ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID)*/
+
+struct msdos_volume_info {
+
+    unsigned char drive_no;
+    unsigned char boot_flags;
+    unsigned char ext_boot_sign;
+    unsigned char volume_id[4];
+    unsigned char volume_label[11];
+    unsigned char fs_type[8];
+
+};
+
+struct msdos_boot_sector {
+
+    unsigned char boot_jump[3];
+    unsigned char system_id[8];
+    unsigned char bytes_per_sector[2];
+    unsigned char sectors_per_cluster;
+    unsigned char reserved_sectors[2];
+    unsigned char no_fats;
+    unsigned char root_entries[2];
+    unsigned char total_sectors16[2];
+    unsigned char media_descriptor;
+    unsigned char sectors_per_fat16[2];
+    unsigned char sectors_per_track[2];
+    unsigned char heads_per_cylinder[2];
+    unsigned char hidden_sectors[4];
+    unsigned char total_sectors32[4];
+    
+    union {
+    
+        struct {
+        
+            struct msdos_volume_info vi;
+            unsigned char boot_code[448];
+        
+        } _oldfat;
+        
+        struct {
+        
+            unsigned char sectors_per_fat32[4];
+            unsigned char flags[2];
+            unsigned char version[2];
+            unsigned char root_cluster[4];
+            unsigned char info_sector[2];
+            unsigned char backup_boot[2];
+            unsigned short reserved2[6];
+            
+            struct msdos_volume_info vi;
+            unsigned char boot_code[420];
+        
+        } _fat32;
+    
+    } fstype;
+    
+    unsigned char boot_sign[2];
+
+};
+
+#define     oldfat                      fstype._oldfat
+#define     fat32                       fstype._fat32
+
+struct fat32_fsinfo {
+
+    unsigned char reserved1[4];
+    unsigned char signature[4];
+    unsigned char free_clusters[4];
+    unsigned char next_cluster[4];
+
+};
+
+struct msdos_dirent {
+
+    unsigned char name[11];
+    unsigned char attr;
+    unsigned char lcase;
+    unsigned char ctime_cs;
+    unsigned char ctime[2];
+    unsigned char cdate[2];
+    unsigned char adate[2];
+    unsigned char starthi[2];
+    unsigned char time[2];
+    unsigned char date[2];
+    unsigned char startlo[2];
+    unsigned char size[4];
+
+};
+
+#endif      /* _MSDOS_H */
diff --git a/report.c b/report.c
new file mode 100644 (file)
index 0000000..8e128ef
--- /dev/null
+++ b/report.c
@@ -0,0 +1,150 @@
+/******************************************************************************
+ * @file            report.c
+ *****************************************************************************/
+#include    <stdarg.h>
+#include    <stdio.h>
+#include    <string.h>
+
+#include    "report.h"
+
+unsigned long errors = 0;
+
+#ifndef     __PDOS__
+#if     defined (_WIN32)
+# include   <windows.h>
+static int OriginalConsoleColor = -1;
+#endif
+
+static void reset_console_color (void) {
+
+#if     defined (_WIN32)
+
+    HANDLE hStdError = GetStdHandle (STD_ERROR_HANDLE);
+    
+    if (OriginalConsoleColor == -1) { return; }
+    
+    SetConsoleTextAttribute (hStdError, OriginalConsoleColor);
+    OriginalConsoleColor = -1;
+
+#else
+
+    fprintf (stderr, "\033[0m");
+
+#endif
+
+}
+
+static void set_console_color (int color) {
+
+#if     defined (_WIN32)
+
+    HANDLE hStdError = GetStdHandle (STD_ERROR_HANDLE);
+    WORD wColor;
+    
+    if (OriginalConsoleColor == -1) {
+    
+        CONSOLE_SCREEN_BUFFER_INFO csbi;
+        
+        if (!GetConsoleScreenBufferInfo (hStdError, &csbi)) {
+            return;
+        }
+        
+        OriginalConsoleColor = csbi.wAttributes;
+    
+    }
+    
+    wColor = (OriginalConsoleColor & 0xF0) + (color & 0xF);
+    SetConsoleTextAttribute (hStdError, wColor);
+
+#else
+
+    fprintf (stderr, "\033[%dm", color);
+
+#endif
+
+}
+#endif
+
+static void output_message (const char *filename, unsigned long lineno, unsigned long idx, enum report_type type, const char *fmt, va_list ap) {
+
+    if (filename) {
+    
+        if (lineno == 0 && idx == 0) {
+            fprintf (stderr, "%s: ", filename);
+        } else {
+            fprintf (stderr, "%s:", filename);
+        }
+    
+    }
+    
+    if (lineno > 0) {
+    
+        if (idx == 0) {
+            fprintf (stderr, "%lu: ", lineno);
+        } else {
+            fprintf (stderr, "%lu:", lineno);
+        }
+    
+    }
+    
+    if (idx > 0) {
+        fprintf (stderr, "%lu: ", idx);
+    }
+    
+    if (type == REPORT_ERROR || type == REPORT_FATAL_ERROR) {
+    
+#ifndef     __PDOS__
+        set_console_color (COLOR_ERROR);
+#endif
+        
+        if (type == REPORT_ERROR) {
+            fprintf (stderr, "error:");
+        } else {
+            fprintf (stderr, "fatal error:");
+        }
+    
+    } else if (type == REPORT_INTERNAL_ERROR) {
+    
+#ifndef     __PDOS__
+        set_console_color (COLOR_INTERNAL_ERROR);
+#endif
+        
+        fprintf (stderr, "internal error:");
+    
+    } else if (type == REPORT_WARNING) {
+    
+#ifndef     __PDOS__
+        set_console_color (COLOR_WARNING);
+#endif
+        
+        fprintf (stderr, "warning:");
+    
+    }
+    
+#ifndef     __PDOS__
+    reset_console_color ();
+#endif
+    
+    fprintf (stderr, " ");
+    vfprintf (stderr, fmt, ap);
+    fprintf (stderr, "\n");
+    
+    if (type != REPORT_WARNING) {
+        ++errors;
+    }
+
+}
+
+unsigned long get_error_count (void) {
+    return errors;
+}
+
+void report_at (const char *filename, unsigned long lineno, enum report_type type, const char *fmt, ...) {
+
+    va_list ap;
+    
+    va_start (ap, fmt);
+    output_message (filename, lineno, 0, type, fmt, ap);
+    va_end (ap);
+
+}
diff --git a/report.h b/report.h
new file mode 100644 (file)
index 0000000..8fc8758
--- /dev/null
+++ b/report.h
@@ -0,0 +1,29 @@
+/******************************************************************************
+ * @file            report.h
+ *****************************************************************************/
+#ifndef     _REPORT_H
+#define     _REPORT_H
+
+enum report_type {
+
+    REPORT_ERROR = 0,
+    REPORT_FATAL_ERROR,
+    REPORT_INTERNAL_ERROR,
+    REPORT_WARNING
+
+};
+
+#if     defined (_WIN32)
+# define    COLOR_ERROR                 12
+# define    COLOR_WARNING               13
+# define    COLOR_INTERNAL_ERROR        19
+#else
+# define    COLOR_ERROR                 91
+# define    COLOR_INTERNAL_ERROR        94
+# define    COLOR_WARNING               95
+#endif
+
+unsigned long get_error_count (void);
+void report_at (const char *filename, unsigned long line_number, enum report_type type, const char *fmt, ...);
+
+#endif      /* _REPORT_H */
diff --git a/write7x.c b/write7x.c
new file mode 100644 (file)
index 0000000..41bd6b9
--- /dev/null
+++ b/write7x.c
@@ -0,0 +1,20 @@
+/******************************************************************************
+ * @file            write7x.c
+ *****************************************************************************/
+#include    "write7x.h"
+
+void write721_to_byte_array (unsigned char *dest, unsigned short val) {
+
+    dest[0] = (val & 0xFF);
+    dest[1] = (val >> 8) & 0xFF;
+
+}
+
+void write741_to_byte_array (unsigned char *dest, unsigned int val) {
+
+    dest[0] = (val & 0xFF);
+    dest[1] = (val >> 8) & 0xFF;
+    dest[2] = (val >> 16) & 0xFF;
+    dest[3] = (val >> 24) & 0xFF;
+
+}
diff --git a/write7x.h b/write7x.h
new file mode 100644 (file)
index 0000000..deb84a0
--- /dev/null
+++ b/write7x.h
@@ -0,0 +1,10 @@
+/******************************************************************************
+ * @file            write7x.h
+ *****************************************************************************/
+#ifndef     _WRITE7X_H
+#define     _WRITE7X_H
+
+void write721_to_byte_array (unsigned char *dest, unsigned short val);
+void write741_to_byte_array (unsigned char *dest, unsigned int val);
+
+#endif      /* _WRITE7X_H */