DrvVD.cpp revision 38ceb5cfc147448ea4087a1f5971cb8fb4434bd9
/* $Id$ */
/** @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;
* 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/pdmasynccompletion.h>
#include <iprt/semaphore.h>
#ifdef VBOX_WITH_INIP
/* All lwip header files are not C++ safe. So hack around this. */
#endif /* VBOX_WITH_INIP */
#include "Builtins.h"
#ifdef VBOX_WITH_INIP
/* Small hack to get at lwIP initialized status */
extern bool DevINIPConfigured(void);
#endif /* VBOX_WITH_INIP */
/*******************************************************************************
* Defined types, constants and macros *
*******************************************************************************/
/** Converts a pointer to VBOXDISK::IMedia to a PVBOXDISK. */
#define PDMIMEDIA_2_VBOXDISK(pInterface) \
/** Converts a pointer to PDMDRVINS::IBase to a PPDMDRVINS. */
#define PDMIBASE_2_DRVINS(pInterface) \
/** Converts a pointer to PDMDRVINS::IBase to a PVBOXDISK. */
#define PDMIBASE_2_VBOXDISK(pInterface) \
/** Converts a pointer to VBOXDISK::IMediaAsync to a PVBOXDISK. */
#define PDMIMEDIAASYNC_2_VBOXDISK(pInterface) \
/**
* VBox disk container, image information, private part.
*/
typedef struct VBOXIMAGE
{
/** Pointer to next image. */
/** Pointer to list of VD interfaces. Per-image. */
/** Common structure for the configuration information interface. */
} VBOXIMAGE, *PVBOXIMAGE;
/**
* Storage backend data.
*/
typedef struct DRVVDSTORAGEBACKEND
{
/** PDM async completion end point. */
/** The template. */
/** Event semaphore for synchronous operations. */
/** Flag whether a synchronous operation is currently pending. */
volatile bool fSyncIoPending;
/** Callback routine */
/**
* VBox disk container media main structure, private part.
*
* @implements PDMIMEDIA
* @implements PDMIMEDIAASYNC
* @implements VDINTERFACEERROR
* @implements VDINTERFACETCPNET
* @implements VDINTERFACEASYNCIO
* @implements VDINTERFACECONFIG
*/
typedef struct VBOXDISK
{
/** The VBox disk container. */
/** The media interface. */
/** Pointer to the driver instance. */
/** Flag whether suspend has changed image open mode to read only. */
bool fTempReadOnly;
/** Flag whether to use the runtime (true) or startup error facility. */
bool fErrorUseRuntime;
/** Pointer to list of VD interfaces. Per-disk. */
/** Common structure for the supported error interface. */
/** Callback table for error interface. */
/** Common structure for the supported TCP network stack interface. */
/** Callback table for TCP network stack interface. */
/** Common structure for the supported async I/O interface. */
/** Callback table for async I/O interface. */
/** Callback table for the configuration information interface. */
/** Flag whether opened disk suppports async I/O operations. */
bool fAsyncIOSupported;
/** The async media interface. */
/** The async media port interface above. */
/** Pointer to the list of data we need to keep per image. */
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
/**
* Internal: allocate new image descriptor and put it in the list
*/
{
if (pImage)
{
}
return pImage;
}
/**
* Internal: free the list of images descriptors.
*/
{
{
RTMemFree(p);
}
}
/**
* Undo the temporary read-only status of the image.
*
* @returns VBox status code.
* @param pThis The driver instance data.
*/
{
int rc = VINF_SUCCESS;
if (pThis->fTempReadOnly)
{
unsigned uOpenFlags;
if (RT_SUCCESS(rc))
pThis->fTempReadOnly = false;
else
}
return rc;
}
/*******************************************************************************
* Error reporting callback *
*******************************************************************************/
{
if (pThis->fErrorUseRuntime)
/* We must not pass VMSETRTERR_FLAGS_FATAL as it could lead to a
* deadlock: We are probably executed in a thread context != EMT
* and the EM thread would wait until every thread is suspended
* but we would wait for the EM thread ... */
else
}
/*******************************************************************************
* VD Async I/O interface implementation *
*******************************************************************************/
static DECLCALLBACK(void) drvvdAsyncTaskCompleted(PPDMDRVINS pDrvIns, void *pvTemplateUser, void *pvUser)
{
{
pStorageBackend->fSyncIoPending = false;
}
else
{
int rc = VINF_VD_ASYNC_IO_FINISHED;
void *pvCallerUser = NULL;
if (pStorageBackend->pfnCompleted)
else
if (rc == VINF_VD_ASYNC_IO_FINISHED)
{
}
else
AssertMsg(rc == VERR_VD_ASYNC_IO_IN_PROGRESS, ("Invalid return code from disk backend rc=%Rrc\n", rc));
}
}
static DECLCALLBACK(int) drvvdAsyncIOOpen(void *pvUser, const char *pszLocation, unsigned uOpenFlags,
{
PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)RTMemAllocZ(sizeof(DRVVDSTORAGEBACKEND));
int rc = VINF_SUCCESS;
if (pStorageBackend)
{
pStorageBackend->fSyncIoPending = false;
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
return VINF_SUCCESS;
}
}
}
}
else
rc = VERR_NO_MEMORY;
return rc;
}
{
return VINF_SUCCESS;;
}
{
}
{
return VERR_NOT_SUPPORTED;
}
{
int rc = PDMR3AsyncCompletionEpRead(pStorageBackend->pEndpoint, uOffset, &DataSeg, 1, cbRead, NULL, &pTask);
if (RT_FAILURE(rc))
return rc;
if (rc == VINF_AIO_TASK_PENDING)
{
/* Wait */
}
else
if (pcbRead)
return VINF_SUCCESS;
}
{
int rc = PDMR3AsyncCompletionEpWrite(pStorageBackend->pEndpoint, uOffset, &DataSeg, 1, cbWrite, NULL, &pTask);
if (RT_FAILURE(rc))
return rc;
if (rc == VINF_AIO_TASK_PENDING)
{
/* Wait */
}
else
if (pcbWritten)
*pcbWritten = cbWrite;
return VINF_SUCCESS;
}
{
if (RT_FAILURE(rc))
return rc;
if (rc == VINF_AIO_TASK_PENDING)
{
/* Wait */
}
else
return VINF_SUCCESS;
}
void **ppTask)
{
return PDMR3AsyncCompletionEpRead(pStorageBackend->pEndpoint, uOffset, paSegments, cSegments, cbRead,
}
void **ppTask)
{
return PDMR3AsyncCompletionEpWrite(pStorageBackend->pEndpoint, uOffset, paSegments, cSegments, cbWrite,
}
void *pvCompletion, void **ppTask)
{
}
#endif /* VBOX_WITH_PDM_ASYNC_COMPLETION */
/*******************************************************************************
* VD Configuration interface implementation *
*******************************************************************************/
{
}
{
}
{
}
#ifdef VBOX_WITH_INIP
/*******************************************************************************
* 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. */
{
return VERR_NET_HOST_UNREACHABLE;
}
/* Create socket and connect. */
if (Sock != -1)
{
struct sockaddr_in InAddr = {0};
{
return VINF_SUCCESS;
}
}
else
return rc;
}
/** @copydoc VDINTERFACETCPNET::pfnClientClose */
{
return VINF_SUCCESS; /** @todo real solution needed */
}
/** @copydoc VDINTERFACETCPNET::pfnSelectOne */
{
int rc;
if (cMillies == RT_INDEFINITE_WAIT)
else
{
}
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 */
{
AssertMsgFailed(("Invalid params\n"));
return VERR_INVALID_PARAMETER;
}
/*
* Read loop.
* If pcbRead is NULL we have to fill the entire buffer!
*/
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. */
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;
break;
/* next */
}
return VINF_SUCCESS;
}
/** @copydoc VDINTERFACETCPNET::pfnWrite */
{
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. */
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",
} while (cbBuffer);
return VINF_SUCCESS;
}
/** @copydoc VDINTERFACETCPNET::pfnFlush */
{
int fFlag = 1;
fFlag = 0;
return VINF_SUCCESS;
}
#endif /* VBOX_WITH_INIP */
/*******************************************************************************
* Media interface methods *
*******************************************************************************/
/** @copydoc PDMIMEDIA::pfnRead */
{
if (RT_SUCCESS(rc))
return rc;
}
/** @copydoc PDMIMEDIA::pfnWrite */
{
return rc;
}
/** @copydoc PDMIMEDIA::pfnFlush */
{
return rc;
}
/** @copydoc PDMIMEDIA::pfnGetSize */
{
return cb;
}
/** @copydoc PDMIMEDIA::pfnIsReadOnly */
{
return f;
}
/** @copydoc PDMIMEDIA::pfnBiosGetPCHSGeometry */
{
if (RT_FAILURE(rc))
{
}
return rc;
}
/** @copydoc PDMIMEDIA::pfnBiosSetPCHSGeometry */
{
if (rc == VERR_VD_GEOMETRY_NOT_SET)
return rc;
}
/** @copydoc PDMIMEDIA::pfnBiosGetLCHSGeometry */
{
if (RT_FAILURE(rc))
{
}
return rc;
}
/** @copydoc PDMIMEDIA::pfnBiosSetLCHSGeometry */
{
if (rc == VERR_VD_GEOMETRY_NOT_SET)
return rc;
}
/** @copydoc PDMIMEDIA::pfnGetUuid */
{
return rc;
}
/*******************************************************************************
* Async Media interface methods *
*******************************************************************************/
{
return rc;
}
{
return rc;
}
/*******************************************************************************
* Async transport port interface methods *
*******************************************************************************/
{
return VERR_NOT_IMPLEMENTED;
}
/*******************************************************************************
* Base interface methods *
*******************************************************************************/
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
{
PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAASYNC, pThis->fAsyncIOSupported ? &pThis->IMediaAsync : NULL);
return NULL;
}
/*******************************************************************************
* Saved state notification methods *
*******************************************************************************/
/**
* Load done callback for re-opening the image writable during teleportation.
*
* This is called both for successful and failed load runs, we only care about
* successfull ones.
*
* @returns VBox status code.
* @param pDrvIns The driver instance.
* @param pSSM The saved state handle.
*/
{
/* Drop out if we don't have any work to do or if it's a failed load. */
if ( !pThis->fTempReadOnly
return VINF_SUCCESS;
N_("Failed to write lock the images"));
return VINF_SUCCESS;
}
/*******************************************************************************
* Driver methods *
*******************************************************************************/
{
/*
* We must close the disk here to ensure that
* the backend closes all files before the
* async transport driver is destructed.
*/
}
/**
* VM resume notification that we use to undo what the temporary read-only image
* mode set by drvvdSuspend.
*
* Also switch to runtime error mode if we're resuming after a state load
* without having been powered on first.
*
* @param pDrvIns The driver instance data.
*
* @todo The VMSetError vs VMSetRuntimeError mess must be fixed elsewhere,
* we're making assumptions about Main behavior here!
*/
{
pThis->fErrorUseRuntime = true;
}
/**
* The VM is being suspended, temporarily change to read-only image mode.
*
* This is important for several reasons:
* -# It makes sure that there are no pending writes to the image. Most
* backends implements this by closing and reopening the image in read-only
* mode.
* -# It allows Main to read the images during snapshotting without having
* to account for concurrent writes.
* -# This is essential for making teleportation targets sharing images work
* right. Both with regards to caching and with regards to file sharing
* locks (RTFILE_O_DENY_*). (See also drvvdLoadDone.)
*
* @param pDrvIns The driver instance data.
*/
{
{
unsigned uOpenFlags;
pThis->fTempReadOnly = true;
}
}
/**
* VM PowerOn notification for undoing the TempReadOnly config option and
* changing to runtime error mode.
*
* @param pDrvIns The driver instance data.
*
* @todo The VMSetError vs VMSetRuntimeError mess must be fixed elsewhere,
* we're making assumptions about Main behavior here!
*/
{
pThis->fErrorUseRuntime = true;
}
/**
* @copydoc FNPDMDRVDESTRUCT
*/
{
{
}
}
/**
* Construct a VBox disk media driver instance.
*
* @copydoc FNPDMDRVCONSTRUCT
*/
{
int rc = VINF_SUCCESS;
bool fReadOnly; /**< True if the media is read-only. */
bool fHonorZeroWrites; /**< True if zero blocks should be written. */
/*
* Init the static parts.
*/
pThis->fTempReadOnly = false;
pThis->fAsyncIOSupported = false;
/* IMedia */
/* IMediaAsync */
/* Initialize supported VD interfaces. */
/* This is just prepared here, the actual interface is per-image, so it's
* added later. No need to have separate callback tables. */
/* List of images is empty now. */
/* Try to attach async media port interface above.*/
/*
* Validate configuration and find all parent images.
* It's sort of up side down from the image dependency tree.
*/
bool fHostIP = false;
bool fUseNewIo = false;
unsigned iLevel = 0;
for (;;)
{
bool fValid;
{
/* Toplevel configuration additionally contains the global image
* open flags. Some might be converted to per-image flags later. */
"Format\0Path\0"
"ReadOnly\0TempReadOnly\0HonorZeroWrites\0"
"HostIPStack\0UseNewIo\0");
}
else
{
/* All other image configurations only contain image name and
* the format information. */
}
if (!fValid)
{
break;
}
{
if (RT_FAILURE(rc))
{
N_("DrvVD: Configuration error: Querying \"HostIPStack\" as boolean failed"));
break;
}
if (RT_FAILURE(rc))
{
N_("DrvVD: Configuration error: Querying \"HonorZeroWrites\" as boolean failed"));
break;
}
if (RT_FAILURE(rc))
{
N_("DrvVD: Configuration error: Querying \"ReadOnly\" as boolean failed"));
break;
}
if (RT_FAILURE(rc))
{
N_("DrvVD: Configuration error: Querying \"TempReadOnly\" as boolean failed"));
break;
}
{
N_("DrvVD: Configuration error: Both \"ReadOnly\" and \"TempReadOnly\" are set"));
break;
}
if (RT_FAILURE(rc))
{
N_("DrvVD: Configuration error: Querying \"UseNewIo\" as boolean failed"));
break;
}
}
if (!pParent)
break;
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)
{
}
else
{
#ifndef VBOX_WITH_INIP
#else /* VBOX_WITH_INIP */
#endif /* VBOX_WITH_INIP */
}
if (RT_SUCCESS(rc))
{
&pThis->pVDIfsDisk);
}
{
#else /* !VBOX_WITH_PDM_ASYNC_COMPLETION */
#endif /* !VBOX_WITH_PDM_ASYNC_COMPLETION */
}
if (RT_SUCCESS(rc))
{
/* Error message is already set correctly. */
}
}
if (pThis->pDrvMediaAsyncPort)
pThis->fAsyncIOSupported = true;
{
/* Allocate per-image data. */
if (!pImage)
{
rc = VERR_NO_MEMORY;
break;
}
/*
* Read the image configuration.
*/
if (RT_FAILURE(rc))
{
N_("DrvVD: Configuration error: Querying \"Path\" as string failed"));
break;
}
if (RT_FAILURE(rc))
{
N_("DrvVD: Configuration error: Querying \"Format\" as string failed"));
break;
}
/*
* Open the image.
*/
unsigned uOpenFlags;
else
if (fHonorZeroWrites)
if (pThis->fAsyncIOSupported)
/* Try to open backend in async I/O mode first. */
if (rc == VERR_NOT_SUPPORTED)
{
pThis->fAsyncIOSupported = false;
}
if (RT_SUCCESS(rc))
{
&& !fReadOnly
&& !pThis->fTempReadOnly
&& iLevel == 0)
{
N_("Failed to open image '%s' for writing due to wrong permissions"),
pszName);
break;
}
}
else
{
break;
}
/* next */
iLevel--;
}
/*
* Register a load-done callback so we can undo TempReadOnly config before
* we get to drvvdResume. Autoamtically deregistered upon destruction.
*/
if (RT_SUCCESS(rc))
if (RT_FAILURE(rc))
{
/* drvvdDestruct does the rest. */
}
return rc;
}
/**
* VBox disk container media driver registration record.
*/
{
/* u32Version */
/* szName */
"VD",
/* szRCMod */
"",
/* szR0Mod */
"",
/* pszDescription */
"Generic VBox disk media driver.",
/* fFlags */
/* fClass. */
/* cMaxInstances */
~0,
/* cbInstance */
sizeof(VBOXDISK),
/* pfnConstruct */
/* pfnDestruct */
/* pfnRelocate */
NULL,
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
/* pfnReset */
NULL,
/* pfnSuspend */
/* pfnResume */
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnPowerOff */
/* pfnSoftReset */
NULL,
/* u32EndVersion */
};