/* $Id: VDMemDisk.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */ /** @file * VBox HDD container test utility, memory disk/file. */ /* * Copyright (C) 2011-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ #define LOGGROUP LOGGROUP_DEFAULT /** @todo Log group */ #include #include #include #include #include #include #include "VDMemDisk.h" /** * Memory disk/file. */ typedef struct VDMEMDISK { /** Current size of the disk. */ uint64_t cbDisk; /** Flag whether the disk can grow. */ bool fGrowable; /** Pointer to the AVL tree holding the segments. */ PAVLRU64TREE pTreeSegments; } VDMEMDISK; /** * A disk segment. */ typedef struct VDMEMDISKSEG { /** AVL tree core. */ AVLRU64NODECORE Core; /** Pointer to the data. */ void *pvSeg; } VDMEMDISKSEG, *PVDMEMDISKSEG; int VDMemDiskCreate(PPVDMEMDISK ppMemDisk, uint64_t cbSize) { AssertPtrReturn(ppMemDisk, VERR_INVALID_POINTER); int rc = VINF_SUCCESS; PVDMEMDISK pMemDisk = (PVDMEMDISK)RTMemAllocZ(sizeof(VDMEMDISK)); if (pMemDisk) { pMemDisk->fGrowable = cbSize ? false : true; pMemDisk->cbDisk = cbSize; pMemDisk->pTreeSegments = (PAVLRU64TREE)RTMemAllocZ(sizeof(AVLRU64TREE)); if (pMemDisk->pTreeSegments) *ppMemDisk = pMemDisk; else { RTMemFree(pMemDisk); rc = VERR_NO_MEMORY; } } else rc = VERR_NO_MEMORY; LogFlowFunc(("returns rc=%Rrc\n", rc)); return rc; } static DECLCALLBACK(int) vdMemDiskDestroy(PAVLRU64NODECORE pNode, void *pvUser) { RT_NOREF1(pvUser); PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)pNode; RTMemFree(pSeg->pvSeg); RTMemFree(pSeg); return VINF_SUCCESS; } void VDMemDiskDestroy(PVDMEMDISK pMemDisk) { AssertPtrReturnVoid(pMemDisk); RTAvlrU64Destroy(pMemDisk->pTreeSegments, vdMemDiskDestroy, NULL); RTMemFree(pMemDisk->pTreeSegments); RTMemFree(pMemDisk); } int VDMemDiskWrite(PVDMEMDISK pMemDisk, uint64_t off, size_t cbWrite, PRTSGBUF pSgBuf) { int rc = VINF_SUCCESS; LogFlowFunc(("pMemDisk=%#p off=%llu cbWrite=%zu pSgBuf=%#p\n", pMemDisk, off, cbWrite, pSgBuf)); AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER); AssertPtrReturn(pSgBuf, VERR_INVALID_POINTER); /* Check for a write beyond the end of a disk. */ if ( !pMemDisk->fGrowable && (off + cbWrite) > pMemDisk->cbDisk) return VERR_INVALID_PARAMETER; /* Update the segments */ size_t cbLeft = cbWrite; uint64_t offCurr = off; while ( cbLeft && RT_SUCCESS(rc)) { PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64RangeGet(pMemDisk->pTreeSegments, offCurr); size_t cbRange = 0; unsigned offSeg = 0; if (!pSeg) { /* Get next segment */ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, offCurr, true); if ( !pSeg || offCurr + cbLeft <= pSeg->Core.Key) cbRange = cbLeft; else cbRange = pSeg->Core.Key - offCurr; /* Create new segment */ pSeg = (PVDMEMDISKSEG)RTMemAllocZ(sizeof(VDMEMDISKSEG)); if (pSeg) { pSeg->Core.Key = offCurr; pSeg->Core.KeyLast = offCurr + cbRange - 1; pSeg->pvSeg = RTMemAllocZ(cbRange); if (!pSeg->pvSeg) { RTMemFree(pSeg); rc = VERR_NO_MEMORY; } else { bool fInserted = RTAvlrU64Insert(pMemDisk->pTreeSegments, &pSeg->Core); AssertMsg(fInserted, ("Bug!\n")); NOREF(fInserted); } } else rc = VERR_NO_MEMORY; } else { offSeg = offCurr - pSeg->Core.Key; cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr)); } if (RT_SUCCESS(rc)) { AssertPtr(pSeg); size_t cbCopied = RTSgBufCopyToBuf(pSgBuf, (uint8_t *)pSeg->pvSeg + offSeg, cbRange); Assert(cbCopied == cbRange); NOREF(cbCopied); } offCurr += cbRange; cbLeft -= cbRange; } /* Update size of the disk. */ if ( RT_SUCCESS(rc) && pMemDisk->fGrowable && (off + cbWrite) > pMemDisk->cbDisk) { pMemDisk->cbDisk = off + cbWrite; } return rc; } int VDMemDiskRead(PVDMEMDISK pMemDisk, uint64_t off, size_t cbRead, PRTSGBUF pSgBuf) { LogFlowFunc(("pMemDisk=%#p off=%llu cbRead=%zu pSgBuf=%#p\n", pMemDisk, off, cbRead, pSgBuf)); AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER); AssertPtrReturn(pSgBuf, VERR_INVALID_POINTER); /* Check for a read beyond the end of a disk. */ if ((off + cbRead) > pMemDisk->cbDisk) return VERR_INVALID_PARAMETER; /* Compare read data */ size_t cbLeft = cbRead; uint64_t offCurr = off; while (cbLeft) { PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64RangeGet(pMemDisk->pTreeSegments, offCurr); size_t cbRange = 0; unsigned offSeg = 0; if (!pSeg) { /* Get next segment */ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, offCurr, true); if ( !pSeg || offCurr + cbLeft <= pSeg->Core.Key) { /* No data in the tree for this read. Fill with 0. */ cbRange = cbLeft; } else cbRange = pSeg->Core.Key - offCurr; RTSgBufSet(pSgBuf, 0, cbRange); } else { offSeg = offCurr - pSeg->Core.Key; cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr)); RTSgBufCopyFromBuf(pSgBuf, (uint8_t *)pSeg->pvSeg + offSeg, cbRange); } offCurr += cbRange; cbLeft -= cbRange; } return VINF_SUCCESS; } int VDMemDiskSetSize(PVDMEMDISK pMemDisk, uint64_t cbSize) { AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER); if (!pMemDisk->fGrowable) return VERR_NOT_SUPPORTED; if (pMemDisk->cbDisk <= cbSize) { /* Increase. */ pMemDisk->cbDisk = cbSize; } else { /* We have to delete all parts beyond the new end. */ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64Get(pMemDisk->pTreeSegments, cbSize); if (pSeg) { RTAvlrU64Remove(pMemDisk->pTreeSegments, pSeg->Core.Key); if (pSeg->Core.Key < cbSize) { /* Cut off the part which is not in the file anymore. */ pSeg->pvSeg = RTMemRealloc(pSeg->pvSeg, pSeg->Core.KeyLast - cbSize + 1); pSeg->Core.KeyLast = cbSize - pSeg->Core.Key - 1; bool fInserted = RTAvlrU64Insert(pMemDisk->pTreeSegments, &pSeg->Core); AssertMsg(fInserted, ("Bug!\n")); NOREF(fInserted); } else { /* Free the whole block. */ RTMemFree(pSeg->pvSeg); RTMemFree(pSeg); } } /* Kill all blocks coming after. */ do { pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, cbSize, true); if (pSeg) { RTAvlrU64Remove(pMemDisk->pTreeSegments, pSeg->Core.Key); RTMemFree(pSeg->pvSeg); pSeg->pvSeg = NULL; RTMemFree(pSeg); } else break; } while (true); pMemDisk->cbDisk = cbSize; } return VINF_SUCCESS; } int VDMemDiskGetSize(PVDMEMDISK pMemDisk, uint64_t *pcbSize) { AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER); AssertPtrReturn(pcbSize, VERR_INVALID_POINTER); *pcbSize = pMemDisk->cbDisk; return VINF_SUCCESS; } /** * Writes a segment to the given file. * * @returns IPRT status code. * * @param pNode The disk segment to write to the file. * @param pvParam Opaque user data containing the pointer to * the file handle. */ static DECLCALLBACK(int) vdMemDiskSegmentWriteToFile(PAVLRU64NODECORE pNode, void *pvParam) { PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)pNode; RTFILE hFile = *(PRTFILE)pvParam; return RTFileWriteAt(hFile, pSeg->Core.Key, pSeg->pvSeg, pSeg->Core.KeyLast - pSeg->Core.Key + 1, NULL); } int VDMemDiskWriteToFile(PVDMEMDISK pMemDisk, const char *pcszFilename) { int rc = VINF_SUCCESS; RTFILE hFile = NIL_RTFILE; LogFlowFunc(("pMemDisk=%#p pcszFilename=%s\n", pMemDisk, pcszFilename)); AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER); AssertPtrReturn(pcszFilename, VERR_INVALID_POINTER); rc = RTFileOpen(&hFile, pcszFilename, RTFILE_O_DENY_NONE | RTFILE_O_CREATE | RTFILE_O_WRITE); if (RT_SUCCESS(rc)) { rc = RTAvlrU64DoWithAll(pMemDisk->pTreeSegments, true, vdMemDiskSegmentWriteToFile, &hFile); RTFileClose(hFile); if (RT_FAILURE(rc)) RTFileDelete(pcszFilename); } LogFlowFunc(("returns rc=%Rrc\n", rc)); return rc; } int VDMemDiskReadFromFile(PVDMEMDISK pMemDisk, const char *pcszFilename) { RT_NOREF2(pMemDisk, pcszFilename); return VERR_NOT_IMPLEMENTED; } int VDMemDiskCmp(PVDMEMDISK pMemDisk, uint64_t off, size_t cbCmp, PRTSGBUF pSgBuf) { LogFlowFunc(("pMemDisk=%#p off=%llx cbCmp=%u pSgBuf=%#p\n", pMemDisk, off, cbCmp, pSgBuf)); /* Compare data */ size_t cbLeft = cbCmp; uint64_t offCurr = off; while (cbLeft) { PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64Get(pMemDisk->pTreeSegments, offCurr); size_t cbRange = 0; bool fCmp = false; unsigned offSeg = 0; if (!pSeg) { /* Get next segment */ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, offCurr, true); if (!pSeg) { /* No data in the tree for this read. Assume everything is ok. */ cbRange = cbLeft; } else if (offCurr + cbLeft <= pSeg->Core.Key) cbRange = cbLeft; else cbRange = pSeg->Core.Key - offCurr; } else { fCmp = true; offSeg = offCurr - pSeg->Core.Key; cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr)); } if (fCmp) { RTSGSEG Seg; RTSGBUF SgBufCmp; size_t cbOff = 0; int rc = 0; Seg.cbSeg = cbRange; Seg.pvSeg = (uint8_t *)pSeg->pvSeg + offSeg; RTSgBufInit(&SgBufCmp, &Seg, 1); rc = RTSgBufCmpEx(pSgBuf, &SgBufCmp, cbRange, &cbOff, true); if (rc) return rc; } else RTSgBufAdvance(pSgBuf, cbRange); offCurr += cbRange; cbLeft -= cbRange; } return 0; }