From e7821f2e5118c8cf4a9c8cf24ad30ea3da1da174 Mon Sep 17 00:00:00 2001 From: Robert Pengelly Date: Mon, 3 Jun 2024 04:32:25 +0100 Subject: [PATCH] New server --- LICENSE | 24 + Makefile.pdw | 34 + Makefile.std | 35 + Makefile.unix | 53 ++ Makefile.w32 | 37 + README.md | 23 + common.c | 84 ++ common.h | 10 + lib.c | 471 ++++++++++ lib.h | 16 + mcopy.c | 2342 +++++++++++++++++++++++++++++++++++++++++++++++++ mcopy.h | 21 + mkfs.c | 1361 ++++++++++++++++++++++++++++ mkfs.h | 23 + mls.c | 1068 ++++++++++++++++++++++ mmd.c | 1436 ++++++++++++++++++++++++++++++ mmd.h | 19 + msdos.h | 131 +++ report.c | 150 ++++ report.h | 29 + write7x.c | 20 + write7x.h | 10 + 22 files changed, 7397 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile.pdw create mode 100644 Makefile.std create mode 100644 Makefile.unix create mode 100644 Makefile.w32 create mode 100644 README.md create mode 100644 common.c create mode 100644 common.h create mode 100644 lib.c create mode 100644 lib.h create mode 100644 mcopy.c create mode 100644 mcopy.h create mode 100644 mkfs.c create mode 100644 mkfs.h create mode 100644 mls.c create mode 100644 mmd.c create mode 100644 mmd.h create mode 100644 msdos.h create mode 100644 report.c create mode 100644 report.h create mode 100644 write7x.c create mode 100644 write7x.h diff --git a/LICENSE b/LICENSE new file mode 100644 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 diff --git a/Makefile.pdw b/Makefile.pdw new file mode 100644 index 0000000..07da597 --- /dev/null +++ b/Makefile.pdw @@ -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 index 0000000..5d4980e --- /dev/null +++ b/Makefile.std @@ -0,0 +1,35 @@ +#****************************************************************************** +# @file Makefile.std +#****************************************************************************** +AS=pdas --oformat coff +CC=gccwin +LD=pdld --no-insert-timestamp + +COPTS=-S -O2 -fno-common -ansi -I. -I../pdos/pdpclib -D__WIN32__ -D__NOBIVA__ -D__PDOS__ +COBJ=common.obj report.obj write7x.obj + +all: clean mkdosfs.exe mcopy.exe mmd.exe mls.exe + +mkdosfs.exe: lib.obj mkfs.obj $(COBJ) + $(LD) -s -o mkdosfs.exe ../pdos/pdpclib/w32start.obj lib.obj mkfs.obj $(COBJ) ../pdos/pdpclib/msvcrt.lib + +mcopy.exe: mcopy.obj $(COBJ) + $(LD) -s -o mcopy.exe ../pdos/pdpclib/w32start.obj mcopy.obj $(COBJ) ../pdos/pdpclib/msvcrt.lib + +mmd.exe: mmd.obj $(COBJ) + $(LD) -s -o mmd.exe ../pdos/pdpclib/w32start.obj mmd.obj $(COBJ) ../pdos/pdpclib/msvcrt.lib + +mls.exe: mls.obj $(COBJ) + $(LD) -s -o mls.exe ../pdos/pdpclib/w32start.obj mls.obj $(COBJ) ../pdos/pdpclib/msvcrt.lib + +.c.obj: + $(CC) $(COPTS) $< + $(AS) -o $@ $*.s + rm -f $*.s + +clean: + rm -f *.obj + rm -f mkdosfs.exe + rm -f mcopy.exe + rm -f mmd.exe + rm -f mls.exe diff --git a/Makefile.unix b/Makefile.unix new file mode 100644 index 0000000..55b2bde --- /dev/null +++ b/Makefile.unix @@ -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 index 0000000..b1ac3ba --- /dev/null +++ b/Makefile.w32 @@ -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 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 index 0000000..35241d6 --- /dev/null +++ b/common.c @@ -0,0 +1,84 @@ +/****************************************************************************** + * @file common.c + *****************************************************************************/ +#include +#include +#include +#include +#include +#include + +#ifndef __PDOS__ +# if defined (__GNUC__) +# include +# include +# else +# include +# 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 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 index 0000000..50f7dd1 --- /dev/null +++ b/lib.c @@ -0,0 +1,471 @@ +/****************************************************************************** + * @file lib.c + *****************************************************************************/ +#include +#include +#include +#include +#include +#include + +#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 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 index 0000000..9db14f0 --- /dev/null +++ b/mcopy.c @@ -0,0 +1,2342 @@ +/****************************************************************************** + * @file mcopy.c + *****************************************************************************/ +#include +#include +#include +#include +#include +#include + +#ifndef __PDOS__ +# if defined (_WIN32) +# include +# include +# elif defined (__GNUC__) +# include +# include +# include +# if defined (__CYGWIN__) +# include +# 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 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 index 0000000..3113607 --- /dev/null +++ b/mkfs.c @@ -0,0 +1,1361 @@ +/****************************************************************************** + * @file mkfs.c + *****************************************************************************/ +#include +#include +#include +#include + +#include "common.h" +#include "lib.h" +#include "mkfs.h" +#include "msdos.h" +#include "report.h" +#include "write7x.h" + +#ifndef __PDOS__ +# if defined (__GNUC__) +# include +# include +# else +# include +# 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 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 index 0000000..5b1d9c6 --- /dev/null +++ b/mls.c @@ -0,0 +1,1068 @@ +/****************************************************************************** + * @file mls.c + *****************************************************************************/ +#include +#include +#include +#include +#include +#include + +#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 (" "); + } 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 index 0000000..65780d4 --- /dev/null +++ b/mmd.c @@ -0,0 +1,1436 @@ +/****************************************************************************** + * @file mmd.c + *****************************************************************************/ +#include +#include +#include +#include +#include +#include + +#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 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 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 index 0000000..8e128ef --- /dev/null +++ b/report.c @@ -0,0 +1,150 @@ +/****************************************************************************** + * @file report.c + *****************************************************************************/ +#include +#include +#include + +#include "report.h" + +unsigned long errors = 0; + +#ifndef __PDOS__ +#if defined (_WIN32) +# include +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 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 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 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 */ -- 2.34.1