diff options
Diffstat (limited to 'src/VBox/Runtime/common/dvm')
-rw-r--r-- | src/VBox/Runtime/common/dvm/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Runtime/common/dvm/dvm.cpp | 693 | ||||
-rw-r--r-- | src/VBox/Runtime/common/dvm/dvmbsdlabel.cpp | 560 | ||||
-rw-r--r-- | src/VBox/Runtime/common/dvm/dvmgpt.cpp | 572 | ||||
-rw-r--r-- | src/VBox/Runtime/common/dvm/dvmmbr.cpp | 721 | ||||
-rw-r--r-- | src/VBox/Runtime/common/dvm/dvmvfs.cpp | 1453 |
6 files changed, 3999 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/dvm/Makefile.kup b/src/VBox/Runtime/common/dvm/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/common/dvm/Makefile.kup diff --git a/src/VBox/Runtime/common/dvm/dvm.cpp b/src/VBox/Runtime/common/dvm/dvm.cpp new file mode 100644 index 00000000..683d5b40 --- /dev/null +++ b/src/VBox/Runtime/common/dvm/dvm.cpp @@ -0,0 +1,693 @@ +/* $Id: dvm.cpp $ */ +/** @file + * IPRT Disk Volume Management API (DVM) - generic code. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * 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. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/types.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/dvm.h> +#include <iprt/err.h> +#include <iprt/asm.h> +#include <iprt/string.h> +#include <iprt/list.h> +#include "internal/dvm.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * The internal volume manager structure. + */ +typedef struct RTDVMINTERNAL +{ + /** The DVM magic (RTDVM_MAGIC). */ + uint32_t u32Magic; + /** The disk descriptor. */ + RTDVMDISK DvmDisk; + /** Pointer to the backend operations table after a successful probe. */ + PCRTDVMFMTOPS pDvmFmtOps; + /** The format specific volume manager data. */ + RTDVMFMT hVolMgrFmt; + /** Flags passed on manager creation. */ + uint32_t fFlags; + /** Reference counter. */ + uint32_t volatile cRefs; + /** List of recognised volumes (RTDVMVOLUMEINTERNAL). */ + RTLISTANCHOR VolumeList; +} RTDVMINTERNAL; +/** Pointer to an internal volume manager. */ +typedef RTDVMINTERNAL *PRTDVMINTERNAL; + +/** + * The internal volume structure. + */ +typedef struct RTDVMVOLUMEINTERNAL +{ + /** The DVM volume magic (RTDVMVOLUME_MAGIC). */ + uint32_t u32Magic; + /** Node for the volume list. */ + RTLISTNODE VolumeNode; + /** Pointer to the owning volume manager. */ + PRTDVMINTERNAL pVolMgr; + /** Format specific volume data. */ + RTDVMVOLUMEFMT hVolFmt; + /** Set block status.callback */ + PFNDVMVOLUMEQUERYBLOCKSTATUS pfnQueryBlockStatus; + /** Opaque user data. */ + void *pvUser; + /** Reference counter. */ + uint32_t volatile cRefs; +} RTDVMVOLUMEINTERNAL; +/** Pointer to an internal volume. */ +typedef RTDVMVOLUMEINTERNAL *PRTDVMVOLUMEINTERNAL; + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ +extern RTDVMFMTOPS g_rtDvmFmtMbr; +extern RTDVMFMTOPS g_rtDvmFmtGpt; +extern RTDVMFMTOPS g_rtDvmFmtBsdLbl; + +/** + * Supported volume formats. + */ +static PCRTDVMFMTOPS const g_aDvmFmts[] = +{ + &g_rtDvmFmtMbr, + &g_rtDvmFmtGpt, + &g_rtDvmFmtBsdLbl +}; + +/** + * Descriptions of the volume types. + * + * This is indexed by RTDVMVOLTYPE. + */ +static const char * const g_apszDvmVolTypes[] = +{ + "Invalid", + "Unknown", + "NTFS", + "FAT12", + "FAT16", + "FAT32", + + "EFI system partition", + + "Mac OS X HFS or HFS+", + "Mac OS X APFS", + + "Linux swap", + "Linux native", + "Linux LVM", + "Linux SoftRaid", + + "FreeBSD", + "NetBSD", + "OpenBSD", + "Solaris", + + "Basic data partition", + "Microsoft reserved partition", + "Windows LDM metadata", + "Windows LDM data", + "Windows recovery partition", + "Windows storage spaces", + + "IBM GPFS", +}; +AssertCompile(RT_ELEMENTS(g_apszDvmVolTypes) == RTDVMVOLTYPE_END); + + +/** + * Creates a new volume. + * + * @returns IPRT status code. + * @param pThis The DVM map instance. + * @param hVolFmt The format specific volume handle. + * @param phVol Where to store the generic volume handle on success. + */ +static int rtDvmVolumeCreate(PRTDVMINTERNAL pThis, RTDVMVOLUMEFMT hVolFmt, PRTDVMVOLUME phVol) +{ + PRTDVMVOLUMEINTERNAL pVol = (PRTDVMVOLUMEINTERNAL)RTMemAllocZ(sizeof(RTDVMVOLUMEINTERNAL)); + if (pVol) + { + pVol->u32Magic = RTDVMVOLUME_MAGIC; + pVol->cRefs = 0; + pVol->pVolMgr = pThis; + pVol->hVolFmt = hVolFmt; + + *phVol = pVol; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + +/** + * Destroys a volume handle. + * + * @param pThis The volume manager instance. + * @param pVol The volume to destroy. + */ +static void rtDvmVolumeDestroy(PRTDVMINTERNAL pThis, PRTDVMVOLUMEINTERNAL pVol) +{ + AssertPtr(pThis); + AssertPtr(pThis->pDvmFmtOps); + Assert(pVol->pVolMgr == pThis); + + /* Close the volume. */ + pThis->pDvmFmtOps->pfnVolumeClose(pVol->hVolFmt); + + pVol->u32Magic = RTDVMVOLUME_MAGIC_DEAD; + pVol->pVolMgr = NULL; + pVol->hVolFmt = NIL_RTDVMVOLUMEFMT; + RTMemFree(pVol); +} + + +RTDECL(int) RTDvmCreate(PRTDVM phVolMgr, RTVFSFILE hVfsFile, uint32_t cbSector, uint32_t fFlags) +{ + AssertMsgReturn(!(fFlags & ~DVM_FLAGS_VALID_MASK), ("Invalid flags given %#x\n", fFlags), VERR_INVALID_FLAGS); + uint32_t cRefs = RTVfsFileRetain(hVfsFile); + AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); + + uint64_t cbDisk; + int rc = RTVfsFileGetSize(hVfsFile, &cbDisk); + if (RT_SUCCESS(rc)) + { + PRTDVMINTERNAL pThis = (PRTDVMINTERNAL)RTMemAllocZ(sizeof(RTDVMINTERNAL)); + if (pThis) + { + pThis->u32Magic = RTDVM_MAGIC; + pThis->DvmDisk.cbDisk = cbDisk; + pThis->DvmDisk.cbSector = cbSector; + pThis->DvmDisk.hVfsFile = hVfsFile; + + pThis->pDvmFmtOps = NULL; + pThis->hVolMgrFmt = NIL_RTDVMFMT; + pThis->fFlags = fFlags; + pThis->cRefs = 1; + RTListInit(&pThis->VolumeList); + + *phVolMgr = pThis; + return VINF_SUCCESS; + } + rc = VERR_NO_MEMORY; + } + RTVfsFileRelease(hVfsFile); + return rc; +} + + +RTDECL(uint32_t) RTDvmRetain(RTDVM hVolMgr) +{ + PRTDVMINTERNAL pThis = hVolMgr; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + +/** + * Destroys a volume manager handle. + * + * @param pThis The volume manager to destroy. + */ +static void rtDvmDestroy(PRTDVMINTERNAL pThis) +{ + pThis->u32Magic = RTDVM_MAGIC_DEAD; + + if (pThis->hVolMgrFmt != NIL_RTDVMFMT) + { + AssertPtr(pThis->pDvmFmtOps); + + /* */ + PRTDVMVOLUMEINTERNAL pItNext, pIt; + RTListForEachSafe(&pThis->VolumeList, pIt, pItNext, RTDVMVOLUMEINTERNAL, VolumeNode) + { + RTListNodeRemove(&pIt->VolumeNode); + rtDvmVolumeDestroy(pThis, pIt); + } + + /* Let the backend do it's own cleanup first. */ + pThis->pDvmFmtOps->pfnClose(pThis->hVolMgrFmt); + pThis->hVolMgrFmt = NIL_RTDVMFMT; + pThis->pDvmFmtOps = NULL; + } + + pThis->DvmDisk.cbDisk = 0; + pThis->DvmDisk.cbSector = 0; + if (pThis->DvmDisk.hVfsFile != NIL_RTVFSFILE) + { + RTVfsFileRelease(pThis->DvmDisk.hVfsFile); + pThis->DvmDisk.hVfsFile = NIL_RTVFSFILE; + } + + RTMemFree(pThis); +} + +RTDECL(uint32_t) RTDvmRelease(RTDVM hVolMgr) +{ + PRTDVMINTERNAL pThis = hVolMgr; + if (pThis == NIL_RTDVM) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + rtDvmDestroy(pThis); + return cRefs; +} + +RTDECL(int) RTDvmMapOpen(RTDVM hVolMgr) +{ + PRTDVMINTERNAL pThis = hVolMgr; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->hVolMgrFmt == NIL_RTDVMFMT, VERR_WRONG_ORDER); + + Assert(!pThis->pDvmFmtOps); + + /* + * Let each format backend have a go at the disk, pick the one which scores the highest. + */ + int rc = VINF_SUCCESS; + uint32_t uScoreMax = RTDVM_MATCH_SCORE_UNSUPPORTED; + PCRTDVMFMTOPS pDvmFmtOpsMatch = NULL; + for (unsigned i = 0; i < RT_ELEMENTS(g_aDvmFmts); i++) + { + uint32_t uScore = 0; + PCRTDVMFMTOPS pDvmFmtOps = g_aDvmFmts[i]; + + rc = pDvmFmtOps->pfnProbe(&pThis->DvmDisk, &uScore); + if (RT_SUCCESS(rc)) + { + if (uScore > uScoreMax) + { + pDvmFmtOpsMatch = pDvmFmtOps; + uScoreMax = uScore; + } + } + else + return rc; + } + if (uScoreMax > RTDVM_MATCH_SCORE_UNSUPPORTED) + { + AssertPtr(pDvmFmtOpsMatch); + + /* + * Open the format. + */ + rc = pDvmFmtOpsMatch->pfnOpen(&pThis->DvmDisk, &pThis->hVolMgrFmt); + if (RT_SUCCESS(rc)) + { + pThis->pDvmFmtOps = pDvmFmtOpsMatch; + + /* + * Construct volume list (we're done if none). + */ + uint32_t cVols = pThis->pDvmFmtOps->pfnGetValidVolumes(pThis->hVolMgrFmt); + if (cVols == 0) + return VINF_SUCCESS; + + /* First volume. */ + RTDVMVOLUMEFMT hVolFmt = NIL_RTDVMVOLUMEFMT; + rc = pThis->pDvmFmtOps->pfnQueryFirstVolume(pThis->hVolMgrFmt, &hVolFmt); + if (RT_SUCCESS(rc)) + { + for (;;) + { + PRTDVMVOLUMEINTERNAL pVol = NULL; + rc = rtDvmVolumeCreate(pThis, hVolFmt, &pVol); + if (RT_FAILURE(rc)) + { + pThis->pDvmFmtOps->pfnVolumeClose(hVolFmt); + break; + } + RTListAppend(&pThis->VolumeList, &pVol->VolumeNode); + + /* Done?*/ + cVols--; + if (cVols < 1) + return VINF_SUCCESS; + + /* Next volume. */ + rc = pThis->pDvmFmtOps->pfnQueryNextVolume(pThis->hVolMgrFmt, pVol->hVolFmt, &hVolFmt); + if (RT_FAILURE(rc)) + break; + } + + /* Bail out. */ + PRTDVMVOLUMEINTERNAL pItNext, pIt; + RTListForEachSafe(&pThis->VolumeList, pIt, pItNext, RTDVMVOLUMEINTERNAL, VolumeNode) + { + RTListNodeRemove(&pIt->VolumeNode); + rtDvmVolumeDestroy(pThis, pIt); + } + } + + /** @todo shouldn't we close the format too here? */ + } + } + else + rc = VERR_NOT_SUPPORTED; + return rc; +} + +RTDECL(int) RTDvmMapInitialize(RTDVM hVolMgr, const char *pszFmt) +{ + PRTDVMINTERNAL pThis = hVolMgr; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pszFmt, VERR_INVALID_POINTER); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->hVolMgrFmt == NIL_RTDVMFMT, VERR_WRONG_ORDER); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aDvmFmts); i++) + { + PCRTDVMFMTOPS pDvmFmtOps = g_aDvmFmts[i]; + if (!RTStrCmp(pDvmFmtOps->pszFmt, pszFmt)) + { + int rc = pDvmFmtOps->pfnInitialize(&pThis->DvmDisk, &pThis->hVolMgrFmt); + if (RT_SUCCESS(rc)) + pThis->pDvmFmtOps = pDvmFmtOps; + return rc; + } + } + return VERR_NOT_SUPPORTED; +} + +RTDECL(const char *) RTDvmMapGetFormatName(RTDVM hVolMgr) +{ + PRTDVMINTERNAL pThis = hVolMgr; + AssertPtrReturn(pThis, NULL); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, NULL); + AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, NULL); + + return pThis->pDvmFmtOps->pszFmt; +} + +RTDECL(RTDVMFORMATTYPE) RTDvmMapGetFormatType(RTDVM hVolMgr) +{ + PRTDVMINTERNAL pThis = hVolMgr; + AssertPtrReturn(pThis, RTDVMFORMATTYPE_INVALID); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, RTDVMFORMATTYPE_INVALID); + AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, RTDVMFORMATTYPE_INVALID); + + return pThis->pDvmFmtOps->enmFormat; +} + +RTDECL(uint32_t) RTDvmMapGetValidVolumes(RTDVM hVolMgr) +{ + PRTDVMINTERNAL pThis = hVolMgr; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, UINT32_MAX); + AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, UINT32_MAX); + + return pThis->pDvmFmtOps->pfnGetValidVolumes(pThis->hVolMgrFmt); +} + +RTDECL(uint32_t) RTDvmMapGetMaxVolumes(RTDVM hVolMgr) +{ + PRTDVMINTERNAL pThis = hVolMgr; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, UINT32_MAX); + AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, UINT32_MAX); + + return pThis->pDvmFmtOps->pfnGetMaxVolumes(pThis->hVolMgrFmt); +} + +RTDECL(int) RTDvmMapQueryFirstVolume(RTDVM hVolMgr, PRTDVMVOLUME phVol) +{ + PRTDVMINTERNAL pThis = hVolMgr; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, VERR_INVALID_HANDLE); + AssertPtrReturn(phVol, VERR_INVALID_POINTER); + + int rc = VERR_DVM_MAP_EMPTY; + PRTDVMVOLUMEINTERNAL pVol = RTListGetFirst(&pThis->VolumeList, RTDVMVOLUMEINTERNAL, VolumeNode); + if (pVol) + { + rc = VINF_SUCCESS; + RTDvmVolumeRetain(pVol); + *phVol = pVol; + } + + return rc; +} + +RTDECL(int) RTDvmMapQueryNextVolume(RTDVM hVolMgr, RTDVMVOLUME hVol, PRTDVMVOLUME phVolNext) +{ + PRTDVMINTERNAL pThis = hVolMgr; + PRTDVMVOLUMEINTERNAL pVol = hVol; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, VERR_INVALID_HANDLE); + AssertPtrReturn(pVol, VERR_INVALID_HANDLE); + AssertReturn(pVol->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(phVolNext, VERR_INVALID_POINTER); + + int rc = VERR_DVM_MAP_NO_VOLUME; + PRTDVMVOLUMEINTERNAL pVolNext = RTListGetNext(&pThis->VolumeList, pVol, RTDVMVOLUMEINTERNAL, VolumeNode); + if (pVolNext) + { + rc = VINF_SUCCESS; + RTDvmVolumeRetain(pVolNext); + *phVolNext = pVolNext; + } + + return rc; +} + +RTDECL(int) RTDvmMapQueryBlockStatus(RTDVM hVolMgr, uint64_t off, uint64_t cb, bool *pfAllocated) +{ + PRTDVMINTERNAL pThis = hVolMgr; + + /* + * Input validation. + */ + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pfAllocated, VERR_INVALID_POINTER); + AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, VERR_WRONG_ORDER); + AssertMsgReturn( off <= pThis->DvmDisk.cbDisk + || cb <= pThis->DvmDisk.cbDisk + || off + cb <= pThis->DvmDisk.cbDisk, + ("off=%#RX64 cb=%#RX64 cbDisk=%#RX64\n", off, cb, pThis->DvmDisk.cbDisk), + VERR_OUT_OF_RANGE); + + /* + * Check whether the range is inuse by the volume manager metadata first. + */ + int rc = pThis->pDvmFmtOps->pfnQueryRangeUse(pThis->hVolMgrFmt, off, cb, pfAllocated); + if (RT_FAILURE(rc) || *pfAllocated) + return rc; + + /* + * Not used by volume manager metadata, so work thru the specified range one + * volume / void (free space) at a time. All must be unallocated for us to + * reach the end, we return immediately if any portion is allocated. + */ + while (cb > 0) + { + /* + * Search through all volumes. + * + * It is not possible to get all start sectors and sizes of all volumes + * here because volumes can be scattered around the disk for certain formats. + * Linux LVM is one example, it extents of logical volumes don't need to be + * contiguous on the medium. + */ + bool fVolFound = false; + PRTDVMVOLUMEINTERNAL pVol; + RTListForEach(&pThis->VolumeList, pVol, RTDVMVOLUMEINTERNAL, VolumeNode) + { + uint64_t cbIntersect; + uint64_t offVol; + bool fIntersect = pThis->pDvmFmtOps->pfnVolumeIsRangeIntersecting(pVol->hVolFmt, off, cb, &offVol, &cbIntersect); + if (fIntersect) + { + fVolFound = true; + if (pVol->pfnQueryBlockStatus) + { + bool fVolAllocated = true; + rc = pVol->pfnQueryBlockStatus(pVol->pvUser, offVol, cbIntersect, &fVolAllocated); + if (RT_FAILURE(rc) || fVolAllocated) + { + *pfAllocated = true; + return rc; + } + } + else if (!(pThis->fFlags & DVM_FLAGS_NO_STATUS_CALLBACK_MARK_AS_UNUSED)) + { + *pfAllocated = true; + return VINF_SUCCESS; + } + /* else, flag is set, continue. */ + + cb -= cbIntersect; + off += cbIntersect; + break; + } + } + + if (!fVolFound) + { + if (pThis->fFlags & DVM_FLAGS_UNUSED_SPACE_MARK_AS_USED) + { + *pfAllocated = true; + return VINF_SUCCESS; + } + + cb -= pThis->DvmDisk.cbSector; + off += pThis->DvmDisk.cbSector; + } + } + + *pfAllocated = false; + return rc; +} + +RTDECL(uint32_t) RTDvmVolumeRetain(RTDVMVOLUME hVol) +{ + PRTDVMVOLUMEINTERNAL pThis = hVol; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs >= 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 1) + RTDvmRetain(pThis->pVolMgr); + return cRefs; +} + +RTDECL(uint32_t) RTDvmVolumeRelease(RTDVMVOLUME hVol) +{ + PRTDVMVOLUMEINTERNAL pThis = hVol; + if (pThis == NIL_RTDVMVOLUME) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + { + /* Release the volume manager. */ + pThis->pfnQueryBlockStatus = NULL; + RTDvmRelease(pThis->pVolMgr); + } + return cRefs; +} + +RTDECL(void) RTDvmVolumeSetQueryBlockStatusCallback(RTDVMVOLUME hVol, + PFNDVMVOLUMEQUERYBLOCKSTATUS pfnQueryBlockStatus, + void *pvUser) +{ + PRTDVMVOLUMEINTERNAL pThis = hVol; + AssertPtrReturnVoid(pThis); + AssertReturnVoid(pThis->u32Magic == RTDVMVOLUME_MAGIC); + + pThis->pfnQueryBlockStatus = pfnQueryBlockStatus; + pThis->pvUser = pvUser; +} + +RTDECL(uint64_t) RTDvmVolumeGetSize(RTDVMVOLUME hVol) +{ + PRTDVMVOLUMEINTERNAL pThis = hVol; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, 0); + + return pThis->pVolMgr->pDvmFmtOps->pfnVolumeGetSize(pThis->hVolFmt); +} + +RTDECL(int) RTDvmVolumeQueryName(RTDVMVOLUME hVol, char **ppszVolName) +{ + PRTDVMVOLUMEINTERNAL pThis = hVol; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(ppszVolName, VERR_INVALID_POINTER); + + return pThis->pVolMgr->pDvmFmtOps->pfnVolumeQueryName(pThis->hVolFmt, ppszVolName); +} + +RTDECL(RTDVMVOLTYPE) RTDvmVolumeGetType(RTDVMVOLUME hVol) +{ + PRTDVMVOLUMEINTERNAL pThis = hVol; + AssertPtrReturn(pThis, RTDVMVOLTYPE_INVALID); + AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, RTDVMVOLTYPE_INVALID); + + return pThis->pVolMgr->pDvmFmtOps->pfnVolumeGetType(pThis->hVolFmt); +} + +RTDECL(uint64_t) RTDvmVolumeGetFlags(RTDVMVOLUME hVol) +{ + PRTDVMVOLUMEINTERNAL pThis = hVol; + AssertPtrReturn(pThis, UINT64_MAX); + AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, UINT64_MAX); + + return pThis->pVolMgr->pDvmFmtOps->pfnVolumeGetFlags(pThis->hVolFmt); +} + +RTDECL(int) RTDvmVolumeRead(RTDVMVOLUME hVol, uint64_t off, void *pvBuf, size_t cbRead) +{ + PRTDVMVOLUMEINTERNAL pThis = hVol; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbRead > 0, VERR_INVALID_PARAMETER); + + return pThis->pVolMgr->pDvmFmtOps->pfnVolumeRead(pThis->hVolFmt, off, pvBuf, cbRead); +} + +RTDECL(int) RTDvmVolumeWrite(RTDVMVOLUME hVol, uint64_t off, const void *pvBuf, size_t cbWrite) +{ + PRTDVMVOLUMEINTERNAL pThis = hVol; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbWrite > 0, VERR_INVALID_PARAMETER); + + return pThis->pVolMgr->pDvmFmtOps->pfnVolumeWrite(pThis->hVolFmt, off, pvBuf, cbWrite); +} + +RTDECL(const char *) RTDvmVolumeTypeGetDescr(RTDVMVOLTYPE enmVolType) +{ + AssertReturn(enmVolType >= RTDVMVOLTYPE_INVALID && enmVolType < RTDVMVOLTYPE_END, NULL); + + return g_apszDvmVolTypes[enmVolType]; +} + diff --git a/src/VBox/Runtime/common/dvm/dvmbsdlabel.cpp b/src/VBox/Runtime/common/dvm/dvmbsdlabel.cpp new file mode 100644 index 00000000..728ab05d --- /dev/null +++ b/src/VBox/Runtime/common/dvm/dvmbsdlabel.cpp @@ -0,0 +1,560 @@ +/* $Id: dvmbsdlabel.cpp $ */ +/** @file + * IPRT Disk Volume Management API (DVM) - BSD disklabel format backend. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * 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. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#include <iprt/types.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/dvm.h> +#include <iprt/string.h> +#include <iprt/asm.h> +#include "internal/dvm.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/* + * Below are the on disk structures of a bsd disklabel as found in + * /usr/include/sys/disklabel.h from a FreeBSD system. + * + * Everything is stored in little endian on the disk. + */ + +/** BSD disklabel magic. */ +#define RTDVM_BSDLBL_MAGIC UINT32_C(0x82564557) +/** Maximum number of partitions in the label. */ +#define RTDVM_BSDLBL_MAX_PARTITIONS 8 + +/** + * A BSD disk label partition. + */ +#pragma pack(1) +typedef struct BsdLabelPartition +{ + /** Number of sectors in the partition. */ + uint32_t cSectors; + /** Start sector. */ + uint32_t offSectorStart; + /** Filesystem fragment size. */ + uint32_t cbFsFragment; + /** Filesystem type. */ + uint8_t bFsType; + /** Filesystem fragments per block. */ + uint8_t cFsFragmentsPerBlock; + /** Filesystem cylinders per group. */ + uint16_t cFsCylPerGroup; +} BsdLabelPartition; +#pragma pack() +AssertCompileSize(BsdLabelPartition, 16); +/** Pointer to a BSD disklabel partition structure. */ +typedef BsdLabelPartition *PBsdLabelPartition; + +/** + * On disk BSD label structure. + */ +#pragma pack(1) +typedef struct BsdLabel +{ + /** Magic identifying the BSD disk label. */ + uint32_t u32Magic; + /** Drive type */ + uint16_t u16DriveType; + /** Subtype depending on the drive type above. */ + uint16_t u16SubType; + /** Type name. */ + uint8_t abTypeName[16]; + /** Pack identifier. */ + uint8_t abPackName[16]; + /** Number of bytes per sector. */ + uint32_t cbSector; + /** Number of sectors per track. */ + uint32_t cSectorsPerTrack; + /** Number of tracks per cylinder. */ + uint32_t cTracksPerCylinder; + /** Number of data cylinders pre unit. */ + uint32_t cDataCylindersPerUnit; + /** Number of data sectors per cylinder. */ + uint32_t cDataSectorsPerCylinder; + /** Number of data sectors per unit (unit as in disk drive?). */ + uint32_t cSectorsPerUnit; + /** Number of spare sectors per track. */ + uint16_t cSpareSectorsPerTrack; + /** Number of spare sectors per cylinder. */ + uint16_t cSpareSectorsPerCylinder; + /** Number of alternate cylinders per unit. */ + uint32_t cSpareCylindersPerUnit; + /** Rotational speed of the disk drive in rotations per minute. */ + uint16_t cRotationsPerMinute; + /** Sector interleave. */ + uint16_t uSectorInterleave; + /** Sector 0 skew, per track. */ + uint16_t uSectorSkewPerTrack; + /** Sector 0 skew, per cylinder. */ + uint16_t uSectorSkewPerCylinder; + /** Head switch time in us. */ + uint32_t usHeadSwitch; + /** Time of a track-to-track seek in us. */ + uint32_t usTrackSeek; + /** Flags. */ + uint32_t fFlags; + /** Drive type sepcific information. */ + uint32_t au32DriveData[5]; + /** Reserved. */ + uint32_t au32Reserved[5]; + /** The magic number again. */ + uint32_t u32Magic2; + /** Checksum (xor of the whole structure). */ + uint16_t u16ChkSum; + /** Number of partitions in the array. */ + uint16_t cPartitions; + /** Boot area size in bytes. */ + uint32_t cbBootArea; + /** Maximum size of the filesystem super block. */ + uint32_t cbFsSuperBlock; + /** The partition array. */ + BsdLabelPartition aPartitions[RTDVM_BSDLBL_MAX_PARTITIONS]; +} BsdLabel; +#pragma pack() +AssertCompileSize(BsdLabel, 148 + RTDVM_BSDLBL_MAX_PARTITIONS * 16); +/** Pointer to a BSD disklabel structure. */ +typedef BsdLabel *PBsdLabel; + +/** + * BSD disk label volume manager data. + */ +typedef struct RTDVMFMTINTERNAL +{ + /** Pointer to the underlying disk. */ + PCRTDVMDISK pDisk; + /** Number of used partitions. */ + uint32_t cPartitions; + /** Saved BSD disklabel structure. */ + BsdLabel DiskLabel; +} RTDVMFMTINTERNAL; +/** Pointer to the MBR volume manager. */ +typedef RTDVMFMTINTERNAL *PRTDVMFMTINTERNAL; + +/** + * MBR volume data. + */ +typedef struct RTDVMVOLUMEFMTINTERNAL +{ + /** Pointer to the volume manager. */ + PRTDVMFMTINTERNAL pVolMgr; + /** Partition table entry index. */ + uint32_t idxEntry; + /** Start offset of the volume. */ + uint64_t offStart; + /** Size of the volume. */ + uint64_t cbVolume; + /** Pointer to the raw partition table entry. */ + PBsdLabelPartition pBsdPartitionEntry; +} RTDVMVOLUMEFMTINTERNAL; +/** Pointer to an MBR volume. */ +typedef RTDVMVOLUMEFMTINTERNAL *PRTDVMVOLUMEFMTINTERNAL; + +/** Converts a LBA number to the byte offset. */ +#define RTDVM_BSDLBL_LBA2BYTE(lba, disk) ((lba) * (disk)->cbSector) +/** Converts a Byte offset to the LBA number. */ +#define RTDVM_BSDLBL_BYTE2LBA(lba, disk) ((lba) / (disk)->cbSector) + +/** + * Calculates the checksum of the entire bsd disklabel structure. + * + * @returns The checksum. + * @param pBsdLabel BSD disklabel to get the checksum for. + */ +static uint16_t rtDvmFmtBsdLblDiskLabelChkSum(PBsdLabel pBsdLabel) +{ + uint16_t uChkSum = 0; + uint16_t *pCurr = (uint16_t *)pBsdLabel; + uint16_t *pEnd = (uint16_t *)&pBsdLabel->aPartitions[pBsdLabel->cPartitions]; + + while (pCurr < pEnd) + uChkSum ^= *pCurr++; + + return uChkSum; +} + +/** + * Converts a partition entry to the host endianness. + * + * @returns nothing. + * @param pPartition The partition to decode. + */ +static void rtDvmFmtBsdLblDiskLabelDecodePartition(PBsdLabelPartition pPartition) +{ + pPartition->cSectors = RT_LE2H_U32(pPartition->cSectors); + pPartition->offSectorStart = RT_LE2H_U32(pPartition->offSectorStart); + pPartition->cbFsFragment = RT_LE2H_U32(pPartition->cbFsFragment); + pPartition->cFsCylPerGroup = RT_LE2H_U16(pPartition->cFsCylPerGroup); +} + +/** + * Converts the on disk BSD label to the host endianness. + * + * @returns Whether the given label structure is a valid BSD disklabel. + * @param pBsdLabel Pointer to the BSD disklabel to decode. + */ +static bool rtDvmFmtBsdLblDiskLabelDecode(PBsdLabel pBsdLabel) +{ + pBsdLabel->u32Magic = RT_LE2H_U32(pBsdLabel->u32Magic); + pBsdLabel->u16DriveType = RT_LE2H_U16(pBsdLabel->u16DriveType); + pBsdLabel->u16SubType = RT_LE2H_U16(pBsdLabel->u16SubType); + pBsdLabel->cbSector = RT_LE2H_U32(pBsdLabel->cbSector); + pBsdLabel->cSectorsPerTrack = RT_LE2H_U32(pBsdLabel->cSectorsPerTrack); + pBsdLabel->cTracksPerCylinder = RT_LE2H_U32(pBsdLabel->cTracksPerCylinder); + pBsdLabel->cDataCylindersPerUnit = RT_LE2H_U32(pBsdLabel->cDataCylindersPerUnit); + pBsdLabel->cDataSectorsPerCylinder = RT_LE2H_U32(pBsdLabel->cDataSectorsPerCylinder); + pBsdLabel->cSectorsPerUnit = RT_LE2H_U32(pBsdLabel->cSectorsPerUnit); + pBsdLabel->cSpareSectorsPerTrack = RT_LE2H_U16(pBsdLabel->cSpareSectorsPerTrack); + pBsdLabel->cSpareSectorsPerCylinder = RT_LE2H_U16(pBsdLabel->cSpareSectorsPerCylinder); + pBsdLabel->cSpareCylindersPerUnit = RT_LE2H_U32(pBsdLabel->cSpareCylindersPerUnit); + pBsdLabel->cRotationsPerMinute = RT_LE2H_U16(pBsdLabel->cRotationsPerMinute); + pBsdLabel->uSectorInterleave = RT_LE2H_U16(pBsdLabel->uSectorInterleave); + pBsdLabel->uSectorSkewPerTrack = RT_LE2H_U16(pBsdLabel->uSectorSkewPerTrack); + pBsdLabel->uSectorSkewPerCylinder = RT_LE2H_U16(pBsdLabel->uSectorSkewPerCylinder); + pBsdLabel->usHeadSwitch = RT_LE2H_U16(pBsdLabel->usHeadSwitch); + pBsdLabel->usTrackSeek = RT_LE2H_U16(pBsdLabel->usTrackSeek); + pBsdLabel->fFlags = RT_LE2H_U32(pBsdLabel->fFlags); + + for (unsigned i = 0; i < RT_ELEMENTS(pBsdLabel->au32DriveData); i++) + pBsdLabel->au32DriveData[i] = RT_LE2H_U32(pBsdLabel->au32DriveData[i]); + for (unsigned i = 0; i < RT_ELEMENTS(pBsdLabel->au32Reserved); i++) + pBsdLabel->au32Reserved[i] = RT_LE2H_U32(pBsdLabel->au32Reserved[i]); + + pBsdLabel->u32Magic2 = RT_LE2H_U32(pBsdLabel->u32Magic2); + pBsdLabel->u16ChkSum = RT_LE2H_U16(pBsdLabel->u16ChkSum); + pBsdLabel->cPartitions = RT_LE2H_U16(pBsdLabel->cPartitions); + pBsdLabel->cbBootArea = RT_LE2H_U32(pBsdLabel->cbBootArea); + pBsdLabel->cbFsSuperBlock = RT_LE2H_U32(pBsdLabel->cbFsSuperBlock); + + /* Check the magics now. */ + if ( pBsdLabel->u32Magic != RTDVM_BSDLBL_MAGIC + || pBsdLabel->u32Magic2 != RTDVM_BSDLBL_MAGIC + || pBsdLabel->cPartitions != RTDVM_BSDLBL_MAX_PARTITIONS) + return false; + + /* Convert the partitions array. */ + for (unsigned i = 0; i < RT_ELEMENTS(pBsdLabel->aPartitions); i++) + rtDvmFmtBsdLblDiskLabelDecodePartition(&pBsdLabel->aPartitions[i]); + + /* Check the checksum now. */ + uint16_t u16ChkSumSaved = pBsdLabel->u16ChkSum; + + pBsdLabel->u16ChkSum = 0; + if (u16ChkSumSaved != rtDvmFmtBsdLblDiskLabelChkSum(pBsdLabel)) + return false; + + pBsdLabel->u16ChkSum = u16ChkSumSaved; + return true; +} + +static DECLCALLBACK(int) rtDvmFmtBsdLblProbe(PCRTDVMDISK pDisk, uint32_t *puScore) +{ + BsdLabel DiskLabel; + int rc = VINF_SUCCESS; + + *puScore = RTDVM_MATCH_SCORE_UNSUPPORTED; + + if (pDisk->cbDisk >= sizeof(BsdLabel)) + { + /* Read from the disk and check for the disk label structure. */ + rc = rtDvmDiskRead(pDisk, RTDVM_BSDLBL_LBA2BYTE(1, pDisk), &DiskLabel, sizeof(BsdLabel)); + if ( RT_SUCCESS(rc) + && rtDvmFmtBsdLblDiskLabelDecode(&DiskLabel)) + *puScore = RTDVM_MATCH_SCORE_PERFECT; + } + return rc; +} + +static DECLCALLBACK(int) rtDvmFmtBsdLblOpen(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt) +{ + int rc = VINF_SUCCESS; + PRTDVMFMTINTERNAL pThis = NULL; + + pThis = (PRTDVMFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMFMTINTERNAL)); + if (pThis) + { + pThis->pDisk = pDisk; + pThis->cPartitions = 0; + + /* Read from the disk and check for the disk label structure. */ + rc = rtDvmDiskRead(pDisk, RTDVM_BSDLBL_LBA2BYTE(1, pDisk), &pThis->DiskLabel, sizeof(BsdLabel)); + if ( RT_SUCCESS(rc) + && rtDvmFmtBsdLblDiskLabelDecode(&pThis->DiskLabel)) + { + /* Count number of used entries. */ + for (unsigned i = 0; i < pThis->DiskLabel.cPartitions; i++) + if (pThis->DiskLabel.aPartitions[i].cSectors) + pThis->cPartitions++; + + *phVolMgrFmt = pThis; + } + else + { + RTMemFree(pThis); + rc = VERR_INVALID_MAGIC; + } + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +static DECLCALLBACK(int) rtDvmFmtBsdLblInitialize(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt) +{ + NOREF(pDisk); NOREF(phVolMgrFmt); + return VERR_NOT_IMPLEMENTED; +} + +static DECLCALLBACK(void) rtDvmFmtBsdLblClose(RTDVMFMT hVolMgrFmt) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + + pThis->pDisk = NULL; + pThis->cPartitions = 0; + memset(&pThis->DiskLabel, 0, sizeof(BsdLabel)); + RTMemFree(pThis); +} + +static DECLCALLBACK(int) rtDvmFmtBsdLblQueryRangeUse(RTDVMFMT hVolMgrFmt, + uint64_t off, uint64_t cbRange, + bool *pfUsed) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + + NOREF(cbRange); + + if (off <= RTDVM_BSDLBL_LBA2BYTE(1, pThis->pDisk)) + *pfUsed = true; + else + *pfUsed = false; + + return VINF_SUCCESS; +} + +static DECLCALLBACK(uint32_t) rtDvmFmtBsdLblGetValidVolumes(RTDVMFMT hVolMgrFmt) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + return pThis->cPartitions; +} + +static DECLCALLBACK(uint32_t) rtDvmFmtBsdLblGetMaxVolumes(RTDVMFMT hVolMgrFmt) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + return pThis->DiskLabel.cPartitions; +} + +/** + * Creates a new volume. + * + * @returns IPRT status code. + * @param pThis The MBR volume manager data. + * @param pbBsdLblEntry The raw MBR entry data. + * @param idx The index in the partition table. + * @param phVolFmt Where to store the volume data on success. + */ +static int rtDvmFmtBsdLblVolumeCreate(PRTDVMFMTINTERNAL pThis, PBsdLabelPartition pBsdPartitionEntry, + uint32_t idx, PRTDVMVOLUMEFMT phVolFmt) +{ + int rc = VINF_SUCCESS; + PRTDVMVOLUMEFMTINTERNAL pVol = (PRTDVMVOLUMEFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMVOLUMEFMTINTERNAL)); + + if (pVol) + { + pVol->pVolMgr = pThis; + pVol->idxEntry = idx; + pVol->pBsdPartitionEntry = pBsdPartitionEntry; + pVol->offStart = (uint64_t)pBsdPartitionEntry->offSectorStart * pThis->DiskLabel.cbSector; + pVol->cbVolume = (uint64_t)pBsdPartitionEntry->cSectors * pThis->DiskLabel.cbSector; + + *phVolFmt = pVol; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +static DECLCALLBACK(int) rtDvmFmtBsdLblQueryFirstVolume(RTDVMFMT hVolMgrFmt, PRTDVMVOLUMEFMT phVolFmt) +{ + int rc = VINF_SUCCESS; + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + + if (pThis->cPartitions != 0) + { + /* Search for the first non empty entry. */ + for (unsigned i = 0; i < pThis->DiskLabel.cPartitions; i++) + { + if (pThis->DiskLabel.aPartitions[i].cSectors) + { + rc = rtDvmFmtBsdLblVolumeCreate(pThis, &pThis->DiskLabel.aPartitions[i], i, phVolFmt); + break; + } + } + } + else + rc = VERR_DVM_MAP_EMPTY; + + return rc; +} + +static DECLCALLBACK(int) rtDvmFmtBsdLblQueryNextVolume(RTDVMFMT hVolMgrFmt, RTDVMVOLUMEFMT hVolFmt, PRTDVMVOLUMEFMT phVolFmtNext) +{ + int rc = VERR_DVM_MAP_NO_VOLUME; + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + PBsdLabelPartition pBsdPartitionEntry = pVol->pBsdPartitionEntry + 1; + + for (unsigned i = pVol->idxEntry + 1; i < pThis->DiskLabel.cPartitions; i++) + { + if (pBsdPartitionEntry->cSectors) + { + rc = rtDvmFmtBsdLblVolumeCreate(pThis, pBsdPartitionEntry, i, phVolFmtNext); + break; + } + pBsdPartitionEntry++; + } + + return rc; +} + +static DECLCALLBACK(void) rtDvmFmtBsdLblVolumeClose(RTDVMVOLUMEFMT hVolFmt) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + pVol->pVolMgr = NULL; + pVol->offStart = 0; + pVol->cbVolume = 0; + pVol->pBsdPartitionEntry = NULL; + + RTMemFree(pVol); +} + +static DECLCALLBACK(uint64_t) rtDvmFmtBsdLblVolumeGetSize(RTDVMVOLUMEFMT hVolFmt) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + return pVol->cbVolume; +} + +static DECLCALLBACK(int) rtDvmFmtBsdLblVolumeQueryName(RTDVMVOLUMEFMT hVolFmt, char **ppszVolName) +{ + NOREF(hVolFmt); NOREF(ppszVolName); + return VERR_NOT_SUPPORTED; +} + +static DECLCALLBACK(RTDVMVOLTYPE) rtDvmFmtBsdLblVolumeGetType(RTDVMVOLUMEFMT hVolFmt) +{ + NOREF(hVolFmt); + return RTDVMVOLTYPE_UNKNOWN; +} + +static DECLCALLBACK(uint64_t) rtDvmFmtBsdLblVolumeGetFlags(RTDVMVOLUMEFMT hVolFmt) +{ + NOREF(hVolFmt); + return 0; +} + +static DECLCALLBACK(bool) rtDvmFmtBsdLblVolumeIsRangeIntersecting(RTDVMVOLUMEFMT hVolFmt, + uint64_t offStart, size_t cbRange, + uint64_t *poffVol, + uint64_t *pcbIntersect) +{ + bool fIntersect = false; + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + if (RTDVM_RANGE_IS_INTERSECTING(pVol->offStart, pVol->cbVolume, offStart)) + { + fIntersect = true; + *poffVol = offStart - pVol->offStart; + *pcbIntersect = RT_MIN(cbRange, pVol->offStart + pVol->cbVolume - offStart); + } + + return fIntersect; +} + +static DECLCALLBACK(int) rtDvmFmtBsdLblVolumeRead(RTDVMVOLUMEFMT hVolFmt, uint64_t off, void *pvBuf, size_t cbRead) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + AssertReturn(off + cbRead <= pVol->cbVolume, VERR_INVALID_PARAMETER); + + return rtDvmDiskRead(pVol->pVolMgr->pDisk, pVol->offStart + off, pvBuf, cbRead); +} + +static DECLCALLBACK(int) rtDvmFmtBsdLblVolumeWrite(RTDVMVOLUMEFMT hVolFmt, uint64_t off, const void *pvBuf, size_t cbWrite) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + AssertReturn(off + cbWrite <= pVol->cbVolume, VERR_INVALID_PARAMETER); + + return rtDvmDiskWrite(pVol->pVolMgr->pDisk, pVol->offStart + off, pvBuf, cbWrite); +} + +DECLHIDDEN(RTDVMFMTOPS) g_rtDvmFmtBsdLbl = +{ + /* pcszFmt */ + "BsdLabel", + /* enmFormat, */ + RTDVMFORMATTYPE_BSD_LABLE, + /* pfnProbe */ + rtDvmFmtBsdLblProbe, + /* pfnOpen */ + rtDvmFmtBsdLblOpen, + /* pfnInitialize */ + rtDvmFmtBsdLblInitialize, + /* pfnClose */ + rtDvmFmtBsdLblClose, + /* pfnQueryRangeUse */ + rtDvmFmtBsdLblQueryRangeUse, + /* pfnGetValidVolumes */ + rtDvmFmtBsdLblGetValidVolumes, + /* pfnGetMaxVolumes */ + rtDvmFmtBsdLblGetMaxVolumes, + /* pfnQueryFirstVolume */ + rtDvmFmtBsdLblQueryFirstVolume, + /* pfnQueryNextVolume */ + rtDvmFmtBsdLblQueryNextVolume, + /* pfnVolumeClose */ + rtDvmFmtBsdLblVolumeClose, + /* pfnVolumeGetSize */ + rtDvmFmtBsdLblVolumeGetSize, + /* pfnVolumeQueryName */ + rtDvmFmtBsdLblVolumeQueryName, + /* pfnVolumeGetType */ + rtDvmFmtBsdLblVolumeGetType, + /* pfnVolumeGetFlags */ + rtDvmFmtBsdLblVolumeGetFlags, + /* pfnVolumeIsRangeIntersecting */ + rtDvmFmtBsdLblVolumeIsRangeIntersecting, + /* pfnVolumeRead */ + rtDvmFmtBsdLblVolumeRead, + /* pfnVolumeWrite */ + rtDvmFmtBsdLblVolumeWrite +}; + diff --git a/src/VBox/Runtime/common/dvm/dvmgpt.cpp b/src/VBox/Runtime/common/dvm/dvmgpt.cpp new file mode 100644 index 00000000..b5b1eff8 --- /dev/null +++ b/src/VBox/Runtime/common/dvm/dvmgpt.cpp @@ -0,0 +1,572 @@ +/* $Id: dvmgpt.cpp $ */ +/** @file + * IPRT Disk Volume Management API (DVM) - GPT format backend. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * 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. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/dvm.h> + +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <iprt/uuid.h> +#include "internal/dvm.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** The GPT signature. */ +#define RTDVM_GPT_SIGNATURE "EFI PART" + +/** + * GPT on disk header. + */ +typedef struct GPTHDR +{ + /** 0x00: Signature ("EFI PART"). */ + char abSignature[8]; + /** 0x08: Revision. */ + uint32_t u32Revision; + /** 0x0c: Header size. */ + uint32_t cbHeader; + /** 0x10: CRC of header. */ + uint32_t u32Crc; +} GPTHDR; +/** Pointer to a GPT header. */ +typedef struct GPTHDR *PGPTHDR; +AssertCompileSize(GPTHDR, 20); + +/** + * Complete GPT table header for revision 1.0. + */ +#pragma pack(1) +typedef struct GPTHDRREV1 +{ + /** 0x00: Header. */ + GPTHDR Hdr; + /** 0x14: Reserved. */ + uint32_t u32Reserved; + /** 0x18: Current LBA. */ + uint64_t u64LbaCurrent; + /** 0x20: Backup LBA. */ + uint64_t u64LbaBackup; + /** 0x28:First usable LBA for partitions. */ + uint64_t u64LbaFirstPartition; + /** 0x30: Last usable LBA for partitions. */ + uint64_t u64LbaLastPartition; + /** 0x38: Disk UUID. */ + RTUUID DiskUuid; + /** 0x48: LBA of first partition entry. */ + uint64_t u64LbaPartitionEntries; + /** 0x50: Number of partition entries. */ + uint32_t cPartitionEntries; + /** 0x54: Partition entry size. */ + uint32_t cbPartitionEntry; + /** 0x58: CRC of partition entries. */ + uint32_t u32CrcPartitionEntries; +} GPTHDRREV1; +/** Pointer to a revision 1.0 GPT header. */ +typedef GPTHDRREV1 *PGPTHDRREV1; +#pragma pack() +AssertCompileSize(GPTHDRREV1, 92); + +/** + * GPT partition table entry. + */ +typedef struct GPTENTRY +{ + /** 0x00: Partition type UUID. */ + RTUUID UuidType; + /** 0x10: Partition UUID. */ + RTUUID UuidPartition; + /** 0x20: First LBA. */ + uint64_t u64LbaFirst; + /** 0x28: Last LBA. */ + uint64_t u64LbaLast; + /** 0x30: Attribute flags. */ + uint64_t u64Flags; + /** 0x38: Partition name (UTF-16LE code units). */ + RTUTF16 aPartitionName[36]; +} GPTENTRY; +/** Pointer to a GPT entry. */ +typedef struct GPTENTRY *PGPTENTRY; +AssertCompileSize(GPTENTRY, 128); + +/** Partition flags - System partition. */ +#define RTDVM_GPT_ENTRY_SYSTEM RT_BIT_64(0) +/** Partition flags - Partition is readonly. */ +#define RTDVM_GPT_ENTRY_READONLY RT_BIT_64(60) +/** Partition flags - Partition is hidden. */ +#define RTDVM_GPT_ENTRY_HIDDEN RT_BIT_64(62) +/** Partition flags - Don't automount this partition. */ +#define RTDVM_GPT_ENTRY_NO_AUTOMOUNT RT_BIT_64(63) + +/** + * GPT volume manager data. + */ +typedef struct RTDVMFMTINTERNAL +{ + /** Pointer to the underlying disk. */ + PCRTDVMDISK pDisk; + /** GPT header. */ + GPTHDRREV1 HdrRev1; + /** GPT array. */ + PGPTENTRY paGptEntries; + /** Number of occupied partition entries. */ + uint32_t cPartitions; +} RTDVMFMTINTERNAL; +/** Pointer to the MBR volume manager. */ +typedef RTDVMFMTINTERNAL *PRTDVMFMTINTERNAL; + +/** + * GPT volume data. + */ +typedef struct RTDVMVOLUMEFMTINTERNAL +{ + /** Pointer to the volume manager. */ + PRTDVMFMTINTERNAL pVolMgr; + /** Partition table entry index. */ + uint32_t idxEntry; + /** Start offset of the volume. */ + uint64_t offStart; + /** Size of the volume. */ + uint64_t cbVolume; + /** Pointer to the GPT entry in the array. */ + PGPTENTRY pGptEntry; +} RTDVMVOLUMEFMTINTERNAL; +/** Pointer to an MBR volume. */ +typedef RTDVMVOLUMEFMTINTERNAL *PRTDVMVOLUMEFMTINTERNAL; + +/** + * GPT partition type to DVM volume type mapping entry. + */ + +typedef struct RTDVMGPTPARTTYPE2VOLTYPE +{ + /** Type UUID. */ + const char *pcszUuid; + /** DVM volume type. */ + RTDVMVOLTYPE enmVolType; +} RTDVMGPTPARTTYPE2VOLTYPE; +/** Pointer to a MBR FS Type to volume type mapping entry. */ +typedef RTDVMGPTPARTTYPE2VOLTYPE *PRTDVMGPTPARTTYPE2VOLTYPE; + +/** Converts a LBA number to the byte offset. */ +#define RTDVM_GPT_LBA2BYTE(lba, disk) ((lba) * (disk)->cbSector) +/** Converts a Byte offset to the LBA number. */ +#define RTDVM_GPT_BYTE2LBA(lba, disk) ((lba) / (disk)->cbSector) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Mapping of partition types to DVM volume types. + * + * From http://en.wikipedia.org/wiki/GUID_Partition_Table + */ +static const RTDVMGPTPARTTYPE2VOLTYPE g_aPartType2DvmVolTypes[] = +{ + { "C12A7328-F81F-11D2-BA4B-00A0C93EC93B", RTDVMVOLTYPE_EFI_SYSTEM }, + + { "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", RTDVMVOLTYPE_WIN_BASIC }, + { "E3C9E316-0B5C-4DB8-817D-F92DF00215AE", RTDVMVOLTYPE_WIN_MSR }, + { "5808C8AA-7E8F-42E0-85D2-E1E90434CFB3", RTDVMVOLTYPE_WIN_LDM_META }, + { "AF9B60A0-1431-4F62-BC68-3311714A69AD", RTDVMVOLTYPE_WIN_LDM_DATA }, + { "DE94BBA4-06D1-4D40-A16A-BFD50179D6AC", RTDVMVOLTYPE_WIN_RECOVERY }, + { "E75CAF8F-F680-4CEE-AFA3-B001E56EFC2D", RTDVMVOLTYPE_WIN_STORAGE_SPACES }, + + { "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F", RTDVMVOLTYPE_LINUX_SWAP }, + { "0FC63DAF-8483-4772-8E79-3D69D8477DE4", RTDVMVOLTYPE_LINUX_NATIVE }, + { "44479540-F297-41B2-9AF7-D131D5F0458A", RTDVMVOLTYPE_LINUX_NATIVE }, /* x86 root */ + { "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709", RTDVMVOLTYPE_LINUX_NATIVE }, /* AMD64 root */ + { "69DAD710-2CE4-4E3C-B16C-21A1D49ABED3", RTDVMVOLTYPE_LINUX_NATIVE }, /* ARM32 root */ + { "B921B045-1DF0-41C3-AF44-4C6F280D3FAE", RTDVMVOLTYPE_LINUX_NATIVE }, /* ARM64 root */ + { "E6D6D379-F507-44C2-A23C-238F2A3DF928", RTDVMVOLTYPE_LINUX_LVM }, + { "A19D880F-05FC-4D3B-A006-743F0F84911E", RTDVMVOLTYPE_LINUX_SOFTRAID }, + + { "83BD6B9D-7F41-11DC-BE0B-001560B84F0F", RTDVMVOLTYPE_FREEBSD }, /* Boot */ + { "516E7CB4-6ECF-11D6-8FF8-00022D09712B", RTDVMVOLTYPE_FREEBSD }, /* Data */ + { "516E7CB5-6ECF-11D6-8FF8-00022D09712B", RTDVMVOLTYPE_FREEBSD }, /* Swap */ + { "516E7CB6-6ECF-11D6-8FF8-00022D09712B", RTDVMVOLTYPE_FREEBSD }, /* UFS */ + { "516E7CB8-6ECF-11D6-8FF8-00022D09712B", RTDVMVOLTYPE_FREEBSD }, /* Vinum */ + { "516E7CBA-6ECF-11D6-8FF8-00022D09712B", RTDVMVOLTYPE_FREEBSD }, /* ZFS */ + + { "49F48D32-B10E-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* Swap */ + { "49F48D5A-B10E-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* FFS */ + { "49F48D82-B10E-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* LFS */ + { "49F48DAA-B10E-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* Raid */ + { "2DB519C4-B10F-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* Concatenated */ + { "2DB519EC-B10F-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* Encrypted */ + + { "48465300-0000-11AA-AA11-00306543ECAC", RTDVMVOLTYPE_DARWIN_HFS }, + { "7C3457EF-0000-11AA-AA11-00306543ECAC", RTDVMVOLTYPE_DARWIN_APFS }, + + { "6A82CB45-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* Boot */ + { "6A85CF4D-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* Root */ + { "6A87C46F-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* Swap */ + { "6A8B642B-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* Backup */ + { "6A898CC3-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* /usr */ + { "6A8EF2E9-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* /var */ + { "6A90BA39-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* /home */ + { "6A9283A5-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* Alternate sector */ + + { "37AFFC90-EF7D-4E96-91C3-2D7AE055B174", RTDVMVOLTYPE_IBM_GPFS }, +}; + +static DECLCALLBACK(int) rtDvmFmtGptProbe(PCRTDVMDISK pDisk, uint32_t *puScore) +{ + int rc = VINF_SUCCESS; + GPTHDR Hdr; + + *puScore = RTDVM_MATCH_SCORE_UNSUPPORTED; + + if (rtDvmDiskGetSectors(pDisk) >= 2) + { + /* Read from the disk and check for the signature. */ + rc = rtDvmDiskRead(pDisk, RTDVM_GPT_LBA2BYTE(1, pDisk), &Hdr, sizeof(GPTHDR)); + if ( RT_SUCCESS(rc) + && !strncmp(&Hdr.abSignature[0], RTDVM_GPT_SIGNATURE, RT_ELEMENTS(Hdr.abSignature)) + && RT_LE2H_U32(Hdr.u32Revision) == 0x00010000 + && RT_LE2H_U32(Hdr.cbHeader) == sizeof(GPTHDRREV1)) + *puScore = RTDVM_MATCH_SCORE_PERFECT; + } + + return rc; +} + +static DECLCALLBACK(int) rtDvmFmtGptOpen(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt) +{ + int rc = VINF_SUCCESS; + PRTDVMFMTINTERNAL pThis = NULL; + + pThis = (PRTDVMFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMFMTINTERNAL)); + if (pThis) + { + pThis->pDisk = pDisk; + pThis->cPartitions = 0; + + /* Read the complete GPT header and convert to host endianess. */ + rc = rtDvmDiskRead(pDisk, RTDVM_GPT_LBA2BYTE(1, pDisk), &pThis->HdrRev1, sizeof(pThis->HdrRev1)); + if (RT_SUCCESS(rc)) + { + pThis->HdrRev1.Hdr.u32Revision = RT_LE2H_U32(pThis->HdrRev1.Hdr.u32Revision); + pThis->HdrRev1.Hdr.cbHeader = RT_LE2H_U32(pThis->HdrRev1.Hdr.cbHeader); + pThis->HdrRev1.Hdr.u32Crc = RT_LE2H_U32(pThis->HdrRev1.Hdr.u32Crc); + pThis->HdrRev1.u64LbaCurrent = RT_LE2H_U64(pThis->HdrRev1.u64LbaCurrent); + pThis->HdrRev1.u64LbaBackup = RT_LE2H_U64(pThis->HdrRev1.u64LbaBackup); + pThis->HdrRev1.u64LbaFirstPartition = RT_LE2H_U64(pThis->HdrRev1.u64LbaFirstPartition); + pThis->HdrRev1.u64LbaLastPartition = RT_LE2H_U64(pThis->HdrRev1.u64LbaLastPartition); + /** @todo Disk UUID */ + pThis->HdrRev1.u64LbaPartitionEntries = RT_LE2H_U64(pThis->HdrRev1.u64LbaPartitionEntries); + pThis->HdrRev1.cPartitionEntries = RT_LE2H_U32(pThis->HdrRev1.cPartitionEntries); + pThis->HdrRev1.cbPartitionEntry = RT_LE2H_U32(pThis->HdrRev1.cbPartitionEntry); + pThis->HdrRev1.u32CrcPartitionEntries = RT_LE2H_U32(pThis->HdrRev1.u32CrcPartitionEntries); + + if (pThis->HdrRev1.cbPartitionEntry == sizeof(GPTENTRY)) + { + pThis->paGptEntries = (PGPTENTRY)RTMemAllocZ(pThis->HdrRev1.cPartitionEntries * pThis->HdrRev1.cbPartitionEntry); + if (pThis->paGptEntries) + { + rc = rtDvmDiskRead(pDisk, RTDVM_GPT_LBA2BYTE(pThis->HdrRev1.u64LbaPartitionEntries, pDisk), + pThis->paGptEntries, pThis->HdrRev1.cPartitionEntries * pThis->HdrRev1.cbPartitionEntry); + if (RT_SUCCESS(rc)) + { + /* Count the occupied entries. */ + for (unsigned i = 0; i < pThis->HdrRev1.cPartitionEntries; i++) + if (!RTUuidIsNull(&pThis->paGptEntries[i].UuidType)) + { + /* Convert to host endianess. */ + /** @todo Uuids */ + pThis->paGptEntries[i].u64LbaFirst = RT_LE2H_U64(pThis->paGptEntries[i].u64LbaFirst); + pThis->paGptEntries[i].u64LbaLast = RT_LE2H_U64(pThis->paGptEntries[i].u64LbaLast); + pThis->paGptEntries[i].u64Flags = RT_LE2H_U64(pThis->paGptEntries[i].u64Flags); + for (unsigned cwc = 0; cwc < RT_ELEMENTS(pThis->paGptEntries[i].aPartitionName); cwc++) + pThis->paGptEntries[i].aPartitionName[cwc] = RT_LE2H_U16(pThis->paGptEntries[i].aPartitionName[cwc]); + + pThis->cPartitions++; + } + } + + if (RT_FAILURE(rc)) + RTMemFree(pThis->paGptEntries); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_NOT_SUPPORTED; + + if (RT_SUCCESS(rc)) + *phVolMgrFmt = pThis; + else + RTMemFree(pThis); + } + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +static DECLCALLBACK(int) rtDvmFmtGptInitialize(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt) +{ + NOREF(pDisk); NOREF(phVolMgrFmt); + return VERR_NOT_IMPLEMENTED; +} + +static DECLCALLBACK(void) rtDvmFmtGptClose(RTDVMFMT hVolMgrFmt) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + + pThis->pDisk = NULL; + memset(&pThis->HdrRev1, 0, sizeof(pThis->HdrRev1)); + RTMemFree(pThis->paGptEntries); + + pThis->paGptEntries = NULL; + RTMemFree(pThis); +} + +static DECLCALLBACK(int) rtDvmFmtGptQueryRangeUse(RTDVMFMT hVolMgrFmt, + uint64_t off, uint64_t cbRange, + bool *pfUsed) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + + NOREF(cbRange); + + if (off < 33*pThis->pDisk->cbSector) + *pfUsed = true; + else + *pfUsed = false; + + return VINF_SUCCESS; +} + +static DECLCALLBACK(uint32_t) rtDvmFmtGptGetValidVolumes(RTDVMFMT hVolMgrFmt) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + + return pThis->cPartitions; +} + +static DECLCALLBACK(uint32_t) rtDvmFmtGptGetMaxVolumes(RTDVMFMT hVolMgrFmt) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + + return pThis->HdrRev1.cPartitionEntries; +} + +/** + * Creates a new volume. + * + * @returns IPRT status code. + * @param pThis The MBR volume manager data. + * @param pGptEntry The GPT entry. + * @param idx The index in the partition array. + * @param phVolFmt Where to store the volume data on success. + */ +static int rtDvmFmtMbrVolumeCreate(PRTDVMFMTINTERNAL pThis, PGPTENTRY pGptEntry, + uint32_t idx, PRTDVMVOLUMEFMT phVolFmt) +{ + int rc = VINF_SUCCESS; + PRTDVMVOLUMEFMTINTERNAL pVol = (PRTDVMVOLUMEFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMVOLUMEFMTINTERNAL)); + + if (pVol) + { + pVol->pVolMgr = pThis; + pVol->idxEntry = idx; + pVol->pGptEntry = pGptEntry; + pVol->offStart = RTDVM_GPT_LBA2BYTE(pGptEntry->u64LbaFirst, pThis->pDisk); + pVol->cbVolume = RTDVM_GPT_LBA2BYTE(pGptEntry->u64LbaLast - pGptEntry->u64LbaFirst + 1, pThis->pDisk); + + *phVolFmt = pVol; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +static DECLCALLBACK(int) rtDvmFmtGptQueryFirstVolume(RTDVMFMT hVolMgrFmt, PRTDVMVOLUMEFMT phVolFmt) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + + if (pThis->cPartitions != 0) + { + PGPTENTRY pGptEntry = &pThis->paGptEntries[0]; + + /* Search for the first non empty entry. */ + for (unsigned i = 0; i < pThis->HdrRev1.cPartitionEntries; i++) + { + if (!RTUuidIsNull(&pGptEntry->UuidType)) + return rtDvmFmtMbrVolumeCreate(pThis, pGptEntry, i, phVolFmt); + pGptEntry++; + } + AssertFailed(); + } + return VERR_DVM_MAP_EMPTY; +} + +static DECLCALLBACK(int) rtDvmFmtGptQueryNextVolume(RTDVMFMT hVolMgrFmt, RTDVMVOLUMEFMT hVolFmt, PRTDVMVOLUMEFMT phVolFmtNext) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + PGPTENTRY pGptEntry = pVol->pGptEntry + 1; + + for (unsigned i = pVol->idxEntry + 1; i < pThis->HdrRev1.cPartitionEntries; i++) + { + if (!RTUuidIsNull(&pGptEntry->UuidType)) + return rtDvmFmtMbrVolumeCreate(pThis, pGptEntry, i, phVolFmtNext); + pGptEntry++; + } + + return VERR_DVM_MAP_NO_VOLUME; +} + +static DECLCALLBACK(void) rtDvmFmtGptVolumeClose(RTDVMVOLUMEFMT hVolFmt) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + pVol->pVolMgr = NULL; + pVol->offStart = 0; + pVol->cbVolume = 0; + pVol->pGptEntry = NULL; + + RTMemFree(pVol); +} + +static DECLCALLBACK(uint64_t) rtDvmFmtGptVolumeGetSize(RTDVMVOLUMEFMT hVolFmt) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + return pVol->cbVolume; +} + +static DECLCALLBACK(int) rtDvmFmtGptVolumeQueryName(RTDVMVOLUMEFMT hVolFmt, char **ppszVolName) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + *ppszVolName = NULL; + return RTUtf16ToUtf8Ex(&pVol->pGptEntry->aPartitionName[0], RT_ELEMENTS(pVol->pGptEntry->aPartitionName), + ppszVolName, 0, NULL); +} + +static DECLCALLBACK(RTDVMVOLTYPE) rtDvmFmtGptVolumeGetType(RTDVMVOLUMEFMT hVolFmt) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + for (unsigned i = 0; i < RT_ELEMENTS(g_aPartType2DvmVolTypes); i++) + if (!RTUuidCompareStr(&pVol->pGptEntry->UuidType, g_aPartType2DvmVolTypes[i].pcszUuid)) + return g_aPartType2DvmVolTypes[i].enmVolType; + + return RTDVMVOLTYPE_UNKNOWN; +} + +static DECLCALLBACK(uint64_t) rtDvmFmtGptVolumeGetFlags(RTDVMVOLUMEFMT hVolFmt) +{ + NOREF(hVolFmt); /* No supported flags for now. */ + return 0; +} + +static DECLCALLBACK(bool) rtDvmFmtGptVolumeIsRangeIntersecting(RTDVMVOLUMEFMT hVolFmt, + uint64_t offStart, size_t cbRange, + uint64_t *poffVol, + uint64_t *pcbIntersect) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + if (RTDVM_RANGE_IS_INTERSECTING(pVol->offStart, pVol->cbVolume, offStart)) + { + *poffVol = offStart - pVol->offStart; + *pcbIntersect = RT_MIN(cbRange, pVol->offStart + pVol->cbVolume - offStart); + return true; + } + return false; +} + +static DECLCALLBACK(int) rtDvmFmtGptVolumeRead(RTDVMVOLUMEFMT hVolFmt, uint64_t off, void *pvBuf, size_t cbRead) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + AssertReturn(off + cbRead <= pVol->cbVolume, VERR_INVALID_PARAMETER); + + return rtDvmDiskRead(pVol->pVolMgr->pDisk, pVol->offStart + off, pvBuf, cbRead); +} + +static DECLCALLBACK(int) rtDvmFmtGptVolumeWrite(RTDVMVOLUMEFMT hVolFmt, uint64_t off, const void *pvBuf, size_t cbWrite) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + AssertReturn(off + cbWrite <= pVol->cbVolume, VERR_INVALID_PARAMETER); + + return rtDvmDiskWrite(pVol->pVolMgr->pDisk, pVol->offStart + off, pvBuf, cbWrite); +} + +RTDVMFMTOPS g_rtDvmFmtGpt = +{ + /* pszFmt */ + "GPT", + /* enmFormat, */ + RTDVMFORMATTYPE_GPT, + /* pfnProbe */ + rtDvmFmtGptProbe, + /* pfnOpen */ + rtDvmFmtGptOpen, + /* pfnInitialize */ + rtDvmFmtGptInitialize, + /* pfnClose */ + rtDvmFmtGptClose, + /* pfnQueryRangeUse */ + rtDvmFmtGptQueryRangeUse, + /* pfnGetValidVolumes */ + rtDvmFmtGptGetValidVolumes, + /* pfnGetMaxVolumes */ + rtDvmFmtGptGetMaxVolumes, + /* pfnQueryFirstVolume */ + rtDvmFmtGptQueryFirstVolume, + /* pfnQueryNextVolume */ + rtDvmFmtGptQueryNextVolume, + /* pfnVolumeClose */ + rtDvmFmtGptVolumeClose, + /* pfnVolumeGetSize */ + rtDvmFmtGptVolumeGetSize, + /* pfnVolumeQueryName */ + rtDvmFmtGptVolumeQueryName, + /* pfnVolumeGetType */ + rtDvmFmtGptVolumeGetType, + /* pfnVolumeGetFlags */ + rtDvmFmtGptVolumeGetFlags, + /* pfnVolumeIsRangeIntersecting */ + rtDvmFmtGptVolumeIsRangeIntersecting, + /* pfnVolumeRead */ + rtDvmFmtGptVolumeRead, + /* pfnVolumeWrite */ + rtDvmFmtGptVolumeWrite +}; + diff --git a/src/VBox/Runtime/common/dvm/dvmmbr.cpp b/src/VBox/Runtime/common/dvm/dvmmbr.cpp new file mode 100644 index 00000000..464223c4 --- /dev/null +++ b/src/VBox/Runtime/common/dvm/dvmmbr.cpp @@ -0,0 +1,721 @@ +/* $Id: dvmmbr.cpp $ */ +/** @file + * IPRT Disk Volume Management API (DVM) - MBR format backend. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * 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. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include <iprt/types.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/dvm.h> +#include <iprt/list.h> +#include <iprt/log.h> +#include <iprt/string.h> +#include "internal/dvm.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Checks if the partition type is an extended partition container. */ +#define RTDVMMBR_IS_EXTENDED(a_bType) ((a_bType) == 0x05 || (a_bType) == 0x0f) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to a MBR sector. */ +typedef struct RTDVMMBRSECTOR *PRTDVMMBRSECTOR; + +/** + * MBR entry. + */ +typedef struct RTDVMMBRENTRY +{ + /** Our entry in the in-use partition entry list (RTDVMMBRENTRY). */ + RTLISTNODE ListEntry; + /** Pointer to the MBR sector containing this entry. */ + PRTDVMMBRSECTOR pSector; + /** Pointer to the next sector in the extended partition table chain. */ + PRTDVMMBRSECTOR pChain; + /** The byte offset of the start of the partition (relative to disk). */ + uint64_t offPart; + /** Number of bytes for this partition. */ + uint64_t cbPart; + /** The partition/filesystem type. */ + uint8_t bType; + /** The partition flags. */ + uint8_t fFlags; + /** Bad entry. */ + bool fBad; +} RTDVMMBRENTRY; +/** Pointer to an MBR entry. */ +typedef RTDVMMBRENTRY *PRTDVMMBRENTRY; + +/** + * A MBR sector. + */ +typedef struct RTDVMMBRSECTOR +{ + /** Internal representation of the entries. */ + RTDVMMBRENTRY aEntries[4]; + /** The byte offset of this MBR sector (relative to disk). + * We keep this for detecting cycles now, but it will be needed if we start + * updating the partition table at some point. */ + uint64_t offOnDisk; + /** Pointer to the previous sector if this isn't a primary one. */ + PRTDVMMBRENTRY pPrevSector; + /** Set if this is the primary MBR, cleared if an extended. */ + bool fIsPrimary; + /** Number of used entries. */ + uint8_t cUsed; + /** Number of extended entries. */ + uint8_t cExtended; + /** The extended entry we're following (we only follow one, except when + * fIsPrimary is @c true). UINT8_MAX if none. */ + uint8_t idxExtended; + /** The raw data. */ + uint8_t abData[512]; +} RTDVMMBRSECTOR; + +/** + * MBR volume manager data. + */ +typedef struct RTDVMFMTINTERNAL +{ + /** Pointer to the underlying disk. */ + PCRTDVMDISK pDisk; + /** Head of the list of in-use RTDVMMBRENTRY structures. This excludes + * extended partition table entries. */ + RTLISTANCHOR PartitionHead; + /** The total number of partitions, not counting extended ones. */ + uint32_t cPartitions; + /** The actual primary MBR sector. */ + RTDVMMBRSECTOR Primary; +} RTDVMFMTINTERNAL; +/** Pointer to the MBR volume manager. */ +typedef RTDVMFMTINTERNAL *PRTDVMFMTINTERNAL; + +/** + * MBR volume data. + */ +typedef struct RTDVMVOLUMEFMTINTERNAL +{ + /** Pointer to the volume manager. */ + PRTDVMFMTINTERNAL pVolMgr; + /** The MBR entry. */ + PRTDVMMBRENTRY pEntry; +} RTDVMVOLUMEFMTINTERNAL; +/** Pointer to an MBR volume. */ +typedef RTDVMVOLUMEFMTINTERNAL *PRTDVMVOLUMEFMTINTERNAL; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Mapping of FS types to DVM volume types. + * + * @see https://en.wikipedia.org/wiki/Partition_type + * @see http://www.win.tue.nl/~aeb/partitions/partition_types-1.html + */ +static const struct RTDVMMBRFS2VOLTYPE +{ + /** MBR FS Id. */ + uint8_t bFsId; + /** DVM volume type. */ + RTDVMVOLTYPE enmVolType; +} g_aFs2DvmVolTypes[] = +{ + { 0x01, RTDVMVOLTYPE_FAT12 }, + { 0x04, RTDVMVOLTYPE_FAT16 }, + { 0x06, RTDVMVOLTYPE_FAT16 }, /* big FAT16 */ + { 0x07, RTDVMVOLTYPE_NTFS }, /* Simplification: Used for HPFS, exFAT, ++, too but NTFS is the more common one. */ + { 0x0b, RTDVMVOLTYPE_FAT32 }, + { 0x0c, RTDVMVOLTYPE_FAT32 }, + { 0x0e, RTDVMVOLTYPE_FAT16 }, + + /* Hidden variants of the above: */ + { 0x11, RTDVMVOLTYPE_FAT12 }, + { 0x14, RTDVMVOLTYPE_FAT16 }, + { 0x16, RTDVMVOLTYPE_FAT16 }, + { 0x17, RTDVMVOLTYPE_NTFS }, + { 0x1b, RTDVMVOLTYPE_FAT32 }, + { 0x1c, RTDVMVOLTYPE_FAT32 }, + { 0x1e, RTDVMVOLTYPE_FAT16 }, + + { 0x82, RTDVMVOLTYPE_LINUX_SWAP }, + { 0x83, RTDVMVOLTYPE_LINUX_NATIVE }, + { 0x8e, RTDVMVOLTYPE_LINUX_LVM }, + { 0xa5, RTDVMVOLTYPE_FREEBSD }, + { 0xa9, RTDVMVOLTYPE_NETBSD }, + { 0xa6, RTDVMVOLTYPE_OPENBSD }, + { 0xaf, RTDVMVOLTYPE_DARWIN_HFS }, + { 0xbf, RTDVMVOLTYPE_SOLARIS }, + { 0xfd, RTDVMVOLTYPE_LINUX_SOFTRAID } +}; + +static DECLCALLBACK(int) rtDvmFmtMbrProbe(PCRTDVMDISK pDisk, uint32_t *puScore) +{ + int rc = VINF_SUCCESS; + *puScore = RTDVM_MATCH_SCORE_UNSUPPORTED; + if (pDisk->cbDisk >= 512) + { + /* Read from the disk and check for the 0x55aa signature at the end. */ + uint8_t abMbr[512]; + rc = rtDvmDiskRead(pDisk, 0, &abMbr[0], sizeof(abMbr)); + if ( RT_SUCCESS(rc) + && abMbr[510] == 0x55 + && abMbr[511] == 0xaa) + *puScore = RTDVM_MATCH_SCORE_SUPPORTED; /* Not perfect because GPTs have a protective MBR. */ + } + + return rc; +} + + +static void rtDvmFmtMbrDestroy(PRTDVMFMTINTERNAL pThis) +{ + /* + * Delete chains of extended partitions. + */ + for (unsigned i = 0; i < 4; i++) + { + PRTDVMMBRSECTOR pCur = pThis->Primary.aEntries[i].pChain; + while (pCur) + { + PRTDVMMBRSECTOR pNext = pCur->idxExtended != UINT8_MAX ? pCur->aEntries[pCur->idxExtended].pChain : NULL; + + RT_ZERO(pCur->aEntries); + pCur->pPrevSector = NULL; + RTMemFree(pCur); + + pCur = pNext; + } + } + + /* + * Now kill this. + */ + pThis->pDisk = NULL; + RT_ZERO(pThis->Primary.aEntries); + RTMemFree(pThis); +} + + +static int rtDvmFmtMbrReadExtended(PRTDVMFMTINTERNAL pThis, PRTDVMMBRENTRY pPrimaryEntry) +{ + uint64_t const cbExt = pPrimaryEntry->cbPart; + uint64_t const offExtBegin = pPrimaryEntry->offPart; + + uint64_t offCurBegin = offExtBegin; + PRTDVMMBRENTRY pCurEntry = pPrimaryEntry; + for (unsigned cTables = 1; ; cTables++) + { + /* + * Do some sanity checking. + */ + /* Check the address of the partition table. */ + if (offCurBegin - offExtBegin >= cbExt) + { + LogRel(("rtDvmFmtMbrReadExtended: offCurBegin=%#RX64 is outside the extended partition: %#RX64..%#RX64 (LB %#RX64)\n", + offCurBegin, offExtBegin, offExtBegin + cbExt - 1, cbExt)); + pCurEntry->fBad = true; + return -VERR_OUT_OF_RANGE; + } + + /* Limit the chain length. */ + if (cTables > 64) + { + LogRel(("rtDvmFmtMbrReadExtended: offCurBegin=%#RX64 is the %uth table, we stop here.\n", offCurBegin, cTables)); + pCurEntry->fBad = true; + return -VERR_TOO_MANY_SYMLINKS; + } + + /* Check for obvious cycles. */ + for (PRTDVMMBRENTRY pPrev = pCurEntry->pSector->pPrevSector; pPrev != NULL; pPrev = pPrev->pSector->pPrevSector) + if (pPrev->offPart == offCurBegin) + { + LogRel(("rtDvmFmtMbrReadExtended: Cycle! We've seen offCurBegin=%#RX64 before\n", offCurBegin)); + pCurEntry->fBad = true; + return -VERR_TOO_MANY_SYMLINKS; + } + + /* + * Allocate a new sector entry and read the sector with the table. + */ + PRTDVMMBRSECTOR pNext = (PRTDVMMBRSECTOR)RTMemAllocZ(sizeof(*pNext)); + if (!pNext) + return VERR_NO_MEMORY; + pNext->offOnDisk = offCurBegin; + pNext->pPrevSector = pCurEntry; + //pNext->fIsPrimary = false; + //pNext->cUsed = 0; + //pNext->cExtended = 0; + pNext->idxExtended = UINT8_MAX; + + int rc = rtDvmDiskRead(pThis->pDisk, pNext->offOnDisk, &pNext->abData[0], sizeof(pNext->abData)); + if ( RT_FAILURE(rc) + || pNext->abData[510] != 0x55 + || pNext->abData[511] != 0xaa) + { + if (RT_FAILURE(rc)) + LogRel(("rtDvmFmtMbrReadExtended: Error reading extended partition table at sector %#RX64: %Rrc\n", offCurBegin, rc)); + else + LogRel(("rtDvmFmtMbrReadExtended: Extended partition table at sector %#RX64 does not have a valid DOS signature: %#x %#x\n", + offCurBegin, pNext->abData[510], pNext->abData[511])); + RTMemFree(pNext); + pCurEntry->fBad = true; + return rc; + } + pCurEntry->pChain = pNext; + + /* + * Process the table, taking down the first forward entry. + * + * As noted in the caller of this function, we only deal with one extended + * partition entry at this level since noone really ever put more than one + * here anyway. + */ + PRTDVMMBRENTRY pEntry = &pNext->aEntries[0]; + uint8_t *pbMbrEntry = &pNext->abData[446]; + for (unsigned i = 0; i < 4; i++, pEntry++, pbMbrEntry += 16) + { + uint8_t const bType = pbMbrEntry[4]; + pEntry->pSector = pNext; + RTListInit(&pEntry->ListEntry); + if (bType != 0) + { + pEntry->bType = bType; + pEntry->fFlags = pbMbrEntry[0]; + pEntry->offPart = RT_MAKE_U32_FROM_U8(pbMbrEntry[0x08], + pbMbrEntry[0x08 + 1], + pbMbrEntry[0x08 + 2], + pbMbrEntry[0x08 + 3]); + pEntry->offPart *= 512; + pEntry->cbPart = RT_MAKE_U32_FROM_U8(pbMbrEntry[0x0c], + pbMbrEntry[0x0c + 1], + pbMbrEntry[0x0c + 2], + pbMbrEntry[0x0c + 3]); + pEntry->cbPart *= 512; + if (!RTDVMMBR_IS_EXTENDED(bType)) + { + pEntry->offPart += offCurBegin; + pThis->cPartitions++; + RTListAppend(&pThis->PartitionHead, &pEntry->ListEntry); + Log2(("rtDvmFmtMbrReadExtended: %#012RX64::%u: vol%u bType=%#04x fFlags=%#04x offPart=%#012RX64 cbPart=%#012RX64\n", + offCurBegin, i, pThis->cPartitions - 1, pEntry->bType, pEntry->fFlags, pEntry->offPart, pEntry->cbPart)); + } + else + { + pEntry->offPart += offExtBegin; + pNext->cExtended++; + if (pNext->idxExtended == UINT8_MAX) + pNext->idxExtended = (uint8_t)i; + else + { + pEntry->fBad = true; + LogRel(("rtDvmFmtMbrReadExtended: Warning! Both #%u and #%u are extended partition table entries! Only following the former\n", + i, pNext->idxExtended)); + } + Log2(("rtDvmFmtMbrReadExtended: %#012RX64::%u: ext%u bType=%#04x fFlags=%#04x offPart=%#012RX64 cbPart=%#012RX64\n", + offCurBegin, i, pNext->cExtended - 1, pEntry->bType, pEntry->fFlags, pEntry->offPart, pEntry->cbPart)); + } + pNext->cUsed++; + + } + /* else: unused */ + } + + /* + * We're done if we didn't find any extended partition table entry. + * Otherwise, advance to the next one. + */ + if (!pNext->cExtended) + return VINF_SUCCESS; + pCurEntry = &pNext->aEntries[pNext->idxExtended]; + offCurBegin = pCurEntry->offPart; + } +} + + +static DECLCALLBACK(int) rtDvmFmtMbrOpen(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt) +{ + int rc; + PRTDVMFMTINTERNAL pThis = (PRTDVMFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMFMTINTERNAL)); + if (pThis) + { + pThis->pDisk = pDisk; + //pThis->cPartitions = 0; + RTListInit(&pThis->PartitionHead); + //pThis->Primary.offOnDisk = 0; + //pThis->Primary.pPrevSector = NULL; + pThis->Primary.fIsPrimary = true; + //pThis->Primary.cUsed = 0; + //pThis->Primary.cExtended = 0; + pThis->Primary.idxExtended = UINT8_MAX; + + /* + * Read the primary MBR. + */ + rc = rtDvmDiskRead(pDisk, 0, &pThis->Primary.abData[0], sizeof(pThis->Primary.abData)); + if (RT_SUCCESS(rc)) + { + Assert(pThis->Primary.abData[510] == 0x55 && pThis->Primary.abData[511] == 0xaa); + + /* + * Setup basic data for the 4 entries. + */ + PRTDVMMBRENTRY pEntry = &pThis->Primary.aEntries[0]; + uint8_t *pbMbrEntry = &pThis->Primary.abData[446]; + for (unsigned i = 0; i < 4; i++, pEntry++, pbMbrEntry += 16) + { + pEntry->pSector = &pThis->Primary; + RTListInit(&pEntry->ListEntry); + + uint8_t const bType = pbMbrEntry[4]; + if (bType != 0) + { + pEntry->offPart = RT_MAKE_U32_FROM_U8(pbMbrEntry[0x08 + 0], + pbMbrEntry[0x08 + 1], + pbMbrEntry[0x08 + 2], + pbMbrEntry[0x08 + 3]); + pEntry->offPart *= 512; + pEntry->cbPart = RT_MAKE_U32_FROM_U8(pbMbrEntry[0x0c + 0], + pbMbrEntry[0x0c + 1], + pbMbrEntry[0x0c + 2], + pbMbrEntry[0x0c + 3]); + pEntry->cbPart *= 512; + pEntry->bType = bType; + pEntry->fFlags = pbMbrEntry[0]; + if (!RTDVMMBR_IS_EXTENDED(bType)) + { + pThis->cPartitions++; + RTListAppend(&pThis->PartitionHead, &pEntry->ListEntry); + Log2(("rtDvmFmtMbrOpen: %u: vol%u bType=%#04x fFlags=%#04x offPart=%#012RX64 cbPart=%#012RX64\n", + i, pThis->cPartitions - 1, pEntry->bType, pEntry->fFlags, pEntry->offPart, pEntry->cbPart)); + } + else + { + pThis->Primary.cExtended++; + Log2(("rtDvmFmtMbrOpen: %u: ext%u bType=%#04x fFlags=%#04x offPart=%#012RX64 cbPart=%#012RX64\n", + i, pThis->Primary.cExtended - 1, pEntry->bType, pEntry->fFlags, pEntry->offPart, pEntry->cbPart)); + } + pThis->Primary.cUsed++; + } + /* else: unused */ + } + + /* + * Now read any extended partitions. Since it's no big deal for us, we allow + * the primary partition table to have more than one extended partition. However + * in the extended tables we only allow a single forward link to avoid having to + * deal with recursion. + */ + if (pThis->Primary.cExtended > 0) + for (unsigned i = 0; i < 4; i++) + if (RTDVMMBR_IS_EXTENDED(pThis->Primary.aEntries[i].bType)) + { + if (pThis->Primary.idxExtended == UINT8_MAX) + pThis->Primary.idxExtended = (uint8_t)i; + rc = rtDvmFmtMbrReadExtended(pThis, &pThis->Primary.aEntries[i]); + if (RT_FAILURE(rc)) + break; + } + if (RT_SUCCESS(rc)) + { + *phVolMgrFmt = pThis; + return rc; + } + + } + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +static DECLCALLBACK(int) rtDvmFmtMbrInitialize(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt) +{ + int rc; + PRTDVMFMTINTERNAL pThis = (PRTDVMFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMFMTINTERNAL)); + if (pThis) + { + pThis->pDisk = pDisk; + //pThis->cPartitions = 0; + RTListInit(&pThis->PartitionHead); + //pThis->Primary.offOnDisk = 0 + //pThis->Primary.pPrevSector = NULL; + pThis->Primary.fIsPrimary = true; + //pThis->Primary.cUsed = 0; + //pThis->Primary.cExtended = 0; + pThis->Primary.idxExtended = UINT8_MAX; + + /* Setup a new MBR and write it to the disk. */ + pThis->Primary.abData[510] = 0x55; + pThis->Primary.abData[511] = 0xaa; + rc = rtDvmDiskWrite(pDisk, 0, &pThis->Primary.abData[0], sizeof(pThis->Primary.abData)); + if (RT_SUCCESS(rc)) + { + pThis->pDisk = pDisk; + *phVolMgrFmt = pThis; + } + else + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +static DECLCALLBACK(void) rtDvmFmtMbrClose(RTDVMFMT hVolMgrFmt) +{ + rtDvmFmtMbrDestroy(hVolMgrFmt); +} + +static DECLCALLBACK(int) rtDvmFmtMbrQueryRangeUse(RTDVMFMT hVolMgrFmt, uint64_t off, uint64_t cbRange, bool *pfUsed) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + + /* + * The MBR definitely uses the first 512 bytes, but we consider anything up + * to 1MB of alignment padding / cylinder gap to be considered in use too. + * + * The cylinder gap has been used by several boot managers and boot loaders + * to store code and data. + */ + if (off < (uint64_t)_1M) + { + *pfUsed = true; + return VINF_SUCCESS; + } + + /* Ditto for any extended partition tables. */ + for (uint32_t iPrimary = 0; iPrimary < 4; iPrimary++) + { + PRTDVMMBRSECTOR pCur = pThis->Primary.aEntries[iPrimary].pChain; + while (pCur) + { + if ( off < pCur->offOnDisk + _1M + && off + cbRange > pCur->offOnDisk) + { + *pfUsed = true; + return VINF_SUCCESS; + } + + + if (pCur->idxExtended == UINT8_MAX) + break; + pCur = pCur->aEntries[pCur->idxExtended].pChain; + } + + } + + /* Not in use. */ + *pfUsed = false; + return VINF_SUCCESS; +} + +static DECLCALLBACK(uint32_t) rtDvmFmtMbrGetValidVolumes(RTDVMFMT hVolMgrFmt) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + + return pThis->cPartitions; +} + +static DECLCALLBACK(uint32_t) rtDvmFmtMbrGetMaxVolumes(RTDVMFMT hVolMgrFmt) +{ + NOREF(hVolMgrFmt); + return 4; /** @todo Add support for EBR? */ +} + +/** + * Creates a new volume. + * + * @returns IPRT status code. + * @param pThis The MBR volume manager data. + * @param pEntry The MBR entry to create a volume handle for. + * @param phVolFmt Where to store the volume data on success. + */ +static int rtDvmFmtMbrVolumeCreate(PRTDVMFMTINTERNAL pThis, PRTDVMMBRENTRY pEntry, PRTDVMVOLUMEFMT phVolFmt) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = (PRTDVMVOLUMEFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMVOLUMEFMTINTERNAL)); + if (pVol) + { + pVol->pVolMgr = pThis; + pVol->pEntry = pEntry; + *phVolFmt = pVol; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + +static DECLCALLBACK(int) rtDvmFmtMbrQueryFirstVolume(RTDVMFMT hVolMgrFmt, PRTDVMVOLUMEFMT phVolFmt) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + if (pThis->cPartitions != 0) + return rtDvmFmtMbrVolumeCreate(pThis, RTListGetFirst(&pThis->PartitionHead, RTDVMMBRENTRY, ListEntry), phVolFmt); + return VERR_DVM_MAP_EMPTY; +} + +static DECLCALLBACK(int) rtDvmFmtMbrQueryNextVolume(RTDVMFMT hVolMgrFmt, RTDVMVOLUMEFMT hVolFmt, PRTDVMVOLUMEFMT phVolFmtNext) +{ + PRTDVMFMTINTERNAL pThis = hVolMgrFmt; + PRTDVMVOLUMEFMTINTERNAL pCurVol = hVolFmt; + if (pCurVol) + { + PRTDVMMBRENTRY pNextEntry = RTListGetNext(&pThis->PartitionHead, pCurVol->pEntry, RTDVMMBRENTRY, ListEntry); + if (pNextEntry) + return rtDvmFmtMbrVolumeCreate(pThis, pNextEntry, phVolFmtNext); + return VERR_DVM_MAP_NO_VOLUME; + } + if (pThis->cPartitions != 0) + return rtDvmFmtMbrVolumeCreate(pThis, RTListGetFirst(&pThis->PartitionHead, RTDVMMBRENTRY, ListEntry), phVolFmtNext); + return VERR_DVM_MAP_EMPTY; +} + +static DECLCALLBACK(void) rtDvmFmtMbrVolumeClose(RTDVMVOLUMEFMT hVolFmt) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + pVol->pVolMgr = NULL; + pVol->pEntry = NULL; + + RTMemFree(pVol); +} + +static DECLCALLBACK(uint64_t) rtDvmFmtMbrVolumeGetSize(RTDVMVOLUMEFMT hVolFmt) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + return pVol->pEntry->cbPart; +} + +static DECLCALLBACK(int) rtDvmFmtMbrVolumeQueryName(RTDVMVOLUMEFMT hVolFmt, char **ppszVolName) +{ + NOREF(hVolFmt); NOREF(ppszVolName); + return VERR_NOT_SUPPORTED; +} + +static DECLCALLBACK(RTDVMVOLTYPE) rtDvmFmtMbrVolumeGetType(RTDVMVOLUMEFMT hVolFmt) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + uint8_t const bType = pVol->pEntry->bType; + for (unsigned i = 0; i < RT_ELEMENTS(g_aFs2DvmVolTypes); i++) + if (g_aFs2DvmVolTypes[i].bFsId == bType) + return g_aFs2DvmVolTypes[i].enmVolType; + + return RTDVMVOLTYPE_UNKNOWN; +} + +static DECLCALLBACK(uint64_t) rtDvmFmtMbrVolumeGetFlags(RTDVMVOLUMEFMT hVolFmt) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + uint64_t fFlags = 0; + if (pVol->pEntry->bType & 0x80) + fFlags |= DVMVOLUME_FLAGS_BOOTABLE | DVMVOLUME_FLAGS_ACTIVE; + + return fFlags; +} + +static DECLCALLBACK(bool) rtDvmFmtMbrVolumeIsRangeIntersecting(RTDVMVOLUMEFMT hVolFmt, uint64_t offStart, size_t cbRange, + uint64_t *poffVol, uint64_t *pcbIntersect) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + + if (RTDVM_RANGE_IS_INTERSECTING(pVol->pEntry->offPart, pVol->pEntry->cbPart, offStart)) + { + *poffVol = offStart - pVol->pEntry->offPart; + *pcbIntersect = RT_MIN(cbRange, pVol->pEntry->offPart + pVol->pEntry->cbPart - offStart); + return true; + } + return false; +} + +static DECLCALLBACK(int) rtDvmFmtMbrVolumeRead(RTDVMVOLUMEFMT hVolFmt, uint64_t off, void *pvBuf, size_t cbRead) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + AssertReturn(off + cbRead <= pVol->pEntry->cbPart, VERR_INVALID_PARAMETER); + + return rtDvmDiskRead(pVol->pVolMgr->pDisk, pVol->pEntry->offPart + off, pvBuf, cbRead); +} + +static DECLCALLBACK(int) rtDvmFmtMbrVolumeWrite(RTDVMVOLUMEFMT hVolFmt, uint64_t off, const void *pvBuf, size_t cbWrite) +{ + PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt; + AssertReturn(off + cbWrite <= pVol->pEntry->cbPart, VERR_INVALID_PARAMETER); + + return rtDvmDiskWrite(pVol->pVolMgr->pDisk, pVol->pEntry->offPart + off, pvBuf, cbWrite); +} + +RTDVMFMTOPS g_rtDvmFmtMbr = +{ + /* pszFmt */ + "MBR", + /* enmFormat */ + RTDVMFORMATTYPE_MBR, + /* pfnProbe */ + rtDvmFmtMbrProbe, + /* pfnOpen */ + rtDvmFmtMbrOpen, + /* pfnInitialize */ + rtDvmFmtMbrInitialize, + /* pfnClose */ + rtDvmFmtMbrClose, + /* pfnQueryRangeUse */ + rtDvmFmtMbrQueryRangeUse, + /* pfnGetValidVolumes */ + rtDvmFmtMbrGetValidVolumes, + /* pfnGetMaxVolumes */ + rtDvmFmtMbrGetMaxVolumes, + /* pfnQueryFirstVolume */ + rtDvmFmtMbrQueryFirstVolume, + /* pfnQueryNextVolume */ + rtDvmFmtMbrQueryNextVolume, + /* pfnVolumeClose */ + rtDvmFmtMbrVolumeClose, + /* pfnVolumeGetSize */ + rtDvmFmtMbrVolumeGetSize, + /* pfnVolumeQueryName */ + rtDvmFmtMbrVolumeQueryName, + /* pfnVolumeGetType */ + rtDvmFmtMbrVolumeGetType, + /* pfnVolumeGetFlags */ + rtDvmFmtMbrVolumeGetFlags, + /* pfnVOlumeIsRangeIntersecting */ + rtDvmFmtMbrVolumeIsRangeIntersecting, + /* pfnVolumeRead */ + rtDvmFmtMbrVolumeRead, + /* pfnVolumeWrite */ + rtDvmFmtMbrVolumeWrite +}; + diff --git a/src/VBox/Runtime/common/dvm/dvmvfs.cpp b/src/VBox/Runtime/common/dvm/dvmvfs.cpp new file mode 100644 index 00000000..27be5373 --- /dev/null +++ b/src/VBox/Runtime/common/dvm/dvmvfs.cpp @@ -0,0 +1,1453 @@ +/* $Id: dvmvfs.cpp $ */ +/** @file + * IPRT Disk Volume Management API (DVM) - VFS glue. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * 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. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS /** @todo fix log group */ +#include <iprt/types.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/dvm.h> +#include <iprt/err.h> +#include <iprt/asm.h> +#include <iprt/string.h> +#include <iprt/file.h> +#include <iprt/sg.h> +#include <iprt/vfslowlevel.h> +#include <iprt/poll.h> +#include <iprt/log.h> +#include "internal/dvm.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to a volume manager VFS. */ +typedef struct RTDVMVFSVOL *PRTDVMVFSVOL; + +/** + * The internal data of a DVM volume I/O stream. + */ +typedef struct RTVFSDVMFILE +{ + /** The volume the VFS file belongs to. */ + RTDVMVOLUME hVol; + /** Pointer to the VFS volume. Can be NULL. */ + PRTDVMVFSVOL pVfsVol; + /** Current position. */ + uint64_t offCurPos; + /** Set if readable. */ + bool fCanRead; + /** Set if writable. */ + bool fCanWrite; +} RTVFSDVMFILE; +/** Pointer to a the internal data of a DVM volume file. */ +typedef RTVFSDVMFILE *PRTVFSDVMFILE; + +/** + * The internal data of a DVM volume symlink. + */ +typedef struct RTVFSDVMSYMLINK +{ + /** The DVM volume the symlink represent. */ + RTDVMVOLUME hVol; + /** The DVM volume manager @a hVol belongs to. */ + RTDVM hVolMgr; + /** The symlink name. */ + char *pszSymlink; + /** The symlink target (volXX). */ + char szTarget[16]; +} RTVFSDVMSYMLINK; +/** Pointer to a the internal data of a DVM volume file. */ +typedef RTVFSDVMSYMLINK *PRTVFSDVMSYMLINK; + +/** + * The volume manager VFS (root) dir data. + */ +typedef struct RTDVMVFSDIR +{ + /** Pointer to the VFS volume. */ + PRTDVMVFSVOL pVfsVol; + /** The current directory offset. */ + uint32_t offDir; + /** Set if we need to try return hCurVolume again because of buffer overflow. */ + bool fReturnCurrent; + /** Pointer to name alias string (returned by RTDvmVolumeQueryName, free it). */ + char *pszNameAlias; + /** The current DVM volume. */ + RTDVMVOLUME hCurVolume; +} RTDVMVFSDIR; +/** Pointer to a volume manager VFS (root) dir. */ +typedef RTDVMVFSDIR *PRTDVMVFSDIR; + +/** + * A volume manager VFS for use in chains (thing pseudo/devfs). + */ +typedef struct RTDVMVFSVOL +{ + /** The volume manager. */ + RTDVM hVolMgr; + /** Whether to close it on success. */ + bool fCloseDvm; + /** Whether the access is read-only. */ + bool fReadOnly; + /** Number of volumes. */ + uint32_t cVolumes; + /** Self reference. */ + RTVFS hVfsSelf; +} RTDVMVFSVOL; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) rtDvmVfsVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir); + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnClose} + */ +static DECLCALLBACK(int) rtDvmVfsFile_Close(void *pvThis) +{ + PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis; + + RTDvmVolumeRelease(pThis->hVol); + return VINF_SUCCESS; +} + + +/** + * Worker for rtDvmVfsFile_QueryInfoWorker and rtDvmVfsSym_QueryInfoWorker. + */ +static int rtDvmVfsFileSym_QueryAddAttrWorker(RTDVMVOLUME hVolume, RTDVM hVolMgr, + PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + switch (enmAddAttr) + { + case RTFSOBJATTRADD_NOTHING: + case RTFSOBJATTRADD_UNIX: + pObjInfo->Attr.u.Unix.uid = (RTUID)RTDvmVolumeGetType(hVolume); + pObjInfo->Attr.u.Unix.gid = hVolMgr != NIL_RTDVM ? (RTGID)RTDvmMapGetFormatType(hVolMgr) : NIL_RTGID; + pObjInfo->Attr.u.Unix.cHardlinks = 1; + pObjInfo->Attr.u.Unix.INodeIdDevice = 0; + pObjInfo->Attr.u.Unix.INodeId = 0; + pObjInfo->Attr.u.Unix.fFlags = 0; + pObjInfo->Attr.u.Unix.GenerationId = 0; + pObjInfo->Attr.u.Unix.Device = 0; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + { + RTDVMVOLTYPE enmType = RTDvmVolumeGetType(hVolume); + pObjInfo->Attr.u.UnixOwner.uid = (RTUID)enmType; + RTStrCopy(pObjInfo->Attr.u.UnixOwner.szName, sizeof(pObjInfo->Attr.u.UnixOwner.szName), + RTDvmVolumeTypeGetDescr(enmType)); + break; + } + + case RTFSOBJATTRADD_UNIX_GROUP: + if (hVolMgr != NIL_RTDVM) + { + pObjInfo->Attr.u.UnixGroup.gid = (RTGID)RTDvmMapGetFormatType(hVolMgr); + RTStrCopy(pObjInfo->Attr.u.UnixGroup.szName, sizeof(pObjInfo->Attr.u.UnixGroup.szName), + RTDvmMapGetFormatName(hVolMgr)); + } + else + { + pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID; + pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; + } + break; + + case RTFSOBJATTRADD_EASIZE: + pObjInfo->Attr.u.EASize.cb = 0; + break; + + default: + return VERR_INVALID_PARAMETER; + } + return VINF_SUCCESS; +} + + +/** + * Worker for rtDvmVfsFile_QueryInfo, rtDvmVfsDir_QueryEntryInfo, and + * rtDvmVfsDir_ReadDir. + */ +static int rtDvmVfsFile_QueryInfoWorker(RTDVMVOLUME hVolume, RTDVM hVolMgr, bool fReadOnly, + PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + + pObjInfo->cbObject = RTDvmVolumeGetSize(hVolume); + pObjInfo->cbAllocated = pObjInfo->cbObject; + RTTimeSpecSetNano(&pObjInfo->AccessTime, 0); + RTTimeSpecSetNano(&pObjInfo->ModificationTime, 0); + RTTimeSpecSetNano(&pObjInfo->ChangeTime, 0); + RTTimeSpecSetNano(&pObjInfo->BirthTime, 0); + pObjInfo->Attr.fMode = RTFS_TYPE_FILE | RTFS_DOS_NT_NORMAL; + if (fReadOnly) + pObjInfo->Attr.fMode |= RTFS_DOS_READONLY | 0444; + else + pObjInfo->Attr.fMode |= 0666; + + return rtDvmVfsFileSym_QueryAddAttrWorker(hVolume, hVolMgr, pObjInfo, enmAddAttr); +} + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} + */ +static DECLCALLBACK(int) rtDvmVfsFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis; + return rtDvmVfsFile_QueryInfoWorker(pThis->hVol, + pThis->pVfsVol ? pThis->pVfsVol->hVolMgr : NIL_RTDVM, + pThis->pVfsVol ? pThis->pVfsVol->fReadOnly : !pThis->fCanWrite, + pObjInfo, enmAddAttr); +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} + */ +static DECLCALLBACK(int) rtDvmVfsFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) +{ + PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis; + int rc = VINF_SUCCESS; + + Assert(pSgBuf->cSegs == 1); + NOREF(fBlocking); + + /* + * Find the current position and check if it's within the volume. + */ + uint64_t offUnsigned = off < 0 ? pThis->offCurPos : (uint64_t)off; + if (offUnsigned >= RTDvmVolumeGetSize(pThis->hVol)) + { + if (pcbRead) + { + *pcbRead = 0; + pThis->offCurPos = offUnsigned; + return VINF_EOF; + } + return VERR_EOF; + } + + size_t cbLeftToRead; + if (offUnsigned + pSgBuf->paSegs[0].cbSeg > RTDvmVolumeGetSize(pThis->hVol)) + { + if (!pcbRead) + return VERR_EOF; + *pcbRead = cbLeftToRead = (size_t)(RTDvmVolumeGetSize(pThis->hVol) - offUnsigned); + } + else + { + cbLeftToRead = pSgBuf->paSegs[0].cbSeg; + if (pcbRead) + *pcbRead = cbLeftToRead; + } + + /* + * Ok, we've got a valid stretch within the file. Do the reading. + */ + if (cbLeftToRead > 0) + { + rc = RTDvmVolumeRead(pThis->hVol, offUnsigned, pSgBuf->paSegs[0].pvSeg, cbLeftToRead); + if (RT_SUCCESS(rc)) + offUnsigned += cbLeftToRead; + } + + pThis->offCurPos = offUnsigned; + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite} + */ +static DECLCALLBACK(int) rtDvmVfsFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) +{ + PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis; + int rc = VINF_SUCCESS; + + Assert(pSgBuf->cSegs == 1); + NOREF(fBlocking); + + /* + * Find the current position and check if it's within the volume. + * Writing beyond the end of a volume is not supported. + */ + uint64_t offUnsigned = off < 0 ? pThis->offCurPos : (uint64_t)off; + if (offUnsigned >= RTDvmVolumeGetSize(pThis->hVol)) + { + if (pcbWritten) + { + *pcbWritten = 0; + pThis->offCurPos = offUnsigned; + } + return VERR_NOT_SUPPORTED; + } + + size_t cbLeftToWrite; + if (offUnsigned + pSgBuf->paSegs[0].cbSeg > RTDvmVolumeGetSize(pThis->hVol)) + { + if (!pcbWritten) + return VERR_EOF; + *pcbWritten = cbLeftToWrite = (size_t)(RTDvmVolumeGetSize(pThis->hVol) - offUnsigned); + } + else + { + cbLeftToWrite = pSgBuf->paSegs[0].cbSeg; + if (pcbWritten) + *pcbWritten = cbLeftToWrite; + } + + /* + * Ok, we've got a valid stretch within the file. Do the reading. + */ + if (cbLeftToWrite > 0) + { + rc = RTDvmVolumeWrite(pThis->hVol, offUnsigned, pSgBuf->paSegs[0].pvSeg, cbLeftToWrite); + if (RT_SUCCESS(rc)) + offUnsigned += cbLeftToWrite; + } + + pThis->offCurPos = offUnsigned; + return rc; +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush} + */ +static DECLCALLBACK(int) rtDvmVfsFile_Flush(void *pvThis) +{ + NOREF(pvThis); + return VINF_SUCCESS; /** @todo Implement missing DVM API. */ +} + + +/** + * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell} + */ +static DECLCALLBACK(int) rtDvmVfsFile_Tell(void *pvThis, PRTFOFF poffActual) +{ + PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis; + *poffActual = pThis->offCurPos; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} + */ +static DECLCALLBACK(int) rtDvmVfsFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) +{ + NOREF(pvThis); + NOREF(fMode); + NOREF(fMask); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} + */ +static DECLCALLBACK(int) rtDvmVfsFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + NOREF(pvThis); + NOREF(pAccessTime); + NOREF(pModificationTime); + NOREF(pChangeTime); + NOREF(pBirthTime); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} + */ +static DECLCALLBACK(int) rtDvmVfsFile_SetOwner(void *pvThis, RTUID uid, RTGID gid) +{ + NOREF(pvThis); + NOREF(uid); + NOREF(gid); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{RTVFSFILEOPS,pfnSeek} + */ +static DECLCALLBACK(int) rtDvmVfsFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual) +{ + PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis; + + /* + * Seek relative to which position. + */ + uint64_t offWrt; + switch (uMethod) + { + case RTFILE_SEEK_BEGIN: + offWrt = 0; + break; + + case RTFILE_SEEK_CURRENT: + offWrt = pThis->offCurPos; + break; + + case RTFILE_SEEK_END: + offWrt = RTDvmVolumeGetSize(pThis->hVol); + break; + + default: + return VERR_INTERNAL_ERROR_5; + } + + /* + * Calc new position, take care to stay within bounds. + * + * @todo: Setting position beyond the end of the volume does not make sense. + */ + uint64_t offNew; + if (offSeek == 0) + offNew = offWrt; + else if (offSeek > 0) + { + offNew = offWrt + offSeek; + if ( offNew < offWrt + || offNew > RTFOFF_MAX) + offNew = RTFOFF_MAX; + } + else if ((uint64_t)-offSeek < offWrt) + offNew = offWrt + offSeek; + else + offNew = 0; + + /* + * Update the state and set return value. + */ + pThis->offCurPos = offNew; + + *poffActual = offNew; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize} + */ +static DECLCALLBACK(int) rtDvmVfsFile_QuerySize(void *pvThis, uint64_t *pcbFile) +{ + PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis; + *pcbFile = RTDvmVolumeGetSize(pThis->hVol); + return VINF_SUCCESS; +} + + +/** + * Standard file operations. + */ +DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtDvmVfsStdFileOps = +{ + { /* Stream */ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_FILE, + "DvmFile", + rtDvmVfsFile_Close, + rtDvmVfsFile_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSIOSTREAMOPS_VERSION, + RTVFSIOSTREAMOPS_FEAT_NO_SG, + rtDvmVfsFile_Read, + rtDvmVfsFile_Write, + rtDvmVfsFile_Flush, + NULL /*pfnPollOne*/, + rtDvmVfsFile_Tell, + NULL /*Skip*/, + NULL /*ZeroFill*/, + RTVFSIOSTREAMOPS_VERSION, + }, + RTVFSFILEOPS_VERSION, + /*RTVFSIOFILEOPS_FEAT_NO_AT_OFFSET*/ 0, + { /* ObjSet */ + RTVFSOBJSETOPS_VERSION, + RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj), + rtDvmVfsFile_SetMode, + rtDvmVfsFile_SetTimes, + rtDvmVfsFile_SetOwner, + RTVFSOBJSETOPS_VERSION + }, + rtDvmVfsFile_Seek, + rtDvmVfsFile_QuerySize, + NULL /*SetSize*/, + NULL /*QueryMaxSize*/, + RTVFSFILEOPS_VERSION +}; + + +/** + * Internal worker for RTDvmVolumeCreateVfsFile and rtDvmVfsDir_OpenFile. + * + * @returns IPRT status code. + * @param pVfsVol The VFS volume, optional. + * @param hVol The volume handle. (Reference not consumed.) + * @param fOpen RTFILE_O_XXX (valid). + * @param phVfsFileOut Where to return the handle to the file. + */ +static int rtDvmVfsCreateFileForVolume(PRTDVMVFSVOL pVfsVol, RTDVMVOLUME hVol, uint64_t fOpen, PRTVFSFILE phVfsFileOut) +{ + uint32_t cRefs = RTDvmVolumeRetain(hVol); + AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); + + /* + * Create the volume file. + */ + RTVFSFILE hVfsFile; + PRTVFSDVMFILE pThis; + int rc = RTVfsNewFile(&g_rtDvmVfsStdFileOps, sizeof(*pThis), fOpen, NIL_RTVFS, NIL_RTVFSLOCK, &hVfsFile, (void **)&pThis); + if (RT_SUCCESS(rc)) + { + pThis->offCurPos = 0; + pThis->hVol = hVol; + pThis->fCanRead = RT_BOOL(fOpen & RTFILE_O_READ); + pThis->fCanWrite = RT_BOOL(fOpen & RTFILE_O_WRITE); + pThis->pVfsVol = pVfsVol; + + *phVfsFileOut = hVfsFile; + return VINF_SUCCESS; + } + + RTDvmVolumeRelease(hVol); + return rc; +} + + +RTDECL(int) RTDvmVolumeCreateVfsFile(RTDVMVOLUME hVol, uint64_t fOpen, PRTVFSFILE phVfsFileOut) +{ + AssertPtrReturn(hVol, VERR_INVALID_HANDLE); + AssertPtrReturn(phVfsFileOut, VERR_INVALID_POINTER); + AssertReturn(fOpen & RTFILE_O_ACCESS_MASK, VERR_INVALID_FLAGS); + AssertReturn(!(fOpen & ~RTFILE_O_VALID_MASK), VERR_INVALID_FLAGS); + return rtDvmVfsCreateFileForVolume(NULL, hVol, fOpen, phVfsFileOut); +} + + +/********************************************************************************************************************************* +* DVM Symbolic Link Objects * +*********************************************************************************************************************************/ +/** + * @interface_method_impl{RTVFSOBJOPS,pfnClose} + */ +static DECLCALLBACK(int) rtDvmVfsSym_Close(void *pvThis) +{ + PRTVFSDVMSYMLINK pThis = (PRTVFSDVMSYMLINK)pvThis; + if (pThis->pszSymlink) + { + RTStrFree(pThis->pszSymlink); + pThis->pszSymlink = NULL; + } + if (pThis->hVol != NIL_RTDVMVOLUME) + { + RTDvmVolumeRelease(pThis->hVol); + pThis->hVol = NIL_RTDVMVOLUME; + } + if (pThis->hVolMgr != NIL_RTDVM) + { + RTDvmRelease(pThis->hVolMgr); + pThis->hVolMgr = NIL_RTDVM; + } + return VINF_SUCCESS; +} + + +/** + * Worker for rtDvmVfsSym_QueryInfo and rtDvmVfsDir_Read. + * + * @returns IPRT status code. + * @param hVolume The volume handle. + * @param hVolMgr The volume manager handle. Optional. + * @param pszTarget The link target. + * @param pObjInfo The object info structure to populate. + * @param enmAddAttr The additional attributes to supply. + */ +static int rtDvmVfsSym_QueryInfoWorker(RTDVMVOLUME hVolume, RTDVM hVolMgr, const char *pszTarget, + PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RT_ZERO(*pObjInfo); + pObjInfo->cbObject = pObjInfo->cbAllocated = pszTarget ? strlen(pszTarget) : 0; + pObjInfo->Attr.fMode = 0777 | RTFS_TYPE_SYMLINK | RTFS_DOS_NT_REPARSE_POINT; + + return rtDvmVfsFileSym_QueryAddAttrWorker(hVolume, hVolMgr, pObjInfo, enmAddAttr); +} + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} + */ +static DECLCALLBACK(int) rtDvmVfsSym_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + PRTVFSDVMSYMLINK pThis = (PRTVFSDVMSYMLINK)pvThis; + return rtDvmVfsSym_QueryInfoWorker(pThis->hVol, pThis->hVolMgr, pThis->szTarget, pObjInfo, enmAddAttr); +} + + +/** + * @interface_method_impl{RTVFSSYMLINKOPS,pfnRead} + */ +static DECLCALLBACK(int) rtDvmVfsSym_Read(void *pvThis, char *pszTarget, size_t cbTarget) +{ + PRTVFSDVMSYMLINK pThis = (PRTVFSDVMSYMLINK)pvThis; + return RTStrCopy(pszTarget, cbTarget, pThis->szTarget); +} + + +/** + * DVM symbolic link operations. + */ +static const RTVFSSYMLINKOPS g_rtDvmVfsSymOps = +{ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_SYMLINK, + "DvmSymlink", + rtDvmVfsSym_Close, + rtDvmVfsSym_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSSYMLINKOPS_VERSION, + 0, + { /* ObjSet */ + RTVFSOBJSETOPS_VERSION, + RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj), + NULL /*rtDvmVfsSym_SetMode*/, + NULL /*rtDvmVfsSym_SetTimes*/, + NULL /*rtDvmVfsSym_SetOwner*/, + RTVFSOBJSETOPS_VERSION + }, + rtDvmVfsSym_Read, + RTVFSSYMLINKOPS_VERSION +}; + + +/** + * Internal worker for rtDvmVfsDir_OpenFile. + * + * @returns IPRT status code. + * @param hVol The volume handle (not consumed). + * @param hVolMgr The volume manager handle (not consumed). + * @param iVol The volume number. + * @param pszSymlink The volume name. Consumed on success. + * @param phVfsSymlinkOut Where to return the handle to the file. + */ +static int rtDvmVfsCreateSymlinkForVolume(RTDVMVOLUME hVol, RTDVM hVolMgr, uint32_t iVol, char *pszSymlink, + PRTVFSSYMLINK phVfsSymlinkOut) +{ + uint32_t cRefs = RTDvmVolumeRetain(hVol); + AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); + + cRefs = RTDvmRetain(hVolMgr); + AssertReturnStmt(cRefs != UINT32_MAX, RTDvmVolumeRelease(hVol), VERR_INVALID_HANDLE); + + /* + * Create the symlink. + */ + RTVFSSYMLINK hVfsSym; + PRTVFSDVMSYMLINK pThis; + int rc = RTVfsNewSymlink(&g_rtDvmVfsSymOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, &hVfsSym, (void **)&pThis); + if (RT_SUCCESS(rc)) + { + pThis->hVol = hVol; + pThis->hVolMgr = hVolMgr; + pThis->pszSymlink = pszSymlink; + RTStrPrintf(pThis->szTarget, sizeof(pThis->szTarget), "vol%u", iVol); + + *phVfsSymlinkOut = hVfsSym; + return VINF_SUCCESS; + } + RTDvmRelease(hVolMgr); + RTDvmVolumeRelease(hVol); + return rc; +} + + + +/********************************************************************************************************************************* +* DVM Directory Objects * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnClose} + */ +static DECLCALLBACK(int) rtDvmVfsDir_Close(void *pvThis) +{ + PRTDVMVFSDIR pThis = (PRTDVMVFSDIR)pvThis; + + if (pThis->hCurVolume != NIL_RTDVMVOLUME) + { + RTDvmVolumeRelease(pThis->hCurVolume); + pThis->hCurVolume = NIL_RTDVMVOLUME; + } + + if (pThis->pszNameAlias) + { + RTStrFree(pThis->pszNameAlias); + pThis->pszNameAlias = NULL; + } + + pThis->pVfsVol = NULL; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} + */ +static DECLCALLBACK(int) rtDvmVfsDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + PRTDVMVFSDIR pThis = (PRTDVMVFSDIR)pvThis; + pObjInfo->cbObject = pThis->pVfsVol->cVolumes; + pObjInfo->cbAllocated = pThis->pVfsVol->cVolumes; + RTTimeSpecSetNano(&pObjInfo->AccessTime, 0); + RTTimeSpecSetNano(&pObjInfo->ModificationTime, 0); + RTTimeSpecSetNano(&pObjInfo->ChangeTime, 0); + RTTimeSpecSetNano(&pObjInfo->BirthTime, 0); + pObjInfo->Attr.fMode = RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY; + if (pThis->pVfsVol->fReadOnly) + pObjInfo->Attr.fMode |= RTFS_DOS_READONLY | 0555; + else + pObjInfo->Attr.fMode |= 0777; + + switch (enmAddAttr) + { + case RTFSOBJATTRADD_NOTHING: + case RTFSOBJATTRADD_UNIX: + pObjInfo->Attr.u.Unix.uid = NIL_RTUID; + pObjInfo->Attr.u.Unix.gid = (RTGID)RTDvmMapGetFormatType(pThis->pVfsVol->hVolMgr); + pObjInfo->Attr.u.Unix.cHardlinks = pThis->pVfsVol->cVolumes; + pObjInfo->Attr.u.Unix.INodeIdDevice = 0; + pObjInfo->Attr.u.Unix.INodeId = 0; + pObjInfo->Attr.u.Unix.fFlags = 0; + pObjInfo->Attr.u.Unix.GenerationId = 0; + pObjInfo->Attr.u.Unix.Device = 0; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID; + pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + pObjInfo->Attr.u.UnixGroup.gid = (RTGID)RTDvmMapGetFormatType(pThis->pVfsVol->hVolMgr); + RTStrCopy(pObjInfo->Attr.u.UnixGroup.szName, sizeof(pObjInfo->Attr.u.UnixGroup.szName), + RTDvmMapGetFormatName(pThis->pVfsVol->hVolMgr)); + break; + + case RTFSOBJATTRADD_EASIZE: + pObjInfo->Attr.u.EASize.cb = 0; + break; + + default: + return VERR_INVALID_PARAMETER; + } + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} + */ +static DECLCALLBACK(int) rtDvmVfsDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) +{ + NOREF(pvThis); NOREF(fMode); NOREF(fMask); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} + */ +static DECLCALLBACK(int) rtDvmVfsDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) +{ + NOREF(pvThis); NOREF(pAccessTime); NOREF(pModificationTime); NOREF(pChangeTime); NOREF(pBirthTime); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} + */ +static DECLCALLBACK(int) rtDvmVfsDir_SetOwner(void *pvThis, RTUID uid, RTGID gid) +{ + RT_NOREF(pvThis, uid, gid); + return VERR_NOT_SUPPORTED; +} + + +static int rtDvmVfsDir_FindEntry(PRTDVMVFSDIR pThis, const char *pszEntry, + PRTDVMVOLUME phVolume, uint32_t *piVol, char **ppszSymlink) +{ + *phVolume = NIL_RTDVMVOLUME; + *ppszSymlink = NULL; + *piVol = UINT32_MAX; + + /* + * Enumerate the volumes and try match the volume name. + */ + int rc; + PRTDVMVFSVOL pVfsVol = pThis->pVfsVol; + if (pVfsVol->cVolumes > 0) + { + /* The first volume. */ + uint32_t iVol = 0; + RTDVMVOLUME hVol; + rc = RTDvmMapQueryFirstVolume(pThis->pVfsVol->hVolMgr, &hVol); + while (RT_SUCCESS(rc)) + { + /* Match the name. */ + bool fMatch; + char *pszVolName; + rc = RTDvmVolumeQueryName(hVol, &pszVolName); + if (RT_SUCCESS(rc)) + { + fMatch = RTStrCmp(pszEntry, pszVolName) == 0 && *pszVolName != '\0'; + if (fMatch) + { + *phVolume = hVol; + *ppszSymlink = pszVolName; + *piVol = iVol; + return VINF_SUCCESS; + } + RTStrFree(pszVolName); + } + else if (rc == VERR_NOT_SUPPORTED) + fMatch = false; + else + { + RTDvmVolumeRelease(hVol); + break; + } + + /* Match the sequential volume number. */ + if (!fMatch) + { + char szTmp[16]; + RTStrPrintf(szTmp, sizeof(szTmp), "vol%u", iVol); + fMatch = RTStrCmp(pszEntry, szTmp) == 0; + } + + if (fMatch) + { + *phVolume = hVol; + *piVol = iVol; + return VINF_SUCCESS; + } + + /* More volumes? */ + iVol++; + if (iVol >= pVfsVol->cVolumes) + { + RTDvmVolumeRelease(hVol); + rc = VERR_FILE_NOT_FOUND; + break; + } + + /* Get the next volume. */ + RTDVMVOLUME hVolNext; + rc = RTDvmMapQueryNextVolume(pThis->pVfsVol->hVolMgr, hVol, &hVolNext); + RTDvmVolumeRelease(hVol); + hVol = hVolNext; + } + } + else + rc = VERR_FILE_NOT_FOUND; + return rc; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnOpen} + */ +static DECLCALLBACK(int) rtDvmVfsDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen, uint32_t fFlags, PRTVFSOBJ phVfsObj) +{ + PRTDVMVFSDIR pThis = (PRTDVMVFSDIR)pvThis; + + /* + * Special case: '.' and '..' + */ + if ( pszEntry[0] == '.' + && ( pszEntry[1] == '\0' + || ( pszEntry[1] == '.' + && pszEntry[2] == '\0'))) + { + if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN + || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE + || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) + { + if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY) + { + RTVFSDIR hVfsDir; + int rc = rtDvmVfsVol_OpenRoot(pThis->pVfsVol, &hVfsDir); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromDir(hVfsDir); + RTVfsDirRelease(hVfsDir); + AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); + } + return rc; + } + return VERR_IS_A_DIRECTORY; + } + return VERR_ACCESS_DENIED; + } + + /* + * Open volume file. + */ + RTDVMVOLUME hVolume = NIL_RTDVMVOLUME; + uint32_t iVol = 0; + char *pszSymlink = NULL; + int rc = rtDvmVfsDir_FindEntry(pThis, pszEntry, &hVolume, &iVol, &pszSymlink); + if (RT_SUCCESS(rc)) + { + if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN + || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE + || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) + { + if (fFlags & (RTVFSOBJ_F_OPEN_FILE | RTVFSOBJ_F_OPEN_DEV_BLOCK)) + { + if (!pszSymlink) + { + if ( !(fOpen & RTFILE_O_WRITE) + || !pThis->pVfsVol->fReadOnly) + { + /* Create file object. */ + RTVFSFILE hVfsFile; + rc = rtDvmVfsCreateFileForVolume(pThis->pVfsVol, hVolume, fOpen, &hVfsFile); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromFile(hVfsFile); + RTVfsFileRelease(hVfsFile); + AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); + } + } + else + rc = VERR_WRITE_PROTECT; + } + else + rc = VERR_IS_A_SYMLINK; + } + else if (fFlags & RTVFSOBJ_F_OPEN_SYMLINK) + { + /* Create symlink object */ + RTVFSSYMLINK hVfsSym = NIL_RTVFSSYMLINK; /* (older gcc maybe used uninitialized) */ + rc = rtDvmVfsCreateSymlinkForVolume(hVolume, pThis->pVfsVol ? pThis->pVfsVol->hVolMgr : NIL_RTDVM, iVol, + pszSymlink, &hVfsSym); + if (RT_SUCCESS(rc)) + { + *phVfsObj = RTVfsObjFromSymlink(hVfsSym); + RTVfsSymlinkRelease(hVfsSym); + AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); + pszSymlink = NULL; + } + } + else + rc = VERR_IS_A_FILE; + } + else + rc = VERR_ALREADY_EXISTS; + RTDvmVolumeRelease(hVolume); + if (pszSymlink) + RTStrFree(pszSymlink); + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnOpenFile} + */ +static DECLCALLBACK(int) rtDvmVfsDir_OpenFile(void *pvThis, const char *pszFilename, uint64_t fOpen, PRTVFSFILE phVfsFile) +{ + RTVFSOBJ hVfsObj; + int rc = rtDvmVfsDir_Open(pvThis, pszFilename, fOpen, RTVFSOBJ_F_OPEN_FILE, &hVfsObj); + if (RT_SUCCESS(rc)) + { + *phVfsFile = RTVfsObjToFile(hVfsObj); + RTVfsObjRelease(hVfsObj); + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnCreateDir} + */ +static DECLCALLBACK(int) rtDvmVfsDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir) +{ + RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink} + */ +static DECLCALLBACK(int) rtDvmVfsDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink) +{ + RT_NOREF(pvThis, pszSymlink, phVfsSymlink); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink} + */ +static DECLCALLBACK(int) rtDvmVfsDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget, + RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink) +{ + RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry} + */ +static DECLCALLBACK(int) rtDvmVfsDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType) +{ + RT_NOREF(pvThis, pszEntry, fType); + return VERR_NOT_IMPLEMENTED; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry} + */ +static DECLCALLBACK(int) rtDvmVfsDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName) +{ + RT_NOREF(pvThis, pszEntry, fType, pszNewName); + return VERR_NOT_IMPLEMENTED; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnRewindDir} + */ +static DECLCALLBACK(int) rtDvmVfsDir_RewindDir(void *pvThis) +{ + PRTDVMVFSDIR pThis = (PRTDVMVFSDIR)pvThis; + + if (pThis->hCurVolume != NIL_RTDVMVOLUME) + { + RTDvmVolumeRelease(pThis->hCurVolume); + pThis->hCurVolume = NIL_RTDVMVOLUME; + } + pThis->fReturnCurrent = false; + pThis->offDir = 0; + if (pThis->pszNameAlias) + { + RTStrFree(pThis->pszNameAlias); + pThis->pszNameAlias = NULL; + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSDIROPS,pfnReadDir} + */ +static DECLCALLBACK(int) rtDvmVfsDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, + RTFSOBJATTRADD enmAddAttr) +{ + PRTDVMVFSDIR pThis = (PRTDVMVFSDIR)pvThis; + PRTDVMVFSVOL pVfsVol = pThis->pVfsVol; + int rc; + + /* + * Format the volume name since we'll be needing it all but the final call. + */ + char szVolNo[16]; + size_t const cchVolNo = RTStrPrintf(szVolNo, sizeof(szVolNo), "vol%u", pThis->offDir); + + if (!pThis->fReturnCurrent) + { + /* + * Do we have a pending name alias to return? + */ + if (pThis->pszNameAlias) + { + size_t cchNameAlias = strlen(pThis->pszNameAlias); + size_t cbNeeded = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cchNameAlias + 1]); + if (cbNeeded <= *pcbDirEntry) + { + *pcbDirEntry = cbNeeded; + + /* Do the names. */ + pDirEntry->cbName = (uint16_t)cchNameAlias; + memcpy(pDirEntry->szName, pThis->pszNameAlias, cchNameAlias + 1); + pDirEntry->cwcShortName = 0; + pDirEntry->wszShortName[0] = '\0'; + + + /* Do the rest. */ + rc = rtDvmVfsSym_QueryInfoWorker(pThis->hCurVolume, pVfsVol->hVolMgr, szVolNo, &pDirEntry->Info, enmAddAttr); + if (RT_SUCCESS(rc)) + { + RTStrFree(pThis->pszNameAlias); + pThis->pszNameAlias = NULL; + pThis->offDir += 1; + } + return rc; + } + + *pcbDirEntry = cbNeeded; + return VERR_BUFFER_OVERFLOW; + } + + /* + * Get the next volume to return info about. + */ + if (pThis->offDir < pVfsVol->cVolumes) + { + RTDVMVOLUME hNextVolume; + if (pThis->offDir == 0) + rc = RTDvmMapQueryFirstVolume(pVfsVol->hVolMgr, &hNextVolume); + else + rc = RTDvmMapQueryNextVolume(pVfsVol->hVolMgr, pThis->hCurVolume, &hNextVolume); + if (RT_FAILURE(rc)) + return rc; + RTDvmVolumeRelease(pThis->hCurVolume); + pThis->hCurVolume = hNextVolume; + + /* Check if we need to return a name alias later. */ + rc = RTDvmVolumeQueryName(pThis->hCurVolume, &pThis->pszNameAlias); + if (RT_FAILURE(rc)) + pThis->pszNameAlias = NULL; + else if (*pThis->pszNameAlias == '\0') + { + RTStrFree(pThis->pszNameAlias); + pThis->pszNameAlias = NULL; + } + } + else + { + RTDvmVolumeRelease(pThis->hCurVolume); + pThis->hCurVolume = NIL_RTDVMVOLUME; + return VERR_NO_MORE_FILES; + } + } + + /* + * Figure out the name length. + */ + size_t cbNeeded = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cchVolNo + 1]); + if (cbNeeded <= *pcbDirEntry) + { + *pcbDirEntry = cbNeeded; + + /* Do the names. */ + pDirEntry->cbName = (uint16_t)cchVolNo; + memcpy(pDirEntry->szName, szVolNo, cchVolNo + 1); + pDirEntry->cwcShortName = 0; + pDirEntry->wszShortName[0] = '\0'; + + /* Do the rest. */ + rc = rtDvmVfsFile_QueryInfoWorker(pThis->hCurVolume, pVfsVol->hVolMgr, pVfsVol->fReadOnly, &pDirEntry->Info, enmAddAttr); + if (RT_SUCCESS(rc)) + { + pThis->fReturnCurrent = false; + if (!pThis->pszNameAlias) + pThis->offDir += 1; + return rc; + } + } + else + { + *pcbDirEntry = cbNeeded; + rc = VERR_BUFFER_OVERFLOW; + } + pThis->fReturnCurrent = true; + return rc; +} + + +/** + * DVM (root) directory operations. + */ +static const RTVFSDIROPS g_rtDvmVfsDirOps = +{ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_DIR, + "DvmDir", + rtDvmVfsDir_Close, + rtDvmVfsDir_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSDIROPS_VERSION, + 0, + { /* ObjSet */ + RTVFSOBJSETOPS_VERSION, + RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj), + rtDvmVfsDir_SetMode, + rtDvmVfsDir_SetTimes, + rtDvmVfsDir_SetOwner, + RTVFSOBJSETOPS_VERSION + }, + rtDvmVfsDir_Open, + NULL /* pfnFollowAbsoluteSymlink */, + rtDvmVfsDir_OpenFile, + NULL /* pfnOpenDir */, + rtDvmVfsDir_CreateDir, + rtDvmVfsDir_OpenSymlink, + rtDvmVfsDir_CreateSymlink, + NULL /* pfnQueryEntryInfo */, + rtDvmVfsDir_UnlinkEntry, + rtDvmVfsDir_RenameEntry, + rtDvmVfsDir_RewindDir, + rtDvmVfsDir_ReadDir, + RTVFSDIROPS_VERSION, +}; + + + +/** + * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose} + */ +static DECLCALLBACK(int) rtDvmVfsVol_Close(void *pvThis) +{ + PRTDVMVFSVOL pThis = (PRTDVMVFSVOL)pvThis; + LogFlow(("rtDvmVfsVol_Close(%p)\n", pThis)); + + if ( pThis->fCloseDvm + && pThis->hVolMgr != NIL_RTDVM ) + RTDvmRelease(pThis->hVolMgr); + pThis->hVolMgr = NIL_RTDVM; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo} + */ +static DECLCALLBACK(int) rtDvmVfsVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + RT_NOREF(pvThis, pObjInfo, enmAddAttr); + return VERR_WRONG_TYPE; +} + + +/** + * @interface_method_impl{RTVFSOPS,pfnOpenRoot} + */ +static DECLCALLBACK(int) rtDvmVfsVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir) +{ + PRTDVMVFSVOL pThis = (PRTDVMVFSVOL)pvThis; + + PRTDVMVFSDIR pNewDir; + int rc = RTVfsNewDir(&g_rtDvmVfsDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf, + NIL_RTVFSLOCK /*use volume lock*/, phVfsDir, (void **)&pNewDir); + if (RT_SUCCESS(rc)) + { + pNewDir->offDir = 0; + pNewDir->pVfsVol = pThis; + pNewDir->fReturnCurrent = false; + pNewDir->pszNameAlias = NULL; + pNewDir->hCurVolume = NIL_RTDVMVOLUME; + } + return rc; +} + + +/** + * @interface_method_impl{RTVFSOPS,pfnQueryRangeState} + */ +static DECLCALLBACK(int) rtDvmVfsVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed) +{ + RT_NOREF(pvThis, off, cb, pfUsed); + return VERR_NOT_IMPLEMENTED; +} + + +DECL_HIDDEN_CONST(const RTVFSOPS) g_rtDvmVfsVolOps = +{ + { /* Obj */ + RTVFSOBJOPS_VERSION, + RTVFSOBJTYPE_VFS, + "DvmVol", + rtDvmVfsVol_Close, + rtDvmVfsVol_QueryInfo, + RTVFSOBJOPS_VERSION + }, + RTVFSOPS_VERSION, + 0 /* fFeatures */, + rtDvmVfsVol_OpenRoot, + rtDvmVfsVol_QueryRangeState, + RTVFSOPS_VERSION +}; + + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate} + */ +static DECLCALLBACK(int) rtDvmVfsChain_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec, + PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg, pSpec); + + /* + * Basic checks. + */ + if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE) + return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE; + if (pElement->enmType != RTVFSOBJTYPE_VFS) + return VERR_VFS_CHAIN_ONLY_VFS; + + if (pElement->cArgs > 1) + return VERR_VFS_CHAIN_AT_MOST_ONE_ARG; + + /* + * Parse the flag if present, save in pElement->uProvider. + */ + /** @todo allow specifying sector size */ + bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ; + if (pElement->cArgs > 0) + { + const char *psz = pElement->paArgs[0].psz; + if (*psz) + { + if ( !strcmp(psz, "ro") + || !strcmp(psz, "r")) + fReadOnly = true; + else if (!strcmp(psz, "rw")) + fReadOnly = false; + else + { + *poffError = pElement->paArgs[0].offSpec; + return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument"); + } + } + } + + pElement->uProvider = fReadOnly; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate} + */ +static DECLCALLBACK(int) rtDvmVfsChain_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec, + PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj, + PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + RT_NOREF(pProviderReg, pSpec, poffError, pErrInfo); + AssertReturn(hPrevVfsObj != NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE); + + /* + * Instantiate the volume manager and open the map stuff. + */ + RTVFSFILE hPrevVfsFile = RTVfsObjToFile(hPrevVfsObj); + AssertReturn(hPrevVfsFile != NIL_RTVFSFILE, VERR_VFS_CHAIN_CAST_FAILED); + + RTDVM hVolMgr; + int rc = RTDvmCreate(&hVolMgr, hPrevVfsFile, 512, 0 /*fFlags*/); + RTVfsFileRelease(hPrevVfsFile); + if (RT_SUCCESS(rc)) + { + rc = RTDvmMapOpen(hVolMgr); + if (RT_SUCCESS(rc)) + { + /* + * Create a VFS instance for the volume manager. + */ + RTVFS hVfs = NIL_RTVFS; + PRTDVMVFSVOL pThis = NULL; + rc = RTVfsNew(&g_rtDvmVfsVolOps, sizeof(RTDVMVFSVOL), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis); + if (RT_SUCCESS(rc)) + { + pThis->hVolMgr = hVolMgr; + pThis->fCloseDvm = true; + pThis->fReadOnly = pElement->uProvider == (uint64_t)true; + pThis->cVolumes = RTDvmMapGetValidVolumes(hVolMgr); + pThis->hVfsSelf = hVfs; + + *phVfsObj = RTVfsObjFromVfs(hVfs); + RTVfsRelease(hVfs); + return *phVfsObj != NIL_RTVFSOBJ ? VINF_SUCCESS : VERR_VFS_CHAIN_CAST_FAILED; + } + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "RTDvmMapOpen failed: %Rrc", rc); + RTDvmRelease(hVolMgr); + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "RTDvmCreate failed: %Rrc", rc); + return rc; +} + + +/** + * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement} + */ +static DECLCALLBACK(bool) rtDvmVfsChain_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg, + PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement, + PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement) +{ + RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement); + return false; +} + + +/** VFS chain element 'file'. */ +static RTVFSCHAINELEMENTREG g_rtVfsChainIsoFsVolReg = +{ + /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION, + /* fReserved = */ 0, + /* pszName = */ "dvm", + /* ListEntry = */ { NULL, NULL }, + /* pszHelp = */ "Opens a container image using the VD API.\n" + "Optionally takes one parameter 'ro' (read only) or 'rw' (read write).\n", + /* pfnValidate = */ rtDvmVfsChain_Validate, + /* pfnInstantiate = */ rtDvmVfsChain_Instantiate, + /* pfnCanReuseElement = */ rtDvmVfsChain_CanReuseElement, + /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION +}; + +RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainIsoFsVolReg, rtVfsChainIsoFsVolReg); + |