summaryrefslogtreecommitdiff
path: root/src/VBox/Devices/Storage
diff options
context:
space:
mode:
authorMichael Meskes <meskes@debian.org>2009-02-09 17:42:08 +0100
committerMichael Meskes <meskes@debian.org>2009-02-09 17:42:08 +0100
commit5b633c860b9ccb98910812f91c2474fda316b50b (patch)
treedb6656e828509048e53ad8aea69d2edb9f64f442 /src/VBox/Devices/Storage
parentce414e6eec1583def0dc7be0926f1a07364cb5e3 (diff)
downloadvirtualbox-5b633c860b9ccb98910812f91c2474fda316b50b.tar.gz
Imported 2.1.2-dfsgupstream/2.1.2-dfsg
Diffstat (limited to 'src/VBox/Devices/Storage')
-rw-r--r--src/VBox/Devices/Storage/ATAController.h515
-rw-r--r--src/VBox/Devices/Storage/Debug.cpp943
-rw-r--r--src/VBox/Devices/Storage/DevATA.cpp6532
-rw-r--r--src/VBox/Devices/Storage/DrvBlock.cpp930
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase.cpp2048
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase.h192
-rw-r--r--src/VBox/Devices/Storage/DrvHostDVD.cpp854
-rw-r--r--src/VBox/Devices/Storage/DrvHostFloppy.cpp233
-rw-r--r--src/VBox/Devices/Storage/DrvMediaISO.cpp354
-rw-r--r--src/VBox/Devices/Storage/DrvRawImage.cpp404
-rw-r--r--src/VBox/Devices/Storage/DrvVD.cpp1223
-rw-r--r--src/VBox/Devices/Storage/Makefile.kup0
-rw-r--r--src/VBox/Devices/Storage/PIIX3ATABmDma.h78
-rw-r--r--src/VBox/Devices/Storage/RawHDDCore.cpp1175
-rw-r--r--src/VBox/Devices/Storage/VBoxHDD-new.cpp3588
-rw-r--r--src/VBox/Devices/Storage/VBoxHDD-newInternal.h540
-rw-r--r--src/VBox/Devices/Storage/VDICore.h655
-rw-r--r--src/VBox/Devices/Storage/VDIHDDCore.cpp1958
-rw-r--r--src/VBox/Devices/Storage/VHDHDDCore.cpp1959
-rw-r--r--src/VBox/Devices/Storage/VmdkHDDCore.cpp5409
-rw-r--r--src/VBox/Devices/Storage/fdc.c2996
-rw-r--r--src/VBox/Devices/Storage/ide.h190
-rw-r--r--src/VBox/Devices/Storage/swab.h64
-rw-r--r--src/VBox/Devices/Storage/testcase/Makefile.kmk77
-rw-r--r--src/VBox/Devices/Storage/testcase/tstVD-2.cpp249
-rw-r--r--src/VBox/Devices/Storage/testcase/tstVD.cpp1078
-rw-r--r--src/VBox/Devices/Storage/testcase/tstVDI.cpp121
-rw-r--r--src/VBox/Devices/Storage/testcase/vditool.cpp445
28 files changed, 34810 insertions, 0 deletions
diff --git a/src/VBox/Devices/Storage/ATAController.h b/src/VBox/Devices/Storage/ATAController.h
new file mode 100644
index 000000000..7f1eaf642
--- /dev/null
+++ b/src/VBox/Devices/Storage/ATAController.h
@@ -0,0 +1,515 @@
+/* $Id: ATAController.h 15252 2008-12-10 15:37:23Z vboxsync $ */
+/** @file
+ * DevATA, DevAHCI - Shared ATA/ATAPI controller types.
+ */
+
+/*
+ * Copyright (C) 2006-2008 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+#ifndef ___Storage_ATAController_h
+#define ___Storage_ATAController_h
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <VBox/pdmdev.h>
+#ifdef IN_RING3
+# include <iprt/semaphore.h>
+# include <iprt/thread.h>
+#endif /* IN_RING3 */
+#include <iprt/critsect.h>
+#include <VBox/stam.h>
+
+#include "PIIX3ATABmDma.h"
+#include "ide.h"
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/**
+ * Maximum number of sectors to transfer in a READ/WRITE MULTIPLE request.
+ * Set to 1 to disable multi-sector read support. According to the ATA
+ * specification this must be a power of 2 and it must fit in an 8 bit
+ * value. Thus the only valid values are 1, 2, 4, 8, 16, 32, 64 and 128.
+ */
+#define ATA_MAX_MULT_SECTORS 128
+
+/**
+ * Fastest PIO mode supported by the drive.
+ */
+#define ATA_PIO_MODE_MAX 4
+/**
+ * Fastest MDMA mode supported by the drive.
+ */
+#define ATA_MDMA_MODE_MAX 2
+/**
+ * Fastest UDMA mode supported by the drive.
+ */
+#define ATA_UDMA_MODE_MAX 6
+
+/** ATAPI sense info size. */
+#define ATAPI_SENSE_SIZE 64
+
+/** The maximum number of release log entries per device. */
+#define MAX_LOG_REL_ERRORS 1024
+
+/* MediaEventStatus */
+#define ATA_EVENT_STATUS_UNCHANGED 0 /**< medium event status not changed */
+#define ATA_EVENT_STATUS_MEDIA_NEW 1 /**< new medium inserted */
+#define ATA_EVENT_STATUS_MEDIA_REMOVED 2 /**< medium removed */
+#define ATA_EVENT_STATUS_MEDIA_CHANGED 3 /**< medium was removed + new medium was inserted */
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+typedef struct AHCIATADevState {
+ /** Flag indicating whether the current command uses LBA48 mode. */
+ bool fLBA48;
+ /** Flag indicating whether this drive implements the ATAPI command set. */
+ bool fATAPI;
+ /** Set if this interface has asserted the IRQ. */
+ bool fIrqPending;
+ /** Currently configured number of sectors in a multi-sector transfer. */
+ uint8_t cMultSectors;
+ /** PCHS disk geometry. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** Total number of sectors on this disk. */
+ uint64_t cTotalSectors;
+ /** Number of sectors to transfer per IRQ. */
+ uint32_t cSectorsPerIRQ;
+
+ /** ATA/ATAPI register 1: feature (write-only). */
+ uint8_t uATARegFeature;
+ /** ATA/ATAPI register 1: feature, high order byte. */
+ uint8_t uATARegFeatureHOB;
+ /** ATA/ATAPI register 1: error (read-only). */
+ uint8_t uATARegError;
+ /** ATA/ATAPI register 2: sector count (read/write). */
+ uint8_t uATARegNSector;
+ /** ATA/ATAPI register 2: sector count, high order byte. */
+ uint8_t uATARegNSectorHOB;
+ /** ATA/ATAPI register 3: sector (read/write). */
+ uint8_t uATARegSector;
+ /** ATA/ATAPI register 3: sector, high order byte. */
+ uint8_t uATARegSectorHOB;
+ /** ATA/ATAPI register 4: cylinder low (read/write). */
+ uint8_t uATARegLCyl;
+ /** ATA/ATAPI register 4: cylinder low, high order byte. */
+ uint8_t uATARegLCylHOB;
+ /** ATA/ATAPI register 5: cylinder high (read/write). */
+ uint8_t uATARegHCyl;
+ /** ATA/ATAPI register 5: cylinder high, high order byte. */
+ uint8_t uATARegHCylHOB;
+ /** ATA/ATAPI register 6: select drive/head (read/write). */
+ uint8_t uATARegSelect;
+ /** ATA/ATAPI register 7: status (read-only). */
+ uint8_t uATARegStatus;
+ /** ATA/ATAPI register 7: command (write-only). */
+ uint8_t uATARegCommand;
+ /** ATA/ATAPI drive control register (write-only). */
+ uint8_t uATARegDevCtl;
+
+ /** Currently active transfer mode (MDMA/UDMA) and speed. */
+ uint8_t uATATransferMode;
+ /** Current transfer direction. */
+ uint8_t uTxDir;
+ /** Index of callback for begin transfer. */
+ uint8_t iBeginTransfer;
+ /** Index of callback for source/sink of data. */
+ uint8_t iSourceSink;
+ /** Flag indicating whether the current command transfers data in DMA mode. */
+ bool fDMA;
+ /** Set to indicate that ATAPI transfer semantics must be used. */
+ bool fATAPITransfer;
+
+ /** Total ATA/ATAPI transfer size, shared PIO/DMA. */
+ uint32_t cbTotalTransfer;
+ /** Elementary ATA/ATAPI transfer size, shared PIO/DMA. */
+ uint32_t cbElementaryTransfer;
+ /** Current read/write buffer position, shared PIO/DMA. */
+ uint32_t iIOBufferCur;
+ /** First element beyond end of valid buffer content, shared PIO/DMA. */
+ uint32_t iIOBufferEnd;
+
+ /** ATA/ATAPI current PIO read/write transfer position. Not shared with DMA for safety reasons. */
+ uint32_t iIOBufferPIODataStart;
+ /** ATA/ATAPI current PIO read/write transfer end. Not shared with DMA for safety reasons. */
+ uint32_t iIOBufferPIODataEnd;
+
+ /** ATAPI current LBA position. */
+ uint32_t iATAPILBA;
+ /** ATAPI current sector size. */
+ uint32_t cbATAPISector;
+ /** ATAPI current command. */
+ uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
+ /** ATAPI sense data. */
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ /** HACK: Countdown till we report a newly unmounted drive as mounted. */
+ uint8_t cNotifiedMediaChange;
+ /** The same for GET_EVENT_STATUS for mechanism */
+ volatile uint32_t MediaEventStatus;
+
+ /** The status LED state for this drive. */
+ R3PTRTYPE(PPDMLED) pLed;
+#if HC_ARCH_BITS == 64
+ uint32_t uAlignment3;
+#endif
+
+ /** Size of I/O buffer. */
+ uint32_t cbIOBuffer;
+ /** Pointer to the I/O buffer. */
+ R3PTRTYPE(uint8_t *) pbIOBufferR3;
+ /** Pointer to the I/O buffer. */
+ R0PTRTYPE(uint8_t *) pbIOBufferR0;
+ /** Pointer to the I/O buffer. */
+ RCPTRTYPE(uint8_t *) pbIOBufferRC;
+
+ RTRCPTR Aligmnent1; /**< Align the statistics at an 8-byte boundrary. */
+
+ /*
+ * No data that is part of the saved state after this point!!!!!
+ */
+
+ /* Release statistics: number of ATA DMA commands. */
+ STAMCOUNTER StatATADMA;
+ /* Release statistics: number of ATA PIO commands. */
+ STAMCOUNTER StatATAPIO;
+ /* Release statistics: number of ATAPI PIO commands. */
+ STAMCOUNTER StatATAPIDMA;
+ /* Release statistics: number of ATAPI PIO commands. */
+ STAMCOUNTER StatATAPIPIO;
+#ifdef VBOX_INSTRUMENT_DMA_WRITES
+ /* Release statistics: number of DMA sector writes and the time spent. */
+ STAMPROFILEADV StatInstrVDWrites;
+#endif
+
+ /** Statistics: number of read operations and the time spent reading. */
+ STAMPROFILEADV StatReads;
+ /** Statistics: number of bytes read. */
+ R3PTRTYPE(PSTAMCOUNTER) pStatBytesRead;
+#if HC_ARCH_BITS == 64
+ uint64_t uAlignment4;
+#endif
+ /** Statistics: number of write operations and the time spent writing. */
+ STAMPROFILEADV StatWrites;
+ /** Statistics: number of bytes written. */
+ R3PTRTYPE(PSTAMCOUNTER) pStatBytesWritten;
+#if HC_ARCH_BITS == 64
+ uint64_t uAlignment5;
+#endif
+ /** Statistics: number of flush operations and the time spend flushing. */
+ STAMPROFILE StatFlushes;
+
+ /** Enable passing through commands directly to the ATAPI drive. */
+ bool fATAPIPassthrough;
+ /** Number of errors we've reported to the release log.
+ * This is to prevent flooding caused by something going horribly wrong.
+ * this value against MAX_LOG_REL_ERRORS in places likely to cause floods
+ * like the ones we currently seeing on the linux smoke tests (2006-11-10). */
+ uint32_t cErrors;
+ /** Timestamp of last started command. 0 if no command pending. */
+ uint64_t u64CmdTS;
+
+ /** Pointer to the attached driver's base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Pointer to the attached driver's block interface. */
+ R3PTRTYPE(PPDMIBLOCK) pDrvBlock;
+ /** Pointer to the attached driver's block bios interface. */
+ R3PTRTYPE(PPDMIBLOCKBIOS) pDrvBlockBios;
+ /** Pointer to the attached driver's mount interface.
+ * This is NULL if the driver isn't a removable unit. */
+ R3PTRTYPE(PPDMIMOUNT) pDrvMount;
+ /** The base interface. */
+ PDMIBASE IBase;
+ /** The block port interface. */
+ PDMIBLOCKPORT IPort;
+ /** The mount notify interface. */
+ PDMIMOUNTNOTIFY IMountNotify;
+ /** The LUN #. */
+ RTUINT iLUN;
+#if HC_ARCH_BITS == 64
+ RTUINT Alignment2; /**< Align pDevInsR3 correctly. */
+#endif
+ /** Pointer to device instance. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to controller instance. */
+ R3PTRTYPE(struct AHCIATACONTROLLER *) pControllerR3;
+ /** Pointer to device instance. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to controller instance. */
+ R0PTRTYPE(struct AHCIATACONTROLLER *) pControllerR0;
+ /** Pointer to device instance. */
+ PPDMDEVINSRC pDevInsRC;
+ /** Pointer to controller instance. */
+ RCPTRTYPE(struct AHCIATACONTROLLER *) pControllerRC;
+} AHCIATADevState;
+
+
+typedef struct AHCIATATransferRequest
+{
+ uint8_t iIf;
+ uint8_t iBeginTransfer;
+ uint8_t iSourceSink;
+ uint32_t cbTotalTransfer;
+ uint8_t uTxDir;
+} AHCIATATransferRequest;
+
+
+typedef struct AHCIATAAbortRequest
+{
+ uint8_t iIf;
+ bool fResetDrive;
+} AHCIATAAbortRequest;
+
+
+typedef enum
+{
+ /** Begin a new transfer. */
+ AHCIATA_AIO_NEW = 0,
+ /** Continue a DMA transfer. */
+ AHCIATA_AIO_DMA,
+ /** Continue a PIO transfer. */
+ AHCIATA_AIO_PIO,
+ /** Reset the drives on current controller, stop all transfer activity. */
+ AHCIATA_AIO_RESET_ASSERTED,
+ /** Reset the drives on current controller, resume operation. */
+ AHCIATA_AIO_RESET_CLEARED,
+ /** Abort the current transfer of a particular drive. */
+ AHCIATA_AIO_ABORT
+} AHCIATAAIO;
+
+
+typedef struct AHCIATARequest
+{
+ AHCIATAAIO ReqType;
+ union
+ {
+ AHCIATATransferRequest t;
+ AHCIATAAbortRequest a;
+ } u;
+} AHCIATARequest;
+
+
+typedef struct AHCIATACONTROLLER
+{
+ /** The base of the first I/O Port range. */
+ RTIOPORT IOPortBase1;
+ /** The base of the second I/O Port range. (0 if none) */
+ RTIOPORT IOPortBase2;
+ /** The assigned IRQ. */
+ RTUINT irq;
+ /** Access critical section */
+ PDMCRITSECT lock;
+
+ /** Selected drive. */
+ uint8_t iSelectedIf;
+ /** The interface on which to handle async I/O. */
+ uint8_t iAIOIf;
+ /** The state of the async I/O thread. */
+ uint8_t uAsyncIOState;
+ /** Flag indicating whether the next transfer is part of the current command. */
+ bool fChainedTransfer;
+ /** Set when the reset processing is currently active on this controller. */
+ bool fReset;
+ /** Flag whether the current transfer needs to be redone. */
+ bool fRedo;
+ /** Flag whether the redo suspend has been finished. */
+ bool fRedoIdle;
+ /** Flag whether the DMA operation to be redone is the final transfer. */
+ bool fRedoDMALastDesc;
+ /** The BusMaster DMA state. */
+ BMDMAState BmDma;
+ /** Pointer to first DMA descriptor. */
+ RTGCPHYS32 pFirstDMADesc;
+ /** Pointer to last DMA descriptor. */
+ RTGCPHYS32 pLastDMADesc;
+ /** Pointer to current DMA buffer (for redo operations). */
+ RTGCPHYS32 pRedoDMABuffer;
+ /** Size of current DMA buffer (for redo operations). */
+ uint32_t cbRedoDMABuffer;
+
+ /** The ATA/ATAPI interfaces of this controller. */
+ AHCIATADevState aIfs[2];
+
+ /** Pointer to device instance. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to device instance. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to device instance. */
+ PPDMDEVINSRC pDevInsRC;
+
+ /** Set when the destroying the device instance and the thread must exit. */
+ uint32_t volatile fShutdown;
+ /** The async I/O thread handle. NIL_RTTHREAD if no thread. */
+ RTTHREAD AsyncIOThread;
+ /** The event semaphore the thread is waiting on for requests. */
+ RTSEMEVENT AsyncIOSem;
+ /** The request queue for the AIO thread. One element is always unused. */
+ AHCIATARequest aAsyncIORequests[4];
+ /** The position at which to insert a new request for the AIO thread. */
+ uint8_t AsyncIOReqHead;
+ /** The position at which to get a new request for the AIO thread. */
+ uint8_t AsyncIOReqTail;
+ uint8_t Alignment3[2]; /**< Explicit padding of the 2 byte gap. */
+ /** Magic delay before triggering interrupts in DMA mode. */
+ uint32_t DelayIRQMillies;
+ /** The mutex protecting the request queue. */
+ RTSEMMUTEX AsyncIORequestMutex;
+ /** The event semaphore the thread is waiting on during suspended I/O. */
+ RTSEMEVENT SuspendIOSem;
+#if 0 /*HC_ARCH_BITS == 32*/
+ uint32_t Alignment0;
+#endif
+
+ /* Statistics */
+ STAMCOUNTER StatAsyncOps;
+ uint64_t StatAsyncMinWait;
+ uint64_t StatAsyncMaxWait;
+ STAMCOUNTER StatAsyncTimeUS;
+ STAMPROFILEADV StatAsyncTime;
+ STAMPROFILE StatLockWait;
+} AHCIATACONTROLLER, *PAHCIATACONTROLLER;
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+#define ATADEVSTATE_2_CONTROLLER(pIf) ( (pIf)->CTX_SUFF(pController) )
+#define ATADEVSTATE_2_DEVINS(pIf) ( (pIf)->CTX_SUFF(pDevIns) )
+#define CONTROLLER_2_DEVINS(pController) ( (pController)->CTX_SUFF(pDevIns) )
+#define PDMIBASE_2_ATASTATE(pInterface) ( (AHCIATADevState *)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCIATADevState, IBase)) )
+
+
+/*******************************************************************************
+ * Internal Functions *
+ ******************************************************************************/
+__BEGIN_DECLS
+int ataControllerIOPortWrite1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t u32, unsigned cb);
+int ataControllerIOPortRead1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t *u32, unsigned cb);
+int ataControllerIOPortWriteStr1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb);
+int ataControllerIOPortReadStr1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb);
+int ataControllerIOPortWrite2(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t u32, unsigned cb);
+int ataControllerIOPortRead2(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t *u32, unsigned cb);
+int ataControllerBMDMAIOPortRead(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t *pu32, unsigned cb);
+int ataControllerBMDMAIOPortWrite(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t u32, unsigned cb);
+__END_DECLS
+
+#ifdef IN_RING3
+/**
+ * Initialize a controller state.
+ *
+ * @returns VBox status code.
+ * @param pDevIns Pointer to the device instance which creates a controller.
+ * @param pCtl Pointer to the unitialized ATA controller structure.
+ * @param pDrvBaseMaster Pointer to the base driver interface which acts as the master.
+ * @param pDrvBaseSlave Pointer to the base driver interface which acts as the slave.
+ * @param pcbSSMState Where to store the size of the device state for loading/saving.
+ * @param szName Name of the controller (Used to initialize the critical section).
+ */
+int ataControllerInit(PPDMDEVINS pDevIns, PAHCIATACONTROLLER pCtl, PPDMIBASE pDrvBaseMaster, PPDMIBASE pDrvBaseSlave,
+ uint32_t *pcbSSMState, const char *szName, PPDMLED pLed, PSTAMCOUNTER pStatBytesRead, PSTAMCOUNTER pStatBytesWritten);
+
+/**
+ * Free all allocated resources for one controller instance.
+ *
+ * @returns VBox status code.
+ * @param pCtl The controller instance.
+ */
+int ataControllerDestroy(PAHCIATACONTROLLER pCtl);
+
+/**
+ * Power off a controller.
+ *
+ * @returns nothing.
+ * @param pCtl the controller instance.
+ */
+void ataControllerPowerOff(PAHCIATACONTROLLER pCtl);
+
+/**
+ * Reset a controller instance to an initial state.
+ *
+ * @returns VBox status code.
+ * @param pCtl Pointer to the controller.
+ */
+void ataControllerReset(PAHCIATACONTROLLER pCtl);
+
+/**
+ * Suspend operation of an controller.
+ *
+ * @returns nothing
+ * @param pCtl The controller instance.
+ */
+void ataControllerSuspend(PAHCIATACONTROLLER pCtl);
+
+/**
+ * Resume operation of an controller.
+ *
+ * @returns nothing
+ * @param pCtl The controller instance.
+ */
+
+void ataControllerResume(PAHCIATACONTROLLER pCtl);
+
+/**
+ * Relocate neccessary pointers.
+ *
+ * @returns nothing.
+ * @param pCtl The controller instance.
+ * @param offDelta The relocation delta relative to the old location.
+ */
+void ataControllerRelocate(PAHCIATACONTROLLER pCtl, RTGCINTPTR offDelta);
+
+/**
+ * Execute state save operation.
+ *
+ * @returns VBox status code.
+ * @param pCtl The controller instance.
+ * @param pSSM SSM operation handle.
+ */
+int ataControllerSaveExec(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSM);
+
+/**
+ * Prepare state save operation.
+ *
+ * @returns VBox status code.
+ * @param pCtl The controller instance.
+ * @param pSSM SSM operation handle.
+ */
+int ataControllerSavePrep(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSM);
+
+/**
+ * Excute state load operation.
+ *
+ * @returns VBox status code.
+ * @param pCtl The controller instance.
+ * @param pSSM SSM operation handle.
+ */
+int ataControllerLoadExec(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSM);
+
+/**
+ * Prepare state load operation.
+ *
+ * @returns VBox status code.
+ * @param pCtl The controller instance.
+ * @param pSSM SSM operation handle.
+ */
+int ataControllerLoadPrep(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSM);
+
+#endif /* IN_RING3 */
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
+#endif /* !___Storage_ATAController_h */
+
diff --git a/src/VBox/Devices/Storage/Debug.cpp b/src/VBox/Devices/Storage/Debug.cpp
new file mode 100644
index 000000000..c8322554d
--- /dev/null
+++ b/src/VBox/Devices/Storage/Debug.cpp
@@ -0,0 +1,943 @@
+/* $Id: Debug.cpp 12927 2008-10-02 11:34:15Z vboxsync $ */
+/** @file
+ * VBox storage devices: debug helpers
+ */
+
+/*
+ * Copyright (C) 2008 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+
+#include <iprt/assert.h>
+#include <iprt/types.h>
+#include <VBox/scsi.h>
+#include "ide.h"
+
+/**
+ * ATA command codes
+ */
+static const char * const g_apszATACmdNames[256] =
+{
+ "NOP", /* 0x00 */
+ "", /* 0x01 */
+ "", /* 0x02 */
+ "CFA REQUEST EXTENDED ERROR CODE", /* 0x03 */
+ "", /* 0x04 */
+ "", /* 0x05 */
+ "", /* 0x06 */
+ "", /* 0x07 */
+ "DEVICE RESET", /* 0x08 */
+ "", /* 0x09 */
+ "", /* 0x0a */
+ "", /* 0x0b */
+ "", /* 0x0c */
+ "", /* 0x0d */
+ "", /* 0x0e */
+ "", /* 0x0f */
+ "RECALIBRATE", /* 0x10 */
+ "", /* 0x11 */
+ "", /* 0x12 */
+ "", /* 0x13 */
+ "", /* 0x14 */
+ "", /* 0x15 */
+ "", /* 0x16 */
+ "", /* 0x17 */
+ "", /* 0x18 */
+ "", /* 0x19 */
+ "", /* 0x1a */
+ "", /* 0x1b */
+ "", /* 0x1c */
+ "", /* 0x1d */
+ "", /* 0x1e */
+ "", /* 0x1f */
+ "READ SECTORS", /* 0x20 */
+ "READ SECTORS WITHOUT RETRIES", /* 0x21 */
+ "READ LONG", /* 0x22 */
+ "READ LONG WITHOUT RETRIES", /* 0x23 */
+ "READ SECTORS EXT", /* 0x24 */
+ "READ DMA EXT", /* 0x25 */
+ "READ DMA QUEUED EXT", /* 0x26 */
+ "READ NATIVE MAX ADDRESS EXT", /* 0x27 */
+ "", /* 0x28 */
+ "READ MULTIPLE EXT", /* 0x29 */
+ "READ STREAM DMA EXT", /* 0x2a */
+ "READ STREAM EXT", /* 0x2b */
+ "", /* 0x2c */
+ "", /* 0x2d */
+ "", /* 0x2e */
+ "READ LOG EXT", /* 0x2f */
+ "WRITE SECTORS", /* 0x30 */
+ "WRITE SECTORS WITHOUT RETRIES", /* 0x31 */
+ "WRITE LONG", /* 0x32 */
+ "WRITE LONG WITHOUT RETRIES", /* 0x33 */
+ "WRITE SECTORS EXT", /* 0x34 */
+ "WRITE DMA EXT", /* 0x35 */
+ "WRITE DMA QUEUED EXT", /* 0x36 */
+ "SET MAX ADDRESS EXT", /* 0x37 */
+ "CFA WRITE SECTORS WITHOUT ERASE", /* 0x38 */
+ "WRITE MULTIPLE EXT", /* 0x39 */
+ "WRITE STREAM DMA EXT", /* 0x3a */
+ "WRITE STREAM EXT", /* 0x3b */
+ "WRITE VERIFY", /* 0x3c */
+ "WRITE DMA FUA EXT", /* 0x3d */
+ "WRITE DMA QUEUED FUA EXT", /* 0x3e */
+ "WRITE LOG EXT", /* 0x3f */
+ "READ VERIFY SECTORS", /* 0x40 */
+ "READ VERIFY SECTORS WITHOUT RETRIES", /* 0x41 */
+ "READ VERIFY SECTORS EXT", /* 0x42 */
+ "", /* 0x43 */
+ "", /* 0x44 */
+ "WRITE UNCORRECTABLE EXT", /* 0x45 */
+ "", /* 0x46 */
+ "READ LOG DMA EXT", /* 0x47 */
+ "", /* 0x48 */
+ "", /* 0x49 */
+ "", /* 0x4a */
+ "", /* 0x4b */
+ "", /* 0x4c */
+ "", /* 0x4d */
+ "", /* 0x4e */
+ "", /* 0x4f */
+ "FORMAT TRACK", /* 0x50 */
+ "CONFIGURE STREAM", /* 0x51 */
+ "", /* 0x52 */
+ "", /* 0x53 */
+ "", /* 0x54 */
+ "", /* 0x55 */
+ "", /* 0x56 */
+ "WRITE LOG DMA EXT", /* 0x57 */
+ "", /* 0x58 */
+ "", /* 0x59 */
+ "", /* 0x5a */
+ "", /* 0x5b */
+ "TRUSTED RECEIVE", /* 0x5c */
+ "TRUSTED RECEIVE DMA", /* 0x5d */
+ "TRUSTED SEND", /* 0x5e */
+ "TRUSTED SEND DMA", /* 0x5f */
+ "READ FPDMA QUEUED", /* 0x60 */
+ "WRITE FPDMA QUEUED", /* 0x61 */
+ "", /* 0x62 */
+ "", /* 0x63 */
+ "", /* 0x64 */
+ "", /* 0x65 */
+ "", /* 0x66 */
+ "", /* 0x67 */
+ "", /* 0x68 */
+ "", /* 0x69 */
+ "", /* 0x6a */
+ "", /* 0x6b */
+ "", /* 0x6c */
+ "", /* 0x6d */
+ "", /* 0x6e */
+ "", /* 0x6f */
+ "SEEK", /* 0x70 */
+ "", /* 0x71 */
+ "", /* 0x72 */
+ "", /* 0x73 */
+ "", /* 0x74 */
+ "", /* 0x75 */
+ "", /* 0x76 */
+ "", /* 0x77 */
+ "", /* 0x78 */
+ "", /* 0x79 */
+ "", /* 0x7a */
+ "", /* 0x7b */
+ "", /* 0x7c */
+ "", /* 0x7d */
+ "", /* 0x7e */
+ "", /* 0x7f */
+ "", /* 0x80 */
+ "", /* 0x81 */
+ "", /* 0x82 */
+ "", /* 0x83 */
+ "", /* 0x84 */
+ "", /* 0x85 */
+ "", /* 0x86 */
+ "CFA TRANSLATE SECTOR", /* 0x87 */
+ "", /* 0x88 */
+ "", /* 0x89 */
+ "", /* 0x8a */
+ "", /* 0x8b */
+ "", /* 0x8c */
+ "", /* 0x8d */
+ "", /* 0x8e */
+ "", /* 0x8f */
+ "EXECUTE DEVICE DIAGNOSTIC", /* 0x90 */
+ "INITIALIZE DEVICE PARAMETERS", /* 0x91 */
+ "DOWNLOAD MICROCODE", /* 0x92 */
+ "", /* 0x93 */
+ "STANDBY IMMEDIATE ALT", /* 0x94 */
+ "IDLE IMMEDIATE ALT", /* 0x95 */
+ "STANDBY ALT", /* 0x96 */
+ "IDLE ALT", /* 0x97 */
+ "CHECK POWER MODE ALT", /* 0x98 */
+ "SLEEP ALT", /* 0x99 */
+ "", /* 0x9a */
+ "", /* 0x9b */
+ "", /* 0x9c */
+ "", /* 0x9d */
+ "", /* 0x9e */
+ "", /* 0x9f */
+ "PACKET", /* 0xa0 */
+ "IDENTIFY PACKET DEVICE", /* 0xa1 */
+ "SERVICE", /* 0xa2 */
+ "", /* 0xa3 */
+ "", /* 0xa4 */
+ "", /* 0xa5 */
+ "", /* 0xa6 */
+ "", /* 0xa7 */
+ "", /* 0xa8 */
+ "", /* 0xa9 */
+ "", /* 0xaa */
+ "", /* 0xab */
+ "", /* 0xac */
+ "", /* 0xad */
+ "", /* 0xae */
+ "", /* 0xaf */
+ "SMART", /* 0xb0 */
+ "DEVICE CONFIGURATION OVERLAY", /* 0xb1 */
+ "", /* 0xb2 */
+ "", /* 0xb3 */
+ "", /* 0xb4 */
+ "", /* 0xb5 */
+ "NV CACHE", /* 0xb6 */
+ "", /* 0xb7 */
+ "", /* 0xb8 */
+ "", /* 0xb9 */
+ "", /* 0xba */
+ "", /* 0xbb */
+ "", /* 0xbc */
+ "", /* 0xbd */
+ "", /* 0xbe */
+ "", /* 0xbf */
+ "CFA ERASE SECTORS", /* 0xc0 */
+ "", /* 0xc1 */
+ "", /* 0xc2 */
+ "", /* 0xc3 */
+ "READ MULTIPLE", /* 0xc4 */
+ "WRITE MULTIPLE", /* 0xc5 */
+ "SET MULTIPLE MODE", /* 0xc6 */
+ "READ DMA QUEUED", /* 0xc7 */
+ "READ DMA", /* 0xc8 */
+ "READ DMA WITHOUT RETRIES", /* 0xc9 */
+ "WRITE DMA", /* 0xca */
+ "WRITE DMA WITHOUT RETRIES", /* 0xcb */
+ "WRITE DMA QUEUED", /* 0xcc */
+ "CFA WRITE MULTIPLE WITHOUT ERASE", /* 0xcd */
+ "WRITE MULTIPLE FUA EXT", /* 0xce */
+ "", /* 0xcf */
+ "", /* 0xd0 */
+ "CHECK MEDIA CARD TYPE", /* 0xd1 */
+ "", /* 0xd2 */
+ "", /* 0xd3 */
+ "", /* 0xd4 */
+ "", /* 0xd5 */
+ "", /* 0xd6 */
+ "", /* 0xd7 */
+ "", /* 0xd8 */
+ "", /* 0xd9 */
+ "GET MEDIA STATUS", /* 0xda */
+ "ACKNOWLEDGE MEDIA CHANGE", /* 0xdb */
+ "BOOT POST BOOT", /* 0xdc */
+ "BOOT PRE BOOT", /* 0xdd */
+ "MEDIA LOCK", /* 0xde */
+ "MEDIA UNLOCK", /* 0xdf */
+ "STANDBY IMMEDIATE", /* 0xe0 */
+ "IDLE IMMEDIATE", /* 0xe1 */
+ "STANDBY", /* 0xe2 */
+ "IDLE", /* 0xe3 */
+ "READ BUFFER", /* 0xe4 */
+ "CHECK POWER MODE", /* 0xe5 */
+ "SLEEP", /* 0xe6 */
+ "FLUSH CACHE", /* 0xe7 */
+ "WRITE BUFFER", /* 0xe8 */
+ "WRITE SAME", /* 0xe9 */
+ "FLUSH CACHE EXT", /* 0xea */
+ "", /* 0xeb */
+ "IDENTIFY DEVICE", /* 0xec */
+ "MEDIA EJECT", /* 0xed */
+ "IDENTIFY DMA", /* 0xee */
+ "SET FEATURES", /* 0xef */
+ "", /* 0xf0 */
+ "SECURITY SET PASSWORD", /* 0xf1 */
+ "SECURITY UNLOCK", /* 0xf2 */
+ "SECURITY ERASE PREPARE", /* 0xf3 */
+ "SECURITY ERASE UNIT", /* 0xf4 */
+ "SECURITY FREEZE LOCK", /* 0xf5 */
+ "SECURITY DISABLE PASSWORD", /* 0xf6 */
+ "", /* 0xf7 */
+ "READ NATIVE MAX ADDRESS", /* 0xf8 */
+ "SET MAX", /* 0xf9 */
+ "", /* 0xfa */
+ "", /* 0xfb */
+ "", /* 0xfc */
+ "", /* 0xfd */
+ "", /* 0xfe */
+ "" /* 0xff */
+};
+
+/**
+ * SCSI command codes.
+ */
+static const char * const g_apszSCSICmdNames[256] =
+{
+ "TEST UNIT READY", /* 0x00 */
+ "REZERO UNIT", /* 0x01 */
+ "", /* 0x02 */
+ "REQUEST SENSE", /* 0x03 */
+ "FORMAT UNIT", /* 0x04 */
+ "READ BLOCK LIMITS", /* 0x05 */
+ "", /* 0x06 */
+ "REASSIGN BLOCKS", /* 0x07 */
+ "READ (6)", /* 0x08 */
+ "", /* 0x09 */
+ "WRITE (6)", /* 0x0a */
+ "SEEK (6)", /* 0x0b */
+ "", /* 0x0c */
+ "", /* 0x0d */
+ "", /* 0x0e */
+ "READ REVERSE (6)", /* 0x0f */
+ "READ FILEMARKS (6)", /* 0x10 */
+ "SPACE (6)", /* 0x11 */
+ "INQUIRY", /* 0x12 */
+ "VERIFY (6)", /* 0x13 */
+ "RECOVER BUFFERED DATA", /* 0x14 */
+ "MODE SELECT (6)", /* 0x15 */
+ "RESERVE (6)", /* 0x16 */
+ "RELEASE (6)", /* 0x17 */
+ "COPY", /* 0x18 */
+ "ERASE (6)", /* 0x19 */
+ "MODE SENSE (6)", /* 0x1a */
+ "START STOP UNIT", /* 0x1b */
+ "RECEIVE DIAGNOSTIC RESULTS", /* 0x1c */
+ "SEND DIAGNOSTIC", /* 0x1d */
+ "PREVENT ALLOW MEDIUM REMOVAL", /* 0x1e */
+ "", /* 0x1f */
+ "", /* 0x20 */
+ "", /* 0x21 */
+ "", /* 0x22 */
+ "READ FORMAT CAPACITIES", /* 0x23 */
+ "SET WINDOW", /* 0x24 */
+ "READ CAPACITY", /* 0x25 */
+ "", /* 0x26 */
+ "", /* 0x27 */
+ "READ (10)", /* 0x28 */
+ "READ GENERATION", /* 0x29 */
+ "WRITE (10)", /* 0x2a */
+ "SEEK (10)", /* 0x2b */
+ "ERASE (10)", /* 0x2c */
+ "READ UPDATED BLOCK", /* 0x2d */
+ "WRITE AND VERIFY (10)", /* 0x2e */
+ "VERIFY (10)", /* 0x2f */
+ "SEARCH DATA HIGH (10)", /* 0x30 */
+ "SEARCH DATA EQUAL (10)", /* 0x31 */
+ "SEARCH DATA LOW (10)", /* 0x32 */
+ "SET LIMITS (10)", /* 0x33 */
+ "PRE-FETCH (10)", /* 0x34 */
+ "SYNCHRONIZE CACHE (10)", /* 0x35 */
+ "LOCK UNLOCK CACHE (10)", /* 0x36 */
+ "READ DEFECT DATA (10)", /* 0x37 */
+ "MEDIUM SCAN", /* 0x38 */
+ "COMPARE", /* 0x39 */
+ "COPY AND VERIFY", /* 0x3a */
+ "WRITE BUFFER", /* 0x3b */
+ "READ BUFFER", /* 0x3c */
+ "UPDATE BLOCK", /* 0x3d */
+ "READ LONG (10)", /* 0x3e */
+ "WRITE LONG (10)", /* 0x3f */
+ "CHANGE DEFINITION", /* 0x40 */
+ "WRITE SAME (10)", /* 0x41 */
+ "READ SUBCHANNEL", /* 0x42 */
+ "READ TOC/PMA/ATIP", /* 0x43 */
+ "REPORT DENSITY SUPPORT", /* 0x44 */
+ "PLAY AUDIO (10)", /* 0x45 */
+ "GET CONFIGURATION", /* 0x46 */
+ "PLAY AUDIO MSF", /* 0x47 */
+ "", /* 0x48 */
+ "", /* 0x49 */
+ "GET EVENT STATUS NOTIFICATION", /* 0x4a */
+ "PAUSE/RESUME", /* 0x4b */
+ "LOG SELECT", /* 0x4c */
+ "LOG SENSE", /* 0x4d */
+ "STOP PLAY/SCAN", /* 0x4e */
+ "", /* 0x4f */
+ "XDWRITE (10)", /* 0x50 */
+ "READ DISC INFORMATION", /* 0x51 */
+ "READ TRACK INFORMATION", /* 0x52 */
+ "RESERVE TRACK", /* 0x53 */
+ "SEND OPC INFORMATION", /* 0x54 */
+ "MODE SELECT (10)", /* 0x55 */
+ "RESERVE (10)", /* 0x56 */
+ "RELEASE (10)", /* 0x57 */
+ "REPAIR TRACK", /* 0x58 */
+ "", /* 0x59 */
+ "MODE SENSE (10)", /* 0x5a */
+ "CLOSE TRACK/SESSION", /* 0x5b */
+ "READ BUFFER CAPACITY", /* 0x5c */
+ "SEND CUE SHEET", /* 0x5d */
+ "PERSISTENT RESERVE IN", /* 0x5e */
+ "PERSISTENT RESERVE OUT", /* 0x5f */
+ "", /* 0x60 */
+ "", /* 0x61 */
+ "", /* 0x62 */
+ "", /* 0x63 */
+ "", /* 0x64 */
+ "", /* 0x65 */
+ "", /* 0x66 */
+ "", /* 0x67 */
+ "", /* 0x68 */
+ "", /* 0x69 */
+ "", /* 0x6a */
+ "", /* 0x6b */
+ "", /* 0x6c */
+ "", /* 0x6d */
+ "", /* 0x6e */
+ "", /* 0x6f */
+ "", /* 0x70 */
+ "", /* 0x71 */
+ "", /* 0x72 */
+ "", /* 0x73 */
+ "", /* 0x74 */
+ "", /* 0x75 */
+ "", /* 0x76 */
+ "", /* 0x77 */
+ "", /* 0x78 */
+ "", /* 0x79 */
+ "", /* 0x7a */
+ "", /* 0x7b */
+ "", /* 0x7c */
+ "", /* 0x7d */
+ "", /* 0x7e */
+ "", /* 0x7f */
+ "WRITE FILEMARKS (16)", /* 0x80 */
+ "READ REVERSE (16)", /* 0x81 */
+ "REGENERATE (16)", /* 0x82 */
+ "EXTENDED COPY", /* 0x83 */
+ "RECEIVE COPY RESULTS", /* 0x84 */
+ "ATA COMMAND PASS THROUGH (16)", /* 0x85 */
+ "ACCESS CONTROL IN", /* 0x86 */
+ "ACCESS CONTROL OUT", /* 0x87 */
+ "READ (16)", /* 0x88 */
+ "", /* 0x89 */
+ "WRITE(16)", /* 0x8a */
+ "", /* 0x8b */
+ "READ ATTRIBUTE", /* 0x8c */
+ "WRITE ATTRIBUTE", /* 0x8d */
+ "WRITE AND VERIFY (16)", /* 0x8e */
+ "VERIFY (16)", /* 0x8f */
+ "PRE-FETCH (16)", /* 0x90 */
+ "SYNCHRONIZE CACHE (16)", /* 0x91 */
+ "LOCK UNLOCK CACHE (16)", /* 0x92 */
+ "WRITE SAME (16)", /* 0x93 */
+ "", /* 0x94 */
+ "", /* 0x95 */
+ "", /* 0x96 */
+ "", /* 0x97 */
+ "", /* 0x98 */
+ "", /* 0x99 */
+ "", /* 0x9a */
+ "", /* 0x9b */
+ "", /* 0x9c */
+ "", /* 0x9d */
+ "SERVICE ACTION IN (16)", /* 0x9e */
+ "SERVICE ACTION OUT (16)", /* 0x9f */
+ "REPORT LUNS", /* 0xa0 */
+ "BLANK", /* 0xa1 */
+ "SEND EVENT", /* 0xa2 */
+ "SEND KEY", /* 0xa3 */
+ "REPORT KEY", /* 0xa4 */
+ "PLAY AUDIO (12)", /* 0xa5 */
+ "LOAD/UNLOAD MEDIUM", /* 0xa6 */
+ "SET READ AHEAD", /* 0xa7 */
+ "READ (12)", /* 0xa8 */
+ "SERVICE ACTION OUT (12)", /* 0xa9 */
+ "WRITE (12)", /* 0xaa */
+ "SERVICE ACTION IN (12)", /* 0xab */
+ "GET PERFORMANCE", /* 0xac */
+ "READ DVD STRUCTURE", /* 0xad */
+ "WRITE AND VERIFY (12)", /* 0xae */
+ "VERIFY (12)", /* 0xaf */
+ "SEARCH DATA HIGH (12)", /* 0xb0 */
+ "SEARCH DATA EQUAL (12)", /* 0xb1 */
+ "SEARCH DATA LOW (12)", /* 0xb2 */
+ "SET LIMITS (12)", /* 0xb3 */
+ "READ ELEMENT STATUS ATTACHED", /* 0xb4 */
+ "REQUEST VOLUME ELEMENT ADDRESS", /* 0xb5 */
+ "SET STREAMING", /* 0xb6 */
+ "READ DEFECT DATA (12)", /* 0xb7 */
+ "READ ELEMENT STATUS", /* 0xb8 */
+ "READ CD MSF", /* 0xb9 */
+ "SCAN", /* 0xba */
+ "SET CD SPEED", /* 0xbb */
+ "SPARE (IN)", /* 0xbc */
+ "MECHANISM STATUS", /* 0xbd */
+ "READ CD", /* 0xbe */
+ "SEND DVD STRUCTURE", /* 0xbf */
+ "", /* 0xc0 */
+ "", /* 0xc1 */
+ "", /* 0xc2 */
+ "", /* 0xc3 */
+ "", /* 0xc4 */
+ "", /* 0xc5 */
+ "", /* 0xc6 */
+ "", /* 0xc7 */
+ "", /* 0xc8 */
+ "", /* 0xc9 */
+ "", /* 0xca */
+ "", /* 0xcb */
+ "", /* 0xcc */
+ "", /* 0xcd */
+ "", /* 0xce */
+ "", /* 0xcf */
+ "", /* 0xd0 */
+ "", /* 0xd1 */
+ "", /* 0xd2 */
+ "", /* 0xd3 */
+ "", /* 0xd4 */
+ "", /* 0xd5 */
+ "", /* 0xd6 */
+ "", /* 0xd7 */
+ "", /* 0xd8 */
+ "", /* 0xd9 */
+ "", /* 0xda */
+ "", /* 0xdb */
+ "", /* 0xdc */
+ "", /* 0xdd */
+ "", /* 0xde */
+ "", /* 0xdf */
+ "", /* 0xe0 */
+ "", /* 0xe1 */
+ "", /* 0xe2 */
+ "", /* 0xe3 */
+ "", /* 0xe4 */
+ "", /* 0xe5 */
+ "", /* 0xe6 */
+ "", /* 0xe7 */
+ "", /* 0xe8 */
+ "", /* 0xe9 */
+ "", /* 0xea */
+ "", /* 0xeb */
+ "", /* 0xec */
+ "", /* 0xed */
+ "", /* 0xee */
+ "", /* 0xef */
+ "", /* 0xf0 */
+ "", /* 0xf1 */
+ "", /* 0xf2 */
+ "", /* 0xf3 */
+ "", /* 0xf4 */
+ "", /* 0xf5 */
+ "", /* 0xf6 */
+ "", /* 0xf7 */
+ "", /* 0xf8 */
+ "", /* 0xf9 */
+ "", /* 0xfa */
+ "", /* 0xfb */
+ "", /* 0xfc */
+ "", /* 0xfd */
+ "", /* 0xfe */
+ "" /* 0xff */
+};
+
+static const char * const g_apszSCSISenseNames[] =
+{
+ "NO SENSE",
+ "RECOVERED ERROR",
+ "NOT READY",
+ "MEDIUM ERROR",
+ "HARDWARE ERROR",
+ "ILLEGAL REQUEST",
+ "UNIT ATTENTION",
+ "DATA PROTECT",
+ "BLANK CHECK",
+ "VENDOR-SPECIFIC",
+ "COPY ABORTED",
+ "ABORTED COMMAND",
+ "(obsolete)",
+ "VOLUME OVERFLOW",
+ "MISCOMPARE",
+ "(reserved)"
+};
+
+/**
+ * SCSI Sense text
+ */
+static struct
+{
+ uint8_t uASC;
+ uint8_t uASCQ;
+ const char * const pszSenseText;
+} g_aSCSISenseText[]
+=
+{
+ { 0x67, 0x02, "A ADD LOGICAL UNIT FAILED" },
+ { 0x13, 0x00, "ADDRESS MARK NOT FOUND FOR DATA FIELD" },
+ { 0x12, 0x00, "ADDRESS MARK NOT FOUND FOR ID FIELD" },
+ { 0x27, 0x03, "ASSOCIATED WRITE PROTECT" },
+ { 0x67, 0x06, "ATTACHMENT OF LOGICAL UNIT FAILED" },
+ { 0x00, 0x11, "AUDIO PLAY OPERATION IN PROGRESS" },
+ { 0x00, 0x12, "AUDIO PLAY OPERATION PAUSED" },
+ { 0x00, 0x14, "AUDIO PLAY OPERATION STOPPED DUE TO ERROR" },
+ { 0x00, 0x13, "AUDIO PLAY OPERATION SUCCESSFULLY COMPLETED" },
+ { 0x66, 0x00, "AUTOMATIC DOCUMENT FEEDER COVER UP" },
+ { 0x66, 0x01, "AUTOMATIC DOCUMENT FEEDER LIFT UP" },
+ { 0x00, 0x04, "BEGINNING-OF-PARTITION/MEDIUM DETECTED" },
+ { 0x0C, 0x06, "BLOCK NOT COMPRESSIBLE" },
+ { 0x14, 0x04, "BLOCK SEQUENCE ERROR" },
+ { 0x29, 0x03, "BUS DEVICE RESET FUNCTION OCCURRED" },
+ { 0x11, 0x0E, "CANNOT DECOMPRESS USING DECLARED ALGORITHM" },
+ { 0x30, 0x06, "CANNOT FORMAT MEDIUM - INCOMPATIBLE MEDIUM" },
+ { 0x30, 0x02, "CANNOT READ MEDIUM - INCOMPATIBLE FORMAT" },
+ { 0x30, 0x01, "CANNOT READ MEDIUM - UNKNOWN FORMAT" },
+ { 0x30, 0x08, "CANNOT WRITE - APPLICATION CODE MISMATCH" },
+ { 0x30, 0x05, "CANNOT WRITE MEDIUM - INCOMPATIBLE FORMAT" },
+ { 0x30, 0x04, "CANNOT WRITE MEDIUM - UNKNOWN FORMAT" },
+ { 0x52, 0x00, "CARTRIDGE FAULT" },
+ { 0x73, 0x00, "CD CONTROL ERROR" },
+ { 0x3F, 0x02, "CHANGED OPERATING DEFINITION" },
+ { 0x11, 0x06, "CIRC UNRECOVERED ERROR" },
+ { 0x30, 0x03, "CLEANING CARTRIDGE INSTALLED" },
+ { 0x30, 0x07, "CLEANING FAILURE" },
+ { 0x00, 0x17, "CLEANING REQUESTED" },
+ { 0x4A, 0x00, "COMMAND PHASE ERROR" },
+ { 0x2C, 0x00, "COMMAND SEQUENCE ERROR" },
+ { 0x6E, 0x00, "COMMAND TO LOGICAL UNIT FAILED" },
+ { 0x2F, 0x00, "COMMANDS CLEARED BY ANOTHER INITIATOR" },
+ { 0x0C, 0x04, "COMPRESSION CHECK MISCOMPARE ERROR" },
+ { 0x67, 0x00, "CONFIGURATION FAILURE" },
+ { 0x67, 0x01, "CONFIGURATION OF INCAPABLE LOGICAL UNITS FAILED" },
+ { 0x2B, 0x00, "COPY CANNOT EXECUTE SINCE HOST CANNOT DISCONNECT" },
+ { 0x67, 0x07, "CREATION OF LOGICAL UNIT FAILED" },
+ { 0x2C, 0x04, "CURRENT PROGRAM AREA IS EMPTY" },
+ { 0x2C, 0x03, "CURRENT PROGRAM AREA IS NOT EMPTY" },
+ { 0x30, 0x09, "CURRENT SESSION NOT FIXATED FOR APPEND" },
+ { 0x0C, 0x05, "DATA EXPANSION OCCURRED DURING COMPRESSION" },
+ { 0x69, 0x00, "DATA LOSS ON LOGICAL UNIT" },
+ { 0x41, 0x00, "DATA PATH FAILURE (SHOULD USE 40 NN)" },
+ { 0x4B, 0x00, "DATA PHASE ERROR" },
+ { 0x11, 0x07, "DATA RE-SYNCHRONIZATION ERROR" },
+ { 0x16, 0x03, "DATA SYNC ERROR - DATA AUTO-REALLOCATED" },
+ { 0x16, 0x01, "DATA SYNC ERROR - DATA REWRITTEN" },
+ { 0x16, 0x04, "DATA SYNC ERROR - RECOMMEND REASSIGNMENT" },
+ { 0x16, 0x02, "DATA SYNC ERROR - RECOMMEND REWRITE" },
+ { 0x16, 0x00, "DATA SYNCHRONIZATION MARK ERROR" },
+ { 0x11, 0x0D, "DE-COMPRESSION CRC ERROR" },
+ { 0x71, 0x00, "DECOMPRESSION EXCEPTION LONG ALGORITHM ID" },
+ { 0x70, 0xFF, "DECOMPRESSION EXCEPTION SHORT ALGORITHM ID OF NN" },
+ { 0x19, 0x00, "DEFECT LIST ERROR" },
+ { 0x19, 0x03, "DEFECT LIST ERROR IN GROWN LIST" },
+ { 0x19, 0x02, "DEFECT LIST ERROR IN PRIMARY LIST" },
+ { 0x19, 0x01, "DEFECT LIST NOT AVAILABLE" },
+ { 0x1C, 0x00, "DEFECT LIST NOT FOUND" },
+ { 0x32, 0x01, "DEFECT LIST UPDATE FAILURE" },
+ { 0x29, 0x04, "DEVICE INTERNAL RESET" },
+ { 0x40, 0xFF, "DIAGNOSTIC FAILURE ON COMPONENT NN (80H-FFH)" },
+ { 0x66, 0x02, "DOCUMENT JAM IN AUTOMATIC DOCUMENT FEEDER" },
+ { 0x66, 0x03, "DOCUMENT MISS FEED AUTOMATIC IN DOCUMENT FEEDER" },
+ { 0x72, 0x04, "EMPTY OR PARTIALLY WRITTEN RESERVED TRACK" },
+ { 0x34, 0x00, "ENCLOSURE FAILURE" },
+ { 0x35, 0x00, "ENCLOSURE SERVICES FAILURE" },
+ { 0x35, 0x03, "ENCLOSURE SERVICES TRANSFER FAILURE" },
+ { 0x35, 0x04, "ENCLOSURE SERVICES TRANSFER REFUSED" },
+ { 0x35, 0x02, "ENCLOSURE SERVICES UNAVAILABLE" },
+ { 0x3B, 0x0F, "END OF MEDIUM REACHED" },
+ { 0x63, 0x00, "END OF USER AREA ENCOUNTERED ON THIS TRACK" },
+ { 0x00, 0x05, "END-OF-DATA DETECTED" },
+ { 0x14, 0x03, "END-OF-DATA NOT FOUND" },
+ { 0x00, 0x02, "END-OF-PARTITION/MEDIUM DETECTED" },
+ { 0x51, 0x00, "ERASE FAILURE" },
+ { 0x0A, 0x00, "ERROR LOG OVERFLOW" },
+ { 0x11, 0x10, "ERROR READING ISRC NUMBER" },
+ { 0x11, 0x0F, "ERROR READING UPC/EAN NUMBER" },
+ { 0x11, 0x02, "ERROR TOO LONG TO CORRECT" },
+ { 0x03, 0x02, "EXCESSIVE WRITE ERRORS" },
+ { 0x67, 0x04, "EXCHANGE OF LOGICAL UNIT FAILED" },
+ { 0x3B, 0x07, "FAILED TO SENSE BOTTOM-OF-FORM" },
+ { 0x3B, 0x06, "FAILED TO SENSE TOP-OF-FORM" },
+ { 0x5D, 0x00, "FAILURE PREDICTION THRESHOLD EXCEEDED" },
+ { 0x5D, 0xFF, "FAILURE PREDICTION THRESHOLD EXCEEDED (FALSE)" },
+ { 0x00, 0x01, "FILEMARK DETECTED" },
+ { 0x14, 0x02, "FILEMARK OR SETMARK NOT FOUND" },
+ { 0x09, 0x02, "FOCUS SERVO FAILURE" },
+ { 0x31, 0x01, "FORMAT COMMAND FAILED" },
+ { 0x58, 0x00, "GENERATION DOES NOT EXIST" },
+ { 0x1C, 0x02, "GROWN DEFECT LIST NOT FOUND" },
+ { 0x27, 0x01, "HARDWARE WRITE PROTECTED" },
+ { 0x09, 0x04, "HEAD SELECT FAULT" },
+ { 0x00, 0x06, "I/O PROCESS TERMINATED" },
+ { 0x10, 0x00, "ID CRC OR ECC ERROR" },
+ { 0x5E, 0x03, "IDLE CONDITION ACTIVATED BY COMMAND" },
+ { 0x5E, 0x01, "IDLE CONDITION ACTIVATED BY TIMER" },
+ { 0x22, 0x00, "ILLEGAL FUNCTION (USE 20 00, 24 00, OR 26 00)" },
+ { 0x64, 0x00, "ILLEGAL MODE FOR THIS TRACK" },
+ { 0x28, 0x01, "IMPORT OR EXPORT ELEMENT ACCESSED" },
+ { 0x30, 0x00, "INCOMPATIBLE MEDIUM INSTALLED" },
+ { 0x11, 0x08, "INCOMPLETE BLOCK READ" },
+ { 0x6A, 0x00, "INFORMATIONAL, REFER TO LOG" },
+ { 0x48, 0x00, "INITIATOR DETECTED ERROR MESSAGE RECEIVED" },
+ { 0x3F, 0x03, "INQUIRY DATA HAS CHANGED" },
+ { 0x44, 0x00, "INTERNAL TARGET FAILURE" },
+ { 0x3D, 0x00, "INVALID BITS IN IDENTIFY MESSAGE" },
+ { 0x2C, 0x02, "INVALID COMBINATION OF WINDOWS SPECIFIED" },
+ { 0x20, 0x00, "INVALID COMMAND OPERATION CODE" },
+ { 0x21, 0x01, "INVALID ELEMENT ADDRESS" },
+ { 0x24, 0x00, "INVALID FIELD IN CDB" },
+ { 0x26, 0x00, "INVALID FIELD IN PARAMETER LIST" },
+ { 0x49, 0x00, "INVALID MESSAGE ERROR" },
+ { 0x64, 0x01, "INVALID PACKET SIZE" },
+ { 0x26, 0x04, "INVALID RELEASE OF ACTIVE PERSISTENT RESERVATION" },
+ { 0x11, 0x05, "L-EC UNCORRECTABLE ERROR" },
+ { 0x60, 0x00, "LAMP FAILURE" },
+ { 0x5B, 0x02, "LOG COUNTER AT MAXIMUM" },
+ { 0x5B, 0x00, "LOG EXCEPTION" },
+ { 0x5B, 0x03, "LOG LIST CODES EXHAUSTED" },
+ { 0x2A, 0x02, "LOG PARAMETERS CHANGED" },
+ { 0x21, 0x00, "LOGICAL BLOCK ADDRESS OUT OF RANGE" },
+ { 0x08, 0x03, "LOGICAL UNIT COMMUNICATION CRC ERROR (ULTRA-DMA/32)" },
+ { 0x08, 0x00, "LOGICAL UNIT COMMUNICATION FAILURE" },
+ { 0x08, 0x02, "LOGICAL UNIT COMMUNICATION PARITY ERROR" },
+ { 0x08, 0x01, "LOGICAL UNIT COMMUNICATION TIME-OUT" },
+ { 0x05, 0x00, "LOGICAL UNIT DOES NOT RESPOND TO SELECTION" },
+ { 0x4C, 0x00, "LOGICAL UNIT FAILED SELF-CONFIGURATION" },
+ { 0x3E, 0x01, "LOGICAL UNIT FAILURE" },
+ { 0x3E, 0x00, "LOGICAL UNIT HAS NOT SELF-CONFIGURED YET" },
+ { 0x04, 0x01, "LOGICAL UNIT IS IN PROCESS OF BECOMING READY" },
+ { 0x68, 0x00, "LOGICAL UNIT NOT CONFIGURED" },
+ { 0x04, 0x00, "LOGICAL UNIT NOT READY, CAUSE NOT REPORTABLE" },
+ { 0x04, 0x04, "LOGICAL UNIT NOT READY, FORMAT IN PROGRESS" },
+ { 0x04, 0x02, "LOGICAL UNIT NOT READY, INITIALIZING CMD. REQUIRED" },
+ { 0x04, 0x08, "LOGICAL UNIT NOT READY, LONG WRITE IN PROGRESS" },
+ { 0x04, 0x03, "LOGICAL UNIT NOT READY, MANUAL INTERVENTION REQUIRED" },
+ { 0x04, 0x07, "LOGICAL UNIT NOT READY, OPERATION IN PROGRESS" },
+ { 0x04, 0x05, "LOGICAL UNIT NOT READY, REBUILD IN PROGRESS" },
+ { 0x04, 0x06, "LOGICAL UNIT NOT READY, RECALCULATION IN PROGRESS" },
+ { 0x25, 0x00, "LOGICAL UNIT NOT SUPPORTED" },
+ { 0x27, 0x02, "LOGICAL UNIT SOFTWARE WRITE PROTECTED" },
+ { 0x5E, 0x00, "LOW POWER CONDITION ON" },
+ { 0x15, 0x01, "MECHANICAL POSITIONING ERROR" },
+ { 0x53, 0x00, "MEDIA LOAD OR EJECT FAILED" },
+ { 0x3B, 0x0D, "MEDIUM DESTINATION ELEMENT FULL" },
+ { 0x31, 0x00, "MEDIUM FORMAT CORRUPTED" },
+ { 0x3B, 0x13, "MEDIUM MAGAZINE INSERTED" },
+ { 0x3B, 0x14, "MEDIUM MAGAZINE LOCKED" },
+ { 0x3B, 0x11, "MEDIUM MAGAZINE NOT ACCESSIBLE" },
+ { 0x3B, 0x12, "MEDIUM MAGAZINE REMOVED" },
+ { 0x3B, 0x15, "MEDIUM MAGAZINE UNLOCKED" },
+ { 0x3A, 0x00, "MEDIUM NOT PRESENT" },
+ { 0x3A, 0x01, "MEDIUM NOT PRESENT - TRAY CLOSED" },
+ { 0x3A, 0x02, "MEDIUM NOT PRESENT - TRAY OPEN" },
+ { 0x53, 0x02, "MEDIUM REMOVAL PREVENTED" },
+ { 0x3B, 0x0E, "MEDIUM SOURCE ELEMENT EMPTY" },
+ { 0x43, 0x00, "MESSAGE ERROR" },
+ { 0x3F, 0x01, "MICROCODE HAS BEEN CHANGED" },
+ { 0x1D, 0x00, "MISCOMPARE DURING VERIFY OPERATION" },
+ { 0x11, 0x0A, "MISCORRECTED ERROR" },
+ { 0x2A, 0x01, "MODE PARAMETERS CHANGED" },
+ { 0x67, 0x03, "MODIFICATION OF LOGICAL UNIT FAILED" },
+ { 0x69, 0x01, "MULTIPLE LOGICAL UNIT FAILURES" },
+ { 0x07, 0x00, "MULTIPLE PERIPHERAL DEVICES SELECTED" },
+ { 0x11, 0x03, "MULTIPLE READ ERRORS" },
+ { 0x00, 0x00, "NO ADDITIONAL SENSE INFORMATION" },
+ { 0x00, 0x15, "NO CURRENT AUDIO STATUS TO RETURN" },
+ { 0x32, 0x00, "NO DEFECT SPARE LOCATION AVAILABLE" },
+ { 0x11, 0x09, "NO GAP FOUND" },
+ { 0x01, 0x00, "NO INDEX/SECTOR SIGNAL" },
+ { 0x06, 0x00, "NO REFERENCE POSITION FOUND" },
+ { 0x02, 0x00, "NO SEEK COMPLETE" },
+ { 0x03, 0x01, "NO WRITE CURRENT" },
+ { 0x28, 0x00, "NOT READY TO READY CHANGE, MEDIUM MAY HAVE CHANGED" },
+ { 0x00, 0x16, "OPERATION IN PROGRESS" },
+ { 0x5A, 0x01, "OPERATOR MEDIUM REMOVAL REQUEST" },
+ { 0x5A, 0x00, "OPERATOR REQUEST OR STATE CHANGE INPUT" },
+ { 0x5A, 0x03, "OPERATOR SELECTED WRITE PERMIT" },
+ { 0x5A, 0x02, "OPERATOR SELECTED WRITE PROTECT" },
+ { 0x61, 0x02, "OUT OF FOCUS" },
+ { 0x4E, 0x00, "OVERLAPPED COMMANDS ATTEMPTED" },
+ { 0x2D, 0x00, "OVERWRITE ERROR ON UPDATE IN PLACE" },
+ { 0x63, 0x01, "PACKET DOES NOT FIT IN AVAILABLE SPACE" },
+ { 0x3B, 0x05, "PAPER JAM" },
+ { 0x1A, 0x00, "PARAMETER LIST LENGTH ERROR" },
+ { 0x26, 0x01, "PARAMETER NOT SUPPORTED" },
+ { 0x26, 0x02, "PARAMETER VALUE INVALID" },
+ { 0x2A, 0x00, "PARAMETERS CHANGED" },
+ { 0x69, 0x02, "PARITY/DATA MISMATCH" },
+ { 0x1F, 0x00, "PARTIAL DEFECT LIST TRANSFER" },
+ { 0x03, 0x00, "PERIPHERAL DEVICE WRITE FAULT" },
+ { 0x27, 0x05, "PERMANENT WRITE PROTECT" },
+ { 0x27, 0x04, "PERSISTENT WRITE PROTECT" },
+ { 0x50, 0x02, "POSITION ERROR RELATED TO TIMING" },
+ { 0x3B, 0x0C, "POSITION PAST BEGINNING OF MEDIUM" },
+ { 0x3B, 0x0B, "POSITION PAST END OF MEDIUM" },
+ { 0x15, 0x02, "POSITIONING ERROR DETECTED BY READ OF MEDIUM" },
+ { 0x73, 0x01, "POWER CALIBRATION AREA ALMOST FULL" },
+ { 0x73, 0x03, "POWER CALIBRATION AREA ERROR" },
+ { 0x73, 0x02, "POWER CALIBRATION AREA IS FULL" },
+ { 0x29, 0x01, "POWER ON OCCURRED" },
+ { 0x29, 0x00, "POWER ON, RESET, OR BUS DEVICE RESET OCCURRED" },
+ { 0x42, 0x00, "POWER-ON OR SELF-TEST FAILURE (SHOULD USE 40 NN)" },
+ { 0x1C, 0x01, "PRIMARY DEFECT LIST NOT FOUND" },
+ { 0x73, 0x05, "PROGRAM MEMORY AREA IS FULL" },
+ { 0x73, 0x04, "PROGRAM MEMORY AREA UPDATE FAILURE" },
+ { 0x40, 0x00, "RAM FAILURE (SHOULD USE 40 NN)" },
+ { 0x15, 0x00, "RANDOM POSITIONING ERROR" },
+ { 0x11, 0x11, "READ ERROR - LOSS OF STREAMING" },
+ { 0x3B, 0x0A, "READ PAST BEGINNING OF MEDIUM" },
+ { 0x3B, 0x09, "READ PAST END OF MEDIUM" },
+ { 0x11, 0x01, "READ RETRIES EXHAUSTED" },
+ { 0x6C, 0x00, "REBUILD FAILURE OCCURRED" },
+ { 0x6D, 0x00, "RECALCULATE FAILURE OCCURRED" },
+ { 0x14, 0x01, "RECORD NOT FOUND" },
+ { 0x14, 0x06, "RECORD NOT FOUND - DATA AUTO-REALLOCATED" },
+ { 0x14, 0x05, "RECORD NOT FOUND - RECOMMEND REASSIGNMENT" },
+ { 0x14, 0x00, "RECORDED ENTITY NOT FOUND" },
+ { 0x18, 0x02, "RECOVERED DATA - DATA AUTO-REALLOCATED" },
+ { 0x18, 0x05, "RECOVERED DATA - RECOMMEND REASSIGNMENT" },
+ { 0x18, 0x06, "RECOVERED DATA - RECOMMEND REWRITE" },
+ { 0x17, 0x05, "RECOVERED DATA USING PREVIOUS SECTOR ID" },
+ { 0x18, 0x03, "RECOVERED DATA WITH CIRC" },
+ { 0x18, 0x07, "RECOVERED DATA WITH ECC - DATA REWRITTEN" },
+ { 0x18, 0x01, "RECOVERED DATA WITH ERROR CORR. & RETRIES APPLIED" },
+ { 0x18, 0x00, "RECOVERED DATA WITH ERROR CORRECTION APPLIED" },
+ { 0x18, 0x04, "RECOVERED DATA WITH L-EC" },
+ { 0x17, 0x03, "RECOVERED DATA WITH NEGATIVE HEAD OFFSET" },
+ { 0x17, 0x00, "RECOVERED DATA WITH NO ERROR CORRECTION APPLIED" },
+ { 0x17, 0x02, "RECOVERED DATA WITH POSITIVE HEAD OFFSET" },
+ { 0x17, 0x01, "RECOVERED DATA WITH RETRIES" },
+ { 0x17, 0x04, "RECOVERED DATA WITH RETRIES AND/OR CIRC APPLIED" },
+ { 0x17, 0x06, "RECOVERED DATA WITHOUT ECC - DATA AUTO-REALLOCATED" },
+ { 0x17, 0x09, "RECOVERED DATA WITHOUT ECC - DATA REWRITTEN" },
+ { 0x17, 0x07, "RECOVERED DATA WITHOUT ECC - RECOMMEND REASSIGNMENT" },
+ { 0x17, 0x08, "RECOVERED DATA WITHOUT ECC - RECOMMEND REWRITE" },
+ { 0x1E, 0x00, "RECOVERED ID WITH ECC CORRECTION" },
+ { 0x6B, 0x01, "REDUNDANCY LEVEL GOT BETTER" },
+ { 0x6B, 0x02, "REDUNDANCY LEVEL GOT WORSE" },
+ { 0x67, 0x05, "REMOVE OF LOGICAL UNIT FAILED" },
+ { 0x3B, 0x08, "REPOSITION ERROR" },
+ { 0x2A, 0x03, "RESERVATIONS PREEMPTED" },
+ { 0x36, 0x00, "RIBBON, INK, OR TONER FAILURE" },
+ { 0x37, 0x00, "ROUNDED PARAMETER" },
+ { 0x5C, 0x00, "RPL STATUS CHANGE" },
+ { 0x39, 0x00, "SAVING PARAMETERS NOT SUPPORTED" },
+ { 0x62, 0x00, "SCAN HEAD POSITIONING ERROR" },
+ { 0x29, 0x02, "SCSI BUS RESET OCCURRED" },
+ { 0x47, 0x00, "SCSI PARITY ERROR" },
+ { 0x54, 0x00, "SCSI TO HOST SYSTEM INTERFACE FAILURE" },
+ { 0x45, 0x00, "SELECT OR RESELECT FAILURE" },
+ { 0x3B, 0x00, "SEQUENTIAL POSITIONING ERROR" },
+ { 0x72, 0x00, "SESSION FIXATION ERROR" },
+ { 0x72, 0x03, "SESSION FIXATION ERROR - INCOMPLETE TRACK IN SESSION" },
+ { 0x72, 0x01, "SESSION FIXATION ERROR WRITING LEAD-IN" },
+ { 0x72, 0x02, "SESSION FIXATION ERROR WRITING LEAD-OUT" },
+ { 0x00, 0x03, "SETMARK DETECTED" },
+ { 0x3B, 0x04, "SLEW FAILURE" },
+ { 0x09, 0x03, "SPINDLE SERVO FAILURE" },
+ { 0x5C, 0x02, "SPINDLES NOT SYNCHRONIZED" },
+ { 0x5C, 0x01, "SPINDLES SYNCHRONIZED" },
+ { 0x5E, 0x04, "STANDBY CONDITION ACTIVATED BY COMMAND" },
+ { 0x5E, 0x02, "STANDBY CONDITION ACTIVATED BY TIMER" },
+ { 0x6B, 0x00, "STATE CHANGE HAS OCCURRED" },
+ { 0x1B, 0x00, "SYNCHRONOUS DATA TRANSFER ERROR" },
+ { 0x55, 0x01, "SYSTEM BUFFER FULL" },
+ { 0x55, 0x00, "SYSTEM RESOURCE FAILURE" },
+ { 0x4D, 0xFF, "TAGGED OVERLAPPED COMMANDS (NN = QUEUE TAG)" },
+ { 0x33, 0x00, "TAPE LENGTH ERROR" },
+ { 0x3B, 0x03, "TAPE OR ELECTRONIC VERTICAL FORMS UNIT NOT READY" },
+ { 0x3B, 0x01, "TAPE POSITION ERROR AT BEGINNING-OF-MEDIUM" },
+ { 0x3B, 0x02, "TAPE POSITION ERROR AT END-OF-MEDIUM" },
+ { 0x3F, 0x00, "TARGET OPERATING CONDITIONS HAVE CHANGED" },
+ { 0x5B, 0x01, "THRESHOLD CONDITION MET" },
+ { 0x26, 0x03, "THRESHOLD PARAMETERS NOT SUPPORTED" },
+ { 0x3E, 0x02, "TIMEOUT ON LOGICAL UNIT" },
+ { 0x2C, 0x01, "TOO MANY WINDOWS SPECIFIED" },
+ { 0x09, 0x00, "TRACK FOLLOWING ERROR" },
+ { 0x09, 0x01, "TRACKING SERVO FAILURE" },
+ { 0x61, 0x01, "UNABLE TO ACQUIRE VIDEO" },
+ { 0x57, 0x00, "UNABLE TO RECOVER TABLE-OF-CONTENTS" },
+ { 0x53, 0x01, "UNLOAD TAPE FAILURE" },
+ { 0x11, 0x00, "UNRECOVERED READ ERROR" },
+ { 0x11, 0x04, "UNRECOVERED READ ERROR - AUTO REALLOCATE FAILED" },
+ { 0x11, 0x0B, "UNRECOVERED READ ERROR - RECOMMEND REASSIGNMENT" },
+ { 0x11, 0x0C, "UNRECOVERED READ ERROR - RECOMMEND REWRITE THE DATA" },
+ { 0x46, 0x00, "UNSUCCESSFUL SOFT RESET" },
+ { 0x35, 0x01, "UNSUPPORTED ENCLOSURE FUNCTION" },
+ { 0x59, 0x00, "UPDATED BLOCK READ" },
+ { 0x61, 0x00, "VIDEO ACQUISITION ERROR" },
+ { 0x65, 0x00, "VOLTAGE FAULT" },
+ { 0x0B, 0x00, "WARNING" },
+ { 0x0B, 0x02, "WARNING - ENCLOSURE DEGRADED" },
+ { 0x0B, 0x01, "WARNING - SPECIFIED TEMPERATURE EXCEEDED" },
+ { 0x50, 0x00, "WRITE APPEND ERROR" },
+ { 0x50, 0x01, "WRITE APPEND POSITION ERROR" },
+ { 0x0C, 0x00, "WRITE ERROR" },
+ { 0x0C, 0x02, "WRITE ERROR - AUTO REALLOCATION FAILED" },
+ { 0x0C, 0x09, "WRITE ERROR - LOSS OF STREAMING" },
+ { 0x0C, 0x0A, "WRITE ERROR - PADDING BLOCKS ADDED" },
+ { 0x0C, 0x03, "WRITE ERROR - RECOMMEND REASSIGNMENT" },
+ { 0x0C, 0x01, "WRITE ERROR - RECOVERED WITH AUTO REALLOCATION" },
+ { 0x0C, 0x08, "WRITE ERROR - RECOVERY FAILED" },
+ { 0x0C, 0x07, "WRITE ERROR - RECOVERY NEEDED" },
+ { 0x27, 0x00, "WRITE PROTECTED" },
+};
+
+/**
+ * Return the plain text of an ATA command for debugging purposes.
+ * Don't allocate the string as we use this function in Log() statements.
+ */
+const char * ATACmdText(uint8_t uCmd)
+{
+ AssertCompile(RT_ELEMENTS(g_apszATACmdNames) == (1 << (8*sizeof(uCmd))));
+ return g_apszATACmdNames[uCmd];
+}
+
+/**
+ * Return the plain text of a SCSI command for debugging purposes.
+ * Don't allocate the string as we use this function in Log() statements.
+ */
+const char * SCSICmdText(uint8_t uCmd)
+{
+ AssertCompile(RT_ELEMENTS(g_apszSCSICmdNames) == (1 << (8*sizeof(uCmd))));
+ return g_apszSCSICmdNames[uCmd];
+}
+
+/**
+ * Return the plain text of a SCSI sense code.
+ * Don't allocate the string as we use this function in Log() statements.
+ */
+const char * SCSISenseText(uint8_t uSense)
+{
+ if (uSense < RT_ELEMENTS(g_apszSCSISenseNames))
+ return g_apszSCSISenseNames[uSense];
+
+ return "(SCSI sense out of range)";
+}
+
+/**
+ * Return the plain text of an extended SCSI sense key.
+ * Don't allocate the string as we use this function in Log() statements.
+ */
+const char * SCSISenseExtText(uint8_t uASC, uint8_t uASCQ)
+{
+ unsigned iIdx;
+
+ /* Linear search. Doesn't hurt as we don't call this function very frequently */
+ for (iIdx = 0; iIdx < RT_ELEMENTS(g_aSCSISenseText); iIdx++)
+ {
+ if ( g_aSCSISenseText[iIdx].uASC == uASC
+ && ( g_aSCSISenseText[iIdx].uASCQ == uASCQ
+ || g_aSCSISenseText[iIdx].uASCQ == 0xff))
+ return g_aSCSISenseText[iIdx].pszSenseText;
+ }
+ return "(Unknown extended sense code)";
+}
diff --git a/src/VBox/Devices/Storage/DevATA.cpp b/src/VBox/Devices/Storage/DevATA.cpp
new file mode 100644
index 000000000..6ae66c60c
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevATA.cpp
@@ -0,0 +1,6532 @@
+/* $Id: DevATA.cpp 16114 2009-01-21 09:13:08Z vboxsync $ */
+/** @file
+ * VBox storage devices: ATA/ATAPI controller device (disk and cdrom).
+ */
+
+/*
+ * Copyright (C) 2006-2008 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/** Temporary instrumentation for tracking down potential virtual disk
+ * write performance issues. */
+#undef VBOX_INSTRUMENT_DMA_WRITES
+
+/**
+ * The SSM saved state versions.
+ */
+#define ATA_SAVED_STATE_VERSION 18
+#define ATA_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE 16
+#define ATA_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS 17
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_IDE
+#include <VBox/pdmdev.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#ifdef IN_RING3
+# include <iprt/uuid.h>
+# include <iprt/semaphore.h>
+# include <iprt/thread.h>
+# include <iprt/time.h>
+# include <iprt/alloc.h>
+#endif /* IN_RING3 */
+#include <iprt/critsect.h>
+#include <iprt/asm.h>
+#include <VBox/stam.h>
+#include <VBox/mm.h>
+#include <VBox/pgm.h>
+
+#include <VBox/scsi.h>
+
+#include "PIIX3ATABmDma.h"
+#include "ide.h"
+#include "../Builtins.h"
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/**
+ * Maximum number of sectors to transfer in a READ/WRITE MULTIPLE request.
+ * Set to 1 to disable multi-sector read support. According to the ATA
+ * specification this must be a power of 2 and it must fit in an 8 bit
+ * value. Thus the only valid values are 1, 2, 4, 8, 16, 32, 64 and 128.
+ */
+#define ATA_MAX_MULT_SECTORS 128
+
+/**
+ * Fastest PIO mode supported by the drive.
+ */
+#define ATA_PIO_MODE_MAX 4
+/**
+ * Fastest MDMA mode supported by the drive.
+ */
+#define ATA_MDMA_MODE_MAX 2
+/**
+ * Fastest UDMA mode supported by the drive.
+ */
+#define ATA_UDMA_MODE_MAX 6
+
+/** ATAPI sense info size. */
+#define ATAPI_SENSE_SIZE 64
+
+/** The maximum number of release log entries per device. */
+#define MAX_LOG_REL_ERRORS 1024
+
+/* MediaEventStatus */
+#define ATA_EVENT_STATUS_UNCHANGED 0 /**< medium event status not changed */
+#define ATA_EVENT_STATUS_MEDIA_NEW 1 /**< new medium inserted */
+#define ATA_EVENT_STATUS_MEDIA_REMOVED 2 /**< medium removed */
+#define ATA_EVENT_STATUS_MEDIA_CHANGED 3 /**< medium was removed + new medium was inserted */
+
+/**
+ * Length of the configurable VPD data (without termination)
+ */
+#define ATA_SERIAL_NUMBER_LENGTH 20
+#define ATA_FIRMWARE_REVISION_LENGTH 8
+#define ATA_MODEL_NUMBER_LENGTH 40
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+typedef struct ATADevState {
+ /** Flag indicating whether the current command uses LBA48 mode. */
+ bool fLBA48;
+ /** Flag indicating whether this drive implements the ATAPI command set. */
+ bool fATAPI;
+ /** Set if this interface has asserted the IRQ. */
+ bool fIrqPending;
+ /** Currently configured number of sectors in a multi-sector transfer. */
+ uint8_t cMultSectors;
+ /** PCHS disk geometry. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** Total number of sectors on this disk. */
+ uint64_t cTotalSectors;
+ /** Number of sectors to transfer per IRQ. */
+ uint32_t cSectorsPerIRQ;
+
+ /** ATA/ATAPI register 1: feature (write-only). */
+ uint8_t uATARegFeature;
+ /** ATA/ATAPI register 1: feature, high order byte. */
+ uint8_t uATARegFeatureHOB;
+ /** ATA/ATAPI register 1: error (read-only). */
+ uint8_t uATARegError;
+ /** ATA/ATAPI register 2: sector count (read/write). */
+ uint8_t uATARegNSector;
+ /** ATA/ATAPI register 2: sector count, high order byte. */
+ uint8_t uATARegNSectorHOB;
+ /** ATA/ATAPI register 3: sector (read/write). */
+ uint8_t uATARegSector;
+ /** ATA/ATAPI register 3: sector, high order byte. */
+ uint8_t uATARegSectorHOB;
+ /** ATA/ATAPI register 4: cylinder low (read/write). */
+ uint8_t uATARegLCyl;
+ /** ATA/ATAPI register 4: cylinder low, high order byte. */
+ uint8_t uATARegLCylHOB;
+ /** ATA/ATAPI register 5: cylinder high (read/write). */
+ uint8_t uATARegHCyl;
+ /** ATA/ATAPI register 5: cylinder high, high order byte. */
+ uint8_t uATARegHCylHOB;
+ /** ATA/ATAPI register 6: select drive/head (read/write). */
+ uint8_t uATARegSelect;
+ /** ATA/ATAPI register 7: status (read-only). */
+ uint8_t uATARegStatus;
+ /** ATA/ATAPI register 7: command (write-only). */
+ uint8_t uATARegCommand;
+ /** ATA/ATAPI drive control register (write-only). */
+ uint8_t uATARegDevCtl;
+
+ /** Currently active transfer mode (MDMA/UDMA) and speed. */
+ uint8_t uATATransferMode;
+ /** Current transfer direction. */
+ uint8_t uTxDir;
+ /** Index of callback for begin transfer. */
+ uint8_t iBeginTransfer;
+ /** Index of callback for source/sink of data. */
+ uint8_t iSourceSink;
+ /** Flag indicating whether the current command transfers data in DMA mode. */
+ bool fDMA;
+ /** Set to indicate that ATAPI transfer semantics must be used. */
+ bool fATAPITransfer;
+
+ /** Total ATA/ATAPI transfer size, shared PIO/DMA. */
+ uint32_t cbTotalTransfer;
+ /** Elementary ATA/ATAPI transfer size, shared PIO/DMA. */
+ uint32_t cbElementaryTransfer;
+ /** Current read/write buffer position, shared PIO/DMA. */
+ uint32_t iIOBufferCur;
+ /** First element beyond end of valid buffer content, shared PIO/DMA. */
+ uint32_t iIOBufferEnd;
+
+ /** ATA/ATAPI current PIO read/write transfer position. Not shared with DMA for safety reasons. */
+ uint32_t iIOBufferPIODataStart;
+ /** ATA/ATAPI current PIO read/write transfer end. Not shared with DMA for safety reasons. */
+ uint32_t iIOBufferPIODataEnd;
+
+ /** ATAPI current LBA position. */
+ uint32_t iATAPILBA;
+ /** ATAPI current sector size. */
+ uint32_t cbATAPISector;
+ /** ATAPI current command. */
+ uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
+ /** ATAPI sense data. */
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ /** HACK: Countdown till we report a newly unmounted drive as mounted. */
+ uint8_t cNotifiedMediaChange;
+ /** The same for GET_EVENT_STATUS for mechanism */
+ volatile uint32_t MediaEventStatus;
+
+ uint32_t Alignment0;
+
+ /** The status LED state for this drive. */
+ PDMLED Led;
+
+ /** Size of I/O buffer. */
+ uint32_t cbIOBuffer;
+ /** Pointer to the I/O buffer. */
+ R3PTRTYPE(uint8_t *) pbIOBufferR3;
+ /** Pointer to the I/O buffer. */
+ R0PTRTYPE(uint8_t *) pbIOBufferR0;
+ /** Pointer to the I/O buffer. */
+ RCPTRTYPE(uint8_t *) pbIOBufferRC;
+
+ RTRCPTR Aligmnent1; /**< Align the statistics at an 8-byte boundrary. */
+
+ /*
+ * No data that is part of the saved state after this point!!!!!
+ */
+
+ /* Release statistics: number of ATA DMA commands. */
+ STAMCOUNTER StatATADMA;
+ /* Release statistics: number of ATA PIO commands. */
+ STAMCOUNTER StatATAPIO;
+ /* Release statistics: number of ATAPI PIO commands. */
+ STAMCOUNTER StatATAPIDMA;
+ /* Release statistics: number of ATAPI PIO commands. */
+ STAMCOUNTER StatATAPIPIO;
+#ifdef VBOX_INSTRUMENT_DMA_WRITES
+ /* Release statistics: number of DMA sector writes and the time spent. */
+ STAMPROFILEADV StatInstrVDWrites;
+#endif
+
+ /** Statistics: number of read operations and the time spent reading. */
+ STAMPROFILEADV StatReads;
+ /** Statistics: number of bytes read. */
+ STAMCOUNTER StatBytesRead;
+ /** Statistics: number of write operations and the time spent writing. */
+ STAMPROFILEADV StatWrites;
+ /** Statistics: number of bytes written. */
+ STAMCOUNTER StatBytesWritten;
+ /** Statistics: number of flush operations and the time spend flushing. */
+ STAMPROFILE StatFlushes;
+
+ /** Enable passing through commands directly to the ATAPI drive. */
+ bool fATAPIPassthrough;
+ /** Number of errors we've reported to the release log.
+ * This is to prevent flooding caused by something going horribly wrong.
+ * this value against MAX_LOG_REL_ERRORS in places likely to cause floods
+ * like the ones we currently seeing on the linux smoke tests (2006-11-10). */
+ uint32_t cErrors;
+ /** Timestamp of last started command. 0 if no command pending. */
+ uint64_t u64CmdTS;
+
+ /** Pointer to the attached driver's base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Pointer to the attached driver's block interface. */
+ R3PTRTYPE(PPDMIBLOCK) pDrvBlock;
+ /** Pointer to the attached driver's block bios interface. */
+ R3PTRTYPE(PPDMIBLOCKBIOS) pDrvBlockBios;
+ /** Pointer to the attached driver's mount interface.
+ * This is NULL if the driver isn't a removable unit. */
+ R3PTRTYPE(PPDMIMOUNT) pDrvMount;
+ /** The base interface. */
+ PDMIBASE IBase;
+ /** The block port interface. */
+ PDMIBLOCKPORT IPort;
+ /** The mount notify interface. */
+ PDMIMOUNTNOTIFY IMountNotify;
+ /** The LUN #. */
+ RTUINT iLUN;
+#if HC_ARCH_BITS == 64
+ RTUINT Alignment2; /**< Align pDevInsR3 correctly. */
+#endif
+ /** Pointer to device instance. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to controller instance. */
+ R3PTRTYPE(struct ATACONTROLLER *) pControllerR3;
+ /** Pointer to device instance. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to controller instance. */
+ R0PTRTYPE(struct ATACONTROLLER *) pControllerR0;
+ /** Pointer to device instance. */
+ PPDMDEVINSRC pDevInsRC;
+ /** Pointer to controller instance. */
+ RCPTRTYPE(struct ATACONTROLLER *) pControllerRC;
+
+ /** The serial numnber to use for IDENTIFY DEVICE commands. */
+ char szSerialNumber[ATA_SERIAL_NUMBER_LENGTH+1];
+ /** The firmware revision to use for IDENTIFY DEVICE commands. */
+ char szFirmwareRevision[ATA_FIRMWARE_REVISION_LENGTH+1];
+ /** The model number to use for IDENTIFY DEVICE commands. */
+ char szModelNumber[ATA_MODEL_NUMBER_LENGTH+1];
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment3[2];
+#endif
+} ATADevState;
+
+
+typedef struct ATATransferRequest
+{
+ uint8_t iIf;
+ uint8_t iBeginTransfer;
+ uint8_t iSourceSink;
+ uint32_t cbTotalTransfer;
+ uint8_t uTxDir;
+} ATATransferRequest;
+
+
+typedef struct ATAAbortRequest
+{
+ uint8_t iIf;
+ bool fResetDrive;
+} ATAAbortRequest;
+
+
+typedef enum
+{
+ /** Begin a new transfer. */
+ ATA_AIO_NEW = 0,
+ /** Continue a DMA transfer. */
+ ATA_AIO_DMA,
+ /** Continue a PIO transfer. */
+ ATA_AIO_PIO,
+ /** Reset the drives on current controller, stop all transfer activity. */
+ ATA_AIO_RESET_ASSERTED,
+ /** Reset the drives on current controller, resume operation. */
+ ATA_AIO_RESET_CLEARED,
+ /** Abort the current transfer of a particular drive. */
+ ATA_AIO_ABORT
+} ATAAIO;
+
+
+typedef struct ATARequest
+{
+ ATAAIO ReqType;
+ union
+ {
+ ATATransferRequest t;
+ ATAAbortRequest a;
+ } u;
+} ATARequest;
+
+
+typedef struct ATACONTROLLER
+{
+ /** The base of the first I/O Port range. */
+ RTIOPORT IOPortBase1;
+ /** The base of the second I/O Port range. (0 if none) */
+ RTIOPORT IOPortBase2;
+ /** The assigned IRQ. */
+ RTUINT irq;
+ /** Access critical section */
+ PDMCRITSECT lock;
+
+ /** Selected drive. */
+ uint8_t iSelectedIf;
+ /** The interface on which to handle async I/O. */
+ uint8_t iAIOIf;
+ /** The state of the async I/O thread. */
+ uint8_t uAsyncIOState;
+ /** Flag indicating whether the next transfer is part of the current command. */
+ bool fChainedTransfer;
+ /** Set when the reset processing is currently active on this controller. */
+ bool fReset;
+ /** Flag whether the current transfer needs to be redone. */
+ bool fRedo;
+ /** Flag whether the redo suspend has been finished. */
+ bool fRedoIdle;
+ /** Flag whether the DMA operation to be redone is the final transfer. */
+ bool fRedoDMALastDesc;
+ /** The BusMaster DMA state. */
+ BMDMAState BmDma;
+ /** Pointer to first DMA descriptor. */
+ RTGCPHYS32 pFirstDMADesc;
+ /** Pointer to last DMA descriptor. */
+ RTGCPHYS32 pLastDMADesc;
+ /** Pointer to current DMA buffer (for redo operations). */
+ RTGCPHYS32 pRedoDMABuffer;
+ /** Size of current DMA buffer (for redo operations). */
+ uint32_t cbRedoDMABuffer;
+
+ /** The ATA/ATAPI interfaces of this controller. */
+ ATADevState aIfs[2];
+
+ /** Pointer to device instance. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to device instance. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to device instance. */
+ PPDMDEVINSRC pDevInsRC;
+
+ /** Set when the destroying the device instance and the thread must exit. */
+ uint32_t volatile fShutdown;
+ /** The async I/O thread handle. NIL_RTTHREAD if no thread. */
+ RTTHREAD AsyncIOThread;
+ /** The event semaphore the thread is waiting on for requests. */
+ RTSEMEVENT AsyncIOSem;
+ /** The request queue for the AIO thread. One element is always unused. */
+ ATARequest aAsyncIORequests[4];
+ /** The position at which to insert a new request for the AIO thread. */
+ uint8_t AsyncIOReqHead;
+ /** The position at which to get a new request for the AIO thread. */
+ uint8_t AsyncIOReqTail;
+ uint8_t Alignment3[2]; /**< Explicit padding of the 2 byte gap. */
+ /** Magic delay before triggering interrupts in DMA mode. */
+ uint32_t DelayIRQMillies;
+ /** The mutex protecting the request queue. */
+ RTSEMMUTEX AsyncIORequestMutex;
+ /** The event semaphore the thread is waiting on during suspended I/O. */
+ RTSEMEVENT SuspendIOSem;
+#if 0 /*HC_ARCH_BITS == 32*/
+ uint32_t Alignment0;
+#endif
+
+ /** Timestamp we started the reset. */
+ uint64_t u64ResetTime;
+
+ /* Statistics */
+ STAMCOUNTER StatAsyncOps;
+ uint64_t StatAsyncMinWait;
+ uint64_t StatAsyncMaxWait;
+ STAMCOUNTER StatAsyncTimeUS;
+ STAMPROFILEADV StatAsyncTime;
+ STAMPROFILE StatLockWait;
+} ATACONTROLLER, *PATACONTROLLER;
+
+typedef struct PCIATAState {
+ PCIDEVICE dev;
+ /** The controllers. */
+ ATACONTROLLER aCts[2];
+ /** Pointer to device instance. */
+ PPDMDEVINSR3 pDevIns;
+ /** Status Port - Base interface. */
+ PDMIBASE IBase;
+ /** Status Port - Leds interface. */
+ PDMILEDPORTS ILeds;
+ /** Partner of ILeds. */
+ R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector;
+ /** Flag whether GC is enabled. */
+ bool fGCEnabled;
+ /** Flag whether R0 is enabled. */
+ bool fR0Enabled;
+ /** Flag indicating whether PIIX4 or PIIX3 is being emulated. */
+ bool fPIIX4;
+ bool Alignment0[HC_ARCH_BITS == 64 ? 5 : 1]; /**< Align the struct size. */
+} PCIATAState;
+
+#define PDMIBASE_2_PCIATASTATE(pInterface) ( (PCIATAState *)((uintptr_t)(pInterface) - RT_OFFSETOF(PCIATAState, IBase)) )
+#define PDMILEDPORTS_2_PCIATASTATE(pInterface) ( (PCIATAState *)((uintptr_t)(pInterface) - RT_OFFSETOF(PCIATAState, ILeds)) )
+#define PDMIBLOCKPORT_2_ATASTATE(pInterface) ( (ATADevState *)((uintptr_t)(pInterface) - RT_OFFSETOF(ATADevState, IPort)) )
+#define PDMIMOUNT_2_ATASTATE(pInterface) ( (ATADevState *)((uintptr_t)(pInterface) - RT_OFFSETOF(ATADevState, IMount)) )
+#define PDMIMOUNTNOTIFY_2_ATASTATE(pInterface) ( (ATADevState *)((uintptr_t)(pInterface) - RT_OFFSETOF(ATADevState, IMountNotify)) )
+#define PCIDEV_2_PCIATASTATE(pPciDev) ( (PCIATAState *)(pPciDev) )
+
+#define ATACONTROLLER_IDX(pController) ( (pController) - PDMINS_2_DATA(CONTROLLER_2_DEVINS(pController), PCIATAState *)->aCts )
+
+#define ATADEVSTATE_2_CONTROLLER(pIf) ( (pIf)->CTX_SUFF(pController) )
+#define ATADEVSTATE_2_DEVINS(pIf) ( (pIf)->CTX_SUFF(pDevIns) )
+#define CONTROLLER_2_DEVINS(pController) ( (pController)->CTX_SUFF(pDevIns) )
+#define PDMIBASE_2_ATASTATE(pInterface) ( (ATADevState *)((uintptr_t)(pInterface) - RT_OFFSETOF(ATADevState, IBase)) )
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+/*******************************************************************************
+ * Internal Functions *
+ ******************************************************************************/
+__BEGIN_DECLS
+PDMBOTHCBDECL(int) ataIOPortWrite1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortRead1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *u32, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortWriteStr1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortReadStr1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortWrite2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortRead2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *u32, unsigned cb);
+PDMBOTHCBDECL(int) ataBMDMAIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) ataBMDMAIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb);
+__END_DECLS
+
+
+
+DECLINLINE(void) ataSetStatusValue(ATADevState *s, uint8_t stat)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+ /* Freeze status register contents while processing RESET. */
+ if (!pCtl->fReset)
+ {
+ s->uATARegStatus = stat;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+ }
+}
+
+
+DECLINLINE(void) ataSetStatus(ATADevState *s, uint8_t stat)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+ /* Freeze status register contents while processing RESET. */
+ if (!pCtl->fReset)
+ {
+ s->uATARegStatus |= stat;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+ }
+}
+
+
+DECLINLINE(void) ataUnsetStatus(ATADevState *s, uint8_t stat)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+ /* Freeze status register contents while processing RESET. */
+ if (!pCtl->fReset)
+ {
+ s->uATARegStatus &= ~stat;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+ }
+}
+
+#ifdef IN_RING3
+
+typedef void (*PBeginTransferFunc)(ATADevState *);
+typedef bool (*PSourceSinkFunc)(ATADevState *);
+
+static void ataReadWriteSectorsBT(ATADevState *);
+static void ataPacketBT(ATADevState *);
+static void atapiCmdBT(ATADevState *);
+static void atapiPassthroughCmdBT(ATADevState *);
+
+static bool ataIdentifySS(ATADevState *);
+static bool ataFlushSS(ATADevState *);
+static bool ataReadSectorsSS(ATADevState *);
+static bool ataWriteSectorsSS(ATADevState *);
+static bool ataExecuteDeviceDiagnosticSS(ATADevState *);
+static bool ataPacketSS(ATADevState *);
+static bool atapiGetConfigurationSS(ATADevState *);
+static bool atapiGetEventStatusNotificationSS(ATADevState *);
+static bool atapiIdentifySS(ATADevState *);
+static bool atapiInquirySS(ATADevState *);
+static bool atapiMechanismStatusSS(ATADevState *);
+static bool atapiModeSenseErrorRecoverySS(ATADevState *);
+static bool atapiModeSenseCDStatusSS(ATADevState *);
+static bool atapiReadSS(ATADevState *);
+static bool atapiReadCapacitySS(ATADevState *);
+static bool atapiReadDiscInformationSS(ATADevState *);
+static bool atapiReadTOCNormalSS(ATADevState *);
+static bool atapiReadTOCMultiSS(ATADevState *);
+static bool atapiReadTOCRawSS(ATADevState *);
+static bool atapiReadTrackInformationSS(ATADevState *);
+static bool atapiRequestSenseSS(ATADevState *);
+static bool atapiPassthroughSS(ATADevState *);
+
+/**
+ * Begin of transfer function indexes for g_apfnBeginTransFuncs.
+ */
+typedef enum ATAFNBT
+{
+ ATAFN_BT_NULL = 0,
+ ATAFN_BT_READ_WRITE_SECTORS,
+ ATAFN_BT_PACKET,
+ ATAFN_BT_ATAPI_CMD,
+ ATAFN_BT_ATAPI_PASSTHROUGH_CMD,
+ ATAFN_BT_MAX
+} ATAFNBT;
+
+/**
+ * Array of end transfer functions, the index is ATAFNET.
+ * Make sure ATAFNET and this array match!
+ */
+static const PBeginTransferFunc g_apfnBeginTransFuncs[ATAFN_BT_MAX] =
+{
+ NULL,
+ ataReadWriteSectorsBT,
+ ataPacketBT,
+ atapiCmdBT,
+ atapiPassthroughCmdBT,
+};
+
+/**
+ * Source/sink function indexes for g_apfnSourceSinkFuncs.
+ */
+typedef enum ATAFNSS
+{
+ ATAFN_SS_NULL = 0,
+ ATAFN_SS_IDENTIFY,
+ ATAFN_SS_FLUSH,
+ ATAFN_SS_READ_SECTORS,
+ ATAFN_SS_WRITE_SECTORS,
+ ATAFN_SS_EXECUTE_DEVICE_DIAGNOSTIC,
+ ATAFN_SS_PACKET,
+ ATAFN_SS_ATAPI_GET_CONFIGURATION,
+ ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION,
+ ATAFN_SS_ATAPI_IDENTIFY,
+ ATAFN_SS_ATAPI_INQUIRY,
+ ATAFN_SS_ATAPI_MECHANISM_STATUS,
+ ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY,
+ ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS,
+ ATAFN_SS_ATAPI_READ,
+ ATAFN_SS_ATAPI_READ_CAPACITY,
+ ATAFN_SS_ATAPI_READ_DISC_INFORMATION,
+ ATAFN_SS_ATAPI_READ_TOC_NORMAL,
+ ATAFN_SS_ATAPI_READ_TOC_MULTI,
+ ATAFN_SS_ATAPI_READ_TOC_RAW,
+ ATAFN_SS_ATAPI_READ_TRACK_INFORMATION,
+ ATAFN_SS_ATAPI_REQUEST_SENSE,
+ ATAFN_SS_ATAPI_PASSTHROUGH,
+ ATAFN_SS_MAX
+} ATAFNSS;
+
+/**
+ * Array of source/sink functions, the index is ATAFNSS.
+ * Make sure ATAFNSS and this array match!
+ */
+static const PSourceSinkFunc g_apfnSourceSinkFuncs[ATAFN_SS_MAX] =
+{
+ NULL,
+ ataIdentifySS,
+ ataFlushSS,
+ ataReadSectorsSS,
+ ataWriteSectorsSS,
+ ataExecuteDeviceDiagnosticSS,
+ ataPacketSS,
+ atapiGetConfigurationSS,
+ atapiGetEventStatusNotificationSS,
+ atapiIdentifySS,
+ atapiInquirySS,
+ atapiMechanismStatusSS,
+ atapiModeSenseErrorRecoverySS,
+ atapiModeSenseCDStatusSS,
+ atapiReadSS,
+ atapiReadCapacitySS,
+ atapiReadDiscInformationSS,
+ atapiReadTOCNormalSS,
+ atapiReadTOCMultiSS,
+ atapiReadTOCRawSS,
+ atapiReadTrackInformationSS,
+ atapiRequestSenseSS,
+ atapiPassthroughSS
+};
+
+
+static const ATARequest ataDMARequest = { ATA_AIO_DMA, };
+static const ATARequest ataPIORequest = { ATA_AIO_PIO, };
+static const ATARequest ataResetARequest = { ATA_AIO_RESET_ASSERTED, };
+static const ATARequest ataResetCRequest = { ATA_AIO_RESET_CLEARED, };
+
+
+static void ataAsyncIOClearRequests(PATACONTROLLER pCtl)
+{
+ int rc;
+
+ rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ pCtl->AsyncIOReqHead = 0;
+ pCtl->AsyncIOReqTail = 0;
+ rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+ AssertRC(rc);
+}
+
+
+static void ataAsyncIOPutRequest(PATACONTROLLER pCtl, const ATARequest *pReq)
+{
+ int rc;
+
+ rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ Assert((pCtl->AsyncIOReqHead + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests) != pCtl->AsyncIOReqTail);
+ memcpy(&pCtl->aAsyncIORequests[pCtl->AsyncIOReqHead], pReq, sizeof(*pReq));
+ pCtl->AsyncIOReqHead++;
+ pCtl->AsyncIOReqHead %= RT_ELEMENTS(pCtl->aAsyncIORequests);
+ rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+ AssertRC(rc);
+ LogBird(("ata: %x: signalling\n", pCtl->IOPortBase1));
+ rc = PDMR3CritSectScheduleExitEvent(&pCtl->lock, pCtl->AsyncIOSem);
+ if (RT_FAILURE(rc))
+ {
+ LogBird(("ata: %x: schedule failed, rc=%Rrc\n", pCtl->IOPortBase1, rc));
+ rc = RTSemEventSignal(pCtl->AsyncIOSem);
+ AssertRC(rc);
+ }
+}
+
+
+static const ATARequest *ataAsyncIOGetCurrentRequest(PATACONTROLLER pCtl)
+{
+ int rc;
+ const ATARequest *pReq;
+
+ rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail)
+ pReq = &pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail];
+ else
+ pReq = NULL;
+ rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+ AssertRC(rc);
+ return pReq;
+}
+
+
+/**
+ * Remove the request with the given type, as it's finished. The request
+ * is not removed blindly, as this could mean a RESET request that is not
+ * yet processed (but has cleared the request queue) is lost.
+ *
+ * @param pCtl Controller for which to remove the request.
+ * @param ReqType Type of the request to remove.
+ */
+static void ataAsyncIORemoveCurrentRequest(PATACONTROLLER pCtl, ATAAIO ReqType)
+{
+ int rc;
+
+ rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail && pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail].ReqType == ReqType)
+ {
+ pCtl->AsyncIOReqTail++;
+ pCtl->AsyncIOReqTail %= RT_ELEMENTS(pCtl->aAsyncIORequests);
+ }
+ rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+ AssertRC(rc);
+}
+
+
+/**
+ * Dump the request queue for a particular controller. First dump the queue
+ * contents, then the already processed entries, as long as they haven't been
+ * overwritten.
+ *
+ * @param pCtl Controller for which to dump the queue.
+ */
+static void ataAsyncIODumpRequests(PATACONTROLLER pCtl)
+{
+ int rc;
+ uint8_t curr;
+
+ rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ LogRel(("PIIX3 ATA: Ctl#%d: request queue dump (topmost is current):\n", ATACONTROLLER_IDX(pCtl)));
+ curr = pCtl->AsyncIOReqTail;
+ do
+ {
+ if (curr == pCtl->AsyncIOReqHead)
+ LogRel(("PIIX3 ATA: Ctl#%d: processed requests (topmost is oldest):\n", ATACONTROLLER_IDX(pCtl)));
+ switch (pCtl->aAsyncIORequests[curr].ReqType)
+ {
+ case ATA_AIO_NEW:
+ LogRel(("new transfer request, iIf=%d iBeginTransfer=%d iSourceSink=%d cbTotalTransfer=%d uTxDir=%d\n", pCtl->aAsyncIORequests[curr].u.t.iIf, pCtl->aAsyncIORequests[curr].u.t.iBeginTransfer, pCtl->aAsyncIORequests[curr].u.t.iSourceSink, pCtl->aAsyncIORequests[curr].u.t.cbTotalTransfer, pCtl->aAsyncIORequests[curr].u.t.uTxDir));
+ break;
+ case ATA_AIO_DMA:
+ LogRel(("dma transfer finished\n"));
+ break;
+ case ATA_AIO_PIO:
+ LogRel(("pio transfer finished\n"));
+ break;
+ case ATA_AIO_RESET_ASSERTED:
+ LogRel(("reset asserted request\n"));
+ break;
+ case ATA_AIO_RESET_CLEARED:
+ LogRel(("reset cleared request\n"));
+ break;
+ case ATA_AIO_ABORT:
+ LogRel(("abort request, iIf=%d fResetDrive=%d\n", pCtl->aAsyncIORequests[curr].u.a.iIf, pCtl->aAsyncIORequests[curr].u.a.fResetDrive));
+ break;
+ default:
+ LogRel(("unknown request %d\n", pCtl->aAsyncIORequests[curr].ReqType));
+ }
+ curr = (curr + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests);
+ } while (curr != pCtl->AsyncIOReqTail);
+ rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+ AssertRC(rc);
+}
+
+
+/**
+ * Checks whether the request queue for a particular controller is empty
+ * or whether a particular controller is idle.
+ *
+ * @param pCtl Controller for which to check the queue.
+ * @param fStrict If set then the controller is checked to be idle.
+ */
+static bool ataAsyncIOIsIdle(PATACONTROLLER pCtl, bool fStrict)
+{
+ int rc;
+ bool fIdle;
+
+ rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ fIdle = pCtl->fRedoIdle;
+ if (!fIdle)
+ fIdle = (pCtl->AsyncIOReqHead == pCtl->AsyncIOReqTail);
+ if (fStrict)
+ fIdle &= (pCtl->uAsyncIOState == ATA_AIO_NEW);
+ rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
+ AssertRC(rc);
+ return fIdle;
+}
+
+
+/**
+ * Send a transfer request to the async I/O thread.
+ *
+ * @param s Pointer to the ATA device state data.
+ * @param cbTotalTransfer Data transfer size.
+ * @param uTxDir Data transfer direction.
+ * @param iBeginTransfer Index of BeginTransfer callback.
+ * @param iSourceSink Index of SourceSink callback.
+ * @param fChainedTransfer Whether this is a transfer that is part of the previous command/transfer.
+ */
+static void ataStartTransfer(ATADevState *s, uint32_t cbTotalTransfer, uint8_t uTxDir, ATAFNBT iBeginTransfer, ATAFNSS iSourceSink, bool fChainedTransfer)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ ATARequest Req;
+
+ Assert(PDMCritSectIsOwner(&pCtl->lock));
+
+ /* Do not issue new requests while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed new request as RESET is active\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ return;
+ }
+
+ /* If the controller is already doing something else right now, ignore
+ * the command that is being submitted. Some broken guests issue commands
+ * twice (e.g. the Linux kernel that comes with Acronis True Image 8). */
+ if (!fChainedTransfer && !ataAsyncIOIsIdle(pCtl, true))
+ {
+ Log(("%s: Ctl#%d: ignored command %#04x, controller state %d\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl), s->uATARegCommand, pCtl->uAsyncIOState));
+ LogRel(("PIIX3 IDE: guest issued command %#04x while controller busy\n", s->uATARegCommand));
+ return;
+ }
+
+ Req.ReqType = ATA_AIO_NEW;
+ if (fChainedTransfer)
+ Req.u.t.iIf = pCtl->iAIOIf;
+ else
+ Req.u.t.iIf = pCtl->iSelectedIf;
+ Req.u.t.cbTotalTransfer = cbTotalTransfer;
+ Req.u.t.uTxDir = uTxDir;
+ Req.u.t.iBeginTransfer = iBeginTransfer;
+ Req.u.t.iSourceSink = iSourceSink;
+ ataSetStatusValue(s, ATA_STAT_BUSY);
+ pCtl->fChainedTransfer = fChainedTransfer;
+
+ /*
+ * Kick the worker thread into action.
+ */
+ Log2(("%s: Ctl#%d: message to async I/O thread, new request\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ ataAsyncIOPutRequest(pCtl, &Req);
+}
+
+
+/**
+ * Send an abort command request to the async I/O thread.
+ *
+ * @param s Pointer to the ATA device state data.
+ * @param fResetDrive Whether to reset the drive or just abort a command.
+ */
+static void ataAbortCurrentCommand(ATADevState *s, bool fResetDrive)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ ATARequest Req;
+
+ Assert(PDMCritSectIsOwner(&pCtl->lock));
+
+ /* Do not issue new requests while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed aborting command as RESET is active\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ return;
+ }
+
+ Req.ReqType = ATA_AIO_ABORT;
+ Req.u.a.iIf = pCtl->iSelectedIf;
+ Req.u.a.fResetDrive = fResetDrive;
+ ataSetStatus(s, ATA_STAT_BUSY);
+ Log2(("%s: Ctl#%d: message to async I/O thread, abort command on LUN#%d\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl), s->iLUN));
+ ataAsyncIOPutRequest(pCtl, &Req);
+}
+
+
+static void ataSetIRQ(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
+
+ if (!(s->uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ))
+ {
+ Log2(("%s: LUN#%d asserting IRQ\n", __FUNCTION__, s->iLUN));
+ /* The BMDMA unit unconditionally sets BM_STATUS_INT if the interrupt
+ * line is asserted. It monitors the line for a rising edge. */
+ if (!s->fIrqPending)
+ pCtl->BmDma.u8Status |= BM_STATUS_INT;
+ /* Only actually set the IRQ line if updating the currently selected drive. */
+ if (s == &pCtl->aIfs[pCtl->iSelectedIf])
+ {
+ /** @todo experiment with adaptive IRQ delivery: for reads it is
+ * better to wait for IRQ delivery, as it reduces latency. */
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrqNoWait(pDevIns, 0, 1);
+ else
+ PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 1);
+ }
+ }
+ s->fIrqPending = true;
+}
+
+#endif /* IN_RING3 */
+
+static void ataUnsetIRQ(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
+
+ if (!(s->uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ))
+ {
+ Log2(("%s: LUN#%d deasserting IRQ\n", __FUNCTION__, s->iLUN));
+ /* Only actually unset the IRQ line if updating the currently selected drive. */
+ if (s == &pCtl->aIfs[pCtl->iSelectedIf])
+ {
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrqNoWait(pDevIns, 0, 0);
+ else
+ PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 0);
+ }
+ }
+ s->fIrqPending = false;
+}
+
+#ifdef IN_RING3
+
+static void ataPIOTransferStart(ATADevState *s, uint32_t start, uint32_t size)
+{
+ Log2(("%s: LUN#%d start %d size %d\n", __FUNCTION__, s->iLUN, start, size));
+ s->iIOBufferPIODataStart = start;
+ s->iIOBufferPIODataEnd = start + size;
+ ataSetStatus(s, ATA_STAT_DRQ);
+}
+
+
+static void ataPIOTransferStop(ATADevState *s)
+{
+ Log2(("%s: LUN#%d\n", __FUNCTION__, s->iLUN));
+ if (s->fATAPITransfer)
+ {
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ ataSetIRQ(s);
+ s->fATAPITransfer = false;
+ }
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iIOBufferPIODataStart = 0;
+ s->iIOBufferPIODataEnd = 0;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+static void ataPIOTransferLimitATAPI(ATADevState *s)
+{
+ uint32_t cbLimit, cbTransfer;
+
+ cbLimit = s->uATARegLCyl | (s->uATARegHCyl << 8);
+ /* Use maximum transfer size if the guest requested 0. Avoids a hang. */
+ if (cbLimit == 0)
+ cbLimit = 0xfffe;
+ Log2(("%s: byte count limit=%d\n", __FUNCTION__, cbLimit));
+ if (cbLimit == 0xffff)
+ cbLimit--;
+ cbTransfer = RT_MIN(s->cbTotalTransfer, s->iIOBufferEnd - s->iIOBufferCur);
+ if (cbTransfer > cbLimit)
+ {
+ /* Byte count limit for clipping must be even in this case */
+ if (cbLimit & 1)
+ cbLimit--;
+ cbTransfer = cbLimit;
+ }
+ s->uATARegLCyl = cbTransfer;
+ s->uATARegHCyl = cbTransfer >> 8;
+ s->cbElementaryTransfer = cbTransfer;
+}
+
+
+static uint32_t ataGetNSectors(ATADevState *s)
+{
+ /* 0 means either 256 (LBA28) or 65536 (LBA48) sectors. */
+ if (s->fLBA48)
+ {
+ if (!s->uATARegNSector && !s->uATARegNSectorHOB)
+ return 65536;
+ else
+ return s->uATARegNSectorHOB << 8 | s->uATARegNSector;
+ }
+ else
+ {
+ if (!s->uATARegNSector)
+ return 256;
+ else
+ return s->uATARegNSector;
+ }
+}
+
+
+static void ataPadString(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
+{
+ for (uint32_t i = 0; i < cbSize; i++)
+ {
+ if (*pbSrc)
+ pbDst[i ^ 1] = *pbSrc++;
+ else
+ pbDst[i ^ 1] = ' ';
+ }
+}
+
+
+static void ataSCSIPadStr(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
+{
+ for (uint32_t i = 0; i < cbSize; i++)
+ {
+ if (*pbSrc)
+ pbDst[i] = *pbSrc++;
+ else
+ pbDst[i] = ' ';
+ }
+}
+
+
+DECLINLINE(void) ataH2BE_U16(uint8_t *pbBuf, uint16_t val)
+{
+ pbBuf[0] = val >> 8;
+ pbBuf[1] = val;
+}
+
+
+DECLINLINE(void) ataH2BE_U24(uint8_t *pbBuf, uint32_t val)
+{
+ pbBuf[0] = val >> 16;
+ pbBuf[1] = val >> 8;
+ pbBuf[2] = val;
+}
+
+
+DECLINLINE(void) ataH2BE_U32(uint8_t *pbBuf, uint32_t val)
+{
+ pbBuf[0] = val >> 24;
+ pbBuf[1] = val >> 16;
+ pbBuf[2] = val >> 8;
+ pbBuf[3] = val;
+}
+
+
+DECLINLINE(uint16_t) ataBE2H_U16(const uint8_t *pbBuf)
+{
+ return (pbBuf[0] << 8) | pbBuf[1];
+}
+
+
+DECLINLINE(uint32_t) ataBE2H_U24(const uint8_t *pbBuf)
+{
+ return (pbBuf[0] << 16) | (pbBuf[1] << 8) | pbBuf[2];
+}
+
+
+DECLINLINE(uint32_t) ataBE2H_U32(const uint8_t *pbBuf)
+{
+ return (pbBuf[0] << 24) | (pbBuf[1] << 16) | (pbBuf[2] << 8) | pbBuf[3];
+}
+
+
+DECLINLINE(void) ataLBA2MSF(uint8_t *pbBuf, uint32_t iATAPILBA)
+{
+ iATAPILBA += 150;
+ pbBuf[0] = (iATAPILBA / 75) / 60;
+ pbBuf[1] = (iATAPILBA / 75) % 60;
+ pbBuf[2] = iATAPILBA % 75;
+}
+
+
+DECLINLINE(uint32_t) ataMSF2LBA(const uint8_t *pbBuf)
+{
+ return (pbBuf[0] * 60 + pbBuf[1]) * 75 + pbBuf[2];
+}
+
+
+static void ataCmdOK(ATADevState *s, uint8_t status)
+{
+ s->uATARegError = 0; /* Not needed by ATA spec, but cannot hurt. */
+ ataSetStatusValue(s, ATA_STAT_READY | status);
+}
+
+
+static void ataCmdError(ATADevState *s, uint8_t uErrorCode)
+{
+ Log(("%s: code=%#x\n", __FUNCTION__, uErrorCode));
+ s->uATARegError = uErrorCode;
+ ataSetStatusValue(s, ATA_STAT_READY | ATA_STAT_ERR);
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = 0;
+ s->uTxDir = PDMBLOCKTXDIR_NONE;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+static bool ataIdentifySS(ATADevState *s)
+{
+ uint16_t *p;
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer == 512);
+
+ p = (uint16_t *)s->CTX_SUFF(pbIOBuffer);
+ memset(p, 0, 512);
+ p[0] = RT_H2LE_U16(0x0040);
+ p[1] = RT_H2LE_U16(RT_MIN(s->PCHSGeometry.cCylinders, 16383));
+ p[3] = RT_H2LE_U16(s->PCHSGeometry.cHeads);
+ /* Block size; obsolete, but required for the BIOS. */
+ p[5] = RT_H2LE_U16(512);
+ p[6] = RT_H2LE_U16(s->PCHSGeometry.cSectors);
+ ataPadString((uint8_t *)(p + 10), s->szSerialNumber, ATA_SERIAL_NUMBER_LENGTH); /* serial number */
+ p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+ p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+ p[22] = RT_H2LE_U16(0); /* ECC bytes per sector */
+ ataPadString((uint8_t *)(p + 23), s->szFirmwareRevision, ATA_FIRMWARE_REVISION_LENGTH); /* firmware version */
+ ataPadString((uint8_t *)(p + 27), s->szModelNumber, ATA_MODEL_NUMBER_LENGTH); /* model */
+#if ATA_MAX_MULT_SECTORS > 1
+ p[47] = RT_H2LE_U16(0x8000 | ATA_MAX_MULT_SECTORS);
+#endif
+ p[48] = RT_H2LE_U16(1); /* dword I/O, used by the BIOS */
+ p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+ p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
+ p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+ p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+ p[53] = RT_H2LE_U16(1 | 1 << 1 | 1 << 2); /* words 54-58,64-70,88 valid */
+ p[54] = RT_H2LE_U16(RT_MIN(s->PCHSGeometry.cCylinders, 16383));
+ p[55] = RT_H2LE_U16(s->PCHSGeometry.cHeads);
+ p[56] = RT_H2LE_U16(s->PCHSGeometry.cSectors);
+ p[57] = RT_H2LE_U16( RT_MIN(s->PCHSGeometry.cCylinders, 16383)
+ * s->PCHSGeometry.cHeads
+ * s->PCHSGeometry.cSectors);
+ p[58] = RT_H2LE_U16( RT_MIN(s->PCHSGeometry.cCylinders, 16383)
+ * s->PCHSGeometry.cHeads
+ * s->PCHSGeometry.cSectors >> 16);
+ if (s->cMultSectors)
+ p[59] = RT_H2LE_U16(0x100 | s->cMultSectors);
+ if (s->cTotalSectors <= (1 << 28) - 1)
+ {
+ p[60] = RT_H2LE_U16(s->cTotalSectors);
+ p[61] = RT_H2LE_U16(s->cTotalSectors >> 16);
+ }
+ else
+ {
+ /* Report maximum number of sectors possible with LBA28 */
+ p[60] = RT_H2LE_U16(((1 << 28) - 1) & 0xffff);
+ p[61] = RT_H2LE_U16(((1 << 28) - 1) >> 16);
+ }
+ p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, s->uATATransferMode)); /* MDMA modes supported / mode enabled */
+ p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+ p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+ p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+ p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+ p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+ p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+ p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+ p[82] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* supports power management, write cache and look-ahead */
+ if (s->cTotalSectors <= (1 << 28) - 1)
+ p[83] = RT_H2LE_U16(1 << 14 | 1 << 12); /* supports FLUSH CACHE */
+ else
+ p[83] = RT_H2LE_U16(1 << 14 | 1 << 10 | 1 << 12 | 1 << 13); /* supports LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+ p[84] = RT_H2LE_U16(1 << 14);
+ p[85] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* enabled power management, write cache and look-ahead */
+ if (s->cTotalSectors <= (1 << 28) - 1)
+ p[86] = RT_H2LE_U16(1 << 12); /* enabled FLUSH CACHE */
+ else
+ p[86] = RT_H2LE_U16(1 << 10 | 1 << 12 | 1 << 13); /* enabled LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+ p[87] = RT_H2LE_U16(1 << 14);
+ p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, s->uATATransferMode)); /* UDMA modes supported / mode enabled */
+ p[93] = RT_H2LE_U16((1 | 1 << 1) << ((s->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
+ if (s->cTotalSectors > (1 << 28) - 1)
+ {
+ p[100] = RT_H2LE_U16(s->cTotalSectors);
+ p[101] = RT_H2LE_U16(s->cTotalSectors >> 16);
+ p[102] = RT_H2LE_U16(s->cTotalSectors >> 32);
+ p[103] = RT_H2LE_U16(s->cTotalSectors >> 48);
+ }
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataCmdOK(s, ATA_STAT_SEEK);
+ return false;
+}
+
+
+static bool ataFlushSS(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc;
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_NONE);
+ Assert(!s->cbElementaryTransfer);
+
+ PDMCritSectLeave(&pCtl->lock);
+
+ STAM_PROFILE_START(&s->StatFlushes, f);
+ rc = s->pDrvBlock->pfnFlush(s->pDrvBlock);
+ AssertRC(rc);
+ STAM_PROFILE_STOP(&s->StatFlushes, f);
+
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+ ataCmdOK(s, 0);
+ return false;
+}
+
+
+static bool atapiIdentifySS(ATADevState *s)
+{
+ uint16_t *p;
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer == 512);
+
+ p = (uint16_t *)s->CTX_SUFF(pbIOBuffer);
+ memset(p, 0, 512);
+ /* Removable CDROM, 50us response, 12 byte packets */
+ p[0] = RT_H2LE_U16(2 << 14 | 5 << 8 | 1 << 7 | 2 << 5 | 0 << 0);
+ ataPadString((uint8_t *)(p + 10), s->szSerialNumber, ATA_SERIAL_NUMBER_LENGTH); /* serial number */
+ p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+ p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+ ataPadString((uint8_t *)(p + 23), s->szFirmwareRevision, ATA_FIRMWARE_REVISION_LENGTH); /* firmware version */
+ ataPadString((uint8_t *)(p + 27), s->szModelNumber, ATA_MODEL_NUMBER_LENGTH); /* model */
+ p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+ p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
+ p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+ p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+ p[53] = RT_H2LE_U16(1 << 1 | 1 << 2); /* words 64-70,88 are valid */
+ p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, s->uATATransferMode)); /* MDMA modes supported / mode enabled */
+ p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+ p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+ p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+ p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+ p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+ p[73] = RT_H2LE_U16(0x003e); /* ATAPI CDROM major */
+ p[74] = RT_H2LE_U16(9); /* ATAPI CDROM minor */
+ p[75] = RT_H2LE_U16(1); /* queue depth 1 */
+ p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+ p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+ p[82] = RT_H2LE_U16(1 << 4 | 1 << 9); /* supports packet command set and DEVICE RESET */
+ p[83] = RT_H2LE_U16(1 << 14);
+ p[84] = RT_H2LE_U16(1 << 14);
+ p[85] = RT_H2LE_U16(1 << 4 | 1 << 9); /* enabled packet command set and DEVICE RESET */
+ p[86] = RT_H2LE_U16(0);
+ p[87] = RT_H2LE_U16(1 << 14);
+ p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, s->uATATransferMode)); /* UDMA modes supported / mode enabled */
+ p[93] = RT_H2LE_U16((1 | 1 << 1) << ((s->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataCmdOK(s, ATA_STAT_SEEK);
+ return false;
+}
+
+
+static void ataSetSignature(ATADevState *s)
+{
+ s->uATARegSelect &= 0xf0; /* clear head */
+ /* put signature */
+ s->uATARegNSector = 1;
+ s->uATARegSector = 1;
+ if (s->fATAPI)
+ {
+ s->uATARegLCyl = 0x14;
+ s->uATARegHCyl = 0xeb;
+ }
+ else if (s->pDrvBlock)
+ {
+ s->uATARegLCyl = 0;
+ s->uATARegHCyl = 0;
+ }
+ else
+ {
+ s->uATARegLCyl = 0xff;
+ s->uATARegHCyl = 0xff;
+ }
+}
+
+
+static uint64_t ataGetSector(ATADevState *s)
+{
+ uint64_t iLBA;
+ if (s->uATARegSelect & 0x40)
+ {
+ /* any LBA variant */
+ if (s->fLBA48)
+ {
+ /* LBA48 */
+ iLBA = ((uint64_t)s->uATARegHCylHOB << 40) |
+ ((uint64_t)s->uATARegLCylHOB << 32) |
+ ((uint64_t)s->uATARegSectorHOB << 24) |
+ ((uint64_t)s->uATARegHCyl << 16) |
+ ((uint64_t)s->uATARegLCyl << 8) |
+ s->uATARegSector;
+ }
+ else
+ {
+ /* LBA */
+ iLBA = ((s->uATARegSelect & 0x0f) << 24) | (s->uATARegHCyl << 16) |
+ (s->uATARegLCyl << 8) | s->uATARegSector;
+ }
+ }
+ else
+ {
+ /* CHS */
+ iLBA = ((s->uATARegHCyl << 8) | s->uATARegLCyl) * s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors +
+ (s->uATARegSelect & 0x0f) * s->PCHSGeometry.cSectors +
+ (s->uATARegSector - 1);
+ }
+ return iLBA;
+}
+
+static void ataSetSector(ATADevState *s, uint64_t iLBA)
+{
+ uint32_t cyl, r;
+ if (s->uATARegSelect & 0x40)
+ {
+ /* any LBA variant */
+ if (s->fLBA48)
+ {
+ /* LBA48 */
+ s->uATARegHCylHOB = iLBA >> 40;
+ s->uATARegLCylHOB = iLBA >> 32;
+ s->uATARegSectorHOB = iLBA >> 24;
+ s->uATARegHCyl = iLBA >> 16;
+ s->uATARegLCyl = iLBA >> 8;
+ s->uATARegSector = iLBA;
+ }
+ else
+ {
+ /* LBA */
+ s->uATARegSelect = (s->uATARegSelect & 0xf0) | (iLBA >> 24);
+ s->uATARegHCyl = (iLBA >> 16);
+ s->uATARegLCyl = (iLBA >> 8);
+ s->uATARegSector = (iLBA);
+ }
+ }
+ else
+ {
+ /* CHS */
+ cyl = iLBA / (s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors);
+ r = iLBA % (s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors);
+ s->uATARegHCyl = cyl >> 8;
+ s->uATARegLCyl = cyl;
+ s->uATARegSelect = (s->uATARegSelect & 0xf0) | ((r / s->PCHSGeometry.cSectors) & 0x0f);
+ s->uATARegSector = (r % s->PCHSGeometry.cSectors) + 1;
+ }
+}
+
+
+static int ataReadSectors(ATADevState *s, uint64_t u64Sector, void *pvBuf, uint32_t cSectors)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc;
+
+ PDMCritSectLeave(&pCtl->lock);
+
+ STAM_PROFILE_ADV_START(&s->StatReads, r);
+ s->Led.Asserted.s.fReading = s->Led.Actual.s.fReading = 1;
+ rc = s->pDrvBlock->pfnRead(s->pDrvBlock, u64Sector * 512, pvBuf, cSectors * 512);
+ s->Led.Actual.s.fReading = 0;
+ STAM_PROFILE_ADV_STOP(&s->StatReads, r);
+
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, cSectors * 512);
+
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+ return rc;
+}
+
+
+static int ataWriteSectors(ATADevState *s, uint64_t u64Sector, const void *pvBuf, uint32_t cSectors)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc;
+
+ PDMCritSectLeave(&pCtl->lock);
+
+ STAM_PROFILE_ADV_START(&s->StatWrites, w);
+ s->Led.Asserted.s.fWriting = s->Led.Actual.s.fWriting = 1;
+#ifdef VBOX_INSTRUMENT_DMA_WRITES
+ if (s->fDMA)
+ STAM_PROFILE_ADV_START(&s->StatInstrVDWrites, vw);
+#endif
+ rc = s->pDrvBlock->pfnWrite(s->pDrvBlock, u64Sector * 512, pvBuf, cSectors * 512);
+#ifdef VBOX_INSTRUMENT_DMA_WRITES
+ if (s->fDMA)
+ STAM_PROFILE_ADV_STOP(&s->StatInstrVDWrites, vw);
+#endif
+ s->Led.Actual.s.fWriting = 0;
+ STAM_PROFILE_ADV_STOP(&s->StatWrites, w);
+
+ STAM_REL_COUNTER_ADD(&s->StatBytesWritten, cSectors * 512);
+
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+ return rc;
+}
+
+
+static void ataReadWriteSectorsBT(ATADevState *s)
+{
+ uint32_t cSectors;
+
+ cSectors = s->cbTotalTransfer / 512;
+ if (cSectors > s->cSectorsPerIRQ)
+ s->cbElementaryTransfer = s->cSectorsPerIRQ * 512;
+ else
+ s->cbElementaryTransfer = cSectors * 512;
+ if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
+ ataCmdOK(s, 0);
+}
+
+
+static void ataWarningDiskFull(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: Host disk full\n"));
+ rc = VMSetRuntimeError(PDMDevHlpGetVM(pDevIns),
+ false, "DevATA_DISKFULL",
+ N_("Host system reported disk full. VM execution is suspended. You can resume after freeing some space"));
+ AssertRC(rc);
+}
+
+
+static void ataWarningFileTooBig(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: File too big\n"));
+ rc = VMSetRuntimeError(PDMDevHlpGetVM(pDevIns),
+ false, "DevATA_FILETOOBIG",
+ N_("Host system reported that the file size limit of the host file system has been exceeded. VM execution is suspended. You need to move your virtual hard disk to a filesystem which allows bigger files"));
+ AssertRC(rc);
+}
+
+
+static void ataWarningISCSI(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: iSCSI target unavailable\n"));
+ rc = VMSetRuntimeError(PDMDevHlpGetVM(pDevIns),
+ false, "DevATA_ISCSIDOWN",
+ N_("The iSCSI target has stopped responding. VM execution is suspended. You can resume when it is available again"));
+ AssertRC(rc);
+}
+
+
+static bool ataReadSectorsSS(ATADevState *s)
+{
+ int rc;
+ uint32_t cSectors;
+ uint64_t iLBA;
+
+ cSectors = s->cbElementaryTransfer / 512;
+ Assert(cSectors);
+ iLBA = ataGetSector(s);
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
+ rc = ataReadSectors(s, iLBA, s->CTX_SUFF(pbIOBuffer), cSectors);
+ if (RT_SUCCESS(rc))
+ {
+ ataSetSector(s, iLBA + cSectors);
+ if (s->cbElementaryTransfer == s->cbTotalTransfer)
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataCmdOK(s, ATA_STAT_SEEK);
+ }
+ else
+ {
+ if (rc == VERR_DISK_FULL)
+ {
+ ataWarningDiskFull(ATADEVSTATE_2_DEVINS(s));
+ return true;
+ }
+ if (rc == VERR_FILE_TOO_BIG)
+ {
+ ataWarningFileTooBig(ATADEVSTATE_2_DEVINS(s));
+ return true;
+ }
+ if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
+ {
+ /* iSCSI connection abort (first error) or failure to reestablish
+ * connection (second error). Pause VM. On resume we'll retry. */
+ ataWarningISCSI(ATADEVSTATE_2_DEVINS(s));
+ return true;
+ }
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: disk read error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
+ s->iLUN, rc, iLBA, cSectors));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ ataCmdError(s, ID_ERR);
+ }
+ /** @todo implement redo for iSCSI */
+ return false;
+}
+
+
+static bool ataWriteSectorsSS(ATADevState *s)
+{
+ int rc;
+ uint32_t cSectors;
+ uint64_t iLBA;
+
+ cSectors = s->cbElementaryTransfer / 512;
+ Assert(cSectors);
+ iLBA = ataGetSector(s);
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
+ rc = ataWriteSectors(s, iLBA, s->CTX_SUFF(pbIOBuffer), cSectors);
+ if (RT_SUCCESS(rc))
+ {
+ ataSetSector(s, iLBA + cSectors);
+ if (!s->cbTotalTransfer)
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataCmdOK(s, ATA_STAT_SEEK);
+ }
+ else
+ {
+ if (rc == VERR_DISK_FULL)
+ {
+ ataWarningDiskFull(ATADEVSTATE_2_DEVINS(s));
+ return true;
+ }
+ if (rc == VERR_FILE_TOO_BIG)
+ {
+ ataWarningFileTooBig(ATADEVSTATE_2_DEVINS(s));
+ return true;
+ }
+ if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
+ {
+ /* iSCSI connection abort (first error) or failure to reestablish
+ * connection (second error). Pause VM. On resume we'll retry. */
+ ataWarningISCSI(ATADEVSTATE_2_DEVINS(s));
+ return true;
+ }
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: disk write error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
+ s->iLUN, rc, iLBA, cSectors));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ ataCmdError(s, ID_ERR);
+ }
+ /** @todo implement redo for iSCSI */
+ return false;
+}
+
+
+static void atapiCmdOK(ATADevState *s)
+{
+ s->uATARegError = 0;
+ ataSetStatusValue(s, ATA_STAT_READY);
+ s->uATARegNSector = (s->uATARegNSector & ~7)
+ | ((s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE) ? ATAPI_INT_REASON_IO : 0)
+ | (!s->cbTotalTransfer ? ATAPI_INT_REASON_CD : 0);
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+
+ memset(s->abATAPISense, '\0', sizeof(s->abATAPISense));
+ s->abATAPISense[0] = 0x70 | (1 << 7);
+ s->abATAPISense[7] = 10;
+}
+
+
+static void atapiCmdError(ATADevState *s, const uint8_t *pabATAPISense, size_t cbATAPISense)
+{
+ Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, pabATAPISense[2] & 0x0f, SCSISenseText(pabATAPISense[2] & 0x0f),
+ pabATAPISense[12], pabATAPISense[13], SCSISenseExtText(pabATAPISense[12], pabATAPISense[13])));
+ s->uATARegError = pabATAPISense[2] << 4;
+ ataSetStatusValue(s, ATA_STAT_READY | ATA_STAT_ERR);
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ memset(s->abATAPISense, '\0', sizeof(s->abATAPISense));
+ memcpy(s->abATAPISense, pabATAPISense, RT_MIN(cbATAPISense, sizeof(s->abATAPISense)));
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = 0;
+ s->uTxDir = PDMBLOCKTXDIR_NONE;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+/** @todo deprecated function - doesn't provide enough info. Replace by direct
+ * calls to atapiCmdError() with full data. */
+static void atapiCmdErrorSimple(ATADevState *s, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
+{
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ memset(abATAPISense, '\0', sizeof(abATAPISense));
+ abATAPISense[0] = 0x70 | (1 << 7);
+ abATAPISense[2] = uATAPISenseKey & 0x0f;
+ abATAPISense[7] = 10;
+ abATAPISense[12] = uATAPIASC;
+ atapiCmdError(s, abATAPISense, sizeof(abATAPISense));
+}
+
+
+static void atapiCmdBT(ATADevState *s)
+{
+ s->fATAPITransfer = true;
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
+ atapiCmdOK(s);
+}
+
+
+static void atapiPassthroughCmdBT(ATADevState *s)
+{
+ /* @todo implement an algorithm for correctly determining the read and
+ * write sector size without sending additional commands to the drive.
+ * This should be doable by saving processing the configuration requests
+ * and replies. */
+#if 0
+ if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
+ {
+ uint8_t cmd = s->aATAPICmd[0];
+ if (cmd == SCSI_WRITE_10 || cmd == SCSI_WRITE_12 || cmd == SCSI_WRITE_AND_VERIFY_10)
+ {
+ uint8_t aModeSenseCmd[10];
+ uint8_t aModeSenseResult[16];
+ uint8_t uDummySense;
+ uint32_t cbTransfer;
+ int rc;
+
+ cbTransfer = sizeof(aModeSenseResult);
+ aModeSenseCmd[0] = SCSI_MODE_SENSE_10;
+ aModeSenseCmd[1] = 0x08; /* disable block descriptor = 1 */
+ aModeSenseCmd[2] = (SCSI_PAGECONTROL_CURRENT << 6) | SCSI_MODEPAGE_WRITE_PARAMETER;
+ aModeSenseCmd[3] = 0; /* subpage code */
+ aModeSenseCmd[4] = 0; /* reserved */
+ aModeSenseCmd[5] = 0; /* reserved */
+ aModeSenseCmd[6] = 0; /* reserved */
+ aModeSenseCmd[7] = cbTransfer >> 8;
+ aModeSenseCmd[8] = cbTransfer & 0xff;
+ aModeSenseCmd[9] = 0; /* control */
+ rc = s->pDrvBlock->pfnSendCmd(s->pDrvBlock, aModeSenseCmd, PDMBLOCKTXDIR_FROM_DEVICE, aModeSenseResult, &cbTransfer, &uDummySense, 500);
+ if (RT_FAILURE(rc))
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_NONE);
+ return;
+ }
+ /* Select sector size based on the current data block type. */
+ switch (aModeSenseResult[12] & 0x0f)
+ {
+ case 0:
+ s->cbATAPISector = 2352;
+ break;
+ case 1:
+ s->cbATAPISector = 2368;
+ break;
+ case 2:
+ case 3:
+ s->cbATAPISector = 2448;
+ break;
+ case 8:
+ case 10:
+ s->cbATAPISector = 2048;
+ break;
+ case 9:
+ s->cbATAPISector = 2336;
+ break;
+ case 11:
+ s->cbATAPISector = 2056;
+ break;
+ case 12:
+ s->cbATAPISector = 2324;
+ break;
+ case 13:
+ s->cbATAPISector = 2332;
+ break;
+ default:
+ s->cbATAPISector = 0;
+ }
+ Log2(("%s: sector size %d\n", __FUNCTION__, s->cbATAPISector));
+ s->cbTotalTransfer *= s->cbATAPISector;
+ if (s->cbTotalTransfer == 0)
+ s->uTxDir = PDMBLOCKTXDIR_NONE;
+ }
+ }
+#endif
+ atapiCmdBT(s);
+}
+
+
+static bool atapiReadSS(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc = VINF_SUCCESS;
+ uint32_t cbTransfer, cSectors;
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ cbTransfer = RT_MIN(s->cbTotalTransfer, s->cbIOBuffer);
+ cSectors = cbTransfer / s->cbATAPISector;
+ Assert(cSectors * s->cbATAPISector <= cbTransfer);
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, s->iATAPILBA));
+
+ PDMCritSectLeave(&pCtl->lock);
+
+ STAM_PROFILE_ADV_START(&s->StatReads, r);
+ s->Led.Asserted.s.fReading = s->Led.Actual.s.fReading = 1;
+ switch (s->cbATAPISector)
+ {
+ case 2048:
+ rc = s->pDrvBlock->pfnRead(s->pDrvBlock, (uint64_t)s->iATAPILBA * s->cbATAPISector, s->CTX_SUFF(pbIOBuffer), s->cbATAPISector * cSectors);
+ break;
+ case 2352:
+ {
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ for (uint32_t i = s->iATAPILBA; i < s->iATAPILBA + cSectors; i++)
+ {
+ /* sync bytes */
+ *pbBuf++ = 0x00;
+ memset(pbBuf, 0xff, 11);
+ pbBuf += 11;
+ /* MSF */
+ ataLBA2MSF(pbBuf, i);
+ pbBuf += 3;
+ *pbBuf++ = 0x01; /* mode 1 data */
+ /* data */
+ rc = s->pDrvBlock->pfnRead(s->pDrvBlock, (uint64_t)i * 2048, pbBuf, 2048);
+ if (RT_FAILURE(rc))
+ break;
+ pbBuf += 2048;
+ /* ECC */
+ memset(pbBuf, 0, 288);
+ pbBuf += 288;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ STAM_PROFILE_ADV_STOP(&s->StatReads, r);
+
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+
+ if (RT_SUCCESS(rc))
+ {
+ s->Led.Actual.s.fReading = 0;
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, s->cbATAPISector * cSectors);
+
+ /* The initial buffer end value has been set up based on the total
+ * transfer size. But the I/O buffer size limits what can actually be
+ * done in one transfer, so set the actual value of the buffer end. */
+ s->cbElementaryTransfer = cbTransfer;
+ if (cbTransfer >= s->cbTotalTransfer)
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ s->iATAPILBA += cSectors;
+ }
+ else
+ {
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM read error, %d sectors at LBA %d\n", s->iLUN, cSectors, s->iATAPILBA));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ atapiCmdErrorSimple(s, SCSI_SENSE_MEDIUM_ERROR, SCSI_ASC_READ_ERROR);
+ }
+ return false;
+}
+
+
+static bool atapiPassthroughSS(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc = VINF_SUCCESS;
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ size_t cbTransfer;
+ PSTAMPROFILEADV pProf = NULL;
+
+ cbTransfer = s->cbElementaryTransfer;
+
+ if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
+ Log3(("ATAPI PT data write (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTX_SUFF(pbIOBuffer)));
+
+ /* Simple heuristics: if there is at least one sector of data
+ * to transfer, it's worth updating the LEDs. */
+ if (cbTransfer >= 2048)
+ {
+ if (s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
+ {
+ s->Led.Asserted.s.fReading = s->Led.Actual.s.fReading = 1;
+ pProf = &s->StatReads;
+ }
+ else
+ {
+ s->Led.Asserted.s.fWriting = s->Led.Actual.s.fWriting = 1;
+ pProf = &s->StatWrites;
+ }
+ }
+
+ PDMCritSectLeave(&pCtl->lock);
+
+ if (pProf) { STAM_PROFILE_ADV_START(pProf, b); }
+ if (cbTransfer > SCSI_MAX_BUFFER_SIZE)
+ {
+ /* Linux accepts commands with up to 100KB of data, but expects
+ * us to handle commands with up to 128KB of data. The usual
+ * imbalance of powers. */
+ uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
+ uint32_t iATAPILBA, cSectors, cReqSectors;
+ size_t cbCurrTX;
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ switch (s->aATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ iATAPILBA = ataBE2H_U32(s->aATAPICmd + 2);
+ cSectors = ataBE2H_U16(s->aATAPICmd + 7);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ iATAPILBA = ataBE2H_U32(s->aATAPICmd + 2);
+ cSectors = ataBE2H_U32(s->aATAPICmd + 6);
+ break;
+ case SCSI_READ_CD:
+ iATAPILBA = ataBE2H_U32(s->aATAPICmd + 2);
+ cSectors = ataBE2H_U24(s->aATAPICmd + 6) / s->cbATAPISector;
+ break;
+ case SCSI_READ_CD_MSF:
+ iATAPILBA = ataMSF2LBA(s->aATAPICmd + 3);
+ cSectors = ataMSF2LBA(s->aATAPICmd + 6) - iATAPILBA;
+ break;
+ default:
+ AssertMsgFailed(("Don't know how to split command %#04x\n", s->aATAPICmd[0]));
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough split error\n", s->iLUN));
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ {
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+ }
+ return false;
+ }
+ memcpy(aATAPICmd, s->aATAPICmd, ATAPI_PACKET_SIZE);
+ cReqSectors = 0;
+ for (uint32_t i = cSectors; i > 0; i -= cReqSectors)
+ {
+ if (i * s->cbATAPISector > SCSI_MAX_BUFFER_SIZE)
+ cReqSectors = SCSI_MAX_BUFFER_SIZE / s->cbATAPISector;
+ else
+ cReqSectors = i;
+ cbCurrTX = s->cbATAPISector * cReqSectors;
+ switch (s->aATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ ataH2BE_U32(aATAPICmd + 2, iATAPILBA);
+ ataH2BE_U16(aATAPICmd + 7, cReqSectors);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ ataH2BE_U32(aATAPICmd + 2, iATAPILBA);
+ ataH2BE_U32(aATAPICmd + 6, cReqSectors);
+ break;
+ case SCSI_READ_CD:
+ ataH2BE_U32(s->aATAPICmd + 2, iATAPILBA);
+ ataH2BE_U24(s->aATAPICmd + 6, cbCurrTX);
+ break;
+ case SCSI_READ_CD_MSF:
+ ataLBA2MSF(aATAPICmd + 3, iATAPILBA);
+ ataLBA2MSF(aATAPICmd + 6, iATAPILBA + cReqSectors);
+ break;
+ }
+ rc = s->pDrvBlock->pfnSendCmd(s->pDrvBlock, aATAPICmd, (PDMBLOCKTXDIR)s->uTxDir, pbBuf, &cbCurrTX, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
+ if (rc != VINF_SUCCESS)
+ break;
+ iATAPILBA += cReqSectors;
+ pbBuf += s->cbATAPISector * cReqSectors;
+ }
+ }
+ else
+ rc = s->pDrvBlock->pfnSendCmd(s->pDrvBlock, s->aATAPICmd, (PDMBLOCKTXDIR)s->uTxDir, s->CTX_SUFF(pbIOBuffer), &cbTransfer, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
+ if (pProf) { STAM_PROFILE_ADV_STOP(pProf, b); }
+
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+
+ /* Update the LEDs and the read/write statistics. */
+ if (cbTransfer >= 2048)
+ {
+ if (s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
+ {
+ s->Led.Actual.s.fReading = 0;
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, cbTransfer);
+ }
+ else
+ {
+ s->Led.Actual.s.fWriting = 0;
+ STAM_REL_COUNTER_ADD(&s->StatBytesWritten, cbTransfer);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+ {
+ Assert(cbTransfer <= s->cbTotalTransfer);
+ /* Reply with the same amount of data as the real drive. */
+ s->cbTotalTransfer = cbTransfer;
+ /* The initial buffer end value has been set up based on the total
+ * transfer size. But the I/O buffer size limits what can actually be
+ * done in one transfer, so set the actual value of the buffer end. */
+ s->cbElementaryTransfer = cbTransfer;
+ if (s->aATAPICmd[0] == SCSI_INQUIRY)
+ {
+ /* Make sure that the real drive cannot be identified.
+ * Motivation: changing the VM configuration should be as
+ * invisible as possible to the guest. */
+ Log3(("ATAPI PT inquiry data before (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTX_SUFF(pbIOBuffer)));
+ ataSCSIPadStr(s->CTX_SUFF(pbIOBuffer) + 8, "VBOX", 8);
+ ataSCSIPadStr(s->CTX_SUFF(pbIOBuffer) + 16, "CD-ROM", 16);
+ ataSCSIPadStr(s->CTX_SUFF(pbIOBuffer) + 32, "1.0", 4);
+ }
+ if (cbTransfer)
+ Log3(("ATAPI PT data read (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTX_SUFF(pbIOBuffer)));
+ }
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ }
+ else
+ {
+ if (s->cErrors < MAX_LOG_REL_ERRORS)
+ {
+ uint8_t u8Cmd = s->aATAPICmd[0];
+ do
+ {
+ /* don't log superflous errors */
+ if ( rc == VERR_DEV_IO_ERROR
+ && ( u8Cmd == SCSI_TEST_UNIT_READY
+ || u8Cmd == SCSI_READ_CAPACITY
+ || u8Cmd == SCSI_READ_DVD_STRUCTURE
+ || u8Cmd == SCSI_READ_TOC_PMA_ATIP))
+ break;
+ s->cErrors++;
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough cmd=%#04x sense=%d ASC=%#02x ASCQ=%#02x %Rrc\n",
+ s->iLUN, u8Cmd, abATAPISense[2] & 0x0f, abATAPISense[12], abATAPISense[13], rc));
+ } while (0);
+ }
+ atapiCmdError(s, abATAPISense, sizeof(abATAPISense));
+ }
+ return false;
+}
+
+
+static bool atapiReadSectors(ATADevState *s, uint32_t iATAPILBA, uint32_t cSectors, uint32_t cbSector)
+{
+ Assert(cSectors > 0);
+ s->iATAPILBA = iATAPILBA;
+ s->cbATAPISector = cbSector;
+ ataStartTransfer(s, cSectors * cbSector, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ, true);
+ return false;
+}
+
+
+static bool atapiReadCapacitySS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 8);
+ ataH2BE_U32(pbBuf, s->cTotalSectors - 1);
+ ataH2BE_U32(pbBuf + 4, 2048);
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiReadDiscInformationSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 34);
+ memset(pbBuf, '\0', 34);
+ ataH2BE_U16(pbBuf, 32);
+ pbBuf[2] = (0 << 4) | (3 << 2) | (2 << 0); /* not erasable, complete session, complete disc */
+ pbBuf[3] = 1; /* number of first track */
+ pbBuf[4] = 1; /* number of sessions (LSB) */
+ pbBuf[5] = 1; /* first track number in last session (LSB) */
+ pbBuf[6] = 1; /* last track number in last session (LSB) */
+ pbBuf[7] = (0 << 7) | (0 << 6) | (1 << 5) | (0 << 2) | (0 << 0); /* disc id not valid, disc bar code not valid, unrestricted use, not dirty, not RW medium */
+ pbBuf[8] = 0; /* disc type = CD-ROM */
+ pbBuf[9] = 0; /* number of sessions (MSB) */
+ pbBuf[10] = 0; /* number of sessions (MSB) */
+ pbBuf[11] = 0; /* number of sessions (MSB) */
+ ataH2BE_U32(pbBuf + 16, 0x00ffffff); /* last session lead-in start time is not available */
+ ataH2BE_U32(pbBuf + 20, 0x00ffffff); /* last possible start time for lead-out is not available */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiReadTrackInformationSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 36);
+ /* Accept address/number type of 1 only, and only track 1 exists. */
+ if ((s->aATAPICmd[1] & 0x03) != 1 || ataBE2H_U32(&s->aATAPICmd[2]) != 1)
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+ memset(pbBuf, '\0', 36);
+ ataH2BE_U16(pbBuf, 34);
+ pbBuf[2] = 1; /* track number (LSB) */
+ pbBuf[3] = 1; /* session number (LSB) */
+ pbBuf[5] = (0 << 5) | (0 << 4) | (4 << 0); /* not damaged, primary copy, data track */
+ pbBuf[6] = (0 << 7) | (0 << 6) | (0 << 5) | (0 << 6) | (1 << 0); /* not reserved track, not blank, not packet writing, not fixed packet, data mode 1 */
+ pbBuf[7] = (0 << 1) | (0 << 0); /* last recorded address not valid, next recordable address not valid */
+ ataH2BE_U32(pbBuf + 8, 0); /* track start address is 0 */
+ ataH2BE_U32(pbBuf + 24, s->cTotalSectors); /* track size */
+ pbBuf[32] = 0; /* track number (MSB) */
+ pbBuf[33] = 0; /* session number (MSB) */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiGetConfigurationSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+ uint16_t u16Sfn = ataBE2H_U16(&s->aATAPICmd[2]);
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 32);
+ /* Accept valid request types only, and only starting feature 0. */
+ if ((s->aATAPICmd[1] & 0x03) == 3 || u16Sfn != 0)
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+ memset(pbBuf, '\0', 32);
+ ataH2BE_U32(pbBuf, 16);
+ /** @todo implement switching between CD-ROM and DVD-ROM profile (the only
+ * way to differentiate them right now is based on the image size). Also
+ * implement signalling "no current profile" if no medium is loaded. */
+ ataH2BE_U16(pbBuf + 6, 0x08); /* current profile: read-only CD */
+
+ ataH2BE_U16(pbBuf + 8, 0); /* feature 0: list of profiles supported */
+ pbBuf[10] = (0 << 2) | (1 << 1) | (1 || 0); /* version 0, persistent, current */
+ pbBuf[11] = 8; /* additional bytes for profiles */
+ /* The MMC-3 spec says that DVD-ROM read capability should be reported
+ * before CD-ROM read capability. */
+ ataH2BE_U16(pbBuf + 12, 0x10); /* profile: read-only DVD */
+ pbBuf[14] = (0 << 0); /* NOT current profile */
+ ataH2BE_U16(pbBuf + 16, 0x08); /* profile: read only CD */
+ pbBuf[18] = (1 << 0); /* current profile */
+ /* Other profiles we might want to add in the future: 0x40 (BD-ROM) and 0x50 (HDDVD-ROM) */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiGetEventStatusNotificationSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 8);
+
+ if (!(s->aATAPICmd[1] & 1))
+ {
+ /* no asynchronous operation supported */
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ uint32_t OldStatus, NewStatus;
+ do
+ {
+ OldStatus = ASMAtomicReadU32(&s->MediaEventStatus);
+ NewStatus = ATA_EVENT_STATUS_UNCHANGED;
+ switch (OldStatus)
+ {
+ case ATA_EVENT_STATUS_MEDIA_NEW:
+ /* mount */
+ ataH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x04;
+ pbBuf[3] = 0x5e;
+ pbBuf[4] = 0x02;
+ pbBuf[5] = 0x02;
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ break;
+
+ case ATA_EVENT_STATUS_MEDIA_CHANGED:
+ case ATA_EVENT_STATUS_MEDIA_REMOVED:
+ /* umount */
+ ataH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x04;
+ pbBuf[3] = 0x5e;
+ pbBuf[4] = 0x03;
+ pbBuf[5] = 0x00;
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ if (OldStatus == ATA_EVENT_STATUS_MEDIA_CHANGED)
+ NewStatus = ATA_EVENT_STATUS_MEDIA_NEW;
+ break;
+
+ case ATA_EVENT_STATUS_UNCHANGED:
+ default:
+ ataH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x01;
+ pbBuf[3] = 0x5e;
+ pbBuf[4] = 0x00;
+ pbBuf[5] = 0x00;
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ break;
+ }
+ } while (!ASMAtomicCmpXchgU32(&s->MediaEventStatus, NewStatus, OldStatus));
+
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiInquirySS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 36);
+ pbBuf[0] = 0x05; /* CD-ROM */
+ pbBuf[1] = 0x80; /* removable */
+#if 1/*ndef VBOX*/ /** @todo implement MESN + AENC. (async notification on removal and stuff.) */
+ pbBuf[2] = 0x00; /* ISO */
+ pbBuf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */
+#else
+ pbBuf[2] = 0x00; /* ISO */
+ pbBuf[3] = 0x91; /* format 1, MESN=1, AENC=9 ??? */
+#endif
+ pbBuf[4] = 31; /* additional length */
+ pbBuf[5] = 0; /* reserved */
+ pbBuf[6] = 0; /* reserved */
+ pbBuf[7] = 0; /* reserved */
+ ataSCSIPadStr(pbBuf + 8, "VBOX", 8);
+ ataSCSIPadStr(pbBuf + 16, "CD-ROM", 16);
+ ataSCSIPadStr(pbBuf + 32, "1.0", 4);
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiModeSenseErrorRecoverySS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 16);
+ ataH2BE_U16(&pbBuf[0], 16 + 6);
+ pbBuf[2] = 0x70;
+ pbBuf[3] = 0;
+ pbBuf[4] = 0;
+ pbBuf[5] = 0;
+ pbBuf[6] = 0;
+ pbBuf[7] = 0;
+
+ pbBuf[8] = 0x01;
+ pbBuf[9] = 0x06;
+ pbBuf[10] = 0x00;
+ pbBuf[11] = 0x05;
+ pbBuf[12] = 0x00;
+ pbBuf[13] = 0x00;
+ pbBuf[14] = 0x00;
+ pbBuf[15] = 0x00;
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiModeSenseCDStatusSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 40);
+ ataH2BE_U16(&pbBuf[0], 38);
+ pbBuf[2] = 0x70;
+ pbBuf[3] = 0;
+ pbBuf[4] = 0;
+ pbBuf[5] = 0;
+ pbBuf[6] = 0;
+ pbBuf[7] = 0;
+
+ pbBuf[8] = 0x2a;
+ pbBuf[9] = 30; /* page length */
+ pbBuf[10] = 0x08; /* DVD-ROM read support */
+ pbBuf[11] = 0x00; /* no write support */
+ /* The following claims we support audio play. This is obviously false,
+ * but the Linux generic CDROM support makes many features depend on this
+ * capability. If it's not set, this causes many things to be disabled. */
+ pbBuf[12] = 0x71; /* multisession support, mode 2 form 1/2 support, audio play */
+ pbBuf[13] = 0x00; /* no subchannel reads supported */
+ pbBuf[14] = (1 << 0) | (1 << 3) | (1 << 5); /* lock supported, eject supported, tray type loading mechanism */
+ if (s->pDrvMount->pfnIsLocked(s->pDrvMount))
+ pbBuf[14] |= 1 << 1; /* report lock state */
+ pbBuf[15] = 0; /* no subchannel reads supported, no separate audio volume control, no changer etc. */
+ ataH2BE_U16(&pbBuf[16], 5632); /* (obsolete) claim 32x speed support */
+ ataH2BE_U16(&pbBuf[18], 2); /* number of audio volume levels */
+ ataH2BE_U16(&pbBuf[20], s->cbIOBuffer / _1K); /* buffer size supported in Kbyte */
+ ataH2BE_U16(&pbBuf[22], 5632); /* (obsolete) current read speed 32x */
+ pbBuf[24] = 0; /* reserved */
+ pbBuf[25] = 0; /* reserved for digital audio (see idx 15) */
+ ataH2BE_U16(&pbBuf[26], 0); /* (obsolete) maximum write speed */
+ ataH2BE_U16(&pbBuf[28], 0); /* (obsolete) current write speed */
+ ataH2BE_U16(&pbBuf[30], 0); /* copy management revision supported 0=no CSS */
+ pbBuf[32] = 0; /* reserved */
+ pbBuf[33] = 0; /* reserved */
+ pbBuf[34] = 0; /* reserved */
+ pbBuf[35] = 1; /* rotation control CAV */
+ ataH2BE_U16(&pbBuf[36], 0); /* current write speed */
+ ataH2BE_U16(&pbBuf[38], 0); /* number of write speed performance descriptors */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiRequestSenseSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ memset(pbBuf, '\0', s->cbElementaryTransfer);
+ memcpy(pbBuf, s->abATAPISense, RT_MIN(s->cbElementaryTransfer, sizeof(s->abATAPISense)));
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiMechanismStatusSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 8);
+ ataH2BE_U16(pbBuf, 0);
+ /* no current LBA */
+ pbBuf[2] = 0;
+ pbBuf[3] = 0;
+ pbBuf[4] = 0;
+ pbBuf[5] = 1;
+ ataH2BE_U16(pbBuf + 6, 0);
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiReadTOCNormalSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer), *q, iStartTrack;
+ bool fMSF;
+ uint32_t cbSize;
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ fMSF = (s->aATAPICmd[1] >> 1) & 1;
+ iStartTrack = s->aATAPICmd[6];
+ if (iStartTrack > 1 && iStartTrack != 0xaa)
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+ q = pbBuf + 2;
+ *q++ = 1; /* first session */
+ *q++ = 1; /* last session */
+ if (iStartTrack <= 1)
+ {
+ *q++ = 0; /* reserved */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 1; /* track number */
+ *q++ = 0; /* reserved */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ ataLBA2MSF(q, 0);
+ q += 3;
+ }
+ else
+ {
+ /* sector 0 */
+ ataH2BE_U32(q, 0);
+ q += 4;
+ }
+ }
+ /* lead out track */
+ *q++ = 0; /* reserved */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 0xaa; /* track number */
+ *q++ = 0; /* reserved */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ ataLBA2MSF(q, s->cTotalSectors);
+ q += 3;
+ }
+ else
+ {
+ ataH2BE_U32(q, s->cTotalSectors);
+ q += 4;
+ }
+ cbSize = q - pbBuf;
+ ataH2BE_U16(pbBuf, cbSize - 2);
+ if (cbSize < s->cbTotalTransfer)
+ s->cbTotalTransfer = cbSize;
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiReadTOCMultiSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+ bool fMSF;
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 12);
+ fMSF = (s->aATAPICmd[1] >> 1) & 1;
+ /* multi session: only a single session defined */
+/** @todo double-check this stuff against what a real drive says for a CD-ROM (not a CD-R) with only a single data session. Maybe solve the problem with "cdrdao read-toc" not being able to figure out whether numbers are in BCD or hex. */
+ memset(pbBuf, 0, 12);
+ pbBuf[1] = 0x0a;
+ pbBuf[2] = 0x01;
+ pbBuf[3] = 0x01;
+ pbBuf[5] = 0x14; /* ADR, control */
+ pbBuf[6] = 1; /* first track in last complete session */
+ if (fMSF)
+ {
+ pbBuf[8] = 0; /* reserved */
+ ataLBA2MSF(&pbBuf[9], 0);
+ }
+ else
+ {
+ /* sector 0 */
+ ataH2BE_U32(pbBuf + 8, 0);
+ }
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static bool atapiReadTOCRawSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer), *q, iStartTrack;
+ bool fMSF;
+ uint32_t cbSize;
+
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ fMSF = (s->aATAPICmd[1] >> 1) & 1;
+ iStartTrack = s->aATAPICmd[6];
+
+ q = pbBuf + 2;
+ *q++ = 1; /* first session */
+ *q++ = 1; /* last session */
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa0; /* first track in program area */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ *q++ = 0;
+ *q++ = 1; /* first track */
+ *q++ = 0x00; /* disk type CD-DA or CD data */
+ *q++ = 0;
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa1; /* last track in program area */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ *q++ = 0;
+ *q++ = 1; /* last track */
+ *q++ = 0;
+ *q++ = 0;
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa2; /* lead-out */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ ataLBA2MSF(q, s->cTotalSectors);
+ q += 3;
+ }
+ else
+ {
+ ataH2BE_U32(q, s->cTotalSectors);
+ q += 4;
+ }
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 0; /* track number */
+ *q++ = 1; /* point */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ ataLBA2MSF(q, 0);
+ q += 3;
+ }
+ else
+ {
+ /* sector 0 */
+ ataH2BE_U32(q, 0);
+ q += 4;
+ }
+
+ cbSize = q - pbBuf;
+ ataH2BE_U16(pbBuf, cbSize - 2);
+ if (cbSize < s->cbTotalTransfer)
+ s->cbTotalTransfer = cbSize;
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiCmdOK(s);
+ return false;
+}
+
+
+static void atapiParseCmdVirtualATAPI(ATADevState *s)
+{
+ const uint8_t *pbPacket;
+ uint8_t *pbBuf;
+ uint32_t cbMax;
+
+ pbPacket = s->aATAPICmd;
+ pbBuf = s->CTX_SUFF(pbIOBuffer);
+ switch (pbPacket[0])
+ {
+ case SCSI_TEST_UNIT_READY:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ if (s->cNotifiedMediaChange-- > 2)
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ else
+ atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ }
+ else if (s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ atapiCmdOK(s);
+ else
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+ cbMax = ataBE2H_U16(pbPacket + 7);
+ ataStartTransfer(s, RT_MIN(cbMax, 8), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION, true);
+ break;
+ case SCSI_MODE_SENSE_10:
+ {
+ uint8_t uPageControl, uPageCode;
+ cbMax = ataBE2H_U16(pbPacket + 7);
+ uPageControl = pbPacket[2] >> 6;
+ uPageCode = pbPacket[2] & 0x3f;
+ switch (uPageControl)
+ {
+ case SCSI_PAGECONTROL_CURRENT:
+ switch (uPageCode)
+ {
+ case SCSI_MODEPAGE_ERROR_RECOVERY:
+ ataStartTransfer(s, RT_MIN(cbMax, 16), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY, true);
+ break;
+ case SCSI_MODEPAGE_CD_STATUS:
+ ataStartTransfer(s, RT_MIN(cbMax, 40), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS, true);
+ break;
+ default:
+ goto error_cmd;
+ }
+ break;
+ case SCSI_PAGECONTROL_CHANGEABLE:
+ goto error_cmd;
+ case SCSI_PAGECONTROL_DEFAULT:
+ goto error_cmd;
+ default:
+ case SCSI_PAGECONTROL_SAVED:
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
+ break;
+ }
+ }
+ break;
+ case SCSI_REQUEST_SENSE:
+ cbMax = pbPacket[4];
+ ataStartTransfer(s, RT_MIN(cbMax, 18), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_REQUEST_SENSE, true);
+ break;
+ case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+ if (s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ if (pbPacket[4] & 1)
+ s->pDrvMount->pfnLock(s->pDrvMount);
+ else
+ s->pDrvMount->pfnUnlock(s->pDrvMount);
+ atapiCmdOK(s);
+ }
+ else
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ case SCSI_READ_10:
+ case SCSI_READ_12:
+ {
+ uint32_t cSectors, iATAPILBA;
+
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ if (pbPacket[0] == SCSI_READ_10)
+ cSectors = ataBE2H_U16(pbPacket + 7);
+ else
+ cSectors = ataBE2H_U32(pbPacket + 6);
+ iATAPILBA = ataBE2H_U32(pbPacket + 2);
+ if (cSectors == 0)
+ {
+ atapiCmdOK(s);
+ break;
+ }
+ if ((uint64_t)iATAPILBA + cSectors > s->cTotalSectors)
+ {
+ /* Rate limited logging, one log line per second. For
+ * guests that insist on reading from places outside the
+ * valid area this often generates too many release log
+ * entries otherwise. */
+ static uint64_t uLastLogTS = 0;
+ if (RTTimeMilliTS() >= uLastLogTS + 1000)
+ {
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM block number %Ld invalid (READ)\n", s->iLUN, (uint64_t)iATAPILBA + cSectors));
+ uLastLogTS = RTTimeMilliTS();
+ }
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+ break;
+ }
+ atapiReadSectors(s, iATAPILBA, cSectors, 2048);
+ }
+ break;
+ case SCSI_READ_CD:
+ {
+ uint32_t cSectors, iATAPILBA;
+
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cSectors = (pbPacket[6] << 16) | (pbPacket[7] << 8) | pbPacket[8];
+ iATAPILBA = ataBE2H_U32(pbPacket + 2);
+ if (cSectors == 0)
+ {
+ atapiCmdOK(s);
+ break;
+ }
+ if ((uint64_t)iATAPILBA + cSectors > s->cTotalSectors)
+ {
+ /* Rate limited logging, one log line per second. For
+ * guests that insist on reading from places outside the
+ * valid area this often generates too many release log
+ * entries otherwise. */
+ static uint64_t uLastLogTS = 0;
+ if (RTTimeMilliTS() >= uLastLogTS + 1000)
+ {
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM block number %Ld invalid (READ CD)\n", s->iLUN, (uint64_t)iATAPILBA + cSectors));
+ uLastLogTS = RTTimeMilliTS();
+ }
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+ break;
+ }
+ switch (pbPacket[9] & 0xf8)
+ {
+ case 0x00:
+ /* nothing */
+ atapiCmdOK(s);
+ break;
+ case 0x10:
+ /* normal read */
+ atapiReadSectors(s, iATAPILBA, cSectors, 2048);
+ break;
+ case 0xf8:
+ /* read all data */
+ atapiReadSectors(s, iATAPILBA, cSectors, 2352);
+ break;
+ default:
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM sector format not supported (%#x)\n", s->iLUN, pbPacket[9] & 0xf8));
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ }
+ break;
+ case SCSI_SEEK_10:
+ {
+ uint32_t iATAPILBA;
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ iATAPILBA = ataBE2H_U32(pbPacket + 2);
+ if (iATAPILBA > s->cTotalSectors)
+ {
+ /* Rate limited logging, one log line per second. For
+ * guests that insist on seeking to places outside the
+ * valid area this often generates too many release log
+ * entries otherwise. */
+ static uint64_t uLastLogTS = 0;
+ if (RTTimeMilliTS() >= uLastLogTS + 1000)
+ {
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM block number %Ld invalid (SEEK)\n", s->iLUN, (uint64_t)iATAPILBA));
+ uLastLogTS = RTTimeMilliTS();
+ }
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+ break;
+ }
+ atapiCmdOK(s);
+ ataSetStatus(s, ATA_STAT_SEEK); /* Linux expects this. */
+ }
+ break;
+ case SCSI_START_STOP_UNIT:
+ {
+ int rc = VINF_SUCCESS;
+ switch (pbPacket[4] & 3)
+ {
+ case 0: /* 00 - Stop motor */
+ case 1: /* 01 - Start motor */
+ break;
+ case 2: /* 10 - Eject media */
+ /* This must be done from EMT. */
+ {
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
+ PVMREQ pReq;
+
+ PDMCritSectLeave(&pCtl->lock);
+ rc = VMR3ReqCall(PDMDevHlpGetVM(pDevIns), VMREQDEST_ANY, &pReq, RT_INDEFINITE_WAIT,
+ (PFNRT)s->pDrvMount->pfnUnmount, 2, s->pDrvMount, false);
+ AssertReleaseRC(rc);
+ VMR3ReqFree(pReq);
+ {
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+ }
+ }
+ break;
+ case 3: /* 11 - Load media */
+ /** @todo rc = s->pDrvMount->pfnLoadMedia(s->pDrvMount) */
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ atapiCmdOK(s);
+ else
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIA_LOAD_OR_EJECT_FAILED);
+ }
+ break;
+ case SCSI_MECHANISM_STATUS:
+ {
+ cbMax = ataBE2H_U16(pbPacket + 8);
+ ataStartTransfer(s, RT_MIN(cbMax, 8), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MECHANISM_STATUS, true);
+ }
+ break;
+ case SCSI_READ_TOC_PMA_ATIP:
+ {
+ uint8_t format;
+
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = ataBE2H_U16(pbPacket + 7);
+ /* SCSI MMC-3 spec says format is at offset 2 (lower 4 bits),
+ * but Linux kernel uses offset 9 (topmost 2 bits). Hope that
+ * the other field is clear... */
+ format = (pbPacket[2] & 0xf) | (pbPacket[9] >> 6);
+ switch (format)
+ {
+ case 0:
+ ataStartTransfer(s, cbMax, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_NORMAL, true);
+ break;
+ case 1:
+ ataStartTransfer(s, RT_MIN(cbMax, 12), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_MULTI, true);
+ break;
+ case 2:
+ ataStartTransfer(s, cbMax, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_RAW, true);
+ break;
+ default:
+ error_cmd:
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ }
+ break;
+ case SCSI_READ_CAPACITY:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ ataStartTransfer(s, 8, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_CAPACITY, true);
+ break;
+ case SCSI_READ_DISC_INFORMATION:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = ataBE2H_U16(pbPacket + 7);
+ ataStartTransfer(s, RT_MIN(cbMax, 34), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_DISC_INFORMATION, true);
+ break;
+ case SCSI_READ_TRACK_INFORMATION:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = ataBE2H_U16(pbPacket + 7);
+ ataStartTransfer(s, RT_MIN(cbMax, 36), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TRACK_INFORMATION, true);
+ break;
+ case SCSI_GET_CONFIGURATION:
+ /* No media change stuff here, it can confuse Linux guests. */
+ cbMax = ataBE2H_U16(pbPacket + 7);
+ ataStartTransfer(s, RT_MIN(cbMax, 32), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_CONFIGURATION, true);
+ break;
+ case SCSI_INQUIRY:
+ cbMax = ataBE2H_U16(pbPacket + 3);
+ ataStartTransfer(s, RT_MIN(cbMax, 36), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_INQUIRY, true);
+ break;
+ default:
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ break;
+ }
+}
+
+
+/*
+ * Parse ATAPI commands, passing them directly to the CD/DVD drive.
+ */
+static void atapiParseCmdPassthrough(ATADevState *s)
+{
+ const uint8_t *pbPacket;
+ uint8_t *pbBuf;
+ uint32_t cSectors, iATAPILBA;
+ uint32_t cbTransfer = 0;
+ PDMBLOCKTXDIR uTxDir = PDMBLOCKTXDIR_NONE;
+
+ pbPacket = s->aATAPICmd;
+ pbBuf = s->CTX_SUFF(pbIOBuffer);
+ switch (pbPacket[0])
+ {
+ case SCSI_BLANK:
+ goto sendcmd;
+ case SCSI_CLOSE_TRACK_SESSION:
+ goto sendcmd;
+ case SCSI_ERASE_10:
+ iATAPILBA = ataBE2H_U32(pbPacket + 2);
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ Log2(("ATAPI PT: lba %d\n", iATAPILBA));
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_FORMAT_UNIT:
+ cbTransfer = s->uATARegLCyl | (s->uATARegHCyl << 8); /* use ATAPI transfer length */
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_GET_CONFIGURATION:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ if (ASMAtomicReadU32(&s->MediaEventStatus) != ATA_EVENT_STATUS_UNCHANGED)
+ {
+ ataStartTransfer(s, RT_MIN(cbTransfer, 8), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION, true);
+ break;
+ }
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_GET_PERFORMANCE:
+ cbTransfer = s->uATARegLCyl | (s->uATARegHCyl << 8); /* use ATAPI transfer length */
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_INQUIRY:
+ cbTransfer = ataBE2H_U16(pbPacket + 3);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_LOAD_UNLOAD_MEDIUM:
+ goto sendcmd;
+ case SCSI_MECHANISM_STATUS:
+ cbTransfer = ataBE2H_U16(pbPacket + 8);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_MODE_SELECT_10:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_MODE_SENSE_10:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_PAUSE_RESUME:
+ goto sendcmd;
+ case SCSI_PLAY_AUDIO_10:
+ goto sendcmd;
+ case SCSI_PLAY_AUDIO_12:
+ goto sendcmd;
+ case SCSI_PLAY_AUDIO_MSF:
+ goto sendcmd;
+ case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+ /** @todo do not forget to unlock when a VM is shut down */
+ goto sendcmd;
+ case SCSI_READ_10:
+ iATAPILBA = ataBE2H_U32(pbPacket + 2);
+ cSectors = ataBE2H_U16(pbPacket + 7);
+ Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
+ s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+ cbTransfer = cSectors * s->cbATAPISector;
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_12:
+ iATAPILBA = ataBE2H_U32(pbPacket + 2);
+ cSectors = ataBE2H_U32(pbPacket + 6);
+ Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
+ s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+ cbTransfer = cSectors * s->cbATAPISector;
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_BUFFER:
+ cbTransfer = ataBE2H_U24(pbPacket + 6);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_BUFFER_CAPACITY:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_CAPACITY:
+ cbTransfer = 8;
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_CD:
+ s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+ cbTransfer = ataBE2H_U24(pbPacket + 6) / s->cbATAPISector * s->cbATAPISector;
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_CD_MSF:
+ cSectors = ataMSF2LBA(pbPacket + 6) - ataMSF2LBA(pbPacket + 3);
+ if (cSectors > 32)
+ cSectors = 32; /* Limit transfer size to 64~74K. Safety first. In any case this can only harm software doing CDDA extraction. */
+ s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+ cbTransfer = cSectors * s->cbATAPISector;
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_DISC_INFORMATION:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_DVD_STRUCTURE:
+ cbTransfer = ataBE2H_U16(pbPacket + 8);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_FORMAT_CAPACITIES:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_SUBCHANNEL:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_TOC_PMA_ATIP:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_READ_TRACK_INFORMATION:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_REPAIR_TRACK:
+ goto sendcmd;
+ case SCSI_REPORT_KEY:
+ cbTransfer = ataBE2H_U16(pbPacket + 8);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_REQUEST_SENSE:
+ cbTransfer = pbPacket[4];
+ if ((s->abATAPISense[2] & 0x0f) != SCSI_SENSE_NONE)
+ {
+ ataStartTransfer(s, RT_MIN(cbTransfer, 18), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_REQUEST_SENSE, true);
+ break;
+ }
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_RESERVE_TRACK:
+ goto sendcmd;
+ case SCSI_SCAN:
+ goto sendcmd;
+ case SCSI_SEEK_10:
+ goto sendcmd;
+ case SCSI_SEND_CUE_SHEET:
+ cbTransfer = ataBE2H_U24(pbPacket + 6);
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_SEND_DVD_STRUCTURE:
+ cbTransfer = ataBE2H_U16(pbPacket + 8);
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_SEND_EVENT:
+ cbTransfer = ataBE2H_U16(pbPacket + 8);
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_SEND_KEY:
+ cbTransfer = ataBE2H_U16(pbPacket + 8);
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_SEND_OPC_INFORMATION:
+ cbTransfer = ataBE2H_U16(pbPacket + 7);
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_SET_CD_SPEED:
+ goto sendcmd;
+ case SCSI_SET_READ_AHEAD:
+ goto sendcmd;
+ case SCSI_SET_STREAMING:
+ cbTransfer = ataBE2H_U16(pbPacket + 9);
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_START_STOP_UNIT:
+ goto sendcmd;
+ case SCSI_STOP_PLAY_SCAN:
+ goto sendcmd;
+ case SCSI_SYNCHRONIZE_CACHE:
+ goto sendcmd;
+ case SCSI_TEST_UNIT_READY:
+ goto sendcmd;
+ case SCSI_VERIFY_10:
+ goto sendcmd;
+ case SCSI_WRITE_10:
+ iATAPILBA = ataBE2H_U32(pbPacket + 2);
+ cSectors = ataBE2H_U16(pbPacket + 7);
+ Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
+#if 0
+ /* The sector size is determined by the async I/O thread. */
+ s->cbATAPISector = 0;
+ /* Preliminary, will be corrected once the sector size is known. */
+ cbTransfer = cSectors;
+#else
+ s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+ cbTransfer = cSectors * s->cbATAPISector;
+#endif
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_WRITE_12:
+ iATAPILBA = ataBE2H_U32(pbPacket + 2);
+ cSectors = ataBE2H_U32(pbPacket + 6);
+ Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
+#if 0
+ /* The sector size is determined by the async I/O thread. */
+ s->cbATAPISector = 0;
+ /* Preliminary, will be corrected once the sector size is known. */
+ cbTransfer = cSectors;
+#else
+ s->cbATAPISector = 2048; /**< @todo this size is not always correct */
+ cbTransfer = cSectors * s->cbATAPISector;
+#endif
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_WRITE_AND_VERIFY_10:
+ iATAPILBA = ataBE2H_U32(pbPacket + 2);
+ cSectors = ataBE2H_U16(pbPacket + 7);
+ Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
+ /* The sector size is determined by the async I/O thread. */
+ s->cbATAPISector = 0;
+ /* Preliminary, will be corrected once the sector size is known. */
+ cbTransfer = cSectors;
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ case SCSI_WRITE_BUFFER:
+ switch (pbPacket[1] & 0x1f)
+ {
+ case 0x04: /* download microcode */
+ case 0x05: /* download microcode and save */
+ case 0x06: /* download microcode with offsets */
+ case 0x07: /* download microcode with offsets and save */
+ case 0x0e: /* download microcode with offsets and defer activation */
+ case 0x0f: /* activate deferred microcode */
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough command attempted to update firmware, blocked\n", s->iLUN));
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ default:
+ cbTransfer = ataBE2H_U16(pbPacket + 6);
+ uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
+ goto sendcmd;
+ }
+ break;
+ case SCSI_REPORT_LUNS: /* Not part of MMC-3, but used by Windows. */
+ cbTransfer = ataBE2H_U32(pbPacket + 6);
+ uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
+ goto sendcmd;
+ case SCSI_REZERO_UNIT:
+ /* Obsolete command used by cdrecord. What else would one expect?
+ * This command is not sent to the drive, it is handled internally,
+ * as the Linux kernel doesn't like it (message "scsi: unknown
+ * opcode 0x01" in syslog) and replies with a sense code of 0,
+ * which sends cdrecord to an endless loop. */
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ break;
+ default:
+ LogRel(("PIIX3 ATA: LUN#%d: passthrough unimplemented for command %#x\n", s->iLUN, pbPacket[0]));
+ atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ break;
+ sendcmd:
+ /* Send a command to the drive, passing data in/out as required. */
+ Log2(("ATAPI PT: max size %d\n", cbTransfer));
+ Assert(cbTransfer <= s->cbIOBuffer);
+ if (cbTransfer == 0)
+ uTxDir = PDMBLOCKTXDIR_NONE;
+ ataStartTransfer(s, cbTransfer, uTxDir, ATAFN_BT_ATAPI_PASSTHROUGH_CMD, ATAFN_SS_ATAPI_PASSTHROUGH, true);
+ }
+}
+
+
+static void atapiParseCmd(ATADevState *s)
+{
+ const uint8_t *pbPacket;
+
+ pbPacket = s->aATAPICmd;
+#ifdef DEBUG
+ Log(("%s: LUN#%d DMA=%d CMD=%#04x \"%s\"\n", __FUNCTION__, s->iLUN, s->fDMA, pbPacket[0], SCSICmdText(pbPacket[0])));
+#else /* !DEBUG */
+ Log(("%s: LUN#%d DMA=%d CMD=%#04x\n", __FUNCTION__, s->iLUN, s->fDMA, pbPacket[0]));
+#endif /* !DEBUG */
+ Log2(("%s: limit=%#x packet: %.*Rhxs\n", __FUNCTION__, s->uATARegLCyl | (s->uATARegHCyl << 8), ATAPI_PACKET_SIZE, pbPacket));
+
+ if (s->fATAPIPassthrough)
+ atapiParseCmdPassthrough(s);
+ else
+ atapiParseCmdVirtualATAPI(s);
+}
+
+
+static bool ataPacketSS(ATADevState *s)
+{
+ s->fDMA = !!(s->uATARegFeature & 1);
+ memcpy(s->aATAPICmd, s->CTX_SUFF(pbIOBuffer), ATAPI_PACKET_SIZE);
+ s->uTxDir = PDMBLOCKTXDIR_NONE;
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ atapiParseCmd(s);
+ return false;
+}
+
+
+/**
+ * SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium removed" event
+ * from now on, regardless if there was a medium inserted or not.
+ */
+static void ataMediumRemoved(ATADevState *s)
+{
+ ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_MEDIA_REMOVED);
+}
+
+
+/**
+ * SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium inserted". If
+ * there was already a medium inserted, don't forget to send the "medium
+ * removed" event first.
+ */
+static void ataMediumInserted(ATADevState *s)
+{
+ uint32_t OldStatus, NewStatus;
+ do
+ {
+ OldStatus = ASMAtomicReadU32(&s->MediaEventStatus);
+ switch (OldStatus)
+ {
+ case ATA_EVENT_STATUS_MEDIA_CHANGED:
+ case ATA_EVENT_STATUS_MEDIA_REMOVED:
+ /* no change, we will send "medium removed" + "medium inserted" */
+ NewStatus = ATA_EVENT_STATUS_MEDIA_CHANGED;
+ break;
+ default:
+ NewStatus = ATA_EVENT_STATUS_MEDIA_NEW;
+ break;
+ }
+ } while (!ASMAtomicCmpXchgU32(&s->MediaEventStatus, NewStatus, OldStatus));
+}
+
+
+/**
+ * Called when a media is mounted.
+ *
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ */
+static DECLCALLBACK(void) ataMountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ ATADevState *pIf = PDMIMOUNTNOTIFY_2_ATASTATE(pInterface);
+ Log(("%s: changing LUN#%d\n", __FUNCTION__, pIf->iLUN));
+
+ /* Ignore the call if we're called while being attached. */
+ if (!pIf->pDrvBlock)
+ return;
+
+ if (pIf->fATAPI)
+ pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 2048;
+ else
+ pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 512;
+
+ /* Report media changed in TEST UNIT and other (probably incorrect) places. */
+ if (pIf->cNotifiedMediaChange < 2)
+ pIf->cNotifiedMediaChange = 2;
+ ataMediumInserted(pIf);
+}
+
+/**
+ * Called when a media is unmounted
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ */
+static DECLCALLBACK(void) ataUnmountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ ATADevState *pIf = PDMIMOUNTNOTIFY_2_ATASTATE(pInterface);
+ Log(("%s:\n", __FUNCTION__));
+ pIf->cTotalSectors = 0;
+
+ /*
+ * Whatever I do, XP will not use the GET MEDIA STATUS nor the EVENT stuff.
+ * However, it will respond to TEST UNIT with a 0x6 0x28 (media changed) sense code.
+ * So, we'll give it 4 TEST UNIT command to catch up, two which the media is not
+ * present and 2 in which it is changed.
+ */
+ pIf->cNotifiedMediaChange = 4;
+ ataMediumRemoved(pIf);
+}
+
+static void ataPacketBT(ATADevState *s)
+{
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ ataSetStatusValue(s, ATA_STAT_READY);
+}
+
+
+static void ataResetDevice(ATADevState *s)
+{
+ s->cMultSectors = ATA_MAX_MULT_SECTORS;
+ s->cNotifiedMediaChange = 0;
+ ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_UNCHANGED);
+ ataUnsetIRQ(s);
+
+ s->uATARegSelect = 0x20;
+ ataSetStatusValue(s, ATA_STAT_READY);
+ ataSetSignature(s);
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iIOBufferPIODataStart = 0;
+ s->iIOBufferPIODataEnd = 0;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+ s->fATAPITransfer = false;
+ s->uATATransferMode = ATA_MODE_UDMA | 2; /* PIIX3 supports only up to UDMA2 */
+
+ s->uATARegFeature = 0;
+}
+
+
+static bool ataExecuteDeviceDiagnosticSS(ATADevState *s)
+{
+ ataSetSignature(s);
+ if (s->fATAPI)
+ ataSetStatusValue(s, 0); /* NOTE: READY is _not_ set */
+ else
+ ataSetStatusValue(s, ATA_STAT_READY);
+ s->uATARegError = 0x01;
+ return false;
+}
+
+
+static void ataParseCmd(ATADevState *s, uint8_t cmd)
+{
+#ifdef DEBUG
+ Log(("%s: LUN#%d CMD=%#04x \"%s\"\n", __FUNCTION__, s->iLUN, cmd, ATACmdText(cmd)));
+#else /* !DEBUG */
+ Log(("%s: LUN#%d CMD=%#04x\n", __FUNCTION__, s->iLUN, cmd));
+#endif /* !DEBUG */
+ s->fLBA48 = false;
+ s->fDMA = false;
+ if (cmd == ATA_IDLE_IMMEDIATE)
+ {
+ /* Detect Linux timeout recovery, first tries IDLE IMMEDIATE (which
+ * would overwrite the failing command unfortunately), then RESET. */
+ int32_t uCmdWait = -1;
+ uint64_t uNow = RTTimeNanoTS();
+ if (s->u64CmdTS)
+ uCmdWait = (uNow - s->u64CmdTS) / 1000;
+ LogRel(("PIIX3 ATA: LUN#%d: IDLE IMMEDIATE, CmdIf=%#04x (%d usec ago)\n",
+ s->iLUN, s->uATARegCommand, uCmdWait));
+ }
+ s->uATARegCommand = cmd;
+ switch (cmd)
+ {
+ case ATA_IDENTIFY_DEVICE:
+ if (s->pDrvBlock && !s->fATAPI)
+ ataStartTransfer(s, 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_IDENTIFY, false);
+ else
+ {
+ if (s->fATAPI)
+ ataSetSignature(s);
+ ataCmdError(s, ABRT_ERR);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ }
+ break;
+ case ATA_INITIALIZE_DEVICE_PARAMETERS:
+ case ATA_RECALIBRATE:
+ ataCmdOK(s, ATA_STAT_SEEK);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_SET_MULTIPLE_MODE:
+ if ( s->uATARegNSector != 0
+ && ( s->uATARegNSector > ATA_MAX_MULT_SECTORS
+ || (s->uATARegNSector & (s->uATARegNSector - 1)) != 0))
+ {
+ ataCmdError(s, ABRT_ERR);
+ }
+ else
+ {
+ Log2(("%s: set multi sector count to %d\n", __FUNCTION__, s->uATARegNSector));
+ s->cMultSectors = s->uATARegNSector;
+ ataCmdOK(s, 0);
+ }
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_READ_VERIFY_SECTORS_EXT:
+ s->fLBA48 = true;
+ case ATA_READ_VERIFY_SECTORS:
+ case ATA_READ_VERIFY_SECTORS_WITHOUT_RETRIES:
+ /* do sector number check ? */
+ ataCmdOK(s, 0);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_READ_SECTORS_EXT:
+ s->fLBA48 = true;
+ case ATA_READ_SECTORS:
+ case ATA_READ_SECTORS_WITHOUT_RETRIES:
+ if (!s->pDrvBlock)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = 1;
+ ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+ break;
+ case ATA_WRITE_SECTORS_EXT:
+ s->fLBA48 = true;
+ case ATA_WRITE_SECTORS:
+ case ATA_WRITE_SECTORS_WITHOUT_RETRIES:
+ s->cSectorsPerIRQ = 1;
+ ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+ break;
+ case ATA_READ_MULTIPLE_EXT:
+ s->fLBA48 = true;
+ case ATA_READ_MULTIPLE:
+ if (!s->cMultSectors)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = s->cMultSectors;
+ ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+ break;
+ case ATA_WRITE_MULTIPLE_EXT:
+ s->fLBA48 = true;
+ case ATA_WRITE_MULTIPLE:
+ if (!s->cMultSectors)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = s->cMultSectors;
+ ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+ break;
+ case ATA_READ_DMA_EXT:
+ s->fLBA48 = true;
+ case ATA_READ_DMA:
+ case ATA_READ_DMA_WITHOUT_RETRIES:
+ if (!s->pDrvBlock)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
+ s->fDMA = true;
+ ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+ break;
+ case ATA_WRITE_DMA_EXT:
+ s->fLBA48 = true;
+ case ATA_WRITE_DMA:
+ case ATA_WRITE_DMA_WITHOUT_RETRIES:
+ if (!s->pDrvBlock)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
+ s->fDMA = true;
+ ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+ break;
+ case ATA_READ_NATIVE_MAX_ADDRESS_EXT:
+ s->fLBA48 = true;
+ ataSetSector(s, s->cTotalSectors - 1);
+ ataCmdOK(s, 0);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_SEEK: /* Used by the SCO OpenServer. Command is marked as obsolete */
+ ataCmdOK(s, 0);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_READ_NATIVE_MAX_ADDRESS:
+ ataSetSector(s, RT_MIN(s->cTotalSectors, 1 << 28) - 1);
+ ataCmdOK(s, 0);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_CHECK_POWER_MODE:
+ s->uATARegNSector = 0xff; /* drive active or idle */
+ ataCmdOK(s, 0);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_SET_FEATURES:
+ Log2(("%s: feature=%#x\n", __FUNCTION__, s->uATARegFeature));
+ if (!s->pDrvBlock)
+ goto abort_cmd;
+ switch (s->uATARegFeature)
+ {
+ case 0x02: /* write cache enable */
+ Log2(("%s: write cache enable\n", __FUNCTION__));
+ ataCmdOK(s, ATA_STAT_SEEK);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0xaa: /* read look-ahead enable */
+ Log2(("%s: read look-ahead enable\n", __FUNCTION__));
+ ataCmdOK(s, ATA_STAT_SEEK);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0x55: /* read look-ahead disable */
+ Log2(("%s: read look-ahead disable\n", __FUNCTION__));
+ ataCmdOK(s, ATA_STAT_SEEK);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0xcc: /* reverting to power-on defaults enable */
+ Log2(("%s: revert to power-on defaults enable\n", __FUNCTION__));
+ ataCmdOK(s, ATA_STAT_SEEK);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0x66: /* reverting to power-on defaults disable */
+ Log2(("%s: revert to power-on defaults disable\n", __FUNCTION__));
+ ataCmdOK(s, ATA_STAT_SEEK);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0x82: /* write cache disable */
+ Log2(("%s: write cache disable\n", __FUNCTION__));
+ /* As per the ATA/ATAPI-6 specs, a write cache disable
+ * command MUST flush the write buffers to disc. */
+ ataStartTransfer(s, 0, PDMBLOCKTXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
+ break;
+ case 0x03: { /* set transfer mode */
+ Log2(("%s: transfer mode %#04x\n", __FUNCTION__, s->uATARegNSector));
+ switch (s->uATARegNSector & 0xf8)
+ {
+ case 0x00: /* PIO default */
+ case 0x08: /* PIO mode */
+ break;
+ case ATA_MODE_MDMA: /* MDMA mode */
+ s->uATATransferMode = (s->uATARegNSector & 0xf8) | RT_MIN(s->uATARegNSector & 0x07, ATA_MDMA_MODE_MAX);
+ break;
+ case ATA_MODE_UDMA: /* UDMA mode */
+ s->uATATransferMode = (s->uATARegNSector & 0xf8) | RT_MIN(s->uATARegNSector & 0x07, ATA_UDMA_MODE_MAX);
+ break;
+ default:
+ goto abort_cmd;
+ }
+ ataCmdOK(s, ATA_STAT_SEEK);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ }
+ default:
+ goto abort_cmd;
+ }
+ /*
+ * OS/2 workarond:
+ * The OS/2 IDE driver from MCP2 appears to rely on the feature register being
+ * reset here. According to the specification, this is a driver bug as the register
+ * contents are undefined after the call. This means we can just as well reset it.
+ */
+ s->uATARegFeature = 0;
+ break;
+ case ATA_FLUSH_CACHE_EXT:
+ case ATA_FLUSH_CACHE:
+ if (!s->pDrvBlock || s->fATAPI)
+ goto abort_cmd;
+ ataStartTransfer(s, 0, PDMBLOCKTXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
+ break;
+ case ATA_STANDBY_IMMEDIATE:
+ ataCmdOK(s, 0);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_IDLE_IMMEDIATE:
+ LogRel(("PIIX3 ATA: LUN#%d: aborting current command\n", s->iLUN));
+ ataAbortCurrentCommand(s, false);
+ break;
+ /* ATAPI commands */
+ case ATA_IDENTIFY_PACKET_DEVICE:
+ if (s->fATAPI)
+ ataStartTransfer(s, 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_ATAPI_IDENTIFY, false);
+ else
+ {
+ ataCmdError(s, ABRT_ERR);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ }
+ break;
+ case ATA_EXECUTE_DEVICE_DIAGNOSTIC:
+ ataStartTransfer(s, 0, PDMBLOCKTXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_EXECUTE_DEVICE_DIAGNOSTIC, false);
+ break;
+ case ATA_DEVICE_RESET:
+ if (!s->fATAPI)
+ goto abort_cmd;
+ LogRel(("PIIX3 ATA: LUN#%d: performing device RESET\n", s->iLUN));
+ ataAbortCurrentCommand(s, true);
+ break;
+ case ATA_PACKET:
+ if (!s->fATAPI)
+ goto abort_cmd;
+ /* overlapping commands not supported */
+ if (s->uATARegFeature & 0x02)
+ goto abort_cmd;
+ ataStartTransfer(s, ATAPI_PACKET_SIZE, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_PACKET, ATAFN_SS_PACKET, false);
+ break;
+ default:
+ abort_cmd:
+ ataCmdError(s, ABRT_ERR);
+ ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ }
+}
+
+
+/**
+ * Waits for a particular async I/O thread to complete whatever it
+ * is doing at the moment.
+ *
+ * @returns true on success.
+ * @returns false when the thread is still processing.
+ * @param pThis Pointer to the controller data.
+ * @param cMillies How long to wait (total).
+ */
+static bool ataWaitForAsyncIOIsIdle(PATACONTROLLER pCtl, unsigned cMillies)
+{
+ uint64_t u64Start;
+
+ /*
+ * Wait for any pending async operation to finish
+ */
+ u64Start = RTTimeMilliTS();
+ for (;;)
+ {
+ if (ataAsyncIOIsIdle(pCtl, false))
+ return true;
+ if (RTTimeMilliTS() - u64Start >= cMillies)
+ break;
+
+ /* Sleep for a bit. */
+ RTThreadSleep(100);
+ }
+
+ return false;
+}
+
+#endif /* IN_RING3 */
+
+static int ataIOPortWriteU8(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ Log2(("%s: write addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
+ addr &= 7;
+ switch (addr)
+ {
+ case 0:
+ break;
+ case 1: /* feature register */
+ /* NOTE: data is written to the two drives */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegFeatureHOB = pCtl->aIfs[0].uATARegFeature;
+ pCtl->aIfs[1].uATARegFeatureHOB = pCtl->aIfs[1].uATARegFeature;
+ pCtl->aIfs[0].uATARegFeature = val;
+ pCtl->aIfs[1].uATARegFeature = val;
+ break;
+ case 2: /* sector count */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegNSectorHOB = pCtl->aIfs[0].uATARegNSector;
+ pCtl->aIfs[1].uATARegNSectorHOB = pCtl->aIfs[1].uATARegNSector;
+ pCtl->aIfs[0].uATARegNSector = val;
+ pCtl->aIfs[1].uATARegNSector = val;
+ break;
+ case 3: /* sector number */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegSectorHOB = pCtl->aIfs[0].uATARegSector;
+ pCtl->aIfs[1].uATARegSectorHOB = pCtl->aIfs[1].uATARegSector;
+ pCtl->aIfs[0].uATARegSector = val;
+ pCtl->aIfs[1].uATARegSector = val;
+ break;
+ case 4: /* cylinder low */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegLCylHOB = pCtl->aIfs[0].uATARegLCyl;
+ pCtl->aIfs[1].uATARegLCylHOB = pCtl->aIfs[1].uATARegLCyl;
+ pCtl->aIfs[0].uATARegLCyl = val;
+ pCtl->aIfs[1].uATARegLCyl = val;
+ break;
+ case 5: /* cylinder high */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegHCylHOB = pCtl->aIfs[0].uATARegHCyl;
+ pCtl->aIfs[1].uATARegHCylHOB = pCtl->aIfs[1].uATARegHCyl;
+ pCtl->aIfs[0].uATARegHCyl = val;
+ pCtl->aIfs[1].uATARegHCyl = val;
+ break;
+ case 6: /* drive/head */
+ pCtl->aIfs[0].uATARegSelect = (val & ~0x10) | 0xa0;
+ pCtl->aIfs[1].uATARegSelect = (val | 0x10) | 0xa0;
+ if (((val >> 4) & 1) != pCtl->iSelectedIf)
+ {
+ PPDMDEVINS pDevIns = CONTROLLER_2_DEVINS(pCtl);
+
+ /* select another drive */
+ pCtl->iSelectedIf = (val >> 4) & 1;
+ /* The IRQ line is multiplexed between the two drives, so
+ * update the state when switching to another drive. Only need
+ * to update interrupt line if it is enabled and there is a
+ * state change. */
+ if ( !(pCtl->aIfs[pCtl->iSelectedIf].uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ)
+ && ( pCtl->aIfs[pCtl->iSelectedIf].fIrqPending
+ != pCtl->aIfs[pCtl->iSelectedIf ^ 1].fIrqPending))
+ {
+ if (pCtl->aIfs[pCtl->iSelectedIf].fIrqPending)
+ {
+ Log2(("%s: LUN#%d asserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+ /* The BMDMA unit unconditionally sets BM_STATUS_INT if
+ * the interrupt line is asserted. It monitors the line
+ * for a rising edge. */
+ pCtl->BmDma.u8Status |= BM_STATUS_INT;
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrqNoWait(pDevIns, 0, 1);
+ else
+ PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 1);
+ }
+ else
+ {
+ Log2(("%s: LUN#%d deasserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrqNoWait(pDevIns, 0, 0);
+ else
+ PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 0);
+ }
+ }
+ }
+ break;
+ default:
+ case 7: /* command */
+ /* ignore commands to non existant slave */
+ if (pCtl->iSelectedIf && !pCtl->aIfs[pCtl->iSelectedIf].pDrvBlock)
+ break;
+#ifndef IN_RING3
+ /* Don't do anything complicated in GC */
+ return VINF_IOM_HC_IOPORT_WRITE;
+#else /* IN_RING3 */
+ ataParseCmd(&pCtl->aIfs[pCtl->iSelectedIf], val);
+#endif /* !IN_RING3 */
+ }
+ return VINF_SUCCESS;
+}
+
+
+static int ataIOPortReadU8(PATACONTROLLER pCtl, uint32_t addr, uint32_t *pu32)
+{
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+ uint32_t val;
+ bool fHOB;
+
+ fHOB = !!(s->uATARegDevCtl & (1 << 7));
+ switch (addr & 7)
+ {
+ case 0: /* data register */
+ val = 0xff;
+ break;
+ case 1: /* error register */
+ /* The ATA specification is very terse when it comes to specifying
+ * the precise effects of reading back the error/feature register.
+ * The error register (read-only) shares the register number with
+ * the feature register (write-only), so it seems that it's not
+ * necessary to support the usual HOB readback here. */
+ if (!s->pDrvBlock)
+ val = 0;
+ else
+ val = s->uATARegError;
+ break;
+ case 2: /* sector count */
+ if (!s->pDrvBlock)
+ val = 0;
+ else if (fHOB)
+ val = s->uATARegNSectorHOB;
+ else
+ val = s->uATARegNSector;
+ break;
+ case 3: /* sector number */
+ if (!s->pDrvBlock)
+ val = 0;
+ else if (fHOB)
+ val = s->uATARegSectorHOB;
+ else
+ val = s->uATARegSector;
+ break;
+ case 4: /* cylinder low */
+ if (!s->pDrvBlock)
+ val = 0;
+ else if (fHOB)
+ val = s->uATARegLCylHOB;
+ else
+ val = s->uATARegLCyl;
+ break;
+ case 5: /* cylinder high */
+ if (!s->pDrvBlock)
+ val = 0;
+ else if (fHOB)
+ val = s->uATARegHCylHOB;
+ else
+ val = s->uATARegHCyl;
+ break;
+ case 6: /* drive/head */
+ /* This register must always work as long as there is at least
+ * one drive attached to the controller. It is common between
+ * both drives anyway (completely identical content). */
+ if (!pCtl->aIfs[0].pDrvBlock && !pCtl->aIfs[1].pDrvBlock)
+ val = 0;
+ else
+ val = s->uATARegSelect;
+ break;
+ default:
+ case 7: /* primary status */
+ {
+ /* Counter for number of busy status seen in GC in a row. */
+ static unsigned cBusy = 0;
+
+ if (!s->pDrvBlock)
+ val = 0;
+ else
+ val = s->uATARegStatus;
+
+ /* Give the async I/O thread an opportunity to make progress,
+ * don't let it starve by guests polling frequently. EMT has a
+ * lower priority than the async I/O thread, but sometimes the
+ * host OS doesn't care. With some guests we are only allowed to
+ * be busy for about 5 milliseconds in some situations. Note that
+ * this is no guarantee for any other VBox thread getting
+ * scheduled, so this just lowers the CPU load a bit when drives
+ * are busy. It cannot help with timing problems. */
+ if (val & ATA_STAT_BUSY)
+ {
+#ifdef IN_RING3
+ cBusy = 0;
+ PDMCritSectLeave(&pCtl->lock);
+
+#ifndef RT_OS_WINDOWS
+ /*
+ * The thread might be stuck in an I/O operation
+ * due to a high I/O load on the host. (see @bugref{3301})
+ * To perform the reset successfully
+ * we interrupt the operation by sending a signal to the thread
+ * if the thread didn't responded in 10ms.
+ * This works only on POSIX hosts (Windows has a CancelSynchronousIo function which
+ * does the same but it was introduced with Vista) but so far
+ * this hang was only observed on Linux and Mac OS X.
+ *
+ * This is a workaround and needs to be solved properly.
+ */
+ if (pCtl->fReset)
+ {
+ uint64_t u64ResetTimeStop = RTTimeMilliTS();
+
+ if ((u64ResetTimeStop - pCtl->u64ResetTime) >= 10)
+ {
+ LogRel(("PIIX3 ATA: Async I/O thread probably stuck in operation, interrupting\n"));
+ pCtl->u64ResetTime = u64ResetTimeStop;
+ RTThreadPoke(pCtl->AsyncIOThread);
+ }
+ }
+#endif
+
+ RTThreadYield();
+
+ {
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+ }
+
+ val = s->uATARegStatus;
+#else /* !IN_RING3 */
+ /* Cannot yield CPU in guest context. And switching to host
+ * context for each and every busy status is too costly,
+ * especially on SMP systems where we don't gain much by
+ * yielding the CPU to someone else. */
+ if (++cBusy >= 20)
+ {
+ cBusy = 0;
+ return VINF_IOM_HC_IOPORT_READ;
+ }
+#endif /* !IN_RING3 */
+ }
+ else
+ cBusy = 0;
+ ataUnsetIRQ(s);
+ break;
+ }
+ }
+ Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
+ *pu32 = val;
+ return VINF_SUCCESS;
+}
+
+
+static uint32_t ataStatusRead(PATACONTROLLER pCtl, uint32_t addr)
+{
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+ uint32_t val;
+
+ if ((!pCtl->aIfs[0].pDrvBlock && !pCtl->aIfs[1].pDrvBlock) ||
+ (pCtl->iSelectedIf == 1 && !s->pDrvBlock))
+ val = 0;
+ else
+ val = s->uATARegStatus;
+ Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+static int ataControlWrite(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+#ifndef IN_RING3
+ if ((val ^ pCtl->aIfs[0].uATARegDevCtl) & ATA_DEVCTL_RESET)
+ return VINF_IOM_HC_IOPORT_WRITE; /* The RESET stuff is too complicated for GC. */
+#endif /* !IN_RING3 */
+
+ Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
+ /* RESET is common for both drives attached to a controller. */
+ if (!(pCtl->aIfs[0].uATARegDevCtl & ATA_DEVCTL_RESET) &&
+ (val & ATA_DEVCTL_RESET))
+ {
+#ifdef IN_RING3
+ /* Software RESET low to high */
+ int32_t uCmdWait0 = -1, uCmdWait1 = -1;
+ uint64_t uNow = RTTimeNanoTS();
+ if (pCtl->aIfs[0].u64CmdTS)
+ uCmdWait0 = (uNow - pCtl->aIfs[0].u64CmdTS) / 1000;
+ if (pCtl->aIfs[1].u64CmdTS)
+ uCmdWait1 = (uNow - pCtl->aIfs[1].u64CmdTS) / 1000;
+ LogRel(("PIIX3 ATA: Ctl#%d: RESET, DevSel=%d AIOIf=%d CmdIf0=%#04x (%d usec ago) CmdIf1=%#04x (%d usec ago)\n",
+ ATACONTROLLER_IDX(pCtl), pCtl->iSelectedIf, pCtl->iAIOIf,
+ pCtl->aIfs[0].uATARegCommand, uCmdWait0,
+ pCtl->aIfs[1].uATARegCommand, uCmdWait1));
+ pCtl->fReset = true;
+ /* Everything must be done after the reset flag is set, otherwise
+ * there are unavoidable races with the currently executing request
+ * (which might just finish in the mean time). */
+ pCtl->fChainedTransfer = false;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+ {
+ ataResetDevice(&pCtl->aIfs[i]);
+ /* The following cannot be done using ataSetStatusValue() since the
+ * reset flag is already set, which suppresses all status changes. */
+ pCtl->aIfs[i].uATARegStatus = ATA_STAT_BUSY | ATA_STAT_SEEK;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, pCtl->aIfs[i].iLUN, pCtl->aIfs[i].uATARegStatus));
+ pCtl->aIfs[i].uATARegError = 0x01;
+ }
+ ataAsyncIOClearRequests(pCtl);
+ Log2(("%s: Ctl#%d: message to async I/O thread, resetA\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ if (val & ATA_DEVCTL_HOB)
+ {
+ val &= ~ATA_DEVCTL_HOB;
+ Log2(("%s: ignored setting HOB\n", __FUNCTION__));
+ }
+
+ /* Save the timestamp we started the reset. */
+ pCtl->u64ResetTime = RTTimeMilliTS();
+
+ /* Issue the reset request now. */
+ ataAsyncIOPutRequest(pCtl, &ataResetARequest);
+#else /* !IN_RING3 */
+ AssertMsgFailed(("RESET handling is too complicated for GC\n"));
+#endif /* IN_RING3 */
+ }
+ else if ((pCtl->aIfs[0].uATARegDevCtl & ATA_DEVCTL_RESET) &&
+ !(val & ATA_DEVCTL_RESET))
+ {
+#ifdef IN_RING3
+ /* Software RESET high to low */
+ Log(("%s: deasserting RESET\n", __FUNCTION__));
+ Log2(("%s: Ctl#%d: message to async I/O thread, resetC\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ if (val & ATA_DEVCTL_HOB)
+ {
+ val &= ~ATA_DEVCTL_HOB;
+ Log2(("%s: ignored setting HOB\n", __FUNCTION__));
+ }
+ ataAsyncIOPutRequest(pCtl, &ataResetCRequest);
+#else /* !IN_RING3 */
+ AssertMsgFailed(("RESET handling is too complicated for GC\n"));
+#endif /* IN_RING3 */
+ }
+
+ /* Change of interrupt disable flag. Update interrupt line if interrupt
+ * is pending on the current interface. */
+ if ((val ^ pCtl->aIfs[0].uATARegDevCtl) & ATA_DEVCTL_DISABLE_IRQ
+ && pCtl->aIfs[pCtl->iSelectedIf].fIrqPending)
+ {
+ if (!(val & ATA_DEVCTL_DISABLE_IRQ))
+ {
+ Log2(("%s: LUN#%d asserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+ /* The BMDMA unit unconditionally sets BM_STATUS_INT if the
+ * interrupt line is asserted. It monitors the line for a rising
+ * edge. */
+ pCtl->BmDma.u8Status |= BM_STATUS_INT;
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), 0, 1);
+ else
+ PDMDevHlpISASetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), pCtl->irq, 1);
+ }
+ else
+ {
+ Log2(("%s: LUN#%d deasserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), 0, 0);
+ else
+ PDMDevHlpISASetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), pCtl->irq, 0);
+ }
+ }
+
+ if (val & ATA_DEVCTL_HOB)
+ Log2(("%s: set HOB\n", __FUNCTION__));
+
+ pCtl->aIfs[0].uATARegDevCtl = val;
+ pCtl->aIfs[1].uATARegDevCtl = val;
+
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+static void ataPIOTransfer(PATACONTROLLER pCtl)
+{
+ ATADevState *s;
+
+ s = &pCtl->aIfs[pCtl->iAIOIf];
+ Log3(("%s: if=%p\n", __FUNCTION__, s));
+
+ if (s->cbTotalTransfer && s->iIOBufferCur > s->iIOBufferEnd)
+ {
+ LogRel(("PIIX3 ATA: LUN#%d: %s data in the middle of a PIO transfer - VERY SLOW\n", s->iLUN, s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE ? "loading" : "storing"));
+ /* Any guest OS that triggers this case has a pathetic ATA driver.
+ * In a real system it would block the CPU via IORDY, here we do it
+ * very similarly by not continuing with the current instruction
+ * until the transfer to/from the storage medium is completed. */
+ if (s->iSourceSink != ATAFN_SS_NULL)
+ {
+ bool fRedo;
+ uint8_t status = s->uATARegStatus;
+ ataSetStatusValue(s, ATA_STAT_BUSY);
+ Log2(("%s: calling source/sink function\n", __FUNCTION__));
+ fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo))
+ return;
+ ataSetStatusValue(s, status);
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ }
+ if (s->cbTotalTransfer)
+ {
+ if (s->fATAPITransfer)
+ ataPIOTransferLimitATAPI(s);
+
+ if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE && s->cbElementaryTransfer > s->cbTotalTransfer)
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+
+ Log2(("%s: %s tx_size=%d elem_tx_size=%d index=%d end=%d\n",
+ __FUNCTION__, s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE ? "T2I" : "I2T",
+ s->cbTotalTransfer, s->cbElementaryTransfer,
+ s->iIOBufferCur, s->iIOBufferEnd));
+ ataPIOTransferStart(s, s->iIOBufferCur, s->cbElementaryTransfer);
+ s->cbTotalTransfer -= s->cbElementaryTransfer;
+ s->iIOBufferCur += s->cbElementaryTransfer;
+
+ if (s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE && s->cbElementaryTransfer > s->cbTotalTransfer)
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ }
+ else
+ ataPIOTransferStop(s);
+}
+
+
+DECLINLINE(void) ataPIOTransferFinish(PATACONTROLLER pCtl, ATADevState *s)
+{
+ /* Do not interfere with RESET processing if the PIO transfer finishes
+ * while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed continuing PIO transfer as RESET is active\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ return;
+ }
+
+ if ( s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE
+ || ( s->iSourceSink != ATAFN_SS_NULL
+ && s->iIOBufferCur >= s->iIOBufferEnd))
+ {
+ /* Need to continue the transfer in the async I/O thread. This is
+ * the case for write operations or generally for not yet finished
+ * transfers (some data might need to be read). */
+ ataUnsetStatus(s, ATA_STAT_READY | ATA_STAT_DRQ);
+ ataSetStatus(s, ATA_STAT_BUSY);
+
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing PIO transfer\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ ataAsyncIOPutRequest(pCtl, &ataPIORequest);
+ }
+ else
+ {
+ /* Either everything finished (though some data might still be pending)
+ * or some data is pending before the next read is due. */
+
+ /* Continue a previously started transfer. */
+ ataUnsetStatus(s, ATA_STAT_DRQ);
+ ataSetStatus(s, ATA_STAT_READY);
+
+ if (s->cbTotalTransfer)
+ {
+ /* There is more to transfer, happens usually for large ATAPI
+ * reads - the protocol limits the chunk size to 65534 bytes. */
+ ataPIOTransfer(pCtl);
+ ataSetIRQ(s);
+ }
+ else
+ {
+ Log2(("%s: Ctl#%d: skipping message to async I/O thread, ending PIO transfer\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ /* Finish PIO transfer. */
+ ataPIOTransfer(pCtl);
+ Assert(!pCtl->fRedo);
+ }
+ }
+}
+
+#endif /* IN_RING3 */
+
+static int ataDataWrite(PATACONTROLLER pCtl, uint32_t addr, uint32_t cbSize, const uint8_t *pbBuf)
+{
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+ uint8_t *p;
+
+ if (s->iIOBufferPIODataStart < s->iIOBufferPIODataEnd)
+ {
+ Assert(s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE);
+ p = s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart;
+#ifndef IN_RING3
+ /* All but the last transfer unit is simple enough for GC, but
+ * sending a request to the async IO thread is too complicated. */
+ if (s->iIOBufferPIODataStart + cbSize < s->iIOBufferPIODataEnd)
+ {
+ memcpy(p, pbBuf, cbSize);
+ s->iIOBufferPIODataStart += cbSize;
+ }
+ else
+ return VINF_IOM_HC_IOPORT_WRITE;
+#else /* IN_RING3 */
+ memcpy(p, pbBuf, cbSize);
+ s->iIOBufferPIODataStart += cbSize;
+ if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+ ataPIOTransferFinish(pCtl, s);
+#endif /* !IN_RING3 */
+ }
+ else
+ Log2(("%s: DUMMY data\n", __FUNCTION__));
+ Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, addr, cbSize, pbBuf));
+ return VINF_SUCCESS;
+}
+
+static int ataDataRead(PATACONTROLLER pCtl, uint32_t addr, uint32_t cbSize, uint8_t *pbBuf)
+{
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+ uint8_t *p;
+
+ if (s->iIOBufferPIODataStart < s->iIOBufferPIODataEnd)
+ {
+ Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
+ p = s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart;
+#ifndef IN_RING3
+ /* All but the last transfer unit is simple enough for GC, but
+ * sending a request to the async IO thread is too complicated. */
+ if (s->iIOBufferPIODataStart + cbSize < s->iIOBufferPIODataEnd)
+ {
+ memcpy(pbBuf, p, cbSize);
+ s->iIOBufferPIODataStart += cbSize;
+ }
+ else
+ return VINF_IOM_HC_IOPORT_READ;
+#else /* IN_RING3 */
+ memcpy(pbBuf, p, cbSize);
+ s->iIOBufferPIODataStart += cbSize;
+ if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+ ataPIOTransferFinish(pCtl, s);
+#endif /* !IN_RING3 */
+ }
+ else
+ {
+ Log2(("%s: DUMMY data\n", __FUNCTION__));
+ memset(pbBuf, '\xff', cbSize);
+ }
+ Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, addr, cbSize, pbBuf));
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+static void ataDMATransferStop(ATADevState *s)
+{
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+/**
+ * Perform the entire DMA transfer in one go (unless a source/sink operation
+ * has to be redone or a RESET comes in between). Unlike the PIO counterpart
+ * this function cannot handle empty transfers.
+ *
+ * @param pCtl Controller for which to perform the transfer.
+ */
+static void ataDMATransfer(PATACONTROLLER pCtl)
+{
+ PPDMDEVINS pDevIns = CONTROLLER_2_DEVINS(pCtl);
+ ATADevState *s = &pCtl->aIfs[pCtl->iAIOIf];
+ bool fRedo;
+ RTGCPHYS32 pDesc;
+ uint32_t cbTotalTransfer, cbElementaryTransfer;
+ uint32_t iIOBufferCur, iIOBufferEnd;
+ uint32_t dmalen;
+ PDMBLOCKTXDIR uTxDir;
+ bool fLastDesc = false;
+
+ Assert(sizeof(BMDMADesc) == 8);
+
+ fRedo = pCtl->fRedo;
+ if (RT_LIKELY(!fRedo))
+ Assert(s->cbTotalTransfer);
+ uTxDir = (PDMBLOCKTXDIR)s->uTxDir;
+ cbTotalTransfer = s->cbTotalTransfer;
+ cbElementaryTransfer = s->cbElementaryTransfer;
+ iIOBufferCur = s->iIOBufferCur;
+ iIOBufferEnd = s->iIOBufferEnd;
+
+ /* The DMA loop is designed to hold the lock only when absolutely
+ * necessary. This avoids long freezes should the guest access the
+ * ATA registers etc. for some reason. */
+ PDMCritSectLeave(&pCtl->lock);
+
+ Log2(("%s: %s tx_size=%d elem_tx_size=%d index=%d end=%d\n",
+ __FUNCTION__, uTxDir == PDMBLOCKTXDIR_FROM_DEVICE ? "T2I" : "I2T",
+ cbTotalTransfer, cbElementaryTransfer,
+ iIOBufferCur, iIOBufferEnd));
+ for (pDesc = pCtl->pFirstDMADesc; pDesc <= pCtl->pLastDMADesc; pDesc += sizeof(BMDMADesc))
+ {
+ BMDMADesc DMADesc;
+ RTGCPHYS32 pBuffer;
+ uint32_t cbBuffer;
+
+ if (RT_UNLIKELY(fRedo))
+ {
+ pBuffer = pCtl->pRedoDMABuffer;
+ cbBuffer = pCtl->cbRedoDMABuffer;
+ fLastDesc = pCtl->fRedoDMALastDesc;
+ }
+ else
+ {
+ PDMDevHlpPhysRead(pDevIns, pDesc, &DMADesc, sizeof(BMDMADesc));
+ pBuffer = RT_LE2H_U32(DMADesc.pBuffer);
+ cbBuffer = RT_LE2H_U32(DMADesc.cbBuffer);
+ fLastDesc = !!(cbBuffer & 0x80000000);
+ cbBuffer &= 0xfffe;
+ if (cbBuffer == 0)
+ cbBuffer = 0x10000;
+ if (cbBuffer > cbTotalTransfer)
+ cbBuffer = cbTotalTransfer;
+ }
+
+ while (RT_UNLIKELY(fRedo) || (cbBuffer && cbTotalTransfer))
+ {
+ if (RT_LIKELY(!fRedo))
+ {
+ dmalen = RT_MIN(cbBuffer, iIOBufferEnd - iIOBufferCur);
+ Log2(("%s: DMA desc %#010x: addr=%#010x size=%#010x\n", __FUNCTION__,
+ (int)pDesc, pBuffer, cbBuffer));
+ if (uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+ PDMDevHlpPhysWrite(pDevIns, pBuffer, s->CTX_SUFF(pbIOBuffer) + iIOBufferCur, dmalen);
+ else
+ PDMDevHlpPhysRead(pDevIns, pBuffer, s->CTX_SUFF(pbIOBuffer) + iIOBufferCur, dmalen);
+ iIOBufferCur += dmalen;
+ cbTotalTransfer -= dmalen;
+ cbBuffer -= dmalen;
+ pBuffer += dmalen;
+ }
+ if ( iIOBufferCur == iIOBufferEnd
+ && (uTxDir == PDMBLOCKTXDIR_TO_DEVICE || cbTotalTransfer))
+ {
+ if (uTxDir == PDMBLOCKTXDIR_FROM_DEVICE && cbElementaryTransfer > cbTotalTransfer)
+ cbElementaryTransfer = cbTotalTransfer;
+
+ {
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+ }
+
+ /* The RESET handler could have cleared the DMA transfer
+ * state (since we didn't hold the lock until just now
+ * the guest can continue in parallel). If so, the state
+ * is already set up so the loop is exited immediately. */
+ if (s->iSourceSink != ATAFN_SS_NULL)
+ {
+ s->iIOBufferCur = iIOBufferCur;
+ s->iIOBufferEnd = iIOBufferEnd;
+ s->cbElementaryTransfer = cbElementaryTransfer;
+ s->cbTotalTransfer = cbTotalTransfer;
+ Log2(("%s: calling source/sink function\n", __FUNCTION__));
+ fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+ if (RT_UNLIKELY(fRedo))
+ {
+ pCtl->pFirstDMADesc = pDesc;
+ pCtl->pRedoDMABuffer = pBuffer;
+ pCtl->cbRedoDMABuffer = cbBuffer;
+ pCtl->fRedoDMALastDesc = fLastDesc;
+ }
+ else
+ {
+ cbTotalTransfer = s->cbTotalTransfer;
+ cbElementaryTransfer = s->cbElementaryTransfer;
+
+ if (uTxDir == PDMBLOCKTXDIR_TO_DEVICE && cbElementaryTransfer > cbTotalTransfer)
+ cbElementaryTransfer = cbTotalTransfer;
+ iIOBufferCur = 0;
+ iIOBufferEnd = cbElementaryTransfer;
+ }
+ pCtl->fRedo = fRedo;
+ }
+ else
+ {
+ /* This forces the loop to exit immediately. */
+ pDesc = pCtl->pLastDMADesc + 1;
+ }
+
+ PDMCritSectLeave(&pCtl->lock);
+ if (RT_UNLIKELY(fRedo))
+ break;
+ }
+ }
+
+ if (RT_UNLIKELY(fRedo))
+ break;
+
+ /* end of transfer */
+ if (!cbTotalTransfer || fLastDesc)
+ break;
+
+ {
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+ }
+
+ if (!(pCtl->BmDma.u8Cmd & BM_CMD_START) || pCtl->fReset)
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: ABORT DMA%s\n", ATACONTROLLER_IDX(pCtl), pCtl->fReset ? " due to RESET" : ""));
+ if (!pCtl->fReset)
+ ataDMATransferStop(s);
+ /* This forces the loop to exit immediately. */
+ pDesc = pCtl->pLastDMADesc + 1;
+ }
+
+ PDMCritSectLeave(&pCtl->lock);
+ }
+
+ {
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+ }
+
+ if (RT_UNLIKELY(fRedo))
+ return;
+
+ if (fLastDesc)
+ pCtl->BmDma.u8Status &= ~BM_STATUS_DMAING;
+ s->cbTotalTransfer = cbTotalTransfer;
+ s->cbElementaryTransfer = cbElementaryTransfer;
+ s->iIOBufferCur = iIOBufferCur;
+ s->iIOBufferEnd = iIOBufferEnd;
+}
+
+
+/**
+ * Suspend I/O operations on a controller. Also suspends EMT, because it's
+ * waiting for I/O to make progress. The next attempt to perform an I/O
+ * operation will be made when EMT is resumed up again (as the resume
+ * callback below restarts I/O).
+ *
+ * @param pCtl Controller for which to suspend I/O.
+ */
+static void ataSuspendRedo(PATACONTROLLER pCtl)
+{
+ PPDMDEVINS pDevIns = CONTROLLER_2_DEVINS(pCtl);
+ PVMREQ pReq;
+ int rc;
+
+ pCtl->fRedoIdle = true;
+ rc = VMR3ReqCall(PDMDevHlpGetVM(pDevIns), VMREQDEST_ANY, &pReq, RT_INDEFINITE_WAIT,
+ (PFNRT)PDMDevHlpVMSuspend, 1, pDevIns);
+ AssertReleaseRC(rc);
+ VMR3ReqFree(pReq);
+}
+
+/** Asynch I/O thread for an interface. Once upon a time this was readable
+ * code with several loops and a different semaphore for each purpose. But
+ * then came the "how can one save the state in the middle of a PIO transfer"
+ * question. The solution was to use an ASM, which is what's there now. */
+static DECLCALLBACK(int) ataAsyncIOLoop(RTTHREAD ThreadSelf, void *pvUser)
+{
+ const ATARequest *pReq;
+ uint64_t u64TS = 0; /* shut up gcc */
+ uint64_t uWait;
+ int rc = VINF_SUCCESS;
+ PATACONTROLLER pCtl = (PATACONTROLLER)pvUser;
+ ATADevState *s;
+
+ pReq = NULL;
+ pCtl->fChainedTransfer = false;
+ while (!pCtl->fShutdown)
+ {
+ /* Keep this thread from doing anything as long as EMT is suspended. */
+ while (pCtl->fRedoIdle)
+ {
+ rc = RTSemEventWait(pCtl->SuspendIOSem, RT_INDEFINITE_WAIT);
+ /* Continue if we got a signal by RTThreadPoke().
+ * We will get notified if there is a request to process.
+ */
+ if (RT_UNLIKELY(rc == VERR_INTERRUPTED))
+ continue;
+ if (RT_FAILURE(rc) || pCtl->fShutdown)
+ break;
+
+ pCtl->fRedoIdle = false;
+ }
+
+ /* Wait for work. */
+ while (pReq == NULL)
+ {
+ LogBird(("ata: %x: going to sleep...\n", pCtl->IOPortBase1));
+ rc = RTSemEventWait(pCtl->AsyncIOSem, RT_INDEFINITE_WAIT);
+ LogBird(("ata: %x: waking up\n", pCtl->IOPortBase1));
+ /* Continue if we got a signal by RTThreadPoke().
+ * We will get notified if there is a request to process.
+ */
+ if (RT_UNLIKELY(rc == VERR_INTERRUPTED))
+ continue;
+ if (RT_FAILURE(rc) || RT_UNLIKELY(pCtl->fShutdown))
+ break;
+
+ pReq = ataAsyncIOGetCurrentRequest(pCtl);
+ }
+
+ if (RT_FAILURE(rc) || pCtl->fShutdown)
+ break;
+
+ if (pReq == NULL)
+ continue;
+
+ ATAAIO ReqType = pReq->ReqType;
+
+ Log2(("%s: Ctl#%d: state=%d, req=%d\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl), pCtl->uAsyncIOState, ReqType));
+ if (pCtl->uAsyncIOState != ReqType)
+ {
+ /* The new state is not the state that was expected by the normal
+ * state changes. This is either a RESET/ABORT or there's something
+ * really strange going on. */
+ if ( (pCtl->uAsyncIOState == ATA_AIO_PIO || pCtl->uAsyncIOState == ATA_AIO_DMA)
+ && (ReqType == ATA_AIO_PIO || ReqType == ATA_AIO_DMA))
+ {
+ /* Incorrect sequence of PIO/DMA states. Dump request queue. */
+ ataAsyncIODumpRequests(pCtl);
+ }
+ AssertReleaseMsg(ReqType == ATA_AIO_RESET_ASSERTED || ReqType == ATA_AIO_RESET_CLEARED || ReqType == ATA_AIO_ABORT || pCtl->uAsyncIOState == ReqType, ("I/O state inconsistent: state=%d request=%d\n", pCtl->uAsyncIOState, ReqType));
+ }
+
+ /* Do our work. */
+ {
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ LogBird(("ata: %x: entering critsect\n", pCtl->IOPortBase1));
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ LogBird(("ata: %x: entered\n", pCtl->IOPortBase1));
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+ }
+
+ if (pCtl->uAsyncIOState == ATA_AIO_NEW && !pCtl->fChainedTransfer)
+ {
+ u64TS = RTTimeNanoTS();
+#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+ STAM_PROFILE_ADV_START(&pCtl->StatAsyncTime, a);
+#endif /* DEBUG || VBOX_WITH_STATISTICS */
+ }
+
+ switch (ReqType)
+ {
+ case ATA_AIO_NEW:
+
+ pCtl->iAIOIf = pReq->u.t.iIf;
+ s = &pCtl->aIfs[pCtl->iAIOIf];
+ s->cbTotalTransfer = pReq->u.t.cbTotalTransfer;
+ s->uTxDir = pReq->u.t.uTxDir;
+ s->iBeginTransfer = pReq->u.t.iBeginTransfer;
+ s->iSourceSink = pReq->u.t.iSourceSink;
+ s->iIOBufferEnd = 0;
+ s->u64CmdTS = u64TS;
+
+ if (s->fATAPI)
+ {
+ if (pCtl->fChainedTransfer)
+ {
+ /* Only count the actual transfers, not the PIO
+ * transfer of the ATAPI command bytes. */
+ if (s->fDMA)
+ STAM_REL_COUNTER_INC(&s->StatATAPIDMA);
+ else
+ STAM_REL_COUNTER_INC(&s->StatATAPIPIO);
+ }
+ }
+ else
+ {
+ if (s->fDMA)
+ STAM_REL_COUNTER_INC(&s->StatATADMA);
+ else
+ STAM_REL_COUNTER_INC(&s->StatATAPIO);
+ }
+
+ pCtl->fChainedTransfer = false;
+
+ if (s->iBeginTransfer != ATAFN_BT_NULL)
+ {
+ Log2(("%s: Ctl#%d: calling begin transfer function\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ g_apfnBeginTransFuncs[s->iBeginTransfer](s);
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ if (s->uTxDir != PDMBLOCKTXDIR_FROM_DEVICE)
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ else
+ {
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ s->iIOBufferEnd = s->cbTotalTransfer;
+ }
+ s->iIOBufferCur = 0;
+
+ if (s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
+ {
+ if (s->iSourceSink != ATAFN_SS_NULL)
+ {
+ bool fRedo;
+ Log2(("%s: Ctl#%d: calling source/sink function\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo))
+ {
+ /* Operation failed at the initial transfer, restart
+ * everything from scratch by resending the current
+ * request. Occurs very rarely, not worth optimizing. */
+ LogRel(("%s: Ctl#%d: redo entire operation\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ ataAsyncIOPutRequest(pCtl, pReq);
+ ataSuspendRedo(pCtl);
+ break;
+ }
+ }
+ else
+ ataCmdOK(s, 0);
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+
+ }
+
+ /* Do not go into the transfer phase if RESET is asserted.
+ * The CritSect is released while waiting for the host OS
+ * to finish the I/O, thus RESET is possible here. Most
+ * important: do not change uAsyncIOState. */
+ if (pCtl->fReset)
+ break;
+
+ if (s->fDMA)
+ {
+ if (s->cbTotalTransfer)
+ {
+ ataSetStatus(s, ATA_STAT_DRQ);
+
+ pCtl->uAsyncIOState = ATA_AIO_DMA;
+ /* If BMDMA is already started, do the transfer now. */
+ if (pCtl->BmDma.u8Cmd & BM_CMD_START)
+ {
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing DMA transfer immediately\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ ataAsyncIOPutRequest(pCtl, &ataDMARequest);
+ }
+ }
+ else
+ {
+ Assert(s->uTxDir == PDMBLOCKTXDIR_NONE); /* Any transfer which has an initial transfer size of 0 must be marked as such. */
+ /* Finish DMA transfer. */
+ ataDMATransferStop(s);
+ ataSetIRQ(s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ else
+ {
+ if (s->cbTotalTransfer)
+ {
+ ataPIOTransfer(pCtl);
+ Assert(!pCtl->fRedo);
+ if (s->fATAPITransfer || s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
+ ataSetIRQ(s);
+
+ if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE || s->iSourceSink != ATAFN_SS_NULL)
+ {
+ /* Write operations and not yet finished transfers
+ * must be completed in the async I/O thread. */
+ pCtl->uAsyncIOState = ATA_AIO_PIO;
+ }
+ else
+ {
+ /* Finished read operation can be handled inline
+ * in the end of PIO transfer handling code. Linux
+ * depends on this, as it waits only briefly for
+ * devices to become ready after incoming data
+ * transfer. Cannot find anything in the ATA spec
+ * that backs this assumption, but as all kernels
+ * are affected (though most of the time it does
+ * not cause any harm) this must work. */
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ else
+ {
+ Assert(s->uTxDir == PDMBLOCKTXDIR_NONE); /* Any transfer which has an initial transfer size of 0 must be marked as such. */
+ /* Finish PIO transfer. */
+ ataPIOTransfer(pCtl);
+ Assert(!pCtl->fRedo);
+ if (!s->fATAPITransfer)
+ ataSetIRQ(s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ break;
+
+ case ATA_AIO_DMA:
+ {
+ BMDMAState *bm = &pCtl->BmDma;
+ s = &pCtl->aIfs[pCtl->iAIOIf]; /* Do not remove or there's an instant crash after loading the saved state */
+ ATAFNSS iOriginalSourceSink = (ATAFNSS)s->iSourceSink; /* Used by the hack below, but gets reset by then. */
+
+ if (s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+ AssertRelease(bm->u8Cmd & BM_CMD_WRITE);
+ else
+ AssertRelease(!(bm->u8Cmd & BM_CMD_WRITE));
+
+ if (RT_LIKELY(!pCtl->fRedo))
+ {
+ /* The specs say that the descriptor table must not cross a
+ * 4K boundary. */
+ pCtl->pFirstDMADesc = bm->pvAddr;
+ pCtl->pLastDMADesc = RT_ALIGN_32(bm->pvAddr + 1, _4K) - sizeof(BMDMADesc);
+ }
+ ataDMATransfer(pCtl);
+
+ if (RT_UNLIKELY(pCtl->fRedo))
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: redo DMA operation\n", ATACONTROLLER_IDX(pCtl)));
+ ataAsyncIOPutRequest(pCtl, &ataDMARequest);
+ ataSuspendRedo(pCtl);
+ break;
+ }
+
+ /* The infamous delay IRQ hack. */
+ if ( iOriginalSourceSink == ATAFN_SS_WRITE_SECTORS
+ && s->cbTotalTransfer == 0
+ && pCtl->DelayIRQMillies)
+ {
+ /* Delay IRQ for writing. Required to get the Win2K
+ * installation work reliably (otherwise it crashes,
+ * usually during component install). So far no better
+ * solution has been found. */
+ Log(("%s: delay IRQ hack\n", __FUNCTION__));
+ PDMCritSectLeave(&pCtl->lock);
+ RTThreadSleep(pCtl->DelayIRQMillies);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ }
+
+ ataUnsetStatus(s, ATA_STAT_DRQ);
+ Assert(!pCtl->fChainedTransfer);
+ Assert(s->iSourceSink == ATAFN_SS_NULL);
+ if (s->fATAPITransfer)
+ {
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: Ctl#%d: interrupt reason %#04x\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl), s->uATARegNSector));
+ s->fATAPITransfer = false;
+ }
+ ataSetIRQ(s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ break;
+ }
+
+ case ATA_AIO_PIO:
+ s = &pCtl->aIfs[pCtl->iAIOIf]; /* Do not remove or there's an instant crash after loading the saved state */
+
+ if (s->iSourceSink != ATAFN_SS_NULL)
+ {
+ bool fRedo;
+ Log2(("%s: Ctl#%d: calling source/sink function\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo))
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: redo PIO operation\n", ATACONTROLLER_IDX(pCtl)));
+ ataAsyncIOPutRequest(pCtl, &ataPIORequest);
+ ataSuspendRedo(pCtl);
+ break;
+ }
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ else
+ {
+ /* Continue a previously started transfer. */
+ ataUnsetStatus(s, ATA_STAT_BUSY);
+ ataSetStatus(s, ATA_STAT_READY);
+ }
+
+ /* It is possible that the drives on this controller get RESET
+ * during the above call to the source/sink function. If that's
+ * the case, don't restart the transfer and don't finish it the
+ * usual way. RESET handling took care of all that already.
+ * Most important: do not change uAsyncIOState. */
+ if (pCtl->fReset)
+ break;
+
+ if (s->cbTotalTransfer)
+ {
+ ataPIOTransfer(pCtl);
+ ataSetIRQ(s);
+
+ if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE || s->iSourceSink != ATAFN_SS_NULL)
+ {
+ /* Write operations and not yet finished transfers
+ * must be completed in the async I/O thread. */
+ pCtl->uAsyncIOState = ATA_AIO_PIO;
+ }
+ else
+ {
+ /* Finished read operation can be handled inline
+ * in the end of PIO transfer handling code. Linux
+ * depends on this, as it waits only briefly for
+ * devices to become ready after incoming data
+ * transfer. Cannot find anything in the ATA spec
+ * that backs this assumption, but as all kernels
+ * are affected (though most of the time it does
+ * not cause any harm) this must work. */
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ else
+ {
+ /* Finish PIO transfer. */
+ ataPIOTransfer(pCtl);
+ if ( !pCtl->fChainedTransfer
+ && !s->fATAPITransfer
+ && s->uTxDir != PDMBLOCKTXDIR_FROM_DEVICE)
+ {
+ ataSetIRQ(s);
+ }
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ break;
+
+ case ATA_AIO_RESET_ASSERTED:
+ pCtl->uAsyncIOState = ATA_AIO_RESET_CLEARED;
+ ataPIOTransferStop(&pCtl->aIfs[0]);
+ ataPIOTransferStop(&pCtl->aIfs[1]);
+ /* Do not change the DMA registers, they are not affected by the
+ * ATA controller reset logic. It should be sufficient to issue a
+ * new command, which is now possible as the state is cleared. */
+ break;
+
+ case ATA_AIO_RESET_CLEARED:
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ pCtl->fReset = false;
+ LogRel(("PIIX3 ATA: Ctl#%d: finished processing RESET\n",
+ ATACONTROLLER_IDX(pCtl)));
+ for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+ {
+ if (pCtl->aIfs[i].fATAPI)
+ ataSetStatusValue(&pCtl->aIfs[i], 0); /* NOTE: READY is _not_ set */
+ else
+ ataSetStatusValue(&pCtl->aIfs[i], ATA_STAT_READY | ATA_STAT_SEEK);
+ ataSetSignature(&pCtl->aIfs[i]);
+ }
+ break;
+
+ case ATA_AIO_ABORT:
+ /* Abort the current command only if it operates on the same interface. */
+ if (pCtl->iAIOIf == pReq->u.a.iIf)
+ {
+ s = &pCtl->aIfs[pCtl->iAIOIf];
+
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ /* Do not change the DMA registers, they are not affected by the
+ * ATA controller reset logic. It should be sufficient to issue a
+ * new command, which is now possible as the state is cleared. */
+ if (pReq->u.a.fResetDrive)
+ {
+ ataResetDevice(s);
+ ataExecuteDeviceDiagnosticSS(s);
+ }
+ else
+ {
+ ataPIOTransferStop(s);
+ ataUnsetStatus(s, ATA_STAT_BUSY | ATA_STAT_DRQ | ATA_STAT_SEEK | ATA_STAT_ERR);
+ ataSetStatus(s, ATA_STAT_READY);
+ ataSetIRQ(s);
+ }
+ }
+ break;
+
+ default:
+ AssertMsgFailed(("Undefined async I/O state %d\n", pCtl->uAsyncIOState));
+ }
+
+ ataAsyncIORemoveCurrentRequest(pCtl, ReqType);
+ pReq = ataAsyncIOGetCurrentRequest(pCtl);
+
+ if (pCtl->uAsyncIOState == ATA_AIO_NEW && !pCtl->fChainedTransfer)
+ {
+#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+ STAM_PROFILE_ADV_STOP(&pCtl->StatAsyncTime, a);
+#endif /* DEBUG || VBOX_WITH_STATISTICS */
+
+ u64TS = RTTimeNanoTS() - u64TS;
+ uWait = u64TS / 1000;
+ Log(("%s: Ctl#%d: LUN#%d finished I/O transaction in %d microseconds\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl), pCtl->aIfs[pCtl->iAIOIf].iLUN, (uint32_t)(uWait)));
+ /* Mark command as finished. */
+ pCtl->aIfs[pCtl->iAIOIf].u64CmdTS = 0;
+
+ /*
+ * Release logging of command execution times depends on the
+ * command type. ATAPI commands often take longer (due to CD/DVD
+ * spin up time etc.) so the threshold is different.
+ */
+ if (pCtl->aIfs[pCtl->iAIOIf].uATARegCommand != ATA_PACKET)
+ {
+ if (uWait > 8 * 1000 * 1000)
+ {
+ /*
+ * Command took longer than 8 seconds. This is close
+ * enough or over the guest's command timeout, so place
+ * an entry in the release log to allow tracking such
+ * timing errors (which are often caused by the host).
+ */
+ LogRel(("PIIX3 ATA: execution time for ATA command %#04x was %d seconds\n", pCtl->aIfs[pCtl->iAIOIf].uATARegCommand, uWait / (1000 * 1000)));
+ }
+ }
+ else
+ {
+ if (uWait > 20 * 1000 * 1000)
+ {
+ /*
+ * Command took longer than 20 seconds. This is close
+ * enough or over the guest's command timeout, so place
+ * an entry in the release log to allow tracking such
+ * timing errors (which are often caused by the host).
+ */
+ LogRel(("PIIX3 ATA: execution time for ATAPI command %#04x was %d seconds\n", pCtl->aIfs[pCtl->iAIOIf].aATAPICmd[0], uWait / (1000 * 1000)));
+ }
+ }
+
+#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+ if (uWait < pCtl->StatAsyncMinWait || !pCtl->StatAsyncMinWait)
+ pCtl->StatAsyncMinWait = uWait;
+ if (uWait > pCtl->StatAsyncMaxWait)
+ pCtl->StatAsyncMaxWait = uWait;
+
+ STAM_COUNTER_ADD(&pCtl->StatAsyncTimeUS, uWait);
+ STAM_COUNTER_INC(&pCtl->StatAsyncOps);
+#endif /* DEBUG || VBOX_WITH_STATISTICS */
+ }
+
+ LogBird(("ata: %x: leaving critsect\n", pCtl->IOPortBase1));
+ PDMCritSectLeave(&pCtl->lock);
+ }
+
+ /* Cleanup the state. */
+ if (pCtl->AsyncIOSem)
+ {
+ RTSemEventDestroy(pCtl->AsyncIOSem);
+ pCtl->AsyncIOSem = NIL_RTSEMEVENT;
+ }
+ if (pCtl->SuspendIOSem)
+ {
+ RTSemEventDestroy(pCtl->SuspendIOSem);
+ pCtl->SuspendIOSem = NIL_RTSEMEVENT;
+ }
+ /* Do not destroy request mutex yet, still needed for proper shutdown. */
+ pCtl->fShutdown = false;
+ /* This must be last, as it also signals thread exit to EMT. */
+ pCtl->AsyncIOThread = NIL_RTTHREAD;
+
+ Log2(("%s: Ctl#%d: return %Rrc\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl), rc));
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+static uint32_t ataBMDMACmdReadB(PATACONTROLLER pCtl, uint32_t addr)
+{
+ uint32_t val = pCtl->BmDma.u8Cmd;
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+
+static void ataBMDMACmdWriteB(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ if (!(val & BM_CMD_START))
+ {
+ pCtl->BmDma.u8Status &= ~BM_STATUS_DMAING;
+ pCtl->BmDma.u8Cmd = val & (BM_CMD_START | BM_CMD_WRITE);
+ }
+ else
+ {
+#ifdef IN_RING3
+ /* Check whether the guest OS wants to change DMA direction in
+ * mid-flight. Not allowed, according to the PIIX3 specs. */
+ Assert(!(pCtl->BmDma.u8Status & BM_STATUS_DMAING) || !((val ^ pCtl->BmDma.u8Cmd) & 0x04));
+ pCtl->BmDma.u8Status |= BM_STATUS_DMAING;
+ pCtl->BmDma.u8Cmd = val & (BM_CMD_START | BM_CMD_WRITE);
+
+ /* Do not continue DMA transfers while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed continuing DMA transfer as RESET is active\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ return;
+ }
+
+ /* Do not start DMA transfers if there's a PIO transfer going on. */
+ if (!pCtl->aIfs[pCtl->iSelectedIf].fDMA)
+ return;
+
+ if (pCtl->aIfs[pCtl->iAIOIf].uATARegStatus & ATA_STAT_DRQ)
+ {
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing DMA transfer\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ ataAsyncIOPutRequest(pCtl, &ataDMARequest);
+ }
+#else /* !IN_RING3 */
+ AssertMsgFailed(("DMA START handling is too complicated for GC\n"));
+#endif /* IN_RING3 */
+ }
+}
+
+static uint32_t ataBMDMAStatusReadB(PATACONTROLLER pCtl, uint32_t addr)
+{
+ uint32_t val = pCtl->BmDma.u8Status;
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+static void ataBMDMAStatusWriteB(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ pCtl->BmDma.u8Status = (val & (BM_STATUS_D0DMA | BM_STATUS_D1DMA))
+ | (pCtl->BmDma.u8Status & BM_STATUS_DMAING)
+ | (pCtl->BmDma.u8Status & ~val & (BM_STATUS_ERROR | BM_STATUS_INT));
+}
+
+static uint32_t ataBMDMAAddrReadL(PATACONTROLLER pCtl, uint32_t addr)
+{
+ uint32_t val = (uint32_t)pCtl->BmDma.pvAddr;
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+static void ataBMDMAAddrWriteL(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ pCtl->BmDma.pvAddr = val & ~3;
+}
+
+static void ataBMDMAAddrWriteLowWord(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ pCtl->BmDma.pvAddr = (pCtl->BmDma.pvAddr & 0xFFFF0000) | RT_LOWORD(val & ~3);
+
+}
+
+static void ataBMDMAAddrWriteHighWord(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ pCtl->BmDma.pvAddr = (RT_LOWORD(val) << 16) | RT_LOWORD(pCtl->BmDma.pvAddr);
+}
+
+#define VAL(port, size) ( ((port) & 7) | ((size) << 3) )
+
+/**
+ * Port I/O Handler for bus master DMA IN operations.
+ * @see FNIOMIOPORTIN for details.
+ */
+PDMBOTHCBDECL(int) ataBMDMAIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ int rc;
+
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ switch (VAL(Port, cb))
+ {
+ case VAL(0, 1): *pu32 = ataBMDMACmdReadB(pCtl, Port); break;
+ case VAL(0, 2): *pu32 = ataBMDMACmdReadB(pCtl, Port); break;
+ case VAL(2, 1): *pu32 = ataBMDMAStatusReadB(pCtl, Port); break;
+ case VAL(2, 2): *pu32 = ataBMDMAStatusReadB(pCtl, Port); break;
+ case VAL(4, 4): *pu32 = ataBMDMAAddrReadL(pCtl, Port); break;
+ case VAL(0, 4):
+ /* The SCO OpenServer tries to read 4 bytes starting from offset 0. */
+ *pu32 = ataBMDMACmdReadB(pCtl, Port) | (ataBMDMAStatusReadB(pCtl, Port) << 16);
+ break;
+ default:
+ AssertMsgFailed(("%s: Unsupported read from port %x size=%d\n", __FUNCTION__, Port, cb));
+ PDMCritSectLeave(&pCtl->lock);
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+ PDMCritSectLeave(&pCtl->lock);
+ return rc;
+}
+
+/**
+ * Port I/O Handler for bus master DMA OUT operations.
+ * @see FNIOMIOPORTOUT for details.
+ */
+PDMBOTHCBDECL(int) ataBMDMAIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ int rc;
+
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ switch (VAL(Port, cb))
+ {
+ case VAL(0, 1):
+#ifndef IN_RING3
+ if (u32 & BM_CMD_START)
+ {
+ rc = VINF_IOM_HC_IOPORT_WRITE;
+ break;
+ }
+#endif /* !IN_RING3 */
+ ataBMDMACmdWriteB(pCtl, Port, u32);
+ break;
+ case VAL(2, 1): ataBMDMAStatusWriteB(pCtl, Port, u32); break;
+ case VAL(4, 4): ataBMDMAAddrWriteL(pCtl, Port, u32); break;
+ case VAL(4, 2): ataBMDMAAddrWriteLowWord(pCtl, Port, u32); break;
+ case VAL(6, 2): ataBMDMAAddrWriteHighWord(pCtl, Port, u32); break;
+ default: AssertMsgFailed(("%s: Unsupported write to port %x size=%d val=%x\n", __FUNCTION__, Port, cb, u32)); break;
+ }
+ PDMCritSectLeave(&pCtl->lock);
+ return rc;
+}
+
+#undef VAL
+
+#ifdef IN_RING3
+
+/**
+ * Callback function for mapping an PCI I/O region.
+ *
+ * @return VBox status code.
+ * @param pPciDev Pointer to PCI device. Use pPciDev->pDevIns to get the device instance.
+ * @param iRegion The region number.
+ * @param GCPhysAddress Physical address of the region. If iType is PCI_ADDRESS_SPACE_IO, this is an
+ * I/O port, else it's a physical address.
+ * This address is *NOT* relative to pci_mem_base like earlier!
+ * @param enmType One of the PCI_ADDRESS_SPACE_* values.
+ */
+static DECLCALLBACK(int) ataBMDMAIORangeMap(PPCIDEVICE pPciDev, /*unsigned*/ int iRegion, RTGCPHYS GCPhysAddress, uint32_t cb, PCIADDRESSSPACE enmType)
+{
+ PCIATAState *pThis = PCIDEV_2_PCIATASTATE(pPciDev);
+ int rc = VINF_SUCCESS;
+ Assert(enmType == PCI_ADDRESS_SPACE_IO);
+ Assert(iRegion == 4);
+ AssertMsg(RT_ALIGN(GCPhysAddress, 8) == GCPhysAddress, ("Expected 8 byte alignment. GCPhysAddress=%#x\n", GCPhysAddress));
+
+ /* Register the port range. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ int rc2 = PDMDevHlpIOPortRegister(pPciDev->pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
+ (RTHCPTR)i, ataBMDMAIOPortWrite, ataBMDMAIOPortRead, NULL, NULL, "ATA Bus Master DMA");
+ AssertRC(rc2);
+ if (rc2 < rc)
+ rc = rc2;
+
+ if (pThis->fGCEnabled)
+ {
+ rc2 = PDMDevHlpIOPortRegisterGC(pPciDev->pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
+ (RTGCPTR)i, "ataBMDMAIOPortWrite", "ataBMDMAIOPortRead", NULL, NULL, "ATA Bus Master DMA");
+ AssertRC(rc2);
+ if (rc2 < rc)
+ rc = rc2;
+ }
+ if (pThis->fR0Enabled)
+ {
+ rc2 = PDMDevHlpIOPortRegisterR0(pPciDev->pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
+ (RTR0PTR)i, "ataBMDMAIOPortWrite", "ataBMDMAIOPortRead", NULL, NULL, "ATA Bus Master DMA");
+ AssertRC(rc2);
+ if (rc2 < rc)
+ rc = rc2;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Reset notification.
+ *
+ * @returns VBox status.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataReset(PPDMDEVINS pDevIns)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThis->aCts[i].iSelectedIf = 0;
+ pThis->aCts[i].iAIOIf = 0;
+ pThis->aCts[i].BmDma.u8Cmd = 0;
+ /* Report that both drives present on the bus are in DMA mode. This
+ * pretends that there is a BIOS that has set it up. Normal reset
+ * default is 0x00. */
+ pThis->aCts[i].BmDma.u8Status = (pThis->aCts[i].aIfs[0].pDrvBase != NULL ? BM_STATUS_D0DMA : 0)
+ | (pThis->aCts[i].aIfs[1].pDrvBase != NULL ? BM_STATUS_D1DMA : 0);
+ pThis->aCts[i].BmDma.pvAddr = 0;
+
+ pThis->aCts[i].fReset = true;
+ pThis->aCts[i].fRedo = false;
+ pThis->aCts[i].fRedoIdle = false;
+ ataAsyncIOClearRequests(&pThis->aCts[i]);
+ Log2(("%s: Ctl#%d: message to async I/O thread, reset controller\n", __FUNCTION__, i));
+ ataAsyncIOPutRequest(&pThis->aCts[i], &ataResetARequest);
+ ataAsyncIOPutRequest(&pThis->aCts[i], &ataResetCRequest);
+ if (!ataWaitForAsyncIOIsIdle(&pThis->aCts[i], 30000))
+ {
+ VMSetRuntimeError(PDMDevHlpGetVM(pDevIns),
+ false, "DevATA_ASYNCBUSY",
+ N_("The IDE async I/O thread remained busy after a reset, usually a host filesystem performance problem\n"));
+ AssertMsgFailed(("Async I/O thread busy after reset\n"));
+ }
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ ataResetDevice(&pThis->aCts[i].aIfs[j]);
+ }
+}
+
+
+/* -=-=-=-=-=- PCIATAState::IBase -=-=-=-=-=- */
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the device.
+ * @param pInterface Pointer to ATADevState::IBase.
+ * @param enmInterface The requested interface identification.
+ */
+static DECLCALLBACK(void *) ataStatus_QueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+ PCIATAState *pThis = PDMIBASE_2_PCIATASTATE(pInterface);
+ switch (enmInterface)
+ {
+ case PDMINTERFACE_BASE:
+ return &pThis->IBase;
+ case PDMINTERFACE_LED_PORTS:
+ return &pThis->ILeds;
+ default:
+ return NULL;
+ }
+}
+
+
+/* -=-=-=-=-=- PCIATAState::ILeds -=-=-=-=-=- */
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) ataStatus_QueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PCIATAState *pThis = PDMILEDPORTS_2_PCIATASTATE(pInterface);
+ if (iLUN < 4)
+ {
+ switch (iLUN)
+ {
+ case 0: *ppLed = &pThis->aCts[0].aIfs[0].Led; break;
+ case 1: *ppLed = &pThis->aCts[0].aIfs[1].Led; break;
+ case 2: *ppLed = &pThis->aCts[1].aIfs[0].Led; break;
+ case 3: *ppLed = &pThis->aCts[1].aIfs[1].Led; break;
+ }
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/* -=-=-=-=-=- ATADevState::IBase -=-=-=-=-=- */
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the device.
+ * @param pInterface Pointer to ATADevState::IBase.
+ * @param enmInterface The requested interface identification.
+ */
+static DECLCALLBACK(void *) ataQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+ ATADevState *pIf = PDMIBASE_2_ATASTATE(pInterface);
+ switch (enmInterface)
+ {
+ case PDMINTERFACE_BASE:
+ return &pIf->IBase;
+ case PDMINTERFACE_BLOCK_PORT:
+ return &pIf->IPort;
+ case PDMINTERFACE_MOUNT_NOTIFY:
+ return &pIf->IMountNotify;
+ default:
+ return NULL;
+ }
+}
+
+#endif /* IN_RING3 */
+
+
+/* -=-=-=-=-=- Wrappers -=-=-=-=-=- */
+
+/**
+ * Port I/O Handler for primary port range OUT operations.
+ * @see FNIOMIOPORTOUT for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortWrite1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ int rc = VINF_SUCCESS;
+
+ Assert(i < 2);
+
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ if (cb == 1)
+ rc = ataIOPortWriteU8(pCtl, Port, u32);
+ else if (Port == pCtl->IOPortBase1)
+ {
+ Assert(cb == 2 || cb == 4);
+ rc = ataDataWrite(pCtl, Port, cb, (const uint8_t *)&u32);
+ }
+ else
+ AssertMsgFailed(("ataIOPortWrite1: unsupported write to port %x val=%x size=%d\n", Port, u32, cb));
+ LogBird(("ata: leaving critsect\n"));
+ PDMCritSectLeave(&pCtl->lock);
+ LogBird(("ata: left critsect\n"));
+ return rc;
+}
+
+
+/**
+ * Port I/O Handler for primary port range IN operations.
+ * @see FNIOMIOPORTIN for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortRead1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ int rc = VINF_SUCCESS;
+
+ Assert(i < 2);
+
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ if (cb == 1)
+ {
+ rc = ataIOPortReadU8(pCtl, Port, pu32);
+ }
+ else if (Port == pCtl->IOPortBase1)
+ {
+ Assert(cb == 2 || cb == 4);
+ rc = ataDataRead(pCtl, Port, cb, (uint8_t *)pu32);
+ if (cb == 2)
+ *pu32 &= 0xffff;
+ }
+ else
+ {
+ AssertMsgFailed(("ataIOPortRead1: unsupported read from port %x size=%d\n", Port, cb));
+ rc = VERR_IOM_IOPORT_UNUSED;
+ }
+ PDMCritSectLeave(&pCtl->lock);
+ return rc;
+}
+
+#ifndef IN_RING0
+/**
+ * Port I/O Handler for primary port range IN string operations.
+ * @see FNIOMIOPORTINSTRING for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortReadStr1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ int rc = VINF_SUCCESS;
+
+ Assert(i < 2);
+
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ if (Port == pCtl->IOPortBase1)
+ {
+ uint32_t cTransAvailable, cTransfer = *pcTransfer, cbTransfer;
+ RTGCPTR GCDst = *pGCPtrDst;
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+ Assert(cb == 2 || cb == 4);
+
+ cTransAvailable = (s->iIOBufferPIODataEnd - s->iIOBufferPIODataStart) / cb;
+#ifndef IN_RING3
+ /* The last transfer unit cannot be handled in GC, as it involves thread communication. */
+ cTransAvailable--;
+#endif /* !IN_RING3 */
+ /* Do not handle the dummy transfer stuff here, leave it to the single-word transfers.
+ * They are not performance-critical and generally shouldn't occur at all. */
+ if (cTransAvailable > cTransfer)
+ cTransAvailable = cTransfer;
+ cbTransfer = cTransAvailable * cb;
+
+#ifdef IN_RC
+ for (uint32_t i = 0; i < cbTransfer; i += cb)
+ MMGCRamWriteNoTrapHandler((char *)GCDst + i, s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart + i, cb);
+#else /* !IN_RC */
+ rc = PGMPhysSimpleDirtyWriteGCPtr(PDMDevHlpGetVM(pDevIns), GCDst, s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart, cbTransfer);
+ Assert(rc == VINF_SUCCESS);
+#endif /* IN_RC */
+
+ if (cbTransfer)
+ Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, Port, cbTransfer, s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart));
+ s->iIOBufferPIODataStart += cbTransfer;
+ *pGCPtrDst = (RTGCPTR)((RTGCUINTPTR)GCDst + cbTransfer);
+ *pcTransfer = cTransfer - cTransAvailable;
+#ifdef IN_RING3
+ if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+ ataPIOTransferFinish(pCtl, s);
+#endif /* IN_RING3 */
+ }
+ PDMCritSectLeave(&pCtl->lock);
+ return rc;
+}
+
+
+/**
+ * Port I/O Handler for primary port range OUT string operations.
+ * @see FNIOMIOPORTOUTSTRING for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortWriteStr1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ int rc;
+
+ Assert(i < 2);
+
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ if (Port == pCtl->IOPortBase1)
+ {
+ uint32_t cTransAvailable, cTransfer = *pcTransfer, cbTransfer;
+ RTGCPTR GCSrc = *pGCPtrSrc;
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+ Assert(cb == 2 || cb == 4);
+
+ cTransAvailable = (s->iIOBufferPIODataEnd - s->iIOBufferPIODataStart) / cb;
+#ifndef IN_RING3
+ /* The last transfer unit cannot be handled in GC, as it involves thread communication. */
+ cTransAvailable--;
+#endif /* !IN_RING3 */
+ /* Do not handle the dummy transfer stuff here, leave it to the single-word transfers.
+ * They are not performance-critical and generally shouldn't occur at all. */
+ if (cTransAvailable > cTransfer)
+ cTransAvailable = cTransfer;
+ cbTransfer = cTransAvailable * cb;
+
+#ifdef IN_RC
+ for (uint32_t i = 0; i < cbTransfer; i += cb)
+ MMGCRamReadNoTrapHandler(s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart + i, (char *)GCSrc + i, cb);
+#else /* !IN_RC */
+ rc = PGMPhysSimpleReadGCPtr(PDMDevHlpGetVM(pDevIns), s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart, GCSrc, cbTransfer);
+ Assert(rc == VINF_SUCCESS);
+#endif /* IN_RC */
+
+ if (cbTransfer)
+ Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, Port, cbTransfer, s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart));
+ s->iIOBufferPIODataStart += cbTransfer;
+ *pGCPtrSrc = (RTGCPTR)((RTGCUINTPTR)GCSrc + cbTransfer);
+ *pcTransfer = cTransfer - cTransAvailable;
+#ifdef IN_RING3
+ if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+ ataPIOTransferFinish(pCtl, s);
+#endif /* IN_RING3 */
+ }
+ PDMCritSectLeave(&pCtl->lock);
+ return rc;
+}
+#endif /* !IN_RING0 */
+
+/**
+ * Port I/O Handler for secondary port range OUT operations.
+ * @see FNIOMIOPORTOUT for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortWrite2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ int rc;
+
+ Assert(i < 2);
+
+ if (cb != 1)
+ return VINF_SUCCESS;
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ rc = ataControlWrite(pCtl, Port, u32);
+ PDMCritSectLeave(&pCtl->lock);
+ return rc;
+}
+
+
+/**
+ * Port I/O Handler for secondary port range IN operations.
+ * @see FNIOMIOPORTIN for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortRead2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ int rc;
+
+ Assert(i < 2);
+
+ if (cb != 1)
+ return VERR_IOM_IOPORT_UNUSED;
+
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ *pu32 = ataStatusRead(pCtl, Port);
+ PDMCritSectLeave(&pCtl->lock);
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Waits for all async I/O threads to complete whatever they
+ * are doing at the moment.
+ *
+ * @returns true on success.
+ * @returns false when one or more threads is still processing.
+ * @param pThis Pointer to the instance data.
+ * @param cMillies How long to wait (total).
+ */
+static bool ataWaitForAllAsyncIOIsIdle(PPDMDEVINS pDevIns, unsigned cMillies)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ bool fVMLocked;
+ uint64_t u64Start;
+ PATACONTROLLER pCtl;
+ bool fAllIdle = false;
+
+ /* The only way to deal cleanly with the VM lock is to check whether
+ * it is owned now (it always is owned by EMT, which is the current
+ * thread). Since this function is called several times during VM
+ * shutdown, and the VM lock is only held for the first call (which
+ * can be either from ataPowerOff or ataSuspend), there is no other
+ * reasonable solution. */
+ fVMLocked = VMMR3LockIsOwner(PDMDevHlpGetVM(pDevIns));
+
+ if (fVMLocked)
+ pDevIns->pDevHlpR3->pfnUnlockVM(pDevIns);
+ /*
+ * Wait for any pending async operation to finish
+ */
+ u64Start = RTTimeMilliTS();
+ for (;;)
+ {
+ /* Check all async I/O threads. */
+ fAllIdle = true;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pCtl = &pThis->aCts[i];
+
+ /*
+ * Only check if the thread is idling if the request mutex is set up.
+ * It is possible that the creation of the first controller failed and that
+ * the request mutex is not initialized on the second one yet
+ * But it would be called without the following check.
+ */
+ if (pCtl->AsyncIORequestMutex != NIL_RTSEMEVENT)
+ {
+ fAllIdle &= ataAsyncIOIsIdle(pCtl, false);
+ if (!fAllIdle)
+ break;
+ }
+ }
+ if ( fAllIdle
+ || RTTimeMilliTS() - u64Start >= cMillies)
+ break;
+
+ /* Sleep for a bit. */
+ RTThreadSleep(100);
+ }
+
+ if (fVMLocked)
+ pDevIns->pDevHlpR3->pfnLockVM(pDevIns);
+
+ if (!fAllIdle)
+ LogRel(("PIIX3 ATA: Ctl#%d is still executing, DevSel=%d AIOIf=%d CmdIf0=%#04x CmdIf1=%#04x\n",
+ ATACONTROLLER_IDX(pCtl), pCtl->iSelectedIf, pCtl->iAIOIf,
+ pCtl->aIfs[0].uATARegCommand, pCtl->aIfs[1].uATARegCommand));
+
+ return fAllIdle;
+}
+
+
+DECLINLINE(void) ataRelocBuffer(PPDMDEVINS pDevIns, ATADevState *s)
+{
+ if (s->pbIOBufferR3)
+ s->pbIOBufferRC = MMHyperR3ToRC(PDMDevHlpGetVM(pDevIns), s->pbIOBufferR3);
+}
+
+
+/**
+ * @copydoc FNPDMDEVRELOCATE
+ */
+static DECLCALLBACK(void) ataRelocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThis->aCts[i].pDevInsRC += offDelta;
+ pThis->aCts[i].aIfs[0].pDevInsRC += offDelta;
+ pThis->aCts[i].aIfs[0].pControllerRC += offDelta;
+ ataRelocBuffer(pDevIns, &pThis->aCts[i].aIfs[0]);
+ pThis->aCts[i].aIfs[1].pDevInsRC += offDelta;
+ pThis->aCts[i].aIfs[1].pControllerRC += offDelta;
+ ataRelocBuffer(pDevIns, &pThis->aCts[i].aIfs[1]);
+ }
+}
+
+
+/**
+ * Destroy a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(int) ataDestruct(PPDMDEVINS pDevIns)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ int rc;
+
+ Log(("%s:\n", __FUNCTION__));
+
+ /*
+ * Terminate all async helper threads
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].AsyncIOThread != NIL_RTTHREAD)
+ {
+ ASMAtomicXchgU32(&pThis->aCts[i].fShutdown, true);
+ rc = RTSemEventSignal(pThis->aCts[i].AsyncIOSem);
+ AssertRC(rc);
+ }
+ }
+
+ /*
+ * Wait for them to complete whatever they are doing and then
+ * for them to terminate.
+ */
+ if (ataWaitForAllAsyncIOIsIdle(pDevIns, 20000))
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ rc = RTThreadWait(pThis->aCts[i].AsyncIOThread, 30000 /* 30 s*/, NULL);
+ AssertMsgRC(rc || rc == VERR_INVALID_HANDLE, ("rc=%Rrc i=%d\n", rc, i));
+ }
+ }
+ else
+ AssertMsgFailed(("Async I/O is still busy!\n"));
+
+ /*
+ * Now the request mutexes are no longer needed. Free resources.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].AsyncIORequestMutex != NIL_RTSEMEVENT)
+ {
+ RTSemMutexDestroy(pThis->aCts[i].AsyncIORequestMutex);
+ pThis->aCts[i].AsyncIORequestMutex = NIL_RTSEMEVENT;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Detach notification.
+ *
+ * The DVD drive has been unplugged.
+ *
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ */
+static DECLCALLBACK(void) ataDetach(PPDMDEVINS pDevIns, unsigned iLUN)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl;
+ ATADevState *pIf;
+ unsigned iController;
+ unsigned iInterface;
+
+ /*
+ * Locate the controller and stuff.
+ */
+ iController = iLUN / RT_ELEMENTS(pThis->aCts[0].aIfs);
+ AssertReleaseMsg(iController < RT_ELEMENTS(pThis->aCts), ("iController=%d iLUN=%d\n", iController, iLUN));
+ pCtl = &pThis->aCts[iController];
+
+ iInterface = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
+ pIf = &pCtl->aIfs[iInterface];
+
+ /*
+ * Zero some important members.
+ */
+ pIf->pDrvBase = NULL;
+ pIf->pDrvBlock = NULL;
+ pIf->pDrvBlockBios = NULL;
+ pIf->pDrvMount = NULL;
+
+ /*
+ * In case there was a medium inserted.
+ */
+ ataMediumRemoved(pIf);
+}
+
+
+/**
+ * Configure a LUN.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pIf The ATA unit state.
+ */
+static int ataConfigLun(PPDMDEVINS pDevIns, ATADevState *pIf)
+{
+ int rc;
+ PDMBLOCKTYPE enmType;
+
+ /*
+ * Query Block, Bios and Mount interfaces.
+ */
+ pIf->pDrvBlock = (PDMIBLOCK *)pIf->pDrvBase->pfnQueryInterface(pIf->pDrvBase, PDMINTERFACE_BLOCK);
+ if (!pIf->pDrvBlock)
+ {
+ AssertMsgFailed(("Configuration error: LUN#%d hasn't a block interface!\n", pIf->iLUN));
+ return VERR_PDM_MISSING_INTERFACE;
+ }
+
+ /** @todo implement the BIOS invisible code path. */
+ pIf->pDrvBlockBios = (PDMIBLOCKBIOS *)pIf->pDrvBase->pfnQueryInterface(pIf->pDrvBase, PDMINTERFACE_BLOCK_BIOS);
+ if (!pIf->pDrvBlockBios)
+ {
+ AssertMsgFailed(("Configuration error: LUN#%d hasn't a block BIOS interface!\n", pIf->iLUN));
+ return VERR_PDM_MISSING_INTERFACE;
+ }
+ pIf->pDrvMount = (PDMIMOUNT *)pIf->pDrvBase->pfnQueryInterface(pIf->pDrvBase, PDMINTERFACE_MOUNT);
+
+ /*
+ * Validate type.
+ */
+ enmType = pIf->pDrvBlock->pfnGetType(pIf->pDrvBlock);
+ if ( enmType != PDMBLOCKTYPE_CDROM
+ && enmType != PDMBLOCKTYPE_DVD
+ && enmType != PDMBLOCKTYPE_HARD_DISK)
+ {
+ AssertMsgFailed(("Configuration error: LUN#%d isn't a disk or cd/dvd-rom. enmType=%d\n", pIf->iLUN, enmType));
+ return VERR_PDM_UNSUPPORTED_BLOCK_TYPE;
+ }
+ if ( ( enmType == PDMBLOCKTYPE_DVD
+ || enmType == PDMBLOCKTYPE_CDROM)
+ && !pIf->pDrvMount)
+ {
+ AssertMsgFailed(("Internal error: cdrom without a mountable interface, WTF???!\n"));
+ return VERR_INTERNAL_ERROR;
+ }
+ pIf->fATAPI = enmType == PDMBLOCKTYPE_DVD || enmType == PDMBLOCKTYPE_CDROM;
+ pIf->fATAPIPassthrough = pIf->fATAPI ? (pIf->pDrvBlock->pfnSendCmd != NULL) : false;
+
+ /*
+ * Allocate I/O buffer.
+ */
+ PVM pVM = PDMDevHlpGetVM(pDevIns);
+ if (pIf->cbIOBuffer)
+ {
+ /* Buffer is (probably) already allocated. Validate the fields,
+ * because memory corruption can also overwrite pIf->cbIOBuffer. */
+ if (pIf->fATAPI)
+ AssertRelease(pIf->cbIOBuffer == _128K);
+ else
+ AssertRelease(pIf->cbIOBuffer == ATA_MAX_MULT_SECTORS * 512);
+ Assert(pIf->pbIOBufferR3);
+ Assert(pIf->pbIOBufferR0 == MMHyperR3ToR0(pVM, pIf->pbIOBufferR3));
+ Assert(pIf->pbIOBufferRC == MMHyperR3ToRC(pVM, pIf->pbIOBufferR3));
+ }
+ else
+ {
+ if (pIf->fATAPI)
+ pIf->cbIOBuffer = _128K;
+ else
+ pIf->cbIOBuffer = ATA_MAX_MULT_SECTORS * 512;
+ Assert(!pIf->pbIOBufferR3);
+ rc = MMR3HyperAllocOnceNoRel(pVM, pIf->cbIOBuffer, 0, MM_TAG_PDM_DEVICE_USER, (void **)&pIf->pbIOBufferR3);
+ if (RT_FAILURE(rc))
+ return VERR_NO_MEMORY;
+ pIf->pbIOBufferR0 = MMHyperR3ToR0(pVM, pIf->pbIOBufferR3);
+ pIf->pbIOBufferRC = MMHyperR3ToRC(pVM, pIf->pbIOBufferR3);
+ }
+
+ /*
+ * Init geometry (only for non-CD/DVD media).
+ */
+ if (pIf->fATAPI)
+ {
+ pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 2048;
+ pIf->PCHSGeometry.cCylinders = 0; /* dummy */
+ pIf->PCHSGeometry.cHeads = 0; /* dummy */
+ pIf->PCHSGeometry.cSectors = 0; /* dummy */
+ LogRel(("PIIX3 ATA: LUN#%d: CD/DVD, total number of sectors %Ld, passthrough %s\n", pIf->iLUN, pIf->cTotalSectors, (pIf->fATAPIPassthrough ? "enabled" : "disabled")));
+ }
+ else
+ {
+ pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 512;
+ rc = pIf->pDrvBlockBios->pfnGetPCHSGeometry(pIf->pDrvBlockBios,
+ &pIf->PCHSGeometry);
+ if (rc == VERR_PDM_MEDIA_NOT_MOUNTED)
+ {
+ pIf->PCHSGeometry.cCylinders = 0;
+ pIf->PCHSGeometry.cHeads = 16; /*??*/
+ pIf->PCHSGeometry.cSectors = 63; /*??*/
+ }
+ else if (rc == VERR_PDM_GEOMETRY_NOT_SET)
+ {
+ pIf->PCHSGeometry.cCylinders = 0; /* autodetect marker */
+ rc = VINF_SUCCESS;
+ }
+ AssertRC(rc);
+
+ if ( pIf->PCHSGeometry.cCylinders == 0
+ || pIf->PCHSGeometry.cHeads == 0
+ || pIf->PCHSGeometry.cSectors == 0
+ )
+ {
+ uint64_t cCylinders = pIf->cTotalSectors / (16 * 63);
+ pIf->PCHSGeometry.cCylinders = RT_MAX(RT_MIN(cCylinders, 16383), 1);
+ pIf->PCHSGeometry.cHeads = 16;
+ pIf->PCHSGeometry.cSectors = 63;
+ /* Set the disk geometry information. */
+ rc = pIf->pDrvBlockBios->pfnSetPCHSGeometry(pIf->pDrvBlockBios,
+ &pIf->PCHSGeometry);
+ }
+ LogRel(("PIIX3 ATA: LUN#%d: disk, PCHS=%u/%u/%u, total number of sectors %Ld\n", pIf->iLUN, pIf->PCHSGeometry.cCylinders, pIf->PCHSGeometry.cHeads, pIf->PCHSGeometry.cSectors, pIf->cTotalSectors));
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Attach command.
+ *
+ * This is called when we change block driver for the DVD drive.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ */
+static DECLCALLBACK(int) ataAttach(PPDMDEVINS pDevIns, unsigned iLUN)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl;
+ ATADevState *pIf;
+ int rc;
+ unsigned iController;
+ unsigned iInterface;
+
+ /*
+ * Locate the controller and stuff.
+ */
+ iController = iLUN / RT_ELEMENTS(pThis->aCts[0].aIfs);
+ AssertReleaseMsg(iController < RT_ELEMENTS(pThis->aCts), ("iController=%d iLUN=%d\n", iController, iLUN));
+ pCtl = &pThis->aCts[iController];
+
+ iInterface = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
+ pIf = &pCtl->aIfs[iInterface];
+
+ /* the usual paranoia */
+ AssertRelease(!pIf->pDrvBase);
+ AssertRelease(!pIf->pDrvBlock);
+ Assert(ATADEVSTATE_2_CONTROLLER(pIf) == pCtl);
+ Assert(pIf->iLUN == iLUN);
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIf->IBase, &pIf->pDrvBase, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ataConfigLun(pDevIns, pIf);
+ /*
+ * In case there is a medium inserted.
+ */
+ ataMediumInserted(pIf);
+ }
+ else
+ AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pIf->iLUN, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pIf->pDrvBase = NULL;
+ pIf->pDrvBlock = NULL;
+ }
+ return rc;
+}
+
+
+/**
+ * Suspend notification.
+ *
+ * @returns VBox status.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataSuspend(PPDMDEVINS pDevIns)
+{
+ Log(("%s:\n", __FUNCTION__));
+ if (!ataWaitForAllAsyncIOIsIdle(pDevIns, 20000))
+ AssertMsgFailed(("Async I/O didn't stop in 20 seconds!\n"));
+ return;
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @returns VBox status.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataResume(PPDMDEVINS pDevIns)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ int rc;
+
+ Log(("%s:\n", __FUNCTION__));
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].fRedo && pThis->aCts[i].fRedoIdle)
+ {
+ rc = RTSemEventSignal(pThis->aCts[i].SuspendIOSem);
+ AssertRC(rc);
+ }
+ }
+ return;
+}
+
+
+/**
+ * Power Off notification.
+ *
+ * @returns VBox status.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataPowerOff(PPDMDEVINS pDevIns)
+{
+ Log(("%s:\n", __FUNCTION__));
+ if (!ataWaitForAllAsyncIOIsIdle(pDevIns, 20000))
+ AssertMsgFailed(("Async I/O didn't stop in 20 seconds!\n"));
+ return;
+}
+
+
+/**
+ * Prepare state save and load operation.
+ *
+ * @returns VBox status code.
+ * @param pDevIns Device instance of the device which registered the data unit.
+ * @param pSSM SSM operation handle.
+ */
+static DECLCALLBACK(int) ataSaveLoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ /* sanity - the suspend notification will wait on the async stuff. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ Assert(ataAsyncIOIsIdle(&pThis->aCts[i], false));
+ if (!ataAsyncIOIsIdle(&pThis->aCts[i], false))
+ return VERR_SSM_IDE_ASYNC_TIMEOUT;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Saves a state of the ATA device.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pSSMHandle The handle to save the state to.
+ */
+static DECLCALLBACK(int) ataSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].iSelectedIf);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].iAIOIf);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].uAsyncIOState);
+ SSMR3PutBool(pSSMHandle, pThis->aCts[i].fChainedTransfer);
+ SSMR3PutBool(pSSMHandle, pThis->aCts[i].fReset);
+ SSMR3PutBool(pSSMHandle, pThis->aCts[i].fRedo);
+ SSMR3PutBool(pSSMHandle, pThis->aCts[i].fRedoIdle);
+ SSMR3PutBool(pSSMHandle, pThis->aCts[i].fRedoDMALastDesc);
+ SSMR3PutMem(pSSMHandle, &pThis->aCts[i].BmDma, sizeof(pThis->aCts[i].BmDma));
+ SSMR3PutGCPhys32(pSSMHandle, pThis->aCts[i].pFirstDMADesc);
+ SSMR3PutGCPhys32(pSSMHandle, pThis->aCts[i].pLastDMADesc);
+ SSMR3PutGCPhys32(pSSMHandle, pThis->aCts[i].pRedoDMABuffer);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].cbRedoDMABuffer);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ SSMR3PutBool(pSSMHandle, pThis->aCts[i].aIfs[j].fLBA48);
+ SSMR3PutBool(pSSMHandle, pThis->aCts[i].aIfs[j].fATAPI);
+ SSMR3PutBool(pSSMHandle, pThis->aCts[i].aIfs[j].fIrqPending);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].cMultSectors);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].PCHSGeometry.cCylinders);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].PCHSGeometry.cHeads);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].PCHSGeometry.cSectors);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].cSectorsPerIRQ);
+ SSMR3PutU64(pSSMHandle, pThis->aCts[i].aIfs[j].cTotalSectors);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegFeature);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegFeatureHOB);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegError);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegNSector);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegNSectorHOB);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegSector);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegSectorHOB);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegLCyl);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegLCylHOB);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegHCyl);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegHCylHOB);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegSelect);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegStatus);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegCommand);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATARegDevCtl);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uATATransferMode);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].uTxDir);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].iBeginTransfer);
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].iSourceSink);
+ SSMR3PutBool(pSSMHandle, pThis->aCts[i].aIfs[j].fDMA);
+ SSMR3PutBool(pSSMHandle, pThis->aCts[i].aIfs[j].fATAPITransfer);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].cbTotalTransfer);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].cbElementaryTransfer);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].iIOBufferCur);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].iIOBufferEnd);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].iIOBufferPIODataStart);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].iIOBufferPIODataEnd);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].iATAPILBA);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].cbATAPISector);
+ SSMR3PutMem(pSSMHandle, &pThis->aCts[i].aIfs[j].aATAPICmd, sizeof(pThis->aCts[i].aIfs[j].aATAPICmd));
+ SSMR3PutMem(pSSMHandle, &pThis->aCts[i].aIfs[j].abATAPISense, sizeof(pThis->aCts[i].aIfs[j].abATAPISense));
+ SSMR3PutU8(pSSMHandle, pThis->aCts[i].aIfs[j].cNotifiedMediaChange);
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].MediaEventStatus);
+ SSMR3PutMem(pSSMHandle, &pThis->aCts[i].aIfs[j].Led, sizeof(pThis->aCts[i].aIfs[j].Led));
+ SSMR3PutU32(pSSMHandle, pThis->aCts[i].aIfs[j].cbIOBuffer);
+ if (pThis->aCts[i].aIfs[j].cbIOBuffer)
+ SSMR3PutMem(pSSMHandle, pThis->aCts[i].aIfs[j].CTX_SUFF(pbIOBuffer), pThis->aCts[i].aIfs[j].cbIOBuffer);
+ else
+ Assert(pThis->aCts[i].aIfs[j].CTX_SUFF(pbIOBuffer) == NULL);
+ }
+ }
+ SSMR3PutBool(pSSMHandle, pThis->fPIIX4);
+
+ return SSMR3PutU32(pSSMHandle, ~0); /* sanity/terminator */
+}
+
+
+/**
+ * Loads a saved ATA device state.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pSSMHandle The handle to the saved state.
+ * @param u32Version The data unit version number.
+ */
+static DECLCALLBACK(int) ataLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle, uint32_t u32Version)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ int rc;
+ uint32_t u32;
+
+ if ( u32Version != ATA_SAVED_STATE_VERSION
+ && u32Version != ATA_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE
+ && u32Version != ATA_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
+ {
+ AssertMsgFailed(("u32Version=%d\n", u32Version));
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ }
+
+ /*
+ * Restore valid parts of the PCIATAState structure
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ /* integrity check */
+ if (!ataAsyncIOIsIdle(&pThis->aCts[i], false))
+ {
+ AssertMsgFailed(("Async I/O for controller %d is active\n", i));
+ rc = VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ return rc;
+ }
+
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].iSelectedIf);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].iAIOIf);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].uAsyncIOState);
+ SSMR3GetBool(pSSMHandle, &pThis->aCts[i].fChainedTransfer);
+ SSMR3GetBool(pSSMHandle, (bool *)&pThis->aCts[i].fReset);
+ SSMR3GetBool(pSSMHandle, (bool *)&pThis->aCts[i].fRedo);
+ SSMR3GetBool(pSSMHandle, (bool *)&pThis->aCts[i].fRedoIdle);
+ SSMR3GetBool(pSSMHandle, (bool *)&pThis->aCts[i].fRedoDMALastDesc);
+ SSMR3GetMem(pSSMHandle, &pThis->aCts[i].BmDma, sizeof(pThis->aCts[i].BmDma));
+ SSMR3GetGCPhys32(pSSMHandle, &pThis->aCts[i].pFirstDMADesc);
+ SSMR3GetGCPhys32(pSSMHandle, &pThis->aCts[i].pLastDMADesc);
+ SSMR3GetGCPhys32(pSSMHandle, &pThis->aCts[i].pRedoDMABuffer);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].cbRedoDMABuffer);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ SSMR3GetBool(pSSMHandle, &pThis->aCts[i].aIfs[j].fLBA48);
+ SSMR3GetBool(pSSMHandle, &pThis->aCts[i].aIfs[j].fATAPI);
+ SSMR3GetBool(pSSMHandle, &pThis->aCts[i].aIfs[j].fIrqPending);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].cMultSectors);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].PCHSGeometry.cCylinders);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].PCHSGeometry.cHeads);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].PCHSGeometry.cSectors);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].cSectorsPerIRQ);
+ SSMR3GetU64(pSSMHandle, &pThis->aCts[i].aIfs[j].cTotalSectors);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegFeature);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegFeatureHOB);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegError);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegNSector);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegNSectorHOB);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegSector);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegSectorHOB);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegLCyl);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegLCylHOB);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegHCyl);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegHCylHOB);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegSelect);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegStatus);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegCommand);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATARegDevCtl);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uATATransferMode);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].uTxDir);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].iBeginTransfer);
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].iSourceSink);
+ SSMR3GetBool(pSSMHandle, &pThis->aCts[i].aIfs[j].fDMA);
+ SSMR3GetBool(pSSMHandle, &pThis->aCts[i].aIfs[j].fATAPITransfer);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].cbTotalTransfer);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].cbElementaryTransfer);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].iIOBufferCur);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].iIOBufferEnd);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].iIOBufferPIODataStart);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].iIOBufferPIODataEnd);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].iATAPILBA);
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].cbATAPISector);
+ SSMR3GetMem(pSSMHandle, &pThis->aCts[i].aIfs[j].aATAPICmd, sizeof(pThis->aCts[i].aIfs[j].aATAPICmd));
+ if (u32Version > ATA_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE)
+ {
+ SSMR3GetMem(pSSMHandle, pThis->aCts[i].aIfs[j].abATAPISense, sizeof(pThis->aCts[i].aIfs[j].abATAPISense));
+ }
+ else
+ {
+ uint8_t uATAPISenseKey, uATAPIASC;
+ memset(pThis->aCts[i].aIfs[j].abATAPISense, '\0', sizeof(pThis->aCts[i].aIfs[j].abATAPISense));
+ pThis->aCts[i].aIfs[j].abATAPISense[0] = 0x70 | (1 << 7);
+ pThis->aCts[i].aIfs[j].abATAPISense[7] = 10;
+ SSMR3GetU8(pSSMHandle, &uATAPISenseKey);
+ SSMR3GetU8(pSSMHandle, &uATAPIASC);
+ pThis->aCts[i].aIfs[j].abATAPISense[2] = uATAPISenseKey & 0x0f;
+ pThis->aCts[i].aIfs[j].abATAPISense[12] = uATAPIASC;
+ }
+ /** @todo triple-check this hack after passthrough is working */
+ SSMR3GetU8(pSSMHandle, &pThis->aCts[i].aIfs[j].cNotifiedMediaChange);
+ if (u32Version > ATA_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
+ SSMR3GetU32(pSSMHandle, (uint32_t*)&pThis->aCts[i].aIfs[j].MediaEventStatus);
+ else
+ pThis->aCts[i].aIfs[j].MediaEventStatus = ATA_EVENT_STATUS_UNCHANGED;
+ SSMR3GetMem(pSSMHandle, &pThis->aCts[i].aIfs[j].Led, sizeof(pThis->aCts[i].aIfs[j].Led));
+ SSMR3GetU32(pSSMHandle, &pThis->aCts[i].aIfs[j].cbIOBuffer);
+ if (pThis->aCts[i].aIfs[j].cbIOBuffer)
+ {
+ if (pThis->aCts[i].aIfs[j].CTX_SUFF(pbIOBuffer))
+ SSMR3GetMem(pSSMHandle, pThis->aCts[i].aIfs[j].CTX_SUFF(pbIOBuffer), pThis->aCts[i].aIfs[j].cbIOBuffer);
+ else
+ {
+ LogRel(("ATA: No buffer for %d/%d\n", i, j));
+ if (SSMR3HandleGetAfter(pSSMHandle) != SSMAFTER_DEBUG_IT)
+ return VERR_SSM_LOAD_CONFIG_MISMATCH;
+
+ /* skip the buffer if we're loading for the debugger / animator. */
+ uint8_t u8Ignored;
+ size_t cbLeft = pThis->aCts[i].aIfs[j].cbIOBuffer;
+ while (cbLeft-- > 0)
+ SSMR3GetU8(pSSMHandle, &u8Ignored);
+ }
+ }
+ else
+ Assert(pThis->aCts[i].aIfs[j].CTX_SUFF(pbIOBuffer) == NULL);
+ }
+ }
+ SSMR3GetBool(pSSMHandle, &pThis->fPIIX4);
+
+ rc = SSMR3GetU32(pSSMHandle, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (u32 != ~0U)
+ {
+ AssertMsgFailed(("u32=%#x expected ~0\n", u32));
+ rc = VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Construct a device instance for a VM.
+ *
+ * @returns VBox status.
+ * @param pDevIns The device instance data.
+ * If the registration structure is needed, pDevIns->pDevReg points to it.
+ * @param iInstance Instance number. Use this to figure out which registers and such to use.
+ * The device number is also found in pDevIns->iInstance, but since it's
+ * likely to be freqently used PDM passes it as parameter.
+ * @param pCfgHandle Configuration node handle for the device. Use this to obtain the configuration
+ * of the device instance. It's also found in pDevIns->pCfgHandle, but like
+ * iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) ataConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfgHandle)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PPDMIBASE pBase;
+ int rc;
+ bool fGCEnabled;
+ bool fR0Enabled;
+ uint32_t DelayIRQMillies;
+
+ Assert(iInstance == 0);
+
+ /*
+ * Initialize NIL handle values (for the destructor).
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThis->aCts[i].AsyncIOSem = NIL_RTSEMEVENT;
+ pThis->aCts[i].SuspendIOSem = NIL_RTSEMEVENT;
+ pThis->aCts[i].AsyncIORequestMutex = NIL_RTSEMEVENT;
+ }
+
+ /*
+ * Validate and read configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfgHandle, "GCEnabled\0IRQDelay\0R0Enabled\0PIIX4\0"))
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("PIIX3 configuration error: unknown option specified"));
+
+ rc = CFGMR3QueryBoolDef(pCfgHandle, "GCEnabled", &fGCEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read GCEnabled as boolean"));
+ Log(("%s: fGCEnabled=%d\n", __FUNCTION__, fGCEnabled));
+
+ rc = CFGMR3QueryBoolDef(pCfgHandle, "R0Enabled", &fR0Enabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read R0Enabled as boolean"));
+ Log(("%s: fR0Enabled=%d\n", __FUNCTION__, fR0Enabled));
+
+ rc = CFGMR3QueryU32Def(pCfgHandle, "IRQDelay", &DelayIRQMillies, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read IRQDelay as integer"));
+ Log(("%s: DelayIRQMillies=%d\n", __FUNCTION__, DelayIRQMillies));
+ Assert(DelayIRQMillies < 50);
+
+ rc = CFGMR3QueryBoolDef(pCfgHandle, "PIIX4", &pThis->fPIIX4, false);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read PIIX4 as boolean"));
+ Log(("%s: fPIIX4=%d\n", __FUNCTION__, pThis->fPIIX4));
+
+ /*
+ * Initialize data (most of it anyway).
+ */
+ /* Status LUN. */
+ pThis->IBase.pfnQueryInterface = ataStatus_QueryInterface;
+ pThis->ILeds.pfnQueryStatusLed = ataStatus_QueryStatusLed;
+
+ /* PCI configuration space. */
+ PCIDevSetVendorId(&pThis->dev, 0x8086); /* Intel */
+ if (pThis->fPIIX4)
+ {
+ PCIDevSetDeviceId(&pThis->dev, 0x7111); /* PIIX4 IDE */
+ PCIDevSetRevisionId(&pThis->dev, 0x01); /* PIIX4E */
+ pThis->dev.config[0x48] = 0x00; /* UDMACTL */
+ pThis->dev.config[0x4A] = 0x00; /* UDMATIM */
+ pThis->dev.config[0x4B] = 0x00;
+ }
+ else
+ PCIDevSetDeviceId(&pThis->dev, 0x7010); /* PIIX3 IDE */
+ PCIDevSetCommand( &pThis->dev, PCI_COMMAND_IOACCESS | PCI_COMMAND_MEMACCESS | PCI_COMMAND_BUSMASTER);
+ PCIDevSetClassProg( &pThis->dev, 0x8a); /* programming interface = PCI_IDE bus master is supported */
+ PCIDevSetClassSub( &pThis->dev, 0x01); /* class_sub = PCI_IDE */
+ PCIDevSetClassBase( &pThis->dev, 0x01); /* class_base = PCI_mass_storage */
+ PCIDevSetHeaderType(&pThis->dev, 0x00);
+
+ pThis->pDevIns = pDevIns;
+ pThis->fGCEnabled = fGCEnabled;
+ pThis->fR0Enabled = fR0Enabled;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThis->aCts[i].pDevInsR3 = pDevIns;
+ pThis->aCts[i].pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->aCts[i].pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pThis->aCts[i].DelayIRQMillies = (uint32_t)DelayIRQMillies;
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ ATADevState *pIf = &pThis->aCts[i].aIfs[j];
+
+ pIf->iLUN = i * RT_ELEMENTS(pThis->aCts) + j;
+ pIf->pDevInsR3 = pDevIns;
+ pIf->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pIf->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pIf->pControllerR3 = &pThis->aCts[i];
+ pIf->pControllerR0 = MMHyperR3ToR0(PDMDevHlpGetVM(pDevIns), &pThis->aCts[i]);
+ pIf->pControllerRC = MMHyperR3ToRC(PDMDevHlpGetVM(pDevIns), &pThis->aCts[i]);
+ pIf->IBase.pfnQueryInterface = ataQueryInterface;
+ pIf->IMountNotify.pfnMountNotify = ataMountNotify;
+ pIf->IMountNotify.pfnUnmountNotify = ataUnmountNotify;
+ pIf->Led.u32Magic = PDMLED_MAGIC;
+ }
+ }
+
+ Assert(RT_ELEMENTS(pThis->aCts) == 2);
+ pThis->aCts[0].irq = 14;
+ pThis->aCts[0].IOPortBase1 = 0x1f0;
+ pThis->aCts[0].IOPortBase2 = 0x3f6;
+ pThis->aCts[1].irq = 15;
+ pThis->aCts[1].IOPortBase1 = 0x170;
+ pThis->aCts[1].IOPortBase2 = 0x376;
+
+ /*
+ * Register the PCI device.
+ * N.B. There's a hack in the PIIX3 PCI bridge device to assign this
+ * device the slot next to itself.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, &pThis->dev);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 cannot register PCI device"));
+ AssertMsg(pThis->dev.devfn == 9 || iInstance != 0, ("pThis->dev.devfn=%d\n", pThis->dev.devfn));
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 4, 0x10, PCI_ADDRESS_SPACE_IO, ataBMDMAIORangeMap);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 cannot register PCI I/O region for BMDMA"));
+
+ /*
+ * Register the I/O ports.
+ * The ports are all hardcoded and enforced by the PIIX3 host bridge controller.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ rc = PDMDevHlpIOPortRegister(pDevIns, pThis->aCts[i].IOPortBase1, 8, (RTHCPTR)i,
+ ataIOPortWrite1, ataIOPortRead1, ataIOPortWriteStr1, ataIOPortReadStr1, "ATA I/O Base 1");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register I/O handlers"));
+
+ if (fGCEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterGC(pDevIns, pThis->aCts[i].IOPortBase1, 8, (RTGCPTR)i,
+ "ataIOPortWrite1", "ataIOPortRead1", "ataIOPortWriteStr1", "ataIOPortReadStr1", "ATA I/O Base 1");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register I/O handlers (GC)"));
+ }
+
+ if (fR0Enabled)
+ {
+#if 1
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, pThis->aCts[i].IOPortBase1, 8, (RTR0PTR)i,
+ "ataIOPortWrite1", "ataIOPortRead1", NULL, NULL, "ATA I/O Base 1");
+#else
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, pThis->aCts[i].IOPortBase1, 8, (RTR0PTR)i,
+ "ataIOPortWrite1", "ataIOPortRead1", "ataIOPortWriteStr1", "ataIOPortReadStr1", "ATA I/O Base 1");
+#endif
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, "PIIX3 cannot register I/O handlers (R0).");
+ }
+
+ rc = PDMDevHlpIOPortRegister(pDevIns, pThis->aCts[i].IOPortBase2, 1, (RTHCPTR)i,
+ ataIOPortWrite2, ataIOPortRead2, NULL, NULL, "ATA I/O Base 2");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register base2 I/O handlers"));
+
+ if (fGCEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterGC(pDevIns, pThis->aCts[i].IOPortBase2, 1, (RTGCPTR)i,
+ "ataIOPortWrite2", "ataIOPortRead2", NULL, NULL, "ATA I/O Base 2");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register base2 I/O handlers (GC)"));
+ }
+ if (fR0Enabled)
+ {
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, pThis->aCts[i].IOPortBase2, 1, (RTR0PTR)i,
+ "ataIOPortWrite2", "ataIOPortRead2", NULL, NULL, "ATA I/O Base 2");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register base2 I/O handlers (R0)"));
+ }
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ ATADevState *pIf = &pThis->aCts[i].aIfs[j];
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATADMA, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Number of ATA DMA transfers.", "/Devices/ATA%d/Unit%d/DMA", i, j);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATAPIO, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Number of ATA PIO transfers.", "/Devices/ATA%d/Unit%d/PIO", i, j);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATAPIDMA, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Number of ATAPI DMA transfers.", "/Devices/ATA%d/Unit%d/AtapiDMA", i, j);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATAPIPIO, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Number of ATAPI PIO transfers.", "/Devices/ATA%d/Unit%d/AtapiPIO", i, j);
+#ifdef VBOX_WITH_STATISTICS /** @todo release too. */
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatReads, STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling of the read operations.", "/Devices/ATA%d/Unit%d/Reads", i, j);
+#endif
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Amount of data read.", "/Devices/ATA%d/Unit%d/ReadBytes", i, j);
+#ifdef VBOX_INSTRUMENT_DMA_WRITES
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatInstrVDWrites,STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling of the VD DMA write operations.","/Devices/ATA%d/Unit%d/InstrVDWrites", i, j);
+#endif
+#ifdef VBOX_WITH_STATISTICS
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatWrites, STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling of the write operations.","/Devices/ATA%d/Unit%d/Writes", i, j);
+#endif
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Amount of data written.", "/Devices/ATA%d/Unit%d/WrittenBytes", i, j);
+#ifdef VBOX_WITH_STATISTICS
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatFlushes, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling of the flush operations.","/Devices/ATA%d/Unit%d/Flushes", i, j);
+#endif
+ }
+#ifdef VBOX_WITH_STATISTICS /** @todo release too. */
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncOps, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "The number of async operations.", "/Devices/ATA%d/Async/Operations", i);
+ /** @todo STAMUNIT_MICROSECS */
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncMinWait, STAMTYPE_U64_RESET, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE, "Minimum wait in microseconds.", "/Devices/ATA%d/Async/MinWait", i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncMaxWait, STAMTYPE_U64_RESET, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE, "Maximum wait in microseconds.", "/Devices/ATA%d/Async/MaxWait", i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncTimeUS, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE, "Total time spent in microseconds.","/Devices/ATA%d/Async/TotalTimeUS", i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncTime, STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling of async operations.", "/Devices/ATA%d/Async/Time", i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatLockWait, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling of locks.", "/Devices/ATA%d/Async/LockWait", i);
+#endif /* VBOX_WITH_STATISTICS */
+
+ /* Initialize per-controller critical section */
+ char szName[24];
+ RTStrPrintf(szName, sizeof(szName), "ATA%d", i);
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->aCts[i].lock, szName);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot initialize critical section"));
+ }
+
+ /*
+ * Attach status driver (optional).
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ pThis->pLedsConnector = (PDMILEDCONNECTORS *)pBase->pfnQueryInterface(pBase, PDMINTERFACE_LED_CONNECTORS);
+ else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot attach to status driver"));
+ }
+
+ /*
+ * Attach the units.
+ */
+ uint32_t cbTotalBuffer = 0;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+
+ /*
+ * Start the worker thread.
+ */
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ rc = RTSemEventCreate(&pCtl->AsyncIOSem);
+ AssertRC(rc);
+ rc = RTSemEventCreate(&pCtl->SuspendIOSem);
+ AssertRC(rc);
+ rc = RTSemMutexCreate(&pCtl->AsyncIORequestMutex);
+ AssertRC(rc);
+ ataAsyncIOClearRequests(pCtl);
+ rc = RTThreadCreateF(&pCtl->AsyncIOThread, ataAsyncIOLoop, (void *)pCtl, 128*1024, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ATA-%u", i);
+ AssertRC(rc);
+ Assert(pCtl->AsyncIOThread != NIL_RTTHREAD && pCtl->AsyncIOSem != NIL_RTSEMEVENT && pCtl->SuspendIOSem != NIL_RTSEMEVENT && pCtl->AsyncIORequestMutex != NIL_RTSEMMUTEX);
+ Log(("%s: controller %d AIO thread id %#x; sem %p susp_sem %p mutex %p\n", __FUNCTION__, i, pCtl->AsyncIOThread, pCtl->AsyncIOSem, pCtl->SuspendIOSem, pCtl->AsyncIORequestMutex));
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
+ {
+ static const char *s_apszDescs[RT_ELEMENTS(pThis->aCts)][RT_ELEMENTS(pCtl->aIfs)] =
+ {
+ { "Primary Master", "Primary Slave" },
+ { "Secondary Master", "Secondary Slave" }
+ };
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ ATADevState *pIf = &pCtl->aIfs[j];
+
+ rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIf->IBase, &pIf->pDrvBase, s_apszDescs[i][j]);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ataConfigLun(pDevIns, pIf);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Init vendor product data.
+ */
+ static const char *s_apszCFGMKeys[RT_ELEMENTS(pThis->aCts)][RT_ELEMENTS(pCtl->aIfs)] =
+ {
+ { "PrimaryMaster", "PrimarySlave" },
+ { "SecondaryMaster", "SecondarySlave" }
+ };
+
+ /* Generate a default serial number. */
+ char szSerial[ATA_SERIAL_NUMBER_LENGTH+1];
+ RTUUID Uuid;
+ if (pIf->pDrvBlock)
+ rc = pIf->pDrvBlock->pfnGetUuid(pIf->pDrvBlock, &Uuid);
+ else
+ RTUuidClear(&Uuid);
+
+ if (RT_FAILURE(rc) || RTUuidIsNull(&Uuid))
+ {
+ /* Generate a predictable serial for drives which don't have a UUID. */
+ RTStrPrintf(szSerial, sizeof(szSerial), "VB%x-%04x%04x",
+ pIf->iLUN + pDevIns->iInstance * 32,
+ pThis->aCts[i].IOPortBase1, pThis->aCts[i].IOPortBase2);
+ }
+ else
+ RTStrPrintf(szSerial, sizeof(szSerial), "VB%08x-%08x", Uuid.au32[0], Uuid.au32[3]);
+
+ /* Get user config if present using defaults otherwise. */
+ PCFGMNODE pCfgNode = CFGMR3GetChild(pCfgHandle, s_apszCFGMKeys[i][j]);
+ rc = CFGMR3QueryStringDef(pCfgNode, "SerialNumber", pIf->szSerialNumber, sizeof(pIf->szSerialNumber),
+ szSerial);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"SerialNumber\" is longer than 20 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"SerialNumber\" as string"));
+ }
+
+ rc = CFGMR3QueryStringDef(pCfgNode, "FirmwareRevision", pIf->szFirmwareRevision, sizeof(pIf->szFirmwareRevision),
+ "1.0");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"FirmwareRevision\" is longer than 8 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"FirmwareRevision\" as string"));
+ }
+
+ rc = CFGMR3QueryStringDef(pCfgNode, "ModelNumber", pIf->szModelNumber, sizeof(pIf->szModelNumber),
+ pIf->fATAPI ? "VBOX CD-ROM" : "VBOX HARDDISK");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"ModelNumber\" is longer than 40 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"ModelNumber\" as string"));
+ }
+ }
+
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pIf->pDrvBase = NULL;
+ pIf->pDrvBlock = NULL;
+ pIf->cbIOBuffer = 0;
+ pIf->pbIOBufferR3 = NULL;
+ pIf->pbIOBufferR0 = NIL_RTR0PTR;
+ pIf->pbIOBufferRC = NIL_RTGCPTR;
+ LogRel(("PIIX3 ATA: LUN#%d: no unit\n", pIf->iLUN));
+ }
+ else
+ {
+ switch (rc)
+ {
+ case VERR_ACCESS_DENIED:
+ /* Error already catched by DrvHostBase */
+ return rc;
+ default:
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("PIIX3 cannot attach drive to the %s"),
+ s_apszDescs[i][j]);
+ }
+ }
+ cbTotalBuffer += pIf->cbIOBuffer;
+ }
+ }
+
+ rc = PDMDevHlpSSMRegister(pDevIns, pDevIns->pDevReg->szDeviceName, iInstance,
+ ATA_SAVED_STATE_VERSION, sizeof(*pThis) + cbTotalBuffer,
+ ataSaveLoadPrep, ataSaveExec, NULL,
+ ataSaveLoadPrep, ataLoadExec, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register save state handlers"));
+
+ /*
+ * Initialize the device state.
+ */
+ ataReset(pDevIns);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DevicePIIX3IDE =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szDeviceName */
+ "piix3ide",
+ /* szRCMod */
+ "VBoxDDGC.gc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "Intel PIIX3 ATA controller.\n"
+ " LUN #0 is primary master.\n"
+ " LUN #1 is primary slave.\n"
+ " LUN #2 is secondary master.\n"
+ " LUN #3 is secondary slave.\n"
+ " LUN #999 is the LED/Status connector.",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0 |
+ PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION,
+ /* fClass */
+ PDM_DEVREG_CLASS_STORAGE,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(PCIATAState),
+ /* pfnConstruct */
+ ataConstruct,
+ /* pfnDestruct */
+ ataDestruct,
+ /* pfnRelocate */
+ ataRelocate,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ ataReset,
+ /* pfnSuspend */
+ ataSuspend,
+ /* pfnResume */
+ ataResume,
+ /* pfnAttach */
+ ataAttach,
+ /* pfnDetach */
+ ataDetach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ ataPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
+
diff --git a/src/VBox/Devices/Storage/DrvBlock.cpp b/src/VBox/Devices/Storage/DrvBlock.cpp
new file mode 100644
index 000000000..3fd24fab0
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvBlock.cpp
@@ -0,0 +1,930 @@
+/** @file
+ *
+ * VBox storage devices:
+ * Generic block driver
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_BLOCK
+#include <VBox/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+
+#include <string.h>
+
+#include "Builtins.h"
+
+
+/** @def VBOX_PERIODIC_FLUSH
+ * Enable support for periodically flushing the VDI to disk. This may prove
+ * useful for those nasty problems with the ultra-slow host filesystems.
+ * If this is enabled, it can be configured via the CFGM key
+ * "VBoxInternal/Devices/piix3ide/0/LUN#<x>/Config/FlushInterval". <x>
+ * must be replaced with the correct LUN number of the disk that should
+ * do the periodic flushes. The value of the key is the number of bytes
+ * written between flushes. A value of 0 (the default) denotes no flushes. */
+#define VBOX_PERIODIC_FLUSH
+
+/** @def VBOX_IGNORE_FLUSH
+ * Enable support for ignoring VDI flush requests. This can be useful for
+ * filesystems that show bad guest IDE write performance (especially with
+ * Windows guests). NOTE that this does not disable the flushes caused by
+ * the periodic flush cache feature above.
+ * If this feature is enabled, it can be configured via the CFGM key
+ * "VBoxInternal/Devices/piix3ide/0/LUN#<x>/Config/IgnoreFlush". <x>
+ * must be replaced with the correct LUN number of the disk that should
+ * ignore flush requests. The value of the key is a boolean. The default
+ * is to ignore flushes, i.e. true. */
+#define VBOX_IGNORE_FLUSH
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+/**
+ * Block driver instance data.
+ */
+typedef struct DRVBLOCK
+{
+ /** Pointer driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Drive type. */
+ PDMBLOCKTYPE enmType;
+ /** Locked indicator. */
+ bool fLocked;
+ /** Mountable indicator. */
+ bool fMountable;
+ /** Visible to the BIOS. */
+ bool fBiosVisible;
+#ifdef VBOX_PERIODIC_FLUSH
+ /** HACK: Configuration value for number of bytes written after which to flush. */
+ uint32_t cbFlushInterval;
+ /** HACK: Current count for the number of bytes written since the last flush. */
+ uint32_t cbDataWritten;
+#endif /* VBOX_PERIODIC_FLUSH */
+#ifdef VBOX_IGNORE_FLUSH
+ /** HACK: Disable flushes for this drive. */
+ bool fIgnoreFlush;
+#endif /* VBOX_IGNORE_FLUSH */
+ /** Pointer to the media driver below us.
+ * This is NULL if the media is not mounted. */
+ PPDMIMEDIA pDrvMedia;
+ /** Pointer to the block port interface above us. */
+ PPDMIBLOCKPORT pDrvBlockPort;
+ /** Pointer to the mount notify interface above us. */
+ PPDMIMOUNTNOTIFY pDrvMountNotify;
+ /** Our block interface. */
+ PDMIBLOCK IBlock;
+ /** Our block interface. */
+ PDMIBLOCKBIOS IBlockBios;
+ /** Our mountable interface. */
+ PDMIMOUNT IMount;
+
+ /** Pointer to the async media driver below us.
+ * This is NULL if the media is not mounted. */
+ PPDMIMEDIAASYNC pDrvMediaAsync;
+ /** Our media async port. */
+ PDMIMEDIAASYNCPORT IMediaAsyncPort;
+ /** Pointer to the async block port interface above us. */
+ PPDMIBLOCKASYNCPORT pDrvBlockAsyncPort;
+ /** Our async block interface. */
+ PDMIBLOCKASYNC IBlockAsync;
+
+ /** Uuid of the drive. */
+ RTUUID Uuid;
+
+ /** BIOS PCHS Geometry. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** BIOS LCHS Geometry. */
+ PDMMEDIAGEOMETRY LCHSGeometry;
+} DRVBLOCK, *PDRVBLOCK;
+
+
+/* -=-=-=-=- IBlock -=-=-=-=- */
+
+/** Makes a PDRVBLOCK out of a PPDMIBLOCK. */
+#define PDMIBLOCK_2_DRVBLOCK(pInterface) ( (PDRVBLOCK)((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IBlock)) )
+
+/** @copydoc PDMIBLOCK::pfnRead */
+static DECLCALLBACK(int) drvblockRead(PPDMIBLOCK pInterface, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMedia)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ int rc = pThis->pDrvMedia->pfnRead(pThis->pDrvMedia, off, pvBuf, cbRead);
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnWrite */
+static DECLCALLBACK(int) drvblockWrite(PPDMIBLOCK pInterface, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMedia)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ int rc = pThis->pDrvMedia->pfnWrite(pThis->pDrvMedia, off, pvBuf, cbWrite);
+#ifdef VBOX_PERIODIC_FLUSH
+ if (pThis->cbFlushInterval)
+ {
+ pThis->cbDataWritten += cbWrite;
+ if (pThis->cbDataWritten > pThis->cbFlushInterval)
+ {
+ pThis->cbDataWritten = 0;
+ pThis->pDrvMedia->pfnFlush(pThis->pDrvMedia);
+ }
+ }
+#endif /* VBOX_PERIODIC_FLUSH */
+
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnFlush */
+static DECLCALLBACK(int) drvblockFlush(PPDMIBLOCK pInterface)
+{
+ PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMedia)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+#ifdef VBOX_IGNORE_FLUSH
+ if (pThis->fIgnoreFlush)
+ return VINF_SUCCESS;
+#endif /* VBOX_IGNORE_FLUSH */
+
+ int rc = pThis->pDrvMedia->pfnFlush(pThis->pDrvMedia);
+ if (rc == VERR_NOT_IMPLEMENTED)
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnIsReadOnly */
+static DECLCALLBACK(bool) drvblockIsReadOnly(PPDMIBLOCK pInterface)
+{
+ PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMedia)
+ return false;
+
+ bool fRc = pThis->pDrvMedia->pfnIsReadOnly(pThis->pDrvMedia);
+ return fRc;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnGetSize */
+static DECLCALLBACK(uint64_t) drvblockGetSize(PPDMIBLOCK pInterface)
+{
+ PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMedia)
+ return 0;
+
+ uint64_t cb = pThis->pDrvMedia->pfnGetSize(pThis->pDrvMedia);
+ LogFlow(("drvblockGetSize: returns %llu\n", cb));
+ return cb;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnGetType */
+static DECLCALLBACK(PDMBLOCKTYPE) drvblockGetType(PPDMIBLOCK pInterface)
+{
+ PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
+ LogFlow(("drvblockGetType: returns %d\n", pThis->enmType));
+ return pThis->enmType;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnGetUuid */
+static DECLCALLBACK(int) drvblockGetUuid(PPDMIBLOCK pInterface, PRTUUID pUuid)
+{
+ PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
+
+ /*
+ * Copy the uuid.
+ */
+ *pUuid = pThis->Uuid;
+ return VINF_SUCCESS;
+}
+
+/* -=-=-=-=- IBlockAsync -=-=-=-=- */
+
+/** Makes a PDRVBLOCK out of a PPDMIBLOCKASYNC. */
+#define PDMIBLOCKASYNC_2_DRVBLOCK(pInterface) ( (PDRVBLOCK)((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IBlockAsync)) )
+
+/** @copydoc PDMIBLOCKASYNC::pfnStartRead */
+static DECLCALLBACK(int) drvblockAsyncReadStart(PPDMIBLOCKASYNC pInterface, uint64_t off, PPDMDATASEG pSeg, unsigned cSeg, size_t cbRead, void *pvUser)
+{
+ PDRVBLOCK pThis = PDMIBLOCKASYNC_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMediaAsync)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ int rc = pThis->pDrvMediaAsync->pfnStartRead(pThis->pDrvMediaAsync, off, pSeg, cSeg, cbRead, pvUser);
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCKASYNC::pfnStartWrite */
+static DECLCALLBACK(int) drvblockAsyncWriteStart(PPDMIBLOCKASYNC pInterface, uint64_t off, PPDMDATASEG pSeg, unsigned cSeg, size_t cbWrite, void *pvUser)
+{
+ PDRVBLOCK pThis = PDMIBLOCKASYNC_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMediaAsync)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ int rc = pThis->pDrvMediaAsync->pfnStartWrite(pThis->pDrvMediaAsync, off, pSeg, cSeg, cbWrite, pvUser);
+
+ return rc;
+}
+
+/* -=-=-=-=- IMediaAsyncPort -=-=-=-=- */
+
+/** Makes a PDRVBLOCKASYNC out of a PPDMIMEDIAASYNCPORT. */
+#define PDMIMEDIAASYNCPORT_2_DRVBLOCK(pInterface) ( (PDRVBLOCK((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IMediaAsyncPort))) )
+
+static DECLCALLBACK(int) drvblockAsyncTransferCompleteNotify(PPDMIMEDIAASYNCPORT pInterface, void *pvUser)
+{
+ PDRVBLOCK pThis = PDMIMEDIAASYNCPORT_2_DRVBLOCK(pInterface);
+
+ return pThis->pDrvBlockAsyncPort->pfnTransferCompleteNotify(pThis->pDrvBlockAsyncPort, pvUser);
+}
+
+/* -=-=-=-=- IBlockBios -=-=-=-=- */
+
+/** Makes a PDRVBLOCK out of a PPDMIBLOCKBIOS. */
+#define PDMIBLOCKBIOS_2_DRVBLOCK(pInterface) ( (PDRVBLOCK((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IBlockBios))) )
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) drvblockGetPCHSGeometry(PPDMIBLOCKBIOS pInterface, PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMedia)
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+
+ /*
+ * Use configured/cached values if present.
+ */
+ if ( pThis->PCHSGeometry.cCylinders > 0
+ && pThis->PCHSGeometry.cHeads > 0
+ && pThis->PCHSGeometry.cSectors > 0)
+ {
+ *pPCHSGeometry = pThis->PCHSGeometry;
+ LogFlow(("%s: returns VINF_SUCCESS {%d,%d,%d}\n", __FUNCTION__, pThis->PCHSGeometry.cCylinders, pThis->PCHSGeometry.cHeads, pThis->PCHSGeometry.cSectors));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Call media.
+ */
+ int rc = pThis->pDrvMedia->pfnBiosGetPCHSGeometry(pThis->pDrvMedia, &pThis->PCHSGeometry);
+
+ if (RT_SUCCESS(rc))
+ {
+ *pPCHSGeometry = pThis->PCHSGeometry;
+ LogFlow(("%s: returns %Rrc {%d,%d,%d}\n", __FUNCTION__, rc, pThis->PCHSGeometry.cCylinders, pThis->PCHSGeometry.cHeads, pThis->PCHSGeometry.cSectors));
+ }
+ else if (rc == VERR_NOT_IMPLEMENTED)
+ {
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ }
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) drvblockSetPCHSGeometry(PPDMIBLOCKBIOS pInterface, PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlow(("%s: cCylinders=%d cHeads=%d cSectors=%d\n", __FUNCTION__, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMedia)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ /*
+ * Call media. Ignore the not implemented return code.
+ */
+ int rc = pThis->pDrvMedia->pfnBiosSetPCHSGeometry(pThis->pDrvMedia, pPCHSGeometry);
+
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_IMPLEMENTED)
+ {
+ pThis->PCHSGeometry = *pPCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) drvblockGetLCHSGeometry(PPDMIBLOCKBIOS pInterface, PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMedia)
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+
+ /*
+ * Use configured/cached values if present.
+ */
+ if ( pThis->LCHSGeometry.cCylinders > 0
+ && pThis->LCHSGeometry.cHeads > 0
+ && pThis->LCHSGeometry.cSectors > 0)
+ {
+ *pLCHSGeometry = pThis->LCHSGeometry;
+ LogFlow(("%s: returns VINF_SUCCESS {%d,%d,%d}\n", __FUNCTION__, pThis->LCHSGeometry.cCylinders, pThis->LCHSGeometry.cHeads, pThis->LCHSGeometry.cSectors));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Call media.
+ */
+ int rc = pThis->pDrvMedia->pfnBiosGetLCHSGeometry(pThis->pDrvMedia, &pThis->LCHSGeometry);
+
+ if (RT_SUCCESS(rc))
+ {
+ *pLCHSGeometry = pThis->LCHSGeometry;
+ LogFlow(("%s: returns %Rrc {%d,%d,%d}\n", __FUNCTION__, rc, pThis->LCHSGeometry.cCylinders, pThis->LCHSGeometry.cHeads, pThis->LCHSGeometry.cSectors));
+ }
+ else if (rc == VERR_NOT_IMPLEMENTED)
+ {
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ }
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) drvblockSetLCHSGeometry(PPDMIBLOCKBIOS pInterface, PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlow(("%s: cCylinders=%d cHeads=%d cSectors=%d\n", __FUNCTION__, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDrvMedia)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ /*
+ * Call media. Ignore the not implemented return code.
+ */
+ int rc = pThis->pDrvMedia->pfnBiosSetLCHSGeometry(pThis->pDrvMedia, pLCHSGeometry);
+
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_IMPLEMENTED)
+ {
+ pThis->LCHSGeometry = *pLCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnIsVisible */
+static DECLCALLBACK(bool) drvblockIsVisible(PPDMIBLOCKBIOS pInterface)
+{
+ PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
+ LogFlow(("drvblockIsVisible: returns %d\n", pThis->fBiosVisible));
+ return pThis->fBiosVisible;
+}
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnGetType */
+static DECLCALLBACK(PDMBLOCKTYPE) drvblockBiosGetType(PPDMIBLOCKBIOS pInterface)
+{
+ PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
+ LogFlow(("drvblockBiosGetType: returns %d\n", pThis->enmType));
+ return pThis->enmType;
+}
+
+
+
+/* -=-=-=-=- IMount -=-=-=-=- */
+
+/** Makes a PDRVBLOCK out of a PPDMIMOUNT. */
+#define PDMIMOUNT_2_DRVBLOCK(pInterface) ( (PDRVBLOCK)((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IMount)) )
+
+
+/** @copydoc PDMIMOUNT::pfnMount */
+static DECLCALLBACK(int) drvblockMount(PPDMIMOUNT pInterface, const char *pszFilename, const char *pszCoreDriver)
+{
+ LogFlow(("drvblockMount: pszFilename=%p:{%s} pszCoreDriver=%p:{%s}\n", pszFilename, pszFilename, pszCoreDriver, pszCoreDriver));
+ PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
+
+ /*
+ * Validate state.
+ */
+ if (pThis->pDrvMedia)
+ {
+ AssertMsgFailed(("Already mounted\n"));
+ return VERR_PDM_MEDIA_MOUNTED;
+ }
+
+ /*
+ * Prepare configuration.
+ */
+ if (pszFilename)
+ {
+ int rc = pThis->pDrvIns->pDrvHlp->pfnMountPrepare(pThis->pDrvIns, pszFilename, pszCoreDriver);
+ if (RT_FAILURE(rc))
+ {
+ Log(("drvblockMount: Prepare failed for \"%s\" rc=%Rrc\n", pszFilename, rc));
+ return rc;
+ }
+ }
+
+ /*
+ * Attach the media driver and query it's interface.
+ */
+ PPDMIBASE pBase;
+ int rc = pThis->pDrvIns->pDrvHlp->pfnAttach(pThis->pDrvIns, &pBase);
+ if (RT_FAILURE(rc))
+ {
+ Log(("drvblockMount: Attach failed rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ pThis->pDrvMedia = (PPDMIMEDIA)pBase->pfnQueryInterface(pBase, PDMINTERFACE_MEDIA);
+ if (pThis->pDrvMedia)
+ {
+ /*
+ * Initialize state.
+ */
+ pThis->fLocked = false;
+ pThis->PCHSGeometry.cCylinders = 0;
+ pThis->PCHSGeometry.cHeads = 0;
+ pThis->PCHSGeometry.cSectors = 0;
+ pThis->LCHSGeometry.cCylinders = 0;
+ pThis->LCHSGeometry.cHeads = 0;
+ pThis->LCHSGeometry.cSectors = 0;
+#ifdef VBOX_PERIODIC_FLUSH
+ pThis->cbDataWritten = 0;
+#endif /* VBOX_PERIODIC_FLUSH */
+
+ /*
+ * Notify driver/device above us.
+ */
+ if (pThis->pDrvMountNotify)
+ pThis->pDrvMountNotify->pfnMountNotify(pThis->pDrvMountNotify);
+ Log(("drvblockMount: Success\n"));
+ return VINF_SUCCESS;
+ }
+ else
+ rc = VERR_PDM_MISSING_INTERFACE_BELOW;
+
+ /*
+ * Failed, detatch the media driver.
+ */
+ AssertMsgFailed(("No media interface!\n"));
+ int rc2 = pThis->pDrvIns->pDrvHlp->pfnDetach(pThis->pDrvIns);
+ AssertRC(rc2);
+ pThis->pDrvMedia = NULL;
+ return rc;
+}
+
+
+/** @copydoc PDMIMOUNT::pfnUnmount */
+static DECLCALLBACK(int) drvblockUnmount(PPDMIMOUNT pInterface, bool fForce)
+{
+ PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
+
+ /*
+ * Validate state.
+ */
+ if (!pThis->pDrvMedia)
+ {
+ Log(("drvblockUmount: Not mounted\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+ if (pThis->fLocked && !fForce)
+ {
+ Log(("drvblockUmount: Locked\n"));
+ return VERR_PDM_MEDIA_LOCKED;
+ }
+
+ /* Media is no longer locked even if it was previously. */
+ pThis->fLocked = false;
+
+ /*
+ * Detach the media driver and query it's interface.
+ */
+ int rc = pThis->pDrvIns->pDrvHlp->pfnDetach(pThis->pDrvIns);
+ if (RT_FAILURE(rc))
+ {
+ Log(("drvblockUnmount: Detach failed rc=%Rrc\n", rc));
+ return rc;
+ }
+ Assert(!pThis->pDrvMedia);
+
+ /*
+ * Notify driver/device above us.
+ */
+ if (pThis->pDrvMountNotify)
+ pThis->pDrvMountNotify->pfnUnmountNotify(pThis->pDrvMountNotify);
+ Log(("drvblockUnmount: success\n"));
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc PDMIMOUNT::pfnIsMounted */
+static DECLCALLBACK(bool) drvblockIsMounted(PPDMIMOUNT pInterface)
+{
+ PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
+ return pThis->pDrvMedia != NULL;
+}
+
+/** @copydoc PDMIMOUNT::pfnLock */
+static DECLCALLBACK(int) drvblockLock(PPDMIMOUNT pInterface)
+{
+ PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
+ Log(("drvblockLock: %d -> %d\n", pThis->fLocked, true));
+ pThis->fLocked = true;
+ return VINF_SUCCESS;
+}
+
+/** @copydoc PDMIMOUNT::pfnUnlock */
+static DECLCALLBACK(int) drvblockUnlock(PPDMIMOUNT pInterface)
+{
+ PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
+ Log(("drvblockUnlock: %d -> %d\n", pThis->fLocked, false));
+ pThis->fLocked = false;
+ return VINF_SUCCESS;
+}
+
+/** @copydoc PDMIMOUNT::pfnIsLocked */
+static DECLCALLBACK(bool) drvblockIsLocked(PPDMIMOUNT pInterface)
+{
+ PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
+ return pThis->fLocked;
+}
+
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/** @copydoc PDMIBASE::pfnQueryInterface. */
+static DECLCALLBACK(void *) drvblockQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVBLOCK pThis = PDMINS_2_DATA(pDrvIns, PDRVBLOCK);
+ switch (enmInterface)
+ {
+ case PDMINTERFACE_BASE:
+ return &pDrvIns->IBase;
+ case PDMINTERFACE_BLOCK:
+ return &pThis->IBlock;
+ case PDMINTERFACE_BLOCK_BIOS:
+ return pThis->fBiosVisible ? &pThis->IBlockBios : NULL;
+ case PDMINTERFACE_MOUNT:
+ return pThis->fMountable ? &pThis->IMount : NULL;
+ case PDMINTERFACE_BLOCK_ASYNC:
+ return pThis->pDrvMediaAsync ? &pThis->IBlockAsync : NULL;
+ case PDMINTERFACE_MEDIA_ASYNC_PORT:
+ return pThis->pDrvBlockAsyncPort ? &pThis->IMediaAsyncPort : NULL;
+ default:
+ return NULL;
+ }
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+/** @copydoc FNPDMDRVDETACH. */
+static DECLCALLBACK(void) drvblockDetach(PPDMDRVINS pDrvIns)
+{
+ PDRVBLOCK pThis = PDMINS_2_DATA(pDrvIns, PDRVBLOCK);
+ pThis->pDrvMedia = NULL;
+ pThis->pDrvMediaAsync = NULL;
+}
+
+/**
+ * Reset notification.
+ *
+ * @returns VBox status.
+ * @param pDevIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvblockReset(PPDMDRVINS pDrvIns)
+{
+ PDRVBLOCK pThis = PDMINS_2_DATA(pDrvIns, PDRVBLOCK);
+
+ pThis->fLocked = false;
+}
+
+/**
+ * Construct a block driver instance.
+ *
+ * @returns VBox status.
+ * @param pDrvIns The driver instance data.
+ * If the registration structure is needed, pDrvIns->pDrvReg points to it.
+ * @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
+ * of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
+ * iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) drvblockConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+ PDRVBLOCK pThis = PDMINS_2_DATA(pDrvIns, PDRVBLOCK);
+ LogFlow(("drvblockConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+#if defined(VBOX_PERIODIC_FLUSH) || defined(VBOX_IGNORE_FLUSH)
+ if (!CFGMR3AreValuesValid(pCfgHandle, "Type\0Locked\0BIOSVisible\0AttachFailError\0Cylinders\0Heads\0Sectors\0Mountable\0FlushInterval\0IgnoreFlush\0"))
+#else /* !(VBOX_PERIODIC_FLUSH || VBOX_IGNORE_FLUSH) */
+ if (!CFGMR3AreValuesValid(pCfgHandle, "Type\0Locked\0BIOSVisible\0AttachFailError\0Cylinders\0Heads\0Sectors\0Mountable\0"))
+#endif /* !(VBOX_PERIODIC_FLUSH || VBOX_IGNORE_FLUSH) */
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+ /*
+ * Initialize most of the data members.
+ */
+ pThis->pDrvIns = pDrvIns;
+
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvblockQueryInterface;
+
+ /* IBlock. */
+ pThis->IBlock.pfnRead = drvblockRead;
+ pThis->IBlock.pfnWrite = drvblockWrite;
+ pThis->IBlock.pfnFlush = drvblockFlush;
+ pThis->IBlock.pfnIsReadOnly = drvblockIsReadOnly;
+ pThis->IBlock.pfnGetSize = drvblockGetSize;
+ pThis->IBlock.pfnGetType = drvblockGetType;
+ pThis->IBlock.pfnGetUuid = drvblockGetUuid;
+
+ /* IBlockBios. */
+ pThis->IBlockBios.pfnGetPCHSGeometry = drvblockGetPCHSGeometry;
+ pThis->IBlockBios.pfnSetPCHSGeometry = drvblockSetPCHSGeometry;
+ pThis->IBlockBios.pfnGetLCHSGeometry = drvblockGetLCHSGeometry;
+ pThis->IBlockBios.pfnSetLCHSGeometry = drvblockSetLCHSGeometry;
+ pThis->IBlockBios.pfnIsVisible = drvblockIsVisible;
+ pThis->IBlockBios.pfnGetType = drvblockBiosGetType;
+
+ /* IMount. */
+ pThis->IMount.pfnMount = drvblockMount;
+ pThis->IMount.pfnUnmount = drvblockUnmount;
+ pThis->IMount.pfnIsMounted = drvblockIsMounted;
+ pThis->IMount.pfnLock = drvblockLock;
+ pThis->IMount.pfnUnlock = drvblockUnlock;
+ pThis->IMount.pfnIsLocked = drvblockIsLocked;
+
+ /* IBlockAsync. */
+ pThis->IBlockAsync.pfnStartRead = drvblockAsyncReadStart;
+ pThis->IBlockAsync.pfnStartWrite = drvblockAsyncWriteStart;
+
+ /* IMediaAsyncPort. */
+ pThis->IMediaAsyncPort.pfnTransferCompleteNotify = drvblockAsyncTransferCompleteNotify;
+
+ /*
+ * Get the IBlockPort & IMountNotify interfaces of the above driver/device.
+ */
+ pThis->pDrvBlockPort = (PPDMIBLOCKPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_BLOCK_PORT);
+ if (!pThis->pDrvBlockPort)
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE,
+ N_("No block port interface above"));
+
+ /* Try to get the optional async block port interface above. */
+ pThis->pDrvBlockAsyncPort = (PPDMIBLOCKASYNCPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_BLOCK_ASYNC_PORT);
+
+ pThis->pDrvMountNotify = (PPDMIMOUNTNOTIFY)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_MOUNT_NOTIFY);
+
+ /*
+ * Query configuration.
+ */
+ /* type */
+ char *psz;
+ int rc = CFGMR3QueryStringAlloc(pCfgHandle, "Type", &psz);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_BLOCK_NO_TYPE, N_("Failed to obtain the type"));
+ if (!strcmp(psz, "HardDisk"))
+ pThis->enmType = PDMBLOCKTYPE_HARD_DISK;
+ else if (!strcmp(psz, "DVD"))
+ pThis->enmType = PDMBLOCKTYPE_DVD;
+ else if (!strcmp(psz, "CDROM"))
+ pThis->enmType = PDMBLOCKTYPE_CDROM;
+ else if (!strcmp(psz, "Floppy 2.88"))
+ pThis->enmType = PDMBLOCKTYPE_FLOPPY_2_88;
+ else if (!strcmp(psz, "Floppy 1.44"))
+ pThis->enmType = PDMBLOCKTYPE_FLOPPY_1_44;
+ else if (!strcmp(psz, "Floppy 1.20"))
+ pThis->enmType = PDMBLOCKTYPE_FLOPPY_1_20;
+ else if (!strcmp(psz, "Floppy 720"))
+ pThis->enmType = PDMBLOCKTYPE_FLOPPY_720;
+ else if (!strcmp(psz, "Floppy 360"))
+ pThis->enmType = PDMBLOCKTYPE_FLOPPY_360;
+ else
+ {
+ PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_BLOCK_UNKNOWN_TYPE, RT_SRC_POS,
+ N_("Unknown type \"%s\""), psz);
+ MMR3HeapFree(psz);
+ return VERR_PDM_BLOCK_UNKNOWN_TYPE;
+ }
+ Log2(("drvblockConstruct: enmType=%d\n", pThis->enmType));
+ MMR3HeapFree(psz); psz = NULL;
+
+ /* Mountable */
+ rc = CFGMR3QueryBoolDef(pCfgHandle, "Mountable", &pThis->fMountable, false);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Mountable\" from the config"));
+
+ /* Locked */
+ rc = CFGMR3QueryBoolDef(pCfgHandle, "Locked", &pThis->fLocked, false);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Locked\" from the config"));
+
+ /* BIOS visible */
+ rc = CFGMR3QueryBoolDef(pCfgHandle, "BIOSVisible", &pThis->fBiosVisible, true);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"BIOSVisible\" from the config"));
+
+ /** @todo AttachFailError is currently completely ignored. */
+
+ /* Cylinders */
+ rc = CFGMR3QueryU32Def(pCfgHandle, "Cylinders", &pThis->LCHSGeometry.cCylinders, 0);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Cylinders\" from the config"));
+
+ /* Heads */
+ rc = CFGMR3QueryU32Def(pCfgHandle, "Heads", &pThis->LCHSGeometry.cHeads, 0);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Heads\" from the config"));
+
+ /* Sectors */
+ rc = CFGMR3QueryU32Def(pCfgHandle, "Sectors", &pThis->LCHSGeometry.cSectors, 0);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Sectors\" from the config"));
+
+ /* Uuid */
+ rc = CFGMR3QueryStringAlloc(pCfgHandle, "Uuid", &psz);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ RTUuidClear(&pThis->Uuid);
+ else if (RT_SUCCESS(rc))
+ {
+ rc = RTUuidFromStr(&pThis->Uuid, psz);
+ if (RT_FAILURE(rc))
+ {
+ PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, "%s",
+ N_("Uuid from string failed on \"%s\""), psz);
+ MMR3HeapFree(psz);
+ return rc;
+ }
+ MMR3HeapFree(psz); psz = NULL;
+ }
+ else
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Uuid\" from the config"));
+
+#ifdef VBOX_PERIODIC_FLUSH
+ rc = CFGMR3QueryU32Def(pCfgHandle, "FlushInterval", &pThis->cbFlushInterval, 0);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"FlushInterval\" from the config"));
+#endif /* VBOX_PERIODIC_FLUSH */
+
+#ifdef VBOX_IGNORE_FLUSH
+ rc = CFGMR3QueryBoolDef(pCfgHandle, "IgnoreFlush", &pThis->fIgnoreFlush, true);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"IgnoreFlush\" from the config"));
+#endif /* VBOX_IGNORE_FLUSH */
+
+ /*
+ * Try attach driver below and query it's media interface.
+ */
+ PPDMIBASE pBase;
+ rc = pDrvIns->pDrvHlp->pfnAttach(pDrvIns, &pBase);
+ if ( rc == VERR_PDM_NO_ATTACHED_DRIVER
+ && pThis->enmType != PDMBLOCKTYPE_HARD_DISK)
+ return VINF_SUCCESS;
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("Failed to attach driver below us! rc=%Rra\n"), rc);
+
+ pThis->pDrvMedia = (PPDMIMEDIA)pBase->pfnQueryInterface(pBase, PDMINTERFACE_MEDIA);
+ if (!pThis->pDrvMedia)
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
+ N_("No media or async media interface below"));
+
+ /* Try to get the optional async interface. */
+ pThis->pDrvMediaAsync = (PPDMIMEDIAASYNC)pBase->pfnQueryInterface(pBase, PDMINTERFACE_MEDIA_ASYNC);
+
+ if (RTUuidIsNull(&pThis->Uuid))
+ {
+ if (pThis->enmType == PDMBLOCKTYPE_HARD_DISK)
+ pThis->pDrvMedia->pfnGetUuid(pThis->pDrvMedia, &pThis->Uuid);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Block driver registration record.
+ */
+const PDMDRVREG g_DrvBlock =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szDriverName */
+ "Block",
+ /* pszDescription */
+ "Generic block driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_BLOCK,
+ /* cMaxInstances */
+ ~0,
+ /* cbInstance */
+ sizeof(DRVBLOCK),
+ /* pfnConstruct */
+ drvblockConstruct,
+ /* pfnDestruct */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ drvblockReset,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnDetach */
+ drvblockDetach
+};
diff --git a/src/VBox/Devices/Storage/DrvHostBase.cpp b/src/VBox/Devices/Storage/DrvHostBase.cpp
new file mode 100644
index 000000000..25bd976a0
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase.cpp
@@ -0,0 +1,2048 @@
+/* $Id: DrvHostBase.cpp 13840 2008-11-05 03:31:46Z vboxsync $ */
+/** @file
+ * DrvHostBase - Host base drive access driver.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_BASE
+#ifdef RT_OS_DARWIN
+# include <mach/mach.h>
+# include <Carbon/Carbon.h>
+# include <IOKit/IOKitLib.h>
+# include <IOKit/storage/IOStorageDeviceCharacteristics.h>
+# include <IOKit/scsi-commands/SCSITaskLib.h>
+# include <IOKit/scsi-commands/SCSICommandOperationCodes.h>
+# include <IOKit/IOBSD.h>
+# include <DiskArbitration/DiskArbitration.h>
+# include <mach/mach_error.h>
+# include <VBox/scsi.h>
+
+#elif defined(RT_OS_L4)
+ /* Nothing special requires... yeah, right. */
+
+#elif defined(RT_OS_LINUX)
+# include <sys/ioctl.h>
+# include <sys/fcntl.h>
+# include <errno.h>
+
+#elif defined(RT_OS_SOLARIS)
+# include <fcntl.h>
+# include <errno.h>
+# include <stropts.h>
+# include <malloc.h>
+# include <sys/dkio.h>
+extern "C" char *getfullblkname(char *);
+
+#elif defined(RT_OS_WINDOWS)
+# define WIN32_NO_STATUS
+# include <Windows.h>
+# include <dbt.h>
+# undef WIN32_NO_STATUS
+# include <ntstatus.h>
+
+/* from ntdef.h */
+typedef LONG NTSTATUS;
+
+/* from ntddk.h */
+typedef struct _IO_STATUS_BLOCK {
+ union {
+ NTSTATUS Status;
+ PVOID Pointer;
+ };
+ ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+
+/* from ntinternals.com */
+typedef enum _FS_INFORMATION_CLASS {
+ FileFsVolumeInformation=1,
+ FileFsLabelInformation,
+ FileFsSizeInformation,
+ FileFsDeviceInformation,
+ FileFsAttributeInformation,
+ FileFsControlInformation,
+ FileFsFullSizeInformation,
+ FileFsObjectIdInformation,
+ FileFsMaximumInformation
+} FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS;
+
+typedef struct _FILE_FS_SIZE_INFORMATION {
+ LARGE_INTEGER TotalAllocationUnits;
+ LARGE_INTEGER AvailableAllocationUnits;
+ ULONG SectorsPerAllocationUnit;
+ ULONG BytesPerSector;
+} FILE_FS_SIZE_INFORMATION, *PFILE_FS_SIZE_INFORMATION;
+
+extern "C"
+NTSTATUS __stdcall NtQueryVolumeInformationFile(
+ /*IN*/ HANDLE FileHandle,
+ /*OUT*/ PIO_STATUS_BLOCK IoStatusBlock,
+ /*OUT*/ PVOID FileSystemInformation,
+ /*IN*/ ULONG Length,
+ /*IN*/ FS_INFORMATION_CLASS FileSystemInformationClass );
+
+#else
+# error "Unsupported Platform."
+#endif
+
+#include <VBox/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/uuid.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/ctype.h>
+
+#include "DrvHostBase.h"
+
+
+
+
+/* -=-=-=-=- IBlock -=-=-=-=- */
+
+/** @copydoc PDMIBLOCK::pfnRead */
+static DECLCALLBACK(int) drvHostBaseRead(PPDMIBLOCK pInterface, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
+ LogFlow(("%s-%d: drvHostBaseRead: off=%#llx pvBuf=%p cbRead=%#x (%s)\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, off, pvBuf, cbRead, pThis->pszDevice));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Check the state.
+ */
+ int rc;
+#ifdef RT_OS_DARWIN
+ if ( pThis->fMediaPresent
+ && pThis->ppScsiTaskDI
+ && pThis->cbBlock)
+#else
+ if (pThis->fMediaPresent)
+#endif
+ {
+#ifdef RT_OS_DARWIN
+ /*
+ * Issue a READ(12) request.
+ */
+ const uint32_t LBA = off / pThis->cbBlock;
+ AssertReturn(!(off % pThis->cbBlock), VERR_INVALID_PARAMETER);
+ const uint32_t cBlocks = cbRead / pThis->cbBlock;
+ AssertReturn(!(cbRead % pThis->cbBlock), VERR_INVALID_PARAMETER);
+ uint8_t abCmd[16] =
+ {
+ SCSI_READ_12, 0,
+ RT_BYTE4(LBA), RT_BYTE3(LBA), RT_BYTE2(LBA), RT_BYTE1(LBA),
+ RT_BYTE4(cBlocks), RT_BYTE3(cBlocks), RT_BYTE2(cBlocks), RT_BYTE1(cBlocks),
+ 0, 0, 0, 0, 0
+ };
+ rc = DRVHostBaseScsiCmd(pThis, abCmd, 12, PDMBLOCKTXDIR_FROM_DEVICE, pvBuf, &cbRead, NULL, 0, 0);
+
+#else
+ /*
+ * Seek and read.
+ */
+ rc = RTFileSeek(pThis->FileDevice, off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileRead(pThis->FileDevice, pvBuf, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("%s-%d: drvHostBaseRead: off=%#llx cbRead=%#x\n"
+ "%16.*Rhxd\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, off, cbRead, cbRead, pvBuf));
+ }
+ else
+ Log(("%s-%d: drvHostBaseRead: RTFileRead(%d, %p, %#x) -> %Rrc (off=%#llx '%s')\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pThis->FileDevice,
+ pvBuf, cbRead, rc, off, pThis->pszDevice));
+ }
+ else
+ Log(("%s-%d: drvHostBaseRead: RTFileSeek(%d,%#llx,) -> %Rrc\n", pThis->pDrvIns->pDrvReg->szDriverName,
+ pThis->pDrvIns->iInstance, pThis->FileDevice, off, rc));
+#endif
+ }
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseRead: returns %Rrc\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnWrite */
+static DECLCALLBACK(int) drvHostBaseWrite(PPDMIBLOCK pInterface, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
+ LogFlow(("%s-%d: drvHostBaseWrite: off=%#llx pvBuf=%p cbWrite=%#x (%s)\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, off, pvBuf, cbWrite, pThis->pszDevice));
+ Log2(("%s-%d: drvHostBaseWrite: off=%#llx cbWrite=%#x\n"
+ "%16.*Rhxd\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, off, cbWrite, cbWrite, pvBuf));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Check the state.
+ */
+ int rc;
+ if (!pThis->fReadOnly)
+ {
+ if (pThis->fMediaPresent)
+ {
+#ifdef RT_OS_DARWIN
+ /** @todo write support... */
+ rc = VERR_WRITE_PROTECT;
+
+#else
+ /*
+ * Seek and write.
+ */
+ rc = RTFileSeek(pThis->FileDevice, off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(pThis->FileDevice, pvBuf, cbWrite, NULL);
+ if (RT_FAILURE(rc))
+ Log(("%s-%d: drvHostBaseWrite: RTFileWrite(%d, %p, %#x) -> %Rrc (off=%#llx '%s')\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pThis->FileDevice,
+ pvBuf, cbWrite, rc, off, pThis->pszDevice));
+ }
+ else
+ Log(("%s-%d: drvHostBaseWrite: RTFileSeek(%d,%#llx,) -> %Rrc\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pThis->FileDevice, off, rc));
+#endif
+ }
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseWrite: returns %Rrc\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnFlush */
+static DECLCALLBACK(int) drvHostBaseFlush(PPDMIBLOCK pInterface)
+{
+ int rc;
+ PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
+ LogFlow(("%s-%d: drvHostBaseFlush: (%s)\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pThis->pszDevice));
+ RTCritSectEnter(&pThis->CritSect);
+
+ if (pThis->fMediaPresent)
+ {
+#ifdef RT_OS_DARWIN
+ rc = VINF_SUCCESS;
+ /** @todo scsi device buffer flush... */
+#else
+ rc = RTFileFlush(pThis->FileDevice);
+#endif
+ }
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseFlush: returns %Rrc\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnIsReadOnly */
+static DECLCALLBACK(bool) drvHostBaseIsReadOnly(PPDMIBLOCK pInterface)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
+ return pThis->fReadOnly;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnGetSize */
+static DECLCALLBACK(uint64_t) drvHostBaseGetSize(PPDMIBLOCK pInterface)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
+ RTCritSectEnter(&pThis->CritSect);
+
+ uint64_t cb = 0;
+ if (pThis->fMediaPresent)
+ cb = pThis->cbSize;
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseGetSize: returns %llu\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, cb));
+ return cb;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnGetType */
+static DECLCALLBACK(PDMBLOCKTYPE) drvHostBaseGetType(PPDMIBLOCK pInterface)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
+ LogFlow(("%s-%d: drvHostBaseGetType: returns %d\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pThis->enmType));
+ return pThis->enmType;
+}
+
+
+/** @copydoc PDMIBLOCK::pfnGetUuid */
+static DECLCALLBACK(int) drvHostBaseGetUuid(PPDMIBLOCK pInterface, PRTUUID pUuid)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
+
+ *pUuid = pThis->Uuid;
+
+ LogFlow(("%s-%d: drvHostBaseGetUuid: returns VINF_SUCCESS *pUuid=%RTuuid\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pUuid));
+ return VINF_SUCCESS;
+}
+
+
+/* -=-=-=-=- IBlockBios -=-=-=-=- */
+
+/** Makes a PDRVHOSTBASE out of a PPDMIBLOCKBIOS. */
+#define PDMIBLOCKBIOS_2_DRVHOSTBASE(pInterface) ( (PDRVHOSTBASE((uintptr_t)pInterface - RT_OFFSETOF(DRVHOSTBASE, IBlockBios))) )
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) drvHostBaseGetPCHSGeometry(PPDMIBLOCKBIOS pInterface, PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCKBIOS_2_DRVHOSTBASE(pInterface);
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fMediaPresent)
+ {
+ if ( pThis->PCHSGeometry.cCylinders > 0
+ && pThis->PCHSGeometry.cHeads > 0
+ && pThis->PCHSGeometry.cSectors > 0)
+ {
+ *pPCHSGeometry = pThis->PCHSGeometry;
+ }
+ else
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_PDM_MEDIA_NOT_MOUNTED;
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: %s: returns %Rrc CHS={%d,%d,%d}\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, __FUNCTION__, rc, pThis->PCHSGeometry.cCylinders, pThis->PCHSGeometry.cHeads, pThis->PCHSGeometry.cSectors));
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) drvHostBaseSetPCHSGeometry(PPDMIBLOCKBIOS pInterface, PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCKBIOS_2_DRVHOSTBASE(pInterface);
+ LogFlow(("%s-%d: %s: cCylinders=%d cHeads=%d cSectors=%d\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, __FUNCTION__, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fMediaPresent)
+ {
+ pThis->PCHSGeometry = *pPCHSGeometry;
+ }
+ else
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ rc = VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) drvHostBaseGetLCHSGeometry(PPDMIBLOCKBIOS pInterface, PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCKBIOS_2_DRVHOSTBASE(pInterface);
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fMediaPresent)
+ {
+ if ( pThis->LCHSGeometry.cCylinders > 0
+ && pThis->LCHSGeometry.cHeads > 0
+ && pThis->LCHSGeometry.cSectors > 0)
+ {
+ *pLCHSGeometry = pThis->LCHSGeometry;
+ }
+ else
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_PDM_MEDIA_NOT_MOUNTED;
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: %s: returns %Rrc CHS={%d,%d,%d}\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, __FUNCTION__, rc, pThis->LCHSGeometry.cCylinders, pThis->LCHSGeometry.cHeads, pThis->LCHSGeometry.cSectors));
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) drvHostBaseSetLCHSGeometry(PPDMIBLOCKBIOS pInterface, PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCKBIOS_2_DRVHOSTBASE(pInterface);
+ LogFlow(("%s-%d: %s: cCylinders=%d cHeads=%d cSectors=%d\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, __FUNCTION__, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fMediaPresent)
+ {
+ pThis->LCHSGeometry = *pLCHSGeometry;
+ }
+ else
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ rc = VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnIsVisible */
+static DECLCALLBACK(bool) drvHostBaseIsVisible(PPDMIBLOCKBIOS pInterface)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCKBIOS_2_DRVHOSTBASE(pInterface);
+ return pThis->fBiosVisible;
+}
+
+
+/** @copydoc PDMIBLOCKBIOS::pfnGetType */
+static DECLCALLBACK(PDMBLOCKTYPE) drvHostBaseBiosGetType(PPDMIBLOCKBIOS pInterface)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCKBIOS_2_DRVHOSTBASE(pInterface);
+ return pThis->enmType;
+}
+
+
+
+/* -=-=-=-=- IMount -=-=-=-=- */
+
+/** @copydoc PDMIMOUNT::pfnMount */
+static DECLCALLBACK(int) drvHostBaseMount(PPDMIMOUNT pInterface, const char *pszFilename, const char *pszCoreDriver)
+{
+ /* We're not mountable. */
+ AssertMsgFailed(("drvHostBaseMount: This shouldn't be called!\n"));
+ return VERR_PDM_MEDIA_MOUNTED;
+}
+
+
+/** @copydoc PDMIMOUNT::pfnUnmount */
+static DECLCALLBACK(int) drvHostBaseUnmount(PPDMIMOUNT pInterface, bool fForce)
+{
+ LogFlow(("drvHostBaseUnmount: returns VERR_NOT_SUPPORTED\n"));
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/** @copydoc PDMIMOUNT::pfnIsMounted */
+static DECLCALLBACK(bool) drvHostBaseIsMounted(PPDMIMOUNT pInterface)
+{
+ PDRVHOSTBASE pThis = PDMIMOUNT_2_DRVHOSTBASE(pInterface);
+ RTCritSectEnter(&pThis->CritSect);
+
+ bool fRc = pThis->fMediaPresent;
+
+ RTCritSectLeave(&pThis->CritSect);
+ return fRc;
+}
+
+
+/** @copydoc PDMIMOUNT::pfnIsLocked */
+static DECLCALLBACK(int) drvHostBaseLock(PPDMIMOUNT pInterface)
+{
+ PDRVHOSTBASE pThis = PDMIMOUNT_2_DRVHOSTBASE(pInterface);
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (!pThis->fLocked)
+ {
+ if (pThis->pfnDoLock)
+ rc = pThis->pfnDoLock(pThis, true);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = true;
+ }
+ else
+ LogFlow(("%s-%d: drvHostBaseLock: already locked\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance));
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseLock: returns %Rrc\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+
+/** @copydoc PDMIMOUNT::pfnIsLocked */
+static DECLCALLBACK(int) drvHostBaseUnlock(PPDMIMOUNT pInterface)
+{
+ PDRVHOSTBASE pThis = PDMIMOUNT_2_DRVHOSTBASE(pInterface);
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fLocked)
+ {
+ if (pThis->pfnDoLock)
+ rc = pThis->pfnDoLock(pThis, false);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = false;
+ }
+ else
+ LogFlow(("%s-%d: drvHostBaseUnlock: not locked\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance));
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseUnlock: returns %Rrc\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+
+/** @copydoc PDMIMOUNT::pfnIsLocked */
+static DECLCALLBACK(bool) drvHostBaseIsLocked(PPDMIMOUNT pInterface)
+{
+ PDRVHOSTBASE pThis = PDMIMOUNT_2_DRVHOSTBASE(pInterface);
+ RTCritSectEnter(&pThis->CritSect);
+
+ bool fRc = pThis->fLocked;
+
+ RTCritSectLeave(&pThis->CritSect);
+ return fRc;
+}
+
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/** @copydoc PDMIBASE::pfnQueryInterface. */
+static DECLCALLBACK(void *) drvHostBaseQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
+ switch (enmInterface)
+ {
+ case PDMINTERFACE_BASE:
+ return &pDrvIns->IBase;
+ case PDMINTERFACE_BLOCK:
+ return &pThis->IBlock;
+ case PDMINTERFACE_BLOCK_BIOS:
+ return pThis->fBiosVisible ? &pThis->IBlockBios : NULL;
+ case PDMINTERFACE_MOUNT:
+ return &pThis->IMount;
+ default:
+ return NULL;
+ }
+}
+
+
+/* -=-=-=-=- poller thread -=-=-=-=- */
+
+#ifdef RT_OS_DARWIN
+/** The runloop input source name for the disk arbitration events. */
+#define MY_RUN_LOOP_MODE CFSTR("drvHostBaseDA")
+
+/**
+ * Gets the BSD Name (/dev/disc[0-9]+) for the service.
+ *
+ * This is done by recursing down the I/O registry until we hit upon an entry
+ * with a BSD Name. Usually we find it two levels down. (Further down under
+ * the IOCDPartitionScheme, the volume (slices) BSD Name is found. We don't
+ * seem to have to go this far fortunately.)
+ *
+ * @return VINF_SUCCESS if found, VERR_FILE_NOT_FOUND otherwise.
+ * @param Entry The current I/O registry entry reference.
+ * @param pszName Where to store the name. 128 bytes.
+ * @param cRecursions Number of recursions. This is used as an precation
+ * just to limit the depth and avoid blowing the stack
+ * should we hit a bug or something.
+ */
+static int drvHostBaseGetBSDName(io_registry_entry_t Entry, char *pszName, unsigned cRecursions)
+{
+ int rc = VERR_FILE_NOT_FOUND;
+ io_iterator_t Children = 0;
+ kern_return_t krc = IORegistryEntryGetChildIterator(Entry, kIOServicePlane, &Children);
+ if (krc == KERN_SUCCESS)
+ {
+ io_object_t Child;
+ while ( rc == VERR_FILE_NOT_FOUND
+ && (Child = IOIteratorNext(Children)) != 0)
+ {
+ CFStringRef BSDNameStrRef = (CFStringRef)IORegistryEntryCreateCFProperty(Child, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
+ if (BSDNameStrRef)
+ {
+ if (CFStringGetCString(BSDNameStrRef, pszName, 128, kCFStringEncodingUTF8))
+ rc = VINF_SUCCESS;
+ else
+ AssertFailed();
+ CFRelease(BSDNameStrRef);
+ }
+ if (rc == VERR_FILE_NOT_FOUND && cRecursions < 10)
+ rc = drvHostBaseGetBSDName(Child, pszName, cRecursions + 1);
+ IOObjectRelease(Child);
+ }
+ IOObjectRelease(Children);
+ }
+ return rc;
+}
+
+
+/**
+ * Callback notifying us that the async DADiskClaim()/DADiskUnmount call has completed.
+ *
+ * @param DiskRef The disk that was attempted claimed / unmounted.
+ * @param DissenterRef NULL on success, contains details on failure.
+ * @param pvContext Pointer to the return code variable.
+ */
+static void drvHostBaseDADoneCallback(DADiskRef DiskRef, DADissenterRef DissenterRef, void *pvContext)
+{
+ int *prc = (int *)pvContext;
+ if (!DissenterRef)
+ *prc = 0;
+ else
+ *prc = DADissenterGetStatus(DissenterRef) ? DADissenterGetStatus(DissenterRef) : -1;
+ CFRunLoopStop(CFRunLoopGetCurrent());
+}
+
+
+/**
+ * Obtain exclusive access to the DVD device, umount it if necessary.
+ *
+ * @return VBox status code.
+ * @param pThis The driver instance.
+ * @param DVDService The DVD service object.
+ */
+static int drvHostBaseObtainExclusiveAccess(PDRVHOSTBASE pThis, io_object_t DVDService)
+{
+ PPDMDRVINS pDrvIns = pThis->pDrvIns; NOREF(pDrvIns);
+
+ for (unsigned iTry = 0;; iTry++)
+ {
+ IOReturn irc = (*pThis->ppScsiTaskDI)->ObtainExclusiveAccess(pThis->ppScsiTaskDI);
+ if (irc == kIOReturnSuccess)
+ {
+ /*
+ * This is a bit weird, but if we unmounted the DVD drive we also need to
+ * unlock it afterwards or the guest won't be able to eject it later on.
+ */
+ if (pThis->pDADisk)
+ {
+ uint8_t abCmd[16] =
+ {
+ SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, false, 0,
+ 0,0,0,0,0,0,0,0,0,0
+ };
+ DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, NULL, 0, 0);
+ }
+ return VINF_SUCCESS;
+ }
+ if (irc == kIOReturnExclusiveAccess)
+ return VERR_SHARING_VIOLATION; /* already used exclusivly. */
+ if (irc != kIOReturnBusy)
+ return VERR_GENERAL_FAILURE; /* not mounted */
+
+ /*
+ * Attempt to the unmount all volumes of the device.
+ * It seems we can can do this all in one go without having to enumerate the
+ * volumes (sessions) and deal with them one by one. This is very fortuitous
+ * as the disk arbitration API is a bit cumbersome to deal with.
+ */
+ if (iTry > 2)
+ return VERR_DRIVE_LOCKED;
+ char szName[128];
+ int rc = drvHostBaseGetBSDName(DVDService, &szName[0], 0);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pDASession = DASessionCreate(kCFAllocatorDefault);
+ if (pThis->pDASession)
+ {
+ DASessionScheduleWithRunLoop(pThis->pDASession, CFRunLoopGetCurrent(), MY_RUN_LOOP_MODE);
+ pThis->pDADisk = DADiskCreateFromBSDName(kCFAllocatorDefault, pThis->pDASession, szName);
+ if (pThis->pDADisk)
+ {
+ /*
+ * Try claim the device.
+ */
+ Log(("%s-%d: calling DADiskClaim on '%s'.\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance, szName));
+ int rcDA = -2;
+ DADiskClaim(pThis->pDADisk, kDADiskClaimOptionDefault, NULL, NULL, drvHostBaseDADoneCallback, &rcDA);
+ SInt32 rc32 = CFRunLoopRunInMode(MY_RUN_LOOP_MODE, 120.0, FALSE);
+ AssertMsg(rc32 == kCFRunLoopRunStopped, ("rc32=%RI32 (%RX32)\n", rc32, rc32));
+ if ( rc32 == kCFRunLoopRunStopped
+ && !rcDA)
+ {
+ /*
+ * Try unmount the device.
+ */
+ Log(("%s-%d: calling DADiskUnmount on '%s'.\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance, szName));
+ rcDA = -2;
+ DADiskUnmount(pThis->pDADisk, kDADiskUnmountOptionWhole, drvHostBaseDADoneCallback, &rcDA);
+ SInt32 rc32 = CFRunLoopRunInMode(MY_RUN_LOOP_MODE, 120.0, FALSE);
+ AssertMsg(rc32 == kCFRunLoopRunStopped, ("rc32=%RI32 (%RX32)\n", rc32, rc32));
+ if ( rc32 == kCFRunLoopRunStopped
+ && !rcDA)
+ {
+ iTry = 99;
+ DASessionUnscheduleFromRunLoop(pThis->pDASession, CFRunLoopGetCurrent(), MY_RUN_LOOP_MODE);
+ Log(("%s-%d: unmount succeed - retrying.\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance));
+ continue;
+ }
+ Log(("%s-%d: umount => rc32=%d & rcDA=%#x\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance, rc32, rcDA));
+
+ /* failed - cleanup */
+ DADiskUnclaim(pThis->pDADisk);
+ }
+ else
+ Log(("%s-%d: claim => rc32=%d & rcDA=%#x\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance, rc32, rcDA));
+
+ CFRelease(pThis->pDADisk);
+ pThis->pDADisk = NULL;
+ }
+ else
+ Log(("%s-%d: failed to open disk '%s'!\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance, szName));
+
+ DASessionUnscheduleFromRunLoop(pThis->pDASession, CFRunLoopGetCurrent(), MY_RUN_LOOP_MODE);
+ CFRelease(pThis->pDASession);
+ pThis->pDASession = NULL;
+ }
+ else
+ Log(("%s-%d: failed to create DA session!\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance));
+ }
+ RTThreadSleep(10);
+ }
+}
+#endif /* RT_OS_DARWIN */
+
+
+#ifndef RT_OS_SOLARIS
+/**
+ * Wrapper for open / RTFileOpen / IOKit.
+ *
+ * @remark The Darwin code must correspond exactly to the enumeration
+ * done in Main/darwin/iokit.c.
+ */
+static int drvHostBaseOpen(PDRVHOSTBASE pThis, PRTFILE pFileDevice, bool fReadOnly)
+{
+#ifdef RT_OS_DARWIN
+ /* Darwin is kind of special... */
+ Assert(!pFileDevice); NOREF(pFileDevice);
+ Assert(!pThis->cbBlock);
+ Assert(!pThis->MasterPort);
+ Assert(!pThis->ppMMCDI);
+ Assert(!pThis->ppScsiTaskDI);
+
+ /*
+ * Open the master port on the first invocation.
+ */
+ kern_return_t krc = IOMasterPort(MACH_PORT_NULL, &pThis->MasterPort);
+ AssertReturn(krc == KERN_SUCCESS, VERR_GENERAL_FAILURE);
+
+ /*
+ * Create a matching dictionary for searching for DVD services in the IOKit.
+ *
+ * [If I understand this correctly, plain CDROMs doesn't show up as
+ * IODVDServices. Too keep things simple, we will only support DVDs
+ * until somebody complains about it and we get hardware to test it on.
+ * (Unless I'm much mistaken, there aren't any (orignal) intel macs with
+ * plain cdroms.)]
+ */
+ CFMutableDictionaryRef RefMatchingDict = IOServiceMatching("IODVDServices");
+ AssertReturn(RefMatchingDict, NULL);
+
+ /*
+ * do the search and get a collection of keyboards.
+ */
+ io_iterator_t DVDServices = NULL;
+ IOReturn irc = IOServiceGetMatchingServices(pThis->MasterPort, RefMatchingDict, &DVDServices);
+ AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%d\n", irc), NULL);
+ RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */
+
+ /*
+ * Enumerate the DVD drives (services).
+ * (This enumeration must be identical to the one performed in DrvHostBase.cpp.)
+ */
+ int rc = VERR_FILE_NOT_FOUND;
+ unsigned i = 0;
+ io_object_t DVDService;
+ while ((DVDService = IOIteratorNext(DVDServices)) != 0)
+ {
+ /*
+ * Get the properties we use to identify the DVD drive.
+ *
+ * While there is a (weird 12 byte) GUID, it isn't persistent
+ * accross boots. So, we have to use a combination of the
+ * vendor name and product name properties with an optional
+ * sequence number for identification.
+ */
+ CFMutableDictionaryRef PropsRef = 0;
+ kern_return_t krc = IORegistryEntryCreateCFProperties(DVDService, &PropsRef, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ /* Get the Device Characteristics dictionary. */
+ CFDictionaryRef DevCharRef = (CFDictionaryRef)CFDictionaryGetValue(PropsRef, CFSTR(kIOPropertyDeviceCharacteristicsKey));
+ if (DevCharRef)
+ {
+ /* The vendor name. */
+ char szVendor[128];
+ char *pszVendor = &szVendor[0];
+ CFTypeRef ValueRef = CFDictionaryGetValue(DevCharRef, CFSTR(kIOPropertyVendorNameKey));
+ if ( ValueRef
+ && CFGetTypeID(ValueRef) == CFStringGetTypeID()
+ && CFStringGetCString((CFStringRef)ValueRef, szVendor, sizeof(szVendor), kCFStringEncodingUTF8))
+ pszVendor = RTStrStrip(szVendor);
+ else
+ *pszVendor = '\0';
+
+ /* The product name. */
+ char szProduct[128];
+ char *pszProduct = &szProduct[0];
+ ValueRef = CFDictionaryGetValue(DevCharRef, CFSTR(kIOPropertyProductNameKey));
+ if ( ValueRef
+ && CFGetTypeID(ValueRef) == CFStringGetTypeID()
+ && CFStringGetCString((CFStringRef)ValueRef, szProduct, sizeof(szProduct), kCFStringEncodingUTF8))
+ pszProduct = RTStrStrip(szProduct);
+ else
+ *pszProduct = '\0';
+
+ /* Construct the two names and compare thwm with the one we're searching for. */
+ char szName1[256 + 32];
+ char szName2[256 + 32];
+ if (*pszVendor || *pszProduct)
+ {
+ if (*pszVendor && *pszProduct)
+ {
+ RTStrPrintf(szName1, sizeof(szName1), "%s %s", pszVendor, pszProduct);
+ RTStrPrintf(szName2, sizeof(szName2), "%s %s (#%u)", pszVendor, pszProduct, i);
+ }
+ else
+ {
+ strcpy(szName1, *pszVendor ? pszVendor : pszProduct);
+ RTStrPrintf(szName2, sizeof(szName2), "%s %s (#%u)", *pszVendor ? pszVendor : pszProduct, i);
+ }
+ }
+ else
+ {
+ RTStrPrintf(szName1, sizeof(szName1), "(#%u)", i);
+ strcpy(szName2, szName1);
+ }
+
+ if ( !strcmp(szName1, pThis->pszDeviceOpen)
+ || !strcmp(szName2, pThis->pszDeviceOpen))
+ {
+ /*
+ * Found it! Now, get the client interface and stuff.
+ * Note that we could also query kIOSCSITaskDeviceUserClientTypeID here if the
+ * MMC client plugin is missing. For now we assume this won't be necessary.
+ */
+ SInt32 Score = 0;
+ IOCFPlugInInterface **ppPlugInInterface = NULL;
+ krc = IOCreatePlugInInterfaceForService(DVDService, kIOMMCDeviceUserClientTypeID, kIOCFPlugInInterfaceID,
+ &ppPlugInInterface, &Score);
+ if (krc == KERN_SUCCESS)
+ {
+ HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
+ CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID),
+ (LPVOID *)&pThis->ppMMCDI);
+ (*ppPlugInInterface)->Release(ppPlugInInterface);
+ ppPlugInInterface = NULL;
+ if (hrc == S_OK)
+ {
+ pThis->ppScsiTaskDI = (*pThis->ppMMCDI)->GetSCSITaskDeviceInterface(pThis->ppMMCDI);
+ if (pThis->ppScsiTaskDI)
+ rc = VINF_SUCCESS;
+ else
+ {
+ LogRel(("GetSCSITaskDeviceInterface failed on '%s'\n", pThis->pszDeviceOpen));
+ rc = VERR_NOT_SUPPORTED;
+ (*pThis->ppMMCDI)->Release(pThis->ppMMCDI);
+ }
+ }
+ else
+ {
+ rc = VERR_GENERAL_FAILURE;//RTErrConvertFromDarwinCOM(krc);
+ pThis->ppMMCDI = NULL;
+ }
+ }
+ else /* Check for kIOSCSITaskDeviceUserClientTypeID? */
+ rc = VERR_GENERAL_FAILURE;//RTErrConvertFromDarwinKern(krc);
+
+ /* Obtain exclusive access to the device so we can send SCSI commands. */
+ if (RT_SUCCESS(rc))
+ rc = drvHostBaseObtainExclusiveAccess(pThis, DVDService);
+
+ /* Cleanup on failure. */
+ if (RT_FAILURE(rc))
+ {
+ if (pThis->ppScsiTaskDI)
+ {
+ (*pThis->ppScsiTaskDI)->Release(pThis->ppScsiTaskDI);
+ pThis->ppScsiTaskDI = NULL;
+ }
+ if (pThis->ppMMCDI)
+ {
+ (*pThis->ppMMCDI)->Release(pThis->ppMMCDI);
+ pThis->ppMMCDI = NULL;
+ }
+ }
+
+ IOObjectRelease(DVDService);
+ break;
+ }
+ }
+ CFRelease(PropsRef);
+ }
+ else
+ AssertMsgFailed(("krc=%#x\n", krc));
+
+ IOObjectRelease(DVDService);
+ i++;
+ }
+
+ IOObjectRelease(DVDServices);
+ return rc;
+
+#elif defined(RT_OS_LINUX)
+ /** @todo we've got RTFILE_O_NON_BLOCK now. Change the code to use RTFileOpen. */
+ int FileDevice = open(pThis->pszDeviceOpen, (pThis->fReadOnlyConfig ? O_RDONLY : O_RDWR) | O_NONBLOCK);
+ if (FileDevice < 0)
+ return RTErrConvertFromErrno(errno);
+ *pFileDevice = FileDevice;
+ return VINF_SUCCESS;
+
+#else
+ return RTFileOpen(pFileDevice, pThis->pszDeviceOpen,
+ (fReadOnly ? RTFILE_O_READ : RTFILE_O_READWRITE) | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+#endif
+}
+
+#else /* RT_OS_SOLARIS */
+
+/**
+ * Solaris wrapper for RTFileOpen.
+ *
+ * Solaris has to deal with two filehandles, a block and a raw one. Rather than messing
+ * with drvHostBaseOpen's function signature & body, having a seperate one is better.
+ *
+ * @returns VBox status code.
+ */
+static int drvHostBaseOpen(PDRVHOSTBASE pThis, PRTFILE pFileBlockDevice, PRTFILE pFileRawDevice, bool fReadOnly)
+{
+ unsigned fFlags = (fReadOnly ? RTFILE_O_READ : RTFILE_O_READWRITE) | RTFILE_O_NON_BLOCK;
+ int rc = RTFileOpen(pFileBlockDevice, pThis->pszDeviceOpen, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileOpen(pFileRawDevice, pThis->pszRawDeviceOpen, fFlags);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DVD: failed to open device %s\n", pThis->pszRawDeviceOpen));
+ RTFileClose(*pFileBlockDevice);
+ }
+ }
+ else
+ LogRel(("DVD: failed to open device %s\n", pThis->pszRawDeviceOpen));
+ return rc;
+}
+#endif /* RT_OS_SOLARIS */
+
+
+/**
+ * (Re)opens the device.
+ *
+ * This is used to open the device during construction, but it's also used to re-open
+ * the device when a media is inserted. This re-open will kill off any cached data
+ * that Linux for some peculiar reason thinks should survive a media change...
+ *
+ * @returns VBOX status code.
+ * @param pThis Instance data.
+ */
+static int drvHostBaseReopen(PDRVHOSTBASE pThis)
+{
+#ifndef RT_OS_DARWIN /* Only *one* open for darwin. */
+ LogFlow(("%s-%d: drvHostBaseReopen: '%s'\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pThis->pszDeviceOpen));
+
+ RTFILE FileDevice;
+#ifdef RT_OS_SOLARIS
+ RTFILE FileRawDevice;
+ int rc = drvHostBaseOpen(pThis, &FileDevice, &FileRawDevice, pThis->fReadOnlyConfig);
+#else
+ int rc = drvHostBaseOpen(pThis, &FileDevice, pThis->fReadOnlyConfig);
+#endif
+ if (RT_FAILURE(rc))
+ {
+ if (!pThis->fReadOnlyConfig)
+ {
+ LogFlow(("%s-%d: drvHostBaseReopen: '%s' - retry readonly (%Rrc)\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pThis->pszDeviceOpen, rc));
+#ifdef RT_OS_SOLARIS
+ rc = drvHostBaseOpen(pThis, &FileDevice, &FileRawDevice, false);
+#else
+ rc = drvHostBaseOpen(pThis, &FileDevice, false);
+#endif
+ }
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("%s-%d: failed to open device '%s', rc=%Rrc\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pThis->pszDevice, rc));
+ return rc;
+ }
+ pThis->fReadOnly = true;
+ }
+ else
+ pThis->fReadOnly = pThis->fReadOnlyConfig;
+
+#ifdef RT_OS_SOLARIS
+ if (pThis->FileRawDevice != NIL_RTFILE)
+ RTFileClose(pThis->FileRawDevice);
+ pThis->FileRawDevice = FileRawDevice;
+#endif
+
+ if (pThis->FileDevice != NIL_RTFILE)
+ RTFileClose(pThis->FileDevice);
+ pThis->FileDevice = FileDevice;
+#endif /* !RT_OS_DARWIN */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Queries the media size.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the instance data.
+ * @param pcb Where to store the media size in bytes.
+ */
+static int drvHostBaseGetMediaSize(PDRVHOSTBASE pThis, uint64_t *pcb)
+{
+#ifdef RT_OS_DARWIN
+ /*
+ * Try a READ_CAPACITY command...
+ */
+ struct
+ {
+ uint32_t cBlocks;
+ uint32_t cbBlock;
+ } Buf = {0, 0};
+ size_t cbBuf = sizeof(Buf);
+ uint8_t abCmd[16] =
+ {
+ SCSI_READ_CAPACITY, 0, 0, 0, 0, 0, 0,
+ 0,0,0,0,0,0,0,0,0
+ };
+ int rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_FROM_DEVICE, &Buf, &cbBuf, NULL, 0, 0);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbBuf == sizeof(Buf));
+ Buf.cBlocks = RT_BE2H_U32(Buf.cBlocks);
+ Buf.cbBlock = RT_BE2H_U32(Buf.cbBlock);
+ //if (Buf.cbBlock > 2048) /* everyone else is doing this... check if it needed/right.*/
+ // Buf.cbBlock = 2048;
+ pThis->cbBlock = Buf.cbBlock;
+
+ *pcb = (uint64_t)Buf.cBlocks * Buf.cbBlock;
+ }
+ return rc;
+
+#elif defined(RT_OS_SOLARIS)
+ /*
+ * Sun docs suggests using DKIOCGGEOM instead of DKIOCGMEDIAINFO, but
+ * Sun themselves use DKIOCGMEDIAINFO for DVDs/CDs, and use DKIOCGGEOM
+ * for secondary storage devices.
+ */
+ struct dk_minfo MediaInfo;
+ if (ioctl(pThis->FileRawDevice, DKIOCGMEDIAINFO, &MediaInfo) == 0)
+ {
+ *pcb = MediaInfo.dki_capacity * (uint64_t)MediaInfo.dki_lbsize;
+ return VINF_SUCCESS;
+ }
+ return RTFileSeek(pThis->FileDevice, 0, RTFILE_SEEK_END, pcb);
+
+#elif defined(RT_OS_WINDOWS)
+ /* use NT api, retry a few times if the media is being verified. */
+ IO_STATUS_BLOCK IoStatusBlock = {0};
+ FILE_FS_SIZE_INFORMATION FsSize= {0};
+ NTSTATUS rcNt = NtQueryVolumeInformationFile((HANDLE)pThis->FileDevice, &IoStatusBlock,
+ &FsSize, sizeof(FsSize), FileFsSizeInformation);
+ int cRetries = 5;
+ while (rcNt == STATUS_VERIFY_REQUIRED && cRetries-- > 0)
+ {
+ RTThreadSleep(10);
+ rcNt = NtQueryVolumeInformationFile((HANDLE)pThis->FileDevice, &IoStatusBlock,
+ &FsSize, sizeof(FsSize), FileFsSizeInformation);
+ }
+ if (rcNt >= 0)
+ {
+ *pcb = FsSize.TotalAllocationUnits.QuadPart * FsSize.BytesPerSector;
+ return VINF_SUCCESS;
+ }
+
+ /* convert nt status code to VBox status code. */
+ /** @todo Make convertion function!. */
+ int rc = VERR_GENERAL_FAILURE;
+ switch (rcNt)
+ {
+ case STATUS_NO_MEDIA_IN_DEVICE: rc = VERR_MEDIA_NOT_PRESENT; break;
+ case STATUS_VERIFY_REQUIRED: rc = VERR_TRY_AGAIN; break;
+ }
+ LogFlow(("drvHostBaseGetMediaSize: NtQueryVolumeInformationFile -> %#lx\n", rcNt, rc));
+ return rc;
+#else
+ return RTFileSeek(pThis->FileDevice, 0, RTFILE_SEEK_END, pcb);
+#endif
+}
+
+
+#ifdef RT_OS_DARWIN
+/**
+ * Execute a SCSI command.
+ *
+ * @param pThis The instance data.
+ * @param pbCmd Pointer to the SCSI command.
+ * @param cbCmd The size of the SCSI command.
+ * @param enmTxDir The transfer direction.
+ * @param pvBuf The buffer. Can be NULL if enmTxDir is PDMBLOCKTXDIR_NONE.
+ * @param pcbBuf Where to get the buffer size from and put the actual transfer size. Can be NULL.
+ * @param pbSense Where to put the sense data. Can be NULL.
+ * @param cbSense Size of the sense data buffer.
+ * @param cTimeoutMillies The timeout. 0 mean the default timeout.
+ *
+ * @returns VINF_SUCCESS on success (no sense code).
+ * @returns VERR_UNRESOLVED_ERROR if sense code is present.
+ * @returns Some other VBox status code on failures without sense code.
+ *
+ * @todo Fix VERR_UNRESOLVED_ERROR abuse.
+ */
+DECLCALLBACK(int) DRVHostBaseScsiCmd(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMBLOCKTXDIR enmTxDir,
+ void *pvBuf, size_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies)
+{
+ /*
+ * Minimal input validation.
+ */
+ Assert(enmTxDir == PDMBLOCKTXDIR_NONE || enmTxDir == PDMBLOCKTXDIR_FROM_DEVICE || enmTxDir == PDMBLOCKTXDIR_TO_DEVICE);
+ Assert(!pvBuf || pcbBuf);
+ Assert(pvBuf || enmTxDir == PDMBLOCKTXDIR_NONE);
+ Assert(pbSense || !cbSense);
+ AssertPtr(pbCmd);
+ Assert(cbCmd <= 16 && cbCmd >= 1);
+ const size_t cbBuf = pcbBuf ? *pcbBuf : 0;
+ if (pcbBuf)
+ *pcbBuf = 0;
+
+# ifdef RT_OS_DARWIN
+ Assert(pThis->ppScsiTaskDI);
+
+ int rc = VERR_GENERAL_FAILURE;
+ SCSITaskInterface **ppScsiTaskI = (*pThis->ppScsiTaskDI)->CreateSCSITask(pThis->ppScsiTaskDI);
+ if (!ppScsiTaskI)
+ return VERR_NO_MEMORY;
+ do
+ {
+ /* Setup the scsi command. */
+ SCSICommandDescriptorBlock cdb = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
+ memcpy(&cdb[0], pbCmd, cbCmd);
+ IOReturn irc = (*ppScsiTaskI)->SetCommandDescriptorBlock(ppScsiTaskI, cdb, cbCmd);
+ AssertBreak(irc == kIOReturnSuccess);
+
+ /* Setup the buffer. */
+ if (enmTxDir == PDMBLOCKTXDIR_NONE)
+ irc = (*ppScsiTaskI)->SetScatterGatherEntries(ppScsiTaskI, NULL, 0, 0, kSCSIDataTransfer_NoDataTransfer);
+ else
+ {
+ IOVirtualRange Range = { (IOVirtualAddress)pvBuf, cbBuf };
+ irc = (*ppScsiTaskI)->SetScatterGatherEntries(ppScsiTaskI, &Range, 1, cbBuf,
+ enmTxDir == PDMBLOCKTXDIR_FROM_DEVICE
+ ? kSCSIDataTransfer_FromTargetToInitiator
+ : kSCSIDataTransfer_FromInitiatorToTarget);
+ }
+ AssertBreak(irc == kIOReturnSuccess);
+
+ /* Set the timeout. */
+ irc = (*ppScsiTaskI)->SetTimeoutDuration(ppScsiTaskI, cTimeoutMillies ? cTimeoutMillies : 30000 /*ms*/);
+ AssertBreak(irc == kIOReturnSuccess);
+
+ /* Execute the command and get the response. */
+ SCSI_Sense_Data SenseData = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
+ SCSIServiceResponse ServiceResponse = kSCSIServiceResponse_Request_In_Process;
+ SCSITaskStatus TaskStatus = kSCSITaskStatus_GOOD;
+ UInt64 cbReturned = 0;
+ irc = (*ppScsiTaskI)->ExecuteTaskSync(ppScsiTaskI, &SenseData, &TaskStatus, &cbReturned);
+ AssertBreak(irc == kIOReturnSuccess);
+ if (pcbBuf)
+ *pcbBuf = cbReturned;
+
+ irc = (*ppScsiTaskI)->GetSCSIServiceResponse(ppScsiTaskI, &ServiceResponse);
+ AssertBreak(irc == kIOReturnSuccess);
+ AssertBreak(ServiceResponse == kSCSIServiceResponse_TASK_COMPLETE);
+
+ if (TaskStatus == kSCSITaskStatus_GOOD)
+ rc = VINF_SUCCESS;
+ else if ( TaskStatus == kSCSITaskStatus_CHECK_CONDITION
+ && pbSense)
+ {
+ memset(pbSense, 0, cbSense); /* lazy */
+ memcpy(pbSense, &SenseData, RT_MIN(sizeof(SenseData), cbSense));
+ rc = VERR_UNRESOLVED_ERROR;
+ }
+ /** @todo convert sense codes when caller doesn't wish to do this himself. */
+ /*else if ( TaskStatus == kSCSITaskStatus_CHECK_CONDITION
+ && SenseData.ADDITIONAL_SENSE_CODE == 0x3A)
+ rc = VERR_MEDIA_NOT_PRESENT; */
+ else
+ {
+ rc = enmTxDir == PDMBLOCKTXDIR_NONE
+ ? VERR_DEV_IO_ERROR
+ : enmTxDir == PDMBLOCKTXDIR_FROM_DEVICE
+ ? VERR_READ_ERROR
+ : VERR_WRITE_ERROR;
+ if (pThis->cLogRelErrors++ < 10)
+ LogRel(("DVD scsi error: cmd={%.*Rhxs} TaskStatus=%#x key=%#x ASC=%#x ASCQ=%#x (%Rrc)\n",
+ cbCmd, pbCmd, TaskStatus, SenseData.SENSE_KEY, SenseData.ADDITIONAL_SENSE_CODE,
+ SenseData.ADDITIONAL_SENSE_CODE_QUALIFIER, rc));
+ }
+ } while (0);
+
+ (*ppScsiTaskI)->Release(ppScsiTaskI);
+
+# endif
+
+ return rc;
+}
+#endif
+
+
+/**
+ * Media present.
+ * Query the size and notify the above driver / device.
+ *
+ * @param pThis The instance data.
+ */
+int DRVHostBaseMediaPresent(PDRVHOSTBASE pThis)
+{
+ /*
+ * Open the drive.
+ */
+ int rc = drvHostBaseReopen(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Determin the size.
+ */
+ uint64_t cb;
+ rc = pThis->pfnGetMediaSize(pThis, &cb);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("%s-%d: failed to figure media size of %s, rc=%Rrc\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pThis->pszDevice, rc));
+ return rc;
+ }
+
+ /*
+ * Update the data and inform the unit.
+ */
+ pThis->cbSize = cb;
+ pThis->fMediaPresent = true;
+ if (pThis->pDrvMountNotify)
+ pThis->pDrvMountNotify->pfnMountNotify(pThis->pDrvMountNotify);
+ LogFlow(("%s-%d: drvHostBaseMediaPresent: cbSize=%lld (%#llx)\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, pThis->cbSize, pThis->cbSize));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Media no longer present.
+ * @param pThis The instance data.
+ */
+void DRVHostBaseMediaNotPresent(PDRVHOSTBASE pThis)
+{
+ pThis->fMediaPresent = false;
+ pThis->fLocked = false;
+ pThis->PCHSGeometry.cCylinders = 0;
+ pThis->PCHSGeometry.cHeads = 0;
+ pThis->PCHSGeometry.cSectors = 0;
+ pThis->LCHSGeometry.cCylinders = 0;
+ pThis->LCHSGeometry.cHeads = 0;
+ pThis->LCHSGeometry.cSectors = 0;
+ if (pThis->pDrvMountNotify)
+ pThis->pDrvMountNotify->pfnUnmountNotify(pThis->pDrvMountNotify);
+}
+
+
+#ifdef RT_OS_WINDOWS
+
+/**
+ * Window procedure for the invisible window used to catch the WM_DEVICECHANGE broadcasts.
+ */
+static LRESULT CALLBACK DeviceChangeWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ Log2(("DeviceChangeWindowProc: hwnd=%08x uMsg=%08x\n", hwnd, uMsg));
+ if (uMsg == WM_DESTROY)
+ {
+ PDRVHOSTBASE pThis = (PDRVHOSTBASE)GetWindowLong(hwnd, GWLP_USERDATA);
+ if (pThis)
+ ASMAtomicXchgSize(&pThis->hwndDeviceChange, NULL);
+ PostQuitMessage(0);
+ }
+
+ if (uMsg != WM_DEVICECHANGE)
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+
+ PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
+ PDRVHOSTBASE pThis = (PDRVHOSTBASE)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ Assert(pThis);
+ if (pThis == NULL)
+ return 0;
+
+ switch (wParam)
+ {
+ case DBT_DEVICEARRIVAL:
+ case DBT_DEVICEREMOVECOMPLETE:
+ // Check whether a CD or DVD was inserted into or removed from a drive.
+ if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
+ {
+ PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
+ if ( (lpdbv->dbcv_flags & DBTF_MEDIA)
+ && (pThis->fUnitMask & lpdbv->dbcv_unitmask))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ if (wParam == DBT_DEVICEARRIVAL)
+ {
+ int cRetries = 10;
+ int rc = DRVHostBaseMediaPresent(pThis);
+ while (RT_FAILURE(rc) && cRetries-- > 0)
+ {
+ RTThreadSleep(50);
+ rc = DRVHostBaseMediaPresent(pThis);
+ }
+ }
+ else
+ DRVHostBaseMediaNotPresent(pThis);
+ RTCritSectLeave(&pThis->CritSect);
+ }
+ }
+ break;
+ }
+ return TRUE;
+}
+
+#endif /* RT_OS_WINDOWS */
+
+
+/**
+ * This thread will periodically poll the device for media presence.
+ *
+ * @returns Ignored.
+ * @param ThreadSelf Handle of this thread. Ignored.
+ * @param pvUser Pointer to the driver instance structure.
+ */
+static DECLCALLBACK(int) drvHostBaseMediaThread(RTTHREAD ThreadSelf, void *pvUser)
+{
+ PDRVHOSTBASE pThis = (PDRVHOSTBASE)pvUser;
+ LogFlow(("%s-%d: drvHostBaseMediaThread: ThreadSelf=%p pvUser=%p\n",
+ pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, ThreadSelf, pvUser));
+#ifdef RT_OS_WINDOWS
+ static WNDCLASS s_classDeviceChange = {0};
+ static ATOM s_hAtomDeviceChange = 0;
+
+ /*
+ * Register custom window class.
+ */
+ if (s_hAtomDeviceChange == 0)
+ {
+ memset(&s_classDeviceChange, 0, sizeof(s_classDeviceChange));
+ s_classDeviceChange.lpfnWndProc = DeviceChangeWindowProc;
+ s_classDeviceChange.lpszClassName = "VBOX_DeviceChangeClass";
+ s_classDeviceChange.hInstance = GetModuleHandle("VBOXDD.DLL");
+ Assert(s_classDeviceChange.hInstance);
+ s_hAtomDeviceChange = RegisterClassA(&s_classDeviceChange);
+ Assert(s_hAtomDeviceChange);
+ }
+
+ /*
+ * Create Window w/ the pThis as user data.
+ */
+ HWND hwnd = CreateWindow((LPCTSTR)s_hAtomDeviceChange, "", WS_POPUP, 0, 0, 0, 0, 0, 0, s_classDeviceChange.hInstance, 0);
+ AssertMsg(hwnd, ("CreateWindow failed with %d\n", GetLastError()));
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);
+
+ /*
+ * Signal the waiting EMT thread that everything went fine.
+ */
+ ASMAtomicXchgSize(&pThis->hwndDeviceChange, hwnd);
+ RTThreadUserSignal(ThreadSelf);
+ if (!hwnd)
+ {
+ LogFlow(("%s-%d: drvHostBaseMediaThread: returns VERR_GENERAL_FAILURE\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance));
+ return VERR_GENERAL_FAILURE;
+ }
+ LogFlow(("%s-%d: drvHostBaseMediaThread: Created hwndDeviceChange=%p\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance, hwnd));
+
+ /*
+ * Message pump.
+ */
+ MSG Msg;
+ BOOL fRet;
+ while ((fRet = GetMessage(&Msg, NULL, 0, 0)) != FALSE)
+ {
+ if (fRet != -1)
+ {
+ TranslateMessage(&Msg);
+ DispatchMessage(&Msg);
+ }
+ //else: handle the error and possibly exit
+ }
+ Assert(!pThis->hwndDeviceChange);
+
+#else /* !RT_OS_WINDOWS */
+ bool fFirst = true;
+ int cRetries = 10;
+ while (!pThis->fShutdownPoller)
+ {
+ /*
+ * Perform the polling (unless we've run out of 50ms retries).
+ */
+ if ( pThis->pfnPoll
+ && cRetries-- > 0)
+ {
+
+ int rc = pThis->pfnPoll(pThis);
+ if (RT_FAILURE(rc))
+ {
+ RTSemEventWait(pThis->EventPoller, 50);
+ continue;
+ }
+ }
+
+ /*
+ * Signal EMT after the first go.
+ */
+ if (fFirst)
+ {
+ RTThreadUserSignal(ThreadSelf);
+ fFirst = false;
+ }
+
+ /*
+ * Sleep.
+ */
+ int rc = RTSemEventWait(pThis->EventPoller, pThis->cMilliesPoller);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_TIMEOUT)
+ {
+ AssertMsgFailed(("rc=%Rrc\n", rc));
+ pThis->ThreadPoller = NIL_RTTHREAD;
+ LogFlow(("drvHostBaseMediaThread: returns %Rrc\n", rc));
+ return rc;
+ }
+ cRetries = 10;
+ }
+
+#endif /* !RT_OS_WINDOWS */
+
+ /* (Don't clear the thread handle here, the destructor thread is using it to wait.) */
+ LogFlow(("%s-%d: drvHostBaseMediaThread: returns VINF_SUCCESS\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance));
+ return VINF_SUCCESS;
+}
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+
+/**
+ * Done state load operation.
+ *
+ * @returns VBox load code.
+ * @param pDrvIns Driver instance of the driver which registered the data unit.
+ * @param pSSM SSM operation handle.
+ */
+static DECLCALLBACK(int) drvHostBaseLoadDone(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
+ LogFlow(("%s-%d: drvHostBaseMediaThread:\n", pThis->pDrvIns->pDrvReg->szDriverName, pThis->pDrvIns->iInstance));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Tell the device/driver above us that the media status is uncertain.
+ */
+ if (pThis->pDrvMountNotify)
+ {
+ pThis->pDrvMountNotify->pfnUnmountNotify(pThis->pDrvMountNotify);
+ if (pThis->fMediaPresent)
+ pThis->pDrvMountNotify->pfnMountNotify(pThis->pDrvMountNotify);
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc FNPDMDRVDESTRUCT */
+DECLCALLBACK(void) DRVHostBaseDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
+ LogFlow(("%s-%d: drvHostBaseDestruct: iInstance=%d\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance, pDrvIns->iInstance));
+
+ /*
+ * Terminate the thread.
+ */
+ if (pThis->ThreadPoller != NIL_RTTHREAD)
+ {
+ pThis->fShutdownPoller = true;
+ int rc;
+ int cTimes = 50;
+ do
+ {
+#ifdef RT_OS_WINDOWS
+ if (pThis->hwndDeviceChange)
+ PostMessage(pThis->hwndDeviceChange, WM_CLOSE, 0, 0); /* default win proc will destroy the window */
+#else
+ RTSemEventSignal(pThis->EventPoller);
+#endif
+ rc = RTThreadWait(pThis->ThreadPoller, 100, NULL);
+ } while (cTimes-- > 0 && rc == VERR_TIMEOUT);
+
+ if (!rc)
+ pThis->ThreadPoller = NIL_RTTHREAD;
+ }
+
+ /*
+ * Unlock the drive if we've locked it or we're in passthru mode.
+ */
+#ifdef RT_OS_DARWIN
+ if ( ( pThis->fLocked
+ || pThis->IBlock.pfnSendCmd)
+ && pThis->ppScsiTaskDI
+#else /** @todo Check if the other guys can mix pfnDoLock with scsi passthru.
+ * (We're currently not unlocking the device after use. See todo in DevATA.cpp.) */
+ if ( pThis->fLocked
+ && pThis->FileDevice != NIL_RTFILE
+#endif
+ && pThis->pfnDoLock)
+ {
+ int rc = pThis->pfnDoLock(pThis, false);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = false;
+ }
+
+ /*
+ * Cleanup the other resources.
+ */
+#ifdef RT_OS_WINDOWS
+ if (pThis->hwndDeviceChange)
+ {
+ if (SetWindowLongPtr(pThis->hwndDeviceChange, GWLP_USERDATA, 0) == (LONG_PTR)pThis)
+ PostMessage(pThis->hwndDeviceChange, WM_CLOSE, 0, 0); /* default win proc will destroy the window */
+ pThis->hwndDeviceChange = NULL;
+ }
+#else
+ if (pThis->EventPoller != NULL)
+ {
+ RTSemEventDestroy(pThis->EventPoller);
+ pThis->EventPoller = NULL;
+ }
+#endif
+
+#ifdef RT_OS_DARWIN
+ /*
+ * The unclaiming doesn't seem to mean much, the DVD is actaully
+ * remounted when we release exclusive access. I'm not quite sure
+ * if I should put the unclaim first or not...
+ *
+ * Anyway, that it's automatically remounted very good news for us,
+ * because that means we don't have to mess with that ourselves. Of
+ * course there is the unlikely scenario that we've succeeded in claiming
+ * and umount the DVD but somehow failed to gain exclusive scsi access...
+ */
+ if (pThis->ppScsiTaskDI)
+ {
+ LogFlow(("%s-%d: releasing exclusive scsi access!\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance));
+ (*pThis->ppScsiTaskDI)->ReleaseExclusiveAccess(pThis->ppScsiTaskDI);
+ (*pThis->ppScsiTaskDI)->Release(pThis->ppScsiTaskDI);
+ pThis->ppScsiTaskDI = NULL;
+ }
+ if (pThis->pDADisk)
+ {
+ LogFlow(("%s-%d: unclaiming the disk!\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance));
+ DADiskUnclaim(pThis->pDADisk);
+ CFRelease(pThis->pDADisk);
+ pThis->pDADisk = NULL;
+ }
+ if (pThis->ppMMCDI)
+ {
+ LogFlow(("%s-%d: releasing the MMC object!\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance));
+ (*pThis->ppMMCDI)->Release(pThis->ppMMCDI);
+ pThis->ppMMCDI = NULL;
+ }
+ if (pThis->MasterPort)
+ {
+ mach_port_deallocate(mach_task_self(), pThis->MasterPort);
+ pThis->MasterPort = NULL;
+ }
+ if (pThis->pDASession)
+ {
+ LogFlow(("%s-%d: releasing the DA session!\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance));
+ CFRelease(pThis->pDASession);
+ pThis->pDASession = NULL;
+ }
+#else
+ if (pThis->FileDevice != NIL_RTFILE)
+ {
+ int rc = RTFileClose(pThis->FileDevice);
+ AssertRC(rc);
+ pThis->FileDevice = NIL_RTFILE;
+ }
+#endif
+
+#ifdef RT_OS_SOLARIS
+ if (pThis->FileRawDevice != NIL_RTFILE)
+ {
+ int rc = RTFileClose(pThis->FileRawDevice);
+ AssertRC(rc);
+ pThis->FileRawDevice = NIL_RTFILE;
+ }
+
+ if (pThis->pszRawDeviceOpen)
+ {
+ RTStrFree(pThis->pszRawDeviceOpen);
+ pThis->pszRawDeviceOpen = NULL;
+ }
+#endif
+
+ if (pThis->pszDevice)
+ {
+ MMR3HeapFree(pThis->pszDevice);
+ pThis->pszDevice = NULL;
+ }
+
+ if (pThis->pszDeviceOpen)
+ {
+ RTStrFree(pThis->pszDeviceOpen);
+ pThis->pszDeviceOpen = NULL;
+ }
+
+ /* Forget about the notifications. */
+ pThis->pDrvMountNotify = NULL;
+
+ /* Leave the instance operational if this is just a cleanup of the state
+ * after an attach error happened. So don't destry the critsect then. */
+ if (!pThis->fKeepInstance && RTCritSectIsInitialized(&pThis->CritSect))
+ RTCritSectDelete(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseDestruct completed\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance));
+}
+
+
+/**
+ * Initializes the instance data (init part 1).
+ *
+ * The driver which derives from this base driver will override function pointers after
+ * calling this method, and complete the construction by calling DRVHostBaseInitFinish().
+ *
+ * On failure call DRVHostBaseDestruct().
+ *
+ * @returns VBox status code.
+ * @param pDrvIns Driver instance.
+ * @param pCfgHandle Configuration handle.
+ * @param enmType Device type.
+ */
+int DRVHostBaseInitData(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle, PDMBLOCKTYPE enmType)
+{
+ PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
+ LogFlow(("%s-%d: DRVHostBaseInitData: iInstance=%d\n", pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance, pDrvIns->iInstance));
+
+ /*
+ * Initialize most of the data members.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->fKeepInstance = false;
+ pThis->ThreadPoller = NIL_RTTHREAD;
+#ifdef RT_OS_DARWIN
+ pThis->MasterPort = NULL;
+ pThis->ppMMCDI = NULL;
+ pThis->ppScsiTaskDI = NULL;
+ pThis->cbBlock = 0;
+ pThis->pDADisk = NULL;
+ pThis->pDASession = NULL;
+#else
+ pThis->FileDevice = NIL_RTFILE;
+#endif
+#ifdef RT_OS_SOLARIS
+ pThis->FileRawDevice = NIL_RTFILE;
+#endif
+ pThis->enmType = enmType;
+ //pThis->cErrors = 0;
+
+ pThis->pfnGetMediaSize = drvHostBaseGetMediaSize;
+
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvHostBaseQueryInterface;
+
+ /* IBlock. */
+ pThis->IBlock.pfnRead = drvHostBaseRead;
+ pThis->IBlock.pfnWrite = drvHostBaseWrite;
+ pThis->IBlock.pfnFlush = drvHostBaseFlush;
+ pThis->IBlock.pfnIsReadOnly = drvHostBaseIsReadOnly;
+ pThis->IBlock.pfnGetSize = drvHostBaseGetSize;
+ pThis->IBlock.pfnGetType = drvHostBaseGetType;
+ pThis->IBlock.pfnGetUuid = drvHostBaseGetUuid;
+
+ /* IBlockBios. */
+ pThis->IBlockBios.pfnGetPCHSGeometry = drvHostBaseGetPCHSGeometry;
+ pThis->IBlockBios.pfnSetPCHSGeometry = drvHostBaseSetPCHSGeometry;
+ pThis->IBlockBios.pfnGetLCHSGeometry = drvHostBaseGetLCHSGeometry;
+ pThis->IBlockBios.pfnSetLCHSGeometry = drvHostBaseSetLCHSGeometry;
+ pThis->IBlockBios.pfnIsVisible = drvHostBaseIsVisible;
+ pThis->IBlockBios.pfnGetType = drvHostBaseBiosGetType;
+
+ /* IMount. */
+ pThis->IMount.pfnMount = drvHostBaseMount;
+ pThis->IMount.pfnUnmount = drvHostBaseUnmount;
+ pThis->IMount.pfnIsMounted = drvHostBaseIsMounted;
+ pThis->IMount.pfnLock = drvHostBaseLock;
+ pThis->IMount.pfnUnlock = drvHostBaseUnlock;
+ pThis->IMount.pfnIsLocked = drvHostBaseIsLocked;
+
+ /*
+ * Get the IBlockPort & IMountNotify interfaces of the above driver/device.
+ */
+ pThis->pDrvBlockPort = (PPDMIBLOCKPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_BLOCK_PORT);
+ if (!pThis->pDrvBlockPort)
+ {
+ AssertMsgFailed(("Configuration error: No block port interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+ pThis->pDrvMountNotify = (PPDMIMOUNTNOTIFY)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_MOUNT_NOTIFY);
+
+ /*
+ * Query configuration.
+ */
+ /* Device */
+ int rc = CFGMR3QueryStringAlloc(pCfgHandle, "Path", &pThis->pszDevice);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: query for \"Path\" string returned %Rra.\n", rc));
+ return rc;
+ }
+
+ /* Mountable */
+ uint32_t u32;
+ rc = CFGMR3QueryU32(pCfgHandle, "Interval", &u32);
+ if (RT_SUCCESS(rc))
+ pThis->cMilliesPoller = u32;
+ else if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ pThis->cMilliesPoller = 1000;
+ else if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: Query \"Mountable\" resulted in %Rrc.\n", rc));
+ return rc;
+ }
+
+ /* ReadOnly */
+ rc = CFGMR3QueryBool(pCfgHandle, "ReadOnly", &pThis->fReadOnlyConfig);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ pThis->fReadOnlyConfig = enmType == PDMBLOCKTYPE_DVD || enmType == PDMBLOCKTYPE_CDROM ? true : false;
+ else if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: Query \"ReadOnly\" resulted in %Rrc.\n", rc));
+ return rc;
+ }
+
+ /* Locked */
+ rc = CFGMR3QueryBool(pCfgHandle, "Locked", &pThis->fLocked);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ pThis->fLocked = false;
+ else if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: Query \"Locked\" resulted in %Rrc.\n", rc));
+ return rc;
+ }
+
+ /* BIOS visible */
+ rc = CFGMR3QueryBool(pCfgHandle, "BIOSVisible", &pThis->fBiosVisible);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ pThis->fBiosVisible = true;
+ else if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: Query \"BIOSVisible\" resulted in %Rrc.\n", rc));
+ return rc;
+ }
+
+ /* Uuid */
+ char *psz;
+ rc = CFGMR3QueryStringAlloc(pCfgHandle, "Uuid", &psz);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ RTUuidClear(&pThis->Uuid);
+ else if (RT_SUCCESS(rc))
+ {
+ rc = RTUuidFromStr(&pThis->Uuid, psz);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: Uuid from string failed on \"%s\", rc=%Rrc.\n", psz, rc));
+ MMR3HeapFree(psz);
+ return rc;
+ }
+ MMR3HeapFree(psz);
+ }
+ else
+ {
+ AssertMsgFailed(("Configuration error: Failed to obtain the uuid, rc=%Rrc.\n", rc));
+ return rc;
+ }
+
+ /* Define whether attach failure is an error (default) or not. */
+ bool fAttachFailError;
+ rc = CFGMR3QueryBool(pCfgHandle, "AttachFailError", &fAttachFailError);
+ if (RT_FAILURE(rc))
+ fAttachFailError = true;
+ pThis->fAttachFailError = fAttachFailError;
+
+ /* name to open & watch for */
+#ifdef RT_OS_WINDOWS
+ int iBit = toupper(pThis->pszDevice[0]) - 'A';
+ if ( iBit > 'Z' - 'A'
+ || pThis->pszDevice[1] != ':'
+ || pThis->pszDevice[2])
+ {
+ AssertMsgFailed(("Configuration error: Invalid drive specification: '%s'\n", pThis->pszDevice));
+ return VERR_INVALID_PARAMETER;
+ }
+ pThis->fUnitMask = 1 << iBit;
+ RTStrAPrintf(&pThis->pszDeviceOpen, "\\\\.\\%s", pThis->pszDevice);
+
+#elif defined(RT_OS_SOLARIS)
+ char *pszBlockDevName = getfullblkname(pThis->pszDevice);
+ if (!pszBlockDevName)
+ return VERR_NO_MEMORY;
+ pThis->pszDeviceOpen = RTStrDup(pszBlockDevName); /* for RTStrFree() */
+ free(pszBlockDevName);
+ pThis->pszRawDeviceOpen = RTStrDup(pThis->pszDevice);
+
+#else
+ pThis->pszDeviceOpen = RTStrDup(pThis->pszDevice);
+#endif
+
+ if (!pThis->pszDeviceOpen)
+ return VERR_NO_MEMORY;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Do the 2nd part of the init after the derived driver has overridden the defaults.
+ *
+ * On failure call DRVHostBaseDestruct().
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the instance data.
+ */
+int DRVHostBaseInitFinish(PDRVHOSTBASE pThis)
+{
+ int src = VINF_SUCCESS;
+ PPDMDRVINS pDrvIns = pThis->pDrvIns;
+
+ /* log config summary */
+ Log(("%s-%d: pszDevice='%s' (%s) cMilliesPoller=%d fReadOnlyConfig=%d fLocked=%d fBIOSVisible=%d Uuid=%RTuuid\n",
+ pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance, pThis->pszDevice, pThis->pszDeviceOpen, pThis->cMilliesPoller,
+ pThis->fReadOnlyConfig, pThis->fLocked, pThis->fBiosVisible, &pThis->Uuid));
+
+ /*
+ * Check that there are no drivers below us.
+ */
+ PPDMIBASE pBase;
+ int rc = pDrvIns->pDrvHlp->pfnAttach(pDrvIns, &pBase);
+ if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("Configuration error: No attached driver, please! (rc=%Rrc)\n", rc));
+ return VERR_PDM_DRVINS_NO_ATTACH;
+ }
+
+ /*
+ * Register saved state.
+ */
+ rc = pDrvIns->pDrvHlp->pfnSSMRegister(pDrvIns, pDrvIns->pDrvReg->szDriverName, pDrvIns->iInstance, 1, 0,
+ NULL, NULL, NULL,
+ NULL, NULL, drvHostBaseLoadDone);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Verify type.
+ */
+#ifdef RT_OS_WINDOWS
+ UINT uDriveType = GetDriveType(pThis->pszDevice);
+ switch (pThis->enmType)
+ {
+ case PDMBLOCKTYPE_FLOPPY_360:
+ case PDMBLOCKTYPE_FLOPPY_720:
+ case PDMBLOCKTYPE_FLOPPY_1_20:
+ case PDMBLOCKTYPE_FLOPPY_1_44:
+ case PDMBLOCKTYPE_FLOPPY_2_88:
+ if (uDriveType != DRIVE_REMOVABLE)
+ {
+ AssertMsgFailed(("Configuration error: '%s' is not a floppy (type=%d)\n",
+ pThis->pszDevice, uDriveType));
+ return VERR_INVALID_PARAMETER;
+ }
+ break;
+ case PDMBLOCKTYPE_CDROM:
+ case PDMBLOCKTYPE_DVD:
+ if (uDriveType != DRIVE_CDROM)
+ {
+ AssertMsgFailed(("Configuration error: '%s' is not a cdrom (type=%d)\n",
+ pThis->pszDevice, uDriveType));
+ return VERR_INVALID_PARAMETER;
+ }
+ break;
+ case PDMBLOCKTYPE_HARD_DISK:
+ default:
+ AssertMsgFailed(("enmType=%d\n", pThis->enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+#endif
+
+ /*
+ * Open the device.
+ */
+#ifdef RT_OS_DARWIN
+ rc = drvHostBaseOpen(pThis, NULL, pThis->fReadOnlyConfig);
+#else
+ rc = drvHostBaseReopen(pThis);
+#endif
+ if (RT_FAILURE(rc))
+ {
+ char *pszDevice = pThis->pszDevice;
+#ifndef RT_OS_DARWIN
+ char szPathReal[256];
+ if ( RTPathExists(pszDevice)
+ && RT_SUCCESS(RTPathReal(pszDevice, szPathReal, sizeof(szPathReal))))
+ pszDevice = szPathReal;
+ pThis->FileDevice = NIL_RTFILE;
+#endif
+#ifdef RT_OS_SOLARIS
+ pThis->FileRawDevice = NIL_RTFILE;
+#endif
+
+ /*
+ * Disable CD/DVD passthrough in case it was enabled. Would cause
+ * weird failures later when the guest issues commands. These would
+ * all fail because of the invalid file handle. So use the normal
+ * virtual CD/DVD code, which deals more gracefully with unavailable
+ * "media" - actually a complete drive in this case.
+ */
+ pThis->IBlock.pfnSendCmd = NULL;
+ AssertMsgFailed(("Could not open host device %s, rc=%Rrc\n", pszDevice, rc));
+ switch (rc)
+ {
+ case VERR_ACCESS_DENIED:
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+#ifdef RT_OS_LINUX
+ N_("Cannot open host device '%s' for %s access. Check the permissions "
+ "of that device ('/bin/ls -l %s'): Most probably you need to be member "
+ "of the device group. Make sure that you logout/login after changing "
+ "the group settings of the current user"),
+#else
+ N_("Cannot open host device '%s' for %s access. Check the permissions "
+ "of that device"),
+#endif
+ pszDevice, pThis->fReadOnlyConfig ? "readonly" : "read/write",
+ pszDevice);
+ default:
+ {
+ if (pThis->fAttachFailError)
+ return rc;
+ int erc = PDMDrvHlpVMSetRuntimeError(pDrvIns,
+ false, "DrvHost_MOUNTFAIL",
+ N_("Cannot attach to host device '%s'"), pszDevice);
+ AssertRC(erc);
+ src = rc;
+ }
+ }
+ }
+#ifdef RT_OS_WINDOWS
+ if (RT_SUCCESS(src))
+ DRVHostBaseMediaPresent(pThis);
+#endif
+
+ /*
+ * Lock the drive if that's required by the configuration.
+ */
+ if (pThis->fLocked)
+ {
+ if (pThis->pfnDoLock)
+ rc = pThis->pfnDoLock(pThis, true);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Failed to lock the dvd drive. rc=%Rrc\n", rc));
+ return rc;
+ }
+ }
+
+#ifndef RT_OS_WINDOWS
+ if (RT_SUCCESS(src))
+ {
+ /*
+ * Create the event semaphore which the poller thread will wait on.
+ */
+ rc = RTSemEventCreate(&pThis->EventPoller);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+#endif
+
+ /*
+ * Initialize the critical section used for serializing the access to the media.
+ */
+ rc = RTCritSectInit(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (RT_SUCCESS(src))
+ {
+ /*
+ * Start the thread which will poll for the media.
+ */
+ rc = RTThreadCreate(&pThis->ThreadPoller, drvHostBaseMediaThread, pThis, 0,
+ RTTHREADTYPE_INFREQUENT_POLLER, RTTHREADFLAGS_WAITABLE, "DVDMEDIA");
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Failed to create poller thread. rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Wait for the thread to start up (!w32:) and do one detection loop.
+ */
+ rc = RTThreadUserWait(pThis->ThreadPoller, 10000);
+ AssertRC(rc);
+#ifdef RT_OS_WINDOWS
+ if (!pThis->hwndDeviceChange)
+ return VERR_GENERAL_FAILURE;
+#endif
+ }
+
+ if (RT_FAILURE(src))
+ return src;
+ return rc;
+}
+
diff --git a/src/VBox/Devices/Storage/DrvHostBase.h b/src/VBox/Devices/Storage/DrvHostBase.h
new file mode 100644
index 000000000..b4202abb7
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase.h
@@ -0,0 +1,192 @@
+/* $Id: DrvHostBase.h 15831 2009-01-07 12:51:42Z vboxsync $ */
+/** @file
+ * DrvHostBase - Host base drive access driver.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+#ifndef __HostDrvBase_h__
+#define __HostDrvBase_h__
+
+#include <VBox/cdefs.h>
+
+__BEGIN_DECLS
+
+
+/** Pointer to host base drive access driver instance data. */
+typedef struct DRVHOSTBASE *PDRVHOSTBASE;
+/**
+ * Host base drive access driver instance data.
+ */
+typedef struct DRVHOSTBASE
+{
+ /** Critical section used to serialize access to the handle and other
+ * members of this struct. */
+ RTCRITSECT CritSect;
+ /** Pointer driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Drive type. */
+ PDMBLOCKTYPE enmType;
+ /** Visible to the BIOS. */
+ bool fBiosVisible;
+ /** The configuration readonly value. */
+ bool fReadOnlyConfig;
+ /** The current readonly status. */
+ bool fReadOnly;
+ /** Flag whether failure to attach is an error or not. */
+ bool fAttachFailError;
+ /** Flag whether to keep instance working (as unmounted though). */
+ bool fKeepInstance;
+ /** Device name (MMHeap). */
+ char *pszDevice;
+ /** Device name to open (RTStrFree). */
+ char *pszDeviceOpen;
+#ifdef RT_OS_SOLARIS
+ /** Device name of raw device (RTStrFree). */
+ char *pszRawDeviceOpen;
+#endif
+ /** Uuid of the drive. */
+ RTUUID Uuid;
+
+ /** Pointer to the block port interface above us. */
+ PPDMIBLOCKPORT pDrvBlockPort;
+ /** Pointer to the mount notify interface above us. */
+ PPDMIMOUNTNOTIFY pDrvMountNotify;
+ /** Our block interface. */
+ PDMIBLOCK IBlock;
+ /** Our block interface. */
+ PDMIBLOCKBIOS IBlockBios;
+ /** Our mountable interface. */
+ PDMIMOUNT IMount;
+
+ /** Media present indicator. */
+ bool volatile fMediaPresent;
+ /** Locked indicator. */
+ bool fLocked;
+ /** The size of the media currently in the drive.
+ * This is invalid if no drive is in the drive. */
+ uint64_t volatile cbSize;
+#ifndef RT_OS_DARWIN
+ /** The filehandle of the device. */
+ RTFILE FileDevice;
+#endif
+#ifdef RT_OS_SOLARIS
+ /** The raw filehandle of the device. */
+ RTFILE FileRawDevice;
+#endif
+
+ /** Handle of the poller thread. */
+ RTTHREAD ThreadPoller;
+#ifndef RT_OS_WINDOWS
+ /** Event semaphore the thread will wait on. */
+ RTSEMEVENT EventPoller;
+#endif
+ /** The poller interval. */
+ unsigned cMilliesPoller;
+ /** The shutdown indicator. */
+ bool volatile fShutdownPoller;
+
+ /** BIOS PCHS geometry. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** BIOS LCHS geometry. */
+ PDMMEDIAGEOMETRY LCHSGeometry;
+
+ /** The number of errors that could go into the release log. (flood gate) */
+ uint32_t cLogRelErrors;
+
+#ifdef RT_OS_DARWIN
+ /** The master port. */
+ mach_port_t MasterPort;
+ /** The MMC-2 Device Interface. (This is only used to get the scsi task interface.) */
+ MMCDeviceInterface **ppMMCDI;
+ /** The SCSI Task Device Interface. */
+ SCSITaskDeviceInterface **ppScsiTaskDI;
+ /** The block size. Set when querying the media size. */
+ uint32_t cbBlock;
+ /** The disk arbitration session reference. NULL if we didn't have to claim & unmount the device. */
+ DASessionRef pDASession;
+ /** The disk arbritation disk reference. NULL if we didn't have to claim & unmount the device. */
+ DADiskRef pDADisk;
+#endif
+
+#ifdef RT_OS_WINDOWS
+ /** Handle to the window we use to catch the device change broadcast messages. */
+ volatile HWND hwndDeviceChange;
+ /** The unit mask. */
+ DWORD fUnitMask;
+#endif
+
+#ifdef RT_OS_LINUX
+ /** Double buffer required for ioctl with the Linux kernel as long as we use
+ * remap_pfn_range() instead of vm_insert_page(). */
+ uint8_t *pbDoubleBuffer;
+#endif
+
+
+ /**
+ * Performs the locking / unlocking of the device.
+ *
+ * This callback pointer should be set to NULL if the device doesn't support this action.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the instance data.
+ * @param fLock Set if locking, clear if unlocking.
+ */
+ DECLCALLBACKMEMBER(int, pfnDoLock)(PDRVHOSTBASE pThis, bool fLock);
+
+ /**
+ * Queries the media size.
+ * Can also be used to perform actions on media change.
+ *
+ * This callback pointer should be set to NULL if the default action is fine for this device.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the instance data.
+ * @param pcb Where to store the media size in bytes.
+ */
+ DECLCALLBACKMEMBER(int, pfnGetMediaSize)(PDRVHOSTBASE pThis, uint64_t *pcb);
+
+ /***
+ * Performs the polling operation.
+ *
+ * @returns VBox status code. (Failure means retry.)
+ * @param pThis Pointer to the instance data.
+ */
+ DECLCALLBACKMEMBER(int, pfnPoll)(PDRVHOSTBASE pThis);
+} DRVHOSTBASE;
+
+
+int DRVHostBaseInitData(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle, PDMBLOCKTYPE enmType);
+int DRVHostBaseInitFinish(PDRVHOSTBASE pThis);
+int DRVHostBaseMediaPresent(PDRVHOSTBASE pThis);
+void DRVHostBaseMediaNotPresent(PDRVHOSTBASE pThis);
+DECLCALLBACK(void) DRVHostBaseDestruct(PPDMDRVINS pDrvIns);
+#ifdef RT_OS_DARWIN
+DECLCALLBACK(int) DRVHostBaseScsiCmd(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMBLOCKTXDIR enmTxDir,
+ void *pvBuf, size_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies);
+#endif
+
+
+/** Makes a PDRVHOSTBASE out of a PPDMIMOUNT. */
+#define PDMIMOUNT_2_DRVHOSTBASE(pInterface) ( (PDRVHOSTBASE)((uintptr_t)pInterface - RT_OFFSETOF(DRVHOSTBASE, IMount)) )
+
+/** Makes a PDRVHOSTBASE out of a PPDMIBLOCK. */
+#define PDMIBLOCK_2_DRVHOSTBASE(pInterface) ( (PDRVHOSTBASE)((uintptr_t)pInterface - RT_OFFSETOF(DRVHOSTBASE, IBlock)) )
+
+__END_DECLS
+
+#endif
diff --git a/src/VBox/Devices/Storage/DrvHostDVD.cpp b/src/VBox/Devices/Storage/DrvHostDVD.cpp
new file mode 100644
index 000000000..636b60ca0
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostDVD.cpp
@@ -0,0 +1,854 @@
+/* $Id: DrvHostDVD.cpp 15831 2009-01-07 12:51:42Z vboxsync $ */
+/** @file
+ * DrvHostDVD - Host DVD block driver.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_DVD
+#define __STDC_LIMIT_MACROS
+#define __STDC_CONSTANT_MACROS
+#ifdef RT_OS_DARWIN
+# include <mach/mach.h>
+# include <Carbon/Carbon.h>
+# include <IOKit/IOKitLib.h>
+# include <IOKit/IOCFPlugIn.h>
+# include <IOKit/scsi-commands/SCSITaskLib.h>
+# include <IOKit/scsi-commands/SCSICommandOperationCodes.h>
+# include <IOKit/storage/IOStorageDeviceCharacteristics.h>
+# include <mach/mach_error.h>
+# define USE_MEDIA_POLLING
+
+#elif defined(RT_OS_L4)
+/* nothing (yet). */
+
+#elif defined RT_OS_LINUX
+# include <sys/ioctl.h>
+/* This is a hack to work around conflicts between these linux kernel headers
+ * and the GLIBC tcpip headers. They have different declarations of the 4
+ * standard byte order functions. */
+# define _LINUX_BYTEORDER_GENERIC_H
+/* This is another hack for not bothering with C++ unfriendly byteswap macros. */
+# define _LINUX_BYTEORDER_SWAB_H
+# define _LINUX_BYTEORDER_SWABB_H
+/* Those macros that are needed are defined in the header below */
+# include "swab.h"
+# include <linux/cdrom.h>
+# include <sys/fcntl.h>
+# include <errno.h>
+# include <limits.h>
+# include <iprt/mem.h>
+# define USE_MEDIA_POLLING
+
+#elif defined(RT_OS_SOLARIS)
+# include <stropts.h>
+# include <fcntl.h>
+# include <ctype.h>
+# include <errno.h>
+# include <pwd.h>
+# include <unistd.h>
+# include <syslog.h>
+# ifdef VBOX_WITH_SUID_WRAPPER
+# include <auth_attr.h>
+# endif
+# include <sys/dkio.h>
+# include <sys/sockio.h>
+# include <sys/scsi/scsi.h>
+# define USE_MEDIA_POLLING
+
+#elif defined(RT_OS_WINDOWS)
+# include <Windows.h>
+# include <winioctl.h>
+# include <ntddscsi.h>
+# undef USE_MEDIA_POLLING
+
+#else
+# error "Unsupported Platform."
+#endif
+
+#include <VBox/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/critsect.h>
+#include <VBox/scsi.h>
+
+#include "Builtins.h"
+#include "DrvHostBase.h"
+
+
+/* Forward declarations. */
+
+static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock);
+#ifdef VBOX_WITH_SUID_WRAPPER
+static int solarisCheckUserAuth();
+static int solarisEnterRootMode(uid_t *pEffUserID);
+static int solarisExitRootMode(uid_t *pEffUserID);
+#endif
+
+
+/** @copydoc PDMIMOUNT::pfnUnmount */
+static DECLCALLBACK(int) drvHostDvdUnmount(PPDMIMOUNT pInterface, bool fForce)
+{
+ PDRVHOSTBASE pThis = PDMIMOUNT_2_DRVHOSTBASE(pInterface);
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Validate state.
+ */
+ int rc = VINF_SUCCESS;
+ if (!pThis->fLocked || fForce)
+ {
+ /* Unlock drive if necessary. */
+ if (pThis->fLocked)
+ drvHostDvdDoLock(pThis, false);
+
+ /*
+ * Eject the disc.
+ */
+#ifdef RT_OS_DARWIN
+ uint8_t abCmd[16] =
+ {
+ SCSI_START_STOP_UNIT, 0, 0, 0, 2 /*eject+stop*/, 0,
+ 0,0,0,0,0,0,0,0,0,0
+ };
+ rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, NULL, 0, 0);
+
+#elif defined(RT_OS_LINUX)
+ rc = ioctl(pThis->FileDevice, CDROMEJECT, 0);
+ if (rc < 0)
+ {
+ if (errno == EBUSY)
+ rc = VERR_PDM_MEDIA_LOCKED;
+ else if (errno == ENOSYS)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+#elif defined(RT_OS_SOLARIS)
+ rc = ioctl(pThis->FileRawDevice, DKIOCEJECT, 0);
+ if (rc < 0)
+ {
+ if (errno == EBUSY)
+ rc = VERR_PDM_MEDIA_LOCKED;
+ else if (errno == ENOSYS || errno == ENOTSUP)
+ rc = VERR_NOT_SUPPORTED;
+ else if (errno == ENODEV)
+ rc = VERR_PDM_MEDIA_NOT_MOUNTED;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+#elif defined(RT_OS_WINDOWS)
+ RTFILE FileDevice = pThis->FileDevice;
+ if (FileDevice == NIL_RTFILE) /* obsolete crap */
+ rc = RTFileOpen(&FileDevice, pThis->pszDeviceOpen, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ /* do ioctl */
+ DWORD cbReturned;
+ if (DeviceIoControl((HANDLE)FileDevice, IOCTL_STORAGE_EJECT_MEDIA,
+ NULL, 0,
+ NULL, 0, &cbReturned,
+ NULL))
+ rc = VINF_SUCCESS;
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+
+ /* clean up handle */
+ if (FileDevice != pThis->FileDevice)
+ RTFileClose(FileDevice);
+ }
+ else
+ AssertMsgFailed(("Failed to open '%s' for ejecting this tray.\n", rc));
+
+
+#else
+ AssertMsgFailed(("Eject is not implemented!\n"));
+ rc = VINF_SUCCESS;
+#endif
+
+ /*
+ * Media is no longer present.
+ */
+ DRVHostBaseMediaNotPresent(pThis); /** @todo This isn't thread safe! */
+ }
+ else
+ {
+ Log(("drvHostDvdUnmount: Locked\n"));
+ rc = VERR_PDM_MEDIA_LOCKED;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("drvHostDvdUnmount: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Locks or unlocks the drive.
+ *
+ * @returns VBox status code.
+ * @param pThis The instance data.
+ * @param fLock True if the request is to lock the drive, false if to unlock.
+ */
+static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock)
+{
+#ifdef RT_OS_DARWIN
+ uint8_t abCmd[16] =
+ {
+ SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, fLock, 0,
+ 0,0,0,0,0,0,0,0,0,0
+ };
+ int rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, NULL, 0, 0);
+
+#elif defined(RT_OS_LINUX)
+ int rc = ioctl(pThis->FileDevice, CDROM_LOCKDOOR, (int)fLock);
+ if (rc < 0)
+ {
+ if (errno == EBUSY)
+ rc = VERR_ACCESS_DENIED;
+ else if (errno == EDRIVE_CANT_DO_THIS)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+#elif defined(RT_OS_SOLARIS)
+ int rc = ioctl(pThis->FileRawDevice, fLock ? DKIOCLOCK : DKIOCUNLOCK, 0);
+ if (rc < 0)
+ {
+ if (errno == EBUSY)
+ rc = VERR_ACCESS_DENIED;
+ else if (errno == ENOTSUP || errno == ENOSYS)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+#elif defined(RT_OS_WINDOWS)
+
+ PREVENT_MEDIA_REMOVAL PreventMediaRemoval = {fLock};
+ DWORD cbReturned;
+ int rc;
+ if (DeviceIoControl((HANDLE)pThis->FileDevice, IOCTL_STORAGE_MEDIA_REMOVAL,
+ &PreventMediaRemoval, sizeof(PreventMediaRemoval),
+ NULL, 0, &cbReturned,
+ NULL))
+ rc = VINF_SUCCESS;
+ else
+ /** @todo figure out the return codes for already locked. */
+ rc = RTErrConvertFromWin32(GetLastError());
+
+#else
+ AssertMsgFailed(("Lock/Unlock is not implemented!\n"));
+ int rc = VINF_SUCCESS;
+
+#endif
+
+ LogFlow(("drvHostDvdDoLock(, fLock=%RTbool): returns %Rrc\n", fLock, rc));
+ return rc;
+}
+
+
+
+#ifdef RT_OS_LINUX
+/**
+ * Get the media size.
+ *
+ * @returns VBox status code.
+ * @param pThis The instance data.
+ * @param pcb Where to store the size.
+ */
+static int drvHostDvdGetMediaSize(PDRVHOSTBASE pThis, uint64_t *pcb)
+{
+ /*
+ * Query the media size.
+ */
+ /* Clear the media-changed-since-last-call-thingy just to be on the safe side. */
+ ioctl(pThis->FileDevice, CDROM_MEDIA_CHANGED, CDSL_CURRENT);
+ return RTFileSeek(pThis->FileDevice, 0, RTFILE_SEEK_END, pcb);
+
+}
+#endif /* RT_OS_LINUX */
+
+
+#ifdef USE_MEDIA_POLLING
+/**
+ * Do media change polling.
+ */
+DECLCALLBACK(int) drvHostDvdPoll(PDRVHOSTBASE pThis)
+{
+ /*
+ * Poll for media change.
+ */
+#ifdef RT_OS_DARWIN
+ AssertReturn(pThis->ppScsiTaskDI, VERR_INTERNAL_ERROR);
+
+ /*
+ * Issue a TEST UNIT READY request.
+ */
+ bool fMediaChanged = false;
+ bool fMediaPresent = false;
+ uint8_t abCmd[16] = { SCSI_TEST_UNIT_READY, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
+ uint8_t abSense[32];
+ int rc2 = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, abSense, sizeof(abSense), 0);
+ if (RT_SUCCESS(rc2))
+ fMediaPresent = true;
+ else if ( rc2 == VERR_UNRESOLVED_ERROR
+ && abSense[2] == 6 /* unit attention */
+ && ( (abSense[12] == 0x29 && abSense[13] < 5 /* reset */)
+ || (abSense[12] == 0x2a && abSense[13] == 0 /* parameters changed */) //???
+ || (abSense[12] == 0x3f && abSense[13] == 0 /* target operating conditions have changed */) //???
+ || (abSense[12] == 0x3f && abSense[13] == 2 /* changed operating definition */) //???
+ || (abSense[12] == 0x3f && abSense[13] == 3 /* inquery parameters changed */)
+ || (abSense[12] == 0x3f && abSense[13] == 5 /* device identifier changed */)
+ )
+ )
+ {
+ fMediaPresent = false;
+ fMediaChanged = true;
+ /** @todo check this media chance stuff on Darwin. */
+ }
+
+#elif defined(RT_OS_LINUX)
+ bool fMediaPresent = ioctl(pThis->FileDevice, CDROM_DRIVE_STATUS, CDSL_CURRENT) == CDS_DISC_OK;
+
+#elif defined(RT_OS_SOLARIS)
+ bool fMediaPresent = false;
+ bool fMediaChanged = false;
+
+ /* Need to pass the previous state and DKIO_NONE for the first time. */
+ static dkio_state s_DeviceState = DKIO_NONE;
+ dkio_state PreviousState = s_DeviceState;
+ int rc2 = ioctl(pThis->FileRawDevice, DKIOCSTATE, &s_DeviceState);
+ if (rc2 == 0)
+ {
+ fMediaPresent = (s_DeviceState == DKIO_INSERTED);
+ if (PreviousState != s_DeviceState)
+ fMediaChanged = true;
+ }
+ else
+ fMediaChanged = true;
+
+#else
+# error "Unsupported platform."
+#endif
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fMediaPresent != fMediaPresent)
+ {
+ LogFlow(("drvHostDvdPoll: %d -> %d\n", pThis->fMediaPresent, fMediaPresent));
+ pThis->fMediaPresent = false;
+ if (fMediaPresent)
+ rc = DRVHostBaseMediaPresent(pThis);
+ else
+ DRVHostBaseMediaNotPresent(pThis);
+ }
+ else if (fMediaPresent)
+ {
+ /*
+ * Poll for media change.
+ */
+#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS)
+ /* taken care of above. */
+#elif defined(RT_OS_LINUX)
+ bool fMediaChanged = ioctl(pThis->FileDevice, CDROM_MEDIA_CHANGED, CDSL_CURRENT) == 1;
+#else
+# error "Unsupported platform."
+#endif
+ if (fMediaChanged)
+ {
+ LogFlow(("drvHostDVDMediaThread: Media changed!\n"));
+ DRVHostBaseMediaNotPresent(pThis);
+ rc = DRVHostBaseMediaPresent(pThis);
+ }
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+#endif /* USE_MEDIA_POLLING */
+
+
+/** @copydoc PDMIBLOCK::pfnSendCmd */
+static int drvHostDvdSendCmd(PPDMIBLOCK pInterface, const uint8_t *pbCmd,
+ PDMBLOCKTXDIR enmTxDir, void *pvBuf, size_t *pcbBuf,
+ uint8_t *pabSense, size_t cbSense, uint32_t cTimeoutMillies)
+{
+ PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
+ int rc;
+ LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCmd[0], enmTxDir, *pcbBuf, cTimeoutMillies));
+
+#ifdef RT_OS_DARWIN
+ /*
+ * Pass the request on to the internal scsi command interface.
+ * The command seems to be 12 bytes long, the docs a bit copy&pasty on the command length point...
+ */
+ if (enmTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
+ memset(pvBuf, '\0', *pcbBuf); /* we got read size, but zero it anyway. */
+ rc = DRVHostBaseScsiCmd(pThis, pbCmd, 12, PDMBLOCKTXDIR_FROM_DEVICE, pvBuf, pcbBuf, pabSense, cbSense, cTimeoutMillies);
+ if (rc == VERR_UNRESOLVED_ERROR)
+ /* sense information set */
+ rc = VERR_DEV_IO_ERROR;
+
+#elif defined(RT_OS_L4)
+ /* Not really ported to L4 yet. */
+ rc = VERR_INTERNAL_ERROR;
+
+#elif defined(RT_OS_LINUX)
+ int direction;
+ struct cdrom_generic_command cgc;
+
+ switch (enmTxDir)
+ {
+ case PDMBLOCKTXDIR_NONE:
+ Assert(*pcbBuf == 0);
+ direction = CGC_DATA_NONE;
+ break;
+ case PDMBLOCKTXDIR_FROM_DEVICE:
+ Assert(*pcbBuf != 0);
+ Assert(*pcbBuf <= SCSI_MAX_BUFFER_SIZE);
+ /* Make sure that the buffer is clear for commands reading
+ * data. The actually received data may be shorter than what
+ * we expect, and due to the unreliable feedback about how much
+ * data the ioctl actually transferred, it's impossible to
+ * prevent that. Returning previous buffer contents may cause
+ * security problems inside the guest OS, if users can issue
+ * commands to the CDROM device. */
+ memset(pThis->pbDoubleBuffer, '\0', *pcbBuf);
+ direction = CGC_DATA_READ;
+ break;
+ case PDMBLOCKTXDIR_TO_DEVICE:
+ Assert(*pcbBuf != 0);
+ Assert(*pcbBuf <= SCSI_MAX_BUFFER_SIZE);
+ memcpy(pThis->pbDoubleBuffer, pvBuf, *pcbBuf);
+ direction = CGC_DATA_WRITE;
+ break;
+ default:
+ AssertMsgFailed(("enmTxDir invalid!\n"));
+ direction = CGC_DATA_NONE;
+ }
+ memset(&cgc, '\0', sizeof(cgc));
+ memcpy(cgc.cmd, pbCmd, CDROM_PACKET_SIZE);
+ cgc.buffer = (unsigned char *)pThis->pbDoubleBuffer;
+ cgc.buflen = *pcbBuf;
+ cgc.stat = 0;
+ Assert(cbSense >= sizeof(struct request_sense));
+ cgc.sense = (struct request_sense *)pabSense;
+ cgc.data_direction = direction;
+ cgc.quiet = false;
+ cgc.timeout = cTimeoutMillies;
+ rc = ioctl(pThis->FileDevice, CDROM_SEND_PACKET, &cgc);
+ if (rc < 0)
+ {
+ if (errno == EBUSY)
+ rc = VERR_PDM_MEDIA_LOCKED;
+ else if (errno == ENOSYS)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ {
+ rc = RTErrConvertFromErrno(errno);
+ if (rc == VERR_ACCESS_DENIED && cgc.sense->sense_key == SCSI_SENSE_NONE)
+ cgc.sense->sense_key = SCSI_SENSE_ILLEGAL_REQUEST;
+ Log2(("%s: error status %d, rc=%Rrc\n", __FUNCTION__, cgc.stat, rc));
+ }
+ }
+ switch (enmTxDir)
+ {
+ case PDMBLOCKTXDIR_FROM_DEVICE:
+ memcpy(pvBuf, pThis->pbDoubleBuffer, *pcbBuf);
+ break;
+ default:
+ ;
+ }
+ Log2(("%s: after ioctl: cgc.buflen=%d txlen=%d\n", __FUNCTION__, cgc.buflen, *pcbBuf));
+ /* The value of cgc.buflen does not reliably reflect the actual amount
+ * of data transferred (for packet commands with little data transfer
+ * it's 0). So just assume that everything worked ok. */
+
+#elif defined(RT_OS_SOLARIS)
+ struct uscsi_cmd usc;
+ union scsi_cdb scdb;
+ memset(&usc, 0, sizeof(struct uscsi_cmd));
+ memset(&scdb, 0, sizeof(scdb));
+
+ switch (enmTxDir)
+ {
+ case PDMBLOCKTXDIR_NONE:
+ Assert(*pcbBuf == 0);
+ usc.uscsi_flags = USCSI_READ;
+ /* nothing to do */
+ break;
+
+ case PDMBLOCKTXDIR_FROM_DEVICE:
+ Assert(*pcbBuf != 0);
+ /* Make sure that the buffer is clear for commands reading
+ * data. The actually received data may be shorter than what
+ * we expect, and due to the unreliable feedback about how much
+ * data the ioctl actually transferred, it's impossible to
+ * prevent that. Returning previous buffer contents may cause
+ * security problems inside the guest OS, if users can issue
+ * commands to the CDROM device. */
+ memset(pvBuf, '\0', *pcbBuf);
+ usc.uscsi_flags = USCSI_READ;
+ break;
+ case PDMBLOCKTXDIR_TO_DEVICE:
+ Assert(*pcbBuf != 0);
+ usc.uscsi_flags = USCSI_WRITE;
+ break;
+ default:
+ AssertMsgFailedReturn(("%d\n", enmTxDir), VERR_INTERNAL_ERROR);
+ }
+ usc.uscsi_flags |= USCSI_RQENABLE;
+ usc.uscsi_rqbuf = (char *)pabSense;
+ usc.uscsi_rqlen = cbSense;
+ usc.uscsi_cdb = (caddr_t)&scdb;
+ usc.uscsi_cdblen = 12;
+ memcpy (usc.uscsi_cdb, pbCmd, usc.uscsi_cdblen);
+ usc.uscsi_bufaddr = (caddr_t)pvBuf;
+ usc.uscsi_buflen = *pcbBuf;
+ usc.uscsi_timeout = (cTimeoutMillies + 999) / 1000;
+
+ /* We need root privileges for user-SCSI under Solaris. */
+#ifdef VBOX_WITH_SUID_WRAPPER
+ uid_t effUserID = geteuid();
+ solarisEnterRootMode(&effUserID); /** @todo check return code when this really works. */
+#endif
+ rc = ioctl(pThis->FileRawDevice, USCSICMD, &usc);
+#ifdef VBOX_WITH_SUID_WRAPPER
+ solarisExitRootMode(&effUserID);
+#endif
+ if (rc < 0)
+ {
+ if (errno == EPERM)
+ return VERR_PERMISSION_DENIED;
+ if (usc.uscsi_status)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ Log2(("%s: error status. rc=%Rrc\n", __FUNCTION__, rc));
+ }
+ }
+ Log2(("%s: after ioctl: residual buflen=%d original buflen=%d\n", __FUNCTION__, usc.uscsi_resid, usc.uscsi_buflen));
+
+#elif defined(RT_OS_WINDOWS)
+ int direction;
+ struct _REQ
+ {
+ SCSI_PASS_THROUGH_DIRECT spt;
+ uint8_t aSense[64];
+ } Req;
+ DWORD cbReturned = 0;
+
+ switch (enmTxDir)
+ {
+ case PDMBLOCKTXDIR_NONE:
+ direction = SCSI_IOCTL_DATA_UNSPECIFIED;
+ break;
+ case PDMBLOCKTXDIR_FROM_DEVICE:
+ Assert(*pcbBuf != 0);
+ /* Make sure that the buffer is clear for commands reading
+ * data. The actually received data may be shorter than what
+ * we expect, and due to the unreliable feedback about how much
+ * data the ioctl actually transferred, it's impossible to
+ * prevent that. Returning previous buffer contents may cause
+ * security problems inside the guest OS, if users can issue
+ * commands to the CDROM device. */
+ memset(pvBuf, '\0', *pcbBuf);
+ direction = SCSI_IOCTL_DATA_IN;
+ break;
+ case PDMBLOCKTXDIR_TO_DEVICE:
+ direction = SCSI_IOCTL_DATA_OUT;
+ break;
+ default:
+ AssertMsgFailed(("enmTxDir invalid!\n"));
+ direction = SCSI_IOCTL_DATA_UNSPECIFIED;
+ }
+ memset(&Req, '\0', sizeof(Req));
+ Req.spt.Length = sizeof(Req.spt);
+ Req.spt.CdbLength = 12;
+ memcpy(Req.spt.Cdb, pbCmd, Req.spt.CdbLength);
+ Req.spt.DataBuffer = pvBuf;
+ Req.spt.DataTransferLength = *pcbBuf;
+ Req.spt.DataIn = direction;
+ Req.spt.TimeOutValue = (cTimeoutMillies + 999) / 1000; /* Convert to seconds */
+ Assert(cbSense <= sizeof(Req.aSense));
+ Req.spt.SenseInfoLength = cbSense;
+ Req.spt.SenseInfoOffset = RT_OFFSETOF(struct _REQ, aSense);
+ if (DeviceIoControl((HANDLE)pThis->FileDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &Req, sizeof(Req), &Req, sizeof(Req), &cbReturned, NULL))
+ {
+ if (cbReturned > RT_OFFSETOF(struct _REQ, aSense))
+ memcpy(pabSense, Req.aSense, cbSense);
+ else
+ memset(pabSense, '\0', cbSense);
+ /* Windows shares the property of not properly reflecting the actually
+ * transferred data size. See above. Assume that everything worked ok.
+ * Except if there are sense information. */
+ rc = (pabSense[2] & 0x0f) == SCSI_SENSE_NONE
+ ? VINF_SUCCESS
+ : VERR_DEV_IO_ERROR;
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+ Log2(("%s: scsistatus=%d bytes returned=%d tlength=%d\n", __FUNCTION__, Req.spt.ScsiStatus, cbReturned, Req.spt.DataTransferLength));
+
+#else
+# error "Unsupported platform."
+#endif
+
+ if (pbCmd[0] == SCSI_GET_EVENT_STATUS_NOTIFICATION)
+ {
+ uint8_t *pbBuf = (uint8_t*)pvBuf;
+ Log2(("Event Status Notification class=%#02x supported classes=%#02x\n", pbBuf[2], pbBuf[3]));
+ if (RT_BE2H_U16(*(uint16_t*)pbBuf) >= 6)
+ Log2((" event %#02x %#02x %#02x %#02x\n", pbBuf[4], pbBuf[5], pbBuf[6], pbBuf[7]));
+ }
+
+ LogFlow(("%s: rc=%Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+
+#ifdef VBOX_WITH_SUID_WRAPPER
+/* These functions would have to go into a seperate solaris binary with
+ * the setuid permission set, which would run the user-SCSI ioctl and
+ * return the value. BUT... this might be prohibitively slow.
+ */
+# ifdef RT_OS_SOLARIS
+
+/**
+ * Checks if the current user is authorized using Solaris' role-based access control.
+ * Made as a seperate function with so that it need not be invoked each time we need
+ * to gain root access.
+ *
+ * @returns VBox error code.
+ */
+static int solarisCheckUserAuth()
+{
+ /* Uses Solaris' role-based access control (RBAC).*/
+ struct passwd *pPass = getpwuid(getuid());
+ if (pPass == NULL || chkauthattr("solaris.device.cdrw", pPass->pw_name) == 0)
+ return VERR_PERMISSION_DENIED;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Setuid wrapper to gain root access.
+ *
+ * @returns VBox error code.
+ * @param pEffUserID Pointer to effective user ID.
+ */
+static int solarisEnterRootMode(uid_t *pEffUserID)
+{
+ /* Increase privilege if required */
+ if (*pEffUserID != 0)
+ {
+ if (seteuid(0) == 0)
+ {
+ *pEffUserID = 0;
+ return VINF_SUCCESS;
+ }
+ return VERR_PERMISSION_DENIED;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Setuid wrapper to relinquish root access.
+ *
+ * @returns VBox error code.
+ * @param pEffUserID Pointer to effective user ID.
+ */
+static int solarisExitRootMode(uid_t *pEffUserID)
+{
+ /* Get back to user mode. */
+ if (*pEffUserID == 0)
+ {
+ uid_t realID = getuid();
+ if (seteuid(realID) == 0)
+ {
+ *pEffUserID = realID;
+ return VINF_SUCCESS;
+ }
+ return VERR_PERMISSION_DENIED;
+ }
+ return VINF_SUCCESS;
+}
+
+# endif /* RT_OS_SOLARIS */
+#endif /* VBOX_WITH_SUID_WRAPPER */
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+
+/** @copydoc FNPDMDRVDESTRUCT */
+DECLCALLBACK(void) drvHostDvdDestruct(PPDMDRVINS pDrvIns)
+{
+#ifdef RT_OS_LINUX
+ PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
+
+ if (pThis->pbDoubleBuffer)
+ {
+ RTMemFree(pThis->pbDoubleBuffer);
+ pThis->pbDoubleBuffer = NULL;
+ }
+#endif
+ return DRVHostBaseDestruct(pDrvIns);
+}
+
+
+/**
+ * Construct a host dvd drive driver instance.
+ *
+ * @returns VBox status.
+ * @param pDrvIns The driver instance data.
+ * If the registration structure is needed, pDrvIns->pDrvReg points to it.
+ * @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
+ * of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
+ * iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) drvHostDvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+ PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
+ LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfgHandle, "Path\0Interval\0Locked\0BIOSVisible\0AttachFailError\0Passthrough\0"))
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+
+ /*
+ * Init instance data.
+ */
+ int rc = DRVHostBaseInitData(pDrvIns, pCfgHandle, PDMBLOCKTYPE_DVD);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Override stuff.
+ */
+#ifdef RT_OS_LINUX
+ pThis->pbDoubleBuffer = (uint8_t *)RTMemAlloc(SCSI_MAX_BUFFER_SIZE);
+ if (!pThis->pbDoubleBuffer)
+ return VERR_NO_MEMORY;
+#endif
+
+#ifndef RT_OS_L4 /* Passthrough is not supported on L4 yet */
+ bool fPassthrough;
+ rc = CFGMR3QueryBool(pCfgHandle, "Passthrough", &fPassthrough);
+ if (RT_SUCCESS(rc) && fPassthrough)
+ {
+ pThis->IBlock.pfnSendCmd = drvHostDvdSendCmd;
+ /* Passthrough requires opening the device in R/W mode. */
+ pThis->fReadOnlyConfig = false;
+# ifdef VBOX_WITH_SUID_WRAPPER /* Solaris setuid for Passthrough mode. */
+ rc = solarisCheckUserAuth();
+ if (RT_FAILURE(rc))
+ {
+ Log(("DVD: solarisCheckUserAuth failed. Permission denied!\n"));
+ return rc;
+ }
+# endif /* VBOX_WITH_SUID_WRAPPER */
+ }
+#endif /* !RT_OS_L4 */
+
+ pThis->IMount.pfnUnmount = drvHostDvdUnmount;
+ pThis->pfnDoLock = drvHostDvdDoLock;
+#ifdef USE_MEDIA_POLLING
+ if (!fPassthrough)
+ pThis->pfnPoll = drvHostDvdPoll;
+ else
+ pThis->pfnPoll = NULL;
+#endif
+#ifdef RT_OS_LINUX
+ pThis->pfnGetMediaSize = drvHostDvdGetMediaSize;
+#endif
+
+ /*
+ * 2nd init part.
+ */
+ rc = DRVHostBaseInitFinish(pThis);
+ }
+ if (RT_FAILURE(rc))
+ {
+ if (!pThis->fAttachFailError)
+ {
+ /* Suppressing the attach failure error must not affect the normal
+ * DRVHostBaseDestruct, so reset this flag below before leaving. */
+ pThis->fKeepInstance = true;
+ rc = VINF_SUCCESS;
+ }
+ DRVHostBaseDestruct(pDrvIns);
+ pThis->fKeepInstance = false;
+ }
+
+ LogFlow(("drvHostDvdConstruct: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Block driver registration record.
+ */
+const PDMDRVREG g_DrvHostDVD =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szDriverName */
+ "HostDVD",
+ /* pszDescription */
+ "Host DVD Block Driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_BLOCK,
+ /* cMaxInstances */
+ ~0,
+ /* cbInstance */
+ sizeof(DRVHOSTBASE),
+ /* pfnConstruct */
+ drvHostDvdConstruct,
+ /* pfnDestruct */
+ drvHostDvdDestruct,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnDetach */
+ NULL
+};
+
diff --git a/src/VBox/Devices/Storage/DrvHostFloppy.cpp b/src/VBox/Devices/Storage/DrvHostFloppy.cpp
new file mode 100644
index 000000000..68a706239
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostFloppy.cpp
@@ -0,0 +1,233 @@
+/** @file
+ *
+ * VBox storage devices:
+ * Host floppy block driver
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_FLOPPY
+#ifdef RT_OS_LINUX
+# include <sys/ioctl.h>
+# include <linux/fd.h>
+# include <sys/fcntl.h>
+# include <errno.h>
+
+# elif defined(RT_OS_WINDOWS)
+# include <windows.h>
+# include <dbt.h>
+
+#elif defined(RT_OS_L4)
+
+#else /* !RT_OS_WINDOWS nor RT_OS_LINUX nor RT_OS_L4 */
+# error "Unsupported Platform."
+#endif /* !RT_OS_WINDOWS nor RT_OS_LINUX nor RT_OS_L4 */
+
+#include <VBox/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/uuid.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+
+#include "Builtins.h"
+#include "DrvHostBase.h"
+
+
+/**
+ * Floppy driver instance data.
+ */
+typedef struct DRVHOSTFLOPPY
+{
+ DRVHOSTBASE Base;
+ /** Previous poll status. */
+ bool fPrevDiskIn;
+
+} DRVHOSTFLOPPY, *PDRVHOSTFLOPPY;
+
+
+
+#ifdef RT_OS_LINUX
+/**
+ * Get media size and do change processing.
+ *
+ * @param pThis The instance data.
+ */
+static DECLCALLBACK(int) drvHostFloppyGetMediaSize(PDRVHOSTBASE pThis, uint64_t *pcb)
+{
+ int rc = ioctl(pThis->FileDevice, FDFLUSH);
+ if (rc)
+ {
+ rc = RTErrConvertFromErrno (errno);
+ Log(("DrvHostFloppy: FDFLUSH ioctl(%s) failed, errno=%d rc=%Rrc\n", pThis->pszDevice, errno, rc));
+ return rc;
+ }
+
+ floppy_drive_struct DrvStat;
+ rc = ioctl(pThis->FileDevice, FDGETDRVSTAT, &DrvStat);
+ if (rc)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ Log(("DrvHostFloppy: FDGETDRVSTAT ioctl(%s) failed, errno=%d rc=%Rrc\n", pThis->pszDevice, errno, rc));
+ return rc;
+ }
+ pThis->fReadOnly = !(DrvStat.flags & FD_DISK_WRITABLE);
+
+ return RTFileSeek(pThis->FileDevice, 0, RTFILE_SEEK_END, pcb);
+}
+#endif /* RT_OS_LINUX */
+
+
+#ifdef RT_OS_LINUX
+/**
+ * This thread will periodically poll the Floppy for media presence.
+ *
+ * @returns Ignored.
+ * @param ThreadSelf Handle of this thread. Ignored.
+ * @param pvUser Pointer to the driver instance structure.
+ */
+static DECLCALLBACK(int) drvHostFloppyPoll(PDRVHOSTBASE pThis)
+{
+ PDRVHOSTFLOPPY pThisFloppy = (PDRVHOSTFLOPPY)pThis;
+ floppy_drive_struct DrvStat;
+ int rc = ioctl(pThis->FileDevice, FDPOLLDRVSTAT, &DrvStat);
+ if (rc)
+ return RTErrConvertFromErrno(errno);
+
+ RTCritSectEnter(&pThis->CritSect);
+ bool fDiskIn = !(DrvStat.flags & (FD_VERIFY | FD_DISK_NEWCHANGE));
+ if ( fDiskIn
+ && !pThisFloppy->fPrevDiskIn)
+ {
+ if (pThis->fMediaPresent)
+ DRVHostBaseMediaNotPresent(pThis);
+ rc = DRVHostBaseMediaPresent(pThis);
+ if (RT_FAILURE(rc))
+ {
+ pThisFloppy->fPrevDiskIn = fDiskIn;
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+ }
+ }
+
+ if ( !fDiskIn
+ && pThisFloppy->fPrevDiskIn
+ && pThis->fMediaPresent)
+ DRVHostBaseMediaNotPresent(pThis);
+ pThisFloppy->fPrevDiskIn = fDiskIn;
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+#endif /* RT_OS_LINUX */
+
+
+/**
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHostFloppyConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+ PDRVHOSTFLOPPY pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTFLOPPY);
+ LogFlow(("drvHostFloppyConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfgHandle, "Path\0ReadOnly\0Interval\0Locked\0BIOSVisible\0"))
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+ /*
+ * Init instance data.
+ */
+ int rc = DRVHostBaseInitData(pDrvIns, pCfgHandle, PDMBLOCKTYPE_FLOPPY_1_44);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Override stuff.
+ */
+#ifdef RT_OS_LINUX
+ pThis->Base.pfnPoll = drvHostFloppyPoll;
+ pThis->Base.pfnGetMediaSize = drvHostFloppyGetMediaSize;
+#endif
+
+ /*
+ * 2nd init part.
+ */
+ rc = DRVHostBaseInitFinish(&pThis->Base);
+ }
+ if (RT_FAILURE(rc))
+ {
+ if (!pThis->Base.fAttachFailError)
+ {
+ /* Suppressing the attach failure error must not affect the normal
+ * DRVHostBaseDestruct, so reset this flag below before leaving. */
+ pThis->Base.fKeepInstance = true;
+ rc = VINF_SUCCESS;
+ }
+ DRVHostBaseDestruct(pDrvIns);
+ pThis->Base.fKeepInstance = false;
+ }
+
+ LogFlow(("drvHostFloppyConstruct: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Block driver registration record.
+ */
+const PDMDRVREG g_DrvHostFloppy =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szDriverName */
+ "HostFloppy",
+ /* pszDescription */
+ "Host Floppy Block Driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_BLOCK,
+ /* cMaxInstances */
+ ~0,
+ /* cbInstance */
+ sizeof(DRVHOSTFLOPPY),
+ /* pfnConstruct */
+ drvHostFloppyConstruct,
+ /* pfnDestruct */
+ DRVHostBaseDestruct,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnDetach */
+ NULL
+};
+
diff --git a/src/VBox/Devices/Storage/DrvMediaISO.cpp b/src/VBox/Devices/Storage/DrvMediaISO.cpp
new file mode 100644
index 000000000..72c69ff1b
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvMediaISO.cpp
@@ -0,0 +1,354 @@
+/** @file
+ *
+ * VBox storage devices:
+ * ISO image media driver
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_ISO
+#include <VBox/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+
+#include <string.h>
+
+#include "Builtins.h"
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+
+/** Converts a pointer to MEDIAISO::IMedia to a PRDVMEDIAISO. */
+#define PDMIMEDIA_2_DRVMEDIAISO(pInterface) ( (PDRVMEDIAISO)((uintptr_t)pInterface - RT_OFFSETOF(DRVMEDIAISO, IMedia)) )
+
+/** Converts a pointer to PDMDRVINS::IBase to a PPDMDRVINS. */
+#define PDMIBASE_2_DRVINS(pInterface) ( (PPDMDRVINS)((uintptr_t)pInterface - RT_OFFSETOF(PDMDRVINS, IBase)) )
+
+/** Converts a pointer to PDMDRVINS::IBase to a PVBOXHDD. */
+#define PDMIBASE_2_DRVMEDIAISO(pInterface) ( PDMINS_2_DATA(PDMIBASE_2_DRVINS(pInterface), PDRVMEDIAISO) )
+
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+/**
+ * Block driver instance data.
+ */
+typedef struct DRVMEDIAISO
+{
+ /** The media interface. */
+ PDMIMEDIA IMedia;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the filename. (Freed by MM) */
+ char *pszFilename;
+ /** File handle of the ISO file. */
+ RTFILE File;
+} DRVMEDIAISO, *PDRVMEDIAISO;
+
+
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+static DECLCALLBACK(int) drvMediaISORead(PPDMIMEDIA pInterface, uint64_t off, void *pvBuf, size_t cbRead);
+static DECLCALLBACK(int) drvMediaISOWrite(PPDMIMEDIA pInterface, uint64_t off, const void *pvBuf, size_t cbWrite);
+static DECLCALLBACK(int) drvMediaISOFlush(PPDMIMEDIA pInterface);
+static DECLCALLBACK(bool) drvMediaISOIsReadOnly(PPDMIMEDIA pInterface);
+static DECLCALLBACK(uint64_t) drvMediaISOGetSize(PPDMIMEDIA pInterface);
+static DECLCALLBACK(int) drvMediaISOGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid);
+static DECLCALLBACK(int) drvMediaISOBiosGetPCHSGeometry(PPDMIMEDIA pInterface, PPDMMEDIAGEOMETRY pPCHSGeometry);
+static DECLCALLBACK(int) drvMediaISOBiosSetPCHSGeometry(PPDMIMEDIA pInterface, PCPDMMEDIAGEOMETRY pPCHSGeometry);
+static DECLCALLBACK(int) drvMediaISOBiosGetLCHSGeometry(PPDMIMEDIA pInterface, PPDMMEDIAGEOMETRY pLCHSGeometry);
+static DECLCALLBACK(int) drvMediaISOBiosSetLCHSGeometry(PPDMIMEDIA pInterface, PCPDMMEDIAGEOMETRY pLCHSGeometry);
+
+static DECLCALLBACK(void *) drvMediaISOQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface);
+
+
+
+
+/**
+ * Construct a ISO media driver instance.
+ *
+ * @returns VBox status.
+ * @param pDrvIns The driver instance data.
+ * If the registration structure is needed, pDrvIns->pDrvReg points to it.
+ * @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
+ * of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
+ * iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) drvMediaISOConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+ PDRVMEDIAISO pThis = PDMINS_2_DATA(pDrvIns, PDRVMEDIAISO);
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->File = NIL_RTFILE;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvMediaISOQueryInterface;
+ /* IMedia */
+ pThis->IMedia.pfnRead = drvMediaISORead;
+ pThis->IMedia.pfnWrite = drvMediaISOWrite;
+ pThis->IMedia.pfnFlush = drvMediaISOFlush;
+ pThis->IMedia.pfnGetSize = drvMediaISOGetSize;
+ pThis->IMedia.pfnGetUuid = drvMediaISOGetUuid;
+ pThis->IMedia.pfnIsReadOnly = drvMediaISOIsReadOnly;
+ pThis->IMedia.pfnBiosGetPCHSGeometry = drvMediaISOBiosGetPCHSGeometry;
+ pThis->IMedia.pfnBiosSetPCHSGeometry = drvMediaISOBiosSetPCHSGeometry;
+ pThis->IMedia.pfnBiosGetLCHSGeometry = drvMediaISOBiosGetLCHSGeometry;
+ pThis->IMedia.pfnBiosSetLCHSGeometry = drvMediaISOBiosSetLCHSGeometry;
+
+ /*
+ * Read the configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfgHandle, "Path\0"))
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+ char *pszName;
+ int rc = CFGMR3QueryStringAlloc(pCfgHandle, "Path", &pszName);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Path\" from the config"));
+
+ /*
+ * Open the image.
+ */
+ rc = RTFileOpen(&pThis->File, pszName,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("drvMediaISOConstruct: ISO image '%s' opened successfully.\n", pszName));
+ pThis->pszFilename = pszName;
+ }
+ else
+ {
+ PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("Failed to open ISO file \"%s\""), pszName);
+ MMR3HeapFree(pszName);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Destruct a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvMediaISODestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVMEDIAISO pThis = PDMINS_2_DATA(pDrvIns, PDRVMEDIAISO);
+ LogFlow(("drvMediaISODestruct: '%s'\n", pThis->pszFilename));
+
+ if (pThis->File != NIL_RTFILE)
+ {
+ RTFileClose(pThis->File);
+ pThis->File = NIL_RTFILE;
+ }
+ if (pThis->pszFilename)
+ MMR3HeapFree(pThis->pszFilename);
+}
+
+
+/** @copydoc PDMIMEDIA::pfnGetSize */
+static DECLCALLBACK(uint64_t) drvMediaISOGetSize(PPDMIMEDIA pInterface)
+{
+ PDRVMEDIAISO pThis = PDMIMEDIA_2_DRVMEDIAISO(pInterface);
+ LogFlow(("drvMediaISOGetSize: '%s'\n", pThis->pszFilename));
+
+ uint64_t cbFile;
+ int rc = RTFileGetSize(pThis->File, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("drvMediaISOGetSize: returns %lld (%s)\n", cbFile, pThis->pszFilename));
+ return cbFile;
+ }
+
+ AssertMsgFailed(("Error querying ISO file size, rc=%Rrc. (%s)\n", rc, pThis->pszFilename));
+ return 0;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnBiosGetPCHSGeometry */
+static DECLCALLBACK(int) drvMediaISOBiosGetPCHSGeometry(PPDMIMEDIA pInterface, PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnBiosSetPCHSGeometry */
+static DECLCALLBACK(int) drvMediaISOBiosSetPCHSGeometry(PPDMIMEDIA pInterface, PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnBiosGetLCHSGeometry */
+static DECLCALLBACK(int) drvMediaISOBiosGetLCHSGeometry(PPDMIMEDIA pInterface, PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnBiosSetLCHSGeometry */
+static DECLCALLBACK(int) drvMediaISOBiosSetLCHSGeometry(PPDMIMEDIA pInterface, PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Read bits.
+ *
+ * @see PDMIMEDIA::pfnRead for details.
+ */
+static DECLCALLBACK(int) drvMediaISORead(PPDMIMEDIA pInterface, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PDRVMEDIAISO pThis = PDMIMEDIA_2_DRVMEDIAISO(pInterface);
+ LogFlow(("drvMediaISORead: off=%#llx pvBuf=%p cbRead=%#x (%s)\n", off, pvBuf, cbRead, pThis->pszFilename));
+
+ Assert(pThis->File);
+ Assert(pvBuf);
+
+ /*
+ * Seek to the position and read.
+ */
+ int rc = RTFileSeek(pThis->File, off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileRead(pThis->File, pvBuf, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("drvMediaISORead: off=%#llx pvBuf=%p cbRead=%#x (%s)\n"
+ "%16.*Rhxd\n",
+ off, pvBuf, cbRead, pThis->pszFilename,
+ cbRead, pvBuf));
+ }
+ else
+ AssertMsgFailed(("RTFileRead(%d, %p, %#x) -> %Rrc (off=%#llx '%s')\n",
+ pThis->File, pvBuf, cbRead, rc, off, pThis->pszFilename));
+ }
+ else
+ AssertMsgFailed(("RTFileSeek(%d,%#llx,) -> %Rrc\n", pThis->File, off, rc));
+ LogFlow(("drvMediaISORead: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnWrite */
+static DECLCALLBACK(int) drvMediaISOWrite(PPDMIMEDIA pInterface, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ AssertMsgFailed(("Attempt to write to an ISO file!\n"));
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnFlush */
+static DECLCALLBACK(int) drvMediaISOFlush(PPDMIMEDIA pInterface)
+{
+ /* No buffered data that still needs to be written. */
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnGetUuid */
+static DECLCALLBACK(int) drvMediaISOGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+ LogFlow(("drvMediaISOGetUuid: returns VERR_NOT_IMPLEMENTED\n"));
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnIsReadOnly */
+static DECLCALLBACK(bool) drvMediaISOIsReadOnly(PPDMIMEDIA pInterface)
+{
+ return true;
+}
+
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the driver.
+ * @param pInterface Pointer to this interface structure.
+ * @param enmInterface The requested interface identification.
+ * @thread Any thread.
+ */
+static DECLCALLBACK(void *) drvMediaISOQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_DRVINS(pInterface);
+ PDRVMEDIAISO pThis = PDMINS_2_DATA(pDrvIns, PDRVMEDIAISO);
+ switch (enmInterface)
+ {
+ case PDMINTERFACE_BASE:
+ return &pDrvIns->IBase;
+ case PDMINTERFACE_MEDIA:
+ return &pThis->IMedia;
+ default:
+ return NULL;
+ }
+}
+
+
+/**
+ * ISO media driver registration record.
+ */
+const PDMDRVREG g_DrvMediaISO =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szDriverName */
+ "MediaISO",
+ /* pszDescription */
+ "ISO media access driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_MEDIA,
+ /* cMaxInstances */
+ ~0,
+ /* cbInstance */
+ sizeof(DRVMEDIAISO),
+ /* pfnConstruct */
+ drvMediaISOConstruct,
+ /* pfnDestruct */
+ drvMediaISODestruct,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL
+};
diff --git a/src/VBox/Devices/Storage/DrvRawImage.cpp b/src/VBox/Devices/Storage/DrvRawImage.cpp
new file mode 100644
index 000000000..e3c1b51ef
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvRawImage.cpp
@@ -0,0 +1,404 @@
+/** @file
+ *
+ * VBox storage devices:
+ * Raw image driver
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_RAW_IMAGE
+#include <VBox/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+
+#include "Builtins.h"
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+
+/** Converts a pointer to RAWIMAGE::IMedia to a PRDVRAWIMAGE. */
+#define PDMIMEDIA_2_DRVRAWIMAGE(pInterface) ( (PDRVRAWIMAGE)((uintptr_t)pInterface - RT_OFFSETOF(DRVRAWIMAGE, IMedia)) )
+
+/** Converts a pointer to PDMDRVINS::IBase to a PPDMDRVINS. */
+#define PDMIBASE_2_DRVINS(pInterface) ( (PPDMDRVINS)((uintptr_t)pInterface - RT_OFFSETOF(PDMDRVINS, IBase)) )
+
+/** Converts a pointer to PDMDRVINS::IBase to a PVBOXHDD. */
+#define PDMIBASE_2_DRVRAWIMAGE(pInterface) ( PDMINS_2_DATA(PDMIBASE_2_DRVINS(pInterface), PDRVRAWIMAGE) )
+
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+/**
+ * Block driver instance data.
+ */
+typedef struct DRVRAWIMAGE
+{
+ /** The media interface. */
+ PDMIMEDIA IMedia;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the filename. (Freed by MM) */
+ char *pszFilename;
+ /** File handle of the raw image file. */
+ RTFILE File;
+ /** True if the image is operating in readonly mode. */
+ bool fReadOnly;
+} DRVRAWIMAGE, *PDRVRAWIMAGE;
+
+
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+static DECLCALLBACK(int) drvRawImageRead(PPDMIMEDIA pInterface, uint64_t off, void *pvBuf, size_t cbRead);
+static DECLCALLBACK(int) drvRawImageWrite(PPDMIMEDIA pInterface, uint64_t off, const void *pvBuf, size_t cbWrite);
+static DECLCALLBACK(int) drvRawImageFlush(PPDMIMEDIA pInterface);
+static DECLCALLBACK(bool) drvRawImageIsReadOnly(PPDMIMEDIA pInterface);
+static DECLCALLBACK(uint64_t) drvRawImageGetSize(PPDMIMEDIA pInterface);
+static DECLCALLBACK(int) drvRawImageGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid);
+static DECLCALLBACK(int) drvRawImageBiosGetPCHSGeometry(PPDMIMEDIA pInterface, PPDMMEDIAGEOMETRY pPCHSGeometry);
+static DECLCALLBACK(int) drvRawImageBiosSetPCHSGeometry(PPDMIMEDIA pInterface, PCPDMMEDIAGEOMETRY pPCHSGeometry);
+static DECLCALLBACK(int) drvRawImageBiosGetLCHSGeometry(PPDMIMEDIA pInterface, PPDMMEDIAGEOMETRY pLCHSGeometry);
+static DECLCALLBACK(int) drvRawImageBiosSetLCHSGeometry(PPDMIMEDIA pInterface, PCPDMMEDIAGEOMETRY pLCHSGeometry);
+
+static DECLCALLBACK(void *) drvRawImageQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface);
+
+
+
+
+/**
+ * Construct a raw image driver instance.
+ *
+ * @returns VBox status.
+ * @param pDrvIns The driver instance data.
+ * If the registration structure is needed, pDrvIns->pDrvReg points to it.
+ * @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
+ * of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
+ * iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) drvRawImageConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+ PDRVRAWIMAGE pThis = PDMINS_2_DATA(pDrvIns, PDRVRAWIMAGE);
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->File = NIL_RTFILE;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvRawImageQueryInterface;
+ /* IMedia */
+ pThis->IMedia.pfnRead = drvRawImageRead;
+ pThis->IMedia.pfnWrite = drvRawImageWrite;
+ pThis->IMedia.pfnFlush = drvRawImageFlush;
+ pThis->IMedia.pfnGetSize = drvRawImageGetSize;
+ pThis->IMedia.pfnGetUuid = drvRawImageGetUuid;
+ pThis->IMedia.pfnIsReadOnly = drvRawImageIsReadOnly;
+ pThis->IMedia.pfnBiosGetPCHSGeometry = drvRawImageBiosGetPCHSGeometry;
+ pThis->IMedia.pfnBiosSetPCHSGeometry = drvRawImageBiosSetPCHSGeometry;
+ pThis->IMedia.pfnBiosGetLCHSGeometry = drvRawImageBiosGetLCHSGeometry;
+ pThis->IMedia.pfnBiosSetLCHSGeometry = drvRawImageBiosSetLCHSGeometry;
+
+ /*
+ * Read the configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfgHandle, "Path\0"))
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+ char *pszName;
+ int rc = CFGMR3QueryStringAlloc(pCfgHandle, "Path", &pszName);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: query for \"Path\" string return %Rrc.\n", rc));
+ return rc;
+ }
+
+ /*
+ * Open the image.
+ */
+ rc = RTFileOpen(&pThis->File, pszName,
+ RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("drvRawImageConstruct: Raw image '%s' opened successfully.\n", pszName));
+ pThis->pszFilename = pszName;
+ pThis->fReadOnly = false;
+ }
+ else
+ {
+ rc = RTFileOpen(&pThis->File, pszName,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("drvRawImageConstruct: Raw image '%s' opened successfully.\n", pszName));
+ pThis->pszFilename = pszName;
+ pThis->fReadOnly = true;
+ }
+ else
+ {
+ AssertMsgFailed(("Could not open Raw image file %s, rc=%Rrc\n", pszName, rc));
+ MMR3HeapFree(pszName);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Destruct a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvRawImageDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVRAWIMAGE pThis = PDMINS_2_DATA(pDrvIns, PDRVRAWIMAGE);
+ LogFlow(("drvRawImageDestruct: '%s'\n", pThis->pszFilename));
+
+ if (pThis->File != NIL_RTFILE)
+ {
+ RTFileClose(pThis->File);
+ pThis->File = NIL_RTFILE;
+ }
+ if (pThis->pszFilename)
+ MMR3HeapFree(pThis->pszFilename);
+}
+
+
+/** @copydoc PDMIMEDIA::pfnGetSize */
+static DECLCALLBACK(uint64_t) drvRawImageGetSize(PPDMIMEDIA pInterface)
+{
+ PDRVRAWIMAGE pThis = PDMIMEDIA_2_DRVRAWIMAGE(pInterface);
+ LogFlow(("drvRawImageGetSize: '%s'\n", pThis->pszFilename));
+
+ uint64_t cbFile;
+ int rc = RTFileGetSize(pThis->File, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("drvRawImageGetSize: returns %lld (%s)\n", cbFile, pThis->pszFilename));
+ return cbFile;
+ }
+
+ AssertMsgFailed(("Error querying Raw image file size, rc=%Rrc. (%s)\n", rc, pThis->pszFilename));
+ return 0;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnBiosGetPCHSGeometry */
+static DECLCALLBACK(int) drvRawImageBiosGetPCHSGeometry(PPDMIMEDIA pInterface, PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnBiosSetPCHSGeometry */
+static DECLCALLBACK(int) drvRawImageBiosSetPCHSGeometry(PPDMIMEDIA pInterface, PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnBiosGetLCHSGeometry */
+static DECLCALLBACK(int) drvRawImageBiosGetLCHSGeometry(PPDMIMEDIA pInterface, PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnBiosSetLCHSGeometry */
+static DECLCALLBACK(int) drvRawImageBiosSetLCHSGeometry(PPDMIMEDIA pInterface, PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Read bits.
+ *
+ * @see PDMIMEDIA::pfnRead for details.
+ */
+static DECLCALLBACK(int) drvRawImageRead(PPDMIMEDIA pInterface, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PDRVRAWIMAGE pThis = PDMIMEDIA_2_DRVRAWIMAGE(pInterface);
+ LogFlow(("drvRawImageRead: off=%#llx pvBuf=%p cbRead=%#x (%s)\n", off, pvBuf, cbRead, pThis->pszFilename));
+
+ Assert(pThis->File);
+ Assert(pvBuf);
+
+ /*
+ * Seek to the position and read.
+ */
+ int rc = RTFileSeek(pThis->File, off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileRead(pThis->File, pvBuf, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("drvRawImageRead: off=%#llx pvBuf=%p cbRead=%#x (%s)\n"
+ "%16.*Rhxd\n",
+ off, pvBuf, cbRead, pThis->pszFilename,
+ cbRead, pvBuf));
+ }
+ else
+ AssertMsgFailed(("RTFileRead(%d, %p, %#x) -> %Rrc (off=%#llx '%s')\n",
+ pThis->File, pvBuf, cbRead, rc, off, pThis->pszFilename));
+ }
+ else
+ AssertMsgFailed(("RTFileSeek(%d,%#llx,) -> %Rrc\n", pThis->File, off, rc));
+ LogFlow(("drvRawImageRead: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnWrite */
+static DECLCALLBACK(int) drvRawImageWrite(PPDMIMEDIA pInterface, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ PDRVRAWIMAGE pThis = PDMIMEDIA_2_DRVRAWIMAGE(pInterface);
+ LogFlow(("drvRawImageWrite: off=%#llx pvBuf=%p cbWrite=%#x (%s)\n", off, pvBuf, cbWrite, pThis->pszFilename));
+
+ Assert(pThis->File);
+ Assert(pvBuf);
+
+ /*
+ * Seek to the position and write.
+ */
+ int rc = RTFileSeek(pThis->File, off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(pThis->File, pvBuf, cbWrite, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("drvRawImageWrite: off=%#llx pvBuf=%p cbWrite=%#x (%s)\n"
+ "%16.*Rhxd\n",
+ off, pvBuf, cbWrite, pThis->pszFilename,
+ cbWrite, pvBuf));
+ }
+ else
+ AssertMsgFailed(("RTFileWrite(%d, %p, %#x) -> %Rrc (off=%#llx '%s')\n",
+ pThis->File, pvBuf, cbWrite, rc, off, pThis->pszFilename));
+ }
+ else
+ AssertMsgFailed(("RTFileSeek(%d,%#llx,) -> %Rrc\n", pThis->File, off, rc));
+ LogFlow(("drvRawImageWrite: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnFlush */
+static DECLCALLBACK(int) drvRawImageFlush(PPDMIMEDIA pInterface)
+{
+ PDRVRAWIMAGE pThis = PDMIMEDIA_2_DRVRAWIMAGE(pInterface);
+ LogFlow(("drvRawImageFlush: (%s)\n", pThis->pszFilename));
+
+ Assert(pThis->File != NIL_RTFILE);
+ int rc = RTFileFlush(pThis->File);
+ LogFlow(("drvRawImageFlush: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnGetUuid */
+static DECLCALLBACK(int) drvRawImageGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+ LogFlow(("drvRawImageGetUuid: returns VERR_NOT_IMPLEMENTED\n"));
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/** @copydoc PDMIMEDIA::pfnIsReadOnly */
+static DECLCALLBACK(bool) drvRawImageIsReadOnly(PPDMIMEDIA pInterface)
+{
+ PDRVRAWIMAGE pThis = PDMIMEDIA_2_DRVRAWIMAGE(pInterface);
+ return pThis->fReadOnly;
+}
+
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the driver.
+ * @param pInterface Pointer to this interface structure.
+ * @param enmInterface The requested interface identification.
+ * @thread Any thread.
+ */
+static DECLCALLBACK(void *) drvRawImageQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_DRVINS(pInterface);
+ PDRVRAWIMAGE pThis = PDMINS_2_DATA(pDrvIns, PDRVRAWIMAGE);
+ switch (enmInterface)
+ {
+ case PDMINTERFACE_BASE:
+ return &pDrvIns->IBase;
+ case PDMINTERFACE_MEDIA:
+ return &pThis->IMedia;
+ default:
+ return NULL;
+ }
+}
+
+
+/**
+ * Raw image driver registration record.
+ */
+const PDMDRVREG g_DrvRawImage =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szDriverName */
+ "RawImage",
+ /* pszDescription */
+ "Raw image access driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_MEDIA,
+ /* cMaxInstances */
+ ~0,
+ /* cbInstance */
+ sizeof(DRVRAWIMAGE),
+ /* pfnConstruct */
+ drvRawImageConstruct,
+ /* pfnDestruct */
+ drvRawImageDestruct,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL
+};
diff --git a/src/VBox/Devices/Storage/DrvVD.cpp b/src/VBox/Devices/Storage/DrvVD.cpp
new file mode 100644
index 000000000..37d84677b
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvVD.cpp
@@ -0,0 +1,1223 @@
+/* $Id: DrvVD.cpp 15592 2008-12-16 14:48:13Z vboxsync $ */
+/** @file
+ * DrvVD - Generic VBox disk media driver.
+ */
+
+/*
+ * Copyright (C) 2006-2008 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+
+/*******************************************************************************
+* Header files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VD
+#include <VBox/VBoxHDD-new.h>
+#include <VBox/pdmdrv.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/cache.h>
+#include <iprt/tcp.h>
+
+#ifndef VBOX_OSE
+/* All lwip header files are not C++ safe. So hack around this. */
+__BEGIN_DECLS
+#include <lwip/inet.h>
+#include <lwip/tcp.h>
+#include <lwip/sockets.h>
+__END_DECLS
+#endif /* !VBOX_OSE */
+
+#include "Builtins.h"
+
+#ifndef VBOX_OSE
+/* Small hack to get at lwIP initialized status */
+extern bool DevINIPConfigured(void);
+#endif /* !VBOX_OSE */
+
+
+/*******************************************************************************
+* Defined types, constants and macros *
+*******************************************************************************/
+
+/** Converts a pointer to VDIDISK::IMedia to a PVBOXDISK. */
+#define PDMIMEDIA_2_VBOXDISK(pInterface) \
+ ( (PVBOXDISK)((uintptr_t)pInterface - RT_OFFSETOF(VBOXDISK, IMedia)) )
+
+/** Converts a pointer to PDMDRVINS::IBase to a PPDMDRVINS. */
+#define PDMIBASE_2_DRVINS(pInterface) \
+ ( (PPDMDRVINS)((uintptr_t)pInterface - RT_OFFSETOF(PDMDRVINS, IBase)) )
+
+/** Converts a pointer to PDMDRVINS::IBase to a PVBOXDISK. */
+#define PDMIBASE_2_VBOXDISK(pInterface) \
+ ( PDMINS_2_DATA(PDMIBASE_2_DRVINS(pInterface), PVBOXDISK) )
+
+/** Converts a pointer to VBOXDISK::IMediaAsync to a PVBOXDISK. */
+#define PDMIMEDIAASYNC_2_VBOXDISK(pInterface) \
+ ( (PVBOXDISK)((uintptr_t)pInterface - RT_OFFSETOF(VBOXDISK, IMediaAsync)) )
+
+/** Converts a pointer to VBOXDISK::ITransportAsyncPort to a PVBOXDISK. */
+#define PDMITRANSPORTASYNCPORT_2_VBOXDISK(pInterface) \
+ ( (PVBOXDISK)((uintptr_t)pInterface - RT_OFFSETOF(VBOXDISK, ITransportAsyncPort)) )
+
+/**
+ * Structure for an async I/O task.
+ */
+typedef struct DRVVDASYNCTASK
+{
+ /** Callback which is called on completion. */
+ PFNVDCOMPLETED pfnCompleted;
+ /** Opqaue user data which is passed on completion. */
+ void *pvUser;
+ /** Opaque user data the caller passed on transfer initiation. */
+ void *pvUserCaller;
+} DRVVDASYNCTASK, *PDRVVDASYNCTASK;
+
+/**
+ * VBox disk container, image information, private part.
+ */
+
+typedef struct VBOXIMAGE
+{
+ /** Pointer to next image. */
+ struct VBOXIMAGE *pNext;
+ /** Pointer to list of VD interfaces. Per-image. */
+ PVDINTERFACE pVDIfsImage;
+ /** Common structure for the configuration information interface. */
+ VDINTERFACE VDIConfig;
+} VBOXIMAGE, *PVBOXIMAGE;
+
+/**
+ * VBox disk container media main structure, private part.
+ */
+typedef struct VBOXDISK
+{
+ /** The VBox disk container. */
+ PVBOXHDD pDisk;
+ /** The media interface. */
+ PDMIMEDIA IMedia;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Flag whether suspend has changed image open mode to read only. */
+ bool fTempReadOnly;
+ /** Pointer to list of VD interfaces. Per-disk. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Common structure for the supported error interface. */
+ VDINTERFACE VDIError;
+ /** Callback table for error interface. */
+ VDINTERFACEERROR VDIErrorCallbacks;
+ /** Common structure for the supported TCP network stack interface. */
+ VDINTERFACE VDITcpNet;
+ /** Callback table for TCP network stack interface. */
+ VDINTERFACETCPNET VDITcpNetCallbacks;
+ /** Common structure for the supported async I/O interface. */
+ VDINTERFACE VDIAsyncIO;
+ /** Callback table for async I/O interface. */
+ VDINTERFACEASYNCIO VDIAsyncIOCallbacks;
+ /** Callback table for the configuration information interface. */
+ VDINTERFACECONFIG VDIConfigCallbacks;
+ /** Flag whether opened disk suppports async I/O operations. */
+ bool fAsyncIOSupported;
+ /** The async media interface. */
+ PDMIMEDIAASYNC IMediaAsync;
+ /** The async media port interface above. */
+ PPDMIMEDIAASYNCPORT pDrvMediaAsyncPort;
+ /** Pointer to the asynchronous media driver below. */
+ PPDMITRANSPORTASYNC pDrvTransportAsync;
+ /** Async transport port interface. */
+ PDMITRANSPORTASYNCPORT ITransportAsyncPort;
+ /** Our cache to reduce allocation overhead. */
+ PRTOBJCACHE pCache;
+ /** Pointer to the list of data we need to keep per image. */
+ PVBOXIMAGE pImages;
+} VBOXDISK, *PVBOXDISK;
+
+/*******************************************************************************
+* Error reporting callback *
+*******************************************************************************/
+
+static void drvvdErrorCallback(void *pvUser, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, va_list va)
+{
+ PPDMDRVINS pDrvIns = (PPDMDRVINS)pvUser;
+ pDrvIns->pDrvHlp->pfnVMSetErrorV(pDrvIns, rc, RT_SRC_POS_ARGS, pszFormat, va);
+}
+
+
+/**
+ * Internal: allocate new image descriptor and put it in the list
+ */
+static PVBOXIMAGE drvvdNewImage(PVBOXDISK pThis)
+{
+ AssertPtr(pThis);
+ PVBOXIMAGE pImage = (PVBOXIMAGE)RTMemAllocZ(sizeof(VBOXIMAGE));
+ if (pImage)
+ {
+ pImage->pVDIfsImage = NULL;
+ PVBOXIMAGE *pp = &pThis->pImages;
+ while (*pp != NULL)
+ pp = &(*pp)->pNext;
+ *pp = pImage;
+ pImage->pNext = NULL;
+ }
+
+ return pImage;
+}
+
+/**
+ * Internal: free the list of images descriptors.
+ */
+static void drvvdFreeImages(PVBOXDISK pThis)
+{
+ while (pThis->pImages != NULL)
+ {
+ PVBOXIMAGE p = pThis->pImages;
+ pThis->pImages = pThis->pImages->pNext;
+ RTMemFree(p);
+ }
+}
+
+/*******************************************************************************
+* VD Async I/O interface implementation *
+*******************************************************************************/
+
+static DECLCALLBACK(int) drvvdAsyncIOOpen(void *pvUser, const char *pszLocation, bool fReadonly, void **ppStorage)
+{
+ PVBOXDISK pDrvVD = (PVBOXDISK)pvUser;
+
+ return pDrvVD->pDrvTransportAsync->pfnOpen(pDrvVD->pDrvTransportAsync, pszLocation, fReadonly, ppStorage);
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOClose(void *pvUser, void *pStorage)
+{
+ PVBOXDISK pDrvVD = (PVBOXDISK)pvUser;
+
+ AssertMsg(pDrvVD->pDrvTransportAsync, ("Asynchronous function called but no async transport interface below\n"));
+
+ return pDrvVD->pDrvTransportAsync->pfnClose(pDrvVD->pDrvTransportAsync, pStorage);
+}
+
+static DECLCALLBACK(int) drvvdAsyncIORead(void *pvUser, void *pStorage, uint64_t uOffset,
+ size_t cbRead, void *pvBuf, size_t *pcbRead)
+{
+ PVBOXDISK pDrvVD = (PVBOXDISK)pvUser;
+
+ AssertMsg(pDrvVD->pDrvTransportAsync, ("Asynchronous function called but no async transport interface below\n"));
+
+ return pDrvVD->pDrvTransportAsync->pfnReadSynchronous(pDrvVD->pDrvTransportAsync,
+ pStorage,
+ uOffset, pvBuf, cbRead, pcbRead);
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOWrite(void *pvUser, void *pStorage, uint64_t uOffset,
+ size_t cbWrite, const void *pvBuf, size_t *pcbWritten)
+{
+ PVBOXDISK pDrvVD = (PVBOXDISK)pvUser;
+
+ AssertMsg(pDrvVD->pDrvTransportAsync, ("Asynchronous function called but no async transport interface below\n"));
+
+ return pDrvVD->pDrvTransportAsync->pfnWriteSynchronous(pDrvVD->pDrvTransportAsync,
+ pStorage,
+ uOffset, pvBuf, cbWrite, pcbWritten);
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOFlush(void *pvUser, void *pStorage)
+{
+ PVBOXDISK pDrvVD = (PVBOXDISK)pvUser;
+
+ AssertMsg(pDrvVD->pDrvTransportAsync, ("Asynchronous function called but no async transport interface below\n"));
+
+ return pDrvVD->pDrvTransportAsync->pfnFlushSynchronous(pDrvVD->pDrvTransportAsync, pStorage);
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOPrepareRead(void *pvUser, void *pStorage, uint64_t uOffset, void *pvBuf, size_t cbRead, void **ppTask)
+{
+ PVBOXDISK pDrvVD = (PVBOXDISK)pvUser;
+
+ AssertMsg(pDrvVD->pDrvTransportAsync, ("Asynchronous function called but no async transport interface below\n"));
+
+ return pDrvVD->pDrvTransportAsync->pfnPrepareRead(pDrvVD->pDrvTransportAsync, pStorage, uOffset, pvBuf, cbRead, ppTask);
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOPrepareWrite(void *pvUser, void *pStorage, uint64_t uOffset, void *pvBuf, size_t cbWrite, void **ppTask)
+{
+ PVBOXDISK pDrvVD = (PVBOXDISK)pvUser;
+
+ AssertMsg(pDrvVD->pDrvTransportAsync, ("Asynchronous function called but no async transport interface below\n"));
+
+ return pDrvVD->pDrvTransportAsync->pfnPrepareWrite(pDrvVD->pDrvTransportAsync, pStorage, uOffset, pvBuf, cbWrite, ppTask);
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOTasksSubmit(void *pvUser, void *apTasks[], unsigned cTasks, void *pvUser2,
+ void *pvUserCaller, PFNVDCOMPLETED pfnTasksCompleted)
+{
+ PVBOXDISK pDrvVD = (PVBOXDISK)pvUser;
+ PDRVVDASYNCTASK pDrvVDAsyncTask;
+ int rc;
+
+ AssertMsg(pDrvVD->pDrvTransportAsync, ("Asynchronous function called but no async transport interface below\n"));
+
+ rc = RTCacheRequest(pDrvVD->pCache, (void **)&pDrvVDAsyncTask);
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pDrvVDAsyncTask->pfnCompleted = pfnTasksCompleted;
+ pDrvVDAsyncTask->pvUser = pvUser2;
+ pDrvVDAsyncTask->pvUserCaller = pvUserCaller;
+
+ return pDrvVD->pDrvTransportAsync->pfnTasksSubmit(pDrvVD->pDrvTransportAsync, apTasks, cTasks, pDrvVDAsyncTask);
+}
+
+/*******************************************************************************
+* VD Configuration interface implementation *
+*******************************************************************************/
+
+static bool drvvdCfgAreKeysValid(void *pvUser, const char *pszzValid)
+{
+ return CFGMR3AreValuesValid((PCFGMNODE)pvUser, pszzValid);
+}
+
+static int drvvdCfgQuerySize(void *pvUser, const char *pszName, size_t *pcb)
+{
+ return CFGMR3QuerySize((PCFGMNODE)pvUser, pszName, pcb);
+}
+
+static int drvvdCfgQuery(void *pvUser, const char *pszName, char *pszString, size_t cchString)
+{
+ return CFGMR3QueryString((PCFGMNODE)pvUser, pszName, pszString, cchString);
+}
+
+
+#ifndef VBOX_OSE
+/*******************************************************************************
+* VD TCP network stack interface implementation - INIP case *
+*******************************************************************************/
+
+/** @copydoc VDINTERFACETCPNET::pfnClientConnect */
+static DECLCALLBACK(int) drvvdINIPClientConnect(const char *pszAddress, uint32_t uPort, PRTSOCKET pSock)
+{
+ int rc = VINF_SUCCESS;
+ /* First check whether lwIP is set up in this VM instance. */
+ if (!DevINIPConfigured())
+ {
+ LogRelFunc(("no IP stack\n"));
+ return VERR_NET_HOST_UNREACHABLE;
+ }
+ /* Resolve hostname. As there is no standard resolver for lwIP yet,
+ * just accept numeric IP addresses for now. */
+ struct in_addr ip;
+ if (!lwip_inet_aton(pszAddress, &ip))
+ {
+ LogRelFunc(("cannot resolve IP %s\n", pszAddress));
+ return VERR_NET_HOST_UNREACHABLE;
+ }
+ /* Create socket and connect. */
+ RTSOCKET Sock = lwip_socket(PF_INET, SOCK_STREAM, 0);
+ if (Sock != -1)
+ {
+ struct sockaddr_in InAddr = {0};
+ InAddr.sin_family = AF_INET;
+ InAddr.sin_port = htons(uPort);
+ InAddr.sin_addr = ip;
+ if (!lwip_connect(Sock, (struct sockaddr *)&InAddr, sizeof(InAddr)))
+ {
+ *pSock = Sock;
+ return VINF_SUCCESS;
+ }
+ rc = VERR_NET_CONNECTION_REFUSED; /* @todo real solution needed */
+ lwip_close(Sock);
+ }
+ else
+ rc = VERR_NET_CONNECTION_REFUSED; /* @todo real solution needed */
+ return rc;
+}
+
+/** @copydoc VDINTERFACETCPNET::pfnClientClose */
+static DECLCALLBACK(int) drvvdINIPClientClose(RTSOCKET Sock)
+{
+ lwip_close(Sock);
+ return VINF_SUCCESS; /** @todo real solution needed */
+}
+
+/** @copydoc VDINTERFACETCPNET::pfnSelectOne */
+static DECLCALLBACK(int) drvvdINIPSelectOne(RTSOCKET Sock, unsigned cMillies)
+{
+ fd_set fdsetR;
+ FD_ZERO(&fdsetR);
+ FD_SET(Sock, &fdsetR);
+ fd_set fdsetE = fdsetR;
+
+ int rc;
+ if (cMillies == RT_INDEFINITE_WAIT)
+ rc = lwip_select(Sock + 1, &fdsetR, NULL, &fdsetE, NULL);
+ else
+ {
+ struct timeval timeout;
+ timeout.tv_sec = cMillies / 1000;
+ timeout.tv_usec = (cMillies % 1000) * 1000;
+ rc = lwip_select(Sock + 1, &fdsetR, NULL, &fdsetE, &timeout);
+ }
+ if (rc > 0)
+ return VINF_SUCCESS;
+ if (rc == 0)
+ return VERR_TIMEOUT;
+ return VERR_NET_CONNECTION_REFUSED; /** @todo real solution needed */
+}
+
+/** @copydoc VDINTERFACETCPNET::pfnRead */
+static DECLCALLBACK(int) drvvdINIPRead(RTSOCKET Sock, void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
+{
+ /* Do params checking */
+ if (!pvBuffer || !cbBuffer)
+ {
+ AssertMsgFailed(("Invalid params\n"));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Read loop.
+ * If pcbRead is NULL we have to fill the entire buffer!
+ */
+ size_t cbRead = 0;
+ size_t cbToRead = cbBuffer;
+ for (;;)
+ {
+ /** @todo this clipping here is just in case (the send function
+ * needed it, so I added it here, too). Didn't investigate if this
+ * really has issues. Better be safe than sorry. */
+ ssize_t cbBytesRead = lwip_recv(Sock, (char *)pvBuffer + cbRead,
+ RT_MIN(cbToRead, 32768), 0);
+ if (cbBytesRead < 0)
+ return VERR_NET_CONNECTION_REFUSED; /** @todo real solution */
+ if (cbBytesRead == 0 && errno)
+ return VERR_NET_CONNECTION_REFUSED; /** @todo real solution */
+ if (pcbRead)
+ {
+ /* return partial data */
+ *pcbRead = cbBytesRead;
+ break;
+ }
+
+ /* read more? */
+ cbRead += cbBytesRead;
+ if (cbRead == cbBuffer)
+ break;
+
+ /* next */
+ cbToRead = cbBuffer - cbRead;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDINTERFACETCPNET::pfnWrite */
+static DECLCALLBACK(int) drvvdINIPWrite(RTSOCKET Sock, const void *pvBuffer, size_t cbBuffer)
+{
+ do
+ {
+ /** @todo lwip send only supports up to 65535 bytes in a single
+ * send (stupid limitation buried in the code), so make sure we
+ * don't get any wraparounds. This should be moved to DevINIP
+ * stack interface once that's implemented. */
+ ssize_t cbWritten = lwip_send(Sock, (void *)pvBuffer,
+ RT_MIN(cbBuffer, 32768), 0);
+ if (cbWritten < 0)
+ return VERR_NET_CONNECTION_REFUSED; /** @todo real solution needed */
+ AssertMsg(cbBuffer >= (size_t)cbWritten, ("Wrote more than we requested!!! cbWritten=%d cbBuffer=%d\n",
+ cbWritten, cbBuffer));
+ cbBuffer -= cbWritten;
+ pvBuffer = (const char *)pvBuffer + cbWritten;
+ } while (cbBuffer);
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDINTERFACETCPNET::pfnFlush */
+static DECLCALLBACK(int) drvvdINIPFlush(RTSOCKET Sock)
+{
+ int fFlag = 1;
+ lwip_setsockopt(Sock, IPPROTO_TCP, TCP_NODELAY,
+ (const char *)&fFlag, sizeof(fFlag));
+ fFlag = 0;
+ lwip_setsockopt(Sock, IPPROTO_TCP, TCP_NODELAY,
+ (const char *)&fFlag, sizeof(fFlag));
+ return VINF_SUCCESS;
+}
+#endif /* !VBOX_OSE */
+
+
+/*******************************************************************************
+* Media interface methods *
+*******************************************************************************/
+
+/** @copydoc PDMIMEDIA::pfnRead */
+static DECLCALLBACK(int) drvvdRead(PPDMIMEDIA pInterface,
+ uint64_t off, void *pvBuf, size_t cbRead)
+{
+ LogFlow(("%s: off=%#llx pvBuf=%p cbRead=%d\n", __FUNCTION__,
+ off, pvBuf, cbRead));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ int rc = VDRead(pThis->pDisk, off, pvBuf, cbRead);
+ if (RT_SUCCESS(rc))
+ Log2(("%s: off=%#llx pvBuf=%p cbRead=%d %.*Rhxd\n", __FUNCTION__,
+ off, pvBuf, cbRead, cbRead, pvBuf));
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnWrite */
+static DECLCALLBACK(int) drvvdWrite(PPDMIMEDIA pInterface,
+ uint64_t off, const void *pvBuf,
+ size_t cbWrite)
+{
+ LogFlow(("%s: off=%#llx pvBuf=%p cbWrite=%d\n", __FUNCTION__,
+ off, pvBuf, cbWrite));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ Log2(("%s: off=%#llx pvBuf=%p cbWrite=%d %.*Rhxd\n", __FUNCTION__,
+ off, pvBuf, cbWrite, cbWrite, pvBuf));
+ int rc = VDWrite(pThis->pDisk, off, pvBuf, cbWrite);
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnFlush */
+static DECLCALLBACK(int) drvvdFlush(PPDMIMEDIA pInterface)
+{
+ LogFlow(("%s:\n", __FUNCTION__));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ int rc = VDFlush(pThis->pDisk);
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetSize */
+static DECLCALLBACK(uint64_t) drvvdGetSize(PPDMIMEDIA pInterface)
+{
+ LogFlow(("%s:\n", __FUNCTION__));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ uint64_t cb = VDGetSize(pThis->pDisk, VD_LAST_IMAGE);
+ LogFlow(("%s: returns %#llx (%llu)\n", __FUNCTION__, cb, cb));
+ return cb;
+}
+
+/** @copydoc PDMIMEDIA::pfnIsReadOnly */
+static DECLCALLBACK(bool) drvvdIsReadOnly(PPDMIMEDIA pInterface)
+{
+ LogFlow(("%s:\n", __FUNCTION__));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ bool f = VDIsReadOnly(pThis->pDisk);
+ LogFlow(("%s: returns %d\n", __FUNCTION__, f));
+ return f;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosGetPCHSGeometry */
+static DECLCALLBACK(int) drvvdBiosGetPCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlow(("%s:\n", __FUNCTION__));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ int rc = VDGetPCHSGeometry(pThis->pDisk, VD_LAST_IMAGE, pPCHSGeometry);
+ if (RT_FAILURE(rc))
+ {
+ Log(("%s: geometry not available.\n", __FUNCTION__));
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ }
+ LogFlow(("%s: returns %Rrc (CHS=%d/%d/%d)\n", __FUNCTION__,
+ rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosSetPCHSGeometry */
+static DECLCALLBACK(int) drvvdBiosSetPCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlow(("%s: CHS=%d/%d/%d\n", __FUNCTION__,
+ pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ int rc = VDSetPCHSGeometry(pThis->pDisk, VD_LAST_IMAGE, pPCHSGeometry);
+ if (rc == VERR_VD_GEOMETRY_NOT_SET)
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosGetLCHSGeometry */
+static DECLCALLBACK(int) drvvdBiosGetLCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlow(("%s:\n", __FUNCTION__));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ int rc = VDGetLCHSGeometry(pThis->pDisk, VD_LAST_IMAGE, pLCHSGeometry);
+ if (RT_FAILURE(rc))
+ {
+ Log(("%s: geometry not available.\n", __FUNCTION__));
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ }
+ LogFlow(("%s: returns %Rrc (CHS=%d/%d/%d)\n", __FUNCTION__,
+ rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosSetLCHSGeometry */
+static DECLCALLBACK(int) drvvdBiosSetLCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlow(("%s: CHS=%d/%d/%d\n", __FUNCTION__,
+ pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ int rc = VDSetLCHSGeometry(pThis->pDisk, VD_LAST_IMAGE, pLCHSGeometry);
+ if (rc == VERR_VD_GEOMETRY_NOT_SET)
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetUuid */
+static DECLCALLBACK(int) drvvdGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+ LogFlow(("%s:\n", __FUNCTION__));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ int rc = VDGetUuid(pThis->pDisk, 0, pUuid);
+ LogFlow(("%s: returns %Rrc ({%RTuuid})\n", __FUNCTION__, rc, pUuid));
+ return rc;
+}
+
+/*******************************************************************************
+* Async Media interface methods *
+*******************************************************************************/
+
+static DECLCALLBACK(int) drvvdStartRead(PPDMIMEDIAASYNC pInterface, uint64_t uOffset,
+ PPDMDATASEG paSeg, unsigned cSeg,
+ size_t cbRead, void *pvUser)
+{
+ LogFlow(("%s: uOffset=%#llx paSeg=%#p cSeg=%u cbRead=%d\n pvUser=%#p", __FUNCTION__,
+ uOffset, paSeg, cSeg, cbRead, pvUser));
+ PVBOXDISK pThis = PDMIMEDIAASYNC_2_VBOXDISK(pInterface);
+ int rc = VDAsyncRead(pThis->pDisk, uOffset, cbRead, paSeg, cSeg, pvUser);
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvvdStartWrite(PPDMIMEDIAASYNC pInterface, uint64_t uOffset,
+ PPDMDATASEG paSeg, unsigned cSeg,
+ size_t cbWrite, void *pvUser)
+{
+ LogFlow(("%s: uOffset=%#llx paSeg=%#p cSeg=%u cbWrite=%d\n pvUser=%#p", __FUNCTION__,
+ uOffset, paSeg, cSeg, cbWrite, pvUser));
+ PVBOXDISK pThis = PDMIMEDIAASYNC_2_VBOXDISK(pInterface);
+ int rc = VDAsyncWrite(pThis->pDisk, uOffset, cbWrite, paSeg, cSeg, pvUser);
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+/*******************************************************************************
+* Async transport port interface methods *
+*******************************************************************************/
+
+static DECLCALLBACK(int) drvvdTasksCompleteNotify(PPDMITRANSPORTASYNCPORT pInterface, void *pvUser)
+{
+ PVBOXDISK pThis = PDMITRANSPORTASYNCPORT_2_VBOXDISK(pInterface);
+ PDRVVDASYNCTASK pDrvVDAsyncTask = (PDRVVDASYNCTASK)pvUser;
+ int rc = VINF_VD_ASYNC_IO_FINISHED;
+
+ /* Having a completion callback for a task is not mandatory. */
+ if (pDrvVDAsyncTask->pfnCompleted)
+ rc = pDrvVDAsyncTask->pfnCompleted(pDrvVDAsyncTask->pvUser);
+
+ /* Check if the request is finished. */
+ if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ {
+ rc = pThis->pDrvMediaAsyncPort->pfnTransferCompleteNotify(pThis->pDrvMediaAsyncPort, pDrvVDAsyncTask->pvUserCaller);
+ }
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_SUCCESS;
+
+ rc = RTCacheInsert(pThis->pCache, pDrvVDAsyncTask);
+ AssertRC(rc);
+
+ return rc;
+}
+
+
+/*******************************************************************************
+* Base interface methods *
+*******************************************************************************/
+
+/** @copydoc PDMIBASE::pfnQueryInterface */
+static DECLCALLBACK(void *) drvvdQueryInterface(PPDMIBASE pInterface,
+ PDMINTERFACE enmInterface)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_DRVINS(pInterface);
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ switch (enmInterface)
+ {
+ case PDMINTERFACE_BASE:
+ return &pDrvIns->IBase;
+ case PDMINTERFACE_MEDIA:
+ return &pThis->IMedia;
+ case PDMINTERFACE_MEDIA_ASYNC:
+ return pThis->fAsyncIOSupported ? &pThis->IMediaAsync : NULL;
+ case PDMINTERFACE_TRANSPORT_ASYNC_PORT:
+ return &pThis->ITransportAsyncPort;
+ default:
+ return NULL;
+ }
+}
+
+
+/*******************************************************************************
+* Driver methods *
+*******************************************************************************/
+
+
+/**
+ * Construct a VBox disk media driver instance.
+ *
+ * @returns VBox status.
+ * @param pDrvIns The driver instance data.
+ * If the registration structure is needed, pDrvIns->pDrvReg points to it.
+ * @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
+ * of the driver instance. It's also found in pDrvIns->pCfgHandle as it's expected
+ * to be used frequently in this function.
+ */
+static DECLCALLBACK(int) drvvdConstruct(PPDMDRVINS pDrvIns,
+ PCFGMNODE pCfgHandle)
+{
+ LogFlow(("%s:\n", __FUNCTION__));
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ int rc = VINF_SUCCESS;
+ char *pszName = NULL; /**< The path of the disk image file. */
+ char *pszFormat = NULL; /**< The format backed to use for this image. */
+ bool fReadOnly; /**< True if the media is readonly. */
+ bool fHonorZeroWrites; /**< True if zero blocks should be written. */
+
+ /*
+ * Init the static parts.
+ */
+ pDrvIns->IBase.pfnQueryInterface = drvvdQueryInterface;
+ pThis->pDrvIns = pDrvIns;
+ pThis->fTempReadOnly = false;
+ pThis->pDisk = NULL;
+ pThis->fAsyncIOSupported = false;
+
+ /* IMedia */
+ pThis->IMedia.pfnRead = drvvdRead;
+ pThis->IMedia.pfnWrite = drvvdWrite;
+ pThis->IMedia.pfnFlush = drvvdFlush;
+ pThis->IMedia.pfnGetSize = drvvdGetSize;
+ pThis->IMedia.pfnIsReadOnly = drvvdIsReadOnly;
+ pThis->IMedia.pfnBiosGetPCHSGeometry = drvvdBiosGetPCHSGeometry;
+ pThis->IMedia.pfnBiosSetPCHSGeometry = drvvdBiosSetPCHSGeometry;
+ pThis->IMedia.pfnBiosGetLCHSGeometry = drvvdBiosGetLCHSGeometry;
+ pThis->IMedia.pfnBiosSetLCHSGeometry = drvvdBiosSetLCHSGeometry;
+ pThis->IMedia.pfnGetUuid = drvvdGetUuid;
+
+ /* IMediaAsync */
+ pThis->IMediaAsync.pfnStartRead = drvvdStartRead;
+ pThis->IMediaAsync.pfnStartWrite = drvvdStartWrite;
+
+ /* ITransportAsyncPort */
+ pThis->ITransportAsyncPort.pfnTaskCompleteNotify = drvvdTasksCompleteNotify;
+
+ /* Initialize supported VD interfaces. */
+ pThis->pVDIfsDisk = NULL;
+
+ pThis->VDIErrorCallbacks.cbSize = sizeof(VDINTERFACEERROR);
+ pThis->VDIErrorCallbacks.enmInterface = VDINTERFACETYPE_ERROR;
+ pThis->VDIErrorCallbacks.pfnError = drvvdErrorCallback;
+
+ rc = VDInterfaceAdd(&pThis->VDIError, "DrvVD_VDIError", VDINTERFACETYPE_ERROR,
+ &pThis->VDIErrorCallbacks, pDrvIns, &pThis->pVDIfsDisk);
+ AssertRC(rc);
+
+ pThis->VDIAsyncIOCallbacks.cbSize = sizeof(VDINTERFACEASYNCIO);
+ pThis->VDIAsyncIOCallbacks.enmInterface = VDINTERFACETYPE_ASYNCIO;
+ pThis->VDIAsyncIOCallbacks.pfnOpen = drvvdAsyncIOOpen;
+ pThis->VDIAsyncIOCallbacks.pfnClose = drvvdAsyncIOClose;
+ pThis->VDIAsyncIOCallbacks.pfnRead = drvvdAsyncIORead;
+ pThis->VDIAsyncIOCallbacks.pfnWrite = drvvdAsyncIOWrite;
+ pThis->VDIAsyncIOCallbacks.pfnFlush = drvvdAsyncIOFlush;
+ pThis->VDIAsyncIOCallbacks.pfnPrepareRead = drvvdAsyncIOPrepareRead;
+ pThis->VDIAsyncIOCallbacks.pfnPrepareWrite = drvvdAsyncIOPrepareWrite;
+ pThis->VDIAsyncIOCallbacks.pfnTasksSubmit = drvvdAsyncIOTasksSubmit;
+
+ rc = VDInterfaceAdd(&pThis->VDIAsyncIO, "DrvVD_AsyncIO", VDINTERFACETYPE_ASYNCIO,
+ &pThis->VDIAsyncIOCallbacks, pThis, &pThis->pVDIfsDisk);
+ AssertRC(rc);
+
+ /* This is just prepared here, the actual interface is per-image, so it's
+ * added later. No need to have separate callback tables. */
+ pThis->VDIConfigCallbacks.cbSize = sizeof(VDINTERFACECONFIG);
+ pThis->VDIConfigCallbacks.enmInterface = VDINTERFACETYPE_CONFIG;
+ pThis->VDIConfigCallbacks.pfnAreKeysValid = drvvdCfgAreKeysValid;
+ pThis->VDIConfigCallbacks.pfnQuerySize = drvvdCfgQuerySize;
+ pThis->VDIConfigCallbacks.pfnQuery = drvvdCfgQuery;
+
+ /* List of images is empty now. */
+ pThis->pImages = NULL;
+
+ /* Try to attach async media port interface above.*/
+ pThis->pDrvMediaAsyncPort = (PPDMIMEDIAASYNCPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_MEDIA_ASYNC_PORT);
+
+ /*
+ * Attach the async transport driver below if the device above us implements the
+ * async interface.
+ */
+ if (pThis->pDrvMediaAsyncPort)
+ {
+ /* Try to attach the driver. */
+ PPDMIBASE pBase;
+
+ rc = pDrvIns->pDrvHlp->pfnAttach(pDrvIns, &pBase);
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ /*
+ * Though the device supports async I/O there is no transport driver
+ * which processes async requests.
+ * Revert to non async I/O.
+ */
+ rc = VINF_SUCCESS;
+ pThis->pDrvMediaAsyncPort = NULL;
+ pThis->fAsyncIOSupported = false;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Failed to attach async transport driver below rc=%Rrc\n", rc));
+ }
+ else
+ {
+ /*
+ * The device supports async I/O and we successfully attached the transport driver.
+ * Indicate that async I/O is supported for now as we check if the image backend supports
+ * it later.
+ */
+ pThis->fAsyncIOSupported = true;
+
+ /* Success query the async transport interface. */
+ pThis->pDrvTransportAsync = (PPDMITRANSPORTASYNC)pBase->pfnQueryInterface(pBase, PDMINTERFACE_TRANSPORT_ASYNC);
+ if (!pThis->pDrvTransportAsync)
+ {
+ /* An attached driver without an async transport interface - impossible. */
+ AssertMsgFailed(("Configuration error: No async transport interface below!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+ }
+ }
+
+ /*
+ * Validate configuration and find all parent images.
+ * It's sort of up side down from the image dependency tree.
+ */
+ bool fHostIP = false;
+ unsigned iLevel = 0;
+ PCFGMNODE pCurNode = pCfgHandle;
+
+ for (;;)
+ {
+ bool fValid;
+
+ if (pCurNode == pCfgHandle)
+ {
+ /* Toplevel configuration additionally contains the global image
+ * open flags. Some might be converted to per-image flags later. */
+ fValid = CFGMR3AreValuesValid(pCurNode,
+ "Format\0Path\0"
+ "ReadOnly\0HonorZeroWrites\0"
+ "HostIPStack\0");
+ }
+ else
+ {
+ /* All other image configurations only contain image name and
+ * the format information. */
+ fValid = CFGMR3AreValuesValid(pCurNode, "Format\0Path\0");
+ }
+ if (!fValid)
+ {
+ rc = PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES,
+ RT_SRC_POS, N_("DrvVD: Configuration error: keys incorrect at level %d"), iLevel);
+ break;
+ }
+
+ if (pCurNode == pCfgHandle)
+ {
+ rc = CFGMR3QueryBool(pCurNode, "HostIPStack", &fHostIP);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ {
+ fHostIP = true;
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"HostIPStack\" as boolean failed"));
+ break;
+ }
+
+ rc = CFGMR3QueryBool(pCurNode, "HonorZeroWrites", &fHonorZeroWrites);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ {
+ fHonorZeroWrites = false;
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"HonorZeroWrites\" as boolean failed"));
+ break;
+ }
+
+ rc = CFGMR3QueryBool(pCurNode, "ReadOnly", &fReadOnly);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ {
+ fReadOnly = false;
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"ReadOnly\" as boolean failed"));
+ break;
+ }
+ }
+
+ PCFGMNODE pParent = CFGMR3GetChild(pCurNode, "Parent");
+ if (!pParent)
+ break;
+ pCurNode = pParent;
+ iLevel++;
+ }
+
+ /*
+ * Open the images.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ /* First of all figure out what kind of TCP networking stack interface
+ * to use. This is done unconditionally, as backends which don't need
+ * it will just ignore it. */
+ if (fHostIP)
+ {
+ pThis->VDITcpNetCallbacks.cbSize = sizeof(VDINTERFACETCPNET);
+ pThis->VDITcpNetCallbacks.enmInterface = VDINTERFACETYPE_TCPNET;
+ pThis->VDITcpNetCallbacks.pfnClientConnect = RTTcpClientConnect;
+ pThis->VDITcpNetCallbacks.pfnClientClose = RTTcpClientClose;
+ pThis->VDITcpNetCallbacks.pfnSelectOne = RTTcpSelectOne;
+ pThis->VDITcpNetCallbacks.pfnRead = RTTcpRead;
+ pThis->VDITcpNetCallbacks.pfnWrite = RTTcpWrite;
+ pThis->VDITcpNetCallbacks.pfnFlush = RTTcpFlush;
+ }
+ else
+ {
+#ifdef VBOX_OSE
+ rc = PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES,
+ RT_SRC_POS, N_("DrvVD: Configuration error: TCP over Internal Networking not supported in VirtualBox OSE"));
+#else /* !VBOX_OSE */
+ pThis->VDITcpNetCallbacks.cbSize = sizeof(VDINTERFACETCPNET);
+ pThis->VDITcpNetCallbacks.enmInterface = VDINTERFACETYPE_TCPNET;
+ pThis->VDITcpNetCallbacks.pfnClientConnect = drvvdINIPClientConnect;
+ pThis->VDITcpNetCallbacks.pfnClientClose = drvvdINIPClientClose;
+ pThis->VDITcpNetCallbacks.pfnSelectOne = drvvdINIPSelectOne;
+ pThis->VDITcpNetCallbacks.pfnRead = drvvdINIPRead;
+ pThis->VDITcpNetCallbacks.pfnWrite = drvvdINIPWrite;
+ pThis->VDITcpNetCallbacks.pfnFlush = drvvdINIPFlush;
+#endif /* !VBOX_OSE */
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = VDInterfaceAdd(&pThis->VDITcpNet, "DrvVD_INIP",
+ VDINTERFACETYPE_TCPNET,
+ &pThis->VDITcpNetCallbacks, NULL,
+ &pThis->pVDIfsDisk);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = VDCreate(pThis->pVDIfsDisk, &pThis->pDisk);
+ /* Error message is already set correctly. */
+ }
+ }
+
+ while (pCurNode && RT_SUCCESS(rc))
+ {
+ /* Allocate per-image data. */
+ PVBOXIMAGE pImage = drvvdNewImage(pThis);
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /*
+ * Read the image configuration.
+ */
+ rc = CFGMR3QueryStringAlloc(pCurNode, "Path", &pszName);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"Path\" as string failed"));
+ break;
+ }
+
+ rc = CFGMR3QueryStringAlloc(pCurNode, "Format", &pszFormat);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"Format\" as string failed"));
+ break;
+ }
+
+ PCFGMNODE pCfg = CFGMR3GetChild(pCurNode, "VDConfig");
+ rc = VDInterfaceAdd(&pImage->VDIConfig, "DrvVD_Config", VDINTERFACETYPE_CONFIG,
+ &pThis->VDIConfigCallbacks, pCfg, &pImage->pVDIfsImage);
+ AssertRC(rc);
+
+ /*
+ * Open the image.
+ */
+ unsigned uOpenFlags;
+ if (fReadOnly || iLevel != 0)
+ uOpenFlags = VD_OPEN_FLAGS_READONLY;
+ else
+ uOpenFlags = VD_OPEN_FLAGS_NORMAL;
+ if (fHonorZeroWrites)
+ uOpenFlags |= VD_OPEN_FLAGS_HONOR_ZEROES;
+ if (pThis->pDrvMediaAsyncPort)
+ uOpenFlags |= VD_OPEN_FLAGS_ASYNC_IO;
+
+ /** Try to open backend in asyc I/O mode first. */
+ rc = VDOpen(pThis->pDisk, pszFormat, pszName, uOpenFlags, pImage->pVDIfsImage);
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ /* Seems async I/O is not supported by the backend, open in normal mode. */
+ uOpenFlags &= ~VD_OPEN_FLAGS_ASYNC_IO;
+ rc = VDOpen(pThis->pDisk, pszFormat, pszName, uOpenFlags, pImage->pVDIfsImage);
+ }
+
+ if (RT_SUCCESS(rc))
+ Log(("%s: %d - Opened '%s' in %s mode\n", __FUNCTION__,
+ iLevel, pszName,
+ VDIsReadOnly(pThis->pDisk) ? "read-only" : "read-write"));
+ else
+ {
+ rc = PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("Failed to open image '%s' in %s mode rc=%Rrc\n"), pszName,
+ (uOpenFlags & VD_OPEN_FLAGS_READONLY) ? "readonly" : "read-write", rc);
+ break;
+ }
+
+
+ MMR3HeapFree(pszName);
+ pszName = NULL;
+ MMR3HeapFree(pszFormat);
+ pszFormat = NULL;
+
+ /* next */
+ iLevel--;
+ pCurNode = CFGMR3GetParent(pCurNode);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (VALID_PTR(pThis->pDisk))
+ {
+ VDDestroy(pThis->pDisk);
+ pThis->pDisk = NULL;
+ }
+ drvvdFreeImages(pThis);
+ if (VALID_PTR(pszName))
+ MMR3HeapFree(pszName);
+ if (VALID_PTR(pszFormat))
+ MMR3HeapFree(pszFormat);
+
+ return rc;
+ }
+ else
+ {
+ /*
+ * Check if every opened image supports async I/O.
+ * If not we revert to non async I/O.
+ */
+ if (pThis->fAsyncIOSupported)
+ {
+ for (unsigned i = 0; i < VDGetCount(pThis->pDisk); i++)
+ {
+ VDBACKENDINFO vdBackendInfo;
+
+ rc = VDBackendInfoSingle(pThis->pDisk, i, &vdBackendInfo);
+ AssertRC(rc);
+
+ if (vdBackendInfo.uBackendCaps & VD_CAP_ASYNC)
+ {
+ /*
+ * Backend indicates support for at least some files.
+ * Check if current file is supported with async I/O)
+ */
+ rc = VDImageIsAsyncIOSupported(pThis->pDisk, i, &pThis->fAsyncIOSupported);
+ AssertRC(rc);
+
+ /*
+ * Check if current image is supported.
+ * If not we can stop checking because
+ * at least one does not support it.
+ */
+ if (!pThis->fAsyncIOSupported)
+ break;
+ }
+ else
+ {
+ pThis->fAsyncIOSupported = false;
+ break;
+ }
+ }
+ }
+
+ /*
+ * We know definitly if async I/O is supported now.
+ * Create cache if it is supported.
+ */
+ if (pThis->fAsyncIOSupported)
+ {
+ rc = RTCacheCreate(&pThis->pCache, 0, sizeof(DRVVDASYNCTASK), RTOBJCACHE_PROTECT_INSERT);
+ AssertMsgRC(rc, ("Failed to create cache rc=%Rrc\n", rc));
+ }
+ }
+
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+/**
+ * Destruct a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvvdDestruct(PPDMDRVINS pDrvIns)
+{
+ int rc;
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ LogFlow(("%s:\n", __FUNCTION__));
+
+ drvvdFreeImages(pThis);
+ if (pThis->pCache)
+ {
+ rc = RTCacheDestroy(pThis->pCache);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * When the VM has been suspended we'll change the image mode to read-only
+ * so that main and others can read the VDIs. This is important when
+ * saving state and so forth.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvvdSuspend(PPDMDRVINS pDrvIns)
+{
+ LogFlow(("%s:\n", __FUNCTION__));
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ if (!VDIsReadOnly(pThis->pDisk))
+ {
+ unsigned uOpenFlags;
+ int rc = VDGetOpenFlags(pThis->pDisk, VD_LAST_IMAGE, &uOpenFlags);
+ AssertRC(rc);
+ uOpenFlags |= VD_OPEN_FLAGS_READONLY;
+ rc = VDSetOpenFlags(pThis->pDisk, VD_LAST_IMAGE, uOpenFlags);
+ AssertRC(rc);
+ pThis->fTempReadOnly = true;
+ }
+}
+
+/**
+ * Before the VM resumes we'll have to undo the read-only mode change
+ * done in drvvdSuspend.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvvdResume(PPDMDRVINS pDrvIns)
+{
+ LogFlow(("%s:\n", __FUNCTION__));
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ if (pThis->fTempReadOnly)
+ {
+ unsigned uOpenFlags;
+ int rc = VDGetOpenFlags(pThis->pDisk, VD_LAST_IMAGE, &uOpenFlags);
+ AssertRC(rc);
+ uOpenFlags &= ~VD_OPEN_FLAGS_READONLY;
+ rc = VDSetOpenFlags(pThis->pDisk, VD_LAST_IMAGE, uOpenFlags);
+ AssertRC(rc);
+ pThis->fTempReadOnly = false;
+ }
+}
+
+static DECLCALLBACK(void) drvvdPowerOff(PPDMDRVINS pDrvIns)
+{
+ LogFlow(("%s:\n", __FUNCTION__));
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+
+ /*
+ * We must close the disk here to ensure that
+ * the backend closes all files before the
+ * async transport driver is destructed.
+ */
+ int rc = VDCloseAll(pThis->pDisk);
+ AssertRC(rc);
+}
+
+/**
+ * VBox disk container media driver registration record.
+ */
+const PDMDRVREG g_DrvVD =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szDriverName */
+ "VD",
+ /* pszDescription */
+ "Generic VBox disk media driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_MEDIA,
+ /* cMaxInstances */
+ ~0,
+ /* cbInstance */
+ sizeof(VBOXDISK),
+ /* pfnConstruct */
+ drvvdConstruct,
+ /* pfnDestruct */
+ drvvdDestruct,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ drvvdSuspend,
+ /* pfnResume */
+ drvvdResume,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ drvvdPowerOff
+};
diff --git a/src/VBox/Devices/Storage/Makefile.kup b/src/VBox/Devices/Storage/Makefile.kup
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/VBox/Devices/Storage/Makefile.kup
diff --git a/src/VBox/Devices/Storage/PIIX3ATABmDma.h b/src/VBox/Devices/Storage/PIIX3ATABmDma.h
new file mode 100644
index 000000000..81e58651f
--- /dev/null
+++ b/src/VBox/Devices/Storage/PIIX3ATABmDma.h
@@ -0,0 +1,78 @@
+/** @file
+ *
+ * VBox storage devices:
+ * PIIX3 ATA busmaster controller definitions
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+#ifndef __PIIX3ATABmDma_h__
+#define __PIIX3ATABmDma_h__
+
+
+/** @defgroup grp_piix3atabmdma PIIX3 ATA Bus Master DMA
+ * @{
+ */
+
+/** @name BM_STATUS
+ * @{
+ */
+/** Currently performing a DMA operation. */
+#define BM_STATUS_DMAING 0x01
+/** An error occurred during the DMA operation. */
+#define BM_STATUS_ERROR 0x02
+/** The DMA unit has raised the IDE interrupt line. */
+#define BM_STATUS_INT 0x04
+/** User-defined bit 0, commonly used to signal that drive 0 supports DMA. */
+#define BM_STATUS_D0DMA 0x20
+/** User-defined bit 1, commonly used to signal that drive 1 supports DMA. */
+#define BM_STATUS_D1DMA 0x40
+/** @} */
+
+/** @name BM_CMD
+ * @{
+ */
+/** Start the DMA operation. */
+#define BM_CMD_START 0x01
+/** Data transfer direction: from device to memory if set. */
+#define BM_CMD_WRITE 0x08
+/** @} */
+
+
+/** PIIX3 Bus Master DMA unit state. */
+typedef struct BMDMAState {
+ /** Command register. */
+ uint8_t u8Cmd;
+ /** Status register. */
+ uint8_t u8Status;
+ /** Address of the MMIO region in the guest's memory space. */
+ RTGCPHYS32 pvAddr;
+} BMDMAState;
+
+
+/** PIIX3 Bus Master DMA descriptor entry. */
+typedef struct BMDMADesc {
+ /** Address of the DMA source/target buffer. */
+ RTGCPHYS32 pBuffer;
+ /** Size of the DMA source/target buffer. */
+ uint32_t cbBuffer;
+} BMDMADesc;
+
+/** @} */
+
+
+#endif /* !__PIIX3ATABmDma_h__ */
diff --git a/src/VBox/Devices/Storage/RawHDDCore.cpp b/src/VBox/Devices/Storage/RawHDDCore.cpp
new file mode 100644
index 000000000..ec0f4c839
--- /dev/null
+++ b/src/VBox/Devices/Storage/RawHDDCore.cpp
@@ -0,0 +1,1175 @@
+/* $Id: RawHDDCore.cpp 15366 2008-12-12 13:50:32Z vboxsync $ */
+/** @file
+ * RawHDDCore - Raw Disk image, Core Code.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_RAW
+#include "VBoxHDD-newInternal.h"
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/file.h>
+
+
+/*******************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*******************************************************************************/
+
+/**
+ * Raw image data structure.
+ */
+typedef struct RAWIMAGE
+{
+ /** Base image name. */
+ const char *pszFilename;
+ /** File descriptor. */
+ RTFILE File;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+
+ /** Error callback. */
+ PVDINTERFACE pInterfaceError;
+ /** Opaque data for error callback. */
+ PVDINTERFACEERROR pInterfaceErrorCallbacks;
+
+ /** Open flags passed by VBoxHD layer. */
+ unsigned uOpenFlags;
+ /** Image type. */
+ VDIMAGETYPE enmImageType;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+ /** Physical geometry of this image. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ PDMMEDIAGEOMETRY LCHSGeometry;
+
+} RAWIMAGE, *PRAWIMAGE;
+
+/*******************************************************************************
+* Static Variables *
+*******************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const char *const s_apszRawFileExtensions[] =
+{
+ /** @todo At the monment this backend doesn't claim any extensions, but it might
+ * be useful to add a few later. However this needs careful testing, as the
+ * CheckIfValid function never returns success. */
+ NULL
+};
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+
+static int rawFlushImage(PRAWIMAGE pImage);
+static void rawFreeImage(PRAWIMAGE pImage, bool fDelete);
+
+
+/**
+ * Internal: signal an error to the frontend.
+ */
+DECLINLINE(int) rawError(PRAWIMAGE pImage, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pImage->pInterfaceError)
+ pImage->pInterfaceErrorCallbacks->pfnError(pImage->pInterfaceError->pvUser, rc, RT_SRC_POS_ARGS,
+ pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int rawOpenImage(PRAWIMAGE pImage, unsigned uOpenFlags)
+{
+ int rc;
+ RTFILE File;
+
+ if (uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)
+ return VERR_NOT_SUPPORTED;
+
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pInterfaceError = VDInterfaceGet(pImage->pVDIfsDisk, VDINTERFACETYPE_ERROR);
+ if (pImage->pInterfaceError)
+ pImage->pInterfaceErrorCallbacks = VDGetInterfaceError(pImage->pInterfaceError);
+
+ /*
+ * Open the image.
+ */
+ rc = RTFileOpen(&File, pImage->pszFilename,
+ uOpenFlags & VD_OPEN_FLAGS_READONLY
+ ? RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+ : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ goto out;
+ }
+ pImage->File = File;
+
+ rc = RTFileGetSize(pImage->File, &pImage->cbSize);
+ if (RT_FAILURE(rc))
+ goto out;
+ if (pImage->cbSize % 512)
+ {
+ rc = VERR_VD_RAW_INVALID_HEADER;
+ goto out;
+ }
+ pImage->enmImageType = VD_IMAGE_TYPE_FIXED;
+
+out:
+ if (RT_FAILURE(rc))
+ rawFreeImage(pImage, false);
+ return rc;
+}
+
+/**
+ * Internal: Create a raw image.
+ */
+static int rawCreateImage(PRAWIMAGE pImage, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ const char *pszComment,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry,
+ PFNVMPROGRESS pfnProgress, void *pvUser,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ int rc;
+ RTFILE File;
+ RTFOFF cbFree = 0;
+ uint64_t uOff;
+ size_t cbBuf = 128 * _1K;
+ void *pvBuf = NULL;
+
+ if (enmType != VD_IMAGE_TYPE_FIXED)
+ {
+ rc = rawError(pImage, VERR_VD_RAW_INVALID_TYPE, RT_SRC_POS, N_("Raw: cannot create diff image '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ pImage->enmImageType = enmType;
+ pImage->uImageFlags = uImageFlags;
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ pImage->LCHSGeometry = *pLCHSGeometry;
+
+ pImage->pInterfaceError = VDInterfaceGet(pImage->pVDIfsDisk, VDINTERFACETYPE_ERROR);
+ if (pImage->pInterfaceError)
+ pImage->pInterfaceErrorCallbacks = VDGetInterfaceError(pImage->pInterfaceError);
+
+ /* Create image file. */
+ rc = RTFileOpen(&File, pImage->pszFilename,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_ALL);
+ if (RT_FAILURE(rc))
+ {
+ rc = rawError(pImage, rc, RT_SRC_POS, N_("Raw: cannot create image '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ pImage->File = File;
+
+ /* Check the free space on the disk and leave early if there is not
+ * sufficient space available. */
+ rc = RTFsQuerySizes(pImage->pszFilename, NULL, &cbFree, NULL, NULL);
+ if (RT_SUCCESS(rc) /* ignore errors */ && ((uint64_t)cbFree < cbSize))
+ {
+ rc = rawError(pImage, VERR_DISK_FULL, RT_SRC_POS, N_("Raw: disk would overflow creating image '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ /* Allocate & commit whole file if fixed image, it must be more
+ * effective than expanding file by write operations. */
+ rc = RTFileSetSize(File, cbSize);
+ if (RT_FAILURE(rc))
+ {
+ rc = rawError(pImage, rc, RT_SRC_POS, N_("Raw: setting image size failed for '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ /* Fill image with zeroes. We do this for every fixed-size image since on
+ * some systems (for example Windows Vista), it takes ages to write a block
+ * near the end of a sparse file and the guest could complain about an ATA
+ * timeout. */
+ pvBuf = RTMemTmpAllocZ(cbBuf);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ uOff = 0;
+ /* Write data to all image blocks. */
+ while (uOff < cbSize)
+ {
+ unsigned cbChunk = (unsigned)RT_MIN(cbSize, cbBuf);
+
+ rc = RTFileWriteAt(File, uOff, pvBuf, cbChunk, NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = rawError(pImage, rc, RT_SRC_POS, N_("Raw: writing block failed for '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ uOff += cbChunk;
+
+ if (pfnProgress)
+ {
+ rc = pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uPercentStart + uOff * uPercentSpan * 98 / (cbSize * 100),
+ pvUser);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+ }
+ RTMemTmpFree(pvBuf);
+
+ if (RT_SUCCESS(rc) && pfnProgress)
+ pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uPercentStart + uPercentSpan * 98 / 100, pvUser);
+
+ pImage->enmImageType = enmType;
+ pImage->cbSize = cbSize;
+
+ rc = rawFlushImage(pImage);
+
+out:
+ if (RT_SUCCESS(rc) && pfnProgress)
+ pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uPercentStart + uPercentSpan, pvUser);
+
+ if (RT_FAILURE(rc))
+ rawFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image, and optionally
+ * delete the image from disk.
+ */
+static void rawFreeImage(PRAWIMAGE pImage, bool fDelete)
+{
+ Assert(pImage);
+
+ if (pImage->enmImageType)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rawFlushImage(pImage);
+ }
+ if (pImage->File != NIL_RTFILE)
+ {
+ RTFileClose(pImage->File);
+ pImage->File = NIL_RTFILE;
+ }
+ if (fDelete && pImage->pszFilename)
+ RTFileDelete(pImage->pszFilename);
+}
+
+/**
+ * Internal. Flush image data to disk.
+ */
+static int rawFlushImage(PRAWIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( pImage->File != NIL_RTFILE
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rc = RTFileFlush(pImage->File);
+
+ return rc;
+}
+
+
+/** @copydoc VBOXHDDBACKEND::pfnCheckIfValid */
+static int rawCheckIfValid(const char *pszFilename)
+{
+ LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename));
+ int rc = VINF_SUCCESS;
+
+ if ( !VALID_PTR(pszFilename)
+ || !*pszFilename)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Always return failure, to avoid opening everything as a raw image. */
+ rc = VERR_VD_RAW_INVALID_HEADER;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnOpen */
+static int rawOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p ppBackendData=%#p\n", pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, ppBackendData));
+ int rc;
+ PRAWIMAGE pImage;
+
+ /* Check open flags. All valid flags are supported. */
+ if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Check remaining arguments. */
+ if ( !VALID_PTR(pszFilename)
+ || !*pszFilename)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+
+ pImage = (PRAWIMAGE)RTMemAllocZ(sizeof(RAWIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pImage->pszFilename = pszFilename;
+ pImage->File = NIL_RTFILE;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+
+ rc = rawOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+
+out:
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnCreate */
+static int rawCreate(const char *pszFilename, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ const char *pszComment,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry, PCRTUUID pUuid,
+ unsigned uOpenFlags, unsigned uPercentStart,
+ unsigned uPercentSpan, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, PVDINTERFACE pVDIfsOperation,
+ void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" enmType=%d cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p ppBackendData=%#p", pszFilename, enmType, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, ppBackendData));
+ int rc;
+ PRAWIMAGE pImage;
+
+ PFNVMPROGRESS pfnProgress = NULL;
+ void *pvUser = NULL;
+ PVDINTERFACE pIfProgress = VDInterfaceGet(pVDIfsOperation,
+ VDINTERFACETYPE_PROGRESS);
+ PVDINTERFACEPROGRESS pCbProgress = NULL;
+ if (pIfProgress)
+ {
+ pCbProgress = VDGetInterfaceProgress(pIfProgress);
+ if (pCbProgress)
+ pfnProgress = pCbProgress->pfnProgress;
+ pvUser = pIfProgress->pvUser;
+ }
+
+ /* Check open flags. All valid flags are supported. */
+ if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Check remaining arguments. */
+ if ( !VALID_PTR(pszFilename)
+ || !*pszFilename
+ || (enmType != VD_IMAGE_TYPE_FIXED)
+ || !VALID_PTR(pPCHSGeometry)
+ || !VALID_PTR(pLCHSGeometry))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ pImage = (PRAWIMAGE)RTMemAllocZ(sizeof(RAWIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pImage->pszFilename = pszFilename;
+ pImage->File = NIL_RTFILE;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+
+ rc = rawCreateImage(pImage, enmType, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry,
+ pfnProgress, pvUser, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rawFreeImage(pImage, false);
+ rc = rawOpenImage(pImage, uOpenFlags);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+ *ppBackendData = pImage;
+ }
+
+out:
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnRename */
+static int rawRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VERR_NOT_IMPLEMENTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnClose */
+static int rawClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ rawFreeImage(pImage, fDelete);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnRead */
+static int rawRead(void *pBackendData, uint64_t uOffset, void *pvBuf,
+ size_t cbToRead, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pvBuf=%#p cbToRead=%zu pcbActuallyRead=%#p\n", pBackendData, uOffset, pvBuf, cbToRead, pcbActuallyRead));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToRead % 512 == 0);
+
+ if ( uOffset + cbToRead > pImage->cbSize
+ || cbToRead == 0)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ rc = RTFileReadAt(pImage->File, uOffset, pvBuf, cbToRead, NULL);
+ *pcbActuallyRead = cbToRead;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnWrite */
+static int rawWrite(void *pBackendData, uint64_t uOffset, const void *pvBuf,
+ size_t cbToWrite, size_t *pcbWriteProcess,
+ size_t *pcbPreRead, size_t *pcbPostRead, unsigned fWrite)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pvBuf=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n", pBackendData, uOffset, pvBuf, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToWrite % 512 == 0);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ if ( uOffset + cbToWrite > pImage->cbSize
+ || cbToWrite == 0)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ rc = RTFileWriteAt(pImage->File, uOffset, pvBuf, cbToWrite, NULL);
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnFlush */
+static int rawFlush(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ rc = rawFlushImage(pImage);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetVersion */
+static unsigned rawGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ Assert(pImage);
+
+ if (pImage)
+ return 1;
+ else
+ return 0;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetImageType */
+static int rawGetImageType(void *pBackendData, PVDIMAGETYPE penmImageType)
+{
+ LogFlowFunc(("pBackendData=%#p penmImageType=%#p\n", pBackendData, penmImageType));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ Assert(pImage);
+ Assert(penmImageType);
+
+ if (pImage)
+ *penmImageType = pImage->enmImageType;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc enmImageType=%u\n", rc, *penmImageType));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetSize */
+static uint64_t rawGetSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ Assert(pImage);
+
+ if (pImage)
+ return pImage->cbSize;
+ else
+ return 0;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetFileSize */
+static uint64_t rawGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ uint64_t cb = 0;
+
+ Assert(pImage);
+
+ if (pImage)
+ {
+ uint64_t cbFile;
+ if (pImage->File != NIL_RTFILE)
+ {
+ int rc = RTFileGetSize(pImage->File, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb += cbFile;
+ }
+ }
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetPCHSGeometry */
+static int rawGetPCHSGeometry(void *pBackendData,
+ PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ {
+ if (pImage->PCHSGeometry.cCylinders)
+ {
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetPCHSGeometry */
+static int rawSetPCHSGeometry(void *pBackendData,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetLCHSGeometry */
+static int rawGetLCHSGeometry(void *pBackendData,
+ PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ {
+ if (pImage->LCHSGeometry.cCylinders)
+ {
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetLCHSGeometry */
+static int rawSetLCHSGeometry(void *pBackendData,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetImageFlags */
+static unsigned rawGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ unsigned uImageFlags;
+
+ Assert(pImage);
+
+ if (pImage)
+ uImageFlags = pImage->uImageFlags;
+ else
+ uImageFlags = 0;
+
+ LogFlowFunc(("returns %#x\n", uImageFlags));
+ return uImageFlags;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetOpenFlags */
+static unsigned rawGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ unsigned uOpenFlags;
+
+ Assert(pImage);
+
+ if (pImage)
+ uOpenFlags = pImage->uOpenFlags;
+ else
+ uOpenFlags = 0;
+
+ LogFlowFunc(("returns %#x\n", uOpenFlags));
+ return uOpenFlags;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetOpenFlags */
+static int rawSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ /* Image must be opened and the new flags must be valid. Just readonly and
+ * info flags are supported. */
+ if (!pImage || (uOpenFlags & ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO)))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Implement this operation via reopening the image. */
+ rawFreeImage(pImage, false);
+ rc = rawOpenImage(pImage, uOpenFlags);
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetComment */
+static int rawGetComment(void *pBackendData, char *pszComment,
+ size_t cbComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ {
+ if (pszComment)
+ *pszComment = '\0';
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc comment='%s'\n", rc, pszComment));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetComment */
+static int rawSetComment(void *pBackendData, const char *pszComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ if (pImage)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetUuid */
+static int rawGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetUuid */
+static int rawSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ LogFlowFunc(("%RTuuid\n", pUuid));
+ Assert(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetModificationUuid */
+static int rawGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetModificationUuid */
+static int rawSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetParentUuid */
+static int rawGetParentUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetParentUuid */
+static int rawSetParentUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetParentModificationUuid */
+static int rawGetParentModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetParentModificationUuid */
+static int rawSetParentModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc;
+
+ Assert(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnDump */
+static void rawDump(void *pBackendData)
+{
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ Assert(pImage);
+ if (pImage)
+ {
+ RTLogPrintf("Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cbSector=%llu\n",
+ pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors,
+ pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors,
+ pImage->cbSize / 512);
+ }
+}
+
+static int rawGetTimeStamp(void *pvBackendData, PRTTIMESPEC pTimeStamp)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int rawGetParentTimeStamp(void *pvBackendData, PRTTIMESPEC pTimeStamp)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int rawSetParentTimeStamp(void *pvBackendData, PCRTTIMESPEC pTimeStamp)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int rawGetParentFilename(void *pvBackendData, char **ppszParentFilename)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int rawSetParentFilename(void *pvBackendData, const char *pszParentFilename)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static bool rawIsAsyncIOSupported(void *pvBackendData)
+{
+ return false;
+}
+
+static int rawAsyncRead(void *pvBackendData, uint64_t uOffset, size_t cbRead,
+ PPDMDATASEG paSeg, unsigned cSeg, void *pvUser)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static int rawAsyncWrite(void *pvBackendData, uint64_t uOffset, size_t cbWrite,
+ PPDMDATASEG paSeg, unsigned cSeg, void *pvUser)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+VBOXHDDBACKEND g_RawBackend =
+{
+ /* pszBackendName */
+ "raw",
+ /* cbSize */
+ sizeof(VBOXHDDBACKEND),
+ /* uBackendCaps */
+ VD_CAP_CREATE_FIXED | VD_CAP_FILE,
+ /* papszFileExtensions */
+ s_apszRawFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* hPlugin */
+ NIL_RTLDRMOD,
+ /* pfnCheckIfValid */
+ rawCheckIfValid,
+ /* pfnOpen */
+ rawOpen,
+ /* pfnCreate */
+ rawCreate,
+ /* pfnRename */
+ rawRename,
+ /* pfnClose */
+ rawClose,
+ /* pfnRead */
+ rawRead,
+ /* pfnWrite */
+ rawWrite,
+ /* pfnFlush */
+ rawFlush,
+ /* pfnGetVersion */
+ rawGetVersion,
+ /* pfnGetImageType */
+ rawGetImageType,
+ /* pfnGetSize */
+ rawGetSize,
+ /* pfnGetFileSize */
+ rawGetFileSize,
+ /* pfnGetPCHSGeometry */
+ rawGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ rawSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ rawGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ rawSetLCHSGeometry,
+ /* pfnGetImageFlags */
+ rawGetImageFlags,
+ /* pfnGetOpenFlags */
+ rawGetOpenFlags,
+ /* pfnSetOpenFlags */
+ rawSetOpenFlags,
+ /* pfnGetComment */
+ rawGetComment,
+ /* pfnSetComment */
+ rawSetComment,
+ /* pfnGetUuid */
+ rawGetUuid,
+ /* pfnSetUuid */
+ rawSetUuid,
+ /* pfnGetModificationUuid */
+ rawGetModificationUuid,
+ /* pfnSetModificationUuid */
+ rawSetModificationUuid,
+ /* pfnGetParentUuid */
+ rawGetParentUuid,
+ /* pfnSetParentUuid */
+ rawSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ rawGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ rawSetParentModificationUuid,
+ /* pfnDump */
+ rawDump,
+ /* pfnGetTimeStamp */
+ rawGetTimeStamp,
+ /* pfnGetParentTimeStamp */
+ rawGetParentTimeStamp,
+ /* pfnSetParentTimeStamp */
+ rawSetParentTimeStamp,
+ /* pfnGetParentFilename */
+ rawGetParentFilename,
+ /* pfnSetParentFilename */
+ rawSetParentFilename,
+ /* pfnIsAsyncIOSupported */
+ rawIsAsyncIOSupported,
+ /* pfnAsyncRead */
+ rawAsyncRead,
+ /* pfnAsyncWrite */
+ rawAsyncWrite,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName
+};
+
diff --git a/src/VBox/Devices/Storage/VBoxHDD-new.cpp b/src/VBox/Devices/Storage/VBoxHDD-new.cpp
new file mode 100644
index 000000000..4c39ba12d
--- /dev/null
+++ b/src/VBox/Devices/Storage/VBoxHDD-new.cpp
@@ -0,0 +1,3588 @@
+/* $Id: VBoxHDD-new.cpp 15591 2008-12-16 14:46:08Z vboxsync $ */
+/** @file
+ * VBoxHDD - VBox HDD Container implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2008 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD
+#include <VBox/VBoxHDD-new.h>
+#include <VBox/err.h>
+#include <VBox/sup.h>
+#include <VBox/log.h>
+
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include <iprt/ldr.h>
+#include <iprt/dir.h>
+#include <iprt/path.h>
+#include <iprt/param.h>
+
+#include "VBoxHDD-newInternal.h"
+
+
+#define VBOXHDDDISK_SIGNATURE 0x6f0e2a7d
+
+/** Buffer size used for merging images. */
+#define VD_MERGE_BUFFER_SIZE (16 * _1M)
+
+/**
+ * VBox HDD Container image descriptor.
+ */
+typedef struct VDIMAGE
+{
+ /** Link to parent image descriptor, if any. */
+ struct VDIMAGE *pPrev;
+ /** Link to child image descriptor, if any. */
+ struct VDIMAGE *pNext;
+ /** Container base filename. (UTF-8) */
+ char *pszFilename;
+ /** Data managed by the backend which keeps the actual info. */
+ void *pvBackendData;
+ /** Cached sanitized image type. */
+ VDIMAGETYPE enmImageType;
+ /** Image open flags (only those handled generically in this code and which
+ * the backends will never ever see). */
+ unsigned uOpenFlags;
+
+ /** Function pointers for the various backend methods. */
+ PCVBOXHDDBACKEND Backend;
+
+ /** Pointer to list of VD interfaces, per-image. */
+ PVDINTERFACE pVDIfsImage;
+} VDIMAGE, *PVDIMAGE;
+
+/**
+ * uModified bit flags.
+ */
+#define VD_IMAGE_MODIFIED_FLAG RT_BIT(0)
+#define VD_IMAGE_MODIFIED_FIRST RT_BIT(1)
+#define VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE RT_BIT(2)
+
+
+/**
+ * VBox HDD Container main structure, private part.
+ */
+struct VBOXHDD
+{
+ /** Structure signature (VBOXHDDDISK_SIGNATURE). */
+ uint32_t u32Signature;
+
+ /** Number of opened images. */
+ unsigned cImages;
+
+ /** Base image. */
+ PVDIMAGE pBase;
+
+ /** Last opened image in the chain.
+ * The same as pBase if only one image is used. */
+ PVDIMAGE pLast;
+
+ /** Flags representing the modification state. */
+ unsigned uModified;
+
+ /** Cached size of this disk. */
+ uint64_t cbSize;
+ /** Cached PCHS geometry for this disk. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** Cached LCHS geometry for this disk. */
+ PDMMEDIAGEOMETRY LCHSGeometry;
+
+ /** Pointer to list of VD interfaces, per-disk. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the common interface structure for error reporting. */
+ PVDINTERFACE pInterfaceError;
+ /** Pointer to the error interface we use if available. */
+ PVDINTERFACEERROR pInterfaceErrorCallbacks;
+};
+
+
+extern VBOXHDDBACKEND g_RawBackend;
+extern VBOXHDDBACKEND g_VmdkBackend;
+extern VBOXHDDBACKEND g_VDIBackend;
+extern VBOXHDDBACKEND g_VhdBackend;
+#ifdef VBOX_WITH_ISCSI
+extern VBOXHDDBACKEND g_ISCSIBackend;
+#endif
+
+static unsigned g_cBackends = 0;
+static PVBOXHDDBACKEND *g_apBackends = NULL;
+static PVBOXHDDBACKEND aStaticBackends[] =
+{
+ &g_RawBackend,
+ &g_VmdkBackend,
+ &g_VDIBackend,
+ &g_VhdBackend
+#ifdef VBOX_WITH_ISCSI
+ ,&g_ISCSIBackend
+#endif
+};
+
+/**
+ * internal: add several backends.
+ */
+static int vdAddBackends(PVBOXHDDBACKEND *ppBackends, unsigned cBackends)
+{
+ PVBOXHDDBACKEND *pTmp = (PVBOXHDDBACKEND*)RTMemRealloc(g_apBackends,
+ (g_cBackends + cBackends) * sizeof(PVBOXHDDBACKEND));
+ if (RT_UNLIKELY(!pTmp))
+ return VERR_NO_MEMORY;
+ g_apBackends = pTmp;
+ memcpy(&g_apBackends[g_cBackends], ppBackends, cBackends * sizeof(PVBOXHDDBACKEND));
+ g_cBackends += cBackends;
+ return VINF_SUCCESS;
+}
+
+/**
+ * internal: add single backend.
+ */
+DECLINLINE(int) vdAddBackend(PVBOXHDDBACKEND pBackend)
+{
+ return vdAddBackends(&pBackend, 1);
+}
+
+/**
+ * internal: issue error message.
+ */
+static int vdError(PVBOXHDD pDisk, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pDisk->pInterfaceErrorCallbacks)
+ pDisk->pInterfaceErrorCallbacks->pfnError(pDisk->pInterfaceError->pvUser, rc, RT_SRC_POS_ARGS, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * internal: find image format backend.
+ */
+static int vdFindBackend(const char *pszBackend, PCVBOXHDDBACKEND *ppBackend)
+{
+ int rc = VINF_SUCCESS;
+ PCVBOXHDDBACKEND pBackend = NULL;
+
+ if (!g_apBackends)
+ VDInit();
+
+ for (unsigned i = 0; i < g_cBackends; i++)
+ {
+ if (!RTStrICmp(pszBackend, g_apBackends[i]->pszBackendName))
+ {
+ pBackend = g_apBackends[i];
+ break;
+ }
+ }
+ *ppBackend = pBackend;
+ return rc;
+}
+
+/**
+ * internal: add image structure to the end of images list.
+ */
+static void vdAddImageToList(PVBOXHDD pDisk, PVDIMAGE pImage)
+{
+ pImage->pPrev = NULL;
+ pImage->pNext = NULL;
+
+ if (pDisk->pBase)
+ {
+ Assert(pDisk->cImages > 0);
+ pImage->pPrev = pDisk->pLast;
+ pDisk->pLast->pNext = pImage;
+ pDisk->pLast = pImage;
+ }
+ else
+ {
+ Assert(pDisk->cImages == 0);
+ pDisk->pBase = pImage;
+ pDisk->pLast = pImage;
+ }
+
+ pDisk->cImages++;
+}
+
+/**
+ * internal: remove image structure from the images list.
+ */
+static void vdRemoveImageFromList(PVBOXHDD pDisk, PVDIMAGE pImage)
+{
+ Assert(pDisk->cImages > 0);
+
+ if (pImage->pPrev)
+ pImage->pPrev->pNext = pImage->pNext;
+ else
+ pDisk->pBase = pImage->pNext;
+
+ if (pImage->pNext)
+ pImage->pNext->pPrev = pImage->pPrev;
+ else
+ pDisk->pLast = pImage->pPrev;
+
+ pImage->pPrev = NULL;
+ pImage->pNext = NULL;
+
+ pDisk->cImages--;
+}
+
+/**
+ * internal: find image by index into the images list.
+ */
+static PVDIMAGE vdGetImageByNumber(PVBOXHDD pDisk, unsigned nImage)
+{
+ PVDIMAGE pImage = pDisk->pBase;
+ if (nImage == VD_LAST_IMAGE)
+ return pDisk->pLast;
+ while (pImage && nImage)
+ {
+ pImage = pImage->pNext;
+ nImage--;
+ }
+ return pImage;
+}
+
+/**
+ * internal: read the specified amount of data in whatever blocks the backend
+ * will give us.
+ */
+static int vdReadHelper(PVBOXHDD pDisk, PVDIMAGE pImage, uint64_t uOffset,
+ void *pvBuf, size_t cbRead)
+{
+ int rc;
+ size_t cbThisRead;
+
+ /* Loop until all read. */
+ do
+ {
+ /* Search for image with allocated block. Do not attempt to read more
+ * than the previous reads marked as valid. Otherwise this would return
+ * stale data when different block sizes are used for the images. */
+ cbThisRead = cbRead;
+ rc = VERR_VD_BLOCK_FREE;
+ for (PVDIMAGE pCurrImage = pImage;
+ pCurrImage != NULL && rc == VERR_VD_BLOCK_FREE;
+ pCurrImage = pCurrImage->pPrev)
+ {
+ rc = pCurrImage->Backend->pfnRead(pCurrImage->pvBackendData,
+ uOffset, pvBuf, cbThisRead,
+ &cbThisRead);
+ }
+
+ /* No image in the chain contains the data for the block. */
+ if (rc == VERR_VD_BLOCK_FREE)
+ {
+ memset(pvBuf, '\0', cbThisRead);
+ rc = VINF_SUCCESS;
+ }
+
+ cbRead -= cbThisRead;
+ uOffset += cbThisRead;
+ pvBuf = (char *)pvBuf + cbThisRead;
+ } while (cbRead != 0 && RT_SUCCESS(rc));
+
+ return rc;
+}
+
+/**
+ * internal: mark the disk as not modified.
+ */
+static void vdResetModifiedFlag(PVBOXHDD pDisk)
+{
+ if (pDisk->uModified & VD_IMAGE_MODIFIED_FLAG)
+ {
+ /* generate new last-modified uuid */
+ if (!(pDisk->uModified & VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE))
+ {
+ RTUUID Uuid;
+
+ RTUuidCreate(&Uuid);
+ pDisk->pLast->Backend->pfnSetModificationUuid(pDisk->pLast->pvBackendData,
+ &Uuid);
+ }
+
+ pDisk->uModified &= ~VD_IMAGE_MODIFIED_FLAG;
+ }
+}
+
+/**
+ * internal: mark the disk as modified.
+ */
+static void vdSetModifiedFlag(PVBOXHDD pDisk)
+{
+ pDisk->uModified |= VD_IMAGE_MODIFIED_FLAG;
+ if (pDisk->uModified & VD_IMAGE_MODIFIED_FIRST)
+ {
+ pDisk->uModified &= ~VD_IMAGE_MODIFIED_FIRST;
+
+ /* First modify, so create a UUID and ensure it's written to disk. */
+ vdResetModifiedFlag(pDisk);
+
+ if (!(pDisk->uModified | VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE))
+ pDisk->pLast->Backend->pfnFlush(pDisk->pLast->pvBackendData);
+ }
+}
+
+/**
+ * internal: write a complete block (only used for diff images), taking the
+ * remaining data from parent images. This implementation does not optimize
+ * anything (except that it tries to read only that portions from parent
+ * images that are really needed).
+ */
+static int vdWriteHelperStandard(PVBOXHDD pDisk, PVDIMAGE pImage,
+ uint64_t uOffset, size_t cbWrite,
+ size_t cbThisWrite, size_t cbPreRead,
+ size_t cbPostRead, const void *pvBuf,
+ void *pvTmp)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Read the data that goes before the write to fill the block. */
+ if (cbPreRead)
+ {
+ rc = vdReadHelper(pDisk, pImage->pPrev, uOffset - cbPreRead, pvTmp,
+ cbPreRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /* Copy the data to the right place in the buffer. */
+ memcpy((char *)pvTmp + cbPreRead, pvBuf, cbThisWrite);
+
+ /* Read the data that goes after the write to fill the block. */
+ if (cbPostRead)
+ {
+ /* If we have data to be written, use that instead of reading
+ * data from the image. */
+ size_t cbWriteCopy;
+ if (cbWrite > cbThisWrite)
+ cbWriteCopy = RT_MIN(cbWrite - cbThisWrite, cbPostRead);
+ else
+ cbWriteCopy = 0;
+ /* Figure out how much we cannnot read from the image, because
+ * the last block to write might exceed the nominal size of the
+ * image for technical reasons. */
+ size_t cbFill;
+ if (uOffset + cbThisWrite + cbPostRead > pDisk->cbSize)
+ cbFill = uOffset + cbThisWrite + cbPostRead - pDisk->cbSize;
+ else
+ cbFill = 0;
+ /* The rest must be read from the image. */
+ size_t cbReadImage = cbPostRead - cbWriteCopy - cbFill;
+
+ /* Now assemble the remaining data. */
+ if (cbWriteCopy)
+ memcpy((char *)pvTmp + cbPreRead + cbThisWrite,
+ (char *)pvBuf + cbThisWrite, cbWriteCopy);
+ if (cbReadImage)
+ rc = vdReadHelper(pDisk, pImage->pPrev,
+ uOffset + cbThisWrite + cbWriteCopy,
+ (char *)pvTmp + cbPreRead + cbThisWrite + cbWriteCopy,
+ cbReadImage);
+ if (RT_FAILURE(rc))
+ return rc;
+ /* Zero out the remainder of this block. Will never be visible, as this
+ * is beyond the limit of the image. */
+ if (cbFill)
+ memset((char *)pvTmp + cbPreRead + cbThisWrite + cbWriteCopy + cbReadImage,
+ '\0', cbFill);
+ }
+
+ /* Write the full block to the virtual disk. */
+ rc = pImage->Backend->pfnWrite(pImage->pvBackendData,
+ uOffset - cbPreRead, pvTmp,
+ cbPreRead + cbThisWrite + cbPostRead,
+ NULL, &cbPreRead, &cbPostRead, 0);
+ Assert(rc != VERR_VD_BLOCK_FREE);
+ Assert(cbPreRead == 0);
+ Assert(cbPostRead == 0);
+
+ return rc;
+}
+
+/**
+ * internal: write a complete block (only used for diff images), taking the
+ * remaining data from parent images. This implementation optimizes out writes
+ * that do not change the data relative to the state as of the parent images.
+ * All backends which support differential/growing images support this.
+ */
+static int vdWriteHelperOptimized(PVBOXHDD pDisk, PVDIMAGE pImage,
+ uint64_t uOffset, size_t cbWrite,
+ size_t cbThisWrite, size_t cbPreRead,
+ size_t cbPostRead, const void *pvBuf,
+ void *pvTmp)
+{
+ size_t cbFill = 0;
+ size_t cbWriteCopy = 0;
+ size_t cbReadImage = 0;
+ int rc;
+
+ if (cbPostRead)
+ {
+ /* Figure out how much we cannnot read from the image, because
+ * the last block to write might exceed the nominal size of the
+ * image for technical reasons. */
+ if (uOffset + cbThisWrite + cbPostRead > pDisk->cbSize)
+ cbFill = uOffset + cbThisWrite + cbPostRead - pDisk->cbSize;
+
+ /* If we have data to be written, use that instead of reading
+ * data from the image. */
+ if (cbWrite > cbThisWrite)
+ cbWriteCopy = RT_MIN(cbWrite - cbThisWrite, cbPostRead);
+
+ /* The rest must be read from the image. */
+ cbReadImage = cbPostRead - cbWriteCopy - cbFill;
+ }
+
+ /* Read the entire data of the block so that we can compare whether it will
+ * be modified by the write or not. */
+ rc = vdReadHelper(pDisk, pImage->pPrev, uOffset - cbPreRead, pvTmp,
+ cbPreRead + cbThisWrite + cbPostRead - cbFill);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Check if the write would modify anything in this block. */
+ if ( !memcmp((char *)pvTmp + cbPreRead, pvBuf, cbThisWrite)
+ && (!cbWriteCopy || !memcmp((char *)pvTmp + cbPreRead + cbThisWrite,
+ (char *)pvBuf + cbThisWrite, cbWriteCopy)))
+ {
+ /* Block is completely unchanged, so no need to write anything. */
+ return VINF_SUCCESS;
+ }
+
+ /* Copy the data to the right place in the buffer. */
+ memcpy((char *)pvTmp + cbPreRead, pvBuf, cbThisWrite);
+
+ /* Handle the data that goes after the write to fill the block. */
+ if (cbPostRead)
+ {
+ /* Now assemble the remaining data. */
+ if (cbWriteCopy)
+ memcpy((char *)pvTmp + cbPreRead + cbThisWrite,
+ (char *)pvBuf + cbThisWrite, cbWriteCopy);
+ /* Zero out the remainder of this block. Will never be visible, as this
+ * is beyond the limit of the image. */
+ if (cbFill)
+ memset((char *)pvTmp + cbPreRead + cbThisWrite + cbWriteCopy + cbReadImage,
+ '\0', cbFill);
+ }
+
+ /* Write the full block to the virtual disk. */
+ rc = pImage->Backend->pfnWrite(pImage->pvBackendData,
+ uOffset - cbPreRead, pvTmp,
+ cbPreRead + cbThisWrite + cbPostRead,
+ NULL, &cbPreRead, &cbPostRead, 0);
+ Assert(rc != VERR_VD_BLOCK_FREE);
+ Assert(cbPreRead == 0);
+ Assert(cbPostRead == 0);
+
+ return rc;
+}
+
+/**
+ * internal: write buffer to the image, taking care of block boundaries and
+ * write optimizations.
+ */
+static int vdWriteHelper(PVBOXHDD pDisk, PVDIMAGE pImage, uint64_t uOffset,
+ const void *pvBuf, size_t cbWrite)
+{
+ int rc;
+ unsigned fWrite;
+ size_t cbThisWrite;
+ size_t cbPreRead, cbPostRead;
+
+ /* Loop until all written. */
+ do
+ {
+ /* Try to write the possibly partial block to the last opened image.
+ * This works when the block is already allocated in this image or
+ * if it is a full-block write (and allocation isn't suppressed below).
+ * For image formats which don't support zero blocks, it's beneficial
+ * to avoid unnecessarily allocating unchanged blocks. This prevents
+ * unwanted expanding of images. VMDK is an example. */
+ cbThisWrite = cbWrite;
+ fWrite = (pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME)
+ ? 0 : VD_WRITE_NO_ALLOC;
+ rc = pImage->Backend->pfnWrite(pImage->pvBackendData, uOffset, pvBuf,
+ cbThisWrite, &cbThisWrite, &cbPreRead,
+ &cbPostRead, fWrite);
+ if (rc == VERR_VD_BLOCK_FREE)
+ {
+ void *pvTmp = RTMemTmpAlloc(cbPreRead + cbThisWrite + cbPostRead);
+ AssertBreakStmt(VALID_PTR(pvTmp), rc = VERR_NO_MEMORY);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME))
+ {
+ /* Optimized write, suppress writing to a so far unallocated
+ * block if the data is in fact not changed. */
+ rc = vdWriteHelperOptimized(pDisk, pImage, uOffset, cbWrite,
+ cbThisWrite, cbPreRead, cbPostRead,
+ pvBuf, pvTmp);
+ }
+ else
+ {
+ /* Normal write, not optimized in any way. The block will
+ * be written no matter what. This will usually (unless the
+ * backend has some further optimization enabled) cause the
+ * block to be allocated. */
+ rc = vdWriteHelperStandard(pDisk, pImage, uOffset, cbWrite,
+ cbThisWrite, cbPreRead, cbPostRead,
+ pvBuf, pvTmp);
+ }
+ RTMemTmpFree(pvTmp);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ cbWrite -= cbThisWrite;
+ uOffset += cbThisWrite;
+ pvBuf = (char *)pvBuf + cbThisWrite;
+ } while (cbWrite != 0 && RT_SUCCESS(rc));
+
+ return rc;
+}
+
+
+/**
+ * internal: scans plugin directory and loads the backends have been found.
+ */
+static int vdLoadDynamicBackends()
+{
+ int rc = VINF_SUCCESS;
+ PRTDIR pPluginDir = NULL;
+
+ /* Enumerate plugin backends. */
+ char szPath[RTPATH_MAX];
+ rc = RTPathAppPrivateArch(szPath, sizeof(szPath));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* To get all entries with VBoxHDD as prefix. */
+ char *pszPluginFilter;
+ rc = RTStrAPrintf(&pszPluginFilter, "%s/%s*", szPath,
+ VBOX_HDDFORMAT_PLUGIN_PREFIX);
+ if (RT_FAILURE(rc))
+ {
+ rc = VERR_NO_MEMORY;
+ return rc;
+ }
+
+ PRTDIRENTRYEX pPluginDirEntry = NULL;
+ size_t cbPluginDirEntry = sizeof(RTDIRENTRYEX);
+ /* The plugins are in the same directory as the other shared libs. */
+ rc = RTDirOpenFiltered(&pPluginDir, pszPluginFilter, RTDIRFILTER_WINNT);
+ if (RT_FAILURE(rc))
+ {
+ /* On Windows the above immediately signals that there are no
+ * files matching, while on other platforms enumerating the
+ * files below fails. Either way: no plugins. */
+ goto out;
+ }
+
+ pPluginDirEntry = (PRTDIRENTRYEX)RTMemAllocZ(sizeof(RTDIRENTRYEX));
+ if (!pPluginDirEntry)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ while ((rc = RTDirReadEx(pPluginDir, pPluginDirEntry, &cbPluginDirEntry, RTFSOBJATTRADD_NOTHING)) != VERR_NO_MORE_FILES)
+ {
+ RTLDRMOD hPlugin = NIL_RTLDRMOD;
+ PFNVBOXHDDFORMATLOAD pfnHDDFormatLoad = NULL;
+ PVBOXHDDBACKEND pBackend = NULL;
+ char *pszPluginPath = NULL;
+
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* allocate new buffer. */
+ RTMemFree(pPluginDirEntry);
+ pPluginDirEntry = (PRTDIRENTRYEX)RTMemAllocZ(cbPluginDirEntry);
+ /* Retry. */
+ rc = RTDirReadEx(pPluginDir, pPluginDirEntry, &cbPluginDirEntry, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (RT_FAILURE(rc))
+ break;
+
+ /* We got the new entry. */
+ if (!RTFS_IS_FILE(pPluginDirEntry->Info.Attr.fMode))
+ continue;
+
+ /* Prepend the path to the libraries. */
+ rc = RTStrAPrintf(&pszPluginPath, "%s/%s", szPath, pPluginDirEntry->szName);
+ if (RT_FAILURE(rc))
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc = SUPR3HardenedLdrLoad(pszPluginPath, &hPlugin);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hPlugin, VBOX_HDDFORMAT_LOAD_NAME, (void**)&pfnHDDFormatLoad);
+ if (RT_FAILURE(rc) || !pfnHDDFormatLoad)
+ {
+ LogFunc(("error resolving the entry point %s in plugin %s, rc=%Rrc, pfnHDDFormat=%#p\n", VBOX_HDDFORMAT_LOAD_NAME, pPluginDirEntry->szName, rc, pfnHDDFormatLoad));
+ if (RT_SUCCESS(rc))
+ rc = VERR_SYMBOL_NOT_FOUND;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Get the function table. */
+ rc = pfnHDDFormatLoad(&pBackend);
+ if (RT_SUCCESS(rc) && pBackend->cbSize == sizeof(VBOXHDDBACKEND))
+ {
+ pBackend->hPlugin = hPlugin;
+ vdAddBackend(pBackend);
+ }
+ else
+ LogFunc(("ignored plugin '%s': pBackend->cbSize=%d rc=%Rrc\n", pszPluginPath, pBackend->cbSize, rc));
+ }
+ else
+ LogFunc(("ignored plugin '%s': rc=%Rrc\n", pszPluginPath, rc));
+
+ if (RT_FAILURE(rc))
+ RTLdrClose(hPlugin);
+ }
+ RTStrFree(pszPluginPath);
+ }
+out:
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ RTStrFree(pszPluginFilter);
+ if (pPluginDirEntry)
+ RTMemFree(pPluginDirEntry);
+ if (pPluginDir)
+ RTDirClose(pPluginDir);
+ return rc;
+}
+
+/**
+ * Initializes HDD backends.
+ *
+ * @returns VBox status code.
+ */
+VBOXDDU_DECL(int) VDInit()
+{
+ int rc = vdAddBackends(aStaticBackends, RT_ELEMENTS(aStaticBackends));
+ if (RT_SUCCESS(rc))
+ rc = vdLoadDynamicBackends();
+ LogRel(("VDInit finished\n"));
+ return rc;
+}
+
+/**
+ * Destroys loaded HDD backends.
+ *
+ * @returns VBox status code.
+ */
+VBOXDDU_DECL(int) VDShutdown()
+{
+ PVBOXHDDBACKEND *pBackends = g_apBackends;
+ unsigned cBackends = g_cBackends;
+
+ if (!pBackends)
+ return VERR_INTERNAL_ERROR;
+
+ g_cBackends = 0;
+ g_apBackends = NULL;
+
+ for (unsigned i = 0; i < cBackends; i++)
+ if (pBackends[i]->hPlugin != NIL_RTLDRMOD)
+ RTLdrClose(pBackends[i]->hPlugin);
+
+ RTMemFree(pBackends);
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Lists all HDD backends and their capabilities in a caller-provided buffer.
+ *
+ * @todo this code contains memory leaks, inconsistent (and probably buggy)
+ * allocation, and it lacks documentation what the caller needs to free.
+ *
+ * @returns VBox status code.
+ * VERR_BUFFER_OVERFLOW if not enough space is passed.
+ * @param cEntriesAlloc Number of list entries available.
+ * @param pEntries Pointer to array for the entries.
+ * @param pcEntriesUsed Number of entries returned.
+ */
+VBOXDDU_DECL(int) VDBackendInfo(unsigned cEntriesAlloc, PVDBACKENDINFO pEntries,
+ unsigned *pcEntriesUsed)
+{
+ int rc = VINF_SUCCESS;
+ PRTDIR pPluginDir = NULL;
+ unsigned cEntries = 0;
+
+ LogFlowFunc(("cEntriesAlloc=%u pEntries=%#p pcEntriesUsed=%#p\n", cEntriesAlloc, pEntries, pcEntriesUsed));
+ /* Check arguments. */
+ AssertMsgReturn(cEntriesAlloc,
+ ("cEntriesAlloc=%u\n", cEntriesAlloc),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(VALID_PTR(pEntries),
+ ("pEntries=%#p\n", pEntries),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(VALID_PTR(pcEntriesUsed),
+ ("pcEntriesUsed=%#p\n", pcEntriesUsed),
+ VERR_INVALID_PARAMETER);
+ if (!g_apBackends)
+ VDInit();
+
+ if (cEntriesAlloc < g_cBackends)
+ {
+ *pcEntriesUsed = g_cBackends;
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ for (unsigned i = 0; i < g_cBackends; i++)
+ {
+ pEntries[i].pszBackend = g_apBackends[i]->pszBackendName;
+ pEntries[i].uBackendCaps = g_apBackends[i]->uBackendCaps;
+ pEntries[i].papszFileExtensions = g_apBackends[i]->papszFileExtensions;
+ pEntries[i].paConfigInfo = g_apBackends[i]->paConfigInfo;
+ pEntries[i].pfnComposeLocation = g_apBackends[i]->pfnComposeLocation;
+ pEntries[i].pfnComposeName = g_apBackends[i]->pfnComposeName;
+ }
+
+ LogFlowFunc(("returns %Rrc *pcEntriesUsed=%u\n", rc, cEntries));
+ *pcEntriesUsed = g_cBackends;
+ return rc;
+}
+
+/**
+ * Lists the capablities of a backend indentified by its name.
+ * Free all returned names with RTStrFree() when you no longer need them.
+ *
+ * @returns VBox status code.
+ * @param pszBackend The backend name.
+ * @param pEntries Pointer to an entry.
+ */
+VBOXDDU_DECL(int) VDBackendInfoOne(const char *pszBackend, PVDBACKENDINFO pEntry)
+{
+ LogFlowFunc(("pszBackend=%#p pEntry=%#p\n", pszBackend, pEntry));
+ /* Check arguments. */
+ AssertMsgReturn(VALID_PTR(pszBackend),
+ ("pszBackend=%#p\n", pszBackend),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(VALID_PTR(pEntry),
+ ("pEntry=%#p\n", pEntry),
+ VERR_INVALID_PARAMETER);
+ if (!g_apBackends)
+ VDInit();
+
+ /* Go through loaded backends. */
+ for (unsigned i = 0; i < g_cBackends; i++)
+ {
+ if (!RTStrICmp(pszBackend, g_apBackends[i]->pszBackendName))
+ {
+ pEntry->pszBackend = g_apBackends[i]->pszBackendName;
+ pEntry->uBackendCaps = g_apBackends[i]->uBackendCaps;
+ pEntry->papszFileExtensions = g_apBackends[i]->papszFileExtensions;
+ pEntry->paConfigInfo = g_apBackends[i]->paConfigInfo;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Allocates and initializes an empty HDD container.
+ * No image files are opened.
+ *
+ * @returns VBox status code.
+ * @param pVDIfsDisk Pointer to the per-disk VD interface list.
+ * @param ppDisk Where to store the reference to HDD container.
+ */
+VBOXDDU_DECL(int) VDCreate(PVDINTERFACE pVDIfsDisk, PVBOXHDD *ppDisk)
+{
+ int rc = VINF_SUCCESS;
+ PVBOXHDD pDisk = NULL;
+
+ LogFlowFunc(("pVDIfsDisk=%#p\n", pVDIfsDisk));
+ do
+ {
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(ppDisk),
+ ("ppDisk=%#p\n", ppDisk),
+ rc = VERR_INVALID_PARAMETER);
+
+ pDisk = (PVBOXHDD)RTMemAllocZ(sizeof(VBOXHDD));
+ if (pDisk)
+ {
+ pDisk->u32Signature = VBOXHDDDISK_SIGNATURE;
+ pDisk->cImages = 0;
+ pDisk->pBase = NULL;
+ pDisk->pLast = NULL;
+ pDisk->cbSize = 0;
+ pDisk->PCHSGeometry.cCylinders = 0;
+ pDisk->PCHSGeometry.cHeads = 0;
+ pDisk->PCHSGeometry.cSectors = 0;
+ pDisk->LCHSGeometry.cCylinders = 0;
+ pDisk->LCHSGeometry.cHeads = 0;
+ pDisk->LCHSGeometry.cSectors = 0;
+ pDisk->pVDIfsDisk = pVDIfsDisk;
+ pDisk->pInterfaceError = NULL;
+ pDisk->pInterfaceErrorCallbacks = NULL;
+
+ pDisk->pInterfaceError = VDInterfaceGet(pVDIfsDisk, VDINTERFACETYPE_ERROR);
+ if (pDisk->pInterfaceError)
+ pDisk->pInterfaceErrorCallbacks = VDGetInterfaceError(pDisk->pInterfaceError);
+ *ppDisk = pDisk;
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc (pDisk=%#p)\n", rc, pDisk));
+ return rc;
+}
+
+/**
+ * Destroys HDD container.
+ * If container has opened image files they will be closed.
+ *
+ * @param pDisk Pointer to HDD container.
+ */
+VBOXDDU_DECL(void) VDDestroy(PVBOXHDD pDisk)
+{
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreak(pDisk);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+ VDCloseAll(pDisk);
+ RTMemFree(pDisk);
+ } while (0);
+ LogFlowFunc(("returns\n"));
+}
+
+/**
+ * Try to get the backend name which can use this image.
+ *
+ * @returns VBox status code.
+ * VINF_SUCCESS if a plugin was found.
+ * ppszFormat contains the string which can be used as backend name.
+ * VERR_NOT_SUPPORTED if no backend was found.
+ * @param pszFilename Name of the image file for which the backend is queried.
+ * @param ppszFormat Receives pointer of the UTF-8 string which contains the format name.
+ * The returned pointer must be freed using RTStrFree().
+ */
+VBOXDDU_DECL(int) VDGetFormat(const char *pszFilename, char **ppszFormat)
+{
+ int rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename));
+ /* Check arguments. */
+ AssertMsgReturn(VALID_PTR(pszFilename) && *pszFilename,
+ ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(VALID_PTR(ppszFormat),
+ ("ppszFormat=%#p\n", ppszFormat),
+ VERR_INVALID_PARAMETER);
+
+ if (!g_apBackends)
+ VDInit();
+
+ /* Find the backend supporting this file format. */
+ for (unsigned i = 0; i < g_cBackends; i++)
+ {
+ if (g_apBackends[i]->pfnCheckIfValid)
+ {
+ rc = g_apBackends[i]->pfnCheckIfValid(pszFilename);
+ if ( RT_SUCCESS(rc)
+ /* The correct backend has been found, but there is a small
+ * incompatibility so that the file cannot be used. Stop here
+ * and signal success - the actual open will of course fail,
+ * but that will create a really sensible error message. */
+ || ( rc != VERR_VD_GEN_INVALID_HEADER
+ && rc != VERR_VD_VDI_INVALID_HEADER
+ && rc != VERR_VD_VMDK_INVALID_HEADER
+ && rc != VERR_VD_ISCSI_INVALID_HEADER
+ && rc != VERR_VD_VHD_INVALID_HEADER
+ && rc != VERR_VD_RAW_INVALID_HEADER))
+ {
+ /* Copy the name into the new string. */
+ char *pszFormat = RTStrDup(g_apBackends[i]->pszBackendName);
+ if (!pszFormat)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ *ppszFormat = pszFormat;
+ rc = VINF_SUCCESS;
+ break;
+ }
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc *ppszFormat=\"%s\"\n", rc, *ppszFormat));
+ return rc;
+}
+
+/**
+ * Opens an image file.
+ *
+ * The first opened image file in HDD container must have a base image type,
+ * others (next opened images) must be a differencing or undo images.
+ * Linkage is checked for differencing image to be in consistence with the previously opened image.
+ * When another differencing image is opened and the last image was opened in read/write access
+ * mode, then the last image is reopened in read-only with deny write sharing mode. This allows
+ * other processes to use images in read-only mode too.
+ *
+ * Note that the image is opened in read-only mode if a read/write open is not possible.
+ * Use VDIsReadOnly to check open mode.
+ *
+ * @returns VBox status code.
+ * @param pDisk Pointer to HDD container.
+ * @param pszBackend Name of the image file backend to use.
+ * @param pszFilename Name of the image file to open.
+ * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants.
+ * @param pVDIfsImage Pointer to the per-image VD interface list.
+ */
+VBOXDDU_DECL(int) VDOpen(PVBOXHDD pDisk, const char *pszBackend,
+ const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsImage)
+{
+ int rc = VINF_SUCCESS;
+ PVDIMAGE pImage = NULL;
+
+ LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" uOpenFlags=%#x, pVDIfsImage=%#p\n",
+ pDisk, pszBackend, pszFilename, uOpenFlags, pVDIfsImage));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pszBackend) && *pszBackend,
+ ("pszBackend=%#p \"%s\"\n", pszBackend, pszBackend),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename,
+ ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0,
+ ("uOpenFlags=%#x\n", uOpenFlags),
+ rc = VERR_INVALID_PARAMETER);
+
+ /* Set up image descriptor. */
+ pImage = (PVDIMAGE)RTMemAllocZ(sizeof(VDIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pImage->pszFilename = RTStrDup(pszFilename);
+ if (!pImage->pszFilename)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vdFindBackend(pszBackend, &pImage->Backend);
+ if (RT_FAILURE(rc))
+ break;
+ if (!pImage->Backend)
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: unknown backend name '%s'"), pszBackend);
+ break;
+ }
+
+ pImage->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME;
+ rc = pImage->Backend->pfnOpen(pImage->pszFilename,
+ uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME,
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ &pImage->pvBackendData);
+ /* If the open in read-write mode failed, retry in read-only mode. */
+ if (RT_FAILURE(rc))
+ {
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && (rc == VERR_ACCESS_DENIED
+ || rc == VERR_PERMISSION_DENIED
+ || rc == VERR_WRITE_PROTECT
+ || rc == VERR_SHARING_VIOLATION
+ || rc == VERR_FILE_LOCK_FAILED))
+ rc = pImage->Backend->pfnOpen(pImage->pszFilename,
+ (uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME)
+ | VD_OPEN_FLAGS_READONLY,
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ &pImage->pvBackendData);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdError(pDisk, rc, RT_SRC_POS,
+ N_("VD: error opening image file '%s'"), pszFilename);
+ break;
+ }
+ }
+
+ VDIMAGETYPE enmImageType;
+ rc = pImage->Backend->pfnGetImageType(pImage->pvBackendData,
+ &enmImageType);
+ /* Check image type. As the image itself has only partial knowledge
+ * whether it's a base image or not, this info is derived here. The
+ * base image can be fixed or normal, all others must be normal or
+ * diff images. Some image formats don't distinguish between normal
+ * and diff images, so this must be corrected here. */
+ if (RT_FAILURE(rc))
+ enmImageType = VD_IMAGE_TYPE_INVALID;
+ if ( RT_SUCCESS(rc)
+ && !(uOpenFlags & VD_OPEN_FLAGS_INFO))
+ {
+ if ( pDisk->cImages == 0
+ && enmImageType != VD_IMAGE_TYPE_FIXED
+ && enmImageType != VD_IMAGE_TYPE_NORMAL)
+ {
+ rc = VERR_VD_INVALID_TYPE;
+ break;
+ }
+ else if (pDisk->cImages != 0)
+ {
+ if ( enmImageType != VD_IMAGE_TYPE_NORMAL
+ && enmImageType != VD_IMAGE_TYPE_DIFF)
+ {
+ rc = VERR_VD_INVALID_TYPE;
+ break;
+ }
+ else
+ enmImageType = VD_IMAGE_TYPE_DIFF;
+ }
+ }
+ pImage->enmImageType = enmImageType;
+
+ /* Force sane optimization settings. It's not worth avoiding writes
+ * to fixed size images. The overhead would have almost no payback. */
+ if (enmImageType == VD_IMAGE_TYPE_FIXED)
+ pImage->uOpenFlags |= VD_OPEN_FLAGS_HONOR_SAME;
+
+ /** @todo optionally check UUIDs */
+
+ int rc2;
+
+ /* Cache disk information. */
+ pDisk->cbSize = pImage->Backend->pfnGetSize(pImage->pvBackendData);
+
+ /* Cache PCHS geometry. */
+ rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pvBackendData,
+ &pDisk->PCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->PCHSGeometry.cCylinders = 0;
+ pDisk->PCHSGeometry.cHeads = 0;
+ pDisk->PCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the PCHS geometry is properly clipped. */
+ pDisk->PCHSGeometry.cCylinders = RT_MIN(pDisk->PCHSGeometry.cCylinders, 16383);
+ pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 16);
+ pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63);
+ }
+
+ /* Cache LCHS geometry. */
+ rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pvBackendData,
+ &pDisk->LCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->LCHSGeometry.cCylinders = 0;
+ pDisk->LCHSGeometry.cHeads = 0;
+ pDisk->LCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the LCHS geometry is properly clipped. */
+ pDisk->LCHSGeometry.cCylinders = RT_MIN(pDisk->LCHSGeometry.cCylinders, 1024);
+ pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255);
+ pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63);
+ }
+
+ if (pDisk->cImages != 0)
+ {
+ /* Switch previous image to read-only mode. */
+ unsigned uOpenFlagsPrevImg;
+ uOpenFlagsPrevImg = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pvBackendData);
+ if (!(uOpenFlagsPrevImg & VD_OPEN_FLAGS_READONLY))
+ {
+ uOpenFlagsPrevImg |= VD_OPEN_FLAGS_READONLY;
+ rc = pDisk->pLast->Backend->pfnSetOpenFlags(pDisk->pLast->pvBackendData, uOpenFlagsPrevImg);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Image successfully opened, make it the last image. */
+ vdAddImageToList(pDisk, pImage);
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ pDisk->uModified = VD_IMAGE_MODIFIED_FIRST;
+ }
+ else
+ {
+ /* Error detected, but image opened. Close image. */
+ int rc2;
+ rc2 = pImage->Backend->pfnClose(pImage->pvBackendData, false);
+ AssertRC(rc2);
+ pImage->pvBackendData = NULL;
+ }
+ } while (0);
+
+ if (RT_FAILURE(rc))
+ {
+ if (pImage)
+ {
+ if (pImage->pszFilename)
+ RTStrFree(pImage->pszFilename);
+ RTMemFree(pImage);
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Creates and opens a new base image file.
+ *
+ * @returns VBox status code.
+ * @param pDisk Pointer to HDD container.
+ * @param pszBackend Name of the image file backend to use.
+ * @param pszFilename Name of the image file to create.
+ * @param enmType Image type, only base image types are acceptable.
+ * @param cbSize Image size in bytes.
+ * @param uImageFlags Flags specifying special image features.
+ * @param pszComment Pointer to image comment. NULL is ok.
+ * @param pPCHSGeometry Pointer to physical disk geometry <= (16383,16,63). Not NULL.
+ * @param pLCHSGeometry Pointer to logical disk geometry <= (1024,255,63). Not NULL.
+ * @param pUuid New UUID of the image. If NULL, a new UUID is created.
+ * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants.
+ * @param pVDIfsImage Pointer to the per-image VD interface list.
+ * @param pVDIfsOperation Pointer to the per-operation VD interface list.
+ */
+VBOXDDU_DECL(int) VDCreateBase(PVBOXHDD pDisk, const char *pszBackend,
+ const char *pszFilename, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ const char *pszComment,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation)
+{
+ int rc = VINF_SUCCESS;
+ PVDIMAGE pImage = NULL;
+ RTUUID uuid;
+
+ LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" enmType=%#x cbSize=%llu uImageFlags=%#x pszComment=\"%s\" PCHS=%u/%u/%u LCHS=%u/%u/%u Uuid=%RTuuid uOpenFlags=%#x pVDIfsImage=%#p pVDIfsOperation=%#p\n",
+ pDisk, pszBackend, pszFilename, enmType, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads,
+ pPCHSGeometry->cSectors, pLCHSGeometry->cCylinders,
+ pLCHSGeometry->cHeads, pLCHSGeometry->cSectors, pUuid,
+ uOpenFlags, pVDIfsImage, pVDIfsOperation));
+
+ PVDINTERFACE pIfProgress = VDInterfaceGet(pVDIfsOperation,
+ VDINTERFACETYPE_PROGRESS);
+ PVDINTERFACEPROGRESS pCbProgress = NULL;
+ if (pIfProgress)
+ pCbProgress = VDGetInterfaceProgress(pIfProgress);
+
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pszBackend) && *pszBackend,
+ ("pszBackend=%#p \"%s\"\n", pszBackend, pszBackend),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename,
+ ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(enmType == VD_IMAGE_TYPE_NORMAL || enmType == VD_IMAGE_TYPE_FIXED,
+ ("enmType=%#x\n", enmType),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(cbSize,
+ ("cbSize=%llu\n", cbSize),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt((uImageFlags & ~VD_IMAGE_FLAGS_MASK) == 0,
+ ("uImageFlags=%#x\n", uImageFlags),
+ rc = VERR_INVALID_PARAMETER);
+ /* The PCHS geometry fields may be 0 to leave it for later. */
+ AssertMsgBreakStmt( VALID_PTR(pPCHSGeometry)
+ && pPCHSGeometry->cCylinders <= 16383
+ && pPCHSGeometry->cHeads <= 16
+ && pPCHSGeometry->cSectors <= 63,
+ ("pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pPCHSGeometry,
+ pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads,
+ pPCHSGeometry->cSectors),
+ rc = VERR_INVALID_PARAMETER);
+ /* The LCHS geometry fields may be 0 to leave it to later autodetection. */
+ AssertMsgBreakStmt( VALID_PTR(pLCHSGeometry)
+ && pLCHSGeometry->cCylinders <= 1024
+ && pLCHSGeometry->cHeads <= 255
+ && pLCHSGeometry->cSectors <= 63,
+ ("pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pLCHSGeometry,
+ pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads,
+ pLCHSGeometry->cSectors),
+ rc = VERR_INVALID_PARAMETER);
+ /* The UUID may be NULL. */
+ AssertMsgBreakStmt(pUuid == NULL || VALID_PTR(pUuid),
+ ("pUuid=%#p UUID=%RTuuid\n", pUuid, pUuid),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0,
+ ("uOpenFlags=%#x\n", uOpenFlags),
+ rc = VERR_INVALID_PARAMETER);
+
+ /* Check state. */
+ AssertMsgBreakStmt(pDisk->cImages == 0,
+ ("Create base image cannot be done with other images open\n"),
+ rc = VERR_VD_INVALID_STATE);
+
+ /* Set up image descriptor. */
+ pImage = (PVDIMAGE)RTMemAllocZ(sizeof(VDIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pImage->pszFilename = RTStrDup(pszFilename);
+ if (!pImage->pszFilename)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vdFindBackend(pszBackend, &pImage->Backend);
+ if (RT_FAILURE(rc))
+ break;
+ if (!pImage->Backend)
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: unknown backend name '%s'"), pszBackend);
+ break;
+ }
+
+ /* Create UUID if the caller didn't specify one. */
+ if (!pUuid)
+ {
+ rc = RTUuidCreate(&uuid);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdError(pDisk, rc, RT_SRC_POS,
+ N_("VD: cannot generate UUID for image '%s'"),
+ pszFilename);
+ break;
+ }
+ pUuid = &uuid;
+ }
+
+ pImage->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME;
+ rc = pImage->Backend->pfnCreate(pImage->pszFilename, enmType, cbSize,
+ uImageFlags, pszComment, pPCHSGeometry,
+ pLCHSGeometry, pUuid,
+ uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME,
+ 0, 99,
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ pVDIfsOperation,
+ &pImage->pvBackendData);
+
+ if (RT_SUCCESS(rc))
+ {
+ pImage->enmImageType = enmType;
+
+ /* Force sane optimization settings. It's not worth avoiding writes
+ * to fixed size images. The overhead would have almost no payback. */
+ if (enmType == VD_IMAGE_TYPE_FIXED)
+ pImage->uOpenFlags |= VD_OPEN_FLAGS_HONOR_SAME;
+
+ /** @todo optionally check UUIDs */
+
+ int rc2;
+
+ /* Cache disk information. */
+ pDisk->cbSize = pImage->Backend->pfnGetSize(pImage->pvBackendData);
+
+ /* Cache PCHS geometry. */
+ rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pvBackendData,
+ &pDisk->PCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->PCHSGeometry.cCylinders = 0;
+ pDisk->PCHSGeometry.cHeads = 0;
+ pDisk->PCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the CHS geometry is properly clipped. */
+ pDisk->PCHSGeometry.cCylinders = RT_MIN(pDisk->PCHSGeometry.cCylinders, 16383);
+ pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 16);
+ pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63);
+ }
+
+ /* Cache LCHS geometry. */
+ rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pvBackendData,
+ &pDisk->LCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->LCHSGeometry.cCylinders = 0;
+ pDisk->LCHSGeometry.cHeads = 0;
+ pDisk->LCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the CHS geometry is properly clipped. */
+ pDisk->LCHSGeometry.cCylinders = RT_MIN(pDisk->LCHSGeometry.cCylinders, 1024);
+ pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255);
+ pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Image successfully opened, make it the last image. */
+ vdAddImageToList(pDisk, pImage);
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ pDisk->uModified = VD_IMAGE_MODIFIED_FIRST;
+ }
+ else
+ {
+ /* Error detected, but image opened. Close and delete image. */
+ int rc2;
+ rc2 = pImage->Backend->pfnClose(pImage->pvBackendData, true);
+ AssertRC(rc2);
+ pImage->pvBackendData = NULL;
+ }
+ } while (0);
+
+ if (RT_FAILURE(rc))
+ {
+ if (pImage)
+ {
+ if (pImage->pszFilename)
+ RTStrFree(pImage->pszFilename);
+ RTMemFree(pImage);
+ }
+ }
+
+ if (RT_SUCCESS(rc) && pCbProgress && pCbProgress->pfnProgress)
+ pCbProgress->pfnProgress(NULL /* WARNING! pVM=NULL */, 100,
+ pIfProgress->pvUser);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Creates and opens a new differencing image file in HDD container.
+ * See comments for VDOpen function about differencing images.
+ *
+ * @returns VBox status code.
+ * @param pDisk Pointer to HDD container.
+ * @param pszBackend Name of the image file backend to use.
+ * @param pszFilename Name of the differencing image file to create.
+ * @param uImageFlags Flags specifying special image features.
+ * @param pszComment Pointer to image comment. NULL is ok.
+ * @param pUuid New UUID of the image. If NULL, a new UUID is created.
+ * @param pParentUuid New parent UUID of the image. If NULL, the UUID is queried automatically.
+ * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants.
+ * @param pVDIfsImage Pointer to the per-image VD interface list.
+ * @param pVDIfsOperation Pointer to the per-operation VD interface list.
+ */
+VBOXDDU_DECL(int) VDCreateDiff(PVBOXHDD pDisk, const char *pszBackend,
+ const char *pszFilename, unsigned uImageFlags,
+ const char *pszComment, PCRTUUID pUuid,
+ PCRTUUID pParentUuid, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation)
+{
+ int rc = VINF_SUCCESS;
+ PVDIMAGE pImage = NULL;
+ RTUUID uuid;
+
+ LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" uImageFlags=%#x pszComment=\"%s\" Uuid=%RTuuid ParentUuid=%RTuuid uOpenFlags=%#x pVDIfsImage=%#p pVDIfsOperation=%#p\n",
+ pDisk, pszBackend, pszFilename, uImageFlags, pszComment, pUuid, pParentUuid, uOpenFlags,
+ pVDIfsImage, pVDIfsOperation));
+
+ PVDINTERFACE pIfProgress = VDInterfaceGet(pVDIfsOperation,
+ VDINTERFACETYPE_PROGRESS);
+ PVDINTERFACEPROGRESS pCbProgress = NULL;
+ if (pIfProgress)
+ pCbProgress = VDGetInterfaceProgress(pIfProgress);
+
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pszBackend) && *pszBackend,
+ ("pszBackend=%#p \"%s\"\n", pszBackend, pszBackend),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename,
+ ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt((uImageFlags & ~VD_IMAGE_FLAGS_MASK) == 0,
+ ("uImageFlags=%#x\n", uImageFlags),
+ rc = VERR_INVALID_PARAMETER);
+ /* The UUID may be NULL. */
+ AssertMsgBreakStmt(pUuid == NULL || VALID_PTR(pUuid),
+ ("pUuid=%#p UUID=%RTuuid\n", pUuid, pUuid),
+ rc = VERR_INVALID_PARAMETER);
+ /* The parent UUID may be NULL. */
+ AssertMsgBreakStmt(pParentUuid == NULL || VALID_PTR(pParentUuid),
+ ("pParentUuid=%#p ParentUUID=%RTuuid\n", pParentUuid, pParentUuid),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0,
+ ("uOpenFlags=%#x\n", uOpenFlags),
+ rc = VERR_INVALID_PARAMETER);
+
+ /* Check state. */
+ AssertMsgBreakStmt(pDisk->cImages != 0,
+ ("Create diff image cannot be done without other images open\n"),
+ rc = VERR_VD_INVALID_STATE);
+
+ /* Set up image descriptor. */
+ pImage = (PVDIMAGE)RTMemAllocZ(sizeof(VDIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pImage->pszFilename = RTStrDup(pszFilename);
+ if (!pImage->pszFilename)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc = vdFindBackend(pszBackend, &pImage->Backend);
+ if (RT_FAILURE(rc))
+ break;
+ if (!pImage->Backend)
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: unknown backend name '%s'"), pszBackend);
+ break;
+ }
+
+ /* Create UUID if the caller didn't specify one. */
+ if (!pUuid)
+ {
+ rc = RTUuidCreate(&uuid);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdError(pDisk, rc, RT_SRC_POS,
+ N_("VD: cannot generate UUID for image '%s'"),
+ pszFilename);
+ break;
+ }
+ pUuid = &uuid;
+ }
+
+ pImage->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME;
+ rc = pImage->Backend->pfnCreate(pImage->pszFilename,
+ VD_IMAGE_TYPE_DIFF, pDisk->cbSize,
+ uImageFlags, pszComment,
+ &pDisk->PCHSGeometry,
+ &pDisk->LCHSGeometry, pUuid,
+ uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME,
+ 0, 99,
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ pVDIfsOperation,
+ &pImage->pvBackendData);
+
+ if (RT_SUCCESS(rc) && pDisk->cImages != 0)
+ {
+ pImage->enmImageType = VD_IMAGE_TYPE_DIFF;
+
+ /* Switch previous image to read-only mode. */
+ unsigned uOpenFlagsPrevImg;
+ uOpenFlagsPrevImg = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pvBackendData);
+ if (!(uOpenFlagsPrevImg & VD_OPEN_FLAGS_READONLY))
+ {
+ uOpenFlagsPrevImg |= VD_OPEN_FLAGS_READONLY;
+ rc = pDisk->pLast->Backend->pfnSetOpenFlags(pDisk->pLast->pvBackendData, uOpenFlagsPrevImg);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ RTUUID Uuid;
+ RTTIMESPEC ts;
+ int rc2;
+
+ if (pParentUuid && !RTUuidIsNull(pParentUuid))
+ {
+ Uuid = *pParentUuid;
+ pImage->Backend->pfnSetParentUuid(pImage->pvBackendData, &Uuid);
+ }
+ else
+ {
+ rc2 = pDisk->pLast->Backend->pfnGetUuid(pDisk->pLast->pvBackendData,
+ &Uuid);
+ if (RT_SUCCESS(rc2))
+ pImage->Backend->pfnSetParentUuid(pImage->pvBackendData, &Uuid);
+ }
+ rc2 = pDisk->pLast->Backend->pfnGetModificationUuid(pDisk->pLast->pvBackendData,
+ &Uuid);
+ if (RT_SUCCESS(rc2))
+ pImage->Backend->pfnSetParentModificationUuid(pImage->pvBackendData,
+ &Uuid);
+ rc2 = pDisk->pLast->Backend->pfnGetTimeStamp(pDisk->pLast->pvBackendData,
+ &ts);
+ if (RT_SUCCESS(rc2))
+ pImage->Backend->pfnSetParentTimeStamp(pImage->pvBackendData, &ts);
+
+ rc2 = pImage->Backend->pfnSetParentFilename(pImage->pvBackendData, pDisk->pLast->pszFilename);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo optionally check UUIDs */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Image successfully opened, make it the last image. */
+ vdAddImageToList(pDisk, pImage);
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ pDisk->uModified = VD_IMAGE_MODIFIED_FIRST;
+ }
+ else
+ {
+ /* Error detected, but image opened. Close and delete image. */
+ int rc2;
+ rc2 = pImage->Backend->pfnClose(pImage->pvBackendData, true);
+ AssertRC(rc2);
+ pImage->pvBackendData = NULL;
+ }
+ } while (0);
+
+ if (RT_FAILURE(rc))
+ {
+ if (pImage)
+ {
+ if (pImage->pszFilename)
+ RTStrFree(pImage->pszFilename);
+ RTMemFree(pImage);
+ }
+ }
+
+ if (RT_SUCCESS(rc) && pCbProgress && pCbProgress->pfnProgress)
+ pCbProgress->pfnProgress(NULL /* WARNING! pVM=NULL */, 100,
+ pIfProgress->pvUser);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Merges two images (not necessarily with direct parent/child relationship).
+ * As a side effect the source image and potentially the other images which
+ * are also merged to the destination are deleted from both the disk and the
+ * images in the HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImageFrom Name of the image file to merge from.
+ * @param nImageTo Name of the image file to merge to.
+ * @param pVDIfsOperation Pointer to the per-operation VD interface list.
+ */
+VBOXDDU_DECL(int) VDMerge(PVBOXHDD pDisk, unsigned nImageFrom,
+ unsigned nImageTo, PVDINTERFACE pVDIfsOperation)
+{
+ int rc = VINF_SUCCESS;
+ void *pvBuf = NULL;
+
+ LogFlowFunc(("pDisk=%#p nImageFrom=%u nImageTo=%u pVDIfsOperation=%#p\n",
+ pDisk, nImageFrom, nImageTo, pVDIfsOperation));
+
+ PVDINTERFACE pIfProgress = VDInterfaceGet(pVDIfsOperation,
+ VDINTERFACETYPE_PROGRESS);
+ PVDINTERFACEPROGRESS pCbProgress = NULL;
+ if (pIfProgress)
+ pCbProgress = VDGetInterfaceProgress(pIfProgress);
+
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ PVDIMAGE pImageFrom = vdGetImageByNumber(pDisk, nImageFrom);
+ PVDIMAGE pImageTo = vdGetImageByNumber(pDisk, nImageTo);
+ if (!pImageFrom || !pImageTo)
+ {
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+ break;
+ }
+ AssertBreakStmt(pImageFrom != pImageTo, rc = VERR_INVALID_PARAMETER);
+
+ /* Make sure destination image is writable. */
+ unsigned uOpenFlags = pImageTo->Backend->pfnGetOpenFlags(pImageTo->pvBackendData);
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ uOpenFlags &= ~VD_OPEN_FLAGS_READONLY;
+ rc = pImageTo->Backend->pfnSetOpenFlags(pImageTo->pvBackendData,
+ uOpenFlags);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* Get size of destination image. */
+ uint64_t cbSize = pImageTo->Backend->pfnGetSize(pImageTo->pvBackendData);
+
+ /* Allocate tmp buffer. */
+ pvBuf = RTMemTmpAlloc(VD_MERGE_BUFFER_SIZE);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Merging is done directly on the images itself. This potentially
+ * causes trouble if the disk is full in the middle of operation. */
+ /** @todo write alternative implementation which works with temporary
+ * images (which is safer, but requires even more space). Also has the
+ * drawback that merging into a raw disk parent simply isn't possible
+ * this way (but in that case disk full isn't really a problem). */
+ if (nImageFrom < nImageTo)
+ {
+ /* Merge parent state into child. This means writing all not
+ * allocated blocks in the destination image which are allocated in
+ * the images to be merged. */
+ uint64_t uOffset = 0;
+ uint64_t cbRemaining = cbSize;
+ do
+ {
+ size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining);
+ rc = pImageTo->Backend->pfnRead(pImageTo->pvBackendData,
+ uOffset, pvBuf, cbThisRead,
+ &cbThisRead);
+ if (rc == VERR_VD_BLOCK_FREE)
+ {
+ /* Search for image with allocated block. Do not attempt to
+ * read more than the previous reads marked as valid.
+ * Otherwise this would return stale data when different
+ * block sizes are used for the images. */
+ for (PVDIMAGE pCurrImage = pImageTo->pPrev;
+ pCurrImage != NULL && pCurrImage != pImageFrom->pPrev && rc == VERR_VD_BLOCK_FREE;
+ pCurrImage = pCurrImage->pPrev)
+ {
+ rc = pCurrImage->Backend->pfnRead(pCurrImage->pvBackendData,
+ uOffset, pvBuf,
+ cbThisRead,
+ &cbThisRead);
+ }
+
+ if (rc != VERR_VD_BLOCK_FREE)
+ {
+ if (RT_FAILURE(rc))
+ break;
+ rc = vdWriteHelper(pDisk, pImageTo, uOffset, pvBuf,
+ cbThisRead);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ break;
+
+ uOffset += cbThisRead;
+ cbRemaining -= cbThisRead;
+
+ if (pCbProgress && pCbProgress->pfnProgress)
+ {
+ rc = pCbProgress->pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uOffset * 99 / cbSize,
+ pIfProgress->pvUser);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ } while (uOffset < cbSize);
+ }
+ else
+ {
+ /* Merge child state into parent. This means writing all blocks
+ * which are allocated in the image up to the source image to the
+ * destination image. */
+ uint64_t uOffset = 0;
+ uint64_t cbRemaining = cbSize;
+ do
+ {
+ size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining);
+ rc = VERR_VD_BLOCK_FREE;
+ /* Search for image with allocated block. Do not attempt to
+ * read more than the previous reads marked as valid. Otherwise
+ * this would return stale data when different block sizes are
+ * used for the images. */
+ for (PVDIMAGE pCurrImage = pImageFrom;
+ pCurrImage != NULL && pCurrImage != pImageTo && rc == VERR_VD_BLOCK_FREE;
+ pCurrImage = pCurrImage->pPrev)
+ {
+ rc = pCurrImage->Backend->pfnRead(pCurrImage->pvBackendData,
+ uOffset, pvBuf,
+ cbThisRead, &cbThisRead);
+ }
+
+ if (rc != VERR_VD_BLOCK_FREE)
+ {
+ if (RT_FAILURE(rc))
+ break;
+ rc = vdWriteHelper(pDisk, pImageTo, uOffset, pvBuf,
+ cbThisRead);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ uOffset += cbThisRead;
+ cbRemaining -= cbThisRead;
+
+ if (pCbProgress && pCbProgress->pfnProgress)
+ {
+ rc = pCbProgress->pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uOffset * 99 / cbSize,
+ pIfProgress->pvUser);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ } while (uOffset < cbSize);
+ }
+
+ /* Update parent UUID so that image chain is consistent. */
+ RTUUID Uuid;
+ if (nImageFrom < nImageTo)
+ {
+ if (pImageTo->pPrev)
+ {
+ rc = pImageTo->Backend->pfnGetUuid(pImageTo->pPrev->pvBackendData,
+ &Uuid);
+ AssertRC(rc);
+ }
+ else
+ RTUuidClear(&Uuid);
+ rc = pImageTo->Backend->pfnSetParentUuid(pImageTo->pvBackendData,
+ &Uuid);
+ AssertRC(rc);
+ }
+ else
+ {
+ if (pImageFrom->pNext)
+ {
+ rc = pImageTo->Backend->pfnGetUuid(pImageTo->pvBackendData,
+ &Uuid);
+ AssertRC(rc);
+ rc = pImageFrom->Backend->pfnSetParentUuid(pImageFrom->pNext,
+ &Uuid);
+ AssertRC(rc);
+ }
+ }
+
+ /* Make sure destination image is back to read only if necessary. */
+ if (pImageTo != pDisk->pLast && pImageFrom != pDisk->pLast)
+ {
+ uOpenFlags = pImageTo->Backend->pfnGetOpenFlags(pImageTo->pvBackendData);
+ uOpenFlags |= VD_OPEN_FLAGS_READONLY;
+ rc = pImageTo->Backend->pfnSetOpenFlags(pImageTo->pvBackendData,
+ uOpenFlags);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* Delete the no longer needed images. */
+ PVDIMAGE pImg = pImageFrom, pTmp;
+ while (pImg != pImageTo)
+ {
+ if (nImageFrom < nImageTo)
+ pTmp = pImg->pNext;
+ else
+ pTmp = pImg->pPrev;
+ vdRemoveImageFromList(pDisk, pImg);
+ pImg->Backend->pfnClose(pImg->pvBackendData, true);
+ RTMemFree(pImg->pszFilename);
+ RTMemFree(pImg);
+ pImg = pTmp;
+ }
+ } while (0);
+
+ if (pvBuf)
+ RTMemTmpFree(pvBuf);
+
+ if (RT_SUCCESS(rc) && pCbProgress && pCbProgress->pfnProgress)
+ pCbProgress->pfnProgress(NULL /* WARNING! pVM=NULL */, 100,
+ pIfProgress->pvUser);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Copies an image from one HDD container to another.
+ * The copy is opened in the target HDD container.
+ * It is possible to convert between different image formats, because the
+ * backend for the destination may be different from the source.
+ * If both the source and destination reference the same HDD container,
+ * then the image is moved (by copying/deleting or renaming) to the new location.
+ * The source container is unchanged if the move operation fails, otherwise
+ * the image at the new location is opened in the same way as the old one was.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDiskFrom Pointer to source HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pDiskTo Pointer to destination HDD container.
+ * @param pszBackend Name of the image file backend to use.
+ * @param pszFilename New name of the image (may be NULL if pDiskFrom == pDiskTo).
+ * @param fMoveByRename If true, attempt to perform a move by renaming (if successful the new size is ignored).
+ * @param cbSize New image size (0 means leave unchanged).
+ * @param pDstUuid New UUID of the destination image. If NULL, a new UUID is created.
+ * This parameter is used if and only if a true copy is created.
+ * In all rename/move cases the UUIDs are copied over.
+ * @param pVDIfsOperation Pointer to the per-operation VD interface list.
+ * @param pDstVDIfsImage Pointer to the per-image VD interface list, for the
+ * destination image.
+ * @param pDstVDIfsOperation Pointer to the per-image VD interface list,
+ * for the destination image.
+ */
+VBOXDDU_DECL(int) VDCopy(PVBOXHDD pDiskFrom, unsigned nImage, PVBOXHDD pDiskTo,
+ const char *pszBackend, const char *pszFilename,
+ bool fMoveByRename, uint64_t cbSize, PCRTUUID pDstUuid,
+ PVDINTERFACE pVDIfsOperation,
+ PVDINTERFACE pDstVDIfsImage,
+ PVDINTERFACE pDstVDIfsOperation)
+{
+ int rc, rc2 = VINF_SUCCESS;
+ void *pvBuf = NULL;
+ PVDIMAGE pImageTo = NULL;
+
+ LogFlowFunc(("pDiskFrom=%#p nImage=%u pDiskTo=%#p pszBackend=\"%s\" pszFilename=\"%s\" fMoveByRename=%d cbSize=%llu pVDIfsOperation=%#p pDstVDIfsImage=%#p pDstVDIfsOperation=%#p\n",
+ pDiskFrom, nImage, pDiskTo, pszBackend, pszFilename, fMoveByRename, cbSize, pVDIfsOperation, pDstVDIfsImage, pDstVDIfsOperation));
+
+ PVDINTERFACE pIfProgress = VDInterfaceGet(pVDIfsOperation,
+ VDINTERFACETYPE_PROGRESS);
+ PVDINTERFACEPROGRESS pCbProgress = NULL;
+ if (pIfProgress)
+ pCbProgress = VDGetInterfaceProgress(pIfProgress);
+
+ PVDINTERFACE pDstIfProgress = VDInterfaceGet(pDstVDIfsOperation,
+ VDINTERFACETYPE_PROGRESS);
+ PVDINTERFACEPROGRESS pDstCbProgress = NULL;
+ if (pDstIfProgress)
+ pDstCbProgress = VDGetInterfaceProgress(pDstIfProgress);
+
+ do {
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pDiskFrom), ("pDiskFrom=%#p\n", pDiskFrom),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDiskFrom->u32Signature == VBOXHDDDISK_SIGNATURE,
+ ("u32Signature=%08x\n", pDiskFrom->u32Signature));
+
+ PVDIMAGE pImageFrom = vdGetImageByNumber(pDiskFrom, nImage);
+ AssertPtrBreakStmt(pImageFrom, rc = VERR_VD_IMAGE_NOT_FOUND);
+ AssertMsgBreakStmt(VALID_PTR(pDiskTo), ("pDiskTo=%#p\n", pDiskTo),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDiskTo->u32Signature == VBOXHDDDISK_SIGNATURE,
+ ("u32Signature=%08x\n", pDiskTo->u32Signature));
+
+ /* Move the image. */
+ if (pDiskFrom == pDiskTo)
+ {
+ /* Rename only works when backends are the same. */
+ if ( fMoveByRename
+ && !RTStrICmp(pszBackend, pImageFrom->Backend->pszBackendName))
+ {
+ rc = pImageFrom->Backend->pfnRename(pImageFrom->pvBackendData, pszFilename ? pszFilename : pImageFrom->pszFilename);
+ break;
+ }
+
+ /** @todo Moving (including shrinking/growing) of the image is
+ * requested, but the rename attempt failed or it wasn't possible.
+ * Must now copy image to temp location. */
+ AssertReleaseMsgFailed(("VDCopy: moving by copy/delete not implemented\n"));
+ }
+
+ /* When moving an image pszFilename is allowed to be NULL, so do the parameter check here. */
+ AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename,
+ ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename),
+ rc = VERR_INVALID_PARAMETER);
+
+ /* Collect properties of source image. */
+ VDIMAGETYPE enmTypeFrom = pImageFrom->enmImageType;
+
+ uint64_t cbSizeFrom;
+ cbSizeFrom = pImageFrom->Backend->pfnGetSize(pImageFrom->pvBackendData);
+ if (cbSizeFrom == 0)
+ {
+ rc = VERR_VD_VALUE_NOT_FOUND;
+ break;
+ }
+
+ if (cbSize == 0)
+ cbSize = cbSizeFrom;
+
+ unsigned uImageFlagsFrom;
+ uImageFlagsFrom = pImageFrom->Backend->pfnGetImageFlags(pImageFrom->pvBackendData);
+
+ PDMMEDIAGEOMETRY PCHSGeometryFrom = {0, 0, 0};
+ PDMMEDIAGEOMETRY LCHSGeometryFrom = {0, 0, 0};
+ pImageFrom->Backend->pfnGetPCHSGeometry(pImageFrom->pvBackendData, &PCHSGeometryFrom);
+ pImageFrom->Backend->pfnGetLCHSGeometry(pImageFrom->pvBackendData, &LCHSGeometryFrom);
+
+ RTUUID ImageUuid, ImageModificationUuid;
+ RTUUID ParentUuid, ParentModificationUuid;
+ if (pDiskFrom != pDiskTo)
+ {
+ if (pDstUuid)
+ ImageUuid = *pDstUuid;
+ else
+ RTUuidCreate(&ImageUuid);
+ }
+ else
+ {
+ rc = pImageFrom->Backend->pfnGetUuid(pImageFrom->pvBackendData, &ImageUuid);
+ if (RT_FAILURE(rc))
+ RTUuidCreate(&ImageUuid);
+ }
+ rc = pImageFrom->Backend->pfnGetModificationUuid(pImageFrom->pvBackendData, &ImageModificationUuid);
+ if (RT_FAILURE(rc))
+ RTUuidClear(&ImageModificationUuid);
+ rc = pImageFrom->Backend->pfnGetParentUuid(pImageFrom->pvBackendData, &ParentUuid);
+ if (RT_FAILURE(rc))
+ RTUuidClear(&ParentUuid);
+ rc = pImageFrom->Backend->pfnGetParentModificationUuid(pImageFrom->pvBackendData, &ParentModificationUuid);
+ if (RT_FAILURE(rc))
+ RTUuidClear(&ParentModificationUuid);
+
+ char szComment[1024];
+ rc = pImageFrom->Backend->pfnGetComment(pImageFrom->pvBackendData, szComment, sizeof(szComment));
+ if (RT_FAILURE(rc))
+ szComment[0] = '\0';
+ else
+ szComment[sizeof(szComment) - 1] = '\0';
+
+ unsigned uOpenFlagsFrom;
+ uOpenFlagsFrom = pImageFrom->Backend->pfnGetOpenFlags(pImageFrom->pvBackendData);
+
+ /* Create destination image with the properties of the source image. */
+ /** @todo replace the VDCreateDiff/VDCreateBase calls by direct
+ * calls to the backend. Unifies the code and reduces the API
+ * dependencies. */
+ if (enmTypeFrom == VD_IMAGE_TYPE_DIFF)
+ {
+ rc = VDCreateDiff(pDiskTo, pszBackend, pszFilename, uImageFlagsFrom,
+ szComment, &ImageUuid, &ParentUuid, uOpenFlagsFrom & ~VD_OPEN_FLAGS_READONLY, NULL, NULL);
+ } else {
+ rc = VDCreateBase(pDiskTo, pszBackend, pszFilename, enmTypeFrom,
+ cbSize, uImageFlagsFrom, szComment,
+ &PCHSGeometryFrom, &LCHSGeometryFrom,
+ NULL, uOpenFlagsFrom & ~VD_OPEN_FLAGS_READONLY, NULL, NULL);
+ if (RT_SUCCESS(rc) && !RTUuidIsNull(&ImageUuid))
+ pDiskTo->pLast->Backend->pfnSetUuid(pDiskTo->pLast->pvBackendData, &ImageUuid);
+ if (RT_SUCCESS(rc) && !RTUuidIsNull(&ParentUuid))
+ pDiskTo->pLast->Backend->pfnSetParentUuid(pDiskTo->pLast->pvBackendData, &ParentUuid);
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ pImageTo = pDiskTo->pLast;
+ AssertPtrBreakStmt(pImageTo, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ /* Allocate tmp buffer. */
+ pvBuf = RTMemTmpAlloc(VD_MERGE_BUFFER_SIZE);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Copy the data. */
+ uint64_t uOffset = 0;
+ uint64_t cbRemaining = cbSize;
+
+ do
+ {
+ size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining);
+
+ rc = vdReadHelper(pDiskFrom, pImageFrom, uOffset, pvBuf,
+ cbThisRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vdWriteHelper(pDiskTo, pImageTo, uOffset, pvBuf,
+ cbThisRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ uOffset += cbThisRead;
+ cbRemaining -= cbThisRead;
+
+ if (pCbProgress && pCbProgress->pfnProgress)
+ {
+ rc = pCbProgress->pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uOffset * 99 / cbSize,
+ pIfProgress->pvUser);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (pDstCbProgress && pDstCbProgress->pfnProgress)
+ {
+ rc = pDstCbProgress->pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uOffset * 99 / cbSize,
+ pDstIfProgress->pvUser);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ } while (uOffset < cbSize);
+
+ if (RT_SUCCESS(rc))
+ {
+ pImageTo->Backend->pfnSetModificationUuid(pImageTo->pvBackendData, &ImageModificationUuid);
+ pImageTo->Backend->pfnGetParentModificationUuid(pImageTo->pvBackendData, &ParentModificationUuid);
+ }
+ } while (0);
+
+ if (RT_FAILURE(rc) && pImageTo)
+ {
+ /* Error detected, but new image created. Remove image from list. */
+ vdRemoveImageFromList(pDiskTo, pImageTo);
+
+ /* Close and delete image. */
+ rc2 = pImageTo->Backend->pfnClose(pImageTo->pvBackendData, true);
+ AssertRC(rc2);
+ pImageTo->pvBackendData = NULL;
+
+ /* Free remaining resources. */
+ if (pImageTo->pszFilename)
+ RTStrFree(pImageTo->pszFilename);
+
+ RTMemFree(pImageTo);
+ }
+
+ if (pvBuf)
+ RTMemTmpFree(pvBuf);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pCbProgress && pCbProgress->pfnProgress)
+ pCbProgress->pfnProgress(NULL /* WARNING! pVM=NULL */, 100,
+ pIfProgress->pvUser);
+ if (pDstCbProgress && pDstCbProgress->pfnProgress)
+ pDstCbProgress->pfnProgress(NULL /* WARNING! pVM=NULL */, 100,
+ pDstIfProgress->pvUser);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Closes the last opened image file in HDD container.
+ * If previous image file was opened in read-only mode (that is normal) and closing image
+ * was opened in read-write mode (the whole disk was in read-write mode) - the previous image
+ * will be reopened in read/write mode.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_NOT_OPENED if no image is opened in HDD container.
+ * @param pDisk Pointer to HDD container.
+ * @param fDelete If true, delete the image from the host disk.
+ */
+VBOXDDU_DECL(int) VDClose(PVBOXHDD pDisk, bool fDelete)
+{
+ int rc = VINF_SUCCESS;;
+
+ LogFlowFunc(("pDisk=%#p fDelete=%d\n", pDisk, fDelete));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED);
+ unsigned uOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pvBackendData);
+ /* Remove image from list of opened images. */
+ vdRemoveImageFromList(pDisk, pImage);
+ /* Close (and optionally delete) image. */
+ rc = pImage->Backend->pfnClose(pImage->pvBackendData, fDelete);
+ /* Free remaining resources related to the image. */
+ RTStrFree(pImage->pszFilename);
+ RTMemFree(pImage);
+
+ pImage = pDisk->pLast;
+ if (!pImage)
+ break;
+
+ /* If disk was previously in read/write mode, make sure it will stay
+ * like this (if possible) after closing this image. Set the open flags
+ * accordingly. */
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ uOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pvBackendData);
+ uOpenFlags &= ~ VD_OPEN_FLAGS_READONLY;
+ rc = pImage->Backend->pfnSetOpenFlags(pImage->pvBackendData, uOpenFlags);
+ }
+
+ int rc2;
+
+ /* Cache disk information. */
+ pDisk->cbSize = pImage->Backend->pfnGetSize(pImage->pvBackendData);
+
+ /* Cache PCHS geometry. */
+ rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pvBackendData,
+ &pDisk->PCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->PCHSGeometry.cCylinders = 0;
+ pDisk->PCHSGeometry.cHeads = 0;
+ pDisk->PCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the PCHS geometry is properly clipped. */
+ pDisk->PCHSGeometry.cCylinders = RT_MIN(pDisk->PCHSGeometry.cCylinders, 16383);
+ pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 16);
+ pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63);
+ }
+
+ /* Cache LCHS geometry. */
+ rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pvBackendData,
+ &pDisk->LCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->LCHSGeometry.cCylinders = 0;
+ pDisk->LCHSGeometry.cHeads = 0;
+ pDisk->LCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the LCHS geometry is properly clipped. */
+ pDisk->LCHSGeometry.cCylinders = RT_MIN(pDisk->LCHSGeometry.cCylinders, 1024);
+ pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255);
+ pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63);
+ }
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Closes all opened image files in HDD container.
+ *
+ * @returns VBox status code.
+ * @param pDisk Pointer to HDD container.
+ */
+VBOXDDU_DECL(int) VDCloseAll(PVBOXHDD pDisk)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ PVDIMAGE pImage = pDisk->pLast;
+ while (VALID_PTR(pImage))
+ {
+ PVDIMAGE pPrev = pImage->pPrev;
+ /* Remove image from list of opened images. */
+ vdRemoveImageFromList(pDisk, pImage);
+ /* Close image. */
+ int rc2 = pImage->Backend->pfnClose(pImage->pvBackendData, false);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ /* Free remaining resources related to the image. */
+ RTStrFree(pImage->pszFilename);
+ RTMemFree(pImage);
+ pImage = pPrev;
+ }
+ Assert(!VALID_PTR(pDisk->pLast));
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Read data from virtual HDD.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_NOT_OPENED if no image is opened in HDD container.
+ * @param pDisk Pointer to HDD container.
+ * @param uOffset Offset of first reading byte from start of disk.
+ * @param pvBuf Pointer to buffer for reading data.
+ * @param cbRead Number of bytes to read.
+ */
+VBOXDDU_DECL(int) VDRead(PVBOXHDD pDisk, uint64_t uOffset, void *pvBuf,
+ size_t cbRead)
+{
+ int rc;
+
+ LogFlowFunc(("pDisk=%#p uOffset=%llu pvBuf=%p cbRead=%zu\n",
+ pDisk, uOffset, pvBuf, cbRead));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pvBuf),
+ ("pvBuf=%#p\n", pvBuf),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(cbRead,
+ ("cbRead=%zu\n", cbRead),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(uOffset + cbRead <= pDisk->cbSize,
+ ("uOffset=%llu cbRead=%zu pDisk->cbSize=%llu\n",
+ uOffset, cbRead, pDisk->cbSize),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED);
+
+ rc = vdReadHelper(pDisk, pImage, uOffset, pvBuf, cbRead);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Write data to virtual HDD.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_NOT_OPENED if no image is opened in HDD container.
+ * @param pDisk Pointer to HDD container.
+ * @param uOffset Offset of the first byte being
+ * written from start of disk.
+ * @param pvBuf Pointer to buffer for writing data.
+ * @param cbWrite Number of bytes to write.
+ */
+VBOXDDU_DECL(int) VDWrite(PVBOXHDD pDisk, uint64_t uOffset, const void *pvBuf,
+ size_t cbWrite)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p uOffset=%llu pvBuf=%p cbWrite=%zu\n",
+ pDisk, uOffset, pvBuf, cbWrite));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pvBuf),
+ ("pvBuf=%#p\n", pvBuf),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(cbWrite,
+ ("cbWrite=%zu\n", cbWrite),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(uOffset + cbWrite <= pDisk->cbSize,
+ ("uOffset=%llu cbWrite=%zu pDisk->cbSize=%llu\n",
+ uOffset, cbWrite, pDisk->cbSize),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED);
+
+ vdSetModifiedFlag(pDisk);
+ rc = vdWriteHelper(pDisk, pImage, uOffset, pvBuf, cbWrite);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Make sure the on disk representation of a virtual HDD is up to date.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_NOT_OPENED if no image is opened in HDD container.
+ * @param pDisk Pointer to HDD container.
+ */
+VBOXDDU_DECL(int) VDFlush(PVBOXHDD pDisk)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED);
+
+ vdResetModifiedFlag(pDisk);
+ rc = pImage->Backend->pfnFlush(pImage->pvBackendData);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Get number of opened images in HDD container.
+ *
+ * @returns Number of opened images for HDD container. 0 if no images have been opened.
+ * @param pDisk Pointer to HDD container.
+ */
+VBOXDDU_DECL(unsigned) VDGetCount(PVBOXHDD pDisk)
+{
+ unsigned cImages;
+
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, cImages = 0);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ cImages = pDisk->cImages;
+ } while (0);
+
+ LogFlowFunc(("returns %u\n", cImages));
+ return cImages;
+}
+
+/**
+ * Get read/write mode of HDD container.
+ *
+ * @returns Virtual disk ReadOnly status.
+ * @returns true if no image is opened in HDD container.
+ * @param pDisk Pointer to HDD container.
+ */
+VBOXDDU_DECL(bool) VDIsReadOnly(PVBOXHDD pDisk)
+{
+ bool fReadOnly;
+
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, fReadOnly = false);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtrBreakStmt(pImage, fReadOnly = true);
+
+ unsigned uOpenFlags;
+ uOpenFlags = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pvBackendData);
+ fReadOnly = !!(uOpenFlags & VD_OPEN_FLAGS_READONLY);
+ } while (0);
+
+ LogFlowFunc(("returns %d\n", fReadOnly));
+ return fReadOnly;
+}
+
+/**
+ * Get total capacity of an image in HDD container.
+ *
+ * @returns Virtual disk size in bytes.
+ * @returns 0 if no image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counds from 0. 0 is always base image of container.
+ */
+VBOXDDU_DECL(uint64_t) VDGetSize(PVBOXHDD pDisk, unsigned nImage)
+{
+ uint64_t cbSize;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u\n", pDisk, nImage));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, cbSize = 0);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, cbSize = 0);
+ cbSize = pImage->Backend->pfnGetSize(pImage->pvBackendData);
+ } while (0);
+
+ LogFlowFunc(("returns %llu\n", cbSize));
+ return cbSize;
+}
+
+/**
+ * Get total file size of an image in HDD container.
+ *
+ * @returns Virtual disk size in bytes.
+ * @returns 0 if no image is opened in HDD container.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ */
+VBOXDDU_DECL(uint64_t) VDGetFileSize(PVBOXHDD pDisk, unsigned nImage)
+{
+ uint64_t cbSize;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u\n", pDisk, nImage));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, cbSize = 0);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, cbSize = 0);
+ cbSize = pImage->Backend->pfnGetFileSize(pImage->pvBackendData);
+ } while (0);
+
+ LogFlowFunc(("returns %llu\n", cbSize));
+ return cbSize;
+}
+
+/**
+ * Get virtual disk PCHS geometry stored in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @returns VERR_VD_GEOMETRY_NOT_SET if no geometry present in the HDD container.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pPCHSGeometry Where to store PCHS geometry. Not NULL.
+ */
+VBOXDDU_DECL(int) VDGetPCHSGeometry(PVBOXHDD pDisk, unsigned nImage,
+ PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pPCHSGeometry=%#p\n",
+ pDisk, nImage, pPCHSGeometry));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pPCHSGeometry),
+ ("pPCHSGeometry=%#p\n", pPCHSGeometry),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ if (pImage == pDisk->pLast)
+ {
+ /* Use cached information if possible. */
+ if (pDisk->PCHSGeometry.cCylinders != 0)
+ *pPCHSGeometry = pDisk->PCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = pImage->Backend->pfnGetPCHSGeometry(pImage->pvBackendData,
+ pPCHSGeometry);
+ } while (0);
+
+ LogFlowFunc(("%s: %Rrc (PCHS=%u/%u/%u)\n", __FUNCTION__, rc,
+ pDisk->PCHSGeometry.cCylinders, pDisk->PCHSGeometry.cHeads,
+ pDisk->PCHSGeometry.cSectors));
+ return rc;
+}
+
+/**
+ * Store virtual disk PCHS geometry in HDD container.
+ *
+ * Note that in case of unrecoverable error all images in HDD container will be closed.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @returns VERR_VD_GEOMETRY_NOT_SET if no geometry present in the HDD container.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pPCHSGeometry Where to load PCHS geometry from. Not NULL.
+ */
+VBOXDDU_DECL(int) VDSetPCHSGeometry(PVBOXHDD pDisk, unsigned nImage,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pDisk, nImage, pPCHSGeometry, pPCHSGeometry->cCylinders,
+ pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt( VALID_PTR(pPCHSGeometry)
+ && pPCHSGeometry->cCylinders <= 16383
+ && pPCHSGeometry->cHeads <= 16
+ && pPCHSGeometry->cSectors <= 63,
+ ("pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pPCHSGeometry,
+ pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads,
+ pPCHSGeometry->cSectors),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ if (pImage == pDisk->pLast)
+ {
+ if ( pPCHSGeometry->cCylinders != pDisk->PCHSGeometry.cCylinders
+ || pPCHSGeometry->cHeads != pDisk->PCHSGeometry.cHeads
+ || pPCHSGeometry->cSectors != pDisk->PCHSGeometry.cSectors)
+ {
+ /* Only update geometry if it is changed. Avoids similar checks
+ * in every backend. Most of the time the new geometry is set
+ * to the previous values, so no need to go through the hassle
+ * of updating an image which could be opened in read-only mode
+ * right now. */
+ rc = pImage->Backend->pfnSetPCHSGeometry(pImage->pvBackendData,
+ pPCHSGeometry);
+
+ /* Cache new geometry values in any case. */
+ int rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pvBackendData,
+ &pDisk->PCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->PCHSGeometry.cCylinders = 0;
+ pDisk->PCHSGeometry.cHeads = 0;
+ pDisk->PCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the CHS geometry is properly clipped. */
+ pDisk->PCHSGeometry.cCylinders = RT_MIN(pDisk->PCHSGeometry.cCylinders, 1024);
+ pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 255);
+ pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63);
+ }
+ }
+ }
+ else
+ {
+ PDMMEDIAGEOMETRY PCHS;
+ rc = pImage->Backend->pfnGetPCHSGeometry(pImage->pvBackendData,
+ &PCHS);
+ if ( RT_FAILURE(rc)
+ || pPCHSGeometry->cCylinders != PCHS.cCylinders
+ || pPCHSGeometry->cHeads != PCHS.cHeads
+ || pPCHSGeometry->cSectors != PCHS.cSectors)
+ {
+ /* Only update geometry if it is changed. Avoids similar checks
+ * in every backend. Most of the time the new geometry is set
+ * to the previous values, so no need to go through the hassle
+ * of updating an image which could be opened in read-only mode
+ * right now. */
+ rc = pImage->Backend->pfnSetPCHSGeometry(pImage->pvBackendData,
+ pPCHSGeometry);
+ }
+ }
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Get virtual disk LCHS geometry stored in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @returns VERR_VD_GEOMETRY_NOT_SET if no geometry present in the HDD container.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pLCHSGeometry Where to store LCHS geometry. Not NULL.
+ */
+VBOXDDU_DECL(int) VDGetLCHSGeometry(PVBOXHDD pDisk, unsigned nImage,
+ PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pLCHSGeometry=%#p\n",
+ pDisk, nImage, pLCHSGeometry));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pLCHSGeometry),
+ ("pLCHSGeometry=%#p\n", pLCHSGeometry),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ if (pImage == pDisk->pLast)
+ {
+ /* Use cached information if possible. */
+ if (pDisk->LCHSGeometry.cCylinders != 0)
+ *pLCHSGeometry = pDisk->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = pImage->Backend->pfnGetLCHSGeometry(pImage->pvBackendData,
+ pLCHSGeometry);
+ } while (0);
+
+ LogFlowFunc((": %Rrc (LCHS=%u/%u/%u)\n", rc,
+ pDisk->LCHSGeometry.cCylinders, pDisk->LCHSGeometry.cHeads,
+ pDisk->LCHSGeometry.cSectors));
+ return rc;
+}
+
+/**
+ * Store virtual disk LCHS geometry in HDD container.
+ *
+ * Note that in case of unrecoverable error all images in HDD container will be closed.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @returns VERR_VD_GEOMETRY_NOT_SET if no geometry present in the HDD container.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pLCHSGeometry Where to load LCHS geometry from. Not NULL.
+ */
+VBOXDDU_DECL(int) VDSetLCHSGeometry(PVBOXHDD pDisk, unsigned nImage,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pLCHSGeometry=%#p LCHS=%u/%u/%u\n",
+ pDisk, nImage, pLCHSGeometry, pLCHSGeometry->cCylinders,
+ pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt( VALID_PTR(pLCHSGeometry)
+ && pLCHSGeometry->cCylinders <= 1024
+ && pLCHSGeometry->cHeads <= 255
+ && pLCHSGeometry->cSectors <= 63,
+ ("pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pLCHSGeometry,
+ pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads,
+ pLCHSGeometry->cSectors),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ if (pImage == pDisk->pLast)
+ {
+ if ( pLCHSGeometry->cCylinders != pDisk->LCHSGeometry.cCylinders
+ || pLCHSGeometry->cHeads != pDisk->LCHSGeometry.cHeads
+ || pLCHSGeometry->cSectors != pDisk->LCHSGeometry.cSectors)
+ {
+ /* Only update geometry if it is changed. Avoids similar checks
+ * in every backend. Most of the time the new geometry is set
+ * to the previous values, so no need to go through the hassle
+ * of updating an image which could be opened in read-only mode
+ * right now. */
+ rc = pImage->Backend->pfnSetLCHSGeometry(pImage->pvBackendData,
+ pLCHSGeometry);
+
+ /* Cache new geometry values in any case. */
+ int rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pvBackendData,
+ &pDisk->LCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->LCHSGeometry.cCylinders = 0;
+ pDisk->LCHSGeometry.cHeads = 0;
+ pDisk->LCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the CHS geometry is properly clipped. */
+ pDisk->LCHSGeometry.cCylinders = RT_MIN(pDisk->LCHSGeometry.cCylinders, 1024);
+ pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255);
+ pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63);
+ }
+ }
+ }
+ else
+ {
+ PDMMEDIAGEOMETRY LCHS;
+ rc = pImage->Backend->pfnGetLCHSGeometry(pImage->pvBackendData,
+ &LCHS);
+ if ( RT_FAILURE(rc)
+ || pLCHSGeometry->cCylinders != LCHS.cCylinders
+ || pLCHSGeometry->cHeads != LCHS.cHeads
+ || pLCHSGeometry->cSectors != LCHS.cSectors)
+ {
+ /* Only update geometry if it is changed. Avoids similar checks
+ * in every backend. Most of the time the new geometry is set
+ * to the previous values, so no need to go through the hassle
+ * of updating an image which could be opened in read-only mode
+ * right now. */
+ rc = pImage->Backend->pfnSetLCHSGeometry(pImage->pvBackendData,
+ pLCHSGeometry);
+ }
+ }
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Get version of image in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param puVersion Where to store the image version.
+ */
+VBOXDDU_DECL(int) VDGetVersion(PVBOXHDD pDisk, unsigned nImage,
+ unsigned *puVersion)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u puVersion=%#p\n",
+ pDisk, nImage, puVersion));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(puVersion),
+ ("puVersion=%#p\n", puVersion),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ *puVersion = pImage->Backend->pfnGetVersion(pImage->pvBackendData);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc uVersion=%#x\n", rc, *puVersion));
+ return rc;
+}
+
+/**
+ * Get type of image in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param penmType Where to store the image type.
+ */
+VBOXDDU_DECL(int) VDGetImageType(PVBOXHDD pDisk, unsigned nImage,
+ PVDIMAGETYPE penmType)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u penmType=%#p\n",
+ pDisk, nImage, penmType));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(penmType),
+ ("penmType=%#p\n", penmType),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ if ( pImage->enmImageType >= VD_IMAGE_TYPE_FIRST
+ && pImage->enmImageType <= VD_IMAGE_TYPE_DIFF)
+ {
+ *penmType = pImage->enmImageType;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_INVALID_TYPE;
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc uenmType=%u\n", rc, *penmType));
+ return rc;
+}
+
+
+/**
+ * List the capabilities of image backend in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to the HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pbackendInfo Where to store the backend information.
+ */
+VBOXDDU_DECL(int) VDBackendInfoSingle(PVBOXHDD pDisk, unsigned nImage,
+ PVDBACKENDINFO pBackendInfo)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u penmType=%#p\n",
+ pDisk, nImage, pBackendInfo));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pBackendInfo),
+ ("pBackendInfo=%#p\n", pBackendInfo),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ if ( pImage->enmImageType >= VD_IMAGE_TYPE_FIRST
+ && pImage->enmImageType <= VD_IMAGE_TYPE_DIFF)
+ {
+ pBackendInfo->pszBackend = RTStrDup(pImage->Backend->pszBackendName);
+ pBackendInfo->uBackendCaps = pImage->Backend->uBackendCaps;
+ pBackendInfo->papszFileExtensions = pImage->Backend->papszFileExtensions;
+ pBackendInfo->paConfigInfo = pImage->Backend->paConfigInfo;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_INVALID_TYPE;
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Get flags of image in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param puImageFlags Where to store the image flags.
+ */
+VBOXDDU_DECL(int) VDGetImageFlags(PVBOXHDD pDisk, unsigned nImage,
+ unsigned *puImageFlags)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u puImageFlags=%#p\n",
+ pDisk, nImage, puImageFlags));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(puImageFlags),
+ ("puImageFlags=%#p\n", puImageFlags),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ *puImageFlags = pImage->Backend->pfnGetImageFlags(pImage->pvBackendData);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc uImageFlags=%#x\n", rc, *puImageFlags));
+ return rc;
+}
+
+/**
+ * Get open flags of image in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param puOpenFlags Where to store the image open flags.
+ */
+VBOXDDU_DECL(int) VDGetOpenFlags(PVBOXHDD pDisk, unsigned nImage,
+ unsigned *puOpenFlags)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u puOpenFlags=%#p\n",
+ pDisk, nImage, puOpenFlags));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(puOpenFlags),
+ ("puOpenFlags=%#p\n", puOpenFlags),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ *puOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pvBackendData);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc uOpenFlags=%#x\n", rc, *puOpenFlags));
+ return rc;
+}
+
+/**
+ * Set open flags of image in HDD container.
+ * This operation may cause file locking changes and/or files being reopened.
+ * Note that in case of unrecoverable error all images in HDD container will be closed.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants.
+ */
+VBOXDDU_DECL(int) VDSetOpenFlags(PVBOXHDD pDisk, unsigned nImage,
+ unsigned uOpenFlags)
+{
+ int rc;
+
+ LogFlowFunc(("pDisk=%#p uOpenFlags=%#u\n", pDisk, uOpenFlags));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0,
+ ("uOpenFlags=%#x\n", uOpenFlags),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ rc = pImage->Backend->pfnSetOpenFlags(pImage->pvBackendData,
+ uOpenFlags);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Get base filename of image in HDD container. Some image formats use
+ * other filenames as well, so don't use this for anything but informational
+ * purposes.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @returns VERR_BUFFER_OVERFLOW if pszFilename buffer too small to hold filename.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pszFilename Where to store the image file name.
+ * @param cbFilename Size of buffer pszFilename points to.
+ */
+VBOXDDU_DECL(int) VDGetFilename(PVBOXHDD pDisk, unsigned nImage,
+ char *pszFilename, unsigned cbFilename)
+{
+ int rc;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pszFilename=%#p cbFilename=%u\n",
+ pDisk, nImage, pszFilename, cbFilename));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename,
+ ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(cbFilename,
+ ("cbFilename=%u\n", cbFilename),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ size_t cb = strlen(pImage->pszFilename);
+ if (cb <= cbFilename)
+ {
+ strcpy(pszFilename, pImage->pszFilename);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ strncpy(pszFilename, pImage->pszFilename, cbFilename - 1);
+ pszFilename[cbFilename - 1] = '\0';
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc, pszFilename=\"%s\"\n", rc, pszFilename));
+ return rc;
+}
+
+/**
+ * Get the comment line of image in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @returns VERR_BUFFER_OVERFLOW if pszComment buffer too small to hold comment text.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pszComment Where to store the comment string of image. NULL is ok.
+ * @param cbComment The size of pszComment buffer. 0 is ok.
+ */
+VBOXDDU_DECL(int) VDGetComment(PVBOXHDD pDisk, unsigned nImage,
+ char *pszComment, unsigned cbComment)
+{
+ int rc;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pszComment=%#p cbComment=%u\n",
+ pDisk, nImage, pszComment, cbComment));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pszComment),
+ ("pszComment=%#p \"%s\"\n", pszComment, pszComment),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(cbComment,
+ ("cbComment=%u\n", cbComment),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ rc = pImage->Backend->pfnGetComment(pImage->pvBackendData, pszComment,
+ cbComment);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc, pszComment=\"%s\"\n", rc, pszComment));
+ return rc;
+}
+
+/**
+ * Changes the comment line of image in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pszComment New comment string (UTF-8). NULL is allowed to reset the comment.
+ */
+VBOXDDU_DECL(int) VDSetComment(PVBOXHDD pDisk, unsigned nImage,
+ const char *pszComment)
+{
+ int rc;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pszComment=%#p \"%s\"\n",
+ pDisk, nImage, pszComment, pszComment));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pszComment) || pszComment == NULL,
+ ("pszComment=%#p \"%s\"\n", pszComment, pszComment),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ rc = pImage->Backend->pfnSetComment(pImage->pvBackendData, pszComment);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Get UUID of image in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pUuid Where to store the image creation UUID.
+ */
+VBOXDDU_DECL(int) VDGetUuid(PVBOXHDD pDisk, unsigned nImage, PRTUUID pUuid)
+{
+ int rc;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p\n", pDisk, nImage, pUuid));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pUuid),
+ ("pUuid=%#p\n", pUuid),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ rc = pImage->Backend->pfnGetUuid(pImage->pvBackendData, pUuid);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc, Uuid={%RTuuid}\n", rc, pUuid));
+ return rc;
+}
+
+/**
+ * Set the image's UUID. Should not be used by normal applications.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pUuid New UUID of the image. If NULL, a new UUID is created.
+ */
+VBOXDDU_DECL(int) VDSetUuid(PVBOXHDD pDisk, unsigned nImage, PCRTUUID pUuid)
+{
+ int rc;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p {%RTuuid}\n",
+ pDisk, nImage, pUuid, pUuid));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ AssertMsgBreakStmt(VALID_PTR(pUuid) || pUuid == NULL,
+ ("pUuid=%#p\n", pUuid),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ RTUUID Uuid;
+ if (!pUuid)
+ {
+ RTUuidCreate(&Uuid);
+ pUuid = &Uuid;
+ }
+ rc = pImage->Backend->pfnSetUuid(pImage->pvBackendData, pUuid);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Get last modification UUID of image in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pUuid Where to store the image modification UUID.
+ */
+VBOXDDU_DECL(int) VDGetModificationUuid(PVBOXHDD pDisk, unsigned nImage, PRTUUID pUuid)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p\n", pDisk, nImage, pUuid));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pUuid),
+ ("pUuid=%#p\n", pUuid),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ rc = pImage->Backend->pfnGetModificationUuid(pImage->pvBackendData,
+ pUuid);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc, Uuid={%RTuuid}\n", rc, pUuid));
+ return rc;
+}
+
+/**
+ * Set the image's last modification UUID. Should not be used by normal applications.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pUuid New modification UUID of the image. If NULL, a new UUID is created.
+ */
+VBOXDDU_DECL(int) VDSetModificationUuid(PVBOXHDD pDisk, unsigned nImage, PCRTUUID pUuid)
+{
+ int rc;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p {%RTuuid}\n",
+ pDisk, nImage, pUuid, pUuid));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pUuid) || pUuid == NULL,
+ ("pUuid=%#p\n", pUuid),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ RTUUID Uuid;
+ if (!pUuid)
+ {
+ RTUuidCreate(&Uuid);
+ pUuid = &Uuid;
+ }
+ rc = pImage->Backend->pfnSetModificationUuid(pImage->pvBackendData,
+ pUuid);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Get parent UUID of image in HDD container.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pUuid Where to store the parent image UUID.
+ */
+VBOXDDU_DECL(int) VDGetParentUuid(PVBOXHDD pDisk, unsigned nImage,
+ PRTUUID pUuid)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p\n", pDisk, nImage, pUuid));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pUuid),
+ ("pUuid=%#p\n", pUuid),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ rc = pImage->Backend->pfnGetParentUuid(pImage->pvBackendData, pUuid);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc, Uuid={%RTuuid}\n", rc, pUuid));
+ return rc;
+}
+
+/**
+ * Set the image's parent UUID. Should not be used by normal applications.
+ *
+ * @returns VBox status code.
+ * @param pDisk Pointer to HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pUuid New parent UUID of the image. If NULL, a new UUID is created.
+ */
+VBOXDDU_DECL(int) VDSetParentUuid(PVBOXHDD pDisk, unsigned nImage,
+ PCRTUUID pUuid)
+{
+ int rc;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p {%RTuuid}\n",
+ pDisk, nImage, pUuid, pUuid));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pUuid) || pUuid == NULL,
+ ("pUuid=%#p\n", pUuid),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ RTUUID Uuid;
+ if (!pUuid)
+ {
+ RTUuidCreate(&Uuid);
+ pUuid = &Uuid;
+ }
+ rc = pImage->Backend->pfnSetParentUuid(pImage->pvBackendData, pUuid);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Debug helper - dumps all opened images in HDD container into the log file.
+ *
+ * @param pDisk Pointer to HDD container.
+ */
+VBOXDDU_DECL(void) VDDumpImages(PVBOXHDD pDisk)
+{
+ do
+ {
+ /* sanity check */
+ AssertPtrBreak(pDisk);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ RTLogPrintf("--- Dumping VD Disk, Images=%u\n", pDisk->cImages);
+ for (PVDIMAGE pImage = pDisk->pBase; pImage; pImage = pImage->pNext)
+ {
+ RTLogPrintf("Dumping VD image \"%s\" (Backend=%s)\n",
+ pImage->pszFilename, pImage->Backend->pszBackendName);
+ pImage->Backend->pfnDump(pImage->pvBackendData);
+ }
+ } while (0);
+}
+
+/**
+ * Query if asynchronous operations are supported for this disk.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened.
+ * @param pDisk Pointer to the HDD container.
+ * @param nImage Image number, counts from 0. 0 is always base image of container.
+ * @param pfAIOSupported Where to store if async IO is supported.
+ */
+VBOXDDU_DECL(int) VDImageIsAsyncIOSupported(PVBOXHDD pDisk, unsigned nImage, bool *pfAIOSupported)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pfAIOSupported=%#p\n", pDisk, nImage, pfAIOSupported));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(VALID_PTR(pfAIOSupported),
+ ("pfAIOSupported=%#p\n", pfAIOSupported),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ if (pImage->Backend->uBackendCaps & VD_CAP_ASYNC)
+ *pfAIOSupported = pImage->Backend->pfnIsAsyncIOSupported(pImage->pvBackendData);
+ else
+ *pfAIOSupported = false;
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc, fAIOSupported=%u\n", rc, *pfAIOSupported));
+ return rc;
+}
+
+/**
+ * Start a asynchronous read request.
+ *
+ * @returns VBox status code.
+ * @param pDisk Pointer to the HDD container.
+ * @param uOffset The offset of the virtual disk to read from.
+ * @param cbRead How many bytes to read.
+ * @param paSeg Pointer to an array of segments.
+ * @param cSeg Number of segments in the array.
+ * @param pvUser User data which is passed on completion
+ */
+VBOXDDU_DECL(int) VDAsyncRead(PVBOXHDD pDisk, uint64_t uOffset, size_t cbRead,
+ PPDMDATASEG paSeg, unsigned cSeg,
+ void *pvUser)
+{
+ int rc = VERR_VD_BLOCK_FREE;
+
+ LogFlowFunc(("pDisk=%#p uOffset=%llu paSeg=%p cSeg=%u cbRead=%zu\n",
+ pDisk, uOffset, paSeg, cSeg, cbRead));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(cbRead,
+ ("cbRead=%zu\n", cbRead),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(uOffset + cbRead <= pDisk->cbSize,
+ ("uOffset=%llu cbRead=%zu pDisk->cbSize=%llu\n",
+ uOffset, cbRead, pDisk->cbSize),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(VALID_PTR(paSeg),
+ ("paSeg=%#p\n", paSeg),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(cSeg,
+ ("cSeg=%zu\n", cSeg),
+ rc = VERR_INVALID_PARAMETER);
+
+
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED);
+
+ /* @todo: This does not work for images which do not have all meta data in memory. */
+ for (PVDIMAGE pCurrImage = pImage;
+ pCurrImage != NULL && rc == VERR_VD_BLOCK_FREE;
+ pCurrImage = pCurrImage->pPrev)
+ {
+ rc = pCurrImage->Backend->pfnAsyncRead(pCurrImage->pvBackendData,
+ uOffset, cbRead, paSeg, cSeg,
+ pvUser);
+ }
+
+ /* No image in the chain contains the data for the block. */
+ if (rc == VERR_VD_BLOCK_FREE)
+ {
+ for (unsigned i = 0; i < cSeg && (cbRead > 0); i++)
+ {
+ memset(paSeg[i].pvSeg, '\0', paSeg[i].cbSeg);
+ cbRead -= paSeg[i].cbSeg;
+ }
+ /* Request finished without the need to enqueue a async I/O request. Tell caller. */
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ }
+
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Start a asynchronous write request.
+ *
+ * @returns VBox status code.
+ * @param pDisk Pointer to the HDD container.
+ * @param uOffset The offset of the virtual disk to write to.
+ * @param cbWrtie How many bytes to write.
+ * @param paSeg Pointer to an array of segments.
+ * @param cSeg Number of segments in the array.
+ * @param pvUser User data which is passed on completion.
+ */
+VBOXDDU_DECL(int) VDAsyncWrite(PVBOXHDD pDisk, uint64_t uOffset, size_t cbWrite,
+ PPDMDATASEG paSeg, unsigned cSeg,
+ void *pvUser)
+{
+ int rc;
+
+ LogFlowFunc(("pDisk=%#p uOffset=%llu paSeg=%p cSeg=%u cbWrite=%zu\n",
+ pDisk, uOffset, paSeg, cSeg, cbWrite));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VBOXHDDDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgBreakStmt(cbWrite,
+ ("cbWrite=%zu\n", cbWrite),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(uOffset + cbWrite <= pDisk->cbSize,
+ ("uOffset=%llu cbWrite=%zu pDisk->cbSize=%llu\n",
+ uOffset, cbWrite, pDisk->cbSize),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(VALID_PTR(paSeg),
+ ("paSeg=%#p\n", paSeg),
+ rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(cSeg,
+ ("cSeg=%zu\n", cSeg),
+ rc = VERR_INVALID_PARAMETER);
+
+
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED);
+
+ vdSetModifiedFlag(pDisk);
+ rc = pImage->Backend->pfnAsyncWrite(pImage->pvBackendData,
+ uOffset, cbWrite,
+ paSeg, cSeg, pvUser);
+ } while (0);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+
+}
+
+#if 0
+/** @copydoc VBOXHDDBACKEND::pfnComposeLocation */
+int genericFileComposeLocation(PVDINTERFACE pConfig, char **pszLocation)
+{
+ return NULL;
+}
+
+
+/** @copydoc VBOXHDDBACKEND::pfnComposeName */
+int genericFileComposeName(PVDINTERFACE pConfig, char **pszName)
+{
+ return NULL;
+}
+#endif
diff --git a/src/VBox/Devices/Storage/VBoxHDD-newInternal.h b/src/VBox/Devices/Storage/VBoxHDD-newInternal.h
new file mode 100644
index 000000000..fe0a1b771
--- /dev/null
+++ b/src/VBox/Devices/Storage/VBoxHDD-newInternal.h
@@ -0,0 +1,540 @@
+/** @file
+ * Internal header file for VBox HDD Container.
+ */
+
+/*
+ * Copyright (C) 2006-2008 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+#ifndef __VBoxHDD_newInternal_h__
+
+
+#include <VBox/pdm.h>
+#include <VBox/VBoxHDD-new.h>
+
+
+/** @name VBox HDD backend write flags
+ * @{
+ */
+/** Do not allocate a new block on this write. This is just an advisory
+ * flag. The backend may still decide in some circumstances that it wants
+ * to ignore this flag (which may cause extra dynamic image expansion). */
+#define VD_WRITE_NO_ALLOC RT_BIT(1)
+/** @}*/
+
+
+/**
+ * Image format backend interface used by VBox HDD Container implementation.
+ */
+typedef struct VBOXHDDBACKEND
+{
+ /**
+ * The name of the backend (constant string).
+ */
+ const char *pszBackendName;
+
+ /**
+ * The size of the structure.
+ */
+ uint32_t cbSize;
+
+ /**
+ * The capabilities of the backend.
+ */
+ uint64_t uBackendCaps;
+
+ /**
+ * Pointer to a NULL-terminated array of strings, containing the supported
+ * file extensions. Note that some backends do not work on files, so this
+ * pointer may just contain NULL.
+ */
+ const char * const *papszFileExtensions;
+
+ /**
+ * Pointer to an array of structs describing each supported config key.
+ * Terminated by a NULL config key. Note that some backends do not support
+ * the configuration interface, so this pointer may just contain NULL.
+ * Mandatory if the backend sets VD_CAP_CONFIG.
+ */
+ PCVDCONFIGINFO paConfigInfo;
+
+ /**
+ * Handle of loaded plugin library, NIL_RTLDRMOD for static backends.
+ */
+ RTLDRMOD hPlugin;
+
+ /**
+ * Check if a file is valid for the backend.
+ *
+ * @returns VBox status code.
+ * @param pszFilename Name of the image file.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnCheckIfValid, (const char *pszFilename));
+
+ /**
+ * Open a disk image.
+ *
+ * @returns VBox status code.
+ * @param pszFilename Name of the image file to open. Guaranteed to be available and
+ * unchanged during the lifetime of this image.
+ * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants.
+ * @param pVDIfsDisk Pointer to the per-disk VD interface list.
+ * @param pVDIfsImage Pointer to the per-image VD interface list.
+ * @param ppvBackendData Opaque state data for this image.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnOpen, (const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ void **ppvBackendData));
+
+ /**
+ * Create a disk image.
+ *
+ * @returns VBox status code.
+ * @param pszFilename Name of the image file to create. Guaranteed to be available and
+ * unchanged during the lifetime of this image.
+ * @param enmType Image type. Both base and diff image types are valid.
+ * @param cbSize Image size in bytes.
+ * @param uImageFlags Flags specifying special image features.
+ * @param pszComment Pointer to image comment. NULL is ok.
+ * @param pPCHSGeometry Physical drive geometry CHS <= (16383,16,255).
+ * @param pLCHSGeometry Logical drive geometry CHS <= (1024,255,63).
+ * @param pUuid New UUID of the image. Not NULL.
+ * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants.
+ * @param uPercentStart Starting value for progress percentage.
+ * @param uPercentSpan Span for varying progress percentage.
+ * @param pVDIfsDisk Pointer to the per-disk VD interface list.
+ * @param pVDIfsImage Pointer to the per-image VD interface list.
+ * @param pVDIfsOperation Pointer to the per-operation VD interface list.
+ * @param ppvBackendData Opaque state data for this image.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnCreate, (const char *pszFilename, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ const char *pszComment,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation,
+ void **ppvBackendData));
+
+ /**
+ * Rename a disk image. Only needs to work as long as the operating
+ * system's rename file functionality is usable. If an attempt is made to
+ * rename an image to a location on another disk/filesystem, this function
+ * may just fail with an appropriate error code (not changing the opened
+ * image data at all). Also works only on images which actually refer to
+ * files (and not for raw disk images).
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pszFilename New name of the image file. Guaranteed to be available and
+ * unchanged during the lifetime of this image.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnRename, (void *pvBackendData, const char *pszFilename));
+
+ /**
+ * Close a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param fDelete If true, delete the image from the host disk.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnClose, (void *pvBackendData, bool fDelete));
+
+ /**
+ * Read data from a disk image. The area read never crosses a block
+ * boundary.
+ *
+ * @returns VBox status code.
+ * @returns VINF_VD_BLOCK_FREE if this image contains no data for this block.
+ * @returns VINF_VD_BLOCK_ZERO if this image contains a zero data block.
+ * @param pvBackendData Opaque state data for this image.
+ * @param off Offset to start reading from.
+ * @param pvBuf Where to store the read bits.
+ * @param cbRead Number of bytes to read.
+ * @param pcbActuallyRead Pointer to returned number of bytes read.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnRead, (void *pvBackendData, uint64_t off, void *pvBuf,
+ size_t cbRead, size_t *pcbActuallyRead));
+
+ /**
+ * Write data to a disk image. The area written never crosses a block
+ * boundary.
+ *
+ * @returns VBox status code.
+ * @returns VINF_VD_BLOCK_FREE if this image contains no data for this block and
+ * this is not a full-block write. The write must be repeated with
+ * the correct amount of prefix/postfix data read from the images below
+ * in the image stack. This might not be the most convenient interface,
+ * but it works with arbitrary block sizes, especially when the image
+ * stack uses different block sizes.
+ * @param pvBackendData Opaque state data for this image.
+ * @param off Offset to start writing to.
+ * @param pvBuf Where to retrieve the written bits.
+ * @param cbWrite Number of bytes to write.
+ * @param pcbWriteProcess Pointer to returned number of bytes that could
+ * be processed. In case the function returned
+ * VINF_VD_BLOCK_FREE this is the number of bytes
+ * that could be written in a full block write,
+ * when prefixed/postfixed by the appropriate
+ * amount of (previously read) padding data.
+ * @param pcbPreRead Pointer to the returned amount of data that must
+ * be prefixed to perform a full block write.
+ * @param pcbPostRead Pointer to the returned amount of data that must
+ * be postfixed to perform a full block write.
+ * @param fWrite Flags which affect write behavior. Combination
+ * of the VD_WRITE_* flags.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnWrite, (void *pvBackendData, uint64_t off,
+ const void *pvBuf, size_t cbWrite,
+ size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite));
+
+ /**
+ * Flush data to disk.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnFlush, (void *pvBackendData));
+
+ /**
+ * Get the version of a disk image.
+ *
+ * @returns version of disk image.
+ * @param pvBackendData Opaque state data for this image.
+ */
+ DECLR3CALLBACKMEMBER(unsigned, pfnGetVersion, (void *pvBackendData));
+
+ /**
+ * Get the type information for a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param penmType Image type of this image.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetImageType, (void *pvBackendData, PVDIMAGETYPE penmType));
+
+ /**
+ * Get the capacity of a disk image.
+ *
+ * @returns size of disk image in bytes.
+ * @param pvBackendData Opaque state data for this image.
+ */
+ DECLR3CALLBACKMEMBER(uint64_t, pfnGetSize, (void *pvBackendData));
+
+ /**
+ * Get the file size of a disk image.
+ *
+ * @returns size of disk image in bytes.
+ * @param pvBackendData Opaque state data for this image.
+ */
+ DECLR3CALLBACKMEMBER(uint64_t, pfnGetFileSize, (void *pvBackendData));
+
+ /**
+ * Get virtual disk PCHS geometry stored in a disk image.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_GEOMETRY_NOT_SET if no geometry present in the image.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pPCHSGeometry Where to store the geometry. Not NULL.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetPCHSGeometry, (void *pvBackendData, PPDMMEDIAGEOMETRY pPCHSGeometry));
+
+ /**
+ * Set virtual disk PCHS geometry stored in a disk image.
+ * Only called if geometry is different than before.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pPCHSGeometry Where to load the geometry from. Not NULL.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetPCHSGeometry, (void *pvBackendData, PCPDMMEDIAGEOMETRY pPCHSGeometry));
+
+ /**
+ * Get virtual disk LCHS geometry stored in a disk image.
+ *
+ * @returns VBox status code.
+ * @returns VERR_VD_GEOMETRY_NOT_SET if no geometry present in the image.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pLCHSGeometry Where to store the geometry. Not NULL.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetLCHSGeometry, (void *pvBackendData, PPDMMEDIAGEOMETRY pLCHSGeometry));
+
+ /**
+ * Set virtual disk LCHS geometry stored in a disk image.
+ * Only called if geometry is different than before.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pLCHSGeometry Where to load the geometry from. Not NULL.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetLCHSGeometry, (void *pvBackendData, PCPDMMEDIAGEOMETRY pLCHSGeometry));
+
+ /**
+ * Get the image flags of a disk image.
+ *
+ * @returns image flags of disk image.
+ * @param pvBackendData Opaque state data for this image.
+ */
+ DECLR3CALLBACKMEMBER(unsigned, pfnGetImageFlags, (void *pvBackendData));
+
+ /**
+ * Get the open flags of a disk image.
+ *
+ * @returns open flags of disk image.
+ * @param pvBackendData Opaque state data for this image.
+ */
+ DECLR3CALLBACKMEMBER(unsigned, pfnGetOpenFlags, (void *pvBackendData));
+
+ /**
+ * Set the open flags of a disk image. May cause the image to be locked
+ * in a different mode or be reopened (which can fail).
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param uOpenFlags New open flags for this image.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetOpenFlags, (void *pvBackendData, unsigned uOpenFlags));
+
+ /**
+ * Get comment of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pszComment Where to store the comment.
+ * @param cbComment Size of the comment buffer.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetComment, (void *pvBackendData, char *pszComment, size_t cbComment));
+
+ /**
+ * Set comment of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pszComment Where to get the comment from. NULL resets comment.
+ * The comment is silently truncated if the image format
+ * limit is exceeded.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetComment, (void *pvBackendData, const char *pszComment));
+
+ /**
+ * Get UUID of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pUuid Where to store the image UUID.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetUuid, (void *pvBackendData, PRTUUID pUuid));
+
+ /**
+ * Set UUID of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pUuid Where to get the image UUID from.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetUuid, (void *pvBackendData, PCRTUUID pUuid));
+
+ /**
+ * Get last modification UUID of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pUuid Where to store the image modification UUID.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetModificationUuid, (void *pvBackendData, PRTUUID pUuid));
+
+ /**
+ * Set last modification UUID of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pUuid Where to get the image modification UUID from.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetModificationUuid, (void *pvBackendData, PCRTUUID pUuid));
+
+ /**
+ * Get parent UUID of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pUuid Where to store the parent image UUID.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetParentUuid, (void *pvBackendData, PRTUUID pUuid));
+
+ /**
+ * Set parent UUID of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pUuid Where to get the parent image UUID from.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetParentUuid, (void *pvBackendData, PCRTUUID pUuid));
+
+ /**
+ * Get parent modification UUID of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pUuid Where to store the parent image modification UUID.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetParentModificationUuid, (void *pvBackendData, PRTUUID pUuid));
+
+ /**
+ * Set parent modification UUID of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pUuid Where to get the parent image modification UUID from.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetParentModificationUuid, (void *pvBackendData, PCRTUUID pUuid));
+
+ /**
+ * Dump information about a disk image.
+ *
+ * @param pvBackendData Opaque state data for this image.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnDump, (void *pvBackendData));
+
+ /**
+ * Get a time stamp of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pTimeStamp Where to store the time stamp.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetTimeStamp, (void *pvBackendData, PRTTIMESPEC pTimeStamp));
+
+ /**
+ * Get the parent time stamp of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pTimeStamp Where to store the time stamp.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetParentTimeStamp, (void *pvBackendData, PRTTIMESPEC pTimeStamp));
+
+ /**
+ * Set the parent time stamp of a disk image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pTimeStamp Where to get the time stamp from.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetParentTimeStamp, (void *pvBackendData, PCRTTIMESPEC pTimeStamp));
+
+ /**
+ * Get the relative path to parent image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pszParentFilename Where to store the path.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGetParentFilename, (void *pvBackendData, char **ppszParentFilename));
+
+ /**
+ * Set the relative path to parent image.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param pszParentFilename Where to get the path from.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetParentFilename, (void *pvBackendData, const char *pszParentFilename));
+
+ /**
+ * Return whether asynchronous I/O operations are supported for this image.
+ *
+ * @returns true if asynchronous I/O is supported
+ * false otherwise.
+ * @param pvBackendData Opaque state data for this image.
+ */
+ DECLR3CALLBACKMEMBER(bool, pfnIsAsyncIOSupported, (void *pvBackendData));
+
+ /**
+ * Start an asynchronous read request.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param uOffset The offset of the virtual disk to read from.
+ * @param cbRead How many bytes to read.
+ * @param paSeg Pointer to the segment array.
+ * @param cSeg Number of segments.
+ * @param pvUser Opaque user data.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnAsyncRead, (void *pvBackendData, uint64_t uOffset, size_t cbRead,
+ PPDMDATASEG paSeg, unsigned cSeg, void *pvUser));
+
+ /**
+ * Start an asynchronous write request.
+ *
+ * @returns VBox status code.
+ * @param pvBackendData Opaque state data for this image.
+ * @param uOffset The offset of the virtual disk to write to.
+ * @param cbWrite How many bytes to write.
+ * @param paSeg Pointer to the segment array.
+ * @param cSeg Number of segments.
+ * @param pvUser Oaque user data-
+ */
+ DECLR3CALLBACKMEMBER(int, pfnAsyncWrite, (void *pvBackendData, uint64_t uOffset, size_t cbWrite,
+ PPDMDATASEG paSeg, unsigned cSeg, void *pvUser));
+
+ /** Returns a human readable hard disk location string given a
+ * set of hard disk configuration keys. The returned string is an
+ * equivalent of the full file path for image-based hard disks.
+ * Mandatory for backends with no VD_CAP_FILE and NULL otherwise. */
+ DECLR3CALLBACKMEMBER(int, pfnComposeLocation, (PVDINTERFACE pConfig, char **pszLocation));
+
+ /** Returns a human readable hard disk name string given a
+ * set of hard disk configuration keys. The returned string is an
+ * equivalent of the file name part in the full file path for
+ * image-based hard disks. Mandatory for backends with no
+ * VD_CAP_FILE and NULL otherwise. */
+ DECLR3CALLBACKMEMBER(int, pfnComposeName, (PVDINTERFACE pConfig, char **pszName));
+
+} VBOXHDDBACKEND;
+
+/** Pointer to VD backend. */
+typedef VBOXHDDBACKEND *PVBOXHDDBACKEND;
+
+/** Constant pointer to VD backend. */
+typedef const VBOXHDDBACKEND *PCVBOXHDDBACKEND;
+
+/** @copydoc VBOXHDDBACKEND::pfnComposeLocation */
+DECLINLINE(int) genericFileComposeLocation(PVDINTERFACE pConfig, char **pszLocation)
+{
+ *pszLocation = NULL;
+ return VINF_SUCCESS;
+}
+/** @copydoc VBOXHDDBACKEND::pfnComposeName */
+DECLINLINE(int) genericFileComposeName(PVDINTERFACE pConfig, char **pszName)
+{
+ *pszName = NULL;
+ return VINF_SUCCESS;
+}
+
+/** Initialization entry point. */
+typedef DECLCALLBACK(int) VBOXHDDFORMATLOAD(PVBOXHDDBACKEND *ppBackendTable);
+typedef VBOXHDDFORMATLOAD *PFNVBOXHDDFORMATLOAD;
+#define VBOX_HDDFORMAT_LOAD_NAME "VBoxHDDFormatLoad"
+
+/** The prefix to identify Storage Plugins. */
+#define VBOX_HDDFORMAT_PLUGIN_PREFIX "VBoxHDD"
+/** The size of the prefix excluding the '\0' terminator. */
+#define VBOX_HDDFORMAT_PLUGIN_PREFIX_LENGTH (sizeof(VBOX_HDDFORMAT_PLUGIN_PREFIX)-1)
+
+#endif
diff --git a/src/VBox/Devices/Storage/VDICore.h b/src/VBox/Devices/Storage/VDICore.h
new file mode 100644
index 000000000..e6166ca7b
--- /dev/null
+++ b/src/VBox/Devices/Storage/VDICore.h
@@ -0,0 +1,655 @@
+/* $Id: VDICore.h 12639 2008-09-22 13:19:14Z vboxsync $ */
+/** @file
+ * Virtual Disk Image (VDI), Core Code Header (internal).
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+#ifndef __VDICore_h__
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#ifndef VBOX_VDICORE_VD
+#include <VBox/VBoxHDD.h>
+#else /* VBOX_VDICORE_VD */
+#include <VBox/VBoxHDD-new.h>
+#endif /* VBOX_VDICORE_VD */
+#include <VBox/pdm.h>
+#include <VBox/mm.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+
+
+/*******************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*******************************************************************************/
+
+/** Image info, not handled anyhow.
+ * Must be less than 64 bytes in length, including the trailing 0.
+ */
+#define VDI_IMAGE_FILE_INFO "<<< Sun xVM VirtualBox Disk Image >>>\n"
+
+/** The Sector size.
+ * Currently we support only 512 bytes sectors.
+ */
+#define VDI_GEOMETRY_SECTOR_SIZE (512)
+/** 512 = 2^^9 */
+#define VDI_GEOMETRY_SECTOR_SHIFT (9)
+
+/**
+ * Harddisk geometry.
+ */
+#pragma pack(1)
+typedef struct VDIDISKGEOMETRY
+{
+ /** Cylinders. */
+ uint32_t cCylinders;
+ /** Heads. */
+ uint32_t cHeads;
+ /** Sectors per track. */
+ uint32_t cSectors;
+ /** Sector size. (bytes per sector) */
+ uint32_t cbSector;
+} VDIDISKGEOMETRY, *PVDIDISKGEOMETRY;
+#pragma pack()
+
+/** Image signature. */
+#define VDI_IMAGE_SIGNATURE (0xbeda107f)
+
+/**
+ * Pre-Header to be stored in image file - used for version control.
+ */
+#pragma pack(1)
+typedef struct VDIPREHEADER
+{
+ /** Just text info about image type, for eyes only. */
+ char szFileInfo[64];
+ /** The image signature (VDI_IMAGE_SIGNATURE). */
+ uint32_t u32Signature;
+ /** The image version (VDI_IMAGE_VERSION). */
+ uint32_t u32Version;
+} VDIPREHEADER, *PVDIPREHEADER;
+#pragma pack()
+
+/**
+ * Size of szComment field of HDD image header.
+ */
+#define VDI_IMAGE_COMMENT_SIZE 256
+
+/**
+ * Header to be stored in image file, VDI_IMAGE_VERSION_MAJOR = 0.
+ * Prepended by VDIPREHEADER.
+ */
+#pragma pack(1)
+typedef struct VDIHEADER0
+{
+ /** The image type (VDI_IMAGE_TYPE_*). */
+ uint32_t u32Type;
+ /** Image flags (VDI_IMAGE_FLAGS_*). */
+ uint32_t fFlags;
+ /** Image comment. (UTF-8) */
+ char szComment[VDI_IMAGE_COMMENT_SIZE];
+ /** Legacy image geometry (previous code stored PCHS there). */
+ VDIDISKGEOMETRY LegacyGeometry;
+ /** Size of disk (in bytes). */
+ uint64_t cbDisk;
+ /** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) */
+ uint32_t cbBlock;
+ /** Number of blocks. */
+ uint32_t cBlocks;
+ /** Number of allocated blocks. */
+ uint32_t cBlocksAllocated;
+ /** UUID of image. */
+ RTUUID uuidCreate;
+ /** UUID of image's last modification. */
+ RTUUID uuidModify;
+ /** Only for secondary images - UUID of primary image. */
+ RTUUID uuidLinkage;
+} VDIHEADER0, *PVDIHEADER0;
+#pragma pack()
+
+/**
+ * Header to be stored in image file, VDI_IMAGE_VERSION_MAJOR = 1,
+ * VDI_IMAGE_VERSION_MINOR = 1. Prepended by VDIPREHEADER.
+ */
+#pragma pack(1)
+typedef struct VDIHEADER1
+{
+ /** Size of this structure in bytes. */
+ uint32_t cbHeader;
+ /** The image type (VDI_IMAGE_TYPE_*). */
+ uint32_t u32Type;
+ /** Image flags (VDI_IMAGE_FLAGS_*). */
+ uint32_t fFlags;
+ /** Image comment. (UTF-8) */
+ char szComment[VDI_IMAGE_COMMENT_SIZE];
+ /** Offset of Blocks array from the begining of image file.
+ * Should be sector-aligned for HDD access optimization. */
+ uint32_t offBlocks;
+ /** Offset of image data from the begining of image file.
+ * Should be sector-aligned for HDD access optimization. */
+ uint32_t offData;
+ /** Legacy image geometry (previous code stored PCHS there). */
+ VDIDISKGEOMETRY LegacyGeometry;
+ /** Was BIOS HDD translation mode, now unused. */
+ uint32_t u32Dummy;
+ /** Size of disk (in bytes). */
+ uint64_t cbDisk;
+ /** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) Should be a power of 2! */
+ uint32_t cbBlock;
+ /** Size of additional service information of every data block.
+ * Prepended before block data. May be 0.
+ * Should be a power of 2 and sector-aligned for optimization reasons. */
+ uint32_t cbBlockExtra;
+ /** Number of blocks. */
+ uint32_t cBlocks;
+ /** Number of allocated blocks. */
+ uint32_t cBlocksAllocated;
+ /** UUID of image. */
+ RTUUID uuidCreate;
+ /** UUID of image's last modification. */
+ RTUUID uuidModify;
+ /** Only for secondary images - UUID of previous image. */
+ RTUUID uuidLinkage;
+ /** Only for secondary images - UUID of previous image's last modification. */
+ RTUUID uuidParentModify;
+} VDIHEADER1, *PVDIHEADER1;
+#pragma pack()
+
+/**
+ * Header to be stored in image file, VDI_IMAGE_VERSION_MAJOR = 1,
+ * VDI_IMAGE_VERSION_MINOR = 1, the slightly changed variant necessary as the
+ * old released code doesn't support changing the minor version at all.
+ */
+#pragma pack(1)
+typedef struct VDIHEADER1PLUS
+{
+ /** Size of this structure in bytes. */
+ uint32_t cbHeader;
+ /** The image type (VDI_IMAGE_TYPE_*). */
+ uint32_t u32Type;
+ /** Image flags (VDI_IMAGE_FLAGS_*). */
+ uint32_t fFlags;
+ /** Image comment. (UTF-8) */
+ char szComment[VDI_IMAGE_COMMENT_SIZE];
+ /** Offset of Blocks array from the begining of image file.
+ * Should be sector-aligned for HDD access optimization. */
+ uint32_t offBlocks;
+ /** Offset of image data from the begining of image file.
+ * Should be sector-aligned for HDD access optimization. */
+ uint32_t offData;
+ /** Legacy image geometry (previous code stored PCHS there). */
+ VDIDISKGEOMETRY LegacyGeometry;
+ /** Was BIOS HDD translation mode, now unused. */
+ uint32_t u32Dummy;
+ /** Size of disk (in bytes). */
+ uint64_t cbDisk;
+ /** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) Should be a power of 2! */
+ uint32_t cbBlock;
+ /** Size of additional service information of every data block.
+ * Prepended before block data. May be 0.
+ * Should be a power of 2 and sector-aligned for optimization reasons. */
+ uint32_t cbBlockExtra;
+ /** Number of blocks. */
+ uint32_t cBlocks;
+ /** Number of allocated blocks. */
+ uint32_t cBlocksAllocated;
+ /** UUID of image. */
+ RTUUID uuidCreate;
+ /** UUID of image's last modification. */
+ RTUUID uuidModify;
+ /** Only for secondary images - UUID of previous image. */
+ RTUUID uuidLinkage;
+ /** Only for secondary images - UUID of previous image's last modification. */
+ RTUUID uuidParentModify;
+ /** LCHS image geometry (new field in VDI1.2 version. */
+ VDIDISKGEOMETRY LCHSGeometry;
+} VDIHEADER1PLUS, *PVDIHEADER1PLUS;
+#pragma pack()
+
+/**
+ * Header structure for all versions.
+ */
+typedef struct VDIHEADER
+{
+ unsigned uVersion;
+ union
+ {
+ VDIHEADER0 v0;
+ VDIHEADER1 v1;
+ VDIHEADER1PLUS v1plus;
+ } u;
+} VDIHEADER, *PVDIHEADER;
+
+/** Block 'pointer'. */
+typedef uint32_t VDIIMAGEBLOCKPOINTER;
+/** Pointer to a block 'pointer'. */
+typedef VDIIMAGEBLOCKPOINTER *PVDIIMAGEBLOCKPOINTER;
+
+/**
+ * Block marked as free is not allocated in image file, read from this
+ * block may returns any random data.
+ */
+#define VDI_IMAGE_BLOCK_FREE ((VDIIMAGEBLOCKPOINTER)~0)
+
+/**
+ * Block marked as zero is not allocated in image file, read from this
+ * block returns zeroes.
+ */
+#define VDI_IMAGE_BLOCK_ZERO ((VDIIMAGEBLOCKPOINTER)~1)
+
+/**
+ * Block 'pointer' >= VDI_IMAGE_BLOCK_UNALLOCATED indicates block is not
+ * allocated in image file.
+ */
+#define VDI_IMAGE_BLOCK_UNALLOCATED (VDI_IMAGE_BLOCK_ZERO)
+#define IS_VDI_IMAGE_BLOCK_ALLOCATED(bp) (bp < VDI_IMAGE_BLOCK_UNALLOCATED)
+
+#define GET_MAJOR_HEADER_VERSION(ph) (VDI_GET_VERSION_MAJOR((ph)->uVersion))
+#define GET_MINOR_HEADER_VERSION(ph) (VDI_GET_VERSION_MINOR((ph)->uVersion))
+
+#ifdef VBOX_VDICORE_VD
+/** @name VDI image types
+ * @{ */
+typedef enum VDIIMAGETYPE
+{
+ /** Normal dynamically growing base image file. */
+ VDI_IMAGE_TYPE_NORMAL = 1,
+ /** Preallocated base image file of a fixed size. */
+ VDI_IMAGE_TYPE_FIXED,
+ /** Dynamically growing image file for undo/commit changes support. */
+ VDI_IMAGE_TYPE_UNDO,
+ /** Dynamically growing image file for differencing support. */
+ VDI_IMAGE_TYPE_DIFF,
+
+ /** First valid image type value. */
+ VDI_IMAGE_TYPE_FIRST = VDI_IMAGE_TYPE_NORMAL,
+ /** Last valid image type value. */
+ VDI_IMAGE_TYPE_LAST = VDI_IMAGE_TYPE_DIFF
+} VDIIMAGETYPE;
+/** Pointer to VDI image type. */
+typedef VDIIMAGETYPE *PVDIIMAGETYPE;
+/** @} */
+#endif /* VBOX_VDICORE_VD */
+
+/*******************************************************************************
+* Internal Functions for header access *
+*******************************************************************************/
+DECLINLINE(VDIIMAGETYPE) getImageType(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return (VDIIMAGETYPE)ph->u.v0.u32Type;
+ case 1: return (VDIIMAGETYPE)ph->u.v1.u32Type;
+ }
+ AssertFailed();
+ return (VDIIMAGETYPE)0;
+}
+
+#ifdef VBOX_VDICORE_VD
+DECLINLINE(unsigned) getImageFlags(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0:
+ /* VDI image flag conversion to VD image flags. */
+ return ph->u.v0.fFlags << 8;
+ case 1:
+ /* VDI image flag conversion to VD image flags. */
+ return ph->u.v1.fFlags << 8;
+ }
+ AssertFailed();
+ return 0;
+}
+#else /* !VBOX_VDICORE_VD */
+DECLINLINE(unsigned) getImageFlags(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return ph->u.v0.fFlags;
+ case 1: return ph->u.v1.fFlags;
+ }
+ AssertFailed();
+ return 0;
+}
+#endif /* !VBOX_VDICORE_VD */
+
+DECLINLINE(char *) getImageComment(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return &ph->u.v0.szComment[0];
+ case 1: return &ph->u.v1.szComment[0];
+ }
+ AssertFailed();
+ return NULL;
+}
+
+DECLINLINE(unsigned) getImageBlocksOffset(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return (sizeof(VDIPREHEADER) + sizeof(VDIHEADER0));
+ case 1: return ph->u.v1.offBlocks;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(unsigned) getImageDataOffset(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return sizeof(VDIPREHEADER) + sizeof(VDIHEADER0) + \
+ (ph->u.v0.cBlocks * sizeof(VDIIMAGEBLOCKPOINTER));
+ case 1: return ph->u.v1.offData;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(PVDIDISKGEOMETRY) getImageLCHSGeometry(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return NULL;
+ case 1:
+ switch (GET_MINOR_HEADER_VERSION(ph))
+ {
+ case 1:
+ if (ph->u.v1.cbHeader < sizeof(ph->u.v1plus))
+ return NULL;
+ else
+ return &ph->u.v1plus.LCHSGeometry;
+ }
+ }
+ AssertFailed();
+ return NULL;
+}
+
+DECLINLINE(uint64_t) getImageDiskSize(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return ph->u.v0.cbDisk;
+ case 1: return ph->u.v1.cbDisk;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(unsigned) getImageBlockSize(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return ph->u.v0.cbBlock;
+ case 1: return ph->u.v1.cbBlock;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(unsigned) getImageExtraBlockSize(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return 0;
+ case 1: return ph->u.v1.cbBlockExtra;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(unsigned) getImageBlocks(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return ph->u.v0.cBlocks;
+ case 1: return ph->u.v1.cBlocks;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(unsigned) getImageBlocksAllocated(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return ph->u.v0.cBlocksAllocated;
+ case 1: return ph->u.v1.cBlocksAllocated;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(void) setImageBlocksAllocated(PVDIHEADER ph, unsigned cBlocks)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: ph->u.v0.cBlocksAllocated = cBlocks; return;
+ case 1: ph->u.v1.cBlocksAllocated = cBlocks; return;
+ }
+ AssertFailed();
+}
+
+DECLINLINE(PRTUUID) getImageCreationUUID(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return &ph->u.v0.uuidCreate;
+ case 1: return &ph->u.v1.uuidCreate;
+ }
+ AssertFailed();
+ return NULL;
+}
+
+DECLINLINE(PRTUUID) getImageModificationUUID(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return &ph->u.v0.uuidModify;
+ case 1: return &ph->u.v1.uuidModify;
+ }
+ AssertFailed();
+ return NULL;
+}
+
+DECLINLINE(PRTUUID) getImageParentUUID(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return &ph->u.v0.uuidLinkage;
+ case 1: return &ph->u.v1.uuidLinkage;
+ }
+ AssertFailed();
+ return NULL;
+}
+
+DECLINLINE(PRTUUID) getImageParentModificationUUID(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 1: return &ph->u.v1.uuidParentModify;
+ }
+ AssertFailed();
+ return NULL;
+}
+
+#ifndef VBOX_VDICORE_VD
+/**
+ * Default image block size, may be changed by setBlockSize/getBlockSize.
+ *
+ * Note: for speed reasons block size should be a power of 2 !
+ */
+#define VDI_IMAGE_DEFAULT_BLOCK_SIZE _1M
+#endif /* !VBOX_VDICORE_VD */
+
+#ifndef VBOX_VDICORE_VD
+/**
+ * fModified bit flags.
+ */
+#define VDI_IMAGE_MODIFIED_FLAG RT_BIT(0)
+#define VDI_IMAGE_MODIFIED_FIRST RT_BIT(1)
+#define VDI_IMAGE_MODIFIED_DISABLE_UUID_UPDATE RT_BIT(2)
+#endif /* !VBOX_VDICORE_VD */
+
+/**
+ * Image structure
+ */
+typedef struct VDIIMAGEDESC
+{
+#ifndef VBOX_VDICORE_VD
+ /** Link to parent image descriptor, if any. */
+ struct VDIIMAGEDESC *pPrev;
+ /** Link to child image descriptor, if any. */
+ struct VDIIMAGEDESC *pNext;
+#endif /* !VBOX_VDICORE_VD */
+ /** File handle. */
+ RTFILE File;
+#ifndef VBOX_VDICORE_VD
+ /** True if the image is operating in readonly mode. */
+ bool fReadOnly;
+ /** Image open flags, VDI_OPEN_FLAGS_*. */
+ unsigned fOpen;
+#else /* VBOX_VDICORE_VD */
+ /** Image open flags, VD__OPEN_FLAGS_*. */
+ unsigned uOpenFlags;
+#endif /* VBOX_VDICORE_VD */
+ /** Image pre-header. */
+ VDIPREHEADER PreHeader;
+ /** Image header. */
+ VDIHEADER Header;
+ /** Pointer to a block array. */
+ PVDIIMAGEBLOCKPOINTER paBlocks;
+#ifndef VBOX_VDICORE_VD
+ /** fFlags copy from image header, for speed optimization. */
+ unsigned fFlags;
+#else /* VBOX_VDICORE_VD */
+ /** fFlags copy from image header, for speed optimization. */
+ unsigned uImageFlags;
+#endif /* VBOX_VDICORE_VD */
+ /** Start offset of block array in image file, here for speed optimization. */
+ unsigned offStartBlocks;
+ /** Start offset of data in image file, here for speed optimization. */
+ unsigned offStartData;
+ /** Block mask for getting the offset into a block from a byte hdd offset. */
+ unsigned uBlockMask;
+ /** Block shift value for converting byte hdd offset into paBlock index. */
+ unsigned uShiftOffset2Index;
+#ifndef VBOX_VDICORE_VD
+ /** Block shift value for converting block index into offset in image. */
+ unsigned uShiftIndex2Offset;
+#endif /* !VBOX_VDICORE_VD */
+ /** Offset of data from the beginning of block. */
+ unsigned offStartBlockData;
+#ifndef VBOX_VDICORE_VD
+ /** Image is modified flags (VDI_IMAGE_MODIFIED*). */
+ unsigned fModified;
+ /** Container filename. (UTF-8)
+ * @todo Make this variable length to save a bunch of bytes. (low prio) */
+ char szFilename[RTPATH_MAX];
+#else /* VBOX_VDICORE_VD */
+ /** Total size of image block (including the extra data). */
+ unsigned cbTotalBlockData;
+ /** Container filename. (UTF-8) */
+ const char *pszFilename;
+ /** Physical geometry of this image (never actually stored). */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Error interface. */
+ PVDINTERFACE pInterfaceError;
+ /** Error interface callback table. */
+ PVDINTERFACEERROR pInterfaceErrorCallbacks;
+#endif /* VBOX_VDICORE_VD */
+} VDIIMAGEDESC, *PVDIIMAGEDESC;
+
+#ifndef VBOX_VDICORE_VD
+/**
+ * Default work buffer size, may be changed by setBufferSize() method.
+ *
+ * For best speed performance it must be equal to image block size.
+ */
+#define VDIDISK_DEFAULT_BUFFER_SIZE (VDI_IMAGE_DEFAULT_BLOCK_SIZE)
+#endif /* !VBOX_VDICORE_VD */
+
+/** VDIDISK Signature. */
+#define VDIDISK_SIGNATURE (0xbedafeda)
+
+/**
+ * VBox HDD Container main structure, private part.
+ */
+struct VDIDISK
+{
+ /** Structure signature (VDIDISK_SIGNATURE). */
+ uint32_t u32Signature;
+
+ /** Number of opened images. */
+ unsigned cImages;
+
+ /** Base image. */
+ PVDIIMAGEDESC pBase;
+
+ /** Last opened image in the chain.
+ * The same as pBase if only one image is used or the last opened diff image. */
+ PVDIIMAGEDESC pLast;
+
+ /** Default block size for newly created images. */
+ unsigned cbBlock;
+
+ /** Working buffer size, allocated only while committing data,
+ * copying block from primary image to secondary and saving previously
+ * zero block. Buffer deallocated after operation complete.
+ * @remark For best performance buffer size must be equal to image's
+ * block size, however it may be decreased for memory saving.
+ */
+ unsigned cbBuf;
+
+ /** Flag whether zero writes should be handled normally or optimized
+ * away if possible. */
+ bool fHonorZeroWrites;
+
+ /** The media interface. */
+ PDMIMEDIA IMedia;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+};
+
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+__BEGIN_DECLS
+
+#ifndef VBOX_VDICORE_VD
+VBOXDDU_DECL(void) vdiInitVDIDisk(PVDIDISK pDisk);
+VBOXDDU_DECL(void) VDIFlushImage(PVDIIMAGEDESC pImage);
+VBOXDDU_DECL(int) vdiChangeImageMode(PVDIIMAGEDESC pImage, bool fReadOnly);
+#endif /* !VBOX_VDICORE_VD */
+
+__END_DECLS
+
+#endif
diff --git a/src/VBox/Devices/Storage/VDIHDDCore.cpp b/src/VBox/Devices/Storage/VDIHDDCore.cpp
new file mode 100644
index 000000000..f708d2a22
--- /dev/null
+++ b/src/VBox/Devices/Storage/VDIHDDCore.cpp
@@ -0,0 +1,1958 @@
+/** @file
+ * Virtual Disk Image (VDI), Core Code.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_VDI
+#include "VBoxHDD-newInternal.h"
+#define VBOX_VDICORE_VD /* Signal that the header is included from here. */
+#include "VDICore.h"
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+
+#define VDI_IMAGE_DEFAULT_BLOCK_SIZE _1M
+
+/*******************************************************************************
+* Static Variables *
+*******************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const char *const s_apszVdiFileExtensions[] =
+{
+ "vdi",
+ NULL
+};
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+static unsigned getPowerOfTwo(unsigned uNumber);
+static void vdiInitPreHeader(PVDIPREHEADER pPreHdr);
+static int vdiValidatePreHeader(PVDIPREHEADER pPreHdr);
+static void vdiInitHeader(PVDIHEADER pHeader, VDIMAGETYPE enmType,
+ uint32_t uImageFlags, const char *pszComment,
+ uint64_t cbDisk, uint32_t cbBlock,
+ uint32_t cbBlockExtra);
+static int vdiValidateHeader(PVDIHEADER pHeader);
+static void vdiSetupImageDesc(PVDIIMAGEDESC pImage);
+static int vdiUpdateHeader(PVDIIMAGEDESC pImage);
+static int vdiUpdateBlockInfo(PVDIIMAGEDESC pImage, unsigned uBlock);
+static void vdiFreeImage(PVDIIMAGEDESC pImage, bool fDelete);
+
+
+/**
+ * Internal: signal an error to the frontend.
+ */
+DECLINLINE(int) vdiError(PVDIIMAGEDESC pImage, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pImage->pInterfaceError && pImage->pInterfaceErrorCallbacks)
+ pImage->pInterfaceErrorCallbacks->pfnError(pImage->pInterfaceError->pvUser,
+ rc, RT_SRC_POS_ARGS,
+ pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * internal: return power of 2 or 0 if num error.
+ */
+static unsigned getPowerOfTwo(unsigned uNumber)
+{
+ if (uNumber == 0)
+ return 0;
+ unsigned uPower2 = 0;
+ while ((uNumber & 1) == 0)
+ {
+ uNumber >>= 1;
+ uPower2++;
+ }
+ return uNumber == 1 ? uPower2 : 0;
+}
+
+
+/**
+ * Internal: Init VDI preheader.
+ */
+static void vdiInitPreHeader(PVDIPREHEADER pPreHdr)
+{
+ pPreHdr->u32Signature = VDI_IMAGE_SIGNATURE;
+ pPreHdr->u32Version = VDI_IMAGE_VERSION;
+ memset(pPreHdr->szFileInfo, 0, sizeof(pPreHdr->szFileInfo));
+ strncat(pPreHdr->szFileInfo, VDI_IMAGE_FILE_INFO, sizeof(pPreHdr->szFileInfo));
+}
+
+/**
+ * Internal: check VDI preheader.
+ */
+static int vdiValidatePreHeader(PVDIPREHEADER pPreHdr)
+{
+ if (pPreHdr->u32Signature != VDI_IMAGE_SIGNATURE)
+ return VERR_VD_VDI_INVALID_HEADER;
+
+ if ( VDI_GET_VERSION_MAJOR(pPreHdr->u32Version) != VDI_IMAGE_VERSION_MAJOR
+ && pPreHdr->u32Version != 0x00000002) /* old version. */
+ return VERR_VD_VDI_UNSUPPORTED_VERSION;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: translate VD image type enum to VDI image type enum.
+ */
+static VDIIMAGETYPE vdiTranslateTypeVD2VDI(VDIMAGETYPE enmType)
+{
+ switch (enmType)
+ {
+ case VD_IMAGE_TYPE_NORMAL:
+ return VDI_IMAGE_TYPE_NORMAL;
+ case VD_IMAGE_TYPE_FIXED:
+ return VDI_IMAGE_TYPE_FIXED;
+ case VD_IMAGE_TYPE_UNDO:
+ return VDI_IMAGE_TYPE_UNDO;
+ case VD_IMAGE_TYPE_DIFF:
+ return VDI_IMAGE_TYPE_DIFF;
+ default:
+ AssertMsgFailed(("invalid VDIMAGETYPE enmType=%d\n", (int)enmType));
+ return VDI_IMAGE_TYPE_NORMAL;
+ }
+}
+
+/**
+ * Internal: translate VDI image type enum to VD image type enum.
+ */
+static VDIMAGETYPE vdiTranslateTypeVDI2VD(VDIIMAGETYPE enmType)
+{
+ switch (enmType)
+ {
+ case VDI_IMAGE_TYPE_NORMAL:
+ return VD_IMAGE_TYPE_NORMAL;
+ case VDI_IMAGE_TYPE_FIXED:
+ return VD_IMAGE_TYPE_FIXED;
+ case VDI_IMAGE_TYPE_UNDO:
+ return VD_IMAGE_TYPE_UNDO;
+ case VDI_IMAGE_TYPE_DIFF:
+ return VD_IMAGE_TYPE_DIFF;
+ default:
+ AssertMsgFailed(("invalid VDIIMAGETYPE enmType=%d\n", (int)enmType));
+ return VD_IMAGE_TYPE_INVALID;
+ }
+}
+
+/**
+ * Internal: Init VDI header. Always use latest header version.
+ * @param pHeader Assumes it was initially initialized to all zeros.
+ */
+static void vdiInitHeader(PVDIHEADER pHeader, VDIMAGETYPE enmType,
+ uint32_t uImageFlags, const char *pszComment,
+ uint64_t cbDisk, uint32_t cbBlock,
+ uint32_t cbBlockExtra)
+{
+ pHeader->uVersion = VDI_IMAGE_VERSION;
+ pHeader->u.v1.cbHeader = sizeof(VDIHEADER1);
+ pHeader->u.v1.u32Type = (uint32_t)vdiTranslateTypeVD2VDI(enmType);
+ pHeader->u.v1.fFlags = (uImageFlags & VD_VDI_IMAGE_FLAGS_ZERO_EXPAND) ? 1 : 0;
+#ifdef VBOX_STRICT
+ char achZero[VDI_IMAGE_COMMENT_SIZE] = {0};
+ Assert(!memcmp(pHeader->u.v1.szComment, achZero, VDI_IMAGE_COMMENT_SIZE));
+#endif
+ pHeader->u.v1.szComment[0] = '\0';
+ if (pszComment)
+ {
+ AssertMsg(strlen(pszComment) < sizeof(pHeader->u.v1.szComment),
+ ("HDD Comment is too long, cb=%d\n", strlen(pszComment)));
+ strncat(pHeader->u.v1.szComment, pszComment, sizeof(pHeader->u.v1.szComment));
+ }
+
+ /* Mark the legacy geometry not-calculated. */
+ pHeader->u.v1.LegacyGeometry.cCylinders = 0;
+ pHeader->u.v1.LegacyGeometry.cHeads = 0;
+ pHeader->u.v1.LegacyGeometry.cSectors = 0;
+ pHeader->u.v1.LegacyGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+ pHeader->u.v1.u32Dummy = 0; /* used to be the translation value */
+
+ pHeader->u.v1.cbDisk = cbDisk;
+ pHeader->u.v1.cbBlock = cbBlock;
+ pHeader->u.v1.cBlocks = (uint32_t)(cbDisk / cbBlock);
+ if (cbDisk % cbBlock)
+ pHeader->u.v1.cBlocks++;
+ pHeader->u.v1.cbBlockExtra = cbBlockExtra;
+ pHeader->u.v1.cBlocksAllocated = 0;
+
+ /* Init offsets. */
+ pHeader->u.v1.offBlocks = RT_ALIGN_32(sizeof(VDIPREHEADER) + sizeof(VDIHEADER1), VDI_GEOMETRY_SECTOR_SIZE);
+ pHeader->u.v1.offData = RT_ALIGN_32(pHeader->u.v1.offBlocks + (pHeader->u.v1.cBlocks * sizeof(VDIIMAGEBLOCKPOINTER)), VDI_GEOMETRY_SECTOR_SIZE);
+
+ /* Init uuids. */
+ RTUuidCreate(&pHeader->u.v1.uuidCreate);
+ RTUuidClear(&pHeader->u.v1.uuidModify);
+ RTUuidClear(&pHeader->u.v1.uuidLinkage);
+ RTUuidClear(&pHeader->u.v1.uuidParentModify);
+
+ /* Mark LCHS geometry not-calculated. */
+ pHeader->u.v1plus.LCHSGeometry.cCylinders = 0;
+ pHeader->u.v1plus.LCHSGeometry.cHeads = 0;
+ pHeader->u.v1plus.LCHSGeometry.cSectors = 0;
+ pHeader->u.v1plus.LCHSGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+}
+
+/**
+ * Internal: Check VDI header.
+ */
+static int vdiValidateHeader(PVDIHEADER pHeader)
+{
+ /* Check version-dependend header parameters. */
+ switch (GET_MAJOR_HEADER_VERSION(pHeader))
+ {
+ case 0:
+ {
+ /* Old header version. */
+ break;
+ }
+ case 1:
+ {
+ /* Current header version. */
+
+ if (pHeader->u.v1.cbHeader < sizeof(VDIHEADER1))
+ {
+ LogRel(("VDI: v1 header size wrong (%d < %d)\n",
+ pHeader->u.v1.cbHeader, sizeof(VDIHEADER1)));
+ return VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ if (getImageBlocksOffset(pHeader) < (sizeof(VDIPREHEADER) + sizeof(VDIHEADER1)))
+ {
+ LogRel(("VDI: v1 blocks offset wrong (%d < %d)\n",
+ getImageBlocksOffset(pHeader), sizeof(VDIPREHEADER) + sizeof(VDIHEADER1)));
+ return VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ if (getImageDataOffset(pHeader) < (getImageBlocksOffset(pHeader) + getImageBlocks(pHeader) * sizeof(VDIIMAGEBLOCKPOINTER)))
+ {
+ LogRel(("VDI: v1 image data offset wrong (%d < %d)\n",
+ getImageDataOffset(pHeader), getImageBlocksOffset(pHeader) + getImageBlocks(pHeader) * sizeof(VDIIMAGEBLOCKPOINTER)));
+ return VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ break;
+ }
+ default:
+ /* Unsupported. */
+ return VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+
+ /* Check common header parameters. */
+
+ bool fFailed = false;
+
+ if ( getImageType(pHeader) < VDI_IMAGE_TYPE_FIRST
+ || getImageType(pHeader) > VDI_IMAGE_TYPE_LAST)
+ {
+ LogRel(("VDI: bad image type %d\n", getImageType(pHeader)));
+ fFailed = true;
+ }
+
+ if (getImageFlags(pHeader) & ~VD_VDI_IMAGE_FLAGS_MASK)
+ {
+ LogRel(("VDI: bad image flags %08x\n", getImageFlags(pHeader)));
+ fFailed = true;
+ }
+
+ if ( getImageLCHSGeometry(pHeader)
+ && (getImageLCHSGeometry(pHeader))->cbSector != VDI_GEOMETRY_SECTOR_SIZE)
+ {
+ LogRel(("VDI: wrong sector size (%d != %d)\n",
+ (getImageLCHSGeometry(pHeader))->cbSector, VDI_GEOMETRY_SECTOR_SIZE));
+ fFailed = true;
+ }
+
+ if ( getImageDiskSize(pHeader) == 0
+ || getImageBlockSize(pHeader) == 0
+ || getImageBlocks(pHeader) == 0
+ || getPowerOfTwo(getImageBlockSize(pHeader)) == 0)
+ {
+ LogRel(("VDI: wrong size (%lld, %d, %d, %d)\n",
+ getImageDiskSize(pHeader), getImageBlockSize(pHeader),
+ getImageBlocks(pHeader), getPowerOfTwo(getImageBlockSize(pHeader))));
+ fFailed = true;
+ }
+
+ if (getImageBlocksAllocated(pHeader) > getImageBlocks(pHeader))
+ {
+ LogRel(("VDI: too many blocks allocated (%d > %d)\n"
+ " blocksize=%d disksize=%lld\n",
+ getImageBlocksAllocated(pHeader), getImageBlocks(pHeader),
+ getImageBlockSize(pHeader), getImageDiskSize(pHeader)));
+ fFailed = true;
+ }
+
+ if ( getImageExtraBlockSize(pHeader) != 0
+ && getPowerOfTwo(getImageExtraBlockSize(pHeader)) == 0)
+ {
+ LogRel(("VDI: wrong extra size (%d, %d)\n",
+ getImageExtraBlockSize(pHeader), getPowerOfTwo(getImageExtraBlockSize(pHeader))));
+ fFailed = true;
+ }
+
+ if ((uint64_t)getImageBlockSize(pHeader) * getImageBlocks(pHeader) < getImageDiskSize(pHeader))
+ {
+ LogRel(("VDI: wrong disk size (%d, %d, %lld)\n",
+ getImageBlockSize(pHeader), getImageBlocks(pHeader), getImageDiskSize(pHeader)));
+ fFailed = true;
+ }
+
+ if (RTUuidIsNull(getImageCreationUUID(pHeader)))
+ {
+ LogRel(("VDI: uuid of creator is 0\n"));
+ fFailed = true;
+ }
+
+ if (RTUuidIsNull(getImageModificationUUID(pHeader)))
+ {
+ LogRel(("VDI: uuid of modificator is 0\n"));
+ fFailed = true;
+ }
+
+ return fFailed ? VERR_VD_VDI_INVALID_HEADER : VINF_SUCCESS;
+}
+
+/**
+ * Internal: Set up VDIIMAGEDESC structure by image header.
+ */
+static void vdiSetupImageDesc(PVDIIMAGEDESC pImage)
+{
+ pImage->uImageFlags = getImageFlags(&pImage->Header);
+ pImage->offStartBlocks = getImageBlocksOffset(&pImage->Header);
+ pImage->offStartData = getImageDataOffset(&pImage->Header);
+ pImage->uBlockMask = getImageBlockSize(&pImage->Header) - 1;
+ pImage->uShiftOffset2Index = getPowerOfTwo(getImageBlockSize(&pImage->Header));
+ pImage->offStartBlockData = getImageExtraBlockSize(&pImage->Header);
+ pImage->cbTotalBlockData = pImage->offStartBlockData
+ + getImageBlockSize(&pImage->Header);
+}
+
+/**
+ * Internal: Create VDI image file.
+ */
+static int vdiCreateImage(PVDIIMAGEDESC pImage, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ const char *pszComment,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry, PCRTUUID pUuid,
+ PFNVMPROGRESS pfnProgress, void *pvUser,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ int rc;
+ RTFILE File;
+ uint64_t cbTotal;
+ uint64_t cbFill;
+ uint64_t uOff;
+
+ /* Special check for comment length. */
+ if ( VALID_PTR(pszComment)
+ && strlen(pszComment) >= VDI_IMAGE_COMMENT_SIZE)
+ {
+ rc = vdiError(pImage, VERR_VD_VDI_COMMENT_TOO_LONG, RT_SRC_POS, N_("VDI: comment is too long for '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ AssertPtr(pPCHSGeometry);
+ AssertPtr(pLCHSGeometry);
+
+ pImage->pInterfaceError = VDInterfaceGet(pImage->pVDIfsDisk, VDINTERFACETYPE_ERROR);
+ if (pImage->pInterfaceError)
+ pImage->pInterfaceErrorCallbacks = VDGetInterfaceError(pImage->pInterfaceError);
+
+ vdiInitPreHeader(&pImage->PreHeader);
+ vdiInitHeader(&pImage->Header, enmType, uImageFlags, pszComment, cbSize, VDI_IMAGE_DEFAULT_BLOCK_SIZE, 0);
+ /* Save PCHS geometry. Not much work, and makes the flow of information
+ * quite a bit clearer - relying on the higher level isn't obvious. */
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ /* Set LCHS geometry (legacy geometry is ignored for the current 1.1+). */
+ pImage->Header.u.v1plus.LCHSGeometry.cCylinders = pLCHSGeometry->cCylinders;
+ pImage->Header.u.v1plus.LCHSGeometry.cHeads = pLCHSGeometry->cHeads;
+ pImage->Header.u.v1plus.LCHSGeometry.cSectors = pLCHSGeometry->cSectors;
+ pImage->Header.u.v1plus.LCHSGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+
+ pImage->paBlocks = (PVDIIMAGEBLOCKPOINTER)RTMemAlloc(sizeof(VDIIMAGEBLOCKPOINTER) * getImageBlocks(&pImage->Header));
+ if (!pImage->paBlocks)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ if (enmType != VD_IMAGE_TYPE_FIXED)
+ {
+ /* for growing images mark all blocks in paBlocks as free. */
+ for (unsigned i = 0; i < pImage->Header.u.v1.cBlocks; i++)
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ }
+ else
+ {
+ /* for fixed images mark all blocks in paBlocks as allocated */
+ for (unsigned i = 0; i < pImage->Header.u.v1.cBlocks; i++)
+ pImage->paBlocks[i] = i;
+ pImage->Header.u.v1.cBlocksAllocated = pImage->Header.u.v1.cBlocks;
+ }
+
+ /* Setup image parameters. */
+ vdiSetupImageDesc(pImage);
+
+ /* Create image file. */
+ rc = RTFileOpen(&File, pImage->pszFilename,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_ALL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: cannot create image '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ pImage->File = File;
+
+ cbTotal = pImage->offStartData
+ + (uint64_t)getImageBlocks(&pImage->Header) * pImage->cbTotalBlockData;
+
+ if (enmType == VD_IMAGE_TYPE_FIXED)
+ {
+ /* Check the free space on the disk and leave early if there is not
+ * sufficient space available. */
+ RTFOFF cbFree = 0;
+ rc = RTFsQuerySizes(pImage->pszFilename, NULL, &cbFree, NULL, NULL);
+ if (RT_SUCCESS(rc) /* ignore errors */ && ((uint64_t)cbFree < cbTotal))
+ {
+ rc = vdiError(pImage, VERR_DISK_FULL, RT_SRC_POS, N_("VDI: disk would overflow creating image '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ }
+
+ if (enmType == VD_IMAGE_TYPE_FIXED)
+ {
+ /*
+ * Allocate & commit whole file if fixed image, it must be more
+ * effective than expanding file by write operations.
+ */
+ rc = RTFileSetSize(File, cbTotal);
+ }
+ else
+ {
+ /* Set file size to hold header and blocks array. */
+ rc = RTFileSetSize(pImage->File, pImage->offStartData);
+ }
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: setting image size failed for '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ /* Use specified image uuid */
+ *getImageCreationUUID(&pImage->Header) = *pUuid;
+
+ /* Generate image last-modify uuid */
+ RTUuidCreate(getImageModificationUUID(&pImage->Header));
+
+ /* Write pre-header. */
+ rc = RTFileWriteAt(File, 0, &pImage->PreHeader, sizeof(pImage->PreHeader), NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: writing pre-header failed for '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ /* Write header. */
+ rc = RTFileWriteAt(File, sizeof(pImage->PreHeader), &pImage->Header.u.v1plus, sizeof(pImage->Header.u.v1plus), NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: writing header failed for '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ rc = RTFileWriteAt(File, pImage->offStartBlocks,
+ pImage->paBlocks,
+ getImageBlocks(&pImage->Header) * sizeof(VDIIMAGEBLOCKPOINTER),
+ NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: writing block pointers failed for '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ if (enmType == VD_IMAGE_TYPE_FIXED)
+ {
+ /* Fill image with zeroes. We do this for every fixed-size image since on some systems
+ * (for example Windows Vista), it takes ages to write a block near the end of a sparse
+ * file and the guest could complain about an ATA timeout. */
+
+ /** @todo Starting with Linux 2.6.23, there is an fallocate() system call.
+ * Currently supported file systems are ext4 and ocfs2. */
+
+ /* Allocate a temporary zero-filled buffer. Use a bigger block size to optimize writing */
+ const size_t cbBuf = 128 * _1K;
+ void *pvBuf = RTMemTmpAllocZ(cbBuf);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ cbFill = (uint64_t)getImageBlocks(&pImage->Header) * pImage->cbTotalBlockData;
+ uOff = 0;
+ /* Write data to all image blocks. */
+ while (uOff < cbFill)
+ {
+ unsigned cbChunk = (unsigned)RT_MIN(cbFill, cbBuf);
+
+ rc = RTFileWriteAt(File, pImage->offStartData + uOff,
+ pvBuf, cbChunk, NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: writing block failed for '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ uOff += cbChunk;
+
+ if (pfnProgress)
+ {
+ rc = pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uPercentStart + uOff * uPercentSpan / cbFill,
+ pvUser);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+ }
+ RTMemTmpFree(pvBuf);
+ }
+
+out:
+ if (RT_SUCCESS(rc) && pfnProgress)
+ pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uPercentStart + uPercentSpan, pvUser);
+
+ if (RT_FAILURE(rc))
+ vdiFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+/**
+ * Internal: Open a VDI image.
+ */
+static int vdiOpenImage(PVDIIMAGEDESC pImage, unsigned uOpenFlags)
+{
+ int rc;
+ RTFILE File;
+
+ if (uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)
+ return VERR_NOT_SUPPORTED;
+
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pInterfaceError = VDInterfaceGet(pImage->pVDIfsDisk, VDINTERFACETYPE_ERROR);
+ if (pImage->pInterfaceError)
+ pImage->pInterfaceErrorCallbacks = VDGetInterfaceError(pImage->pInterfaceError);
+
+ /*
+ * Open the image.
+ */
+ rc = RTFileOpen(&File, pImage->pszFilename,
+ uOpenFlags & VD_OPEN_FLAGS_READONLY
+ ? RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+ : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ goto out;
+ }
+ pImage->File = File;
+
+ /* Read pre-header. */
+ rc = RTFileReadAt(File, 0, &pImage->PreHeader, sizeof(pImage->PreHeader),
+ NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: error reading pre-header in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ rc = vdiValidatePreHeader(&pImage->PreHeader);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: invalid pre-header in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ /* Read header. */
+ pImage->Header.uVersion = pImage->PreHeader.u32Version;
+ switch (GET_MAJOR_HEADER_VERSION(&pImage->Header))
+ {
+ case 0:
+ rc = RTFileReadAt(File, sizeof(pImage->PreHeader),
+ &pImage->Header.u.v0, sizeof(pImage->Header.u.v0),
+ NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: error reading v0 header in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ break;
+ case 1:
+ rc = RTFileReadAt(File, sizeof(pImage->PreHeader),
+ &pImage->Header.u.v1, sizeof(pImage->Header.u.v1),
+ NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: error reading v1 header in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ /* Convert VDI 1.1 images to VDI 1.1+ on open in read/write mode.
+ * Conversion is harmless, as any VirtualBox version supporting VDI
+ * 1.1 doesn't touch fields it doesn't know about. */
+ if ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && GET_MINOR_HEADER_VERSION(&pImage->Header) == 1
+ && pImage->Header.u.v1.cbHeader < sizeof(pImage->Header.u.v1plus))
+ {
+ pImage->Header.u.v1plus.cbHeader = sizeof(pImage->Header.u.v1plus);
+ /* Mark LCHS geometry not-calculated. */
+ pImage->Header.u.v1plus.LCHSGeometry.cCylinders = 0;
+ pImage->Header.u.v1plus.LCHSGeometry.cHeads = 0;
+ pImage->Header.u.v1plus.LCHSGeometry.cSectors = 0;
+ pImage->Header.u.v1plus.LCHSGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+ }
+ else if (pImage->Header.u.v1.cbHeader >= sizeof(pImage->Header.u.v1plus))
+ {
+ /* Read the actual VDI 1.1+ header completely. */
+ rc = RTFileReadAt(File, sizeof(pImage->PreHeader), &pImage->Header.u.v1plus, sizeof(pImage->Header.u.v1plus), NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, rc, RT_SRC_POS, N_("VDI: error reading v1.1+ header in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ }
+ break;
+ default:
+ rc = vdiError(pImage, VERR_VD_VDI_UNSUPPORTED_VERSION, RT_SRC_POS, N_("VDI: unsupported major version %u in '%s'"), GET_MAJOR_HEADER_VERSION(&pImage->Header), pImage->pszFilename);
+ goto out;
+ }
+
+ rc = vdiValidateHeader(&pImage->Header);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdiError(pImage, VERR_VD_VDI_INVALID_HEADER, RT_SRC_POS, N_("VDI: invalid header in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ /* Setup image parameters by header. */
+ vdiSetupImageDesc(pImage);
+
+ /* Allocate memory for blocks array. */
+ pImage->paBlocks = (PVDIIMAGEBLOCKPOINTER)RTMemAlloc(sizeof(VDIIMAGEBLOCKPOINTER) * getImageBlocks(&pImage->Header));
+ if (!pImage->paBlocks)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ /* Read blocks array. */
+ rc = RTFileReadAt(pImage->File, pImage->offStartBlocks, pImage->paBlocks,
+ getImageBlocks(&pImage->Header) * sizeof(VDIIMAGEBLOCKPOINTER),
+ NULL);
+
+out:
+ if (RT_FAILURE(rc))
+ vdiFreeImage(pImage, false);
+ return rc;
+}
+
+/**
+ * Internal: Save header to file.
+ */
+static int vdiUpdateHeader(PVDIIMAGEDESC pImage)
+{
+ int rc;
+ switch (GET_MAJOR_HEADER_VERSION(&pImage->Header))
+ {
+ case 0:
+ rc = RTFileWriteAt(pImage->File, sizeof(VDIPREHEADER), &pImage->Header.u.v0, sizeof(pImage->Header.u.v0), NULL);
+ break;
+ case 1:
+ if (pImage->Header.u.v1plus.cbHeader < sizeof(pImage->Header.u.v1plus))
+ rc = RTFileWriteAt(pImage->File, sizeof(VDIPREHEADER), &pImage->Header.u.v1, sizeof(pImage->Header.u.v1), NULL);
+ else
+ rc = RTFileWriteAt(pImage->File, sizeof(VDIPREHEADER), &pImage->Header.u.v1plus, sizeof(pImage->Header.u.v1plus), NULL);
+ break;
+ default:
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ break;
+ }
+ AssertMsgRC(rc, ("vdiUpdateHeader failed, filename=\"%s\" rc=%Rrc\n", pImage->pszFilename, rc));
+ return rc;
+}
+
+/**
+ * Internal: Save block pointer to file, save header to file.
+ */
+static int vdiUpdateBlockInfo(PVDIIMAGEDESC pImage, unsigned uBlock)
+{
+ /* Update image header. */
+ int rc = vdiUpdateHeader(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* write only one block pointer. */
+ rc = RTFileWriteAt(pImage->File,
+ pImage->offStartBlocks + uBlock * sizeof(VDIIMAGEBLOCKPOINTER),
+ &pImage->paBlocks[uBlock],
+ sizeof(VDIIMAGEBLOCKPOINTER),
+ NULL);
+ AssertMsgRC(rc, ("vdiUpdateBlockInfo failed to update block=%u, filename=\"%s\", rc=%Rrc\n",
+ uBlock, pImage->pszFilename, rc));
+ }
+ return rc;
+}
+
+/**
+ * Internal: Flush the image file to disk.
+ */
+static void vdiFlushImage(PVDIIMAGEDESC pImage)
+{
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* Save header. */
+ int rc = vdiUpdateHeader(pImage);
+ AssertMsgRC(rc, ("vdiUpdateHeader() failed, filename=\"%s\", rc=%Rrc\n",
+ pImage->pszFilename, rc));
+ RTFileFlush(pImage->File);
+ }
+}
+
+/**
+ * Internal: Free all allocated space for representing an image, and optionally
+ * delete the image from disk.
+ */
+static void vdiFreeImage(PVDIIMAGEDESC pImage, bool fDelete)
+{
+ AssertPtr(pImage);
+
+ if (pImage->File != NIL_RTFILE)
+ {
+ vdiFlushImage(pImage);
+ RTFileClose(pImage->File);
+ pImage->File = NIL_RTFILE;
+ }
+ if (pImage->paBlocks)
+ {
+ RTMemFree(pImage->paBlocks);
+ pImage->paBlocks = NULL;
+ }
+ if (fDelete && pImage->pszFilename)
+ RTFileDelete(pImage->pszFilename);
+}
+
+
+/** @copydoc VBOXHDDBACKEND::pfnCheckIfValid */
+static int vdiCheckIfValid(const char *pszFilename)
+{
+ LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename));
+ int rc = VINF_SUCCESS;
+ PVDIIMAGEDESC pImage;
+
+ if ( !VALID_PTR(pszFilename)
+ || !*pszFilename)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ pImage = (PVDIIMAGEDESC)RTMemAllocZ(sizeof(VDIIMAGEDESC));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pImage->pszFilename = pszFilename;
+ pImage->File = NIL_RTFILE;
+ pImage->paBlocks = NULL;
+ pImage->pVDIfsDisk = NULL;
+
+ rc = vdiOpenImage(pImage, VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_READONLY);
+ vdiFreeImage(pImage, false);
+ RTMemFree(pImage);
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnOpen */
+static int vdiOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p ppBackendData=%#p\n", pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, ppBackendData));
+ int rc;
+ PVDIIMAGEDESC pImage;
+
+ /* Check open flags. All valid flags are supported. */
+ if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Check remaining arguments. */
+ if ( !VALID_PTR(pszFilename)
+ || !*pszFilename)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ pImage = (PVDIIMAGEDESC)RTMemAllocZ(sizeof(VDIIMAGEDESC));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pImage->pszFilename = pszFilename;
+ pImage->File = NIL_RTFILE;
+ pImage->paBlocks = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+
+ rc = vdiOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+
+out:
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnCreate */
+static int vdiCreate(const char *pszFilename, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ const char *pszComment,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry, PCRTUUID pUuid,
+ unsigned uOpenFlags, unsigned uPercentStart,
+ unsigned uPercentSpan, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, PVDINTERFACE pVDIfsOperation,
+ void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" enmType=%d cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p ppBackendData=%#p", pszFilename, enmType, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, ppBackendData));
+ int rc;
+ PVDIIMAGEDESC pImage;
+
+ PFNVMPROGRESS pfnProgress = NULL;
+ void *pvUser = NULL;
+ PVDINTERFACE pIfProgress = VDInterfaceGet(pVDIfsOperation,
+ VDINTERFACETYPE_PROGRESS);
+ PVDINTERFACEPROGRESS pCbProgress = NULL;
+ if (pIfProgress)
+ {
+ pCbProgress = VDGetInterfaceProgress(pIfProgress);
+ if (pCbProgress)
+ pfnProgress = pCbProgress->pfnProgress;
+ pvUser = pIfProgress->pvUser;
+ }
+
+ /* Check open flags. All valid flags are supported. */
+ if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Check remaining arguments. */
+ if ( !VALID_PTR(pszFilename)
+ || !*pszFilename
+ || (enmType != VD_IMAGE_TYPE_NORMAL && enmType != VD_IMAGE_TYPE_FIXED
+ && enmType != VD_IMAGE_TYPE_DIFF)
+ || cbSize < VDI_IMAGE_DEFAULT_BLOCK_SIZE
+ || !VALID_PTR(pPCHSGeometry)
+ || !VALID_PTR(pLCHSGeometry))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ pImage = (PVDIIMAGEDESC)RTMemAllocZ(sizeof(VDIIMAGEDESC));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pImage->pszFilename = pszFilename;
+ pImage->File = NIL_RTFILE;
+ pImage->paBlocks = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+
+ rc = vdiCreateImage(pImage, enmType, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, pUuid,
+ pfnProgress, pvUser, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ vdiFreeImage(pImage, false);
+ rc = vdiOpenImage(pImage, uOpenFlags);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+ *ppBackendData = pImage;
+ }
+ else
+ RTMemFree(pImage);
+
+out:
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnRename */
+static int vdiRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+
+ int rc = VINF_SUCCESS;
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ /* Check arguments. */
+ if ( !pImage
+ || !pszFilename
+ || !*pszFilename)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Close the image. */
+ vdiFreeImage(pImage, false);
+
+ /* Rename the file. */
+ rc = RTFileMove(pImage->pszFilename, pszFilename, 0);
+ if (RT_FAILURE(rc))
+ {
+ /* The move failed, try to reopen the original image. */
+ int rc2 = vdiOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+
+ goto out;
+ }
+
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the new image. */
+ rc = vdiOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc))
+ goto out;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnClose */
+static int vdiClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ vdiFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnRead */
+static int vdiRead(void *pBackendData, uint64_t uOffset, void *pvBuf,
+ size_t cbToRead, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pvBuf=%#p cbToRead=%zu pcbActuallyRead=%#p\n", pBackendData, uOffset, pvBuf, cbToRead, pcbActuallyRead));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uBlock;
+ unsigned offRead;
+ int rc;
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbToRead % 512));
+
+ if ( uOffset + cbToRead > getImageDiskSize(&pImage->Header)
+ || !VALID_PTR(pvBuf)
+ || !cbToRead)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Calculate starting block number and offset inside it. */
+ uBlock = (unsigned)(uOffset >> pImage->uShiftOffset2Index);
+ offRead = (unsigned)uOffset & pImage->uBlockMask;
+
+ /* Clip read range to at most the rest of the block. */
+ cbToRead = RT_MIN(cbToRead, getImageBlockSize(&pImage->Header) - offRead);
+ Assert(!(cbToRead % 512));
+
+ if (pImage->paBlocks[uBlock] == VDI_IMAGE_BLOCK_FREE)
+ rc = VERR_VD_BLOCK_FREE;
+ else if (pImage->paBlocks[uBlock] == VDI_IMAGE_BLOCK_ZERO)
+ {
+ memset(pvBuf, 0, cbToRead);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ /* Block present in image file, read relevant data. */
+ uint64_t u64Offset = (uint64_t)pImage->paBlocks[uBlock] * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData + offRead);
+ rc = RTFileReadAt(pImage->File, u64Offset, pvBuf, cbToRead, NULL);
+ }
+
+ if (pcbActuallyRead)
+ *pcbActuallyRead = cbToRead;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**@copydoc VBOXHDDBACKEND::pfnWrite */
+static int vdiWrite(void *pBackendData, uint64_t uOffset, const void *pvBuf,
+ size_t cbToWrite, size_t *pcbWriteProcess,
+ size_t *pcbPreRead, size_t *pcbPostRead, unsigned fWrite)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pvBuf=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n", pBackendData, uOffset, pvBuf, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uBlock;
+ unsigned offWrite;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbToWrite % 512));
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ if (!VALID_PTR(pvBuf) || !cbToWrite)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* No size check here, will do that later. For dynamic images which are
+ * not multiples of the block size in length, this would prevent writing to
+ * the last grain. */
+
+ /* Calculate starting block number and offset inside it. */
+ uBlock = (unsigned)(uOffset >> pImage->uShiftOffset2Index);
+ offWrite = (unsigned)uOffset & pImage->uBlockMask;
+
+ /* Clip write range to at most the rest of the block. */
+ cbToWrite = RT_MIN(cbToWrite, getImageBlockSize(&pImage->Header) - offWrite);
+ Assert(!(cbToWrite % 512));
+
+ do
+ {
+ if (!IS_VDI_IMAGE_BLOCK_ALLOCATED(pImage->paBlocks[uBlock]))
+ {
+ /* Block is either free or zero. */
+ if ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_ZEROES)
+ && ( pImage->paBlocks[uBlock] == VDI_IMAGE_BLOCK_ZERO
+ || cbToWrite == getImageBlockSize(&pImage->Header)))
+ {
+ /* If the destination block is unallocated at this point, it's
+ * either a zero block or a block which hasn't been used so far
+ * (which also means that it's a zero block. Don't need to write
+ * anything to this block if the data consists of just zeroes. */
+ Assert(!(cbToWrite % 4));
+ Assert(cbToWrite * 8 <= UINT32_MAX);
+ if (ASMBitFirstSet((volatile void *)pvBuf, (uint32_t)cbToWrite * 8) == -1)
+ {
+ pImage->paBlocks[uBlock] = VDI_IMAGE_BLOCK_ZERO;
+ break;
+ }
+ }
+
+ if (cbToWrite == getImageBlockSize(&pImage->Header))
+ {
+ /* Full block write to previously unallocated block.
+ * Allocate block and write data. */
+ Assert(!offWrite);
+ unsigned cBlocksAllocated = getImageBlocksAllocated(&pImage->Header);
+ uint64_t u64Offset = (uint64_t)cBlocksAllocated * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData);
+ rc = RTFileWriteAt(pImage->File, u64Offset, pvBuf, cbToWrite, NULL);
+ if (RT_FAILURE(rc))
+ goto out;
+ pImage->paBlocks[uBlock] = cBlocksAllocated;
+ setImageBlocksAllocated(&pImage->Header, cBlocksAllocated + 1);
+
+ rc = vdiUpdateBlockInfo(pImage, uBlock);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+ }
+ else
+ {
+ /* Trying to do a partial write to an unallocated block. Don't do
+ * anything except letting the upper layer know what to do. */
+ *pcbPreRead = offWrite % getImageBlockSize(&pImage->Header);
+ *pcbPostRead = getImageBlockSize(&pImage->Header) - cbToWrite - *pcbPreRead;
+ rc = VERR_VD_BLOCK_FREE;
+ }
+ }
+ else
+ {
+ /* Block present in image file, write relevant data. */
+ uint64_t u64Offset = (uint64_t)pImage->paBlocks[uBlock] * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData + offWrite);
+ rc = RTFileWriteAt(pImage->File, u64Offset, pvBuf, cbToWrite, NULL);
+ }
+ } while (0);
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnFlush */
+static int vdiFlush(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ Assert(pImage);
+
+ vdiFlushImage(pImage);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetVersion */
+static unsigned vdiGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uVersion;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ uVersion = pImage->PreHeader.u32Version;
+ else
+ uVersion = 0;
+
+ LogFlowFunc(("returns %#x\n", uVersion));
+ return uVersion;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetImageType */
+static int vdiGetImageType(void *pBackendData, PVDIMAGETYPE penmImageType)
+{
+ LogFlowFunc(("pBackendData=%#p penmImageType=%#p\n", pBackendData, penmImageType));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ AssertPtr(penmImageType);
+
+ if (pImage)
+ *penmImageType = vdiTranslateTypeVDI2VD(getImageType(&pImage->Header));
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc enmImageType=%u\n", rc, *penmImageType));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetSize */
+static uint64_t vdiGetSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ uint64_t cbSize;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ cbSize = getImageDiskSize(&pImage->Header);
+ else
+ cbSize = 0;
+
+ LogFlowFunc(("returns %llu\n", cbSize));
+ return cbSize;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetFileSize */
+static uint64_t vdiGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ uint64_t cbFile;
+ if (pImage->File != NIL_RTFILE)
+ {
+ int rc = RTFileGetSize(pImage->File, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb += cbFile;
+ }
+ }
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetPCHSGeometry */
+static int vdiGetPCHSGeometry(void *pBackendData,
+ PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->PCHSGeometry.cCylinders)
+ {
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetPCHSGeometry */
+static int vdiSetPCHSGeometry(void *pBackendData,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetLCHSGeometry */
+static int vdiGetLCHSGeometry(void *pBackendData,
+ PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ VDIDISKGEOMETRY DummyGeo = { 0, 0, 0, VDI_GEOMETRY_SECTOR_SIZE };
+ PVDIDISKGEOMETRY pGeometry = getImageLCHSGeometry(&pImage->Header);
+ if (!pGeometry)
+ pGeometry = &DummyGeo;
+
+ if ( pGeometry->cCylinders > 0
+ && pGeometry->cHeads > 0
+ && pGeometry->cSectors > 0)
+ {
+ pLCHSGeometry->cCylinders = pGeometry->cCylinders;
+ pLCHSGeometry->cHeads = pGeometry->cHeads;
+ pLCHSGeometry->cSectors = pGeometry->cSectors;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetLCHSGeometry */
+static int vdiSetLCHSGeometry(void *pBackendData,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ PVDIDISKGEOMETRY pGeometry;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ pGeometry = getImageLCHSGeometry(&pImage->Header);
+ if (pGeometry)
+ {
+ pGeometry->cCylinders = pLCHSGeometry->cCylinders;
+ pGeometry->cHeads = pLCHSGeometry->cHeads;
+ pGeometry->cSectors = pLCHSGeometry->cSectors;
+ pGeometry->cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+
+ /* Update header information in base image file. */
+ vdiFlushImage(pImage);
+ }
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetImageFlags */
+static unsigned vdiGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uImageFlags;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ uImageFlags = pImage->uImageFlags;
+ else
+ uImageFlags = 0;
+
+ LogFlowFunc(("returns %#x\n", uImageFlags));
+ return uImageFlags;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetOpenFlags */
+static unsigned vdiGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uOpenFlags;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ uOpenFlags = pImage->uOpenFlags;
+ else
+ uOpenFlags = 0;
+
+ LogFlowFunc(("returns %#x\n", uOpenFlags));
+ return uOpenFlags;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetOpenFlags */
+static int vdiSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p uOpenFlags=%#x\n", pBackendData, uOpenFlags));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+ const char *pszFilename;
+
+ /* Image must be opened and the new flags must be valid. Just readonly and
+ * info flags are supported. */
+ if (!pImage || (uOpenFlags & ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO)))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Implement this operation via reopening the image. */
+ pszFilename = pImage->pszFilename;
+ vdiFreeImage(pImage, false);
+ rc = vdiOpenImage(pImage, uOpenFlags);
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetComment */
+static int vdiGetComment(void *pBackendData, char *pszComment,
+ size_t cbComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ char *pszTmp = getImageComment(&pImage->Header);
+ unsigned cb = strlen(pszTmp);
+ if (cb < cbComment)
+ {
+ /* memcpy is much better than strncpy. */
+ memcpy(pszComment, pszTmp, cb + 1);
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc comment=\"%s\"\n", rc, pszComment));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetComment */
+static int vdiSetComment(void *pBackendData, const char *pszComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ if (pImage)
+ {
+ size_t cchComment = pszComment ? strlen(pszComment) : 0;
+ if (cchComment >= VDI_IMAGE_COMMENT_SIZE)
+ {
+ LogFunc(("pszComment is too long, %d bytes!\n", cchComment));
+ rc = VERR_VD_VDI_COMMENT_TOO_LONG;
+ goto out;
+ }
+
+ /* we don't support old style images */
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ {
+ /*
+ * Update the comment field, making sure to zero out all of the previous comment.
+ */
+ memset(pImage->Header.u.v1.szComment, '\0', VDI_IMAGE_COMMENT_SIZE);
+ memcpy(pImage->Header.u.v1.szComment, pszComment, cchComment);
+
+ /* write out new the header */
+ rc = vdiUpdateHeader(pImage);
+ }
+ else
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetUuid */
+static int vdiGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ *pUuid = *getImageCreationUUID(&pImage->Header);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetUuid */
+static int vdiSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidCreate = *pUuid;
+ /* Make it possible to clone old VDIs. */
+ else if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0)
+ pImage->Header.u.v0.uuidCreate = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetModificationUuid */
+static int vdiGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ *pUuid = *getImageModificationUUID(&pImage->Header);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetModificationUuid */
+static int vdiSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidModify = *pUuid;
+ /* Make it possible to clone old VDIs. */
+ else if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0)
+ pImage->Header.u.v0.uuidModify = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetParentUuid */
+static int vdiGetParentUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ *pUuid = *getImageParentUUID(&pImage->Header);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetParentUuid */
+static int vdiSetParentUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidLinkage = *pUuid;
+ /* Make it possible to clone old VDIs. */
+ else if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0)
+ pImage->Header.u.v0.uuidLinkage = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetParentModificationUuid */
+static int vdiGetParentModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ *pUuid = *getImageParentModificationUUID(&pImage->Header);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetParentModificationUuid */
+static int vdiSetParentModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidParentModify = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnDump */
+static void vdiDump(void *pBackendData)
+{
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ RTLogPrintf("Dumping VDI image \"%s\" mode=%s uOpenFlags=%X File=%08X\n",
+ pImage->pszFilename,
+ (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) ? "r/o" : "r/w",
+ pImage->uOpenFlags,
+ pImage->File);
+ RTLogPrintf("Header: Version=%08X Type=%X Flags=%X Size=%llu\n",
+ pImage->PreHeader.u32Version,
+ getImageType(&pImage->Header),
+ getImageFlags(&pImage->Header),
+ getImageDiskSize(&pImage->Header));
+ RTLogPrintf("Header: cbBlock=%u cbBlockExtra=%u cBlocks=%u cBlocksAllocated=%u\n",
+ getImageBlockSize(&pImage->Header),
+ getImageExtraBlockSize(&pImage->Header),
+ getImageBlocks(&pImage->Header),
+ getImageBlocksAllocated(&pImage->Header));
+ RTLogPrintf("Header: offBlocks=%u offData=%u\n",
+ getImageBlocksOffset(&pImage->Header),
+ getImageDataOffset(&pImage->Header));
+ PVDIDISKGEOMETRY pg = getImageLCHSGeometry(&pImage->Header);
+ if (pg)
+ RTLogPrintf("Header: Geometry: C/H/S=%u/%u/%u cbSector=%u\n",
+ pg->cCylinders, pg->cHeads, pg->cSectors, pg->cbSector);
+ RTLogPrintf("Header: uuidCreation={%RTuuid}\n", getImageCreationUUID(&pImage->Header));
+ RTLogPrintf("Header: uuidModification={%RTuuid}\n", getImageModificationUUID(&pImage->Header));
+ RTLogPrintf("Header: uuidParent={%RTuuid}\n", getImageParentUUID(&pImage->Header));
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) >= 1)
+ RTLogPrintf("Header: uuidParentModification={%RTuuid}\n", getImageParentModificationUUID(&pImage->Header));
+ RTLogPrintf("Image: fFlags=%08X offStartBlocks=%u offStartData=%u\n",
+ pImage->uImageFlags, pImage->offStartBlocks, pImage->offStartData);
+ RTLogPrintf("Image: uBlockMask=%08X cbTotalBlockData=%u uShiftOffset2Index=%u offStartBlockData=%u\n",
+ pImage->uBlockMask,
+ pImage->cbTotalBlockData,
+ pImage->uShiftOffset2Index,
+ pImage->offStartBlockData);
+
+ unsigned uBlock, cBlocksNotFree, cBadBlocks, cBlocks = getImageBlocks(&pImage->Header);
+ for (uBlock=0, cBlocksNotFree=0, cBadBlocks=0; uBlock<cBlocks; uBlock++)
+ {
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(pImage->paBlocks[uBlock]))
+ {
+ cBlocksNotFree++;
+ if (pImage->paBlocks[uBlock] >= cBlocks)
+ cBadBlocks++;
+ }
+ }
+ if (cBlocksNotFree != getImageBlocksAllocated(&pImage->Header))
+ {
+ RTLogPrintf("!! WARNING: %u blocks actually allocated (cBlocksAllocated=%u) !!\n",
+ cBlocksNotFree, getImageBlocksAllocated(&pImage->Header));
+ }
+ if (cBadBlocks)
+ {
+ RTLogPrintf("!! WARNING: %u bad blocks found !!\n",
+ cBadBlocks);
+ }
+}
+
+static int vdiGetTimeStamp(void *pvBackendData, PRTTIMESPEC pTimeStamp)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int vdiGetParentTimeStamp(void *pvBackendData, PRTTIMESPEC pTimeStamp)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int vdiSetParentTimeStamp(void *pvBackendData, PCRTTIMESPEC pTimeStamp)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int vdiGetParentFilename(void *pvBackendData, char **ppszParentFilename)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int vdiSetParentFilename(void *pvBackendData, const char *pszParentFilename)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static bool vdiIsAsyncIOSupported(void *pvBackendData)
+{
+ return false;
+}
+
+static int vdiAsyncRead(void *pvBackendData, uint64_t uOffset, size_t cbRead,
+ PPDMDATASEG paSeg, unsigned cSeg, void *pvUser)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static int vdiAsyncWrite(void *pvBackendData, uint64_t uOffset, size_t cbWrite,
+ PPDMDATASEG paSeg, unsigned cSeg, void *pvUser)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXHDDBACKEND g_VDIBackend =
+{
+ /* pszBackendName */
+ "VDI",
+ /* cbSize */
+ sizeof(VBOXHDDBACKEND),
+ /* uBackendCaps */
+ VD_CAP_UUID | VD_CAP_CREATE_FIXED | VD_CAP_CREATE_DYNAMIC
+ | VD_CAP_CREATE_SPLIT_2G | VD_CAP_DIFF | VD_CAP_FILE,
+ /* papszFileExtensions */
+ s_apszVdiFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* hPlugin */
+ NIL_RTLDRMOD,
+ /* pfnCheckIfValid */
+ vdiCheckIfValid,
+ /* pfnOpen */
+ vdiOpen,
+ /* pfnCreate */
+ vdiCreate,
+ /* pfnRename */
+ vdiRename,
+ /* pfnClose */
+ vdiClose,
+ /* pfnRead */
+ vdiRead,
+ /* pfnWrite */
+ vdiWrite,
+ /* pfnFlush */
+ vdiFlush,
+ /* pfnGetVersion */
+ vdiGetVersion,
+ /* pfnGetImageType */
+ vdiGetImageType,
+ /* pfnGetSize */
+ vdiGetSize,
+ /* pfnGetFileSize */
+ vdiGetFileSize,
+ /* pfnGetPCHSGeometry */
+ vdiGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ vdiSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ vdiGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ vdiSetLCHSGeometry,
+ /* pfnGetImageFlags */
+ vdiGetImageFlags,
+ /* pfnGetOpenFlags */
+ vdiGetOpenFlags,
+ /* pfnSetOpenFlags */
+ vdiSetOpenFlags,
+ /* pfnGetComment */
+ vdiGetComment,
+ /* pfnSetComment */
+ vdiSetComment,
+ /* pfnGetUuid */
+ vdiGetUuid,
+ /* pfnSetUuid */
+ vdiSetUuid,
+ /* pfnGetModificationUuid */
+ vdiGetModificationUuid,
+ /* pfnSetModificationUuid */
+ vdiSetModificationUuid,
+ /* pfnGetParentUuid */
+ vdiGetParentUuid,
+ /* pfnSetParentUuid */
+ vdiSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ vdiGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ vdiSetParentModificationUuid,
+ /* pfnDump */
+ vdiDump,
+ /* pfnGetTimeStamp */
+ vdiGetTimeStamp,
+ /* pfnGetParentTimeStamp */
+ vdiGetParentTimeStamp,
+ /* pfnSetParentTimeStamp */
+ vdiSetParentTimeStamp,
+ /* pfnGetParentFilename */
+ vdiGetParentFilename,
+ /* pfnSetParentFilename */
+ vdiSetParentFilename,
+ /* pfnIsAsyncIOSupported */
+ vdiIsAsyncIOSupported,
+ /* pfnAsyncRead */
+ vdiAsyncRead,
+ /* pfnAsyncWrite */
+ vdiAsyncWrite,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName
+};
+
diff --git a/src/VBox/Devices/Storage/VHDHDDCore.cpp b/src/VBox/Devices/Storage/VHDHDDCore.cpp
new file mode 100644
index 000000000..173b84ad0
--- /dev/null
+++ b/src/VBox/Devices/Storage/VHDHDDCore.cpp
@@ -0,0 +1,1959 @@
+/** @file
+ * VHD Disk image, Core Code.
+ */
+
+/*
+ * Copyright (C) 2006-2008 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_VHD
+#include "VBoxHDD-newInternal.h"
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include <iprt/cdefs.h>
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/rand.h>
+
+#define VHD_RELATIVE_MAX_PATH 512
+#define VHD_ABSOLUTE_MAX_PATH 512
+
+#define VHD_SECTOR_SIZE 512
+#define VHD_BLOCK_SIZE 0x00200000
+
+/* This is common to all VHD disk types and is located at the end of the image */
+#pragma pack(1)
+typedef struct VHDFooter {
+ char Cookie[8];
+ uint32_t Features;
+ uint32_t Version;
+ uint64_t DataOffset;
+ uint32_t TimeStamp;
+ uint8_t CreatorApp[4];
+ uint32_t CreatorVer;
+ uint32_t CreatorOS;
+ uint64_t OrigSize;
+ uint64_t CurSize;
+ uint16_t DiskGeometryCylinder;
+ uint8_t DiskGeometryHeads;
+ uint8_t DiskGeometrySectors;
+ uint32_t DiskType;
+ uint32_t Checksum;
+ char UniqueID[16];
+ uint8_t SavedState;
+ uint8_t Reserved[427];
+} VHDFooter;
+#pragma pack()
+
+#define VHD_FOOTER_COOKIE "conectix"
+#define VHD_FOOTER_COOKIE_SIZE 8
+
+#define VHD_FOOTER_FEATURES_NOT_ENABLED 0
+#define VHD_FOOTER_FEATURES_TEMPORARY 1
+#define VHD_FOOTER_FEATURES_RESERVED 2
+
+#define VHD_FOOTER_FILE_FORMAT_VERSION 0x00010000
+#define VHD_FOOTER_DATA_OFFSET_FIXED UINT64_C(0xffffffffffffffff)
+#define VHD_FOOTER_DISK_TYPE_FIXED 2
+#define VHD_FOOTER_DISK_TYPE_DYNAMIC 3
+#define VHD_FOOTER_DISK_TYPE_DIFFERENCING 4
+
+#define VHD_MAX_LOCATOR_ENTRIES 8
+#define VHD_PLATFORM_CODE_NONE 0
+#define VHD_PLATFORM_CODE_WI2R 0x57693272
+#define VHD_PLATFORM_CODE_WI2K 0x5769326B
+#define VHD_PLATFORM_CODE_W2RU 0x57327275
+#define VHD_PLATFORM_CODE_W2KU 0x57326B75
+#define VHD_PLATFORM_CODE_MAC 0x4D163220
+#define VHD_PLATFORM_CODE_MACX 0x4D163258
+
+/* Header for expanding disk images. */
+#pragma pack(1)
+typedef struct VHDParentLocatorEntry
+{
+ uint32_t u32Code;
+ uint32_t u32DataSpace;
+ uint32_t u32DataLength;
+ uint32_t u32Reserved;
+ uint64_t u64DataOffset;
+} VHDPLE, *PVHDPLE;
+
+typedef struct VHDDynamicDiskHeader
+{
+ char Cookie[8];
+ uint64_t DataOffset;
+ uint64_t TableOffset;
+ uint32_t HeaderVersion;
+ uint32_t MaxTableEntries;
+ uint32_t BlockSize;
+ uint32_t Checksum;
+ uint8_t ParentUuid[16];
+ uint32_t ParentTimeStamp;
+ uint32_t Reserved0;
+ uint8_t ParentUnicodeName[512];
+ VHDPLE ParentLocatorEntry[VHD_MAX_LOCATOR_ENTRIES];
+ uint8_t Reserved1[256];
+} VHDDynamicDiskHeader;
+#pragma pack()
+
+#define VHD_DYNAMIC_DISK_HEADER_COOKIE "cxsparse"
+#define VHD_DYNAMIC_DISK_HEADER_COOKIE_SIZE 8
+#define VHD_DYNAMIC_DISK_HEADER_VERSION 0x00010000
+
+/**
+ * Complete VHD image data structure.
+ */
+typedef struct VHDIMAGE
+{
+ /** Base image name. */
+ const char *pszFilename;
+ /** Descriptor file if applicable. */
+ RTFILE File;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Error interface. */
+ PVDINTERFACE pInterfaceError;
+ /** Error interface callback table. */
+ PVDINTERFACEERROR pInterfaceErrorCallbacks;
+
+ /** Open flags passed by VBoxHD layer. */
+ unsigned uOpenFlags;
+ /** Image type. */
+ VDIMAGETYPE enmImageType;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+ /** Original size of the image. */
+ uint64_t cbOrigSize;
+ /** Physical geometry of this image. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ PDMMEDIAGEOMETRY LCHSGeometry;
+ /** Image UUID. */
+ RTUUID ImageUuid;
+ /** Parent image UUID. */
+ RTUUID ParentUuid;
+ /** Parent's time stamp at the time of image creation. */
+ uint32_t u32ParentTimeStamp;
+ /** Relative path to the parent image. */
+ char *pszParentFilename;
+ /** File size on the host disk (including all headers). */
+ uint64_t FileSize;
+ /** The Block Allocation Table. */
+ uint32_t *pBlockAllocationTable;
+ /** Number of entries in the table. */
+ uint32_t cBlockAllocationTableEntries;
+ /** Size of one data block. */
+ uint32_t cbDataBlock;
+ /** Sectors per data block. */
+ uint32_t cSectorsPerDataBlock;
+ /** Length of the sector bitmap in bytes. */
+ uint32_t cbDataBlockBitmap;
+ /** A copy of the disk footer. */
+ VHDFooter vhdFooterCopy;
+ /** Current end offset of the file (without the disk footer). */
+ uint64_t uCurrentEndOfFile;
+ /** Start offset of data blocks. */
+ uint64_t uDataBlockStart;
+ /** Size of the data block bitmap in sectors. */
+ uint32_t cDataBlockBitmapSectors;
+ /** Start of the block allocation table. */
+ uint64_t uBlockAllocationTableOffset;
+ /** Buffer to hold block's bitmap for bit search operations. */
+ uint8_t *pu8Bitmap;
+ /** Offset to the next data structure (dynamic disk header). */
+ uint64_t u64DataOffset;
+ /** Flag to force dynamic disk header update. */
+ bool fDynHdrNeedsUpdate;
+} VHDIMAGE, *PVHDIMAGE;
+
+/*******************************************************************************
+* Static Variables *
+*******************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const char *const s_apszVhdFileExtensions[] =
+{
+ "vhd",
+ NULL
+};
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+
+static int vhdFlush(void *pBackendData);
+static int vhdLoadDynamicDisk(PVHDIMAGE pImage, uint64_t uDynamicDiskHeaderOffset);
+
+/* 946684800 is a number of seconds between 1/1/1970 and 1/1/2000 */
+#define VHD_TO_UNIX_EPOCH_SECONDS UINT64_C(946684800)
+
+static uint32_t vhdRtTime2VhdTime(PCRTTIMESPEC pRtTimeStamp)
+{
+ uint64_t u64Seconds = RTTimeSpecGetSeconds(pRtTimeStamp);
+ return (uint32_t)(u64Seconds - VHD_TO_UNIX_EPOCH_SECONDS);
+}
+
+static void vhdTime2RtTime(PRTTIMESPEC pRtTimeStamp, uint32_t u32VhdTimeStamp)
+{
+ RTTimeSpecSetSeconds(pRtTimeStamp, VHD_TO_UNIX_EPOCH_SECONDS + u32VhdTimeStamp);
+}
+
+/**
+ * Internal: Compute and update header checksum.
+ */
+static uint32_t vhdChecksum(void *pHeader, uint32_t cbSize)
+{
+ uint32_t checksum = 0;
+ for (uint32_t i = 0; i < cbSize; i++)
+ checksum += ((unsigned char *)pHeader)[i];
+ return ~checksum;
+}
+
+static int vhdFilenameToUtf16(const char *pszFilename, void *pvBuf, uint32_t cbBufSize, uint32_t *pcbActualSize)
+{
+ int rc;
+ PRTUTF16 tmp16 = NULL;
+ size_t cTmp16Len;
+
+ rc = RTStrToUtf16(pszFilename, &tmp16);
+ if (RT_FAILURE(rc))
+ goto out;
+ cTmp16Len = RTUtf16Len(tmp16);
+ if (cTmp16Len * sizeof(*tmp16) > cbBufSize)
+ {
+ rc = VERR_FILENAME_TOO_LONG;
+ goto out;
+ }
+ memcpy(pvBuf, tmp16, cTmp16Len * sizeof(*tmp16));
+ if (pcbActualSize)
+ *pcbActualSize = cTmp16Len * sizeof(*tmp16);
+
+out:
+ if (tmp16)
+ RTUtf16Free(tmp16);
+ return rc;
+}
+
+/**
+ * Internal: Update one locator entry.
+ */
+static int vhdLocatorUpdate(PVHDIMAGE pImage, PVHDPLE pLocator, const char *pszFilename)
+{
+ int rc;
+ uint32_t cb, cbMaxLen = RT_BE2H_U32(pLocator->u32DataSpace) * VHD_SECTOR_SIZE;
+ void *pvBuf = RTMemTmpAllocZ(cbMaxLen);
+ char *pszTmp;
+
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ switch (RT_BE2H_U32(pLocator->u32Code))
+ {
+ case VHD_PLATFORM_CODE_WI2R:
+ /* Update plain relative name. */
+ cb = strlen(pszFilename);
+ if (cb > cbMaxLen)
+ {
+ rc = VERR_FILENAME_TOO_LONG;
+ goto out;
+ }
+ memcpy(pvBuf, pszFilename, cb);
+ pLocator->u32DataLength = RT_H2BE_U32(cb);
+ break;
+ case VHD_PLATFORM_CODE_WI2K:
+ /* Update plain absolute name. */
+ rc = RTPathAbs(pszFilename, (char *)pvBuf, cbMaxLen);
+ if (RT_FAILURE(rc))
+ goto out;
+ pLocator->u32DataLength = RT_H2BE_U32(strlen((char *)pvBuf));
+ break;
+ case VHD_PLATFORM_CODE_W2RU:
+ /* Update unicode relative name. */
+ rc = vhdFilenameToUtf16(pszFilename, pvBuf, cbMaxLen, &cb);
+ if (RT_FAILURE(rc))
+ goto out;
+ pLocator->u32DataLength = RT_H2BE_U32(cb);
+ break;
+ case VHD_PLATFORM_CODE_W2KU:
+ /* Update unicode absolute name. */
+ pszTmp = (char*)RTMemTmpAllocZ(cbMaxLen);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ rc = RTPathAbs(pszFilename, pszTmp, cbMaxLen);
+ if (RT_FAILURE(rc))
+ {
+ RTMemTmpFree(pszTmp);
+ goto out;
+ }
+ rc = vhdFilenameToUtf16(pszTmp, pvBuf, cbMaxLen, &cb);
+ RTMemTmpFree(pszTmp);
+ if (RT_FAILURE(rc))
+ goto out;
+ pLocator->u32DataLength = RT_H2BE_U32(cb);
+ break;
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ goto out;
+ }
+ rc = RTFileWriteAt(pImage->File, RT_BE2H_U64(pLocator->u64DataOffset), pvBuf,
+ RT_BE2H_U32(pLocator->u32DataSpace) * VHD_SECTOR_SIZE, NULL);
+
+out:
+ if (pvBuf)
+ RTMemTmpFree(pvBuf);
+ return rc;
+}
+
+/**
+ * Internal: Update dynamic disk header from VHDIMAGE.
+ */
+static int vhdDynamicHeaderUpdate(PVHDIMAGE pImage)
+{
+ VHDDynamicDiskHeader ddh;
+ int rc, i;
+
+ if (!pImage)
+ return VERR_VD_NOT_OPENED;
+
+ rc = RTFileReadAt(pImage->File, pImage->u64DataOffset, &ddh, sizeof(ddh), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (memcmp(ddh.Cookie, VHD_DYNAMIC_DISK_HEADER_COOKIE, VHD_DYNAMIC_DISK_HEADER_COOKIE_SIZE) != 0)
+ {
+ return VERR_VD_VHD_INVALID_HEADER;
+ }
+ uint32_t u32Checksum = RT_BE2H_U32(ddh.Checksum);
+ ddh.Checksum = 0;
+ if (u32Checksum != vhdChecksum(&ddh, sizeof(ddh)))
+ {
+ return VERR_VD_VHD_INVALID_HEADER;
+ }
+ /* Update parent's timestamp. */
+ ddh.ParentTimeStamp = RT_H2BE_U32(pImage->u32ParentTimeStamp);
+ /* Update parent's filename. */
+ rc = vhdFilenameToUtf16(RTPathFilename(pImage->pszParentFilename),
+ ddh.ParentUnicodeName, sizeof(ddh.ParentUnicodeName) - 1, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ /* Update parent's locators. */
+ for (i = 0; i < VHD_MAX_LOCATOR_ENTRIES; i++)
+ {
+ /* Skip empty locators */
+ if (ddh.ParentLocatorEntry[i].u32Code != RT_H2BE_U32(VHD_PLATFORM_CODE_NONE))
+ {
+ rc = vhdLocatorUpdate(pImage, &ddh.ParentLocatorEntry[i], pImage->pszParentFilename);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+ }
+ /* Update parent's UUID */
+ memcpy(ddh.ParentUuid, pImage->ParentUuid.au8, sizeof(ddh.ParentUuid));
+ ddh.Checksum = 0;
+ ddh.Checksum = RT_H2BE_U32(vhdChecksum(&ddh, sizeof(ddh)));
+ rc = RTFileWriteAt(pImage->File, pImage->u64DataOffset, &ddh, sizeof(ddh), NULL);
+
+out:
+ return rc;
+}
+
+
+static int vhdOpenImage(PVHDIMAGE pImage, unsigned uOpenFlags)
+{
+ RTFILE File;
+ uint64_t FileSize;
+ VHDFooter vhdFooter;
+
+ if (uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)
+ return VERR_NOT_SUPPORTED;
+
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pInterfaceError = VDInterfaceGet(pImage->pVDIfsDisk, VDINTERFACETYPE_ERROR);
+ if (pImage->pInterfaceError)
+ pImage->pInterfaceErrorCallbacks = VDGetInterfaceError(pImage->pInterfaceError);
+
+ /*
+ * Open the image.
+ */
+ int rc = RTFileOpen(&File, pImage->pszFilename, uOpenFlags & VD_OPEN_FLAGS_READONLY
+ ? RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+ : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ return rc;
+ }
+ pImage->File = File;
+
+ rc = RTFileGetSize(File, &FileSize);
+ pImage->FileSize = FileSize;
+ pImage->uCurrentEndOfFile = FileSize - sizeof(VHDFooter);
+
+ rc = RTFileReadAt(File, pImage->uCurrentEndOfFile, &vhdFooter, sizeof(VHDFooter), NULL);
+ if (memcmp(vhdFooter.Cookie, VHD_FOOTER_COOKIE, VHD_FOOTER_COOKIE_SIZE) != 0) {
+ return VERR_VD_VHD_INVALID_HEADER;
+ }
+
+ switch (RT_BE2H_U32(vhdFooter.DiskType))
+ {
+ case VHD_FOOTER_DISK_TYPE_FIXED:
+ {
+ pImage->enmImageType = VD_IMAGE_TYPE_FIXED;
+ }
+ break;
+ case VHD_FOOTER_DISK_TYPE_DYNAMIC:
+ {
+ pImage->enmImageType = VD_IMAGE_TYPE_NORMAL;
+ }
+ break;
+ case VHD_FOOTER_DISK_TYPE_DIFFERENCING:
+ {
+ pImage->enmImageType = VD_IMAGE_TYPE_DIFF;
+ }
+ break;
+ default:
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ pImage->cbSize = RT_BE2H_U64(vhdFooter.CurSize);
+ pImage->LCHSGeometry.cCylinders = 0;
+ pImage->LCHSGeometry.cHeads = 0;
+ pImage->LCHSGeometry.cSectors = 0;
+ pImage->PCHSGeometry.cCylinders = RT_BE2H_U16(vhdFooter.DiskGeometryCylinder);
+ pImage->PCHSGeometry.cHeads = vhdFooter.DiskGeometryHeads;
+ pImage->PCHSGeometry.cSectors = vhdFooter.DiskGeometrySectors;
+
+ /*
+ * Copy of the disk footer.
+ * If we allocate new blocks in differencing disks on write access
+ * the footer is overwritten. We need to write it at the end of the file.
+ */
+ memcpy(&pImage->vhdFooterCopy, &vhdFooter, sizeof(VHDFooter));
+
+ /*
+ * Is there a better way?
+ */
+ memcpy(&pImage->ImageUuid, &vhdFooter.UniqueID, 16);
+
+ pImage->u64DataOffset = RT_BE2H_U64(vhdFooter.DataOffset);
+ LogFlowFunc(("DataOffset=%llu\n", pImage->u64DataOffset));
+
+ if (pImage->enmImageType == VD_IMAGE_TYPE_NORMAL || pImage->enmImageType == VD_IMAGE_TYPE_DIFF)
+ rc = vhdLoadDynamicDisk(pImage, pImage->u64DataOffset);
+
+ return rc;
+}
+
+static int vhdOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ void **ppvBackendData)
+{
+ int rc = VINF_SUCCESS;
+ PVHDIMAGE pImage;
+
+ /* Check open flags. All valid flags are supported. */
+ if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ return rc;
+ }
+
+ pImage = (PVHDIMAGE)RTMemAllocZ(sizeof(VHDIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ return rc;
+ }
+ pImage->pszFilename = pszFilename;
+ pImage->File = NIL_RTFILE;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+
+ rc = vhdOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppvBackendData = pImage;
+ return rc;
+}
+
+static int vhdLoadDynamicDisk(PVHDIMAGE pImage, uint64_t uDynamicDiskHeaderOffset)
+{
+ VHDDynamicDiskHeader vhdDynamicDiskHeader;
+ int rc = VINF_SUCCESS;
+ uint32_t *pBlockAllocationTable;
+ uint64_t uBlockAllocationTableOffset;
+ unsigned i = 0;
+
+ Log(("Open a dynamic disk.\n"));
+
+ /*
+ * Read the dynamic disk header.
+ */
+ rc = RTFileReadAt(pImage->File, uDynamicDiskHeaderOffset, &vhdDynamicDiskHeader, sizeof(VHDDynamicDiskHeader), NULL);
+ if (memcmp(vhdDynamicDiskHeader.Cookie, VHD_DYNAMIC_DISK_HEADER_COOKIE, VHD_DYNAMIC_DISK_HEADER_COOKIE_SIZE) != 0)
+ return VERR_INVALID_PARAMETER;
+
+ pImage->cbDataBlock = RT_BE2H_U32(vhdDynamicDiskHeader.BlockSize);
+ LogFlowFunc(("BlockSize=%u\n", pImage->cbDataBlock));
+ pImage->cBlockAllocationTableEntries = RT_BE2H_U32(vhdDynamicDiskHeader.MaxTableEntries);
+ LogFlowFunc(("MaxTableEntries=%lu\n", pImage->cBlockAllocationTableEntries));
+ AssertMsg(!(pImage->cbDataBlock % 512), ("%s: Data block size is not a multiple of 512!!\n", __FUNCTION__));
+
+ pImage->cSectorsPerDataBlock = pImage->cbDataBlock / 512;
+ LogFlowFunc(("SectorsPerDataBlock=%u\n", pImage->cSectorsPerDataBlock));
+
+ /*
+ * Every block starts with a bitmap indicating which sectors are valid and which are not.
+ * We store the size of it to be able to calculate the real offset.
+ */
+ pImage->cbDataBlockBitmap = pImage->cSectorsPerDataBlock / 8;
+ pImage->cDataBlockBitmapSectors = pImage->cbDataBlockBitmap / 512;
+ LogFlowFunc(("cbDataBlockBitmap=%u\n", pImage->cbDataBlockBitmap));
+
+ pImage->pu8Bitmap = (uint8_t *)RTMemAllocZ(pImage->cbDataBlockBitmap);
+ if (!pImage->pu8Bitmap)
+ return VERR_NO_MEMORY;
+
+ pBlockAllocationTable = (uint32_t *)RTMemAllocZ(pImage->cBlockAllocationTableEntries * sizeof(uint32_t));
+ if (!pBlockAllocationTable)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Read the table.
+ */
+ uBlockAllocationTableOffset = RT_BE2H_U64(vhdDynamicDiskHeader.TableOffset);
+ LogFlowFunc(("uBlockAllocationTableOffset=%llu\n", uBlockAllocationTableOffset));
+ pImage->uBlockAllocationTableOffset = uBlockAllocationTableOffset;
+ rc = RTFileReadAt(pImage->File, uBlockAllocationTableOffset, pBlockAllocationTable, pImage->cBlockAllocationTableEntries * sizeof(uint32_t), NULL);
+ pImage->uDataBlockStart = uBlockAllocationTableOffset + pImage->cBlockAllocationTableEntries * sizeof(uint32_t);
+ LogFlowFunc(("uDataBlockStart=%llu\n", pImage->uDataBlockStart));
+
+ /*
+ * Because the offset entries inside the allocation table are stored big endian
+ * we need to convert them into host endian.
+ */
+ pImage->pBlockAllocationTable = (uint32_t *)RTMemAllocZ(pImage->cBlockAllocationTableEntries * sizeof(uint32_t));
+ if (!pImage->pBlockAllocationTable)
+ return VERR_NO_MEMORY;
+
+ for (i = 0; i < pImage->cBlockAllocationTableEntries; i++)
+ {
+ pImage->pBlockAllocationTable[i] = RT_BE2H_U32(pBlockAllocationTable[i]);
+ }
+
+ RTMemFree(pBlockAllocationTable);
+
+ if (pImage->enmImageType == VD_IMAGE_TYPE_DIFF)
+ memcpy(pImage->ParentUuid.au8, vhdDynamicDiskHeader.ParentUuid, sizeof(pImage->ParentUuid));
+
+ return rc;
+}
+
+static int vhdCheckIfValid(const char *pszFilename)
+{
+ int rc = VINF_SUCCESS;
+ RTFILE File;
+ uint64_t cbFile;
+ VHDFooter vhdFooter;
+
+ rc = RTFileOpen(&File, pszFilename, RTFILE_O_READ | RTFILE_O_OPEN);
+ if (RT_FAILURE(rc))
+ return VERR_VD_VHD_INVALID_HEADER;
+
+ rc = RTFileGetSize(File, &cbFile);
+ if (RT_FAILURE(rc))
+ {
+ RTFileClose(File);
+ return VERR_VD_VHD_INVALID_HEADER;
+ }
+
+ rc = RTFileReadAt(File, cbFile - sizeof(VHDFooter), &vhdFooter, sizeof(VHDFooter), NULL);
+ if (RT_FAILURE(rc) || (memcmp(vhdFooter.Cookie, VHD_FOOTER_COOKIE, VHD_FOOTER_COOKIE_SIZE) != 0))
+ rc = VERR_VD_VHD_INVALID_HEADER;
+ else
+ rc = VINF_SUCCESS;
+
+ RTFileClose(File);
+
+ return rc;
+}
+
+static unsigned vhdGetVersion(void *pBackendData)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ return 1; /**< @todo use correct version */
+ else
+ return 0;
+}
+
+static int vhdGetImageType(void *pBackendData, PVDIMAGETYPE penmImageType)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ AssertPtr(penmImageType);
+
+ if (pImage)
+ *penmImageType = pImage->enmImageType;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ return rc;
+}
+
+static int vhdGetPCHSGeometry(void *pBackendData, PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->PCHSGeometry.cCylinders)
+ {
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returned %Rrc (CHS=%u/%u/%u)\n", rc, pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors));
+ return rc;
+}
+
+static int vhdSetPCHSGeometry(void *pBackendData, PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdGetLCHSGeometry(void *pBackendData, PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->LCHSGeometry.cCylinders)
+ {
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returned %Rrc (CHS=%u/%u/%u)\n", rc, pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors));
+ return rc;
+}
+
+static int vhdSetLCHSGeometry(void *pBackendData, PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static unsigned vhdGetImageFlags(void *pBackendData)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ unsigned uImageFlags;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ uImageFlags = pImage->uImageFlags;
+ else
+ uImageFlags = 0;
+
+ LogFlowFunc(("returned %#x\n", uImageFlags));
+ return uImageFlags;
+}
+
+static unsigned vhdGetOpenFlags(void *pBackendData)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ unsigned uOpenFlags;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ uOpenFlags = pImage->uOpenFlags;
+ else
+ uOpenFlags = 0;
+
+ LogFlowFunc(("returned %d\n", uOpenFlags));
+ return uOpenFlags;
+}
+
+static int vhdSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ /* Image must be opened and the new flags must be valid. Just readonly and
+ * info flags are supported. */
+ if (!pImage || (uOpenFlags & ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO)))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ rc = vhdFlush(pImage);
+ if (RT_FAILURE(rc))
+ goto out;
+ RTFileClose(pImage->File);
+ pImage->uOpenFlags = uOpenFlags;
+ rc = RTFileOpen(&pImage->File, pImage->pszFilename, uOpenFlags & VD_OPEN_FLAGS_READONLY
+ ? RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+ : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+
+out:
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdRename(void *pBackendData, const char *pszFilename)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static void vhdFreeImageMemory(PVHDIMAGE pImage)
+{
+ if (pImage->pszParentFilename)
+ {
+ RTStrFree(pImage->pszParentFilename);
+ pImage->pszParentFilename = NULL;
+ }
+ if (pImage->pBlockAllocationTable)
+ {
+ RTMemFree(pImage->pBlockAllocationTable);
+ pImage->pBlockAllocationTable = NULL;
+ }
+ if (pImage->pu8Bitmap)
+ {
+ RTMemFree(pImage->pu8Bitmap);
+ pImage->pu8Bitmap = NULL;
+ }
+ RTMemFree(pImage);
+}
+
+static int vhdFreeImage(PVHDIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage) {
+ vhdFlush(pImage);
+ RTFileClose(pImage->File);
+ vhdFreeImageMemory(pImage);
+ }
+
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdClose(void *pBackendData, bool fDelete)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage) {
+ if (fDelete)
+ {
+ /* No point in updating the file that is deleted anyway. */
+ RTFileClose(pImage->File);
+ RTFileDelete(pImage->pszFilename);
+ vhdFreeImageMemory(pImage);
+ }
+ else
+ rc = vhdFreeImage(pImage);
+ }
+
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdRead(void *pBackendData, uint64_t uOffset, void *pvBuf, size_t cbRead, size_t *pcbActuallyRead)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pBackendData=%p uOffset=%#llx pvBuf=%p cbRead=%u pcbActuallyRead=%p\n", pBackendData, uOffset, pvBuf, cbRead, pcbActuallyRead));
+
+ if (uOffset + cbRead > pImage->cbSize)
+ return VERR_INVALID_PARAMETER;
+
+ /*
+ * If we have a dynamic disk image, we need to find the data block and sector to read.
+ */
+ if (pImage->pBlockAllocationTable)
+ {
+ /*
+ * Get the data block first.
+ */
+ uint32_t cBlockAllocationTableEntry = (uOffset / 512) / pImage->cSectorsPerDataBlock;
+ uint32_t cBATEntryIndex = (uOffset / 512) % pImage->cSectorsPerDataBlock;
+ uint64_t uVhdOffset;
+
+ LogFlowFunc(("cBlockAllocationTableEntry=%u cBatEntryIndex=%u\n", cBlockAllocationTableEntry, cBATEntryIndex));
+ LogFlowFunc(("BlockAllocationEntry=%u\n", pImage->pBlockAllocationTable[cBlockAllocationTableEntry]));
+
+ /*
+ * If the block is not allocated the content of the entry is ~0
+ */
+ if (pImage->pBlockAllocationTable[cBlockAllocationTableEntry] == ~0U)
+ {
+ /* Return block size as read. */
+ *pcbActuallyRead = RT_MIN(cbRead, pImage->cSectorsPerDataBlock * VHD_SECTOR_SIZE);
+ return VERR_VD_BLOCK_FREE;
+ }
+
+ uVhdOffset = ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry] + pImage->cDataBlockBitmapSectors + cBATEntryIndex) * 512;
+ LogFlowFunc(("uVhdOffset=%llu cbRead=%u\n", uVhdOffset, cbRead));
+
+ /*
+ * Clip read range to remain in this data block.
+ */
+ cbRead = RT_MIN(cbRead, (pImage->cbDataBlock - (cBATEntryIndex * 512)));
+
+ /* Read in the block's bitmap. */
+ rc = RTFileReadAt(pImage->File,
+ (uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry] * VHD_SECTOR_SIZE,
+ pImage->pu8Bitmap, pImage->cbDataBlockBitmap, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cSectors = 0;
+ uint32_t iBitmap = cBATEntryIndex / 8; /* Byte in the block bitmap. */
+ Assert(iBitmap < pImage->cbDataBlockBitmap);
+
+ /*
+ * The index of the bit in the byte of the data block bitmap.
+ * The most signifcant bit stands for a lower sector number.
+ *
+ * There are 8 bits in a byte but they go from 0 .. 7
+ * hence the (8 - 1) to get the index in the bitmap byte.
+ */
+ uint8_t iBitInByte = (8 - 1) - (cBATEntryIndex % 8);
+ uint8_t *puBitmap = pImage->pu8Bitmap + iBitmap;
+
+ if (ASMBitTest(puBitmap, iBitInByte))
+ {
+ uint32_t iBATEntryIndexCurr = cBATEntryIndex + 1;
+
+ /*
+ * The first sector being read is marked dirty, read as much as we
+ * can from child. Note that only sectors that are marked dirty
+ * must be read from child.
+ */
+ do
+ {
+ cSectors++;
+
+ iBitmap = iBATEntryIndexCurr / 8; /* Byte in the block bitmap. */
+ iBitInByte = (8 - 1) - (iBATEntryIndexCurr % 8);
+ puBitmap = pImage->pu8Bitmap + iBitmap;
+ if (!ASMBitTest(puBitmap, iBitInByte))
+ break;
+
+ iBATEntryIndexCurr++;
+ } while (cSectors < (cbRead / VHD_SECTOR_SIZE));
+
+ cbRead = cSectors * VHD_SECTOR_SIZE;
+
+ LogFlowFunc(("uVhdOffset=%llu cbRead=%u\n", uVhdOffset, cbRead));
+ rc = RTFileReadAt(pImage->File, uVhdOffset, pvBuf, cbRead, NULL);
+ }
+ else
+ {
+ uint32_t iBATEntryIndexCurr = cBATEntryIndex + 1;
+
+ /*
+ * The first sector being read is marked clean, so we should read from
+ * our parent instead, but only as much as there are the following
+ * clean sectors, because the block may still contain dirty sectors
+ * further on. We just need to compute the number of clean sectors
+ * and pass it to our caller along with the notification that they
+ * should be read from the parent.
+ */
+ do
+ {
+ cSectors++;
+
+ iBitmap = iBATEntryIndexCurr / 8; /* Byte in the block bitmap. */
+ iBitInByte = (8 - 1) - (iBATEntryIndexCurr % 8);
+ puBitmap = pImage->pu8Bitmap + iBitmap;
+ if (ASMBitTest(puBitmap, iBitInByte))
+ break;
+
+ iBATEntryIndexCurr++;
+ } while (cSectors < (cbRead / VHD_SECTOR_SIZE));
+
+ cbRead = cSectors * VHD_SECTOR_SIZE;
+ Log(("%s: Sectors free: uVhdOffset=%llu cbRead=%u\n", uVhdOffset, cbRead));
+ rc = VERR_VD_BLOCK_FREE;
+ }
+ }
+ else
+ AssertMsgFailed(("Reading block bitmap failed rc=%Rrc\n", rc));
+ }
+ else
+ {
+ rc = RTFileReadAt(pImage->File, uOffset, pvBuf, cbRead, NULL);
+ }
+
+ if (pcbActuallyRead)
+ *pcbActuallyRead = cbRead;
+
+ Log2(("vhdRead: off=%#llx pvBuf=%p cbRead=%d\n"
+ "%.*Rhxd\n",
+ uOffset, pvBuf, cbRead, cbRead, pvBuf));
+
+ return rc;
+}
+
+static int vhdWrite(void *pBackendData, uint64_t uOffset, const void *pvBuf, size_t cbToWrite, size_t *pcbWriteProcess, size_t *pcbPreRead, size_t *pcbPostRead, unsigned fWrite)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pBackendData=%p uOffset=%llu pvBuf=%p cbToWrite=%u pcbWriteProcess=%p pcbPreRead=%p pcbPostRead=%p fWrite=%u\n",
+ pBackendData, uOffset, pvBuf, cbToWrite, pcbPreRead, pcbPostRead, fWrite));
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToWrite % 512 == 0);
+
+ if (pImage->pBlockAllocationTable)
+ {
+ /*
+ * Get the data block first.
+ */
+ uint32_t cSector = uOffset / 512;
+ uint32_t cBlockAllocationTableEntry = cSector / pImage->cSectorsPerDataBlock;
+ uint32_t cBATEntryIndex = cSector % pImage->cSectorsPerDataBlock;
+ uint64_t uVhdOffset;
+
+ /*
+ * If the block is not allocated the content of the entry is ~0
+ * and we need to allocate a new block. Note that while blocks are
+ * allocated with a relatively big granularity, each sector has its
+ * own bitmap entry, indicating whether it has been written or not.
+ * So that means for the purposes of the higher level that the
+ * granularity is invisible.
+ */
+ if (pImage->pBlockAllocationTable[cBlockAllocationTableEntry] == ~0U)
+ {
+ size_t cbNewBlock = (pImage->cbDataBlock + pImage->cbDataBlockBitmap) * sizeof(uint8_t);
+ uint8_t *pNewBlock = (uint8_t *)RTMemAllocZ(cbNewBlock);
+
+ if (!pNewBlock)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Write the new block at the current end of the file.
+ */
+ rc = RTFileWriteAt(pImage->File, pImage->uCurrentEndOfFile, pNewBlock, cbNewBlock, NULL);
+
+ /*
+ * Set the new end of the file and link the new block into the BAT.
+ */
+ pImage->pBlockAllocationTable[cBlockAllocationTableEntry] = pImage->uCurrentEndOfFile / 512;
+ pImage->uCurrentEndOfFile += cbNewBlock;
+ RTMemFree(pNewBlock);
+ }
+
+ /*
+ * Calculate the real offset in the file.
+ */
+ uVhdOffset = ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry] + pImage->cDataBlockBitmapSectors + cBATEntryIndex) * 512;
+
+ /*
+ * Clip write range.
+ */
+ cbToWrite = RT_MIN(cbToWrite, (pImage->cbDataBlock - (cBATEntryIndex * 512)));
+ RTFileWriteAt(pImage->File, uVhdOffset, pvBuf, cbToWrite, NULL);
+
+ /* Read in the block's bitmap. */
+ rc = RTFileReadAt(pImage->File,
+ (uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry] * VHD_SECTOR_SIZE,
+ pImage->pu8Bitmap, pImage->cbDataBlockBitmap, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Set the bits for all sectors having been written. */
+ for (uint32_t iSector = 0; iSector < (cbToWrite / VHD_SECTOR_SIZE); iSector++)
+ {
+ uint32_t iBitmap = cBATEntryIndex / 8; /* Byte in the block bitmap. */
+ uint8_t iBitInByte = (8 - 1) - (cBATEntryIndex % 8);
+ uint8_t *puBitmap = pImage->pu8Bitmap + iBitmap;
+
+ ASMBitSet(puBitmap, iBitInByte);
+ cBATEntryIndex++;
+ }
+
+ /* Write the bitmap back. */
+ rc = RTFileWriteAt(pImage->File,
+ pImage->pBlockAllocationTable[cBlockAllocationTableEntry] * VHD_SECTOR_SIZE,
+ pImage->pu8Bitmap, pImage->cbDataBlockBitmap, NULL);
+ }
+ }
+ else
+ {
+ rc = RTFileWriteAt(pImage->File, uOffset, pvBuf, cbToWrite, NULL);
+ }
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+
+ /* Stay on the safe side. Do not run the risk of confusing the higher
+ * level, as that can be pretty lethal to image consistency. */
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+
+ return rc;
+}
+
+static int vhdFlush(void *pBackendData)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ return VINF_SUCCESS;
+
+ if (pImage->pBlockAllocationTable)
+ {
+ /*
+ * This is an expanding image. Write the BAT and copy of the disk footer.
+ */
+ size_t cbBlockAllocationTableToWrite = pImage->cBlockAllocationTableEntries * sizeof(uint32_t);
+ uint32_t *pBlockAllocationTableToWrite = (uint32_t *)RTMemAllocZ(cbBlockAllocationTableToWrite);
+
+ if (!pBlockAllocationTableToWrite)
+ return VERR_NO_MEMORY;
+
+ /*
+ * The BAT entries have to be stored in big endian format.
+ */
+ unsigned i = 0;
+ for (i = 0; i < pImage->cBlockAllocationTableEntries; i++)
+ {
+ pBlockAllocationTableToWrite[i] = RT_H2BE_U32(pImage->pBlockAllocationTable[i]);
+ }
+
+ /*
+ * Write the block allocation table after the copy of the disk footer and the dynamic disk header.
+ */
+ RTFileWriteAt(pImage->File, pImage->uBlockAllocationTableOffset, pBlockAllocationTableToWrite, cbBlockAllocationTableToWrite, NULL);
+ RTFileWriteAt(pImage->File, pImage->uCurrentEndOfFile, &pImage->vhdFooterCopy, sizeof(VHDFooter), NULL);
+ if (pImage->fDynHdrNeedsUpdate)
+ vhdDynamicHeaderUpdate(pImage);
+ RTMemFree(pBlockAllocationTableToWrite);
+ }
+
+ int rc = RTFileFlush(pImage->File);
+
+ return rc;
+}
+
+static uint64_t vhdGetSize(void *pBackendData)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ Log(("%s: cbSize=%llu\n", __FUNCTION__, pImage->cbSize));
+ return pImage->cbSize;
+ }
+ else
+ return 0;
+}
+
+static uint64_t vhdGetFileSize(void *pBackendData)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ uint64_t cb;
+ int rc = RTFileGetSize(pImage->File, &cb);
+ if (RT_SUCCESS(rc))
+ return cb;
+ else
+ return 0;
+ }
+ else
+ return 0;
+}
+
+static int vhdGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ *pUuid = pImage->ImageUuid;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+static int vhdSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ LogFlowFunc(("Uuid=%RTuuid\n", pUuid));
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ pImage->ImageUuid = *pUuid;
+ /**@todo: implement */
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdGetComment(void *pBackendData, char *pszComment, size_t cbComment)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returned %Rrc comment='%s'\n", rc, pszComment));
+ return rc;
+}
+
+static int vhdSetComment(void *pBackendData, const char *pszComment)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ LogFlowFunc(("pszComment='%s'\n", pszComment));
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ /**@todo: implement */
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+static int vhdSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ LogFlowFunc(("Uuid=%RTuuid\n", pUuid));
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdGetParentUuid(void *pBackendData, PRTUUID pUuid)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ *pUuid = pImage->ParentUuid;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+static int vhdSetParentUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc((" %RTuuid\n", pUuid));
+ AssertPtr(pImage);
+
+ if (pImage && pImage->File != NIL_RTFILE)
+ {
+ if (pImage->enmImageType != VD_IMAGE_TYPE_FIXED)
+ {
+ pImage->ParentUuid = *pUuid;
+ pImage->fDynHdrNeedsUpdate = true;
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdGetParentModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+static int vhdSetParentModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc;
+
+ LogFlowFunc(("%RTuuid\n", pUuid));
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal: Derive drive geometry from its size.
+ */
+static void vhdSetDiskGeometry(PVHDIMAGE pImage, uint64_t cbSize)
+{
+ uint64_t u64TotalSectors = cbSize / 512;
+ uint32_t u32CylinderTimesHeads, u32Heads, u32SectorsPerTrack;
+
+ if (u64TotalSectors > 65535 * 16 * 255)
+ {
+ /* ATA disks limited to 127 GB. */
+ u64TotalSectors = 65535 * 16 * 255;
+ }
+
+ if (u64TotalSectors >= 65535 * 16 * 63)
+ {
+ u32SectorsPerTrack = 255;
+ u32Heads = 16;
+ u32CylinderTimesHeads = u64TotalSectors / u32SectorsPerTrack;
+ }
+ else
+ {
+ u32SectorsPerTrack = 17;
+ u32CylinderTimesHeads = u64TotalSectors / u32SectorsPerTrack;
+
+ u32Heads = (u32CylinderTimesHeads + 1023) / 1024;
+
+ if (u32Heads < 4)
+ {
+ u32Heads = 4;
+ }
+ if (u32CylinderTimesHeads >= (u32Heads * 1024) || u32Heads > 16)
+ {
+ u32SectorsPerTrack = 31;
+ u32Heads = 16;
+ u32CylinderTimesHeads = u64TotalSectors / u32SectorsPerTrack;
+ }
+ if (u32CylinderTimesHeads >= (u32Heads * 1024))
+ {
+ u32SectorsPerTrack = 63;
+ u32Heads = 16;
+ u32CylinderTimesHeads = u64TotalSectors / u32SectorsPerTrack;
+ }
+ }
+ pImage->PCHSGeometry.cCylinders = u32CylinderTimesHeads / u32Heads;
+ pImage->PCHSGeometry.cHeads = u32Heads;
+ pImage->PCHSGeometry.cSectors = u32SectorsPerTrack;
+ pImage->LCHSGeometry.cCylinders = 0;
+ pImage->LCHSGeometry.cHeads = 0;
+ pImage->LCHSGeometry.cSectors = 0;
+}
+
+
+/**
+ * Internal: signal an error to the frontend.
+ */
+DECLINLINE(int) vhdError(PVHDIMAGE pImage, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pImage->pInterfaceError && pImage->pInterfaceErrorCallbacks)
+ pImage->pInterfaceErrorCallbacks->pfnError(pImage->pInterfaceError->pvUser, rc, RT_SRC_POS_ARGS,
+ pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+static uint32_t vhdAllocateParentLocators(PVHDIMAGE pImage, VHDDynamicDiskHeader *pDDH, uint64_t u64Offset)
+{
+ PVHDPLE pLocator = pDDH->ParentLocatorEntry;
+ /* Relative Windows path. */
+ pLocator->u32Code = RT_H2BE_U32(VHD_PLATFORM_CODE_WI2R);
+ pLocator->u32DataSpace = RT_H2BE_U32(VHD_RELATIVE_MAX_PATH / VHD_SECTOR_SIZE);
+ pLocator->u64DataOffset = RT_H2BE_U64(u64Offset);
+ u64Offset += VHD_RELATIVE_MAX_PATH;
+ pLocator++;
+ /* Absolute Windows path. */
+ pLocator->u32Code = RT_H2BE_U32(VHD_PLATFORM_CODE_WI2K);
+ pLocator->u32DataSpace = RT_H2BE_U32(VHD_ABSOLUTE_MAX_PATH / VHD_SECTOR_SIZE);
+ pLocator->u64DataOffset = RT_H2BE_U64(u64Offset);
+ u64Offset += VHD_ABSOLUTE_MAX_PATH;
+ pLocator++;
+ /* Unicode relative Windows path. */
+ pLocator->u32Code = RT_H2BE_U32(VHD_PLATFORM_CODE_W2RU);
+ pLocator->u32DataSpace = RT_H2BE_U32(VHD_RELATIVE_MAX_PATH * sizeof(RTUTF16) / VHD_SECTOR_SIZE);
+ pLocator->u64DataOffset = RT_H2BE_U64(u64Offset);
+ u64Offset += VHD_RELATIVE_MAX_PATH * sizeof(RTUTF16);
+ pLocator++;
+ /* Unicode absolute Windows path. */
+ pLocator->u32Code = RT_H2BE_U32(VHD_PLATFORM_CODE_W2KU);
+ pLocator->u32DataSpace = RT_H2BE_U32(VHD_ABSOLUTE_MAX_PATH * sizeof(RTUTF16) / VHD_SECTOR_SIZE);
+ pLocator->u64DataOffset = RT_H2BE_U64(u64Offset);
+ return u64Offset + VHD_ABSOLUTE_MAX_PATH * sizeof(RTUTF16);
+}
+
+/**
+ * Internal: Additional code for dynamic VHD image creation.
+ */
+static int vhdCreateDynamicImage(PVHDIMAGE pImage, uint64_t cbSize)
+{
+ int rc;
+ VHDDynamicDiskHeader DynamicDiskHeader;
+ uint32_t u32BlockAllocationTableSectors;
+
+ memset(&DynamicDiskHeader, 0, sizeof(DynamicDiskHeader));
+
+ pImage->u64DataOffset = sizeof(VHDFooter);
+ pImage->cbDataBlock = VHD_BLOCK_SIZE; /* 2 MB */
+ pImage->cSectorsPerDataBlock = pImage->cbDataBlock / VHD_SECTOR_SIZE;
+ pImage->cbDataBlockBitmap = pImage->cSectorsPerDataBlock / 8;
+ pImage->cDataBlockBitmapSectors = pImage->cbDataBlockBitmap / VHD_SECTOR_SIZE;
+ pImage->pu8Bitmap = (uint8_t *)RTMemAllocZ(pImage->cbDataBlockBitmap);
+ if (!pImage->pu8Bitmap)
+ return vhdError(pImage, VERR_NO_MEMORY, RT_SRC_POS, N_("VHD: cannot allocate memory for bitmap storage"));
+
+ /* Initialize BAT. */
+ pImage->uBlockAllocationTableOffset = (uint64_t)sizeof(VHDFooter) + sizeof(VHDDynamicDiskHeader);
+ pImage->cBlockAllocationTableEntries = (uint32_t)(cbSize / pImage->cbDataBlock);
+ u32BlockAllocationTableSectors = (pImage->cBlockAllocationTableEntries * sizeof(uint32_t) + VHD_SECTOR_SIZE - 1) / VHD_SECTOR_SIZE;
+ pImage->pBlockAllocationTable = (uint32_t *)RTMemAllocZ(pImage->cBlockAllocationTableEntries * sizeof(uint32_t));
+ if (!pImage->pBlockAllocationTable)
+ return vhdError(pImage, VERR_NO_MEMORY, RT_SRC_POS, N_("VHD: cannot allocate memory for BAT"));
+
+ for (unsigned i = 0; i < pImage->cBlockAllocationTableEntries; i++)
+ {
+ pImage->pBlockAllocationTable[i] = 0xFFFFFFFF; /* It is actually big endian. */
+ }
+ /* Round up to the sector size. */
+ pImage->uCurrentEndOfFile = vhdAllocateParentLocators(pImage, &DynamicDiskHeader,
+ pImage->uBlockAllocationTableOffset + u32BlockAllocationTableSectors * VHD_SECTOR_SIZE);
+
+ rc = RTFileSetSize(pImage->File, pImage->uCurrentEndOfFile + sizeof(VHDFooter));
+ if (RT_FAILURE(rc))
+ return vhdError(pImage, rc, RT_SRC_POS, N_("VHD: cannot set the file size for '%s'"), pImage->pszFilename);
+
+ /* Initialize and write the dynamic disk header. */
+ memcpy(DynamicDiskHeader.Cookie, VHD_DYNAMIC_DISK_HEADER_COOKIE, sizeof(DynamicDiskHeader.Cookie));
+ DynamicDiskHeader.DataOffset = UINT64_C(0xFFFFFFFFFFFFFFFF); /* Initially the disk has no data. */
+ DynamicDiskHeader.TableOffset = RT_H2BE_U64(pImage->uBlockAllocationTableOffset);
+ DynamicDiskHeader.HeaderVersion = RT_H2BE_U32(VHD_DYNAMIC_DISK_HEADER_VERSION);
+ DynamicDiskHeader.BlockSize = RT_H2BE_U32(pImage->cbDataBlock);
+ DynamicDiskHeader.MaxTableEntries = RT_H2BE_U32(pImage->cBlockAllocationTableEntries);
+ /* Compute and update checksum. */
+ DynamicDiskHeader.Checksum = 0;
+ DynamicDiskHeader.Checksum = RT_H2BE_U32(vhdChecksum(&DynamicDiskHeader, sizeof(DynamicDiskHeader)));
+
+ rc = RTFileWriteAt(pImage->File, sizeof(VHDFooter), &DynamicDiskHeader, sizeof(DynamicDiskHeader), NULL);
+ if (RT_FAILURE(rc))
+ return vhdError(pImage, rc, RT_SRC_POS, N_("VHD: cannot write dynamic disk header to image '%s'"), pImage->pszFilename);
+
+ /* Write BAT. */
+ rc = RTFileWriteAt(pImage->File, pImage->uBlockAllocationTableOffset, pImage->pBlockAllocationTable,
+ pImage->cBlockAllocationTableEntries * sizeof(uint32_t), NULL);
+ if (RT_FAILURE(rc))
+ return vhdError(pImage, rc, RT_SRC_POS, N_("VHD: cannot write BAT to image '%s'"), pImage->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Internal: The actual code for VHD image creation, both fixed and dynamic.
+ */
+static int vhdCreateImage(PVHDIMAGE pImage, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ const char *pszComment,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry, PCRTUUID pUuid,
+ unsigned uOpenFlags,
+ PFNVMPROGRESS pfnProgress, void *pvUser,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ int rc;
+ RTFILE File;
+ VHDFooter Footer;
+ RTTIMESPEC now;
+
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pInterfaceError = VDInterfaceGet(pImage->pVDIfsDisk, VDINTERFACETYPE_ERROR);
+ if (pImage->pInterfaceError)
+ pImage->pInterfaceErrorCallbacks = VDGetInterfaceError(pImage->pInterfaceError);
+
+ /* Create image file. */
+ rc = RTFileOpen(&File, pImage->pszFilename,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_ALL);
+ if (RT_FAILURE(rc))
+ return vhdError(pImage, rc, RT_SRC_POS, N_("VHD: cannot create image '%s'"), pImage->pszFilename);
+ pImage->File = File;
+
+ pImage->enmImageType = enmType;
+ pImage->cbSize = cbSize;
+ pImage->ImageUuid = *pUuid;
+ RTUuidClear(&pImage->ParentUuid);
+ vhdSetDiskGeometry(pImage, cbSize);
+
+ /* Initialize the footer. */
+ memset(&Footer, 0, sizeof(Footer));
+ memcpy(Footer.Cookie, VHD_FOOTER_COOKIE, sizeof(Footer.Cookie));
+ Footer.Features = RT_H2BE_U32(0x2);
+ Footer.Version = RT_H2BE_U32(VHD_FOOTER_FILE_FORMAT_VERSION);
+ Footer.TimeStamp = RT_H2BE_U32(vhdRtTime2VhdTime(RTTimeNow(&now)));
+ memcpy(Footer.CreatorApp, "vbox", sizeof(Footer.CreatorApp));
+ Footer.CreatorVer = RT_H2BE_U32(VBOX_VERSION);
+#ifdef RT_OS_DARWIN
+ Footer.CreatorOS = RT_H2BE_U32(0x4D616320); /* "Mac " */
+#else /* Virtual PC supports only two platforms atm, so everything else will be Wi2k. */
+ Footer.CreatorOS = RT_H2BE_U32(0x5769326B); /* "Wi2k" */
+#endif
+ Footer.OrigSize = RT_H2BE_U64(cbSize);
+ Footer.CurSize = Footer.OrigSize;
+ Footer.DiskGeometryCylinder = RT_H2BE_U16(pImage->PCHSGeometry.cCylinders);
+ Footer.DiskGeometryHeads = pImage->PCHSGeometry.cHeads;
+ Footer.DiskGeometrySectors = pImage->PCHSGeometry.cSectors;
+ memcpy(Footer.UniqueID, pImage->ImageUuid.au8, sizeof(Footer.UniqueID));
+ Footer.SavedState = 0;
+
+ switch (enmType)
+ {
+ case VD_IMAGE_TYPE_FIXED:
+ Footer.DiskType = RT_H2BE_U32(VHD_FOOTER_DISK_TYPE_FIXED);
+ /*
+ * Initialize fixed image.
+ * "The size of the entire file is the size of the hard disk in
+ * the guest operating system plus the size of the footer."
+ */
+ pImage->u64DataOffset = VHD_FOOTER_DATA_OFFSET_FIXED;
+ pImage->uCurrentEndOfFile = cbSize;
+ rc = RTFileSetSize(File, pImage->uCurrentEndOfFile + sizeof(VHDFooter));
+ if (RT_FAILURE(rc))
+ {
+ vhdError(pImage, rc, RT_SRC_POS, N_("VHD: cannot set the file size for '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ break;
+ case VD_IMAGE_TYPE_NORMAL:
+ case VD_IMAGE_TYPE_DIFF:
+ /*
+ * Initialize dynamic image.
+ *
+ * The overall structure of dynamic disk is:
+ *
+ * [Copy of hard disk footer (512 bytes)]
+ * [Dynamic disk header (1024 bytes)]
+ * [BAT (Block Allocation Table)]
+ * [Parent Locators]
+ * [Data block 1]
+ * [Data block 2]
+ * ...
+ * [Data block N]
+ * [Hard disk footer (512 bytes)]
+ */
+ Footer.DiskType = enmType == VD_IMAGE_TYPE_DIFF ?
+ RT_H2BE_U32(VHD_FOOTER_DISK_TYPE_DIFFERENCING) :
+ RT_H2BE_U32(VHD_FOOTER_DISK_TYPE_DYNAMIC);
+ /* We are half way thourgh with creation of image, let the caller know. */
+ if (pfnProgress)
+ pfnProgress(NULL /* WARNING! pVM=NULL */, (uPercentStart + uPercentSpan) / 2, pvUser);
+
+ rc = vhdCreateDynamicImage(pImage, cbSize);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ break;
+ default:
+ /* Unknown/invalid image type. */
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ Footer.DataOffset = RT_H2BE_U64(pImage->u64DataOffset);
+ pImage->vhdFooterCopy = Footer;
+
+ /* Compute and update the footer checksum. */
+ Footer.Checksum = 0;
+ Footer.Checksum = RT_H2BE_U32(vhdChecksum(&Footer, sizeof(Footer)));
+
+ /* Store the footer */
+ rc = RTFileWriteAt(File, pImage->uCurrentEndOfFile, &Footer, sizeof(Footer), NULL);
+ if (RT_FAILURE(rc))
+ {
+ vhdError(pImage, rc, RT_SRC_POS, N_("VHD: cannot write footer to image '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ /* Dynamic images contain a copy of the footer at the very beginning of the file. */
+ if (enmType == VD_IMAGE_TYPE_NORMAL || enmType == VD_IMAGE_TYPE_DIFF)
+ {
+ /* Write the copy of the footer. */
+ rc = RTFileWriteAt(File, 0, &Footer, sizeof(Footer), NULL);
+ if (RT_FAILURE(rc))
+ {
+ vhdError(pImage, rc, RT_SRC_POS, N_("VHD: cannot write a copy of footer to image '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ }
+
+ if (pfnProgress)
+ pfnProgress(NULL /* WARNING! pVM=NULL */, uPercentStart + uPercentSpan, pvUser);
+
+out:
+ return rc;
+}
+
+static int vhdCreate(const char *pszFilename, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ const char *pszComment,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry, PCRTUUID pUuid,
+ unsigned uOpenFlags, unsigned uPercentStart,
+ unsigned uPercentSpan, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, PVDINTERFACE pVDIfsOperation,
+ void **ppvBackendData)
+{
+ int rc = VINF_SUCCESS;
+ PVHDIMAGE pImage;
+
+ PFNVMPROGRESS pfnProgress = NULL;
+ void *pvUser = NULL;
+ PVDINTERFACE pIfProgress = VDInterfaceGet(pVDIfsOperation,
+ VDINTERFACETYPE_PROGRESS);
+ PVDINTERFACEPROGRESS pCbProgress = NULL;
+ if (pIfProgress)
+ {
+ pCbProgress = VDGetInterfaceProgress(pIfProgress);
+ if (pCbProgress)
+ pfnProgress = pCbProgress->pfnProgress;
+ pvUser = pIfProgress->pvUser;
+ }
+
+ /* Check open flags. All valid flags are supported. */
+ if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ return rc;
+ }
+
+ /* @todo Check the values of other params */
+
+ pImage = (PVHDIMAGE)RTMemAllocZ(sizeof(VHDIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ return rc;
+ }
+ pImage->pszFilename = pszFilename;
+ pImage->File = NIL_RTFILE;
+ pImage->pVDIfsDisk = NULL;
+
+ rc = vhdCreateImage(pImage, enmType, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags,
+ pfnProgress, pvUser, uPercentStart, uPercentSpan);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ vhdClose(pImage, false);
+ rc = vhdOpenImage(pImage, uOpenFlags);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+ *ppvBackendData = pImage;
+ }
+out:
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static void vhdDump(void *pBackendData)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ /** @todo this is just a stub */
+ }
+}
+
+
+static int vhdGetTimeStamp(void *pvBackendData, PRTTIMESPEC pTimeStamp)
+{
+ int rc = VINF_SUCCESS;
+ PVHDIMAGE pImage = (PVHDIMAGE)pvBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ RTFSOBJINFO info;
+
+ rc = RTFileQueryInfo(pImage->File, &info, RTFSOBJATTRADD_NOTHING);
+ *pTimeStamp = info.ModificationTime;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdGetParentTimeStamp(void *pvBackendData, PRTTIMESPEC pTimeStamp)
+{
+ int rc = VINF_SUCCESS;
+ PVHDIMAGE pImage = (PVHDIMAGE)pvBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ vhdTime2RtTime(pTimeStamp, pImage->u32ParentTimeStamp);
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdSetParentTimeStamp(void *pvBackendData, PCRTTIMESPEC pTimeStamp)
+{
+ int rc = VINF_SUCCESS;
+ PVHDIMAGE pImage = (PVHDIMAGE)pvBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ {
+ pImage->u32ParentTimeStamp = vhdRtTime2VhdTime(pTimeStamp);
+ pImage->fDynHdrNeedsUpdate = true;
+ }
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdGetParentFilename(void *pvBackendData, char **ppszParentFilename)
+{
+ int rc = VINF_SUCCESS;
+ PVHDIMAGE pImage = (PVHDIMAGE)pvBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ *ppszParentFilename = RTStrDup(pImage->pszParentFilename);
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdSetParentFilename(void *pvBackendData, const char *pszParentFilename)
+{
+ int rc = VINF_SUCCESS;
+ PVHDIMAGE pImage = (PVHDIMAGE)pvBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ {
+ if (pImage->pszParentFilename)
+ RTStrFree(pImage->pszParentFilename);
+ pImage->pszParentFilename = RTStrDup(pszParentFilename);
+ if (!pImage->pszParentFilename)
+ rc = VERR_NO_MEMORY;
+ else
+ pImage->fDynHdrNeedsUpdate = true;
+ }
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+ LogFlowFunc(("returned %Rrc\n", rc));
+ return rc;
+}
+
+static bool vhdIsAsyncIOSupported(void *pvBackendData)
+{
+ return false;
+}
+
+static int vhdAsyncRead(void *pvBackendData, uint64_t uOffset, size_t cbRead,
+ PPDMDATASEG paSeg, unsigned cSeg, void *pvUser)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static int vhdAsyncWrite(void *pvBackendData, uint64_t uOffset, size_t cbToWrite,
+ PPDMDATASEG paSeg, unsigned cSeg, void *pvUser)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXHDDBACKEND g_VhdBackend =
+{
+ /* pszBackendName */
+ "VHD",
+ /* cbSize */
+ sizeof(VBOXHDDBACKEND),
+ /* uBackendCaps */
+ VD_CAP_UUID | VD_CAP_DIFF | VD_CAP_FILE,
+ /* papszFileExtensions */
+ s_apszVhdFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* hPlugin */
+ NIL_RTLDRMOD,
+ /* pfnCheckIfValid */
+ vhdCheckIfValid,
+ /* pfnOpen */
+ vhdOpen,
+ /* pfnCreate */
+ vhdCreate,
+ /* pfnRename */
+ vhdRename,
+ /* pfnClose */
+ vhdClose,
+ /* pfnRead */
+ vhdRead,
+ /* pfnWrite */
+ vhdWrite,
+ /* pfnFlush */
+ vhdFlush,
+ /* pfnGetVersion */
+ vhdGetVersion,
+ /* pfnGetImageType */
+ vhdGetImageType,
+ /* pfnGetSize */
+ vhdGetSize,
+ /* pfnGetFileSize */
+ vhdGetFileSize,
+ /* pfnGetPCHSGeometry */
+ vhdGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ vhdSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ vhdGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ vhdSetLCHSGeometry,
+ /* pfnGetImageFlags */
+ vhdGetImageFlags,
+ /* pfnGetOpenFlags */
+ vhdGetOpenFlags,
+ /* pfnSetOpenFlags */
+ vhdSetOpenFlags,
+ /* pfnGetComment */
+ vhdGetComment,
+ /* pfnSetComment */
+ vhdSetComment,
+ /* pfnGetUuid */
+ vhdGetUuid,
+ /* pfnSetUuid */
+ vhdSetUuid,
+ /* pfnGetModificationUuid */
+ vhdGetModificationUuid,
+ /* pfnSetModificationUuid */
+ vhdSetModificationUuid,
+ /* pfnGetParentUuid */
+ vhdGetParentUuid,
+ /* pfnSetParentUuid */
+ vhdSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ vhdGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ vhdSetParentModificationUuid,
+ /* pfnDump */
+ vhdDump,
+ /* pfnGetTimeStamp */
+ vhdGetTimeStamp,
+ /* pfnGetParentTimeStamp */
+ vhdGetParentTimeStamp,
+ /* pfnSetParentTimeStamp */
+ vhdSetParentTimeStamp,
+ /* pfnGetParentFilename */
+ vhdGetParentFilename,
+ /* pfnSetParentFilename */
+ vhdSetParentFilename,
+ /* pfnIsAsyncIOSupported */
+ vhdIsAsyncIOSupported,
+ /* pfnAsyncRead */
+ vhdAsyncRead,
+ /* pfnAsyncWrite */
+ vhdAsyncWrite,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName
+};
diff --git a/src/VBox/Devices/Storage/VmdkHDDCore.cpp b/src/VBox/Devices/Storage/VmdkHDDCore.cpp
new file mode 100644
index 000000000..74f12b8fa
--- /dev/null
+++ b/src/VBox/Devices/Storage/VmdkHDDCore.cpp
@@ -0,0 +1,5409 @@
+/* $Id: VmdkHDDCore.cpp 15565 2008-12-16 09:45:10Z vboxsync $ */
+/** @file
+ * VMDK Disk image, Core Code.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_VMDK
+#include "VBoxHDD-newInternal.h"
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/rand.h>
+
+
+/*******************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*******************************************************************************/
+
+/** Maximum encoded string size (including NUL) we allow for VMDK images.
+ * Deliberately not set high to avoid running out of descriptor space. */
+#define VMDK_ENCODED_COMMENT_MAX 1024
+
+/** VMDK descriptor DDB entry for PCHS cylinders. */
+#define VMDK_DDB_GEO_PCHS_CYLINDERS "ddb.geometry.cylinders"
+
+/** VMDK descriptor DDB entry for PCHS heads. */
+#define VMDK_DDB_GEO_PCHS_HEADS "ddb.geometry.heads"
+
+/** VMDK descriptor DDB entry for PCHS sectors. */
+#define VMDK_DDB_GEO_PCHS_SECTORS "ddb.geometry.sectors"
+
+/** VMDK descriptor DDB entry for LCHS cylinders. */
+#define VMDK_DDB_GEO_LCHS_CYLINDERS "ddb.geometry.biosCylinders"
+
+/** VMDK descriptor DDB entry for LCHS heads. */
+#define VMDK_DDB_GEO_LCHS_HEADS "ddb.geometry.biosHeads"
+
+/** VMDK descriptor DDB entry for LCHS sectors. */
+#define VMDK_DDB_GEO_LCHS_SECTORS "ddb.geometry.biosSectors"
+
+/** VMDK descriptor DDB entry for image UUID. */
+#define VMDK_DDB_IMAGE_UUID "ddb.uuid.image"
+
+/** VMDK descriptor DDB entry for image modification UUID. */
+#define VMDK_DDB_MODIFICATION_UUID "ddb.uuid.modification"
+
+/** VMDK descriptor DDB entry for parent image UUID. */
+#define VMDK_DDB_PARENT_UUID "ddb.uuid.parent"
+
+/** VMDK descriptor DDB entry for parent image modification UUID. */
+#define VMDK_DDB_PARENT_MODIFICATION_UUID "ddb.uuid.parentmodification"
+
+/**
+ * Magic number for hosted images created by VMware Workstation 4, VMware
+ * Workstation 5, VMware Server or VMware Player. Not necessarily sparse.
+ */
+#define VMDK_SPARSE_MAGICNUMBER 0x564d444b /* 'V' 'M' 'D' 'K' */
+
+/**
+ * VMDK hosted binary extent header. The "Sparse" is a total misnomer, as
+ * this header is also used for monolithic flat images.
+ */
+#pragma pack(1)
+typedef struct SparseExtentHeader
+{
+ uint32_t magicNumber;
+ uint32_t version;
+ uint32_t flags;
+ uint64_t capacity;
+ uint64_t grainSize;
+ uint64_t descriptorOffset;
+ uint64_t descriptorSize;
+ uint32_t numGTEsPerGT;
+ uint64_t rgdOffset;
+ uint64_t gdOffset;
+ uint64_t overHead;
+ bool uncleanShutdown;
+ char singleEndLineChar;
+ char nonEndLineChar;
+ char doubleEndLineChar1;
+ char doubleEndLineChar2;
+ uint8_t pad[435];
+} SparseExtentHeader;
+#pragma pack()
+
+/** VMDK capacity for a single chunk when 2G splitting is turned on. Should be
+ * divisible by the default grain size (64K) */
+#define VMDK_2G_SPLIT_SIZE (2047 * 1024 * 1024)
+
+
+#ifdef VBOX_WITH_VMDK_ESX
+
+/** @todo the ESX code is not tested, not used, and lacks error messages. */
+
+/**
+ * Magic number for images created by VMware GSX Server 3 or ESX Server 3.
+ */
+#define VMDK_ESX_SPARSE_MAGICNUMBER 0x44574f43 /* 'C' 'O' 'W' 'D' */
+
+#pragma pack(1)
+typedef struct COWDisk_Header
+{
+ uint32_t magicNumber;
+ uint32_t version;
+ uint32_t flags;
+ uint32_t numSectors;
+ uint32_t grainSize;
+ uint32_t gdOffset;
+ uint32_t numGDEntries;
+ uint32_t freeSector;
+ /* The spec incompletely documents quite a few further fields, but states
+ * that they are unused by the current format. Replace them by padding. */
+ char reserved1[1604];
+ uint32_t savedGeneration;
+ char reserved2[8];
+ uint32_t uncleanShutdown;
+ char padding[396];
+} COWDisk_Header;
+#pragma pack()
+#endif /* VBOX_WITH_VMDK_ESX */
+
+
+/** Convert sector number/size to byte offset/size. */
+#define VMDK_SECTOR2BYTE(u) ((u) << 9)
+
+/** Convert byte offset/size to sector number/size. */
+#define VMDK_BYTE2SECTOR(u) ((u) >> 9)
+
+/**
+ * VMDK extent type.
+ */
+typedef enum VMDKETYPE
+{
+ /** Hosted sparse extent. */
+ VMDKETYPE_HOSTED_SPARSE = 1,
+ /** Flat extent. */
+ VMDKETYPE_FLAT,
+ /** Zero extent. */
+ VMDKETYPE_ZERO
+#ifdef VBOX_WITH_VMDK_ESX
+ ,
+ /** ESX sparse extent. */
+ VMDKETYPE_ESX_SPARSE
+#endif /* VBOX_WITH_VMDK_ESX */
+} VMDKETYPE, *PVMDKETYPE;
+
+/**
+ * VMDK access type for a extent.
+ */
+typedef enum VMDKACCESS
+{
+ /** No access allowed. */
+ VMDKACCESS_NOACCESS = 0,
+ /** Read-only access. */
+ VMDKACCESS_READONLY,
+ /** Read-write access. */
+ VMDKACCESS_READWRITE
+} VMDKACCESS, *PVMDKACCESS;
+
+/** Forward declaration for PVMDKIMAGE. */
+typedef struct VMDKIMAGE *PVMDKIMAGE;
+
+/**
+ * Extents files entry. Used for opening a particular file only once.
+ */
+typedef struct VMDKFILE
+{
+ /** Pointer to filename. Local copy. */
+ const char *pszFilename;
+ /** File open flags for consistency checking. */
+ unsigned fOpen;
+ /** File handle. */
+ RTFILE File;
+ /** Handle for asnychronous access if requested.*/
+ void *pStorage;
+ /** Flag whether to use File or pStorage. */
+ bool fAsyncIO;
+ /** Reference counter. */
+ unsigned uReferences;
+ /** Flag whether the file should be deleted on last close. */
+ bool fDelete;
+ /** Pointer to the image we belong to. */
+ PVMDKIMAGE pImage;
+ /** Pointer to next file descriptor. */
+ struct VMDKFILE *pNext;
+ /** Pointer to the previous file descriptor. */
+ struct VMDKFILE *pPrev;
+} VMDKFILE, *PVMDKFILE;
+
+/**
+ * VMDK extent data structure.
+ */
+typedef struct VMDKEXTENT
+{
+ /** File handle. */
+ PVMDKFILE pFile;
+ /** Base name of the image extent. */
+ const char *pszBasename;
+ /** Full name of the image extent. */
+ const char *pszFullname;
+ /** Number of sectors in this extent. */
+ uint64_t cSectors;
+ /** Number of sectors per block (grain in VMDK speak). */
+ uint64_t cSectorsPerGrain;
+ /** Starting sector number of descriptor. */
+ uint64_t uDescriptorSector;
+ /** Size of descriptor in sectors. */
+ uint64_t cDescriptorSectors;
+ /** Starting sector number of grain directory. */
+ uint64_t uSectorGD;
+ /** Starting sector number of redundant grain directory. */
+ uint64_t uSectorRGD;
+ /** Total number of metadata sectors. */
+ uint64_t cOverheadSectors;
+ /** Nominal size (i.e. as described by the descriptor) of this extent. */
+ uint64_t cNominalSectors;
+ /** Sector offset (i.e. as described by the descriptor) of this extent. */
+ uint64_t uSectorOffset;
+ /** Number of entries in a grain table. */
+ uint32_t cGTEntries;
+ /** Number of sectors reachable via a grain directory entry. */
+ uint32_t cSectorsPerGDE;
+ /** Number of entries in the grain directory. */
+ uint32_t cGDEntries;
+ /** Pointer to the next free sector. Legacy information. Do not use. */
+ uint32_t uFreeSector;
+ /** Number of this extent in the list of images. */
+ uint32_t uExtent;
+ /** Pointer to the descriptor (NULL if no descriptor in this extent). */
+ char *pDescData;
+ /** Pointer to the grain directory. */
+ uint32_t *pGD;
+ /** Pointer to the redundant grain directory. */
+ uint32_t *pRGD;
+ /** Type of this extent. */
+ VMDKETYPE enmType;
+ /** Access to this extent. */
+ VMDKACCESS enmAccess;
+ /** Flag whether this extent is marked as unclean. */
+ bool fUncleanShutdown;
+ /** Flag whether the metadata in the extent header needs to be updated. */
+ bool fMetaDirty;
+ /** Reference to the image in which this extent is used. Do not use this
+ * on a regular basis to avoid passing pImage references to functions
+ * explicitly. */
+ struct VMDKIMAGE *pImage;
+} VMDKEXTENT, *PVMDKEXTENT;
+
+/**
+ * Grain table cache size. Allocated per image.
+ */
+#define VMDK_GT_CACHE_SIZE 256
+
+/**
+ * Grain table block size. Smaller than an actual grain table block to allow
+ * more grain table blocks to be cached without having to allocate excessive
+ * amounts of memory for the cache.
+ */
+#define VMDK_GT_CACHELINE_SIZE 128
+
+
+/**
+ * Maximum number of lines in a descriptor file. Not worth the effort of
+ * making it variable. Descriptor files are generally very short (~20 lines).
+ */
+#define VMDK_DESCRIPTOR_LINES_MAX 100U
+
+/**
+ * Parsed descriptor information. Allows easy access and update of the
+ * descriptor (whether separate file or not). Free form text files suck.
+ */
+typedef struct VMDKDESCRIPTOR
+{
+ /** Line number of first entry of the disk descriptor. */
+ unsigned uFirstDesc;
+ /** Line number of first entry in the extent description. */
+ unsigned uFirstExtent;
+ /** Line number of first disk database entry. */
+ unsigned uFirstDDB;
+ /** Total number of lines. */
+ unsigned cLines;
+ /** Total amount of memory available for the descriptor. */
+ size_t cbDescAlloc;
+ /** Set if descriptor has been changed and not yet written to disk. */
+ bool fDirty;
+ /** Array of pointers to the data in the descriptor. */
+ char *aLines[VMDK_DESCRIPTOR_LINES_MAX];
+ /** Array of line indices pointing to the next non-comment line. */
+ unsigned aNextLines[VMDK_DESCRIPTOR_LINES_MAX];
+} VMDKDESCRIPTOR, *PVMDKDESCRIPTOR;
+
+
+/**
+ * Cache entry for translating extent/sector to a sector number in that
+ * extent.
+ */
+typedef struct VMDKGTCACHEENTRY
+{
+ /** Extent number for which this entry is valid. */
+ uint32_t uExtent;
+ /** GT data block number. */
+ uint64_t uGTBlock;
+ /** Data part of the cache entry. */
+ uint32_t aGTData[VMDK_GT_CACHELINE_SIZE];
+} VMDKGTCACHEENTRY, *PVMDKGTCACHEENTRY;
+
+/**
+ * Cache data structure for blocks of grain table entries. For now this is a
+ * fixed size direct mapping cache, but this should be adapted to the size of
+ * the sparse image and maybe converted to a set-associative cache. The
+ * implementation below implements a write-through cache with write allocate.
+ */
+typedef struct VMDKGTCACHE
+{
+ /** Cache entries. */
+ VMDKGTCACHEENTRY aGTCache[VMDK_GT_CACHE_SIZE];
+ /** Number of cache entries (currently unused). */
+ unsigned cEntries;
+} VMDKGTCACHE, *PVMDKGTCACHE;
+
+/**
+ * Complete VMDK image data structure. Mainly a collection of extents and a few
+ * extra global data fields.
+ */
+typedef struct VMDKIMAGE
+{
+ /** Pointer to the image extents. */
+ PVMDKEXTENT pExtents;
+ /** Number of image extents. */
+ unsigned cExtents;
+ /** Pointer to the files list, for opening a file referenced multiple
+ * times only once (happens mainly with raw partition access). */
+ PVMDKFILE pFiles;
+
+ /** Base image name. */
+ const char *pszFilename;
+ /** Descriptor file if applicable. */
+ PVMDKFILE pFile;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+
+ /** Error interface. */
+ PVDINTERFACE pInterfaceError;
+ /** Error interface callbacks. */
+ PVDINTERFACEERROR pInterfaceErrorCallbacks;
+
+ /** Async I/O interface. */
+ PVDINTERFACE pInterfaceAsyncIO;
+ /** Async I/O interface callbacks. */
+ PVDINTERFACEASYNCIO pInterfaceAsyncIOCallbacks;
+ /**
+ * Pointer to an array of task handles for task submission.
+ * This is an optimization because the task number to submit is not known
+ * and allocating/freeing an array in the read/write functions every time
+ * is too expensive.
+ */
+ void **apTask;
+ /** Entries available in the task handle array. */
+ unsigned cTask;
+
+ /** Open flags passed by VBoxHD layer. */
+ unsigned uOpenFlags;
+ /** Image type. */
+ VDIMAGETYPE enmImageType;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+ /** Physical geometry of this image. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ PDMMEDIAGEOMETRY LCHSGeometry;
+ /** Image UUID. */
+ RTUUID ImageUuid;
+ /** Image modification UUID. */
+ RTUUID ModificationUuid;
+ /** Parent image UUID. */
+ RTUUID ParentUuid;
+ /** Parent image modification UUID. */
+ RTUUID ParentModificationUuid;
+
+ /** Pointer to grain table cache, if this image contains sparse extents. */
+ PVMDKGTCACHE pGTCache;
+ /** Pointer to the descriptor (NULL if no separate descriptor file). */
+ char *pDescData;
+ /** Allocation size of the descriptor file. */
+ size_t cbDescAlloc;
+ /** Parsed descriptor file content. */
+ VMDKDESCRIPTOR Descriptor;
+} VMDKIMAGE;
+
+/*******************************************************************************
+ * Static Variables *
+ *******************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const char *const s_apszVmdkFileExtensions[] =
+{
+ "vmdk",
+ NULL
+};
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+
+static void vmdkFreeGrainDirectory(PVMDKEXTENT pExtent);
+
+static void vmdkFreeExtentData(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ bool fDelete);
+
+static int vmdkCreateExtents(PVMDKIMAGE pImage, unsigned cExtents);
+static int vmdkFlushImage(PVMDKIMAGE pImage);
+static int vmdkSetImageComment(PVMDKIMAGE pImage, const char *pszComment);
+static void vmdkFreeImage(PVMDKIMAGE pImage, bool fDelete);
+
+
+/**
+ * Internal: signal an error to the frontend.
+ */
+DECLINLINE(int) vmdkError(PVMDKIMAGE pImage, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pImage->pInterfaceError && pImage->pInterfaceErrorCallbacks)
+ pImage->pInterfaceErrorCallbacks->pfnError(pImage->pInterfaceError->pvUser, rc, RT_SRC_POS_ARGS,
+ pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Internal: open a file (using a file descriptor cache to ensure each file
+ * is only opened once - anything else can cause locking problems).
+ */
+static int vmdkFileOpen(PVMDKIMAGE pImage, PVMDKFILE *ppVmdkFile,
+ const char *pszFilename, unsigned fOpen, bool fAsyncIO)
+{
+ int rc = VINF_SUCCESS;
+ PVMDKFILE pVmdkFile;
+
+ for (pVmdkFile = pImage->pFiles;
+ pVmdkFile != NULL;
+ pVmdkFile = pVmdkFile->pNext)
+ {
+ if (!strcmp(pszFilename, pVmdkFile->pszFilename))
+ {
+ Assert(fOpen == pVmdkFile->fOpen);
+ pVmdkFile->uReferences++;
+
+ *ppVmdkFile = pVmdkFile;
+
+ return rc;
+ }
+ }
+
+ /* If we get here, there's no matching entry in the cache. */
+ pVmdkFile = (PVMDKFILE)RTMemAllocZ(sizeof(VMDKFILE));
+ if (!VALID_PTR(pVmdkFile))
+ {
+ *ppVmdkFile = NULL;
+ return VERR_NO_MEMORY;
+ }
+
+ pVmdkFile->pszFilename = RTStrDup(pszFilename);
+ if (!VALID_PTR(pVmdkFile->pszFilename))
+ {
+ RTMemFree(pVmdkFile);
+ *ppVmdkFile = NULL;
+ return VERR_NO_MEMORY;
+ }
+ pVmdkFile->fOpen = fOpen;
+ if ((pImage->uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO) && (fAsyncIO))
+ {
+ rc = pImage->pInterfaceAsyncIOCallbacks->pfnOpen(pImage->pInterfaceAsyncIO->pvUser,
+ pszFilename,
+ pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY
+ ? true
+ : false,
+ &pVmdkFile->pStorage);
+ pVmdkFile->fAsyncIO = true;
+ }
+ else
+ {
+ rc = RTFileOpen(&pVmdkFile->File, pszFilename, fOpen);
+ pVmdkFile->fAsyncIO = false;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pVmdkFile->uReferences = 1;
+ pVmdkFile->pImage = pImage;
+ pVmdkFile->pNext = pImage->pFiles;
+ if (pImage->pFiles)
+ pImage->pFiles->pPrev = pVmdkFile;
+ pImage->pFiles = pVmdkFile;
+ *ppVmdkFile = pVmdkFile;
+ }
+ else
+ {
+ RTStrFree((char *)(void *)pVmdkFile->pszFilename);
+ RTMemFree(pVmdkFile);
+ *ppVmdkFile = NULL;
+ }
+
+ return rc;
+}
+
+/**
+ * Internal: close a file, updating the file descriptor cache.
+ */
+static int vmdkFileClose(PVMDKIMAGE pImage, PVMDKFILE *ppVmdkFile, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+ PVMDKFILE pVmdkFile = *ppVmdkFile;
+
+ AssertPtr(pVmdkFile);
+
+ pVmdkFile->fDelete |= fDelete;
+ Assert(pVmdkFile->uReferences);
+ pVmdkFile->uReferences--;
+ if (pVmdkFile->uReferences == 0)
+ {
+ PVMDKFILE pPrev;
+ PVMDKFILE pNext;
+
+ /* Unchain the element from the list. */
+ pPrev = pVmdkFile->pPrev;
+ pNext = pVmdkFile->pNext;
+
+ if (pNext)
+ pNext->pPrev = pPrev;
+ if (pPrev)
+ pPrev->pNext = pNext;
+ else
+ pImage->pFiles = pNext;
+
+ if (pVmdkFile->fAsyncIO)
+ {
+ rc = pImage->pInterfaceAsyncIOCallbacks->pfnClose(pImage->pInterfaceAsyncIO->pvUser,
+ pVmdkFile->pStorage);
+ }
+ else
+ {
+ rc = RTFileClose(pVmdkFile->File);
+ }
+ if (RT_SUCCESS(rc) && pVmdkFile->fDelete)
+ rc = RTFileDelete(pVmdkFile->pszFilename);
+ RTStrFree((char *)(void *)pVmdkFile->pszFilename);
+ RTMemFree(pVmdkFile);
+ }
+
+ *ppVmdkFile = NULL;
+ return rc;
+}
+
+/**
+ * Internal: read from a file distinguishing between async and normal operation
+ */
+DECLINLINE(int) vmdkFileReadAt(PVMDKFILE pVmdkFile,
+ uint64_t uOffset, void *pvBuf,
+ size_t cbToRead, size_t *pcbRead)
+{
+ PVMDKIMAGE pImage = pVmdkFile->pImage;
+
+ if (pVmdkFile->fAsyncIO)
+ return pImage->pInterfaceAsyncIOCallbacks->pfnRead(pImage->pInterfaceAsyncIO->pvUser,
+ pVmdkFile->pStorage, uOffset,
+ cbToRead, pvBuf, pcbRead);
+ else
+ return RTFileReadAt(pVmdkFile->File, uOffset, pvBuf, cbToRead, pcbRead);
+}
+
+/**
+ * Internal: write to a file distinguishing between async and normal operation
+ */
+DECLINLINE(int) vmdkFileWriteAt(PVMDKFILE pVmdkFile,
+ uint64_t uOffset, const void *pvBuf,
+ size_t cbToWrite, size_t *pcbWritten)
+{
+ PVMDKIMAGE pImage = pVmdkFile->pImage;
+
+ if (pVmdkFile->fAsyncIO)
+ return pImage->pInterfaceAsyncIOCallbacks->pfnWrite(pImage->pInterfaceAsyncIO->pvUser,
+ pVmdkFile->pStorage, uOffset,
+ cbToWrite, pvBuf, pcbWritten);
+ else
+ return RTFileWriteAt(pVmdkFile->File, uOffset, pvBuf, cbToWrite, pcbWritten);
+}
+
+/**
+ * Internal: get the size of a file distinguishing beween async and normal operation
+ */
+DECLINLINE(int) vmdkFileGetSize(PVMDKFILE pVmdkFile, uint64_t *pcbSize)
+{
+ if (pVmdkFile->fAsyncIO)
+ {
+ AssertMsgFailed(("TODO\n"));
+ return 0;
+ }
+ else
+ return RTFileGetSize(pVmdkFile->File, pcbSize);
+}
+
+/**
+ * Internal: set the size of a file distinguishing beween async and normal operation
+ */
+DECLINLINE(int) vmdkFileSetSize(PVMDKFILE pVmdkFile, uint64_t cbSize)
+{
+ if (pVmdkFile->fAsyncIO)
+ {
+ AssertMsgFailed(("TODO\n"));
+ return VERR_NOT_SUPPORTED;
+ }
+ else
+ return RTFileSetSize(pVmdkFile->File, cbSize);
+}
+
+/**
+ * Internal: flush a file distinguishing between async and normal operation
+ */
+DECLINLINE(int) vmdkFileFlush(PVMDKFILE pVmdkFile)
+{
+ PVMDKIMAGE pImage = pVmdkFile->pImage;
+
+ if (pVmdkFile->fAsyncIO)
+ return pImage->pInterfaceAsyncIOCallbacks->pfnFlush(pImage->pInterfaceAsyncIO->pvUser,
+ pVmdkFile->pStorage);
+ else
+ return RTFileFlush(pVmdkFile->File);
+}
+
+/**
+ * Internal: check if all files are closed, prevent leaking resources.
+ */
+static int vmdkFileCheckAllClose(PVMDKIMAGE pImage)
+{
+ int rc = VINF_SUCCESS, rc2;
+ PVMDKFILE pVmdkFile;
+
+ Assert(pImage->pFiles == NULL);
+ for (pVmdkFile = pImage->pFiles;
+ pVmdkFile != NULL;
+ pVmdkFile = pVmdkFile->pNext)
+ {
+ LogRel(("VMDK: leaking reference to file \"%s\"\n",
+ pVmdkFile->pszFilename));
+ pImage->pFiles = pVmdkFile->pNext;
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)
+ rc2 = pImage->pInterfaceAsyncIOCallbacks->pfnClose(pImage->pInterfaceAsyncIO->pvUser,
+ pVmdkFile->pStorage);
+ else
+ rc2 = RTFileClose(pVmdkFile->File);
+
+ if (RT_SUCCESS(rc) && pVmdkFile->fDelete)
+ rc2 = RTFileDelete(pVmdkFile->pszFilename);
+ RTStrFree((char *)(void *)pVmdkFile->pszFilename);
+ RTMemFree(pVmdkFile);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
+/**
+ * Internal: truncate a string (at a UTF8 code point boundary) and encode the
+ * critical non-ASCII characters.
+ */
+static char *vmdkEncodeString(const char *psz)
+{
+ char szEnc[VMDK_ENCODED_COMMENT_MAX + 3];
+ char *pszDst = szEnc;
+
+ AssertPtr(psz);
+
+ for (; *psz; psz = RTStrNextCp(psz))
+ {
+ char *pszDstPrev = pszDst;
+ RTUNICP Cp = RTStrGetCp(psz);
+ if (Cp == '\\')
+ {
+ pszDst = RTStrPutCp(pszDst, Cp);
+ pszDst = RTStrPutCp(pszDst, Cp);
+ }
+ else if (Cp == '\n')
+ {
+ pszDst = RTStrPutCp(pszDst, '\\');
+ pszDst = RTStrPutCp(pszDst, 'n');
+ }
+ else if (Cp == '\r')
+ {
+ pszDst = RTStrPutCp(pszDst, '\\');
+ pszDst = RTStrPutCp(pszDst, 'r');
+ }
+ else
+ pszDst = RTStrPutCp(pszDst, Cp);
+ if (pszDst - szEnc >= VMDK_ENCODED_COMMENT_MAX - 1)
+ {
+ pszDst = pszDstPrev;
+ break;
+ }
+ }
+ *pszDst = '\0';
+ return RTStrDup(szEnc);
+}
+
+/**
+ * Internal: decode a string and store it into the specified string.
+ */
+static int vmdkDecodeString(const char *pszEncoded, char *psz, size_t cb)
+{
+ int rc = VINF_SUCCESS;
+ char szBuf[4];
+
+ if (!cb)
+ return VERR_BUFFER_OVERFLOW;
+
+ AssertPtr(psz);
+
+ for (; *pszEncoded; pszEncoded = RTStrNextCp(pszEncoded))
+ {
+ char *pszDst = szBuf;
+ RTUNICP Cp = RTStrGetCp(pszEncoded);
+ if (Cp == '\\')
+ {
+ pszEncoded = RTStrNextCp(pszEncoded);
+ RTUNICP CpQ = RTStrGetCp(pszEncoded);
+ if (CpQ == 'n')
+ RTStrPutCp(pszDst, '\n');
+ else if (CpQ == 'r')
+ RTStrPutCp(pszDst, '\r');
+ else if (CpQ == '\0')
+ {
+ rc = VERR_VD_VMDK_INVALID_HEADER;
+ break;
+ }
+ else
+ RTStrPutCp(pszDst, CpQ);
+ }
+ else
+ pszDst = RTStrPutCp(pszDst, Cp);
+
+ /* Need to leave space for terminating NUL. */
+ if ((size_t)(pszDst - szBuf) + 1 >= cb)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ memcpy(psz, szBuf, pszDst - szBuf);
+ psz += pszDst - szBuf;
+ }
+ *psz = '\0';
+ return rc;
+}
+
+static int vmdkReadGrainDirectory(PVMDKEXTENT pExtent)
+{
+ int rc = VINF_SUCCESS;
+ unsigned i;
+ uint32_t *pGD = NULL, *pRGD = NULL, *pGDTmp, *pRGDTmp;
+ size_t cbGD = pExtent->cGDEntries * sizeof(uint32_t);
+
+ if (pExtent->enmType != VMDKETYPE_HOSTED_SPARSE)
+ goto out;
+
+ pGD = (uint32_t *)RTMemAllocZ(cbGD);
+ if (!pGD)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pExtent->pGD = pGD;
+ rc = vmdkFileReadAt(pExtent->pFile, VMDK_SECTOR2BYTE(pExtent->uSectorGD),
+ pGD, cbGD, NULL);
+ AssertRC(rc);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: could not read grain directory in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+ for (i = 0, pGDTmp = pGD; i < pExtent->cGDEntries; i++, pGDTmp++)
+ *pGDTmp = RT_LE2H_U32(*pGDTmp);
+
+ if (pExtent->uSectorRGD)
+ {
+ pRGD = (uint32_t *)RTMemAllocZ(cbGD);
+ if (!pRGD)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pExtent->pRGD = pRGD;
+ rc = vmdkFileReadAt(pExtent->pFile, VMDK_SECTOR2BYTE(pExtent->uSectorRGD),
+ pRGD, cbGD, NULL);
+ AssertRC(rc);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: could not read redundant grain directory in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+ for (i = 0, pRGDTmp = pRGD; i < pExtent->cGDEntries; i++, pRGDTmp++)
+ *pRGDTmp = RT_LE2H_U32(*pRGDTmp);
+
+ /* Check grain table and redundant grain table for consistency. */
+ size_t cbGT = pExtent->cGTEntries;
+ uint32_t *pTmpGT1 = (uint32_t *)RTMemTmpAlloc(cbGT);
+ if (!pTmpGT1)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ uint32_t *pTmpGT2 = (uint32_t *)RTMemTmpAlloc(cbGT);
+ if (!pTmpGT2)
+ {
+ RTMemTmpFree(pTmpGT1);
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ for (i = 0, pGDTmp = pGD, pRGDTmp = pRGD;
+ i < pExtent->cGDEntries;
+ i++, pGDTmp++, pRGDTmp++)
+ {
+ /* If no grain table is allocated skip the entry. */
+ if (*pGDTmp == 0 && *pRGDTmp == 0)
+ continue;
+
+ if (*pGDTmp == 0 || *pRGDTmp == 0 || *pGDTmp == *pRGDTmp)
+ {
+ /* Just one grain directory entry refers to a not yet allocated
+ * grain table or both grain directory copies refer to the same
+ * grain table. Not allowed. */
+ RTMemTmpFree(pTmpGT1);
+ RTMemTmpFree(pTmpGT2);
+ rc = vmdkError(pExtent->pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: inconsistent references to grain directory in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+ rc = vmdkFileReadAt(pExtent->pFile, VMDK_SECTOR2BYTE(*pGDTmp),
+ pTmpGT1, cbGT, NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading grain table in '%s'"), pExtent->pszFullname);
+ RTMemTmpFree(pTmpGT1);
+ RTMemTmpFree(pTmpGT2);
+ goto out;
+ }
+ rc = vmdkFileReadAt(pExtent->pFile, VMDK_SECTOR2BYTE(*pRGDTmp),
+ pTmpGT2, cbGT, NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading backup grain table in '%s'"), pExtent->pszFullname);
+ RTMemTmpFree(pTmpGT1);
+ RTMemTmpFree(pTmpGT2);
+ goto out;
+ }
+ if (memcmp(pTmpGT1, pTmpGT2, cbGT))
+ {
+ RTMemTmpFree(pTmpGT1);
+ RTMemTmpFree(pTmpGT2);
+ rc = vmdkError(pExtent->pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: inconsistency between grain table and backup grain table in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+ }
+
+ /** @todo figure out what to do for unclean VMDKs. */
+ RTMemTmpFree(pTmpGT1);
+ RTMemTmpFree(pTmpGT2);
+ }
+
+out:
+ if (RT_FAILURE(rc))
+ vmdkFreeGrainDirectory(pExtent);
+ return rc;
+}
+
+static int vmdkCreateGrainDirectory(PVMDKEXTENT pExtent, uint64_t uStartSector,
+ bool fPreAlloc)
+{
+ int rc = VINF_SUCCESS;
+ unsigned i;
+ uint32_t *pGD = NULL, *pRGD = NULL;
+ size_t cbGD = pExtent->cGDEntries * sizeof(uint32_t);
+ size_t cbGDRounded = RT_ALIGN_64(pExtent->cGDEntries * sizeof(uint32_t), 512);
+ size_t cbGTRounded;
+ uint64_t cbOverhead;
+
+ if (fPreAlloc)
+ cbGTRounded = RT_ALIGN_64(pExtent->cGDEntries * pExtent->cGTEntries * sizeof(uint32_t), 512);
+ else
+ cbGTRounded = 0;
+
+ pGD = (uint32_t *)RTMemAllocZ(cbGD);
+ if (!pGD)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pExtent->pGD = pGD;
+ pRGD = (uint32_t *)RTMemAllocZ(cbGD);
+ if (!pRGD)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pExtent->pRGD = pRGD;
+
+ cbOverhead = RT_ALIGN_64(VMDK_SECTOR2BYTE(uStartSector) + 2 * (cbGDRounded + cbGTRounded), VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ rc = vmdkFileSetSize(pExtent->pFile, cbOverhead);
+ if (RT_FAILURE(rc))
+ goto out;
+ pExtent->uSectorRGD = uStartSector;
+ pExtent->uSectorGD = uStartSector + VMDK_BYTE2SECTOR(cbGDRounded + cbGTRounded);
+
+ if (fPreAlloc)
+ {
+ uint32_t uGTSectorLE;
+ uint64_t uOffsetSectors;
+
+ uOffsetSectors = pExtent->uSectorRGD + VMDK_BYTE2SECTOR(cbGDRounded);
+ for (i = 0; i < pExtent->cGDEntries; i++)
+ {
+ pRGD[i] = uOffsetSectors;
+ uGTSectorLE = RT_H2LE_U64(uOffsetSectors);
+ /* Write the redundant grain directory entry to disk. */
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(pExtent->uSectorRGD) + i * sizeof(uGTSectorLE),
+ &uGTSectorLE, sizeof(uGTSectorLE), NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write new redundant grain directory entry in '%s'"), pExtent->pszFullname);
+ uOffsetSectors += VMDK_BYTE2SECTOR(pExtent->cGTEntries * sizeof(uint32_t));
+ }
+
+ uOffsetSectors = pExtent->uSectorGD + VMDK_BYTE2SECTOR(cbGDRounded);
+ for (i = 0; i < pExtent->cGDEntries; i++)
+ {
+ pGD[i] = uOffsetSectors;
+ uGTSectorLE = RT_H2LE_U64(uOffsetSectors);
+ /* Write the grain directory entry to disk. */
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(pExtent->uSectorGD) + i * sizeof(uGTSectorLE),
+ &uGTSectorLE, sizeof(uGTSectorLE), NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write new grain directory entry in '%s'"), pExtent->pszFullname);
+ uOffsetSectors += VMDK_BYTE2SECTOR(pExtent->cGTEntries * sizeof(uint32_t));
+ }
+ }
+ pExtent->cOverheadSectors = VMDK_BYTE2SECTOR(cbOverhead);
+
+out:
+ if (RT_FAILURE(rc))
+ vmdkFreeGrainDirectory(pExtent);
+ return rc;
+}
+
+static void vmdkFreeGrainDirectory(PVMDKEXTENT pExtent)
+{
+ if (pExtent->pGD)
+ {
+ RTMemFree(pExtent->pGD);
+ pExtent->pGD = NULL;
+ }
+ if (pExtent->pRGD)
+ {
+ RTMemFree(pExtent->pRGD);
+ pExtent->pRGD = NULL;
+ }
+}
+
+static int vmdkStringUnquote(PVMDKIMAGE pImage, const char *pszStr,
+ char **ppszUnquoted, char **ppszNext)
+{
+ char *pszQ;
+ char *pszUnquoted;
+
+ /* Skip over whitespace. */
+ while (*pszStr == ' ' || *pszStr == '\t')
+ pszStr++;
+ if (*pszStr++ != '"')
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrectly quoted value in descriptor in '%s'"), pImage->pszFilename);
+
+ pszQ = (char *)strchr(pszStr, '"');
+ if (pszQ == NULL)
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrectly quoted value in descriptor in '%s'"), pImage->pszFilename);
+ pszUnquoted = (char *)RTMemTmpAlloc(pszQ - pszStr + 1);
+ if (!pszUnquoted)
+ return VERR_NO_MEMORY;
+ memcpy(pszUnquoted, pszStr, pszQ - pszStr);
+ pszUnquoted[pszQ - pszStr] = '\0';
+ *ppszUnquoted = pszUnquoted;
+ if (ppszNext)
+ *ppszNext = pszQ + 1;
+ return VINF_SUCCESS;
+}
+
+static int vmdkDescInitStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszLine)
+{
+ char *pEnd = pDescriptor->aLines[pDescriptor->cLines];
+ ssize_t cbDiff = strlen(pszLine) + 1;
+
+ if ( pDescriptor->cLines >= VMDK_DESCRIPTOR_LINES_MAX - 1
+ && pEnd - pDescriptor->aLines[0] > (ptrdiff_t)pDescriptor->cbDescAlloc - cbDiff)
+ return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
+
+ memcpy(pEnd, pszLine, cbDiff);
+ pDescriptor->cLines++;
+ pDescriptor->aLines[pDescriptor->cLines] = pEnd + cbDiff;
+ pDescriptor->fDirty = true;
+
+ return VINF_SUCCESS;
+}
+
+static bool vmdkDescGetStr(PVMDKDESCRIPTOR pDescriptor, unsigned uStart,
+ const char *pszKey, const char **ppszValue)
+{
+ size_t cbKey = strlen(pszKey);
+ const char *pszValue;
+
+ while (uStart != 0)
+ {
+ if (!strncmp(pDescriptor->aLines[uStart], pszKey, cbKey))
+ {
+ /* Key matches, check for a '=' (preceded by whitespace). */
+ pszValue = pDescriptor->aLines[uStart] + cbKey;
+ while (*pszValue == ' ' || *pszValue == '\t')
+ pszValue++;
+ if (*pszValue == '=')
+ {
+ *ppszValue = pszValue + 1;
+ break;
+ }
+ }
+ uStart = pDescriptor->aNextLines[uStart];
+ }
+ return !!uStart;
+}
+
+static int vmdkDescSetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ unsigned uStart,
+ const char *pszKey, const char *pszValue)
+{
+ char *pszTmp;
+ size_t cbKey = strlen(pszKey);
+ unsigned uLast = 0;
+
+ while (uStart != 0)
+ {
+ if (!strncmp(pDescriptor->aLines[uStart], pszKey, cbKey))
+ {
+ /* Key matches, check for a '=' (preceded by whitespace). */
+ pszTmp = pDescriptor->aLines[uStart] + cbKey;
+ while (*pszTmp == ' ' || *pszTmp == '\t')
+ pszTmp++;
+ if (*pszTmp == '=')
+ {
+ pszTmp++;
+ while (*pszTmp == ' ' || *pszTmp == '\t')
+ pszTmp++;
+ break;
+ }
+ }
+ if (!pDescriptor->aNextLines[uStart])
+ uLast = uStart;
+ uStart = pDescriptor->aNextLines[uStart];
+ }
+ if (uStart)
+ {
+ if (pszValue)
+ {
+ /* Key already exists, replace existing value. */
+ size_t cbOldVal = strlen(pszTmp);
+ size_t cbNewVal = strlen(pszValue);
+ ssize_t cbDiff = cbNewVal - cbOldVal;
+ /* Check for buffer overflow. */
+ if ( pDescriptor->aLines[pDescriptor->cLines]
+ - pDescriptor->aLines[0] > (ptrdiff_t)pDescriptor->cbDescAlloc - cbDiff)
+ return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
+
+ memmove(pszTmp + cbNewVal, pszTmp + cbOldVal,
+ pDescriptor->aLines[pDescriptor->cLines] - pszTmp - cbOldVal);
+ memcpy(pszTmp, pszValue, cbNewVal + 1);
+ for (unsigned i = uStart + 1; i <= pDescriptor->cLines; i++)
+ pDescriptor->aLines[i] += cbDiff;
+ }
+ else
+ {
+ memmove(pDescriptor->aLines[uStart], pDescriptor->aLines[uStart+1],
+ pDescriptor->aLines[pDescriptor->cLines] - pDescriptor->aLines[uStart+1] + 1);
+ for (unsigned i = uStart + 1; i <= pDescriptor->cLines; i++)
+ {
+ pDescriptor->aLines[i-1] = pDescriptor->aLines[i];
+ if (pDescriptor->aNextLines[i])
+ pDescriptor->aNextLines[i-1] = pDescriptor->aNextLines[i] - 1;
+ else
+ pDescriptor->aNextLines[i-1] = 0;
+ }
+ pDescriptor->cLines--;
+ /* Adjust starting line numbers of following descriptor sections. */
+ if (uStart < pDescriptor->uFirstExtent)
+ pDescriptor->uFirstExtent--;
+ if (uStart < pDescriptor->uFirstDDB)
+ pDescriptor->uFirstDDB--;
+ }
+ }
+ else
+ {
+ /* Key doesn't exist, append after the last entry in this category. */
+ if (!pszValue)
+ {
+ /* Key doesn't exist, and it should be removed. Simply a no-op. */
+ return VINF_SUCCESS;
+ }
+ size_t cbKey = strlen(pszKey);
+ size_t cbValue = strlen(pszValue);
+ ssize_t cbDiff = cbKey + 1 + cbValue + 1;
+ /* Check for buffer overflow. */
+ if ( (pDescriptor->cLines >= VMDK_DESCRIPTOR_LINES_MAX - 1)
+ || ( pDescriptor->aLines[pDescriptor->cLines]
+ - pDescriptor->aLines[0] > (ptrdiff_t)pDescriptor->cbDescAlloc - cbDiff))
+ return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
+ for (unsigned i = pDescriptor->cLines + 1; i > uLast + 1; i--)
+ {
+ pDescriptor->aLines[i] = pDescriptor->aLines[i - 1];
+ if (pDescriptor->aNextLines[i - 1])
+ pDescriptor->aNextLines[i] = pDescriptor->aNextLines[i - 1] + 1;
+ else
+ pDescriptor->aNextLines[i] = 0;
+ }
+ uStart = uLast + 1;
+ pDescriptor->aNextLines[uLast] = uStart;
+ pDescriptor->aNextLines[uStart] = 0;
+ pDescriptor->cLines++;
+ pszTmp = pDescriptor->aLines[uStart];
+ memmove(pszTmp + cbDiff, pszTmp,
+ pDescriptor->aLines[pDescriptor->cLines] - pszTmp);
+ memcpy(pDescriptor->aLines[uStart], pszKey, cbKey);
+ pDescriptor->aLines[uStart][cbKey] = '=';
+ memcpy(pDescriptor->aLines[uStart] + cbKey + 1, pszValue, cbValue + 1);
+ for (unsigned i = uStart + 1; i <= pDescriptor->cLines; i++)
+ pDescriptor->aLines[i] += cbDiff;
+
+ /* Adjust starting line numbers of following descriptor sections. */
+ if (uStart <= pDescriptor->uFirstExtent)
+ pDescriptor->uFirstExtent++;
+ if (uStart <= pDescriptor->uFirstDDB)
+ pDescriptor->uFirstDDB++;
+ }
+ pDescriptor->fDirty = true;
+ return VINF_SUCCESS;
+}
+
+static int vmdkDescBaseGetU32(PVMDKDESCRIPTOR pDescriptor, const char *pszKey,
+ uint32_t *puValue)
+{
+ const char *pszValue;
+
+ if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDesc, pszKey,
+ &pszValue))
+ return VERR_VD_VMDK_VALUE_NOT_FOUND;
+ return RTStrToUInt32Ex(pszValue, NULL, 10, puValue);
+}
+
+static int vmdkDescBaseGetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, const char **ppszValue)
+{
+ const char *pszValue;
+ char *pszValueUnquoted;
+
+ if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDesc, pszKey,
+ &pszValue))
+ return VERR_VD_VMDK_VALUE_NOT_FOUND;
+ int rc = vmdkStringUnquote(pImage, pszValue, &pszValueUnquoted, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ *ppszValue = pszValueUnquoted;
+ return rc;
+}
+
+static int vmdkDescBaseSetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, const char *pszValue)
+{
+ char *pszValueQuoted;
+
+ int rc = RTStrAPrintf(&pszValueQuoted, "\"%s\"", pszValue);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDesc, pszKey,
+ pszValueQuoted);
+ RTStrFree(pszValueQuoted);
+ return rc;
+}
+
+static void vmdkDescExtRemoveDummy(PVMDKIMAGE pImage,
+ PVMDKDESCRIPTOR pDescriptor)
+{
+ unsigned uEntry = pDescriptor->uFirstExtent;
+ ssize_t cbDiff;
+
+ if (!uEntry)
+ return;
+
+ cbDiff = strlen(pDescriptor->aLines[uEntry]) + 1;
+ /* Move everything including \0 in the entry marking the end of buffer. */
+ memmove(pDescriptor->aLines[uEntry], pDescriptor->aLines[uEntry + 1],
+ pDescriptor->aLines[pDescriptor->cLines] - pDescriptor->aLines[uEntry + 1] + 1);
+ for (unsigned i = uEntry + 1; i <= pDescriptor->cLines; i++)
+ {
+ pDescriptor->aLines[i - 1] = pDescriptor->aLines[i] - cbDiff;
+ if (pDescriptor->aNextLines[i])
+ pDescriptor->aNextLines[i - 1] = pDescriptor->aNextLines[i] - 1;
+ else
+ pDescriptor->aNextLines[i - 1] = 0;
+ }
+ pDescriptor->cLines--;
+ if (pDescriptor->uFirstDDB)
+ pDescriptor->uFirstDDB--;
+
+ return;
+}
+
+static int vmdkDescExtInsert(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ VMDKACCESS enmAccess, uint64_t cNominalSectors,
+ VMDKETYPE enmType, const char *pszBasename,
+ uint64_t uSectorOffset)
+{
+ static const char *apszAccess[] = { "NOACCESS", "RDONLY", "RW" };
+ static const char *apszType[] = { "", "SPARSE", "FLAT", "ZERO" };
+ char *pszTmp;
+ unsigned uStart = pDescriptor->uFirstExtent, uLast = 0;
+ char szExt[1024];
+ ssize_t cbDiff;
+
+ /* Find last entry in extent description. */
+ while (uStart)
+ {
+ if (!pDescriptor->aNextLines[uStart])
+ uLast = uStart;
+ uStart = pDescriptor->aNextLines[uStart];
+ }
+
+ if (enmType == VMDKETYPE_ZERO)
+ {
+ RTStrPrintf(szExt, sizeof(szExt), "%s %llu %s ", apszAccess[enmAccess],
+ cNominalSectors, apszType[enmType]);
+ }
+ else
+ {
+ if (!uSectorOffset)
+ RTStrPrintf(szExt, sizeof(szExt), "%s %llu %s \"%s\"",
+ apszAccess[enmAccess], cNominalSectors,
+ apszType[enmType], pszBasename);
+ else
+ RTStrPrintf(szExt, sizeof(szExt), "%s %llu %s \"%s\" %llu",
+ apszAccess[enmAccess], cNominalSectors,
+ apszType[enmType], pszBasename, uSectorOffset);
+ }
+ cbDiff = strlen(szExt) + 1;
+
+ /* Check for buffer overflow. */
+ if ( (pDescriptor->cLines >= VMDK_DESCRIPTOR_LINES_MAX - 1)
+ || ( pDescriptor->aLines[pDescriptor->cLines]
+ - pDescriptor->aLines[0] > (ptrdiff_t)pDescriptor->cbDescAlloc - cbDiff))
+ return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
+
+ for (unsigned i = pDescriptor->cLines + 1; i > uLast + 1; i--)
+ {
+ pDescriptor->aLines[i] = pDescriptor->aLines[i - 1];
+ if (pDescriptor->aNextLines[i - 1])
+ pDescriptor->aNextLines[i] = pDescriptor->aNextLines[i - 1] + 1;
+ else
+ pDescriptor->aNextLines[i] = 0;
+ }
+ uStart = uLast + 1;
+ pDescriptor->aNextLines[uLast] = uStart;
+ pDescriptor->aNextLines[uStart] = 0;
+ pDescriptor->cLines++;
+ pszTmp = pDescriptor->aLines[uStart];
+ memmove(pszTmp + cbDiff, pszTmp,
+ pDescriptor->aLines[pDescriptor->cLines] - pszTmp);
+ memcpy(pDescriptor->aLines[uStart], szExt, cbDiff);
+ for (unsigned i = uStart + 1; i <= pDescriptor->cLines; i++)
+ pDescriptor->aLines[i] += cbDiff;
+
+ /* Adjust starting line numbers of following descriptor sections. */
+ if (uStart <= pDescriptor->uFirstDDB)
+ pDescriptor->uFirstDDB++;
+
+ pDescriptor->fDirty = true;
+ return VINF_SUCCESS;
+}
+
+static int vmdkDescDDBGetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, const char **ppszValue)
+{
+ const char *pszValue;
+ char *pszValueUnquoted;
+
+ if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ &pszValue))
+ return VERR_VD_VMDK_VALUE_NOT_FOUND;
+ int rc = vmdkStringUnquote(pImage, pszValue, &pszValueUnquoted, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ *ppszValue = pszValueUnquoted;
+ return rc;
+}
+
+static int vmdkDescDDBGetU32(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, uint32_t *puValue)
+{
+ const char *pszValue;
+ char *pszValueUnquoted;
+
+ if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ &pszValue))
+ return VERR_VD_VMDK_VALUE_NOT_FOUND;
+ int rc = vmdkStringUnquote(pImage, pszValue, &pszValueUnquoted, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTStrToUInt32Ex(pszValueUnquoted, NULL, 10, puValue);
+ RTMemTmpFree(pszValueUnquoted);
+ return rc;
+}
+
+static int vmdkDescDDBGetUuid(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, PRTUUID pUuid)
+{
+ const char *pszValue;
+ char *pszValueUnquoted;
+
+ if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ &pszValue))
+ return VERR_VD_VMDK_VALUE_NOT_FOUND;
+ int rc = vmdkStringUnquote(pImage, pszValue, &pszValueUnquoted, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTUuidFromStr(pUuid, pszValueUnquoted);
+ RTMemTmpFree(pszValueUnquoted);
+ return rc;
+}
+
+static int vmdkDescDDBSetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, const char *pszVal)
+{
+ int rc;
+ char *pszValQuoted;
+
+ if (pszVal)
+ {
+ rc = RTStrAPrintf(&pszValQuoted, "\"%s\"", pszVal);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ pszValQuoted = NULL;
+ rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ pszValQuoted);
+ if (pszValQuoted)
+ RTStrFree(pszValQuoted);
+ return rc;
+}
+
+static int vmdkDescDDBSetUuid(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, PCRTUUID pUuid)
+{
+ char *pszUuid;
+
+ int rc = RTStrAPrintf(&pszUuid, "\"%RTuuid\"", pUuid);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ pszUuid);
+ RTStrFree(pszUuid);
+ return rc;
+}
+
+static int vmdkDescDDBSetU32(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, uint32_t uValue)
+{
+ char *pszValue;
+
+ int rc = RTStrAPrintf(&pszValue, "\"%d\"", uValue);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ pszValue);
+ RTStrFree(pszValue);
+ return rc;
+}
+
+static int vmdkPreprocessDescriptor(PVMDKIMAGE pImage, char *pDescData,
+ size_t cbDescData,
+ PVMDKDESCRIPTOR pDescriptor)
+{
+ int rc = VINF_SUCCESS;
+ unsigned cLine = 0, uLastNonEmptyLine = 0;
+ char *pTmp = pDescData;
+
+ pDescriptor->cbDescAlloc = cbDescData;
+ while (*pTmp != '\0')
+ {
+ pDescriptor->aLines[cLine++] = pTmp;
+ if (cLine >= VMDK_DESCRIPTOR_LINES_MAX)
+ {
+ rc = vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ while (*pTmp != '\0' && *pTmp != '\n')
+ {
+ if (*pTmp == '\r')
+ {
+ if (*(pTmp + 1) != '\n')
+ {
+ rc = vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: unsupported end of line in descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ else
+ {
+ /* Get rid of CR character. */
+ *pTmp = '\0';
+ }
+ }
+ pTmp++;
+ }
+ /* Get rid of LF character. */
+ if (*pTmp == '\n')
+ {
+ *pTmp = '\0';
+ pTmp++;
+ }
+ }
+ pDescriptor->cLines = cLine;
+ /* Pointer right after the end of the used part of the buffer. */
+ pDescriptor->aLines[cLine] = pTmp;
+
+ if ( strcmp(pDescriptor->aLines[0], "# Disk DescriptorFile")
+ && strcmp(pDescriptor->aLines[0], "# Disk Descriptor File"))
+ {
+ rc = vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: descriptor does not start as expected in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ /* Initialize those, because we need to be able to reopen an image. */
+ pDescriptor->uFirstDesc = 0;
+ pDescriptor->uFirstExtent = 0;
+ pDescriptor->uFirstDDB = 0;
+ for (unsigned i = 0; i < cLine; i++)
+ {
+ if (*pDescriptor->aLines[i] != '#' && *pDescriptor->aLines[i] != '\0')
+ {
+ if ( !strncmp(pDescriptor->aLines[i], "RW", 2)
+ || !strncmp(pDescriptor->aLines[i], "RDONLY", 6)
+ || !strncmp(pDescriptor->aLines[i], "NOACCESS", 8) )
+ {
+ /* An extent descriptor. */
+ if (!pDescriptor->uFirstDesc || pDescriptor->uFirstDDB)
+ {
+ /* Incorrect ordering of entries. */
+ rc = vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect ordering of entries in descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ if (!pDescriptor->uFirstExtent)
+ {
+ pDescriptor->uFirstExtent = i;
+ uLastNonEmptyLine = 0;
+ }
+ }
+ else if (!strncmp(pDescriptor->aLines[i], "ddb.", 4))
+ {
+ /* A disk database entry. */
+ if (!pDescriptor->uFirstDesc || !pDescriptor->uFirstExtent)
+ {
+ /* Incorrect ordering of entries. */
+ rc = vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect ordering of entries in descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ if (!pDescriptor->uFirstDDB)
+ {
+ pDescriptor->uFirstDDB = i;
+ uLastNonEmptyLine = 0;
+ }
+ }
+ else
+ {
+ /* A normal entry. */
+ if (pDescriptor->uFirstExtent || pDescriptor->uFirstDDB)
+ {
+ /* Incorrect ordering of entries. */
+ rc = vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect ordering of entries in descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ if (!pDescriptor->uFirstDesc)
+ {
+ pDescriptor->uFirstDesc = i;
+ uLastNonEmptyLine = 0;
+ }
+ }
+ if (uLastNonEmptyLine)
+ pDescriptor->aNextLines[uLastNonEmptyLine] = i;
+ uLastNonEmptyLine = i;
+ }
+ }
+
+out:
+ return rc;
+}
+
+static int vmdkDescSetPCHSGeometry(PVMDKIMAGE pImage,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ int rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_CYLINDERS,
+ pPCHSGeometry->cCylinders);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_HEADS,
+ pPCHSGeometry->cHeads);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_SECTORS,
+ pPCHSGeometry->cSectors);
+ return rc;
+}
+
+static int vmdkDescSetLCHSGeometry(PVMDKIMAGE pImage,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ int rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_CYLINDERS,
+ pLCHSGeometry->cCylinders);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_HEADS,
+ pLCHSGeometry->cHeads);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_SECTORS,
+ pLCHSGeometry->cSectors);
+ return rc;
+}
+
+static int vmdkCreateDescriptor(PVMDKIMAGE pImage, char *pDescData,
+ size_t cbDescData, PVMDKDESCRIPTOR pDescriptor)
+{
+ int rc;
+
+ pDescriptor->uFirstDesc = 0;
+ pDescriptor->uFirstExtent = 0;
+ pDescriptor->uFirstDDB = 0;
+ pDescriptor->cLines = 0;
+ pDescriptor->cbDescAlloc = cbDescData;
+ pDescriptor->fDirty = false;
+ pDescriptor->aLines[pDescriptor->cLines] = pDescData;
+ memset(pDescriptor->aNextLines, '\0', sizeof(pDescriptor->aNextLines));
+
+ rc = vmdkDescInitStr(pImage, pDescriptor, "# Disk DescriptorFile");
+ if (RT_FAILURE(rc))
+ goto out;
+ rc = vmdkDescInitStr(pImage, pDescriptor, "version=1");
+ if (RT_FAILURE(rc))
+ goto out;
+ pDescriptor->uFirstDesc = pDescriptor->cLines - 1;
+ rc = vmdkDescInitStr(pImage, pDescriptor, "");
+ if (RT_FAILURE(rc))
+ goto out;
+ rc = vmdkDescInitStr(pImage, pDescriptor, "# Extent description");
+ if (RT_FAILURE(rc))
+ goto out;
+ rc = vmdkDescInitStr(pImage, pDescriptor, "NOACCESS 0 ZERO ");
+ if (RT_FAILURE(rc))
+ goto out;
+ pDescriptor->uFirstExtent = pDescriptor->cLines - 1;
+ rc = vmdkDescInitStr(pImage, pDescriptor, "");
+ if (RT_FAILURE(rc))
+ goto out;
+ /* The trailing space is created by VMware, too. */
+ rc = vmdkDescInitStr(pImage, pDescriptor, "# The disk Data Base ");
+ if (RT_FAILURE(rc))
+ goto out;
+ rc = vmdkDescInitStr(pImage, pDescriptor, "#DDB");
+ if (RT_FAILURE(rc))
+ goto out;
+ rc = vmdkDescInitStr(pImage, pDescriptor, "");
+ if (RT_FAILURE(rc))
+ goto out;
+ rc = vmdkDescInitStr(pImage, pDescriptor, "ddb.virtualHWVersion = \"4\"");
+ if (RT_FAILURE(rc))
+ goto out;
+ pDescriptor->uFirstDDB = pDescriptor->cLines - 1;
+
+ /* Now that the framework is in place, use the normal functions to insert
+ * the remaining keys. */
+ char szBuf[9];
+ RTStrPrintf(szBuf, sizeof(szBuf), "%08x", RTRandU32());
+ rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDesc,
+ "CID", szBuf);
+ if (RT_FAILURE(rc))
+ goto out;
+ rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDesc,
+ "parentCID", "ffffffff");
+ if (RT_FAILURE(rc))
+ goto out;
+
+ rc = vmdkDescDDBSetStr(pImage, pDescriptor, "ddb.adapterType", "ide");
+ if (RT_FAILURE(rc))
+ goto out;
+
+out:
+ return rc;
+}
+
+static int vmdkParseDescriptor(PVMDKIMAGE pImage, char *pDescData,
+ size_t cbDescData)
+{
+ int rc;
+ unsigned cExtents;
+ unsigned uLine;
+
+ rc = vmdkPreprocessDescriptor(pImage, pDescData, cbDescData,
+ &pImage->Descriptor);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Check version, must be 1. */
+ uint32_t uVersion;
+ rc = vmdkDescBaseGetU32(&pImage->Descriptor, "version", &uVersion);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error finding key 'version' in descriptor in '%s'"), pImage->pszFilename);
+ if (uVersion != 1)
+ return vmdkError(pImage, VERR_VD_VMDK_UNSUPPORTED_VERSION, RT_SRC_POS, N_("VMDK: unsupported format version in descriptor in '%s'"), pImage->pszFilename);
+
+ /* Get image creation type and determine image flags. */
+ const char *pszCreateType;
+ rc = vmdkDescBaseGetStr(pImage, &pImage->Descriptor, "createType",
+ &pszCreateType);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: cannot get image type from descriptor in '%s'"), pImage->pszFilename);
+ if ( !strcmp(pszCreateType, "twoGbMaxExtentSparse")
+ || !strcmp(pszCreateType, "twoGbMaxExtentFlat"))
+ pImage->uImageFlags = VD_VMDK_IMAGE_FLAGS_SPLIT_2G;
+ if ( !strcmp(pszCreateType, "partitionedDevice")
+ || !strcmp(pszCreateType, "fullDevice"))
+ pImage->uImageFlags = VD_VMDK_IMAGE_FLAGS_RAWDISK;
+ else
+ pImage->uImageFlags = 0;
+ RTStrFree((char *)(void *)pszCreateType);
+
+ /* Count the number of extent config entries. */
+ for (uLine = pImage->Descriptor.uFirstExtent, cExtents = 0;
+ uLine != 0;
+ uLine = pImage->Descriptor.aNextLines[uLine], cExtents++)
+ /* nothing */;
+
+ if (!pImage->pDescData && cExtents != 1)
+ {
+ /* Monolithic image, must have only one extent (already opened). */
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: monolithic image may only have one extent in '%s'"), pImage->pszFilename);
+ }
+
+ if (pImage->pDescData)
+ {
+ /* Non-monolithic image, extents need to be allocated. */
+ rc = vmdkCreateExtents(pImage, cExtents);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ for (unsigned i = 0, uLine = pImage->Descriptor.uFirstExtent;
+ i < cExtents; i++, uLine = pImage->Descriptor.aNextLines[uLine])
+ {
+ char *pszLine = pImage->Descriptor.aLines[uLine];
+
+ /* Access type of the extent. */
+ if (!strncmp(pszLine, "RW", 2))
+ {
+ pImage->pExtents[i].enmAccess = VMDKACCESS_READWRITE;
+ pszLine += 2;
+ }
+ else if (!strncmp(pszLine, "RDONLY", 6))
+ {
+ pImage->pExtents[i].enmAccess = VMDKACCESS_READONLY;
+ pszLine += 6;
+ }
+ else if (!strncmp(pszLine, "NOACCESS", 8))
+ {
+ pImage->pExtents[i].enmAccess = VMDKACCESS_NOACCESS;
+ pszLine += 8;
+ }
+ else
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ if (*pszLine++ != ' ')
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+
+ /* Nominal size of the extent. */
+ rc = RTStrToUInt64Ex(pszLine, &pszLine, 10,
+ &pImage->pExtents[i].cNominalSectors);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ if (*pszLine++ != ' ')
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+
+ /* Type of the extent. */
+#ifdef VBOX_WITH_VMDK_ESX
+ /** @todo Add the ESX extent types. Not necessary for now because
+ * the ESX extent types are only used inside an ESX server. They are
+ * automatically converted if the VMDK is exported. */
+#endif /* VBOX_WITH_VMDK_ESX */
+ if (!strncmp(pszLine, "SPARSE", 6))
+ {
+ pImage->pExtents[i].enmType = VMDKETYPE_HOSTED_SPARSE;
+ pszLine += 6;
+ }
+ else if (!strncmp(pszLine, "FLAT", 4))
+ {
+ pImage->pExtents[i].enmType = VMDKETYPE_FLAT;
+ pszLine += 4;
+ }
+ else if (!strncmp(pszLine, "ZERO", 4))
+ {
+ pImage->pExtents[i].enmType = VMDKETYPE_ZERO;
+ pszLine += 4;
+ }
+ else
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ if (pImage->pExtents[i].enmType == VMDKETYPE_ZERO)
+ {
+ /* This one has no basename or offset. */
+ if (*pszLine == ' ')
+ pszLine++;
+ if (*pszLine != '\0')
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ pImage->pExtents[i].pszBasename = NULL;
+ }
+ else
+ {
+ /* All other extent types have basename and optional offset. */
+ if (*pszLine++ != ' ')
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+
+ /* Basename of the image. Surrounded by quotes. */
+ char *pszBasename;
+ rc = vmdkStringUnquote(pImage, pszLine, &pszBasename, &pszLine);
+ if (RT_FAILURE(rc))
+ return rc;
+ pImage->pExtents[i].pszBasename = pszBasename;
+ if (*pszLine == ' ')
+ {
+ pszLine++;
+ if (*pszLine != '\0')
+ {
+ /* Optional offset in extent specified. */
+ rc = RTStrToUInt64Ex(pszLine, &pszLine, 10,
+ &pImage->pExtents[i].uSectorOffset);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ }
+ }
+
+ if (*pszLine != '\0')
+ return vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ }
+ }
+
+ /* Determine PCHS geometry (autogenerate if necessary). */
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_CYLINDERS,
+ &pImage->PCHSGeometry.cCylinders);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->PCHSGeometry.cCylinders = 0;
+ else if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting PCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_HEADS,
+ &pImage->PCHSGeometry.cHeads);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->PCHSGeometry.cHeads = 0;
+ else if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting PCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_SECTORS,
+ &pImage->PCHSGeometry.cSectors);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->PCHSGeometry.cSectors = 0;
+ else if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting PCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ if ( pImage->PCHSGeometry.cCylinders == 0
+ || pImage->PCHSGeometry.cHeads == 0
+ || pImage->PCHSGeometry.cHeads > 16
+ || pImage->PCHSGeometry.cSectors == 0
+ || pImage->PCHSGeometry.cSectors > 63)
+ {
+ /* Mark PCHS geometry as not yet valid (can't do the calculation here
+ * as the total image size isn't known yet). */
+ pImage->PCHSGeometry.cCylinders = 0;
+ pImage->PCHSGeometry.cHeads = 16;
+ pImage->PCHSGeometry.cSectors = 63;
+ }
+
+ /* Determine LCHS geometry (set to 0 if not specified). */
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_CYLINDERS,
+ &pImage->LCHSGeometry.cCylinders);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->LCHSGeometry.cCylinders = 0;
+ else if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting LCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_HEADS,
+ &pImage->LCHSGeometry.cHeads);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->LCHSGeometry.cHeads = 0;
+ else if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting LCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_SECTORS,
+ &pImage->LCHSGeometry.cSectors);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->LCHSGeometry.cSectors = 0;
+ else if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting LCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ if ( pImage->LCHSGeometry.cCylinders == 0
+ || pImage->LCHSGeometry.cHeads == 0
+ || pImage->LCHSGeometry.cSectors == 0)
+ {
+ pImage->LCHSGeometry.cCylinders = 0;
+ pImage->LCHSGeometry.cHeads = 0;
+ pImage->LCHSGeometry.cSectors = 0;
+ }
+
+ /* Get image UUID. */
+ rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor, VMDK_DDB_IMAGE_UUID,
+ &pImage->ImageUuid);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ {
+ /* Image without UUID. Probably created by VMware and not yet used
+ * by VirtualBox. Can only be added for images opened in read/write
+ * mode, so don't bother producing a sensible UUID otherwise. */
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ RTUuidClear(&pImage->ImageUuid);
+ else
+ {
+ rc = RTUuidCreate(&pImage->ImageUuid);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_IMAGE_UUID, &pImage->ImageUuid);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ }
+ else if (RT_FAILURE(rc))
+ return rc;
+
+ /* Get image modification UUID. */
+ rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_MODIFICATION_UUID,
+ &pImage->ModificationUuid);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ {
+ /* Image without UUID. Probably created by VMware and not yet used
+ * by VirtualBox. Can only be added for images opened in read/write
+ * mode, so don't bother producing a sensible UUID otherwise. */
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ RTUuidClear(&pImage->ModificationUuid);
+ else
+ {
+ rc = RTUuidCreate(&pImage->ModificationUuid);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_MODIFICATION_UUID,
+ &pImage->ModificationUuid);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image modification UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ }
+ else if (RT_FAILURE(rc))
+ return rc;
+
+ /* Get UUID of parent image. */
+ rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor, VMDK_DDB_PARENT_UUID,
+ &pImage->ParentUuid);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ {
+ /* Image without UUID. Probably created by VMware and not yet used
+ * by VirtualBox. Can only be added for images opened in read/write
+ * mode, so don't bother producing a sensible UUID otherwise. */
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ RTUuidClear(&pImage->ParentUuid);
+ else
+ {
+ rc = RTUuidClear(&pImage->ParentUuid);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_UUID, &pImage->ParentUuid);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ }
+ else if (RT_FAILURE(rc))
+ return rc;
+
+ /* Get parent image modification UUID. */
+ rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_MODIFICATION_UUID,
+ &pImage->ParentModificationUuid);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ {
+ /* Image without UUID. Probably created by VMware and not yet used
+ * by VirtualBox. Can only be added for images opened in read/write
+ * mode, so don't bother producing a sensible UUID otherwise. */
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ RTUuidClear(&pImage->ParentModificationUuid);
+ else
+ {
+ rc = RTUuidCreate(&pImage->ParentModificationUuid);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_MODIFICATION_UUID,
+ &pImage->ParentModificationUuid);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent modification UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ }
+ else if (RT_FAILURE(rc))
+ return rc;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: write/update the descriptor part of the image.
+ */
+static int vmdkWriteDescriptor(PVMDKIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+ uint64_t cbLimit;
+ uint64_t uOffset;
+ PVMDKFILE pDescFile;
+
+ if (pImage->pDescData)
+ {
+ /* Separate descriptor file. */
+ uOffset = 0;
+ cbLimit = 0;
+ pDescFile = pImage->pFile;
+ }
+ else
+ {
+ /* Embedded descriptor file. */
+ uOffset = VMDK_SECTOR2BYTE(pImage->pExtents[0].uDescriptorSector);
+ cbLimit = VMDK_SECTOR2BYTE(pImage->pExtents[0].cDescriptorSectors);
+ cbLimit += uOffset;
+ pDescFile = pImage->pExtents[0].pFile;
+ }
+ for (unsigned i = 0; i < pImage->Descriptor.cLines; i++)
+ {
+ const char *psz = pImage->Descriptor.aLines[i];
+ size_t cb = strlen(psz);
+
+ if (cbLimit && uOffset + cb + 1 > cbLimit)
+ return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too long in '%s'"), pImage->pszFilename);
+ rc = vmdkFileWriteAt(pDescFile, uOffset, psz, cb, NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor in '%s'"), pImage->pszFilename);
+ uOffset += cb;
+ rc = vmdkFileWriteAt(pDescFile, uOffset, "\n", 1, NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor in '%s'"), pImage->pszFilename);
+ uOffset++;
+ }
+ if (cbLimit)
+ {
+ /* Inefficient, but simple. */
+ while (uOffset < cbLimit)
+ {
+ rc = vmdkFileWriteAt(pDescFile, uOffset, "", 1, NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor in '%s'"), pImage->pszFilename);
+ uOffset++;
+ }
+ }
+ else
+ {
+ rc = vmdkFileSetSize(pDescFile, uOffset);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error truncating descriptor in '%s'"), pImage->pszFilename);
+ }
+ pImage->Descriptor.fDirty = false;
+ return rc;
+}
+
+/**
+ * Internal: read metadata belonging to an extent with binary header, i.e.
+ * as found in monolithic files.
+ */
+static int vmdkReadBinaryMetaExtent(PVMDKIMAGE pImage, PVMDKEXTENT pExtent)
+{
+ SparseExtentHeader Header;
+ uint64_t cSectorsPerGDE;
+
+ int rc = vmdkFileReadAt(pExtent->pFile, 0, &Header, sizeof(Header), NULL);
+ AssertRC(rc);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading extent header in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+ if (RT_LE2H_U32(Header.magicNumber) != VMDK_SPARSE_MAGICNUMBER)
+ {
+ rc = vmdkError(pExtent->pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect magic in sparse extent header in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+ if (RT_LE2H_U32(Header.version) != 1)
+ {
+ rc = vmdkError(pExtent->pImage, VERR_VD_VMDK_UNSUPPORTED_VERSION, RT_SRC_POS, N_("VMDK: incorrect version in sparse extent header in '%s', not a VMDK 1.0 conforming file"), pExtent->pszFullname);
+ goto out;
+ }
+ if ( (RT_LE2H_U32(Header.flags) & 1)
+ && ( Header.singleEndLineChar != '\n'
+ || Header.nonEndLineChar != ' '
+ || Header.doubleEndLineChar1 != '\r'
+ || Header.doubleEndLineChar2 != '\n') )
+ {
+ rc = vmdkError(pExtent->pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: corrupted by CR/LF translation in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+ pExtent->enmType = VMDKETYPE_HOSTED_SPARSE; /* Just dummy value, changed later. */
+ pExtent->cSectors = RT_LE2H_U64(Header.capacity);
+ pExtent->cSectorsPerGrain = RT_LE2H_U64(Header.grainSize);
+ pExtent->uDescriptorSector = RT_LE2H_U64(Header.descriptorOffset);
+ pExtent->cDescriptorSectors = RT_LE2H_U64(Header.descriptorSize);
+ if (pExtent->uDescriptorSector && !pExtent->cDescriptorSectors)
+ {
+ rc = vmdkError(pExtent->pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: inconsistent embedded descriptor config in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+ pExtent->cGTEntries = RT_LE2H_U32(Header.numGTEsPerGT);
+ if (RT_LE2H_U32(Header.flags) & 2)
+ {
+ pExtent->uSectorRGD = RT_LE2H_U64(Header.rgdOffset);
+ pExtent->uSectorGD = RT_LE2H_U64(Header.gdOffset);
+ }
+ else
+ {
+ /** @todo this is just guesswork, the spec doesn't document this
+ * properly and I don't have a vmdk without RGD. */
+ pExtent->uSectorGD = RT_LE2H_U64(Header.rgdOffset);
+ pExtent->uSectorRGD = 0;
+ }
+ pExtent->cOverheadSectors = RT_LE2H_U64(Header.overHead);
+ pExtent->fUncleanShutdown = !!Header.uncleanShutdown;
+ cSectorsPerGDE = pExtent->cGTEntries * pExtent->cSectorsPerGrain;
+ if (!cSectorsPerGDE || cSectorsPerGDE > UINT32_MAX)
+ {
+ rc = vmdkError(pExtent->pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect grain directory size in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+ pExtent->cSectorsPerGDE = cSectorsPerGDE;
+ pExtent->cGDEntries = (pExtent->cSectors + cSectorsPerGDE - 1) / cSectorsPerGDE;
+
+ /* Fix up the number of descriptor sectors, as some flat images have
+ * really just one, and this causes failures when inserting the UUID
+ * values and other extra information. */
+ if (pExtent->cDescriptorSectors != 0 && pExtent->cDescriptorSectors < 4)
+ {
+ /* Do it the easy way - just fix it for flat images which have no
+ * other complicated metadata which needs space too. */
+ if ( pExtent->uDescriptorSector + 4 < pExtent->cOverheadSectors
+ && pExtent->cGTEntries * pExtent->cGDEntries == 0)
+ pExtent->cDescriptorSectors = 4;
+ }
+
+out:
+ if (RT_FAILURE(rc))
+ vmdkFreeExtentData(pImage, pExtent, false);
+
+ return rc;
+}
+
+/**
+ * Internal: read additional metadata belonging to an extent. For those
+ * extents which have no additional metadata just verify the information.
+ */
+static int vmdkReadMetaExtent(PVMDKIMAGE pImage, PVMDKEXTENT pExtent)
+{
+ int rc = VINF_SUCCESS;
+ uint64_t cbExtentSize;
+
+ /* The image must be a multiple of a sector in size and contain the data
+ * area (flat images only). If not, it means the image is at least
+ * truncated, or even seriously garbled. */
+ rc = vmdkFileGetSize(pExtent->pFile, &cbExtentSize);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+/* disabled the size check again as there are too many too short vmdks out there */
+#ifdef VBOX_WITH_VMDK_STRICT_SIZE_CHECK
+ if ( cbExtentSize != RT_ALIGN_64(cbExtentSize, 512)
+ && (pExtent->enmType != VMDKETYPE_FLAT || pExtent->cNominalSectors + pExtent->uSectorOffset > VMDK_BYTE2SECTOR(cbExtentSize)))
+ {
+ rc = vmdkError(pExtent->pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: file size is not a multiple of 512 in '%s', file is truncated or otherwise garbled"), pExtent->pszFullname);
+ goto out;
+ }
+#endif /* VBOX_WITH_VMDK_STRICT_SIZE_CHECK */
+ if (pExtent->enmType != VMDKETYPE_HOSTED_SPARSE)
+ goto out;
+
+ /* The spec says that this must be a power of two and greater than 8,
+ * but probably they meant not less than 8. */
+ if ( (pExtent->cSectorsPerGrain & (pExtent->cSectorsPerGrain - 1))
+ || pExtent->cSectorsPerGrain < 8)
+ {
+ rc = vmdkError(pExtent->pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: invalid extent grain size %u in '%s'"), pExtent->cSectorsPerGrain, pExtent->pszFullname);
+ goto out;
+ }
+
+ /* This code requires that a grain table must hold a power of two multiple
+ * of the number of entries per GT cache entry. */
+ if ( (pExtent->cGTEntries & (pExtent->cGTEntries - 1))
+ || pExtent->cGTEntries < VMDK_GT_CACHELINE_SIZE)
+ {
+ rc = vmdkError(pExtent->pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: grain table cache size problem in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+
+ rc = vmdkReadGrainDirectory(pExtent);
+
+out:
+ if (RT_FAILURE(rc))
+ vmdkFreeExtentData(pImage, pExtent, false);
+
+ return rc;
+}
+
+/**
+ * Internal: write/update the metadata for a sparse extent.
+ */
+static int vmdkWriteMetaSparseExtent(PVMDKEXTENT pExtent)
+{
+ SparseExtentHeader Header;
+
+ memset(&Header, '\0', sizeof(Header));
+ Header.magicNumber = RT_H2LE_U32(VMDK_SPARSE_MAGICNUMBER);
+ Header.version = RT_H2LE_U32(1);
+ Header.flags = RT_H2LE_U32(1 | ((pExtent->pRGD) ? 2 : 0));
+ Header.capacity = RT_H2LE_U64(pExtent->cSectors);
+ Header.grainSize = RT_H2LE_U64(pExtent->cSectorsPerGrain);
+ Header.descriptorOffset = RT_H2LE_U64(pExtent->uDescriptorSector);
+ Header.descriptorSize = RT_H2LE_U64(pExtent->cDescriptorSectors);
+ Header.numGTEsPerGT = RT_H2LE_U32(pExtent->cGTEntries);
+ if (pExtent->pRGD)
+ {
+ Assert(pExtent->uSectorRGD);
+ Header.rgdOffset = RT_H2LE_U64(pExtent->uSectorRGD);
+ Header.gdOffset = RT_H2LE_U64(pExtent->uSectorGD);
+ }
+ else
+ {
+ /** @todo this is just guesswork, the spec doesn't document this
+ * properly and I don't have a vmdk without RGD. */
+ Header.rgdOffset = RT_H2LE_U64(pExtent->uSectorGD);
+ }
+ Header.overHead = RT_H2LE_U64(pExtent->cOverheadSectors);
+ Header.uncleanShutdown = pExtent->fUncleanShutdown;
+ Header.singleEndLineChar = '\n';
+ Header.nonEndLineChar = ' ';
+ Header.doubleEndLineChar1 = '\r';
+ Header.doubleEndLineChar2 = '\n';
+
+ int rc = vmdkFileWriteAt(pExtent->pFile, 0, &Header, sizeof(Header), NULL);
+ AssertRC(rc);
+ if (RT_FAILURE(rc))
+ rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error writing extent header in '%s'"), pExtent->pszFullname);
+ return rc;
+}
+
+#ifdef VBOX_WITH_VMDK_ESX
+/**
+ * Internal: unused code to read the metadata of a sparse ESX extent.
+ *
+ * Such extents never leave ESX server, so this isn't ever used.
+ */
+static int vmdkReadMetaESXSparseExtent(PVMDKEXTENT pExtent)
+{
+ COWDisk_Header Header;
+ uint64_t cSectorsPerGDE;
+
+ int rc = vmdkFileReadAt(pExtent->pFile, 0, &Header, sizeof(Header), NULL);
+ AssertRC(rc);
+ if (RT_FAILURE(rc))
+ goto out;
+ if ( RT_LE2H_U32(Header.magicNumber) != VMDK_ESX_SPARSE_MAGICNUMBER
+ || RT_LE2H_U32(Header.version) != 1
+ || RT_LE2H_U32(Header.flags) != 3)
+ {
+ rc = VERR_VD_VMDK_INVALID_HEADER;
+ goto out;
+ }
+ pExtent->enmType = VMDKETYPE_ESX_SPARSE;
+ pExtent->cSectors = RT_LE2H_U32(Header.numSectors);
+ pExtent->cSectorsPerGrain = RT_LE2H_U32(Header.grainSize);
+ /* The spec says that this must be between 1 sector and 1MB. This code
+ * assumes it's a power of two, so check that requirement, too. */
+ if ( (pExtent->cSectorsPerGrain & (pExtent->cSectorsPerGrain - 1))
+ || pExtent->cSectorsPerGrain == 0
+ || pExtent->cSectorsPerGrain > 2048)
+ {
+ rc = VERR_VD_VMDK_INVALID_HEADER;
+ goto out;
+ }
+ pExtent->uDescriptorSector = 0;
+ pExtent->cDescriptorSectors = 0;
+ pExtent->uSectorGD = RT_LE2H_U32(Header.gdOffset);
+ pExtent->uSectorRGD = 0;
+ pExtent->cOverheadSectors = 0;
+ pExtent->cGTEntries = 4096;
+ cSectorsPerGDE = pExtent->cGTEntries * pExtent->cSectorsPerGrain;
+ if (!cSectorsPerGDE || cSectorsPerGDE > UINT32_MAX)
+ {
+ rc = VERR_VD_VMDK_INVALID_HEADER;
+ goto out;
+ }
+ pExtent->cSectorsPerGDE = cSectorsPerGDE;
+ pExtent->cGDEntries = (pExtent->cSectors + cSectorsPerGDE - 1) / cSectorsPerGDE;
+ if (pExtent->cGDEntries != RT_LE2H_U32(Header.numGDEntries))
+ {
+ /* Inconsistency detected. Computed number of GD entries doesn't match
+ * stored value. Better be safe than sorry. */
+ rc = VERR_VD_VMDK_INVALID_HEADER;
+ goto out;
+ }
+ pExtent->uFreeSector = RT_LE2H_U32(Header.freeSector);
+ pExtent->fUncleanShutdown = !!Header.uncleanShutdown;
+
+ rc = vmdkReadGrainDirectory(pExtent);
+
+out:
+ if (RT_FAILURE(rc))
+ vmdkFreeExtentData(pImage, pExtent, false);
+
+ return rc;
+}
+#endif /* VBOX_WITH_VMDK_ESX */
+
+/**
+ * Internal: free the memory used by the extent data structure, optionally
+ * deleting the referenced files.
+ */
+static void vmdkFreeExtentData(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ bool fDelete)
+{
+ vmdkFreeGrainDirectory(pExtent);
+ if (pExtent->pDescData)
+ {
+ RTMemFree(pExtent->pDescData);
+ pExtent->pDescData = NULL;
+ }
+ if (pExtent->pFile != NULL)
+ {
+ /* Do not delete raw extents, these have full and base names equal. */
+ vmdkFileClose(pImage, &pExtent->pFile,
+ fDelete
+ && pExtent->pszFullname
+ && strcmp(pExtent->pszFullname, pExtent->pszBasename));
+ }
+ if (pExtent->pszBasename)
+ {
+ RTMemTmpFree((void *)pExtent->pszBasename);
+ pExtent->pszBasename = NULL;
+ }
+ if (pExtent->pszFullname)
+ {
+ RTStrFree((char *)(void *)pExtent->pszFullname);
+ pExtent->pszFullname = NULL;
+ }
+}
+
+/**
+ * Internal: allocate grain table cache if necessary for this image.
+ */
+static int vmdkAllocateGrainTableCache(PVMDKIMAGE pImage)
+{
+ PVMDKEXTENT pExtent;
+
+ /* Allocate grain table cache if any sparse extent is present. */
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ pExtent = &pImage->pExtents[i];
+ if ( pExtent->enmType == VMDKETYPE_HOSTED_SPARSE
+#ifdef VBOX_WITH_VMDK_ESX
+ || pExtent->enmType == VMDKETYPE_ESX_SPARSE
+#endif /* VBOX_WITH_VMDK_ESX */
+ )
+ {
+ /* Allocate grain table cache. */
+ pImage->pGTCache = (PVMDKGTCACHE)RTMemAllocZ(sizeof(VMDKGTCACHE));
+ if (!pImage->pGTCache)
+ return VERR_NO_MEMORY;
+ for (unsigned i = 0; i < VMDK_GT_CACHE_SIZE; i++)
+ {
+ PVMDKGTCACHEENTRY pGCE = &pImage->pGTCache->aGTCache[i];
+ pGCE->uExtent = UINT32_MAX;
+ }
+ pImage->pGTCache->cEntries = VMDK_GT_CACHE_SIZE;
+ break;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: allocate the given number of extents.
+ */
+static int vmdkCreateExtents(PVMDKIMAGE pImage, unsigned cExtents)
+{
+ int rc = VINF_SUCCESS;
+ PVMDKEXTENT pExtents = (PVMDKEXTENT)RTMemAllocZ(cExtents * sizeof(VMDKEXTENT));
+ if (pImage)
+ {
+ for (unsigned i = 0; i < cExtents; i++)
+ {
+ pExtents[i].pFile = NULL;
+ pExtents[i].pszBasename = NULL;
+ pExtents[i].pszFullname = NULL;
+ pExtents[i].pGD = NULL;
+ pExtents[i].pRGD = NULL;
+ pExtents[i].pDescData = NULL;
+ pExtents[i].uExtent = i;
+ pExtents[i].pImage = pImage;
+ }
+ pImage->pExtents = pExtents;
+ pImage->cExtents = cExtents;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int vmdkOpenImage(PVMDKIMAGE pImage, unsigned uOpenFlags)
+{
+ int rc;
+ uint32_t u32Magic;
+ PVMDKFILE pFile;
+ PVMDKEXTENT pExtent;
+
+ pImage->uOpenFlags = uOpenFlags;
+
+ /* Try to get error interface. */
+ pImage->pInterfaceError = VDInterfaceGet(pImage->pVDIfsDisk, VDINTERFACETYPE_ERROR);
+ if (pImage->pInterfaceError)
+ pImage->pInterfaceErrorCallbacks = VDGetInterfaceError(pImage->pInterfaceError);
+
+ /* Try to get async I/O interface. */
+ pImage->pInterfaceAsyncIO = VDInterfaceGet(pImage->pVDIfsDisk, VDINTERFACETYPE_ASYNCIO);
+ if (pImage->pInterfaceAsyncIO)
+ pImage->pInterfaceAsyncIOCallbacks = VDGetInterfaceAsyncIO(pImage->pInterfaceAsyncIO);
+
+ /*
+ * Open the image.
+ * We don't have to check for asynchronous access because
+ * we only support raw access and the opened file is a description
+ * file were no data is stored.
+ */
+ rc = vmdkFileOpen(pImage, &pFile, pImage->pszFilename,
+ uOpenFlags & VD_OPEN_FLAGS_READONLY
+ ? RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+ : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, false);
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ goto out;
+ }
+ pImage->pFile = pFile;
+
+ /* Read magic (if present). */
+ rc = vmdkFileReadAt(pFile, 0, &u32Magic, sizeof(u32Magic), NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error reading the magic number in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ /* Handle the file according to its magic number. */
+ if (RT_LE2H_U32(u32Magic) == VMDK_SPARSE_MAGICNUMBER)
+ {
+ /* It's a hosted single-extent image. */
+ rc = vmdkCreateExtents(pImage, 1);
+ if (RT_FAILURE(rc))
+ goto out;
+ /* The opened file is passed to the extent. No separate descriptor
+ * file, so no need to keep anything open for the image. */
+ pExtent = &pImage->pExtents[0];
+ pExtent->pFile = pFile;
+ pImage->pFile = NULL;
+ pExtent->pszFullname = RTPathAbsDup(pImage->pszFilename);
+ if (!pExtent->pszFullname)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ rc = vmdkReadBinaryMetaExtent(pImage, pExtent);
+ if (RT_FAILURE(rc))
+ goto out;
+ /* As we're dealing with a monolithic image here, there must
+ * be a descriptor embedded in the image file. */
+ if (!pExtent->uDescriptorSector || !pExtent->cDescriptorSectors)
+ {
+ rc = vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: monolithic image without descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ /* Read the descriptor from the extent. */
+ pExtent->pDescData = (char *)RTMemAllocZ(VMDK_SECTOR2BYTE(pExtent->cDescriptorSectors));
+ if (!pExtent->pDescData)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ rc = vmdkFileReadAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(pExtent->uDescriptorSector),
+ pExtent->pDescData,
+ VMDK_SECTOR2BYTE(pExtent->cDescriptorSectors), NULL);
+ AssertRC(rc);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: read error for descriptor in '%s'"), pExtent->pszFullname);
+ goto out;
+ }
+
+ rc = vmdkParseDescriptor(pImage, pExtent->pDescData,
+ VMDK_SECTOR2BYTE(pExtent->cDescriptorSectors));
+ if (RT_FAILURE(rc))
+ goto out;
+
+ rc = vmdkReadMetaExtent(pImage, pExtent);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ /* Mark the extent as unclean if opened in read-write mode. */
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ pExtent->fUncleanShutdown = true;
+ pExtent->fMetaDirty = true;
+ }
+ }
+ else
+ {
+ pImage->cbDescAlloc = VMDK_SECTOR2BYTE(20);
+ pImage->pDescData = (char *)RTMemAllocZ(pImage->cbDescAlloc);
+ if (!pImage->pDescData)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ size_t cbRead;
+ rc = vmdkFileReadAt(pImage->pFile, 0, pImage->pDescData,
+ pImage->cbDescAlloc, &cbRead);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: read error for descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ if (cbRead == pImage->cbDescAlloc)
+ {
+ /* Likely the read is truncated. Better fail a bit too early
+ * (normally the descriptor is much smaller than our buffer). */
+ rc = vmdkError(pImage, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: cannot read descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ rc = vmdkParseDescriptor(pImage, pImage->pDescData,
+ pImage->cbDescAlloc);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ /*
+ * We have to check for the asynchronous open flag. The
+ * extents are parsed and the type of all are known now.
+ * Check if every extent is either FLAT or ZERO.
+ */
+ if (uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)
+ {
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[i];
+
+ if ( (pExtent->enmType != VMDKETYPE_FLAT)
+ && (pExtent->enmType != VMDKETYPE_ZERO))
+ {
+ /*
+ * Opened image contains at least one none flat or zero extent.
+ * Return error but don't set error message as the caller
+ * has the chance to open in non async I/O mode.
+ */
+ rc = VERR_NOT_SUPPORTED;
+ goto out;
+ }
+ }
+ }
+
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[i];
+
+ if (pExtent->pszBasename)
+ {
+ /* Hack to figure out whether the specified name in the
+ * extent descriptor is absolute. Doesn't always work, but
+ * should be good enough for now. */
+ char *pszFullname;
+ /** @todo implement proper path absolute check. */
+ if (pExtent->pszBasename[0] == RTPATH_SLASH)
+ {
+ pszFullname = RTStrDup(pExtent->pszBasename);
+ if (!pszFullname)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ }
+ else
+ {
+ size_t cbDirname;
+ char *pszDirname = RTStrDup(pImage->pszFilename);
+ if (!pszDirname)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ RTPathStripFilename(pszDirname);
+ cbDirname = strlen(pszDirname);
+ rc = RTStrAPrintf(&pszFullname, "%s%c%s", pszDirname,
+ RTPATH_SLASH, pExtent->pszBasename);
+ RTStrFree(pszDirname);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+ pExtent->pszFullname = pszFullname;
+ }
+ else
+ pExtent->pszFullname = NULL;
+
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_HOSTED_SPARSE:
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszFullname,
+ uOpenFlags & VD_OPEN_FLAGS_READONLY
+ ? RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+ : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, false);
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD
+ * layer has the choice of retrying the open if it
+ * failed. */
+ goto out;
+ }
+ rc = vmdkReadBinaryMetaExtent(pImage, pExtent);
+ if (RT_FAILURE(rc))
+ goto out;
+ rc = vmdkReadMetaExtent(pImage, pExtent);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ /* Mark extent as unclean if opened in read-write mode. */
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ pExtent->fUncleanShutdown = true;
+ pExtent->fMetaDirty = true;
+ }
+ break;
+ case VMDKETYPE_FLAT:
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszFullname,
+ uOpenFlags & VD_OPEN_FLAGS_READONLY
+ ? RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+ : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, true);
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD
+ * layer has the choice of retrying the open if it
+ * failed. */
+ goto out;
+ }
+ break;
+ case VMDKETYPE_ZERO:
+ /* Nothing to do. */
+ break;
+ default:
+ AssertMsgFailed(("unknown vmdk extent type %d\n", pExtent->enmType));
+ }
+ }
+ }
+
+ /* Make sure this is not reached accidentally with an error status. */
+ AssertRC(rc);
+
+ /* Determine PCHS geometry if not set. */
+ if (pImage->PCHSGeometry.cCylinders == 0)
+ {
+ uint64_t cCylinders = VMDK_BYTE2SECTOR(pImage->cbSize)
+ / pImage->PCHSGeometry.cHeads
+ / pImage->PCHSGeometry.cSectors;
+ pImage->PCHSGeometry.cCylinders = (unsigned)RT_MIN(cCylinders, 16383);
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ rc = vmdkDescSetPCHSGeometry(pImage, &pImage->PCHSGeometry);
+ AssertRC(rc);
+ }
+ }
+
+ /* Update the image metadata now in case has changed. */
+ rc = vmdkFlushImage(pImage);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ /* Figure out a few per-image constants from the extents. */
+ pImage->cbSize = 0;
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ pExtent = &pImage->pExtents[i];
+ if ( pExtent->enmType == VMDKETYPE_HOSTED_SPARSE
+#ifdef VBOX_WITH_VMDK_ESX
+ || pExtent->enmType == VMDKETYPE_ESX_SPARSE
+#endif /* VBOX_WITH_VMDK_ESX */
+ )
+ {
+ /* Here used to be a check whether the nominal size of an extent
+ * is a multiple of the grain size. The spec says that this is
+ * always the case, but unfortunately some files out there in the
+ * wild violate the spec (e.g. ReactOS 0.3.1). */
+ }
+ pImage->cbSize += VMDK_SECTOR2BYTE(pExtent->cNominalSectors);
+ }
+
+ pImage->enmImageType = VD_IMAGE_TYPE_NORMAL;
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ pExtent = &pImage->pExtents[i];
+ if ( pImage->pExtents[i].enmType == VMDKETYPE_FLAT
+ || pImage->pExtents[i].enmType == VMDKETYPE_ZERO)
+ {
+ pImage->enmImageType = VD_IMAGE_TYPE_FIXED;
+ break;
+ }
+ }
+
+ rc = vmdkAllocateGrainTableCache(pImage);
+ if (RT_FAILURE(rc))
+ goto out;
+
+out:
+ if (RT_FAILURE(rc))
+ vmdkFreeImage(pImage, false);
+ return rc;
+}
+
+/**
+ * Internal: create VMDK images for raw disk/partition access.
+ */
+static int vmdkCreateRawImage(PVMDKIMAGE pImage, const PVBOXHDDRAW pRaw,
+ uint64_t cbSize)
+{
+ int rc = VINF_SUCCESS;
+ PVMDKEXTENT pExtent;
+
+ if (pRaw->fRawDisk)
+ {
+ /* Full raw disk access. This requires setting up a descriptor
+ * file and open the (flat) raw disk. */
+ rc = vmdkCreateExtents(pImage, 1);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new extent list in '%s'"), pImage->pszFilename);
+ pExtent = &pImage->pExtents[0];
+ /* Create raw disk descriptor file. */
+ rc = vmdkFileOpen(pImage, &pImage->pFile, pImage->pszFilename,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE | RTFILE_O_NOT_CONTENT_INDEXED,
+ false);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new file '%s'"), pImage->pszFilename);
+
+ /* Set up basename for extent description. Cannot use StrDup. */
+ size_t cbBasename = strlen(pRaw->pszRawDisk) + 1;
+ char *pszBasename = (char *)RTMemTmpAlloc(cbBasename);
+ if (!pszBasename)
+ return VERR_NO_MEMORY;
+ memcpy(pszBasename, pRaw->pszRawDisk, cbBasename);
+ pExtent->pszBasename = pszBasename;
+ /* For raw disks the full name is identical to the base name. */
+ pExtent->pszFullname = RTStrDup(pszBasename);
+ if (!pExtent->pszFullname)
+ return VERR_NO_MEMORY;
+ pExtent->enmType = VMDKETYPE_FLAT;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(cbSize);
+ pExtent->uSectorOffset = 0;
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+
+ /* Open flat image, the raw disk. */
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszFullname,
+ RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, false);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not open raw disk file '%s'"), pExtent->pszFullname);
+ }
+ else
+ {
+ /* Raw partition access. This requires setting up a descriptor
+ * file, write the partition information to a flat extent and
+ * open all the (flat) raw disk partitions. */
+
+ /* First pass over the partitions to determine how many
+ * extents we need. One partition can require up to 4 extents.
+ * One to skip over unpartitioned space, one for the
+ * partitioning data, one to skip over unpartitioned space
+ * and one for the partition data. */
+ unsigned cExtents = 0;
+ uint64_t uStart = 0;
+ for (unsigned i = 0; i < pRaw->cPartitions; i++)
+ {
+ PVBOXHDDRAWPART pPart = &pRaw->pPartitions[i];
+ if (pPart->cbPartitionData)
+ {
+ if (uStart > pPart->uPartitionDataStart)
+ return vmdkError(pImage, VERR_INVALID_PARAMETER, RT_SRC_POS, N_("VMDK: cannot go backwards for partitioning information in '%s'"), pImage->pszFilename);
+ else if (uStart != pPart->uPartitionDataStart)
+ cExtents++;
+ uStart = pPart->uPartitionDataStart + pPart->cbPartitionData;
+ cExtents++;
+ }
+ if (pPart->cbPartition)
+ {
+ if (uStart > pPart->uPartitionStart)
+ return vmdkError(pImage, VERR_INVALID_PARAMETER, RT_SRC_POS, N_("VMDK: cannot go backwards for partition data in '%s'"), pImage->pszFilename);
+ else if (uStart != pPart->uPartitionStart)
+ cExtents++;
+ uStart = pPart->uPartitionStart + pPart->cbPartition;
+ cExtents++;
+ }
+ }
+ /* Another extent for filling up the rest of the image. */
+ if (uStart != cbSize)
+ cExtents++;
+
+ rc = vmdkCreateExtents(pImage, cExtents);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new extent list in '%s'"), pImage->pszFilename);
+
+ /* Create raw partition descriptor file. */
+ rc = vmdkFileOpen(pImage, &pImage->pFile, pImage->pszFilename,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE | RTFILE_O_NOT_CONTENT_INDEXED,
+ false);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new file '%s'"), pImage->pszFilename);
+
+ /* Create base filename for the partition table extent. */
+ /** @todo remove fixed buffer without creating memory leaks. */
+ char pszPartition[1024];
+ const char *pszBase = RTPathFilename(pImage->pszFilename);
+ const char *pszExt = RTPathExt(pszBase);
+ if (pszExt == NULL)
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: invalid filename '%s'"), pImage->pszFilename);
+ char *pszBaseBase = RTStrDup(pszBase);
+ if (!pszBaseBase)
+ return VERR_NO_MEMORY;
+ RTPathStripExt(pszBaseBase);
+ RTStrPrintf(pszPartition, sizeof(pszPartition), "%s-pt%s",
+ pszBaseBase, pszExt);
+ RTStrFree(pszBaseBase);
+
+ /* Second pass over the partitions, now define all extents. */
+ uint64_t uPartOffset = 0;
+ cExtents = 0;
+ uStart = 0;
+ for (unsigned i = 0; i < pRaw->cPartitions; i++)
+ {
+ PVBOXHDDRAWPART pPart = &pRaw->pPartitions[i];
+ if (pPart->cbPartitionData)
+ {
+ if (uStart != pPart->uPartitionDataStart)
+ {
+ pExtent = &pImage->pExtents[cExtents++];
+ pExtent->pszBasename = NULL;
+ pExtent->pszFullname = NULL;
+ pExtent->enmType = VMDKETYPE_ZERO;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(pPart->uPartitionDataStart - uStart);
+ pExtent->uSectorOffset = 0;
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+ }
+ uStart = pPart->uPartitionDataStart + pPart->cbPartitionData;
+ pExtent = &pImage->pExtents[cExtents++];
+ /* Set up basename for extent description. Can't use StrDup. */
+ size_t cbBasename = strlen(pszPartition) + 1;
+ char *pszBasename = (char *)RTMemTmpAlloc(cbBasename);
+ if (!pszBasename)
+ return VERR_NO_MEMORY;
+ memcpy(pszBasename, pszPartition, cbBasename);
+ pExtent->pszBasename = pszBasename;
+
+ /* Set up full name for partition extent. */
+ size_t cbDirname;
+ char *pszDirname = RTStrDup(pImage->pszFilename);
+ if (!pszDirname)
+ return VERR_NO_MEMORY;
+ RTPathStripFilename(pszDirname);
+ cbDirname = strlen(pszDirname);
+ char *pszFullname;
+ rc = RTStrAPrintf(&pszFullname, "%s%c%s", pszDirname,
+ RTPATH_SLASH, pExtent->pszBasename);
+ RTStrFree(pszDirname);
+ if (RT_FAILURE(rc))
+ return rc;
+ pExtent->pszFullname = pszFullname;
+ pExtent->enmType = VMDKETYPE_FLAT;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(pPart->cbPartitionData);
+ pExtent->uSectorOffset = uPartOffset;
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+
+ /* Create partition table flat image. */
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszFullname,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE | RTFILE_O_NOT_CONTENT_INDEXED,
+ false);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new partition data file '%s'"), pExtent->pszFullname);
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uPartOffset),
+ pPart->pvPartitionData,
+ pPart->cbPartitionData, NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not write partition data to '%s'"), pExtent->pszFullname);
+ uPartOffset += VMDK_BYTE2SECTOR(pPart->cbPartitionData);
+ }
+ if (pPart->cbPartition)
+ {
+ if (uStart != pPart->uPartitionStart)
+ {
+ pExtent = &pImage->pExtents[cExtents++];
+ pExtent->pszBasename = NULL;
+ pExtent->pszFullname = NULL;
+ pExtent->enmType = VMDKETYPE_ZERO;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(pPart->uPartitionStart - uStart);
+ pExtent->uSectorOffset = 0;
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+ }
+ uStart = pPart->uPartitionStart + pPart->cbPartition;
+ pExtent = &pImage->pExtents[cExtents++];
+ if (pPart->pszRawDevice)
+ {
+ /* Set up basename for extent descr. Can't use StrDup. */
+ size_t cbBasename = strlen(pPart->pszRawDevice) + 1;
+ char *pszBasename = (char *)RTMemTmpAlloc(cbBasename);
+ if (!pszBasename)
+ return VERR_NO_MEMORY;
+ memcpy(pszBasename, pPart->pszRawDevice, cbBasename);
+ pExtent->pszBasename = pszBasename;
+ /* For raw disks full name is identical to base name. */
+ pExtent->pszFullname = RTStrDup(pszBasename);
+ if (!pExtent->pszFullname)
+ return VERR_NO_MEMORY;
+ pExtent->enmType = VMDKETYPE_FLAT;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(pPart->cbPartition);
+ pExtent->uSectorOffset = VMDK_BYTE2SECTOR(pPart->uPartitionStartOffset);
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+
+ /* Open flat image, the raw partition. */
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszFullname,
+ RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE,
+ false);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not open raw partition file '%s'"), pExtent->pszFullname);
+ }
+ else
+ {
+ pExtent->pszBasename = NULL;
+ pExtent->pszFullname = NULL;
+ pExtent->enmType = VMDKETYPE_ZERO;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(pPart->cbPartition);
+ pExtent->uSectorOffset = 0;
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+ }
+ }
+ }
+ /* Another extent for filling up the rest of the image. */
+ if (uStart != cbSize)
+ {
+ pExtent = &pImage->pExtents[cExtents++];
+ pExtent->pszBasename = NULL;
+ pExtent->pszFullname = NULL;
+ pExtent->enmType = VMDKETYPE_ZERO;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(cbSize - uStart);
+ pExtent->uSectorOffset = 0;
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+ }
+ }
+
+ rc = vmdkDescBaseSetStr(pImage, &pImage->Descriptor, "createType",
+ pRaw->fRawDisk ?
+ "fullDevice" : "partitionedDevice");
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not set the image type in '%s'"), pImage->pszFilename);
+ return rc;
+}
+
+/**
+ * Internal: create a regular (i.e. file-backed) VMDK image.
+ */
+static int vmdkCreateRegularImage(PVMDKIMAGE pImage, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ PFNVMPROGRESS pfnProgress, void *pvUser,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ int rc = VINF_SUCCESS;
+ unsigned cExtents = 1;
+ uint64_t cbOffset = 0;
+ uint64_t cbRemaining = cbSize;
+
+ if (uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G)
+ {
+ cExtents = cbSize / VMDK_2G_SPLIT_SIZE;
+ /* Do proper extent computation: need one smaller extent if the total
+ * size isn't evenly divisible by the split size. */
+ if (cbSize % VMDK_2G_SPLIT_SIZE)
+ cExtents++;
+ }
+ rc = vmdkCreateExtents(pImage, cExtents);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new extent list in '%s'"), pImage->pszFilename);
+
+ /* Basename strings needed for constructing the extent names. */
+ char *pszBasenameSubstr = RTPathFilename(pImage->pszFilename);
+ AssertPtr(pszBasenameSubstr);
+ size_t cbBasenameSubstr = strlen(pszBasenameSubstr) + 1;
+
+ /* Create searate descriptor file if necessary. */
+ if (cExtents != 1 || enmType == VD_IMAGE_TYPE_FIXED)
+ {
+ rc = vmdkFileOpen(pImage, &pImage->pFile, pImage->pszFilename,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE | RTFILE_O_NOT_CONTENT_INDEXED,
+ false);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new sparse descriptor file '%s'"), pImage->pszFilename);
+ // @todo Is there any sense in the following line I've commented out?
+ //pImage->pszFilename = RTStrDup(pImage->pszFilename);
+ }
+ else
+ pImage->pFile = NULL;
+
+ /* Set up all extents. */
+ for (unsigned i = 0; i < cExtents; i++)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[i];
+ uint64_t cbExtent = cbRemaining;
+
+ /* Set up fullname/basename for extent description. Cannot use StrDup
+ * for basename, as it is not guaranteed that the memory can be freed
+ * with RTMemTmpFree, which must be used as in other code paths
+ * StrDup is not usable. */
+ if (cExtents == 1 && enmType != VD_IMAGE_TYPE_FIXED)
+ {
+ char *pszBasename = (char *)RTMemTmpAlloc(cbBasenameSubstr);
+ if (!pszBasename)
+ return VERR_NO_MEMORY;
+ memcpy(pszBasename, pszBasenameSubstr, cbBasenameSubstr);
+ pExtent->pszBasename = pszBasename;
+ }
+ else
+ {
+ char *pszBasenameExt = RTPathExt(pszBasenameSubstr);
+ char *pszBasenameBase = RTStrDup(pszBasenameSubstr);
+ RTPathStripExt(pszBasenameBase);
+ char *pszTmp;
+ size_t cbTmp;
+ if (enmType == VD_IMAGE_TYPE_FIXED)
+ {
+ if (cExtents == 1)
+ rc = RTStrAPrintf(&pszTmp, "%s-flat%s", pszBasenameBase,
+ pszBasenameExt);
+ else
+ rc = RTStrAPrintf(&pszTmp, "%s-f%03d%s", pszBasenameBase,
+ i+1, pszBasenameExt);
+ }
+ else
+ rc = RTStrAPrintf(&pszTmp, "%s-s%03d%s", pszBasenameBase, i+1,
+ pszBasenameExt);
+ RTStrFree(pszBasenameBase);
+ if (RT_FAILURE(rc))
+ return rc;
+ cbTmp = strlen(pszTmp) + 1;
+ char *pszBasename = (char *)RTMemTmpAlloc(cbTmp);
+ if (!pszBasename)
+ return VERR_NO_MEMORY;
+ memcpy(pszBasename, pszTmp, cbTmp);
+ RTStrFree(pszTmp);
+ pExtent->pszBasename = pszBasename;
+ if (uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G)
+ cbExtent = RT_MIN(cbRemaining, VMDK_2G_SPLIT_SIZE);
+ }
+ char *pszBasedirectory = RTStrDup(pImage->pszFilename);
+ RTPathStripFilename(pszBasedirectory);
+ char *pszFullname;
+ rc = RTStrAPrintf(&pszFullname, "%s%c%s", pszBasedirectory,
+ RTPATH_SLASH, pExtent->pszBasename);
+ RTStrFree(pszBasedirectory);
+ if (RT_FAILURE(rc))
+ return rc;
+ pExtent->pszFullname = pszFullname;
+
+ /* Create file for extent. */
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszFullname,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE | RTFILE_O_NOT_CONTENT_INDEXED,
+ false);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new file '%s'"), pExtent->pszFullname);
+ if (enmType == VD_IMAGE_TYPE_FIXED)
+ {
+ rc = vmdkFileSetSize(pExtent->pFile, cbExtent);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not set size of new file '%s'"), pExtent->pszFullname);
+
+ /* Fill image with zeroes. We do this for every fixed-size image since on some systems
+ * (for example Windows Vista), it takes ages to write a block near the end of a sparse
+ * file and the guest could complain about an ATA timeout. */
+
+ /** @todo Starting with Linux 2.6.23, there is an fallocate() system call.
+ * Currently supported file systems are ext4 and ocfs2. */
+
+ /* Allocate a temporary zero-filled buffer. Use a bigger block size to optimize writing */
+ const size_t cbBuf = 128 * _1K;
+ void *pvBuf = RTMemTmpAllocZ(cbBuf);
+ if (!pvBuf)
+ return VERR_NO_MEMORY;
+
+ uint64_t uOff = 0;
+ /* Write data to all image blocks. */
+ while (uOff < cbExtent)
+ {
+ unsigned cbChunk = (unsigned)RT_MIN(cbExtent, cbBuf);
+
+ rc = vmdkFileWriteAt(pExtent->pFile, uOff, pvBuf, cbChunk, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pvBuf);
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: writing block failed for '%s'"), pImage->pszFilename);
+ }
+
+ uOff += cbChunk;
+
+ if (pfnProgress)
+ {
+ rc = pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uPercentStart + uOff * uPercentSpan / cbExtent,
+ pvUser);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pvBuf);
+ return rc;
+ }
+ }
+ }
+ RTMemTmpFree(pvBuf);
+ }
+
+ /* Place descriptor file information (where integrated). */
+ if (cExtents == 1 && enmType != VD_IMAGE_TYPE_FIXED)
+ {
+ pExtent->uDescriptorSector = 1;
+ pExtent->cDescriptorSectors = VMDK_BYTE2SECTOR(pImage->cbDescAlloc);
+ /* The descriptor is part of the (only) extent. */
+ pExtent->pDescData = pImage->pDescData;
+ pImage->pDescData = NULL;
+ }
+
+ if (enmType == VD_IMAGE_TYPE_NORMAL)
+ {
+ uint64_t cSectorsPerGDE, cSectorsPerGD;
+ pExtent->enmType = VMDKETYPE_HOSTED_SPARSE;
+ pExtent->cSectors = VMDK_BYTE2SECTOR(RT_ALIGN_64(cbExtent, 65536));
+ pExtent->cSectorsPerGrain = VMDK_BYTE2SECTOR(65536);
+ pExtent->cGTEntries = 512;
+ cSectorsPerGDE = pExtent->cGTEntries * pExtent->cSectorsPerGrain;
+ pExtent->cSectorsPerGDE = cSectorsPerGDE;
+ pExtent->cGDEntries = (pExtent->cSectors + cSectorsPerGDE - 1) / cSectorsPerGDE;
+ cSectorsPerGD = (pExtent->cGDEntries + (512 / sizeof(uint32_t) - 1)) / (512 / sizeof(uint32_t));
+ }
+ else
+ pExtent->enmType = VMDKETYPE_FLAT;
+
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fUncleanShutdown = true;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(cbExtent);
+ pExtent->uSectorOffset = VMDK_BYTE2SECTOR(cbOffset);
+ pExtent->fMetaDirty = true;
+
+ if (enmType == VD_IMAGE_TYPE_NORMAL)
+ {
+ rc = vmdkCreateGrainDirectory(pExtent,
+ RT_MAX( pExtent->uDescriptorSector
+ + pExtent->cDescriptorSectors,
+ 1),
+ true);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new grain directory in '%s'"), pExtent->pszFullname);
+ }
+
+ if (RT_SUCCESS(rc) && pfnProgress)
+ pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uPercentStart + i * uPercentSpan / cExtents,
+ pvUser);
+
+ cbRemaining -= cbExtent;
+ cbOffset += cbExtent;
+ }
+
+ const char *pszDescType = NULL;
+ if (enmType == VD_IMAGE_TYPE_FIXED)
+ {
+ pszDescType = (cExtents == 1)
+ ? "monolithicFlat" : "twoGbMaxExtentFlat";
+ }
+ else if (enmType == VD_IMAGE_TYPE_NORMAL)
+ {
+ pszDescType = (cExtents == 1)
+ ? "monolithicSparse" : "twoGbMaxExtentSparse";
+ }
+ else
+ AssertMsgFailed(("invalid image type %d\n", enmType));
+ rc = vmdkDescBaseSetStr(pImage, &pImage->Descriptor, "createType",
+ pszDescType);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not set the image type in '%s'"), pImage->pszFilename);
+ return rc;
+}
+
+/**
+ * Internal: The actual code for creating any VMDK variant currently in
+ * existence on hosted environments.
+ */
+static int vmdkCreateImage(PVMDKIMAGE pImage, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ const char *pszComment,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry, PCRTUUID pUuid,
+ PFNVMPROGRESS pfnProgress, void *pvUser,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ int rc;
+
+ pImage->uImageFlags = uImageFlags;
+
+ /* Try to get error interface. */
+ pImage->pInterfaceError = VDInterfaceGet(pImage->pVDIfsDisk, VDINTERFACETYPE_ERROR);
+ if (pImage->pInterfaceError)
+ pImage->pInterfaceErrorCallbacks = VDGetInterfaceError(pImage->pInterfaceError);
+
+ /* Try to get async I/O interface. */
+ pImage->pInterfaceAsyncIO = VDInterfaceGet(pImage->pVDIfsDisk, VDINTERFACETYPE_ASYNCIO);
+ if (pImage->pInterfaceAsyncIO)
+ pImage->pInterfaceAsyncIOCallbacks = VDGetInterfaceAsyncIO(pImage->pInterfaceAsyncIO);
+
+ rc = vmdkCreateDescriptor(pImage, pImage->pDescData, pImage->cbDescAlloc,
+ &pImage->Descriptor);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ if ( enmType == VD_IMAGE_TYPE_FIXED
+ && (uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK))
+ {
+ /* Raw disk image (includes raw partition). */
+ const PVBOXHDDRAW pRaw = (const PVBOXHDDRAW)pszComment;
+ /* As the comment is misused, zap it so that no garbage comment
+ * is set below. */
+ pszComment = NULL;
+ rc = vmdkCreateRawImage(pImage, pRaw, cbSize);
+ }
+ else if ( enmType == VD_IMAGE_TYPE_FIXED
+ || enmType == VD_IMAGE_TYPE_NORMAL)
+ {
+ /* Regular fixed or sparse image (monolithic or split). */
+ rc = vmdkCreateRegularImage(pImage, enmType, cbSize, uImageFlags,
+ pfnProgress, pvUser, uPercentStart,
+ uPercentSpan * 95 / 100);
+ }
+ else
+ {
+ /* Unknown/invalid image type. */
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+
+ if (RT_FAILURE(rc))
+ goto out;
+
+ if (RT_SUCCESS(rc) && pfnProgress)
+ pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uPercentStart + uPercentSpan * 98 / 100, pvUser);
+
+ pImage->enmImageType = enmType;
+ pImage->cbSize = cbSize;
+
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[i];
+
+ rc = vmdkDescExtInsert(pImage, &pImage->Descriptor, pExtent->enmAccess,
+ pExtent->cNominalSectors, pExtent->enmType,
+ pExtent->pszBasename, pExtent->uSectorOffset);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not insert the extent list into descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ }
+ vmdkDescExtRemoveDummy(pImage, &pImage->Descriptor);
+
+ if ( pPCHSGeometry->cCylinders != 0
+ && pPCHSGeometry->cHeads != 0
+ && pPCHSGeometry->cSectors != 0)
+ {
+ rc = vmdkDescSetPCHSGeometry(pImage, pPCHSGeometry);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+ if ( pLCHSGeometry->cCylinders != 0
+ && pLCHSGeometry->cHeads != 0
+ && pLCHSGeometry->cSectors != 0)
+ {
+ rc = vmdkDescSetLCHSGeometry(pImage, pLCHSGeometry);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ pImage->PCHSGeometry = *pPCHSGeometry;
+
+ pImage->ImageUuid = *pUuid;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_IMAGE_UUID, &pImage->ImageUuid);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image UUID in new descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ RTUuidClear(&pImage->ParentUuid);
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_UUID, &pImage->ParentUuid);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent image UUID in new descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ RTUuidClear(&pImage->ModificationUuid);
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_MODIFICATION_UUID,
+ &pImage->ModificationUuid);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing modification UUID in new descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+ RTUuidClear(&pImage->ParentModificationUuid);
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_MODIFICATION_UUID,
+ &pImage->ParentModificationUuid);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent modification UUID in new descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ rc = vmdkAllocateGrainTableCache(pImage);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ rc = vmdkSetImageComment(pImage, pszComment);
+ if (RT_FAILURE(rc))
+ {
+ rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: cannot set image comment in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+
+ if (RT_SUCCESS(rc) && pfnProgress)
+ pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uPercentStart + uPercentSpan * 99 / 100, pvUser);
+
+ rc = vmdkFlushImage(pImage);
+
+out:
+ if (RT_SUCCESS(rc) && pfnProgress)
+ pfnProgress(NULL /* WARNING! pVM=NULL */,
+ uPercentStart + uPercentSpan, pvUser);
+
+ if (RT_FAILURE(rc))
+ vmdkFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+/**
+ * Internal: Update image comment.
+ */
+static int vmdkSetImageComment(PVMDKIMAGE pImage, const char *pszComment)
+{
+ char *pszCommentEncoded;
+ if (pszComment)
+ {
+ pszCommentEncoded = vmdkEncodeString(pszComment);
+ if (!pszCommentEncoded)
+ return VERR_NO_MEMORY;
+ }
+ else
+ pszCommentEncoded = NULL;
+ int rc = vmdkDescDDBSetStr(pImage, &pImage->Descriptor,
+ "ddb.comment", pszCommentEncoded);
+ if (pszComment)
+ RTStrFree(pszCommentEncoded);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image comment in descriptor in '%s'"), pImage->pszFilename);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image, and optionally
+ * delete the image from disk.
+ */
+static void vmdkFreeImage(PVMDKIMAGE pImage, bool fDelete)
+{
+ AssertPtr(pImage);
+
+ if (pImage->enmImageType)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* Mark all extents as clean. */
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ if (( pImage->pExtents[i].enmType == VMDKETYPE_HOSTED_SPARSE
+#ifdef VBOX_WITH_VMDK_ESX
+ || pImage->pExtents[i].enmType == VMDKETYPE_ESX_SPARSE
+#endif /* VBOX_WITH_VMDK_ESX */
+ )
+ && pImage->pExtents[i].fUncleanShutdown)
+ {
+ pImage->pExtents[i].fUncleanShutdown = false;
+ pImage->pExtents[i].fMetaDirty = true;
+ }
+ }
+ }
+ (void)vmdkFlushImage(pImage);
+ }
+ if (pImage->pExtents != NULL)
+ {
+ for (unsigned i = 0 ; i < pImage->cExtents; i++)
+ vmdkFreeExtentData(pImage, &pImage->pExtents[i], fDelete);
+ RTMemFree(pImage->pExtents);
+ pImage->pExtents = NULL;
+ }
+ pImage->cExtents = 0;
+ if (pImage->pFile != NULL)
+ vmdkFileClose(pImage, &pImage->pFile, fDelete);
+ vmdkFileCheckAllClose(pImage);
+ if (pImage->pGTCache)
+ {
+ RTMemFree(pImage->pGTCache);
+ pImage->pGTCache = NULL;
+ }
+ if (pImage->pDescData)
+ {
+ RTMemFree(pImage->pDescData);
+ pImage->pDescData = NULL;
+ }
+}
+
+/**
+ * Internal. Flush image data (and metadata) to disk.
+ */
+static int vmdkFlushImage(PVMDKIMAGE pImage)
+{
+ PVMDKEXTENT pExtent;
+ int rc = VINF_SUCCESS;
+
+ /* Update descriptor if changed. */
+ if (pImage->Descriptor.fDirty)
+ {
+ rc = vmdkWriteDescriptor(pImage);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ pExtent = &pImage->pExtents[i];
+ if (pExtent->pFile != NULL && pExtent->fMetaDirty)
+ {
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_HOSTED_SPARSE:
+ rc = vmdkWriteMetaSparseExtent(pExtent);
+ if (RT_FAILURE(rc))
+ goto out;
+ break;
+#ifdef VBOX_WITH_VMDK_ESX
+ case VMDKETYPE_ESX_SPARSE:
+ /** @todo update the header. */
+ break;
+#endif /* VBOX_WITH_VMDK_ESX */
+ case VMDKETYPE_FLAT:
+ /* Nothing to do. */
+ break;
+ case VMDKETYPE_ZERO:
+ default:
+ AssertMsgFailed(("extent with type %d marked as dirty\n",
+ pExtent->enmType));
+ break;
+ }
+ }
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_HOSTED_SPARSE:
+#ifdef VBOX_WITH_VMDK_ESX
+ case VMDKETYPE_ESX_SPARSE:
+#endif /* VBOX_WITH_VMDK_ESX */
+ case VMDKETYPE_FLAT:
+ /** @todo implement proper path absolute check. */
+ if ( pExtent->pFile != NULL
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && !(pExtent->pszBasename[0] == RTPATH_SLASH))
+ rc = vmdkFileFlush(pExtent->pFile);
+ break;
+ case VMDKETYPE_ZERO:
+ /* No need to do anything for this extent. */
+ break;
+ default:
+ AssertMsgFailed(("unknown extent type %d\n", pExtent->enmType));
+ break;
+ }
+ }
+
+out:
+ return rc;
+}
+
+/**
+ * Internal. Find extent corresponding to the sector number in the disk.
+ */
+static int vmdkFindExtent(PVMDKIMAGE pImage, uint64_t offSector,
+ PVMDKEXTENT *ppExtent, uint64_t *puSectorInExtent)
+{
+ PVMDKEXTENT pExtent = NULL;
+ int rc = VINF_SUCCESS;
+
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ if (offSector < pImage->pExtents[i].cNominalSectors)
+ {
+ pExtent = &pImage->pExtents[i];
+ *puSectorInExtent = offSector + pImage->pExtents[i].uSectorOffset;
+ break;
+ }
+ offSector -= pImage->pExtents[i].cNominalSectors;
+ }
+
+ if (pExtent)
+ *ppExtent = pExtent;
+ else
+ rc = VERR_IO_SECTOR_NOT_FOUND;
+
+ return rc;
+}
+
+/**
+ * Internal. Hash function for placing the grain table hash entries.
+ */
+static uint32_t vmdkGTCacheHash(PVMDKGTCACHE pCache, uint64_t uSector,
+ unsigned uExtent)
+{
+ /** @todo this hash function is quite simple, maybe use a better one which
+ * scrambles the bits better. */
+ return (uSector + uExtent) % pCache->cEntries;
+}
+
+/**
+ * Internal. Get sector number in the extent file from the relative sector
+ * number in the extent.
+ */
+static int vmdkGetSector(PVMDKGTCACHE pCache, PVMDKEXTENT pExtent,
+ uint64_t uSector, uint64_t *puExtentSector)
+{
+ uint64_t uGDIndex, uGTSector, uGTBlock;
+ uint32_t uGTHash, uGTBlockIndex;
+ PVMDKGTCACHEENTRY pGTCacheEntry;
+ uint32_t aGTDataTmp[VMDK_GT_CACHELINE_SIZE];
+ int rc;
+
+ uGDIndex = uSector / pExtent->cSectorsPerGDE;
+ if (uGDIndex >= pExtent->cGDEntries)
+ return VERR_OUT_OF_RANGE;
+ uGTSector = pExtent->pGD[uGDIndex];
+ if (!uGTSector)
+ {
+ /* There is no grain table referenced by this grain directory
+ * entry. So there is absolutely no data in this area. */
+ *puExtentSector = 0;
+ return VINF_SUCCESS;
+ }
+
+ uGTBlock = uSector / (pExtent->cSectorsPerGrain * VMDK_GT_CACHELINE_SIZE);
+ uGTHash = vmdkGTCacheHash(pCache, uGTBlock, pExtent->uExtent);
+ pGTCacheEntry = &pCache->aGTCache[uGTHash];
+ if ( pGTCacheEntry->uExtent != pExtent->uExtent
+ || pGTCacheEntry->uGTBlock != uGTBlock)
+ {
+ /* Cache miss, fetch data from disk. */
+ rc = vmdkFileReadAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+ aGTDataTmp, sizeof(aGTDataTmp), NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot read grain table entry in '%s'"), pExtent->pszFullname);
+ pGTCacheEntry->uExtent = pExtent->uExtent;
+ pGTCacheEntry->uGTBlock = uGTBlock;
+ for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
+ pGTCacheEntry->aGTData[i] = RT_LE2H_U32(aGTDataTmp[i]);
+ }
+ uGTBlockIndex = (uSector / pExtent->cSectorsPerGrain) % VMDK_GT_CACHELINE_SIZE;
+ uint64_t uGrainSector = pGTCacheEntry->aGTData[uGTBlockIndex];
+ if (uGrainSector)
+ *puExtentSector = uGrainSector + uSector % pExtent->cSectorsPerGrain;
+ else
+ *puExtentSector = 0;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal. Allocates a new grain table (if necessary), writes the grain
+ * and updates the grain table. The cache is also updated by this operation.
+ * This is separate from vmdkGetSector, because that should be as fast as
+ * possible. Most code from vmdkGetSector also appears here.
+ */
+static int vmdkAllocGrain(PVMDKGTCACHE pCache, PVMDKEXTENT pExtent,
+ uint64_t uSector, const void *pvBuf,
+ uint64_t cbWrite)
+{
+ uint64_t uGDIndex, uGTSector, uRGTSector, uGTBlock;
+ uint64_t cbExtentSize;
+ uint32_t uGTHash, uGTBlockIndex;
+ PVMDKGTCACHEENTRY pGTCacheEntry;
+ uint32_t aGTDataTmp[VMDK_GT_CACHELINE_SIZE];
+ int rc;
+
+ uGDIndex = uSector / pExtent->cSectorsPerGDE;
+ if (uGDIndex >= pExtent->cGDEntries)
+ return VERR_OUT_OF_RANGE;
+ uGTSector = pExtent->pGD[uGDIndex];
+ if (pExtent->pRGD)
+ uRGTSector = pExtent->pRGD[uGDIndex];
+ else
+ uRGTSector = 0; /**< avoid compiler warning */
+ if (!uGTSector)
+ {
+ /* There is no grain table referenced by this grain directory
+ * entry. So there is absolutely no data in this area. Allocate
+ * a new grain table and put the reference to it in the GDs. */
+ rc = vmdkFileGetSize(pExtent->pFile, &cbExtentSize);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size in '%s'"), pExtent->pszFullname);
+ Assert(!(cbExtentSize % 512));
+ cbExtentSize = RT_ALIGN_64(cbExtentSize, 512);
+ uGTSector = VMDK_BYTE2SECTOR(cbExtentSize);
+ /* Normally the grain table is preallocated for hosted sparse extents
+ * that support more than 32 bit sector numbers. So this shouldn't
+ * ever happen on a valid extent. */
+ if (uGTSector > UINT32_MAX)
+ return VERR_VD_VMDK_INVALID_HEADER;
+ /* Write grain table by writing the required number of grain table
+ * cache chunks. Avoids dynamic memory allocation, but is a bit
+ * slower. But as this is a pretty infrequently occurring case it
+ * should be acceptable. */
+ memset(aGTDataTmp, '\0', sizeof(aGTDataTmp));
+ for (unsigned i = 0;
+ i < pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE;
+ i++)
+ {
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uGTSector) + i * sizeof(aGTDataTmp),
+ aGTDataTmp, sizeof(aGTDataTmp), NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write grain table allocation in '%s'"), pExtent->pszFullname);
+ }
+ if (pExtent->pRGD)
+ {
+ AssertReturn(!uRGTSector, VERR_VD_VMDK_INVALID_HEADER);
+ rc = vmdkFileGetSize(pExtent->pFile, &cbExtentSize);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size in '%s'"), pExtent->pszFullname);
+ Assert(!(cbExtentSize % 512));
+ uRGTSector = VMDK_BYTE2SECTOR(cbExtentSize);
+ /* Normally the redundant grain table is preallocated for hosted
+ * sparse extents that support more than 32 bit sector numbers. So
+ * this shouldn't ever happen on a valid extent. */
+ if (uRGTSector > UINT32_MAX)
+ return VERR_VD_VMDK_INVALID_HEADER;
+ /* Write backup grain table by writing the required number of grain
+ * table cache chunks. Avoids dynamic memory allocation, but is a
+ * bit slower. But as this is a pretty infrequently occurring case
+ * it should be acceptable. */
+ for (unsigned i = 0;
+ i < pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE;
+ i++)
+ {
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uRGTSector) + i * sizeof(aGTDataTmp),
+ aGTDataTmp, sizeof(aGTDataTmp), NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write backup grain table allocation in '%s'"), pExtent->pszFullname);
+ }
+ }
+
+ /* Update the grain directory on disk (doing it before writing the
+ * grain table will result in a garbled extent if the operation is
+ * aborted for some reason. Otherwise the worst that can happen is
+ * some unused sectors in the extent. */
+ uint32_t uGTSectorLE = RT_H2LE_U64(uGTSector);
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(pExtent->uSectorGD) + uGDIndex * sizeof(uGTSectorLE),
+ &uGTSectorLE, sizeof(uGTSectorLE), NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write grain directory entry in '%s'"), pExtent->pszFullname);
+ if (pExtent->pRGD)
+ {
+ uint32_t uRGTSectorLE = RT_H2LE_U64(uRGTSector);
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(pExtent->uSectorRGD) + uGDIndex * sizeof(uRGTSectorLE),
+ &uRGTSectorLE, sizeof(uRGTSectorLE), NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write backup grain directory entry in '%s'"), pExtent->pszFullname);
+ }
+
+ /* As the final step update the in-memory copy of the GDs. */
+ pExtent->pGD[uGDIndex] = uGTSector;
+ if (pExtent->pRGD)
+ pExtent->pRGD[uGDIndex] = uRGTSector;
+ }
+
+ rc = vmdkFileGetSize(pExtent->pFile, &cbExtentSize);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size in '%s'"), pExtent->pszFullname);
+ Assert(!(cbExtentSize % 512));
+
+ /* Write the data. */
+ rc = vmdkFileWriteAt(pExtent->pFile, cbExtentSize, pvBuf, cbWrite, NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write allocated data block in '%s'"), pExtent->pszFullname);
+
+ /* Update the grain table (and the cache). */
+ uGTBlock = uSector / (pExtent->cSectorsPerGrain * VMDK_GT_CACHELINE_SIZE);
+ uGTHash = vmdkGTCacheHash(pCache, uGTBlock, pExtent->uExtent);
+ pGTCacheEntry = &pCache->aGTCache[uGTHash];
+ if ( pGTCacheEntry->uExtent != pExtent->uExtent
+ || pGTCacheEntry->uGTBlock != uGTBlock)
+ {
+ /* Cache miss, fetch data from disk. */
+ rc = vmdkFileReadAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+ aGTDataTmp, sizeof(aGTDataTmp), NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot read allocated grain table entry in '%s'"), pExtent->pszFullname);
+ pGTCacheEntry->uExtent = pExtent->uExtent;
+ pGTCacheEntry->uGTBlock = uGTBlock;
+ for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
+ pGTCacheEntry->aGTData[i] = RT_LE2H_U32(aGTDataTmp[i]);
+ }
+ else
+ {
+ /* Cache hit. Convert grain table block back to disk format, otherwise
+ * the code below will write garbage for all but the updated entry. */
+ for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
+ aGTDataTmp[i] = RT_H2LE_U32(pGTCacheEntry->aGTData[i]);
+ }
+ uGTBlockIndex = (uSector / pExtent->cSectorsPerGrain) % VMDK_GT_CACHELINE_SIZE;
+ aGTDataTmp[uGTBlockIndex] = RT_H2LE_U32(VMDK_BYTE2SECTOR(cbExtentSize));
+ pGTCacheEntry->aGTData[uGTBlockIndex] = VMDK_BYTE2SECTOR(cbExtentSize);
+ /* Update grain table on disk. */
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+ aGTDataTmp, sizeof(aGTDataTmp), NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write updated grain table in '%s'"), pExtent->pszFullname);
+ if (pExtent->pRGD)
+ {
+ /* Update backup grain table on disk. */
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uRGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+ aGTDataTmp, sizeof(aGTDataTmp), NULL);
+ if (RT_FAILURE(rc))
+ return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write updated backup grain table in '%s'"), pExtent->pszFullname);
+ }
+#ifdef VBOX_WITH_VMDK_ESX
+ if (RT_SUCCESS(rc) && pExtent->enmType == VMDKETYPE_ESX_SPARSE)
+ {
+ pExtent->uFreeSector = uGTSector + VMDK_BYTE2SECTOR(cbWrite);
+ pExtent->fMetaDirty = true;
+ }
+#endif /* VBOX_WITH_VMDK_ESX */
+ return rc;
+}
+
+
+/** @copydoc VBOXHDDBACKEND::pfnCheckIfValid */
+static int vmdkCheckIfValid(const char *pszFilename)
+{
+ LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename));
+ int rc = VINF_SUCCESS;
+ PVMDKIMAGE pImage;
+
+ if ( !pszFilename
+ || !*pszFilename
+ || strchr(pszFilename, '"'))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ pImage = (PVMDKIMAGE)RTMemAllocZ(sizeof(VMDKIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pImage->pszFilename = pszFilename;
+ pImage->pFile = NULL;
+ pImage->pExtents = NULL;
+ pImage->pFiles = NULL;
+ pImage->pGTCache = NULL;
+ pImage->pDescData = NULL;
+ pImage->pVDIfsDisk = NULL;
+ /** @todo speed up this test open (VD_OPEN_FLAGS_INFO) by skipping as
+ * much as possible in vmdkOpenImage. */
+ rc = vmdkOpenImage(pImage, VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_READONLY);
+ vmdkFreeImage(pImage, false);
+ RTMemFree(pImage);
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnOpen */
+static int vmdkOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p ppBackendData=%#p\n", pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, ppBackendData));
+ int rc;
+ PVMDKIMAGE pImage;
+
+ /* Check open flags. All valid flags are supported. */
+ if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Check remaining arguments. */
+ if ( !VALID_PTR(pszFilename)
+ || !*pszFilename
+ || strchr(pszFilename, '"'))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+
+ pImage = (PVMDKIMAGE)RTMemAllocZ(sizeof(VMDKIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pImage->pszFilename = pszFilename;
+ pImage->pFile = NULL;
+ pImage->pExtents = NULL;
+ pImage->pFiles = NULL;
+ pImage->pGTCache = NULL;
+ pImage->pDescData = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+
+ rc = vmdkOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+
+out:
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnCreate */
+static int vmdkCreate(const char *pszFilename, VDIMAGETYPE enmType,
+ uint64_t cbSize, unsigned uImageFlags,
+ const char *pszComment,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry, PCRTUUID pUuid,
+ unsigned uOpenFlags, unsigned uPercentStart,
+ unsigned uPercentSpan, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, PVDINTERFACE pVDIfsOperation,
+ void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" enmType=%d cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p ppBackendData=%#p", pszFilename, enmType, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, ppBackendData));
+ int rc;
+ PVMDKIMAGE pImage;
+
+ PFNVMPROGRESS pfnProgress = NULL;
+ void *pvUser = NULL;
+ PVDINTERFACE pIfProgress = VDInterfaceGet(pVDIfsOperation,
+ VDINTERFACETYPE_PROGRESS);
+ PVDINTERFACEPROGRESS pCbProgress = NULL;
+ if (pIfProgress)
+ {
+ pCbProgress = VDGetInterfaceProgress(pIfProgress);
+ pfnProgress = pCbProgress->pfnProgress;
+ pvUser = pIfProgress->pvUser;
+ }
+
+ /* Check open flags. All valid flags are supported. */
+ if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* @todo A quick hack to support differencing images in VMDK. */
+ if (enmType == VD_IMAGE_TYPE_DIFF)
+ enmType = VD_IMAGE_TYPE_NORMAL;
+
+ /* Check remaining arguments. */
+ if ( !VALID_PTR(pszFilename)
+ || !*pszFilename
+ || strchr(pszFilename, '"')
+ || (enmType != VD_IMAGE_TYPE_NORMAL && enmType != VD_IMAGE_TYPE_FIXED)
+ || !VALID_PTR(pPCHSGeometry)
+ || !VALID_PTR(pLCHSGeometry))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ pImage = (PVMDKIMAGE)RTMemAllocZ(sizeof(VMDKIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pImage->pszFilename = pszFilename;
+ pImage->pFile = NULL;
+ pImage->pExtents = NULL;
+ pImage->pFiles = NULL;
+ pImage->pGTCache = NULL;
+ pImage->pDescData = NULL;
+ pImage->pVDIfsDisk = NULL;
+ pImage->cbDescAlloc = VMDK_SECTOR2BYTE(20);
+ pImage->pDescData = (char *)RTMemAllocZ(pImage->cbDescAlloc);
+ if (!pImage->pDescData)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ rc = vmdkCreateImage(pImage, enmType, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, pUuid,
+ pfnProgress, pvUser, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ vmdkFreeImage(pImage, false);
+ rc = vmdkOpenImage(pImage, uOpenFlags);
+ if (RT_FAILURE(rc))
+ goto out;
+ }
+ *ppBackendData = pImage;
+ }
+ else
+ {
+ RTMemFree(pImage->pDescData);
+ RTMemFree(pImage);
+ }
+
+out:
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/**
+ * Replaces a fragment of a string with the specified string.
+ *
+ * @returns Pointer to the allocated UTF-8 string.
+ * @param pszWhere UTF-8 string to search in.
+ * @param pszWhat UTF-8 string to search for.
+ * @param pszByWhat UTF-8 string to replace the found string with.
+ */
+static char * vmdkStrReplace(const char *pszWhere, const char *pszWhat, const char *pszByWhat)
+{
+ AssertPtr(pszWhere);
+ AssertPtr(pszWhat);
+ AssertPtr(pszByWhat);
+ const char *pszFoundStr = strstr(pszWhere, pszWhat);
+ if (!pszFoundStr)
+ return NULL;
+ size_t cFinal = strlen(pszWhere) + 1 + strlen(pszByWhat) - strlen(pszWhat);
+ char *pszNewStr = (char *)RTMemAlloc(cFinal);
+ if (pszNewStr)
+ {
+ char *pszTmp = pszNewStr;
+ memcpy(pszTmp, pszWhere, pszFoundStr - pszWhere);
+ pszTmp += pszFoundStr - pszWhere;
+ memcpy(pszTmp, pszByWhat, strlen(pszByWhat));
+ pszTmp += strlen(pszByWhat);
+ strcpy(pszTmp, pszFoundStr + strlen(pszWhat));
+ }
+ return pszNewStr;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnRename */
+static int vmdkRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+ char **apszOldName = NULL;
+ char **apszNewName = NULL;
+ char **apszNewLines = NULL;
+ char *pszOldDescName = NULL;
+ bool fImageFreed = false;
+ bool fEmbeddedDesc = false;
+ unsigned cExtents = pImage->cExtents;
+ char *pszNewBaseName = NULL;
+ char *pszOldBaseName = NULL;
+ char *pszNewFullName = NULL;
+ char *pszOldFullName = NULL;
+ const char *pszOldImageName;
+ unsigned i, line;
+ VMDKDESCRIPTOR DescriptorCopy;
+ VMDKEXTENT ExtentCopy;
+
+ memset(&DescriptorCopy, 0, sizeof(DescriptorCopy));
+
+ /* Check arguments. */
+ if ( !pImage
+ || (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK)
+ || !VALID_PTR(pszFilename)
+ || !*pszFilename)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /*
+ * Allocate an array to store both old and new names of renamed files
+ * in case we have to roll back the changes. Arrays are initialized
+ * with zeros. We actually save stuff when and if we change it.
+ */
+ apszOldName = (char **)RTMemTmpAllocZ((cExtents + 1) * sizeof(char*));
+ apszNewName = (char **)RTMemTmpAllocZ((cExtents + 1) * sizeof(char*));
+ apszNewLines = (char **)RTMemTmpAllocZ((cExtents) * sizeof(char*));
+ if (!apszOldName || !apszNewName || !apszNewLines)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ /* Save the descriptor size and position. */
+ if (pImage->pDescData)
+ {
+ /* Separate descriptor file. */
+ fEmbeddedDesc = false;
+ }
+ else
+ {
+ /* Embedded descriptor file. */
+ ExtentCopy = pImage->pExtents[0];
+ fEmbeddedDesc = true;
+ }
+ /* Save the descriptor content. */
+ DescriptorCopy.cLines = pImage->Descriptor.cLines;
+ for (i = 0; i < DescriptorCopy.cLines; i++)
+ {
+ DescriptorCopy.aLines[i] = RTStrDup(pImage->Descriptor.aLines[i]);
+ if (!DescriptorCopy.aLines[i])
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ /* Prepare both old and new base names used for string replacement. */
+ pszNewBaseName = RTStrDup(RTPathFilename(pszFilename));
+ RTPathStripExt(pszNewBaseName);
+ pszOldBaseName = RTStrDup(RTPathFilename(pImage->pszFilename));
+ RTPathStripExt(pszOldBaseName);
+ /* Prepare both old and new full names used for string replacement. */
+ pszNewFullName = RTStrDup(pszFilename);
+ RTPathStripExt(pszNewFullName);
+ pszOldFullName = RTStrDup(pImage->pszFilename);
+ RTPathStripExt(pszOldFullName);
+
+ /* --- Up to this point we have not done any damage yet. --- */
+
+ /* Save the old name for easy access to the old descriptor file. */
+ pszOldDescName = RTStrDup(pImage->pszFilename);
+ /* Save old image name. */
+ pszOldImageName = pImage->pszFilename;
+
+ /* Update the descriptor with modified extent names. */
+ for (i = 0, line = pImage->Descriptor.uFirstExtent;
+ i < cExtents;
+ i++, line = pImage->Descriptor.aNextLines[line])
+ {
+ /* Assume that vmdkStrReplace will fail. */
+ rc = VERR_NO_MEMORY;
+ /* Update the descriptor. */
+ apszNewLines[i] = vmdkStrReplace(pImage->Descriptor.aLines[line],
+ pszOldBaseName, pszNewBaseName);
+ if (!apszNewLines[i])
+ goto rollback;
+ pImage->Descriptor.aLines[line] = apszNewLines[i];
+ }
+ /* Make sure the descriptor gets written back. */
+ pImage->Descriptor.fDirty = true;
+ /* Flush the descriptor now, in case it is embedded. */
+ vmdkFlushImage(pImage);
+
+ /* Close and rename/move extents. */
+ for (i = 0; i < cExtents; i++)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[i];
+ /* Compose new name for the extent. */
+ apszNewName[i] = vmdkStrReplace(pExtent->pszFullname,
+ pszOldFullName, pszNewFullName);
+ if (!apszNewName[i])
+ goto rollback;
+ /* Close the extent file. */
+ vmdkFileClose(pImage, &pExtent->pFile, false);
+ /* Rename the extent file. */
+ rc = RTFileMove(pExtent->pszFullname, apszNewName[i], 0);
+ if (RT_FAILURE(rc))
+ goto rollback;
+ /* Remember the old name. */
+ apszOldName[i] = RTStrDup(pExtent->pszFullname);
+ }
+ /* Release all old stuff. */
+ vmdkFreeImage(pImage, false);
+
+ fImageFreed = true;
+
+ /* Last elements of new/old name arrays are intended for
+ * storing descriptor's names.
+ */
+ apszNewName[cExtents] = RTStrDup(pszFilename);
+ /* Rename the descriptor file if it's separate. */
+ if (!fEmbeddedDesc)
+ {
+ rc = RTFileMove(pImage->pszFilename, apszNewName[cExtents], 0);
+ if (RT_FAILURE(rc))
+ goto rollback;
+ /* Save old name only if we may need to change it back. */
+ apszOldName[cExtents] = RTStrDup(pszFilename);
+ }
+
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the new image. */
+ rc = vmdkOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_SUCCESS(rc))
+ goto out;
+
+rollback:
+ /* Roll back all changes in case of failure. */
+ if (RT_FAILURE(rc))
+ {
+ int rrc;
+ if (!fImageFreed)
+ {
+ /*
+ * Some extents may have been closed, close the rest. We will
+ * re-open the whole thing later.
+ */
+ vmdkFreeImage(pImage, false);
+ }
+ /* Rename files back. */
+ for (i = 0; i <= cExtents; i++)
+ {
+ if (apszOldName[i])
+ {
+ rrc = RTFileMove(apszNewName[i], apszOldName[i], 0);
+ AssertRC(rrc);
+ }
+ }
+ /* Restore the old descriptor. */
+ PVMDKFILE pFile;
+ rrc = vmdkFileOpen(pImage, &pFile, pszOldDescName,
+ RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, false);
+ AssertRC(rrc);
+ if (fEmbeddedDesc)
+ {
+ ExtentCopy.pFile = pFile;
+ pImage->pExtents = &ExtentCopy;
+ }
+ else
+ {
+ /* Shouldn't be null for separate descriptor.
+ * There will be no access to the actual content.
+ */
+ pImage->pDescData = pszOldDescName;
+ pImage->pFile = pFile;
+ }
+ pImage->Descriptor = DescriptorCopy;
+ vmdkWriteDescriptor(pImage);
+ vmdkFileClose(pImage, &pFile, false);
+ /* Get rid of the stuff we implanted. */
+ pImage->pExtents = NULL;
+ pImage->pFile = NULL;
+ pImage->pDescData = NULL;
+ /* Re-open the image back. */
+ pImage->pszFilename = pszOldImageName;
+ rrc = vmdkOpenImage(pImage, pImage->uOpenFlags);
+ AssertRC(rrc);
+ }
+
+out:
+ for (i = 0; i < DescriptorCopy.cLines; i++)
+ if (DescriptorCopy.aLines[i])
+ RTStrFree(DescriptorCopy.aLines[i]);
+ if (apszOldName)
+ {
+ for (i = 0; i <= cExtents; i++)
+ if (apszOldName[i])
+ RTStrFree(apszOldName[i]);
+ RTMemTmpFree(apszOldName);
+ }
+ if (apszNewName)
+ {
+ for (i = 0; i <= cExtents; i++)
+ if (apszNewName[i])
+ RTStrFree(apszNewName[i]);
+ RTMemTmpFree(apszNewName);
+ }
+ if (apszNewLines)
+ {
+ for (i = 0; i < cExtents; i++)
+ if (apszNewLines[i])
+ RTStrFree(apszNewLines[i]);
+ RTMemTmpFree(apszNewLines);
+ }
+ if (pszOldDescName)
+ RTStrFree(pszOldDescName);
+ if (pszOldBaseName)
+ RTStrFree(pszOldBaseName);
+ if (pszNewBaseName)
+ RTStrFree(pszNewBaseName);
+ if (pszOldFullName)
+ RTStrFree(pszOldFullName);
+ if (pszNewFullName)
+ RTStrFree(pszNewFullName);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnClose */
+static int vmdkClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ vmdkFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnRead */
+static int vmdkRead(void *pBackendData, uint64_t uOffset, void *pvBuf,
+ size_t cbToRead, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pvBuf=%#p cbToRead=%zu pcbActuallyRead=%#p\n", pBackendData, uOffset, pvBuf, cbToRead, pcbActuallyRead));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ PVMDKEXTENT pExtent;
+ uint64_t uSectorExtentRel;
+ uint64_t uSectorExtentAbs;
+ int rc;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToRead % 512 == 0);
+
+ if ( uOffset + cbToRead > pImage->cbSize
+ || cbToRead == 0)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ rc = vmdkFindExtent(pImage, VMDK_BYTE2SECTOR(uOffset),
+ &pExtent, &uSectorExtentRel);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ /* Check access permissions as defined in the extent descriptor. */
+ if (pExtent->enmAccess == VMDKACCESS_NOACCESS)
+ {
+ rc = VERR_VD_VMDK_INVALID_STATE;
+ goto out;
+ }
+
+ /* Clip read range to remain in this extent. */
+ cbToRead = RT_MIN(cbToRead, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
+
+ /* Handle the read according to the current extent type. */
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_HOSTED_SPARSE:
+#ifdef VBOX_WITH_VMDK_ESX
+ case VMDKETYPE_ESX_SPARSE:
+#endif /* VBOX_WITH_VMDK_ESX */
+ rc = vmdkGetSector(pImage->pGTCache, pExtent, uSectorExtentRel,
+ &uSectorExtentAbs);
+ if (RT_FAILURE(rc))
+ goto out;
+ /* Clip read range to at most the rest of the grain. */
+ cbToRead = RT_MIN(cbToRead, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - uSectorExtentRel % pExtent->cSectorsPerGrain));
+ Assert(!(cbToRead % 512));
+ if (uSectorExtentAbs == 0)
+ rc = VERR_VD_BLOCK_FREE;
+ else
+ rc = vmdkFileReadAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uSectorExtentAbs),
+ pvBuf, cbToRead, NULL);
+ break;
+ case VMDKETYPE_FLAT:
+ rc = vmdkFileReadAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uSectorExtentRel),
+ pvBuf, cbToRead, NULL);
+ break;
+ case VMDKETYPE_ZERO:
+ memset(pvBuf, '\0', cbToRead);
+ break;
+ }
+ if (pcbActuallyRead)
+ *pcbActuallyRead = cbToRead;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnWrite */
+static int vmdkWrite(void *pBackendData, uint64_t uOffset, const void *pvBuf,
+ size_t cbToWrite, size_t *pcbWriteProcess,
+ size_t *pcbPreRead, size_t *pcbPostRead, unsigned fWrite)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pvBuf=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n", pBackendData, uOffset, pvBuf, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ PVMDKEXTENT pExtent;
+ uint64_t uSectorExtentRel;
+ uint64_t uSectorExtentAbs;
+ int rc;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToWrite % 512 == 0);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ if (cbToWrite == 0)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* No size check here, will do that later when the extent is located.
+ * There are sparse images out there which according to the spec are
+ * invalid, because the total size is not a multiple of the grain size.
+ * Also for sparse images which are stitched together in odd ways (not at
+ * grain boundaries, and with the nominal size not being a multiple of the
+ * grain size), this would prevent writing to the last grain. */
+
+ rc = vmdkFindExtent(pImage, VMDK_BYTE2SECTOR(uOffset),
+ &pExtent, &uSectorExtentRel);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ /* Check access permissions as defined in the extent descriptor. */
+ if (pExtent->enmAccess != VMDKACCESS_READWRITE)
+ {
+ rc = VERR_VD_VMDK_INVALID_STATE;
+ goto out;
+ }
+
+ /* Handle the write according to the current extent type. */
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_HOSTED_SPARSE:
+#ifdef VBOX_WITH_VMDK_ESX
+ case VMDKETYPE_ESX_SPARSE:
+#endif /* VBOX_WITH_VMDK_ESX */
+ rc = vmdkGetSector(pImage->pGTCache, pExtent, uSectorExtentRel,
+ &uSectorExtentAbs);
+ if (RT_FAILURE(rc))
+ goto out;
+ /* Clip write range to at most the rest of the grain. */
+ cbToWrite = RT_MIN(cbToWrite, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - uSectorExtentRel % pExtent->cSectorsPerGrain));
+ if (uSectorExtentAbs == 0)
+ {
+ if (cbToWrite == VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain))
+ {
+ /* Full block write to a previously unallocated block.
+ * Check if the caller wants to avoid this. */
+ if (!(fWrite & VD_WRITE_NO_ALLOC))
+ {
+ /* Allocate GT and find out where to store the grain. */
+ rc = vmdkAllocGrain(pImage->pGTCache, pExtent,
+ uSectorExtentRel, pvBuf, cbToWrite);
+ }
+ else
+ rc = VERR_VD_BLOCK_FREE;
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+ }
+ else
+ {
+ /* Clip write range to remain in this extent. */
+ cbToWrite = RT_MIN(cbToWrite, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
+ *pcbPreRead = VMDK_SECTOR2BYTE(uSectorExtentRel % pExtent->cSectorsPerGrain);
+ *pcbPostRead = VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain) - cbToWrite - *pcbPreRead;
+ rc = VERR_VD_BLOCK_FREE;
+ }
+ }
+ else
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uSectorExtentAbs),
+ pvBuf, cbToWrite, NULL);
+ break;
+ case VMDKETYPE_FLAT:
+ /* Clip write range to remain in this extent. */
+ cbToWrite = RT_MIN(cbToWrite, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
+ rc = vmdkFileWriteAt(pExtent->pFile,
+ VMDK_SECTOR2BYTE(uSectorExtentRel),
+ pvBuf, cbToWrite, NULL);
+ break;
+ case VMDKETYPE_ZERO:
+ /* Clip write range to remain in this extent. */
+ cbToWrite = RT_MIN(cbToWrite, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
+ break;
+ }
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnFlush */
+static int vmdkFlush(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ rc = vmdkFlushImage(pImage);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetVersion */
+static unsigned vmdkGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ return VMDK_IMAGE_VERSION;
+ else
+ return 0;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetImageType */
+static int vmdkGetImageType(void *pBackendData, PVDIMAGETYPE penmImageType)
+{
+ LogFlowFunc(("pBackendData=%#p penmImageType=%#p\n", pBackendData, penmImageType));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ AssertPtr(penmImageType);
+
+ if (pImage && pImage->cExtents != 0)
+ *penmImageType = pImage->enmImageType;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc enmImageType=%u\n", rc, *penmImageType));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetSize */
+static uint64_t vmdkGetSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ return pImage->cbSize;
+ else
+ return 0;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetFileSize */
+static uint64_t vmdkGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ uint64_t cbFile;
+ if (pImage->pFile != NULL)
+ {
+ int rc = vmdkFileGetSize(pImage->pFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb += cbFile;
+ }
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ if (pImage->pExtents[i].pFile != NULL)
+ {
+ int rc = vmdkFileGetSize(pImage->pExtents[i].pFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb += cbFile;
+ }
+ }
+ }
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetPCHSGeometry */
+static int vmdkGetPCHSGeometry(void *pBackendData,
+ PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->PCHSGeometry.cCylinders)
+ {
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetPCHSGeometry */
+static int vmdkSetPCHSGeometry(void *pBackendData,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+ rc = vmdkDescSetPCHSGeometry(pImage, pPCHSGeometry);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetLCHSGeometry */
+static int vmdkGetLCHSGeometry(void *pBackendData,
+ PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->LCHSGeometry.cCylinders)
+ {
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetLCHSGeometry */
+static int vmdkSetLCHSGeometry(void *pBackendData,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+ rc = vmdkDescSetLCHSGeometry(pImage, pLCHSGeometry);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetImageFlags */
+static unsigned vmdkGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ unsigned uImageFlags;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ uImageFlags = pImage->uImageFlags;
+ else
+ uImageFlags = 0;
+
+ LogFlowFunc(("returns %#x\n", uImageFlags));
+ return uImageFlags;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetOpenFlags */
+static unsigned vmdkGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ unsigned uOpenFlags;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ uOpenFlags = pImage->uOpenFlags;
+ else
+ uOpenFlags = 0;
+
+ LogFlowFunc(("returns %#x\n", uOpenFlags));
+ return uOpenFlags;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetOpenFlags */
+static int vmdkSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ /* Image must be opened and the new flags must be valid. Just readonly and
+ * info flags are supported. */
+ if (!pImage || (uOpenFlags & ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_ASYNC_IO)))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Implement this operation via reopening the image. */
+ vmdkFreeImage(pImage, false);
+ rc = vmdkOpenImage(pImage, uOpenFlags);
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetComment */
+static int vmdkGetComment(void *pBackendData, char *pszComment,
+ size_t cbComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ const char *pszCommentEncoded = NULL;
+ rc = vmdkDescDDBGetStr(pImage, &pImage->Descriptor,
+ "ddb.comment", &pszCommentEncoded);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pszCommentEncoded = NULL;
+ else if (RT_FAILURE(rc))
+ goto out;
+
+ if (pszComment && pszCommentEncoded)
+ rc = vmdkDecodeString(pszCommentEncoded, pszComment, cbComment);
+ else
+ {
+ if (pszComment)
+ *pszComment = '\0';
+ rc = VINF_SUCCESS;
+ }
+ if (pszCommentEncoded)
+ RTStrFree((char *)(void *)pszCommentEncoded);
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returns %Rrc comment='%s'\n", rc, pszComment));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetComment */
+static int vmdkSetComment(void *pBackendData, const char *pszComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ goto out;
+ }
+
+ if (pImage)
+ rc = vmdkSetImageComment(pImage, pszComment);
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetUuid */
+static int vmdkGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ *pUuid = pImage->ImageUuid;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetUuid */
+static int vmdkSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ LogFlowFunc(("%RTuuid\n", pUuid));
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ pImage->ImageUuid = *pUuid;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_IMAGE_UUID, pUuid);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image UUID in descriptor in '%s'"), pImage->pszFilename);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetModificationUuid */
+static int vmdkGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ *pUuid = pImage->ModificationUuid;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetModificationUuid */
+static int vmdkSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ pImage->ModificationUuid = *pUuid;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_MODIFICATION_UUID, pUuid);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing modification UUID in descriptor in '%s'"), pImage->pszFilename);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetParentUuid */
+static int vmdkGetParentUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ *pUuid = pImage->ParentUuid;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetParentUuid */
+static int vmdkSetParentUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ pImage->ParentUuid = *pUuid;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_UUID, pUuid);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent image UUID in descriptor in '%s'"), pImage->pszFilename);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnGetParentModificationUuid */
+static int vmdkGetParentModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ *pUuid = pImage->ParentModificationUuid;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnSetParentModificationUuid */
+static int vmdkSetParentModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ pImage->ParentModificationUuid = *pUuid;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_MODIFICATION_UUID, pUuid);
+ if (RT_FAILURE(rc))
+ return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent image UUID in descriptor in '%s'"), pImage->pszFilename);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VBOXHDDBACKEND::pfnDump */
+static void vmdkDump(void *pBackendData)
+{
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ RTLogPrintf("Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cbSector=%llu\n",
+ pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors,
+ pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors,
+ VMDK_BYTE2SECTOR(pImage->cbSize));
+ RTLogPrintf("Header: uuidCreation={%RTuuid}\n", &pImage->ImageUuid);
+ RTLogPrintf("Header: uuidModification={%RTuuid}\n", &pImage->ModificationUuid);
+ RTLogPrintf("Header: uuidParent={%RTuuid}\n", &pImage->ParentUuid);
+ RTLogPrintf("Header: uuidParentModification={%RTuuid}\n", &pImage->ParentModificationUuid);
+ }
+}
+
+
+static int vmdkGetTimeStamp(void *pvBackendData, PRTTIMESPEC pTimeStamp)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int vmdkGetParentTimeStamp(void *pvBackendData, PRTTIMESPEC pTimeStamp)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int vmdkSetParentTimeStamp(void *pvBackendData, PCRTTIMESPEC pTimeStamp)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int vmdkGetParentFilename(void *pvBackendData, char **ppszParentFilename)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static int vmdkSetParentFilename(void *pvBackendData, const char *pszParentFilename)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ LogFlow(("%s: returned %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+static bool vmdkIsAsyncIOSupported(void *pvBackendData)
+{
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pvBackendData;
+ bool fAsyncIOSupported = false;
+
+ if (pImage)
+ {
+ /* We only support async I/O support if the image only consists of FLAT or ZERO extents. */
+ fAsyncIOSupported = true;
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ if ( (pImage->pExtents[i].enmType != VMDKETYPE_FLAT)
+ && (pImage->pExtents[i].enmType != VMDKETYPE_ZERO))
+ {
+ fAsyncIOSupported = false;
+ break; /* Stop search */
+ }
+ }
+ }
+
+ return fAsyncIOSupported;
+}
+
+static int vmdkAsyncRead(void *pvBackendData, uint64_t uOffset, size_t cbRead,
+ PPDMDATASEG paSeg, unsigned cSeg, void *pvUser)
+{
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pvBackendData;
+ PVMDKEXTENT pExtent;
+ int rc = VINF_SUCCESS;
+ unsigned cTasksToSubmit = 0;
+ PPDMDATASEG paSegCurrent = paSeg;
+ unsigned cbLeftInCurrentSegment = paSegCurrent->cbSeg;
+ unsigned uOffsetInCurrentSegment = 0;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbRead % 512 == 0);
+
+ if ( uOffset + cbRead > pImage->cbSize
+ || cbRead == 0)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ while (cbRead && cSeg)
+ {
+ unsigned cbToRead;
+ uint64_t uSectorExtentRel;
+
+ rc = vmdkFindExtent(pImage, VMDK_BYTE2SECTOR(uOffset),
+ &pExtent, &uSectorExtentRel);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ /* Check access permissions as defined in the extent descriptor. */
+ if (pExtent->enmAccess == VMDKACCESS_NOACCESS)
+ {
+ rc = VERR_VD_VMDK_INVALID_STATE;
+ goto out;
+ }
+
+ /* Clip read range to remain in this extent. */
+ cbToRead = RT_MIN(cbRead, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
+ /* Clip read range to remain into current data segment. */
+ cbToRead = RT_MIN(cbToRead, cbLeftInCurrentSegment);
+
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_FLAT:
+ {
+ /* Setup new task. */
+ void *pTask;
+ rc = pImage->pInterfaceAsyncIOCallbacks->pfnPrepareRead(pImage->pInterfaceAsyncIO->pvUser, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uSectorExtentRel),
+ (uint8_t *)paSegCurrent->pvSeg + uOffsetInCurrentSegment,
+ cbToRead, &pTask);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Preparing read failed rc=%Rrc\n", rc));
+ goto out;
+ }
+
+ /* Check for enough room first. */
+ if (cTasksToSubmit >= pImage->cTask)
+ {
+ /* We reached maximum, resize array. Try to realloc memory first. */
+ void **apTaskNew = (void **)RTMemRealloc(pImage->apTask, (cTasksToSubmit + 10)*sizeof(void *));
+
+ if (!apTaskNew)
+ {
+ /* We failed. Allocate completely new. */
+ apTaskNew = (void **)RTMemAllocZ((cTasksToSubmit + 10)* sizeof(void *));
+ if (!apTaskNew)
+ {
+ /* Damn, we are out of memory. */
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ /* Copy task handles over. */
+ for (unsigned i = 0; i < cTasksToSubmit; i++)
+ apTaskNew[i] = pImage->apTask[i];
+
+ /* Free old memory. */
+ RTMemFree(pImage->apTask);
+ }
+
+ pImage->cTask = cTasksToSubmit + 10;
+ pImage->apTask = apTaskNew;
+ }
+
+ pImage->apTask[cTasksToSubmit] = pTask;
+ cTasksToSubmit++;
+ break;
+ }
+ case VMDKETYPE_ZERO:
+ memset((uint8_t *)paSegCurrent->pvSeg + uOffsetInCurrentSegment, 0, cbToRead);
+ break;
+ default:
+ AssertMsgFailed(("Unsupported extent type %u\n", pExtent->enmType));
+ }
+
+ cbRead -= cbToRead;
+ uOffset += cbToRead;
+ cbLeftInCurrentSegment -= cbToRead;
+ uOffsetInCurrentSegment += cbToRead;
+ /* Go to next extent if there is no space left in current one. */
+ if (!cbLeftInCurrentSegment)
+ {
+ uOffsetInCurrentSegment = 0;
+ paSegCurrent++;
+ cSeg--;
+ cbLeftInCurrentSegment = paSegCurrent->cbSeg;
+ }
+ }
+
+ AssertMsg(cbRead == 0, ("No segment left but there is still data to read\n"));
+
+ if (cTasksToSubmit == 0)
+ {
+ /* The request was completely in a ZERO extent nothing to do. */
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ }
+ else
+ {
+ /* Submit tasks. */
+ rc = pImage->pInterfaceAsyncIOCallbacks->pfnTasksSubmit(pImage->pInterfaceAsyncIO->pvUser,
+ pImage->apTask, cTasksToSubmit,
+ NULL, pvUser,
+ NULL /* Nothing required after read. */);
+ AssertMsgRC(rc, ("Failed to enqueue tasks rc=%Rrc\n", rc));
+ }
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static int vmdkAsyncWrite(void *pvBackendData, uint64_t uOffset, size_t cbWrite,
+ PPDMDATASEG paSeg, unsigned cSeg, void *pvUser)
+{
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pvBackendData;
+ PVMDKEXTENT pExtent;
+ int rc = VINF_SUCCESS;
+ unsigned cTasksToSubmit = 0;
+ PPDMDATASEG paSegCurrent = paSeg;
+ unsigned cbLeftInCurrentSegment = paSegCurrent->cbSeg;
+ unsigned uOffsetInCurrentSegment = 0;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbWrite % 512 == 0);
+
+ if ( uOffset + cbWrite > pImage->cbSize
+ || cbWrite == 0)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ while (cbWrite && cSeg)
+ {
+ unsigned cbToWrite;
+ uint64_t uSectorExtentRel;
+
+ rc = vmdkFindExtent(pImage, VMDK_BYTE2SECTOR(uOffset),
+ &pExtent, &uSectorExtentRel);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ /* Check access permissions as defined in the extent descriptor. */
+ if (pExtent->enmAccess == VMDKACCESS_NOACCESS)
+ {
+ rc = VERR_VD_VMDK_INVALID_STATE;
+ goto out;
+ }
+
+ /* Clip write range to remain in this extent. */
+ cbToWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
+ /* Clip write range to remain into current data segment. */
+ cbToWrite = RT_MIN(cbToWrite, cbLeftInCurrentSegment);
+
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_FLAT:
+ {
+ /* Setup new task. */
+ void *pTask;
+ rc = pImage->pInterfaceAsyncIOCallbacks->pfnPrepareWrite(pImage->pInterfaceAsyncIO->pvUser, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uSectorExtentRel),
+ (uint8_t *)paSegCurrent->pvSeg + uOffsetInCurrentSegment,
+ cbToWrite, &pTask);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Preparing read failed rc=%Rrc\n", rc));
+ goto out;
+ }
+
+ /* Check for enough room first. */
+ if (cTasksToSubmit >= pImage->cTask)
+ {
+ /* We reached maximum, resize array. Try to realloc memory first. */
+ void **apTaskNew = (void **)RTMemRealloc(pImage->apTask, (cTasksToSubmit + 10)*sizeof(void *));
+
+ if (!apTaskNew)
+ {
+ /* We failed. Allocate completely new. */
+ apTaskNew = (void **)RTMemAllocZ((cTasksToSubmit + 10)* sizeof(void *));
+ if (!apTaskNew)
+ {
+ /* Damn, we are out of memory. */
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+
+ /* Copy task handles over. */
+ for (unsigned i = 0; i < cTasksToSubmit; i++)
+ apTaskNew[i] = pImage->apTask[i];
+
+ /* Free old memory. */
+ RTMemFree(pImage->apTask);
+ }
+
+ pImage->cTask = cTasksToSubmit + 10;
+ pImage->apTask = apTaskNew;
+ }
+
+ pImage->apTask[cTasksToSubmit] = pTask;
+ cTasksToSubmit++;
+ break;
+ }
+ case VMDKETYPE_ZERO:
+ /* Nothing left to do. */
+ break;
+ default:
+ AssertMsgFailed(("Unsupported extent type %u\n", pExtent->enmType));
+ }
+
+ cbWrite -= cbToWrite;
+ uOffset += cbToWrite;
+ cbLeftInCurrentSegment -= cbToWrite;
+ uOffsetInCurrentSegment += cbToWrite;
+ /* Go to next extent if there is no space left in current one. */
+ if (!cbLeftInCurrentSegment)
+ {
+ uOffsetInCurrentSegment = 0;
+ paSegCurrent++;
+ cSeg--;
+ cbLeftInCurrentSegment = paSegCurrent->cbSeg;
+ }
+ }
+
+ AssertMsg(cbWrite == 0, ("No segment left but there is still data to read\n"));
+
+ if (cTasksToSubmit == 0)
+ {
+ /* The request was completely in a ZERO extent nothing to do. */
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ }
+ else
+ {
+ /* Submit tasks. */
+ rc = pImage->pInterfaceAsyncIOCallbacks->pfnTasksSubmit(pImage->pInterfaceAsyncIO->pvUser,
+ pImage->apTask, cTasksToSubmit,
+ NULL, pvUser,
+ NULL /* Nothing required after read. */);
+ AssertMsgRC(rc, ("Failed to enqueue tasks rc=%Rrc\n", rc));
+ }
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+
+}
+
+
+VBOXHDDBACKEND g_VmdkBackend =
+{
+ /* pszBackendName */
+ "VMDK",
+ /* cbSize */
+ sizeof(VBOXHDDBACKEND),
+ /* uBackendCaps */
+ VD_CAP_UUID | VD_CAP_CREATE_FIXED | VD_CAP_CREATE_DYNAMIC
+ | VD_CAP_CREATE_SPLIT_2G | VD_CAP_DIFF | VD_CAP_FILE |VD_CAP_ASYNC,
+ /* papszFileExtensions */
+ s_apszVmdkFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* hPlugin */
+ NIL_RTLDRMOD,
+ /* pfnCheckIfValid */
+ vmdkCheckIfValid,
+ /* pfnOpen */
+ vmdkOpen,
+ /* pfnCreate */
+ vmdkCreate,
+ /* pfnRename */
+ vmdkRename,
+ /* pfnClose */
+ vmdkClose,
+ /* pfnRead */
+ vmdkRead,
+ /* pfnWrite */
+ vmdkWrite,
+ /* pfnFlush */
+ vmdkFlush,
+ /* pfnGetVersion */
+ vmdkGetVersion,
+ /* pfnGetImageType */
+ vmdkGetImageType,
+ /* pfnGetSize */
+ vmdkGetSize,
+ /* pfnGetFileSize */
+ vmdkGetFileSize,
+ /* pfnGetPCHSGeometry */
+ vmdkGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ vmdkSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ vmdkGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ vmdkSetLCHSGeometry,
+ /* pfnGetImageFlags */
+ vmdkGetImageFlags,
+ /* pfnGetOpenFlags */
+ vmdkGetOpenFlags,
+ /* pfnSetOpenFlags */
+ vmdkSetOpenFlags,
+ /* pfnGetComment */
+ vmdkGetComment,
+ /* pfnSetComment */
+ vmdkSetComment,
+ /* pfnGetUuid */
+ vmdkGetUuid,
+ /* pfnSetUuid */
+ vmdkSetUuid,
+ /* pfnGetModificationUuid */
+ vmdkGetModificationUuid,
+ /* pfnSetModificationUuid */
+ vmdkSetModificationUuid,
+ /* pfnGetParentUuid */
+ vmdkGetParentUuid,
+ /* pfnSetParentUuid */
+ vmdkSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ vmdkGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ vmdkSetParentModificationUuid,
+ /* pfnDump */
+ vmdkDump,
+ /* pfnGetTimeStamp */
+ vmdkGetTimeStamp,
+ /* pfnGetParentTimeStamp */
+ vmdkGetParentTimeStamp,
+ /* pfnSetParentTimeStamp */
+ vmdkSetParentTimeStamp,
+ /* pfnGetParentFilename */
+ vmdkGetParentFilename,
+ /* pfnSetParentFilename */
+ vmdkSetParentFilename,
+ /* pfnIsAsyncIOSupported */
+ vmdkIsAsyncIOSupported,
+ /* pfnAsyncRead */
+ vmdkAsyncRead,
+ /* pfnAsyncWrite */
+ vmdkAsyncWrite,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName
+};
diff --git a/src/VBox/Devices/Storage/fdc.c b/src/VBox/Devices/Storage/fdc.c
new file mode 100644
index 000000000..e47bb7f1e
--- /dev/null
+++ b/src/VBox/Devices/Storage/fdc.c
@@ -0,0 +1,2996 @@
+#ifdef VBOX
+/** @file
+ *
+ * VBox storage devices:
+ * Floppy disk controller
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ * --------------------------------------------------------------------
+ *
+ * This code is based on:
+ *
+ * QEMU Floppy disk emulator (Intel 82078)
+ *
+ * Copyright (c) 2003 Jocelyn Mayer
+ *
+ * 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
+ * THE AUTHORS OR COPYRIGHT HOLDERS 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.
+ *
+ */
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_FDC
+#include <VBox/pdmdev.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/string.h>
+
+#include "Builtins.h"
+#include "../vl_vbox.h"
+
+#define MAX_FD 2
+
+#define PDMIBASE_2_FDRIVE(pInterface) \
+ ((fdrive_t *)((uintptr_t)(pInterface) - RT_OFFSETOF(fdrive_t, IBase)))
+
+#define PDMIMOUNTNOTIFY_2_FDRIVE(p) \
+ ((fdrive_t *)((uintptr_t)(p) - RT_OFFSETOF(fdrive_t, IMountNotify)))
+
+#endif /* VBOX */
+
+#ifndef VBOX
+#include "vl.h"
+#endif /* !VBOX */
+
+/********************************************************/
+/* debug Floppy devices */
+/* #define DEBUG_FLOPPY */
+
+#ifndef VBOX
+ #ifdef DEBUG_FLOPPY
+ #define FLOPPY_DPRINTF(fmt, args...) \
+ do { printf("FLOPPY: " fmt , ##args); } while (0)
+ #endif
+#else /* !VBOX */
+ # ifdef LOG_ENABLED
+ static void FLOPPY_DPRINTF (const char *fmt, ...)
+ {
+ if (LogIsEnabled ()) {
+ va_list args;
+ va_start (args, fmt);
+ RTLogLogger (NULL, NULL, "floppy: %N", fmt, &args); /* %N - nested va_list * type formatting call. */
+ va_end (args);
+ }
+ }
+ # else
+ DECLINLINE(void) FLOPPY_DPRINTF(const char *pszFmt, ...) {}
+ # endif
+#endif /* !VBOX */
+
+#ifndef VBOX
+#define FLOPPY_ERROR(fmt, args...) \
+ do { printf("FLOPPY ERROR: %s: " fmt, __func__ , ##args); } while (0)
+#else /* VBOX */
+# define FLOPPY_ERROR RTLogPrintf
+#endif /* VBOX */
+
+#ifdef VBOX
+typedef struct fdctrl_t fdctrl_t;
+#endif /* VBOX */
+
+/********************************************************/
+/* Floppy drive emulation */
+
+/* Will always be a fixed parameter for us */
+#define FD_SECTOR_LEN 512
+#define FD_SECTOR_SC 2 /* Sector size code */
+
+/* Floppy disk drive emulation */
+typedef enum fdisk_type_t {
+ FDRIVE_DISK_288 = 0x01, /* 2.88 MB disk */
+ FDRIVE_DISK_144 = 0x02, /* 1.44 MB disk */
+ FDRIVE_DISK_720 = 0x03, /* 720 kB disk */
+ FDRIVE_DISK_USER = 0x04, /* User defined geometry */
+ FDRIVE_DISK_NONE = 0x05 /* No disk */
+} fdisk_type_t;
+
+typedef enum fdrive_type_t {
+ FDRIVE_DRV_144 = 0x00, /* 1.44 MB 3"5 drive */
+ FDRIVE_DRV_288 = 0x01, /* 2.88 MB 3"5 drive */
+ FDRIVE_DRV_120 = 0x02, /* 1.2 MB 5"25 drive */
+ FDRIVE_DRV_NONE = 0x03 /* No drive connected */
+} fdrive_type_t;
+
+typedef enum fdrive_flags_t {
+ FDRIVE_MOTOR_ON = 0x01, /* motor on/off */
+ FDRIVE_REVALIDATE = 0x02 /* Revalidated */
+} fdrive_flags_t;
+
+typedef enum fdisk_flags_t {
+ FDISK_DBL_SIDES = 0x01
+} fdisk_flags_t;
+
+typedef struct fdrive_t {
+#ifndef VBOX
+ BlockDriverState *bs;
+#else /* VBOX */
+ /** Pointer to the attached driver's base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Pointer to the attached driver's block interface. */
+ R3PTRTYPE(PPDMIBLOCK) pDrvBlock;
+ /** Pointer to the attached driver's block bios interface. */
+ R3PTRTYPE(PPDMIBLOCKBIOS) pDrvBlockBios;
+ /** Pointer to the attached driver's mount interface.
+ * This is NULL if the driver isn't a removable unit. */
+ R3PTRTYPE(PPDMIMOUNT) pDrvMount;
+ /** The base interface. */
+ PDMIBASE IBase;
+ /** The block port interface. */
+ PDMIBLOCKPORT IPort;
+ /** The mount notify interface. */
+ PDMIMOUNTNOTIFY IMountNotify;
+ /** The LUN #. */
+ RTUINT iLUN;
+ /** The LED for this LUN. */
+ PDMLED Led;
+ /** The Diskette present/missing flag. */
+ bool fMediaPresent;
+#endif
+ /* Drive status */
+ fdrive_type_t drive;
+ fdrive_flags_t drflags;
+ uint8_t perpendicular; /* 2.88 MB access mode */
+ /* Position */
+ uint8_t head;
+ uint8_t track;
+ uint8_t sect;
+ /* Last operation status */
+ uint8_t dir; /* Direction */
+ uint8_t rw; /* Read/write */
+ /* Media */
+ fdisk_flags_t flags;
+ uint8_t last_sect; /* Nb sector per track */
+ uint8_t max_track; /* Nb of tracks */
+ uint16_t bps; /* Bytes per sector */
+ uint8_t ro; /* Is read-only */
+} fdrive_t;
+
+#ifndef VBOX
+static void fd_init (fdrive_t *drv, BlockDriverState *bs)
+{
+ /* Drive */
+ drv->bs = bs;
+ drv->drive = FDRIVE_DRV_NONE;
+ drv->drflags = 0;
+ drv->perpendicular = 0;
+ /* Disk */
+ drv->last_sect = 0;
+ drv->max_track = 0;
+}
+#endif
+
+static unsigned _fd_sector (uint8_t head, uint8_t track,
+ uint8_t sect, uint8_t last_sect)
+{
+ return (((track * 2) + head) * last_sect) + sect - 1; /* sect >= 1 */
+}
+
+/* Returns current position, in sectors, for given drive */
+static unsigned fd_sector (fdrive_t *drv)
+{
+ return _fd_sector(drv->head, drv->track, drv->sect, drv->last_sect);
+}
+
+static int fd_seek (fdrive_t *drv, uint8_t head, uint8_t track, uint8_t sect,
+ int enable_seek)
+{
+ uint32_t sector;
+ int ret;
+
+ if (track > drv->max_track ||
+ (head != 0 && (drv->flags & FDISK_DBL_SIDES) == 0)) {
+ FLOPPY_DPRINTF("try to read %d %02x %02x (max=%d %d %02x %02x)\n",
+ head, track, sect, 1,
+ (drv->flags & FDISK_DBL_SIDES) == 0 ? 0 : 1,
+ drv->max_track, drv->last_sect);
+ return 2;
+ }
+ if (sect > drv->last_sect) {
+ FLOPPY_DPRINTF("try to read %d %02x %02x (max=%d %d %02x %02x)\n",
+ head, track, sect, 1,
+ (drv->flags & FDISK_DBL_SIDES) == 0 ? 0 : 1,
+ drv->max_track, drv->last_sect);
+ return 3;
+ }
+ sector = _fd_sector(head, track, sect, drv->last_sect);
+ ret = 0;
+ if (sector != fd_sector(drv)) {
+#if 0
+ if (!enable_seek) {
+ FLOPPY_ERROR("no implicit seek %d %02x %02x (max=%d %02x %02x)\n",
+ head, track, sect, 1, drv->max_track, drv->last_sect);
+ return 4;
+ }
+#endif
+ drv->head = head;
+ if (drv->track != track)
+ ret = 1;
+ drv->track = track;
+ drv->sect = sect;
+ }
+
+ return ret;
+}
+
+/* Set drive back to track 0 */
+static void fd_recalibrate (fdrive_t *drv)
+{
+ FLOPPY_DPRINTF("recalibrate\n");
+ drv->head = 0;
+ drv->track = 0;
+ drv->sect = 1;
+ drv->dir = 1;
+ drv->rw = 0;
+}
+
+/* Recognize floppy formats */
+typedef struct fd_format_t {
+ fdrive_type_t drive;
+ fdisk_type_t disk;
+ uint8_t last_sect;
+ uint8_t max_track;
+ uint8_t max_head;
+ const char *str;
+} fd_format_t;
+
+static fd_format_t fd_formats[] = {
+ /* First entry is default format */
+ /* 1.44 MB 3"1/2 floppy disks */
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 18, 80, 1, "1.44 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 20, 80, 1, "1.6 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 21, 80, 1, "1.68 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 21, 82, 1, "1.72 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 21, 83, 1, "1.74 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 22, 80, 1, "1.76 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 23, 80, 1, "1.84 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 24, 80, 1, "1.92 MB 3\"1/2", },
+ /* 2.88 MB 3"1/2 floppy disks */
+ { FDRIVE_DRV_288, FDRIVE_DISK_288, 36, 80, 1, "2.88 MB 3\"1/2", },
+ { FDRIVE_DRV_288, FDRIVE_DISK_288, 39, 80, 1, "3.12 MB 3\"1/2", },
+ { FDRIVE_DRV_288, FDRIVE_DISK_288, 40, 80, 1, "3.2 MB 3\"1/2", },
+ { FDRIVE_DRV_288, FDRIVE_DISK_288, 44, 80, 1, "3.52 MB 3\"1/2", },
+ { FDRIVE_DRV_288, FDRIVE_DISK_288, 48, 80, 1, "3.84 MB 3\"1/2", },
+ /* 720 kB 3"1/2 floppy disks */
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 9, 80, 1, "720 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 10, 80, 1, "800 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 10, 82, 1, "820 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 10, 83, 1, "830 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 13, 80, 1, "1.04 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 14, 80, 1, "1.12 MB 3\"1/2", },
+ /* 1.2 MB 5"1/4 floppy disks */
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 15, 80, 1, "1.2 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 18, 80, 1, "1.44 MB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 18, 82, 1, "1.48 MB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 18, 83, 1, "1.49 MB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 20, 80, 1, "1.6 MB 5\"1/4", },
+ /* 720 kB 5"1/4 floppy disks */
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 9, 80, 1, "720 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 11, 80, 1, "880 kB 5\"1/4", },
+ /* 360 kB 5"1/4 floppy disks */
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 9, 40, 1, "360 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 9, 40, 0, "180 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 10, 41, 1, "410 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 10, 42, 1, "420 kB 5\"1/4", },
+ /* 320 kB 5"1/4 floppy disks */
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 8, 40, 1, "320 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 8, 40, 0, "160 kB 5\"1/4", },
+ /* 360 kB must match 5"1/4 better than 3"1/2... */
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 9, 80, 0, "360 kB 3\"1/2", },
+ /* end */
+ { FDRIVE_DRV_NONE, FDRIVE_DISK_NONE, -1, -1, 0, NULL, },
+};
+
+/* Revalidate a disk drive after a disk change */
+static void fd_revalidate (fdrive_t *drv)
+{
+ fd_format_t *parse;
+ int64_t nb_sectors, size;
+ int i, first_match, match;
+ int nb_heads, max_track, last_sect, ro;
+
+ FLOPPY_DPRINTF("revalidate\n");
+ drv->drflags &= ~FDRIVE_REVALIDATE;
+#ifndef VBOX
+ if (drv->bs != NULL && bdrv_is_inserted(drv->bs)) {
+ ro = bdrv_is_read_only(drv->bs);
+ bdrv_get_geometry_hint(drv->bs, &nb_heads, &max_track, &last_sect);
+#else /* VBOX */
+ /** @todo */ /** @todo r=bird: todo what exactly? */
+ if (drv->pDrvBlock
+ && drv->pDrvMount
+ && drv->pDrvMount->pfnIsMounted (drv->pDrvMount)) {
+ ro = drv->pDrvBlock->pfnIsReadOnly (drv->pDrvBlock);
+ nb_heads = 0;
+ max_track = 0;
+ last_sect = 0;
+#endif /* VBOX */
+ if (nb_heads != 0 && max_track != 0 && last_sect != 0) {
+ FLOPPY_DPRINTF("User defined disk (%d %d %d)",
+ nb_heads - 1, max_track, last_sect);
+ } else {
+#ifndef VBOX
+ bdrv_get_geometry(drv->bs, &nb_sectors);
+#else /* VBOX */
+ /* @todo */ /** @todo r=bird: todo what exactly?!?!? */
+ {
+ uint64_t size = drv->pDrvBlock->pfnGetSize (drv->pDrvBlock);
+ nb_sectors = size / 512;
+ }
+#endif /* VBOX */
+ match = -1;
+ first_match = -1;
+ for (i = 0;; i++) {
+ parse = &fd_formats[i];
+ if (parse->drive == FDRIVE_DRV_NONE)
+ break;
+ if (drv->drive == parse->drive ||
+ drv->drive == FDRIVE_DRV_NONE) {
+ size = (parse->max_head + 1) * parse->max_track *
+ parse->last_sect;
+ if (nb_sectors == size) {
+ match = i;
+ break;
+ }
+ if (first_match == -1)
+ first_match = i;
+ }
+ }
+ if (match == -1) {
+ if (first_match == -1)
+ match = 1;
+ else
+ match = first_match;
+ parse = &fd_formats[match];
+ }
+ nb_heads = parse->max_head + 1;
+ max_track = parse->max_track;
+ last_sect = parse->last_sect;
+ drv->drive = parse->drive;
+ FLOPPY_DPRINTF("%s floppy disk (%d h %d t %d s) %s\n", parse->str,
+ nb_heads, max_track, last_sect, ro ? "ro" : "rw");
+ }
+ if (nb_heads == 1) {
+ drv->flags &= ~FDISK_DBL_SIDES;
+ } else {
+ drv->flags |= FDISK_DBL_SIDES;
+ }
+ drv->max_track = max_track;
+ drv->last_sect = last_sect;
+ drv->ro = ro;
+#ifdef VBOX
+ drv->fMediaPresent = true;
+#endif
+ } else {
+ FLOPPY_DPRINTF("No disk in drive\n");
+ drv->last_sect = 0;
+ drv->max_track = 0;
+ drv->flags &= ~FDISK_DBL_SIDES;
+#ifdef VBOX
+ drv->fMediaPresent = false;
+#endif
+ }
+ drv->drflags |= FDRIVE_REVALIDATE;
+}
+
+/* Motor control */
+static void fd_start (fdrive_t *drv)
+{
+ drv->drflags |= FDRIVE_MOTOR_ON;
+}
+
+static void fd_stop (fdrive_t *drv)
+{
+ drv->drflags &= ~FDRIVE_MOTOR_ON;
+}
+
+/* Re-initialise a drives (motor off, repositioned) */
+static void fd_reset (fdrive_t *drv)
+{
+ fd_stop(drv);
+ fd_recalibrate(drv);
+}
+
+/********************************************************/
+/* Intel 82078 floppy disk controller emulation */
+
+#define target_ulong uint32_t
+static void fdctrl_reset (fdctrl_t *fdctrl, int do_irq);
+static void fdctrl_reset_fifo (fdctrl_t *fdctrl);
+#ifndef VBOX
+static int fdctrl_transfer_handler (void *opaque, int nchan, int dma_pos, int dma_len);
+#else /* VBOX: */
+static DECLCALLBACK(uint32_t) fdctrl_transfer_handler (PPDMDEVINS pDevIns,
+ void *opaque,
+ unsigned nchan,
+ uint32_t dma_pos,
+ uint32_t dma_len);
+#endif /* VBOX */
+static void fdctrl_raise_irq (fdctrl_t *fdctrl, uint8_t status);
+static void fdctrl_result_timer(void *opaque);
+
+static uint32_t fdctrl_read_statusB (fdctrl_t *fdctrl);
+static uint32_t fdctrl_read_dor (fdctrl_t *fdctrl);
+static void fdctrl_write_dor (fdctrl_t *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_tape (fdctrl_t *fdctrl);
+static void fdctrl_write_tape (fdctrl_t *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_main_status (fdctrl_t *fdctrl);
+static void fdctrl_write_rate (fdctrl_t *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_data (fdctrl_t *fdctrl);
+static void fdctrl_write_data (fdctrl_t *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_dir (fdctrl_t *fdctrl);
+
+enum {
+ FD_CTRL_ACTIVE = 0x01, /* XXX: suppress that */
+ FD_CTRL_RESET = 0x02,
+ FD_CTRL_SLEEP = 0x04, /* XXX: suppress that */
+ FD_CTRL_BUSY = 0x08, /* dma transfer in progress */
+ FD_CTRL_INTR = 0x10
+};
+
+enum {
+ FD_DIR_WRITE = 0,
+ FD_DIR_READ = 1,
+ FD_DIR_SCANE = 2,
+ FD_DIR_SCANL = 3,
+ FD_DIR_SCANH = 4
+};
+
+enum {
+ FD_STATE_CMD = 0x00,
+ FD_STATE_STATUS = 0x01,
+ FD_STATE_DATA = 0x02,
+ FD_STATE_STATE = 0x03,
+ FD_STATE_MULTI = 0x10,
+ FD_STATE_SEEK = 0x20,
+ FD_STATE_FORMAT = 0x40
+};
+
+#define FD_STATE(state) ((state) & FD_STATE_STATE)
+#define FD_SET_STATE(state, new_state) \
+do { (state) = ((state) & ~FD_STATE_STATE) | (new_state); } while (0)
+#define FD_MULTI_TRACK(state) ((state) & FD_STATE_MULTI)
+#define FD_DID_SEEK(state) ((state) & FD_STATE_SEEK)
+#define FD_FORMAT_CMD(state) ((state) & FD_STATE_FORMAT)
+
+struct fdctrl_t {
+#ifndef VBOX
+ fdctrl_t *fdctrl;
+#endif
+ /* Controller's identification */
+ uint8_t version;
+ /* HW */
+#ifndef VBOX
+ int irq_lvl;
+ int dma_chann;
+#else
+ uint8_t irq_lvl;
+ uint8_t dma_chann;
+#endif
+ uint32_t io_base;
+ /* Controller state */
+ QEMUTimer *result_timer;
+ uint8_t state;
+ uint8_t dma_en;
+ uint8_t cur_drv;
+ uint8_t bootsel;
+ /* Command FIFO */
+ uint8_t fifo[FD_SECTOR_LEN];
+ uint32_t data_pos;
+ uint32_t data_len;
+ uint8_t data_state;
+ uint8_t data_dir;
+ uint8_t int_status;
+ uint8_t eot; /* last wanted sector */
+ /* States kept only to be returned back */
+ /* Timers state */
+ uint8_t timer0;
+ uint8_t timer1;
+ /* precompensation */
+ uint8_t precomp_trk;
+ uint8_t config;
+ uint8_t lock;
+ /* Power down config (also with status regB access mode */
+ uint8_t pwrd;
+ /* Floppy drives */
+ fdrive_t drives[2];
+#ifdef VBOX
+ /** Pointer to device instance. */
+ PPDMDEVINS pDevIns;
+
+ /** Status Port - The base interface. */
+ PDMIBASE IBaseStatus;
+ /** Status Port - The Leds interface. */
+ PDMILEDPORTS ILeds;
+ /** Status Port - The Partner of ILeds. */
+ PPDMILEDCONNECTORS pLedsConnector;
+#endif
+};
+
+static uint32_t fdctrl_read (void *opaque, uint32_t reg)
+{
+ fdctrl_t *fdctrl = opaque;
+ uint32_t retval;
+
+ switch (reg & 0x07) {
+ case 0x01:
+ retval = fdctrl_read_statusB(fdctrl);
+ break;
+ case 0x02:
+ retval = fdctrl_read_dor(fdctrl);
+ break;
+ case 0x03:
+ retval = fdctrl_read_tape(fdctrl);
+ break;
+ case 0x04:
+ retval = fdctrl_read_main_status(fdctrl);
+ break;
+ case 0x05:
+ retval = fdctrl_read_data(fdctrl);
+ break;
+ case 0x07:
+ retval = fdctrl_read_dir(fdctrl);
+ break;
+ default:
+ retval = (uint32_t)(-1);
+ break;
+ }
+ FLOPPY_DPRINTF("read reg%d: 0x%02x\n", reg & 7, retval);
+
+ return retval;
+}
+
+static void fdctrl_write (void *opaque, uint32_t reg, uint32_t value)
+{
+ fdctrl_t *fdctrl = opaque;
+
+ FLOPPY_DPRINTF("write reg%d: 0x%02x\n", reg & 7, value);
+
+ switch (reg & 0x07) {
+ case 0x02:
+ fdctrl_write_dor(fdctrl, value);
+ break;
+ case 0x03:
+ fdctrl_write_tape(fdctrl, value);
+ break;
+ case 0x04:
+ fdctrl_write_rate(fdctrl, value);
+ break;
+ case 0x05:
+ fdctrl_write_data(fdctrl, value);
+ break;
+ default:
+ break;
+ }
+}
+
+#ifndef VBOX
+static void fd_change_cb (void *opaque)
+{
+ fdrive_t *drv = opaque;
+
+ FLOPPY_DPRINTF("disk change\n");
+ fd_revalidate(drv);
+#if 0
+ fd_recalibrate(drv);
+ fdctrl_reset_fifo(drv->fdctrl);
+ fdctrl_raise_irq(drv->fdctrl, 0x20);
+#endif
+}
+
+#else /* VBOX */
+/**
+ * Called when a media is mounted.
+ *
+ * @param pInterface Pointer to the interface structure
+ * containing the called function pointer.
+ */
+static DECLCALLBACK(void) fdMountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ fdrive_t *drv = PDMIMOUNTNOTIFY_2_FDRIVE (pInterface);
+ LogFlow(("fdMountNotify:\n"));
+ fd_revalidate (drv);
+}
+
+/**
+ * Called when a media is unmounted
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ */
+static DECLCALLBACK(void) fdUnmountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ fdrive_t *drv = PDMIMOUNTNOTIFY_2_FDRIVE (pInterface);
+ LogFlow(("fdUnmountNotify:\n"));
+ fd_revalidate (drv);
+}
+#endif
+
+#ifndef VBOX
+fdctrl_t *fdctrl_init (int irq_lvl, int dma_chann, int mem_mapped,
+ uint32_t io_base,
+ BlockDriverState **fds)
+{
+ fdctrl_t *fdctrl;
+/* // int io_mem; */
+ int i;
+
+ FLOPPY_DPRINTF("init controller\n");
+ fdctrl = qemu_mallocz(sizeof(fdctrl_t));
+ if (!fdctrl)
+ return NULL;
+ fdctrl->result_timer = qemu_new_timer(vm_clock,
+ fdctrl_result_timer, fdctrl);
+
+ fdctrl->version = 0x90; /* Intel 82078 controller */
+ fdctrl->irq_lvl = irq_lvl;
+ fdctrl->dma_chann = dma_chann;
+ fdctrl->io_base = io_base;
+ fdctrl->config = 0x60; /* Implicit seek, polling & FIFO enabled */
+ if (fdctrl->dma_chann != -1) {
+ fdctrl->dma_en = 1;
+ DMA_register_channel(dma_chann, &fdctrl_transfer_handler, fdctrl);
+ } else {
+ fdctrl->dma_en = 0;
+ }
+ for (i = 0; i < 2; i++) {
+ fd_init(&fdctrl->drives[i], fds[i]);
+ if (fds[i]) {
+ bdrv_set_change_cb(fds[i],
+ &fd_change_cb, &fdctrl->drives[i]);
+ }
+ }
+ fdctrl_reset(fdctrl, 0);
+ fdctrl->state = FD_CTRL_ACTIVE;
+ if (mem_mapped) {
+ FLOPPY_ERROR("memory mapped floppy not supported by now !\n");
+#if 0
+ io_mem = cpu_register_io_memory(0, fdctrl_mem_read, fdctrl_mem_write);
+ cpu_register_physical_memory(base, 0x08, io_mem);
+#endif
+ } else {
+ register_ioport_read(io_base + 0x01, 5, 1, &fdctrl_read, fdctrl);
+ register_ioport_read(io_base + 0x07, 1, 1, &fdctrl_read, fdctrl);
+ register_ioport_write(io_base + 0x01, 5, 1, &fdctrl_write, fdctrl);
+ register_ioport_write(io_base + 0x07, 1, 1, &fdctrl_write, fdctrl);
+ }
+ for (i = 0; i < 2; i++) {
+ fd_revalidate(&fdctrl->drives[i]);
+ }
+
+ return fdctrl;
+}
+
+/* XXX: may change if moved to bdrv */
+int fdctrl_get_drive_type(fdctrl_t *fdctrl, int drive_num)
+{
+ return fdctrl->drives[drive_num].drive;
+}
+#endif
+
+/* Change IRQ state */
+static void fdctrl_reset_irq (fdctrl_t *fdctrl)
+{
+ FLOPPY_DPRINTF("Reset interrupt\n");
+#ifdef VBOX
+ PDMDevHlpISASetIrq (fdctrl->pDevIns, fdctrl->irq_lvl, 0);
+#else
+ pic_set_irq(fdctrl->irq_lvl, 0);
+#endif
+ fdctrl->state &= ~FD_CTRL_INTR;
+}
+
+static void fdctrl_raise_irq (fdctrl_t *fdctrl, uint8_t status)
+{
+ if (~(fdctrl->state & FD_CTRL_INTR)) {
+#ifdef VBOX
+ PDMDevHlpISASetIrq (fdctrl->pDevIns, fdctrl->irq_lvl, 1);
+#else
+ pic_set_irq(fdctrl->irq_lvl, 1);
+#endif
+ fdctrl->state |= FD_CTRL_INTR;
+ }
+ FLOPPY_DPRINTF("Set interrupt status to 0x%02x\n", status);
+ fdctrl->int_status = status;
+}
+
+/* Reset controller */
+static void fdctrl_reset (fdctrl_t *fdctrl, int do_irq)
+{
+ int i;
+
+ FLOPPY_DPRINTF("reset controller\n");
+ fdctrl_reset_irq(fdctrl);
+ /* Initialise controller */
+ fdctrl->cur_drv = 0;
+ /* FIFO state */
+ fdctrl->data_pos = 0;
+ fdctrl->data_len = 0;
+ fdctrl->data_state = FD_STATE_CMD;
+ fdctrl->data_dir = FD_DIR_WRITE;
+ for (i = 0; i < MAX_FD; i++)
+ fd_reset(&fdctrl->drives[i]);
+ fdctrl_reset_fifo(fdctrl);
+ if (do_irq)
+ fdctrl_raise_irq(fdctrl, 0xc0);
+}
+
+static inline fdrive_t *drv0 (fdctrl_t *fdctrl)
+{
+ return &fdctrl->drives[fdctrl->bootsel];
+}
+
+static inline fdrive_t *drv1 (fdctrl_t *fdctrl)
+{
+ return &fdctrl->drives[1 - fdctrl->bootsel];
+}
+
+static fdrive_t *get_cur_drv (fdctrl_t *fdctrl)
+{
+ return fdctrl->cur_drv == 0 ? drv0(fdctrl) : drv1(fdctrl);
+}
+
+/* Status B register : 0x01 (read-only) */
+static uint32_t fdctrl_read_statusB (fdctrl_t *fdctrl)
+{
+ FLOPPY_DPRINTF("status register: 0x00\n");
+ return 0;
+}
+
+/* Digital output register : 0x02 */
+static uint32_t fdctrl_read_dor (fdctrl_t *fdctrl)
+{
+ uint32_t retval = 0;
+
+ /* Drive motors state indicators */
+#ifndef VBOX
+ if (drv0(fdctrl)->drflags & FDRIVE_MOTOR_ON)
+ retval |= 1 << 5;
+ if (drv1(fdctrl)->drflags & FDRIVE_MOTOR_ON)
+ retval |= 1 << 4;
+#else
+ /* bit4: 0 = drive 0 motor off/1 = on */
+ if (drv0(fdctrl)->drflags & FDRIVE_MOTOR_ON)
+ retval |= RT_BIT(4);
+ /* bit5: 0 = drive 1 motor off/1 = on */
+ if (drv1(fdctrl)->drflags & FDRIVE_MOTOR_ON)
+ retval |= RT_BIT(5);
+#endif
+ /* DMA enable */
+ retval |= fdctrl->dma_en << 3;
+ /* Reset indicator */
+ retval |= (fdctrl->state & FD_CTRL_RESET) == 0 ? 0x04 : 0;
+ /* Selected drive */
+ retval |= fdctrl->cur_drv;
+ FLOPPY_DPRINTF("digital output register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+static void fdctrl_write_dor (fdctrl_t *fdctrl, uint32_t value)
+{
+ /* Reset mode */
+ if (fdctrl->state & FD_CTRL_RESET) {
+ if (!(value & 0x04)) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ }
+ FLOPPY_DPRINTF("digital output register set to 0x%02x\n", value);
+ /* Drive motors state indicators */
+ if (value & 0x20)
+ fd_start(drv1(fdctrl));
+ else
+ fd_stop(drv1(fdctrl));
+ if (value & 0x10)
+ fd_start(drv0(fdctrl));
+ else
+ fd_stop(drv0(fdctrl));
+ /* DMA enable */
+#if 0
+ if (fdctrl->dma_chann != -1)
+ fdctrl->dma_en = 1 - ((value >> 3) & 1);
+#endif
+ /* Reset */
+ if (!(value & 0x04)) {
+ if (!(fdctrl->state & FD_CTRL_RESET)) {
+ FLOPPY_DPRINTF("controller enter RESET state\n");
+ fdctrl->state |= FD_CTRL_RESET;
+ }
+ } else {
+ if (fdctrl->state & FD_CTRL_RESET) {
+ FLOPPY_DPRINTF("controller out of RESET state\n");
+ fdctrl_reset(fdctrl, 1);
+ fdctrl->state &= ~(FD_CTRL_RESET | FD_CTRL_SLEEP);
+ }
+ }
+ /* Selected drive */
+ fdctrl->cur_drv = value & 1;
+}
+
+/* Tape drive register : 0x03 */
+static uint32_t fdctrl_read_tape (fdctrl_t *fdctrl)
+{
+ uint32_t retval = 0;
+
+ /* Disk boot selection indicator */
+ retval |= fdctrl->bootsel << 2;
+ /* Tape indicators: never allowed */
+ FLOPPY_DPRINTF("tape drive register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+static void fdctrl_write_tape (fdctrl_t *fdctrl, uint32_t value)
+{
+ /* Reset mode */
+ if (fdctrl->state & FD_CTRL_RESET) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ FLOPPY_DPRINTF("tape drive register set to 0x%02x\n", value);
+ /* Disk boot selection indicator */
+ fdctrl->bootsel = (value >> 2) & 1;
+ /* Tape indicators: never allow */
+}
+
+/* Main status register : 0x04 (read) */
+static uint32_t fdctrl_read_main_status (fdctrl_t *fdctrl)
+{
+ uint32_t retval = 0;
+
+ fdctrl->state &= ~(FD_CTRL_SLEEP | FD_CTRL_RESET);
+ if (!(fdctrl->state & FD_CTRL_BUSY)) {
+ /* Data transfer allowed */
+ retval |= 0x80;
+ /* Data transfer direction indicator */
+ if (fdctrl->data_dir == FD_DIR_READ)
+ retval |= 0x40;
+ }
+ /* Should handle 0x20 for SPECIFY command */
+ /* Command busy indicator */
+ if (FD_STATE(fdctrl->data_state) == FD_STATE_DATA ||
+ FD_STATE(fdctrl->data_state) == FD_STATE_STATUS)
+ retval |= 0x10;
+ FLOPPY_DPRINTF("main status register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+/* Data select rate register : 0x04 (write) */
+static void fdctrl_write_rate (fdctrl_t *fdctrl, uint32_t value)
+{
+ /* Reset mode */
+ if (fdctrl->state & FD_CTRL_RESET) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ FLOPPY_DPRINTF("select rate register set to 0x%02x\n", value);
+ /* Reset: autoclear */
+ if (value & 0x80) {
+ fdctrl->state |= FD_CTRL_RESET;
+ fdctrl_reset(fdctrl, 1);
+ fdctrl->state &= ~FD_CTRL_RESET;
+ }
+ if (value & 0x40) {
+ fdctrl->state |= FD_CTRL_SLEEP;
+ fdctrl_reset(fdctrl, 1);
+ }
+/* // fdctrl.precomp = (value >> 2) & 0x07; */
+}
+
+/* Digital input register : 0x07 (read-only) */
+static uint32_t fdctrl_read_dir (fdctrl_t *fdctrl)
+{
+ uint32_t retval = 0;
+
+#ifndef VBOX
+ if (drv0(fdctrl)->drflags & FDRIVE_REVALIDATE ||
+ drv1(fdctrl)->drflags & FDRIVE_REVALIDATE)
+#else
+ fdrive_t *cur_drv = get_cur_drv(fdctrl);
+ if ( drv0(fdctrl)->drflags & FDRIVE_REVALIDATE
+ || drv1(fdctrl)->drflags & FDRIVE_REVALIDATE
+ || !cur_drv->fMediaPresent)
+#endif
+ retval |= 0x80;
+ if (retval != 0)
+ FLOPPY_DPRINTF("Floppy digital input register: 0x%02x\n", retval);
+ drv0(fdctrl)->drflags &= ~FDRIVE_REVALIDATE;
+ drv1(fdctrl)->drflags &= ~FDRIVE_REVALIDATE;
+
+ return retval;
+}
+
+/* FIFO state control */
+static void fdctrl_reset_fifo (fdctrl_t *fdctrl)
+{
+ fdctrl->data_dir = FD_DIR_WRITE;
+ fdctrl->data_pos = 0;
+ FD_SET_STATE(fdctrl->data_state, FD_STATE_CMD);
+}
+
+/* Set FIFO status for the host to read */
+static void fdctrl_set_fifo (fdctrl_t *fdctrl, int fifo_len, int do_irq)
+{
+ fdctrl->data_dir = FD_DIR_READ;
+ fdctrl->data_len = fifo_len;
+ fdctrl->data_pos = 0;
+ FD_SET_STATE(fdctrl->data_state, FD_STATE_STATUS);
+ if (do_irq)
+ fdctrl_raise_irq(fdctrl, 0x00);
+}
+
+/* Set an error: unimplemented/unknown command */
+static void fdctrl_unimplemented (fdctrl_t *fdctrl)
+{
+#if 0
+ fdrive_t *cur_drv;
+
+ cur_drv = get_cur_drv(fdctrl);
+ fdctrl->fifo[0] = 0x60 | (cur_drv->head << 2) | fdctrl->cur_drv;
+ fdctrl->fifo[1] = 0x00;
+ fdctrl->fifo[2] = 0x00;
+ fdctrl_set_fifo(fdctrl, 3, 1);
+#else
+ /* // fdctrl_reset_fifo(fdctrl); */
+ fdctrl->fifo[0] = 0x80;
+ fdctrl_set_fifo(fdctrl, 1, 0);
+#endif
+}
+
+/* Callback for transfer end (stop or abort) */
+static void fdctrl_stop_transfer (fdctrl_t *fdctrl, uint8_t status0,
+ uint8_t status1, uint8_t status2)
+{
+ fdrive_t *cur_drv;
+
+ cur_drv = get_cur_drv(fdctrl);
+ FLOPPY_DPRINTF("transfer status: %02x %02x %02x (%02x)\n",
+ status0, status1, status2,
+ status0 | (cur_drv->head << 2) | fdctrl->cur_drv);
+ fdctrl->fifo[0] = status0 | (cur_drv->head << 2) | fdctrl->cur_drv;
+ fdctrl->fifo[1] = status1;
+ fdctrl->fifo[2] = status2;
+ fdctrl->fifo[3] = cur_drv->track;
+ fdctrl->fifo[4] = cur_drv->head;
+ fdctrl->fifo[5] = cur_drv->sect;
+ fdctrl->fifo[6] = FD_SECTOR_SC;
+ fdctrl->data_dir = FD_DIR_READ;
+ if (fdctrl->state & FD_CTRL_BUSY) {
+#ifdef VBOX
+ PDMDevHlpDMASetDREQ (fdctrl->pDevIns, fdctrl->dma_chann, 0);
+#else
+ DMA_release_DREQ(fdctrl->dma_chann);
+#endif
+ fdctrl->state &= ~FD_CTRL_BUSY;
+ }
+ fdctrl_set_fifo(fdctrl, 7, 1);
+}
+
+/* Prepare a data transfer (either DMA or FIFO) */
+static void fdctrl_start_transfer (fdctrl_t *fdctrl, int direction)
+{
+ fdrive_t *cur_drv;
+ uint8_t kh, kt, ks;
+ int did_seek;
+
+ fdctrl->cur_drv = fdctrl->fifo[1] & 1;
+ cur_drv = get_cur_drv(fdctrl);
+ kt = fdctrl->fifo[2];
+ kh = fdctrl->fifo[3];
+ ks = fdctrl->fifo[4];
+ FLOPPY_DPRINTF("Start transfer at %d %d %02x %02x (%d)\n",
+ fdctrl->cur_drv, kh, kt, ks,
+ _fd_sector(kh, kt, ks, cur_drv->last_sect));
+ did_seek = 0;
+ switch (fd_seek(cur_drv, kh, kt, ks, fdctrl->config & 0x40)) {
+ case 2:
+ /* sect too big */
+ fdctrl_stop_transfer(fdctrl, 0x40, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 3:
+ /* track too big */
+ fdctrl_stop_transfer(fdctrl, 0x40, 0x80, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 4:
+ /* No seek enabled */
+ fdctrl_stop_transfer(fdctrl, 0x40, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 1:
+ did_seek = 1;
+ break;
+ default:
+ break;
+ }
+ /* Set the FIFO state */
+ fdctrl->data_dir = direction;
+ fdctrl->data_pos = 0;
+ FD_SET_STATE(fdctrl->data_state, FD_STATE_DATA); /* FIFO ready for data */
+ if (fdctrl->fifo[0] & 0x80)
+ fdctrl->data_state |= FD_STATE_MULTI;
+ else
+ fdctrl->data_state &= ~FD_STATE_MULTI;
+ if (did_seek)
+ fdctrl->data_state |= FD_STATE_SEEK;
+ else
+ fdctrl->data_state &= ~FD_STATE_SEEK;
+ if (fdctrl->fifo[5] == 00) {
+ fdctrl->data_len = fdctrl->fifo[8];
+ } else {
+ int tmp;
+ fdctrl->data_len = 128 << fdctrl->fifo[5];
+ tmp = (cur_drv->last_sect - ks + 1);
+ if (fdctrl->fifo[0] & 0x80)
+ tmp += cur_drv->last_sect;
+ fdctrl->data_len *= tmp;
+ }
+ fdctrl->eot = fdctrl->fifo[6];
+ if (fdctrl->dma_en) {
+ int dma_mode;
+ /* DMA transfer are enabled. Check if DMA channel is well programmed */
+#ifndef VBOX
+ dma_mode = DMA_get_channel_mode(fdctrl->dma_chann);
+#else
+ dma_mode = PDMDevHlpDMAGetChannelMode (fdctrl->pDevIns, fdctrl->dma_chann);
+#endif
+ dma_mode = (dma_mode >> 2) & 3;
+ FLOPPY_DPRINTF("dma_mode=%d direction=%d (%d - %d)\n",
+ dma_mode, direction,
+ (128 << fdctrl->fifo[5]) *
+ (cur_drv->last_sect - ks + 1), fdctrl->data_len);
+ if (((direction == FD_DIR_SCANE || direction == FD_DIR_SCANL ||
+ direction == FD_DIR_SCANH) && dma_mode == 0) ||
+ (direction == FD_DIR_WRITE && dma_mode == 2) ||
+ (direction == FD_DIR_READ && dma_mode == 1)) {
+ /* No access is allowed until DMA transfer has completed */
+ fdctrl->state |= FD_CTRL_BUSY;
+ /* Now, we just have to wait for the DMA controller to
+ * recall us...
+ */
+#ifndef VBOX
+ DMA_hold_DREQ(fdctrl->dma_chann);
+ DMA_schedule(fdctrl->dma_chann);
+#else
+ PDMDevHlpDMASetDREQ (fdctrl->pDevIns, fdctrl->dma_chann, 1);
+ PDMDevHlpDMASchedule (fdctrl->pDevIns);
+#endif
+ return;
+ } else {
+ FLOPPY_ERROR("dma_mode=%d direction=%d\n", dma_mode, direction);
+ }
+ }
+ FLOPPY_DPRINTF("start non-DMA transfer\n");
+ /* IO based transfer: calculate len */
+ fdctrl_raise_irq(fdctrl, 0x00);
+
+ return;
+}
+
+/* Prepare a transfer of deleted data */
+static void fdctrl_start_transfer_del (fdctrl_t *fdctrl, int direction)
+{
+ /* We don't handle deleted data,
+ * so we don't return *ANYTHING*
+ */
+ fdctrl_stop_transfer(fdctrl, 0x60, 0x00, 0x00);
+}
+
+#if 0
+#include <stdio.h>
+#include <stdlib.h>
+
+static FILE * f;
+static void dump (void *p, size_t len)
+{
+ size_t n;
+
+ if (!f) {
+ f = fopen ("dump", "wb");
+ if (!f)
+ exit (0);
+ }
+
+ n = fwrite (p, len, 1, f);
+ if (n != 1) {
+ AssertMsgFailed (("failed to dump\n"));
+ exit (0);
+ }
+}
+#else
+#define dump(a,b) do { } while (0)
+#endif
+
+/* handlers for DMA transfers */
+#ifdef VBOX
+static DECLCALLBACK(uint32_t) fdctrl_transfer_handler (PPDMDEVINS pDevIns,
+ void *opaque,
+ unsigned nchan,
+ uint32_t dma_pos,
+ uint32_t dma_len)
+#else
+static int fdctrl_transfer_handler (void *opaque, int nchan,
+ int dma_pos, int dma_len)
+#endif
+{
+ fdctrl_t *fdctrl;
+ fdrive_t *cur_drv;
+#ifdef VBOX
+ int rc;
+ uint32_t len, start_pos, rel_pos;
+#else
+ int len, start_pos, rel_pos;
+#endif
+ uint8_t status0 = 0x00, status1 = 0x00, status2 = 0x00;
+
+ fdctrl = opaque;
+ if (!(fdctrl->state & FD_CTRL_BUSY)) {
+ FLOPPY_DPRINTF("Not in DMA transfer mode !\n");
+ return 0;
+ }
+ cur_drv = get_cur_drv(fdctrl);
+ if (fdctrl->data_dir == FD_DIR_SCANE || fdctrl->data_dir == FD_DIR_SCANL ||
+ fdctrl->data_dir == FD_DIR_SCANH)
+ status2 = 0x04;
+ if (dma_len > fdctrl->data_len)
+ dma_len = fdctrl->data_len;
+#ifndef VBOX
+ if (cur_drv->bs == NULL) {
+#else /* !VBOX */
+ if (cur_drv->pDrvBlock == NULL) {
+#endif
+ if (fdctrl->data_dir == FD_DIR_WRITE)
+ fdctrl_stop_transfer(fdctrl, 0x60, 0x00, 0x00);
+ else
+ fdctrl_stop_transfer(fdctrl, 0x40, 0x00, 0x00);
+ len = 0;
+ goto transfer_error;
+ }
+ rel_pos = fdctrl->data_pos % FD_SECTOR_LEN;
+ for (start_pos = fdctrl->data_pos; fdctrl->data_pos < dma_len;) {
+ len = dma_len - fdctrl->data_pos;
+ if (len + rel_pos > FD_SECTOR_LEN)
+ len = FD_SECTOR_LEN - rel_pos;
+ FLOPPY_DPRINTF("copy %d bytes (%d %d %d) %d pos %d %02x "
+ "(%d-0x%08x 0x%08x)\n", len, dma_len, fdctrl->data_pos,
+ fdctrl->data_len, fdctrl->cur_drv, cur_drv->head,
+ cur_drv->track, cur_drv->sect, fd_sector(cur_drv),
+ fd_sector(cur_drv) * 512);
+ if (fdctrl->data_dir != FD_DIR_WRITE ||
+ len < FD_SECTOR_LEN || rel_pos != 0) {
+ /* READ & SCAN commands and realign to a sector for WRITE */
+#ifndef VBOX
+ if (bdrv_read(cur_drv->bs, fd_sector(cur_drv),
+ fdctrl->fifo, 1) < 0) {
+ FLOPPY_DPRINTF("Floppy: error getting sector %d\n",
+ fd_sector(cur_drv));
+ /* Sure, image size is too small... */
+ memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
+ }
+#else
+ cur_drv->Led.Asserted.s.fReading
+ = cur_drv->Led.Actual.s.fReading = 1;
+
+ rc = cur_drv->pDrvBlock->pfnRead (
+ cur_drv->pDrvBlock,
+ fd_sector (cur_drv) * 512,
+ fdctrl->fifo,
+ 1 * 512
+ );
+
+ cur_drv->Led.Actual.s.fReading = 0;
+
+ if (RT_FAILURE (rc)) {
+ AssertMsgFailed (
+ ("Floppy: error getting sector %d, rc = %Rrc",
+ fd_sector (cur_drv), rc));
+ memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
+ }
+#endif
+ }
+ switch (fdctrl->data_dir) {
+ case FD_DIR_READ:
+ /* READ commands */
+#ifdef VBOX
+ {
+ uint32_t read;
+ int rc = PDMDevHlpDMAWriteMemory(fdctrl->pDevIns, nchan,
+ fdctrl->fifo + rel_pos,
+ fdctrl->data_pos,
+ len, &read);
+ dump (fdctrl->fifo + rel_pos, len);
+ AssertMsgRC (rc, ("DMAWriteMemory -> %Rrc\n", rc));
+ }
+#else
+ DMA_write_memory (nchan, fdctrl->fifo + rel_pos,
+ fdctrl->data_pos, len);
+#endif
+/* cpu_physical_memory_write(addr + fdctrl->data_pos, */
+/* fdctrl->fifo + rel_pos, len); */
+ break;
+ case FD_DIR_WRITE:
+ /* WRITE commands */
+#ifdef VBOX
+ {
+ uint32_t written;
+ int rc = PDMDevHlpDMAReadMemory(fdctrl->pDevIns, nchan,
+ fdctrl->fifo + rel_pos,
+ fdctrl->data_pos,
+ len, &written);
+ AssertMsgRC (rc, ("DMAReadMemory -> %Rrc\n", rc));
+ }
+#else
+ DMA_read_memory (nchan, fdctrl->fifo + rel_pos,
+ fdctrl->data_pos, len);
+#endif
+/* cpu_physical_memory_read(addr + fdctrl->data_pos, */
+/* fdctrl->fifo + rel_pos, len); */
+#ifndef VBOX
+ if (bdrv_write(cur_drv->bs, fd_sector(cur_drv),
+ fdctrl->fifo, 1) < 0) {
+ FLOPPY_ERROR("writting sector %d\n", fd_sector(cur_drv));
+ fdctrl_stop_transfer(fdctrl, 0x60, 0x00, 0x00);
+ goto transfer_error;
+ }
+#else /* VBOX */
+ cur_drv->Led.Asserted.s.fWriting
+ = cur_drv->Led.Actual.s.fWriting = 1;
+
+ rc = cur_drv->pDrvBlock->pfnWrite (
+ cur_drv->pDrvBlock,
+ fd_sector (cur_drv) * 512,
+ fdctrl->fifo,
+ 1 * 512
+ );
+
+ cur_drv->Led.Actual.s.fWriting = 0;
+
+ if (RT_FAILURE (rc)) {
+ AssertMsgFailed (("Floppy: error writing sector %d. rc=%Rrc",
+ fd_sector (cur_drv), rc));
+ fdctrl_stop_transfer (fdctrl, 0x60, 0x00, 0x00);
+ goto transfer_error;
+ }
+#endif
+ break;
+ default:
+ /* SCAN commands */
+ {
+ uint8_t tmpbuf[FD_SECTOR_LEN];
+ int ret;
+#ifdef VBOX
+ int rc;
+ uint32_t read;
+
+ rc = PDMDevHlpDMAReadMemory (fdctrl->pDevIns, nchan, tmpbuf,
+ fdctrl->data_pos, len, &read);
+ AssertMsg (RT_SUCCESS (rc), ("DMAReadMemory -> %Rrc\n", rc));
+#else
+ DMA_read_memory (nchan, tmpbuf, fdctrl->data_pos, len);
+#endif
+/* cpu_physical_memory_read(addr + fdctrl->data_pos, */
+/* tmpbuf, len); */
+ ret = memcmp(tmpbuf, fdctrl->fifo + rel_pos, len);
+ if (ret == 0) {
+ status2 = 0x08;
+ goto end_transfer;
+ }
+ if ((ret < 0 && fdctrl->data_dir == FD_DIR_SCANL) ||
+ (ret > 0 && fdctrl->data_dir == FD_DIR_SCANH)) {
+ status2 = 0x00;
+ goto end_transfer;
+ }
+ }
+ break;
+ }
+ fdctrl->data_pos += len;
+ rel_pos = fdctrl->data_pos % FD_SECTOR_LEN;
+ if (rel_pos == 0) {
+ /* Seek to next sector */
+ FLOPPY_DPRINTF("seek to next sector (%d %02x %02x => %d) (%d)\n",
+ cur_drv->head, cur_drv->track, cur_drv->sect,
+ fd_sector(cur_drv),
+ fdctrl->data_pos - len);
+ /* XXX: cur_drv->sect >= cur_drv->last_sect should be an
+ error in fact */
+ if (cur_drv->sect >= cur_drv->last_sect ||
+ cur_drv->sect == fdctrl->eot) {
+ cur_drv->sect = 1;
+ if (FD_MULTI_TRACK(fdctrl->data_state)) {
+ if (cur_drv->head == 0 &&
+ (cur_drv->flags & FDISK_DBL_SIDES) != 0) {
+ cur_drv->head = 1;
+ } else {
+ cur_drv->head = 0;
+ cur_drv->track++;
+ if ((cur_drv->flags & FDISK_DBL_SIDES) == 0)
+ break;
+ }
+ } else {
+ cur_drv->track++;
+ break;
+ }
+ FLOPPY_DPRINTF("seek to next track (%d %02x %02x => %d)\n",
+ cur_drv->head, cur_drv->track,
+ cur_drv->sect, fd_sector(cur_drv));
+ } else {
+ cur_drv->sect++;
+ }
+ }
+ }
+end_transfer:
+ len = fdctrl->data_pos - start_pos;
+ FLOPPY_DPRINTF("end transfer %d %d %d\n",
+ fdctrl->data_pos, len, fdctrl->data_len);
+ if (fdctrl->data_dir == FD_DIR_SCANE ||
+ fdctrl->data_dir == FD_DIR_SCANL ||
+ fdctrl->data_dir == FD_DIR_SCANH)
+ status2 = 0x08;
+ if (FD_DID_SEEK(fdctrl->data_state))
+ status0 |= 0x20;
+ fdctrl->data_len -= len;
+ /* if (fdctrl->data_len == 0) */
+ fdctrl_stop_transfer(fdctrl, status0, status1, status2);
+transfer_error:
+
+ return len;
+}
+
+#if 0
+{
+ fdctrl_t *fdctrl;
+ fdrive_t *cur_drv;
+ int len, start_pos, rel_pos;
+ uint8_t status0 = 0x00, status1 = 0x00, status2 = 0x00;
+
+ fdctrl = opaque;
+ if (!(fdctrl->state & FD_CTRL_BUSY)) {
+ FLOPPY_DPRINTF("Not in DMA transfer mode !\n");
+ return 0;
+ }
+ cur_drv = get_cur_drv(fdctrl);
+ if (fdctrl->data_dir == FD_DIR_SCANE || fdctrl->data_dir == FD_DIR_SCANL ||
+ fdctrl->data_dir == FD_DIR_SCANH)
+ status2 = 0x04;
+ if (size > fdctrl->data_len)
+ size = fdctrl->data_len;
+#ifndef VBOX
+ if (cur_drv->bs == NULL) {
+#else
+ if (cur_drv->pDrvBase == NULL) {
+#endif
+ if (fdctrl->data_dir == FD_DIR_WRITE)
+ fdctrl_stop_transfer(fdctrl, 0x60, 0x00, 0x00);
+ else
+ fdctrl_stop_transfer(fdctrl, 0x40, 0x00, 0x00);
+ len = 0;
+ goto transfer_error;
+ }
+ rel_pos = fdctrl->data_pos % FD_SECTOR_LEN;
+ for (start_pos = fdctrl->data_pos; fdctrl->data_pos < size;) {
+ len = size - fdctrl->data_pos;
+ if (len + rel_pos > FD_SECTOR_LEN)
+ len = FD_SECTOR_LEN - rel_pos;
+ FLOPPY_DPRINTF("copy %d bytes (%d %d %d) %d pos %d %02x %02x "
+ "(%d-0x%08x 0x%08x)\n", len, size, fdctrl->data_pos,
+ fdctrl->data_len, fdctrl->cur_drv, cur_drv->head,
+ cur_drv->track, cur_drv->sect, fd_sector(cur_drv),
+ fd_sector(cur_drv) * 512, addr);
+ if (fdctrl->data_dir != FD_DIR_WRITE ||
+ len < FD_SECTOR_LEN || rel_pos != 0) {
+ /* READ & SCAN commands and realign to a sector for WRITE */
+#ifndef VBOX
+ if (bdrv_read(cur_drv->bs, fd_sector(cur_drv),
+ fdctrl->fifo, 1) < 0) {
+ FLOPPY_DPRINTF("Floppy: error getting sector %d\n",
+ fd_sector(cur_drv));
+ /* Sure, image size is too small... */
+ memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
+ }
+#else
+ int rc;
+
+ cur_drv->Led.Asserted.s.fReading
+ = cur_drv->Led.Actual.s.fReading = 1;
+
+ rc = cur_drv->pDrvBlock->pfnRead (
+ cur_drv->pDrvBlock,
+ fd_sector (cur_drv) * 512,
+ fdctrl->fifo,
+ 1 * 512
+ );
+
+ cur_drv->Led.Actual.s.fReading = 0;
+
+ if (RT_FAILURE (rc)) {
+ AssertMsgFailed (
+ ("Floppy: error getting sector %d. rc=%Rrc\n",
+ fd_sector(cur_drv), rc));
+ /* Sure, image size is too small... */
+ memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
+ }
+ /* *p_fd_activity = 1; */
+#endif
+ }
+ switch (fdctrl->data_dir) {
+ case FD_DIR_READ:
+ /* READ commands */
+#ifdef VBOX
+ PDMDevHlpPhysWrite (fdctrl->pDevIns, addr + fdctrl->data_pos,
+ fdctrl->fifo + rel_pos, len);
+#else
+ cpu_physical_memory_write(addr + fdctrl->data_pos,
+ fdctrl->fifo + rel_pos, len);
+#endif
+ break;
+ case FD_DIR_WRITE:
+ /* WRITE commands */
+#ifdef VBOX
+ {
+ int rc;
+
+ PDMDevHlpPhysRead (fdctrl->pDevIns, addr + fdctrl->data_pos,
+ fdctrl->fifo + rel_pos, len);
+
+ cur_drv->Led.Asserted.s.fWriting
+ = cur_drv->Led.Actual.s.fWriting = 1;
+
+ rc = cur_drv->pDrvBlock->pfnWrite (
+ cur_drv->pDrvBlock,
+ fd_sector (cur_drv) * 512,
+ fdctrl->fifo,
+ 1 * 512
+ );
+
+ cur_drv->Led.Actual.s.fWriting = 0;
+
+ if (RT_FAILURE (rc)) {
+ AssertMsgFailed (
+ ("Floppy: error writting sector %d. rc=%Rrc\n",
+ fd_sector(cur_drv), rc));
+ fdctrl_stop_transfer(fdctrl, 0x60, 0x00, 0x00);
+ goto transfer_error;
+ }
+ }
+ /* *p_fd_activity = 2; */
+#else /* !VBOX */
+ cpu_physical_memory_read(addr + fdctrl->data_pos,
+ fdctrl->fifo + rel_pos, len);
+ if (bdrv_write(cur_drv->bs, fd_sector(cur_drv),
+ fdctrl->fifo, 1) < 0) {
+ FLOPPY_ERROR("writting sector %d\n", fd_sector(cur_drv));
+ fdctrl_stop_transfer(fdctrl, 0x60, 0x00, 0x00);
+ goto transfer_error;
+ }
+#endif
+ break;
+ default:
+ /* SCAN commands */
+ {
+ uint8_t tmpbuf[FD_SECTOR_LEN];
+ int ret;
+#ifdef VBOX
+ PDMDevHlpPhysRead (fdctrl->pDevIns, addr + fdctrl->data_pos,
+ tmpbuf, len);
+#else
+ cpu_physical_memory_read(addr + fdctrl->data_pos,
+ tmpbuf, len);
+#endif
+ ret = memcmp(tmpbuf, fdctrl->fifo + rel_pos, len);
+ if (ret == 0) {
+ status2 = 0x08;
+ goto end_transfer;
+ }
+ if ((ret < 0 && fdctrl->data_dir == FD_DIR_SCANL) ||
+ (ret > 0 && fdctrl->data_dir == FD_DIR_SCANH)) {
+ status2 = 0x00;
+ goto end_transfer;
+ }
+ }
+ break;
+ }
+ fdctrl->data_pos += len;
+ rel_pos = fdctrl->data_pos % FD_SECTOR_LEN;
+ if (rel_pos == 0) {
+ /* Seek to next sector */
+ FLOPPY_DPRINTF("seek to next sector (%d %02x %02x => %d) (%d)\n",
+ cur_drv->head, cur_drv->track, cur_drv->sect,
+ fd_sector(cur_drv),
+ fdctrl->data_pos - size);
+ /* XXX: cur_drv->sect >= cur_drv->last_sect should be an
+ error in fact */
+ if (cur_drv->sect >= cur_drv->last_sect ||
+ cur_drv->sect == fdctrl->eot) {
+ cur_drv->sect = 1;
+ if (FD_MULTI_TRACK(fdctrl->data_state)) {
+ if (cur_drv->head == 0 &&
+ (cur_drv->flags & FDISK_DBL_SIDES) != 0) {
+ cur_drv->head = 1;
+ } else {
+ cur_drv->head = 0;
+ cur_drv->track++;
+ if ((cur_drv->flags & FDISK_DBL_SIDES) == 0)
+ break;
+ }
+ } else {
+ cur_drv->track++;
+ break;
+ }
+ FLOPPY_DPRINTF("seek to next track (%d %02x %02x => %d)\n",
+ cur_drv->head, cur_drv->track,
+ cur_drv->sect, fd_sector(cur_drv));
+ } else {
+ cur_drv->sect++;
+ }
+ }
+ }
+end_transfer:
+ len = fdctrl->data_pos - start_pos;
+ FLOPPY_DPRINTF("end transfer %d %d %d\n",
+ fdctrl->data_pos, len, fdctrl->data_len);
+ if (fdctrl->data_dir == FD_DIR_SCANE ||
+ fdctrl->data_dir == FD_DIR_SCANL ||
+ fdctrl->data_dir == FD_DIR_SCANH)
+ status2 = 0x08;
+ if (FD_DID_SEEK(fdctrl->data_state))
+ status0 |= 0x20;
+ fdctrl->data_len -= len;
+ /* // if (fdctrl->data_len == 0) */
+ fdctrl_stop_transfer(fdctrl, status0, status1, status2);
+transfer_error:
+
+ return len;
+}
+#endif
+
+/* Data register : 0x05 */
+static uint32_t fdctrl_read_data (fdctrl_t *fdctrl)
+{
+ fdrive_t *cur_drv;
+ uint32_t retval = 0;
+ int pos, len;
+#ifdef VBOX
+ int rc;
+#endif
+
+ cur_drv = get_cur_drv(fdctrl);
+ fdctrl->state &= ~FD_CTRL_SLEEP;
+ if (FD_STATE(fdctrl->data_state) == FD_STATE_CMD) {
+ FLOPPY_ERROR("can't read data in CMD state\n");
+ return 0;
+ }
+ pos = fdctrl->data_pos;
+ if (FD_STATE(fdctrl->data_state) == FD_STATE_DATA) {
+ pos %= FD_SECTOR_LEN;
+ if (pos == 0) {
+ len = fdctrl->data_len - fdctrl->data_pos;
+ if (len > FD_SECTOR_LEN)
+ len = FD_SECTOR_LEN;
+#ifdef VBOX
+ cur_drv->Led.Asserted.s.fReading
+ = cur_drv->Led.Actual.s.fReading = 1;
+
+ rc = cur_drv->pDrvBlock->pfnRead (
+ cur_drv->pDrvBlock,
+ fd_sector (cur_drv) * 512,
+ fdctrl->fifo,
+ len * 512
+ );
+
+ cur_drv->Led.Actual.s.fReading = 0;
+
+ if (RT_FAILURE (rc)) {
+ AssertMsgFailed (
+ ("Floppy: Failure to read sector %d. rc=%Rrc",
+ fd_sector (cur_drv), rc));
+ }
+#else
+ bdrv_read(cur_drv->bs, fd_sector(cur_drv),
+ fdctrl->fifo, len);
+#endif
+ }
+ }
+ retval = fdctrl->fifo[pos];
+ if (++fdctrl->data_pos == fdctrl->data_len) {
+ fdctrl->data_pos = 0;
+ /* Switch from transfer mode to status mode
+ * then from status mode to command mode
+ */
+ if (FD_STATE(fdctrl->data_state) == FD_STATE_DATA) {
+ fdctrl_stop_transfer(fdctrl, 0x20, 0x00, 0x00);
+ } else {
+ fdctrl_reset_fifo(fdctrl);
+ fdctrl_reset_irq(fdctrl);
+ }
+ }
+ FLOPPY_DPRINTF("data register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+static void fdctrl_format_sector (fdctrl_t *fdctrl)
+{
+ fdrive_t *cur_drv;
+ uint8_t kh, kt, ks;
+ int did_seek;
+#ifdef VBOX
+ int ok = 0, rc;
+#endif
+
+ fdctrl->cur_drv = fdctrl->fifo[1] & 1;
+ cur_drv = get_cur_drv(fdctrl);
+ kt = fdctrl->fifo[6];
+ kh = fdctrl->fifo[7];
+ ks = fdctrl->fifo[8];
+ FLOPPY_DPRINTF("format sector at %d %d %02x %02x (%d)\n",
+ fdctrl->cur_drv, kh, kt, ks,
+ _fd_sector(kh, kt, ks, cur_drv->last_sect));
+ did_seek = 0;
+ switch (fd_seek(cur_drv, kh, kt, ks, fdctrl->config & 0x40)) {
+ case 2:
+ /* sect too big */
+ fdctrl_stop_transfer(fdctrl, 0x40, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 3:
+ /* track too big */
+ fdctrl_stop_transfer(fdctrl, 0x40, 0x80, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 4:
+ /* No seek enabled */
+ fdctrl_stop_transfer(fdctrl, 0x40, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 1:
+ did_seek = 1;
+ fdctrl->data_state |= FD_STATE_SEEK;
+ break;
+ default:
+ break;
+ }
+ memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
+#ifdef VBOX
+ /* *p_fd_activity = 2; */
+ if (cur_drv->pDrvBlock) {
+ cur_drv->Led.Asserted.s.fWriting = cur_drv->Led.Actual.s.fWriting = 1;
+
+ rc = cur_drv->pDrvBlock->pfnWrite (
+ cur_drv->pDrvBlock,
+ fd_sector (cur_drv) * 512,
+ fdctrl->fifo,
+ 1 * 512
+ );
+
+ cur_drv->Led.Actual.s.fWriting = 0;
+
+ if (RT_FAILURE (rc)) {
+ AssertMsgFailed (("Floppy: error formating sector %d. rc=%Rrc\n",
+ fd_sector (cur_drv), rc));
+ fdctrl_stop_transfer(fdctrl, 0x60, 0x00, 0x00);
+ }
+ else {
+ ok = 1;
+ }
+ }
+ if (ok) {
+#else
+ if (cur_drv->bs == NULL ||
+ bdrv_write(cur_drv->bs, fd_sector(cur_drv), fdctrl->fifo, 1) < 0) {
+ FLOPPY_ERROR("formating sector %d\n", fd_sector(cur_drv));
+ fdctrl_stop_transfer(fdctrl, 0x60, 0x00, 0x00);
+ } else {
+#endif
+ if (cur_drv->sect == cur_drv->last_sect) {
+ fdctrl->data_state &= ~FD_STATE_FORMAT;
+ /* Last sector done */
+ if (FD_DID_SEEK(fdctrl->data_state))
+ fdctrl_stop_transfer(fdctrl, 0x20, 0x00, 0x00);
+ else
+ fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00);
+ } else {
+ /* More to do */
+ fdctrl->data_pos = 0;
+ fdctrl->data_len = 4;
+ }
+ }
+}
+
+static void fdctrl_write_data (fdctrl_t *fdctrl, uint32_t value)
+{
+ fdrive_t *cur_drv;
+
+ cur_drv = get_cur_drv(fdctrl);
+ /* Reset mode */
+ if (fdctrl->state & FD_CTRL_RESET) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ fdctrl->state &= ~FD_CTRL_SLEEP;
+ if (FD_STATE(fdctrl->data_state) == FD_STATE_STATUS) {
+ FLOPPY_ERROR("can't write data in status mode\n");
+ return;
+ }
+ /* Is it write command time ? */
+ if (FD_STATE(fdctrl->data_state) == FD_STATE_DATA) {
+ /* FIFO data write */
+ fdctrl->fifo[fdctrl->data_pos++] = value;
+ if (fdctrl->data_pos % FD_SECTOR_LEN == (FD_SECTOR_LEN - 1) ||
+ fdctrl->data_pos == fdctrl->data_len) {
+#ifdef VBOX
+ int rc;
+ /* *p_fd_activity = 2; */
+ cur_drv->Led.Asserted.s.fWriting
+ = cur_drv->Led.Actual.s.fWriting = 1;
+
+ rc = cur_drv->pDrvBlock->pfnWrite (
+ cur_drv->pDrvBlock,
+ fd_sector (cur_drv) * 512,
+ fdctrl->fifo,
+ FD_SECTOR_LEN * 512
+ );
+
+ cur_drv->Led.Actual.s.fWriting = 0;
+
+ if (RT_FAILURE (rc)) {
+ AssertMsgFailed (
+ ("Floppy: failed to write sector %d. rc=%Rrc\n",
+ fd_sector (cur_drv), rc));
+ }
+#else
+ bdrv_write(cur_drv->bs, fd_sector(cur_drv),
+ fdctrl->fifo, FD_SECTOR_LEN);
+#endif
+ }
+ /* Switch from transfer mode to status mode
+ * then from status mode to command mode
+ */
+ if (FD_STATE(fdctrl->data_state) == FD_STATE_DATA)
+ fdctrl_stop_transfer(fdctrl, 0x20, 0x00, 0x00);
+ return;
+ }
+ if (fdctrl->data_pos == 0) {
+ /* Command */
+ switch (value & 0x5F) {
+ case 0x46:
+ /* READ variants */
+ FLOPPY_DPRINTF("READ command\n");
+ /* 8 parameters cmd */
+ fdctrl->data_len = 9;
+ goto enqueue;
+ case 0x4C:
+ /* READ_DELETED variants */
+ FLOPPY_DPRINTF("READ_DELETED command\n");
+ /* 8 parameters cmd */
+ fdctrl->data_len = 9;
+ goto enqueue;
+ case 0x50:
+ /* SCAN_EQUAL variants */
+ FLOPPY_DPRINTF("SCAN_EQUAL command\n");
+ /* 8 parameters cmd */
+ fdctrl->data_len = 9;
+ goto enqueue;
+ case 0x56:
+ /* VERIFY variants */
+ FLOPPY_DPRINTF("VERIFY command\n");
+ /* 8 parameters cmd */
+ fdctrl->data_len = 9;
+ goto enqueue;
+ case 0x59:
+ /* SCAN_LOW_OR_EQUAL variants */
+ FLOPPY_DPRINTF("SCAN_LOW_OR_EQUAL command\n");
+ /* 8 parameters cmd */
+ fdctrl->data_len = 9;
+ goto enqueue;
+ case 0x5D:
+ /* SCAN_HIGH_OR_EQUAL variants */
+ FLOPPY_DPRINTF("SCAN_HIGH_OR_EQUAL command\n");
+ /* 8 parameters cmd */
+ fdctrl->data_len = 9;
+ goto enqueue;
+ default:
+ break;
+ }
+ switch (value & 0x7F) {
+ case 0x45:
+ /* WRITE variants */
+ FLOPPY_DPRINTF("WRITE command\n");
+ /* 8 parameters cmd */
+ fdctrl->data_len = 9;
+ goto enqueue;
+ case 0x49:
+ /* WRITE_DELETED variants */
+ FLOPPY_DPRINTF("WRITE_DELETED command\n");
+ /* 8 parameters cmd */
+ fdctrl->data_len = 9;
+ goto enqueue;
+ default:
+ break;
+ }
+ switch (value) {
+ case 0x03:
+ /* SPECIFY */
+ FLOPPY_DPRINTF("SPECIFY command\n");
+ /* 1 parameter cmd */
+ fdctrl->data_len = 3;
+ goto enqueue;
+ case 0x04:
+ /* SENSE_DRIVE_STATUS */
+ FLOPPY_DPRINTF("SENSE_DRIVE_STATUS command\n");
+ /* 1 parameter cmd */
+ fdctrl->data_len = 2;
+ goto enqueue;
+ case 0x07:
+ /* RECALIBRATE */
+ FLOPPY_DPRINTF("RECALIBRATE command\n");
+ /* 1 parameter cmd */
+ fdctrl->data_len = 2;
+ goto enqueue;
+ case 0x08:
+ /* SENSE_INTERRUPT_STATUS */
+ FLOPPY_DPRINTF("SENSE_INTERRUPT_STATUS command (%02x)\n",
+ fdctrl->int_status);
+ /* No parameters cmd: returns status if no interrupt */
+#if 0
+ fdctrl->fifo[0] =
+ fdctrl->int_status | (cur_drv->head << 2) | fdctrl->cur_drv;
+#else
+ /* XXX: int_status handling is broken for read/write
+ commands, so we do this hack. It should be suppressed
+ ASAP */
+ fdctrl->fifo[0] =
+ 0x20 | (cur_drv->head << 2) | fdctrl->cur_drv;
+#endif
+ fdctrl->fifo[1] = cur_drv->track;
+ fdctrl_set_fifo(fdctrl, 2, 0);
+ fdctrl_reset_irq(fdctrl);
+ fdctrl->int_status = 0xC0;
+ return;
+ case 0x0E:
+ /* DUMPREG */
+ FLOPPY_DPRINTF("DUMPREG command\n");
+ /* Drives position */
+ fdctrl->fifo[0] = drv0(fdctrl)->track;
+ fdctrl->fifo[1] = drv1(fdctrl)->track;
+ fdctrl->fifo[2] = 0;
+ fdctrl->fifo[3] = 0;
+ /* timers */
+ fdctrl->fifo[4] = fdctrl->timer0;
+ fdctrl->fifo[5] = (fdctrl->timer1 << 1) | fdctrl->dma_en;
+ fdctrl->fifo[6] = cur_drv->last_sect;
+ fdctrl->fifo[7] = (fdctrl->lock << 7) |
+ (cur_drv->perpendicular << 2);
+ fdctrl->fifo[8] = fdctrl->config;
+ fdctrl->fifo[9] = fdctrl->precomp_trk;
+ fdctrl_set_fifo(fdctrl, 10, 0);
+ return;
+ case 0x0F:
+ /* SEEK */
+ FLOPPY_DPRINTF("SEEK command\n");
+ /* 2 parameters cmd */
+ fdctrl->data_len = 3;
+ goto enqueue;
+ case 0x10:
+ /* VERSION */
+ FLOPPY_DPRINTF("VERSION command\n");
+ /* No parameters cmd */
+ /* Controller's version */
+ fdctrl->fifo[0] = fdctrl->version;
+ fdctrl_set_fifo(fdctrl, 1, 1);
+ return;
+ case 0x12:
+ /* PERPENDICULAR_MODE */
+ FLOPPY_DPRINTF("PERPENDICULAR_MODE command\n");
+ /* 1 parameter cmd */
+ fdctrl->data_len = 2;
+ goto enqueue;
+ case 0x13:
+ /* CONFIGURE */
+ FLOPPY_DPRINTF("CONFIGURE command\n");
+ /* 3 parameters cmd */
+ fdctrl->data_len = 4;
+ goto enqueue;
+ case 0x14:
+ /* UNLOCK */
+ FLOPPY_DPRINTF("UNLOCK command\n");
+ /* No parameters cmd */
+ fdctrl->lock = 0;
+ fdctrl->fifo[0] = 0;
+ fdctrl_set_fifo(fdctrl, 1, 0);
+ return;
+ case 0x17:
+ /* POWERDOWN_MODE */
+ FLOPPY_DPRINTF("POWERDOWN_MODE command\n");
+ /* 2 parameters cmd */
+ fdctrl->data_len = 3;
+ goto enqueue;
+ case 0x18:
+ /* PART_ID */
+ FLOPPY_DPRINTF("PART_ID command\n");
+ /* No parameters cmd */
+ fdctrl->fifo[0] = 0x41; /* Stepping 1 */
+ fdctrl_set_fifo(fdctrl, 1, 0);
+ return;
+ case 0x2C:
+ /* SAVE */
+ FLOPPY_DPRINTF("SAVE command\n");
+ /* No parameters cmd */
+ fdctrl->fifo[0] = 0;
+ fdctrl->fifo[1] = 0;
+ /* Drives position */
+ fdctrl->fifo[2] = drv0(fdctrl)->track;
+ fdctrl->fifo[3] = drv1(fdctrl)->track;
+ fdctrl->fifo[4] = 0;
+ fdctrl->fifo[5] = 0;
+ /* timers */
+ fdctrl->fifo[6] = fdctrl->timer0;
+ fdctrl->fifo[7] = fdctrl->timer1;
+ fdctrl->fifo[8] = cur_drv->last_sect;
+ fdctrl->fifo[9] = (fdctrl->lock << 7) |
+ (cur_drv->perpendicular << 2);
+ fdctrl->fifo[10] = fdctrl->config;
+ fdctrl->fifo[11] = fdctrl->precomp_trk;
+ fdctrl->fifo[12] = fdctrl->pwrd;
+ fdctrl->fifo[13] = 0;
+ fdctrl->fifo[14] = 0;
+ fdctrl_set_fifo(fdctrl, 15, 1);
+ return;
+ case 0x33:
+ /* OPTION */
+ FLOPPY_DPRINTF("OPTION command\n");
+ /* 1 parameter cmd */
+ fdctrl->data_len = 2;
+ goto enqueue;
+ case 0x42:
+ /* READ_TRACK */
+ FLOPPY_DPRINTF("READ_TRACK command\n");
+ /* 8 parameters cmd */
+ fdctrl->data_len = 9;
+ goto enqueue;
+ case 0x4A:
+ /* READ_ID */
+ FLOPPY_DPRINTF("READ_ID command\n");
+ /* 1 parameter cmd */
+ fdctrl->data_len = 2;
+ goto enqueue;
+ case 0x4C:
+ /* RESTORE */
+ FLOPPY_DPRINTF("RESTORE command\n");
+ /* 17 parameters cmd */
+ fdctrl->data_len = 18;
+ goto enqueue;
+ case 0x4D:
+ /* FORMAT_TRACK */
+ FLOPPY_DPRINTF("FORMAT_TRACK command\n");
+ /* 5 parameters cmd */
+ fdctrl->data_len = 6;
+ goto enqueue;
+ case 0x8E:
+ /* DRIVE_SPECIFICATION_COMMAND */
+ FLOPPY_DPRINTF("DRIVE_SPECIFICATION_COMMAND command\n");
+ /* 5 parameters cmd */
+ fdctrl->data_len = 6;
+ goto enqueue;
+ case 0x8F:
+ /* RELATIVE_SEEK_OUT */
+ FLOPPY_DPRINTF("RELATIVE_SEEK_OUT command\n");
+ /* 2 parameters cmd */
+ fdctrl->data_len = 3;
+ goto enqueue;
+ case 0x94:
+ /* LOCK */
+ FLOPPY_DPRINTF("LOCK command\n");
+ /* No parameters cmd */
+ fdctrl->lock = 1;
+ fdctrl->fifo[0] = 0x10;
+ fdctrl_set_fifo(fdctrl, 1, 1);
+ return;
+ case 0xCD:
+ /* FORMAT_AND_WRITE */
+ FLOPPY_DPRINTF("FORMAT_AND_WRITE command\n");
+ /* 10 parameters cmd */
+ fdctrl->data_len = 11;
+ goto enqueue;
+ case 0xCF:
+ /* RELATIVE_SEEK_IN */
+ FLOPPY_DPRINTF("RELATIVE_SEEK_IN command\n");
+ /* 2 parameters cmd */
+ fdctrl->data_len = 3;
+ goto enqueue;
+ default:
+ /* Unknown command */
+ FLOPPY_ERROR("unknown command: 0x%02x\n", value);
+ fdctrl_unimplemented(fdctrl);
+ return;
+ }
+ }
+enqueue:
+ FLOPPY_DPRINTF("%s: %02x\n", __func__, value);
+ fdctrl->fifo[fdctrl->data_pos] = value;
+ if (++fdctrl->data_pos == fdctrl->data_len) {
+ /* We now have all parameters
+ * and will be able to treat the command
+ */
+ if (fdctrl->data_state & FD_STATE_FORMAT) {
+ fdctrl_format_sector(fdctrl);
+ return;
+ }
+ switch (fdctrl->fifo[0] & 0x1F) {
+ case 0x06:
+ {
+ /* READ variants */
+ FLOPPY_DPRINTF("treat READ command\n");
+ fdctrl_start_transfer(fdctrl, FD_DIR_READ);
+ return;
+ }
+ case 0x0C:
+ /* READ_DELETED variants */
+/* // FLOPPY_DPRINTF("treat READ_DELETED command\n"); */
+ FLOPPY_ERROR("treat READ_DELETED command\n");
+ fdctrl_start_transfer_del(fdctrl, FD_DIR_READ);
+ return;
+ case 0x16:
+ /* VERIFY variants */
+/* // FLOPPY_DPRINTF("treat VERIFY command\n"); */
+ FLOPPY_ERROR("treat VERIFY command\n");
+ fdctrl_stop_transfer(fdctrl, 0x20, 0x00, 0x00);
+ return;
+ case 0x10:
+ /* SCAN_EQUAL variants */
+/* // FLOPPY_DPRINTF("treat SCAN_EQUAL command\n"); */
+ FLOPPY_ERROR("treat SCAN_EQUAL command\n");
+ fdctrl_start_transfer(fdctrl, FD_DIR_SCANE);
+ return;
+ case 0x19:
+ /* SCAN_LOW_OR_EQUAL variants */
+/* // FLOPPY_DPRINTF("treat SCAN_LOW_OR_EQUAL command\n"); */
+ FLOPPY_ERROR("treat SCAN_LOW_OR_EQUAL command\n");
+ fdctrl_start_transfer(fdctrl, FD_DIR_SCANL);
+ return;
+ case 0x1D:
+ /* SCAN_HIGH_OR_EQUAL variants */
+/* // FLOPPY_DPRINTF("treat SCAN_HIGH_OR_EQUAL command\n"); */
+ FLOPPY_ERROR("treat SCAN_HIGH_OR_EQUAL command\n");
+ fdctrl_start_transfer(fdctrl, FD_DIR_SCANH);
+ return;
+ default:
+ break;
+ }
+ switch (fdctrl->fifo[0] & 0x3F) {
+ case 0x05:
+ /* WRITE variants */
+ FLOPPY_DPRINTF("treat WRITE command (%02x)\n", fdctrl->fifo[0]);
+ fdctrl_start_transfer(fdctrl, FD_DIR_WRITE);
+ return;
+ case 0x09:
+ /* WRITE_DELETED variants */
+/* // FLOPPY_DPRINTF("treat WRITE_DELETED command\n"); */
+ FLOPPY_ERROR("treat WRITE_DELETED command\n");
+ fdctrl_start_transfer_del(fdctrl, FD_DIR_WRITE);
+ return;
+ default:
+ break;
+ }
+ switch (fdctrl->fifo[0]) {
+ case 0x03:
+ /* SPECIFY */
+ FLOPPY_DPRINTF("treat SPECIFY command\n");
+ fdctrl->timer0 = (fdctrl->fifo[1] >> 4) & 0xF;
+ fdctrl->timer1 = fdctrl->fifo[2] >> 1;
+ fdctrl->dma_en = 1 - (fdctrl->fifo[2] & 1) ;
+ /* No result back */
+ fdctrl_reset_fifo(fdctrl);
+ break;
+ case 0x04:
+ /* SENSE_DRIVE_STATUS */
+ FLOPPY_DPRINTF("treat SENSE_DRIVE_STATUS command\n");
+ fdctrl->cur_drv = fdctrl->fifo[1] & 1;
+ cur_drv = get_cur_drv(fdctrl);
+ cur_drv->head = (fdctrl->fifo[1] >> 2) & 1;
+ /* 1 Byte status back */
+ fdctrl->fifo[0] = (cur_drv->ro << 6) |
+ (cur_drv->track == 0 ? 0x10 : 0x00) |
+ (cur_drv->head << 2) |
+ fdctrl->cur_drv |
+ 0x28;
+ fdctrl_set_fifo(fdctrl, 1, 0);
+ break;
+ case 0x07:
+ /* RECALIBRATE */
+ FLOPPY_DPRINTF("treat RECALIBRATE command\n");
+ fdctrl->cur_drv = fdctrl->fifo[1] & 1;
+ cur_drv = get_cur_drv(fdctrl);
+ fd_recalibrate(cur_drv);
+ fdctrl_reset_fifo(fdctrl);
+ /* Raise Interrupt */
+ fdctrl_raise_irq(fdctrl, 0x20);
+ break;
+ case 0x0F:
+ /* SEEK */
+ FLOPPY_DPRINTF("treat SEEK command\n");
+ fdctrl->cur_drv = fdctrl->fifo[1] & 1;
+ cur_drv = get_cur_drv(fdctrl);
+ fd_start(cur_drv);
+ if (fdctrl->fifo[2] <= cur_drv->track)
+ cur_drv->dir = 1;
+ else
+ cur_drv->dir = 0;
+ fdctrl_reset_fifo(fdctrl);
+#ifndef VBOX
+ if (fdctrl->fifo[2] > cur_drv->max_track) {
+#else
+ if ( fdctrl->fifo[2] > cur_drv->max_track
+ && cur_drv->fMediaPresent) {
+#endif
+ fdctrl_raise_irq(fdctrl, 0x60);
+ } else {
+ cur_drv->track = fdctrl->fifo[2];
+ /* Raise Interrupt */
+ fdctrl_raise_irq(fdctrl, 0x20);
+ }
+ break;
+ case 0x12:
+ /* PERPENDICULAR_MODE */
+ FLOPPY_DPRINTF("treat PERPENDICULAR_MODE command\n");
+ if (fdctrl->fifo[1] & 0x80)
+ cur_drv->perpendicular = fdctrl->fifo[1] & 0x7;
+ /* No result back */
+ fdctrl_reset_fifo(fdctrl);
+ break;
+ case 0x13:
+ /* CONFIGURE */
+ FLOPPY_DPRINTF("treat CONFIGURE command\n");
+ fdctrl->config = fdctrl->fifo[2];
+ fdctrl->precomp_trk = fdctrl->fifo[3];
+ /* No result back */
+ fdctrl_reset_fifo(fdctrl);
+ break;
+ case 0x17:
+ /* POWERDOWN_MODE */
+ FLOPPY_DPRINTF("treat POWERDOWN_MODE command\n");
+ fdctrl->pwrd = fdctrl->fifo[1];
+ fdctrl->fifo[0] = fdctrl->fifo[1];
+ fdctrl_set_fifo(fdctrl, 1, 1);
+ break;
+ case 0x33:
+ /* OPTION */
+ FLOPPY_DPRINTF("treat OPTION command\n");
+ /* No result back */
+ fdctrl_reset_fifo(fdctrl);
+ break;
+ case 0x42:
+ /* READ_TRACK */
+/* // FLOPPY_DPRINTF("treat READ_TRACK command\n"); */
+ FLOPPY_ERROR("treat READ_TRACK command\n");
+ fdctrl_start_transfer(fdctrl, FD_DIR_READ);
+ break;
+ case 0x4A:
+ /* READ_ID */
+ FLOPPY_DPRINTF("treat READ_ID command\n");
+ /* XXX: should set main status register to busy */
+ cur_drv->head = (fdctrl->fifo[1] >> 2) & 1;
+#ifdef VBOX
+ TMTimerSetMillies(fdctrl->result_timer, 1000 / 50);
+#else
+ qemu_mod_timer(fdctrl->result_timer,
+ qemu_get_clock(vm_clock) + (ticks_per_sec / 50));
+#endif
+ break;
+ case 0x4C:
+ /* RESTORE */
+ FLOPPY_DPRINTF("treat RESTORE command\n");
+ /* Drives position */
+ drv0(fdctrl)->track = fdctrl->fifo[3];
+ drv1(fdctrl)->track = fdctrl->fifo[4];
+ /* timers */
+ fdctrl->timer0 = fdctrl->fifo[7];
+ fdctrl->timer1 = fdctrl->fifo[8];
+ cur_drv->last_sect = fdctrl->fifo[9];
+ fdctrl->lock = fdctrl->fifo[10] >> 7;
+ cur_drv->perpendicular = (fdctrl->fifo[10] >> 2) & 0xF;
+ fdctrl->config = fdctrl->fifo[11];
+ fdctrl->precomp_trk = fdctrl->fifo[12];
+ fdctrl->pwrd = fdctrl->fifo[13];
+ fdctrl_reset_fifo(fdctrl);
+ break;
+ case 0x4D:
+ /* FORMAT_TRACK */
+ FLOPPY_DPRINTF("treat FORMAT_TRACK command\n");
+ fdctrl->cur_drv = fdctrl->fifo[1] & 1;
+ cur_drv = get_cur_drv(fdctrl);
+ fdctrl->data_state |= FD_STATE_FORMAT;
+ if (fdctrl->fifo[0] & 0x80)
+ fdctrl->data_state |= FD_STATE_MULTI;
+ else
+ fdctrl->data_state &= ~FD_STATE_MULTI;
+ fdctrl->data_state &= ~FD_STATE_SEEK;
+ cur_drv->bps =
+ fdctrl->fifo[2] > 7 ? 16384 : 128 << fdctrl->fifo[2];
+#if 0
+ cur_drv->last_sect =
+ cur_drv->flags & FDISK_DBL_SIDES ? fdctrl->fifo[3] :
+ fdctrl->fifo[3] / 2;
+#else
+ cur_drv->last_sect = fdctrl->fifo[3];
+#endif
+ /* Bochs BIOS is buggy and don't send format informations
+ * for each sector. So, pretend all's done right now...
+ */
+ fdctrl->data_state &= ~FD_STATE_FORMAT;
+ fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00);
+ break;
+ case 0x8E:
+ /* DRIVE_SPECIFICATION_COMMAND */
+ FLOPPY_DPRINTF("treat DRIVE_SPECIFICATION_COMMAND command\n");
+ if (fdctrl->fifo[fdctrl->data_pos - 1] & 0x80) {
+ /* Command parameters done */
+ if (fdctrl->fifo[fdctrl->data_pos - 1] & 0x40) {
+ fdctrl->fifo[0] = fdctrl->fifo[1];
+ fdctrl->fifo[2] = 0;
+ fdctrl->fifo[3] = 0;
+ fdctrl_set_fifo(fdctrl, 4, 1);
+ } else {
+ fdctrl_reset_fifo(fdctrl);
+ }
+ } else if (fdctrl->data_len > 7) {
+ /* ERROR */
+ fdctrl->fifo[0] = 0x80 |
+ (cur_drv->head << 2) | fdctrl->cur_drv;
+ fdctrl_set_fifo(fdctrl, 1, 1);
+ }
+ break;
+ case 0x8F:
+ /* RELATIVE_SEEK_OUT */
+ FLOPPY_DPRINTF("treat RELATIVE_SEEK_OUT command\n");
+ fdctrl->cur_drv = fdctrl->fifo[1] & 1;
+ cur_drv = get_cur_drv(fdctrl);
+ fd_start(cur_drv);
+ cur_drv->dir = 0;
+ if (fdctrl->fifo[2] + cur_drv->track >= cur_drv->max_track) {
+ cur_drv->track = cur_drv->max_track - 1;
+ } else {
+ cur_drv->track += fdctrl->fifo[2];
+ }
+ fdctrl_reset_fifo(fdctrl);
+ fdctrl_raise_irq(fdctrl, 0x20);
+ break;
+ case 0xCD:
+ /* FORMAT_AND_WRITE */
+/* // FLOPPY_DPRINTF("treat FORMAT_AND_WRITE command\n"); */
+ FLOPPY_ERROR("treat FORMAT_AND_WRITE command\n");
+ fdctrl_unimplemented(fdctrl);
+ break;
+ case 0xCF:
+ /* RELATIVE_SEEK_IN */
+ FLOPPY_DPRINTF("treat RELATIVE_SEEK_IN command\n");
+ fdctrl->cur_drv = fdctrl->fifo[1] & 1;
+ cur_drv = get_cur_drv(fdctrl);
+ fd_start(cur_drv);
+ cur_drv->dir = 1;
+ if (fdctrl->fifo[2] > cur_drv->track) {
+ cur_drv->track = 0;
+ } else {
+ cur_drv->track -= fdctrl->fifo[2];
+ }
+ fdctrl_reset_fifo(fdctrl);
+ /* Raise Interrupt */
+ fdctrl_raise_irq(fdctrl, 0x20);
+ break;
+ }
+ }
+}
+
+static void fdctrl_result_timer(void *opaque)
+{
+ fdctrl_t *fdctrl = opaque;
+ fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00);
+}
+
+
+#ifdef VBOX
+static DECLCALLBACK(void) fdc_timer (PPDMDEVINS pDevIns, PTMTIMER pTimer)
+{
+ fdctrl_t *fdctrl = PDMINS_2_DATA (pDevIns, fdctrl_t *);
+ fdctrl_result_timer (fdctrl);
+}
+
+static DECLCALLBACK(int) fdc_io_write (PPDMDEVINS pDevIns,
+ void *pvUser,
+ RTIOPORT Port,
+ uint32_t u32,
+ unsigned cb)
+{
+ if (cb == 1) {
+ fdctrl_write (pvUser, Port, u32);
+ }
+ else {
+ AssertMsgFailed(("Port=%#x cb=%d u32=%#x\n", Port, cb, u32));
+ }
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) fdc_io_read (PPDMDEVINS pDevIns,
+ void *pvUser,
+ RTIOPORT Port,
+ uint32_t *pu32,
+ unsigned cb)
+{
+ if (cb == 1) {
+ *pu32 = fdctrl_read (pvUser, Port);
+ return VINF_SUCCESS;
+ }
+ else {
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+}
+
+static DECLCALLBACK(int) SaveExec (PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle)
+{
+ fdctrl_t *s = PDMINS_2_DATA (pDevIns, fdctrl_t *);
+ QEMUFile *f = pSSMHandle;
+ unsigned int i;
+
+ qemu_put_8s (f, &s->version);
+ qemu_put_8s (f, &s->irq_lvl);
+ qemu_put_8s (f, &s->dma_chann);
+ qemu_put_be32s (f, &s->io_base);
+ qemu_put_8s (f, &s->state);
+ qemu_put_8s (f, &s->dma_en);
+ qemu_put_8s (f, &s->cur_drv);
+ qemu_put_8s (f, &s->bootsel);
+ qemu_put_buffer (f, s->fifo, FD_SECTOR_LEN);
+ qemu_put_be32s (f, &s->data_pos);
+ qemu_put_be32s (f, &s->data_len);
+ qemu_put_8s (f, &s->data_state);
+ qemu_put_8s (f, &s->data_dir);
+ qemu_put_8s (f, &s->int_status);
+ qemu_put_8s (f, &s->eot);
+ qemu_put_8s (f, &s->timer0);
+ qemu_put_8s (f, &s->timer1);
+ qemu_put_8s (f, &s->precomp_trk);
+ qemu_put_8s (f, &s->config);
+ qemu_put_8s (f, &s->lock);
+ qemu_put_8s (f, &s->pwrd);
+
+ for (i = 0; i < sizeof (s->drives) / sizeof (s->drives[0]); ++i) {
+ fdrive_t *d = &s->drives[i];
+
+ SSMR3PutMem (pSSMHandle, &d->Led, sizeof (d->Led));
+ qemu_put_be32s (f, &d->drive);
+ qemu_put_be32s (f, &d->drflags);
+ qemu_put_8s (f, &d->perpendicular);
+ qemu_put_8s (f, &d->head);
+ qemu_put_8s (f, &d->track);
+ qemu_put_8s (f, &d->sect);
+ qemu_put_8s (f, &d->dir);
+ qemu_put_8s (f, &d->rw);
+ qemu_put_be32s (f, &d->flags);
+ qemu_put_8s (f, &d->last_sect);
+ qemu_put_8s (f, &d->max_track);
+ qemu_put_be16s (f, &d->bps);
+ qemu_put_8s (f, &d->ro);
+ }
+ return TMR3TimerSave (s->result_timer, pSSMHandle);
+}
+
+static DECLCALLBACK(int) LoadExec (PPDMDEVINS pDevIns,
+ PSSMHANDLE pSSMHandle,
+ uint32_t u32Version)
+{
+ fdctrl_t *s = PDMINS_2_DATA (pDevIns, fdctrl_t *);
+ QEMUFile *f = pSSMHandle;
+ unsigned int i;
+
+ if (u32Version != 1) {
+ AssertMsgFailed(("u32Version=%d\n", u32Version));
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ }
+
+ qemu_get_8s (f, &s->version);
+ qemu_get_8s (f, &s->irq_lvl);
+ qemu_get_8s (f, &s->dma_chann);
+ qemu_get_be32s (f, &s->io_base);
+ qemu_get_8s (f, &s->state);
+ qemu_get_8s (f, &s->dma_en);
+ qemu_get_8s (f, &s->cur_drv);
+ qemu_get_8s (f, &s->bootsel);
+ qemu_get_buffer (f, s->fifo, FD_SECTOR_LEN);
+ qemu_get_be32s (f, &s->data_pos);
+ qemu_get_be32s (f, &s->data_len);
+ qemu_get_8s (f, &s->data_state);
+ qemu_get_8s (f, &s->data_dir);
+ qemu_get_8s (f, &s->int_status);
+ qemu_get_8s (f, &s->eot);
+ qemu_get_8s (f, &s->timer0);
+ qemu_get_8s (f, &s->timer1);
+ qemu_get_8s (f, &s->precomp_trk);
+ qemu_get_8s (f, &s->config);
+ qemu_get_8s (f, &s->lock);
+ qemu_get_8s (f, &s->pwrd);
+
+ for (i = 0; i < sizeof (s->drives) / sizeof (s->drives[0]); ++i) {
+ fdrive_t *d = &s->drives[i];
+
+ SSMR3GetMem (pSSMHandle, &d->Led, sizeof (d->Led));
+ qemu_get_be32s (f, &d->drive);
+ qemu_get_be32s (f, &d->drflags);
+ qemu_get_8s (f, &d->perpendicular);
+ qemu_get_8s (f, &d->head);
+ qemu_get_8s (f, &d->track);
+ qemu_get_8s (f, &d->sect);
+ qemu_get_8s (f, &d->dir);
+ qemu_get_8s (f, &d->rw);
+ qemu_get_be32s (f, &d->flags);
+ qemu_get_8s (f, &d->last_sect);
+ qemu_get_8s (f, &d->max_track);
+ qemu_get_be16s (f, &d->bps);
+ qemu_get_8s (f, &d->ro);
+ }
+ return TMR3TimerLoad (s->result_timer, pSSMHandle);
+}
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the device.
+ * @param pInterface Pointer to IDEState::IBase.
+ * @param enmInterface The requested interface identification.
+ */
+static DECLCALLBACK(void *) fdQueryInterface (PPDMIBASE pInterface,
+ PDMINTERFACE enmInterface)
+{
+ fdrive_t *drv = PDMIBASE_2_FDRIVE(pInterface);
+ switch (enmInterface) {
+ case PDMINTERFACE_BASE:
+ return &drv->IBase;
+ case PDMINTERFACE_BLOCK_PORT:
+ return &drv->IPort;
+ case PDMINTERFACE_MOUNT_NOTIFY:
+ return &drv->IMountNotify;
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) fdcStatusQueryStatusLed (PPDMILEDPORTS pInterface,
+ unsigned iLUN,
+ PPDMLED *ppLed)
+{
+ fdctrl_t *fdctrl = (fdctrl_t *)
+ ((uintptr_t )pInterface - RT_OFFSETOF (fdctrl_t, ILeds));
+ if (iLUN < RT_ELEMENTS(fdctrl->drives)) {
+ *ppLed = &fdctrl->drives[iLUN].Led;
+ Assert ((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/**
+ * Queries an interface to the status LUN.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the device.
+ * @param pInterface Pointer to IDEState::IBase.
+ * @param enmInterface The requested interface identification.
+ */
+static DECLCALLBACK(void *) fdcStatusQueryInterface (PPDMIBASE pInterface,
+ PDMINTERFACE enmInterface)
+{
+ fdctrl_t *fdctrl = (fdctrl_t *)
+ ((uintptr_t)pInterface - RT_OFFSETOF (fdctrl_t, IBaseStatus));
+ switch (enmInterface) {
+ case PDMINTERFACE_BASE:
+ return &fdctrl->IBaseStatus;
+ case PDMINTERFACE_LED_PORTS:
+ return &fdctrl->ILeds;
+ default:
+ return NULL;
+ }
+}
+
+
+/**
+ * Configure a drive.
+ *
+ * @returns VBox status code.
+ * @param drv The drive in question.
+ * @param pDevIns The driver instance.
+ */
+static int fdConfig (fdrive_t *drv, PPDMDEVINS pDevIns)
+{
+ static const char *descs[] = {"Floppy Drive A:", "Floppy Drive B"};
+ int rc;
+
+ /*
+ * Reset the LED just to be on the safe side.
+ */
+ Assert (RT_ELEMENTS(descs) > drv->iLUN);
+ Assert (drv->Led.u32Magic == PDMLED_MAGIC);
+ drv->Led.Actual.u32 = 0;
+ drv->Led.Asserted.u32 = 0;
+
+ /*
+ * Try attach the block device and get the interfaces.
+ */
+ rc = PDMDevHlpDriverAttach (pDevIns, drv->iLUN, &drv->IBase, &drv->pDrvBase, descs[drv->iLUN]);
+ if (RT_SUCCESS (rc)) {
+ drv->pDrvBlock = drv->pDrvBase->pfnQueryInterface (
+ drv->pDrvBase,
+ PDMINTERFACE_BLOCK
+ );
+ if (drv->pDrvBlock) {
+ drv->pDrvBlockBios = drv->pDrvBase->pfnQueryInterface (
+ drv->pDrvBase,
+ PDMINTERFACE_BLOCK_BIOS
+ );
+ if (drv->pDrvBlockBios) {
+ drv->pDrvMount = drv->pDrvBase->pfnQueryInterface (
+ drv->pDrvBase,
+ PDMINTERFACE_MOUNT
+ );
+ if (drv->pDrvMount) {
+ /*
+ * Init the state.
+ */
+ drv->drive = FDRIVE_DRV_NONE;
+ drv->drflags = 0;
+ drv->perpendicular = 0;
+ /* Disk */
+ drv->last_sect = 0;
+ drv->max_track = 0;
+ drv->fMediaPresent = false;
+ } else {
+ AssertMsgFailed (("Configuration error: LUN#%d without mountable interface!\n", drv->iLUN));
+ rc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ } else {
+ AssertMsgFailed (("Configuration error: LUN#%d hasn't a block BIOS interface!\n", drv->iLUN));
+ rc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ } else {
+ AssertMsgFailed (("Configuration error: LUN#%d hasn't a block interface!\n", drv->iLUN));
+ rc = VERR_PDM_MISSING_INTERFACE;
+ }
+ } else {
+ AssertMsg (rc == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Failed to attach LUN#%d. rc=%Rrc\n", drv->iLUN, rc));
+ switch (rc) {
+ case VERR_ACCESS_DENIED:
+ /* Error already catched by DrvHostBase */
+ break;
+ case VERR_PDM_NO_ATTACHED_DRIVER:
+ /* Legal on architectures without a floppy controller */
+ break;
+ default:
+ rc = PDMDevHlpVMSetError (pDevIns, rc, RT_SRC_POS,
+ N_ ("The floppy controller cannot attach to the floppy drive"));
+ break;
+ }
+ }
+
+ if (RT_FAILURE (rc)) {
+ drv->pDrvBase = NULL;
+ drv->pDrvBlock = NULL;
+ drv->pDrvBlockBios = NULL;
+ drv->pDrvMount = NULL;
+ }
+ LogFlow (("fdConfig: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Attach command.
+ *
+ * This is called when we change block driver for a floppy drive.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ */
+static DECLCALLBACK(int) fdcAttach (PPDMDEVINS pDevIns,
+ unsigned iLUN)
+{
+ fdctrl_t *fdctrl = PDMINS_2_DATA (pDevIns, fdctrl_t *);
+ fdrive_t *drv;
+ int rc;
+ LogFlow (("ideDetach: iLUN=%u\n", iLUN));
+
+ /*
+ * Validate.
+ */
+ if (iLUN > 2) {
+ AssertMsgFailed (("Configuration error: cannot attach or detach any but the first two LUNs - iLUN=%u\n",
+ iLUN));
+ return VERR_PDM_DEVINS_NO_ATTACH;
+ }
+
+ /*
+ * Locate the drive and stuff.
+ */
+ drv = &fdctrl->drives[iLUN];
+
+ /* the usual paranoia */
+ AssertRelease (!drv->pDrvBase);
+ AssertRelease (!drv->pDrvBlock);
+ AssertRelease (!drv->pDrvBlockBios);
+ AssertRelease (!drv->pDrvMount);
+
+ rc = fdConfig (drv, pDevIns);
+ AssertMsg (rc != VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: failed to configure drive %d, rc=%Rrc\n", rc));
+ if (RT_SUCCESS(rc)) {
+ fd_revalidate (drv);
+ }
+
+ LogFlow (("floppyAttach: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Detach notification.
+ *
+ * The floppy drive has been temporarily 'unplugged'.
+ *
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ */
+static DECLCALLBACK(void) fdcDetach (PPDMDEVINS pDevIns,
+ unsigned iLUN)
+{
+ fdctrl_t *fdctrl = PDMINS_2_DATA (pDevIns, fdctrl_t *);
+ LogFlow (("ideDetach: iLUN=%u\n", iLUN));
+
+ switch (iLUN) {
+ case 0:
+ case 1: {
+ fdrive_t *drv = &fdctrl->drives[iLUN];
+ drv->pDrvBase = NULL;
+ drv->pDrvBlock = NULL;
+ drv->pDrvBlockBios = NULL;
+ drv->pDrvMount = NULL;
+ break;
+ }
+
+ default:
+ AssertMsgFailed (("Cannot detach LUN#%d!\n", iLUN));
+ break;
+ }
+}
+
+
+/**
+ * Handle reset.
+ *
+ * I haven't check the specs on what's supposed to happen on reset, but we
+ * should get any 'FATAL: floppy recal:f07 ctrl not ready' when resetting
+ * at wrong time like we do if this was all void.
+ *
+ * @param pDevIns The device instance.
+ */
+static DECLCALLBACK(void) fdcReset (PPDMDEVINS pDevIns)
+{
+ fdctrl_t *fdctrl = PDMINS_2_DATA (pDevIns, fdctrl_t *);
+ unsigned i;
+ LogFlow (("fdcReset:\n"));
+
+ fdctrl_reset(fdctrl, 0);
+ fdctrl->state = FD_CTRL_ACTIVE;
+
+ for (i = 0; i < RT_ELEMENTS(fdctrl->drives); i++) {
+ fd_revalidate(&fdctrl->drives[i]);
+ }
+}
+
+
+/**
+ * Construct a device instance for a VM.
+ *
+ * @returns VBox status.
+ * @param pDevIns The device instance data.
+ * If the registration structure is needed, pDevIns->pDevReg points to it.
+ * @param iInstance Instance number. Use this to figure out which registers and such to use.
+ * The device number is also found in pDevIns->iInstance, but since it's
+ * likely to be freqently used PDM passes it as parameter.
+ * @param pCfgHandle Configuration node handle for the device. Use this to obtain the configuration
+ * of the device instance. It's also found in pDevIns->pCfgHandle, but like
+ * iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) fdcConstruct (PPDMDEVINS pDevIns,
+ int iInstance,
+ PCFGMNODE pCfgHandle)
+{
+ int rc;
+ fdctrl_t *fdctrl = PDMINS_2_DATA(pDevIns, fdctrl_t*);
+ unsigned i;
+ bool mem_mapped;
+ uint16_t io_base;
+ uint8_t irq_lvl, dma_chann;
+ PPDMIBASE pBase;
+
+ Assert(iInstance == 0);
+
+ /*
+ * Validate configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfgHandle, "IRQ\0DMA\0MemMapped\0IOBase\0"))
+ return VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES;
+
+ /*
+ * Read the configuration.
+ */
+ rc = CFGMR3QueryU8 (pCfgHandle, "IRQ", &irq_lvl);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ irq_lvl = 6;
+ else if (RT_FAILURE (rc)) {
+ AssertMsgFailed (("Configuration error: Failed to read U8 IRQ, rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ rc = CFGMR3QueryU8 (pCfgHandle, "DMA", &dma_chann);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ dma_chann = 2;
+ else if (RT_FAILURE (rc)) {
+ AssertMsgFailed (("Configuration error: Failed to read U8 DMA, rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ rc = CFGMR3QueryU16 (pCfgHandle, "IOBase", &io_base);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ io_base = 0x3f0;
+ else if (RT_FAILURE (rc)) {
+ AssertMsgFailed (("Configuration error: Failed to read U16 IOBase, rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ rc = CFGMR3QueryBool (pCfgHandle, "MemMapped", &mem_mapped);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ mem_mapped = false;
+ else if (RT_FAILURE (rc)) {
+ AssertMsgFailed (("Configuration error: Failed to read bool value MemMapped rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Initialize data.
+ */
+ LogFlow(("fdcConstruct: irq_lvl=%d dma_chann=%d io_base=%#x\n", irq_lvl, dma_chann, io_base));
+ fdctrl->pDevIns = pDevIns;
+ fdctrl->version = 0x90; /* Intel 82078 controller */
+ fdctrl->irq_lvl = irq_lvl;
+ fdctrl->dma_chann = dma_chann;
+ fdctrl->io_base = io_base;
+ fdctrl->config = 0x60; /* Implicit seek, polling & FIFO enabled */
+
+ fdctrl->IBaseStatus.pfnQueryInterface = fdcStatusQueryInterface;
+ fdctrl->ILeds.pfnQueryStatusLed = fdcStatusQueryStatusLed;
+
+ for (i = 0; i < RT_ELEMENTS(fdctrl->drives); ++i) {
+ fdrive_t *drv = &fdctrl->drives[i];
+
+ drv->drive = FDRIVE_DRV_NONE;
+ drv->iLUN = i;
+
+ drv->IBase.pfnQueryInterface = fdQueryInterface;
+ drv->IMountNotify.pfnMountNotify = fdMountNotify;
+ drv->IMountNotify.pfnUnmountNotify = fdUnmountNotify;
+ drv->Led.u32Magic = PDMLED_MAGIC;
+ }
+
+ /*
+ * Create the FDC timer.
+ */
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, fdc_timer, "FDC Timer", &fdctrl->result_timer);
+ if (RT_FAILURE (rc))
+ return rc;
+
+ /*
+ * Register DMA channel.
+ */
+ if (fdctrl->dma_chann != 0xff) {
+ fdctrl->dma_en = 1;
+ rc = PDMDevHlpDMARegister (pDevIns, dma_chann, &fdctrl_transfer_handler, fdctrl);
+ if (RT_FAILURE (rc))
+ return rc;
+ } else
+ fdctrl->dma_en = 0;
+
+ /*
+ * IO / MMIO.
+ */
+ if (mem_mapped) {
+ AssertMsgFailed (("Memory mapped floppy not support by now\n"));
+ return VERR_NOT_SUPPORTED;
+#if 0
+ FLOPPY_ERROR("memory mapped floppy not supported by now !\n");
+ io_mem = cpu_register_io_memory(0, fdctrl_mem_read, fdctrl_mem_write);
+ cpu_register_physical_memory(base, 0x08, io_mem);
+#endif
+ } else {
+ rc = PDMDevHlpIOPortRegister (pDevIns, io_base + 0x1, 5, fdctrl,
+ fdc_io_write, fdc_io_read, NULL, NULL, "FDC#1");
+ if (RT_FAILURE (rc))
+ return rc;
+
+ rc = PDMDevHlpIOPortRegister (pDevIns, io_base + 0x7, 1, fdctrl,
+ fdc_io_write, fdc_io_read, NULL, NULL, "FDC#2");
+ if (RT_FAILURE (rc))
+ return rc;
+ }
+
+ /*
+ * Register the saved state data unit.
+ */
+ rc = PDMDevHlpSSMRegister (pDevIns, pDevIns->pDevReg->szDeviceName, iInstance, 1, sizeof(*fdctrl),
+ NULL, SaveExec, NULL, NULL, LoadExec, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Attach the status port (optional).
+ */
+ rc = PDMDevHlpDriverAttach (pDevIns, PDM_STATUS_LUN, &fdctrl->IBaseStatus, &pBase, "Status Port");
+ if (RT_SUCCESS (rc)) {
+ fdctrl->pLedsConnector =
+ pBase->pfnQueryInterface (pBase, PDMINTERFACE_LED_CONNECTORS);
+ } else if (rc != VERR_PDM_NO_ATTACHED_DRIVER) {
+ AssertMsgFailed (("Failed to attach to status driver. rc=%Rrc\n",
+ rc));
+ return rc;
+ }
+
+ /*
+ * Initialize drives.
+ */
+ for (i = 0; i < RT_ELEMENTS(fdctrl->drives); i++) {
+ fdrive_t *drv = &fdctrl->drives[i];
+ rc = fdConfig (drv, pDevIns);
+ if ( RT_FAILURE (rc)
+ && rc != VERR_PDM_NO_ATTACHED_DRIVER) {
+ AssertMsgFailed (("Configuration error: failed to configure drive %d, rc=%Rrc\n", rc));
+ return rc;
+ }
+ }
+
+ fdctrl_reset(fdctrl, 0);
+ fdctrl->state = FD_CTRL_ACTIVE;
+
+ for (i = 0; i < RT_ELEMENTS(fdctrl->drives); i++)
+ fd_revalidate(&fdctrl->drives[i]);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceFloppyController =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szDeviceName */
+ "i82078",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Floppy drive controller (Intel 82078)",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS,
+ /* fClass */
+ PDM_DEVREG_CLASS_STORAGE,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(fdctrl_t),
+ /* pfnConstruct */
+ fdcConstruct,
+ /* pfnDestruct */
+ NULL,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ fdcReset,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ fdcAttach,
+ /* pfnDetach */
+ fdcDetach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+#endif /* VBOX */
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-file-style: "k&r"
+ * indent-tabs-mode: nil
+ * End:
+ */
+
diff --git a/src/VBox/Devices/Storage/ide.h b/src/VBox/Devices/Storage/ide.h
new file mode 100644
index 000000000..b4b234531
--- /dev/null
+++ b/src/VBox/Devices/Storage/ide.h
@@ -0,0 +1,190 @@
+/* $Id: ide.h 12931 2008-10-02 11:46:56Z vboxsync $ */
+/** @file
+ * VBox storage devices: ATA/ATAPI declarations
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+#ifndef ___Storage_IDE_h
+#define ___Storage_IDE_h
+
+
+/* Bits of HD_STATUS */
+#define ATA_STAT_ERR 0x01
+#define ATA_STAT_INDEX 0x02
+#define ATA_STAT_ECC 0x04 /* Corrected error */
+#define ATA_STAT_DRQ 0x08
+#define ATA_STAT_SEEK 0x10
+#define ATA_STAT_SRV 0x10
+#define ATA_STAT_WRERR 0x20
+#define ATA_STAT_READY 0x40
+#define ATA_STAT_BUSY 0x80
+
+/* Bits for HD_ERROR */
+#define MARK_ERR 0x01 /* Bad address mark */
+#define TRK0_ERR 0x02 /* couldn't find track 0 */
+#define ABRT_ERR 0x04 /* Command aborted */
+#define MCR_ERR 0x08 /* media change request */
+#define ID_ERR 0x10 /* ID field not found */
+#define MC_ERR 0x20 /* media changed */
+#define ECC_ERR 0x40 /* Uncorrectable ECC error */
+#define BBD_ERR 0x80 /* pre-EIDE meaning: block marked bad */
+#define ICRC_ERR 0x80 /* new meaning: CRC error during transfer */
+
+/* Bits for uATARegDevCtl. */
+#define ATA_DEVCTL_DISABLE_IRQ 0x02
+#define ATA_DEVCTL_RESET 0x04
+#define ATA_DEVCTL_HOB 0x80
+
+
+/* ATA/ATAPI Commands (as of ATA/ATAPI-8 draft T13/1699D Revision 3a).
+ * Please keep this in sync with g_apszATACmdNames. */
+typedef enum ATACMD
+{
+ ATA_NOP = 0x00,
+ ATA_CFA_REQUEST_EXTENDED_ERROR_CODE = 0x03,
+ ATA_DEVICE_RESET = 0x08,
+ ATA_RECALIBRATE = 0x10,
+ ATA_READ_SECTORS = 0x20,
+ ATA_READ_SECTORS_WITHOUT_RETRIES = 0x21,
+ ATA_READ_LONG = 0x22,
+ ATA_READ_LONG_WITHOUT_RETRIES = 0x23,
+ ATA_READ_SECTORS_EXT = 0x24,
+ ATA_READ_DMA_EXT = 0x25,
+ ATA_READ_DMA_QUEUED_EXT = 0x26,
+ ATA_READ_NATIVE_MAX_ADDRESS_EXT = 0x27,
+ ATA_READ_MULTIPLE_EXT = 0x29,
+ ATA_READ_STREAM_DMA_EXT = 0x2a,
+ ATA_READ_STREAM_EXT = 0x2b,
+ ATA_READ_LOG_EXT = 0x2f,
+ ATA_WRITE_SECTORS = 0x30,
+ ATA_WRITE_SECTORS_WITHOUT_RETRIES = 0x31,
+ ATA_WRITE_LONG = 0x32,
+ ATA_WRITE_LONG_WITHOUT_RETRIES = 0x33,
+ ATA_WRITE_SECTORS_EXT = 0x34,
+ ATA_WRITE_DMA_EXT = 0x35,
+ ATA_WRITE_DMA_QUEUED_EXT = 0x36,
+ ATA_SET_MAX_ADDRESS_EXT = 0x37,
+ ATA_CFA_WRITE_SECTORS_WITHOUT_ERASE = 0x38,
+ ATA_WRITE_MULTIPLE_EXT = 0x39,
+ ATA_WRITE_STREAM_DMA_EXT = 0x3a,
+ ATA_WRITE_STREAM_EXT = 0x3b,
+ ATA_WRITE_VERIFY = 0x3c,
+ ATA_WRITE_DMA_FUA_EXT = 0x3d,
+ ATA_WRITE_DMA_QUEUED_FUA_EXT = 0x3e,
+ ATA_WRITE_LOG_EXT = 0x3f,
+ ATA_READ_VERIFY_SECTORS = 0x40,
+ ATA_READ_VERIFY_SECTORS_WITHOUT_RETRIES = 0x41,
+ ATA_READ_VERIFY_SECTORS_EXT = 0x42,
+ ATA_WRITE_UNCORRECTABLE_EXT = 0x45,
+ ATA_READ_LOG_DMA_EXT = 0x47,
+ ATA_FORMAT_TRACK = 0x50,
+ ATA_CONFIGURE_STREAM = 0x51,
+ ATA_WRITE_LOG_DMA_EXT = 0x57,
+ ATA_TRUSTED_RECEIVE = 0x5c,
+ ATA_TRUSTED_RECEIVE_DMA = 0x5d,
+ ATA_TRUSTED_SEND = 0x5e,
+ ATA_TRUSTED_SEND_DMA = 0x5f,
+ ATA_READ_FPDMA_QUEUED = 0x60,
+ ATA_WRITE_FPDMA_QUEUED = 0x61,
+ ATA_SEEK = 0x70,
+ ATA_CFA_TRANSLATE_SECTOR = 0x87,
+ ATA_EXECUTE_DEVICE_DIAGNOSTIC = 0x90,
+ ATA_INITIALIZE_DEVICE_PARAMETERS = 0x91,
+ ATA_DOWNLOAD_MICROCODE = 0x92,
+ ATA_STANDBY_IMMEDIATE__ALT = 0x94,
+ ATA_IDLE_IMMEDIATE__ALT = 0x95,
+ ATA_STANDBY__ALT = 0x96,
+ ATA_IDLE__ALT = 0x97,
+ ATA_CHECK_POWER_MODE__ALT = 0x98,
+ ATA_SLEEP__ALT = 0x99,
+ ATA_PACKET = 0xa0,
+ ATA_IDENTIFY_PACKET_DEVICE = 0xa1,
+ ATA_SERVICE = 0xa2,
+ ATA_SMART = 0xb0,
+ ATA_DEVICE_CONFIGURATION_OVERLAY = 0xb1,
+ ATA_NV_CACHE = 0xb6,
+ ATA_CFA_ERASE_SECTORS = 0xc0,
+ ATA_READ_MULTIPLE = 0xc4,
+ ATA_WRITE_MULTIPLE = 0xc5,
+ ATA_SET_MULTIPLE_MODE = 0xc6,
+ ATA_READ_DMA_QUEUED = 0xc7,
+ ATA_READ_DMA = 0xc8,
+ ATA_READ_DMA_WITHOUT_RETRIES = 0xc9,
+ ATA_WRITE_DMA = 0xca,
+ ATA_WRITE_DMA_WITHOUT_RETRIES = 0xcb,
+ ATA_WRITE_DMA_QUEUED = 0xcc,
+ ATA_CFA_WRITE_MULTIPLE_WITHOUT_ERASE = 0xcd,
+ ATA_WRITE_MULTIPLE_FUA_EXT = 0xce,
+ ATA_CHECK_MEDIA_CARD_TYPE = 0xd1,
+ ATA_GET_MEDIA_STATUS = 0xda,
+ ATA_ACKNOWLEDGE_MEDIA_CHANGE = 0xdb,
+ ATA_BOOT_POST_BOOT = 0xdc,
+ ATA_BOOT_PRE_BOOT = 0xdd,
+ ATA_MEDIA_LOCK = 0xde,
+ ATA_MEDIA_UNLOCK = 0xdf,
+ ATA_STANDBY_IMMEDIATE = 0xe0,
+ ATA_IDLE_IMMEDIATE = 0xe1,
+ ATA_STANDBY = 0xe2,
+ ATA_IDLE = 0xe3,
+ ATA_READ_BUFFER = 0xe4,
+ ATA_CHECK_POWER_MODE = 0xe5,
+ ATA_SLEEP = 0xe6,
+ ATA_FLUSH_CACHE = 0xe7,
+ ATA_WRITE_BUFFER = 0xe8,
+ ATA_WRITE_SAME = 0xe9,
+ ATA_FLUSH_CACHE_EXT = 0xea,
+ ATA_IDENTIFY_DEVICE = 0xec,
+ ATA_MEDIA_EJECT = 0xed,
+ ATA_IDENTIFY_DMA = 0xee,
+ ATA_SET_FEATURES = 0xef,
+ ATA_SECURITY_SET_PASSWORD = 0xf1,
+ ATA_SECURITY_UNLOCK = 0xf2,
+ ATA_SECURITY_ERASE_PREPARE = 0xf3,
+ ATA_SECURITY_ERASE_UNIT = 0xf4,
+ ATA_SECURITY_FREEZE_LOCK = 0xf5,
+ ATA_SECURITY_DISABLE_PASSWORD = 0xf6,
+ ATA_READ_NATIVE_MAX_ADDRESS = 0xf8,
+ ATA_SET_MAX = 0xf9
+} ATACMD;
+
+
+#define ATA_MODE_MDMA 0x20
+#define ATA_MODE_UDMA 0x40
+
+
+#define ATA_TRANSFER_ID(thismode, maxspeed, currmode) \
+ ( ((1 << (maxspeed + 1)) - 1) \
+ | ((((thismode ^ currmode) & 0xf8) == 0) ? 1 << ((currmode & 0x07) + 8) : 0))
+
+
+/* ATAPI defines */
+
+#define ATAPI_PACKET_SIZE 12
+
+
+#define ATAPI_INT_REASON_CD 0x01 /* 0 = data transfer */
+#define ATAPI_INT_REASON_IO 0x02 /* 1 = transfer to the host */
+#define ATAPI_INT_REASON_REL 0x04
+#define ATAPI_INT_REASON_TAG_MASK 0xf8
+
+#if defined(DEBUG) && defined(IN_RING3)
+const char * ATACmdText(uint8_t uCmd);
+#endif
+
+#endif /* !___Storage_IDE_h */
+
diff --git a/src/VBox/Devices/Storage/swab.h b/src/VBox/Devices/Storage/swab.h
new file mode 100644
index 000000000..44f0d3fc4
--- /dev/null
+++ b/src/VBox/Devices/Storage/swab.h
@@ -0,0 +1,64 @@
+/** @file
+ *
+ * VBox storage devices:
+ * C++-safe replacements for some Linux byte order macros
+ *
+ * On Linux, DrvHostDVD.cpp includes <linux/cdrom.h>, which in turn
+ * includes <linux/byteorder/swab.h>. Unfortunately, that file is not very
+ * C++ friendly, and our C++ compiler refuses to look at it. The solution
+ * is to define _LINUX_BYTEORDER_SWAB_H, which prevents that file's contents
+ * from getting included at all, and to provide, in this file, our own
+ * C++-proof versions of the macros which are needed by <linux/cdrom.h>
+ * before we include that file. We actually provide them as inline
+ * functions, due to the way they get resolved in the original.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+#ifndef _VBOX_LINUX_SWAB_H
+#define _VBOX_LINUX_SWAB_H
+
+# define _LINUX_BYTEORDER_SWAB_H
+# define _LINUX_BYTEORDER_SWABB_H
+
+#include <asm/types.h>
+
+/* Sorry for the unnecessary brackets here, but I really think
+ readability requires them */
+static __inline__ __u16 __swab16p(const __u16 *px)
+{
+ __u16 x = *px;
+ return ((x & 0xff) << 8) | ((x >> 8) & 0xff);
+}
+
+static __inline__ __u32 __swab32p(const __u32 *px)
+{
+ __u32 x = *px;
+ return ((x & 0xff) << 24) | ((x & 0xff00) << 8)
+ | ((x >> 8) & 0xff00) | ((x >> 24) & 0xff);
+}
+
+static __inline__ __u64 __swab64p(const __u64 *px)
+{
+ __u64 x = *px;
+ return ((x & 0xff) << 56) | ((x & 0xff00) << 40)
+ | ((x & 0xff0000) << 24) | ((x & 0xff000000) << 8)
+ | ((x >> 8) & 0xff000000) | ((x >> 24) & 0xff0000)
+ | ((x >> 40) & 0xff00) | ((x >> 56) & 0xff);
+}
+
+#endif /* _VBOX_LINUX_SWAB_H */
diff --git a/src/VBox/Devices/Storage/testcase/Makefile.kmk b/src/VBox/Devices/Storage/testcase/Makefile.kmk
new file mode 100644
index 000000000..6e1b336f5
--- /dev/null
+++ b/src/VBox/Devices/Storage/testcase/Makefile.kmk
@@ -0,0 +1,77 @@
+# $Id: Makefile.kmk 15366 2008-12-12 13:50:32Z vboxsync $
+## @file
+# Sub-Makefile for the storage device & driver testcases.
+#
+
+#
+# Copyright (C) 2006-2007 Sun Microsystems, Inc.
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+# Clara, CA 95054 USA or visit http://www.sun.com if you need
+# additional information or have any questions.
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# vditool - useful too for manipulating VDIs, but now pretty obsolete and
+# probably will go away soon. Testcase only now.
+#
+#ifdef VBOX_WITH_TESTCASES
+# PROGRAMS += vditool
+# ifeq ($(KBUILD_TARGET),l4)
+# vditool_TEMPLATE = VBOXLNXHOSTR3EXE
+# vditool_LIBS = \
+# $(PATH_LIB)/VBoxDDULnxHostR3.a \
+# $(PATH_LIB)/RuntimeLnxHostR3.a
+# else
+# vditool_TEMPLATE = VBOXR3TSTEXE
+# vditool_LIBS = $(LIB_DDU) $(LIB_RUNTIME)
+# endif
+# vditool_SOURCES = vditool.cpp
+#endif
+
+
+#
+# Basic testcase for the VDI code.
+#
+#ifdef VBOX_WITH_TESTCASES
+# PROGRAMS += tstVDI
+# ifeq ($(KBUILD_TARGET),l4)
+# tstVDI_TEMPLATE = VBOXLNXHOSTR3EXE
+# else
+# tstVDI_TEMPLATE = VBOXR3TSTEXE
+# endif
+# tstVDI_LIBS = $(vditool_LIBS)
+# tstVDI_SOURCES = tstVDI.cpp
+#endif
+
+#
+# Basic testcases for the VD code.
+#
+ifdef VBOX_WITH_TESTCASES
+ PROGRAMS += tstVD tstVD-2
+ ifeq ($(KBUILD_TARGET),l4)
+ tstVD_TEMPLATE = VBOXLNXHOSTR3EXE
+ tstVD-2_TEMPLATE = VBOXLNXHOSTR3EXE
+ else
+ tstVD_TEMPLATE = VBOXR3TSTEXE
+ tstVD-2_TEMPLATE = VBOXR3TSTEXE
+ endif
+ tstVD_LIBS = $(LIB_DDU) $(LIB_RUNTIME)
+ tstVD-2_LIBS = $(LIB_DDU) $(LIB_RUNTIME)
+ tstVD_SOURCES = tstVD.cpp
+ tstVD-2_SOURCES = tstVD-2.cpp
+endif
+
+include $(KBUILD_PATH)/subfooter.kmk
+
diff --git a/src/VBox/Devices/Storage/testcase/tstVD-2.cpp b/src/VBox/Devices/Storage/testcase/tstVD-2.cpp
new file mode 100644
index 000000000..7bd58af18
--- /dev/null
+++ b/src/VBox/Devices/Storage/testcase/tstVD-2.cpp
@@ -0,0 +1,249 @@
+/** @file
+ *
+ * Simple VBox HDD container test utility. Only fast tests.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+#include <VBox/err.h>
+#include <VBox/VBoxHDD-new.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/initterm.h>
+#include <iprt/rand.h>
+#include "stdio.h"
+#include "stdlib.h"
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+/** The error count. */
+unsigned g_cErrors = 0;
+
+static struct KeyValuePair {
+ const char *key;
+ const char *value;
+} aCfgNode[] = {
+ { "TargetName", "test" },
+ { "LUN", "1" },
+ { "TargetAddress", "address" },
+ { NULL, NULL }
+};
+
+static bool tstAreKeysValid(void *pvUser, const char *pszzValid)
+{
+ return true;
+}
+
+static const char *tstGetValueByKey(const char *pszKey)
+{
+ for (int i = 0; aCfgNode[i].key; i++)
+ if (!strcmp(aCfgNode[i].key, pszKey))
+ return aCfgNode[i].value;
+ return NULL;
+}
+
+static int tstQuerySize(void *pvUser, const char *pszName, size_t *pcbValue)
+{
+ const char *pszValue = tstGetValueByKey(pszName);
+ if (!pszValue)
+ return VERR_CFGM_VALUE_NOT_FOUND;
+ *pcbValue = strlen(pszValue) + 1;
+ return VINF_SUCCESS;
+}
+
+static int tstQuery(void *pvUser, const char *pszName, char *pszValue, size_t cchValue)
+{
+ const char *pszTmp = tstGetValueByKey(pszName);
+ if (!pszValue)
+ return VERR_CFGM_VALUE_NOT_FOUND;
+ size_t cchTmp = strlen(pszTmp) + 1;
+ if (cchValue < cchTmp)
+ return VERR_CFGM_NOT_ENOUGH_SPACE;
+ memcpy(pszValue, pszTmp, cchTmp);
+ return VINF_SUCCESS;
+}
+
+
+VDINTERFACECONFIG icc = {
+ sizeof(VDINTERFACECONFIG),
+ VDINTERFACETYPE_CONFIG,
+ tstAreKeysValid,
+ tstQuerySize,
+ tstQuery
+};
+
+static int tstVDBackendInfo(void)
+{
+ int rc;
+#define MAX_BACKENDS 100
+ VDBACKENDINFO aVDInfo[MAX_BACKENDS];
+ unsigned cEntries;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ } while (0)
+
+ rc = VDBackendInfo(MAX_BACKENDS, aVDInfo, &cEntries);
+ CHECK("VDBackendInfo()");
+
+ for (unsigned i=0; i < cEntries; i++)
+ {
+ RTPrintf("Backend %u: name=%s capabilities=%#06x extensions=",
+ i, aVDInfo[i].pszBackend, aVDInfo[i].uBackendCaps);
+ if (aVDInfo[i].papszFileExtensions)
+ {
+ const char *const *papsz = aVDInfo[i].papszFileExtensions;
+ while (*papsz != NULL)
+ {
+ if (papsz != aVDInfo[i].papszFileExtensions)
+ RTPrintf(",");
+ RTPrintf("%s", *papsz);
+ papsz++;
+ }
+ if (papsz == aVDInfo[i].papszFileExtensions)
+ RTPrintf("<EMPTY>");
+ }
+ else
+ RTPrintf("<NONE>");
+ RTPrintf(" config=");
+ if (aVDInfo[i].paConfigInfo)
+ {
+ PCVDCONFIGINFO pa = aVDInfo[i].paConfigInfo;
+ while (pa->pszKey != NULL)
+ {
+ if (pa != aVDInfo[i].paConfigInfo)
+ RTPrintf(",");
+ RTPrintf("(key=%s type=", pa->pszKey);
+ switch (pa->enmValueType)
+ {
+ case VDCFGVALUETYPE_INTEGER:
+ RTPrintf("integer");
+ break;
+ case VDCFGVALUETYPE_STRING:
+ RTPrintf("string");
+ break;
+ case VDCFGVALUETYPE_BYTES:
+ RTPrintf("bytes");
+ break;
+ default:
+ RTPrintf("INVALID!");
+ }
+ RTPrintf(" default=");
+ if (pa->pszDefaultValue)
+ RTPrintf("%s", pa->pszDefaultValue);
+ else
+ RTPrintf("<NONE>");
+ RTPrintf(" flags=");
+ if (!pa->uKeyFlags)
+ RTPrintf("none");
+ unsigned cFlags = 0;
+ if (pa->uKeyFlags & VD_CFGKEY_MANDATORY)
+ {
+ if (cFlags)
+ RTPrintf(",");
+ RTPrintf("mandatory");
+ cFlags++;
+ }
+ if (pa->uKeyFlags & VD_CFGKEY_EXPERT)
+ {
+ if (cFlags)
+ RTPrintf(",");
+ RTPrintf("expert");
+ cFlags++;
+ }
+ RTPrintf(")");
+ pa++;
+ }
+ if (pa == aVDInfo[i].paConfigInfo)
+ RTPrintf("<EMPTY>");
+ }
+ else
+ RTPrintf("<NONE>");
+ RTPrintf("\n");
+
+ VDINTERFACE ic;
+ ic.cbSize = sizeof(ic);
+ ic.enmInterface = VDINTERFACETYPE_CONFIG;
+ ic.pCallbacks = &icc;
+ char *pszLocation, *pszName;
+ rc = aVDInfo[i].pfnComposeLocation(&ic, &pszLocation);
+ CHECK("pfnComposeLocation()");
+ if (pszLocation)
+ {
+ RTMemFree(pszLocation);
+ if (aVDInfo[i].uBackendCaps & VD_CAP_FILE)
+ {
+ RTPrintf("Non-NULL location returned for file-based backend!\n");
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ rc = aVDInfo[i].pfnComposeName(&ic, &pszName);
+ CHECK("pfnComposeName()");
+ if (pszName)
+ {
+ RTMemFree(pszName);
+ if (aVDInfo[i].uBackendCaps & VD_CAP_FILE)
+ {
+ RTPrintf("Non-NULL name returned for file-based backend!\n");
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ }
+
+#undef CHECK
+ return 0;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int rc;
+
+ RTR3Init();
+ RTPrintf("tstVD-2: TESTING...\n");
+
+ rc = tstVDBackendInfo();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD-2: getting backend info test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD-2: unloading backends failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ /*
+ * Summary
+ */
+ if (!g_cErrors)
+ RTPrintf("tstVD-2: SUCCESS\n");
+ else
+ RTPrintf("tstVD-2: FAILURE - %d errors\n", g_cErrors);
+
+ return !!g_cErrors;
+}
+
diff --git a/src/VBox/Devices/Storage/testcase/tstVD.cpp b/src/VBox/Devices/Storage/testcase/tstVD.cpp
new file mode 100644
index 000000000..2639541c0
--- /dev/null
+++ b/src/VBox/Devices/Storage/testcase/tstVD.cpp
@@ -0,0 +1,1078 @@
+/** @file
+ *
+ * Simple VBox HDD container test utility.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+#include <VBox/VBoxHDD-new.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include <iprt/dir.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/initterm.h>
+#include <iprt/rand.h>
+#include "stdio.h"
+#include "stdlib.h"
+
+#define VHD_TEST
+#define VDI_TEST
+#define VMDK_TEST
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+/** The error count. */
+unsigned g_cErrors = 0;
+
+
+static void tstVDError(void *pvUser, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, va_list va)
+{
+ g_cErrors++;
+ RTPrintf("tstVD: Error %Rrc at %s:%u (%s): ", rc, RT_SRC_POS_ARGS);
+ RTPrintfV(pszFormat, va);
+ RTPrintf("\n");
+}
+
+
+static int tstVDCreateDelete(const char *pszBackend, const char *pszFilename,
+ uint64_t cbSize, VDIMAGETYPE enmType,
+ unsigned uFlags, bool fDelete)
+{
+ int rc;
+ PVBOXHDD pVD = NULL;
+ PDMMEDIAGEOMETRY PCHS = { 0, 0, 0 };
+ PDMMEDIAGEOMETRY LCHS = { 0, 0, 0 };
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACE VDIError;
+ VDINTERFACEERROR VDIErrorCallbacks;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ VDDestroy(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ /* Create error interface. */
+ VDIErrorCallbacks.cbSize = sizeof(VDINTERFACEERROR);
+ VDIErrorCallbacks.enmInterface = VDINTERFACETYPE_ERROR;
+ VDIErrorCallbacks.pfnError = tstVDError;
+
+ rc = VDInterfaceAdd(&VDIError, "tstVD_Error", VDINTERFACETYPE_ERROR, &VDIErrorCallbacks,
+ NULL, &pVDIfs);
+ AssertRC(rc);
+
+ rc = VDCreate(&VDIError, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDCreateBase(pVD, pszBackend, pszFilename, enmType, cbSize,
+ uFlags, "Test image", &PCHS, &LCHS, NULL,
+ VD_OPEN_FLAGS_NORMAL, NULL, NULL);
+ CHECK("VDCreateBase()");
+
+ VDDumpImages(pVD);
+
+ VDClose(pVD, fDelete);
+ if (fDelete)
+ {
+ RTFILE File;
+ rc = RTFileOpen(&File, pszFilename, RTFILE_O_READ);
+ if (RT_SUCCESS(rc))
+ {
+ RTFileClose(File);
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+
+ VDDestroy(pVD);
+#undef CHECK
+ return 0;
+}
+
+static int tstVDOpenDelete(const char *pszBackend, const char *pszFilename)
+{
+ int rc;
+ PVBOXHDD pVD = NULL;
+ PDMMEDIAGEOMETRY PCHS = { 0, 0, 0 };
+ PDMMEDIAGEOMETRY LCHS = { 0, 0, 0 };
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACE VDIError;
+ VDINTERFACEERROR VDIErrorCallbacks;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ VDDestroy(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ /* Create error interface. */
+ VDIErrorCallbacks.cbSize = sizeof(VDINTERFACEERROR);
+ VDIErrorCallbacks.enmInterface = VDINTERFACETYPE_ERROR;
+ VDIErrorCallbacks.pfnError = tstVDError;
+
+ rc = VDInterfaceAdd(&VDIError, "tstVD_Error", VDINTERFACETYPE_ERROR, &VDIErrorCallbacks,
+ NULL, &pVDIfs);
+ AssertRC(rc);
+
+ rc = VDCreate(&VDIError, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDOpen(pVD, pszBackend, pszFilename, VD_OPEN_FLAGS_NORMAL, NULL);
+ CHECK("VDOpen()");
+
+ VDDumpImages(pVD);
+
+ VDClose(pVD, true);
+ RTFILE File;
+ rc = RTFileOpen(&File, pszFilename, RTFILE_O_READ);
+ if (RT_SUCCESS(rc))
+ {
+ RTFileClose(File);
+ return VERR_INTERNAL_ERROR;
+ }
+
+ VDDestroy(pVD);
+#undef CHECK
+ return 0;
+}
+
+
+#undef RTDECL
+#define RTDECL(x) static x
+
+/* Start of IPRT code */
+
+/**
+ * The following code is based on the work of George Marsaglia
+ * taken from
+ * http://groups.google.ws/group/comp.sys.sun.admin/msg/7c667186f6cbf354
+ * and
+ * http://groups.google.ws/group/comp.lang.c/msg/0e170777c6e79e8d
+ */
+
+/*
+A C version of a very very good 64-bit RNG is given below.
+You should be able to adapt it to your particular needs.
+
+It is based on the complimentary-multiple-with-carry
+sequence
+ x(n)=a*x(n-4)+carry mod 2^64-1,
+which works as follows:
+Assume a certain multiplier 'a' and a base 'b'.
+Given a current x value and a current carry 'c',
+form: t=a*x+c
+Then the new carry is c=floor(t/b)
+and the new x value is x = b-1-(t mod b).
+
+
+Ordinarily, for 32-bit mwc or cmwc sequences, the
+value t=a*x+c can be formed in 64 bits, then the new c
+is the top and the new x the bottom 32 bits (with a little
+fiddling when b=2^32-1 and cmwc rather than mwc.)
+
+
+To generate 64-bit x's, it is difficult to form
+t=a*x+c in 128 bits then get the new c and new x
+from the top and bottom halves.
+But if 'a' has a special form, for example,
+a=2^62+2^47+2 and b=2^64-1, then the new c and
+the new x can be formed with shifts, tests and +/-'s,
+again with a little fiddling because b=2^64-1 rather
+than 2^64. (The latter is not an optimal choice because,
+being a square, it cannot be a primitive root of the
+prime a*b^k+1, where 'k' is the 'lag':
+ x(n)=a*x(n-k)+carry mod b.)
+But the multiplier a=2^62+2^47+2 makes a*b^4+1 a prime for
+which b=2^64-1 is a primitive root, and getting the new x and
+new c can be done with arithmetic on integers the size of x.
+*/
+
+struct RndCtx
+{
+ uint64_t x;
+ uint64_t y;
+ uint64_t z;
+ uint64_t w;
+ uint64_t c;
+ uint32_t u32x;
+ uint32_t u32y;
+};
+typedef struct RndCtx RNDCTX;
+typedef RNDCTX *PRNDCTX;
+
+/**
+ * Initialize seeds.
+ *
+ * @remarks You should choose ANY 4 random 64-bit
+ * seeds x,y,z,w < 2^64-1 and a random seed c in
+ * 0<= c < a = 2^62+2^47+2.
+ * There are P=(2^62+2^46+2)*(2^64-1)^4 > 2^318 possible choices
+ * for seeds, the period of the RNG.
+ */
+RTDECL(int) RTPRandInit(PRNDCTX pCtx, uint32_t u32Seed)
+{
+ if (u32Seed == 0)
+ u32Seed = (uint32_t)(ASMReadTSC() >> 8);
+ /* Zero is not a good seed. */
+ if (u32Seed == 0)
+ u32Seed = 362436069;
+ pCtx->x = u32Seed;
+ pCtx->y = 17280675555674358941ULL;
+ pCtx->z = 6376492577913983186ULL;
+ pCtx->w = 9064188857900113776ULL;
+ pCtx->c = 123456789;
+ pCtx->u32x = 2282008;
+ pCtx->u32y = u32Seed;
+ return VINF_SUCCESS;
+}
+
+RTDECL(uint32_t) RTPRandGetSeedInfo(PRNDCTX pCtx)
+{
+ return pCtx->u32y;
+}
+
+/**
+ * Generate a 64-bit unsigned random number.
+ *
+ * @returns The pseudo random number.
+ */
+RTDECL(uint64_t) RTPRandU64(PRNDCTX pCtx)
+{
+ uint64_t t;
+ t = (pCtx->x<<47) + (pCtx->x<<62) + (pCtx->x<<1);
+ t += pCtx->c; t+= (t < pCtx->c);
+ pCtx->c = (t<pCtx->c) + (pCtx->x>>17) + (pCtx->x>>2) + (pCtx->x>>63);
+ pCtx->x = pCtx->y; pCtx->y = pCtx->z ; pCtx->z = pCtx->w;
+ return (pCtx->w = ~(t + pCtx->c)-1);
+}
+
+/**
+ * Generate a 64-bit unsigned pseudo random number in the set
+ * [u64First..u64Last].
+ *
+ * @returns The pseudo random number.
+ * @param u64First First number in the set.
+ * @param u64Last Last number in the set.
+ */
+RTDECL(uint64_t) RTPRandU64Ex(PRNDCTX pCtx, uint64_t u64First, uint64_t u64Last)
+{
+ if (u64First == 0 && u64Last == UINT64_MAX)
+ return RTPRandU64(pCtx);
+
+ uint64_t u64Tmp;
+ uint64_t u64Range = u64Last - u64First + 1;
+ uint64_t u64Scale = UINT64_MAX / u64Range;
+
+ do
+ {
+ u64Tmp = RTPRandU64(pCtx) / u64Scale;
+ } while (u64Tmp >= u64Range);
+ return u64First + u64Tmp;
+}
+
+/**
+ * Generate a 32-bit unsigned random number.
+ *
+ * @returns The pseudo random number.
+ */
+RTDECL(uint32_t) RTPRandU32(PRNDCTX pCtx)
+{
+ return ( pCtx->u32x = 69069 * pCtx->u32x + 123,
+ pCtx->u32y ^= pCtx->u32y<<13,
+ pCtx->u32y ^= pCtx->u32y>>17,
+ pCtx->u32y ^= pCtx->u32y<<5,
+ pCtx->u32x + pCtx->u32y );
+}
+
+/**
+ * Generate a 32-bit unsigned pseudo random number in the set
+ * [u32First..u32Last].
+ *
+ * @returns The pseudo random number.
+ * @param u32First First number in the set.
+ * @param u32Last Last number in the set.
+ */
+RTDECL(uint32_t) RTPRandU32Ex(PRNDCTX pCtx, uint32_t u32First, uint32_t u32Last)
+{
+ if (u32First == 0 && u32Last == UINT32_MAX)
+ return RTPRandU32(pCtx);
+
+ uint32_t u32Tmp;
+ uint32_t u32Range = u32Last - u32First + 1;
+ uint32_t u32Scale = UINT32_MAX / u32Range;
+
+ do
+ {
+ u32Tmp = RTPRandU32(pCtx) / u32Scale;
+ } while (u32Tmp >= u32Range);
+ return u32First + u32Tmp;
+}
+
+/* End of IPRT code */
+
+struct Segment
+{
+ uint64_t u64Offset;
+ uint32_t u32Length;
+ uint32_t u8Value;
+};
+typedef struct Segment *PSEGMENT;
+
+static void initializeRandomGenerator(PRNDCTX pCtx, uint32_t u32Seed)
+{
+ int rc = RTPRandInit(pCtx, u32Seed);
+ if (RT_FAILURE(rc))
+ RTPrintf("ERROR: Failed to initialize random generator. RC=%Rrc\n", rc);
+ else
+ {
+ RTPrintf("INFO: Random generator seed used: %x\n", RTPRandGetSeedInfo(pCtx));
+ RTLogPrintf("INFO: Random generator seed used: %x\n", RTPRandGetSeedInfo(pCtx));
+ }
+}
+
+static int compareSegments(const void *left, const void *right)
+{
+ /* Note that no duplicates are allowed in the array being sorted. */
+ return ((PSEGMENT)left)->u64Offset < ((PSEGMENT)right)->u64Offset ? -1 : 1;
+}
+
+static void generateRandomSegments(PRNDCTX pCtx, PSEGMENT pSegment, uint32_t nSegments, uint32_t u32MaxSegmentSize, uint64_t u64DiskSize, uint32_t u32SectorSize, uint8_t u8ValueLow, uint8_t u8ValueHigh)
+{
+ uint32_t i;
+ /* Generate segment offsets. */
+ for (i = 0; i < nSegments; i++)
+ {
+ bool fDuplicateFound;
+ do
+ {
+ pSegment[i].u64Offset = RTPRandU64Ex(pCtx, 0, u64DiskSize / u32SectorSize - 1) * u32SectorSize;
+ fDuplicateFound = false;
+ for (uint32_t j = 0; j < i; j++)
+ if (pSegment[i].u64Offset == pSegment[j].u64Offset)
+ {
+ fDuplicateFound = true;
+ break;
+ }
+ } while (fDuplicateFound);
+ }
+ /* Sort in offset-ascending order. */
+ qsort(pSegment, nSegments, sizeof(*pSegment), compareSegments);
+ /* Put a sentinel at the end. */
+ pSegment[nSegments].u64Offset = u64DiskSize;
+ pSegment[nSegments].u32Length = 0;
+ /* Generate segment lengths and values. */
+ for (i = 0; i < nSegments; i++)
+ {
+ pSegment[i].u32Length = RTPRandU32Ex(pCtx, 1, RT_MIN(pSegment[i+1].u64Offset - pSegment[i].u64Offset,
+ u32MaxSegmentSize) / u32SectorSize) * u32SectorSize;
+ Assert(pSegment[i].u32Length <= u32MaxSegmentSize);
+ pSegment[i].u8Value = RTPRandU32Ex(pCtx, (uint32_t)u8ValueLow, (uint32_t)u8ValueHigh);
+ }
+}
+
+static void mergeSegments(PSEGMENT pBaseSegment, PSEGMENT pDiffSegment, PSEGMENT pMergeSegment, uint32_t u32MaxLength)
+{
+ while (pBaseSegment->u32Length > 0 || pDiffSegment->u32Length > 0)
+ {
+ if (pBaseSegment->u64Offset < pDiffSegment->u64Offset)
+ {
+ *pMergeSegment = *pBaseSegment;
+ if (pMergeSegment->u64Offset + pMergeSegment->u32Length <= pDiffSegment->u64Offset)
+ pBaseSegment++;
+ else
+ {
+ pMergeSegment->u32Length = pDiffSegment->u64Offset - pMergeSegment->u64Offset;
+ Assert(pMergeSegment->u32Length <= u32MaxLength);
+ if (pBaseSegment->u64Offset + pBaseSegment->u32Length >
+ pDiffSegment->u64Offset + pDiffSegment->u32Length)
+ {
+ pBaseSegment->u32Length -= pDiffSegment->u64Offset + pDiffSegment->u32Length - pBaseSegment->u64Offset;
+ Assert(pBaseSegment->u32Length <= u32MaxLength);
+ pBaseSegment->u64Offset = pDiffSegment->u64Offset + pDiffSegment->u32Length;
+ }
+ else
+ pBaseSegment++;
+ }
+ pMergeSegment++;
+ }
+ else
+ {
+ *pMergeSegment = *pDiffSegment;
+ if (pMergeSegment->u64Offset + pMergeSegment->u32Length <= pBaseSegment->u64Offset)
+ {
+ pDiffSegment++;
+ pMergeSegment++;
+ }
+ else
+ {
+ if (pBaseSegment->u64Offset + pBaseSegment->u32Length > pDiffSegment->u64Offset + pDiffSegment->u32Length)
+ {
+ pBaseSegment->u32Length -= pDiffSegment->u64Offset + pDiffSegment->u32Length - pBaseSegment->u64Offset;
+ Assert(pBaseSegment->u32Length <= u32MaxLength);
+ pBaseSegment->u64Offset = pDiffSegment->u64Offset + pDiffSegment->u32Length;
+ pDiffSegment++;
+ pMergeSegment++;
+ }
+ else
+ pBaseSegment++;
+ }
+ }
+ }
+}
+
+static void writeSegmentsToDisk(PVBOXHDD pVD, void *pvBuf, PSEGMENT pSegment)
+{
+ while (pSegment->u32Length)
+ {
+ //memset((uint8_t*)pvBuf + pSegment->u64Offset, pSegment->u8Value, pSegment->u32Length);
+ memset(pvBuf, pSegment->u8Value, pSegment->u32Length);
+ VDWrite(pVD, pSegment->u64Offset, pvBuf, pSegment->u32Length);
+ pSegment++;
+ }
+}
+
+static int readAndCompareSegments(PVBOXHDD pVD, void *pvBuf, PSEGMENT pSegment)
+{
+ while (pSegment->u32Length)
+ {
+ int rc = VDRead(pVD, pSegment->u64Offset, pvBuf, pSegment->u32Length);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("ERROR: Failed to read from virtual disk\n");
+ return rc;
+ }
+ else
+ {
+ for (unsigned i = 0; i < pSegment->u32Length; i++)
+ if (((uint8_t*)pvBuf)[i] != pSegment->u8Value)
+ {
+ RTPrintf("ERROR: Segment at %Lx of %x bytes is corrupt at offset %x (found %x instead of %x)\n",
+ pSegment->u64Offset, pSegment->u32Length, i, ((uint8_t*)pvBuf)[i],
+ pSegment->u8Value);
+ RTLogPrintf("ERROR: Segment at %Lx of %x bytes is corrupt at offset %x (found %x instead of %x)\n",
+ pSegment->u64Offset, pSegment->u32Length, i, ((uint8_t*)pvBuf)[i],
+ pSegment->u8Value);
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ pSegment++;
+ }
+
+ return VINF_SUCCESS;
+}
+
+static int tstVDOpenCreateWriteMerge(const char *pszBackend,
+ const char *pszBaseFilename,
+ const char *pszDiffFilename,
+ uint32_t u32Seed)
+{
+ int rc;
+ PVBOXHDD pVD = NULL;
+ char *pszFormat;
+ PDMMEDIAGEOMETRY PCHS = { 0, 0, 0 };
+ PDMMEDIAGEOMETRY LCHS = { 0, 0, 0 };
+ uint64_t u64DiskSize = 1000 * _1M;
+ uint32_t u32SectorSize = 512;
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACE VDIError;
+ VDINTERFACEERROR VDIErrorCallbacks;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ if (pvBuf) \
+ RTMemFree(pvBuf); \
+ VDDestroy(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ void *pvBuf = RTMemAlloc(_1M);
+
+ /* Create error interface. */
+ VDIErrorCallbacks.cbSize = sizeof(VDINTERFACEERROR);
+ VDIErrorCallbacks.enmInterface = VDINTERFACETYPE_ERROR;
+ VDIErrorCallbacks.pfnError = tstVDError;
+
+ rc = VDInterfaceAdd(&VDIError, "tstVD_Error", VDINTERFACETYPE_ERROR, &VDIErrorCallbacks,
+ NULL, &pVDIfs);
+ AssertRC(rc);
+
+
+ rc = VDCreate(&VDIError, &pVD);
+ CHECK("VDCreate()");
+
+ RTFILE File;
+ rc = RTFileOpen(&File, pszBaseFilename, RTFILE_O_READ);
+ if (RT_SUCCESS(rc))
+ {
+ RTFileClose(File);
+ rc = VDGetFormat(pszBaseFilename, &pszFormat);
+ RTPrintf("VDGetFormat() pszFormat=%s rc=%Rrc\n", pszFormat, rc);
+ if (RT_SUCCESS(rc) && strcmp(pszFormat, pszBackend))
+ {
+ rc = VERR_GENERAL_FAILURE;
+ RTPrintf("VDGetFormat() returned incorrect backend name\n");
+ }
+ RTStrFree(pszFormat);
+ CHECK("VDGetFormat()");
+
+ rc = VDOpen(pVD, pszBackend, pszBaseFilename, VD_OPEN_FLAGS_NORMAL,
+ NULL);
+ CHECK("VDOpen()");
+ }
+ else
+ {
+ rc = VDCreateBase(pVD, pszBackend, pszBaseFilename,
+ VD_IMAGE_TYPE_NORMAL, u64DiskSize,
+ VD_IMAGE_FLAGS_NONE, "Test image",
+ &PCHS, &LCHS, NULL, VD_OPEN_FLAGS_NORMAL,
+ NULL, NULL);
+ CHECK("VDCreateBase()");
+ }
+
+ int nSegments = 100;
+ /* Allocate one extra element for a sentinel. */
+ PSEGMENT paBaseSegments = (PSEGMENT)RTMemAllocZ(sizeof(struct Segment) * (nSegments + 1));
+ PSEGMENT paDiffSegments = (PSEGMENT)RTMemAllocZ(sizeof(struct Segment) * (nSegments + 1));
+ PSEGMENT paMergeSegments = (PSEGMENT)RTMemAllocZ(sizeof(struct Segment) * (nSegments + 1) * 3);
+
+ RNDCTX ctx;
+ initializeRandomGenerator(&ctx, u32Seed);
+ generateRandomSegments(&ctx, paBaseSegments, nSegments, _1M, u64DiskSize, u32SectorSize, 0u, 127u);
+ generateRandomSegments(&ctx, paDiffSegments, nSegments, _1M, u64DiskSize, u32SectorSize, 128u, 255u);
+
+ /*PSEGMENT pSegment;
+ RTPrintf("Base segments:\n");
+ for (pSegment = paBaseSegments; pSegment->u32Length; pSegment++)
+ RTPrintf("off: %08Lx len: %05x val: %02x\n", pSegment->u64Offset, pSegment->u32Length, pSegment->u8Value);*/
+ writeSegmentsToDisk(pVD, pvBuf, paBaseSegments);
+
+ rc = VDCreateDiff(pVD, pszBackend, pszDiffFilename,
+ VD_IMAGE_FLAGS_NONE, "Test diff image", NULL, NULL,
+ VD_OPEN_FLAGS_NORMAL, NULL, NULL);
+ CHECK("VDCreateDiff()");
+
+ /*RTPrintf("\nDiff segments:\n");
+ for (pSegment = paDiffSegments; pSegment->u32Length; pSegment++)
+ RTPrintf("off: %08Lx len: %05x val: %02x\n", pSegment->u64Offset, pSegment->u32Length, pSegment->u8Value);*/
+ writeSegmentsToDisk(pVD, pvBuf, paDiffSegments);
+
+ VDDumpImages(pVD);
+
+ RTPrintf("Merging diff into base..\n");
+ rc = VDMerge(pVD, VD_LAST_IMAGE, 0, NULL);
+ CHECK("VDMerge()");
+
+ mergeSegments(paBaseSegments, paDiffSegments, paMergeSegments, _1M);
+ /*RTPrintf("\nMerged segments:\n");
+ for (pSegment = paMergeSegments; pSegment->u32Length; pSegment++)
+ RTPrintf("off: %08Lx len: %05x val: %02x\n", pSegment->u64Offset, pSegment->u32Length, pSegment->u8Value);*/
+ rc = readAndCompareSegments(pVD, pvBuf, paMergeSegments);
+ CHECK("readAndCompareSegments()");
+
+ RTMemFree(paMergeSegments);
+ RTMemFree(paDiffSegments);
+ RTMemFree(paBaseSegments);
+
+ VDDumpImages(pVD);
+
+ VDDestroy(pVD);
+ if (pvBuf)
+ RTMemFree(pvBuf);
+#undef CHECK
+ return 0;
+}
+
+static int tstVDCreateWriteOpenRead(const char *pszBackend,
+ const char *pszFilename,
+ uint32_t u32Seed)
+{
+ int rc;
+ PVBOXHDD pVD = NULL;
+ PDMMEDIAGEOMETRY PCHS = { 0, 0, 0 };
+ PDMMEDIAGEOMETRY LCHS = { 0, 0, 0 };
+ uint64_t u64DiskSize = 1000 * _1M;
+ uint32_t u32SectorSize = 512;
+ PVDINTERFACE pVDIfs;
+ VDINTERFACE VDIError;
+ VDINTERFACEERROR VDIErrorCallbacks;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ if (pvBuf) \
+ RTMemFree(pvBuf); \
+ VDDestroy(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ void *pvBuf = RTMemAlloc(_1M);
+
+ /* Create error interface. */
+ VDIErrorCallbacks.cbSize = sizeof(VDINTERFACEERROR);
+ VDIErrorCallbacks.enmInterface = VDINTERFACETYPE_ERROR;
+ VDIErrorCallbacks.pfnError = tstVDError;
+
+ rc = VDInterfaceAdd(&VDIError, "tstVD_Error", VDINTERFACETYPE_ERROR, &VDIErrorCallbacks,
+ NULL, &pVDIfs);
+ AssertRC(rc);
+
+
+ rc = VDCreate(&VDIError, &pVD);
+ CHECK("VDCreate()");
+
+ RTFILE File;
+ rc = RTFileOpen(&File, pszFilename, RTFILE_O_READ);
+ if (RT_SUCCESS(rc))
+ {
+ RTFileClose(File);
+ RTFileDelete(pszFilename);
+ }
+
+ rc = VDCreateBase(pVD, pszBackend, pszFilename,
+ VD_IMAGE_TYPE_NORMAL, u64DiskSize,
+ VD_IMAGE_FLAGS_NONE, "Test image",
+ &PCHS, &LCHS, NULL, VD_OPEN_FLAGS_NORMAL,
+ NULL, NULL);
+ CHECK("VDCreateBase()");
+
+ int nSegments = 100;
+ /* Allocate one extra element for a sentinel. */
+ PSEGMENT paSegments = (PSEGMENT)RTMemAllocZ(sizeof(struct Segment) * (nSegments + 1));
+
+ RNDCTX ctx;
+ initializeRandomGenerator(&ctx, u32Seed);
+ generateRandomSegments(&ctx, paSegments, nSegments, _1M, u64DiskSize, u32SectorSize, 0u, 127u);
+ /*for (PSEGMENT pSegment = paSegments; pSegment->u32Length; pSegment++)
+ RTPrintf("off: %08Lx len: %05x val: %02x\n", pSegment->u64Offset, pSegment->u32Length, pSegment->u8Value);*/
+
+ writeSegmentsToDisk(pVD, pvBuf, paSegments);
+
+ VDCloseAll(pVD);
+
+ rc = VDOpen(pVD, pszBackend, pszFilename, VD_OPEN_FLAGS_NORMAL, NULL);
+ CHECK("VDOpen()");
+ rc = readAndCompareSegments(pVD, pvBuf, paSegments);
+ CHECK("readAndCompareSegments()");
+
+ RTMemFree(paSegments);
+
+ VDDestroy(pVD);
+ if (pvBuf)
+ RTMemFree(pvBuf);
+#undef CHECK
+ return 0;
+}
+
+static int tstVmdkRename(const char *src, const char *dst)
+{
+ int rc;
+ PVBOXHDD pVD = NULL;
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACE VDIError;
+ VDINTERFACEERROR VDIErrorCallbacks;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ VDDestroy(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ /* Create error interface. */
+ VDIErrorCallbacks.cbSize = sizeof(VDINTERFACEERROR);
+ VDIErrorCallbacks.enmInterface = VDINTERFACETYPE_ERROR;
+ VDIErrorCallbacks.pfnError = tstVDError;
+
+ rc = VDInterfaceAdd(&VDIError, "tstVD_Error", VDINTERFACETYPE_ERROR, &VDIErrorCallbacks,
+ NULL, &pVDIfs);
+ AssertRC(rc);
+
+ rc = VDCreate(&VDIError, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDOpen(pVD, "VMDK", src, VD_OPEN_FLAGS_NORMAL, NULL);
+ CHECK("VDOpen()");
+ rc = VDCopy(pVD, 0, pVD, "VMDK", dst, true, 0, NULL, NULL, NULL, NULL);
+ CHECK("VDCopy()");
+
+ VDDestroy(pVD);
+#undef CHECK
+ return 0;
+}
+
+static int tstVmdkCreateRenameOpen(const char *src, const char *dst,
+ uint64_t cbSize, VDIMAGETYPE enmType,
+ unsigned uFlags)
+{
+ int rc = tstVDCreateDelete("VMDK", src, cbSize, enmType, uFlags, false);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = tstVmdkRename(src, dst);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ PVBOXHDD pVD = NULL;
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACE VDIError;
+ VDINTERFACEERROR VDIErrorCallbacks;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ VDCloseAll(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ /* Create error interface. */
+ VDIErrorCallbacks.cbSize = sizeof(VDINTERFACEERROR);
+ VDIErrorCallbacks.enmInterface = VDINTERFACETYPE_ERROR;
+ VDIErrorCallbacks.pfnError = tstVDError;
+
+ rc = VDInterfaceAdd(&VDIError, "tstVD_Error", VDINTERFACETYPE_ERROR, &VDIErrorCallbacks,
+ NULL, &pVDIfs);
+ AssertRC(rc);
+
+ rc = VDCreate(&VDIError, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDOpen(pVD, "VMDK", dst, VD_OPEN_FLAGS_NORMAL, NULL);
+ CHECK("VDOpen()");
+
+ VDClose(pVD, true);
+ CHECK("VDClose()");
+ VDDestroy(pVD);
+#undef CHECK
+ return rc;
+}
+
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+#define DST_PATH "tmp\\tmpVDRename.vmdk"
+#else
+#define DST_PATH "tmp/tmpVDRename.vmdk"
+#endif
+
+static void tstVmdk()
+{
+ int rc = tstVmdkCreateRenameOpen("tmpVDCreate.vmdk", "tmpVDRename.vmdk", _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_IMAGE_FLAGS_NONE);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK rename (single extent, embedded descriptor, same dir) test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVmdkCreateRenameOpen("tmpVDCreate.vmdk", "tmpVDRename.vmdk", _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_VMDK_IMAGE_FLAGS_SPLIT_2G);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK rename (multiple extent, separate descriptor, same dir) test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVmdkCreateRenameOpen("tmpVDCreate.vmdk", DST_PATH, _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_IMAGE_FLAGS_NONE);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK rename (single extent, embedded descriptor, another dir) test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVmdkCreateRenameOpen("tmpVDCreate.vmdk", DST_PATH, _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_VMDK_IMAGE_FLAGS_SPLIT_2G);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK rename (multiple extent, separate descriptor, another dir) test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+
+ RTFILE File;
+ rc = RTFileOpen(&File, DST_PATH, RTFILE_O_CREATE | RTFILE_O_WRITE);
+ if (RT_SUCCESS(rc))
+ RTFileClose(File);
+
+ rc = tstVmdkCreateRenameOpen("tmpVDCreate.vmdk", DST_PATH, _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_VMDK_IMAGE_FLAGS_SPLIT_2G);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("tstVD: VMDK rename (multiple extent, separate descriptor, another dir, already exists) test failed!\n");
+ g_cErrors++;
+ }
+ RTFileDelete(DST_PATH);
+ RTFileDelete("tmpVDCreate.vmdk");
+ RTFileDelete("tmpVDCreate-s001.vmdk");
+ RTFileDelete("tmpVDCreate-s002.vmdk");
+ RTFileDelete("tmpVDCreate-s003.vmdk");
+}
+
+int main(int argc, char *argv[])
+{
+ RTR3Init();
+ int rc;
+
+ uint32_t u32Seed = 0; // Means choose random
+
+ if (argc > 1)
+ if (sscanf(argv[1], "%x", &u32Seed) != 1)
+ {
+ RTPrintf("ERROR: Invalid parameter %s. Valid usage is %s <32-bit seed>.\n",
+ argv[1], argv[0]);
+ return 1;
+ }
+
+ RTPrintf("tstVD: TESTING...\n");
+
+ /*
+ * Clean up potential leftovers from previous unsuccessful runs.
+ */
+ RTFileDelete("tmpVDCreate.vdi");
+ RTFileDelete("tmpVDCreate.vmdk");
+ RTFileDelete("tmpVDCreate.vhd");
+ RTFileDelete("tmpVDBase.vdi");
+ RTFileDelete("tmpVDDiff.vdi");
+ RTFileDelete("tmpVDBase.vmdk");
+ RTFileDelete("tmpVDDiff.vmdk");
+ RTFileDelete("tmpVDBase.vhd");
+ RTFileDelete("tmpVDDiff.vhd");
+ RTFileDelete("tmpVDCreate-s001.vmdk");
+ RTFileDelete("tmpVDCreate-s002.vmdk");
+ RTFileDelete("tmpVDCreate-s003.vmdk");
+ RTFileDelete("tmpVDRename.vmdk");
+ RTFileDelete("tmpVDRename-s001.vmdk");
+ RTFileDelete("tmpVDRename-s002.vmdk");
+ RTFileDelete("tmpVDRename-s003.vmdk");
+ RTFileDelete("tmp/tmpVDRename.vmdk");
+ RTFileDelete("tmp/tmpVDRename-s001.vmdk");
+ RTFileDelete("tmp/tmpVDRename-s002.vmdk");
+ RTFileDelete("tmp/tmpVDRename-s003.vmdk");
+
+ if (!RTDirExists("tmp"))
+ {
+ rc = RTDirCreate("tmp", RTFS_UNIX_IRWXU);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: Failed to create 'tmp' directory! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ }
+
+#ifdef VMDK_TEST
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_IMAGE_FLAGS_NONE,
+ true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_IMAGE_FLAGS_NONE,
+ false);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDOpenDelete("VMDK", "tmpVDCreate.vmdk");
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK delete test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+
+ tstVmdk();
+#endif /* VMDK_TEST */
+#ifdef VDI_TEST
+ rc = tstVDCreateDelete("VDI", "tmpVDCreate.vdi", 2 * _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_IMAGE_FLAGS_NONE,
+ true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic VDI create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VDI", "tmpVDCreate.vdi", 2 * _4G,
+ VD_IMAGE_TYPE_FIXED, VD_IMAGE_FLAGS_NONE,
+ true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: fixed VDI create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VDI_TEST */
+#ifdef VMDK_TEST
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_IMAGE_FLAGS_NONE,
+ true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_VMDK_IMAGE_FLAGS_SPLIT_2G,
+ true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic split VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_TYPE_FIXED, VD_IMAGE_FLAGS_NONE,
+ true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: fixed VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_TYPE_FIXED, VD_VMDK_IMAGE_FLAGS_SPLIT_2G,
+ true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: fixed split VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VMDK_TEST */
+#ifdef VHD_TEST
+ rc = tstVDCreateDelete("VHD", "tmpVDCreate.vhd", 2 * _4G,
+ VD_IMAGE_TYPE_NORMAL, VD_IMAGE_FLAGS_NONE,
+ true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic VHD create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VHD", "tmpVDCreate.vhd", 2 * _4G,
+ VD_IMAGE_TYPE_FIXED, VD_IMAGE_FLAGS_NONE,
+ true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: fixed VHD create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VHD_TEST */
+#ifdef VDI_TEST
+ rc = tstVDOpenCreateWriteMerge("VDI", "tmpVDBase.vdi", "tmpVDDiff.vdi", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VDI test failed (new image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDOpenCreateWriteMerge("VDI", "tmpVDBase.vdi", "tmpVDDiff.vdi", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VDI test failed (existing image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VDI_TEST */
+#ifdef VMDK_TEST
+ rc = tstVDOpenCreateWriteMerge("VMDK", "tmpVDBase.vmdk", "tmpVDDiff.vmdk", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK test failed (new image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDOpenCreateWriteMerge("VMDK", "tmpVDBase.vmdk", "tmpVDDiff.vmdk", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK test failed (existing image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VMDK_TEST */
+#ifdef VHD_TEST
+ rc = tstVDCreateWriteOpenRead("VHD", "tmpVDCreate.vhd", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VHD test failed (creating image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+
+ rc = tstVDOpenCreateWriteMerge("VHD", "tmpVDBase.vhd", "tmpVDDiff.vhd", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VHD test failed (existing image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VHD_TEST */
+
+ /*
+ * Clean up any leftovers.
+ */
+ RTFileDelete("tmpVDCreate.vdi");
+ RTFileDelete("tmpVDCreate.vmdk");
+ RTFileDelete("tmpVDCreate.vhd");
+ RTFileDelete("tmpVDBase.vdi");
+ RTFileDelete("tmpVDDiff.vdi");
+ RTFileDelete("tmpVDBase.vmdk");
+ RTFileDelete("tmpVDDiff.vmdk");
+ RTFileDelete("tmpVDBase.vhd");
+ RTFileDelete("tmpVDDiff.vhd");
+ RTFileDelete("tmpVDCreate-s001.vmdk");
+ RTFileDelete("tmpVDCreate-s002.vmdk");
+ RTFileDelete("tmpVDCreate-s003.vmdk");
+ RTFileDelete("tmpVDRename.vmdk");
+ RTFileDelete("tmpVDRename-s001.vmdk");
+ RTFileDelete("tmpVDRename-s002.vmdk");
+ RTFileDelete("tmpVDRename-s003.vmdk");
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: unloading backends failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ /*
+ * Summary
+ */
+ if (!g_cErrors)
+ RTPrintf("tstVD: SUCCESS\n");
+ else
+ RTPrintf("tstVD: FAILURE - %d errors\n", g_cErrors);
+
+ return !!g_cErrors;
+}
+
diff --git a/src/VBox/Devices/Storage/testcase/tstVDI.cpp b/src/VBox/Devices/Storage/testcase/tstVDI.cpp
new file mode 100644
index 000000000..aaabe3ce3
--- /dev/null
+++ b/src/VBox/Devices/Storage/testcase/tstVDI.cpp
@@ -0,0 +1,121 @@
+/** @file
+ *
+ * Simple VBox HDD container test utility.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+#include <VBox/err.h>
+#include <VBox/VBoxHDD.h>
+#include <iprt/file.h>
+#include <iprt/initterm.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/mem.h>
+
+
+int dotest(const char *pszBaseFilename, const char *pszDiffFilename)
+{
+ PVDIDISK pVdi = VDIDiskCreate();
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ VDIDiskCloseAllImages(pVdi); \
+ return rc; \
+ } \
+ } while (0)
+
+
+ int rc = VDIDiskOpenImage(pVdi, pszBaseFilename, VDI_OPEN_FLAGS_NORMAL);
+ RTPrintf("openImage() rc=%Rrc\n", rc);
+ if (RT_FAILURE(rc))
+ {
+ rc = VDICreateBaseImage(pszBaseFilename, VDI_IMAGE_TYPE_NORMAL,
+#ifdef _MSC_VER
+ (1000 * 1024 * 1024UI64),
+#else
+ (1000 * 1024 * 1024ULL),
+#endif
+ "Test image", NULL, NULL);
+ CHECK("createImage()");
+
+ rc = VDIDiskOpenImage(pVdi, pszBaseFilename, VDI_OPEN_FLAGS_NORMAL);
+ CHECK("openImage()");
+ }
+
+ void *pvBuf = RTMemAlloc(1*1124*1024);
+
+ memset(pvBuf, 0x33, 1*1124*1024);
+ rc = VDIDiskWrite(pVdi, 20*1024*1024 + 594040, pvBuf, 1024*1024);
+ CHECK("write()");
+
+ memset(pvBuf, 0x46, 1*1124*1024);
+ rc = VDIDiskWrite(pVdi, 20*1024*1024 + 594040, pvBuf, 1024);
+ CHECK("write()");
+
+ memset(pvBuf, 0x51, 1*1124*1024);
+ rc = VDIDiskWrite(pVdi, 40*1024*1024 + 594040, pvBuf, 1024);
+ CHECK("write()");
+
+ rc = VDIDiskCreateOpenDifferenceImage(pVdi, pszDiffFilename, "Test diff image", NULL, NULL);
+ CHECK("create undo");
+// rc = VHDDOpenSecondImage(pVdi, "undoimg.vdi");
+// RTPrintf("open undo rc=%Rrc\n", rc);
+
+ memset(pvBuf, '_', 1*1124*1024);
+ rc = VDIDiskWrite(pVdi, 20*1024*1024 + 594040, pvBuf, 512);
+ CHECK("write()");
+
+ rc = VDIDiskWrite(pVdi, 22*1024*1024 + 594040, pvBuf, 78263);
+ CHECK("write()");
+ rc = VDIDiskWrite(pVdi, 13*1024*1024 + 594040, pvBuf, 782630);
+ CHECK("write()");
+ rc = VDIDiskWrite(pVdi, 44*1024*1024 + 594040, pvBuf, 67899);
+ CHECK("write()");
+
+ RTPrintf("committing..\n");
+ VDIDiskDumpImages(pVdi);
+ rc = VDIDiskCommitLastDiff(pVdi, NULL, NULL);
+ CHECK("commit last diff");
+ VDIDiskCloseAllImages(pVdi);
+#undef CHECK
+ return 0;
+}
+
+
+int main()
+{
+ RTR3Init();
+
+ RTFileDelete("tmpVdiBase.vdi");
+ RTFileDelete("tmpVdiDiff.vdi");
+
+ int rc = dotest("tmpVdiBase.vdi", "tmpVdiDiff.vdi");
+ if (!rc)
+ RTPrintf("tstVDI: SUCCESS\n");
+ else
+ RTPrintf("tstVDI: FAILURE\n");
+
+ RTFileDelete("tmpVdiBase.vdi");
+ RTFileDelete("tmpVdiDiff.vdi");
+ return !!rc;
+}
+
diff --git a/src/VBox/Devices/Storage/testcase/vditool.cpp b/src/VBox/Devices/Storage/testcase/vditool.cpp
new file mode 100644
index 000000000..b36db757f
--- /dev/null
+++ b/src/VBox/Devices/Storage/testcase/vditool.cpp
@@ -0,0 +1,445 @@
+/** @file
+ *
+ * VBox HDD container maintenance/conversion utility
+ */
+
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc.
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
+ * Clara, CA 95054 USA or visit http://www.sun.com if you need
+ * additional information or have any questions.
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <VBox/VBoxHDD.h>
+#include <iprt/alloc.h>
+#include <iprt/file.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/initterm.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <stdlib.h>
+
+
+
+static void ascii2upper(char *psz)
+{
+ for (;*psz; psz++)
+ if (*psz >= 'a' && *psz <= 'z')
+ *psz += 'A' - 'a';
+}
+
+static int UsageExit()
+{
+ RTPrintf("Usage: vditool <Command> [Params]\n" \
+ "Commands and params:\n" \
+ " NEW Filename Mbytes - create new image;\n" \
+ " DD Filename DDFilename - create new image from DD format image;\n" \
+ " CONVERT Filename - convert VDI image from old format;\n" \
+ " DUMP Filename - debug dump;\n" \
+ " RESETGEO Filename - reset geometry information;\n" \
+ " COPY FromImage ToImage - make image copy;\n" \
+ " COPYDD FromImage DDFilename - make a DD copy of the image;\n" \
+ " SHRINK Filename - optimize (reduce) VDI image size.\n");
+ return 1;
+}
+
+static int SyntaxError(const char *pszMsg)
+{
+ RTPrintf("Syntax error: %s\n\n", pszMsg);
+ UsageExit();
+ return 1;
+}
+
+/**
+ * Our internal functions use UTF8
+ */
+static int FilenameToUtf8(char **pszUtf8Filename, const char *pszFilename)
+{
+ int rc = RTStrCurrentCPToUtf8(pszUtf8Filename, pszFilename);
+ if (RT_FAILURE(rc))
+ RTPrintf("Error converting filename '%s' to UTF8! (rc=%Rrc)\n",
+ pszFilename, rc);
+ return rc;
+}
+
+/**
+ * Prints a done message indicating success or failure.
+ * @returns rc
+ * @param rc Status code.
+ */
+static int PrintDone(int rc)
+{
+ if (rc == VINF_SUCCESS)
+ RTPrintf("The operation completed successfully!\n");
+ else if (RT_SUCCESS(rc))
+ RTPrintf("The operation completed successfully! (rc=%Rrc)\n", rc);
+ else
+ RTPrintf("FAILURE: %Rrf (%Rrc)\n", rc, rc);
+ return rc;
+}
+
+static int NewImage(const char *pszFilename, uint32_t cMBs)
+{
+ RTPrintf("Creating VDI: file=\"%s\" size=%u MB...\n",
+ pszFilename, cMBs);
+
+ /* translate argv[] to UTF8 */
+ char *pszUtf8Filename;
+ int rc = FilenameToUtf8(&pszUtf8Filename, pszFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = VDICreateBaseImage(pszUtf8Filename,
+ VDI_IMAGE_TYPE_NORMAL,
+ (uint64_t)cMBs * (uint64_t)(1024 * 1024),
+ "Newly created test image", NULL, NULL);
+ return PrintDone(rc);
+}
+
+static int ConvertDDImage(const char *pszFilename, const char *pszDDFilename)
+{
+ RTPrintf("Converting VDI: from DD image file=\"%s\" to file=\"%s\"...\n",
+ pszDDFilename, pszFilename);
+
+ /* translate argv[] to UTF8 */
+ char *pszUtf8Filename, *pszUtf8DDFilename;
+ int rc = FilenameToUtf8(&pszUtf8Filename, pszFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = FilenameToUtf8(&pszUtf8DDFilename, pszDDFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* open raw image file. */
+ RTFILE File;
+ rc = RTFileOpen(&File, pszUtf8DDFilename, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("File=\"%s\" open error: %Rrf\n", pszDDFilename, rc);
+ return rc;
+ }
+
+ /* get image size. */
+ uint64_t cbFile;
+ rc = RTFileGetSize(File, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Creating fixed image with size %u Bytes...\n", (unsigned)cbFile);
+ rc = VDICreateBaseImage(pszUtf8Filename,
+ VDI_IMAGE_TYPE_FIXED,
+ cbFile,
+ "Converted from DD test image", NULL, NULL);
+ PrintDone(rc);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Writing data...\n");
+ PVDIDISK pVdi = VDIDiskCreate();
+ rc = VDIDiskOpenImage(pVdi, pszUtf8Filename, VDI_OPEN_FLAGS_NORMAL);
+ if (RT_SUCCESS(rc))
+ {
+ /* alloc work buffer. */
+ void *pvBuf = RTMemAlloc(VDIDiskGetBufferSize(pVdi));
+ if (pvBuf)
+ {
+ uint64_t off = 0;
+ while (off < cbFile)
+ {
+ size_t cbRead = 0;
+ rc = RTFileRead(File, pvBuf, VDIDiskGetBufferSize(pVdi), &cbRead);
+ if (RT_FAILURE(rc) || !cbRead)
+ break;
+ rc = VDIDiskWrite(pVdi, off, pvBuf, cbRead);
+ if (RT_FAILURE(rc))
+ break;
+ off += cbRead;
+ }
+
+ RTMemFree(pvBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ VDIDiskCloseImage(pVdi);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ /* delete image on error */
+ VDIDeleteImage(pszUtf8Filename);
+ }
+ PrintDone(rc);
+ }
+ }
+ RTFileClose(File);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) ProcessCallback(PVM pVM, unsigned uPercent, void *pvUser)
+{
+ unsigned *pPercent = (unsigned *)pvUser;
+
+ if (*pPercent != uPercent)
+ {
+ *pPercent = uPercent;
+ RTPrintf(".");
+ if ((uPercent % 10) == 0 && uPercent)
+ RTPrintf("%d%%", uPercent);
+ RTStrmFlush(g_pStdOut);
+ }
+
+ return VINF_SUCCESS;
+}
+
+static int ConvertOldImage(const char *pszFilename)
+{
+ RTPrintf("Converting VDI image file=\"%s\" to a new format...\n"
+ "progress: 0%%",
+ pszFilename);
+
+ /* translate argv[] to UTF8 */
+ char *pszUtf8Filename;
+ int rc = FilenameToUtf8(&pszUtf8Filename, pszFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ unsigned uPercent = 0;
+ rc = VDIConvertImage(pszUtf8Filename, ProcessCallback, &uPercent);
+ RTPrintf("\n");
+ return PrintDone(rc);
+}
+
+static int DumpImage(const char *pszFilename)
+{
+ RTPrintf("Dumping VDI image file=\"%s\" into the log file...\n", pszFilename);
+ PVDIDISK pVdi = VDIDiskCreate();
+
+ /* translate argv[] to UTF8 */
+ char *pszUtf8Filename;
+ int rc = FilenameToUtf8(&pszUtf8Filename, pszFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = VDIDiskOpenImage(pVdi, pszUtf8Filename, VDI_OPEN_FLAGS_READONLY);
+ if (RT_SUCCESS(rc))
+ {
+ VDIDiskDumpImages(pVdi);
+ VDIDiskCloseAllImages(pVdi);
+ }
+ return PrintDone(rc);
+}
+
+static int ResetImageGeometry(const char *pszFilename)
+{
+ RTPrintf("Resetting geometry info of VDI image file=\"%s\"\n", pszFilename);
+ PVDIDISK pVdi = VDIDiskCreate();
+
+ /* translate argv[] to UTF8 */
+ char *pszUtf8Filename;
+ int rc = FilenameToUtf8(&pszUtf8Filename, pszFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = VDIDiskOpenImage(pVdi, pszUtf8Filename, VDI_OPEN_FLAGS_NORMAL);
+ if (RT_SUCCESS(rc))
+ {
+ PDMMEDIAGEOMETRY LCHSGeometry = {0, 0, 0};
+ rc = VDIDiskSetLCHSGeometry(pVdi, &LCHSGeometry);
+ }
+ VDIDiskCloseImage(pVdi);
+ return PrintDone(rc);
+}
+
+static int CopyImage(const char *pszDstFile, const char *pszSrcFile)
+{
+ RTPrintf("Copying VDI image file=\"%s\" to image file=\"%s\"...\n"
+ "progress: 0%%",
+ pszSrcFile, pszDstFile);
+
+ /* translate argv[] to UTF8 */
+ char *pszUtf8SrcFile, *pszUtf8DstFile;
+ int rc = FilenameToUtf8(&pszUtf8SrcFile, pszSrcFile);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = FilenameToUtf8(&pszUtf8DstFile, pszDstFile);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ unsigned uPrecent = 0;
+ rc = VDICopyImage(pszUtf8DstFile, pszUtf8SrcFile, NULL, ProcessCallback, &uPrecent);
+ RTPrintf("\n");
+ return PrintDone(rc);
+}
+
+static int CopyToDD(const char *pszDstFile, const char *pszSrcFile)
+{
+ RTPrintf("Copying VDI image file=\"%s\" to DD file=\"%s\"...\n",
+ pszSrcFile, pszDstFile);
+ PVDIDISK pVdi = VDIDiskCreate();
+
+ /* translate argv[] to UTF8 */
+ char *pszUtf8SrcFile, *pszUtf8DstFile;
+ int rc = FilenameToUtf8(&pszUtf8SrcFile, pszSrcFile);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = FilenameToUtf8(&pszUtf8DstFile, pszDstFile);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = VDIDiskOpenImage(pVdi, pszUtf8SrcFile, VDI_OPEN_FLAGS_NORMAL);
+ if (RT_SUCCESS(rc))
+ {
+ RTFILE FileDst;
+ rc = RTFileOpen(&FileDst, pszUtf8DstFile, RTFILE_O_CREATE | RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbSrc = VDIDiskGetSize(pVdi);
+ const unsigned cbBuf = VDIDiskGetBlockSize(pVdi); /* or perhaps VDIDiskGetBufferSize(pVdi)? */
+ void *pvBuf = RTMemAlloc(cbBuf);
+ if (pvBuf)
+ {
+ uint64_t off = 0;
+ while (off < cbSrc)
+ {
+ rc = VDIDiskRead(pVdi, off, pvBuf, cbBuf);
+ if (RT_FAILURE(rc))
+ break;
+ rc = RTFileWrite(FileDst, pvBuf, cbBuf, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ off += cbBuf;
+ }
+ RTMemFree(pvBuf);
+ }
+ RTFileClose(FileDst);
+ }
+ }
+ VDIDiskCloseImage(pVdi);
+ return PrintDone(rc);
+}
+
+static int ShrinkImage(const char *pszFilename)
+{
+ RTPrintf("Shrinking VDI image file=\"%s\"...\n"
+ "progress: 0%%",
+ pszFilename);
+
+ /* translate argv[] to UTF8 */
+ char *pszUtf8Filename;
+ int rc = FilenameToUtf8(&pszUtf8Filename, pszFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ unsigned uPrecent;
+ rc = VDIShrinkImage(pszUtf8Filename, ProcessCallback, &uPrecent);
+ RTPrintf("\n");
+ return PrintDone(rc);
+}
+
+int main(int argc, char **argv)
+{
+ putenv((char*)"VBOX_LOG_DEST=stdout");
+ putenv((char*)"VBOX_LOG_FLAGS=");
+
+ RTR3Init();
+ RTPrintf("vditool Copyright (c) 2008 Sun Microsystems, Inc.\n\n");
+
+ /*
+ * Do cmd line parsing.
+ */
+ if (argc < 2)
+ return UsageExit();
+
+ char szCmd[16];
+ if (strlen(argv[1]) >= sizeof(szCmd))
+ return SyntaxError("Invalid command!");
+ strcpy(szCmd, argv[1]);
+ ascii2upper(szCmd);
+
+ PRTLOGGER pLogger;
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ int rc = RTLogCreate(&pLogger, 0, "all",
+ NULL, RT_ELEMENTS(s_apszGroups), s_apszGroups,
+ RTLOGDEST_STDOUT, NULL);
+ RTLogRelSetDefaultInstance(pLogger);
+
+ if (strcmp(szCmd, "NEW") == 0)
+ {
+ if (argc != 4)
+ return SyntaxError("Invalid argument count!");
+
+ uint32_t cMBs;
+ rc = RTStrToUInt32Ex(argv[3], NULL, 10, &cMBs);
+ if (RT_FAILURE(rc))
+ return SyntaxError("Invalid number!");
+ if ( cMBs < 2
+ || cMBs > 1024*1024)
+ {
+ RTPrintf("error: Disk size %RU32 (MB) is not within the range %u-%u!\n",
+ cMBs, 2, 1024*1024);
+ return 1;
+ }
+
+ rc = NewImage(argv[2], cMBs);
+ }
+ else if (strcmp(szCmd, "DD") == 0)
+ {
+ if (argc != 4)
+ return SyntaxError("Invalid argument count!");
+ rc = ConvertDDImage(argv[2], argv[3]);
+ }
+ else if (strcmp(szCmd, "CONVERT") == 0)
+ {
+ if (argc != 3)
+ return SyntaxError("Invalid argument count!");
+ rc = ConvertOldImage(argv[2]);
+ }
+ else if (strcmp(szCmd, "DUMP") == 0)
+ {
+ if (argc != 3)
+ return SyntaxError("Invalid argument count!");
+ rc = DumpImage(argv[2]);
+ }
+ else if (strcmp(szCmd, "RESETGEO") == 0)
+ {
+ if (argc != 3)
+ return SyntaxError("Invalid argument count!");
+ rc = ResetImageGeometry(argv[2]);
+ }
+ else if (strcmp(szCmd, "COPY") == 0)
+ {
+ if (argc != 4)
+ return SyntaxError("Invalid argument count!");
+ rc = CopyImage(argv[3], argv[2]);
+ }
+ else if (strcmp(szCmd, "COPYDD") == 0)
+ {
+ if (argc != 4)
+ return SyntaxError("Invalid argument count!");
+ rc = CopyToDD(argv[3], argv[2]);
+ }
+ else if (strcmp(szCmd, "SHRINK") == 0)
+ {
+ if (argc != 3)
+ return SyntaxError("Invalid argument count!");
+ rc = ShrinkImage(argv[2]);
+ }
+ else
+ return SyntaxError("Invalid command!");
+
+ RTLogFlush(NULL);
+ return !RT_SUCCESS(rc);
+}