/* 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_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 | |
extern FILE *sim_log; /* log file */ | |
extern int32 sim_switches; | |
extern int32 sim_quiet; | |
extern uint32 sim_taddr_64; | |
extern int32 sim_end; | |
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; | |
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, 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", uptr-ctx->dptr->units); | |
pthread_mutex_lock (&ctx->io_lock); | |
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; | |
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", 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 asynchrcondition 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", 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); | |
} | |
} | |
#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 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_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); | |
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 i, f = DK_GET_FMT (uptr); | |
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; | |
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_attr_init(&attr); | |
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); | |
pthread_create (&ctx->io_thread, &attr, _disk_io, (void *)uptr); | |
pthread_attr_destroy(&attr); | |
uptr->a_check_completion = _disk_completion_dispatch; | |
} | |
#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; | |
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); | |
} | |
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", 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", 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 = 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", 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", 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 = 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 = 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; | |
} | |
} | |
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 = 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 */ | |
} | |
} | |
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 */ | |
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 = (struct disk_context *)uptr->disk_ctx; | |
int (*close_function)(FILE *f); | |
FILE *fileref; | |
t_bool auto_format; | |
if (uptr == NULL) | |
return SCPE_IERR; | |
fileref = uptr->fileref; | |
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; | |
} | |
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 */ | |
#if defined SIM_ASYNCH_IO | |
sim_disk_clr_async (uptr); | |
#endif | |
uptr->flags = uptr->flags & ~(UNIT_ATT | UNIT_RO | UNIT_RAW); | |
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; | |
} | |
/* 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, 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; | |
} | |
#include <winioctl.h> | |
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; | |
#ifndef __GNUC__ | |
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), Device.DeviceNumber); | |
#endif | |
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", 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", 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 fd; | |
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 | |
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", 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", 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 *rawdevicename, const char *openmode) | |
{ | |
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_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. | |
--*/ | |
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 */ | |
static char *VHD_DiskTypes[] = | |
{ | |
"None", /* 0 */ | |
"Reserved (deprecated)", /* 1 */ | |
"Fixed hard disk", /* 2 */ | |
#define VHD_DT_Fixed 2 | |
"Dynamic hard disk", /* 3 */ | |
#define VHD_DT_Dynamic 3 | |
"Differencing hard disk", /* 4 */ | |
#define VHD_DT_Differencing 4 | |
"Reserved (deprecated)", /* 5 */ | |
"Reserved (deprecated)", /* 6 */ | |
}; | |
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 (!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 (!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 = 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; | |
char ParentName[256]; | |
char CheckPath[256]; | |
uint32 ParentModificationTime; | |
if ('\0' == sDynamic->ParentLocatorEntries[j].PlatformCode[0]) | |
continue; | |
memset (ParentName, '\0', sizeof(ParentName)); | |
memset (CheckPath, '\0', sizeof(CheckPath)); | |
Pdata = calloc (1, NtoHl(sDynamic->ParentLocatorEntries[j].PlatformDataSpace)+1); | |
if (!Pdata) | |
continue; | |
if (ReadFilePosition(File, | |
Pdata, | |
NtoHl (sDynamic->ParentLocatorEntries[j].PlatformDataSpace), | |
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)) { | |
char *c; | |
if ((c = strrchr (szVHDPath, '/')) || (c = strrchr (szVHDPath, '\\'))) | |
memcpy (CheckPath, szVHDPath, c-szVHDPath+1); | |
strncpy (CheckPath+strlen(CheckPath), ParentName, sizeof (CheckPath)-(strlen (CheckPath)+1)); | |
} | |
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 = calloc (1, sizeof(*hVHD)); | |
int Status; | |
if (!hVHD) | |
return (FILE *)hVHD; | |
if (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) { | |
hVHD->Parent = (VHDHANDLE)sim_vhd_disk_open (hVHD->ParentVHDPath, "rb"); | |
if (!hVHD->Parent) { | |
Status = errno; | |
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) { | |
free (hVHD->BAT); | |
free (hVHD); | |
hVHD = NULL; | |
} | |
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 = (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 = 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; | |
uint32 MaxTableEntries; | |
VHDHANDLE hVHD = NULL; | |
if (SizeInBytes > ((uint64)(1024*1024*1024))*2040) { | |
Status = EFBIG; | |
goto Cleanup_Return; | |
} | |
if (File = sim_fopen (szVHDPath, "rb")) { | |
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); | |
Dynamic.TableOffset = NtoHll ((uint64)(BytesPerSector*((sizeof(Dynamic)+sizeof(Footer)+BytesPerSector-1)/BytesPerSector))); | |
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 = 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; | |
} | |
static void | |
ExpandToFullPath (const char *szFileSpec, | |
char *szFullFileSpecBuffer, | |
size_t BufferSize) | |
{ | |
#ifdef _WIN32 | |
GetFullPathNameA (szFileSpec, (DWORD)BufferSize, szFullFileSpecBuffer, NULL); | |
#else | |
strncpy (szFullFileSpecBuffer, szFileSpec, BufferSize); | |
#endif | |
} | |
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 *RelativeParentVHDPathBuffer = NULL; | |
char *FullParentVHDPathBuffer = 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 = NtoHll (hVHD->Dynamic.TableOffset)+BytesPerSector*((NtoHl (hVHD->Dynamic.MaxTableEntries)*sizeof(*hVHD->BAT)+BytesPerSector-1)/BytesPerSector); | |
hVHD->Dynamic.Checksum = 0; | |
RelativeParentVHDPath = calloc (1, BytesPerSector+1); | |
FullParentVHDPath = calloc (1, BytesPerSector+1); | |
RelativeParentVHDPathBuffer = calloc (1, BytesPerSector); | |
FullParentVHDPathBuffer = calloc (1, BytesPerSector); | |
FullVHDPath = calloc (1, BytesPerSector+1); | |
ExpandToFullPath (szParentVHDPath, FullParentVHDPath, BytesPerSector); | |
for (i=0; i < strlen (FullParentVHDPath); i++) | |
hVHD->Dynamic.ParentUnicodeName[i*2+1] = FullParentVHDPath[i]; | |
for (i=0; i < strlen (FullParentVHDPath); i++) | |
FullParentVHDPathBuffer[i*2] = FullParentVHDPath[i]; | |
ExpandToFullPath (szVHDPath, FullVHDPath, BytesPerSector); | |
for (i=0, RelativeMatch=UpDirectories=0; i<strlen(FullVHDPath); i++) | |
if ((FullVHDPath[i] == '\\') || (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++) | |
RelativeParentVHDPathBuffer[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].PlatformDataOffset = NtoHll (LocatorPosition+LocatorsWritten*BytesPerSector); | |
++LocatorsWritten; | |
hVHD->Dynamic.ParentLocatorEntries[6].PlatformDataSpace = NtoHl (BytesPerSector); | |
hVHD->Dynamic.ParentLocatorEntries[6].PlatformDataOffset = NtoHll (LocatorPosition+LocatorsWritten*BytesPerSector); | |
++LocatorsWritten; | |
if (RelativeMatch) { | |
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; | |
} | |
LocatorsWritten = 0; | |
if (RelativeMatch) { | |
if (WriteFilePosition (hVHD->File, | |
RelativeParentVHDPath, | |
BytesPerSector, | |
NULL, | |
LocatorPosition+LocatorsWritten*BytesPerSector)) { | |
Status = errno; | |
goto Cleanup_Return; | |
} | |
++LocatorsWritten; | |
} | |
if (WriteFilePosition (hVHD->File, | |
FullParentVHDPath, | |
BytesPerSector, | |
NULL, | |
LocatorPosition+LocatorsWritten*BytesPerSector)) { | |
Status = errno; | |
goto Cleanup_Return; | |
} | |
++LocatorsWritten; | |
if (RelativeMatch) { | |
if (WriteFilePosition (hVHD->File, | |
RelativeParentVHDPathBuffer, | |
BytesPerSector, | |
NULL, | |
LocatorPosition+LocatorsWritten*BytesPerSector)) { | |
Status = errno; | |
goto Cleanup_Return; | |
} | |
++LocatorsWritten; | |
} | |
if (WriteFilePosition (hVHD->File, | |
FullParentVHDPathBuffer, | |
BytesPerSector, | |
NULL, | |
LocatorPosition+LocatorsWritten*BytesPerSector)) { | |
Status = errno; | |
goto Cleanup_Return; | |
} | |
++LocatorsWritten; | |
if (WriteFilePosition (hVHD->File, | |
&hVHD->Footer, | |
sizeof(hVHD->Footer), | |
NULL, | |
LocatorPosition+LocatorsWritten*BytesPerSector)) { | |
Status = errno; | |
goto Cleanup_Return; | |
} | |
Cleanup_Return: | |
free (RelativeParentVHDPath); | |
free (FullParentVHDPath); | |
free (RelativeParentVHDPathBuffer); | |
free (FullParentVHDPathBuffer); | |
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; | |
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 = 1; | |
if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY) { | |
if (!hVHD->Parent) | |
memset (buf, 0, SectorSize); | |
else { | |
SectorsInRead = SectorsPerBlock - lba%SectorsPerBlock; | |
if (SectorsInRead > sects) | |
SectorsInRead = sects; | |
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)); | |
SectorsInRead = SectorsPerBlock - lba%SectorsPerBlock; | |
if (SectorsInRead > sects) | |
SectorsInRead = sects; | |
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_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_bool | |
VhdBlockHasAllZeroSectors(void *Block, size_t BlockSize) | |
{ | |
uint8 *BitMap = (uint8 *)Block; | |
size_t SectorSize = 512; | |
size_t BitMapSize = (BlockSize/SectorSize+7)/8; | |
uint8 *Buffer = BitMap + BitMapSize; | |
size_t Sector; | |
for (Sector=0; Sector<BlockSize/SectorSize; ++Sector) { | |
/* We need Endian rules for BitMap interpretation | |
These are not documented in the Version 1.0 specification, AND | |
observations of Virtual PC's and Hyper-V's use of VHD's suggests | |
that they really don't manage this detail at the sector level. | |
What they appear to do is that whenever a Block is instantiated | |
All potential bitmap bits are set to 1 which means that the | |
current block has fully populated data for the current VHD. | |
The same is true in the differencing disk case (i.e. a copy of | |
the whole block is made from the parent to the current | |
differencing disk whenever any data is written to a new block). */ | |
if (!BufferIsZeros(Buffer, SectorSize)) | |
return FALSE; | |
Buffer += SectorSize; | |
} | |
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; | |
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) { | |
void *BitMap = NULL; | |
void *BlockData = NULL; | |
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; | |
BitMap = malloc(BitMapSectors*SectorSize); | |
memset(BitMap, 0xFF, BitMapBytes); | |
BlockOffset -= sizeof(hVHD->Footer); | |
/* 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); | |
/* the actual block address is the beginning of the block bitmap */ | |
BlockOffset -= BitMapSectors*SectorSize; | |
hVHD->BAT[BlockNumber] = NtoHl((uint32)(BlockOffset/SectorSize)); | |
if (WriteFilePosition(hVHD->File, | |
BitMap, | |
BitMapSectors*SectorSize, | |
NULL, | |
BlockOffset)) { | |
free (BitMap); | |
return SCPE_IOERR; | |
} | |
free(BitMap); | |
BitMap = NULL; | |
BlockOffset += SectorSize * (SectorsPerBlock + BitMapSectors); | |
if (WriteFilePosition(hVHD->File, | |
&hVHD->Footer, | |
sizeof(hVHD->Footer), | |
NULL, | |
BlockOffset)) | |
goto Fatal_IO_Error; | |
if (WriteFilePosition(hVHD->File, | |
hVHD->BAT, | |
SectorSize*((sizeof(*hVHD->BAT)*NtoHl(hVHD->Dynamic.MaxTableEntries)+511)/512), | |
NULL, | |
NtoHll(hVHD->Dynamic.TableOffset))) | |
goto Fatal_IO_Error; | |
if (hVHD->Parent) | |
{ /* Need to populate data block contents from parent VHD */ | |
BlockData = malloc(SectorsPerBlock*SectorSize); | |
if (ReadVirtualDiskSectors(hVHD->Parent, | |
BlockData, | |
SectorsPerBlock, | |
NULL, | |
SectorSize, | |
(lba/SectorsPerBlock)*SectorsPerBlock)) | |
goto Fatal_IO_Error; | |
if (WriteVirtualDiskSectors(hVHD, | |
BlockData, | |
SectorsPerBlock, | |
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 |