/* sim_disk.c: simulator disk support library

   Copyright (c) 2011, Mark Pizzolato

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   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
   ROBERT M SUPNIK 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.

   Except as contained in this notice, the names of Mark Pizzolato shall not be
   used in advertising or otherwise to promote the sale, use or other dealings 
   in this Software without prior written authorization from Mark Pizzolato.



   This is the place which hides processing of various disk formats,
   as well as OS-specific direct hardware access.

   25-Jan-11    MP      Initial Implemementation

Public routines:

   sim_disk_attach           attach disk unit
   sim_disk_detach           detach disk unit
   sim_disk_attach_help      help routine for attaching disks
   sim_disk_rdsect           read disk sectors
   sim_disk_rdsect_a         read disk sectors asynchronously
   sim_disk_wrsect           write disk sectors
   sim_disk_wrsect_a         write disk sectors asynchronously
   sim_disk_unload           unload or detach a disk as needed
   sim_disk_reset            reset unit
   sim_disk_wrp              TRUE if write protected
   sim_disk_isavailable      TRUE if available for I/O
   sim_disk_size             get disk size
   sim_disk_set_fmt          set disk format
   sim_disk_show_fmt         show disk format
   sim_disk_set_capac        set disk capacity
   sim_disk_show_capac       show disk capacity
   sim_disk_set_async        enable asynchronous operation
   sim_disk_clr_async        disable asynchronous operation
   sim_disk_data_trace       debug support

Internal routines:

   sim_os_disk_open_raw      platform specific open raw device
   sim_os_disk_close_raw     platform specific close raw device
   sim_os_disk_size_raw      platform specific raw device size
   sim_os_disk_unload_raw    platform specific disk unload/eject
   sim_os_disk_rdsect        platform specific read sectors
   sim_os_disk_wrsect        platform specific write sectors

   sim_vhd_disk_open         platform independent open virtual disk file
   sim_vhd_disk_create       platform independent create virtual disk file
   sim_vhd_disk_create_diff  platform independent create differencing virtual disk file
   sim_vhd_disk_close        platform independent close virtual disk file
   sim_vhd_disk_size         platform independent virtual disk size
   sim_vhd_disk_rdsect       platform independent read virtual disk sectors
   sim_vhd_disk_wrsect       platform independent write virtual disk sectors


*/

#include "sim_defs.h"
#include "sim_disk.h"
#include <ctype.h>
#include <sys/stat.h>

#ifdef _WIN32
#include <windows.h>
#endif
#if defined SIM_ASYNCH_IO
#include <pthread.h>
#endif

struct disk_context {
    DEVICE              *dptr;              /* Device for unit (access to debug flags) */
    uint32              dbit;               /* debugging bit */
    uint32              sector_size;        /* Disk Sector Size (of the pseudo disk) */
    uint32              capac_factor;       /* Units of Capacity (2 = word, 1 = byte) */
    uint32              xfer_element_size;  /* Disk Bus Transfer size (1 - byte, 2 - word, 4 - longword) */
    uint32              storage_sector_size;/* Sector size of the containing storage */
    uint32              removable;          /* Removable device flag */
    uint32              auto_format;        /* Format determined dynamically */
#if defined _WIN32
    HANDLE              disk_handle;        /* OS specific Raw device handle */
#endif
#if defined SIM_ASYNCH_IO
    int                 asynch_io;          /* Asynchronous Interrupt scheduling enabled */
    int                 asynch_io_latency;  /* instructions to delay pending interrupt */
    pthread_mutex_t     lock;
    pthread_t           io_thread;          /* I/O Thread Id */
    pthread_mutex_t     io_lock;
    pthread_cond_t      io_cond;
    pthread_cond_t      io_done;
    pthread_cond_t      startup_cond;
    int                 io_dop;
    uint8               *buf;
    t_seccnt            *rsects;
    t_seccnt            sects;
    t_lba               lba;
    DISK_PCALLBACK      callback;
    t_stat              io_status;
#endif
    };

#define disk_ctx up8                        /* Field in Unit structure which points to the disk_context */

#if defined SIM_ASYNCH_IO
#define AIO_CALLSETUP                                               \
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;   \
                                                                    \
if ((!callback) || !ctx->asynch_io)

#define AIO_CALL(op, _lba, _buf, _rsects, _sects,  _callback)   \
    if (ctx->asynch_io) {                                       \
        struct disk_context *ctx =                              \
                      (struct disk_context *)uptr->disk_ctx;    \
                                                                \
        pthread_mutex_lock (&ctx->io_lock);                     \
                                                                \
        sim_debug (ctx->dbit, ctx->dptr,                        \
      "sim_disk AIO_CALL(op=%d, unit=%d, lba=0x%X, sects=%d)\n",\
                op, (int)(uptr-ctx->dptr->units), _lba, _sects);\
                                                                \
        if (ctx->callback)                                      \
            abort(); /* horrible mistake, stop */               \
        ctx->io_dop = op;                                       \
        ctx->lba = _lba;                                        \
        ctx->buf = _buf;                                        \
        ctx->sects = _sects;                                    \
        ctx->rsects = _rsects;                                  \
        ctx->callback = _callback;                              \
        pthread_cond_signal (&ctx->io_cond);                    \
        pthread_mutex_unlock (&ctx->io_lock);                   \
        }                                                       \
    else                                                        \
        if (_callback)                                          \
            (_callback) (uptr, r);


#define DOP_DONE  0             /* close */
#define DOP_RSEC  1             /* sim_disk_rdsect_a */
#define DOP_WSEC  2             /* sim_disk_wrsect_a */
#define DOP_IAVL  3             /* sim_disk_isavailable_a */

static void *
_disk_io(void *arg)
{
UNIT* volatile uptr = (UNIT*)arg;
int sched_policy;
struct sched_param sched_priority;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

/* Boost Priority for this I/O thread vs the CPU instruction execution 
   thread which in general won't be readily yielding the processor when 
   this thread needs to run */
pthread_getschedparam (pthread_self(), &sched_policy, &sched_priority);
++sched_priority.sched_priority;
pthread_setschedparam (pthread_self(), sched_policy, &sched_priority);

sim_debug (ctx->dbit, ctx->dptr, "_disk_io(unit=%d) starting\n", (int)(uptr-ctx->dptr->units));

pthread_mutex_lock (&ctx->io_lock);
pthread_cond_signal (&ctx->startup_cond);   /* Signal we're ready to go */
while (ctx->asynch_io) {
    pthread_cond_wait (&ctx->io_cond, &ctx->io_lock);
    if (ctx->io_dop == DOP_DONE)
        break;
    pthread_mutex_unlock (&ctx->io_lock);
    switch (ctx->io_dop) {
        case DOP_RSEC:
            ctx->io_status = sim_disk_rdsect (uptr, ctx->lba, ctx->buf, ctx->rsects, ctx->sects);
            break;
        case DOP_WSEC:
            ctx->io_status = sim_disk_wrsect (uptr, ctx->lba, ctx->buf, ctx->rsects, ctx->sects);
            break;
        case DOP_IAVL:
            ctx->io_status = sim_disk_isavailable (uptr);
            break;
        }
    pthread_mutex_lock (&ctx->io_lock);
    ctx->io_dop = DOP_DONE;
    pthread_cond_signal (&ctx->io_done);
    sim_activate (uptr, ctx->asynch_io_latency);
    }
pthread_mutex_unlock (&ctx->io_lock);

sim_debug (ctx->dbit, ctx->dptr, "_disk_io(unit=%d) exiting\n", (int)(uptr-ctx->dptr->units));

return NULL;
}

/* This routine is called in the context of the main simulator thread before 
   processing events for any unit. It is only called when an asynchronous 
   thread has called sim_activate() to activate a unit.  The job of this 
   routine is to put the unit in proper condition to digest what may have
   occurred in the asynchrconous thread.
   
   Since disk processing only handles a single I/O at a time to a 
   particular disk device (due to using stdio for the SimH Disk format
   and stdio doesn't have an atomic seek+(read|write) operation), 
   we have the opportunity to possibly detect improper attempts to 
   issue multiple concurrent I/O requests. */
static void _disk_completion_dispatch (UNIT *uptr)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
DISK_PCALLBACK callback = ctx->callback;

sim_debug (ctx->dbit, ctx->dptr, "_disk_completion_dispatch(unit=%d, dop=%d, callback=%p)\n", (int)(uptr-ctx->dptr->units), ctx->io_dop, ctx->callback);

if (ctx->io_dop != DOP_DONE)
    abort();                                            /* horribly wrong, stop */

if (ctx->callback && ctx->io_dop == DOP_DONE) {
    ctx->callback = NULL;
    callback (uptr, ctx->io_status);
    }
}

static t_bool _disk_is_active (UNIT *uptr)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

if (ctx) {
    sim_debug (ctx->dbit, ctx->dptr, "_disk_is_active(unit=%d, dop=%d)\n", uptr-ctx->dptr->units, ctx->io_dop);
    return (ctx->io_dop != DOP_DONE);
    }
return FALSE;
}

static void _disk_cancel (UNIT *uptr)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

if (ctx) {
    sim_debug (ctx->dbit, ctx->dptr, "_disk_cancel(unit=%d, dop=%d)\n", uptr-ctx->dptr->units, ctx->io_dop);
    if (ctx->asynch_io) {
        pthread_mutex_lock (&ctx->io_lock);
        while (ctx->io_dop != DOP_DONE)
            pthread_cond_wait (&ctx->io_done, &ctx->io_lock);
        pthread_mutex_unlock (&ctx->io_lock);
        }
    }
}
#else
#define AIO_CALLSETUP
#define AIO_CALL(op, _lba, _buf, _rsects, _sects,  _callback)   \
    if (_callback)                                              \
        (_callback) (uptr, r);
#endif

/* Forward declarations */

static t_stat sim_vhd_disk_implemented (void);
static FILE *sim_vhd_disk_open (const char *rawdevicename, const char *openmode);
static FILE *sim_vhd_disk_create (const char *szVHDPath, t_addr desiredsize);
static FILE *sim_vhd_disk_create_diff (const char *szVHDPath, const char *szParentVHDPath);
static FILE *sim_vhd_disk_merge (const char *szVHDPath, char **ParentVHD);
static int sim_vhd_disk_close (FILE *f);
static void sim_vhd_disk_flush (FILE *f);
static t_addr sim_vhd_disk_size (FILE *f);
static t_stat sim_vhd_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects);
static t_stat sim_vhd_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects);
static t_stat sim_vhd_disk_clearerr (UNIT *uptr);
static t_stat sim_vhd_disk_set_dtype (FILE *f, const char *dtype);
static const char *sim_vhd_disk_get_dtype (FILE *f);
static t_stat sim_os_disk_implemented_raw (void);
static FILE *sim_os_disk_open_raw (const char *rawdevicename, const char *openmode);
static int sim_os_disk_close_raw (FILE *f);
static void sim_os_disk_flush_raw (FILE *f);
static t_addr sim_os_disk_size_raw (FILE *f);
static t_stat sim_os_disk_unload_raw (FILE *f);
static t_bool sim_os_disk_isavailable_raw (FILE *f);
static t_stat sim_os_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects);
static t_stat sim_os_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects);
static t_stat sim_os_disk_info_raw (FILE *f, uint32 *sector_size, uint32 *removable);
static t_stat sim_disk_pdp11_bad_block (UNIT *uptr, int32 sec);
static char *HostPathToVhdPath (const char *szHostPath, char *szVhdPath, size_t VhdPathSize);
static char *VhdPathToHostPath (const char *szVhdPath, char *szHostPath, size_t HostPathSize);

struct sim_disk_fmt {
    char                *name;                          /* name */
    int32               uflags;                         /* unit flags */
    int32               fmtval;                         /* Format type value */
    t_stat              (*impl_fnc)(void);              /* Implemented Test Function */
    };

static struct sim_disk_fmt fmts[DKUF_N_FMT] = {
    { "SIMH", 0, DKUF_F_STD, NULL},
    { "RAW",  0, DKUF_F_RAW, sim_os_disk_implemented_raw},
    { "VHD",  0, DKUF_F_VHD, sim_vhd_disk_implemented},
    { NULL,   0, 0}
    };

/* Set disk format */

t_stat sim_disk_set_fmt (UNIT *uptr, int32 val, char *cptr, void *desc)
{
uint32 f;

if (uptr == NULL)
    return SCPE_IERR;
if (cptr == NULL)
    return SCPE_ARG;
for (f = 0; f < DKUF_N_FMT && fmts[f].name; f++) {
    if (fmts[f].name && (strcmp (cptr, fmts[f].name) == 0)) {
        if ((fmts[f].impl_fnc) && (fmts[f].impl_fnc() != SCPE_OK))
            return SCPE_NOFNC;
        uptr->flags = (uptr->flags & ~DKUF_FMT) |
            (fmts[f].fmtval << DKUF_V_FMT) | fmts[f].uflags;
        return SCPE_OK;
        }
    }
return SCPE_ARG;
}

/* Show disk format */

t_stat sim_disk_show_fmt (FILE *st, UNIT *uptr, int32 val, void *desc)
{
int32 f = DK_GET_FMT (uptr);
size_t i;

for (i = 0; i < DKUF_N_FMT; i++)
    if (fmts[i].fmtval == f) {
        fprintf (st, "%s format", fmts[i].name);
        return SCPE_OK;
        }
fprintf (st, "invalid format");
return SCPE_OK;
}

/* Set disk capacity */

t_stat sim_disk_set_capac (UNIT *uptr, int32 val, char *cptr, void *desc)
{
t_addr cap;
t_stat r;

if ((cptr == NULL) || (*cptr == 0))
    return SCPE_ARG;
if (uptr->flags & UNIT_ATT)
    return SCPE_ALATT;
cap = (t_addr) get_uint (cptr, 10, sim_taddr_64? 2000000: 2000, &r);
if (r != SCPE_OK)
    return SCPE_ARG;
uptr->capac = cap * ((t_addr) 1000000);
return SCPE_OK;
}

/* Show disk capacity */

t_stat sim_disk_show_capac (FILE *st, UNIT *uptr, int32 val, void *desc)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
char *cap_units = "B";

if (ctx->capac_factor == 2)
    cap_units = "W";
if (uptr->capac) {
    if (uptr->capac >= (t_addr) 1000000)
        fprintf (st, "capacity=%dM%s", (uint32) (uptr->capac / ((t_addr) 1000000)), cap_units);
    else if (uptr->capac >= (t_addr) 1000)
        fprintf (st, "capacity=%dK%s", (uint32) (uptr->capac / ((t_addr) 1000)), cap_units);
    else fprintf (st, "capacity=%d%s", (uint32) uptr->capac, cap_units);
    }
else fprintf (st, "undefined capacity");
return SCPE_OK;
}

/* Test for available */

t_bool sim_disk_isavailable (UNIT *uptr)
{
if (!(uptr->flags & UNIT_ATT))                          /* attached? */
    return FALSE;
switch (DK_GET_FMT (uptr)) {                            /* case on format */
    case DKUF_F_STD:                                    /* SIMH format */
        return TRUE;
    case DKUF_F_VHD:                                    /* VHD format */
        return TRUE;
        break;
    case DKUF_F_RAW:                                    /* Raw Physical Disk Access */
        return sim_os_disk_isavailable_raw (uptr->fileref);
        break;
    default:
        return FALSE;
    }
}

t_bool sim_disk_isavailable_a (UNIT *uptr, DISK_PCALLBACK callback)
{
t_bool r = FALSE;
AIO_CALLSETUP
    r = sim_disk_isavailable (uptr);
AIO_CALL(DOP_IAVL, 0, NULL, NULL, 0, callback);
return r;
}

/* Test for write protect */

t_bool sim_disk_wrp (UNIT *uptr)
{
return (uptr->flags & DKUF_WRP)? TRUE: FALSE;
}

/* Get Disk size */

t_addr sim_disk_size (UNIT *uptr)
{
switch (DK_GET_FMT (uptr)) {                            /* case on format */
    case DKUF_F_STD:                                    /* SIMH format */
        return sim_fsize_ex (uptr->fileref);
    case DKUF_F_VHD:                                    /* VHD format */
        return sim_vhd_disk_size (uptr->fileref);
        break;
    case DKUF_F_RAW:                                    /* Raw Physical Disk Access */
        return sim_os_disk_size_raw (uptr->fileref);
        break;
    default:
        return (t_addr)-1;
    }
}

/* Enable asynchronous operation */

t_stat sim_disk_set_async (UNIT *uptr, int latency)
{
#if !defined(SIM_ASYNCH_IO)
char *msg = "Disk: can't operate asynchronously\r\n";
printf ("%s", msg);
if (sim_log) fprintf (sim_log, "%s", msg);
return SCPE_NOFNC;
#else
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
pthread_attr_t attr;

sim_debug (ctx->dbit, ctx->dptr, "sim_disk_set_async(unit=%d)\n", (int)(uptr-ctx->dptr->units));

ctx->asynch_io = sim_asynch_enabled;
ctx->asynch_io_latency = latency;
if (ctx->asynch_io) {
    pthread_mutex_init (&ctx->io_lock, NULL);
    pthread_cond_init (&ctx->io_cond, NULL);
    pthread_cond_init (&ctx->io_done, NULL);
    pthread_cond_init (&ctx->startup_cond, NULL);
    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    pthread_mutex_lock (&ctx->io_lock);
    pthread_create (&ctx->io_thread, &attr, _disk_io, (void *)uptr);
    pthread_attr_destroy(&attr);
    pthread_cond_wait (&ctx->startup_cond, &ctx->io_lock); /* Wait for thread to stabilize */
    pthread_mutex_unlock (&ctx->io_lock);
    pthread_cond_destroy (&ctx->startup_cond);
    }
uptr->a_check_completion = _disk_completion_dispatch;
uptr->a_is_active = _disk_is_active;
uptr->a_cancel = _disk_cancel;
#endif
return SCPE_OK;
}

/* Disable asynchronous operation */

t_stat sim_disk_clr_async (UNIT *uptr)
{
#if !defined(SIM_ASYNCH_IO)
return SCPE_NOFNC;
#else
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

/* make sure device exists */
if (!ctx) return SCPE_UNATT;

sim_debug (ctx->dbit, ctx->dptr, "sim_disk_clr_async(unit=%d)\n", (int)(uptr-ctx->dptr->units));

if (ctx->asynch_io) {
    pthread_mutex_lock (&ctx->io_lock);
    ctx->asynch_io = 0;
    pthread_cond_signal (&ctx->io_cond);
    pthread_mutex_unlock (&ctx->io_lock);
    pthread_join (ctx->io_thread, NULL);
    pthread_mutex_destroy (&ctx->io_lock);
    pthread_cond_destroy (&ctx->io_cond);
    pthread_cond_destroy (&ctx->io_done);
    }
return SCPE_OK;
#endif
}

/* Read Sectors */

static t_stat _sim_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
t_addr da;
uint32 err, tbc;
size_t i;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

sim_debug (ctx->dbit, ctx->dptr, "_sim_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);

da = ((t_addr)lba) * ctx->sector_size;
tbc = sects * ctx->sector_size;
if (sectsread)
    *sectsread = 0;
err = sim_fseek (uptr->fileref, da, SEEK_SET);          /* set pos */
if (!err) {
    i = sim_fread (buf, ctx->xfer_element_size, tbc/ctx->xfer_element_size, uptr->fileref);
    if (i < tbc/ctx->xfer_element_size)                 /* fill */
        memset (&buf[i*ctx->xfer_element_size], 0, tbc-(i*ctx->xfer_element_size));
    err = ferror (uptr->fileref);
    if ((!err) && (sectsread))
        *sectsread = (t_seccnt)((i*ctx->xfer_element_size+ctx->sector_size-1)/ctx->sector_size);
    }
return err;
}

t_stat sim_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
t_stat r;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
t_seccnt sread;

sim_debug (ctx->dbit, ctx->dptr, "sim_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);

if ((sects == 1) &&                                     /* Single sector reads */
    (lba >= (uptr->capac*ctx->capac_factor)/ctx->sector_size)) {/* beyond the end of the disk */
    memset (buf, '\0', ctx->sector_size);               /* are bad block management efforts - zero buffer */
    if (sectsread)
        *sectsread = 1;
    return SCPE_OK;                                     /* return success */
    }

if ((0 == (ctx->sector_size & (ctx->storage_sector_size - 1))) ||   /* Sector Aligned & whole sector transfers */
    ((0 == ((lba*ctx->sector_size) & (ctx->storage_sector_size - 1))) &&
     (0 == ((sects*ctx->sector_size) & (ctx->storage_sector_size - 1))))) {
    switch (DK_GET_FMT (uptr)) {                        /* case on format */
        case DKUF_F_STD:                                /* SIMH format */
            return _sim_disk_rdsect (uptr, lba, buf, sectsread, sects);
        case DKUF_F_VHD:                                /* VHD format */
            r = sim_vhd_disk_rdsect (uptr, lba, buf, &sread, sects);
            break;
        case DKUF_F_RAW:                                /* Raw Physical Disk Access */
            r = sim_os_disk_rdsect (uptr, lba, buf, &sread, sects);
            break;
        default:
            return SCPE_NOFNC;
        }
    if (sectsread)
        *sectsread = sread;
    if (r != SCPE_OK)
        return r;
    sim_buf_swap_data (buf, ctx->xfer_element_size, (sread * ctx->sector_size) / ctx->xfer_element_size);
    return r;
    }
else { /* Unaligned and/or partial sector transfers */
    uint8 *tbuf = (uint8*) malloc (sects*ctx->sector_size + 2*ctx->storage_sector_size);
    t_lba sspsts = ctx->storage_sector_size/ctx->sector_size; /* sim sectors in a storage sector */
    t_lba tlba = lba & ~(sspsts - 1);
    t_seccnt tsects = sects + (lba - tlba);

    tsects = (tsects + (sspsts - 1)) & ~(sspsts - 1);
    if (sectsread)
        *sectsread = 0;
    if (tbuf == NULL)
        return SCPE_MEM;
    switch (DK_GET_FMT (uptr)) {                        /* case on format */
        case DKUF_F_STD:                                /* SIMH format */
            r = _sim_disk_rdsect (uptr, tlba, tbuf, &sread, tsects);
            break;
        case DKUF_F_VHD:                                /* VHD format */
            r = sim_vhd_disk_rdsect (uptr, tlba, tbuf, &sread, tsects);
            if (r == SCPE_OK)
                sim_buf_swap_data (tbuf, ctx->xfer_element_size, (sread * ctx->sector_size) / ctx->xfer_element_size);
            break;
        case DKUF_F_RAW:                                /* Raw Physical Disk Access */
            r = sim_os_disk_rdsect (uptr, tlba, tbuf, &sread, tsects);
            if (r == SCPE_OK)
                sim_buf_swap_data (tbuf, ctx->xfer_element_size, (sread * ctx->sector_size) / ctx->xfer_element_size);
            break;
        default:
            free (tbuf);
            return SCPE_NOFNC;
        }
    if (r == SCPE_OK) {
        memcpy (buf, tbuf + ((lba - tlba) * ctx->sector_size), sects * ctx->sector_size);
        if (sectsread) {
            *sectsread = sread - (lba - tlba);
            if (*sectsread > sects)
                *sectsread = sects;
            }
        }
    free (tbuf);
    return r;
    }
}

t_stat sim_disk_rdsect_a (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects, DISK_PCALLBACK callback)
{
t_stat r = SCPE_OK;
AIO_CALLSETUP
    r = sim_disk_rdsect (uptr, lba, buf, sectsread, sects);
AIO_CALL(DOP_RSEC, lba, buf, sectsread, sects, callback);
return r;
}

/* Write Sectors */

static t_stat _sim_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
t_addr da;
uint32 err, tbc;
size_t i;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

sim_debug (ctx->dbit, ctx->dptr, "_sim_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);

da = ((t_addr)lba) * ctx->sector_size;
tbc = sects * ctx->sector_size;
if (sectswritten)
    *sectswritten = 0;
err = sim_fseek (uptr->fileref, da, SEEK_SET);          /* set pos */
if (!err) {
    i = sim_fwrite (buf, ctx->xfer_element_size, tbc/ctx->xfer_element_size, uptr->fileref);
    err = ferror (uptr->fileref);
    if ((!err) && (sectswritten))
        *sectswritten = (t_seccnt)((i*ctx->xfer_element_size+ctx->sector_size-1)/ctx->sector_size);
    }
return err;
}

t_stat sim_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
uint32 f = DK_GET_FMT (uptr);
t_stat r;
uint8 *tbuf = NULL;

sim_debug (ctx->dbit, ctx->dptr, "sim_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);

if (f == DKUF_F_STD)
    return _sim_disk_wrsect (uptr, lba, buf, sectswritten, sects);
if ((0 == (ctx->sector_size & (ctx->storage_sector_size - 1))) ||   /* Sector Aligned & whole sector transfers */
    ((0 == ((lba*ctx->sector_size) & (ctx->storage_sector_size - 1))) &&
     (0 == ((sects*ctx->sector_size) & (ctx->storage_sector_size - 1))))) {

    if (sim_end || (ctx->xfer_element_size == sizeof (char)))
        switch (DK_GET_FMT (uptr)) {                            /* case on format */
            case DKUF_F_VHD:                                    /* VHD format */
                return sim_vhd_disk_wrsect  (uptr, lba, buf, sectswritten, sects);
            case DKUF_F_RAW:                                    /* Raw Physical Disk Access */
                return sim_os_disk_wrsect  (uptr, lba, buf, sectswritten, sects);
            default:
                return SCPE_NOFNC;
            }

    tbuf = (uint8*) malloc (sects * ctx->sector_size);
    if (NULL == tbuf)
        return SCPE_MEM;
    sim_buf_copy_swapped (tbuf, buf, ctx->xfer_element_size, (sects * ctx->sector_size) / ctx->xfer_element_size);

    switch (DK_GET_FMT (uptr)) {                            /* case on format */
        case DKUF_F_VHD:                                    /* VHD format */
            r = sim_vhd_disk_wrsect (uptr, lba, tbuf, sectswritten, sects);
            break;
        case DKUF_F_RAW:                                    /* Raw Physical Disk Access */
            r = sim_os_disk_wrsect (uptr, lba, tbuf, sectswritten, sects);
            break;
        default:
            r = SCPE_NOFNC;
            break;
        }
    }
else { /* Unaligned and/or partial sector transfers */
    t_lba sspsts = ctx->storage_sector_size/ctx->sector_size; /* sim sectors in a storage sector */
    t_lba tlba = lba & ~(sspsts - 1);
    t_seccnt tsects = sects + (lba - tlba);

    tbuf = (uint8*) malloc (sects*ctx->sector_size + 2*ctx->storage_sector_size);
    tsects = (tsects + (sspsts - 1)) & ~(sspsts - 1);
    if (sectswritten)
        *sectswritten = 0;
    if (tbuf == NULL)
        return SCPE_MEM;
    /* Partial Sector writes require a read-modify-write sequence for the partial sectors */
    if ((lba & (sspsts - 1)) ||
        (sects < sspsts))
        switch (DK_GET_FMT (uptr)) {                            /* case on format */
            case DKUF_F_VHD:                                    /* VHD format */
                sim_vhd_disk_rdsect (uptr, tlba, tbuf, NULL, sspsts);
                break;
            case DKUF_F_RAW:                                    /* Raw Physical Disk Access */
                sim_os_disk_rdsect (uptr, tlba, tbuf, NULL, sspsts);
                break;
            default:
                r = SCPE_NOFNC;
                break;
            }
    if ((tsects > sspsts) &&
        ((sects + lba - tlba) & (sspsts - 1)))
        switch (DK_GET_FMT (uptr)) {                            /* case on format */
            case DKUF_F_VHD:                                    /* VHD format */
                sim_vhd_disk_rdsect (uptr, tlba + tsects - sspsts, 
                                     tbuf + (tsects - sspsts) * ctx->sector_size, 
                                     NULL, sspsts);
                break;
            case DKUF_F_RAW:                                    /* Raw Physical Disk Access */
                sim_os_disk_rdsect (uptr, tlba + tsects - sspsts, 
                                    tbuf + (tsects - sspsts) * ctx->sector_size, 
                                    NULL, sspsts);
                break;
            default:
                r = SCPE_NOFNC;
                break;
            }
    sim_buf_copy_swapped (tbuf + (lba & (sspsts - 1)) * ctx->sector_size, 
                          buf, ctx->xfer_element_size, (sects * ctx->sector_size) / ctx->xfer_element_size);
    switch (DK_GET_FMT (uptr)) {                            /* case on format */
        case DKUF_F_VHD:                                    /* VHD format */
            r = sim_vhd_disk_wrsect (uptr, tlba, tbuf, sectswritten, tsects);
            break;
        case DKUF_F_RAW:                                    /* Raw Physical Disk Access */
            r = sim_os_disk_wrsect (uptr, tlba, tbuf, sectswritten, tsects);
            break;
        default:
            r = SCPE_NOFNC;
            break;
        }
    if ((r == SCPE_OK) && sectswritten) {
        *sectswritten -= (lba - tlba);
        if (*sectswritten > sects)
            *sectswritten = sects;
        }
    }
free (tbuf);
return r;
}

t_stat sim_disk_wrsect_a (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects, DISK_PCALLBACK callback)
{
t_stat r = SCPE_OK;
AIO_CALLSETUP
    r =  sim_disk_wrsect (uptr, lba, buf, sectswritten, sects);
AIO_CALL(DOP_WSEC, lba, buf, sectswritten, sects, callback);
return r;
}

t_stat sim_disk_unload (UNIT *uptr)
{
switch (DK_GET_FMT (uptr)) {                            /* case on format */
    case DKUF_F_STD:                                    /* Simh */
    case DKUF_F_VHD:                                    /* VHD format */
        return sim_disk_detach (uptr);
    case DKUF_F_RAW:                                    /* Raw Physical Disk Access */
        return sim_os_disk_unload_raw (uptr->fileref);  /* remove/eject disk */
        break;
    default:
        return SCPE_NOFNC;
    }
}

/* 
   This routine is called when the simulator stops and any time
   the asynch mode is changed (enabled or disabled)
*/
static void _sim_disk_io_flush (UNIT *uptr)
{
uint32 f = DK_GET_FMT (uptr);

#if defined (SIM_ASYNCH_IO)
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

sim_disk_clr_async (uptr);
if (sim_asynch_enabled)
    sim_disk_set_async (uptr, ctx->asynch_io_latency);
#endif
switch (f) {                                            /* case on format */
    case DKUF_F_STD:                                    /* Simh */
        fflush (uptr->fileref);
        break;
    case DKUF_F_VHD:                                    /* Virtual Disk */
        sim_vhd_disk_flush (uptr->fileref);
        break;
    case DKUF_F_RAW:                                    /* Physical */
        sim_os_disk_flush_raw (uptr->fileref);
        break;
        }
}

static t_stat _err_return (UNIT *uptr, t_stat stat)
{
free (uptr->filename);
uptr->filename = NULL;
free (uptr->disk_ctx);
uptr->disk_ctx = NULL;
return stat;
}


t_stat sim_disk_attach (UNIT *uptr, char *cptr, size_t sector_size, size_t xfer_element_size, t_bool dontautosize, 
                        uint32 dbit, const char *dtype, uint32 pdp11tracksize, int completion_delay)
{
struct disk_context *ctx;
DEVICE *dptr;
FILE *(*open_function)(const char *filename, const char *mode) = sim_fopen;
FILE *(*create_function)(const char *filename, t_addr desiredsize) = NULL;
t_addr (*size_function)(FILE *file);
t_stat (*storage_function)(FILE *file, uint32 *sector_size, uint32 *removable) = NULL;
t_bool created = FALSE;
t_bool auto_format = FALSE;
t_addr capac;

if (uptr->flags & UNIT_DIS)                             /* disabled? */
    return SCPE_UDIS;
if (!(uptr->flags & UNIT_ATTABLE))                      /* not attachable? */
    return SCPE_NOATT;
if ((dptr = find_dev_from_unit (uptr)) == NULL)
    return SCPE_NOATT;
if (sim_switches & SWMASK ('F')) {                      /* format spec? */
    char gbuf[CBUFSIZE];
    cptr = get_glyph (cptr, gbuf, 0);                   /* get spec */
    if (*cptr == 0)                                     /* must be more */
        return SCPE_2FARG;
    if (sim_disk_set_fmt (uptr, 0, gbuf, NULL) != SCPE_OK)
        return SCPE_ARG;
    }
if (sim_switches & SWMASK ('D')) {                      /* create difference disk? */
    char gbuf[CBUFSIZE];
    FILE *vhd;

    sim_switches = sim_switches & ~(SWMASK ('D'));
    cptr = get_glyph_nc (cptr, gbuf, 0);                /* get spec */
    if (*cptr == 0)                                     /* must be more */
        return SCPE_2FARG;
    vhd = sim_vhd_disk_create_diff (gbuf, cptr);
    if (vhd) {
        sim_vhd_disk_close (vhd);
        return sim_disk_attach (uptr, gbuf, sector_size, xfer_element_size, dontautosize, dbit, dtype, pdp11tracksize, completion_delay);
        }
    return SCPE_ARG;
    }
if (sim_switches & SWMASK ('C')) {                      /* create vhd disk & copy contents? */
    char gbuf[CBUFSIZE];
    FILE *vhd;
    int saved_sim_switches = sim_switches;
    int32 saved_sim_quiet = sim_quiet;
    uint32 capac_factor;
    t_stat r;

    sim_switches = sim_switches & ~(SWMASK ('C'));
    cptr = get_glyph_nc (cptr, gbuf, 0);                /* get spec */
    if (*cptr == 0)                                     /* must be more */
        return SCPE_2FARG;
    sim_switches |= SWMASK ('R') | SWMASK ('E');
    sim_quiet = TRUE;
    /* First open the source of the copy operation */
    r = sim_disk_attach (uptr, cptr, sector_size, xfer_element_size, dontautosize, dbit, dtype, pdp11tracksize, completion_delay);
    sim_quiet = saved_sim_quiet;
    if (r != SCPE_OK) {
        sim_switches = saved_sim_switches;
        return r;
        }
    if (!sim_quiet) 
        printf ("%s%d: creating new virtual disk '%s'\n", sim_dname (dptr), (int)(uptr-dptr->units), gbuf);
    capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (word: 2, byte: 1) */
    vhd = sim_vhd_disk_create (gbuf, uptr->capac*capac_factor);
    if (!vhd) {
        if (!sim_quiet)
            printf ("%s%d: can't create virtual disk '%s'\n", sim_dname (dptr), (int)(uptr-dptr->units), gbuf);
        return SCPE_OPENERR;
        }
    else {
        uint8 *copy_buf = (uint8*) malloc (1024*1024);
        t_lba lba;
        t_seccnt sectors_per_buffer = (t_seccnt)((1024*1024)/sector_size);
        t_lba total_sectors = (t_lba)((uptr->capac*capac_factor)/sector_size);
        t_seccnt sects = sectors_per_buffer;

        if (!copy_buf) {
            remove (gbuf);
            return SCPE_MEM;
            }
        for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects) {
            if (!sim_quiet)
                printf ("%s%d: Copied %dMB.  %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)(((t_addr)lba*sector_size)/1000000), (int)((lba*100)/total_sectors));
            sects = sectors_per_buffer;
            if (lba + sects > total_sectors)
                sects = total_sectors - lba;
            r = sim_disk_rdsect (uptr, lba, copy_buf, NULL, sects);
            if (r == SCPE_OK) {
                uint32 saved_unit_flags = uptr->flags;
                FILE *save_unit_fileref = uptr->fileref;

                sim_disk_set_fmt (uptr, 0, "VHD", NULL);
                uptr->fileref = vhd;
                r = sim_disk_wrsect (uptr, lba, copy_buf, NULL, sects);
                uptr->fileref = save_unit_fileref;
                uptr->flags = saved_unit_flags;
                }
            }
        if (!sim_quiet)
            printf ("\n%s%d: Copied %dMB. Done.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)(((t_addr)lba*sector_size)/1000000));
        free (copy_buf);
        created = TRUE;
        sim_vhd_disk_close (vhd);
        sim_disk_detach (uptr);
        strcpy (cptr, gbuf);
        sim_disk_set_fmt (uptr, 0, "VHD", NULL);
        sim_switches = saved_sim_switches;
        /* fall through and open/return the newly created & copied vhd */
        }
    }
else if (sim_switches & SWMASK ('M')) {                 /* merge difference disk? */
    char gbuf[CBUFSIZE], *Parent = NULL;
    FILE *vhd;

    sim_switches = sim_switches & ~(SWMASK ('M'));
    get_glyph_nc (cptr, gbuf, 0);                       /* get spec */
    vhd = sim_vhd_disk_merge (gbuf, &Parent);
    if (vhd) {
        t_stat r;

        sim_vhd_disk_close (vhd);
        r = sim_disk_attach (uptr, Parent, sector_size, xfer_element_size, dontautosize, dbit, dtype, pdp11tracksize, completion_delay);
        free (Parent);
        return r;
        }
    return SCPE_ARG;
    }

switch (DK_GET_FMT (uptr)) {                            /* case on format */
    case DKUF_F_STD:                                    /* SIMH format */
        if (NULL == (uptr->fileref = sim_vhd_disk_open (cptr, "rb"))) {
            open_function = sim_fopen;
            size_function = sim_fsize_ex;
            break;
            }
        sim_disk_set_fmt (uptr, 0, "VHD", NULL);        /* set file format to VHD */
        sim_vhd_disk_close (uptr->fileref);             /* close vhd file*/
        auto_format = TRUE;
        uptr->fileref = NULL;
        /* Fall through to normal VHD processing */
    case DKUF_F_VHD:                                    /* VHD format */
        open_function = sim_vhd_disk_open;
        create_function = sim_vhd_disk_create;
        size_function = sim_vhd_disk_size;
        break;
    case DKUF_F_RAW:                                    /* Raw Physical Disk Access */
        open_function = sim_os_disk_open_raw;
        size_function = sim_os_disk_size_raw;
        storage_function = sim_os_disk_info_raw;
        break;
    default:
        return SCPE_IERR;
    }
uptr->filename = (char *) calloc (CBUFSIZE, sizeof (char));/* alloc name buf */
uptr->disk_ctx = ctx = (struct disk_context *)calloc(1, sizeof(struct disk_context));
if ((uptr->filename == NULL) || (uptr->disk_ctx == NULL))
    return _err_return (uptr, SCPE_MEM);
strncpy (uptr->filename, cptr, CBUFSIZE);               /* save name */
ctx->sector_size = (uint32)sector_size;                 /* save sector_size */
ctx->capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* save capacity units (word: 2, byte: 1) */
ctx->xfer_element_size = (uint32)xfer_element_size;     /* save xfer_element_size */
ctx->dptr = dptr;                                       /* save DEVICE pointer */
ctx->dbit = dbit;                                       /* save debug bit */
sim_debug (ctx->dbit, ctx->dptr, "sim_disk_attach(unit=%d,filename='%s')\n", (int)(uptr-ctx->dptr->units), uptr->filename);
ctx->auto_format = auto_format;                         /* save that we auto selected format */
ctx->storage_sector_size = (uint32)sector_size;         /* Default */
if (sim_switches & SWMASK ('R')) {                      /* read only? */
    if ((uptr->flags & UNIT_ROABLE) == 0)               /* allowed? */
        return _err_return (uptr, SCPE_NORO);           /* no, error */
    uptr->fileref = open_function (cptr, "rb");         /* open rd only */
    if (uptr->fileref == NULL)                          /* open fail? */
        return _err_return (uptr, SCPE_OPENERR);        /* yes, error */
    uptr->flags = uptr->flags | UNIT_RO;                /* set rd only */
    if (!sim_quiet)
        printf ("%s%d: unit is read only\n", sim_dname (dptr), (int)(uptr-dptr->units));
    }
else {                                                  /* normal */
    uptr->fileref = open_function (cptr, "rb+");        /* open r/w */
    if (uptr->fileref == NULL) {                        /* open fail? */
        if ((errno == EROFS) || (errno == EACCES)) {    /* read only? */
            if ((uptr->flags & UNIT_ROABLE) == 0)       /* allowed? */
                return _err_return (uptr, SCPE_NORO);   /* no error */
            uptr->fileref = open_function (cptr, "rb"); /* open rd only */
            if (uptr->fileref == NULL)                  /* open fail? */
                return _err_return (uptr, SCPE_OPENERR);/* yes, error */
            uptr->flags = uptr->flags | UNIT_RO;        /* set rd only */
            if (!sim_quiet)
                printf ("%s%d: unit is read only\n", sim_dname (dptr), (int)(uptr-dptr->units));
            }
        else {                                          /* doesn't exist */
            if (sim_switches & SWMASK ('E'))            /* must exist? */
                return _err_return (uptr, SCPE_OPENERR); /* yes, error */
            if (create_function)
                uptr->fileref = create_function (cptr, uptr->capac*ctx->capac_factor);/* create new file */
            else
                uptr->fileref = open_function (cptr, "wb+");/* open new file */
            if (uptr->fileref == NULL)                  /* open fail? */
                return _err_return (uptr, SCPE_OPENERR);/* yes, error */
            if (!sim_quiet) 
                printf ("%s%d: creating new file\n", sim_dname (dptr), (int)(uptr-dptr->units));
            created = TRUE;
            }
        }                                               /* end if null */
    }                                                   /* end else */
if (DK_GET_FMT (uptr) == DKUF_F_VHD) {
    if ((created) && dtype)
        sim_vhd_disk_set_dtype (uptr->fileref, dtype);
    if (dtype && strcmp (dtype, sim_vhd_disk_get_dtype (uptr->fileref))) {
        char cmd[32];

        sprintf (cmd, "%s%d %s", dptr->name, (int)(uptr-dptr->units), sim_vhd_disk_get_dtype (uptr->fileref));
        set_cmd (0, cmd);
        }
    }
uptr->flags = uptr->flags | UNIT_ATT;
uptr->pos = 0;

/* Get Device attributes if they are available */
if (storage_function)
    storage_function (uptr->fileref, &ctx->storage_sector_size, &ctx->removable);

if (created) {
    t_stat r = SCPE_OK;
    uint8 *secbuf = calloc (1, ctx->sector_size);       /* alloc temp sector buf */

    /* 
       On a newly created disk, we write a zero sector to the last and the 
       first sectors.  This serves 3 purposes: 
         1) it avoids strange allocation delays writing newly allocated 
            storage at the end of the disk during simulator operation
         2) it allocates storage for the whole disk at creation time to 
            avoid strange failures which may happen during simulator execution
            if the containing disk is full
         3) it leaves a Sinh Format disk at the intended size so it may 
            subsequently be autosized with the correct size.
    */
    if (secbuf == NULL)
        r = SCPE_MEM;
    if (r == SCPE_OK)
        r = sim_disk_wrsect (uptr, (t_lba)(((uptr->capac*ctx->capac_factor) - ctx->sector_size)/ctx->sector_size), secbuf, NULL, 1); /* Write Last Sector */
    if (r == SCPE_OK)
        r = sim_disk_wrsect (uptr, (t_lba)(0), secbuf, NULL, 1); /* Write First Sector */
    free (secbuf);
    if (r != SCPE_OK) {
        sim_disk_detach (uptr);                         /* report error now */
        remove (cptr);                                  /* remove the create file */
        return SCPE_OPENERR;
        }
    if (pdp11tracksize)
        sim_disk_pdp11_bad_block (uptr, pdp11tracksize);
    }

capac = size_function (uptr->fileref);
if (capac && (capac != (t_addr)-1)) {
    if (dontautosize) {
        if ((capac < (uptr->capac*ctx->capac_factor)) && (DKUF_F_STD != DK_GET_FMT (uptr))) {
            if (!sim_quiet) {
                printf ("%s%d: non expandable disk %s is smaller than simulated device (", sim_dname (dptr), (int)(uptr-dptr->units), cptr);
                fprint_val (stdout, capac/ctx->capac_factor, 10, T_ADDR_W, PV_LEFT);
                printf ("%s < ", (ctx->capac_factor == 2) ? "W" : "");
                fprint_val (stdout, uptr->capac, 10, T_ADDR_W, PV_LEFT);
                printf ("%s)\n", (ctx->capac_factor == 2) ? "W" : "");
                }
            }
        }
    else
        if ((capac > (uptr->capac*ctx->capac_factor)) || (DKUF_F_STD != DK_GET_FMT (uptr)))
            uptr->capac = capac/ctx->capac_factor;
    }

#if defined (SIM_ASYNCH_IO)
sim_disk_set_async (uptr, completion_delay);
#endif
uptr->io_flush = _sim_disk_io_flush;

return SCPE_OK;
}

t_stat sim_disk_detach (UNIT *uptr)
{
struct disk_context *ctx;
int (*close_function)(FILE *f);
FILE *fileref;
t_bool auto_format;

if ((uptr == NULL) || !(uptr->flags & UNIT_ATT))
    return SCPE_IERR;

ctx = (struct disk_context *)uptr->disk_ctx;
fileref = uptr->fileref;

sim_debug (ctx->dbit, ctx->dptr, "sim_disk_detach(unit=%d,filename='%s')\n", (int)(uptr-ctx->dptr->units), uptr->filename);

switch (DK_GET_FMT (uptr)) {                            /* case on format */
    case DKUF_F_STD:                                    /* Simh */
        close_function = fclose;
        break;
    case DKUF_F_VHD:                                    /* Virtual Disk */
        close_function = sim_vhd_disk_close;
        break;
    case DKUF_F_RAW:                                    /* Physical */
        close_function = sim_os_disk_close_raw;
        break;
    default:
        return SCPE_IERR;
        }
if (!(uptr->flags & UNIT_ATTABLE))                      /* attachable? */
    return SCPE_NOATT;
if (!(uptr->flags & UNIT_ATT))                          /* attached? */
    return SCPE_OK;
if (NULL == find_dev_from_unit (uptr))
    return SCPE_OK;
auto_format = ctx->auto_format;

if (uptr->io_flush)
    uptr->io_flush (uptr);                              /* flush buffered data */

sim_disk_clr_async (uptr);

uptr->flags &= ~(UNIT_ATT | UNIT_RO);
uptr->dynflags &= ~UNIT_NO_FIO;
free (uptr->filename);
uptr->filename = NULL;
uptr->fileref = NULL;
free (uptr->disk_ctx);
uptr->disk_ctx = NULL;
uptr->io_flush = NULL;
if (auto_format)
    sim_disk_set_fmt (uptr, 0, "SIMH", NULL);           /* restore file format */
if (close_function (fileref) == EOF)
    return SCPE_IOERR;
return SCPE_OK;
}

t_stat sim_disk_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
fprintf (st, "%s Disk Attach Help\n\n", dptr->name);

fprintf (st, "Disk container files can be one of 3 different types:\n\n");
fprintf (st, "    SIMH   A disk is an unstructured binary file of the size appropriate\n");
fprintf (st, "           for the disk drive being simulated\n");
fprintf (st, "    VHD    Virtual Disk format which is described in the \"Microsoft\n");
fprintf (st, "           Virtual Hard Disk (VHD) Image Format Specification\".  The\n");
fprintf (st, "           VHD implementation includes support for 1) Fixed (Preallocated)\n");
fprintf (st, "           disks, 2) Dynamically Expanding disks, and 3) Differencing disks.\n");
fprintf (st, "    RAW    platform specific access to physical disk or CDROM drives\n\n");
fprintf (st, "Virtual (VHD) Disks  supported conform to \"Virtual Hard Disk Image Format\n");
fprintf (st, "Specification\", Version 1.0 October 11, 2006.\n");
fprintf (st, "Dynamically expanding disks never change their \"Virtual Size\", but they don't\n");
fprintf (st, "consume disk space on the containing storage until the virtual sectors in the\n");
fprintf (st, "disk are actually written to (i.e. a 2GB Dynamic disk container file with only\n");
fprintf (st, "30MB of data will initially be about 30MB in size and this size will grow up to\n");
fprintf (st, "2GB as different sectors are written to.  The VHD format contains metadata\n");
fprintf (st, "which describes the drive size and the simh device type in use when the VHD\n");
fprintf (st, "was created.  This metadata is therefore available whenever that VHD is\n");
fprintf (st, "attached to an emulated disk device in the future so the device type and\n");
fprintf (st, "size can be automatically be configured.\n\n");

if (0 == (uptr-dptr->units)) {
    if (dptr->numunits > 1) {
        uint32 i;

        for (i=0; i < dptr->numunits; ++i)
            if (dptr->units[i].flags & UNIT_ATTABLE)
                fprintf (st, "  sim> ATTACH {switches} %s%d diskfile\n", dptr->name, i);
        }
    else
        fprintf (st, "  sim> ATTACH {switches} %s diskfile\n", dptr->name);
    }
else
    fprintf (st, "  sim> ATTACH {switches} %s diskfile\n\n", dptr->name);
fprintf (st, "\n%s attach command switches\n", dptr->name);
fprintf (st, "    -R          Attach Read Only.\n");
fprintf (st, "    -E          Must Exist (if not specified an attempt to create the indicated\n");
fprintf (st, "                disk container will be attempted).\n");
fprintf (st, "    -F          Open the indicated disk container in a specific format (default\n");
fprintf (st, "                is to autodetect VHD defaulting to simh if the indicated\n");
fprintf (st, "                container is not a VHD).\n");
fprintf (st, "    -C          Create a VHD and copy its contents from another disk (simh, VHD,\n");
fprintf (st, "                or RAW format).\n");
fprintf (st, "    -X          When creating a VHD, create a fixed sized VHD (vs a Dynamically\n");
fprintf (st, "                expanding one).\n");
fprintf (st, "    -D          Create a Differencing VHD (relative to an already existing VHD\n");
fprintf (st, "                disk)\n");
fprintf (st, "    -M          Merge a Differencing VHD into its parent VHD disk\n");
return SCPE_OK;
}

t_stat sim_disk_reset (UNIT *uptr)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

if (!(uptr->flags & UNIT_ATT))                          /* attached? */
    return SCPE_OK;

sim_debug (ctx->dbit, ctx->dptr, "sim_disk_reset(unit=%d)\n", (int)(uptr-ctx->dptr->units));

_sim_disk_io_flush(uptr);
AIO_VALIDATE;
AIO_UPDATE_QUEUE;
return SCPE_OK;
}

t_stat sim_disk_perror (UNIT *uptr, const char *msg)
{
if (!(uptr->flags & UNIT_ATTABLE))                      /* not attachable? */
    return SCPE_NOATT;
switch (DK_GET_FMT (uptr)) {                            /* case on format */
    case DKUF_F_STD:                                    /* SIMH format */
    case DKUF_F_VHD:                                    /* VHD format */
    case DKUF_F_RAW:                                    /* Raw Physical Disk Access */
        perror (msg);
    default:
        ;
    }
return SCPE_OK;
}

t_stat sim_disk_clearerr (UNIT *uptr)
{
if (!(uptr->flags & UNIT_ATTABLE))                      /* not attachable? */
    return SCPE_NOATT;
switch (DK_GET_FMT (uptr)) {                            /* case on format */
    case DKUF_F_STD:                                    /* SIMH format */
        clearerr (uptr->fileref);
        break;
    case DKUF_F_VHD:                                    /* VHD format */
        sim_vhd_disk_clearerr (uptr);
        break;
    default:
        ;
    }
return SCPE_OK;
}


/* Factory bad block table creation routine

   This routine writes a DEC standard 044 compliant bad block table on the
   last track of the specified unit.  The bad block table consists of 10
   repetitions of the same table, formatted as follows:

        words 0-1       pack id number
        words 2-3       cylinder/sector/surface specifications
         :
        words n-n+1     end of table (-1,-1)

   Inputs:
        uptr    =       pointer to unit
        sec     =       number of sectors per surface
   Outputs:
        sta     =       status code
*/

t_stat sim_disk_pdp11_bad_block (UNIT *uptr, int32 sec)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
int32 i;
t_addr da;
int32 wds = ctx->sector_size/sizeof (uint16);
uint16 *buf;

if ((sec < 2) || (wds < 16))
    return SCPE_ARG;
if ((uptr->flags & UNIT_ATT) == 0)
    return SCPE_UNATT;
if (uptr->flags & UNIT_RO)
    return SCPE_RO;
if (ctx->capac_factor != 2)                  /* Must be Word oriented Capacity */
    return SCPE_IERR;
if (!get_yn ("Overwrite last track? [N]", FALSE))
    return SCPE_OK;
if ((buf = (uint16 *) malloc (wds * sizeof (uint16))) == NULL)
    return SCPE_MEM;
buf[0] = buf[1] = 012345u;
buf[2] = buf[3] = 0;
for (i = 4; i < wds; i++)
    buf[i] = 0177777u;
da = uptr->capac - (sec * wds);
for (i = 0; (i < sec) && (i < 10); i++, da += wds)
    if (sim_disk_wrsect (uptr, (t_lba)(da/wds), (void *)buf, NULL, 1)) {
        free (buf);
        return SCPE_IOERR;
        }
free (buf);
return SCPE_OK;
}

void sim_disk_data_trace(UNIT *uptr, const uint8 *data, size_t lba, size_t len, const char* txt, int detail, uint32 reason)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

if (ctx->dptr->dctrl & reason) {
    sim_debug (reason, ctx->dptr, "%s%d %s lbn: %08X len: %08X\n", ctx->dptr->name, (int)(uptr-ctx->dptr->units), txt, lba, len);
    if (detail) {
        size_t i, same, group, sidx, oidx;
        char outbuf[80], strbuf[18];
        static char hex[] = "0123456789ABCDEF";

        for (i=same=0; i<len; i += 16) {
            if ((i > 0) && (0 == memcmp (&data[i], &data[i-16], 16))) {
                ++same;
                continue;
                }
            if (same > 0) {
                sim_debug (reason, ctx->dptr, "%04X thru %04X same as above\n", i-(16*same), i-1);
                same = 0;
                }
            group = (((len - i) > 16) ? 16 : (len - i));
            for (sidx=oidx=0; sidx<group; ++sidx) {
                outbuf[oidx++] = ' ';
                outbuf[oidx++] = hex[(data[i+sidx]>>4)&0xf];
                outbuf[oidx++] = hex[data[i+sidx]&0xf];
                if (isprint (data[i+sidx]))
                    strbuf[sidx] = data[i+sidx];
                else
                    strbuf[sidx] = '.';
                }
            outbuf[oidx] = '\0';
            strbuf[sidx] = '\0';
            sim_debug (reason, ctx->dptr, "%04X%-48s %s\n", i, outbuf, strbuf);
            }
        if (same > 0) {
            sim_debug (reason, ctx->dptr, "%04X thru %04X same as above\n", i-(16*same), len-1);
            }
        }
    }
}


/* OS Specific RAW Disk I/O support */

#if defined _WIN32

static void _set_errno_from_status (DWORD dwStatus)
{
switch (dwStatus) {
    case ERROR_FILE_NOT_FOUND:    case ERROR_PATH_NOT_FOUND:
    case ERROR_INVALID_DRIVE:     case ERROR_NO_MORE_FILES:
    case ERROR_BAD_NET_NAME:      case ERROR_BAD_NETPATH:
    case ERROR_BAD_PATHNAME:      case ERROR_FILENAME_EXCED_RANGE:
        errno = ENOENT;
        return;
    case ERROR_INVALID_ACCESS:    case ERROR_INVALID_DATA:
    case ERROR_INVALID_FUNCTION:  case ERROR_INVALID_PARAMETER:
    case ERROR_NEGATIVE_SEEK:
        errno = EINVAL;
        return;
    case ERROR_ARENA_TRASHED:     case ERROR_NOT_ENOUGH_MEMORY:
    case ERROR_INVALID_BLOCK:     case ERROR_NOT_ENOUGH_QUOTA:
        errno = ENOMEM;
        return;
    case ERROR_TOO_MANY_OPEN_FILES:
        errno = EMFILE;
        return;
    case ERROR_ACCESS_DENIED:     case ERROR_CURRENT_DIRECTORY:
    case ERROR_LOCK_VIOLATION:    case ERROR_NETWORK_ACCESS_DENIED:
    case ERROR_CANNOT_MAKE:       case ERROR_FAIL_I24:
    case ERROR_DRIVE_LOCKED:      case ERROR_SEEK_ON_DEVICE:
    case ERROR_NOT_LOCKED:        case ERROR_LOCK_FAILED:
        errno = EACCES;
        return;
    case ERROR_ALREADY_EXISTS:    case ERROR_FILE_EXISTS:
        errno = EEXIST;
        return;
    case ERROR_INVALID_HANDLE:    case ERROR_INVALID_TARGET_HANDLE:
    case ERROR_DIRECT_ACCESS_HANDLE:
        errno = EBADF;
        return;
    case ERROR_DIR_NOT_EMPTY:
        errno = ENOTEMPTY;
        return;
    case ERROR_BAD_ENVIRONMENT:
        errno = E2BIG;
        return;
    case ERROR_BAD_FORMAT:
        errno = ENOEXEC;
        return;
    case ERROR_NOT_SAME_DEVICE:
        errno = EXDEV;
        return;
    case ERROR_BROKEN_PIPE:
        errno = EPIPE;
        return;
    case ERROR_DISK_FULL:
        errno = ENOSPC;
        return;
    case ERROR_WAIT_NO_CHILDREN:  case ERROR_CHILD_NOT_COMPLETE:
        errno = ECHILD;
        return;
    case ERROR_NO_PROC_SLOTS:     case ERROR_MAX_THRDS_REACHED:
    case ERROR_NESTING_NOT_ALLOWED:
        errno = EAGAIN;
        return;
    }
if ((dwStatus >= ERROR_WRITE_PROTECT) && (dwStatus <= ERROR_SHARING_BUFFER_EXCEEDED)) {
    errno = EACCES;
    return;
    }
if ((dwStatus >= ERROR_INVALID_STARTING_CODESEG) && (dwStatus <= ERROR_INFLOOP_IN_RELOC_CHAIN)) {
    errno = ENOEXEC;
    return;
    }
errno = EINVAL;
}
#if defined(__GNUC__)
#include <ddk/ntddstor.h>
#include <ddk/ntdddisk.h>
#else
#include <winioctl.h>
#endif
struct _device_type {
    int32 Type;
    char *desc;
    } DeviceTypes[] = {
        {FILE_DEVICE_8042_PORT,             "8042_PORT"},
        {FILE_DEVICE_ACPI,                  "ACPI"},
        {FILE_DEVICE_BATTERY,               "BATTERY"},
        {FILE_DEVICE_BEEP,                  "BEEP"},
#ifdef FILE_DEVICE_BLUETOOTH
        {FILE_DEVICE_BLUETOOTH,             "BLUETOOTH"},
#endif
        {FILE_DEVICE_BUS_EXTENDER,          "BUS_EXTENDER"},
        {FILE_DEVICE_CD_ROM,                "CD_ROM"},
        {FILE_DEVICE_CD_ROM_FILE_SYSTEM,    "CD_ROM_FILE_SYSTEM"},
        {FILE_DEVICE_CHANGER,               "CHANGER"},
        {FILE_DEVICE_CONTROLLER,            "CONTROLLER"},
#ifdef FILE_DEVICE_CRYPT_PROVIDER
        {FILE_DEVICE_CRYPT_PROVIDER,        "CRYPT_PROVIDER"},
#endif
        {FILE_DEVICE_DATALINK,              "DATALINK"},
        {FILE_DEVICE_DFS,                   "DFS"},
        {FILE_DEVICE_DFS_FILE_SYSTEM,       "DFS_FILE_SYSTEM"},
        {FILE_DEVICE_DFS_VOLUME,            "DFS_VOLUME"},
        {FILE_DEVICE_DISK,                  "DISK"},
        {FILE_DEVICE_DISK_FILE_SYSTEM,      "DISK_FILE_SYSTEM"},
        {FILE_DEVICE_DVD,                   "DVD"},
        {FILE_DEVICE_FILE_SYSTEM,           "FILE_SYSTEM"},
#ifdef FILE_DEVICE_FIPS
        {FILE_DEVICE_FIPS,                  "FIPS"},
#endif
        {FILE_DEVICE_FULLSCREEN_VIDEO,      "FULLSCREEN_VIDEO"},
#ifdef FILE_DEVICE_INFINIBAND
        {FILE_DEVICE_INFINIBAND,            "INFINIBAND"},
#endif
        {FILE_DEVICE_INPORT_PORT,           "INPORT_PORT"},
        {FILE_DEVICE_KEYBOARD,              "KEYBOARD"},
        {FILE_DEVICE_KS,                    "KS"},
        {FILE_DEVICE_KSEC,                  "KSEC"},
        {FILE_DEVICE_MAILSLOT,              "MAILSLOT"},
        {FILE_DEVICE_MASS_STORAGE,          "MASS_STORAGE"},
        {FILE_DEVICE_MIDI_IN,               "MIDI_IN"},
        {FILE_DEVICE_MIDI_OUT,              "MIDI_OUT"},
        {FILE_DEVICE_MODEM,                 "MODEM"},
        {FILE_DEVICE_MOUSE,                 "MOUSE"},
        {FILE_DEVICE_MULTI_UNC_PROVIDER,    "MULTI_UNC_PROVIDER"},
        {FILE_DEVICE_NAMED_PIPE,            "NAMED_PIPE"},
        {FILE_DEVICE_NETWORK,               "NETWORK"},
        {FILE_DEVICE_NETWORK_BROWSER,       "NETWORK_BROWSER"},
        {FILE_DEVICE_NETWORK_FILE_SYSTEM,   "NETWORK_FILE_SYSTEM"},
        {FILE_DEVICE_NETWORK_REDIRECTOR,    "NETWORK_REDIRECTOR"},
        {FILE_DEVICE_NULL,                  "NULL"},
        {FILE_DEVICE_PARALLEL_PORT,         "PARALLEL_PORT"},
        {FILE_DEVICE_PHYSICAL_NETCARD,      "PHYSICAL_NETCARD"},
        {FILE_DEVICE_PRINTER,               "PRINTER"},
        {FILE_DEVICE_SCANNER,               "SCANNER"},
        {FILE_DEVICE_SCREEN,                "SCREEN"},
        {FILE_DEVICE_SERENUM,               "SERENUM"},
        {FILE_DEVICE_SERIAL_MOUSE_PORT,     "SERIAL_MOUSE_PORT"},
        {FILE_DEVICE_SERIAL_PORT,           "SERIAL_PORT"},
        {FILE_DEVICE_SMARTCARD,             "SMARTCARD"},
        {FILE_DEVICE_SMB,                   "SMB"},
        {FILE_DEVICE_SOUND,                 "SOUND"},
        {FILE_DEVICE_STREAMS,               "STREAMS"},
        {FILE_DEVICE_TAPE,                  "TAPE"},
        {FILE_DEVICE_TAPE_FILE_SYSTEM,      "TAPE_FILE_SYSTEM"},
        {FILE_DEVICE_TERMSRV,               "TERMSRV"},
        {FILE_DEVICE_TRANSPORT,             "TRANSPORT"},
        {FILE_DEVICE_UNKNOWN,               "UNKNOWN"},
        {FILE_DEVICE_VDM,                   "VDM"},
        {FILE_DEVICE_VIDEO,                 "VIDEO"},
        {FILE_DEVICE_VIRTUAL_DISK,          "VIRTUAL_DISK"},
#ifdef FILE_DEVICE_VMBUS
        {FILE_DEVICE_VMBUS,                 "VMBUS"},
#endif
        {FILE_DEVICE_WAVE_IN,               "WAVE_IN"},
        {FILE_DEVICE_WAVE_OUT,              "WAVE_OUT"},
#ifdef FILE_DEVICE_WPD
        {FILE_DEVICE_WPD,                   "WPD"},
#endif
        {0,                                 NULL}};

static const char *_device_type_name (int DeviceType)
{
int i;

for (i=0; DeviceTypes[i].desc; i++)
    if (DeviceTypes[i].Type == DeviceType)
        return DeviceTypes[i].desc;
return "Unknown";
}

static t_stat sim_os_disk_implemented_raw (void)
{
return SCPE_OK;
}

static FILE *sim_os_disk_open_raw (const char *rawdevicename, const char *openmode)
{
HANDLE Handle;
DWORD DesiredAccess = 0;

if (strchr (openmode, 'r'))
    DesiredAccess |= GENERIC_READ;
if (strchr (openmode, 'w') || strchr (openmode, '+'))
    DesiredAccess |= GENERIC_WRITE;
Handle = CreateFileA (rawdevicename, DesiredAccess, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS|FILE_FLAG_WRITE_THROUGH, NULL);
if (Handle == INVALID_HANDLE_VALUE) {
    _set_errno_from_status (GetLastError ());
    return NULL;
    }
return (FILE *)Handle;
}

static int sim_os_disk_close_raw (FILE *f)
{
if (!CloseHandle ((HANDLE)f)) {
    _set_errno_from_status (GetLastError ());
    return EOF;
    }
return 0;
}

static void sim_os_disk_flush_raw (FILE *f)
{
FlushFileBuffers ((HANDLE)f);
}

static t_addr sim_os_disk_size_raw (FILE *Disk)
{
DWORD IoctlReturnSize;
LARGE_INTEGER Size;
WINBASEAPI BOOL WINAPI GetFileSizeEx(HANDLE hFile, PLARGE_INTEGER lpFileSize);

if (GetFileSizeEx((HANDLE)Disk, &Size))
    return (t_addr)(Size.QuadPart);
#ifdef IOCTL_STORAGE_READ_CAPACITY
if (1) {
    STORAGE_READ_CAPACITY S;

    ZeroMemory (&S, sizeof (S));
    S.Version = sizeof (STORAGE_READ_CAPACITY);
    if (DeviceIoControl((HANDLE)Disk,                      /* handle to volume */
                         IOCTL_STORAGE_READ_CAPACITY,      /* dwIoControlCode */
                         NULL,                             /* lpInBuffer */
                         0,                                /* nInBufferSize */
                         (LPVOID) &S,                      /* output buffer */
                         (DWORD) sizeof(S),                /* size of output buffer */
                         (LPDWORD) &IoctlReturnSize,       /* number of bytes returned */
                         (LPOVERLAPPED) NULL))             /* OVERLAPPED structure */
        return (t_addr)(S.DiskLength.QuadPart);
    }
#endif
#ifdef IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
if (1) {
    DISK_GEOMETRY_EX G;

    ZeroMemory (&G, sizeof (G));
    if (DeviceIoControl((HANDLE)Disk,                      /* handle to volume */
                         IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, /* dwIoControlCode */
                         NULL,                             /* lpInBuffer */
                         0,                                /* nInBufferSize */
                         (LPVOID) &G,                      /* output buffer */
                         (DWORD) sizeof(G),                /* size of output buffer */
                         (LPDWORD) &IoctlReturnSize,       /* number of bytes returned */
                         (LPOVERLAPPED) NULL))             /* OVERLAPPED structure */
        return (t_addr)(G.DiskSize.QuadPart);
    }
#endif
#ifdef IOCTL_DISK_GET_DRIVE_GEOMETRY
if (1) {
    DISK_GEOMETRY G;

    if (DeviceIoControl((HANDLE)Disk,                      /* handle to volume */
                         IOCTL_DISK_GET_DRIVE_GEOMETRY,    /* dwIoControlCode */
                         NULL,                             /* lpInBuffer */
                         0,                                /* nInBufferSize */
                         (LPVOID) &G,                      /* output buffer */
                         (DWORD) sizeof(G),                /* size of output buffer */
                         (LPDWORD) &IoctlReturnSize,       /* number of bytes returned */
                         (LPOVERLAPPED) NULL))             /* OVERLAPPED structure */
        return (t_addr)(G.Cylinders.QuadPart*G.TracksPerCylinder*G.SectorsPerTrack*G.BytesPerSector);
    }
#endif
_set_errno_from_status (GetLastError ());
return (t_addr)-1;
}

static t_stat sim_os_disk_unload_raw (FILE *Disk)
{
#ifdef IOCTL_STORAGE_EJECT_MEDIA
DWORD BytesReturned;
uint32 Removable = FALSE;

sim_os_disk_info_raw (Disk, NULL, &Removable);
if (Removable) {
    if (!DeviceIoControl((HANDLE)Disk,                  /* handle to disk */
                         IOCTL_STORAGE_EJECT_MEDIA,     /* dwIoControlCode */
                         NULL,                          /* lpInBuffer */
                         0,                             /* nInBufferSize */
                         NULL,                          /* lpOutBuffer */
                         0,                             /* nOutBufferSize */
                         (LPDWORD) &BytesReturned,      /* number of bytes returned */
                         (LPOVERLAPPED) NULL)) {        /* OVERLAPPED structure */
        _set_errno_from_status (GetLastError ());
        return SCPE_IOERR;
        }
    }
return SCPE_OK;
#else
return SCPE_NOFNC;
#endif
}

static t_bool sim_os_disk_isavailable_raw (FILE *Disk)
{
#ifdef IOCTL_STORAGE_EJECT_MEDIA
DWORD BytesReturned;
uint32 Removable = FALSE;

sim_os_disk_info_raw (Disk, NULL, &Removable);
if (Removable) {
    if (!DeviceIoControl((HANDLE)Disk,                  /* handle to disk */
                         IOCTL_STORAGE_CHECK_VERIFY,    /* dwIoControlCode */
                         NULL,                          /* lpInBuffer */
                         0,                             /* nInBufferSize */
                         NULL,                          /* lpOutBuffer */
                         0,                             /* nOutBufferSize */
                         (LPDWORD) &BytesReturned,      /* number of bytes returned */
                         (LPOVERLAPPED) NULL)) {        /* OVERLAPPED structure */
        _set_errno_from_status (GetLastError ());
        return FALSE;
        }
    }
#endif
return TRUE;
}

static t_stat sim_os_disk_info_raw (FILE *Disk, uint32 *sector_size, uint32 *removable)
{
DWORD IoctlReturnSize;
STORAGE_DEVICE_NUMBER Device;

ZeroMemory (&Device, sizeof (Device));
if (DeviceIoControl((HANDLE)Disk,                      /* handle to volume */
                     IOCTL_STORAGE_GET_DEVICE_NUMBER,  /* dwIoControlCode */
                     NULL,                             /* lpInBuffer */
                     0,                                /* nInBufferSize */
                     (LPVOID) &Device,                 /* output buffer */
                     (DWORD) sizeof(Device),           /* size of output buffer */
                     (LPDWORD) &IoctlReturnSize,       /* number of bytes returned */
                     (LPOVERLAPPED) NULL))             /* OVERLAPPED structure */
     printf ("Device OK - Type: %s, Number: %d\n", _device_type_name (Device.DeviceType), (int)Device.DeviceNumber);

if (sector_size)
    *sector_size = 512;
if (removable)
    *removable = 0;
#ifdef IOCTL_STORAGE_READ_CAPACITY
if (1) {
    STORAGE_READ_CAPACITY S;

    ZeroMemory (&S, sizeof (S));
    S.Version = sizeof (STORAGE_READ_CAPACITY);
    if (DeviceIoControl((HANDLE)Disk,                      /* handle to volume */
                         IOCTL_STORAGE_READ_CAPACITY,      /* dwIoControlCode */
                         NULL,                             /* lpInBuffer */
                         0,                                /* nInBufferSize */
                         (LPVOID) &S,                      /* output buffer */
                         (DWORD) sizeof(S),                /* size of output buffer */
                         (LPDWORD) &IoctlReturnSize,       /* number of bytes returned */
                         (LPOVERLAPPED) NULL))             /* OVERLAPPED structure */
        if (sector_size)
            *sector_size = S.BlockLength;
    }
#endif
#ifdef IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
if (1) {
    DISK_GEOMETRY_EX G;

    ZeroMemory (&G, sizeof (G));
    if (DeviceIoControl((HANDLE)Disk,                      /* handle to volume */
                         IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, /* dwIoControlCode */
                         NULL,                             /* lpInBuffer */
                         0,                                /* nInBufferSize */
                         (LPVOID) &G,                      /* output buffer */
                         (DWORD) sizeof(G),                /* size of output buffer */
                         (LPDWORD) &IoctlReturnSize,       /* number of bytes returned */
                         (LPOVERLAPPED) NULL))             /* OVERLAPPED structure */
        if (sector_size)
            *sector_size = G.Geometry.BytesPerSector;
    }
#endif
#ifdef IOCTL_DISK_GET_DRIVE_GEOMETRY
if (1) {
    DISK_GEOMETRY G;

    if (DeviceIoControl((HANDLE)Disk,                      /* handle to volume */
                         IOCTL_DISK_GET_DRIVE_GEOMETRY,    /* dwIoControlCode */
                         NULL,                             /* lpInBuffer */
                         0,                                /* nInBufferSize */
                         (LPVOID) &G,                      /* output buffer */
                         (DWORD) sizeof(G),                /* size of output buffer */
                         (LPDWORD) &IoctlReturnSize,       /* number of bytes returned */
                         (LPOVERLAPPED) NULL))             /* OVERLAPPED structure */
        if (sector_size)
            *sector_size = G.BytesPerSector;
    }
#endif
#ifdef IOCTL_STORAGE_GET_HOTPLUG_INFO
if (1) {
    STORAGE_HOTPLUG_INFO H;

    ZeroMemory (&H, sizeof (H));
    if (DeviceIoControl((HANDLE)Disk,                      /* handle to volume */
                         IOCTL_STORAGE_GET_HOTPLUG_INFO,   /* dwIoControlCode */
                         NULL,                             /* lpInBuffer */
                         0,                                /* nInBufferSize */
                         (LPVOID) &H,                      /* output buffer */
                         (DWORD) sizeof(H),                /* size of output buffer */
                         (LPDWORD) &IoctlReturnSize,       /* number of bytes returned */
                         (LPOVERLAPPED) NULL))             /* OVERLAPPED structure */
        if (removable)
            *removable = H.MediaRemovable;
    }
#endif
if (removable && *removable)
    printf ("Removable Device\n");
return SCPE_OK;
}

static t_stat sim_os_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
OVERLAPPED pos;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
long long addr;

sim_debug (ctx->dbit, ctx->dptr, "sim_os_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);

addr = ((long long)lba) * ctx->sector_size;
memset (&pos, 0, sizeof (pos));
pos.Offset = (DWORD)addr;
pos.OffsetHigh = (DWORD)(addr >> 32);
if (ReadFile ((HANDLE)(uptr->fileref), buf, sects * ctx->sector_size, (LPDWORD)sectsread, &pos)) {
    if (sectsread)
        *sectsread /= ctx->sector_size;
    return SCPE_OK;
    }
_set_errno_from_status (GetLastError ());
return SCPE_IOERR;
}

static t_stat sim_os_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
OVERLAPPED pos;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
long long addr;

sim_debug (ctx->dbit, ctx->dptr, "sim_os_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);

addr = ((long long)lba) * ctx->sector_size;
memset (&pos, 0, sizeof (pos));
pos.Offset = (DWORD)addr;
pos.OffsetHigh = (DWORD)(addr >> 32);
if (WriteFile ((HANDLE)(uptr->fileref), buf, sects * ctx->sector_size, (LPDWORD)sectswritten, &pos)) {
    if (sectswritten)
        *sectswritten /= ctx->sector_size;
    return SCPE_OK;
    }
_set_errno_from_status (GetLastError ());
return SCPE_IOERR;
}

#elif defined (__linux) || defined (__sun__)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

static t_stat sim_os_disk_implemented_raw (void)
{
return SCPE_OK;
}

static FILE *sim_os_disk_open_raw (const char *rawdevicename, const char *openmode)
{
int mode = 0;

if (strchr (openmode, 'r') && (strchr (openmode, '+') || strchr (openmode, 'w')))
    mode = O_RDWR;
else
    if (strchr (openmode, 'r'))
        mode = O_RDONLY;
#ifdef O_LARGEFILE
mode |= O_LARGEFILE;
#endif
#ifdef O_DSYNC
mode |= O_DSYNC;
#endif
return (FILE *)((long)open (rawdevicename, mode, 0));
}

static int sim_os_disk_close_raw (FILE *f)
{
return close ((int)((long)f));
}

static void sim_os_disk_flush_raw (FILE *f)
{
fsync ((int)((long)f));
}

static t_addr sim_os_disk_size_raw (FILE *f)
{
struct stat64 statb;

if (fstat64 ((int)((long)f), &statb))
    return (t_addr)-1;
return (t_addr)statb.st_size;
}

static t_stat sim_os_disk_unload_raw (FILE *f)
{
return SCPE_IOERR;
}

static t_bool sim_os_disk_isavailable_raw (FILE *Disk)
{
return TRUE;
}

static t_stat sim_os_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
off_t addr;
ssize_t bytesread;

sim_debug (ctx->dbit, ctx->dptr, "sim_os_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);

addr = ((off_t)lba) * ctx->sector_size;
bytesread = pread((int)((long)uptr->fileref), buf, sects * ctx->sector_size, addr); 
if (bytesread < 0) {
    if (sectsread)
        *sectsread = 0;
    return SCPE_IOERR;
    }
if (sectsread)
    *sectsread = bytesread / ctx->sector_size;
return SCPE_OK;
}

static t_stat sim_os_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
off_t addr;
ssize_t byteswritten;

sim_debug (ctx->dbit, ctx->dptr, "sim_os_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);

addr = ((off_t)lba) * ctx->sector_size;
byteswritten = pwrite((int)((long)uptr->fileref), buf, sects * ctx->sector_size, addr); 
if (byteswritten < 0) {
    if (sectswritten)
        *sectswritten = 0;
    return SCPE_IOERR;
    }
if (sectswritten)
    *sectswritten = byteswritten / ctx->sector_size;
return SCPE_OK;
}

static t_stat sim_os_disk_info_raw (FILE *f, uint32 *sector_size, uint32 *removable)
{
if (sector_size)
    *sector_size = 512;
if (removable)
    *removable = 0;
return SCPE_OK;
}

#else
/*============================================================================*/
/*                        Non-implemented versions                            */
/*============================================================================*/

static t_stat sim_os_disk_implemented_raw (void)
{
return SCPE_NOFNC;
}

static FILE *sim_os_disk_open_raw (const char *rawdevicename, const char *openmode)
{
return NULL;
}

static int sim_os_disk_close_raw (FILE *f)
{
return EOF;
}

static void sim_os_disk_flush_raw (FILE *f)
{
}

static t_addr sim_os_disk_size_raw (FILE *f)
{
return (t_addr)-1;
}

static t_stat sim_os_disk_unload_raw (FILE *f)
{
return SCPE_NOFNC;
}

static t_bool sim_os_disk_isavailable_raw (FILE *Disk)
{
return FALSE;
}

static t_stat sim_os_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
return SCPE_NOFNC;
}

static t_stat sim_os_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
return SCPE_NOFNC;
}

static t_stat sim_os_disk_info_raw (FILE *f, uint32 *sector_size, uint32 *removable)
{
return SCPE_NOFNC;
}

#endif

/* OS Independent Disk Virtual Disk (VHD) I/O support */

#if (defined (VMS) && !(defined (__ALPHA) || defined (__ia64)))
#define DONT_DO_VHD_SUPPORT  /* VAX/VMS compilers don't have 64 bit integers */
#endif

#if defined (DONT_DO_VHD_SUPPORT)

/*============================================================================*/
/*                        Non-implemented version                             */
/*   This is only for hody systems which don't have 64 bit integer types      */
/*============================================================================*/

static t_stat sim_vhd_disk_implemented (void)
{
return SCPE_NOFNC;
}

static FILE *sim_vhd_disk_open (const char *vhdfilename, const char *openmode)
{
return NULL;
}

static FILE *sim_vhd_disk_merge (const char *szVHDPath, char **ParentVHD)
{
return NULL;
}

static FILE *sim_vhd_disk_create (const char *szVHDPath, t_addr desiredsize)
{
return NULL;
}

static FILE *sim_vhd_disk_create_diff (const char *szVHDPath, const char *szParentVHDPath)
{
return NULL;
}

static int sim_vhd_disk_close (FILE *f)
{
return -1;
}

static void sim_vhd_disk_flush (FILE *f)
{
}

static t_addr sim_vhd_disk_size (FILE *f)
{
return (t_addr)-1;
}

static t_stat sim_vhd_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
return SCPE_IOERR;
}

static t_stat sim_vhd_disk_clearerr (UNIT *uptr)
{
return SCPE_IOERR;
}

static t_stat sim_vhd_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
return SCPE_IOERR;
}

static t_stat sim_vhd_disk_set_dtype (FILE *f, const char *dtype)
{
return SCPE_NOFNC;
}

static const char *sim_vhd_disk_get_dtype (FILE *f)
{
return NULL;
}

#else

/*++
    This code follows the details specified in the "Virtual Hard Disk Image 
    Format Specification", Version 1.0 October 11, 2006.  This format 
    specification is available for anyone to implement under the 
    "Microsoft Open Specification Promise" described at:
        http://www.microsoft.com/interop/osp/default.mspx.
--*/

typedef t_uint64    uint64;
typedef t_int64     int64;

typedef struct _VHD_Footer {
    /*
    Cookies are used to uniquely identify the original creator of the hard disk 
    image. The values are case-sensitive.  Microsoft uses the “conectix” string 
    to identify this file as a hard disk image created by Microsoft Virtual 
    Server, Virtual PC, and predecessor products. The cookie is stored as an 
    eight-character ASCII string with the “c” in the first byte, the “o” in 
    the second byte, and so on.
    */
    char Cookie[8];
    /*
    This is a bit field used to indicate specific feature support. The following 
    table displays the list of features. 
    Any fields not listed are reserved. 

    Feature Value:
       No features enabled     0x00000000
       Temporary               0x00000001
       Reserved                0x00000002

       No features enabled. 
              The hard disk image has no special features enabled in it.
       Temporary.
              This bit is set if the current disk is a temporary disk. A 
              temporary disk designation indicates to an application that 
              this disk is a candidate for deletion on shutdown. 
       Reserved.
              This bit must always be set to 1.
       All other bits are also reserved and should be set to 0.
    */
    uint32 Features;
    /*
    This field is divided into a major/minor version and matches the version of 
    the specification used in creating the file. The most-significant two bytes 
    are for the major version. The least-significant two bytes are the minor 
    version.  This must match the file format specification. For the current 
    specification, this field must be initialized to 0x00010000.
    The major version will be incremented only when the file format is modified 
    in such a way that it is no longer compatible with older versions of the 
    file format.
    */
    uint32 FileFormatVersion;
    /*
    This field holds the absolute byte offset, from the beginning of the file, 
    to the next structure. This field is used for dynamic disks and differencing 
    disks, but not fixed disks. For fixed disks, this field should be set to 
    0xFFFFFFFF. 
    */
    uint64 DataOffset;
    /*
    This field stores the creation time of a hard disk image. This is the number 
    of seconds since January 1, 2000 12:00:00 AM in UTC/GMT.
    */
    uint32 TimeStamp;
    /*
    This field is used to document which application created the hard disk. The 
    field is a left-justified text field. It uses a single-byte character set. 
    If the hard disk is created by Microsoft Virtual PC, "vpc " is written in 
    this field. If the hard disk image is created by Microsoft Virtual Server, 
    then "vs  " is written in this field.
    Other applications should use their own unique identifiers. 
    */
    char CreatorApplication[4];
    /*
    This field holds the major/minor version of the application that created 
    the hard disk image.  Virtual Server 2004 sets this value to 0x00010000 and
    Virtual PC 2004 sets this to 0x00050000.
    */
    uint32 CreatorVersion;
    /*
    This field stores the type of host operating system this disk image is 
    created on.
       Host OS type    Value
       Windows         0x5769326B (Wi2k)
       Macintosh       0x4D616320 (Mac )
    */
    uint8 CreatorHostOS[4];
    /*
    This field stores the size of the hard disk in bytes, from the perspective 
    of the virtual machine, at creation time. This field is for informational 
    purposes. 
    */
    uint64 OriginalSize;
    /*
    This field stores the current size of the hard disk, in bytes, from the 
    perspective of the virtual machine.
    This value is same as the original size when the hard disk is created. 
    This value can change depending on whether the hard disk is expanded. 
    */
    uint64 CurrentSize;
    /*
    This field stores the cylinder, heads, and sectors per track value for the 
    hard disk. 
       Disk Geometry field          Size (bytes)
       Cylinder                     2
       Heads                        1
       Sectors per track/cylinder   1

    When a hard disk is configured as an ATA hard disk, the CHS values (that is, 
    Cylinder, Heads, Sectors per track) are used by the ATA controller to 
    determine the size of the disk. When the user creates a hard disk of a 
    certain size, the size of the hard disk image in the virtual machine is 
    smaller than that created by the user. This is because CHS value calculated 
    from the hard disk size is rounded down. The pseudo-code for the algorithm 
    used to determine the CHS values can be found in the appendix of this 
    document. 
    */
    uint32 DiskGeometry;
    /*
       Disk Type field              Value
       None                         0
       Reserved (deprecated)        1
       Fixed hard disk              2
       Dynamic hard disk            3
       Differencing hard disk       4
       Reserved (deprecated)        5
       Reserved (deprecated)        6
    */
    uint32 DiskType;
    /*
    This field holds a basic checksum of the hard disk footer. It is just a 
    one’s complement of the sum of all the bytes in the footer without the 
    checksum field.
    If the checksum verification fails, the Virtual PC and Virtual Server 
    products will instead use the header. If the checksum in the header also 
    fails, the file should be assumed to be corrupt. The pseudo-code for the 
    algorithm used to determine the checksum can be found in the appendix of 
    this document. 
    */
    uint32 Checksum;
    /*
    Every hard disk has a unique ID stored in the hard disk. This is used to 
    identify the hard disk. This is a 128-bit universally unique identifier 
    (UUID). This field is used to associate a parent hard disk image with its 
    differencing hard disk image(s).
    */
    uint8 UniqueID[16];
    /*
    This field holds a one-byte flag that describes whether the system is in 
    saved state. If the hard disk is in the saved state the value is set to 1.
    Operations such as compaction and expansion cannot be performed on a hard 
    disk in a saved state.
    */
    uint8 SavedState;
    /*
    This field contains zeroes. It is 427 bytes in size. 
    */
    uint8 Reserved1[11];
    /*
    This field is an extension to the VHD spec and includes a simh drive type 
    name as a nul terminated string.
    */
    uint8 DriveType[16];
    /*
    This field contains zeroes. It is 400 bytes in size. 
    */
    uint8 Reserved[400];
    } VHD_Footer;

/*
For dynamic and differencing disk images, the “Data Offset” field within 
the image footer points to a secondary structure that provides additional 
information about the disk image. The dynamic disk header should appear on 
a sector (512-byte) boundary.
*/
typedef struct _VHD_DynamicDiskHeader {
    /*
    This field holds the value "cxsparse". This field identifies the header.
    */
    char Cookie[8];
    /*
    This field contains the absolute byte offset to the next structure in the 
    hard disk image. It is currently unused by existing formats and should be 
    set to 0xFFFFFFFF.
    */
    uint64 DataOffset;
    /*
    This field stores the absolute byte offset of the Block Allocation Table 
    (BAT) in the file. 
    */
    uint64 TableOffset;
    /*
    This field stores the version of the dynamic disk header. The field is 
    divided into Major/Minor version. The least-significant two bytes represent
    the minor version, and the most-significant two bytes represent the major 
    version. This must match with the file format specification. For this 
    specification, this field must be initialized to 0x00010000. 
    The major version will be incremented only when the header format is 
    modified in such a way that it is no longer compatible with older versions 
    of the product.
    */
    uint32 HeaderVersion;
    /*
    This field holds the maximum entries present in the BAT. This should be 
    equal to the number of blocks in the disk (that is, the disk size divided 
    by the block size). 
    */
    uint32 MaxTableEntries;
    /*
    A block is a unit of expansion for dynamic and differencing hard disks. It 
    is stored in bytes. This size does not include the size of the block bitmap. 
    It is only the size of the data section of the block. The sectors per block 
    must always be a power of two. The default value is 0x00200000 (indicating a 
    block size of 2 MB).
    */
    uint32 BlockSize;
    /*
    This field holds a basic checksum of the dynamic header. It is a one’s 
    complement of the sum of all the bytes in the header without the checksum 
    field.
    If the checksum verification fails the file should be assumed to be corrupt.
    */
    uint32 Checksum;
    /*
    This field is used for differencing hard disks. A differencing hard disk 
    stores a 128-bit UUID of the parent hard disk. For more information, see 
    “Creating Differencing Hard Disk Images” later in this paper.
    */
    uint8 ParentUniqueID[16];
    /*
    This field stores the modification time stamp of the parent hard disk. This 
    is the number of seconds since January 1, 2000 12:00:00 AM in UTC/GMT.
    */
    uint32 ParentTimeStamp;
    /*
    This field should be set to zero. 
    */
    uint32 Reserved0;
    /*
    This field contains a Unicode string (UTF-16) of the parent hard disk 
    filename. 
    */
    char ParentUnicodeName[512];
    /*
    These entries store an absolute byte offset in the file where the parent 
    locator for a differencing hard disk is stored. This field is used only for 
    differencing disks and should be set to zero for dynamic disks. 
    */
    struct VHD_ParentLocator {
        /*
        The platform code describes which platform-specific format is used for the 
        file locator. For Windows, a file locator is stored as a path (for example. 
        “c:\disksimages\ParentDisk.vhd”). On a Macintosh system, the file locator 
        is a binary large object (blob) that contains an “alias.” The parent locator 
        table is used to support moving hard disk images across platforms.
        Some current platform codes include the following:
           Platform Code        Description
           None (0x0)
           Wi2r (0x57693272)    [deprecated]
           Wi2k (0x5769326B)    [deprecated]
           W2ru (0x57327275)    Unicode pathname (UTF-16) on Windows relative to the differencing disk pathname.
           W2ku (0x57326B75)    Absolute Unicode (UTF-16) pathname on Windows.
           Mac (0x4D616320)     (Mac OS alias stored as a blob)
           MacX(0x4D616358)     A file URL with UTF-8 encoding conforming to RFC 2396.
        */
        uint8 PlatformCode[4];
        /*
        This field stores the number of 512-byte sectors needed to store the parent 
        hard disk locator.
        */
        uint32 PlatformDataSpace;
        /*
        This field stores the actual length of the parent hard disk locator in bytes.
        */
        uint32 PlatformDataLength;
        /*
        This field must be set to zero.
        */
        uint32 Reserved;
        /*
        This field stores the absolute file offset in bytes where the platform 
        specific file locator data is stored.
        */
        uint64 PlatformDataOffset;
        /*
        This field stores the absolute file offset in bytes where the platform 
        specific file locator data is stored.
        */
        } ParentLocatorEntries[8];
    /*
    This must be initialized to zeroes.
    */
    char Reserved[256];
    } VHD_DynamicDiskHeader;

#define VHD_BAT_FREE_ENTRY (0xFFFFFFFF)
#define VHD_DATA_BLOCK_ALIGNMENT ((uint64)4096)    /* Optimum when underlying storage has 4k sectors */

#define VHD_DT_Fixed                 2  /* Fixed hard disk */
#define VHD_DT_Dynamic               3  /* Dynamic hard disk */
#define VHD_DT_Differencing          4  /* Differencing hard disk */

static uint32 NtoHl(uint32 value);

static uint64 NtoHll(uint64 value);

typedef struct VHD_IOData *VHDHANDLE;

static t_stat ReadFilePosition(FILE *File, void *buf, size_t bufsize, size_t *bytesread, uint64 position)
{
uint32 err = sim_fseek (File, (t_addr)position, SEEK_SET);
size_t i;

if (bytesread)
    *bytesread = 0;
if (!err) {
    i = fread (buf, 1, bufsize, File);
    err = ferror (File);
    if ((!err) && bytesread)
        *bytesread = i;
    }
return (err ? SCPE_IOERR : SCPE_OK);
}

static t_stat WriteFilePosition(FILE *File, void *buf, size_t bufsize, size_t *byteswritten, uint64 position)
{
uint32 err = sim_fseek (File, (t_addr)position, SEEK_SET);
size_t i;

if (byteswritten)
    *byteswritten = 0;
if (!err) {
    i = fwrite (buf, 1, bufsize, File);
    err = ferror (File);
    if ((!err) && byteswritten)
        *byteswritten = i;
    }
return (err ? SCPE_IOERR : SCPE_OK);
}

static uint32
CalculateVhdFooterChecksum(void *data, 
                           size_t size)
{
uint32 sum = 0;
uint8 *c = (uint8 *)data;

while (size--)
    sum += *c++;
return ~sum;
}

#if defined(_WIN32) || defined (__ALPHA) || defined (__ia64) || defined (VMS)
#ifndef __BYTE_ORDER__
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__
#endif
#endif
#ifndef __BYTE_ORDER__
#define __BYTE_ORDER__ UNKNOWN
#endif
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
static uint32
NtoHl(uint32 value)
{
uint8 *l = (uint8 *)&value;
return l[3] | (l[2]<<8) | (l[1]<<16) | (l[0]<<24);
}

static uint64
NtoHll(uint64 value)
{
uint8 *l = (uint8 *)&value;
uint64 highresult = l[3] | (l[2]<<8) | (l[1]<<16) | (l[0]<<24);
uint32 lowresult = l[7] | (l[6]<<8) | (l[5]<<16) | (l[4]<<24);
return (highresult << 32) | lowresult;
}
#elif  __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
static uint32
NtoHl(uint32 value)
{
return value;
}

static uint64
NtoHll(uint64 value)
{
return value;
}
#else
static uint32
NtoHl(uint32 value)
{
uint8 *l = (uint8 *)&value;

if (sim_end)
    return l[3] | (l[2]<<8) | (l[1]<<16) | (l[0]<<24);
return value;
}

static uint64
NtoHll(uint64 value)
{
uint8 *l = (uint8 *)&value;

if (sim_end) {
    uint64 highresult = l[3] | (l[2]<<8) | (l[1]<<16) | (l[0]<<24);
    uint32 lowresult = l[7] | (l[6]<<8) | (l[5]<<16) | (l[4]<<24);
    return (highresult << 32) | lowresult;
    }
return value;
}
#endif

static
int
GetVHDFooter(const char *szVHDPath, 
             VHD_Footer *sFooter, 
             VHD_DynamicDiskHeader *sDynamic,
             uint32 **aBAT,
             uint32 *ModifiedTimeStamp,
             char *szParentVHDPath,
             size_t ParentVHDPathSize)
{
FILE *File = NULL;
uint64 position;
uint32 sum, saved_sum;
int Return = 0;
VHD_Footer sHeader;
struct stat statb;

if (sFooter)
    memset(sFooter, '\0', sizeof(*sFooter));
if (sDynamic)
    memset(sDynamic, '\0', sizeof(*sDynamic));
if (aBAT)
    *aBAT = NULL;
File = sim_fopen (szVHDPath, "rb");
if (!File) {
    Return = errno;
    goto Return_Cleanup;
    }
if (ModifiedTimeStamp) {
    if (stat (szVHDPath, &statb)) {
        Return = errno;
        goto Return_Cleanup;
        }
    else
        *ModifiedTimeStamp = NtoHl ((uint32)(statb.st_mtime-946684800));
    }
position = sim_fsize_ex (File);
if (((int64)position) == -1) {
    Return = errno;
    goto Return_Cleanup;
    }
position -= sizeof(*sFooter);
if (ReadFilePosition(File, 
                     sFooter, 
                     sizeof(*sFooter), 
                     NULL, 
                     position)) {
    Return = errno;
    goto Return_Cleanup;
    }
saved_sum = NtoHl(sFooter->Checksum);
sFooter->Checksum = 0;
sum = CalculateVhdFooterChecksum(sFooter, sizeof(*sFooter));
sFooter->Checksum = NtoHl(saved_sum);
if ((sum != saved_sum) || (memcmp("conectix", sFooter->Cookie, sizeof(sFooter->Cookie)))) {
    Return = EINVAL;                                    /* File Corrupt */
    goto Return_Cleanup;
    }
if (ReadFilePosition(File, 
                     &sHeader, 
                     sizeof(sHeader), 
                     NULL, 
                     (uint64)0)) {
    Return = errno;
    goto Return_Cleanup;
    }
if ((NtoHl(sFooter->DiskType) != VHD_DT_Dynamic) &&
    (NtoHl(sFooter->DiskType) != VHD_DT_Differencing) &&
    (NtoHl(sFooter->DiskType) != VHD_DT_Fixed)) {
    Return = EINVAL;                                    /* File Corrupt */
    goto Return_Cleanup;
    }
if (((NtoHl(sFooter->DiskType) == VHD_DT_Dynamic) ||
     (NtoHl(sFooter->DiskType) == VHD_DT_Differencing)) &&
     memcmp(sFooter, &sHeader, sizeof(sHeader))) {
    Return = EINVAL;                                    /* File Corrupt */
    goto Return_Cleanup;
    }
if ((sDynamic) &&
    ((NtoHl(sFooter->DiskType) == VHD_DT_Dynamic) ||
     (NtoHl(sFooter->DiskType) == VHD_DT_Differencing))) {
    if (ReadFilePosition(File, 
                         sDynamic, 
                         sizeof (*sDynamic), 
                         NULL, 
                         NtoHll (sFooter->DataOffset))) {
        Return = errno;
        goto Return_Cleanup;
        }
    saved_sum = NtoHl (sDynamic->Checksum);
    sDynamic->Checksum = 0;
    sum = CalculateVhdFooterChecksum (sDynamic, sizeof(*sDynamic));
    sDynamic->Checksum = NtoHl (saved_sum);
    if ((sum != saved_sum) || (memcmp ("cxsparse", sDynamic->Cookie, sizeof (sDynamic->Cookie)))) {
        Return = errno;
        goto Return_Cleanup;
        }
    if (aBAT) {
        *aBAT = (uint32*) malloc(512*((sizeof(**aBAT)*NtoHl(sDynamic->MaxTableEntries)+511)/512));
        if (ReadFilePosition(File, 
                             *aBAT, 
                             sizeof (**aBAT)*NtoHl(sDynamic->MaxTableEntries), 
                             NULL, 
                             NtoHll (sDynamic->TableOffset))) {
            Return = EINVAL;                            /* File Corrupt */
            goto Return_Cleanup;
            }
        }
    if (szParentVHDPath && ParentVHDPathSize) {
        VHD_Footer sParentFooter;

        memset (szParentVHDPath, '\0', ParentVHDPathSize);
        if (NtoHl (sFooter->DiskType) == VHD_DT_Differencing) {
            size_t i, j;

            for (j=0; j<8; ++j) {
                uint8 *Pdata;
                uint32 PdataSize;
                char ParentName[512];
                char CheckPath[512];
                uint32 ParentModificationTime;

                if ('\0' == sDynamic->ParentLocatorEntries[j].PlatformCode[0])
                    continue;
                memset (ParentName, '\0', sizeof(ParentName));
                memset (CheckPath, '\0', sizeof(CheckPath));
                PdataSize = NtoHl(sDynamic->ParentLocatorEntries[j].PlatformDataSpace);
                Pdata = (uint8*) calloc (1, PdataSize+2);
                if (!Pdata)
                    continue;
                if (ReadFilePosition(File, 
                                     Pdata, 
                                     PdataSize, 
                                     NULL, 
                                     NtoHll (sDynamic->ParentLocatorEntries[j].PlatformDataOffset))) {
                    free (Pdata);
                    continue;
                    }
                for (i=0; i<NtoHl(sDynamic->ParentLocatorEntries[j].PlatformDataLength); i+=2)
                    if ((Pdata[i] == '\0') && (Pdata[i+1] == '\0')) {
                        ParentName[i/2] = '\0';
                        break;
                        }
                    else
                        ParentName[i/2] = Pdata[i] ? Pdata[i] : Pdata[i+1];
                free (Pdata);
                if (0 == memcmp (sDynamic->ParentLocatorEntries[j].PlatformCode, "W2ku", 4))
                    strncpy (CheckPath, ParentName, sizeof (CheckPath)-1);
                else
                    if (0 == memcmp (sDynamic->ParentLocatorEntries[j].PlatformCode, "W2ru", 4)) {
                        const char *c;

                        if ((c = strrchr (szVHDPath, '\\')))
                             memcpy (CheckPath, szVHDPath, c-szVHDPath+1);
                             strncpy (CheckPath+strlen(CheckPath), ParentName, sizeof (CheckPath)-(strlen (CheckPath)+1));
                        }
                VhdPathToHostPath (CheckPath, CheckPath, sizeof (CheckPath));
                if ((0 == GetVHDFooter(CheckPath, 
                                       &sParentFooter, 
                                       NULL,
                                       NULL,
                                       &ParentModificationTime,
                                       NULL, 
                                       0)) &&
                    (0 == memcmp (sDynamic->ParentUniqueID, sParentFooter.UniqueID, sizeof (sParentFooter.UniqueID))) &&
                    (sDynamic->ParentTimeStamp == ParentModificationTime)) {
                    strncpy (szParentVHDPath, CheckPath, ParentVHDPathSize);
                    break;
                    }
                }
            if (!szParentVHDPath)
                Return = EINVAL;                        /* File Corrupt */
            }
        }
    }
Return_Cleanup:
if (File)
    fclose(File);
if (aBAT && (0 != Return)) {
    free (*aBAT);
    *aBAT = NULL;
    }
return errno = Return;
}

struct VHD_IOData {
    VHD_Footer Footer;
    VHD_DynamicDiskHeader Dynamic;
    uint32 *BAT;
    FILE *File;
    char ParentVHDPath[512];
    struct VHD_IOData *Parent;
    };

static t_stat sim_vhd_disk_implemented (void)
{
return SCPE_OK;
}

static t_stat sim_vhd_disk_set_dtype (FILE *f, const char *dtype)
{
VHDHANDLE hVHD  = (VHDHANDLE)f;
int Status = 0;

memset (hVHD->Footer.DriveType, '\0', sizeof hVHD->Footer.DriveType);
memcpy (hVHD->Footer.DriveType, dtype, ((1+strlen (dtype)) < sizeof (hVHD->Footer.DriveType)) ? (1+strlen (dtype)) : sizeof (hVHD->Footer.DriveType));
hVHD->Footer.Checksum = 0;
hVHD->Footer.Checksum = NtoHl (CalculateVhdFooterChecksum (&hVHD->Footer, sizeof(hVHD->Footer)));

if (NtoHl (hVHD->Footer.DiskType) == VHD_DT_Fixed) {
    if (WriteFilePosition(hVHD->File,
                          &hVHD->Footer,
                          sizeof(hVHD->Footer),
                          NULL,
                          NtoHll (hVHD->Footer.CurrentSize)))
        Status = errno;
    goto Cleanup_Return;
    }
else {
    uint64 position;

    position = sim_fsize_ex (hVHD->File);
    if (((int64)position) == -1) {
        Status = errno;
        goto Cleanup_Return;
        }
    position -= sizeof(hVHD->Footer);
    /* Update both copies on a dynamic disk */
    if (WriteFilePosition(hVHD->File,
                          &hVHD->Footer,
                          sizeof(hVHD->Footer),
                          NULL,
                          (uint64)0)) {
        Status = errno;
        goto Cleanup_Return;
        }
    if (WriteFilePosition(hVHD->File,
                          &hVHD->Footer,
                          sizeof(hVHD->Footer),
                          NULL,
                          position)) {
        Status = errno;
        goto Cleanup_Return;
        }
    }
Cleanup_Return:
if (Status)
    return SCPE_IOERR;
return SCPE_OK;
}

static const char *sim_vhd_disk_get_dtype (FILE *f)
{
VHDHANDLE hVHD  = (VHDHANDLE)f;

return (char *)(&hVHD->Footer.DriveType[0]);
}

static FILE *sim_vhd_disk_open (const char *szVHDPath, const char *DesiredAccess)
    {
    VHDHANDLE hVHD = (VHDHANDLE) calloc (1, sizeof(*hVHD));
    int Status;

    if (!hVHD)
        return (FILE *)hVHD;
    Status = GetVHDFooter (szVHDPath, 
                           &hVHD->Footer, 
                           &hVHD->Dynamic, 
                           &hVHD->BAT,
                           NULL,
                           hVHD->ParentVHDPath,
                           sizeof (hVHD->ParentVHDPath));
    if (Status)
        goto Cleanup_Return;
    if (NtoHl (hVHD->Footer.DiskType) == VHD_DT_Differencing) {
        uint32 ParentModifiedTimeStamp;
        VHD_Footer ParentFooter;
        VHD_DynamicDiskHeader ParentDynamic;

        hVHD->Parent = (VHDHANDLE)sim_vhd_disk_open (hVHD->ParentVHDPath, "rb");
        if (!hVHD->Parent) {
            Status = errno;
            goto Cleanup_Return;
            }
        Status = GetVHDFooter (hVHD->ParentVHDPath, 
                               &ParentFooter, 
                               &ParentDynamic, 
                               NULL,
                               &ParentModifiedTimeStamp,
                               NULL,
                               0);
        if (Status)
            goto Cleanup_Return;
        if (ParentModifiedTimeStamp != hVHD->Dynamic.ParentTimeStamp) {
            Status = EBADF;
            goto Cleanup_Return;
            }
        }
    if (hVHD->Footer.SavedState) {
        Status = EAGAIN;                                /* Busy */
        goto Cleanup_Return;
        }
    hVHD->File = sim_fopen (szVHDPath, DesiredAccess);
    if (!hVHD->File) {
        Status = errno;
        goto Cleanup_Return;
        }
Cleanup_Return:
    if (Status) {
        sim_vhd_disk_close ((FILE *)hVHD);
        hVHD = NULL;
        }
    errno = Status;
    return (FILE *)hVHD;
    }

static t_stat
WriteVirtualDiskSectors(VHDHANDLE hVHD,
                        uint8 *buf,
                        t_seccnt sects,
                        t_seccnt *sectswritten,
                        uint32 SectorSize,
                        t_lba lba);

static FILE *sim_vhd_disk_merge (const char *szVHDPath, char **ParentVHD)
    {
    VHDHANDLE hVHD = (VHDHANDLE) calloc (1, sizeof(*hVHD));
    VHDHANDLE Parent = NULL;
    int Status;
    uint32 SectorSize, SectorsPerBlock, BlockSize, BlockNumber, BitMapBytes, BitMapSectors, BlocksToMerge, NeededBlock;
    uint64 BlockOffset;
    size_t BytesRead;
    t_seccnt SectorsWritten;
    void *BlockData = NULL;

    if (!hVHD)
        return (FILE *)hVHD;
    if (0 != (Status = GetVHDFooter (szVHDPath, 
                                     &hVHD->Footer, 
                                     &hVHD->Dynamic, 
                                     &hVHD->BAT,
                                     NULL,
                                     hVHD->ParentVHDPath,
                                     sizeof (hVHD->ParentVHDPath))))
        goto Cleanup_Return;
    if (NtoHl (hVHD->Footer.DiskType) != VHD_DT_Differencing) {
        Status = EINVAL;
        goto Cleanup_Return;
        }
    if (hVHD->Footer.SavedState) {
        Status = EAGAIN;                                /* Busy */
        goto Cleanup_Return;
        }
    SectorSize = 512;
    BlockSize = NtoHl (hVHD->Dynamic.BlockSize);
    BlockData = malloc (BlockSize*SectorSize);
    if (NULL == BlockData) {
        Status = errno;
        goto Cleanup_Return;
        }
    Parent = (VHDHANDLE)sim_vhd_disk_open (hVHD->ParentVHDPath, "rb+");
    if (!Parent) {
        Status = errno;
        goto Cleanup_Return;
        }
    hVHD->File = sim_fopen (szVHDPath, "rb");
    if (!hVHD->File) {
        Status = errno;
        goto Cleanup_Return;
        }
    SectorsPerBlock = NtoHl (hVHD->Dynamic.BlockSize)/SectorSize;
    BitMapBytes = (7+(NtoHl (hVHD->Dynamic.BlockSize)/SectorSize))/8;
    BitMapSectors = (BitMapBytes+SectorSize-1)/SectorSize;
    for (BlockNumber=BlocksToMerge=0; BlockNumber< NtoHl (hVHD->Dynamic.MaxTableEntries); ++BlockNumber) {
        if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY)
            continue;
        ++BlocksToMerge;
        }
    if (!sim_quiet)
        printf ("Merging %s\ninto %s\n", szVHDPath, hVHD->ParentVHDPath);
    for (BlockNumber=NeededBlock=0; BlockNumber < NtoHl (hVHD->Dynamic.MaxTableEntries); ++BlockNumber) {
        uint32 BlockSectors = SectorsPerBlock;

        if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY)
            continue;
        ++NeededBlock;
        BlockOffset = SectorSize*((uint64)(NtoHl (hVHD->BAT[BlockNumber]) + BitMapSectors));
        if ((BlockNumber*SectorsPerBlock + BlockSectors) > ((uint64)NtoHll (hVHD->Footer.CurrentSize))/SectorSize)
            BlockSectors = (uint32)(((uint64)NtoHll (hVHD->Footer.CurrentSize))/SectorSize - (BlockNumber*SectorsPerBlock));
        if (ReadFilePosition(hVHD->File,
                             BlockData,
                             SectorSize*BlockSectors,
                             &BytesRead,
                             BlockOffset))
            break;
        if (WriteVirtualDiskSectors (Parent, 
                                     (uint8*)BlockData, 
                                     BlockSectors, 
                                     &SectorsWritten, 
                                     SectorSize, 
                                     SectorsPerBlock*BlockNumber))
            break;
        if (!sim_quiet)
            printf ("Merged %dMB.  %d%% complete.\r", (int)(((float)NeededBlock*SectorsPerBlock)*SectorSize/1000000), (int)((NeededBlock*100)/BlocksToMerge));
        hVHD->BAT[BlockNumber] = VHD_BAT_FREE_ENTRY;
        }
    if (BlockNumber < NtoHl (hVHD->Dynamic.MaxTableEntries)) {
        Status = errno;
        }
    else {
        Status = 0;
        if (!sim_quiet)
            printf ("Merged %dMB.  100%% complete.\n", (int)(((float)NeededBlock*SectorsPerBlock)*SectorSize/1000000));
        fclose (hVHD->File);
        hVHD->File = NULL;
        remove (szVHDPath);
        *ParentVHD = (char*) malloc (strlen (hVHD->ParentVHDPath)+1);
        strcpy (*ParentVHD, hVHD->ParentVHDPath);
        }
Cleanup_Return:
    free (BlockData);
    if (hVHD->File)
        fclose (hVHD->File);
    if (Status) {
        free (hVHD->BAT);
        free (hVHD);
        hVHD = NULL;
        sim_vhd_disk_close ((FILE *)Parent);
        }
    else {
        free (hVHD->BAT);
        free (hVHD);
        hVHD = Parent;
        }
    errno = Status;
    return (FILE *)hVHD;
    }

static int sim_vhd_disk_close (FILE *f)
{
VHDHANDLE hVHD = (VHDHANDLE)f;

if (NULL != hVHD) {
    if (hVHD->Parent)
        sim_vhd_disk_close ((FILE *)hVHD->Parent);
    free (hVHD->BAT);
    if (hVHD->File) {
        fflush (hVHD->File);
        fclose (hVHD->File);
        }
    free (hVHD);
    return 0;
    }
return -1;
}

static void sim_vhd_disk_flush (FILE *f)
{
VHDHANDLE hVHD = (VHDHANDLE)f;

if ((NULL != hVHD) && (hVHD->File))
    fflush (hVHD->File);
}

static t_addr sim_vhd_disk_size (FILE *f)
{
VHDHANDLE hVHD = (VHDHANDLE)f;

return (t_addr)(NtoHll (hVHD->Footer.CurrentSize));
}

#include <stdlib.h>
#include <time.h>
static void
_rand_uuid_gen (void *uuidaddr)
{
int i;
uint8 *b = (uint8 *)uuidaddr;
uint32 timenow = (uint32)time (NULL);

memcpy (uuidaddr, &timenow, sizeof (timenow));
srand ((unsigned)timenow);
for (i=4; i<16; i++) {
    b[i] = (uint8)rand();
    }
}

#if defined (_WIN32)
static void
uuid_gen (void *uuidaddr)
{
static
RPC_STATUS
(RPC_ENTRY *UuidCreate_c) (void *);

if (!UuidCreate_c) {
    HINSTANCE hDll;
    hDll = LoadLibraryA("rpcrt4.dll");
    UuidCreate_c = (RPC_STATUS (RPC_ENTRY *) (void *))GetProcAddress(hDll, "UuidCreate");
    }
if (UuidCreate_c)
    UuidCreate_c(uuidaddr);
else
    _rand_uuid_gen (uuidaddr);
}
#elif defined (HAVE_DLOPEN)
#include <dlfcn.h>

static void
uuid_gen (void *uuidaddr)
{
void (*uuid_generate_c) (void *) = NULL;
void *handle;

#define __STR_QUOTE(tok) #tok
#define __STR(tok) __STR_QUOTE(tok)
    handle = dlopen("libuuid." __STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL);
    if (handle)
        uuid_generate_c = (void (*)(void *))((size_t)dlsym(handle, "uuid_generate"));
if (uuid_generate_c)
    uuid_generate_c(uuidaddr);
else
    _rand_uuid_gen (uuidaddr);
    if (handle)
        dlclose(handle);
}
#else
static void
uuid_gen (void *uuidaddr)
{
_rand_uuid_gen (uuidaddr);
}
#endif

static VHDHANDLE
CreateVirtualDisk(const char *szVHDPath,
                  uint32 SizeInSectors,
                  uint32 BlockSize,
                  t_bool bFixedVHD)
{
VHD_Footer Footer;
VHD_DynamicDiskHeader Dynamic;
uint32 *BAT = NULL;
time_t now;
uint32 i;
FILE *File = NULL;
uint32 Status = 0;
uint32 BytesPerSector = 512;
uint64 SizeInBytes = ((uint64)SizeInSectors)*BytesPerSector;
uint64 TableOffset;
uint32 MaxTableEntries;
VHDHANDLE hVHD = NULL;

if (SizeInBytes > ((uint64)(1024*1024*1024))*2040) {
    Status = EFBIG;
    goto Cleanup_Return;
    }
File = sim_fopen (szVHDPath, "rb");
if (File) {
    fclose (File);
    File = NULL;
    Status = EEXIST;
    goto Cleanup_Return;
    }
File = sim_fopen (szVHDPath, "wb");
if (!File) {
    Status = errno;
    goto Cleanup_Return;
    }

memset (&Footer, 0, sizeof(Footer));
memcpy (Footer.Cookie, "conectix", 8);
Footer.Features = NtoHl (0x00000002);;
Footer.FileFormatVersion = NtoHl (0x00010000);;
Footer.DataOffset = NtoHll (bFixedVHD ? ((long long)-1) : (long long)(sizeof(Footer)));
time (&now);
Footer.TimeStamp = NtoHl ((uint32)(now-946684800));
memcpy (Footer.CreatorApplication, "simh", 4);
Footer.CreatorVersion = NtoHl (0x00010000);
memcpy (Footer.CreatorHostOS, "Wi2k", 4);
Footer.OriginalSize = NtoHll (SizeInBytes);
Footer.CurrentSize = NtoHll (SizeInBytes);
uuid_gen (Footer.UniqueID);
Footer.DiskType = NtoHl (bFixedVHD ? VHD_DT_Fixed : VHD_DT_Dynamic);
Footer.DiskGeometry = NtoHl (0xFFFF10FF);
if (1) { /* CHS Calculation */
    uint32 totalSectors = (uint32)(SizeInBytes/BytesPerSector);/* Total data sectors present in the disk image */
    uint32 cylinders;                                          /* Number of cylinders present on the disk */
    uint32 heads;                                              /* Number of heads present on the disk */
    uint32 sectorsPerTrack;                                    /* Sectors per track on the disk */
    uint32 cylinderTimesHeads;                                 /* Cylinders x heads */

    if (totalSectors > 65535 * 16 * 255)
        totalSectors = 65535 * 16 * 255;

    if (totalSectors >= 65535 * 16 * 63) {
        sectorsPerTrack = 255;
        heads = 16;
        cylinderTimesHeads = totalSectors / sectorsPerTrack;
        }
    else {
        sectorsPerTrack = 17; 
        cylinderTimesHeads = totalSectors / sectorsPerTrack;

        heads = (cylinderTimesHeads + 1023) / 1024;

        if (heads < 4)
            heads = 4;
        if (cylinderTimesHeads >= (heads * 1024) || heads > 16)
            {
            sectorsPerTrack = 31;
            heads = 16;
            cylinderTimesHeads = totalSectors / sectorsPerTrack;
            }
        if (cylinderTimesHeads >= (heads * 1024))
            {
            sectorsPerTrack = 63;
            heads = 16;
            cylinderTimesHeads = totalSectors / sectorsPerTrack;
            }
        }
    cylinders = cylinderTimesHeads / heads;
    Footer.DiskGeometry = NtoHl ((cylinders<<16)|(heads<<8)|sectorsPerTrack);
    }
Footer.Checksum = NtoHl (CalculateVhdFooterChecksum(&Footer, sizeof(Footer)));

if (bFixedVHD) {
    if (WriteFilePosition(File,
                          &Footer,
                          sizeof(Footer),
                          NULL,
                          SizeInBytes))
        Status = errno;
    goto Cleanup_Return;
    }

/* Dynamic Disk */
memset (&Dynamic, 0, sizeof(Dynamic));
memcpy (Dynamic.Cookie, "cxsparse", 8);
Dynamic.DataOffset = NtoHll (0xFFFFFFFFFFFFFFFFLL);
TableOffset = (uint64)(BytesPerSector*((sizeof(Dynamic)+sizeof(Footer)+BytesPerSector-1)/BytesPerSector));
TableOffset += VHD_DATA_BLOCK_ALIGNMENT-1;
TableOffset &= ~(VHD_DATA_BLOCK_ALIGNMENT-1);
Dynamic.TableOffset = NtoHll (TableOffset);
Dynamic.HeaderVersion = NtoHl (0x00010000);
if (0 == BlockSize)
    BlockSize = 2*1024*1024;
Dynamic.BlockSize = NtoHl (BlockSize);
MaxTableEntries = (uint32)((SizeInBytes+BlockSize-1)/BlockSize);
Dynamic.MaxTableEntries = NtoHl (MaxTableEntries);
Dynamic.Checksum = NtoHl (CalculateVhdFooterChecksum(&Dynamic, sizeof(Dynamic)));
BAT = (uint32*) malloc (BytesPerSector*((MaxTableEntries*sizeof(*BAT)+BytesPerSector-1)/BytesPerSector));
memset (BAT, 0, BytesPerSector*((MaxTableEntries*sizeof(*BAT)+BytesPerSector-1)/BytesPerSector));
for (i=0; i<MaxTableEntries; ++i)
    BAT[i] = VHD_BAT_FREE_ENTRY;

if (WriteFilePosition(File,
                      &Footer,
                      sizeof(Footer),
                      NULL,
                      0)) {
    Status = errno;
    goto Cleanup_Return;
    }
if (WriteFilePosition(File,
                      &Dynamic,
                      sizeof(Dynamic),
                      NULL,
                      NtoHll(Footer.DataOffset))) {
    Status = errno;
    goto Cleanup_Return;
    }
if (WriteFilePosition(File,
                      BAT,
                      BytesPerSector*((MaxTableEntries*sizeof(*BAT)+BytesPerSector-1)/BytesPerSector),
                      NULL,
                      NtoHll(Dynamic.TableOffset))) {
    Status = errno;
    goto Cleanup_Return;
    }
if (WriteFilePosition(File,
                      &Footer,
                      sizeof(Footer),
                      NULL,
                      NtoHll(Dynamic.TableOffset)+BytesPerSector*((MaxTableEntries*sizeof(*BAT)+BytesPerSector-1)/BytesPerSector))) {
    Status = errno;
    goto Cleanup_Return;
    }

Cleanup_Return:
free (BAT);
if (File)
    fclose (File);
if (Status) {
    if (Status != EEXIST)
        remove (szVHDPath);
    }
else {
    hVHD = (VHDHANDLE)sim_vhd_disk_open (szVHDPath, "rb+");
    if (!hVHD)
        Status = errno;
    }
errno = Status;
return hVHD;
}

#if defined(__CYGWIN__) || defined(VMS) || defined(__APPLE__)
#include <unistd.h>
#endif
static void
ExpandToFullPath (const char *szFileSpec,
                  char *szFullFileSpecBuffer,
                  size_t BufferSize)
{
char *c;
#ifdef _WIN32
for (c = strchr (szFullFileSpecBuffer, '/'); c; c = strchr (szFullFileSpecBuffer, '/'))
    *c = '\\';
GetFullPathNameA (szFileSpec, (DWORD)BufferSize, szFullFileSpecBuffer, NULL);
for (c = strchr (szFullFileSpecBuffer, '\\'); c; c = strchr (szFullFileSpecBuffer, '\\'))
    *c = '/';
#else
char buffer[PATH_MAX];
char *wd = getcwd(buffer, PATH_MAX);

if ((szFileSpec[0] != '/') || (strchr (szFileSpec, ':')))
    snprintf (szFullFileSpecBuffer, BufferSize, "%s/%s", wd, szFileSpec);
else
    strncpy (szFullFileSpecBuffer, szFileSpec, BufferSize);
if ((c = strstr (szFullFileSpecBuffer, "]/")))
    strcpy (c+1, c+2);
memset (szFullFileSpecBuffer + strlen (szFullFileSpecBuffer), 0, BufferSize - strlen (szFullFileSpecBuffer));
#endif
}

static char *
HostPathToVhdPath (const char *szHostPath,
                   char *szVhdPath,
                   size_t VhdPathSize)
{
char *c, *d;

strncpy (szVhdPath, szHostPath, VhdPathSize-1);
szVhdPath[VhdPathSize-1] = '\0';
if ((c = strrchr (szVhdPath, ']'))) {
    *c = '\0';
    if (!(d = strchr (szVhdPath, '[')))
        return d;
    *d = '/';
    while ((d = strchr (d, '.')))
        *d = '/';
    *c = '/';
    }
while ((c = strchr (szVhdPath, '/')))
    *c = '\\';
for (c = strstr (szVhdPath, "\\.\\"); c; c = strstr (szVhdPath, "\\.\\"))
    strcpy (c, c+2);
for (c = strstr (szVhdPath, "\\\\"); c; c = strstr (szVhdPath, "\\\\"))
    strcpy (c, c+1);
while ((c = strstr (szVhdPath, "\\..\\"))) {
    *c = '\0';
    d = strrchr (szVhdPath, '\\');
    if (d)
        strcpy (d, c+3);
    else
        return d;
    }
memset (szVhdPath + strlen (szVhdPath), 0, VhdPathSize - strlen (szVhdPath));
return szVhdPath;
}

static char *
VhdPathToHostPath (const char *szVhdPath,
                   char *szHostPath,
                   size_t HostPathSize)
{
char *c;
char *d = szHostPath;

strncpy (szHostPath, szVhdPath, HostPathSize-1);
szHostPath[HostPathSize-1] = '\0';
#if defined(VMS)
c = strchr (szVhdPath, ':');
if (*(c+1) != '\\')
    return NULL;
*(c+1) = '[';
d = strrchr (c+2, '\\');
if (d) {
    *d = ']';
    while ((d = strrchr (c+2, '\\')))
        *d = '.';
    }
else
    return NULL;
#else
while ((c = strchr (d, '\\')))
    *c = '/';
#endif
memset (szHostPath + strlen (szHostPath), 0, HostPathSize - strlen (szHostPath));
return szHostPath;
}

static VHDHANDLE
CreateDifferencingVirtualDisk(const char *szVHDPath, 
                              const char *szParentVHDPath)
{
uint32 BytesPerSector = 512;
VHDHANDLE hVHD = NULL;
VHD_Footer ParentFooter;
VHD_DynamicDiskHeader ParentDynamic;
uint32 ParentTimeStamp;
uint32 Status = 0;
char *RelativeParentVHDPath = NULL;
char *FullParentVHDPath = NULL;
char *RelativeParentVHDPathUnicode = NULL;
char *FullParentVHDPathUnicode = NULL;
char *FullVHDPath = NULL;
size_t i, RelativeMatch, UpDirectories, LocatorsWritten = 0;
int64 LocatorPosition;

if ((Status = GetVHDFooter (szParentVHDPath, 
                            &ParentFooter, 
                            &ParentDynamic, 
                            NULL, 
                            &ParentTimeStamp,
                            NULL, 
                            0)))
    goto Cleanup_Return;
hVHD = CreateVirtualDisk (szVHDPath,
                          (uint32)(NtoHll(ParentFooter.CurrentSize)/BytesPerSector),
                          NtoHl(ParentDynamic.BlockSize),
                          FALSE);
if (!hVHD) {
    Status = errno;
    goto Cleanup_Return;
    }
LocatorPosition = ((sizeof (hVHD->Footer) + BytesPerSector - 1)/BytesPerSector + (sizeof (hVHD->Dynamic) + BytesPerSector - 1)/BytesPerSector)*BytesPerSector;
hVHD->Dynamic.Checksum = 0;
RelativeParentVHDPath = (char*) calloc (1, BytesPerSector+2);
FullParentVHDPath = (char*) calloc (1, BytesPerSector+2);
RelativeParentVHDPathUnicode = (char*) calloc (1, BytesPerSector+2);
FullParentVHDPathUnicode = (char*) calloc (1, BytesPerSector+2);
FullVHDPath = (char*) calloc (1, BytesPerSector+2);
ExpandToFullPath (szParentVHDPath, FullParentVHDPath, BytesPerSector);
HostPathToVhdPath (FullParentVHDPath, FullParentVHDPath, BytesPerSector);
for (i=0; i < strlen (FullParentVHDPath); i++)
    hVHD->Dynamic.ParentUnicodeName[i*2+1] = FullParentVHDPath[i];  /* Big Endian Unicode */
for (i=0; i < strlen (FullParentVHDPath); i++)
    FullParentVHDPathUnicode[i*2] = FullParentVHDPath[i];           /* Little Endian Unicode */
ExpandToFullPath (szVHDPath, FullVHDPath, BytesPerSector);
HostPathToVhdPath (FullVHDPath, FullVHDPath, BytesPerSector);
for (i=0, RelativeMatch=UpDirectories=0; i<strlen(FullVHDPath); i++)
    if (FullVHDPath[i] == '\\') {
        if (memcmp (FullVHDPath, FullParentVHDPath, i+1))
            ++UpDirectories;
        else
            RelativeMatch = i;
        }
if (RelativeMatch) {
    char UpDir[4] = "..\\";

    UpDir[2] = FullParentVHDPath[RelativeMatch];
    if (UpDirectories)
        for (i=0; i<UpDirectories; i++)
            strcpy (RelativeParentVHDPath+strlen (RelativeParentVHDPath), UpDir);
    else
        strcpy (RelativeParentVHDPath+strlen (RelativeParentVHDPath), UpDir+1);
    strcpy (RelativeParentVHDPath+strlen (RelativeParentVHDPath), &FullParentVHDPath[RelativeMatch+1]);
    }
for (i=0; i < strlen(RelativeParentVHDPath); i++)
    RelativeParentVHDPathUnicode[i*2] = RelativeParentVHDPath[i];
hVHD->Dynamic.ParentTimeStamp = ParentTimeStamp;
memcpy (hVHD->Dynamic.ParentUniqueID, ParentFooter.UniqueID, sizeof (hVHD->Dynamic.ParentUniqueID));
hVHD->Dynamic.ParentLocatorEntries[7].PlatformDataSpace = NtoHl (BytesPerSector);
hVHD->Dynamic.ParentLocatorEntries[7].Reserved = 0;
hVHD->Dynamic.ParentLocatorEntries[7].PlatformDataOffset = NtoHll (LocatorPosition+LocatorsWritten*BytesPerSector);
++LocatorsWritten;
memcpy (hVHD->Dynamic.ParentLocatorEntries[6].PlatformCode, "Wi2k", 4);
hVHD->Dynamic.ParentLocatorEntries[6].PlatformDataSpace = NtoHl (BytesPerSector);
hVHD->Dynamic.ParentLocatorEntries[6].Reserved = 0;
hVHD->Dynamic.ParentLocatorEntries[6].PlatformDataOffset = NtoHll (LocatorPosition+LocatorsWritten*BytesPerSector);
++LocatorsWritten;
if (RelativeMatch) {
    memcpy (hVHD->Dynamic.ParentLocatorEntries[7].PlatformCode, "Wi2r", 4);
    hVHD->Dynamic.ParentLocatorEntries[7].PlatformDataLength = NtoHl ((uint32)(strlen(RelativeParentVHDPath)));
    memcpy (hVHD->Dynamic.ParentLocatorEntries[5].PlatformCode, "W2ru", 4);
    hVHD->Dynamic.ParentLocatorEntries[5].PlatformDataSpace = NtoHl (BytesPerSector);
    hVHD->Dynamic.ParentLocatorEntries[5].PlatformDataLength = NtoHl ((uint32)(2*strlen(RelativeParentVHDPath)));
    hVHD->Dynamic.ParentLocatorEntries[5].Reserved = 0;
    hVHD->Dynamic.ParentLocatorEntries[5].PlatformDataOffset = NtoHll (LocatorPosition+LocatorsWritten*BytesPerSector);
    ++LocatorsWritten;
    }
memcpy (hVHD->Dynamic.ParentLocatorEntries[4].PlatformCode, "W2ku", 4);
hVHD->Dynamic.ParentLocatorEntries[4].PlatformDataSpace = NtoHl (BytesPerSector);
hVHD->Dynamic.ParentLocatorEntries[4].PlatformDataLength = NtoHl ((uint32)(2*strlen(FullParentVHDPath)));
hVHD->Dynamic.ParentLocatorEntries[4].Reserved = 0;
hVHD->Dynamic.ParentLocatorEntries[4].PlatformDataOffset = NtoHll (LocatorPosition+LocatorsWritten*BytesPerSector);
++LocatorsWritten;
hVHD->Dynamic.Checksum = NtoHl (CalculateVhdFooterChecksum (&hVHD->Dynamic, sizeof(hVHD->Dynamic)));
hVHD->Footer.Checksum = 0;
hVHD->Footer.DiskType = NtoHl (VHD_DT_Differencing);
memcpy (hVHD->Footer.DriveType, ParentFooter.DriveType, sizeof (hVHD->Footer.DriveType));
hVHD->Footer.Checksum = NtoHl (CalculateVhdFooterChecksum (&hVHD->Footer, sizeof(hVHD->Footer)));

if (WriteFilePosition (hVHD->File,
                       &hVHD->Footer,
                       sizeof (hVHD->Footer),
                       NULL,
                       0)) {
    Status = errno;
    goto Cleanup_Return;
    }
if (WriteFilePosition (hVHD->File,
                       &hVHD->Dynamic,
                       sizeof (hVHD->Dynamic),
                       NULL,
                       NtoHll (hVHD->Footer.DataOffset))) {
    Status = errno;
    goto Cleanup_Return;
    }
if (WriteFilePosition (hVHD->File,
                       &hVHD->Footer,
                       sizeof (hVHD->Footer),
                       NULL,
                       NtoHll (hVHD->Dynamic.TableOffset)+BytesPerSector*((NtoHl (hVHD->Dynamic.MaxTableEntries)*sizeof(*hVHD->BAT)+BytesPerSector-1)/BytesPerSector))) {
    Status = errno;
    goto Cleanup_Return;
    }
if (WriteFilePosition (hVHD->File,
                       RelativeParentVHDPath,
                       BytesPerSector,
                       NULL,
                       NtoHll (hVHD->Dynamic.ParentLocatorEntries[7].PlatformDataOffset))) {
    Status = errno;
    goto Cleanup_Return;
    }
if (WriteFilePosition (hVHD->File,
                       FullParentVHDPath,
                       BytesPerSector,
                       NULL,
                       NtoHll (hVHD->Dynamic.ParentLocatorEntries[6].PlatformDataOffset))) {
    Status = errno;
    goto Cleanup_Return;
    }
if (WriteFilePosition (hVHD->File,
                       RelativeParentVHDPathUnicode,
                       BytesPerSector,
                       NULL,
                       NtoHll (hVHD->Dynamic.ParentLocatorEntries[5].PlatformDataOffset))) {
    Status = errno;
    goto Cleanup_Return;
    }
if (WriteFilePosition (hVHD->File,
                       FullParentVHDPathUnicode,
                       BytesPerSector,
                       NULL,
                       NtoHll (hVHD->Dynamic.ParentLocatorEntries[4].PlatformDataOffset))) {
    Status = errno;
    goto Cleanup_Return;
    }

Cleanup_Return:
free (RelativeParentVHDPath);
free (FullParentVHDPath);
free (RelativeParentVHDPathUnicode);
free (FullParentVHDPathUnicode);
free (FullVHDPath);
sim_vhd_disk_close ((FILE *)hVHD);
hVHD = NULL;
if (Status) {
    if (EEXIST != Status)
        remove (szVHDPath);
    }
else {
    hVHD = (VHDHANDLE)sim_vhd_disk_open (szVHDPath, "rb+");
    if (!hVHD)
        Status = errno;
    }
errno = Status;
return hVHD;
}

static FILE *sim_vhd_disk_create (const char *szVHDPath, t_addr desiredsize)
{
return (FILE *)CreateVirtualDisk (szVHDPath, (uint32)(desiredsize/512), 0, (sim_switches & SWMASK ('X')));
}

static FILE *sim_vhd_disk_create_diff (const char *szVHDPath, const char *szParentVHDPath)
{
return (FILE *)CreateDifferencingVirtualDisk (szVHDPath, szParentVHDPath);
}

static t_stat
ReadVirtualDiskSectors(VHDHANDLE hVHD,
                       uint8 *buf,
                       t_seccnt sects,
                       t_seccnt *sectsread,
                       uint32 SectorSize,
                       t_lba lba)
{
uint64 BlockOffset = ((uint64)lba)*SectorSize;
uint32 BlocksRead = 0;
uint32 SectorsInRead;
size_t BytesRead = 0;

if (!hVHD || (hVHD->File == NULL)) {
    errno = EBADF;
    return SCPE_IOERR;
    }
if ((BlockOffset + sects*SectorSize) > (uint64)NtoHll (hVHD->Footer.CurrentSize)) {
    errno = ERANGE;
    return SCPE_IOERR;
    }
if (NtoHl (hVHD->Footer.DiskType) == VHD_DT_Fixed) {
    if (ReadFilePosition(hVHD->File,
                         buf,
                         sects*SectorSize,
                         &BytesRead,
                         BlockOffset)) {
        if (sectsread)
            *sectsread = (t_seccnt)(BytesRead/SectorSize);
        return SCPE_IOERR;
        }
    if (sectsread)
        *sectsread /= SectorSize;
    return SCPE_OK;
    }
/* We are now dealing with a Dynamically expanding or differencing disk */
while (sects) {
    uint32 SectorsPerBlock = NtoHl (hVHD->Dynamic.BlockSize)/SectorSize;
    uint64 BlockNumber = lba/SectorsPerBlock;
    uint32 BitMapBytes = (7+(NtoHl (hVHD->Dynamic.BlockSize)/SectorSize))/8;
    uint32 BitMapSectors = (BitMapBytes+SectorSize-1)/SectorSize;

    SectorsInRead = SectorsPerBlock - lba%SectorsPerBlock;
    if (SectorsInRead > sects)
        SectorsInRead = sects;
    if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY) {
        if (!hVHD->Parent)
            memset (buf, 0, SectorSize*SectorsInRead);
        else {
            if (ReadVirtualDiskSectors(hVHD->Parent,
                                       buf,
                                       SectorsInRead,
                                       NULL,
                                       SectorSize,
                                       lba)) {
                if (sectsread)
                    *sectsread = BlocksRead;
                return FALSE;
                }
            }
        }
    else {
        BlockOffset = SectorSize*((uint64)(NtoHl (hVHD->BAT[BlockNumber]) + lba%SectorsPerBlock + BitMapSectors));
        if (ReadFilePosition(hVHD->File,
                             buf,
                             SectorsInRead*SectorSize,
                             NULL,
                             BlockOffset)) {
            if (sectsread)
                *sectsread = BlocksRead;
            return SCPE_IOERR;
            }
        }
    sects -= SectorsInRead;
    buf = (uint8 *)(((char *)buf) + SectorSize*SectorsInRead);
    lba += SectorsInRead;
    BlocksRead += SectorsInRead;
    }
if (sectsread)
    *sectsread = BlocksRead;
return SCPE_OK;
}

static t_stat sim_vhd_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
VHDHANDLE hVHD = (VHDHANDLE)uptr->fileref;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

return ReadVirtualDiskSectors(hVHD, buf, sects, sectsread, ctx->sector_size, lba);
}

static t_stat sim_vhd_disk_clearerr (UNIT *uptr)
{
VHDHANDLE hVHD = (VHDHANDLE)uptr->fileref;

clearerr (hVHD->File);
return SCPE_OK;
}

static t_bool
BufferIsZeros(void *Buffer, size_t BufferSize)
{
size_t i;
char *c = (char *)Buffer;

for (i=0; i<BufferSize; ++i)
    if (c[i])
        return FALSE;
return TRUE;
}

static t_stat
WriteVirtualDiskSectors(VHDHANDLE hVHD,
                        uint8 *buf,
                        t_seccnt sects,
                        t_seccnt *sectswritten,
                        uint32 SectorSize,
                        t_lba lba)
{
uint64 BlockOffset = ((uint64)lba)*SectorSize;
uint32 BlocksWritten = 0;
uint32 SectorsInWrite;
size_t BytesWritten = 0;

if (!hVHD || !hVHD->File) {
    errno = EBADF;
    return SCPE_IOERR;
    }
if ((BlockOffset + sects*SectorSize) > (uint64)NtoHll(hVHD->Footer.CurrentSize)) {
    errno = ERANGE;
    return SCPE_IOERR;
    }
if (NtoHl(hVHD->Footer.DiskType) == VHD_DT_Fixed) {
    if (WriteFilePosition(hVHD->File,
                          buf,
                          sects*SectorSize,
                          &BytesWritten,
                          BlockOffset)) {
        if (sectswritten)
            *sectswritten = (t_seccnt)(BytesWritten/SectorSize);
        return SCPE_IOERR;
        }
    if (sectswritten)
        *sectswritten /= SectorSize;
    return SCPE_OK;
    }
/* We are now dealing with a Dynamically expanding or differencing disk */
while (sects) {
    uint32 SectorsPerBlock = NtoHl(hVHD->Dynamic.BlockSize)/SectorSize;
    uint64 BlockNumber = lba/SectorsPerBlock;
    uint32 BitMapBytes = (7+(NtoHl(hVHD->Dynamic.BlockSize)/SectorSize))/8;
    uint32 BitMapSectors = (BitMapBytes+SectorSize-1)/SectorSize;

    if (BlockNumber >= NtoHl(hVHD->Dynamic.MaxTableEntries)) {
        if (sectswritten)
            *sectswritten = BlocksWritten;
        return SCPE_EOF;
        }
    SectorsInWrite = 1;
    if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY) {
        uint8 *BitMap = NULL;
        uint32 BitMapBufferSize = VHD_DATA_BLOCK_ALIGNMENT;
        uint8 *BitMapBuffer = NULL;
        void *BlockData = NULL;
        uint8 *BATUpdateBufferAddress;
        uint32 BATUpdateBufferSize;
        uint64 BATUpdateStorageAddress;

        if (!hVHD->Parent && BufferIsZeros(buf, SectorSize))
            goto IO_Done;
        /* Need to allocate a new Data Block. */
        BlockOffset = sim_fsize_ex (hVHD->File);
        if (((int64)BlockOffset) == -1)
            return SCPE_IOERR;
        if (BitMapSectors*SectorSize > BitMapBufferSize)
            BitMapBufferSize = BitMapSectors*SectorSize;
        BitMapBuffer = (uint8 *)calloc(1, BitMapBufferSize + SectorSize*SectorsPerBlock);
        if (BitMapBufferSize > BitMapSectors*SectorSize)
            BitMap = BitMapBuffer + BitMapBufferSize-BitMapBytes;
        else
            BitMap = BitMapBuffer;
        memset(BitMap, 0xFF, BitMapBytes);
        BlockOffset -= sizeof(hVHD->Footer);
        if (0 == (BlockOffset & ~(VHD_DATA_BLOCK_ALIGNMENT-1)))
            {  // Already aligned, so use padded BitMapBuffer
            if (WriteFilePosition(hVHD->File,
                                  BitMapBuffer,
                                  BitMapBufferSize + SectorSize*SectorsPerBlock,
                                  NULL,
                                  BlockOffset)) {
                free (BitMapBuffer);
                return SCPE_IOERR;
                }
            BlockOffset += BitMapBufferSize;
            }
        else
            {
            // align the data portion of the block to the desired alignment
            // compute the address of the data portion of the block
            BlockOffset += BitMapSectors*SectorSize;
            // round up this address to the desired alignment
            BlockOffset += VHD_DATA_BLOCK_ALIGNMENT-1;
            BlockOffset &= ~(VHD_DATA_BLOCK_ALIGNMENT-1);
            BlockOffset -= BitMapSectors*SectorSize;
            if (WriteFilePosition(hVHD->File,
                                  BitMap,
                                  SectorSize * (BitMapSectors + SectorsPerBlock),
                                  NULL,
                                  BlockOffset)) {
                free (BitMapBuffer);
                return SCPE_IOERR;
                }
            BlockOffset += BitMapSectors*SectorSize;
            }
        free(BitMapBuffer);
        BitMapBuffer = BitMap = NULL;
        /* the BAT block address is the beginning of the block bitmap */
        BlockOffset -= BitMapSectors*SectorSize;
        hVHD->BAT[BlockNumber] = NtoHl((uint32)(BlockOffset/SectorSize));
        BlockOffset += SectorSize * (SectorsPerBlock + BitMapSectors);
        if (WriteFilePosition(hVHD->File,
                              &hVHD->Footer,
                              sizeof(hVHD->Footer),
                              NULL,
                              BlockOffset))
            goto Fatal_IO_Error;
        /* Write back just the aligned sector which contains the updated BAT entry */
        BATUpdateBufferAddress = ((uint8 *)hVHD->BAT) + 
            (((((size_t)&hVHD->BAT[BlockNumber]) - (size_t)hVHD->BAT)/VHD_DATA_BLOCK_ALIGNMENT)*VHD_DATA_BLOCK_ALIGNMENT);
        BATUpdateBufferSize = VHD_DATA_BLOCK_ALIGNMENT;
        if ((size_t)(BATUpdateBufferAddress - (uint8 *)hVHD->BAT + BATUpdateBufferSize) > 512*((sizeof(*hVHD->BAT)*NtoHl(hVHD->Dynamic.MaxTableEntries) + 511)/512))
            BATUpdateBufferSize = 512*((sizeof(*hVHD->BAT)*NtoHl(hVHD->Dynamic.MaxTableEntries) + 511)/512) - (BATUpdateBufferAddress - ((uint8 *)hVHD->BAT));
        BATUpdateStorageAddress = NtoHll(hVHD->Dynamic.TableOffset) + BATUpdateBufferAddress - ((uint8 *)hVHD->BAT);
        if (WriteFilePosition(hVHD->File,
                              BATUpdateBufferAddress,
                              BATUpdateBufferSize,
                              NULL,
                              BATUpdateStorageAddress))
            goto Fatal_IO_Error;
        if (hVHD->Parent)
            { /* Need to populate data block contents from parent VHD */
            uint32 BlockSectors = SectorsPerBlock;

            BlockData = malloc(SectorsPerBlock*SectorSize);

            if (((lba/SectorsPerBlock)*SectorsPerBlock + BlockSectors) > ((uint64)NtoHll (hVHD->Footer.CurrentSize))/SectorSize)
                BlockSectors = (uint32)(((uint64)NtoHll (hVHD->Footer.CurrentSize))/SectorSize - (lba/SectorsPerBlock)*SectorsPerBlock);
            if (ReadVirtualDiskSectors(hVHD->Parent,
                                       (uint8*) BlockData, 
                                       BlockSectors,
                                       NULL,
                                       SectorSize,
                                       (lba/SectorsPerBlock)*SectorsPerBlock))
                goto Fatal_IO_Error;
            if (WriteVirtualDiskSectors(hVHD,
                                        (uint8*) BlockData, 
                                        BlockSectors,
                                        NULL,
                                        SectorSize,
                                        (lba/SectorsPerBlock)*SectorsPerBlock))
                goto Fatal_IO_Error;
            free(BlockData);
            }
        continue;
Fatal_IO_Error:
        free (BitMap);
        free (BlockData);
        fclose (hVHD->File);
        hVHD->File = NULL;
        return SCPE_IOERR;
        }
    else {
        BlockOffset = 512*((uint64)(NtoHl(hVHD->BAT[BlockNumber]) + lba%SectorsPerBlock + BitMapSectors));
        SectorsInWrite = SectorsPerBlock - lba%SectorsPerBlock;
        if (SectorsInWrite > sects)
            SectorsInWrite = sects;
        if (WriteFilePosition(hVHD->File,
                              buf,
                              SectorsInWrite*SectorSize,
                              NULL,
                              BlockOffset)) {
            if (sectswritten)
                *sectswritten = BlocksWritten;
            return SCPE_IOERR;
            }
        }
IO_Done:
    sects -= SectorsInWrite;
    buf = (uint8 *)(((char *)buf) + SectorsInWrite*SectorSize);
    lba += SectorsInWrite;
    BlocksWritten += SectorsInWrite;
    }
if (sectswritten)
    *sectswritten = BlocksWritten;
return SCPE_OK;
}

static t_stat sim_vhd_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
VHDHANDLE hVHD = (VHDHANDLE)uptr->fileref;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;

return WriteVirtualDiskSectors(hVHD, buf, sects, sectswritten, ctx->sector_size, lba);
}
#endif
