VDICore.cpp revision c97989161fbe75bc14cea477a5443bbf474dd3ad
/** @file
* Virtual Disk Image (VDI), Core Code.
*/
/*
* Copyright (C) 2006-2007 innotek GmbH
*
* 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 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.
*
* If you received this file as part of a commercial VirtualBox
* distribution, then only the terms of your commercial VirtualBox
* license agreement apply instead of the previous paragraph.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DRV_VBOXHDD
#include "VDICore.h"
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static unsigned getPowerOfTwo(unsigned uNumber);
#if 0 /* unused */
#endif
/**
* 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++;
}
}
/**
* internal: init HDD preheader.
*/
{
}
/**
* internal: check HDD preheader.
*/
{
return VERR_VDI_INVALID_SIGNATURE;
return VERR_VDI_UNSUPPORTED_VERSION;
return VINF_SUCCESS;
}
/**
* internal: init HDD header. Always use latest header version.
* @param pHeader Assumes it was initially initialized to all zeros.
*/
{
#ifdef VBOX_STRICT
char achZero[VDI_IMAGE_COMMENT_SIZE] = {0};
#endif
if (pszComment)
{
}
/* Mark the geometry not-calculated. */
/* 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. */
}
/**
* internal: check HDD header.
*/
{
/* Check verion-dependend header parameters. */
switch (GET_MAJOR_HEADER_VERSION(pHeader))
{
case 0:
{
/* Old header version. */
break;
}
case 1:
{
/* Current header version. */
{
LogRel(("VDI: v1 header size wrong (%d < %d)\n",
return VERR_VDI_INVALID_HEADER;
}
{
LogRel(("VDI: v1 blocks offset wrong (%d < %d)\n",
return VERR_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_VDI_INVALID_HEADER;
}
{
{
LogRel(("VDI: v1 uuid of parent is 0)\n"));
return VERR_VDI_INVALID_HEADER;
}
{
LogRel(("VDI: v1 uuid of parent modification is 0\n"));
return VERR_VDI_INVALID_HEADER;
}
}
break;
}
default:
/* Unsupported. */
return VERR_VDI_UNSUPPORTED_VERSION;
}
/* Check common header parameters. */
bool fFailed = false;
{
fFailed = true;
}
{
fFailed = true;
}
{
LogRel(("VDI: wrong sector size (%d != %d)\n",
fFailed = true;
}
if ( getImageDiskSize(pHeader) == 0
|| getImageBlockSize(pHeader) == 0
|| getImageBlocks(pHeader) == 0
{
LogRel(("VDI: wrong size (%lld, %d, %d, %d)\n",
fFailed = true;
}
{
LogRel(("VDI: too many blocks allocated (%d > %d)\n"
" blocksize=%d disksize=%lld\n",
fFailed = true;
}
if ( getImageExtraBlockSize(pHeader) != 0
{
LogRel(("VDI: wrong extra size (%d, %d)\n",
fFailed = true;
}
{
LogRel(("VDI: wrong disk size (%d, %d, %lld)\n",
fFailed = true;
}
{
LogRel(("VDI: uuid of creator is 0\n"));
fFailed = true;
}
{
LogRel(("VDI: uuid of modificator is 0\n"));
fFailed = true;
}
}
/**
* internal: init VDIIMAGEDESC structure.
*/
{
}
/**
* internal: setup VDIIMAGEDESC structure by image header.
*/
{
if (pImage->offStartBlockData != 0)
}
/**
* internal: create image.
*/
{
/* Check args. */
/* Special check for comment length. */
if ( pszComment
{
return VERR_VDI_COMMENT_TOO_LONG;
}
if ( enmType == VDI_IMAGE_TYPE_UNDO
|| enmType == VDI_IMAGE_TYPE_DIFF)
{
{
/* Invalid parent image version. */
return VERR_VDI_UNSUPPORTED_VERSION;
}
/* get image params from the parent image. */
}
if (!pImage)
return VERR_NO_MEMORY;
vdiInitHeader(&pImage->Header, enmType, fFlags, pszComment, cbSize, VDI_IMAGE_DEFAULT_BLOCK_SIZE, 0);
if ( enmType == VDI_IMAGE_TYPE_UNDO
|| enmType == VDI_IMAGE_TYPE_DIFF)
{
/* Set up linkage information. */
}
pImage->paBlocks = (PVDIIMAGEBLOCKPOINTER)RTMemAlloc(sizeof(VDIIMAGEBLOCKPOINTER) * getImageBlocks(&pImage->Header));
{
return VERR_NO_MEMORY;
}
if (enmType != VDI_IMAGE_TYPE_FIXED)
{
/* for growing images mark all blocks in paBlocks as free. */
}
else
{
/* for fixed images mark all blocks in paBlocks as allocated */
}
/* Setup image parameters. */
/* create file */
if (VBOX_SUCCESS(rc))
{
/* Lock image exclusively to close any wrong access by VDI API calls. */
if (VBOX_FAILURE(rc))
{
cbLock = 0; /* Not locked. */
goto l_create_failed;
}
if (enmType == VDI_IMAGE_TYPE_FIXED)
{
/*
* Allocate & commit whole file if fixed image, it must be more
* effective than expanding file by write operations.
*/
}
else
{
/* Set file size to hold header and blocks array. */
}
if (VBOX_FAILURE(rc))
goto l_create_failed;
/* Generate image last-modify uuid */
/* Write pre-header. */
if (VBOX_FAILURE(rc))
goto l_create_failed;
/* Write header. */
if (VBOX_FAILURE(rc))
goto l_create_failed;
/* Write blocks array. */
if (VBOX_FAILURE(rc))
goto l_create_failed;
NULL);
if (VBOX_FAILURE(rc))
goto l_create_failed;
if ( (enmType == VDI_IMAGE_TYPE_FIXED)
&& (fFlags & VDI_IMAGE_FLAGS_ZERO_EXPAND))
{
/* Fill image with zeroes. */
if (VBOX_FAILURE(rc))
goto l_create_failed;
/* alloc tmp zero-filled buffer */
if (pvBuf)
{
/* do loop to fill all image. */
while (cbFill > 0)
{
if (VBOX_FAILURE(rc))
break;
if (pfnProgress)
{
pvUser);
if (VBOX_FAILURE(rc))
break;
}
}
}
else
{
/* alloc error */
rc = VERR_NO_MEMORY;
}
}
if (cbLock)
/* Delete image file if error occured while creating */
if (VBOX_FAILURE(rc))
}
if ( VBOX_SUCCESS(rc)
&& pfnProgress)
return rc;
}
/**
* Open an image.
* @internal
*/
{
/*
* Validate input.
*/
{
return VERR_FILENAME_TOO_LONG;
}
if (!pImage)
return VERR_NO_MEMORY;
/*
* Open the image.
*/
if (VBOX_FAILURE(rc))
{
if (!(fOpen & VDI_OPEN_FLAGS_READONLY))
{
/* Try to open image for reading only. */
if (VBOX_SUCCESS(rc))
}
if (VBOX_FAILURE(rc))
{
return rc;
}
}
/* Set up current image r/w state. */
/*
* Set initial file lock for reading header only.
* Length of lock doesn't matter, it just must include image header.
*/
if (VBOX_FAILURE(rc))
{
cbLock = 0;
goto l_open_failed;
}
/* Read pre-header. */
if (VBOX_FAILURE(rc))
goto l_open_failed;
if (VBOX_FAILURE(rc))
goto l_open_failed;
/* Read header. */
{
case 0:
break;
case 1:
break;
default:
break;
}
if (VBOX_FAILURE(rc))
goto l_open_failed;
if (VBOX_FAILURE(rc))
goto l_open_failed;
/* Check diff image correctness. */
if (pParent)
{
{
goto l_open_failed;
}
{
goto l_open_failed;
}
{
goto l_open_failed;
}
/* Check linkage data. */
{
goto l_open_failed;
}
}
/* Setup image parameters by header. */
/* reset modified flag into first-modified state. */
/* Image is validated, set working file lock on it. */
0,
cbLock);
if ( VBOX_FAILURE(rc)
{
/* Failed to lock image for writing, try read-only lock. */
if (VBOX_SUCCESS(rc))
}
if (VBOX_FAILURE(rc))
{
cbLock = 0; /* Not locked. */
goto l_open_failed;
}
/* Allocate memory for blocks array. */
pImage->paBlocks = (PVDIIMAGEBLOCKPOINTER)RTMemAlloc(sizeof(VDIIMAGEBLOCKPOINTER) * getImageBlocks(&pImage->Header));
{
rc = VERR_NO_MEMORY;
goto l_open_failed;
}
/* Read blocks array. */
if (VBOX_FAILURE(rc))
goto l_open_failed;
if (VBOX_FAILURE(rc))
goto l_open_failed;
/* all done. */
return VINF_SUCCESS;
/* Clean up. */
if (cbLock)
return rc;
}
/**
* internal: save header to file.
*/
{
/* Seek to header start. */
if (VBOX_SUCCESS(rc))
{
{
case 0:
break;
case 1:
break;
default:
break;
}
}
return rc;
}
/**
* internal: save block pointer to file, save header to file.
*/
{
/* Update image header. */
if (VBOX_SUCCESS(rc))
{
/* write only one block pointer. */
NULL);
if (VBOX_SUCCESS(rc))
sizeof(VDIIMAGEBLOCKPOINTER),
NULL);
}
return rc;
}
/**
* internal: save blocks array to file, save header to file.
*/
{
/* Update image header. */
if (VBOX_SUCCESS(rc))
{
/* write the block pointers array. */
if (VBOX_SUCCESS(rc))
NULL);
}
return rc;
}
/**
* internal: mark image as modified, if this is the first change - update image header
* on disk with a new uuidModify value.
*/
{
{
/* first modify - generate uuidModify and save to file. */
{
/* save header to file,
* note: no rc checking.
*/
}
}
}
/**
* internal: generate new uuidModify if the image was changed.
*/
{
{
/* generate new last-modified uuid */
}
}
/**
* internal: disables updates of the last-modified UUID
* when performing image writes.
*/
{
}
#if 0 /* unused */
/**
* internal: enables updates of the last-modified UUID
* when performing image writes.
*/
{
}
#endif
/**
* Flush the image file to disk.
*/
{
{
/* Update last-modified uuid if need. */
/* Save header. */
}
}
/**
* internal: close image file.
*/
{
/* Params checking. */
0,
/* free image resources */
}
/**
* internal: read data inside image block.
*
* note: uBlock must be valid, readed data must not overlap block bounds.
*/
{
{
/* block present in image file */
if (VBOX_SUCCESS(rc))
if (VBOX_FAILURE(rc))
Log(("vdiReadInBlock: rc=%Vrc filename=\"%s\" uBlock=%u offRead=%u cbToRead=%u u64Offset=%llu\n",
return rc;
}
/* Returns zeroes for both free and zero block types. */
return VINF_SUCCESS;
}
/**
* Read data from virtual HDD.
*
* @returns VBox status code.
* @param pDisk Pointer to VDI HDD container.
* @param offStart Offset of first reading byte from start of disk.
* @param pvBuf Pointer to buffer for reading data.
* @param cbToRead Number of bytes to read.
*/
{
/* sanity check */
/* Check params. */
|| cbToRead == 0)
{
return VERR_INVALID_PARAMETER;
}
/* Calculate starting block number and offset inside it. */
/* Save block size here for speed optimization. */
/* loop through blocks */
int rc;
for (;;)
{
unsigned to_read;
else
{
/* Differencing images are used, handle them. */
/* Search for image with allocated block. */
{
if (!pImage)
{
/* Block is not allocated in all images of chain. */
break;
}
}
}
if ( cbToRead == 0
|| VBOX_FAILURE(rc))
break;
/* goto next block */
uBlock++;
offRead = 0;
}
return rc;
}
/**
* internal: fill the whole block with zeroes.
*
* note: block id must be valid, block must be already allocated in file.
* note: if pDisk is NULL, the default buffer size is used
*/
{
int rc;
/* seek to start of block in file. */
if (VBOX_FAILURE(rc))
{
Log(("vdiFillBlockByZeroes: seek rc=%Vrc filename=\"%s\" uBlock=%u u64Offset=%llu\n",
return rc;
}
/* alloc tmp zero-filled buffer */
if (!pvBuf)
return VERR_NO_MEMORY;
/* do loop, because buffer size may be less then block size */
while (cbFill > 0)
{
if (VBOX_FAILURE(rc))
{
Log(("vdiFillBlockByZeroes: write rc=%Vrc filename=\"%s\" uBlock=%u u64Offset=%llu cbFill=%u to_fill=%u\n",
break;
}
}
return rc;
}
/**
* internal: write data inside image block.
*
* note: uBlock must be valid, written data must not overlap block bounds.
*/
static int vdiWriteInBlock(PVDIDISK pDisk, PVDIIMAGEDESC pImage, unsigned uBlock, unsigned offWrite, unsigned cbToWrite, const void *pvBuf)
{
int rc;
/* Check if we can write into file. */
{
return VERR_WRITE_PROTECT;
}
/* This could be optimized a little (not setting it when writing zeroes
* to a zeroed block). Won't buy us much, because it's very unlikely
* that only such zero data block writes occur while the VDI is opened. */
{
{
/* 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. */
{
return VINF_SUCCESS;
}
}
/* need to allocate a new block in image file */
/* expand file by one block */
uint64_t u64Size = (((uint64_t)(getImageBlocksAllocated(&pImage->Header) + 1)) << pImage->uShiftIndex2Offset)
+ pImage->offStartData;
if (VBOX_FAILURE(rc))
{
Log(("vdiWriteInBlock: set size rc=%Vrc filename=\"%s\" uBlock=%u u64Size=%llu\n",
return rc;
}
{
/* Fill newly allocated block by zeroes. */
{
if (VBOX_FAILURE(rc))
return rc;
}
}
if (VBOX_FAILURE(rc))
return rc;
}
/* Now block present in image file, write data inside it. */
if (VBOX_SUCCESS(rc))
{
if (VBOX_FAILURE(rc))
Log(("vdiWriteInBlock: write rc=%Vrc filename=\"%s\" uBlock=%u offWrite=%u u64Offset=%llu cbToWrite=%u\n",
}
else
Log(("vdiWriteInBlock: seek rc=%Vrc filename=\"%s\" uBlock=%u offWrite=%u u64Offset=%llu\n",
return rc;
}
/**
* internal: copy data block from one (parent) image to last image.
*/
{
{
/*
* if src block is zero, set dst block to zero too.
*/
return VINF_SUCCESS;
}
/* alloc tmp buffer */
if (!pvBuf)
return VERR_NO_MEMORY;
int rc = VINF_SUCCESS;
unsigned offCopy = 0;
/* do loop, because buffer size may be less then block size */
while (cbCopy > 0)
{
if (VBOX_FAILURE(rc))
break;
if (VBOX_FAILURE(rc))
break;
}
return rc;
}
/**
* Write data to virtual HDD.
*
* @returns VBox status code.
* @param pDisk Pointer to VDI HDD container.
* @param offStart Offset of first writing byte from start of HDD.
* @param pvBuf Pointer to buffer of writing data.
* @param cbToWrite Number of bytes to write.
*/
VBOXDDU_DECL(int) VDIDiskWrite(PVDIDISK pDisk, uint64_t offStart, const void *pvBuf, unsigned cbToWrite)
{
/* sanity check */
/* Check params. */
|| cbToWrite == 0)
{
return VERR_INVALID_PARAMETER;
}
/* Calculate starting block number and offset inside it. */
/* loop through blocks */
int rc;
for (;;)
{
unsigned to_write;
else
/* All callers write less than a VDI block right now (assuming
* default VDI block size). So not worth optimizing for the case
* where a full block is overwritten (no copying required).
* Checking whether a block is all zeroes after the write is too
* expensive (would require reading the rest of the block). */
{
/* Differencing images are used, handle them. */
/* Search for image with allocated block. */
{
if (!pImage)
{
/* Block is not allocated in all images of chain. */
break;
}
}
{
/* One of parent image has a block data, copy it into last image. */
if (VBOX_FAILURE(rc))
break;
}
}
/* Actually write the data into block. */
if ( cbToWrite == 0
|| VBOX_FAILURE(rc))
break;
/* goto next block */
uBlock++;
offWrite = 0;
}
return rc;
}
/**
* internal: commit one image to another, no changes to header, just
* plain copy operation. Blocks that are not allocated in the source
* image (i.e. inherited by its parent(s)) are not merged.
*
* @param pImageFrom source image
* @param pImageTo target image (will receive all the modifications)
* @param fParentToChild true if the source image is parent of the target one,
* false of the target image is the parent of the source.
* @param pfnProgress progress callback (NULL if not to be used)
* @param pvUser user argument for the progress callback
*
* @note this method does not check whether merging is possible!
*/
{
Log(("vdiMergeImages: merging from image \"%s\" to image \"%s\" (fParentToChild=%d)\n",
/* alloc tmp buffer */
if (!pvBuf)
return VERR_NO_MEMORY;
int rc = VINF_SUCCESS;
if (!fParentToChild)
{
/*
* Commit the child image to the parent image.
* Child is the source (from), parent is the target (to).
*/
{
/* only process blocks that are allocated in the source image */
{
/* Found used block in source image, commit it. */
{
/* Block is zero in the source image and not allocated in the target image. */
}
else
{
/* Block is not zero / allocated in source image. */
unsigned offCommit = 0;
/* do loop, because buffer size may be less then block size */
while (cbCommit > 0)
{
if (VBOX_FAILURE(rc))
break;
if (VBOX_FAILURE(rc))
break;
}
if (VBOX_FAILURE(rc))
break;
}
}
if (pfnProgress)
{
pvUser);
/* Note: commiting is non breakable operation, skipping rc here. */
}
}
}
else
{
/*
* Commit the parent image to the child image.
* Parent is the source (from), child is the target (to).
*/
{
/*
* only process blocks that are allocated or zero in the source image
* and NEITHER allocated NOR zero in the target image
*/
{
/* Found used block in source image (but unused in target), commit it. */
{
/* Block is zero in the source image and not allocated in the target image. */
}
else
{
/* Block is not zero / allocated in source image. */
unsigned offCommit = 0;
/* do loop, because buffer size may be less then block size */
while (cbCommit > 0)
{
if (VBOX_FAILURE(rc))
break;
if (VBOX_FAILURE(rc))
break;
}
if (VBOX_FAILURE(rc))
break;
}
}
if (pfnProgress)
{
pvUser);
/* Note: commiting is non breakable operation, skipping rc here. */
}
}
}
return rc;
}
/**
* internal: commit last image(s) to selected previous image.
* note: all images accessed across this call must be opened in R/W mode.
* @remark Only used by tstVDI.
*/
{
/* sanity check */
Log(("vdiCommitToImage: commiting from image \"%s\" to image \"%s\"\n",
{
Log(("vdiCommitToImage: attempt to commit to the same image!\n"));
return VERR_VDI_NO_DIFF_IMAGES;
}
/* Scan images for pDstImage. */
if (!pImage)
{
AssertMsgFailed(("Invalid arguments: pDstImage is not in images chain\n"));
return VERR_INVALID_PARAMETER;
}
/* alloc tmp buffer */
if (!pvBuf)
return VERR_NO_MEMORY;
int rc = VINF_SUCCESS;
{
/* Find allocated block to commit. */
{
/* Found used block in diff image (pImage), commit it. */
{
/* Block is zero in difference image and not allocated in primary image. */
}
else
{
/* Block is not zero / allocated in primary image. */
unsigned offCommit = 0;
/* do loop, because buffer size may be less then block size */
while (cbCommit > 0)
{
if (VBOX_FAILURE(rc))
break;
if (VBOX_FAILURE(rc))
break;
}
if (VBOX_FAILURE(rc))
break;
}
}
if (pfnProgress)
{
pvUser);
/* Note: commiting is non breakable operation, skipping rc here. */
}
}
/* Go forward and update linkage information. */
{
/* generate new last-modified uuid. */
/* fix up linkage. */
*getImageParentModificationUUID(&pImage->Header) = *getImageModificationUUID(&pImage->pPrev->Header);
/* reset modified flag. */
}
/* Process committed images - truncate them. */
{
/* note: can't understand how to do error works here? */
/* Truncate file. */
if (VBOX_FAILURE(rc2))
{
Log(("vdiCommitToImage: set size (truncate) rc=%Vrc filename=\"%s\"\n",
}
/* Save header and blocks array. */
if (VBOX_FAILURE(rc2))
{
Log(("vdiCommitToImage: update blocks and header rc=%Vrc filename=\"%s\"\n",
}
}
if (pfnProgress)
{
/* Note: commiting is non breakable operation, skipping rc here. */
}
return rc;
}
/**
* Checks if image is available and not broken, returns some useful image parameters if requested.
*
* @returns VBox status code.
* @param pszFilename Name of the image file to check.
* @param puVersion Where to store the version of image. NULL is ok.
* @param penmType Where to store the type of image. NULL is ok.
* @param pcbSize Where to store the size of image in bytes. NULL is ok.
* @param pUuid Where to store the uuid of image creation. NULL is ok.
* @param pParentUuid Where to store the UUID of the parent image. NULL is ok.
* @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) VDICheckImage(const char *pszFilename, unsigned *puVersion, PVDIIMAGETYPE penmType,
char *pszComment, unsigned cbComment)
{
LogFlow(("VDICheckImage:\n"));
/* Check arguments. */
if ( !pszFilename
|| *pszFilename == '\0')
{
return VERR_INVALID_PARAMETER;
}
if (VBOX_SUCCESS(rc))
{
Log(("VDICheckImage: filename=\"%s\" version=%08X type=%X cbDisk=%llu uuid={%Vuuid}\n",
if ( pszComment
&& cbComment > 0)
{
else
}
if (VBOX_SUCCESS(rc))
{
if (puVersion)
if (penmType)
if (pcbSize)
if (pUuid)
if (pParentUuid)
}
}
return rc;
}
/**
* Changes an image's comment string.
*
* @returns VBox status code.
* @param pszFilename Name of the image file to operate on.
* @param pszComment New comment string (UTF-8). NULL is allowed to reset the comment.
*/
{
LogFlow(("VDISetImageComment:\n"));
/*
* Validate arguments.
*/
if ( !pszFilename
|| *pszFilename == '\0')
{
return VERR_INVALID_PARAMETER;
}
if (cchComment >= VDI_IMAGE_COMMENT_SIZE)
{
return VERR_VDI_COMMENT_TOO_LONG;
}
/*
* Open the image for updating.
*/
if (VBOX_FAILURE(rc))
{
return rc;
}
{
/* we don't support old style images */
{
/*
* Update the comment field, making sure to zero out all of the previous comment.
*/
/* write out new the header */
}
else
{
Log(("VDISetImageComment: Unsupported version!\n"));
}
}
else
{
}
return rc;
}
/**
* Creates a new base image file.
*
* @returns VBox status code.
* @param pszFilename Name of the creating image file.
* @param enmType Image type, only base image types are acceptable.
* @param cbSize Image size in bytes.
* @param pszComment Pointer to image comment. NULL is ok.
* @param pfnProgress Progress callback. Optional.
* @param pvUser User argument for the progress callback.
*/
VBOXDDU_DECL(int) VDICreateBaseImage(const char *pszFilename, VDIIMAGETYPE enmType, uint64_t cbSize,
{
LogFlow(("VDICreateBaseImage:\n"));
/* Check arguments. */
if ( !pszFilename
|| *pszFilename == '\0'
{
AssertMsgFailed(("Invalid arguments: pszFilename=%p enmType=%x cbSize=%llu\n",
return VERR_INVALID_PARAMETER;
}
return rc;
}
/**
* Creates a differencing dynamically growing image file for specified parent image.
*
* @returns VBox status code.
* @param pszFilename Name of the creating differencing image file.
* @param pszParent Name of the parent image file. May be base or diff image type.
* @param pszComment Pointer to image comment. NULL is ok.
* @param pfnProgress Progress callback. Optional.
* @param pvUser User argument for the progress callback.
*/
void *pvUser)
{
LogFlow(("VDICreateDifferenceImage:\n"));
/* Check arguments. */
if ( !pszFilename
|| *pszFilename == '\0'
|| !pszParent
|| *pszParent == '\0')
{
AssertMsgFailed(("Invalid arguments: pszFilename=%p pszParent=%p\n",
pszFilename, pszParent));
return VERR_INVALID_PARAMETER;
}
if (VBOX_SUCCESS(rc))
{
}
return rc;
}
/**
* Deletes an image. Only valid image files can be deleted by this call.
*
* @returns VBox status code.
* @param pszFilename Name of the image file to check.
*/
{
LogFlow(("VDIDeleteImage:\n"));
/* Check arguments. */
if ( !pszFilename
|| *pszFilename == '\0')
{
return VERR_INVALID_PARAMETER;
}
if (VBOX_SUCCESS(rc))
return rc;
}
/**
* Makes a copy of image file with a new (other) creation uuid.
*
* @returns VBox status code.
* @param pszDstFilename Name of the image file to create.
* @param pszSrcFilename Name of the image file to copy from.
* @param pszComment Pointer to image comment. If NULL specified comment
* will be copied from source image.
* @param pfnProgress Progress callback. Optional.
* @param pvUser User argument for the progress callback.
*/
{
LogFlow(("VDICopyImage:\n"));
/* Check arguments. */
if ( !pszDstFilename
|| *pszDstFilename == '\0'
|| !pszSrcFilename
|| *pszSrcFilename == '\0')
{
AssertMsgFailed(("Invalid arguments: pszDstFilename=%p pszSrcFilename=%p\n",
return VERR_INVALID_PARAMETER;
}
/* Special check for comment length. */
if ( pszComment
{
return VERR_VDI_COMMENT_TOO_LONG;
}
if (VBOX_FAILURE(rc))
{
return rc;
}
/* create file */
if (VBOX_SUCCESS(rc))
{
/* lock new image exclusively to close any wrong access by VDI API calls. */
if (VBOX_SUCCESS(rc))
{
/* Set the size of a new file. */
if (VBOX_SUCCESS(rc))
{
/* A dirty trick - use original image data to fill the new image. */
/* generate a new image creation uuid. */
/* generate a new image last-modified uuid. */
/* set image comment, if present. */
if (pszComment)
/* Write the pre-header to new image. */
if (VBOX_SUCCESS(rc))
NULL);
/* Write the header and the blocks array to new image. */
if (VBOX_SUCCESS(rc))
/* Seek to the data start in both images. */
if (VBOX_SUCCESS(rc))
NULL);
if (VBOX_SUCCESS(rc))
NULL);
if (VBOX_SUCCESS(rc))
{
/* alloc tmp buffer */
if (pvBuf)
{
/* Main copy loop. */
unsigned c = 0;
while (cbData)
{
/* Read. */
if (VBOX_FAILURE(rc))
break;
/* Write. */
if (VBOX_FAILURE(rc))
break;
if (pfnProgress)
{
c++;
(c * 100) / cBlocks,
pvUser);
if (VBOX_FAILURE(rc))
break;
}
}
}
else
rc = VERR_NO_MEMORY;
}
}
}
if (VBOX_FAILURE(rc))
if (pfnProgress)
}
LogFlow(("VDICopyImage: returns %Vrc for pszSrcFilename=\"%s\" pszDstFilename=\"%s\"\n",
return rc;
}
/**
* Shrinks growing image file by removing zeroed data blocks.
*
* @returns VBox status code.
* @param pszFilename Name of the image file to shrink.
* @param pfnProgress Progress callback. Optional.
* @param pvUser User argument for the progress callback.
*/
{
LogFlow(("VDIShrinkImage:\n"));
/* Check arguments. */
if ( !pszFilename
|| *pszFilename == '\0')
{
return VERR_INVALID_PARAMETER;
}
if (VBOX_FAILURE(rc))
{
return rc;
}
{
return VERR_VDI_IMAGE_READ_ONLY;
}
/* Do debug dump. */
/* Working data. */
if (VBOX_FAILURE(rc))
{
return rc;
}
Log(("VDIShrinkImage: invalid image file length, cbBlock=%u cBlocks=%u cBlocksAllocated=%u cBlocksAllocated2=%u cbData=%llu\n",
/* Allocate second blocks array for back resolving. */
if (!paBlocks2)
{
Log(("VDIShrinkImage: failed to allocate paBlocks2 buffer (%u bytes)\n", sizeof(VDIIMAGEBLOCKPOINTER) * cBlocks));
return VERR_NO_MEMORY;
}
/* Init second blocks array. */
for (unsigned n = 0; n < cBlocks; n++)
/* Fill second blocks array, check for allocational errors. */
for (unsigned n = 0; n < cBlocks; n++)
{
{
if (uBlock < cBlocksAllocated2)
{
else
{
/* free second link to block. */
}
}
else
{
Log(("VDIShrinkImage: block n=%u -> uBlock=%u is out of blocks range! (cbBlock=%u cBlocks=%u cBlocksAllocated=%u cBlocksAllocated2=%u cbData=%llu)\n",
/* free link to invalid block. */
}
}
}
/* Allocate a working buffer for one block. */
if (pvBuf)
{
/* Main voodoo loop, search holes and fill it. */
unsigned uBlockWrite = 0;
{
{
/* Read the block from file and check for zeroes. */
if (VBOX_FAILURE(rc))
{
Log(("VDIShrinkImage: seek rc=%Vrc filename=\"%s\" uBlock=%u cBlocks=%u cBlocksAllocated=%u cBlocksAllocated2=%u cbData=%llu\n",
break;
}
if (VBOX_FAILURE(rc))
{
Log(("VDIShrinkImage: read rc=%Vrc filename=\"%s\" cbBlock=%u uBlock=%u cBlocks=%u cBlocksAllocated=%u cBlocksAllocated2=%u cbData=%llu\n",
break;
}
/* Check block for data. */
{
/* Block has a data, may be it must be moved. */
if (uBlockWrite < uBlock)
{
/* Move the block. */
if (VBOX_FAILURE(rc))
{
Log(("VDIShrinkImage: seek(2) rc=%Vrc filename=\"%s\" uBlockWrite=%u cBlocks=%u cBlocksAllocated=%u cBlocksAllocated2=%u cbData=%llu\n",
break;
}
if (VBOX_FAILURE(rc))
{
Log(("VDIShrinkImage: write rc=%Vrc filename=\"%s\" cbBlock=%u uBlockWrite=%u cBlocks=%u cBlocksAllocated=%u cBlocksAllocated2=%u cbData=%llu\n",
rc, pImage->szFilename, cbBlock, uBlockWrite, cBlocks, cBlocksAllocated, cBlocksAllocated2, cbData));
break;
}
}
/* Fix the block pointer. */
uBlockWrite++;
}
else
{
/* Fix the block pointer. */
}
}
else
if (pfnProgress)
{
pvUser);
/* Shrink is unbreakable operation! */
}
}
if ( VBOX_SUCCESS(rc)
&& uBlockWrite < cBlocksAllocated2)
{
/* File size must be shrinked. */
Log(("VDIShrinkImage: shrinking file size from %llu to %llu bytes\n",
if (VBOX_FAILURE(rc))
}
}
else
{
rc = VERR_NO_MEMORY;
}
/* Save header and blocks array. */
if (VBOX_SUCCESS(rc))
{
if (pfnProgress)
}
/* Do debug dump. */
/* Clean up. */
return rc;
}
/**
* Converts image file from older VDI formats to current one.
*
* @returns VBox status code.
* @param pszFilename Name of the image file to convert.
* @param pfnProgress Progress callback. Optional.
* @param pvUser User argument for the progress callback.
* @remark Only used by vditool
*/
{
LogFlow(("VDIConvertImage:\n"));
/* Check arguments. */
if ( !pszFilename
|| *pszFilename == '\0')
{
return VERR_INVALID_PARAMETER;
}
if (VBOX_FAILURE(rc))
{
return rc;
}
int off;
{
goto l_conversion_failed;
}
{
Log(("VDIConvertImage: unsupported version=%08X filename=\"%s\"\n",
goto l_conversion_failed;
}
/* Build new version header from old one. */
VDI_IMAGE_FLAGS_DEFAULT, /* Safety issue: Always use default flags. */
0);
/* Calc data offset. */
if (off <= 0)
{
goto l_conversion_failed;
}
if (VBOX_FAILURE(rc))
goto l_conversion_failed;
/* Check file size. */
{
AssertMsgFailed(("Invalid file size, broken image?\n"));
goto l_conversion_failed;
}
/* Expand file. */
if (VBOX_FAILURE(rc))
goto l_conversion_failed;
if (cbData > 0)
{
/* Calc current file position to move data from. */
else
unsigned c = 0;
/* alloc tmp buffer */
if (pvBuf)
{
/* Move data. */
for (;;)
{
/* Read. */
if (VBOX_FAILURE(rc))
break;
if (VBOX_FAILURE(rc))
break;
/* Write. */
if (VBOX_FAILURE(rc))
break;
if (VBOX_FAILURE(rc))
break;
if (pfnProgress)
{
c++;
(c * 100) / cMoves,
pvUser);
/* Note: conversion is non breakable operation, skipping rc here. */
}
if (cbData == 0)
break;
else
}
/* Fill the beginning of file with zeroes to wipe out old headers etc. */
if (VBOX_SUCCESS(rc))
{
if (VBOX_SUCCESS(rc))
{
}
}
}
else
rc = VERR_NO_MEMORY;
if (VBOX_FAILURE(rc))
goto l_conversion_failed;
}
if (pfnProgress)
{
/* Note: conversion is non breakable operation, skipping rc here. */
}
/* Data moved, now we need to save new pre header, header and blocks array. */
/* Setup image parameters by header. */
/* Write pre-header. */
if (VBOX_FAILURE(rc))
goto l_conversion_failed;
if (VBOX_FAILURE(rc))
goto l_conversion_failed;
/* Write header and blocks array. */
return rc;
}
/**
* Queries the image's UUID and parent UUIDs.
*
* @returns VBox status code.
* @param pszFilename Name of the image file to operate on.
* @param pUuid Where to store image UUID (can be NULL).
* @param pModificationUuid Where to store modification UUID (can be NULL).
* @param pParentUuuid Where to store parent UUID (can be NULL).
* @param pParentModificationUuid Where to store parent modification UUID (can be NULL).
*/
{
LogFlow(("VDIGetImageUUIDs:\n"));
/* Check arguments. */
if ( !pszFilename
|| *pszFilename == '\0')
{
return VERR_INVALID_PARAMETER;
}
/*
* Try open the specified image.
*/
if (VBOX_FAILURE(rc))
{
return rc;
}
/*
* Query data.
*/
if (pUuid)
{
if (pTmpUuid)
else
}
if (pModificationUuid)
{
if (pTmpUuid)
else
}
if (pParentUuid)
{
if (pTmpUuid)
*pParentUuid = *pTmpUuid;
else
}
{
if (pTmpUuid)
else
}
/*
* Close the image.
*/
return VINF_SUCCESS;
}
/**
* Changes the image's UUID and parent UUIDs.
*
* @returns VBox status code.
* @param pszFilename Name of the image file to operate on.
* @param pUuid Optional parameter, new UUID of the image.
* @param pModificationUuid Optional parameter, new modification UUID of the image.
* @param pParentUuuid Optional parameter, new parent UUID of the image.
* @param pParentModificationUuid Optional parameter, new parent modification UUID of the image.
*/
{
LogFlow(("VDISetImageUUIDs:\n"));
/* Check arguments. */
if ( !pszFilename
|| *pszFilename == '\0')
{
return VERR_INVALID_PARAMETER;
}
if (VBOX_FAILURE(rc))
{
return rc;
}
{
/* we only support new images */
{
if (pUuid)
if (pModificationUuid)
if (pParentUuid)
/* write out new header */
}
else
{
Log(("VDISetImageUUIDs: Version is not supported!\n"));
}
}
else
{
}
return rc;
}
/**
*
* @returns VBox status code.
* @param pszFilenameFrom Name of the image file to merge from.
* @param pszFilenameTo Name of the image file to merge into.
* @param pfnProgress Progress callback. Optional. NULL if not to be used.
* @param pvUser User argument for the progress callback.
*/
{
LogFlow(("VDIMergeImage:\n"));
/* Check arguments. */
if ( !pszFilenameFrom
|| *pszFilenameFrom == '\0'
|| !pszFilenameTo
|| *pszFilenameTo == '\0')
{
AssertMsgFailed(("Invalid arguments: pszFilenameFrom=%p, pszFilenameTo=%p\n", pszFilenameFrom, pszFilenameTo));
return VERR_INVALID_PARAMETER;
}
if (VBOX_FAILURE(rc))
{
return rc;
}
if (VBOX_FAILURE(rc))
{
return rc;
}
{
return VERR_VDI_IMAGE_READ_ONLY;
}
/*
* when merging, we should not update the modification uuid of the target
* image, because from the point of view of its children, it hasn't been
* logically changed after the successful merge.
*/
/*
* Check in which direction we merge
*/
bool bParentToChild = false;
{
/* we merge from a child to its parent */
}
else
{
/* we merge from a parent to its child */
bParentToChild = true;
}
else
{
/* the images are not related, we can't merge! */
}
if (pfnProgress)
{
/* Note: commiting is non breakable operation, skipping rc here. */
}
/* cleanup */
return rc;
}
/**
* Initialize the VDIDISK structure.
*/
{
pDisk->fHonorZeroWrites = false;
}
/**
* internal: add image structure to the end of images list.
*/
{
{
}
else
{
}
}
/**
* internal: remove image structure from the images list.
*/
{
else
else
}
/**
* Allocates and initializes VDI HDD container.
*
* @returns Pointer to newly created HDD container with no one opened image file.
* @returns NULL on failure, typically out of memory.
*/
{
if (pDisk)
return pDisk;
}
/**
* Destroys VDI HDD container. If container has opened image files they will be closed.
*
* @param pDisk Pointer to VDI HDD container.
*/
{
/* sanity check */
if (pDisk)
{
}
}
/**
* Get working buffer size of VDI HDD container.
*
* @returns Working buffer size in bytes.
*/
{
/* sanity check */
}
/**
*
* @returns Disk ReadOnly status.
* @returns true if no one VDI image is opened in HDD container.
*/
{
/* sanity check */
{
}
AssertMsgFailed(("No one disk image is opened!\n"));
return true;
}
/**
* Get disk size of VDI HDD container.
*
* @returns Virtual disk size in bytes.
* @returns 0 if no one VDI image is opened in HDD container.
*/
{
/* sanity check */
{
}
AssertMsgFailed(("No one disk image is opened!\n"));
return 0;
}
/**
* Get block size of VDI HDD container.
*
* @returns VDI image block size in bytes.
* @returns 0 if no one VDI image is opened in HDD container.
*/
{
/* sanity check */
{
}
AssertMsgFailed(("No one disk image is opened!\n"));
return 0;
}
/**
* Get virtual disk geometry stored in image file.
*
* @returns VBox status code.
* @returns VERR_VDI_NOT_OPENED if no one VDI image is opened in HDD container.
* @returns VERR_VDI_GEOMETRY_NOT_SET if no geometry has been setted.
* @param pDisk Pointer to VDI HDD container.
* @param pcCylinders Where to store the number of cylinders. NULL is ok.
* @param pcHeads Where to store the number of heads. NULL is ok.
* @param pcSectors Where to store the number of sectors. NULL is ok.
*/
VBOXDDU_DECL(int) VDIDiskGetGeometry(PVDIDISK pDisk, unsigned *pcCylinders, unsigned *pcHeads, unsigned *pcSectors)
{
/* sanity check */
{
int rc = VINF_SUCCESS;
LogFlow(("VDIDiskGetGeometry: C/H/S = %u/%u/%u\n",
if ( pGeometry->cCylinders > 0
{
if (pcCylinders)
if (pcHeads)
if (pcSectors)
}
else
return rc;
}
AssertMsgFailed(("No one disk image is opened!\n"));
return VERR_VDI_NOT_OPENED;
}
/**
* Store virtual disk geometry into base image file of HDD container.
*
* Note that in case of unrecoverable error all images of HDD container will be closed.
*
* @returns VBox status code.
* @returns VERR_VDI_NOT_OPENED if no one VDI image is opened in HDD container.
* @param pDisk Pointer to VDI HDD container.
* @param cCylinders Number of cylinders.
* @param cHeads Number of heads.
* @param cSectors Number of sectors.
*/
VBOXDDU_DECL(int) VDIDiskSetGeometry(PVDIDISK pDisk, unsigned cCylinders, unsigned cHeads, unsigned cSectors)
{
/* sanity check */
{
/* Update header information in base image file. */
return rc;
}
AssertMsgFailed(("No one disk image is opened!\n"));
return VERR_VDI_NOT_OPENED;
}
/**
* Get virtual disk translation mode stored in image file.
*
* @returns VBox status code.
* @returns VERR_VDI_NOT_OPENED if no one VDI image is opened in HDD container.
* @param pDisk Pointer to VDI HDD container.
* @param penmTranslation Where to store the translation mode (see pdm.h).
*/
{
/* sanity check */
{
return VINF_SUCCESS;
}
AssertMsgFailed(("No one disk image is opened!\n"));
return VERR_VDI_NOT_OPENED;
}
/**
* Store virtual disk translation mode into base image file of HDD container.
*
* Note that in case of unrecoverable error all images of HDD container will be closed.
*
* @returns VBox status code.
* @returns VERR_VDI_NOT_OPENED if no one VDI image is opened in HDD container.
* @param pDisk Pointer to VDI HDD container.
* @param enmTranslation Translation mode (see pdm.h).
*/
{
/* sanity check */
{
/* Update header information in base image file. */
return rc;
}
AssertMsgFailed(("No one disk image is opened!\n"));
return VERR_VDI_NOT_OPENED;
}
/**
* Get number of opened images in HDD container.
*
* @returns Number of opened images for HDD container. 0 if no images is opened.
* @param pDisk Pointer to VDI HDD container.
*/
{
/* sanity check */
}
{
{
nImage--;
}
return pImage;
}
/**
* Get version of opened image of HDD container.
*
* @returns VBox status code.
* @returns VERR_VDI_IMAGE_NOT_FOUND if image with specified number was not opened.
* @param pDisk Pointer to VDI HDD container.
* @param nImage Image number, counts from 0. 0 is always base image of container.
* @param puVersion Where to store the image version.
*/
{
/* sanity check */
if (pImage)
{
return VINF_SUCCESS;
}
return VERR_VDI_IMAGE_NOT_FOUND;
}
/**
* Get filename of opened image of HDD container.
*
* @returns VBox status code.
* @returns VERR_VDI_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 VDI 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) VDIDiskGetImageFilename(PVDIDISK pDisk, int nImage, char *pszFilename, unsigned cbFilename)
{
/* sanity check */
if (pImage)
{
if (cb < cbFilename)
{
/* memcpy is much better than strncpy. */
LogFlow(("VDIDiskGetImageFilename: returns VINF_SUCCESS, filename=\"%s\" nImage=%d\n",
pszFilename, nImage));
return VINF_SUCCESS;
}
else
{
return VERR_BUFFER_OVERFLOW;
}
}
return VERR_VDI_IMAGE_NOT_FOUND;
}
/**
* Get the comment line of opened image of HDD container.
*
* @returns VBox status code.
* @returns VERR_VDI_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 VDI 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) VDIDiskGetImageComment(PVDIDISK pDisk, int nImage, char *pszComment, unsigned cbComment)
{
/* sanity check */
if (pImage)
{
{
/* memcpy is much better than strncpy. */
LogFlow(("VDIDiskGetImageComment: returns VINF_SUCCESS, comment=\"%s\" nImage=%d\n",
return VINF_SUCCESS;
}
else
{
return VERR_BUFFER_OVERFLOW;
}
}
return VERR_VDI_IMAGE_NOT_FOUND;
}
/**
* Get type of opened image of HDD container.
*
* @returns VBox status code.
* @returns VERR_VDI_IMAGE_NOT_FOUND if image with specified number was not opened.
* @param pDisk Pointer to VDI HDD container.
* @param nImage Image number, counts from 0. 0 is always base image of container.
* @param penmType Where to store the image type.
*/
{
/* sanity check */
if (pImage)
{
LogFlow(("VDIDiskGetImageType: returns VINF_SUCCESS, type=%X nImage=%d\n",
return VINF_SUCCESS;
}
return VERR_VDI_IMAGE_NOT_FOUND;
}
/**
* Get flags of opened image of HDD container.
*
* @returns VBox status code.
* @returns VERR_VDI_IMAGE_NOT_FOUND if image with specified number was not opened.
* @param pDisk Pointer to VDI HDD container.
* @param nImage Image number, counts from 0. 0 is always base image of container.
* @param pfFlags Where to store the image flags.
*/
{
/* sanity check */
if (pImage)
{
LogFlow(("VDIDiskGetImageFlags: returns VINF_SUCCESS, flags=%08X nImage=%d\n",
return VINF_SUCCESS;
}
return VERR_VDI_IMAGE_NOT_FOUND;
}
/**
* Get Uuid of opened image of HDD container.
*
* @returns VBox status code.
* @returns VERR_VDI_IMAGE_NOT_FOUND if image with specified number was not opened.
* @param pDisk Pointer to VDI 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.
*/
{
/* sanity check */
if (pImage)
{
LogFlow(("VDIDiskGetImageUuid: returns VINF_SUCCESS, uuid={%Vuuid} nImage=%d\n",
return VINF_SUCCESS;
}
return VERR_VDI_IMAGE_NOT_FOUND;
}
/**
* Get last modification Uuid of opened image of HDD container.
*
* @returns VBox status code.
* @returns VERR_VDI_IMAGE_NOT_FOUND if image with specified number was not opened.
* @param pDisk Pointer to VDI 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.
*/
{
/* sanity check */
if (pImage)
{
LogFlow(("VDIDiskGetImageModificationUuid: returns VINF_SUCCESS, uuid={%Vuuid} nImage=%d\n",
return VINF_SUCCESS;
}
return VERR_VDI_IMAGE_NOT_FOUND;
}
/**
* Get Uuid of opened image's parent image.
*
* @returns VBox status code.
* @returns VERR_VDI_IMAGE_NOT_FOUND if image with specified number was not opened.
* @param pDisk Pointer to VDI HDD container.
* @param nImage Image number, counts from 0. 0 is always base image of the container.
* @param pUuid Where to store the image creation uuid.
*/
{
/* sanity check */
if (pImage)
{
LogFlow(("VDIDiskGetParentImageUuid: returns VINF_SUCCESS, *pUuid={%Vuuid} nImage=%d\n",
return VINF_SUCCESS;
}
return VERR_VDI_IMAGE_NOT_FOUND;
}
/**
*/
{
if ( !fReadOnly
{
/* Can't switch read-only opened image to read-write mode. */
Log(("vdiChangeImageMode: can't switch r/o image to r/w mode, filename=\"%s\" fOpen=%X\n",
return VERR_VDI_IMAGE_READ_ONLY;
}
/* Flush last image changes if was r/w mode. */
/* Change image locking. */
(fReadOnly) ?
0,
cbLock);
if (VBOX_SUCCESS(rc))
{
Log(("vdiChangeImageMode: Image \"%s\" mode changed to %s\n",
return VINF_SUCCESS;
}
/* Check for the most bad error in the world. Damn! It must never happens in real life! */
if (rc == VERR_FILE_LOCK_LOST)
{
/* And what we can do now?! */
Log(("vdiChangeImageMode: image lock has been lost for file=\"%s\", blocking on r/o lock wait",
pImage->szFilename));
/* Try to obtain read lock in blocking mode. Maybe it's a very bad method. */
}
Log(("vdiChangeImageMode: Image \"%s\" mode change failed with rc=%Vrc, mode is %s\n",
return rc;
}
/**
* internal: try to save header in image file even if image is in read-only mode.
*/
{
int rc = VINF_SUCCESS;
{
if (VBOX_SUCCESS(rc))
{
}
}
else
return rc;
}
/**
* Opens an image file.
*
* The first opened image file in a 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.
* 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.
*
* Use VDIDiskIsReadOnly to check open mode.
*
* @returns VBox status code.
* @param pDisk Pointer to VDI HDD container.
* @param pszFilename Name of the image file to open.
* @param fOpen Image file open mode, see VDI_OPEN_FLAGS_* constants.
*/
{
/* sanity check */
/* Check arguments. */
if ( !pszFilename
|| *pszFilename == '\0'
|| (fOpen & ~VDI_OPEN_FLAGS_MASK))
{
return VERR_INVALID_PARAMETER;
}
if (VBOX_SUCCESS(rc))
{
{
/* Opening differencing image. */
{
/*
*/
}
}
else
{
/* Opening base image, check its type. */
{
}
}
if (VBOX_SUCCESS(rc))
else
}
return rc;
}
/**
* Closes the last opened image file in the HDD container. Leaves all changes inside it.
* 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
*
* @param pDisk Pointer to VDI HDD container.
*/
{
/* sanity check */
if (pImage)
{
if ( !fWasReadOnly
{
/*
*/
}
return;
}
AssertMsgFailed(("No images to close\n"));
}
/**
* Closes all opened image files in HDD container.
*
* @param pDisk Pointer to VDI HDD container.
*/
{
LogFlow(("VDIDiskCloseAllImages:\n"));
/* sanity check */
while (pImage)
{
}
}
/**
* Commits last opened differencing/undo image file of HDD container to previous one.
* If previous image file was opened in read-only mode (that must be always so) it is reopened
* After successfull commit the previous image file again reopened in read-only mode, last opened
* image file is cleared of data and remains open and active in HDD container.
* If you want to delete image after commit you must do it manually by VDIDiskCloseImage and
* VDIDeleteImage calls.
*
* Note that in case of unrecoverable error all images of HDD container will be closed.
*
* @returns VBox status code.
* @param pDisk Pointer to VDI HDD container.
* @param pfnProgress Progress callback. Optional.
* @param pvUser User argument for the progress callback.
* @remark Only used by tstVDI.
*/
{
LogFlow(("VDIDiskCommitLastDiff:\n"));
/* sanity check */
int rc = VINF_SUCCESS;
if (!pImage)
{
AssertMsgFailed(("No one disk image is opened!\n"));
return VERR_VDI_NOT_OPENED;
}
{
return VERR_VDI_IMAGE_READ_ONLY;
}
{
AssertMsgFailed(("No images to commit to!\n"));
return VERR_VDI_NO_DIFF_IMAGES;
}
if (fWasReadOnly)
{
/* Change previous image mode to r/w. */
if (VBOX_FAILURE(rc))
{
return rc;
}
}
{
/* Change previous image mode back to r/o. */
}
if (VBOX_FAILURE(rc))
{
/* Failed! Close all images, can't work with VHDD at all. */
}
return rc;
}
/**
* Creates and opens a new differencing image file in HDD container.
* See comments for VDIDiskOpenImage function about differencing images.
*
* @returns VBox status code.
* @param pDisk Pointer to VDI HDD container.
* @param pszFilename Name of the image file to create and open.
* @param pszComment Pointer to image comment. NULL is ok.
* @param pfnProgress Progress callback. Optional.
* @param pvUser User argument for the progress callback.
* @remark Only used by tstVDI.
*/
void *pvUser)
{
LogFlow(("VDIDiskCreateOpenDifferenceImage:\n"));
/* sanity check */
{
AssertMsgFailed(("No one disk image is opened!\n"));
return VERR_VDI_NOT_OPENED;
}
/* Flush last parent image changes if possible. */
if (VBOX_SUCCESS(rc))
{
if (VBOX_FAILURE(rc))
}
return rc;
}
/**
* internal: debug image dump.
*
* @remark Only used by tstVDI.
*/
{
RTLogPrintf("Dumping VDI image \"%s\" mode=%s fOpen=%X File=%08X\n",
RTLogPrintf("Header: Version=%08X Type=%X Flags=%X Size=%llu\n",
RTLogPrintf("Header: cbBlock=%u cbBlockExtra=%u cBlocks=%u cBlocksAllocated=%u\n",
RTLogPrintf("Header: offBlocks=%u offData=%u\n",
RTLogPrintf("Header: Geometry: C/H/S=%u/%u/%u cbSector=%u Mode=%u\n",
RTLogPrintf("Header: uuidParentModification={%Vuuid}\n", getImageParentModificationUUID(&pImage->Header));
RTLogPrintf("Image: fFlags=%08X offStartBlocks=%u offStartData=%u\n",
RTLogPrintf("Image: uBlockMask=%08X uShiftIndex2Offset=%u uShiftOffset2Index=%u offStartBlockData=%u\n",
{
{
cBadBlocks++;
}
}
{
RTLogPrintf("!! WARNING: %u blocks actually allocated (cBlocksAllocated=%u) !!\n",
}
if (cBadBlocks)
{
RTLogPrintf("!! WARNING: %u bad blocks found !!\n",
}
}
/**
* Debug helper - dumps all opened images of HDD container into the log file.
*
* @param pDisk Pointer to VDI HDD container.
* @remark Only used by tstVDI and vditool
*/
{
/* sanity check */
}