6139 lines
234 KiB
C
6139 lines
234 KiB
C
/* 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_attach_ex attach disk unit extended parameters
|
|
sim_disk_detach detach disk unit
|
|
sim_disk_attach_help help routine for attaching disks
|
|
sim_disk_rdsect read disk sectors
|
|
sim_disk_rdsect_a read disk sectors asynchronously
|
|
sim_disk_wrsect write disk sectors
|
|
sim_disk_wrsect_a write disk sectors asynchronously
|
|
sim_disk_unload unload or detach a disk as needed
|
|
sim_disk_reset reset unit
|
|
sim_disk_wrp TRUE if write protected
|
|
sim_disk_isavailable TRUE if available for I/O
|
|
sim_disk_size get disk size
|
|
sim_disk_set_fmt set disk format
|
|
sim_disk_show_fmt show disk format
|
|
sim_disk_set_capac set disk capacity
|
|
sim_disk_show_capac show disk capacity
|
|
sim_disk_set_async enable asynchronous operation
|
|
sim_disk_clr_async disable asynchronous operation
|
|
sim_disk_data_trace debug support
|
|
sim_disk_test unit test routine
|
|
|
|
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
|
|
|
|
|
|
*/
|
|
|
|
#define _FILE_OFFSET_BITS 64 /* 64 bit file offset for raw I/O operations */
|
|
|
|
#include "sim_defs.h"
|
|
#include "sim_disk.h"
|
|
#include "sim_ether.h"
|
|
#include <ctype.h>
|
|
#include <sys/stat.h>
|
|
|
|
#if defined SIM_ASYNCH_IO
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
/* Newly created SIMH (and possibly RAW) disk containers */
|
|
/* will have this data as the last 512 bytes of the container */
|
|
/* It will not be considered part of the data in the container */
|
|
/* Previously existing containers will have this appended to */
|
|
/* the end of the container if they are opened for write */
|
|
struct simh_disk_footer {
|
|
uint8 Signature[4]; /* must be 'simh' */
|
|
uint8 CreatingSimulator[64]; /* name of simulator */
|
|
uint8 DriveType[16];
|
|
uint32 SectorSize;
|
|
uint32 SectorCount;
|
|
uint32 TransferElementSize;
|
|
uint8 CreationTime[28]; /* Result of ctime() */
|
|
uint8 FooterVersion; /* Initially 0 */
|
|
uint8 AccessFormat; /* 1 - SIMH, 2 - RAW */
|
|
uint8 Reserved[382]; /* Currently unused */
|
|
uint32 Checksum; /* CRC32 of the prior 508 bytes */
|
|
};
|
|
|
|
/* 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(_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 (uint32)l[3] | ((uint32)l[2]<<8) | ((uint32)l[1]<<16) | ((uint32)l[0]<<24);
|
|
}
|
|
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
|
static uint32
|
|
NtoHl(uint32 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;
|
|
}
|
|
#endif
|
|
|
|
struct disk_context {
|
|
t_offset container_size; /* Size of the data portion (of the pseudo disk) */
|
|
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 (8 = quadword, 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 is_cdrom; /* Host system CDROM Device */
|
|
uint32 media_removed; /* Media not available flag */
|
|
uint32 auto_format; /* Format determined dynamically */
|
|
struct simh_disk_footer
|
|
*footer;
|
|
#if defined _WIN32
|
|
HANDLE disk_handle; /* OS specific Raw device handle */
|
|
#endif
|
|
#if defined SIM_ASYNCH_IO
|
|
int asynch_io; /* Asynchronous Interrupt scheduling enabled */
|
|
int asynch_io_latency; /* instructions to delay pending interrupt */
|
|
pthread_mutex_t lock;
|
|
pthread_t io_thread; /* I/O Thread Id */
|
|
pthread_mutex_t io_lock;
|
|
pthread_cond_t io_cond;
|
|
pthread_cond_t io_done;
|
|
pthread_cond_t startup_cond;
|
|
int io_dop;
|
|
uint8 *buf;
|
|
t_seccnt *rsects;
|
|
t_seccnt sects;
|
|
t_lba lba;
|
|
DISK_PCALLBACK callback;
|
|
t_stat io_status;
|
|
#endif
|
|
};
|
|
|
|
#define disk_ctx up8 /* Field in Unit structure which points to the disk_context */
|
|
|
|
#if defined SIM_ASYNCH_IO
|
|
#define AIO_CALLSETUP \
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx; \
|
|
\
|
|
if ((!callback) || !ctx->asynch_io)
|
|
|
|
#define AIO_CALL(op, _lba, _buf, _rsects, _sects, _callback) \
|
|
if (ctx->asynch_io) { \
|
|
struct disk_context *ctx = \
|
|
(struct disk_context *)uptr->disk_ctx; \
|
|
\
|
|
pthread_mutex_lock (&ctx->io_lock); \
|
|
\
|
|
sim_debug_unit (ctx->dbit, uptr, \
|
|
"sim_disk AIO_CALL(op=%d, unit=%d, lba=0x%X, sects=%d)\n",\
|
|
op, (int)(uptr - ctx->dptr->units), _lba, _sects);\
|
|
\
|
|
if (ctx->callback) \
|
|
abort(); /* horrible mistake, stop */ \
|
|
ctx->io_dop = op; \
|
|
ctx->lba = _lba; \
|
|
ctx->buf = _buf; \
|
|
ctx->sects = _sects; \
|
|
ctx->rsects = _rsects; \
|
|
ctx->callback = _callback; \
|
|
pthread_cond_signal (&ctx->io_cond); \
|
|
pthread_mutex_unlock (&ctx->io_lock); \
|
|
} \
|
|
else \
|
|
if (_callback) \
|
|
(_callback) (uptr, r);
|
|
|
|
|
|
#define DOP_DONE 0 /* close */
|
|
#define DOP_RSEC 1 /* sim_disk_rdsect_a */
|
|
#define DOP_WSEC 2 /* sim_disk_wrsect_a */
|
|
#define DOP_IAVL 3 /* sim_disk_isavailable_a */
|
|
|
|
static void *
|
|
_disk_io(void *arg)
|
|
{
|
|
UNIT* volatile uptr = (UNIT*)arg;
|
|
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 */
|
|
sim_os_set_thread_priority (PRIORITY_ABOVE_NORMAL);
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "_disk_io(unit=%d) starting\n", (int)(uptr - ctx->dptr->units));
|
|
|
|
pthread_mutex_lock (&ctx->io_lock);
|
|
pthread_cond_signal (&ctx->startup_cond); /* Signal we're ready to go */
|
|
while (ctx->asynch_io) {
|
|
pthread_cond_wait (&ctx->io_cond, &ctx->io_lock);
|
|
if (ctx->io_dop == DOP_DONE)
|
|
break;
|
|
pthread_mutex_unlock (&ctx->io_lock);
|
|
switch (ctx->io_dop) {
|
|
case DOP_RSEC:
|
|
ctx->io_status = sim_disk_rdsect (uptr, ctx->lba, ctx->buf, ctx->rsects, ctx->sects);
|
|
break;
|
|
case DOP_WSEC:
|
|
ctx->io_status = sim_disk_wrsect (uptr, ctx->lba, ctx->buf, ctx->rsects, ctx->sects);
|
|
break;
|
|
case DOP_IAVL:
|
|
ctx->io_status = sim_disk_isavailable (uptr);
|
|
break;
|
|
}
|
|
pthread_mutex_lock (&ctx->io_lock);
|
|
ctx->io_dop = DOP_DONE;
|
|
pthread_cond_signal (&ctx->io_done);
|
|
sim_activate (uptr, ctx->asynch_io_latency);
|
|
}
|
|
pthread_mutex_unlock (&ctx->io_lock);
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "_disk_io(unit=%d) exiting\n", (int)(uptr - ctx->dptr->units));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* This routine is called in the context of the main simulator thread before
|
|
processing events for any unit. It is only called when an asynchronous
|
|
thread has called sim_activate() to activate a unit. The job of this
|
|
routine is to put the unit in proper condition to digest what may have
|
|
occurred in the asynchrconous thread.
|
|
|
|
Since disk processing only handles a single I/O at a time to a
|
|
particular disk device (due to using stdio for the SimH Disk format
|
|
and stdio doesn't have an atomic seek+(read|write) operation),
|
|
we have the opportunity to possibly detect improper attempts to
|
|
issue multiple concurrent I/O requests. */
|
|
static void _disk_completion_dispatch (UNIT *uptr)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
DISK_PCALLBACK callback = ctx->callback;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "_disk_completion_dispatch(unit=%d, dop=%d, callback=%p)\n", (int)(uptr - ctx->dptr->units), ctx->io_dop, (void *)(ctx->callback));
|
|
|
|
if (ctx->io_dop != DOP_DONE)
|
|
abort(); /* horribly wrong, stop */
|
|
|
|
if (ctx->callback && ctx->io_dop == DOP_DONE) {
|
|
ctx->callback = NULL;
|
|
callback (uptr, ctx->io_status);
|
|
}
|
|
}
|
|
|
|
static t_bool _disk_is_active (UNIT *uptr)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
if (ctx) {
|
|
sim_debug_unit (ctx->dbit, uptr, "_disk_is_active(unit=%d, dop=%d)\n", (int)(uptr - ctx->dptr->units), ctx->io_dop);
|
|
return (ctx->io_dop != DOP_DONE);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static t_bool _disk_cancel (UNIT *uptr)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
if (ctx) {
|
|
sim_debug_unit (ctx->dbit, uptr, "_disk_cancel(unit=%d, dop=%d)\n", (int)(uptr - ctx->dptr->units), ctx->io_dop);
|
|
if (ctx->asynch_io) {
|
|
pthread_mutex_lock (&ctx->io_lock);
|
|
while (ctx->io_dop != DOP_DONE)
|
|
pthread_cond_wait (&ctx->io_done, &ctx->io_lock);
|
|
pthread_mutex_unlock (&ctx->io_lock);
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
#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_offset desiredsize);
|
|
static FILE *sim_vhd_disk_create_diff (const char *szVHDPath, const char *szParentVHDPath);
|
|
static FILE *sim_vhd_disk_merge (const char *szVHDPath, char **ParentVHD);
|
|
static int sim_vhd_disk_close (FILE *f);
|
|
static void sim_vhd_disk_flush (FILE *f);
|
|
static t_offset sim_vhd_disk_size (FILE *f);
|
|
static t_stat sim_vhd_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects);
|
|
static t_stat sim_vhd_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects);
|
|
static t_stat sim_vhd_disk_clearerr (UNIT *uptr);
|
|
static t_stat sim_vhd_disk_set_dtype (FILE *f, const char *dtype, uint32 SectorSize, uint32 xfer_element_size);
|
|
static const char *sim_vhd_disk_get_dtype (FILE *f, uint32 *SectorSize, uint32 *xfer_element_size, char sim_name[64], time_t *creation_time);
|
|
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_offset 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_read (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *bytesread, uint32 bytes);
|
|
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_write (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *byteswritten, uint32 bytes);
|
|
static t_stat sim_os_disk_info_raw (FILE *f, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom);
|
|
static char *HostPathToVhdPath (const char *szHostPath, char *szVhdPath, size_t VhdPathSize);
|
|
static char *VhdPathToHostPath (const char *szVhdPath, char *szHostPath, size_t HostPathSize);
|
|
static t_offset get_filesystem_size (UNIT *uptr);
|
|
|
|
struct sim_disk_fmt {
|
|
const 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[] = {
|
|
{ "AUTO detect", 0, DKUF_F_AUTO, NULL},
|
|
{ "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, NULL}
|
|
};
|
|
|
|
/* Set disk format */
|
|
|
|
t_stat sim_disk_set_fmt (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
uint32 f;
|
|
|
|
if (uptr == NULL)
|
|
return SCPE_IERR;
|
|
if ((cptr == NULL) || (*cptr == '\0'))
|
|
return SCPE_ARG;
|
|
for (f = 0; fmts[f].name; f++) {
|
|
if (fmts[f].name && (MATCH_CMD (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 sim_messagef (SCPE_ARG, "Unknown disk format: %s\n", cptr);
|
|
}
|
|
|
|
/* Show disk format */
|
|
|
|
static const char *sim_disk_fmt (UNIT *uptr)
|
|
{
|
|
int32 f = DK_GET_FMT (uptr);
|
|
size_t i;
|
|
|
|
for (i = 0; fmts[i].name; i++)
|
|
if (fmts[i].fmtval == f) {
|
|
return fmts[i].name;
|
|
}
|
|
return "invalid";
|
|
}
|
|
|
|
t_stat sim_disk_show_fmt (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
fprintf (st, "%s format", sim_disk_fmt (uptr));
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Set disk capacity */
|
|
|
|
t_stat sim_disk_set_capac (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
t_offset cap;
|
|
t_stat r;
|
|
DEVICE *dptr = find_dev_from_unit (uptr);
|
|
|
|
if ((cptr == NULL) || (*cptr == 0))
|
|
return SCPE_ARG;
|
|
if (uptr->flags & UNIT_ATT)
|
|
return SCPE_ALATT;
|
|
cap = (t_offset) get_uint (cptr, 10, sim_taddr_64? 2000000: 2000, &r);
|
|
if (r != SCPE_OK)
|
|
return SCPE_ARG;
|
|
uptr->capac = (t_addr)((cap * ((t_offset) 1000000))/((dptr->flags & DEV_SECTORS) ? 512 : 1));
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Show disk capacity */
|
|
|
|
t_stat sim_disk_show_capac (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
const char *cap_units = "B";
|
|
DEVICE *dptr = find_dev_from_unit (uptr);
|
|
t_offset capac = ((t_offset)uptr->capac)*((dptr->flags & DEV_SECTORS) ? 512 : 1);
|
|
|
|
if ((dptr->dwidth / dptr->aincr) == 16)
|
|
cap_units = "W";
|
|
if (capac) {
|
|
if (capac >= (t_offset) 1000000)
|
|
fprintf (st, "capacity=%dM%s", (uint32) (capac / ((t_offset) 1000000)), cap_units);
|
|
else if (uptr->capac >= (t_addr) 1000)
|
|
fprintf (st, "capacity=%dK%s", (uint32) (capac / ((t_offset) 1000)), cap_units);
|
|
else fprintf (st, "capacity=%d%s", (uint32) capac, cap_units);
|
|
}
|
|
else fprintf (st, "undefined capacity");
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Test for available */
|
|
|
|
t_bool sim_disk_isavailable (UNIT *uptr)
|
|
{
|
|
struct disk_context *ctx;
|
|
t_bool is_available;
|
|
|
|
if (!(uptr->flags & UNIT_ATT)) /* attached? */
|
|
return FALSE;
|
|
ctx = (struct disk_context *)uptr->disk_ctx;
|
|
switch (DK_GET_FMT (uptr)) { /* case on format */
|
|
case DKUF_F_STD: /* SIMH format */
|
|
is_available = TRUE;
|
|
break;
|
|
case DKUF_F_VHD: /* VHD format */
|
|
is_available = TRUE;
|
|
break;
|
|
case DKUF_F_RAW: /* Raw Physical Disk Access */
|
|
if (sim_os_disk_isavailable_raw (uptr->fileref)) {
|
|
if (ctx->media_removed) {
|
|
int32 saved_switches = sim_switches;
|
|
int32 saved_quiet = sim_quiet;
|
|
char *path = (char *)malloc (1 + strlen (uptr->filename));
|
|
|
|
sim_switches = 0;
|
|
sim_quiet = 1;
|
|
strcpy (path, uptr->filename);
|
|
sim_disk_attach (uptr, path, ctx->sector_size, ctx->xfer_element_size,
|
|
FALSE, ctx->dbit, NULL, 0, 0);
|
|
sim_quiet = saved_quiet;
|
|
sim_switches = saved_switches;
|
|
free (path);
|
|
ctx->media_removed = 0;
|
|
}
|
|
}
|
|
else
|
|
ctx->media_removed = 1;
|
|
is_available = !ctx->media_removed;
|
|
break;
|
|
default:
|
|
is_available = FALSE;
|
|
break;
|
|
}
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_disk_isavailable(unit=%d)=%s\n", (int)(uptr - ctx->dptr->units), is_available ? "true" : "false");
|
|
return is_available;
|
|
}
|
|
|
|
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_offset sim_disk_size (UNIT *uptr)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
t_offset physical_size, filesystem_size;
|
|
t_bool saved_quiet = sim_quiet;
|
|
|
|
if ((uptr->flags & UNIT_ATT) == 0)
|
|
return (t_offset)-1;
|
|
physical_size = ctx->container_size;
|
|
sim_quiet = TRUE;
|
|
filesystem_size = get_filesystem_size (uptr);
|
|
sim_quiet = saved_quiet;
|
|
if ((filesystem_size == (t_offset)-1) ||
|
|
(filesystem_size < physical_size))
|
|
return physical_size;
|
|
return filesystem_size;
|
|
}
|
|
|
|
/* 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";
|
|
sim_printf ("%s", msg);
|
|
return SCPE_NOFNC;
|
|
#else
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
pthread_attr_t attr;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_disk_set_async(unit=%d)\n", (int)(uptr - ctx->dptr->units));
|
|
|
|
ctx->asynch_io = sim_asynch_enabled;
|
|
ctx->asynch_io_latency = latency;
|
|
if (ctx->asynch_io) {
|
|
pthread_mutex_init (&ctx->io_lock, NULL);
|
|
pthread_cond_init (&ctx->io_cond, NULL);
|
|
pthread_cond_init (&ctx->io_done, NULL);
|
|
pthread_cond_init (&ctx->startup_cond, NULL);
|
|
pthread_attr_init(&attr);
|
|
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
|
|
pthread_mutex_lock (&ctx->io_lock);
|
|
pthread_create (&ctx->io_thread, &attr, _disk_io, (void *)uptr);
|
|
pthread_attr_destroy(&attr);
|
|
pthread_cond_wait (&ctx->startup_cond, &ctx->io_lock); /* Wait for thread to stabilize */
|
|
pthread_mutex_unlock (&ctx->io_lock);
|
|
pthread_cond_destroy (&ctx->startup_cond);
|
|
}
|
|
uptr->a_check_completion = _disk_completion_dispatch;
|
|
uptr->a_is_active = _disk_is_active;
|
|
uptr->cancel = _disk_cancel;
|
|
return SCPE_OK;
|
|
#endif
|
|
}
|
|
|
|
/* Disable asynchronous operation */
|
|
|
|
t_stat sim_disk_clr_async (UNIT *uptr)
|
|
{
|
|
#if !defined(SIM_ASYNCH_IO)
|
|
return SCPE_NOFNC;
|
|
#else
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
/* make sure device exists */
|
|
if (!ctx) return SCPE_UNATT;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_disk_clr_async(unit=%d)\n", (int)(uptr - ctx->dptr->units));
|
|
|
|
if (ctx->asynch_io) {
|
|
pthread_mutex_lock (&ctx->io_lock);
|
|
ctx->asynch_io = 0;
|
|
pthread_cond_signal (&ctx->io_cond);
|
|
pthread_mutex_unlock (&ctx->io_lock);
|
|
pthread_join (ctx->io_thread, NULL);
|
|
pthread_mutex_destroy (&ctx->io_lock);
|
|
pthread_cond_destroy (&ctx->io_cond);
|
|
pthread_cond_destroy (&ctx->io_done);
|
|
}
|
|
return SCPE_OK;
|
|
#endif
|
|
}
|
|
|
|
/* Read Sectors */
|
|
|
|
static t_stat _sim_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
|
|
{
|
|
t_offset da;
|
|
uint32 err, tbc;
|
|
size_t i;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "_sim_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
|
|
|
|
da = ((t_offset)lba) * ctx->sector_size;
|
|
tbc = sects * ctx->sector_size;
|
|
if (sectsread)
|
|
*sectsread = 0;
|
|
while (tbc) {
|
|
size_t sectbytes;
|
|
|
|
err = sim_fseeko (uptr->fileref, da, SEEK_SET); /* set pos */
|
|
if (err)
|
|
return SCPE_IOERR;
|
|
i = sim_fread (buf, 1, tbc, uptr->fileref);
|
|
if (i < tbc) /* fill */
|
|
memset (&buf[i], 0, tbc-i);
|
|
if (sectsread)
|
|
*sectsread += i / ctx->sector_size;
|
|
sectbytes = (i / ctx->sector_size) * ctx->sector_size;
|
|
err = ferror (uptr->fileref);
|
|
if (err)
|
|
return SCPE_IOERR;
|
|
tbc -= sectbytes;
|
|
if ((tbc == 0) || (i == 0))
|
|
return SCPE_OK;
|
|
da += sectbytes;
|
|
buf += sectbytes;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
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;
|
|
uint32 f = DK_GET_FMT (uptr);
|
|
t_seccnt sread = 0;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
|
|
|
|
if ((sects == 1) && /* Single sector reads */
|
|
(lba >= (uptr->capac*ctx->capac_factor)/(ctx->sector_size/((ctx->dptr->flags & DEV_SECTORS) ? 512 : 1)))) {/* 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)))) ||
|
|
(f == DKUF_F_STD) || (f == DKUF_F_VHD)) { /* or SIMH or VHD formats */
|
|
switch (f) { /* case on format */
|
|
case DKUF_F_STD: /* SIMH format */
|
|
r = _sim_disk_rdsect (uptr, lba, buf, &sread, sects);
|
|
break;
|
|
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;
|
|
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 in RAW mode */
|
|
size_t tbufsize = sects * ctx->sector_size + 2 * ctx->storage_sector_size;
|
|
uint8 *tbuf = (uint8*) malloc (tbufsize);
|
|
t_offset ssaddr = (lba * (t_offset)ctx->sector_size) & ~(t_offset)(ctx->storage_sector_size -1);
|
|
uint32 soffset = (uint32)((lba * (t_offset)ctx->sector_size) - ssaddr);
|
|
uint32 bytesread;
|
|
|
|
if (sectsread)
|
|
*sectsread = 0;
|
|
if (tbuf == NULL)
|
|
return SCPE_MEM;
|
|
r = sim_os_disk_read (uptr, ssaddr, tbuf, &bytesread, tbufsize & ~(ctx->storage_sector_size - 1));
|
|
sim_buf_swap_data (tbuf + soffset, ctx->xfer_element_size, (bytesread - soffset) / ctx->xfer_element_size);
|
|
memcpy (buf, tbuf + soffset, sects * ctx->sector_size);
|
|
if (sectsread) {
|
|
*sectsread = (bytesread - soffset) / ctx->sector_size;
|
|
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_offset da;
|
|
uint32 err, tbc;
|
|
size_t i;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "_sim_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
|
|
|
|
da = ((t_offset)lba) * ctx->sector_size;
|
|
tbc = sects * ctx->sector_size;
|
|
if (sectswritten)
|
|
*sectswritten = 0;
|
|
err = sim_fseeko (uptr->fileref, da, SEEK_SET); /* set pos */
|
|
if (err)
|
|
return SCPE_IOERR;
|
|
i = sim_fwrite (buf, ctx->xfer_element_size, tbc/ctx->xfer_element_size, uptr->fileref);
|
|
if (sectswritten)
|
|
*sectswritten += (t_seccnt)((i * ctx->xfer_element_size + ctx->sector_size - 1)/ctx->sector_size);
|
|
err = ferror (uptr->fileref);
|
|
if (err)
|
|
return SCPE_IOERR;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
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_unit (ctx->dbit, uptr, "sim_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
|
|
|
|
if (uptr->dynflags & UNIT_DISK_CHK) {
|
|
DEVICE *dptr = find_dev_from_unit (uptr);
|
|
uint32 capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (quadword: 8, word: 2, byte: 1) */
|
|
t_lba total_sectors = (t_lba)((uptr->capac*capac_factor)/(ctx->sector_size/((dptr->flags & DEV_SECTORS) ? 512 : 1)));
|
|
t_lba sect;
|
|
|
|
for (sect = 0; sect < sects; sect++) {
|
|
t_lba offset;
|
|
t_bool sect_error = FALSE;
|
|
|
|
for (offset = 0; offset < ctx->sector_size; offset += sizeof(uint32)) {
|
|
if (*((uint32 *)&buf[sect*ctx->sector_size + offset]) != (uint32)(lba + sect)) {
|
|
sect_error = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (sect_error) {
|
|
uint32 save_dctrl = dptr->dctrl;
|
|
FILE *save_sim_deb = sim_deb;
|
|
|
|
sim_printf ("\n%s: Write Address Verification Error on lbn %d(0x%X) of %d(0x%X).\n", sim_uname (uptr), (int)(lba+sect), (int)(lba+sect), (int)total_sectors, (int)total_sectors);
|
|
dptr->dctrl = 0xFFFFFFFF;
|
|
sim_deb = save_sim_deb ? save_sim_deb : stdout;
|
|
sim_disk_data_trace (uptr, buf+sect*ctx->sector_size, lba+sect, ctx->sector_size, "Found", TRUE, 1);
|
|
dptr->dctrl = save_dctrl;
|
|
sim_deb = save_sim_deb;
|
|
}
|
|
}
|
|
}
|
|
switch (f) { /* case on format */
|
|
case DKUF_F_STD: /* SIMH format */
|
|
return _sim_disk_wrsect (uptr, lba, buf, sectswritten, sects);
|
|
case DKUF_F_VHD: /* VHD format */
|
|
if (!sim_end && (ctx->xfer_element_size != sizeof (char))) {
|
|
tbuf = (uint8*) malloc (sects * ctx->sector_size);
|
|
if (NULL == tbuf)
|
|
return SCPE_MEM;
|
|
sim_buf_copy_swapped (tbuf, buf, ctx->xfer_element_size, (sects * ctx->sector_size) / ctx->xfer_element_size);
|
|
buf = tbuf;
|
|
}
|
|
r = sim_vhd_disk_wrsect (uptr, lba, buf, sectswritten, sects);
|
|
free (tbuf);
|
|
return r;
|
|
case DKUF_F_RAW: /* Raw Physical Disk Access */
|
|
break; /* handle below */
|
|
default:
|
|
return SCPE_NOFNC;
|
|
}
|
|
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))) {
|
|
tbuf = (uint8*) malloc (sects * ctx->sector_size);
|
|
if (NULL == tbuf)
|
|
return SCPE_MEM;
|
|
sim_buf_copy_swapped (tbuf, buf, ctx->xfer_element_size, (sects * ctx->sector_size) / ctx->xfer_element_size);
|
|
buf = tbuf;
|
|
}
|
|
|
|
r = sim_os_disk_wrsect (uptr, lba, buf, sectswritten, sects);
|
|
}
|
|
else { /* Unaligned and/or partial sector transfers in RAW mode */
|
|
size_t tbufsize = sects * ctx->sector_size + 2 * ctx->storage_sector_size;
|
|
t_offset ssaddr = (lba * (t_offset)ctx->sector_size) & ~(t_offset)(ctx->storage_sector_size -1);
|
|
t_offset sladdr = ((lba + sects) * (t_offset)ctx->sector_size) & ~(t_offset)(ctx->storage_sector_size -1);
|
|
uint32 soffset = (uint32)((lba * (t_offset)ctx->sector_size) - ssaddr);
|
|
uint32 byteswritten;
|
|
|
|
tbuf = (uint8*) malloc (tbufsize);
|
|
if (sectswritten)
|
|
*sectswritten = 0;
|
|
if (tbuf == NULL)
|
|
return SCPE_MEM;
|
|
/* Partial Sector writes require a read-modify-write sequence for the partial sectors */
|
|
if (soffset)
|
|
sim_os_disk_read (uptr, ssaddr, tbuf, NULL, ctx->storage_sector_size);
|
|
sim_os_disk_read (uptr, sladdr, tbuf + (size_t)(sladdr - ssaddr), NULL, ctx->storage_sector_size);
|
|
sim_buf_copy_swapped (tbuf + soffset,
|
|
buf, ctx->xfer_element_size, (sects * ctx->sector_size) / ctx->xfer_element_size);
|
|
r = sim_os_disk_write (uptr, ssaddr, tbuf, &byteswritten, (soffset + (sects * ctx->sector_size) + ctx->storage_sector_size - 1) & ~(ctx->storage_sector_size - 1));
|
|
if (sectswritten) {
|
|
*sectswritten = byteswritten / ctx->sector_size;
|
|
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)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
switch (DK_GET_FMT (uptr)) { /* case on format */
|
|
case DKUF_F_STD: /* Simh */
|
|
case DKUF_F_VHD: /* VHD format */
|
|
ctx->media_removed = 1;
|
|
return sim_disk_detach (uptr);
|
|
case DKUF_F_RAW: /* Raw Physical Disk Access */
|
|
ctx->media_removed = 1;
|
|
return sim_os_disk_unload_raw (uptr->fileref); /* remove/eject disk */
|
|
break;
|
|
default:
|
|
return SCPE_NOFNC;
|
|
}
|
|
}
|
|
|
|
/*
|
|
This routine is called when the simulator stops and any time
|
|
the asynch mode is changed (enabled or disabled)
|
|
*/
|
|
static void _sim_disk_io_flush (UNIT *uptr)
|
|
{
|
|
uint32 f = DK_GET_FMT (uptr);
|
|
|
|
#if defined (SIM_ASYNCH_IO)
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
sim_disk_clr_async (uptr);
|
|
if (sim_asynch_enabled)
|
|
sim_disk_set_async (uptr, ctx->asynch_io_latency);
|
|
#endif
|
|
switch (f) { /* case on format */
|
|
case DKUF_F_STD: /* Simh */
|
|
fflush (uptr->fileref);
|
|
break;
|
|
case DKUF_F_VHD: /* Virtual Disk */
|
|
sim_vhd_disk_flush (uptr->fileref);
|
|
break;
|
|
case DKUF_F_RAW: /* Physical */
|
|
sim_os_disk_flush_raw (uptr->fileref);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static t_stat _err_return (UNIT *uptr, t_stat stat)
|
|
{
|
|
free (uptr->filename);
|
|
uptr->filename = NULL;
|
|
free (uptr->disk_ctx);
|
|
uptr->disk_ctx = NULL;
|
|
return stat;
|
|
}
|
|
|
|
#pragma pack(push,1)
|
|
typedef struct _ODS1_HomeBlock
|
|
{
|
|
uint16 hm1_w_ibmapsize;
|
|
uint32 hm1_l_ibmaplbn;
|
|
uint16 hm1_w_maxfiles;
|
|
uint16 hm1_w_cluster;
|
|
uint16 hm1_w_devtype;
|
|
uint16 hm1_w_structlev;
|
|
#define HM1_C_LEVEL1 0401
|
|
#define HM1_C_LEVEL2 0402
|
|
uint8 hm1_t_volname[12];
|
|
uint8 hm1_b_fill_1[4];
|
|
uint16 hm1_w_volowner;
|
|
uint16 hm1_w_protect;
|
|
uint16 hm1_w_volchar;
|
|
uint16 hm1_w_fileprot;
|
|
uint8 hm1_b_fill_2[6];
|
|
uint8 hm1_b_window;
|
|
uint8 hm1_b_extend;
|
|
uint8 hm1_b_lru_lim;
|
|
uint8 hm1_b_fill_3[11];
|
|
uint16 hm1_w_checksum1;
|
|
uint8 hm1_t_credate[14];
|
|
uint8 hm1_b_fill_4[382];
|
|
uint32 hm1_l_serialnum;
|
|
uint8 hm1_b_fill_5[12];
|
|
uint8 hm1_t_volname2[12];
|
|
uint8 hm1_t_ownername[12];
|
|
uint8 hm1_t_format[12];
|
|
uint8 hm1_t_fill_6[2];
|
|
uint16 hm1_w_checksum2;
|
|
} ODS1_HomeBlock;
|
|
|
|
typedef struct _ODS2_HomeBlock
|
|
{
|
|
uint32 hm2_l_homelbn;
|
|
uint32 hm2_l_alhomelbn;
|
|
uint32 hm2_l_altidxlbn;
|
|
uint8 hm2_b_strucver;
|
|
uint8 hm2_b_struclev;
|
|
uint16 hm2_w_cluster;
|
|
uint16 hm2_w_homevbn;
|
|
uint16 hm2_w_alhomevbn;
|
|
uint16 hm2_w_altidxvbn;
|
|
uint16 hm2_w_ibmapvbn;
|
|
uint32 hm2_l_ibmaplbn;
|
|
uint32 hm2_l_maxfiles;
|
|
uint16 hm2_w_ibmapsize;
|
|
uint16 hm2_w_resfiles;
|
|
uint16 hm2_w_devtype;
|
|
uint16 hm2_w_rvn;
|
|
uint16 hm2_w_setcount;
|
|
uint16 hm2_w_volchar;
|
|
uint32 hm2_l_volowner;
|
|
uint32 hm2_l_reserved;
|
|
uint16 hm2_w_protect;
|
|
uint16 hm2_w_fileprot;
|
|
uint16 hm2_w_reserved;
|
|
uint16 hm2_w_checksum1;
|
|
uint32 hm2_q_credate[2];
|
|
uint8 hm2_b_window;
|
|
uint8 hm2_b_lru_lim;
|
|
uint16 hm2_w_extend;
|
|
uint32 hm2_q_retainmin[2];
|
|
uint32 hm2_q_retainmax[2];
|
|
uint32 hm2_q_revdate[2];
|
|
uint8 hm2_r_min_class[20];
|
|
uint8 hm2_r_max_class[20];
|
|
uint8 hm2_r_reserved[320];
|
|
uint32 hm2_l_serialnum;
|
|
uint8 hm2_t_strucname[12];
|
|
uint8 hm2_t_volname[12];
|
|
uint8 hm2_t_ownername[12];
|
|
uint8 hm2_t_format[12];
|
|
uint16 hm2_w_reserved2;
|
|
uint16 hm2_w_checksum2;
|
|
} ODS2_HomeBlock;
|
|
|
|
typedef struct _ODS1_FileHeader
|
|
{
|
|
uint8 fh1_b_idoffset;
|
|
uint8 fh1_b_mpoffset;
|
|
uint16 fh1_w_fid_num;
|
|
uint16 fh1_w_fid_seq;
|
|
uint16 fh1_w_struclev;
|
|
uint16 fh1_w_fileowner;
|
|
uint16 fh1_w_fileprot;
|
|
uint16 fh1_w_filechar;
|
|
uint16 fh1_w_recattr;
|
|
uint8 fh1_b_fill_1[494];
|
|
uint16 fh1_w_checksum;
|
|
} ODS1_FileHeader;
|
|
|
|
typedef struct _ODS2_FileHeader
|
|
{
|
|
uint8 fh2_b_idoffset;
|
|
uint8 fh2_b_mpoffset;
|
|
uint8 fh2_b_acoffset;
|
|
uint8 fh2_b_rsoffset;
|
|
uint16 fh2_w_seg_num;
|
|
uint16 fh2_w_structlev;
|
|
uint16 fh2_w_fid[3];
|
|
uint16 fh2_w_ext_fid[3];
|
|
uint16 fh2_w_recattr[16];
|
|
uint32 fh2_l_filechar;
|
|
uint16 fh2_w_remaining[228];
|
|
} ODS2_FileHeader;
|
|
|
|
typedef union _ODS2_Retreval
|
|
{
|
|
struct
|
|
{
|
|
unsigned fm2___fill : 14; /* type specific data */
|
|
unsigned fm2_v_format : 2; /* format type code */
|
|
} fm2_r_word0_bits;
|
|
struct
|
|
{
|
|
unsigned fm2_v_exact : 1; /* exact placement specified */
|
|
unsigned fm2_v_oncyl : 1; /* on cylinder allocation desired */
|
|
unsigned fm2___fill : 10;
|
|
unsigned fm2_v_lbn : 1; /* use LBN of next map pointer */
|
|
unsigned fm2_v_rvn : 1; /* place on specified RVN */
|
|
unsigned fm2_v_format0 : 2;
|
|
} fm2_r_map_bits0;
|
|
struct
|
|
{
|
|
unsigned fm2_b_count1 : 8; /* low byte described below */
|
|
unsigned fm2_v_highlbn1 : 6; /* high order LBN */
|
|
unsigned fm2_v_format1 : 2;
|
|
unsigned fm2_w_lowlbn1 : 16; /* low order LBN */
|
|
} fm2_r_map_bits1;
|
|
struct
|
|
{
|
|
struct
|
|
{
|
|
unsigned fm2_v_count2 : 14; /* count field */
|
|
unsigned fm2_v_format2 : 2;
|
|
unsigned fm2_l_lowlbn2 : 16; /* low order LBN */
|
|
} fm2_r_map2_long0;
|
|
uint16 fm2_l_highlbn2; /* high order LBN */
|
|
} fm2_r_map_bits2;
|
|
struct
|
|
{
|
|
struct
|
|
{
|
|
unsigned fm2_v_highcount3 : 14; /* low order count field */
|
|
unsigned fm2_v_format3 : 2;
|
|
unsigned fm2_w_lowcount3 : 16; /* high order count field */
|
|
} fm2_r_map3_long0;
|
|
uint32 fm2_l_lbn3;
|
|
} fm2_r_map_bits3;
|
|
} ODS2_Retreval;
|
|
|
|
typedef struct _ODS1_Retreval
|
|
{
|
|
uint8 fm1_b_ex_segnum;
|
|
uint8 fm1_b_ex_rvn;
|
|
uint16 fm1_w_ex_filnum;
|
|
uint16 fm1_w_ex_filseq;
|
|
uint8 fm1_b_countsize;
|
|
uint8 fm1_b_lbnsize;
|
|
uint8 fm1_b_inuse;
|
|
uint8 fm1_b_avail;
|
|
union {
|
|
struct {
|
|
uint8 fm1_b_highlbn;
|
|
uint8 fm1_b_count;
|
|
uint16 fm1_w_lowlbn;
|
|
} fm1_s_fm1def1;
|
|
struct {
|
|
uint8 fm1_b_highlbn;
|
|
uint8 fm1_b_count;
|
|
uint16 fm1_w_lowlbn;
|
|
} fm1_s_fm1def2;
|
|
} fm1_pointers[4];
|
|
} ODS1_Retreval;
|
|
|
|
typedef struct _ODS1_StorageControlBlock
|
|
{
|
|
uint8 scb_b_unused[3];
|
|
uint8 scb_b_bitmapblks;
|
|
struct _bitmapblk {
|
|
uint16 scb_w_freeblks;
|
|
uint16 scb_w_freeptr;
|
|
} scb_r_blocks[1];
|
|
} ODS1_SCB;
|
|
|
|
|
|
typedef struct _ODS2_StorageControlBlock
|
|
{
|
|
uint8 scb_b_strucver; /* 1 */
|
|
uint8 scb_b_struclev; /* 2 */
|
|
uint16 scb_w_cluster;
|
|
uint32 scb_l_volsize;
|
|
uint32 scb_l_blksize;
|
|
uint32 scb_l_sectors;
|
|
uint32 scb_l_tracks;
|
|
uint32 scb_l_cylinder;
|
|
uint32 scb_l_status;
|
|
uint32 scb_l_status2;
|
|
uint16 scb_w_writecnt;
|
|
uint8 scb_t_volockname[12];
|
|
uint32 scb_q_mounttime[2];
|
|
uint16 scb_w_backrev;
|
|
uint32 scb_q_genernum[2];
|
|
uint8 scb_b_reserved[446];
|
|
uint16 scb_w_checksum;
|
|
} ODS2_SCB;
|
|
#pragma pack(pop)
|
|
|
|
static uint16
|
|
ODSChecksum (void *Buffer, uint16 WordCount)
|
|
{
|
|
int i;
|
|
uint16 CheckSum = 0;
|
|
uint16 *Buf = (uint16 *)Buffer;
|
|
|
|
for (i=0; i<WordCount; i++)
|
|
CheckSum += Buf[i];
|
|
return CheckSum;
|
|
}
|
|
|
|
|
|
static t_offset get_ods2_filesystem_size (UNIT *uptr)
|
|
{
|
|
DEVICE *dptr;
|
|
t_addr saved_capac;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
t_offset temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */
|
|
ODS2_HomeBlock Home;
|
|
ODS2_FileHeader Header;
|
|
ODS2_Retreval *Retr;
|
|
ODS2_SCB Scb;
|
|
uint16 CheckSum1, CheckSum2;
|
|
uint32 ScbLbn = 0;
|
|
t_offset ret_val = (t_offset)-1;
|
|
t_seccnt sects_read;
|
|
|
|
if ((dptr = find_dev_from_unit (uptr)) == NULL)
|
|
return ret_val;
|
|
saved_capac = uptr->capac;
|
|
uptr->capac = (t_addr)temp_capac;
|
|
if ((sim_disk_rdsect (uptr, 512 / ctx->sector_size, (uint8 *)&Home, §s_read, sizeof (Home) / ctx->sector_size)) ||
|
|
(sects_read != (sizeof (Home) / ctx->sector_size)))
|
|
goto Return_Cleanup;
|
|
CheckSum1 = ODSChecksum (&Home, (uint16)((((char *)&Home.hm2_w_checksum1)-((char *)&Home.hm2_l_homelbn))/2));
|
|
CheckSum2 = ODSChecksum (&Home, (uint16)((((char *)&Home.hm2_w_checksum2)-((char *)&Home.hm2_l_homelbn))/2));
|
|
if ((Home.hm2_l_homelbn == 0) ||
|
|
(Home.hm2_l_alhomelbn == 0) ||
|
|
(Home.hm2_l_altidxlbn == 0) ||
|
|
((Home.hm2_b_struclev != 2) && (Home.hm2_b_struclev != 5)) ||
|
|
(Home.hm2_b_strucver == 0) ||
|
|
(Home.hm2_w_cluster == 0) ||
|
|
(Home.hm2_w_homevbn == 0) ||
|
|
(Home.hm2_w_alhomevbn == 0) ||
|
|
(Home.hm2_w_ibmapvbn == 0) ||
|
|
(Home.hm2_l_ibmaplbn == 0) ||
|
|
(Home.hm2_w_resfiles >= Home.hm2_l_maxfiles) ||
|
|
(Home.hm2_w_ibmapsize == 0) ||
|
|
(Home.hm2_w_resfiles < 5) ||
|
|
(Home.hm2_w_checksum1 != CheckSum1) ||
|
|
(Home.hm2_w_checksum2 != CheckSum2))
|
|
goto Return_Cleanup;
|
|
if ((sim_disk_rdsect (uptr, (Home.hm2_l_ibmaplbn+Home.hm2_w_ibmapsize+1) * (512 / ctx->sector_size),
|
|
(uint8 *)&Header, §s_read, sizeof (Header) / ctx->sector_size)) ||
|
|
(sects_read != (sizeof (Header) / ctx->sector_size)))
|
|
goto Return_Cleanup;
|
|
CheckSum1 = ODSChecksum (&Header, 255);
|
|
if (CheckSum1 != *(((uint16 *)&Header)+255)) /* Verify Checksum on BITMAP.SYS file header */
|
|
goto Return_Cleanup;
|
|
Retr = (ODS2_Retreval *)(((uint16*)(&Header))+Header.fh2_b_mpoffset);
|
|
/* The BitMap File has a single extent, which may be preceeded by a placement descriptor */
|
|
if (Retr->fm2_r_word0_bits.fm2_v_format == 0)
|
|
Retr = (ODS2_Retreval *)(((uint16 *)Retr)+1); /* skip placement descriptor */
|
|
switch (Retr->fm2_r_word0_bits.fm2_v_format)
|
|
{
|
|
case 1:
|
|
ScbLbn = (Retr->fm2_r_map_bits1.fm2_v_highlbn1<<16)+Retr->fm2_r_map_bits1.fm2_w_lowlbn1;
|
|
break;
|
|
case 2:
|
|
ScbLbn = (Retr->fm2_r_map_bits2.fm2_l_highlbn2<<16)+Retr->fm2_r_map_bits2.fm2_r_map2_long0.fm2_l_lowlbn2;
|
|
break;
|
|
case 3:
|
|
ScbLbn = Retr->fm2_r_map_bits3.fm2_l_lbn3;
|
|
break;
|
|
}
|
|
Retr = (ODS2_Retreval *)(((uint16 *)Retr)+Retr->fm2_r_word0_bits.fm2_v_format+1);
|
|
if ((sim_disk_rdsect (uptr, ScbLbn * (512 / ctx->sector_size), (uint8 *)&Scb, §s_read, sizeof (Scb) / ctx->sector_size)) ||
|
|
(sects_read != (sizeof (Scb) / ctx->sector_size)))
|
|
goto Return_Cleanup;
|
|
CheckSum1 = ODSChecksum (&Scb, 255);
|
|
if (CheckSum1 != *(((uint16 *)&Scb)+255)) /* Verify Checksum on Storage Control Block */
|
|
goto Return_Cleanup;
|
|
if ((Scb.scb_w_cluster != Home.hm2_w_cluster) ||
|
|
(Scb.scb_b_strucver != Home.hm2_b_strucver) ||
|
|
(Scb.scb_b_struclev != Home.hm2_b_struclev))
|
|
goto Return_Cleanup;
|
|
sim_messagef (SCPE_OK, "%s: '%s' Contains ODS%d File system\n", sim_uname (uptr), uptr->filename, Home.hm2_b_struclev);
|
|
sim_messagef (SCPE_OK, "%s: Volume Name: %12.12s ", sim_uname (uptr), Home.hm2_t_volname);
|
|
sim_messagef (SCPE_OK, "Format: %12.12s ", Home.hm2_t_format);
|
|
sim_messagef (SCPE_OK, "Sectors In Volume: %u\n", Scb.scb_l_volsize);
|
|
ret_val = ((t_offset)Scb.scb_l_volsize) * 512;
|
|
|
|
Return_Cleanup:
|
|
uptr->capac = saved_capac;
|
|
return ret_val;
|
|
}
|
|
|
|
static t_offset get_ods1_filesystem_size (UNIT *uptr)
|
|
{
|
|
DEVICE *dptr;
|
|
t_addr saved_capac;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
t_addr temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */
|
|
ODS1_HomeBlock Home;
|
|
ODS1_FileHeader Header;
|
|
ODS1_Retreval *Retr;
|
|
uint8 scb_buf[512];
|
|
ODS1_SCB *Scb = (ODS1_SCB *)scb_buf;
|
|
uint16 CheckSum1, CheckSum2;
|
|
uint32 ScbLbn;
|
|
t_offset ret_val = (t_offset)-1;
|
|
t_seccnt sects_read;
|
|
|
|
if ((dptr = find_dev_from_unit (uptr)) == NULL)
|
|
return ret_val;
|
|
saved_capac = uptr->capac;
|
|
uptr->capac = temp_capac;
|
|
if ((sim_disk_rdsect (uptr, 512 / ctx->sector_size, (uint8 *)&Home, §s_read, sizeof (Home) / ctx->sector_size)) ||
|
|
(sects_read != (sizeof (Home) / ctx->sector_size)))
|
|
goto Return_Cleanup;
|
|
CheckSum1 = ODSChecksum (&Home, (uint16)((((char *)&Home.hm1_w_checksum1)-((char *)&Home.hm1_w_ibmapsize))/2));
|
|
CheckSum2 = ODSChecksum (&Home, (uint16)((((char *)&Home.hm1_w_checksum2)-((char *)&Home.hm1_w_ibmapsize))/2));
|
|
if ((Home.hm1_w_ibmapsize == 0) ||
|
|
(Home.hm1_l_ibmaplbn == 0) ||
|
|
(Home.hm1_w_maxfiles == 0) ||
|
|
(Home.hm1_w_cluster != 1) ||
|
|
((Home.hm1_w_structlev != HM1_C_LEVEL1) && (Home.hm1_w_structlev != HM1_C_LEVEL2)) ||
|
|
(Home.hm1_l_ibmaplbn == 0) ||
|
|
(Home.hm1_w_checksum1 != CheckSum1) ||
|
|
(Home.hm1_w_checksum2 != CheckSum2))
|
|
goto Return_Cleanup;
|
|
if ((sim_disk_rdsect (uptr, (((Home.hm1_l_ibmaplbn << 16) + ((Home.hm1_l_ibmaplbn >> 16) & 0xFFFF)) + Home.hm1_w_ibmapsize + 1) * (512 / ctx->sector_size),
|
|
(uint8 *)&Header, §s_read, sizeof (Header) / ctx->sector_size)) ||
|
|
(sects_read != (sizeof (Header) / ctx->sector_size)))
|
|
goto Return_Cleanup;
|
|
CheckSum1 = ODSChecksum (&Header, 255);
|
|
if (CheckSum1 != *(((uint16 *)&Header)+255)) /* Verify Checksum on BITMAP.SYS file header */
|
|
goto Return_Cleanup;
|
|
|
|
Retr = (ODS1_Retreval *)(((uint16*)(&Header))+Header.fh1_b_mpoffset);
|
|
ScbLbn = (Retr->fm1_pointers[0].fm1_s_fm1def1.fm1_b_highlbn<<16)+Retr->fm1_pointers[0].fm1_s_fm1def1.fm1_w_lowlbn;
|
|
if ((sim_disk_rdsect (uptr, ScbLbn * (512 / ctx->sector_size), (uint8 *)Scb, §s_read, 512 / ctx->sector_size)) ||
|
|
(sects_read != (512 / ctx->sector_size)))
|
|
goto Return_Cleanup;
|
|
if (Scb->scb_b_bitmapblks < 127)
|
|
ret_val = (((t_offset)Scb->scb_r_blocks[Scb->scb_b_bitmapblks].scb_w_freeblks << 16) + Scb->scb_r_blocks[Scb->scb_b_bitmapblks].scb_w_freeptr) * 512;
|
|
else
|
|
ret_val = (((t_offset)Scb->scb_r_blocks[0].scb_w_freeblks << 16) + Scb->scb_r_blocks[0].scb_w_freeptr) * 512;
|
|
sim_messagef (SCPE_OK, "%s: '%s' Contains an ODS1 File system\n", sim_uname (uptr), uptr->filename);
|
|
sim_messagef (SCPE_OK, "%s: Volume Name: %12.12s ", sim_uname (uptr), Home.hm1_t_volname);
|
|
sim_messagef (SCPE_OK, "Format: %12.12s ", Home.hm1_t_format);
|
|
sim_messagef (SCPE_OK, "Sectors In Volume: %u\n", (uint32)(ret_val / 512));
|
|
|
|
Return_Cleanup:
|
|
uptr->capac = saved_capac;
|
|
return ret_val;
|
|
}
|
|
|
|
typedef struct ultrix_disklabel {
|
|
uint32 pt_magic; /* magic no. indicating part. info exits */
|
|
uint32 pt_valid; /* set by driver if pt is current */
|
|
struct pt_info {
|
|
uint32 pi_nblocks; /* no. of sectors */
|
|
uint32 pi_blkoff; /* block offset for start */
|
|
} pt_part[8];
|
|
} ultrix_disklabel;
|
|
|
|
#define PT_MAGIC 0x032957 /* Partition magic number */
|
|
#define PT_VALID 1 /* Indicates if struct is valid */
|
|
|
|
static t_offset get_ultrix_filesystem_size (UNIT *uptr)
|
|
{
|
|
DEVICE *dptr;
|
|
t_addr saved_capac;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
t_addr temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */
|
|
uint8 sector_buf[512];
|
|
ultrix_disklabel *Label = (ultrix_disklabel *)(sector_buf + sizeof (sector_buf) - sizeof (ultrix_disklabel));
|
|
t_offset ret_val = (t_offset)-1;
|
|
int i;
|
|
uint32 max_lbn = 0, max_lbn_partnum = 0;
|
|
t_seccnt sects_read;
|
|
|
|
if ((dptr = find_dev_from_unit (uptr)) == NULL)
|
|
return ret_val;
|
|
saved_capac = uptr->capac;
|
|
uptr->capac = temp_capac;
|
|
if ((sim_disk_rdsect (uptr, 31 * (512 / ctx->sector_size), sector_buf, §s_read, 512 / ctx->sector_size)) ||
|
|
(sects_read != (512 / ctx->sector_size)))
|
|
goto Return_Cleanup;
|
|
|
|
if ((Label->pt_magic != PT_MAGIC) ||
|
|
(Label->pt_valid != PT_VALID))
|
|
goto Return_Cleanup;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
uint32 end_lbn = Label->pt_part[i].pi_blkoff + Label->pt_part[i].pi_nblocks;
|
|
if (end_lbn > max_lbn) {
|
|
max_lbn = end_lbn;
|
|
max_lbn_partnum = i;
|
|
}
|
|
}
|
|
sim_messagef (SCPE_OK, "%s: '%s' Contains Ultrix partitions\n", sim_uname (uptr), uptr->filename);
|
|
sim_messagef (SCPE_OK, "Partition with highest sector: %c, Sectors On Disk: %u\n", 'a' + max_lbn_partnum, max_lbn);
|
|
ret_val = ((t_offset)max_lbn) * 512;
|
|
|
|
Return_Cleanup:
|
|
uptr->capac = saved_capac;
|
|
return ret_val;
|
|
}
|
|
|
|
#pragma pack(push,1)
|
|
/*
|
|
* The first logical block of device cluster 1 is either:
|
|
* 1. MFD label entry (RSTS versions through 7.x)
|
|
* 2. Disk Pack label (RSTS version 8.0 and later)
|
|
*/
|
|
typedef struct _RSTS_MFDLABEL {
|
|
uint16 ml_ulnk;
|
|
uint16 ml_mbm1;
|
|
uint16 ml_reserved1;
|
|
uint16 ml_reserved2;
|
|
uint16 ml_pcs;
|
|
uint16 ml_pstat;
|
|
uint16 ml_packid[2];
|
|
} RSTS_MFDLABEL;
|
|
|
|
typedef struct _RSTS_PACKLABEL {
|
|
uint16 pk_mb01;
|
|
uint16 pk_mbm1;
|
|
uint16 pk_mdcn;
|
|
uint16 pk_plvl;
|
|
#define PK_LVL0 0000
|
|
#define PK_LVL11 0401
|
|
#define PK_LVL12 0402
|
|
uint16 pk_ppcs;
|
|
uint16 pk_pstat;
|
|
#define PK_UC_NEW 0020000
|
|
uint16 pk_packid[2];
|
|
uint16 pk_tapgvn[2];
|
|
uint16 pk_bckdat;
|
|
uint16 pk_bcktim;
|
|
} RSTS_PACKLABEL;
|
|
|
|
typedef union _RSTS_ROOT {
|
|
RSTS_MFDLABEL rt_mfd;
|
|
RSTS_PACKLABEL rt_pack;
|
|
uint8 rt_block[512];
|
|
} RSTS_ROOT;
|
|
|
|
typedef struct _RSTS_MFDBLOCKETTE {
|
|
uint16 mb_ulnk;
|
|
uint16 mb_mbm1;
|
|
uint16 mb_reserved1;
|
|
uint16 mb_reserved2;
|
|
uint16 mb_reserved3;
|
|
uint16 mb_malnk;
|
|
uint16 mb_lppn;
|
|
uint16 mb_lid;
|
|
#define MB_ID 0051064
|
|
} RSTS_MFDBLOCKETTE;
|
|
#define IS_VALID_RSTS_MFD(b) \
|
|
((((b)->mb_ulnk == 0) || ((b)->mb_ulnk == 1)) && \
|
|
((b)->mb_mbm1 == 0177777) && \
|
|
((b)->mb_reserved1 == 0) && \
|
|
((b)->mb_reserved2 == 0) && \
|
|
((b)->mb_reserved3 == 0) && \
|
|
((b)->mb_lppn == 0177777) && \
|
|
((b)->mb_lid == MB_ID))
|
|
|
|
typedef struct _RSTS_GFDBLOCKETTE {
|
|
uint16 gb_ulnk;
|
|
uint16 gb_mbm1;
|
|
uint16 gb_reserved1;
|
|
uint16 gb_reserved2;
|
|
uint16 gb_reserved3;
|
|
uint16 gb_reserved4;
|
|
uint16 gb_lppn;
|
|
uint16 gb_lid;
|
|
#define GB_ID 0026264
|
|
} RSTS_GFDBLOCKETTE;
|
|
#define IS_VALID_RSTS_GFD(b, g) \
|
|
((((b)->gb_ulnk == 0) || ((b)->gb_ulnk == 1)) && \
|
|
((b)->gb_mbm1 == 0177777) && \
|
|
((b)->gb_reserved1 == 0) && \
|
|
((b)->gb_reserved2 == 0) && \
|
|
((b)->gb_reserved3 == 0) && \
|
|
((b)->gb_reserved4 == 0) && \
|
|
((b)->gb_lppn == (((g) << 8) | 0377)) && \
|
|
((b)->gb_lid == GB_ID))
|
|
|
|
typedef struct _RSTS_UFDBLOCKETTE {
|
|
uint16 ub_ulnk;
|
|
uint16 ub_mbm1;
|
|
uint16 ub_reserved1;
|
|
uint16 ub_reserved2;
|
|
uint16 ub_reserved3;
|
|
uint16 ub_reserved4;
|
|
uint16 ub_lppn;
|
|
uint16 ub_lid;
|
|
#define UB_ID 0102064
|
|
} RSTS_UFDBLOCKETTE;
|
|
#define IS_VALID_RSTS_UFD(b, g, u) \
|
|
(((b)->ub_mbm1 == 0177777) && \
|
|
((b)->ub_reserved1 == 0) && \
|
|
((b)->ub_reserved2 == 0) && \
|
|
((b)->ub_reserved3 == 0) && \
|
|
((b)->ub_reserved4 == 0) && \
|
|
((b)->ub_lppn == (((g) << 8) | (u))) && \
|
|
((b)->ub_lid == UB_ID))
|
|
|
|
typedef struct _RSTS_UNAME {
|
|
uint16 un_ulnk;
|
|
uint16 un_unam;
|
|
uint16 un_reserved1;
|
|
uint16 un_reserved2;
|
|
uint16 un_ustat;
|
|
uint16 un_uacnt;
|
|
uint16 un_uaa;
|
|
uint16 un_uar;
|
|
} RSTS_UNAME;
|
|
|
|
typedef struct _RSTS_FNAME {
|
|
uint16 fn_ulnk;
|
|
uint16 fn_unam[3];
|
|
uint16 fn_ustat;
|
|
uint16 fn_uacnt;
|
|
uint16 fn_uaa;
|
|
uint16 fn_uar;
|
|
} RSTS_FNAME;
|
|
|
|
typedef struct _RSTS_ACNT {
|
|
uint16 ac_ulnk;
|
|
uint16 ac_udla;
|
|
uint16 ac_usiz;
|
|
uint16 ac_udc;
|
|
uint16 ac_utc;
|
|
uint16 ac_urts[2];
|
|
uint16 ac_uclus;
|
|
} RSTS_ACNT;
|
|
|
|
typedef struct _RSTS_RETR {
|
|
uint16 rt_ulnk;
|
|
uint16 rt_uent[7];
|
|
#define RT_ENTRIES 7
|
|
} RSTS_RETR;
|
|
|
|
typedef struct _RSTS_DCMAP {
|
|
uint16 dc_clus;
|
|
#define DC_MASK 0077777
|
|
uint16 dc_map[7];
|
|
} RSTS_DCMAP;
|
|
|
|
/*
|
|
* Directory link definitions
|
|
*/
|
|
#define DL_USE 0000001
|
|
#define DL_BAD 0000002
|
|
#define DL_CHE 0000004
|
|
#define DL_CLN 0000010
|
|
#define DL_ENO 0000760
|
|
#define DL_CLO 0007000
|
|
#define DL_BLO 0170000
|
|
|
|
#define DLSH_ENO 4
|
|
#define DLSH_CLO 9
|
|
#define DLSH_BLO 12
|
|
|
|
#define BLOCKETTE_SZ (8 * sizeof(uint16))
|
|
#define MAP_OFFSET (31 * BLOCKETTE_SZ)
|
|
|
|
#define SATT0 0073374
|
|
#define SATT1 0076400
|
|
#define SATT2 0075273
|
|
|
|
#pragma pack(pop)
|
|
|
|
typedef struct _rstsContext {
|
|
UNIT *uptr;
|
|
int dcshift;
|
|
int pcs;
|
|
char packid[8];
|
|
t_seccnt sects;
|
|
RSTS_DCMAP map;
|
|
} rstsContext;
|
|
|
|
static char rad50[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.%0123456789";
|
|
|
|
static void r50Asc(uint16 val, char *buf)
|
|
{
|
|
buf[2] = rad50[val % 050];
|
|
val /= 050;
|
|
buf[1] = rad50[val % 050];
|
|
buf[0] = rad50[val / 050];
|
|
}
|
|
|
|
static t_stat rstsValidateClusterSize(uint16 size, uint16 minSize)
|
|
{
|
|
int i;
|
|
|
|
/*
|
|
* Check that the cluster size is a power of 2 and greater than or equal
|
|
* to some location dependent value.
|
|
*/
|
|
if (size >= minSize)
|
|
for (i = 0; i < 16; i++)
|
|
if (size == (1 << i))
|
|
return SCPE_OK;
|
|
|
|
return SCPE_IOERR;
|
|
}
|
|
|
|
static t_stat rstsReadBlock(rstsContext *context, uint16 cluster, uint16 block, void *buf)
|
|
{
|
|
t_lba blk = (cluster << context->dcshift) + block;
|
|
t_seccnt sects_read;
|
|
|
|
if ((sim_disk_rdsect(context->uptr, blk * context->sects, (uint8 *)buf, §s_read, context->sects) == SCPE_OK) &&
|
|
(sects_read == context->sects))
|
|
return SCPE_OK;
|
|
|
|
return SCPE_IOERR;
|
|
}
|
|
|
|
static t_stat rstsReadBlockette(rstsContext *context, uint16 link, void *buf)
|
|
{
|
|
uint16 block = (link & DL_BLO) >> DLSH_BLO;
|
|
uint16 dcn = (link & DL_CLO) >> DLSH_CLO;
|
|
uint16 blockette = (link & DL_ENO) >> DLSH_ENO;
|
|
uint8 temp[512];
|
|
|
|
if ((dcn != 7) && (blockette != 31) &&
|
|
(block <= (context->map.dc_clus & DC_MASK))) {
|
|
if (rstsReadBlock(context, context->map.dc_map[dcn], block, temp) == SCPE_OK) {
|
|
memcpy(buf, &temp[blockette * BLOCKETTE_SZ], BLOCKETTE_SZ);
|
|
return SCPE_OK;
|
|
}
|
|
}
|
|
return SCPE_IOERR;
|
|
}
|
|
|
|
static t_stat rstsFind01UFD(rstsContext *context, uint16 *ufd, uint16 *level)
|
|
{
|
|
uint16 dcs = 1 << context->dcshift;
|
|
RSTS_ROOT root;
|
|
uint16 buf[256];
|
|
|
|
if (rstsReadBlock(context, 1, 0, &root) == SCPE_OK) {
|
|
/*
|
|
* First validate fields which are common to both the MFD label and
|
|
* Pack label - we'll use Pack label offsets here.
|
|
*/
|
|
if ((root.rt_pack.pk_mbm1 == 0177777) &&
|
|
(rstsValidateClusterSize(root.rt_pack.pk_ppcs, dcs) == SCPE_OK)) {
|
|
char ch, *tmp = &context->packid[1];
|
|
uint16 mfd, gfd;
|
|
|
|
context->pcs = root.rt_pack.pk_ppcs;
|
|
|
|
r50Asc(root.rt_pack.pk_packid[0], &context->packid[0]);
|
|
r50Asc(root.rt_pack.pk_packid[1], &context->packid[3]);
|
|
context->packid[6] = '\0';
|
|
|
|
/*
|
|
* The Pack ID must consist of 1 - 6 alphanumeric characters
|
|
* padded at the end with spaces.
|
|
*/
|
|
if (!isalnum(context->packid[0]))
|
|
return SCPE_IOERR;
|
|
|
|
while ((ch = *tmp++) != 0) {
|
|
if (!isalnum(ch)) {
|
|
if (ch != ' ')
|
|
return SCPE_IOERR;
|
|
|
|
while (*tmp)
|
|
if (*tmp++ != ' ')
|
|
return SCPE_IOERR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Determine the pack revision level and, therefore, the path to
|
|
* [0,1]satt.sys which will allow us to determine the size of the
|
|
* pack used by RSTS.
|
|
*/
|
|
if ((root.rt_pack.pk_pstat & PK_UC_NEW) == 0) {
|
|
uint16 link = root.rt_mfd.ml_ulnk;
|
|
RSTS_UNAME uname;
|
|
|
|
/*
|
|
* Old format used by RSTS up through V07.x
|
|
*/
|
|
if (dcs > 16)
|
|
return SCPE_IOERR;
|
|
|
|
*level = PK_LVL0;
|
|
|
|
memcpy(&context->map, &root.rt_block[MAP_OFFSET], BLOCKETTE_SZ);
|
|
|
|
/*
|
|
* Scan the MFD name entries looking for [0,1]. Note there will
|
|
* always be at least 1 entry.
|
|
*/
|
|
do {
|
|
if (rstsReadBlockette(context, link, &uname) != SCPE_OK)
|
|
break;
|
|
|
|
if (uname.un_unam == ((0 << 8) | 1)) {
|
|
*ufd = uname.un_uar;
|
|
return SCPE_OK;
|
|
}
|
|
} while ((link = uname.un_ulnk) != 0);
|
|
}
|
|
else {
|
|
/*
|
|
* New format used by RSTS V08 and later
|
|
*/
|
|
switch (root.rt_pack.pk_plvl) {
|
|
case PK_LVL11:
|
|
if (dcs > 16)
|
|
return SCPE_IOERR;
|
|
break;
|
|
|
|
case PK_LVL12:
|
|
if (dcs > 64)
|
|
return SCPE_IOERR;
|
|
break;
|
|
|
|
default:
|
|
return SCPE_IOERR;
|
|
}
|
|
*level = root.rt_pack.pk_plvl;
|
|
|
|
mfd = root.rt_pack.pk_mdcn;
|
|
|
|
if (rstsReadBlock(context, mfd, 0, buf) == SCPE_OK) {
|
|
if (IS_VALID_RSTS_MFD((RSTS_MFDBLOCKETTE *)buf)) {
|
|
if (rstsReadBlock(context, mfd, 1, buf) == SCPE_OK)
|
|
if ((gfd = buf[0]) != 0)
|
|
if (rstsReadBlock(context, gfd, 0, buf) == SCPE_OK)
|
|
if (IS_VALID_RSTS_GFD((RSTS_GFDBLOCKETTE *)buf, 0)) {
|
|
if (rstsReadBlock(context, gfd, 1, buf) == SCPE_OK)
|
|
if ((*ufd = buf[1]) != 0)
|
|
return SCPE_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return SCPE_IOERR;
|
|
}
|
|
|
|
static t_stat rstsLoadAndScanSATT(rstsContext *context, uint16 uaa, uint16 uar, t_offset *result)
|
|
{
|
|
t_offset blocks = 0;
|
|
uint8 bitmap[8192];
|
|
int i, j;
|
|
RSTS_ACNT acnt;
|
|
RSTS_RETR retr;
|
|
|
|
if (uar != 0) {
|
|
if (rstsReadBlockette(context, uaa, &acnt) == SCPE_OK) {
|
|
uint16 blocks = acnt.ac_usiz;
|
|
uint16 offset = 0;
|
|
|
|
if ((rstsValidateClusterSize(acnt.ac_uclus, context->pcs) != SCPE_OK) ||
|
|
(blocks > 16))
|
|
return SCPE_IOERR;
|
|
|
|
memset(bitmap, 0xFF, sizeof(bitmap));
|
|
|
|
if (blocks != 0) {
|
|
do {
|
|
int i, j;
|
|
uint16 fcl;
|
|
|
|
if (rstsReadBlockette(context, uar, &retr) != SCPE_OK)
|
|
return SCPE_IOERR;
|
|
|
|
for (i = 0; i < RT_ENTRIES; i++) {
|
|
if ((fcl = retr.rt_uent[i]) == 0)
|
|
goto scanBitmap;
|
|
|
|
for (j = 0; j < acnt.ac_uclus; j++) {
|
|
if ((blocks == 0) || (offset >= sizeof(bitmap)))
|
|
goto scanBitmap;
|
|
|
|
if (rstsReadBlock(context, fcl, j, &bitmap[offset]) != SCPE_OK)
|
|
return SCPE_IOERR;
|
|
|
|
offset += 512;
|
|
blocks--;
|
|
}
|
|
}
|
|
} while ((uar = retr.rt_ulnk) != 0);
|
|
|
|
scanBitmap:
|
|
for (i = sizeof(bitmap) - 1; i != 0; i--)
|
|
if (bitmap[i] != 0xFF) {
|
|
blocks = i * 8;
|
|
for (j = 7; j >= 0; j--)
|
|
if ((bitmap[i] & (1 << j)) == 0) {
|
|
blocks += j + 1;
|
|
goto scanDone;
|
|
}
|
|
}
|
|
scanDone:
|
|
*result = (t_offset)(blocks + 1) * context->pcs;
|
|
return SCPE_OK;
|
|
}
|
|
}
|
|
}
|
|
return SCPE_IOERR;
|
|
}
|
|
|
|
static t_offset get_rsts_filesystem_size (UNIT *uptr)
|
|
{
|
|
DEVICE *dptr;
|
|
t_addr saved_capac;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
t_addr temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */
|
|
uint8 buf[512];
|
|
t_offset ret_val = (t_offset)-1;
|
|
rstsContext context;
|
|
|
|
if ((dptr = find_dev_from_unit (uptr)) == NULL)
|
|
return ret_val;
|
|
saved_capac = uptr->capac;
|
|
uptr->capac = temp_capac;
|
|
|
|
context.uptr = uptr;
|
|
context.sects = 512 / ctx->sector_size;
|
|
|
|
/*
|
|
* Check all possible device cluster sizes
|
|
*/
|
|
for (context.dcshift = 0; context.dcshift < 8; context.dcshift++) {
|
|
uint16 ufd, level;
|
|
|
|
/*
|
|
* We need to find [0,1]SATT.SYS to compute the actual size of the disk.
|
|
* First find the DCN of the [0,1] UFD.
|
|
*/
|
|
if (rstsFind01UFD(&context, &ufd, &level) == SCPE_OK) {
|
|
if (rstsReadBlock(&context, ufd, 0, buf) == SCPE_OK) {
|
|
if (IS_VALID_RSTS_UFD((RSTS_UFDBLOCKETTE *)buf, 0, 1)) {
|
|
uint16 link = ((RSTS_UFDBLOCKETTE *)buf)->ub_ulnk;
|
|
RSTS_FNAME fname;
|
|
|
|
memcpy(&context.map, &buf[MAP_OFFSET], BLOCKETTE_SZ);
|
|
|
|
/*
|
|
* Scan the UFD looking for SATT.SYS - the allocation bitmap
|
|
*/
|
|
do {
|
|
if (rstsReadBlockette(&context, link, &fname) != SCPE_OK)
|
|
break;
|
|
|
|
if ((fname.fn_unam[0] == SATT0) &&
|
|
(fname.fn_unam[1] == SATT1) &&
|
|
(fname.fn_unam[2] == SATT2)) {
|
|
if (rstsLoadAndScanSATT(&context, fname.fn_uaa, fname.fn_uar, &ret_val) == SCPE_OK) {
|
|
const char *fmt = "???";
|
|
|
|
ret_val *= 512;
|
|
|
|
switch (level) {
|
|
case PK_LVL0:
|
|
fmt = "0.0";
|
|
break;
|
|
|
|
case PK_LVL11:
|
|
fmt = "1.1";
|
|
break;
|
|
|
|
case PK_LVL12:
|
|
fmt = "1.2";
|
|
break;
|
|
}
|
|
|
|
sim_messagef(SCPE_OK, "%s: '%s' Contains a RSTS File system\n", sim_uname (uptr), uptr->filename);
|
|
sim_messagef(SCPE_OK, "%s: Pack ID: %6.6s ", sim_uname (uptr), context.packid);
|
|
sim_messagef(SCPE_OK, "Revision Level: %3s ", fmt);
|
|
sim_messagef(SCPE_OK, "Pack Clustersize: %d\n", context.pcs);
|
|
sim_messagef(SCPE_OK, "%s: Last Unallocated Sector In File System: %u\n", sim_uname (uptr), (uint32)(ret_val / 512));
|
|
goto cleanup_done;
|
|
}
|
|
}
|
|
} while ((link = fname.fn_ulnk) != 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
cleanup_done:
|
|
uptr->capac = saved_capac;
|
|
return ret_val;
|
|
}
|
|
|
|
#pragma pack(push,1)
|
|
typedef struct _RT11_HomeBlock {
|
|
uint8 hb_b_bbtable[130];
|
|
uint8 hb_b_unused1[2];
|
|
uint8 hb_b_initrestore[38];
|
|
uint8 hb_b_bup[18];
|
|
uint8 hb_b_unused2[260];
|
|
uint16 hb_w_reserved1;
|
|
uint16 hb_w_reserved2;
|
|
uint8 hb_b_unused3[14];
|
|
uint16 hb_w_clustersize;
|
|
uint16 hb_w_firstdir;
|
|
uint16 hb_w_sysver;
|
|
#define HB_C_SYSVER_V3A 36521
|
|
#define HB_C_SYSVER_V04 36434
|
|
#define HB_C_SYSVER_V05 36435
|
|
uint8 hb_b_volid[12];
|
|
uint8 hb_b_owner[12];
|
|
uint8 hb_b_sysid[12];
|
|
#define HB_C_SYSID "DECRT11A "
|
|
uint8 hb_b_unused4[2];
|
|
uint16 hb_w_checksum;
|
|
} RT11_HomeBlock;
|
|
|
|
typedef struct _RT11_DirHeader {
|
|
uint16 dh_w_count;
|
|
uint16 dh_w_next;
|
|
uint16 dh_w_highest;
|
|
#define DH_C_MAXSEG 31
|
|
uint16 dh_w_extra;
|
|
uint16 dh_w_start;
|
|
} RT11_DirHeader;
|
|
|
|
typedef struct _RT11_DirEntry {
|
|
uint16 de_w_status;
|
|
#define DE_C_PRE 0000020
|
|
#define DE_C_TENT 0000400
|
|
#define DE_C_EMPTY 0001000
|
|
#define DE_C_PERM 0002000
|
|
#define DE_C_EOS 0004000
|
|
#define DE_C_READ 0040000
|
|
#define DE_C_PROT 0100000
|
|
uint16 de_w_fname1;
|
|
uint16 de_w_fname2;
|
|
uint16 de_w_ftype;
|
|
uint16 de_w_length;
|
|
uint16 de_w_jobchannel;
|
|
uint16 de_w_creationdate;
|
|
} RT11_DirEntry;
|
|
#pragma pack(pop)
|
|
|
|
#define RT11_MAXPARTITIONS 256 /* Max partitions supported */
|
|
#define RT11_HOME 1 /* Home block # */
|
|
|
|
#define RT11_NOPART 0
|
|
#define RT11_SINGLEPART 1
|
|
#define RT11_MULTIPART 2
|
|
|
|
static int rt11_get_partition_type(RT11_HomeBlock *home, int part)
|
|
{
|
|
if (strncmp((char *)&home->hb_b_sysid, HB_C_SYSID, strlen(HB_C_SYSID)) == 0) {
|
|
uint16 type = home->hb_w_sysver;
|
|
|
|
if (part == 0) {
|
|
if ((type == HB_C_SYSVER_V3A) || (type == HB_C_SYSVER_V04))
|
|
return RT11_SINGLEPART;
|
|
}
|
|
|
|
if (type == HB_C_SYSVER_V05)
|
|
return RT11_MULTIPART;
|
|
}
|
|
return RT11_NOPART;
|
|
}
|
|
|
|
static t_offset get_rt11_filesystem_size (UNIT *uptr)
|
|
{
|
|
DEVICE *dptr;
|
|
t_addr saved_capac;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
t_addr temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */
|
|
uint8 sector_buf[1024];
|
|
RT11_HomeBlock Home;
|
|
t_seccnt sects_read;
|
|
RT11_DirHeader *dir_hdr = (RT11_DirHeader *)sector_buf;
|
|
int partitions = 0;
|
|
int part;
|
|
uint32 base;
|
|
uint32 dir_sec;
|
|
uint16 dir_seg;
|
|
uint16 version = 0;
|
|
t_offset ret_val = (t_offset)-1;
|
|
|
|
if ((dptr = find_dev_from_unit (uptr)) == NULL)
|
|
return ret_val;
|
|
saved_capac = uptr->capac;
|
|
uptr->capac = temp_capac;
|
|
|
|
for (part = 0; part < RT11_MAXPARTITIONS; part++) {
|
|
uint16 seg_highest;
|
|
int type;
|
|
|
|
base = part << 16;
|
|
|
|
if (sim_disk_rdsect(uptr, (base + RT11_HOME) * (512 / ctx->sector_size), (uint8 *)&Home, §s_read, 512 / ctx->sector_size) ||
|
|
(sects_read != (512 / ctx->sector_size)))
|
|
goto Return_Cleanup;
|
|
|
|
type = rt11_get_partition_type(&Home, part);
|
|
|
|
if (type != RT11_NOPART) {
|
|
uint16 highest = 0;
|
|
uint8 seg_seen[DH_C_MAXSEG + 1];
|
|
|
|
memset(seg_seen, 0, sizeof(seg_seen));
|
|
|
|
partitions++;
|
|
|
|
dir_seg = 1;
|
|
do {
|
|
int offset = sizeof(RT11_DirHeader);
|
|
int dir_size = sizeof(RT11_DirEntry);
|
|
uint16 cur_blk;
|
|
|
|
if (seg_seen[dir_seg]++ != 0)
|
|
goto Next_Partition;
|
|
|
|
dir_sec = Home.hb_w_firstdir + ((dir_seg - 1) * 2);
|
|
|
|
if ((sim_disk_rdsect(uptr, (base + dir_sec) * (512 / ctx->sector_size), sector_buf, §s_read, 1024 / ctx->sector_size)) ||
|
|
(sects_read != (1024 / ctx->sector_size)))
|
|
goto Return_Cleanup;
|
|
|
|
if (dir_seg == 1) {
|
|
seg_highest = dir_hdr->dh_w_highest;
|
|
if (seg_highest > DH_C_MAXSEG)
|
|
goto Next_Partition;
|
|
}
|
|
dir_size += dir_hdr->dh_w_extra;
|
|
cur_blk = dir_hdr->dh_w_start;
|
|
|
|
while ((1024 - offset) >= dir_size) {
|
|
RT11_DirEntry *dir_entry = (RT11_DirEntry *)§or_buf[offset];
|
|
|
|
if (dir_entry->de_w_status & DE_C_EOS)
|
|
break;
|
|
|
|
/*
|
|
* Within each directory segment the bas address should never
|
|
* decrease.
|
|
*/
|
|
if (((cur_blk + dir_entry->de_w_length) & 0xFFFF) < cur_blk)
|
|
goto Next_Partition;
|
|
|
|
cur_blk += dir_entry->de_w_length;
|
|
offset += dir_size;
|
|
}
|
|
if (cur_blk > highest)
|
|
highest = cur_blk;
|
|
dir_seg = dir_hdr->dh_w_next;
|
|
|
|
if (dir_seg > seg_highest)
|
|
goto Next_Partition;
|
|
} while (dir_seg != 0);
|
|
|
|
ret_val = (t_offset)((base + highest) * (t_offset)512);
|
|
version = Home.hb_w_sysver;
|
|
|
|
if (type == RT11_SINGLEPART)
|
|
break;
|
|
}
|
|
Next_Partition:
|
|
;
|
|
}
|
|
|
|
Return_Cleanup:
|
|
if (partitions) {
|
|
const char *parttype;
|
|
|
|
switch (version) {
|
|
case HB_C_SYSVER_V3A:
|
|
parttype = "V3A";
|
|
break;
|
|
|
|
case HB_C_SYSVER_V04:
|
|
parttype = "V04";
|
|
break;
|
|
|
|
case HB_C_SYSVER_V05:
|
|
parttype = "V05";
|
|
break;
|
|
|
|
default:
|
|
parttype = "???";
|
|
break;
|
|
}
|
|
sim_messagef (SCPE_OK, "%s: '%s' Contains RT11 partitions\n", sim_uname (uptr), uptr->filename);
|
|
sim_messagef (SCPE_OK, "%d valid partition%s, Type: %s, Sectors On Disk: %u\n", partitions, partitions == 1 ? "" : "s", parttype, (uint32)(ret_val / 512));
|
|
}
|
|
uptr->capac = saved_capac;
|
|
return ret_val;
|
|
}
|
|
|
|
typedef t_offset (*FILESYSTEM_CHECK)(UNIT *uptr);
|
|
|
|
static t_offset get_filesystem_size (UNIT *uptr)
|
|
{
|
|
static FILESYSTEM_CHECK checks[] = {
|
|
&get_ods2_filesystem_size,
|
|
&get_ods1_filesystem_size,
|
|
&get_ultrix_filesystem_size,
|
|
&get_rsts_filesystem_size,
|
|
&get_rt11_filesystem_size, /* This should be the last entry
|
|
in the table to reduce the
|
|
possibility of matching an RT-11
|
|
container file stored in another
|
|
filesystem */
|
|
NULL
|
|
};
|
|
t_offset ret_val = (t_offset)-1;
|
|
int i;
|
|
|
|
for (i = 0; checks[i] != NULL; i++) {
|
|
ret_val = checks[i] (uptr);
|
|
if (ret_val != (t_offset)-1)
|
|
break;
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
static t_stat get_disk_footer (UNIT *uptr)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
struct simh_disk_footer *f = (struct simh_disk_footer *)calloc (1, sizeof (*f));
|
|
t_offset container_size;
|
|
t_offset sim_fsize_ex (FILE *fptr);
|
|
uint32 bytesread;
|
|
|
|
if (f == NULL)
|
|
return SCPE_MEM;
|
|
sim_debug_unit (ctx->dbit, uptr, "get_disk_footer(%s)\n", sim_uname (uptr));
|
|
switch (DK_GET_FMT (uptr)) { /* case on format */
|
|
case DKUF_F_STD: /* SIMH format */
|
|
container_size = sim_fsize_ex (uptr->fileref);
|
|
if ((container_size != (t_offset)-1) && (container_size > sizeof (*f)) &&
|
|
(sim_fseeko (uptr->fileref, container_size - sizeof (*f), SEEK_SET) == 0) &&
|
|
(sizeof (*f) == sim_fread (f, 1, sizeof (*f), uptr->fileref)))
|
|
break;
|
|
free (f);
|
|
f = NULL;
|
|
break;
|
|
case DKUF_F_RAW: /* RAW format */
|
|
container_size = sim_os_disk_size_raw (uptr->fileref);
|
|
if ((container_size != (t_offset)-1) && (container_size > sizeof (*f)) &&
|
|
(sim_os_disk_read (uptr, container_size - sizeof (*f), (uint8 *)f, &bytesread, sizeof (*f)) == SCPE_OK) &&
|
|
(bytesread == sizeof (*f)))
|
|
break;
|
|
free (f);
|
|
f = NULL;
|
|
break;
|
|
case DKUF_F_VHD: /* VHD format */
|
|
if (1) {
|
|
time_t creation_time;
|
|
|
|
/* Construct a pseudo simh disk footer*/
|
|
memcpy (f->Signature, "simh", 4);
|
|
strncpy ((char *)f->DriveType, sim_vhd_disk_get_dtype (uptr->fileref, &f->SectorSize, &f->TransferElementSize, (char *)f->CreatingSimulator, &creation_time), sizeof (f->DriveType) - 1);
|
|
f->SectorSize = NtoHl (f->SectorSize);
|
|
f->TransferElementSize = NtoHl (f->TransferElementSize);
|
|
if ((f->SectorSize == 0) || (NtoHl (f->SectorSize) == 0x00020000)) { /* Old or mangled format VHD footer */
|
|
sim_vhd_disk_set_dtype (uptr->fileref, (char *)f->DriveType, ctx->sector_size, ctx->xfer_element_size);
|
|
sim_vhd_disk_get_dtype (uptr->fileref, &f->SectorSize, &f->TransferElementSize, (char *)f->CreatingSimulator, NULL);
|
|
f->SectorSize = NtoHl (f->SectorSize);
|
|
f->TransferElementSize = NtoHl (f->TransferElementSize);
|
|
}
|
|
strncpy ((char*)f->CreationTime, ctime (&creation_time), sizeof (f->CreationTime) - 1);
|
|
container_size = sim_vhd_disk_size (uptr->fileref);
|
|
f->SectorCount = NtoHl ((uint32)(container_size / NtoHl (f->SectorSize)));
|
|
container_size += sizeof (*f); /* Adjust since it is removed below */
|
|
f->AccessFormat = DKUF_F_VHD;
|
|
f->Checksum = NtoHl (eth_crc32 (0, f, sizeof (*f) - sizeof (f->Checksum)));
|
|
}
|
|
break;
|
|
default:
|
|
free (f);
|
|
return SCPE_IERR;
|
|
}
|
|
if (f) {
|
|
if (f->Checksum != NtoHl (eth_crc32 (0, f, sizeof (*f) - sizeof (f->Checksum)))) {
|
|
sim_debug_unit (ctx->dbit, uptr, "No footer found on %s format container: %s\n", sim_disk_fmt (uptr), uptr->filename);
|
|
free (f);
|
|
f = NULL;
|
|
}
|
|
else {
|
|
free (ctx->footer);
|
|
ctx->footer = f;
|
|
container_size -= sizeof (*f);
|
|
sim_debug_unit (ctx->dbit, uptr, "Footer: %s - %s\n"
|
|
" Simulator: %s\n"
|
|
" DriveType: %s\n"
|
|
" SectorSize: %u\n"
|
|
" SectorCount: %u\n"
|
|
" TransferElementSize: %u\n"
|
|
" FooterVersion: %u\n"
|
|
" AccessFormat: %u\n"
|
|
" CreationTime: %s",
|
|
sim_uname (uptr), uptr->filename,
|
|
f->CreatingSimulator, f->DriveType, NtoHl(f->SectorSize), NtoHl (f->SectorCount),
|
|
NtoHl (f->TransferElementSize), f->FooterVersion, f->AccessFormat, f->CreationTime);
|
|
}
|
|
}
|
|
sim_debug_unit (ctx->dbit, uptr, "Container Size: %u sectors %u bytes each\n", (uint32)(container_size/ctx->sector_size), ctx->sector_size);
|
|
ctx->container_size = container_size;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat store_disk_footer (UNIT *uptr, const char *dtype)
|
|
{
|
|
DEVICE *dptr;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
struct simh_disk_footer *f;
|
|
time_t now = time (NULL);
|
|
t_offset total_sectors;
|
|
|
|
if ((dptr = find_dev_from_unit (uptr)) == NULL)
|
|
return SCPE_NOATT;
|
|
if (uptr->flags & UNIT_RO)
|
|
return SCPE_RO;
|
|
f = (struct simh_disk_footer *)calloc (1, sizeof (*f));
|
|
f->AccessFormat = DK_GET_FMT (uptr);
|
|
total_sectors = (((t_offset)uptr->capac) * ctx->capac_factor * ((dptr->flags & DEV_SECTORS) ? 512 : 1)) / ctx->sector_size;
|
|
memcpy (f->Signature, "simh", 4);
|
|
strncpy ((char *)f->CreatingSimulator, sim_name, sizeof (f->CreatingSimulator) - 1);
|
|
strncpy ((char *)f->DriveType, dtype, sizeof (f->DriveType) - 1);
|
|
f->SectorSize = NtoHl (ctx->sector_size);
|
|
f->SectorCount = NtoHl ((uint32)total_sectors);
|
|
f->TransferElementSize = NtoHl (ctx->xfer_element_size);
|
|
strncpy ((char*)f->CreationTime, ctime (&now), sizeof (f->CreationTime) - 1);
|
|
f->Checksum = NtoHl (eth_crc32 (0, f, sizeof (*f) - sizeof (f->Checksum)));
|
|
free (ctx->footer);
|
|
ctx->footer = f;
|
|
switch (f->AccessFormat) {
|
|
case DKUF_F_STD: /* SIMH format */
|
|
if (sim_fseeko ((FILE *)uptr->fileref, total_sectors * ctx->sector_size, SEEK_SET) == 0)
|
|
sim_fwrite (f, sizeof (*f), 1, (FILE *)uptr->fileref);
|
|
break;
|
|
case DKUF_F_VHD: /* VHD format */
|
|
break;
|
|
case DKUF_F_RAW: /* Raw Physical Disk Access */
|
|
sim_os_disk_write (uptr, total_sectors * ctx->sector_size, (uint8 *)f, NULL, sizeof (*f));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat sim_disk_attach (UNIT *uptr, const char *cptr, size_t sector_size, size_t xfer_element_size, t_bool dontchangecapac,
|
|
uint32 dbit, const char *dtype, uint32 pdp11tracksize, int completion_delay)
|
|
{
|
|
return sim_disk_attach_ex (uptr, cptr, sector_size, xfer_element_size, dontchangecapac, dbit, dtype, pdp11tracksize, completion_delay, NULL);
|
|
}
|
|
|
|
t_stat sim_disk_attach_ex (UNIT *uptr, const char *cptr, size_t sector_size, size_t xfer_element_size, t_bool dontchangecapac,
|
|
uint32 dbit, const char *dtype, uint32 pdp11tracksize, int completion_delay, const char **drivetypes)
|
|
{
|
|
struct disk_context *ctx;
|
|
DEVICE *dptr;
|
|
char tbuf[4*CBUFSIZE];
|
|
FILE *(*open_function)(const char *filename, const char *mode) = sim_fopen;
|
|
FILE *(*create_function)(const char *filename, t_offset desiredsize) = NULL;
|
|
t_offset (*size_function)(FILE *file);
|
|
t_stat (*storage_function)(FILE *file, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom) = NULL;
|
|
t_bool created = FALSE, copied = FALSE;
|
|
t_bool auto_format = FALSE;
|
|
t_offset container_size, filesystem_size, current_unit_size;
|
|
size_t tmp_size = 1;
|
|
|
|
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;
|
|
switch (xfer_element_size) {
|
|
default:
|
|
return sim_messagef (SCPE_ARG, "Unsupported transfer element size: %u\n", (uint32)xfer_element_size);
|
|
case 1: case 2: case 4: case 8:
|
|
break;
|
|
}
|
|
if ((sector_size % xfer_element_size) != 0)
|
|
return sim_messagef (SCPE_ARG, "Invalid sector size: %u - must be a multiple of the transfer element size %u\n", (uint32)sector_size, (uint32)xfer_element_size);
|
|
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) ||
|
|
(DK_GET_FMT (uptr) == DKUF_F_AUTO))
|
|
return sim_messagef (SCPE_ARG, "Invalid Override Disk Format: %s\n", gbuf);
|
|
sim_switches = sim_switches & ~(SWMASK ('F')); /* Record Format specifier already processed */
|
|
auto_format = TRUE;
|
|
}
|
|
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, dontchangecapac, dbit, dtype, pdp11tracksize, completion_delay);
|
|
}
|
|
return sim_messagef (SCPE_ARG, "Unable to create differencing VHD: %s\n", gbuf);
|
|
}
|
|
if (sim_switches & SWMASK ('C')) { /* create new disk container & copy contents? */
|
|
char gbuf[CBUFSIZE];
|
|
const char *dest_fmt = ((DK_GET_FMT (uptr) == DKUF_F_AUTO) || (DK_GET_FMT (uptr) == DKUF_F_VHD)) ? "VHD" : "SIMH";
|
|
FILE *dest;
|
|
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_ex (uptr, cptr, sector_size, xfer_element_size, dontchangecapac, dbit, dtype, pdp11tracksize, completion_delay, NULL);
|
|
sim_quiet = saved_sim_quiet;
|
|
if (r != SCPE_OK) {
|
|
sim_switches = saved_sim_switches;
|
|
return sim_messagef (r, "%s: Can't open copy source: %s - %s\n", sim_uname (uptr), cptr, sim_error_text (r));
|
|
}
|
|
sim_messagef (SCPE_OK, "%s: creating new %s '%s' disk container copied from '%s'\n", sim_uname (uptr), dest_fmt, gbuf, cptr);
|
|
capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (quadword: 8, word: 2, byte: 1) */
|
|
if (strcmp ("VHD", dest_fmt) == 0)
|
|
dest = sim_vhd_disk_create (gbuf, ((t_offset)uptr->capac)*capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1));
|
|
else
|
|
dest = sim_fopen (gbuf, "wb+");
|
|
if (!dest) {
|
|
sim_disk_detach (uptr);
|
|
return sim_messagef (r, "%s: can't create %s disk container '%s'\n", sim_uname (uptr), dest_fmt, gbuf);
|
|
}
|
|
else {
|
|
uint8 *copy_buf = (uint8*) malloc (1024*1024);
|
|
t_lba lba;
|
|
t_seccnt sectors_per_buffer = (t_seccnt)((1024*1024)/sector_size);
|
|
t_lba total_sectors = (t_lba)((uptr->capac*capac_factor)/(sector_size/((dptr->flags & DEV_SECTORS) ? 512 : 1)));
|
|
t_seccnt sects = sectors_per_buffer;
|
|
t_seccnt sects_read;
|
|
|
|
if (!copy_buf) {
|
|
if (strcmp ("VHD", dest_fmt) == 0)
|
|
sim_vhd_disk_close (dest);
|
|
else
|
|
fclose (dest);
|
|
(void)remove (gbuf);
|
|
sim_disk_detach (uptr);
|
|
return SCPE_MEM;
|
|
}
|
|
sim_messagef (SCPE_OK, "Copying %u sectors each %u bytes in size\n", (uint32)total_sectors, (uint32)sector_size);
|
|
for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects_read) {
|
|
sects = sectors_per_buffer;
|
|
if (lba + sects > total_sectors)
|
|
sects = total_sectors - lba;
|
|
r = sim_disk_rdsect (uptr, lba, copy_buf, §s_read, sects);
|
|
if ((r == SCPE_OK) && (sects_read > 0)) {
|
|
uint32 saved_unit_flags = uptr->flags;
|
|
FILE *save_unit_fileref = uptr->fileref;
|
|
t_seccnt sects_written;
|
|
|
|
sim_disk_set_fmt (uptr, 0, dest_fmt, NULL);
|
|
uptr->fileref = dest;
|
|
r = sim_disk_wrsect (uptr, lba, copy_buf, §s_written, sects_read);
|
|
uptr->fileref = save_unit_fileref;
|
|
uptr->flags = saved_unit_flags;
|
|
if (sects_read != sects_written)
|
|
r = SCPE_IOERR;
|
|
sim_messagef (SCPE_OK, "%s: Copied %u/%u sectors. %d%% complete.\r", sim_uname (uptr), (uint32)(lba + sects_read), (uint32)total_sectors, (int)((((float)lba)*100)/total_sectors));
|
|
}
|
|
}
|
|
if (r == SCPE_OK)
|
|
sim_messagef (SCPE_OK, "\n%s: Copied %u sectors. Done.\n", sim_uname (uptr), (uint32)total_sectors);
|
|
else
|
|
sim_messagef (r, "\n%s: Error copying: %s.\n", sim_uname (uptr), sim_error_text (r));
|
|
if ((r == SCPE_OK) && (sim_switches & SWMASK ('V'))) {
|
|
uint8 *verify_buf = (uint8*) malloc (1024*1024);
|
|
t_seccnt sects_read, verify_read;
|
|
|
|
if (!verify_buf) {
|
|
if (strcmp ("VHD", dest_fmt) == 0)
|
|
sim_vhd_disk_close (dest);
|
|
else
|
|
fclose (dest);
|
|
(void)remove (gbuf);
|
|
free (copy_buf);
|
|
sim_disk_detach (uptr);
|
|
return SCPE_MEM;
|
|
}
|
|
for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects_read) {
|
|
sim_messagef (SCPE_OK, "%s: Verified %u/%u sectors. %d%% complete.\r", sim_uname (uptr), (uint32)lba, (uint32)total_sectors, (int)((((float)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, §s_read, sects);
|
|
if (r == SCPE_OK) {
|
|
uint32 saved_unit_flags = uptr->flags;
|
|
FILE *save_unit_fileref = uptr->fileref;
|
|
|
|
sim_disk_set_fmt (uptr, 0, dest_fmt, NULL);
|
|
uptr->fileref = dest;
|
|
r = sim_disk_rdsect (uptr, lba, verify_buf, &verify_read, sects_read);
|
|
uptr->fileref = save_unit_fileref;
|
|
uptr->flags = saved_unit_flags;
|
|
if (r == SCPE_OK) {
|
|
if ((sects_read != verify_read) ||
|
|
(0 != memcmp (copy_buf, verify_buf, verify_read*sector_size)))
|
|
r = SCPE_IOERR;
|
|
}
|
|
}
|
|
if (r != SCPE_OK)
|
|
break;
|
|
}
|
|
if (!sim_quiet) {
|
|
if (r == SCPE_OK)
|
|
sim_messagef (r, "\n%s: Verified %u sectors. Done.\n", sim_uname (uptr), (uint32)total_sectors);
|
|
else {
|
|
t_lba i;
|
|
uint32 save_dctrl = dptr->dctrl;
|
|
FILE *save_sim_deb = sim_deb;
|
|
|
|
for (i = 0; i < sects_read; ++i)
|
|
if (0 != memcmp (copy_buf+i*sector_size, verify_buf+i*sector_size, sector_size))
|
|
break;
|
|
sim_printf ("\n%s: Verification Error on lbn %d.\n", sim_uname (uptr), lba+i);
|
|
dptr->dctrl = 0xFFFFFFFF;
|
|
sim_deb = stdout;
|
|
sim_disk_data_trace (uptr, copy_buf+i*sector_size, lba+i, sector_size, "Expected", TRUE, 1);
|
|
sim_disk_data_trace (uptr, verify_buf+i*sector_size, lba+i, sector_size, "Found", TRUE, 1);
|
|
dptr->dctrl = save_dctrl;
|
|
sim_deb = save_sim_deb;
|
|
}
|
|
}
|
|
free (verify_buf);
|
|
}
|
|
free (copy_buf);
|
|
if (strcmp ("VHD", dest_fmt) == 0)
|
|
sim_vhd_disk_close (dest);
|
|
else
|
|
fclose (dest);
|
|
sim_disk_detach (uptr);
|
|
if (r == SCPE_OK) {
|
|
created = TRUE;
|
|
copied = TRUE;
|
|
strlcpy (tbuf, gbuf, sizeof(tbuf)-1);
|
|
cptr = tbuf;
|
|
sim_disk_set_fmt (uptr, 0, dest_fmt, NULL);
|
|
sim_switches = saved_sim_switches;
|
|
}
|
|
else
|
|
return r;
|
|
/* fall through and open/return the newly created & copied disk container */
|
|
}
|
|
}
|
|
else
|
|
if (sim_switches & SWMASK ('M')) { /* merge difference disk? */
|
|
char gbuf[CBUFSIZE], *Parent = NULL;
|
|
FILE *vhd;
|
|
|
|
sim_switches = sim_switches & ~(SWMASK ('M'));
|
|
get_glyph_nc (cptr, gbuf, 0); /* get spec */
|
|
vhd = sim_vhd_disk_merge (gbuf, &Parent);
|
|
if (vhd) {
|
|
t_stat r;
|
|
|
|
sim_vhd_disk_close (vhd);
|
|
r = sim_disk_attach (uptr, Parent, sector_size, xfer_element_size, dontchangecapac, dbit, dtype, pdp11tracksize, completion_delay);
|
|
free (Parent);
|
|
return r;
|
|
}
|
|
return SCPE_ARG;
|
|
}
|
|
|
|
switch (DK_GET_FMT (uptr)) { /* case on format */
|
|
case DKUF_F_AUTO: /* SIMH format */
|
|
auto_format = TRUE;
|
|
if (NULL != (uptr->fileref = sim_vhd_disk_open (cptr, "rb"))) { /* Try VHD */
|
|
sim_disk_set_fmt (uptr, 0, "VHD", NULL); /* set file format to VHD */
|
|
sim_vhd_disk_close (uptr->fileref); /* close vhd file*/
|
|
uptr->fileref = NULL;
|
|
open_function = sim_vhd_disk_open;
|
|
size_function = sim_vhd_disk_size;
|
|
break;
|
|
}
|
|
while (tmp_size < sector_size)
|
|
tmp_size <<= 1;
|
|
if (tmp_size == sector_size) { /* Power of 2 sector size can do RAW */
|
|
if (NULL != (uptr->fileref = sim_os_disk_open_raw (cptr, "rb"))) {
|
|
sim_disk_set_fmt (uptr, 0, "RAW", NULL); /* set file format to RAW */
|
|
sim_os_disk_close_raw (uptr->fileref); /* close raw file*/
|
|
open_function = sim_os_disk_open_raw;
|
|
size_function = sim_os_disk_size_raw;
|
|
storage_function = sim_os_disk_info_raw;
|
|
uptr->fileref = NULL;
|
|
break;
|
|
}
|
|
}
|
|
sim_disk_set_fmt (uptr, 0, "SIMH", NULL); /* set file format to SIMH */
|
|
open_function = sim_fopen;
|
|
size_function = sim_fsize_ex;
|
|
break;
|
|
case DKUF_F_STD: /* SIMH format */
|
|
if (NULL != (uptr->fileref = sim_vhd_disk_open (cptr, "rb"))) { /* Try VHD first */
|
|
sim_disk_set_fmt (uptr, 0, "VHD", NULL); /* set file format to VHD */
|
|
sim_vhd_disk_close (uptr->fileref); /* close vhd file*/
|
|
uptr->fileref = NULL;
|
|
open_function = sim_vhd_disk_open;
|
|
size_function = sim_vhd_disk_size;
|
|
auto_format = TRUE;
|
|
break;
|
|
}
|
|
open_function = sim_fopen;
|
|
size_function = sim_fsize_ex;
|
|
break;
|
|
case DKUF_F_VHD: /* VHD format */
|
|
open_function = sim_vhd_disk_open;
|
|
create_function = sim_vhd_disk_create;
|
|
size_function = sim_vhd_disk_size;
|
|
storage_function = sim_os_disk_info_raw;
|
|
break;
|
|
case DKUF_F_RAW: /* Raw Physical Disk Access */
|
|
if (NULL != (uptr->fileref = sim_vhd_disk_open (cptr, "rb"))) { /* Try VHD first */
|
|
sim_disk_set_fmt (uptr, 0, "VHD", NULL); /* set file format to VHD */
|
|
sim_vhd_disk_close (uptr->fileref); /* close vhd file*/
|
|
uptr->fileref = NULL;
|
|
open_function = sim_vhd_disk_open;
|
|
size_function = sim_vhd_disk_size;
|
|
auto_format = TRUE;
|
|
break;
|
|
}
|
|
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);
|
|
strlcpy (uptr->filename, cptr, CBUFSIZE); /* save name */
|
|
ctx->sector_size = (uint32)sector_size; /* save sector_size */
|
|
ctx->capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* save capacity units (quadword: 8, 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->media_removed = 0; /* default present */
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_disk_attach(unit=%d,filename='%s')\n", (int)(uptr - ctx->dptr->units), uptr->filename);
|
|
ctx->auto_format = auto_format; /* save that we auto selected format */
|
|
ctx->storage_sector_size = (uint32)sector_size; /* Default */
|
|
if ((sim_switches & SWMASK ('R')) || /* read only? */
|
|
((uptr->flags & UNIT_RO) != 0)) {
|
|
if (((uptr->flags & UNIT_ROABLE) == 0) && /* allowed? */
|
|
((uptr->flags & UNIT_RO) == 0))
|
|
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 */
|
|
sim_messagef (SCPE_OK, "%s: unit is read only\n", sim_uname (uptr));
|
|
}
|
|
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 */
|
|
sim_messagef (SCPE_OK, "%s: unit is read only\n", sim_uname (uptr));
|
|
}
|
|
else { /* doesn't exist */
|
|
if (sim_switches & SWMASK ('E')) /* must exist? */
|
|
return sim_messagef (_err_return (uptr, SCPE_OPENERR), "%s: File not found: %s\n", sim_uname (uptr), cptr);
|
|
if (create_function)
|
|
uptr->fileref = create_function (cptr, ((t_offset)uptr->capac)*ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1));/* create new file */
|
|
else
|
|
uptr->fileref = open_function (cptr, "wb+");/* open new file */
|
|
if (uptr->fileref == NULL) /* open fail? */
|
|
return sim_messagef (_err_return (uptr, SCPE_OPENERR), "%s: Can't create file: %s\n", sim_uname (uptr), cptr);
|
|
sim_messagef (SCPE_OK, "%s: creating new file: %s\n", sim_uname (uptr), cptr);
|
|
created = TRUE;
|
|
}
|
|
} /* end if null */
|
|
} /* end else */
|
|
(void)get_disk_footer (uptr);
|
|
if ((DK_GET_FMT (uptr) == DKUF_F_VHD) || (ctx->footer)) {
|
|
uint32 sector_size, xfer_element_size;
|
|
char created_name[64];
|
|
const char *container_dtype = ctx->footer ? (char *)ctx->footer->DriveType : sim_vhd_disk_get_dtype (uptr->fileref, §or_size, &xfer_element_size, created_name, NULL);
|
|
|
|
if (ctx->footer) {
|
|
sector_size = NtoHl (ctx->footer->SectorSize);
|
|
xfer_element_size = NtoHl (ctx->footer->TransferElementSize);
|
|
strncpy (created_name, (char *)ctx->footer->CreatingSimulator, sizeof (created_name));
|
|
}
|
|
if ((DK_GET_FMT (uptr) == DKUF_F_VHD) && created && dtype) {
|
|
sim_vhd_disk_set_dtype (uptr->fileref, dtype, ctx->sector_size, ctx->xfer_element_size);
|
|
(void)get_disk_footer (uptr);
|
|
container_dtype = (char *)ctx->footer->DriveType;
|
|
}
|
|
if (dtype) {
|
|
char cmd[32];
|
|
t_stat r = SCPE_OK;
|
|
|
|
if (((sector_size == 0) || (sector_size == ctx->sector_size)) &&
|
|
((xfer_element_size == 0) || (xfer_element_size == ctx->xfer_element_size))) {
|
|
if ((strcmp (container_dtype, dtype) != 0) && (drivetypes == NULL)) /* No Autosize */
|
|
r = sim_messagef (SCPE_OPENERR, "%s: Can't attach %s container to %s unit - Autosizing disk disabled\n", sim_uname (uptr), container_dtype, dtype);
|
|
else {
|
|
cmd[sizeof (cmd) - 1] = '\0';
|
|
snprintf (cmd, sizeof (cmd) - 1, "%s %s", sim_uname (uptr), container_dtype);
|
|
r = set_cmd (0, cmd);
|
|
if (r != SCPE_OK)
|
|
r = sim_messagef (r, "Can't set %s to drive type %s\n", sim_uname (uptr), container_dtype);
|
|
}
|
|
}
|
|
else
|
|
r = sim_messagef (SCPE_INCOMPDSK, "%s container created by the %s simulator is incompatible with the %s device on the %s simulator\n", container_dtype, created_name, uptr->dptr->name, sim_name);
|
|
if (r != SCPE_OK) {
|
|
uptr->flags |= UNIT_ATT;
|
|
sim_disk_detach (uptr); /* report error now */
|
|
sprintf (cmd, "%s%d %s", dptr->name, (int)(uptr-dptr->units), dtype);/* restore original dtype */
|
|
set_cmd (0, cmd);
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
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, &ctx->is_cdrom);
|
|
|
|
if ((created) && (!copied)) {
|
|
t_stat r = SCPE_OK;
|
|
uint8 *secbuf = (uint8 *)calloc (128, ctx->sector_size); /* alloc temp sector buf */
|
|
|
|
/*
|
|
On a newly created disk, we write zeros to the whole disk.
|
|
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 Simh 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) { /* Write all blocks */
|
|
t_lba lba;
|
|
t_lba total_lbas = (t_lba)((((t_offset)uptr->capac)*ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1))/ctx->sector_size);
|
|
|
|
for (lba = 0; (r == SCPE_OK) && (lba < total_lbas); lba += 128) {
|
|
t_seccnt sectors = ((lba + 128) <= total_lbas) ? 128 : total_lbas - lba;
|
|
|
|
r = sim_disk_wrsect (uptr, lba, secbuf, NULL, sectors);
|
|
}
|
|
}
|
|
free (secbuf);
|
|
if (r != SCPE_OK) {
|
|
sim_disk_detach (uptr); /* report error now */
|
|
(void)remove (cptr); /* remove the created file */
|
|
return SCPE_OPENERR;
|
|
}
|
|
if (sim_switches & SWMASK ('I')) { /* Initialize To Sector Address */
|
|
size_t init_buf_size = 1024*1024;
|
|
uint8 *init_buf = (uint8*) malloc (init_buf_size);
|
|
t_lba lba, sect;
|
|
uint32 capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (quadword: 8, word: 2, byte: 1) */
|
|
t_seccnt sectors_per_buffer = (t_seccnt)((init_buf_size)/sector_size);
|
|
t_lba total_sectors = (t_lba)((uptr->capac*capac_factor)/(sector_size/((dptr->flags & DEV_SECTORS) ? 512 : 1)));
|
|
t_seccnt sects = sectors_per_buffer;
|
|
|
|
if (!init_buf) {
|
|
sim_disk_detach (uptr); /* report error now */
|
|
(void)remove (cptr);
|
|
return SCPE_MEM;
|
|
}
|
|
sim_messagef (SCPE_OK, "Initializing %u sectors each %u bytes in size with the sector address\n", (uint32)total_sectors, (uint32)sector_size);
|
|
for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects) {
|
|
t_seccnt sects_written;
|
|
|
|
sects = sectors_per_buffer;
|
|
if (lba + sects > total_sectors)
|
|
sects = total_sectors - lba;
|
|
for (sect = 0; sect < sects; sect++) {
|
|
t_lba offset;
|
|
for (offset = 0; offset < sector_size; offset += sizeof(uint32))
|
|
*((uint32 *)&init_buf[sect*sector_size + offset]) = (uint32)(lba + sect);
|
|
}
|
|
r = sim_disk_wrsect (uptr, lba, init_buf, §s_written, sects);
|
|
if ((r != SCPE_OK) || (sects != sects_written)) {
|
|
free (init_buf);
|
|
sim_disk_detach (uptr); /* report error now */
|
|
(void)remove (cptr); /* remove the created file */
|
|
return sim_messagef (SCPE_OPENERR, "Error initializing each sector with its address: %s\n",
|
|
(r == SCPE_OK) ? sim_error_text (r) : "sectors written not what was requested");
|
|
}
|
|
sim_messagef (SCPE_OK, "%s: Initialized To Sector Address %u/%u sectors. %d%% complete.\r", sim_uname (uptr), (uint32)(lba + sects_written), (uint32)total_sectors, (int)((((float)lba)*100)/total_sectors));
|
|
}
|
|
sim_messagef (SCPE_OK, "%s: Initialized To Sector Address %u sectors. 100%% complete. \n", sim_uname (uptr), (uint32)total_sectors);
|
|
free (init_buf);
|
|
}
|
|
if (pdp11tracksize)
|
|
sim_disk_pdp11_bad_block (uptr, pdp11tracksize, sector_size/sizeof(uint16));
|
|
}
|
|
if (sim_switches & SWMASK ('K')) {
|
|
t_stat r = SCPE_OK;
|
|
t_lba lba, sect;
|
|
uint32 capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (word: 2, byte: 1) */
|
|
t_seccnt sectors_per_buffer = (t_seccnt)((1024*1024)/sector_size);
|
|
t_lba total_sectors = (t_lba)((uptr->capac*capac_factor)/(sector_size/((dptr->flags & DEV_SECTORS) ? 512 : 1)));
|
|
t_seccnt sects = sectors_per_buffer;
|
|
t_seccnt sects_verify;
|
|
uint8 *verify_buf = (uint8*) malloc (1024*1024);
|
|
|
|
if (!verify_buf) {
|
|
sim_disk_detach (uptr); /* report error now */
|
|
return SCPE_MEM;
|
|
}
|
|
for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects_verify) {
|
|
sects = sectors_per_buffer;
|
|
if (lba + sects > total_sectors)
|
|
sects = total_sectors - lba;
|
|
r = sim_disk_rdsect (uptr, lba, verify_buf, §s_verify, sects);
|
|
if (r == SCPE_OK) {
|
|
if (sects != sects_verify)
|
|
sim_printf ("\n%s: Verification Error when reading lbn %d(0x%X) of %d(0x%X) Requested %u sectors, read %u sectors.\n",
|
|
sim_uname (uptr), (int)lba, (int)lba, (int)total_sectors, (int)total_sectors, sects, sects_verify);
|
|
for (sect = 0; sect < sects_verify; sect++) {
|
|
t_lba offset;
|
|
t_bool sect_error = FALSE;
|
|
|
|
for (offset = 0; offset < sector_size; offset += sizeof(uint32)) {
|
|
if (*((uint32 *)&verify_buf[sect*sector_size + offset]) != (uint32)(lba + sect)) {
|
|
sect_error = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (sect_error) {
|
|
uint32 save_dctrl = dptr->dctrl;
|
|
FILE *save_sim_deb = sim_deb;
|
|
|
|
sim_printf ("\n%s: Verification Error on lbn %d(0x%X) of %d(0x%X).\n", sim_uname (uptr), (int)(lba+sect), (int)(lba+sect), (int)total_sectors, (int)total_sectors);
|
|
dptr->dctrl = 0xFFFFFFFF;
|
|
sim_deb = stdout;
|
|
sim_disk_data_trace (uptr, verify_buf+sect*sector_size, lba+sect, sector_size, "Found", TRUE, 1);
|
|
dptr->dctrl = save_dctrl;
|
|
sim_deb = save_sim_deb;
|
|
}
|
|
}
|
|
}
|
|
sim_messagef (SCPE_OK, "%s: Verified containing Sector Address %u/%u sectors. %d%% complete.\r", sim_uname (uptr), (uint32)lba, (uint32)total_sectors, (int)((((float)lba)*100)/total_sectors));
|
|
}
|
|
sim_messagef (SCPE_OK, "%s: Verified containing Sector Address %u sectors. 100%% complete. \n", sim_uname (uptr), (uint32)lba);
|
|
free (verify_buf);
|
|
uptr->dynflags |= UNIT_DISK_CHK;
|
|
}
|
|
|
|
if (get_disk_footer (uptr) != SCPE_OK) {
|
|
sim_disk_detach (uptr);
|
|
return SCPE_OPENERR;
|
|
}
|
|
filesystem_size = get_filesystem_size (uptr);
|
|
container_size = sim_disk_size (uptr);
|
|
current_unit_size = ((t_offset)uptr->capac)*ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1);
|
|
if (container_size && (container_size != (t_offset)-1)) {
|
|
if (dontchangecapac) {
|
|
t_addr saved_capac = uptr->capac;
|
|
|
|
if (drivetypes != NULL) {
|
|
if (filesystem_size != (t_offset)-1) { /* File System found? */
|
|
/* Walk through all potential drive types until we find one the right size */
|
|
while (*drivetypes != NULL) {
|
|
char cmd[CBUFSIZE];
|
|
t_stat st;
|
|
|
|
uptr->flags &= ~UNIT_ATT; /* temporarily mark as un-attached */
|
|
sprintf (cmd, "%s %s", sim_uname (uptr), *drivetypes);
|
|
st = set_cmd (0, cmd);
|
|
uptr->flags |= UNIT_ATT; /* restore attached indicator */
|
|
if (st == SCPE_OK)
|
|
current_unit_size = ((t_offset)uptr->capac)*ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1);
|
|
if (current_unit_size >= filesystem_size)
|
|
break;
|
|
++drivetypes;
|
|
}
|
|
if (filesystem_size > current_unit_size) {
|
|
if (!sim_quiet) {
|
|
uptr->capac = (t_addr)(filesystem_size/(ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1)));
|
|
sim_printf ("%s: The file system on the disk %s is larger than simulated device (%s > ", sim_uname (uptr), cptr, sprint_capac (dptr, uptr));
|
|
uptr->capac = saved_capac;
|
|
sim_printf ("%s)\n", sprint_capac (dptr, uptr));
|
|
}
|
|
sim_disk_detach (uptr);
|
|
return SCPE_FSSIZE;
|
|
}
|
|
}
|
|
else
|
|
sim_messagef (SCPE_OK, "%s: No File System found on '%s', skipping autosizing\n", sim_uname (uptr), cptr);
|
|
}
|
|
if ((container_size != current_unit_size) &&
|
|
((DKUF_F_VHD == DK_GET_FMT (uptr)) || (0 != (uptr->flags & UNIT_RO)) ||
|
|
(ctx->footer))) {
|
|
if (!sim_quiet) {
|
|
int32 saved_switches = sim_switches;
|
|
const char *container_dtype = ctx->footer ? (const char *)ctx->footer->DriveType : "";
|
|
|
|
sim_switches = SWMASK ('R');
|
|
uptr->capac = (t_addr)(container_size/(ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1)));
|
|
sim_printf ("%s: non expandable %s disk container '%s' is %s than simulated device (%s %s ",
|
|
sim_uname (uptr), container_dtype, cptr, (container_size < current_unit_size) ? "smaller" : "larger", sprint_capac (dptr, uptr), (container_size < current_unit_size) ? "<" : ">");
|
|
uptr->capac = saved_capac;
|
|
sim_printf ("%s)\n", sprint_capac (dptr, uptr));
|
|
sim_switches = saved_switches;
|
|
}
|
|
sim_disk_detach (uptr);
|
|
return SCPE_OPENERR;
|
|
}
|
|
}
|
|
else { /* Autosize by changing capacity */
|
|
if (filesystem_size != (t_offset)-1) { /* Known file system data size AND */
|
|
if (filesystem_size > container_size) /* Data size greater than container size? */
|
|
container_size = filesystem_size + /* Use file system data size */
|
|
(pdp11tracksize * sector_size);/* plus any bad block data beyond the file system */
|
|
}
|
|
else { /* Unrecognized file system */
|
|
if (container_size < current_unit_size) /* Use MAX of container or current device size */
|
|
if ((DKUF_F_VHD != DK_GET_FMT (uptr)) && /* when size can be expanded */
|
|
(0 == (uptr->flags & UNIT_RO)))
|
|
container_size = current_unit_size; /* Use MAX of container or current device size */
|
|
}
|
|
uptr->capac = (t_addr)(container_size/(ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1))); /* update current size */
|
|
}
|
|
}
|
|
|
|
if (dtype && (created || (ctx->footer == NULL)))
|
|
store_disk_footer (uptr, dtype);
|
|
|
|
#if defined (SIM_ASYNCH_IO)
|
|
sim_disk_set_async (uptr, completion_delay);
|
|
#endif
|
|
uptr->io_flush = _sim_disk_io_flush;
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat sim_disk_detach (UNIT *uptr)
|
|
{
|
|
struct disk_context *ctx;
|
|
int (*close_function)(FILE *f);
|
|
FILE *fileref;
|
|
t_bool auto_format;
|
|
|
|
if (uptr == NULL)
|
|
return SCPE_IERR;
|
|
if (!(uptr->flags & UNIT_ATT))
|
|
return SCPE_UNATT;
|
|
|
|
ctx = (struct disk_context *)uptr->disk_ctx;
|
|
fileref = uptr->fileref;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_disk_detach(unit=%d,filename='%s')\n", (int)(uptr - ctx->dptr->units), uptr->filename);
|
|
|
|
switch (DK_GET_FMT (uptr)) { /* case on format */
|
|
case DKUF_F_STD: /* Simh */
|
|
close_function = fclose;
|
|
break;
|
|
case DKUF_F_VHD: /* Virtual Disk */
|
|
close_function = sim_vhd_disk_close;
|
|
break;
|
|
case DKUF_F_RAW: /* Physical */
|
|
close_function = sim_os_disk_close_raw;
|
|
break;
|
|
default:
|
|
return SCPE_IERR;
|
|
}
|
|
if (!(uptr->flags & UNIT_ATTABLE)) /* attachable? */
|
|
return SCPE_NOATT;
|
|
if (!(uptr->flags & UNIT_ATT)) /* attached? */
|
|
return SCPE_OK;
|
|
if (NULL == find_dev_from_unit (uptr))
|
|
return SCPE_OK;
|
|
auto_format = ctx->auto_format;
|
|
|
|
if (uptr->io_flush)
|
|
uptr->io_flush (uptr); /* flush buffered data */
|
|
|
|
sim_disk_clr_async (uptr);
|
|
|
|
uptr->flags &= ~(UNIT_ATT | UNIT_RO);
|
|
uptr->dynflags &= ~(UNIT_NO_FIO | UNIT_DISK_CHK);
|
|
free (uptr->filename);
|
|
uptr->filename = NULL;
|
|
uptr->fileref = NULL;
|
|
free (ctx->footer);
|
|
free (uptr->disk_ctx);
|
|
uptr->disk_ctx = NULL;
|
|
uptr->io_flush = NULL;
|
|
if (auto_format)
|
|
sim_disk_set_fmt (uptr, 0, "AUTO", NULL); /* restore file format */
|
|
if (close_function (fileref) == EOF)
|
|
return SCPE_IOERR;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat sim_disk_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
|
|
{
|
|
static struct example_fields {
|
|
const char *dname;
|
|
const char *dtype;
|
|
const char *dsize;
|
|
const char *dtype2;
|
|
const char *dsize2;
|
|
const char *dtype3;
|
|
const char *dsize3;
|
|
const char *dtype4;
|
|
const char *dsize4;
|
|
} ex_data[] = {
|
|
{"RQ", "RD54", "159MB", "RX50", "409KB", "RA81", "456MB", "RA92", "1505MB"},
|
|
{"RP", "RM03", "33MW", "RM03", "33MW", "RP07", "258MW", "RM03", "15MW"},
|
|
{"RP", "RM03", "39MW", "RM03", "39MW", "RP07", "110MW", "RM03", "15MW"},
|
|
};
|
|
struct example_fields *ex = &ex_data[0];
|
|
|
|
if (strcmp (dptr->name, "RP") == 0)
|
|
ex = &ex_data[1];
|
|
if (strstr (sim_name, "-10")) {
|
|
ex = &ex_data[2];
|
|
if (strstr (sim_name, "PDP") == NULL)
|
|
ex->dname = "RPA";
|
|
}
|
|
|
|
fprintf (st, "%s Disk Attach Help\n\n", dptr->name);
|
|
|
|
fprintf (st, "Disk container files can be one of several different types:\n\n");
|
|
if (strstr (sim_name, "-10") == NULL) {
|
|
fprintf (st, " SIMH A disk is an unstructured binary file of the size appropriate\n");
|
|
fprintf (st, " for the disk drive being simulated accessed by C runtime APIs\n");
|
|
fprintf (st, " VHD Virtual Disk format which is described in the \"Microsoft\n");
|
|
fprintf (st, " Virtual Hard Disk (VHD) Image Format Specification\". The\n");
|
|
fprintf (st, " VHD implementation includes support for 1) Fixed (Preallocated)\n");
|
|
fprintf (st, " disks, 2) Dynamically Expanding disks, and 3) Differencing disks.\n");
|
|
fprintf (st, " RAW platform specific access to physical disk or CDROM drives\n\n");
|
|
}
|
|
else {
|
|
fprintf (st, " SIMH A disk is an unstructured binary file of 64bit integers\n"
|
|
" access by C runtime APIs\n");
|
|
fprintf (st, " VHD A disk is an unstructured binary file of 64bit integers\n"
|
|
" contained in a VHD container\n");
|
|
fprintf (st, " RAW A disk is an unstructured binary file of 64bit integers\n"
|
|
" accessed by direct read/write APIs\n");
|
|
fprintf (st, " DBD9 Compatible with KLH10 is a packed big endian word\n");
|
|
fprintf (st, " DLD9 Compatible with KLH10 is a packed little endian word\n\n");
|
|
}
|
|
fprintf (st, "Virtual (VHD) Disk support conforms to the \"Virtual Hard Disk Image Format\n");
|
|
fprintf (st, "Specification\", Version 1.0 October 11, 2006.\n");
|
|
fprintf (st, "Dynamically expanding disks never change their \"Virtual Size\", but they don't\n");
|
|
fprintf (st, "consume disk space on the containing storage until the virtual sectors in the\n");
|
|
fprintf (st, "disk are actually written to (i.e. a 2GB Dynamic disk container file with only\n");
|
|
fprintf (st, "30MB of data will initially be about 30MB in size and this size will grow up to\n");
|
|
fprintf (st, "2GB as different sectors are written to. The VHD format contains metadata\n");
|
|
fprintf (st, "which describes the drive size and the simh device type in use when the VHD\n");
|
|
fprintf (st, "was created. This metadata is therefore available whenever that VHD is\n");
|
|
fprintf (st, "attached to an emulated disk device in the future so the device type and\n");
|
|
fprintf (st, "size can be automatically be configured.\n\n");
|
|
|
|
if (dptr->numunits > 1) {
|
|
uint32 i, attachable_count = 0, out_count = 0, skip_count;
|
|
|
|
for (i=0; i < dptr->numunits; ++i)
|
|
if ((dptr->units[i].flags & UNIT_ATTABLE) &&
|
|
!(dptr->units[i].flags & UNIT_DIS))
|
|
++attachable_count;
|
|
for (i=0; (i < dptr->numunits) && (out_count < 2); ++i)
|
|
if ((dptr->units[i].flags & UNIT_ATTABLE) &&
|
|
!(dptr->units[i].flags & UNIT_DIS)) {
|
|
fprintf (st, " sim> ATTACH {switches} %s%d diskfile\n", dptr->name, i);
|
|
++out_count;
|
|
}
|
|
if (attachable_count > 4) {
|
|
fprintf (st, " .\n");
|
|
fprintf (st, " .\n");
|
|
fprintf (st, " .\n");
|
|
}
|
|
skip_count = attachable_count - 2;
|
|
for (i=0; i < dptr->numunits; ++i)
|
|
if ((dptr->units[i].flags & UNIT_ATTABLE) &&
|
|
!(dptr->units[i].flags & UNIT_DIS)) {
|
|
if (skip_count == 0)
|
|
fprintf (st, " sim> ATTACH {switches} %s%d diskfile\n", dptr->name, i);
|
|
else
|
|
--skip_count;
|
|
}
|
|
}
|
|
else
|
|
fprintf (st, " sim> ATTACH {switches} %s diskfile\n", dptr->name);
|
|
fprintf (st, "\n%s attach command switches\n", dptr->name);
|
|
fprintf (st, " -R Attach Read Only.\n");
|
|
fprintf (st, " -E Must Exist (if not specified an attempt to create the indicated\n");
|
|
fprintf (st, " disk container will be attempted).\n");
|
|
fprintf (st, " -F Open the indicated disk container in a specific format (default\n");
|
|
fprintf (st, " is to autodetect VHD defaulting to RAW if the indicated\n");
|
|
fprintf (st, " container is not a VHD).\n");
|
|
fprintf (st, " -I Initialize newly created disk so that each sector contains its\n");
|
|
fprintf (st, " sector address\n");
|
|
fprintf (st, " -K Verify that the disk contents contain the sector address in each\n");
|
|
fprintf (st, " sector. Whole disk checked at attach time and each sector is\n");
|
|
fprintf (st, " checked when written.\n");
|
|
fprintf (st, " -C Create a disk container and copy its contents from another disk\n");
|
|
fprintf (st, " (simh, VHD, or RAW format). The current (or specified with -F)\n");
|
|
fprintf (st, " container format will be the format of the created container.\n");
|
|
fprintf (st, " AUTO or VHD will create a VHD container, SIMH will create a.\n");
|
|
fprintf (st, " SIMH container. Add a -V switch to verify a copy operation.\n");
|
|
fprintf (st, " -V Perform a verification pass to confirm successful data copy\n");
|
|
fprintf (st, " operation.\n");
|
|
fprintf (st, " -X When creating a VHD, create a fixed sized VHD (vs a Dynamically\n");
|
|
fprintf (st, " expanding one).\n");
|
|
fprintf (st, " -D Create a Differencing VHD (relative to an already existing VHD\n");
|
|
fprintf (st, " disk)\n");
|
|
fprintf (st, " -M Merge a Differencing VHD into its parent VHD disk\n");
|
|
fprintf (st, " -O Override consistency checks when attaching differencing disks\n");
|
|
fprintf (st, " which have unexpected parent disk GUID or timestamps\n\n");
|
|
fprintf (st, " -U Fix inconsistencies which are overridden by the -O switch\n");
|
|
if (strstr (sim_name, "-10") == NULL) {
|
|
fprintf (st, " -Y Answer Yes to prompt to overwrite last track (on disk create)\n");
|
|
fprintf (st, " -N Answer No to prompt to overwrite last track (on disk create)\n");
|
|
}
|
|
fprintf (st, "Examples:\n");
|
|
fprintf (st, " sim> show %s\n", ex->dname);
|
|
fprintf (st, " %s, address=20001468-2000146B*, no vector, 4 units\n", ex->dname);
|
|
fprintf (st, " %s0, %s, not attached, write enabled, %s, autosize, AUTO detect format\n", ex->dname, ex->dsize, ex->dtype);
|
|
fprintf (st, " %s1, %s, not attached, write enabled, %s, autosize, AUTO detect format\n", ex->dname, ex->dsize, ex->dtype);
|
|
fprintf (st, " %s2, %s, not attached, write enabled, %s, autosize, AUTO detect format\n", ex->dname, ex->dsize, ex->dtype);
|
|
fprintf (st, " %s3, %s, not attached, write enabled, %s, autosize, AUTO detect format\n", ex->dname, ex->dsize2, ex->dtype2);
|
|
fprintf (st, " sim> # attach an existing VHD and determine its size and type automatically\n");
|
|
fprintf (st, " sim> attach %s0 %s.vhd\n", ex->dname, ex->dtype3);
|
|
fprintf (st, " sim> show %s0\n", ex->dname);
|
|
fprintf (st, " %s0, %s, attached to %s.vhd, write enabled, %s, autosize, VHD format\n", ex->dname, ex->dsize3, ex->dtype3, ex->dtype3);
|
|
fprintf (st, " sim> # create a new %s drive type VHD\n", ex->dtype4);
|
|
fprintf (st, " sim> set %s2 %s\n", ex->dname, ex->dtype4);
|
|
fprintf (st, " sim> attach %s2 -f vhd %s.vhd\n", ex->dname, ex->dtype4);
|
|
fprintf (st, " %s2: creating new file\n", ex->dname);
|
|
fprintf (st, " sim> show %s2\n", ex->dname);
|
|
fprintf (st, " %s2, %s, attached to %s.vhd, write enabled, %s, autosize, VHD format\n", ex->dname, ex->dsize4, ex->dtype4, ex->dtype4);
|
|
fprintf (st, " sim> # examine the size consumed by the %s VHD file\n", ex->dsize4);
|
|
fprintf (st, " sim> dir %s.vhd\n", ex->dtype4);
|
|
fprintf (st, " Directory of H:\\Data\n\n");
|
|
fprintf (st, " 04/14/2011 12:57 PM 5,120 %s.vhd\n", ex->dtype4);
|
|
fprintf (st, " 1 File(s) 5,120 bytes\n");
|
|
fprintf (st, " sim> # create a differencing vhd (%s-1-Diff.vhd) with %s.vhd as parent\n", ex->dtype4, ex->dtype4);
|
|
fprintf (st, " sim> attach %s3 -d %s-1-Diff.vhd %s.vhd\n", ex->dname, ex->dtype4, ex->dtype4);
|
|
fprintf (st, " sim> # create a VHD (%s-1.vhd) which is a copy of an existing disk\n", ex->dtype4);
|
|
fprintf (st, " sim> attach %s3 -c %s-1.vhd %s.vhd\n", ex->dname, ex->dtype4, ex->dtype4);
|
|
fprintf (st, " %s3: creating new virtual disk '%s-1.vhd'\n", ex->dname, ex->dtype4);
|
|
fprintf (st, " %s3: Copied %s. 99%% complete.\n", ex->dname, ex->dsize4);
|
|
fprintf (st, " %s3: Copied %s. Done.\n", ex->dname, ex->dsize4);
|
|
fprintf (st, " sim> show %s3\n", ex->dname);
|
|
fprintf (st, " %s3, %s, attached to %s-1.vhd, write enabled, %s, autosize, VHD format\n", ex->dname, ex->dsize4, ex->dtype4, ex->dtype4);
|
|
fprintf (st, " sim> dir %s*\n", ex->dtype4);
|
|
fprintf (st, " Directory of H:\\Data\n\n");
|
|
fprintf (st, " 04/14/2011 01:12 PM 5,120 %s-1.vhd\n", ex->dtype4);
|
|
fprintf (st, " 04/14/2011 12:58 PM 5,120 %s.vhd\n", ex->dtype4);
|
|
fprintf (st, " 2 File(s) 10,240 bytes\n");
|
|
fprintf (st, " sim> show %s2\n", ex->dname);
|
|
fprintf (st, " %s2, %s, not attached, write enabled, %s, autosize, VHD format\n", ex->dname, ex->dsize4, ex->dtype4);
|
|
fprintf (st, " sim> set %s2 %s\n", ex->dname, ex->dtype3);
|
|
fprintf (st, " sim> set %s2 noauto\n", ex->dname);
|
|
fprintf (st, " sim> show %s2\n", ex->dname);
|
|
fprintf (st, " %s2, %s, not attached, write enabled, %s, noautosize, VHD format\n", ex->dname, ex->dsize3, ex->dtype3);
|
|
fprintf (st, " sim> set %s2 format=simh\n", ex->dname);
|
|
fprintf (st, " sim> show %s2\n", ex->dname);
|
|
fprintf (st, " %s2, %s, not attached, write enabled, %s, noautosize, SIMH format\n", ex->dname, ex->dsize3, ex->dtype3);
|
|
fprintf (st, " sim> # create a VHD from an existing SIMH format disk\n");
|
|
fprintf (st, " sim> attach %s2 -c %s-Copy.vhd XYZZY.dsk\n", ex->dname, ex->dtype3);
|
|
fprintf (st, " %s2: creating new virtual disk '%s-Copy.vhd'\n", ex->dname, ex->dtype3);
|
|
fprintf (st, " %s2: Copied %s. 99%% complete.\n", ex->dname, ex->dsize3);
|
|
fprintf (st, " %s2: Copied %s. Done.\n", ex->dname, ex->dsize3);
|
|
fprintf (st, " sim> show %s2\n", ex->dname);
|
|
fprintf (st, " %s2, %s, attached to %s-Copy.vhd, write enabled, %s, noautosize, VHD format\n", ex->dname, ex->dsize3, ex->dtype3, ex->dtype3);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_bool sim_disk_vhd_support (void)
|
|
{
|
|
return SCPE_OK == sim_vhd_disk_implemented ();
|
|
}
|
|
|
|
t_bool sim_disk_raw_support (void)
|
|
{
|
|
return SCPE_OK == sim_os_disk_implemented_raw ();
|
|
}
|
|
|
|
t_stat sim_disk_reset (UNIT *uptr)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
if (!(uptr->flags & UNIT_ATT)) /* attached? */
|
|
return SCPE_OK;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_disk_reset(unit=%d)\n", (int)(uptr - ctx->dptr->units));
|
|
|
|
_sim_disk_io_flush(uptr);
|
|
AIO_VALIDATE(uptr);
|
|
AIO_UPDATE_QUEUE;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat sim_disk_perror (UNIT *uptr, const char *msg)
|
|
{
|
|
int saved_errno = errno;
|
|
|
|
if (!(uptr->flags & UNIT_ATTABLE)) /* not attachable? */
|
|
return SCPE_NOATT;
|
|
switch (DK_GET_FMT (uptr)) { /* case on format */
|
|
case DKUF_F_STD: /* SIMH format */
|
|
case DKUF_F_VHD: /* VHD format */
|
|
case DKUF_F_RAW: /* Raw Physical Disk Access */
|
|
#if defined(_WIN32)
|
|
saved_errno = GetLastError ();
|
|
#endif
|
|
perror (msg);
|
|
sim_printf ("%s %s: %s\n", sim_uname(uptr), msg, sim_get_os_error_text (saved_errno));
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat sim_disk_clearerr (UNIT *uptr)
|
|
{
|
|
if (!(uptr->flags & UNIT_ATTABLE)) /* not attachable? */
|
|
return SCPE_NOATT;
|
|
switch (DK_GET_FMT (uptr)) { /* case on format */
|
|
case DKUF_F_STD: /* SIMH format */
|
|
clearerr (uptr->fileref);
|
|
break;
|
|
case DKUF_F_VHD: /* VHD format */
|
|
sim_vhd_disk_clearerr (uptr);
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Factory bad block table creation routine
|
|
|
|
This routine writes a DEC standard 144 compliant bad block table on the
|
|
last track of the specified unit as described in:
|
|
EL-00144_B_DEC_STD_144_Disk_Standard_for_Recording_and_Handling_Bad_Sectors_Nov76.pdf
|
|
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
|
|
wds = number of words per sector
|
|
Outputs:
|
|
sta = status code
|
|
*/
|
|
|
|
t_stat sim_disk_pdp11_bad_block (UNIT *uptr, int32 sec, int32 wds)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
int32 i;
|
|
t_addr da;
|
|
uint16 *buf;
|
|
DEVICE *dptr;
|
|
char *namebuf, *c;
|
|
uint32 packid;
|
|
t_stat stat = SCPE_OK;
|
|
|
|
if ((sec < 2) || (wds < 16))
|
|
return SCPE_ARG;
|
|
if ((uptr->flags & UNIT_ATT) == 0)
|
|
return SCPE_UNATT;
|
|
if ((dptr = find_dev_from_unit (uptr)) == NULL)
|
|
return SCPE_NOATT;
|
|
if (uptr->flags & UNIT_RO)
|
|
return SCPE_RO;
|
|
if (!get_yn ("Overwrite last track? [N]", FALSE))
|
|
return SCPE_OK;
|
|
if ((buf = (uint16 *) malloc (wds * sizeof (uint16))) == NULL)
|
|
return SCPE_MEM;
|
|
namebuf = uptr->filename;
|
|
if ((c = strrchr (namebuf, '/')))
|
|
namebuf = c+1;
|
|
if ((c = strrchr (namebuf, '\\')))
|
|
namebuf = c+1;
|
|
if ((c = strrchr (namebuf, ']')))
|
|
namebuf = c+1;
|
|
packid = eth_crc32(0, namebuf, strlen (namebuf));
|
|
buf[0] = (uint16)packid;
|
|
buf[1] = (uint16)(packid >> 16) & 0x7FFF; /* Make sure MSB is clear */
|
|
buf[2] = buf[3] = 0;
|
|
for (i = 4; i < wds; i++)
|
|
buf[i] = 0177777u;
|
|
da = (uptr->capac*((dptr->flags & DEV_SECTORS) ? 512 : 1)) - (sec * wds);
|
|
for (i = 0; (stat == SCPE_OK) && (i < sec) && (i < 10); i++, da += wds)
|
|
if (ctx)
|
|
stat = sim_disk_wrsect (uptr, (t_lba)(da/wds), (uint8 *)buf, NULL, 1);
|
|
else {
|
|
if (sim_fseek (uptr->fileref, da, SEEK_SET)) {
|
|
stat = SCPE_IOERR;
|
|
break;
|
|
}
|
|
if ((size_t)wds != sim_fwrite (buf, sizeof (uint16), wds, uptr->fileref))
|
|
stat = SCPE_IOERR;
|
|
}
|
|
free (buf);
|
|
return stat;
|
|
}
|
|
|
|
void sim_disk_data_trace(UNIT *uptr, const uint8 *data, size_t lba, size_t len, const char* txt, int detail, uint32 reason)
|
|
{
|
|
DEVICE *dptr = find_dev_from_unit (uptr);
|
|
|
|
if (sim_deb && ((uptr->dctrl | dptr->dctrl) & reason)) {
|
|
char pos[32];
|
|
|
|
sprintf (pos, "lbn: %08X ", (unsigned int)lba);
|
|
sim_data_trace(dptr, uptr, (detail ? data : NULL), pos, len, txt, reason);
|
|
}
|
|
}
|
|
|
|
/* OS Specific RAW Disk I/O support */
|
|
|
|
#if defined _WIN32
|
|
|
|
static void _set_errno_from_status (DWORD dwStatus)
|
|
{
|
|
switch (dwStatus) {
|
|
case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND:
|
|
case ERROR_INVALID_DRIVE: case ERROR_NO_MORE_FILES:
|
|
case ERROR_BAD_NET_NAME: case ERROR_BAD_NETPATH:
|
|
case ERROR_BAD_PATHNAME: case ERROR_FILENAME_EXCED_RANGE:
|
|
errno = ENOENT;
|
|
return;
|
|
case ERROR_INVALID_ACCESS: case ERROR_INVALID_DATA:
|
|
case ERROR_INVALID_FUNCTION: case ERROR_INVALID_PARAMETER:
|
|
case ERROR_NEGATIVE_SEEK:
|
|
errno = EINVAL;
|
|
return;
|
|
case ERROR_ARENA_TRASHED: case ERROR_NOT_ENOUGH_MEMORY:
|
|
case ERROR_INVALID_BLOCK: case ERROR_NOT_ENOUGH_QUOTA:
|
|
errno = ENOMEM;
|
|
return;
|
|
case ERROR_TOO_MANY_OPEN_FILES:
|
|
errno = EMFILE;
|
|
return;
|
|
case ERROR_ACCESS_DENIED: case ERROR_CURRENT_DIRECTORY:
|
|
case ERROR_LOCK_VIOLATION: case ERROR_NETWORK_ACCESS_DENIED:
|
|
case ERROR_CANNOT_MAKE: case ERROR_FAIL_I24:
|
|
case ERROR_DRIVE_LOCKED: case ERROR_SEEK_ON_DEVICE:
|
|
case ERROR_NOT_LOCKED: case ERROR_LOCK_FAILED:
|
|
errno = EACCES;
|
|
return;
|
|
case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS:
|
|
errno = EEXIST;
|
|
return;
|
|
case ERROR_INVALID_HANDLE: case ERROR_INVALID_TARGET_HANDLE:
|
|
case ERROR_DIRECT_ACCESS_HANDLE:
|
|
errno = EBADF;
|
|
return;
|
|
case ERROR_DIR_NOT_EMPTY:
|
|
errno = ENOTEMPTY;
|
|
return;
|
|
case ERROR_BAD_ENVIRONMENT:
|
|
errno = E2BIG;
|
|
return;
|
|
case ERROR_BAD_FORMAT:
|
|
errno = ENOEXEC;
|
|
return;
|
|
case ERROR_NOT_SAME_DEVICE:
|
|
errno = EXDEV;
|
|
return;
|
|
case ERROR_BROKEN_PIPE:
|
|
errno = EPIPE;
|
|
return;
|
|
case ERROR_DISK_FULL:
|
|
errno = ENOSPC;
|
|
return;
|
|
case ERROR_WAIT_NO_CHILDREN: case ERROR_CHILD_NOT_COMPLETE:
|
|
errno = ECHILD;
|
|
return;
|
|
case ERROR_NO_PROC_SLOTS: case ERROR_MAX_THRDS_REACHED:
|
|
case ERROR_NESTING_NOT_ALLOWED:
|
|
errno = EAGAIN;
|
|
return;
|
|
}
|
|
if ((dwStatus >= ERROR_WRITE_PROTECT) && (dwStatus <= ERROR_SHARING_BUFFER_EXCEEDED)) {
|
|
errno = EACCES;
|
|
return;
|
|
}
|
|
if ((dwStatus >= ERROR_INVALID_STARTING_CODESEG) && (dwStatus <= ERROR_INFLOOP_IN_RELOC_CHAIN)) {
|
|
errno = ENOEXEC;
|
|
return;
|
|
}
|
|
errno = EINVAL;
|
|
}
|
|
#if defined(__GNUC__) && defined(HAVE_NTDDDISK_H)
|
|
#include <ddk/ntddstor.h>
|
|
#include <ddk/ntdddisk.h>
|
|
#else
|
|
#include <winioctl.h>
|
|
#endif
|
|
|
|
#if defined(__cplusplus)
|
|
extern "C" {
|
|
#endif
|
|
WINBASEAPI BOOL WINAPI GetFileSizeEx(HANDLE hFile, PLARGE_INTEGER lpFileSize);
|
|
#if defined(__cplusplus)
|
|
}
|
|
#endif
|
|
|
|
struct _device_type {
|
|
int32 Type;
|
|
const 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 sim_toffset_64 ? SCPE_OK : SCPE_NOFNC;
|
|
}
|
|
|
|
static FILE *sim_os_disk_open_raw (const char *rawdevicename, const char *openmode)
|
|
{
|
|
HANDLE Handle;
|
|
DWORD DesiredAccess = 0;
|
|
uint32 is_cdrom;
|
|
char *tmpname = (char *)malloc (2 + strlen (rawdevicename));
|
|
|
|
if (tmpname == NULL)
|
|
return NULL;
|
|
if (strchr (openmode, 'r'))
|
|
DesiredAccess |= GENERIC_READ;
|
|
if (strchr (openmode, 'w') || strchr (openmode, '+'))
|
|
DesiredAccess |= GENERIC_WRITE;
|
|
/* SCP Command Line parsing replaces \\ with \ presuming this is an
|
|
escape sequence. This only affecdts RAW device names and UNC paths.
|
|
We handle the RAW device name case here by prepending paths beginning
|
|
with \.\ with an extra \. */
|
|
if ((!memcmp ("\\.\\", rawdevicename, 3)) ||
|
|
(!memcmp ("/./", rawdevicename, 3))) {
|
|
*tmpname = '\\';
|
|
strcpy (tmpname + 1, rawdevicename);
|
|
}
|
|
else
|
|
strcpy (tmpname, rawdevicename);
|
|
Handle = CreateFileA (tmpname, DesiredAccess, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS|FILE_FLAG_WRITE_THROUGH, NULL);
|
|
free (tmpname);
|
|
if (Handle != INVALID_HANDLE_VALUE) {
|
|
if ((sim_os_disk_info_raw ((FILE *)Handle, NULL, NULL, &is_cdrom)) ||
|
|
((DesiredAccess & GENERIC_WRITE) && is_cdrom)) {
|
|
CloseHandle (Handle);
|
|
errno = EACCES;
|
|
return NULL;
|
|
}
|
|
return (FILE *)Handle;
|
|
}
|
|
_set_errno_from_status (GetLastError ());
|
|
return NULL;
|
|
}
|
|
|
|
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_offset sim_os_disk_size_raw (FILE *Disk)
|
|
{
|
|
DWORD IoctlReturnSize;
|
|
LARGE_INTEGER Size;
|
|
|
|
if (GetFileSizeEx((HANDLE)Disk, &Size))
|
|
return (t_offset)(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_offset)(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_offset)(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_offset)(G.Cylinders.QuadPart*G.TracksPerCylinder*G.SectorsPerTrack*G.BytesPerSector);
|
|
}
|
|
#endif
|
|
_set_errno_from_status (GetLastError ());
|
|
return (t_offset)-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, NULL);
|
|
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, NULL);
|
|
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, uint32 *is_cdrom)
|
|
{
|
|
DWORD IoctlReturnSize;
|
|
STORAGE_DEVICE_NUMBER Device;
|
|
|
|
ZeroMemory (&Device, sizeof (Device));
|
|
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 */
|
|
|
|
if (sector_size)
|
|
*sector_size = 512;
|
|
if (removable)
|
|
*removable = 0;
|
|
if (is_cdrom)
|
|
*is_cdrom = (Device.DeviceType == FILE_DEVICE_CD_ROM) || (Device.DeviceType == FILE_DEVICE_DVD);
|
|
#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
|
|
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 = ((long long)lba) * ctx->sector_size;
|
|
DWORD bytestoread = sects * ctx->sector_size;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
|
|
if (sectsread)
|
|
*sectsread = 0;
|
|
memset (&pos, 0, sizeof (pos));
|
|
while (bytestoread) {
|
|
DWORD bytesread;
|
|
DWORD sectorbytes;
|
|
|
|
pos.Offset = (DWORD)addr;
|
|
pos.OffsetHigh = (DWORD)(addr >> 32);
|
|
if (!ReadFile ((HANDLE)(uptr->fileref), buf, bytestoread, &bytesread, &pos)) {
|
|
if (ERROR_HANDLE_EOF == GetLastError ()) { /* Return 0's for reads past EOF */
|
|
memset (buf, 0, bytestoread);
|
|
if (sectsread)
|
|
*sectsread += bytestoread / ctx->sector_size;
|
|
return SCPE_OK;
|
|
}
|
|
_set_errno_from_status (GetLastError ());
|
|
return SCPE_IOERR;
|
|
}
|
|
sectorbytes = (bytesread / ctx->sector_size) * ctx->sector_size;
|
|
if (sectsread)
|
|
*sectsread += sectorbytes / ctx->sector_size;
|
|
bytestoread -= sectorbytes;
|
|
if (bytestoread == 0)
|
|
break;
|
|
buf += sectorbytes;
|
|
addr += sectorbytes;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat sim_os_disk_read (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *bytesread, uint32 bytes)
|
|
{
|
|
OVERLAPPED pos;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_read(unit=%d, addr=0x%X, bytes=%u)\n", (int)(uptr - ctx->dptr->units), (uint32)addr, bytes);
|
|
|
|
memset (&pos, 0, sizeof (pos));
|
|
pos.Offset = (DWORD)addr;
|
|
pos.OffsetHigh = (DWORD)(addr >> 32);
|
|
if (ReadFile ((HANDLE)(uptr->fileref), buf, (DWORD)bytes, (LPDWORD)bytesread, &pos))
|
|
return SCPE_OK;
|
|
if (ERROR_HANDLE_EOF == GetLastError ()) { /* Return 0's for reads past EOF */
|
|
memset (buf, 0, bytes);
|
|
if (bytesread)
|
|
*bytesread = bytes;
|
|
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;
|
|
DWORD byteswritten;
|
|
DWORD bytestowrite = sects * ctx->sector_size;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
|
|
|
|
if (sectswritten)
|
|
*sectswritten = 0;
|
|
addr = ((long long)lba) * ctx->sector_size;
|
|
memset (&pos, 0, sizeof (pos));
|
|
while (bytestowrite) {
|
|
DWORD sectorbytes;
|
|
|
|
pos.Offset = (DWORD)addr;
|
|
pos.OffsetHigh = (DWORD)(addr >> 32);
|
|
if (!WriteFile ((HANDLE)(uptr->fileref), buf, bytestowrite, &byteswritten, &pos)) {
|
|
_set_errno_from_status (GetLastError ());
|
|
return SCPE_IOERR;
|
|
}
|
|
if (sectswritten)
|
|
*sectswritten += byteswritten / ctx->sector_size;
|
|
sectorbytes = (byteswritten / ctx->sector_size) * ctx->sector_size;
|
|
bytestowrite -= sectorbytes;
|
|
if (bytestowrite == 0)
|
|
break;
|
|
buf += sectorbytes;
|
|
addr += sectorbytes;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat sim_os_disk_write (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *byteswritten, uint32 bytes)
|
|
{
|
|
OVERLAPPED pos;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_write(unit=%d, lba=0x%X, bytes=%u)\n", (int)(uptr - ctx->dptr->units), (uint32)addr, bytes);
|
|
|
|
memset (&pos, 0, sizeof (pos));
|
|
pos.Offset = (DWORD)addr;
|
|
pos.OffsetHigh = (DWORD)(addr >> 32);
|
|
if (WriteFile ((HANDLE)(uptr->fileref), buf, bytes, (LPDWORD)byteswritten, &pos))
|
|
return SCPE_OK;
|
|
_set_errno_from_status (GetLastError ());
|
|
return SCPE_IOERR;
|
|
}
|
|
|
|
#elif defined (__linux) || defined (__linux__) || defined (__APPLE__)|| defined (__sun) || defined (__sun__) || defined (__hpux) || defined (_AIX)
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#if defined(HAVE_SYS_IOCTL)
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
#if defined(HAVE_LINUX_CDROM)
|
|
#include <linux/cdrom.h>
|
|
#endif
|
|
|
|
static t_stat sim_os_disk_implemented_raw (void)
|
|
{
|
|
return sim_toffset_64 ? SCPE_OK : SCPE_NOFNC;
|
|
}
|
|
|
|
static FILE *sim_os_disk_open_raw (const char *rawdevicename, const char *openmode)
|
|
{
|
|
int mode = 0;
|
|
int fd;
|
|
|
|
if (strchr (openmode, 'r') && (strchr (openmode, '+') || strchr (openmode, 'w')))
|
|
mode = O_RDWR;
|
|
else
|
|
if (strchr (openmode, 'r'))
|
|
mode = O_RDONLY;
|
|
#ifdef O_LARGEFILE
|
|
mode |= O_LARGEFILE;
|
|
#endif
|
|
#ifdef O_DSYNC
|
|
mode |= O_DSYNC;
|
|
#endif
|
|
fd = open (rawdevicename, mode, 0);
|
|
if (fd < 0)
|
|
return (FILE *)NULL;
|
|
return (FILE *)((long)fd);
|
|
}
|
|
|
|
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_offset sim_os_disk_size_raw (FILE *f)
|
|
{
|
|
t_offset pos, size;
|
|
|
|
pos = (t_offset)lseek ((int)((long)f), (off_t)0, SEEK_CUR);
|
|
size = (t_offset)lseek ((int)((long)f), (off_t)0, SEEK_END);
|
|
if (pos != (t_offset)-1)
|
|
(void)lseek ((int)((long)f), (off_t)pos, SEEK_SET);
|
|
return size;
|
|
}
|
|
|
|
static t_stat sim_os_disk_unload_raw (FILE *f)
|
|
{
|
|
#if defined(CDROM_GET_CAPABILITY) && defined(CDROMEJECT) && defined(CDROMEJECT_SW)
|
|
if (ioctl ((int)((long)f), CDROM_GET_CAPABILITY, NULL) < 0)
|
|
return SCPE_OK;
|
|
if (ioctl((int)((long)f), CDROM_LOCKDOOR, 0) < 0)
|
|
return SCPE_IOERR;
|
|
if (ioctl((int)((long)f), CDROMEJECT) < 0)
|
|
return SCPE_IOERR;
|
|
#endif
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_bool sim_os_disk_isavailable_raw (FILE *Disk)
|
|
{
|
|
#if defined(CDROMSTART) && defined(CDROM_GET_CAPABILITY)
|
|
if (ioctl ((int)((long)Disk), CDROM_GET_CAPABILITY, NULL) < 0)
|
|
return TRUE;
|
|
switch (ioctl((int)((long)Disk), CDROM_DRIVE_STATUS, CDSL_NONE)) {
|
|
case CDS_NO_INFO:
|
|
case CDS_NO_DISC:
|
|
case CDS_TRAY_OPEN:
|
|
case CDS_DRIVE_NOT_READY:
|
|
default: /* error */
|
|
return FALSE;
|
|
case CDS_DISC_OK:
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
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 = ((off_t)lba) * ctx->sector_size;
|
|
ssize_t bytesread;
|
|
size_t bytestoread = sects * ctx->sector_size;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
|
|
|
|
if (sectsread)
|
|
*sectsread = 0;
|
|
while (bytestoread) {
|
|
size_t sectorbytes;
|
|
|
|
bytesread = pread((int)((long)uptr->fileref), buf, bytestoread, addr);
|
|
if (bytesread < 0) {
|
|
return SCPE_IOERR;
|
|
}
|
|
if (bytesread == 0) { /* read zeros at/past EOF */
|
|
bytesread = bytestoread;
|
|
memset (buf, 0, bytesread);
|
|
}
|
|
sectorbytes = (bytesread / ctx->sector_size) * ctx->sector_size;
|
|
if (sectsread)
|
|
*sectsread += sectorbytes / ctx->sector_size;
|
|
bytestoread -= sectorbytes;
|
|
if (bytestoread == 0)
|
|
break;
|
|
buf += sectorbytes;
|
|
addr += sectorbytes;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat sim_os_disk_read (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *rbytesread, uint32 bytes)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
ssize_t bytesread;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_read(unit=%d, addr=0x%X, bytes=%u)\n", (int)(uptr - ctx->dptr->units), (uint32)addr, bytes);
|
|
|
|
bytesread = pread((int)((long)uptr->fileref), buf, bytes, (off_t)addr);
|
|
if (bytesread < 0) {
|
|
if (rbytesread)
|
|
*rbytesread = 0;
|
|
return SCPE_IOERR;
|
|
}
|
|
if (rbytesread)
|
|
*rbytesread = bytesread;
|
|
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 = ((off_t)lba) * ctx->sector_size;
|
|
ssize_t byteswritten;
|
|
size_t bytestowrite = sects * ctx->sector_size;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
|
|
|
|
if (sectswritten)
|
|
*sectswritten = 0;
|
|
while (bytestowrite) {
|
|
size_t sectorbytes;
|
|
|
|
byteswritten = pwrite((int)((long)uptr->fileref), buf, bytestowrite, addr);
|
|
if (byteswritten < 0) {
|
|
return SCPE_IOERR;
|
|
}
|
|
if (sectswritten)
|
|
*sectswritten += byteswritten / ctx->sector_size;
|
|
sectorbytes = (byteswritten / ctx->sector_size) * ctx->sector_size;
|
|
bytestowrite -= sectorbytes;
|
|
if (bytestowrite == 0)
|
|
break;
|
|
buf += sectorbytes;
|
|
addr += sectorbytes;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat sim_os_disk_write (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *rbyteswritten, uint32 bytes)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
ssize_t byteswritten;
|
|
|
|
sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_write(unit=%d, addr=0x%X, bytes=%u)\n", (int)(uptr - ctx->dptr->units), (uint32)addr, bytes);
|
|
|
|
if (rbyteswritten)
|
|
*rbyteswritten = 0;
|
|
byteswritten = pwrite((int)((long)uptr->fileref), buf, bytes, (off_t)addr);
|
|
if (byteswritten < 0)
|
|
return SCPE_IOERR;
|
|
if (rbyteswritten)
|
|
*rbyteswritten = byteswritten;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat sim_os_disk_info_raw (FILE *f, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom)
|
|
{
|
|
if (sector_size) {
|
|
#if defined(BLKSSZGET)
|
|
if (ioctl ((int)((long)f), BLKSSZGET, sector_size) < 0)
|
|
#endif
|
|
*sector_size = 512;
|
|
}
|
|
if (removable)
|
|
*removable = 0;
|
|
if (is_cdrom) {
|
|
#if defined(CDROM_GET_CAPABILITY)
|
|
int cd_cap = ioctl ((int)((long)f), CDROM_GET_CAPABILITY, NULL);
|
|
|
|
if (cd_cap < 0)
|
|
*is_cdrom = 0;
|
|
else {
|
|
*is_cdrom = 1;
|
|
if (removable)
|
|
*removable = 1;
|
|
if (sector_size)
|
|
*sector_size = 2048;
|
|
}
|
|
#else
|
|
*is_cdrom = 0;
|
|
#endif
|
|
}
|
|
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_offset sim_os_disk_size_raw (FILE *f)
|
|
{
|
|
return (t_offset)-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_read (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *bytesread, uint32 bytes)
|
|
{
|
|
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_write (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *byteswritten, uint32 bytes)
|
|
{
|
|
return SCPE_NOFNC;
|
|
}
|
|
|
|
static t_stat sim_os_disk_info_raw (FILE *f, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom)
|
|
{
|
|
return SCPE_NOFNC;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* OS Independent Disk Virtual Disk (VHD) I/O support */
|
|
|
|
#if (defined (VMS) && !(defined (__ALPHA) || defined (__ia64)))
|
|
#define DONT_DO_VHD_SUPPORT /* VAX/VMS compilers don't have 64 bit integers */
|
|
#endif
|
|
|
|
#if defined (DONT_DO_VHD_SUPPORT)
|
|
|
|
/*============================================================================*/
|
|
/* Non-implemented version */
|
|
/* This is only for hody systems which don't have 64 bit integer types */
|
|
/*============================================================================*/
|
|
|
|
static t_stat sim_vhd_disk_implemented (void)
|
|
{
|
|
return SCPE_NOFNC;
|
|
}
|
|
|
|
static FILE *sim_vhd_disk_open (const char *vhdfilename, const char *openmode)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static FILE *sim_vhd_disk_merge (const char *szVHDPath, char **ParentVHD)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static FILE *sim_vhd_disk_create (const char *szVHDPath, t_offset 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_offset sim_vhd_disk_size (FILE *f)
|
|
{
|
|
return (t_offset)-1;
|
|
}
|
|
|
|
static t_stat sim_vhd_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
|
|
{
|
|
return SCPE_IOERR;
|
|
}
|
|
|
|
static t_stat sim_vhd_disk_clearerr (UNIT *uptr)
|
|
{
|
|
return SCPE_IOERR;
|
|
}
|
|
|
|
static t_stat sim_vhd_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
|
|
{
|
|
return SCPE_IOERR;
|
|
}
|
|
|
|
static t_stat sim_vhd_disk_set_dtype (FILE *f, const char *dtype)
|
|
{
|
|
return SCPE_NOFNC;
|
|
}
|
|
|
|
static const char *sim_vhd_disk_get_dtype (FILE *f, uint32 *SectorSize, uint32 *xfer_element_size, char sim_name[64], time_t *creation_time)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#else
|
|
|
|
/*++
|
|
This code follows the details specified in the "Virtual Hard Disk Image
|
|
Format Specification", Version 1.0 October 11, 2006. This format
|
|
specification is available for anyone to implement under the
|
|
"Microsoft Open Specification Promise" described at:
|
|
http://www.microsoft.com/interop/osp/default.mspx.
|
|
--*/
|
|
|
|
typedef t_uint64 uint64;
|
|
typedef t_int64 int64;
|
|
|
|
typedef struct _VHD_Footer {
|
|
/*
|
|
Cookies are used to uniquely identify the original creator of the hard disk
|
|
image. The values are case-sensitive. Microsoft uses the "conectix" string
|
|
to identify this file as a hard disk image created by Microsoft Virtual
|
|
Server, Virtual PC, and predecessor products. The cookie is stored as an
|
|
eight-character ASCII string with the "c" in the first byte, the "o" in
|
|
the second byte, and so on.
|
|
*/
|
|
char Cookie[8];
|
|
/*
|
|
This is a bit field used to indicate specific feature support. The following
|
|
table displays the list of features.
|
|
Any fields not listed are reserved.
|
|
|
|
Feature Value:
|
|
No features enabled 0x00000000
|
|
Temporary 0x00000001
|
|
Reserved 0x00000002
|
|
|
|
No features enabled.
|
|
The hard disk image has no special features enabled in it.
|
|
Temporary.
|
|
This bit is set if the current disk is a temporary disk. A
|
|
temporary disk designation indicates to an application that
|
|
this disk is a candidate for deletion on shutdown.
|
|
Reserved.
|
|
This bit must always be set to 1.
|
|
All other bits are also reserved and should be set to 0.
|
|
*/
|
|
uint32 Features;
|
|
/*
|
|
This field is divided into a major/minor version and matches the version of
|
|
the specification used in creating the file. The most-significant two bytes
|
|
are for the major version. The least-significant two bytes are the minor
|
|
version. This must match the file format specification. For the current
|
|
specification, this field must be initialized to 0x00010000.
|
|
The major version will be incremented only when the file format is modified
|
|
in such a way that it is no longer compatible with older versions of the
|
|
file format.
|
|
*/
|
|
uint32 FileFormatVersion;
|
|
/*
|
|
This field holds the absolute byte offset, from the beginning of the file,
|
|
to the next structure. This field is used for dynamic disks and differencing
|
|
disks, but not fixed disks. For fixed disks, this field should be set to
|
|
0xFFFFFFFF.
|
|
*/
|
|
uint64 DataOffset;
|
|
/*
|
|
This field stores the creation time of a hard disk image. This is the number
|
|
of seconds since January 1, 2000 12:00:00 AM in UTC/GMT.
|
|
*/
|
|
uint32 TimeStamp;
|
|
/*
|
|
This field is used to document which application created the hard disk. The
|
|
field is a left-justified text field. It uses a single-byte character set.
|
|
If the hard disk is created by Microsoft Virtual PC, "vpc " is written in
|
|
this field. If the hard disk image is created by Microsoft Virtual Server,
|
|
then "vs " is written in this field.
|
|
Other applications should use their own unique identifiers.
|
|
*/
|
|
char CreatorApplication[4];
|
|
/*
|
|
This field holds the major/minor version of the application that created
|
|
the hard disk image. Virtual Server 2004 sets this value to 0x00010000 and
|
|
Virtual PC 2004 sets this to 0x00050000.
|
|
*/
|
|
uint32 CreatorVersion;
|
|
/*
|
|
This field stores the type of host operating system this disk image is
|
|
created on.
|
|
Host OS type Value
|
|
Windows 0x5769326B (Wi2k)
|
|
Macintosh 0x4D616320 (Mac )
|
|
*/
|
|
uint8 CreatorHostOS[4];
|
|
/*
|
|
This field stores the size of the hard disk in bytes, from the perspective
|
|
of the virtual machine, at creation time. This field is for informational
|
|
purposes.
|
|
*/
|
|
uint64 OriginalSize;
|
|
/*
|
|
This field stores the current size of the hard disk, in bytes, from the
|
|
perspective of the virtual machine.
|
|
This value is same as the original size when the hard disk is created.
|
|
This value can change depending on whether the hard disk is expanded.
|
|
*/
|
|
uint64 CurrentSize;
|
|
/*
|
|
This field stores the cylinder, heads, and sectors per track value for the
|
|
hard disk.
|
|
Disk Geometry field Size (bytes)
|
|
Cylinder 2
|
|
Heads 1
|
|
Sectors per track/cylinder 1
|
|
|
|
When a hard disk is configured as an ATA hard disk, the CHS values (that is,
|
|
Cylinder, Heads, Sectors per track) are used by the ATA controller to
|
|
determine the size of the disk. When the user creates a hard disk of a
|
|
certain size, the size of the hard disk image in the virtual machine is
|
|
smaller than that created by the user. This is because CHS value calculated
|
|
from the hard disk size is rounded down. The pseudo-code for the algorithm
|
|
used to determine the CHS values can be found in the appendix of this
|
|
document.
|
|
*/
|
|
uint32 DiskGeometry;
|
|
/*
|
|
Disk Type field Value
|
|
None 0
|
|
Reserved (deprecated) 1
|
|
Fixed hard disk 2
|
|
Dynamic hard disk 3
|
|
Differencing hard disk 4
|
|
Reserved (deprecated) 5
|
|
Reserved (deprecated) 6
|
|
*/
|
|
uint32 DiskType;
|
|
/*
|
|
This field holds a basic checksum of the hard disk footer. It is just a
|
|
one's complement of the sum of all the bytes in the footer without the
|
|
checksum field.
|
|
If the checksum verification fails, the Virtual PC and Virtual Server
|
|
products will instead use the header. If the checksum in the header also
|
|
fails, the file should be assumed to be corrupt. The pseudo-code for the
|
|
algorithm used to determine the checksum can be found in the appendix of
|
|
this document.
|
|
*/
|
|
uint32 Checksum;
|
|
/*
|
|
Every hard disk has a unique ID stored in the hard disk. This is used to
|
|
identify the hard disk. This is a 128-bit universally unique identifier
|
|
(UUID). This field is used to associate a parent hard disk image with its
|
|
differencing hard disk image(s).
|
|
*/
|
|
uint8 UniqueID[16];
|
|
/*
|
|
This field holds a one-byte flag that describes whether the system is in
|
|
saved state. If the hard disk is in the saved state the value is set to 1.
|
|
Operations such as compaction and expansion cannot be performed on a hard
|
|
disk in a saved state.
|
|
*/
|
|
uint8 SavedState;
|
|
/*
|
|
This field contains zeroes. It is 427 bytes in size.
|
|
*/
|
|
uint8 Reserved1[11];
|
|
/*
|
|
This field is an extension to the VHD spec and includes a simh drive type
|
|
name as a nul terminated string.
|
|
*/
|
|
uint8 DriveType[16];
|
|
uint32 DriveSectorSize;
|
|
uint32 DriveTransferElementSize;
|
|
uint8 CreatingSimulator[64];
|
|
/*
|
|
This field contains zeroes. It is 328 bytes in size.
|
|
*/
|
|
uint8 Reserved[328];
|
|
} VHD_Footer;
|
|
|
|
/*
|
|
For dynamic and differencing disk images, the "Data Offset" field within
|
|
the image footer points to a secondary structure that provides additional
|
|
information about the disk image. The dynamic disk header should appear on
|
|
a sector (512-byte) boundary.
|
|
*/
|
|
typedef struct _VHD_DynamicDiskHeader {
|
|
/*
|
|
This field holds the value "cxsparse". This field identifies the header.
|
|
*/
|
|
char Cookie[8];
|
|
/*
|
|
This field contains the absolute byte offset to the next structure in the
|
|
hard disk image. It is currently unused by existing formats and should be
|
|
set to 0xFFFFFFFF.
|
|
*/
|
|
uint64 DataOffset;
|
|
/*
|
|
This field stores the absolute byte offset of the Block Allocation Table
|
|
(BAT) in the file.
|
|
*/
|
|
uint64 TableOffset;
|
|
/*
|
|
This field stores the version of the dynamic disk header. The field is
|
|
divided into Major/Minor version. The least-significant two bytes represent
|
|
the minor version, and the most-significant two bytes represent the major
|
|
version. This must match with the file format specification. For this
|
|
specification, this field must be initialized to 0x00010000.
|
|
The major version will be incremented only when the header format is
|
|
modified in such a way that it is no longer compatible with older versions
|
|
of the product.
|
|
*/
|
|
uint32 HeaderVersion;
|
|
/*
|
|
This field holds the maximum entries present in the BAT. This should be
|
|
equal to the number of blocks in the disk (that is, the disk size divided
|
|
by the block size).
|
|
*/
|
|
uint32 MaxTableEntries;
|
|
/*
|
|
A block is a unit of expansion for dynamic and differencing hard disks. It
|
|
is stored in bytes. This size does not include the size of the block bitmap.
|
|
It is only the size of the data section of the block. The sectors per block
|
|
must always be a power of two. The default value is 0x00200000 (indicating a
|
|
block size of 2 MB).
|
|
*/
|
|
uint32 BlockSize;
|
|
/*
|
|
This field holds a basic checksum of the dynamic header. It is a one's
|
|
complement of the sum of all the bytes in the header without the checksum
|
|
field.
|
|
If the checksum verification fails the file should be assumed to be corrupt.
|
|
*/
|
|
uint32 Checksum;
|
|
/*
|
|
This field is used for differencing hard disks. A differencing hard disk
|
|
stores a 128-bit UUID of the parent hard disk. For more information, see
|
|
"Creating Differencing Hard Disk Images" later in this paper.
|
|
*/
|
|
uint8 ParentUniqueID[16];
|
|
/*
|
|
This field stores the modification time stamp of the parent hard disk. This
|
|
is the number of seconds since January 1, 2000 12:00:00 AM in UTC/GMT.
|
|
*/
|
|
uint32 ParentTimeStamp;
|
|
/*
|
|
This field should be set to zero.
|
|
*/
|
|
uint32 Reserved0;
|
|
/*
|
|
This field contains a Unicode string (UTF-16) of the parent hard disk
|
|
filename.
|
|
*/
|
|
char ParentUnicodeName[512];
|
|
/*
|
|
These entries store an absolute byte offset in the file where the parent
|
|
locator for a differencing hard disk is stored. This field is used only for
|
|
differencing disks and should be set to zero for dynamic disks.
|
|
*/
|
|
struct VHD_ParentLocator {
|
|
/*
|
|
The platform code describes which platform-specific format is used for the
|
|
file locator. For Windows, a file locator is stored as a path (for example.
|
|
"c:\disksimages\ParentDisk.vhd"). On a Macintosh system, the file locator
|
|
is a binary large object (blob) that contains an "alias." The parent locator
|
|
table is used to support moving hard disk images across platforms.
|
|
Some current platform codes include the following:
|
|
Platform Code Description
|
|
None (0x0)
|
|
Wi2r (0x57693272) [deprecated]
|
|
Wi2k (0x5769326B) [deprecated]
|
|
W2ru (0x57327275) Unicode pathname (UTF-16) on Windows relative to the differencing disk pathname.
|
|
W2ku (0x57326B75) Absolute Unicode (UTF-16) pathname on Windows.
|
|
Mac (0x4D616320) (Mac OS alias stored as a blob)
|
|
MacX(0x4D616358) A file URL with UTF-8 encoding conforming to RFC 2396.
|
|
*/
|
|
uint8 PlatformCode[4];
|
|
/*
|
|
This field stores the number of 512-byte sectors needed to store the parent
|
|
hard disk locator.
|
|
*/
|
|
uint32 PlatformDataSpace;
|
|
/*
|
|
This field stores the actual length of the parent hard disk locator in bytes.
|
|
*/
|
|
uint32 PlatformDataLength;
|
|
/*
|
|
This field must be set to zero.
|
|
*/
|
|
uint32 Reserved;
|
|
/*
|
|
This field stores the absolute file offset in bytes where the platform
|
|
specific file locator data is stored.
|
|
*/
|
|
uint64 PlatformDataOffset;
|
|
/*
|
|
This field stores the absolute file offset in bytes where the platform
|
|
specific file locator data is stored.
|
|
*/
|
|
} ParentLocatorEntries[8];
|
|
/*
|
|
This must be initialized to zeroes.
|
|
*/
|
|
char Reserved[256];
|
|
} VHD_DynamicDiskHeader;
|
|
|
|
#define VHD_BAT_FREE_ENTRY (0xFFFFFFFF)
|
|
#define VHD_DATA_BLOCK_ALIGNMENT ((uint64)4096) /* Optimum when underlying storage has 4k sectors */
|
|
|
|
#define VHD_DT_Fixed 2 /* Fixed hard disk */
|
|
#define VHD_DT_Dynamic 3 /* Dynamic hard disk */
|
|
#define VHD_DT_Differencing 4 /* Differencing hard disk */
|
|
|
|
#define VHD_Internal_SectorSize 512
|
|
|
|
typedef struct VHD_IOData *VHDHANDLE;
|
|
|
|
static t_stat ReadFilePosition(FILE *File, void *buf, size_t bufsize, uint32 *bytesread, uint64 position)
|
|
{
|
|
uint32 err = sim_fseeko (File, (t_offset)position, SEEK_SET);
|
|
size_t i;
|
|
|
|
if (bytesread)
|
|
*bytesread = 0;
|
|
if (!err) {
|
|
i = fread (buf, 1, bufsize, File);
|
|
if (bytesread)
|
|
*bytesread = (uint32)i;
|
|
err = ferror (File);
|
|
}
|
|
return (err ? SCPE_IOERR : SCPE_OK);
|
|
}
|
|
|
|
static t_stat WriteFilePosition(FILE *File, void *buf, size_t bufsize, uint32 *byteswritten, uint64 position)
|
|
{
|
|
uint32 err = sim_fseeko (File, (t_offset)position, SEEK_SET);
|
|
size_t i;
|
|
|
|
if (byteswritten)
|
|
*byteswritten = 0;
|
|
if (!err) {
|
|
i = fwrite (buf, 1, bufsize, File);
|
|
if (byteswritten)
|
|
*byteswritten = (uint32)i;
|
|
err = ferror (File);
|
|
}
|
|
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 uint64
|
|
NtoHll(uint64 value)
|
|
{
|
|
uint8 *l = (uint8 *)&value;
|
|
uint64 highresult = (uint64)l[3] | ((uint64)l[2]<<8) | ((uint64)l[1]<<16) | ((uint64)l[0]<<24);
|
|
uint32 lowresult = (uint64)l[7] | ((uint64)l[6]<<8) | ((uint64)l[5]<<16) | ((uint64)l[4]<<24);
|
|
return (highresult << 32) | lowresult;
|
|
}
|
|
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
|
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;
|
|
|
|
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 (fstat (fileno (File), &statb)) {
|
|
Return = errno;
|
|
goto Return_Cleanup;
|
|
}
|
|
else
|
|
*ModifiedTimeStamp = NtoHl ((uint32)(statb.st_mtime-946684800));
|
|
}
|
|
position = sim_fsize_ex (File);
|
|
if (((int64)position) == -1) {
|
|
Return = errno;
|
|
goto Return_Cleanup;
|
|
}
|
|
position -= sizeof(*sFooter);
|
|
if (ReadFilePosition(File,
|
|
sFooter,
|
|
sizeof(*sFooter),
|
|
NULL,
|
|
position)) {
|
|
Return = errno;
|
|
goto Return_Cleanup;
|
|
}
|
|
saved_sum = NtoHl(sFooter->Checksum);
|
|
sFooter->Checksum = 0;
|
|
sum = CalculateVhdFooterChecksum(sFooter, sizeof(*sFooter));
|
|
sFooter->Checksum = NtoHl(saved_sum);
|
|
if ((sum != saved_sum) || (memcmp("conectix", sFooter->Cookie, sizeof(sFooter->Cookie)))) {
|
|
Return = EINVAL; /* File Corrupt */
|
|
goto Return_Cleanup;
|
|
}
|
|
if (ReadFilePosition(File,
|
|
&sHeader,
|
|
sizeof(sHeader),
|
|
NULL,
|
|
(uint64)0)) {
|
|
Return = errno;
|
|
goto Return_Cleanup;
|
|
}
|
|
if ((NtoHl(sFooter->DiskType) != VHD_DT_Dynamic) &&
|
|
(NtoHl(sFooter->DiskType) != VHD_DT_Differencing) &&
|
|
(NtoHl(sFooter->DiskType) != VHD_DT_Fixed)) {
|
|
Return = EINVAL; /* File Corrupt */
|
|
goto Return_Cleanup;
|
|
}
|
|
if (((NtoHl(sFooter->DiskType) == VHD_DT_Dynamic) ||
|
|
(NtoHl(sFooter->DiskType) == VHD_DT_Differencing)) &&
|
|
memcmp(sFooter, &sHeader, sizeof(sHeader))) {
|
|
Return = EINVAL; /* File Corrupt */
|
|
goto Return_Cleanup;
|
|
}
|
|
if ((sDynamic) &&
|
|
((NtoHl(sFooter->DiskType) == VHD_DT_Dynamic) ||
|
|
(NtoHl(sFooter->DiskType) == VHD_DT_Differencing))) {
|
|
if (ReadFilePosition(File,
|
|
sDynamic,
|
|
sizeof (*sDynamic),
|
|
NULL,
|
|
NtoHll (sFooter->DataOffset))) {
|
|
Return = errno;
|
|
goto Return_Cleanup;
|
|
}
|
|
saved_sum = NtoHl (sDynamic->Checksum);
|
|
sDynamic->Checksum = 0;
|
|
sum = CalculateVhdFooterChecksum (sDynamic, sizeof(*sDynamic));
|
|
sDynamic->Checksum = NtoHl (saved_sum);
|
|
if ((sum != saved_sum) || (memcmp ("cxsparse", sDynamic->Cookie, sizeof (sDynamic->Cookie)))) {
|
|
Return = errno;
|
|
goto Return_Cleanup;
|
|
}
|
|
if (aBAT) {
|
|
*aBAT = (uint32*) calloc(1, VHD_Internal_SectorSize * ((sizeof(**aBAT) * NtoHl(sDynamic->MaxTableEntries) + VHD_Internal_SectorSize - 1) / VHD_Internal_SectorSize));
|
|
if (ReadFilePosition(File,
|
|
*aBAT,
|
|
sizeof (**aBAT) * NtoHl(sDynamic->MaxTableEntries),
|
|
NULL,
|
|
NtoHll (sDynamic->TableOffset))) {
|
|
Return = EINVAL; /* File Corrupt */
|
|
goto Return_Cleanup;
|
|
}
|
|
}
|
|
if (szParentVHDPath && ParentVHDPathSize) {
|
|
VHD_Footer sParentFooter;
|
|
|
|
memset (szParentVHDPath, '\0', ParentVHDPathSize);
|
|
if (NtoHl (sFooter->DiskType) == VHD_DT_Differencing) {
|
|
size_t i, j;
|
|
|
|
for (j=0; j<8; ++j) {
|
|
uint8 *Pdata;
|
|
uint32 PdataSize;
|
|
char ParentName[512];
|
|
char CheckPath[512];
|
|
uint32 ParentModificationTime;
|
|
|
|
if ('\0' == sDynamic->ParentLocatorEntries[j].PlatformCode[0])
|
|
continue;
|
|
memset (ParentName, '\0', sizeof(ParentName));
|
|
memset (CheckPath, '\0', sizeof(CheckPath));
|
|
PdataSize = NtoHl(sDynamic->ParentLocatorEntries[j].PlatformDataSpace);
|
|
Pdata = (uint8*) calloc (1, PdataSize+2);
|
|
if (!Pdata)
|
|
continue;
|
|
if (ReadFilePosition(File,
|
|
Pdata,
|
|
PdataSize,
|
|
NULL,
|
|
NtoHll (sDynamic->ParentLocatorEntries[j].PlatformDataOffset))) {
|
|
free (Pdata);
|
|
continue;
|
|
}
|
|
for (i=0; i<NtoHl(sDynamic->ParentLocatorEntries[j].PlatformDataLength); i+=2)
|
|
if ((Pdata[i] == '\0') && (Pdata[i+1] == '\0')) {
|
|
ParentName[i/2] = '\0';
|
|
break;
|
|
}
|
|
else
|
|
ParentName[i/2] = Pdata[i] ? Pdata[i] : Pdata[i+1];
|
|
free (Pdata);
|
|
memset (CheckPath, 0, sizeof (CheckPath));
|
|
if (0 == memcmp (sDynamic->ParentLocatorEntries[j].PlatformCode, "W2ku", 4))
|
|
strlcpy (CheckPath, ParentName, sizeof (CheckPath));
|
|
else
|
|
if (0 == memcmp (sDynamic->ParentLocatorEntries[j].PlatformCode, "W2ru", 4)) {
|
|
const char *c;
|
|
|
|
if ((c = strrchr (szVHDPath, '\\'))) {
|
|
memcpy (CheckPath, szVHDPath, c-szVHDPath+1);
|
|
strlcat (CheckPath, ParentName, sizeof (CheckPath));
|
|
}
|
|
}
|
|
VhdPathToHostPath (CheckPath, CheckPath, sizeof (CheckPath));
|
|
if (0 == GetVHDFooter(CheckPath,
|
|
&sParentFooter,
|
|
NULL,
|
|
NULL,
|
|
&ParentModificationTime,
|
|
NULL,
|
|
0)) {
|
|
if ((0 == memcmp (sDynamic->ParentUniqueID, sParentFooter.UniqueID, sizeof (sParentFooter.UniqueID))) &&
|
|
((sDynamic->ParentTimeStamp == ParentModificationTime) ||
|
|
((NtoHl(sDynamic->ParentTimeStamp)-NtoHl(ParentModificationTime)) == 3600) ||
|
|
(sim_switches & SWMASK ('O')))) {
|
|
memset (szParentVHDPath, 0, ParentVHDPathSize);
|
|
strlcpy (szParentVHDPath, CheckPath, ParentVHDPathSize);
|
|
}
|
|
else {
|
|
if (0 != memcmp (sDynamic->ParentUniqueID, sParentFooter.UniqueID, sizeof (sParentFooter.UniqueID)))
|
|
sim_printf ("Error Invalid Parent VHD '%s' for Differencing VHD: %s\n", CheckPath, szVHDPath);
|
|
else
|
|
sim_printf ("Error Parent VHD '%s' has been modified since Differencing VHD: %s was created\n", CheckPath, szVHDPath);
|
|
Return = EINVAL; /* File Corrupt/Invalid */
|
|
}
|
|
break;
|
|
}
|
|
else {
|
|
struct stat statb;
|
|
|
|
if (0 == stat (CheckPath, &statb)) {
|
|
sim_printf ("Parent VHD '%s' corrupt for Differencing VHD: %s\n", CheckPath, szVHDPath);
|
|
Return = EBADF; /* File Corrupt/Invalid */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!*szParentVHDPath) {
|
|
if (Return != EINVAL) /* File Not Corrupt? */
|
|
sim_printf ("Missing Parent VHD for Differencing VHD: %s\n", szVHDPath);
|
|
Return = EBADF;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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, uint32 SectorSize, uint32 xfer_element_size)
|
|
{
|
|
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.DriveSectorSize = NtoHl (SectorSize);
|
|
hVHD->Footer.DriveTransferElementSize = NtoHl (xfer_element_size);
|
|
strncpy ((char *)hVHD->Footer.CreatingSimulator, sim_name, sizeof (hVHD->Footer.CreatingSimulator) - 1);
|
|
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, uint32 *SectorSize, uint32 *xfer_element_size, char sim_name[64], time_t *creation_time)
|
|
{
|
|
VHDHANDLE hVHD = (VHDHANDLE)f;
|
|
|
|
if (SectorSize)
|
|
*SectorSize = NtoHl (hVHD->Footer.DriveSectorSize);
|
|
if (xfer_element_size)
|
|
*xfer_element_size = NtoHl (hVHD->Footer.DriveTransferElementSize);
|
|
if (sim_name)
|
|
memcpy (sim_name, hVHD->Footer.CreatingSimulator, 64);
|
|
if (creation_time)
|
|
*creation_time = (time_t)NtoHl (hVHD->Footer.TimeStamp) + 946684800;
|
|
return (char *)(&hVHD->Footer.DriveType[0]);
|
|
}
|
|
|
|
static FILE *sim_vhd_disk_open (const char *szVHDPath, const char *DesiredAccess)
|
|
{
|
|
VHDHANDLE hVHD = (VHDHANDLE) calloc (1, sizeof(*hVHD));
|
|
int NeedUpdate = FALSE;
|
|
int Status;
|
|
|
|
if (!hVHD)
|
|
return (FILE *)hVHD;
|
|
Status = GetVHDFooter (szVHDPath,
|
|
&hVHD->Footer,
|
|
&hVHD->Dynamic,
|
|
&hVHD->BAT,
|
|
NULL,
|
|
hVHD->ParentVHDPath,
|
|
sizeof (hVHD->ParentVHDPath));
|
|
if (Status)
|
|
goto Cleanup_Return;
|
|
if (NtoHl (hVHD->Footer.DiskType) == VHD_DT_Differencing) {
|
|
uint32 ParentModifiedTimeStamp;
|
|
VHD_Footer ParentFooter;
|
|
VHD_DynamicDiskHeader ParentDynamic;
|
|
|
|
hVHD->Parent = (VHDHANDLE)sim_vhd_disk_open (hVHD->ParentVHDPath, "rb");
|
|
if (!hVHD->Parent) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
Status = GetVHDFooter (hVHD->ParentVHDPath,
|
|
&ParentFooter,
|
|
&ParentDynamic,
|
|
NULL,
|
|
&ParentModifiedTimeStamp,
|
|
NULL,
|
|
0);
|
|
if (Status)
|
|
goto Cleanup_Return;
|
|
if ((0 != memcmp (hVHD->Dynamic.ParentUniqueID, ParentFooter.UniqueID, sizeof (ParentFooter.UniqueID))) ||
|
|
(ParentModifiedTimeStamp != hVHD->Dynamic.ParentTimeStamp)) {
|
|
if (sim_switches & SWMASK ('O')) { /* OVERRIDE consistency checks? */
|
|
if ((sim_switches & SWMASK ('U')) && /* FIX (UPDATE) consistency checks AND */
|
|
(strchr (DesiredAccess, '+'))) { /* open for write/update? */
|
|
memcpy (hVHD->Dynamic.ParentUniqueID, ParentFooter.UniqueID, sizeof (ParentFooter.UniqueID));
|
|
hVHD->Dynamic.ParentTimeStamp = ParentModifiedTimeStamp;
|
|
hVHD->Dynamic.Checksum = 0;
|
|
hVHD->Dynamic.Checksum = NtoHl (CalculateVhdFooterChecksum (&hVHD->Dynamic, sizeof(hVHD->Dynamic)));
|
|
NeedUpdate = TRUE;
|
|
}
|
|
}
|
|
else {
|
|
Status = EBADF;
|
|
goto Cleanup_Return;
|
|
}
|
|
}
|
|
}
|
|
if (hVHD->Footer.SavedState) {
|
|
Status = EAGAIN; /* Busy */
|
|
goto Cleanup_Return;
|
|
}
|
|
hVHD->File = sim_fopen (szVHDPath, DesiredAccess);
|
|
if (!hVHD->File) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
Cleanup_Return:
|
|
if (Status) {
|
|
sim_vhd_disk_close ((FILE *)hVHD);
|
|
hVHD = NULL;
|
|
}
|
|
else {
|
|
if (NeedUpdate) { /* Update Differencing Disk Header? */
|
|
if (WriteFilePosition(hVHD->File,
|
|
&hVHD->Dynamic,
|
|
sizeof (hVHD->Dynamic),
|
|
NULL,
|
|
NtoHll (hVHD->Footer.DataOffset))) {
|
|
sim_vhd_disk_close ((FILE *)hVHD);
|
|
hVHD = NULL;
|
|
}
|
|
}
|
|
}
|
|
errno = Status;
|
|
return (FILE *)hVHD;
|
|
}
|
|
|
|
static t_stat
|
|
WriteVirtualDiskSectors(VHDHANDLE hVHD,
|
|
uint8 *buf,
|
|
t_seccnt sects,
|
|
t_seccnt *sectswritten,
|
|
uint32 SectorSize,
|
|
t_lba lba);
|
|
|
|
static FILE *sim_vhd_disk_merge (const char *szVHDPath, char **ParentVHD)
|
|
{
|
|
VHDHANDLE hVHD = (VHDHANDLE) calloc (1, sizeof(*hVHD));
|
|
VHDHANDLE Parent = NULL;
|
|
int Status;
|
|
uint32 SectorSize, SectorsPerBlock, BlockSize, BlockNumber, BitMapBytes, BitMapSectors, BlocksToMerge, NeededBlock;
|
|
uint64 BlockOffset;
|
|
uint32 BytesRead;
|
|
t_seccnt SectorsWritten;
|
|
void *BlockData = NULL;
|
|
|
|
if (!hVHD)
|
|
return (FILE *)hVHD;
|
|
if (0 != (Status = GetVHDFooter (szVHDPath,
|
|
&hVHD->Footer,
|
|
&hVHD->Dynamic,
|
|
&hVHD->BAT,
|
|
NULL,
|
|
hVHD->ParentVHDPath,
|
|
sizeof (hVHD->ParentVHDPath))))
|
|
goto Cleanup_Return;
|
|
if (NtoHl (hVHD->Footer.DiskType) != VHD_DT_Differencing) {
|
|
Status = EINVAL;
|
|
goto Cleanup_Return;
|
|
}
|
|
if (hVHD->Footer.SavedState) {
|
|
Status = EAGAIN; /* Busy */
|
|
goto Cleanup_Return;
|
|
}
|
|
SectorSize = 512;
|
|
BlockSize = NtoHl (hVHD->Dynamic.BlockSize);
|
|
BlockData = malloc (BlockSize*SectorSize);
|
|
if (NULL == BlockData) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
Parent = (VHDHANDLE)sim_vhd_disk_open (hVHD->ParentVHDPath, "rb+");
|
|
if (!Parent) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
hVHD->File = sim_fopen (szVHDPath, "rb");
|
|
if (!hVHD->File) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
SectorsPerBlock = NtoHl (hVHD->Dynamic.BlockSize)/SectorSize;
|
|
BitMapBytes = (7+(NtoHl (hVHD->Dynamic.BlockSize)/SectorSize))/8;
|
|
BitMapSectors = (BitMapBytes+SectorSize-1)/SectorSize;
|
|
for (BlockNumber=BlocksToMerge=0; BlockNumber< NtoHl (hVHD->Dynamic.MaxTableEntries); ++BlockNumber) {
|
|
if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY)
|
|
continue;
|
|
++BlocksToMerge;
|
|
}
|
|
sim_messagef (SCPE_OK, "Merging %s\ninto %s\n", szVHDPath, hVHD->ParentVHDPath);
|
|
for (BlockNumber=NeededBlock=0; BlockNumber < NtoHl (hVHD->Dynamic.MaxTableEntries); ++BlockNumber) {
|
|
uint32 BlockSectors = SectorsPerBlock;
|
|
|
|
if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY)
|
|
continue;
|
|
++NeededBlock;
|
|
BlockOffset = SectorSize*((uint64)(NtoHl (hVHD->BAT[BlockNumber]) + BitMapSectors));
|
|
if (((uint64)BlockNumber*SectorsPerBlock + BlockSectors) > ((uint64)NtoHll (hVHD->Footer.CurrentSize))/SectorSize)
|
|
BlockSectors = (uint32)(((uint64)NtoHll (hVHD->Footer.CurrentSize))/SectorSize - (BlockNumber*SectorsPerBlock));
|
|
if (ReadFilePosition(hVHD->File,
|
|
BlockData,
|
|
SectorSize*BlockSectors,
|
|
&BytesRead,
|
|
BlockOffset))
|
|
break;
|
|
if (WriteVirtualDiskSectors (Parent,
|
|
(uint8*)BlockData,
|
|
BlockSectors,
|
|
&SectorsWritten,
|
|
SectorSize,
|
|
SectorsPerBlock*BlockNumber))
|
|
break;
|
|
sim_messagef (SCPE_OK, "Merged %dMB. %d%% complete.\r", (int)((((float)NeededBlock)*SectorsPerBlock)*SectorSize/1000000), (int)((((float)NeededBlock)*100)/BlocksToMerge));
|
|
hVHD->BAT[BlockNumber] = VHD_BAT_FREE_ENTRY;
|
|
}
|
|
if (BlockNumber < NtoHl (hVHD->Dynamic.MaxTableEntries)) {
|
|
Status = errno;
|
|
}
|
|
else {
|
|
Status = 0;
|
|
sim_messagef (SCPE_OK, "Merged %dMB. 100%% complete.\n", (int)((((float)NeededBlock)*SectorsPerBlock)*SectorSize/1000000));
|
|
fclose (hVHD->File);
|
|
hVHD->File = NULL;
|
|
(void)remove (szVHDPath);
|
|
*ParentVHD = (char*) malloc (strlen (hVHD->ParentVHDPath)+1);
|
|
strcpy (*ParentVHD, hVHD->ParentVHDPath);
|
|
}
|
|
Cleanup_Return:
|
|
free (BlockData);
|
|
if (hVHD->File)
|
|
fclose (hVHD->File);
|
|
if (Status) {
|
|
free (hVHD->BAT);
|
|
free (hVHD);
|
|
hVHD = NULL;
|
|
sim_vhd_disk_close ((FILE *)Parent);
|
|
}
|
|
else {
|
|
free (hVHD->BAT);
|
|
free (hVHD);
|
|
hVHD = Parent;
|
|
}
|
|
errno = Status;
|
|
return (FILE *)hVHD;
|
|
}
|
|
|
|
static int sim_vhd_disk_close (FILE *f)
|
|
{
|
|
VHDHANDLE hVHD = (VHDHANDLE)f;
|
|
|
|
if (NULL != hVHD) {
|
|
if (hVHD->Parent)
|
|
sim_vhd_disk_close ((FILE *)hVHD->Parent);
|
|
free (hVHD->BAT);
|
|
if (hVHD->File) {
|
|
fflush (hVHD->File);
|
|
fclose (hVHD->File);
|
|
}
|
|
free (hVHD);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void sim_vhd_disk_flush (FILE *f)
|
|
{
|
|
VHDHANDLE hVHD = (VHDHANDLE)f;
|
|
|
|
if ((NULL != hVHD) && (hVHD->File))
|
|
fflush (hVHD->File);
|
|
}
|
|
|
|
static t_offset sim_vhd_disk_size (FILE *f)
|
|
{
|
|
VHDHANDLE hVHD = (VHDHANDLE)f;
|
|
|
|
return (t_offset)(NtoHll (hVHD->Footer.CurrentSize));
|
|
}
|
|
|
|
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
static void
|
|
_rand_uuid_gen (void *uuidaddr)
|
|
{
|
|
int i;
|
|
uint8 *b = (uint8 *)uuidaddr;
|
|
uint32 timenow = (uint32)time (NULL);
|
|
|
|
memcpy (uuidaddr, &timenow, sizeof (timenow));
|
|
srand ((unsigned)timenow);
|
|
for (i=4; i<16; i++) {
|
|
b[i] = (uint8)rand();
|
|
}
|
|
}
|
|
|
|
#if defined (_WIN32)
|
|
static void
|
|
uuid_gen (void *uuidaddr)
|
|
{
|
|
static
|
|
RPC_STATUS
|
|
(RPC_ENTRY *UuidCreate_c) (void *);
|
|
|
|
if (!UuidCreate_c) {
|
|
HINSTANCE hDll;
|
|
hDll = LoadLibraryA("rpcrt4.dll");
|
|
UuidCreate_c = (RPC_STATUS (RPC_ENTRY *) (void *))GetProcAddress(hDll, "UuidCreate");
|
|
}
|
|
if (UuidCreate_c)
|
|
UuidCreate_c(uuidaddr);
|
|
else
|
|
_rand_uuid_gen (uuidaddr);
|
|
}
|
|
#elif defined (HAVE_DLOPEN)
|
|
#include <dlfcn.h>
|
|
|
|
static void
|
|
uuid_gen (void *uuidaddr)
|
|
{
|
|
void (*uuid_generate_c) (void *) = NULL;
|
|
void *handle;
|
|
|
|
#define S__STR_QUOTE(tok) #tok
|
|
#define S__STR(tok) S__STR_QUOTE(tok)
|
|
handle = dlopen("libuuid." S__STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL);
|
|
if (handle)
|
|
uuid_generate_c = (void (*)(void *))((size_t)dlsym(handle, "uuid_generate"));
|
|
if (uuid_generate_c)
|
|
uuid_generate_c(uuidaddr);
|
|
else
|
|
_rand_uuid_gen (uuidaddr);
|
|
if (handle)
|
|
dlclose(handle);
|
|
}
|
|
#else
|
|
static void
|
|
uuid_gen (void *uuidaddr)
|
|
{
|
|
_rand_uuid_gen (uuidaddr);
|
|
}
|
|
#endif
|
|
|
|
static VHDHANDLE
|
|
CreateVirtualDisk(const char *szVHDPath,
|
|
uint32 SizeInSectors,
|
|
uint32 BlockSize,
|
|
t_bool bFixedVHD)
|
|
{
|
|
VHD_Footer Footer;
|
|
VHD_DynamicDiskHeader Dynamic;
|
|
uint32 *BAT = NULL;
|
|
time_t now;
|
|
uint32 i;
|
|
FILE *File = NULL;
|
|
uint32 Status = 0;
|
|
uint32 BytesPerSector = 512;
|
|
uint64 SizeInBytes = ((uint64)SizeInSectors) * BytesPerSector;
|
|
uint64 TableOffset;
|
|
uint32 MaxTableEntries;
|
|
VHDHANDLE hVHD = NULL;
|
|
|
|
if (SizeInBytes > ((uint64)(1024 * 1024 * 1024)) * 2040) {
|
|
Status = EFBIG;
|
|
goto Cleanup_Return;
|
|
}
|
|
File = sim_fopen (szVHDPath, "rb");
|
|
if (File) {
|
|
fclose (File);
|
|
File = NULL;
|
|
Status = EEXIST;
|
|
goto Cleanup_Return;
|
|
}
|
|
File = sim_fopen (szVHDPath, "wb");
|
|
if (!File) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
|
|
memset (&Footer, 0, sizeof(Footer));
|
|
memcpy (Footer.Cookie, "conectix", 8);
|
|
Footer.Features = NtoHl (0x00000002);;
|
|
Footer.FileFormatVersion = NtoHl (0x00010000);;
|
|
Footer.DataOffset = NtoHll (bFixedVHD ? ((long long)-1) : (long long)(sizeof(Footer)));
|
|
time (&now);
|
|
Footer.TimeStamp = NtoHl ((uint32)(now - 946684800));
|
|
memcpy (Footer.CreatorApplication, "simh", 4);
|
|
Footer.CreatorVersion = NtoHl (0x00040000);
|
|
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 ((uint64)0xFFFFFFFFFFFFFFFFLL);
|
|
TableOffset = NtoHll(Footer.DataOffset)+sizeof(Dynamic);
|
|
Dynamic.TableOffset = NtoHll (TableOffset);
|
|
Dynamic.HeaderVersion = NtoHl (0x00010000);
|
|
if (0 == BlockSize)
|
|
BlockSize = 2*1024*1024;
|
|
Dynamic.BlockSize = NtoHl (BlockSize);
|
|
MaxTableEntries = (uint32)((SizeInBytes+BlockSize-1)/BlockSize);
|
|
Dynamic.MaxTableEntries = NtoHl (MaxTableEntries);
|
|
Dynamic.Checksum = NtoHl (CalculateVhdFooterChecksum(&Dynamic, sizeof(Dynamic)));
|
|
BAT = (uint32*) malloc (BytesPerSector*((MaxTableEntries*sizeof(*BAT)+BytesPerSector-1)/BytesPerSector));
|
|
memset (BAT, 0, BytesPerSector*((MaxTableEntries*sizeof(*BAT)+BytesPerSector-1)/BytesPerSector));
|
|
for (i=0; i<MaxTableEntries; ++i)
|
|
BAT[i] = VHD_BAT_FREE_ENTRY;
|
|
|
|
if (WriteFilePosition(File,
|
|
&Footer,
|
|
sizeof(Footer),
|
|
NULL,
|
|
0)) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
if (WriteFilePosition(File,
|
|
&Dynamic,
|
|
sizeof(Dynamic),
|
|
NULL,
|
|
NtoHll(Footer.DataOffset))) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
if (WriteFilePosition(File,
|
|
BAT,
|
|
BytesPerSector*((MaxTableEntries*sizeof(*BAT)+BytesPerSector-1)/BytesPerSector),
|
|
NULL,
|
|
NtoHll(Dynamic.TableOffset))) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
if (WriteFilePosition(File,
|
|
&Footer,
|
|
sizeof(Footer),
|
|
NULL,
|
|
NtoHll(Dynamic.TableOffset)+BytesPerSector*((MaxTableEntries*sizeof(*BAT)+BytesPerSector-1)/BytesPerSector))) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
|
|
Cleanup_Return:
|
|
free (BAT);
|
|
if (File)
|
|
fclose (File);
|
|
if (Status) {
|
|
if (Status != EEXIST)
|
|
(void)remove (szVHDPath);
|
|
}
|
|
else {
|
|
hVHD = (VHDHANDLE)sim_vhd_disk_open (szVHDPath, "rb+");
|
|
if (!hVHD)
|
|
Status = errno;
|
|
}
|
|
errno = Status;
|
|
return hVHD;
|
|
}
|
|
|
|
#if defined(__CYGWIN__) || defined(VMS) || defined(__APPLE__) || defined(__linux) || defined(__linux__) || defined(__unix__)
|
|
#include <unistd.h>
|
|
#endif
|
|
static void
|
|
ExpandToFullPath (const char *szFileSpec,
|
|
char *szFullFileSpecBuffer,
|
|
size_t BufferSize)
|
|
{
|
|
char *c;
|
|
#ifdef _WIN32
|
|
for (c = strchr (szFullFileSpecBuffer, '/'); c; c = strchr (szFullFileSpecBuffer, '/'))
|
|
*c = '\\';
|
|
GetFullPathNameA (szFileSpec, (DWORD)BufferSize, szFullFileSpecBuffer, NULL);
|
|
for (c = strchr (szFullFileSpecBuffer, '\\'); c; c = strchr (szFullFileSpecBuffer, '\\'))
|
|
*c = '/';
|
|
#else
|
|
char buffer[PATH_MAX];
|
|
char *wd = getcwd(buffer, PATH_MAX);
|
|
|
|
if ((szFileSpec[0] != '/') || (strchr (szFileSpec, ':')))
|
|
snprintf (szFullFileSpecBuffer, BufferSize, "%s/%s", wd, szFileSpec);
|
|
else
|
|
strlcpy (szFullFileSpecBuffer, szFileSpec, BufferSize);
|
|
if ((c = strstr (szFullFileSpecBuffer, "]/")))
|
|
memmove (c+1, c+2, strlen(c+2)+1);
|
|
memset (szFullFileSpecBuffer + strlen (szFullFileSpecBuffer), 0, BufferSize - strlen (szFullFileSpecBuffer));
|
|
#endif
|
|
}
|
|
|
|
static char *
|
|
HostPathToVhdPath (const char *szHostPath,
|
|
char *szVhdPath,
|
|
size_t VhdPathSize)
|
|
{
|
|
char *c, *d;
|
|
|
|
memset (szVhdPath, 0, VhdPathSize);
|
|
strlcpy (szVhdPath, szHostPath, VhdPathSize-1);
|
|
if ((szVhdPath[1] == ':') && islower(szVhdPath[0]))
|
|
szVhdPath[0] = toupper(szVhdPath[0]);
|
|
szVhdPath[VhdPathSize-1] = '\0';
|
|
if ((c = strrchr (szVhdPath, ']'))) {
|
|
*c = '\0';
|
|
if (!(d = strchr (szVhdPath, '[')))
|
|
return d;
|
|
*d = '/';
|
|
while ((d = strchr (d, '.')))
|
|
*d = '/';
|
|
*c = '/';
|
|
}
|
|
while ((c = strchr (szVhdPath, '/')))
|
|
*c = '\\';
|
|
for (c = strstr (szVhdPath, "\\.\\"); c; c = strstr (szVhdPath, "\\.\\"))
|
|
memmove (c, c+2, strlen(c+2)+1);
|
|
for (c = strstr (szVhdPath, "\\\\"); c; c = strstr (szVhdPath, "\\\\"))
|
|
memmove (c, c+1, strlen(c+1)+1);
|
|
while ((c = strstr (szVhdPath, "\\..\\"))) {
|
|
*c = '\0';
|
|
d = strrchr (szVhdPath, '\\');
|
|
if (d)
|
|
memmove (d, c+3, strlen(c+3)+1);
|
|
else
|
|
return d;
|
|
}
|
|
memset (szVhdPath + strlen (szVhdPath), 0, VhdPathSize - strlen (szVhdPath));
|
|
return szVhdPath;
|
|
}
|
|
|
|
static char *
|
|
VhdPathToHostPath (const char *szVhdPath,
|
|
char *szHostPath,
|
|
size_t HostPathSize)
|
|
{
|
|
char *c;
|
|
char *d = szHostPath;
|
|
|
|
memmove (szHostPath, szVhdPath, HostPathSize);
|
|
szHostPath[HostPathSize-1] = '\0';
|
|
#if defined(VMS)
|
|
c = strchr (szHostPath, ':');
|
|
if (*(c+1) != '\\')
|
|
return NULL;
|
|
*(c+1) = '[';
|
|
d = strrchr (c+2, '\\');
|
|
if (d) {
|
|
*d = ']';
|
|
while ((d = strrchr (c+2, '\\')))
|
|
*d = '.';
|
|
}
|
|
else
|
|
return NULL;
|
|
#else
|
|
while ((c = strchr (d, '\\')))
|
|
*c = '/';
|
|
#endif
|
|
memset (szHostPath + strlen (szHostPath), 0, HostPathSize - strlen (szHostPath));
|
|
return szHostPath;
|
|
}
|
|
|
|
static VHDHANDLE
|
|
CreateDifferencingVirtualDisk(const char *szVHDPath,
|
|
const char *szParentVHDPath)
|
|
{
|
|
uint32 BytesPerSector = 512;
|
|
VHDHANDLE hVHD = NULL;
|
|
VHD_Footer ParentFooter;
|
|
VHD_DynamicDiskHeader ParentDynamic;
|
|
uint32 ParentTimeStamp;
|
|
uint32 Status = 0;
|
|
char *RelativeParentVHDPath = NULL;
|
|
char *FullParentVHDPath = NULL;
|
|
char *RelativeParentVHDPathUnicode = NULL;
|
|
char *FullParentVHDPathUnicode = NULL;
|
|
char *FullVHDPath = NULL;
|
|
char *TempPath = NULL;
|
|
size_t i, RelativeMatch, UpDirectories, LocatorsWritten = 0;
|
|
int64 LocatorPosition;
|
|
|
|
if ((Status = GetVHDFooter (szParentVHDPath,
|
|
&ParentFooter,
|
|
&ParentDynamic,
|
|
NULL,
|
|
&ParentTimeStamp,
|
|
NULL,
|
|
0)))
|
|
goto Cleanup_Return;
|
|
hVHD = CreateVirtualDisk (szVHDPath,
|
|
(uint32)(NtoHll(ParentFooter.CurrentSize)/BytesPerSector),
|
|
NtoHl(ParentDynamic.BlockSize),
|
|
FALSE);
|
|
if (!hVHD) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
LocatorPosition = ((sizeof (hVHD->Footer) + BytesPerSector - 1)/BytesPerSector + (sizeof (hVHD->Dynamic) + BytesPerSector - 1)/BytesPerSector)*BytesPerSector;
|
|
hVHD->Dynamic.Checksum = 0;
|
|
RelativeParentVHDPath = (char*) calloc (1, BytesPerSector+2);
|
|
FullParentVHDPath = (char*) calloc (1, BytesPerSector+2);
|
|
RelativeParentVHDPathUnicode = (char*) calloc (1, BytesPerSector+2);
|
|
FullParentVHDPathUnicode = (char*) calloc (1, BytesPerSector+2);
|
|
FullVHDPath = (char*) calloc (1, BytesPerSector+2);
|
|
TempPath = (char*) calloc (1, BytesPerSector+2);
|
|
ExpandToFullPath (szParentVHDPath, TempPath, BytesPerSector);
|
|
HostPathToVhdPath (TempPath, FullParentVHDPath, BytesPerSector);
|
|
for (i=0; i < strlen (FullParentVHDPath); i++)
|
|
hVHD->Dynamic.ParentUnicodeName[i*2+1] = FullParentVHDPath[i]; /* Big Endian Unicode */
|
|
for (i=0; i < strlen (FullParentVHDPath); i++)
|
|
FullParentVHDPathUnicode[i*2] = FullParentVHDPath[i]; /* Little Endian Unicode */
|
|
ExpandToFullPath (szVHDPath, TempPath, BytesPerSector);
|
|
HostPathToVhdPath (TempPath, FullVHDPath, BytesPerSector);
|
|
for (i=0, RelativeMatch=UpDirectories=0; i<strlen(FullVHDPath); i++)
|
|
if (FullVHDPath[i] == '\\') {
|
|
if (memcmp (FullVHDPath, FullParentVHDPath, i+1))
|
|
++UpDirectories;
|
|
else
|
|
RelativeMatch = i;
|
|
}
|
|
if (RelativeMatch) {
|
|
char UpDir[4] = "..\\";
|
|
|
|
UpDir[2] = FullParentVHDPath[RelativeMatch];
|
|
if (UpDirectories)
|
|
for (i=0; i<UpDirectories; i++)
|
|
strcpy (RelativeParentVHDPath+strlen (RelativeParentVHDPath), UpDir);
|
|
else
|
|
strcpy (RelativeParentVHDPath+strlen (RelativeParentVHDPath), UpDir+1);
|
|
strcpy (RelativeParentVHDPath+strlen (RelativeParentVHDPath), &FullParentVHDPath[RelativeMatch+1]);
|
|
}
|
|
for (i=0; i < strlen(RelativeParentVHDPath); i++)
|
|
RelativeParentVHDPathUnicode[i*2] = RelativeParentVHDPath[i];
|
|
hVHD->Dynamic.ParentTimeStamp = ParentTimeStamp;
|
|
memcpy (hVHD->Dynamic.ParentUniqueID, ParentFooter.UniqueID, sizeof (hVHD->Dynamic.ParentUniqueID));
|
|
/* There are two potential parent locators on current vhds */
|
|
memcpy (hVHD->Dynamic.ParentLocatorEntries[0].PlatformCode, "W2ku", 4);
|
|
hVHD->Dynamic.ParentLocatorEntries[0].PlatformDataSpace = NtoHl (BytesPerSector);
|
|
hVHD->Dynamic.ParentLocatorEntries[0].PlatformDataLength = NtoHl ((uint32)(2*strlen(FullParentVHDPath)));
|
|
hVHD->Dynamic.ParentLocatorEntries[0].Reserved = 0;
|
|
hVHD->Dynamic.ParentLocatorEntries[0].PlatformDataOffset = NtoHll (LocatorPosition+LocatorsWritten*BytesPerSector);
|
|
++LocatorsWritten;
|
|
if (RelativeMatch) {
|
|
memcpy (hVHD->Dynamic.ParentLocatorEntries[1].PlatformCode, "W2ru", 4);
|
|
hVHD->Dynamic.ParentLocatorEntries[1].PlatformDataSpace = NtoHl (BytesPerSector);
|
|
hVHD->Dynamic.ParentLocatorEntries[1].PlatformDataLength = NtoHl ((uint32)(2*strlen(RelativeParentVHDPath)));
|
|
hVHD->Dynamic.ParentLocatorEntries[1].Reserved = 0;
|
|
hVHD->Dynamic.ParentLocatorEntries[1].PlatformDataOffset = NtoHll (LocatorPosition+LocatorsWritten*BytesPerSector);
|
|
++LocatorsWritten;
|
|
}
|
|
hVHD->Dynamic.TableOffset = NtoHll (((LocatorPosition+LocatorsWritten*BytesPerSector + VHD_DATA_BLOCK_ALIGNMENT - 1)/VHD_DATA_BLOCK_ALIGNMENT)*VHD_DATA_BLOCK_ALIGNMENT);
|
|
hVHD->Dynamic.Checksum = 0;
|
|
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.DriveSectorSize = ParentFooter.DriveSectorSize;
|
|
hVHD->Footer.DriveTransferElementSize = ParentFooter.DriveTransferElementSize;
|
|
hVHD->Footer.Checksum = NtoHl (CalculateVhdFooterChecksum (&hVHD->Footer, sizeof(hVHD->Footer)));
|
|
|
|
if (WriteFilePosition (hVHD->File,
|
|
&hVHD->Footer,
|
|
sizeof (hVHD->Footer),
|
|
NULL,
|
|
0)) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
if (WriteFilePosition (hVHD->File,
|
|
&hVHD->Dynamic,
|
|
sizeof (hVHD->Dynamic),
|
|
NULL,
|
|
NtoHll (hVHD->Footer.DataOffset))) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
if (WriteFilePosition (hVHD->File,
|
|
hVHD->BAT,
|
|
BytesPerSector*((NtoHl (hVHD->Dynamic.MaxTableEntries)*sizeof(*hVHD->BAT)+BytesPerSector-1)/BytesPerSector),
|
|
NULL,
|
|
NtoHll (hVHD->Dynamic.TableOffset))) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
if (WriteFilePosition (hVHD->File,
|
|
&hVHD->Footer,
|
|
sizeof (hVHD->Footer),
|
|
NULL,
|
|
NtoHll (hVHD->Dynamic.TableOffset)+BytesPerSector*((NtoHl (hVHD->Dynamic.MaxTableEntries)*sizeof(*hVHD->BAT)+BytesPerSector-1)/BytesPerSector))) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
if (hVHD->Dynamic.ParentLocatorEntries[0].PlatformDataLength)
|
|
if (WriteFilePosition (hVHD->File,
|
|
FullParentVHDPathUnicode,
|
|
BytesPerSector,
|
|
NULL,
|
|
NtoHll (hVHD->Dynamic.ParentLocatorEntries[0].PlatformDataOffset))) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
if (hVHD->Dynamic.ParentLocatorEntries[1].PlatformDataLength)
|
|
if (WriteFilePosition (hVHD->File,
|
|
RelativeParentVHDPathUnicode,
|
|
BytesPerSector,
|
|
NULL,
|
|
NtoHll (hVHD->Dynamic.ParentLocatorEntries[1].PlatformDataOffset))) {
|
|
Status = errno;
|
|
goto Cleanup_Return;
|
|
}
|
|
|
|
Cleanup_Return:
|
|
free (RelativeParentVHDPath);
|
|
free (FullParentVHDPath);
|
|
free (RelativeParentVHDPathUnicode);
|
|
free (FullParentVHDPathUnicode);
|
|
free (FullVHDPath);
|
|
free (TempPath);
|
|
sim_vhd_disk_close ((FILE *)hVHD);
|
|
hVHD = NULL;
|
|
if (Status) {
|
|
if ((EEXIST != Status) && (ENOENT != Status))
|
|
(void)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_offset 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
|
|
ReadVirtualDisk(VHDHANDLE hVHD,
|
|
uint8 *buf,
|
|
uint32 BytesToRead,
|
|
uint32 *BytesRead,
|
|
uint64 Offset)
|
|
{
|
|
uint32 TotalBytesRead = 0;
|
|
uint32 BitMapBytes;
|
|
uint32 BitMapSectors;
|
|
t_stat r = SCPE_OK;
|
|
|
|
if (BytesRead)
|
|
*BytesRead = 0;
|
|
if (!hVHD || (hVHD->File == NULL)) {
|
|
errno = EBADF;
|
|
return SCPE_IOERR;
|
|
}
|
|
if (BytesToRead == 0)
|
|
return SCPE_OK;
|
|
if (Offset >= (uint64)NtoHll (hVHD->Footer.CurrentSize)) {
|
|
errno = ERANGE;
|
|
return SCPE_IOERR;
|
|
}
|
|
if (NtoHl (hVHD->Footer.DiskType) == VHD_DT_Fixed) {
|
|
if (ReadFilePosition(hVHD->File,
|
|
buf,
|
|
BytesToRead,
|
|
BytesRead,
|
|
Offset))
|
|
r = SCPE_IOERR;
|
|
return r;
|
|
}
|
|
/* We are now dealing with a Dynamically expanding or differencing disk */
|
|
BitMapBytes = (7+(NtoHl (hVHD->Dynamic.BlockSize)/VHD_Internal_SectorSize))/8;
|
|
BitMapSectors = (BitMapBytes+VHD_Internal_SectorSize-1)/VHD_Internal_SectorSize;
|
|
while (BytesToRead && (r == SCPE_OK)) {
|
|
uint32 BlockNumber = (uint32)(Offset / NtoHl (hVHD->Dynamic.BlockSize));
|
|
uint32 BytesInRead = BytesToRead;
|
|
uint32 BytesThisRead = 0;
|
|
|
|
if (BlockNumber != (Offset + BytesToRead) / NtoHl (hVHD->Dynamic.BlockSize))
|
|
BytesInRead = (uint32)(((BlockNumber + 1) * NtoHl (hVHD->Dynamic.BlockSize)) - Offset);
|
|
if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY) {
|
|
if (!hVHD->Parent) {
|
|
memset (buf, 0, BytesInRead);
|
|
BytesThisRead = BytesInRead;
|
|
}
|
|
else {
|
|
if (ReadVirtualDisk(hVHD->Parent,
|
|
buf,
|
|
BytesInRead,
|
|
&BytesThisRead,
|
|
Offset))
|
|
r = SCPE_IOERR;
|
|
}
|
|
}
|
|
else {
|
|
uint64 BlockOffset = VHD_Internal_SectorSize * ((uint64)(NtoHl (hVHD->BAT[BlockNumber]) + BitMapSectors)) + (Offset % NtoHl (hVHD->Dynamic.BlockSize));
|
|
|
|
if (ReadFilePosition(hVHD->File,
|
|
buf,
|
|
BytesInRead,
|
|
&BytesThisRead,
|
|
BlockOffset))
|
|
r = SCPE_IOERR;
|
|
}
|
|
BytesToRead -= BytesThisRead;
|
|
buf = (uint8 *)(((char *)buf) + BytesThisRead);
|
|
Offset += BytesThisRead;
|
|
TotalBytesRead += BytesThisRead;
|
|
}
|
|
if (BytesRead)
|
|
*BytesRead = TotalBytesRead;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat
|
|
ReadVirtualDiskSectors(VHDHANDLE hVHD,
|
|
uint8 *buf,
|
|
t_seccnt sects,
|
|
t_seccnt *sectsread,
|
|
uint32 SectorSize,
|
|
t_lba lba)
|
|
{
|
|
uint32 BytesRead;
|
|
t_stat r = ReadVirtualDisk(hVHD,
|
|
buf,
|
|
sects * SectorSize,
|
|
&BytesRead,
|
|
SectorSize * (uint64)lba);
|
|
if (sectsread)
|
|
*sectsread = BytesRead / SectorSize;
|
|
return r;
|
|
}
|
|
|
|
static t_stat sim_vhd_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
|
|
{
|
|
VHDHANDLE hVHD = (VHDHANDLE)uptr->fileref;
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
|
|
return ReadVirtualDiskSectors(hVHD, buf, sects, sectsread, ctx->sector_size, lba);
|
|
}
|
|
|
|
static t_stat sim_vhd_disk_clearerr (UNIT *uptr)
|
|
{
|
|
VHDHANDLE hVHD = (VHDHANDLE)uptr->fileref;
|
|
|
|
clearerr (hVHD->File);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_bool
|
|
BufferIsZeros(void *Buffer, size_t BufferSize)
|
|
{
|
|
size_t i;
|
|
char *c = (char *)Buffer;
|
|
|
|
for (i=0; i<BufferSize; ++i)
|
|
if (c[i])
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static t_stat
|
|
WriteVirtualDisk(VHDHANDLE hVHD,
|
|
uint8 *buf,
|
|
uint32 BytesToWrite,
|
|
uint32 *BytesWritten,
|
|
uint64 Offset)
|
|
{
|
|
uint32 TotalBytesWritten = 0;
|
|
uint32 BitMapBytes;
|
|
uint32 BitMapSectors;
|
|
t_stat r = SCPE_OK;
|
|
|
|
if (BytesWritten)
|
|
*BytesWritten = 0;
|
|
if (!hVHD || !hVHD->File) {
|
|
errno = EBADF;
|
|
return SCPE_IOERR;
|
|
}
|
|
if (BytesToWrite == 0)
|
|
return SCPE_OK;
|
|
if (Offset >= (uint64)NtoHll(hVHD->Footer.CurrentSize)) {
|
|
errno = ERANGE;
|
|
return SCPE_IOERR;
|
|
}
|
|
if (NtoHl(hVHD->Footer.DiskType) == VHD_DT_Fixed) {
|
|
if (WriteFilePosition(hVHD->File,
|
|
buf,
|
|
BytesToWrite,
|
|
BytesWritten,
|
|
Offset))
|
|
r = SCPE_IOERR;
|
|
return r;
|
|
}
|
|
/* We are now dealing with a Dynamically expanding or differencing disk */
|
|
BitMapBytes = (7 + (NtoHl(hVHD->Dynamic.BlockSize) / VHD_Internal_SectorSize)) / 8;
|
|
BitMapSectors = (BitMapBytes + VHD_Internal_SectorSize - 1) / VHD_Internal_SectorSize;
|
|
while (BytesToWrite && (r == SCPE_OK)) {
|
|
uint32 BlockNumber = (uint32)(Offset / NtoHl(hVHD->Dynamic.BlockSize));
|
|
uint32 BytesInWrite = BytesToWrite;
|
|
uint32 BytesThisWrite = 0;
|
|
|
|
if (BlockNumber >= NtoHl(hVHD->Dynamic.MaxTableEntries)) {
|
|
return SCPE_EOF;
|
|
}
|
|
if (BlockNumber != (Offset + BytesToWrite) / NtoHl (hVHD->Dynamic.BlockSize))
|
|
BytesInWrite = (uint32)(((BlockNumber + 1) * NtoHl(hVHD->Dynamic.BlockSize)) - Offset);
|
|
if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY) {
|
|
uint8 *BitMap = NULL;
|
|
uint32 BitMapBufferSize = VHD_DATA_BLOCK_ALIGNMENT;
|
|
uint8 *BitMapBuffer = NULL;
|
|
void *BlockData = NULL;
|
|
uint8 *BATUpdateBufferAddress;
|
|
uint32 BATUpdateBufferSize;
|
|
uint64 BATUpdateStorageAddress;
|
|
uint64 BlockOffset;
|
|
|
|
if (!hVHD->Parent && BufferIsZeros(buf, BytesInWrite)) {
|
|
BytesThisWrite = BytesInWrite;
|
|
goto IO_Done;
|
|
}
|
|
/* Need to allocate a new Data Block. */
|
|
BlockOffset = sim_fsize_ex (hVHD->File);
|
|
if (((int64)BlockOffset) == -1)
|
|
return SCPE_IOERR;
|
|
if ((BitMapSectors * VHD_Internal_SectorSize) > BitMapBufferSize)
|
|
BitMapBufferSize = BitMapSectors * VHD_Internal_SectorSize;
|
|
BitMapBuffer = (uint8 *)calloc(1, BitMapBufferSize + NtoHl(hVHD->Dynamic.BlockSize));
|
|
if (BitMapBufferSize > BitMapSectors * VHD_Internal_SectorSize)
|
|
BitMap = BitMapBuffer + BitMapBufferSize - BitMapBytes;
|
|
else
|
|
BitMap = BitMapBuffer;
|
|
memset(BitMap, 0xFF, BitMapBytes);
|
|
BlockOffset -= sizeof(hVHD->Footer);
|
|
if (0 == (BlockOffset & (VHD_DATA_BLOCK_ALIGNMENT-1)))
|
|
{ // Already aligned, so use padded BitMapBuffer
|
|
if (WriteFilePosition(hVHD->File,
|
|
BitMapBuffer,
|
|
BitMapBufferSize + NtoHl(hVHD->Dynamic.BlockSize),
|
|
NULL,
|
|
BlockOffset)) {
|
|
free (BitMapBuffer);
|
|
return SCPE_IOERR;
|
|
}
|
|
BlockOffset += BitMapBufferSize;
|
|
}
|
|
else
|
|
{
|
|
// align the data portion of the block to the desired alignment
|
|
// compute the address of the data portion of the block
|
|
BlockOffset += BitMapSectors * VHD_Internal_SectorSize;
|
|
// round up this address to the desired alignment
|
|
BlockOffset += VHD_DATA_BLOCK_ALIGNMENT-1;
|
|
BlockOffset &= ~(VHD_DATA_BLOCK_ALIGNMENT - 1);
|
|
BlockOffset -= BitMapSectors * VHD_Internal_SectorSize;
|
|
if (WriteFilePosition(hVHD->File,
|
|
BitMap,
|
|
(BitMapSectors * VHD_Internal_SectorSize) + NtoHl(hVHD->Dynamic.BlockSize),
|
|
NULL,
|
|
BlockOffset)) {
|
|
free (BitMapBuffer);
|
|
return SCPE_IOERR;
|
|
}
|
|
BlockOffset += BitMapSectors * VHD_Internal_SectorSize;
|
|
}
|
|
free(BitMapBuffer);
|
|
BitMapBuffer = BitMap = NULL;
|
|
/* the BAT block address is the beginning of the block bitmap */
|
|
BlockOffset -= BitMapSectors * VHD_Internal_SectorSize;
|
|
hVHD->BAT[BlockNumber] = NtoHl((uint32)(BlockOffset / VHD_Internal_SectorSize));
|
|
BlockOffset += (BitMapSectors * VHD_Internal_SectorSize) + NtoHl(hVHD->Dynamic.BlockSize);
|
|
if (WriteFilePosition(hVHD->File,
|
|
&hVHD->Footer,
|
|
sizeof(hVHD->Footer),
|
|
NULL,
|
|
BlockOffset))
|
|
goto Fatal_IO_Error;
|
|
/* Since a large VHD can have a pretty large BAT, and we've only changed one longword bat entry
|
|
in the current BAT, we write just the aligned sector which contains the updated BAT entry */
|
|
BATUpdateBufferAddress = (uint8 *)hVHD->BAT - (size_t)NtoHll(hVHD->Dynamic.TableOffset) +
|
|
(size_t)((((size_t)&hVHD->BAT[BlockNumber]) - (size_t)hVHD->BAT + (size_t)NtoHll(hVHD->Dynamic.TableOffset)) & ~(VHD_DATA_BLOCK_ALIGNMENT-1));
|
|
/* If the starting of the BAT isn't on a VHD_DATA_BLOCK_ALIGNMENT boundary and we've just updated
|
|
a BAT entry early in the array, the buffer computed address might be before the start of the
|
|
BAT table. If so, only write the BAT data needed */
|
|
if (BATUpdateBufferAddress < (uint8 *)hVHD->BAT) {
|
|
BATUpdateBufferAddress = (uint8 *)hVHD->BAT;
|
|
BATUpdateBufferSize = (uint32)((((size_t)&hVHD->BAT[BlockNumber]) - (size_t)hVHD->BAT) + 512) & ~511;
|
|
BATUpdateStorageAddress = NtoHll(hVHD->Dynamic.TableOffset);
|
|
}
|
|
else {
|
|
BATUpdateBufferSize = VHD_DATA_BLOCK_ALIGNMENT;
|
|
BATUpdateStorageAddress = NtoHll(hVHD->Dynamic.TableOffset) + BATUpdateBufferAddress - ((uint8 *)hVHD->BAT);
|
|
}
|
|
/* If the total BAT is smaller than one VHD_DATA_BLOCK_ALIGNMENT, then be sure to only write out the BAT data */
|
|
if ((size_t)(BATUpdateBufferAddress - (uint8 *)hVHD->BAT + BATUpdateBufferSize) > VHD_Internal_SectorSize * ((sizeof(*hVHD->BAT)*NtoHl(hVHD->Dynamic.MaxTableEntries) + VHD_Internal_SectorSize - 1)/VHD_Internal_SectorSize))
|
|
BATUpdateBufferSize = (uint32)(VHD_Internal_SectorSize * ((sizeof(*hVHD->BAT) * NtoHl(hVHD->Dynamic.MaxTableEntries) + VHD_Internal_SectorSize - 1)/VHD_Internal_SectorSize) - (BATUpdateBufferAddress - ((uint8 *)hVHD->BAT)));
|
|
if (WriteFilePosition(hVHD->File,
|
|
BATUpdateBufferAddress,
|
|
BATUpdateBufferSize,
|
|
NULL,
|
|
BATUpdateStorageAddress))
|
|
goto Fatal_IO_Error;
|
|
if (hVHD->Parent)
|
|
{ /* Need to populate data block contents from parent VHD */
|
|
BlockData = malloc (NtoHl (hVHD->Dynamic.BlockSize));
|
|
|
|
if (ReadVirtualDisk(hVHD->Parent,
|
|
(uint8*) BlockData,
|
|
NtoHl (hVHD->Dynamic.BlockSize),
|
|
NULL,
|
|
(Offset / NtoHl (hVHD->Dynamic.BlockSize)) * NtoHl (hVHD->Dynamic.BlockSize)))
|
|
goto Fatal_IO_Error;
|
|
if (WriteVirtualDisk(hVHD,
|
|
(uint8*) BlockData,
|
|
NtoHl (hVHD->Dynamic.BlockSize),
|
|
NULL,
|
|
(Offset / NtoHl (hVHD->Dynamic.BlockSize)) * NtoHl (hVHD->Dynamic.BlockSize)))
|
|
goto Fatal_IO_Error;
|
|
free(BlockData);
|
|
}
|
|
continue;
|
|
Fatal_IO_Error:
|
|
free (BitMap);
|
|
free (BlockData);
|
|
r = SCPE_IOERR;
|
|
}
|
|
else {
|
|
uint64 BlockOffset = VHD_Internal_SectorSize * ((uint64)(NtoHl(hVHD->BAT[BlockNumber]) + BitMapSectors)) + (Offset % NtoHl(hVHD->Dynamic.BlockSize));
|
|
|
|
if (WriteFilePosition(hVHD->File,
|
|
buf,
|
|
BytesInWrite,
|
|
&BytesThisWrite,
|
|
BlockOffset))
|
|
r = SCPE_IOERR;
|
|
}
|
|
IO_Done:
|
|
BytesToWrite -= BytesThisWrite;
|
|
buf = (uint8 *)(((char *)buf) + BytesThisWrite);
|
|
Offset += BytesThisWrite;
|
|
TotalBytesWritten += BytesThisWrite;
|
|
}
|
|
if (BytesWritten)
|
|
*BytesWritten = TotalBytesWritten;
|
|
return r;
|
|
}
|
|
|
|
static t_stat
|
|
WriteVirtualDiskSectors(VHDHANDLE hVHD,
|
|
uint8 *buf,
|
|
t_seccnt sects,
|
|
t_seccnt *sectswritten,
|
|
uint32 SectorSize,
|
|
t_lba lba)
|
|
{
|
|
uint32 BytesWritten;
|
|
t_stat r = WriteVirtualDisk(hVHD,
|
|
buf,
|
|
sects * SectorSize,
|
|
&BytesWritten,
|
|
SectorSize * (uint64)lba);
|
|
|
|
if (sectswritten)
|
|
*sectswritten = BytesWritten / SectorSize;
|
|
return r;
|
|
}
|
|
|
|
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
|
|
|
|
/*
|
|
* Zap Type command to remove incorrectly autosize information that
|
|
* may have been recorded at the end of a disk container file
|
|
*/
|
|
|
|
t_stat sim_disk_info_cmd (int32 flag, CONST char *cptr)
|
|
{
|
|
struct simh_disk_footer footer;
|
|
struct simh_disk_footer *f = &footer;
|
|
FILE *container;
|
|
t_offset container_size;
|
|
|
|
if (flag) { /* zap type */
|
|
container = sim_vhd_disk_open (cptr, "r");
|
|
if (container != NULL) {
|
|
sim_vhd_disk_close (container);
|
|
return sim_messagef (SCPE_OPENERR, "Can't change the disk type of a VHD container file\n");
|
|
}
|
|
container = sim_fopen (cptr, "r+");
|
|
if (container == NULL)
|
|
return sim_messagef (SCPE_OPENERR, "Can't open container file '%s' - %s\n", cptr, strerror (errno));
|
|
container_size = sim_fsize_ex (container);
|
|
if ((container_size != (t_offset)-1) && (container_size > sizeof (*f)) &&
|
|
(sim_fseeko (container, container_size - sizeof (*f), SEEK_SET) == 0) &&
|
|
(sizeof (*f) == sim_fread (f, 1, sizeof (*f), container))) {
|
|
if (f->Checksum == NtoHl (eth_crc32 (0, f, sizeof (*f) - sizeof (f->Checksum)))) {
|
|
(void)sim_set_fsize (container, (t_addr)(container_size - sizeof (*f)));
|
|
fclose (container);
|
|
return sim_messagef (SCPE_OK, "Disk Type Removed from container '%s'\n", cptr);
|
|
}
|
|
}
|
|
fclose (container);
|
|
return sim_messagef (SCPE_ARG, "No footer found on disk container '%s'.\n", cptr);
|
|
}
|
|
if (flag == 0) {
|
|
UNIT unit, *uptr = &unit;
|
|
struct disk_context disk_ctx;
|
|
struct disk_context *ctx = &disk_ctx;
|
|
t_offset (*size_function)(FILE *file);
|
|
int (*close_function)(FILE *f);
|
|
FILE *container;
|
|
t_offset container_size;
|
|
|
|
memset (&unit, 0, sizeof (unit));
|
|
memset (&disk_ctx, 0, sizeof (disk_ctx));
|
|
sim_switches |= SWMASK ('E') | SWMASK ('R'); /* Must exist, Read Only */
|
|
uptr->flags |= UNIT_ATTABLE;
|
|
uptr->disk_ctx = &disk_ctx;
|
|
sim_disk_set_fmt (uptr, 0, "VHD", NULL);
|
|
container = sim_vhd_disk_open (cptr, "r");
|
|
if (container == NULL) {
|
|
sim_disk_set_fmt (uptr, 0, "SIMH", NULL);
|
|
container = sim_fopen (cptr, "r+");
|
|
close_function = fclose;
|
|
size_function = sim_fsize_ex;
|
|
}
|
|
else {
|
|
close_function = sim_vhd_disk_close;
|
|
size_function = sim_vhd_disk_size;
|
|
}
|
|
if (container) {
|
|
container_size = size_function (container);
|
|
uptr->filename = strdup (cptr);
|
|
uptr->fileref = container;
|
|
uptr->flags |= UNIT_ATT;
|
|
get_disk_footer (uptr);
|
|
f = ctx->footer;
|
|
if (f) {
|
|
sim_printf ("Container: %s\n"
|
|
" Simulator: %s\n"
|
|
" DriveType: %s\n"
|
|
" SectorSize: %u\n"
|
|
" SectorCount: %u\n"
|
|
" TransferElementSize: %u\n"
|
|
" AccessFormat: %s\n"
|
|
" CreationTime: %s",
|
|
uptr->filename,
|
|
f->CreatingSimulator, f->DriveType, NtoHl(f->SectorSize), NtoHl (f->SectorCount),
|
|
NtoHl (f->TransferElementSize), fmts[f->AccessFormat].name, f->CreationTime);
|
|
sim_printf ("Container Size: %s bytes\n", sim_fmt_numeric ((double)ctx->container_size));
|
|
}
|
|
else {
|
|
sim_printf ("Container Info for '%s' unavailable\n", uptr->filename);
|
|
sim_printf ("Container Size: %s bytes\n", sim_fmt_numeric ((double)container_size));
|
|
}
|
|
free (f);
|
|
free (uptr->filename);
|
|
close_function (container);
|
|
return SCPE_OK;
|
|
}
|
|
else
|
|
return sim_messagef (SCPE_OPENERR, "Can't open container file '%s' - %s\n", cptr, strerror (errno));
|
|
}
|
|
return SCPE_NOFNC;
|
|
}
|
|
|
|
|
|
/* disk testing */
|
|
|
|
#include <setjmp.h>
|
|
|
|
struct disk_test_coverage {
|
|
t_lba total_sectors;
|
|
uint32 max_xfer_size;
|
|
t_seccnt max_xfer_sectors;
|
|
uint32 wsetbits;
|
|
uint32 *wbitmap;
|
|
uint32 *data;
|
|
};
|
|
|
|
static t_stat sim_disk_test_exercise (UNIT *uptr)
|
|
{
|
|
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
|
|
struct disk_test_coverage *c = (struct disk_test_coverage *)calloc (1, sizeof (*c));
|
|
t_stat r = SCPE_OK;
|
|
uint32 uint32s_per_sector = (ctx->sector_size / sizeof (*c->data));
|
|
DEVICE *dptr = find_dev_from_unit (uptr);
|
|
uint32 capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (quadword: 8, word: 2, byte: 1) */
|
|
uint32 tries = 0;
|
|
t_bool unexpected_data = FALSE;
|
|
|
|
c->max_xfer_size = 1024*1024;
|
|
c->max_xfer_sectors = c->max_xfer_size / ctx->sector_size;
|
|
c->total_sectors = (t_lba)((uptr->capac*capac_factor)/(ctx->sector_size/((dptr->flags & DEV_SECTORS) ? ((ctx->sector_size >= 512) ? 512 : ctx->sector_size): 1)));
|
|
c->data = (uint32 *)malloc (c->max_xfer_size);
|
|
c->wbitmap = (uint32 *)calloc ((c->total_sectors + 32)/32, sizeof (*c->wbitmap));
|
|
#define BITMAP_IS_SET(n) (c->wbitmap[(n) >> 5] & (1 << ((n) & 0x1f)))
|
|
#define SET_BITMAP(n) c->wbitmap[(n) >> 5] |= (1 << ((n) & 0x1f))
|
|
/* Randomly populate the whole drive container with known data (sector # in each sector) */
|
|
srand (0);
|
|
while (c->wsetbits < c->total_sectors) {
|
|
t_lba start_lba = (rand () % c->total_sectors);
|
|
t_lba end_lba = start_lba + 1 + (rand () % (c->max_xfer_sectors - 1));
|
|
t_lba lba;
|
|
t_seccnt i, sectors_to_write, sectors_written;
|
|
|
|
if (end_lba > c->total_sectors)
|
|
end_lba = c->total_sectors;
|
|
if (BITMAP_IS_SET(start_lba)) {
|
|
++tries;
|
|
if (tries < 30)
|
|
continue;
|
|
while (BITMAP_IS_SET(start_lba))
|
|
start_lba = (1 + start_lba) % c->total_sectors;
|
|
end_lba = start_lba + 1;
|
|
}
|
|
tries = 0;
|
|
for (lba = start_lba; lba < end_lba; lba++) {
|
|
if (BITMAP_IS_SET(lba)) {
|
|
end_lba = lba;
|
|
break;
|
|
}
|
|
SET_BITMAP(lba);
|
|
++c->wsetbits;
|
|
}
|
|
sectors_to_write = end_lba - start_lba;
|
|
for (i=0; i < sectors_to_write * uint32s_per_sector; i++)
|
|
c->data[i] = start_lba + i / uint32s_per_sector;
|
|
r = sim_disk_wrsect (uptr, start_lba, (uint8 *)c->data, §ors_written, sectors_to_write);
|
|
if (r != SCPE_OK) {
|
|
sim_printf ("Error writing sectors %u thru %u: %s\n", start_lba, end_lba - 1, sim_error_text (r));
|
|
break;
|
|
}
|
|
else {
|
|
if (sectors_to_write != sectors_written) {
|
|
sim_printf ("Unexpectedly wrote %u sectors instead of %u sectors starting at lba %u\n", sectors_written, sectors_to_write, start_lba);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (r == SCPE_OK) {
|
|
t_seccnt sectors_read, sectors_to_read, sector_to_check;
|
|
t_lba lba;
|
|
|
|
sim_printf("Writing OK\n");
|
|
for (lba = 0; (lba < c->total_sectors) && (r == SCPE_OK); lba += sectors_read) {
|
|
sectors_to_read = 1 + (rand () % (c->max_xfer_sectors - 1));
|
|
if (lba + sectors_to_read > c->total_sectors)
|
|
sectors_to_read = c->total_sectors - lba;
|
|
r = sim_disk_rdsect (uptr, lba, (uint8 *)c->data, §ors_read, sectors_to_read);
|
|
if (r == SCPE_OK) {
|
|
if (sectors_read != sectors_to_read) {
|
|
sim_printf ("Only returned %u sectors when reading %u sectors from lba %u\n", sectors_read, sectors_to_read, lba);
|
|
r = SCPE_INCOMP;
|
|
}
|
|
}
|
|
else
|
|
sim_printf ("Error reading %u sectors at lba %u, %u read - %s\n", sectors_to_read, lba, sectors_read, sim_error_text (r));
|
|
for (sector_to_check = 0; sector_to_check < sectors_read; ++sector_to_check) {
|
|
uint32 i;
|
|
|
|
for (i = 0; i < uint32s_per_sector; i++)
|
|
if (c->data[i + sector_to_check * uint32s_per_sector] != (lba + sector_to_check)) {
|
|
sim_printf ("Sector %u(0x%X) has unexpected data at offset 0x%X: 0x%08X\n",
|
|
lba + sector_to_check, lba + sector_to_check, i, c->data[i + sector_to_check * uint32s_per_sector]);
|
|
unexpected_data = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ((r == SCPE_OK) && !unexpected_data)
|
|
sim_printf("Reading OK\n");
|
|
else {
|
|
sim_printf("Reading BAD\n");
|
|
r = SCPE_IERR;
|
|
}
|
|
}
|
|
free (c->data);
|
|
free (c->wbitmap);
|
|
free (c);
|
|
if (r == SCPE_OK) {
|
|
char *filename = strdup (uptr->filename);
|
|
|
|
sim_disk_detach (uptr);
|
|
(void)remove (filename);
|
|
free (filename);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
t_stat sim_disk_test (DEVICE *dptr)
|
|
{
|
|
const char *fmt[] = {"RAW", "VHD", "VHD", "SIMH", NULL};
|
|
uint32 sect_size[] = {576, 4096, 1024, 512, 256, 128, 64, 0};
|
|
uint32 xfr_size[] = {1, 2, 4, 8, 0};
|
|
int x, s, f;
|
|
UNIT *uptr = &dptr->units[0];
|
|
char filename[256];
|
|
t_stat r;
|
|
int32 saved_switches = sim_switches & ~SWMASK('T');
|
|
SIM_TEST_INIT;
|
|
|
|
for (x = 0; xfr_size[x] != 0; x++) {
|
|
for (f = 0; fmt[f] != 0; f++) {
|
|
for (s = 0; sect_size[s] != 0; s++) {
|
|
snprintf (filename, sizeof (filename) - 1, "Test-%u-%u.%s", sect_size[s], xfr_size[x], fmt[f]);
|
|
if ((f > 0) && (strcmp (fmt[f], "VHD") == 0) && (strcmp (fmt[f - 1], "VHD") == 0)) { /* Second VHD is Fixed */
|
|
sim_switches |= SWMASK('X');
|
|
snprintf (filename, sizeof (filename) - 1, "Test-%u-%u-Fixed.%s", sect_size[s], xfr_size[x], fmt[f]);
|
|
}
|
|
else
|
|
sim_switches = saved_switches;
|
|
(void)remove (filename); /* Remove any prior remnants */
|
|
r = sim_disk_set_fmt (uptr, 0, fmt[f], NULL);
|
|
if (r != SCPE_OK)
|
|
break;
|
|
sim_printf ("Testing %s (%s) using %s\n", sim_uname (uptr), sprint_capac (dptr, uptr), filename);
|
|
if (strcmp (fmt[f], "RAW") == 0) {
|
|
/* There is no innate creation of RAW containers, so create the empty container using SIMH format */
|
|
sim_disk_set_fmt (uptr, 0, "SIMH", NULL);
|
|
sim_disk_attach_ex (uptr, filename, sect_size[s], xfr_size[x], TRUE, 0, NULL, 0, 0, NULL);
|
|
sim_disk_detach (uptr);
|
|
sim_disk_set_fmt (uptr, 0, fmt[f], NULL);
|
|
}
|
|
r = sim_disk_attach_ex (uptr, filename, sect_size[s], xfr_size[x], TRUE, 0, NULL, 0, 0, NULL);
|
|
if (r != SCPE_OK)
|
|
break;
|
|
SIM_TEST(sim_disk_test_exercise (uptr));
|
|
}
|
|
}
|
|
}
|
|
return SCPE_OK;
|
|
}
|