diff options
| author | Michael Meskes <meskes@debian.org> | 2009-02-09 17:42:08 +0100 |
|---|---|---|
| committer | Michael Meskes <meskes@debian.org> | 2009-02-09 17:42:08 +0100 |
| commit | 5b633c860b9ccb98910812f91c2474fda316b50b (patch) | |
| tree | db6656e828509048e53ad8aea69d2edb9f64f442 /src/VBox/Devices/Storage | |
| parent | ce414e6eec1583def0dc7be0926f1a07364cb5e3 (diff) | |
| download | virtualbox-5b633c860b9ccb98910812f91c2474fda316b50b.tar.gz | |
Imported 2.1.2-dfsgupstream/2.1.2-dfsg
Diffstat (limited to 'src/VBox/Devices/Storage')
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); +} |
