diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/ImageMounter | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/ImageMounter')
-rw-r--r-- | src/VBox/ImageMounter/Makefile.kmk | 26 | ||||
-rw-r--r-- | src/VBox/ImageMounter/VBoxFUSE/Makefile.kmk | 57 | ||||
-rw-r--r-- | src/VBox/ImageMounter/VBoxFUSE/VBoxFUSE.cpp | 1476 | ||||
-rw-r--r-- | src/VBox/ImageMounter/vboximg-mount/Makefile.kmk | 65 | ||||
-rw-r--r-- | src/VBox/ImageMounter/vboximg-mount/SelfSizingTable.h | 313 | ||||
-rw-r--r-- | src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp | 1523 | ||||
-rw-r--r-- | src/VBox/ImageMounter/vboximg-mount/vboximg-mount.h | 297 | ||||
-rw-r--r-- | src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.cpp | 412 | ||||
-rw-r--r-- | src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.h | 255 | ||||
-rw-r--r-- | src/VBox/ImageMounter/vboximg-mount/vboximgMedia.cpp | 381 | ||||
-rw-r--r-- | src/VBox/ImageMounter/vboximg-mount/vboximgMedia.h | 40 | ||||
-rw-r--r-- | src/VBox/ImageMounter/vboximg-mount/vboximgOpts.h | 46 |
12 files changed, 4891 insertions, 0 deletions
diff --git a/src/VBox/ImageMounter/Makefile.kmk b/src/VBox/ImageMounter/Makefile.kmk new file mode 100644 index 00000000..81b8cda2 --- /dev/null +++ b/src/VBox/ImageMounter/Makefile.kmk @@ -0,0 +1,26 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the image mounting utilities. +# + +# +# Copyright (C) 2018-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. +# + +SUB_DEPTH = ../../.. +include $(KBUILD_PATH)/subheader.kmk + +ifdef VBOX_WITH_VBOXIMGMOUNT + include $(PATH_SUB_CURRENT)/vboximg-mount/Makefile.kmk +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ImageMounter/VBoxFUSE/Makefile.kmk b/src/VBox/ImageMounter/VBoxFUSE/Makefile.kmk new file mode 100644 index 00000000..67cc8ca5 --- /dev/null +++ b/src/VBox/ImageMounter/VBoxFUSE/Makefile.kmk @@ -0,0 +1,57 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the VBoxFUSE Program. +# + +# +# Copyright (C) 2009-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. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# VBOXR3EXE variant using the Mac OS X 10.5 SDK. +# +# We require this for VBoxFUSE since its libraries doesn't sit well with +# the 10.4 linker. Wonderful. +# +TEMPLATE_VBOXR3EXEOSX105 = VBOXR3EXE variant using the 10.5 SDK. +TEMPLATE_VBOXR3EXEOSX105_EXTENDS = VBOXR3EXE +TEMPLATE_VBOXR3EXEOSX105_DEFS.darwin = $(VBOX_DARWIN_DEF_SDK_10_5_DEFS) +TEMPLATE_VBOXR3EXEOSX105_DEFS.darwin = $(VBOX_DARWIN_DEF_SDK_10_5_DEFS) +TEMPLATE_VBOXR3EXEOSX105_CFLAGS.darwin = $(VBOX_DARWIN_DEF_SDK_10_5_CFLAGS) -fno-common +TEMPLATE_VBOXR3EXEOSX105_CXXFLAGS.darwin = $(VBOX_DARWIN_DEF_SDK_10_5_CXXFLAGS) +TEMPLATE_VBOXR3EXEOSX105_OBJCFLAGS.darwin = $(VBOX_DARWIN_DEF_SDK_10_5_OBJCFLAGS) +TEMPLATE_VBOXR3EXEOSX105_LDFLAGS.darwin = $(VBOX_DARWIN_DEF_SDK_10_5_LDFLAGS) + + +# +# VBoxFUSE - Disk Image Flatting FUSE Program. +# +PROGRAMS += VBoxFUSE +VBoxFUSE_TEMPLATE = VBOXR3EXE$(if-expr "$(KBUILD_TARGET)" == "darwin",OSX105,) +VBoxFUSE_DEFS.darwin = __FreeBSD_==10 +VBoxFUSE_DEFS = _FILE_OFFSET_BITS=64 +VBoxFUSE_INCS.darwin = \ + /usr/local/include +VBoxFUSE_SOURCES = \ + VBoxFUSE.cpp +VBoxFUSE_LIBS = \ + $(LIB_DDU) \ + $(LIB_RUNTIME) +VBoxFUSE_LIBS.darwin = fuse +VBoxFUSE_LIBS.linux = fuse +VBoxFUSE_LIBS.freebsd = fuse +VBoxFUSE_LIBPATHS.darwin = /usr/local/lib + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ImageMounter/VBoxFUSE/VBoxFUSE.cpp b/src/VBox/ImageMounter/VBoxFUSE/VBoxFUSE.cpp new file mode 100644 index 00000000..fdfaeb9a --- /dev/null +++ b/src/VBox/ImageMounter/VBoxFUSE/VBoxFUSE.cpp @@ -0,0 +1,1476 @@ +/* $Id: VBoxFUSE.cpp $ */ +/** @file + * VBoxFUSE - Disk Image Flattening FUSE Program. + */ + +/* + * Copyright (C) 2009-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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEFAULT /** @todo log group */ +#include <iprt/types.h> + +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_LINUX) +# include <sys/param.h> +# undef PVM /* Blasted old BSD mess still hanging around darwin. */ +#endif +#define FUSE_USE_VERSION 27 +#include <fuse.h> +#include <errno.h> +#include <fcntl.h> +#ifndef EDOOFUS +# ifdef EBADMACHO +# define EDOOFUS EBADMACHO +# elif defined(EPROTO) +# define EDOOFUS EPROTO /* What a boring lot. */ +//# elif defined(EXYZ) +//# define EDOOFUS EXYZ +# else +# error "Choose an unlikely and (if possible) fun error number for EDOOFUS." +# endif +#endif + +#include <VBox/vd.h> +#include <VBox/log.h> +#include <VBox/err.h> +#include <iprt/critsect.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Node type. + */ +typedef enum VBOXFUSETYPE +{ + VBOXFUSETYPE_INVALID = 0, + VBOXFUSETYPE_DIRECTORY, + VBOXFUSETYPE_FLAT_IMAGE, + VBOXFUSETYPE_CONTROL_PIPE +} VBOXFUSETYPE; + +/** + * Stuff common to both directories and files. + */ +typedef struct VBOXFUSENODE +{ + /** The directory name. */ + const char *pszName; + /** The name length. */ + size_t cchName; + /** The node type. */ + VBOXFUSETYPE enmType; + /** The number of references. + * The directory linking this node will always retain one. */ + int32_t volatile cRefs; + /** Critical section serializing access to the node data. */ + RTCRITSECT CritSect; + /** Pointer to the directory (parent). */ + struct VBOXFUSEDIR *pDir; + /** The mode mask. */ + RTFMODE fMode; + /** The User ID of the directory owner. */ + RTUID Uid; + /** The Group ID of the directory. */ + RTUID Gid; + /** The link count. */ + uint32_t cLinks; + /** The inode number. */ + RTINODE Ino; + /** The size of the primary stream. */ + RTFOFF cbPrimary; +} VBOXFUSENODE; +typedef VBOXFUSENODE *PVBOXFUSENODE; + +/** + * A flat image file. + */ +typedef struct VBOXFUSEFLATIMAGE +{ + /** The standard bits. */ + VBOXFUSENODE Node; + /** The virtual disk container. */ + PVBOXHDD pDisk; + /** The format name. */ + char *pszFormat; + /** The number of readers. + * Read only images will have this set to INT32_MAX/2 on creation. */ + int32_t cReaders; + /** The number of writers. (Just 1 or 0 really.) */ + int32_t cWriters; +} VBOXFUSEFLATIMAGE; +typedef VBOXFUSEFLATIMAGE *PVBOXFUSEFLATIMAGE; + +/** + * A control pipe (file). + */ +typedef struct VBOXFUSECTRLPIPE +{ + /** The standard bits. */ + VBOXFUSENODE Node; +} VBOXFUSECTRLPIPE; +typedef VBOXFUSECTRLPIPE *PVBOXFUSECTRLPIPE; + + +/** + * A Directory. + * + * This is just a container of files and subdirectories, nothing special. + */ +typedef struct VBOXFUSEDIR +{ + /** The standard bits. */ + VBOXFUSENODE Node; + /** The number of directory entries. */ + uint32_t cEntries; + /** Array of pointers to directory entries. + * Whether what's being pointed to is a file, directory or something else can be + * determined by the enmType field. */ + PVBOXFUSENODE *paEntries; +} VBOXFUSEDIR; +typedef VBOXFUSEDIR *PVBOXFUSEDIR; + +/** The number of elements to grow VBOXFUSEDIR::paEntries by. */ +#define VBOXFUSE_DIR_GROW_BY 2 /* 32 */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The root of the file hierarchy. */ +static VBOXFUSEDIR *g_pTreeRoot; +/** The next inode number. */ +static RTINODE volatile g_NextIno = 1; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vboxfuseTreeLookupParent(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir); +static int vboxfuseTreeLookupParentForInsert(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir); + + +/** + * Node destructor. + * + * @returns true. + * @param pNode The node. + * @param fLocked Whether it's locked. + */ +static bool vboxfuseNodeDestroy(PVBOXFUSENODE pNode, bool fLocked) +{ + Assert(pNode->cRefs == 0); + + /* + * Type specific cleanups. + */ + switch (pNode->enmType) + { + case VBOXFUSETYPE_DIRECTORY: + { + PVBOXFUSEDIR pDir = (PVBOXFUSEDIR)pNode; + RTMemFree(pDir->paEntries); + pDir->paEntries = NULL; + pDir->cEntries = 0; + break; + } + + case VBOXFUSETYPE_FLAT_IMAGE: + { + PVBOXFUSEFLATIMAGE pFlatImage = (PVBOXFUSEFLATIMAGE)pNode; + if (pFlatImage->pDisk) + { + int rc2 = VDClose(pFlatImage->pDisk, false /* fDelete */); AssertRC(rc2); + pFlatImage->pDisk = NULL; + } + RTStrFree(pFlatImage->pszFormat); + pFlatImage->pszFormat = NULL; + break; + } + + case VBOXFUSETYPE_CONTROL_PIPE: + break; + + default: + AssertMsgFailed(("%d\n", pNode->enmType)); + break; + } + + /* + * Generic cleanup. + */ + pNode->enmType = VBOXFUSETYPE_INVALID; + pNode->pszName = NULL; + + /* + * Unlock and destroy the lock, before we finally frees the node. + */ + if (fLocked) + RTCritSectLeave(&pNode->CritSect); + RTCritSectDelete(&pNode->CritSect); + + RTMemFree(pNode); + + return true; +} + + +/** + * Locks a FUSE node. + * + * @param pNode The node. + */ +static void vboxfuseNodeLock(PVBOXFUSENODE pNode) +{ + int rc = RTCritSectEnter(&pNode->CritSect); + AssertRC(rc); +} + + +/** + * Unlocks a FUSE node. + * + * @param pNode The node. + */ +static void vboxfuseNodeUnlock(PVBOXFUSENODE pNode) +{ + int rc = RTCritSectLeave(&pNode->CritSect); + AssertRC(rc); +} + + +/** + * Retain a VBoxFUSE node. + * + * @param pNode The node. + */ +static void vboxfuseNodeRetain(PVBOXFUSENODE pNode) +{ + int32_t cNewRefs = ASMAtomicIncS32(&pNode->cRefs); + Assert(cNewRefs != 1); +} + + +/** + * Releases a VBoxFUSE node reference. + * + * @returns true if deleted, false if not. + * @param pNode The node. + */ +static bool vboxfuseNodeRelease(PVBOXFUSENODE pNode) +{ + if (ASMAtomicDecS32(&pNode->cRefs) == 0) + return vboxfuseNodeDestroy(pNode, false /* fLocked */); + return false; +} + + +/** + * Locks and retains a VBoxFUSE node. + * + * @param pNode The node. + */ +static void vboxfuseNodeLockAndRetain(PVBOXFUSENODE pNode) +{ + vboxfuseNodeLock(pNode); + vboxfuseNodeRetain(pNode); +} + + +/** + * Releases a VBoxFUSE node reference and unlocks it. + * + * @returns true if deleted, false if not. + * @param pNode The node. + */ +static bool vboxfuseNodeReleaseAndUnlock(PVBOXFUSENODE pNode) +{ + if (ASMAtomicDecS32(&pNode->cRefs) == 0) + return vboxfuseNodeDestroy(pNode, true /* fLocked */); + vboxfuseNodeUnlock(pNode); + return false; +} + + +/** + * Creates stat info for a locked node. + * + * @param pNode The node (locked). + */ +static void vboxfuseNodeFillStat(PVBOXFUSENODE pNode, struct stat *pStat) +{ + pStat->st_dev = 0; /* ignored */ + pStat->st_ino = pNode->Ino; /* maybe ignored */ + pStat->st_mode = pNode->fMode; + pStat->st_nlink = pNode->cLinks; + pStat->st_uid = pNode->Uid; + pStat->st_gid = pNode->Gid; + pStat->st_rdev = 0; + /** @todo file times */ + pStat->st_atime = 0; +// pStat->st_atimensec = 0; + pStat->st_mtime = 0; +// pStat->st_mtimensec = 0; + pStat->st_ctime = 0; +// pStat->st_ctimensec = 0; + pStat->st_size = pNode->cbPrimary; + pStat->st_blocks = (pNode->cbPrimary + DEV_BSIZE - 1) / DEV_BSIZE; + pStat->st_blksize = 0x1000; /* ignored */ +#ifndef RT_OS_LINUX + pStat->st_flags = 0; + pStat->st_gen = 0; +#endif +} + + +/** + * Allocates a new node and initialize the node part of it. + * + * The returned node has one reference. + * + * @returns VBox status code. + * + * @param cbNode The size of the node. + * @param pszName The name of the node. + * @param enmType The node type. + * @param pDir The directory (parent). + * @param ppNode Where to return the pointer to the node. + */ +static int vboxfuseNodeAlloc(size_t cbNode, const char *pszName, VBOXFUSETYPE enmType, PVBOXFUSEDIR pDir, + PVBOXFUSENODE *ppNode) +{ + Assert(cbNode >= sizeof(VBOXFUSENODE)); + + /* + * Allocate the memory for it and init the critical section. + */ + size_t cchName = strlen(pszName); + PVBOXFUSENODE pNode = (PVBOXFUSENODE)RTMemAlloc(cchName + 1 + RT_ALIGN_Z(cbNode, 8)); + if (!pNode) + return VERR_NO_MEMORY; + + int rc = RTCritSectInit(&pNode->CritSect); + if (RT_FAILURE(rc)) + { + RTMemFree(pNode); + return rc; + } + + /* + * Initialize the members. + */ + pNode->pszName = (char *)memcpy((uint8_t *)pNode + RT_ALIGN_Z(cbNode, 8), pszName, cchName + 1); + pNode->cchName = cchName; + pNode->enmType = enmType; + pNode->cRefs = 1; + pNode->pDir = pDir; +#if 0 + pNode->fMode = enmType == VBOXFUSETYPE_DIRECTORY ? S_IFDIR | 0755 : S_IFREG | 0644; +#else + pNode->fMode = enmType == VBOXFUSETYPE_DIRECTORY ? S_IFDIR | 0777 : S_IFREG | 0666; +#endif + pNode->Uid = 0; + pNode->Gid = 0; + pNode->cLinks = 0; + pNode->Ino = g_NextIno++; /** @todo make this safe! */ + pNode->cbPrimary = 0; + + *ppNode = pNode; + return VINF_SUCCESS; +} + + +/** + * Inserts a node into a directory + * + * The caller has locked and referenced the directory as well as checked that + * the name doesn't already exist within it. On success both the reference and + * and link counters will be incremented. + * + * @returns VBox status code. + * + * @param pDir The parent directory. Can be NULL when creating the root + * directory. + * @param pNode The node to insert. + */ +static int vboxfuseDirInsertChild(PVBOXFUSEDIR pDir, PVBOXFUSENODE pNode) +{ + if (!pDir) + { + /* + * Special case: Root Directory. + */ + AssertReturn(!g_pTreeRoot, VERR_ALREADY_EXISTS); + AssertReturn(pNode->enmType == VBOXFUSETYPE_DIRECTORY, VERR_INTERNAL_ERROR); + g_pTreeRoot = (PVBOXFUSEDIR)pNode; + } + else + { + /* + * Common case. + */ + if (!(pDir->cEntries % VBOXFUSE_DIR_GROW_BY)) + { + void *pvNew = RTMemRealloc(pDir->paEntries, sizeof(*pDir->paEntries) * (pDir->cEntries + VBOXFUSE_DIR_GROW_BY)); + if (!pvNew) + return VERR_NO_MEMORY; + pDir->paEntries = (PVBOXFUSENODE *)pvNew; + } + pDir->paEntries[pDir->cEntries++] = pNode; + pDir->Node.cLinks++; + } + + vboxfuseNodeRetain(pNode); + pNode->cLinks++; + return VINF_SUCCESS; +} + + +/** + * Create a directory node. + * + * @returns VBox status code. + * @param pszPath The path to the directory. + * @param ppDir Optional, where to return the new directory locked and + * referenced (making cRefs == 2). + */ +static int vboxfuseDirCreate(const char *pszPath, PVBOXFUSEDIR *ppDir) +{ + /* + * Figure out where the directory is going. + */ + const char *pszName; + PVBOXFUSEDIR pParent; + int rc = vboxfuseTreeLookupParentForInsert(pszPath, &pszName, &pParent); + if (RT_FAILURE(rc)) + return rc; + + /* + * Allocate and initialize the new directory node. + */ + PVBOXFUSEDIR pNewDir; + rc = vboxfuseNodeAlloc(sizeof(*pNewDir), pszName, VBOXFUSETYPE_DIRECTORY, pParent, (PVBOXFUSENODE *)&pNewDir); + if (RT_SUCCESS(rc)) + { + pNewDir->cEntries = 0; + pNewDir->paEntries = NULL; + + /* + * Insert it. + */ + rc = vboxfuseDirInsertChild(pParent, &pNewDir->Node); + if ( RT_SUCCESS(rc) + && ppDir) + { + vboxfuseNodeLockAndRetain(&pNewDir->Node); + *ppDir = pNewDir; + } + vboxfuseNodeRelease(&pNewDir->Node); + } + if (pParent) + vboxfuseNodeReleaseAndUnlock(&pParent->Node); + return rc; +} + + +/** + * Creates a flattened image + * + * @returns VBox status code. + * @param pszPath Where to create the flattened file in the FUSE file + * system. + * @param pszImage The image to flatten. + * @param ppFile Where to return the pointer to the instance. + * Optional. + */ +static int vboxfuseFlatImageCreate(const char *pszPath, const char *pszImage, PVBOXFUSEFLATIMAGE *ppFile) +{ + /* + * Check that we can create this file. + */ + const char *pszName; + PVBOXFUSEDIR pParent; + int rc = vboxfuseTreeLookupParentForInsert(pszPath, &pszName, &pParent); + if (RT_FAILURE(rc)) + return rc; + if (pParent) + vboxfuseNodeReleaseAndUnlock(&pParent->Node); + + /* + * Try open the image file (without holding any locks). + */ + char *pszFormat; + VDTYPE enmType; + rc = VDGetFormat(NULL /* pVDIIfsDisk */, NULL /* pVDIIfsImage*/, pszImage, &pszFormat, &enmType); + if (RT_FAILURE(rc)) + { + LogRel(("VDGetFormat(%s,) failed, rc=%Rrc\n", pszImage, rc)); + return rc; + } + + PVBOXHDD pDisk = NULL; + rc = VDCreate(NULL /* pVDIIfsDisk */, enmType, &pDisk); + if (RT_SUCCESS(rc)) + { + rc = VDOpen(pDisk, pszFormat, pszImage, 0, NULL /* pVDIfsImage */); + if (RT_FAILURE(rc)) + { + LogRel(("VDCreate(,%s,%s,,,) failed, rc=%Rrc\n", pszFormat, pszImage, rc)); + VDClose(pDisk, false /* fDeletes */); + } + } + else + Log(("VDCreate failed, rc=%Rrc\n", rc)); + if (RT_FAILURE(rc)) + { + RTStrFree(pszFormat); + return rc; + } + + /* + * Allocate and initialize the new directory node. + */ + rc = vboxfuseTreeLookupParentForInsert(pszPath, &pszName, &pParent); + if (RT_SUCCESS(rc)) + { + PVBOXFUSEFLATIMAGE pNewFlatImage; + rc = vboxfuseNodeAlloc(sizeof(*pNewFlatImage), pszName, VBOXFUSETYPE_FLAT_IMAGE, pParent, (PVBOXFUSENODE *)&pNewFlatImage); + if (RT_SUCCESS(rc)) + { + pNewFlatImage->pDisk = pDisk; + pNewFlatImage->pszFormat = pszFormat; + pNewFlatImage->cReaders = VDIsReadOnly(pNewFlatImage->pDisk) ? INT32_MAX / 2 : 0; + pNewFlatImage->cWriters = 0; + pNewFlatImage->Node.cbPrimary = VDGetSize(pNewFlatImage->pDisk, 0 /* base */); + + /* + * Insert it. + */ + rc = vboxfuseDirInsertChild(pParent, &pNewFlatImage->Node); + if ( RT_SUCCESS(rc) + && ppFile) + { + vboxfuseNodeLockAndRetain(&pNewFlatImage->Node); + *ppFile = pNewFlatImage; + } + vboxfuseNodeRelease(&pNewFlatImage->Node); + pDisk = NULL; + } + if (pParent) + vboxfuseNodeReleaseAndUnlock(&pParent->Node); + } + if (RT_FAILURE(rc) && pDisk != NULL) + VDClose(pDisk, false /* fDelete */); + return rc; +} + + +//static int vboxfuseTreeMkCtrlPipe(const char *pszPath, PVBOXFUSECTRLPIPE *ppPipe) +//{ +//} + + +/** + * Looks up a file in the tree. + * + * Upon successful return the returned node is both referenced and locked. The + * call will have to release and unlock it. + * + * @returns VBox status code + * @param pszPath The path to the file. + * @param ppNode Where to return the node. + */ +static int vboxfuseTreeLookup(const char *pszPath, PVBOXFUSENODE *ppNode) +{ + /* + * Root first. + */ + const char *psz = pszPath; + if (*psz != '/') + return VERR_FILE_NOT_FOUND; + + PVBOXFUSEDIR pDir = g_pTreeRoot; + vboxfuseNodeLockAndRetain(&pDir->Node); + + do psz++; + while (*psz == '/'); + if (!*psz) + { + /* looking for the root. */ + *ppNode = &pDir->Node; + return VINF_SUCCESS; + } + + /* + * Take it bit by bit from here on. + */ + for (;;) + { + /* + * Find the length of the current directory entry and check if it must be file. + */ + const char * const pszName = psz; + psz = strchr(psz, '/'); + if (!psz) + psz = strchr(pszName, '\0'); + size_t cchName = psz - pszName; + + bool fMustBeDir = *psz == '/'; + while (*psz == '/') + psz++; + + /* + * Look it up. + * This is safe as the directory will hold a reference to each node + * so the nodes cannot possibly be destroyed while we're searching them. + */ + PVBOXFUSENODE pNode = NULL; + uint32_t i = pDir->cEntries; + PVBOXFUSENODE *paEntries = pDir->paEntries; + while (i-- > 0) + { + PVBOXFUSENODE pCur = paEntries[i]; + if ( pCur->cchName == cchName + && !memcmp(pCur->pszName, pszName, cchName)) + { + pNode = pCur; + vboxfuseNodeLockAndRetain(pNode); + break; + } + } + vboxfuseNodeReleaseAndUnlock(&pDir->Node); + + if (!pNode) + return *psz ? VERR_PATH_NOT_FOUND : VERR_FILE_NOT_FOUND; + if ( fMustBeDir + && pNode->enmType != VBOXFUSETYPE_DIRECTORY) + { + vboxfuseNodeReleaseAndUnlock(pNode); + return VERR_NOT_A_DIRECTORY; + } + + /* + * Are we done? + */ + if (!*psz) + { + *ppNode = pNode; + return VINF_SUCCESS; + } + + /* advance */ + pDir = (PVBOXFUSEDIR)pNode; + } +} + + +/** + * Errno conversion wrapper around vboxfuseTreeLookup(). + * + * @returns 0 on success, negated errno on failure. + * @param pszPath The path to the file. + * @param ppNode Where to return the node. + */ +static int vboxfuseTreeLookupErrno(const char *pszPath, PVBOXFUSENODE *ppNode) +{ + int rc = vboxfuseTreeLookup(pszPath, ppNode); + if (RT_SUCCESS(rc)) + return 0; + return -RTErrConvertToErrno(rc); +} + + +/** + * Looks up a parent directory in the tree. + * + * Upon successful return the returned directory is both referenced and locked. + * The call will have to release and unlock it. + * + * @returns VBox status code. + * + * @param pszPath The path to the file which parent we seek. + * @param ppszName Where to return the pointer to the child's name within + * pszPath. + * @param ppDir Where to return the parent directory. + */ +static int vboxfuseTreeLookupParent(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir) +{ + /* + * Root first. + */ + const char *psz = pszPath; + if (*psz != '/') + return VERR_INVALID_PARAMETER; + do psz++; + while (*psz == '/'); + if (!*psz) + { + /* looking for the root. */ + *ppszName = psz + 1; + *ppDir = NULL; + return VINF_SUCCESS; + } + + /* + * Take it bit by bit from here on. + */ + PVBOXFUSEDIR pDir = g_pTreeRoot; + AssertReturn(pDir, VERR_WRONG_ORDER); + vboxfuseNodeLockAndRetain(&pDir->Node); + for (;;) + { + /* + * Find the length of the current directory entry and check if it must be file. + */ + const char * const pszName = psz; + psz = strchr(psz, '/'); + if (!psz) + { + /* that's all folks.*/ + *ppszName = pszName; + *ppDir = pDir; + return VINF_SUCCESS; + } + size_t cchName = psz - pszName; + + bool fMustBeDir = *psz == '/'; + while (*psz == '/') + psz++; + + /* Trailing slashes are not allowed (because it's simpler without them). */ + if (!*psz) + return VERR_INVALID_PARAMETER; + + /* + * Look it up. + * This is safe as the directory will hold a reference to each node + * so the nodes cannot possibly be destroyed while we're searching them. + */ + PVBOXFUSENODE pNode = NULL; + uint32_t i = pDir->cEntries; + PVBOXFUSENODE *paEntries = pDir->paEntries; + while (i-- > 0) + { + PVBOXFUSENODE pCur = paEntries[i]; + if ( pCur->cchName == cchName + && !memcmp(pCur->pszName, pszName, cchName)) + { + pNode = pCur; + vboxfuseNodeLockAndRetain(pNode); + break; + } + } + vboxfuseNodeReleaseAndUnlock(&pDir->Node); + + if (!pNode) + return VERR_FILE_NOT_FOUND; + if ( fMustBeDir + && pNode->enmType != VBOXFUSETYPE_DIRECTORY) + { + vboxfuseNodeReleaseAndUnlock(pNode); + return VERR_PATH_NOT_FOUND; + } + + /* advance */ + pDir = (PVBOXFUSEDIR)pNode; + } +} + + +/** + * Looks up a parent directory in the tree and checking that the specified child + * doesn't already exist. + * + * Upon successful return the returned directory is both referenced and locked. + * The call will have to release and unlock it. + * + * @returns VBox status code. + * + * @param pszPath The path to the file which parent we seek. + * @param ppszName Where to return the pointer to the child's name within + * pszPath. + * @param ppDir Where to return the parent directory. + */ +static int vboxfuseTreeLookupParentForInsert(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir) +{ + /* + * Lookup the parent directory using vboxfuseTreeLookupParent first. + */ + const char *pszName; + PVBOXFUSEDIR pDir; + int rc = vboxfuseTreeLookupParent(pszPath, &pszName, &pDir); + if (RT_SUCCESS(rc)) + { + /* + * Check that it doesn't exist already + */ + if (pDir) + { + size_t const cchName = strlen(pszName); + uint32_t i = pDir->cEntries; + PVBOXFUSENODE *paEntries = pDir->paEntries; + while (i-- > 0) + { + PVBOXFUSENODE pCur = paEntries[i]; + if ( pCur->cchName == cchName + && !memcmp(pCur->pszName, pszName, cchName)) + { + vboxfuseNodeReleaseAndUnlock(&pDir->Node); + rc = VERR_ALREADY_EXISTS; + break; + } + } + } + if (RT_SUCCESS(rc)) + { + *ppDir = pDir; + *ppszName = pszName; + } + } + return rc; +} + + + + + +/** @copydoc fuse_operations::getattr */ +static int vboxfuseOp_getattr(const char *pszPath, struct stat *pStat) +{ + PVBOXFUSENODE pNode; + int rc = vboxfuseTreeLookupErrno(pszPath, &pNode); + if (!rc) + { + vboxfuseNodeFillStat(pNode, pStat); + vboxfuseNodeReleaseAndUnlock(pNode); + } + LogFlow(("vboxfuseOp_getattr: rc=%d \"%s\"\n", rc, pszPath)); + return rc; +} + + +/** @copydoc fuse_operations::opendir */ +static int vboxfuseOp_opendir(const char *pszPath, struct fuse_file_info *pInfo) +{ + PVBOXFUSENODE pNode; + int rc = vboxfuseTreeLookupErrno(pszPath, &pNode); + if (!rc) + { + /* + * Check that it's a directory and that the caller should see it. + */ + if (pNode->enmType != VBOXFUSETYPE_DIRECTORY) + rc = -ENOTDIR; + /** @todo access checks. */ + else + { + /** @todo update the accessed TS? */ + + /* + * Put a reference to the node in the fuse_file_info::fh member so + * we don't have to parse the path in readdir. + */ + pInfo->fh = (uintptr_t)pNode; + vboxfuseNodeUnlock(pNode); + } + + /* cleanup */ + if (rc) + vboxfuseNodeReleaseAndUnlock(pNode); + } + LogFlow(("vboxfuseOp_opendir: rc=%d \"%s\"\n", rc, pszPath)); + return rc; +} + + +/** @copydoc fuse_operations::readdir */ +static int vboxfuseOp_readdir(const char *pszPath, void *pvBuf, fuse_fill_dir_t pfnFiller, + off_t offDir, struct fuse_file_info *pInfo) +{ + PVBOXFUSEDIR pDir = (PVBOXFUSEDIR)(uintptr_t)pInfo->fh; + AssertPtr(pDir); + Assert(pDir->Node.enmType == VBOXFUSETYPE_DIRECTORY); + vboxfuseNodeLock(&pDir->Node); + LogFlow(("vboxfuseOp_readdir: offDir=%llx \"%s\"\n", (uint64_t)offDir, pszPath)); + +#define VBOXFUSE_FAKE_DIRENT_SIZE 512 + + /* + * First the mandatory dot and dot-dot entries. + */ + struct stat st; + int rc = 0; + if (!offDir) + { + offDir += VBOXFUSE_FAKE_DIRENT_SIZE; + vboxfuseNodeFillStat(&pDir->Node, &st); + rc = pfnFiller(pvBuf, ".", &st, offDir); + } + if ( offDir == VBOXFUSE_FAKE_DIRENT_SIZE + && !rc) + { + offDir += VBOXFUSE_FAKE_DIRENT_SIZE; + rc = pfnFiller(pvBuf, "..", NULL, offDir); + } + + /* + * Convert the offset to a directory index and start/continue filling the buffer. + * The entries only needs locking since the directory already has a reference + * to each of them. + */ + Assert(offDir >= VBOXFUSE_FAKE_DIRENT_SIZE * 2 || rc); + uint32_t i = offDir / VBOXFUSE_FAKE_DIRENT_SIZE - 2; + while ( !rc + && i < pDir->cEntries) + { + PVBOXFUSENODE pNode = pDir->paEntries[i]; + vboxfuseNodeLock(pNode); + + vboxfuseNodeFillStat(pNode, &st); + offDir = (i + 3) * VBOXFUSE_FAKE_DIRENT_SIZE; + rc = pfnFiller(pvBuf, pNode->pszName, &st, offDir); + + vboxfuseNodeUnlock(pNode); + + /* next */ + i++; + } + + vboxfuseNodeUnlock(&pDir->Node); + LogFlow(("vboxfuseOp_readdir: returns offDir=%llx\n", (uint64_t)offDir)); + return 0; +} + + +/** @copydoc fuse_operations::releasedir */ +static int vboxfuseOp_releasedir(const char *pszPath, struct fuse_file_info *pInfo) +{ + PVBOXFUSEDIR pDir = (PVBOXFUSEDIR)(uintptr_t)pInfo->fh; + AssertPtr(pDir); + Assert(pDir->Node.enmType == VBOXFUSETYPE_DIRECTORY); + pInfo->fh = 0; + vboxfuseNodeRelease(&pDir->Node); + LogFlow(("vboxfuseOp_releasedir: \"%s\"\n", pszPath)); + return 0; +} + + +/** @copydoc fuse_operations::symlink */ +static int vboxfuseOp_symlink(const char *pszDst, const char *pszPath) +{ + /* + * "Interface" for mounting a image. + */ + int rc = vboxfuseFlatImageCreate(pszPath, pszDst, NULL); + if (RT_SUCCESS(rc)) + { + Log(("vboxfuseOp_symlink: \"%s\" => \"%s\" SUCCESS!\n", pszPath, pszDst)); + return 0; + } + + LogFlow(("vboxfuseOp_symlink: \"%s\" => \"%s\" rc=%Rrc\n", pszPath, pszDst, rc)); + return -RTErrConvertToErrno(rc); +} + + +/** @copydoc fuse_operations::open */ +static int vboxfuseOp_open(const char *pszPath, struct fuse_file_info *pInfo) +{ + LogFlow(("vboxfuseOp_open(\"%s\", .flags=%#x)\n", pszPath, pInfo->flags)); + + /* + * Validate incoming flags. + */ +#ifdef RT_OS_DARWIN + if (pInfo->flags & (O_APPEND | O_NONBLOCK | O_SYMLINK | O_NOCTTY | O_SHLOCK | O_EXLOCK | O_ASYNC + | O_CREAT | O_TRUNC | O_EXCL | O_EVTONLY)) + return -EINVAL; + if ((pInfo->flags & O_ACCMODE) == O_ACCMODE) + return -EINVAL; +#elif defined(RT_OS_LINUX) + if (pInfo->flags & ( O_APPEND | O_ASYNC | O_DIRECT /* | O_LARGEFILE ? */ + | O_NOATIME | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK + /* | O_SYNC ? */)) + return -EINVAL; + if ((pInfo->flags & O_ACCMODE) == O_ACCMODE) + return -EINVAL; +#elif defined(RT_OS_FREEBSD) + if (pInfo->flags & ( O_APPEND | O_ASYNC | O_DIRECT /* | O_LARGEFILE ? */ + | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK + /* | O_SYNC ? */)) + return -EINVAL; + if ((pInfo->flags & O_ACCMODE) == O_ACCMODE) + return -EINVAL; +#else +# error "Port me" +#endif + + PVBOXFUSENODE pNode; + int rc = vboxfuseTreeLookupErrno(pszPath, &pNode); + if (!rc) + { + /* + * Check flags and stuff. + */ + switch (pNode->enmType) + { + /* not expected here? */ + case VBOXFUSETYPE_DIRECTORY: + AssertFailed(); + rc = -EISDIR; + break; + + case VBOXFUSETYPE_FLAT_IMAGE: + { + PVBOXFUSEFLATIMAGE pFlatImage = (PVBOXFUSEFLATIMAGE)pNode; +#ifdef O_DIRECTORY + if (pInfo->flags & O_DIRECTORY) + rc = -ENOTDIR; +#endif + if ( (pInfo->flags & O_ACCMODE) == O_WRONLY + || (pInfo->flags & O_ACCMODE) == O_RDWR) + { + if ( pFlatImage->cWriters == 0 + && pFlatImage->cReaders == 0) + pFlatImage->cWriters++; + else + rc = -ETXTBSY; + } + else if ((pInfo->flags & O_ACCMODE) == O_RDONLY) + { + if (pFlatImage->cWriters == 0) + { + if (pFlatImage->cReaders + 1 < ( pFlatImage->cReaders < INT32_MAX / 2 + ? INT32_MAX / 4 + : INT32_MAX / 2 + INT32_MAX / 4) ) + pFlatImage->cReaders++; + else + rc = -EMLINK; + } + else + rc = -ETXTBSY; + } + break; + } + + case VBOXFUSETYPE_CONTROL_PIPE: + rc = -ENOTSUP; + break; + + default: + rc = -EDOOFUS; + break; + } + if (!rc) + { + /* + * Put a reference to the node in the fuse_file_info::fh member so + * we don't have to parse the path in the other file methods. + */ + pInfo->fh = (uintptr_t)pNode; + vboxfuseNodeUnlock(pNode); + } + else + { + /* cleanup */ + vboxfuseNodeReleaseAndUnlock(pNode); + } + } + LogFlow(("vboxfuseOp_opendir: rc=%d \"%s\"\n", rc, pszPath)); + return rc; +} + + +/** @copydoc fuse_operations::release */ +static int vboxfuseOp_release(const char *pszPath, struct fuse_file_info *pInfo) +{ + PVBOXFUSENODE pNode = (PVBOXFUSENODE)(uintptr_t)pInfo->fh; + AssertPtr(pNode); + pInfo->fh = 0; + + switch (pNode->enmType) + { + case VBOXFUSETYPE_DIRECTORY: + /* nothing to do */ + vboxfuseNodeRelease(pNode); + break; + + case VBOXFUSETYPE_FLAT_IMAGE: + { + PVBOXFUSEFLATIMAGE pFlatImage = (PVBOXFUSEFLATIMAGE)pNode; + vboxfuseNodeLock(&pFlatImage->Node); + + if ( (pInfo->flags & O_ACCMODE) == O_WRONLY + || (pInfo->flags & O_ACCMODE) == O_RDWR) + { + pFlatImage->cWriters--; + Assert(pFlatImage->cWriters >= 0); + } + else if ((pInfo->flags & O_ACCMODE) == O_RDONLY) + { + pFlatImage->cReaders--; + Assert(pFlatImage->cReaders >= 0); + } + else + AssertFailed(); + + vboxfuseNodeReleaseAndUnlock(&pFlatImage->Node); + break; + } + + case VBOXFUSETYPE_CONTROL_PIPE: + /* nothing to do yet */ + vboxfuseNodeRelease(pNode); + break; + + default: + AssertMsgFailed(("%s\n", pszPath)); + return -EDOOFUS; + } + + LogFlow(("vboxfuseOp_release: \"%s\"\n", pszPath)); + return 0; +} + +/** The VDRead/VDWrite block granularity. */ +#define VBOXFUSE_MIN_SIZE 512 +/** Offset mask corresponding to VBOXFUSE_MIN_SIZE. */ +#define VBOXFUSE_MIN_SIZE_MASK_OFF (0x1ff) +/** Block mask corresponding to VBOXFUSE_MIN_SIZE. */ +#define VBOXFUSE_MIN_SIZE_MASK_BLK (~UINT64_C(0x1ff)) + +/** @copydoc fuse_operations::read */ +static int vboxfuseOp_read(const char *pszPath, char *pbBuf, size_t cbBuf, + off_t offFile, struct fuse_file_info *pInfo) +{ + /* paranoia */ + AssertReturn((int)cbBuf >= 0, -EINVAL); + AssertReturn((unsigned)cbBuf == cbBuf, -EINVAL); + AssertReturn(offFile >= 0, -EINVAL); + AssertReturn((off_t)(offFile + cbBuf) >= offFile, -EINVAL); + + PVBOXFUSENODE pNode = (PVBOXFUSENODE)(uintptr_t)pInfo->fh; + AssertPtr(pNode); + switch (pNode->enmType) + { + case VBOXFUSETYPE_DIRECTORY: + return -ENOTSUP; + + case VBOXFUSETYPE_FLAT_IMAGE: + { + PVBOXFUSEFLATIMAGE pFlatImage = (PVBOXFUSEFLATIMAGE)(uintptr_t)pInfo->fh; + LogFlow(("vboxfuseOp_read: offFile=%#llx cbBuf=%#zx pszPath=\"%s\"\n", (uint64_t)offFile, cbBuf, pszPath)); + vboxfuseNodeLock(&pFlatImage->Node); + + int rc; + if ((off_t)(offFile + cbBuf) < offFile) + rc = -EINVAL; + else if (offFile >= pFlatImage->Node.cbPrimary) + rc = 0; + else if (!cbBuf) + rc = 0; + else + { + /* Adjust for EOF. */ + if ((off_t)(offFile + cbBuf) >= pFlatImage->Node.cbPrimary) + cbBuf = pFlatImage->Node.cbPrimary - offFile; + + /* + * Aligned read? + */ + int rc2; + if ( !(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF) + && !(cbBuf & VBOXFUSE_MIN_SIZE_MASK_OFF)) + rc2 = VDRead(pFlatImage->pDisk, offFile, pbBuf, cbBuf); + else + { + /* + * Unaligned read - lots of extra work. + */ + uint8_t abBlock[VBOXFUSE_MIN_SIZE]; + if (((offFile + cbBuf) & VBOXFUSE_MIN_SIZE_MASK_BLK) == (offFile & VBOXFUSE_MIN_SIZE_MASK_BLK)) + { + /* a single partial block. */ + rc2 = VDRead(pFlatImage->pDisk, offFile & VBOXFUSE_MIN_SIZE_MASK_BLK, abBlock, VBOXFUSE_MIN_SIZE); + if (RT_SUCCESS(rc2)) + memcpy(pbBuf, &abBlock[offFile & VBOXFUSE_MIN_SIZE_MASK_OFF], cbBuf); + } + else + { + /* read unaligned head. */ + rc2 = VINF_SUCCESS; + if (offFile & VBOXFUSE_MIN_SIZE_MASK_OFF) + { + rc2 = VDRead(pFlatImage->pDisk, offFile & VBOXFUSE_MIN_SIZE_MASK_BLK, abBlock, VBOXFUSE_MIN_SIZE); + if (RT_SUCCESS(rc2)) + { + size_t cbCopy = VBOXFUSE_MIN_SIZE - (offFile & VBOXFUSE_MIN_SIZE_MASK_OFF); + memcpy(pbBuf, &abBlock[offFile & VBOXFUSE_MIN_SIZE_MASK_OFF], cbCopy); + pbBuf += cbCopy; + offFile += cbCopy; + cbBuf -= cbCopy; + } + } + + /* read the middle. */ + Assert(!(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF)); + if (cbBuf >= VBOXFUSE_MIN_SIZE && RT_SUCCESS(rc2)) + { + size_t cbRead = cbBuf & VBOXFUSE_MIN_SIZE_MASK_BLK; + rc2 = VDRead(pFlatImage->pDisk, offFile, pbBuf, cbRead); + if (RT_SUCCESS(rc2)) + { + pbBuf += cbRead; + offFile += cbRead; + cbBuf -= cbRead; + } + } + + /* unaligned tail read. */ + Assert(cbBuf < VBOXFUSE_MIN_SIZE); + Assert(!(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF)); + if (cbBuf && RT_SUCCESS(rc2)) + { + rc2 = VDRead(pFlatImage->pDisk, offFile, abBlock, VBOXFUSE_MIN_SIZE); + if (RT_SUCCESS(rc2)) + memcpy(pbBuf, &abBlock[0], cbBuf); + } + } + } + + /* convert the return code */ + if (RT_SUCCESS(rc2)) + rc = cbBuf; + else + rc = -RTErrConvertToErrno(rc2); + } + + vboxfuseNodeUnlock(&pFlatImage->Node); + return rc; + } + + case VBOXFUSETYPE_CONTROL_PIPE: + return -ENOTSUP; + + default: + AssertMsgFailed(("%s\n", pszPath)); + return -EDOOFUS; + } +} + + +/** @copydoc fuse_operations::write */ +static int vboxfuseOp_write(const char *pszPath, const char *pbBuf, size_t cbBuf, + off_t offFile, struct fuse_file_info *pInfo) +{ + /* paranoia */ + AssertReturn((int)cbBuf >= 0, -EINVAL); + AssertReturn((unsigned)cbBuf == cbBuf, -EINVAL); + AssertReturn(offFile >= 0, -EINVAL); + AssertReturn((off_t)(offFile + cbBuf) >= offFile, -EINVAL); + + PVBOXFUSENODE pNode = (PVBOXFUSENODE)(uintptr_t)pInfo->fh; + AssertPtr(pNode); + switch (pNode->enmType) + { + case VBOXFUSETYPE_DIRECTORY: + return -ENOTSUP; + + case VBOXFUSETYPE_FLAT_IMAGE: + { + PVBOXFUSEFLATIMAGE pFlatImage = (PVBOXFUSEFLATIMAGE)(uintptr_t)pInfo->fh; + LogFlow(("vboxfuseOp_write: offFile=%#llx cbBuf=%#zx pszPath=\"%s\"\n", (uint64_t)offFile, cbBuf, pszPath)); + vboxfuseNodeLock(&pFlatImage->Node); + + int rc; + if ((off_t)(offFile + cbBuf) < offFile) + rc = -EINVAL; + else if (offFile >= pFlatImage->Node.cbPrimary) + rc = 0; + else if (!cbBuf) + rc = 0; + else + { + /* Adjust for EOF. */ + if ((off_t)(offFile + cbBuf) >= pFlatImage->Node.cbPrimary) + cbBuf = pFlatImage->Node.cbPrimary - offFile; + + /* + * Aligned write? + */ + int rc2; + if ( !(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF) + && !(cbBuf & VBOXFUSE_MIN_SIZE_MASK_OFF)) + rc2 = VDWrite(pFlatImage->pDisk, offFile, pbBuf, cbBuf); + else + { + /* + * Unaligned write - lots of extra work. + */ + uint8_t abBlock[VBOXFUSE_MIN_SIZE]; + if (((offFile + cbBuf) & VBOXFUSE_MIN_SIZE_MASK_BLK) == (offFile & VBOXFUSE_MIN_SIZE_MASK_BLK)) + { + /* a single partial block. */ + rc2 = VDRead(pFlatImage->pDisk, offFile & VBOXFUSE_MIN_SIZE_MASK_BLK, abBlock, VBOXFUSE_MIN_SIZE); + if (RT_SUCCESS(rc2)) + { + memcpy(&abBlock[offFile & VBOXFUSE_MIN_SIZE_MASK_OFF], pbBuf, cbBuf); + /* Update the block */ + rc2 = VDWrite(pFlatImage->pDisk, offFile & VBOXFUSE_MIN_SIZE_MASK_BLK, abBlock, VBOXFUSE_MIN_SIZE); + } + } + else + { + /* read unaligned head. */ + rc2 = VINF_SUCCESS; + if (offFile & VBOXFUSE_MIN_SIZE_MASK_OFF) + { + rc2 = VDRead(pFlatImage->pDisk, offFile & VBOXFUSE_MIN_SIZE_MASK_BLK, abBlock, VBOXFUSE_MIN_SIZE); + if (RT_SUCCESS(rc2)) + { + size_t cbCopy = VBOXFUSE_MIN_SIZE - (offFile & VBOXFUSE_MIN_SIZE_MASK_OFF); + memcpy(&abBlock[offFile & VBOXFUSE_MIN_SIZE_MASK_OFF], pbBuf, cbCopy); + pbBuf += cbCopy; + offFile += cbCopy; + cbBuf -= cbCopy; + rc2 = VDWrite(pFlatImage->pDisk, offFile & VBOXFUSE_MIN_SIZE_MASK_BLK, abBlock, VBOXFUSE_MIN_SIZE); + } + } + + /* write the middle. */ + Assert(!(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF)); + if (cbBuf >= VBOXFUSE_MIN_SIZE && RT_SUCCESS(rc2)) + { + size_t cbWrite = cbBuf & VBOXFUSE_MIN_SIZE_MASK_BLK; + rc2 = VDWrite(pFlatImage->pDisk, offFile, pbBuf, cbWrite); + if (RT_SUCCESS(rc2)) + { + pbBuf += cbWrite; + offFile += cbWrite; + cbBuf -= cbWrite; + } + } + + /* unaligned tail write. */ + Assert(cbBuf < VBOXFUSE_MIN_SIZE); + Assert(!(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF)); + if (cbBuf && RT_SUCCESS(rc2)) + { + rc2 = VDRead(pFlatImage->pDisk, offFile, abBlock, VBOXFUSE_MIN_SIZE); + if (RT_SUCCESS(rc2)) + { + memcpy(&abBlock[0], pbBuf, cbBuf); + rc2 = VDWrite(pFlatImage->pDisk, offFile, abBlock, VBOXFUSE_MIN_SIZE); + } + } + } + } + + /* convert the return code */ + if (RT_SUCCESS(rc2)) + rc = cbBuf; + else + rc = -RTErrConvertToErrno(rc2); + } + + vboxfuseNodeUnlock(&pFlatImage->Node); + return rc; + } + + case VBOXFUSETYPE_CONTROL_PIPE: + return -ENOTSUP; + + default: + AssertMsgFailed(("%s\n", pszPath)); + return -EDOOFUS; + } +} + + +/** + * The FUSE operations. + * + * @remarks We'll initialize this manually since we cannot use C99 style + * initialzer designations in C++ (yet). + */ +static struct fuse_operations g_vboxfuseOps; + + + +int main(int argc, char **argv) +{ + /* + * Initialize the runtime and VD. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "VBoxFUSE: RTR3InitExe failed, rc=%Rrc\n", rc); + return 1; + } + RTPrintf("VBoxFUSE: Hello...\n"); + rc = VDInit(); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "VBoxFUSE: VDInit failed, rc=%Rrc\n", rc); + return 1; + } + + /* + * Initializes the globals and populate the file hierarchy. + */ + rc = vboxfuseDirCreate("/", NULL); + if (RT_SUCCESS(rc)) + rc = vboxfuseDirCreate("/FlattenedImages", NULL); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "VBoxFUSE: vboxfuseDirCreate failed, rc=%Rrc\n", rc); + return 1; + } + + /* + * Initialize the g_vboxfuseOps. (C++ sucks!) + */ + memset(&g_vboxfuseOps, 0, sizeof(g_vboxfuseOps)); + g_vboxfuseOps.getattr = vboxfuseOp_getattr; + g_vboxfuseOps.opendir = vboxfuseOp_opendir; + g_vboxfuseOps.readdir = vboxfuseOp_readdir; + g_vboxfuseOps.releasedir = vboxfuseOp_releasedir; + g_vboxfuseOps.symlink = vboxfuseOp_symlink; + g_vboxfuseOps.open = vboxfuseOp_open; + g_vboxfuseOps.read = vboxfuseOp_read; + g_vboxfuseOps.write = vboxfuseOp_write; + g_vboxfuseOps.release = vboxfuseOp_release; + + /* + * Hand control over to libfuse. + */ + +#if 0 + /** @todo multithreaded fun. */ +#else + rc = fuse_main(argc, argv, &g_vboxfuseOps, NULL); +#endif + RTPrintf("VBoxFUSE: fuse_main -> %d\n", rc); + return rc; +} + diff --git a/src/VBox/ImageMounter/vboximg-mount/Makefile.kmk b/src/VBox/ImageMounter/vboximg-mount/Makefile.kmk new file mode 100644 index 00000000..5f80667a --- /dev/null +++ b/src/VBox/ImageMounter/vboximg-mount/Makefile.kmk @@ -0,0 +1,65 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the vboximg-mount Program. + +# +# Copyright (C) 2006-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. +# + + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +ifeq ($(KBUILD_TARGET),darwin) + VBOX_PATH_FUSE ?= $(lastword $(sort $(wildcard $(KBUILD_DEVTOOLS_TRG)/osxfuse/v*))) + ifneq ($(VBOX_PATH_FUSE),) + LIB_FUSE := $(VBOX_PATH_FUSE)/lib/libosxfuse.dylib + LIBPATH_FUSE := $(VBOX_PATH_FUSE)/lib + FUSE_INCS := $(VBOX_PATH_FUSE)/include/osxfuse + else + LIB_FUSE := /usr/local/lib/libosxfuse.dylib + LIBPATH_FUSE := /usr/local/lib + FUSE_INCS := /usr/local/include/osxfuse \ + /usr/local/include + endif +else + LIB_FUSE := fuse + FUSE_INCS := /usr/include +endif + +# +# vboximg-mount - Disk Image Flatting FUSE Program. +# +PROGRAMS += vboximg-mount + +vboximg-mount_TEMPLATE = VBOXMAINCLIENTEXE +vboximg-mount_DEFS.darwin = __FreeBSD_==10 +vboximg-mount_DEFS = _FILE_OFFSET_BITS=64 + +vboximg-mount_SOURCES = \ + vboximg-mount.cpp \ + vboximg-mount.h \ + vboximgCrypto.cpp \ + vboximgCrypto.h \ + vboximgMedia.cpp \ + vboximgMedia.h \ + vboximgOpts.h \ + SelfSizingTable.h + +vboximg-mount_LIBS = \ + $(LIB_DDU) \ + $(LIB_RUNTIME) \ + $(LIB_FUSE) + +vboximg-mount_INCS = $(FUSE_INCS) +#vboximg-mount_CXXFLAGS.darwin = -std=c++11 + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/ImageMounter/vboximg-mount/SelfSizingTable.h b/src/VBox/ImageMounter/vboximg-mount/SelfSizingTable.h new file mode 100644 index 00000000..fbaf21e6 --- /dev/null +++ b/src/VBox/ImageMounter/vboximg-mount/SelfSizingTable.h @@ -0,0 +1,313 @@ +/* $Id: SelfSizingTable.h $ */ +/** @file + * vboxraw header file + */ + +/* + * Copyright (C) 2018-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. + */ + +/* SELFSIZINGTABLE + * + * An ANSI text-display oriented table, whose column widths conform to width of + * their contents. The goal is to optimize whitespace usage, so there's neither too + * much nor too little whitespace (e.g. min. necessary for optimal readability). + * + * Contents can only be added to and redisplayed, not manipulated after adding. + * + * Simple API (see example below): + * + * 1. Create table instance. + * 2. Add column definitions. + * 3. Add each row and set data for each column in a row. + * 4. Invoke the displayTable() method. + * + * Each time the table is [re]displayed its contents are [re]evaluated to determine + * the column sizes and header and data padding. + * + * Example: + * + * SELFSIZINGTABLE tbl(2); + * void *colPlanet = tbl.addCol("Planet" "%s", 1); + * void *colInhabit = tbl.addCol("Inhabitability", "%-12s = %s"); + * + * // This is an 'unrolled loop' example. More typical would be to iterate, + * // providing data content from arrays, indicies, in-place calculations, + * // databases, etc... rather than just hardcoded literals. + * + * void *row = tbl.addRow(); + * tbl.setCell(row, colPlanet, "Earth"); + * tbl.setCell(row, colInhabit, "Viability", "Decreasing"); + * row = tbl.addRow(); + * tbl.setCell(row, colPlanet, "Mars"); + * tbl.setCell(row, colInhabit, "Tolerability", "Miserable"); + * row = tbl.addRow(); + * tbl.setCell(row, colPlanet, "Neptune"); + * tbl.setCell(row, colInhabit, "Plausibility", "Forget it"); + * + * tbl.displayTable(); + * + * Planet Inhabitability + * Earth Viability = Decreasing + * Mars Tolerability = Miserable + * Neptune Plausibility = Forget it + * + * (note: + * Column headers displayed in bold red to distinguish from data) + * + */ + +#ifndef VBOX_INCLUDED_SRC_vboximg_mount_SelfSizingTable_h +#define VBOX_INCLUDED_SRC_vboximg_mount_SelfSizingTable_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/message.h> +#include <iprt/stream.h> + +#define ANSI_BOLD "\x1b[1m" /** ANSI terminal esc. seq [CSI] to switch font to bold */ +#define ANSI_BLACK "\x1b[30m" /** ANSI terminal esc. seq [CSI] to switch font to black */ +#define ANSI_RED "\x1b[31m" /** ANSI terminal esc. seq [CSI] to switch font to red */ +#define ANSI_RESET "\x1b[m" /** ANSI terminal esc. seq to reset terminal attributes mode */ + +#define HDRLABEL_MAX 30 /** Maximum column header label length (for RTStrNLen()) */ +#define COLUMN_WIDTH_MAX 256 /** Maximum width of a display column */ + +typedef class SelfSizingTable +{ + public: + SelfSizingTable(int cbDefaultPadding = 1); + ~SelfSizingTable(); + void *addCol(const char *pszHdr, const char *pszFmt, int8_t align = LEFT, int8_t padRight = 0); + void *addRow(); + void setCell(void *row, void *col, ...); + void displayTable(); + + private: + typedef struct ColDesc { + struct ColDesc *next; + char *pszHdr; + uint8_t hdrLen; + char *pszFmt; + int8_t alignment; + uint8_t cbPadRightOpt; + uint8_t cbWidestDataInCol; + } COLDESC; + + typedef struct ColData + { + struct ColData *next; + COLDESC *pColDesc; + char *pszData; + uint8_t cbData; + } COLDATA; + + typedef struct Row + { + struct Row *next; + uint32_t id; + COLDATA colDataListhead; + } ROW; + + int cbDefaultColPadding; + COLDESC colDescListhead; + ROW rowListhead; + + public: + enum Alignment /* column/cell alignment */ + { + CENTER = 0, RIGHT = 1, LEFT = -1, + }; + +} SELFSIZINGTABLE; + +SELFSIZINGTABLE::SelfSizingTable(int cbDefaultPadding) +{ + this->cbDefaultColPadding = cbDefaultPadding; + colDescListhead.next = NULL; + rowListhead.next = NULL; +} +SELFSIZINGTABLE::~SelfSizingTable() +{ + COLDESC *pColDesc = colDescListhead.next; + while (pColDesc) + { + COLDESC *pColDescNext = pColDesc->next; + RTMemFree(pColDesc->pszHdr); + RTMemFree(pColDesc->pszFmt); + delete pColDesc; + pColDesc = pColDescNext; + } + ROW *pRow = rowListhead.next; + while(pRow) + { + ROW *pRowNext = pRow->next; + COLDATA *pColData = pRow->colDataListhead.next; + while (pColData) + { + COLDATA *pColDataNext = pColData->next; + delete pColData->pszData; + delete pColData; + pColData = pColDataNext; + } + delete pRow; + pRow = pRowNext; + } +} + +void *SELFSIZINGTABLE::addCol(const char *pszHdr, const char *pszFmt, int8_t align, int8_t padRight) +{ + COLDESC *pColDescNew = new COLDESC(); + if (!pColDescNew) + { + RTMsgErrorExitFailure("out of memory"); + return NULL; + } + pColDescNew->pszHdr = RTStrDup(pszHdr); + pColDescNew->hdrLen = RTStrNLen(pszHdr, HDRLABEL_MAX); + pColDescNew->pszFmt = RTStrDup(pszFmt); + pColDescNew->alignment = align; + pColDescNew->cbPadRightOpt = padRight; + COLDESC *pColDesc = &colDescListhead; + + while (pColDesc->next) + pColDesc = pColDesc->next; + + pColDesc->next = pColDescNew; + return (void *)pColDescNew; +} + +void *SELFSIZINGTABLE::addRow() +{ + ROW *pNewRow = new Row(); + COLDESC *pColDesc = colDescListhead.next; + COLDATA *pCurColData = &pNewRow->colDataListhead; + while (pColDesc) + { + COLDATA *pNewColData = new COLDATA(); + pNewColData->pColDesc = pColDesc; + pCurColData = pCurColData->next = pNewColData; + pColDesc = pColDesc->next; + } + ROW *pRow = &rowListhead; + while (pRow->next) + pRow = pRow->next; + pRow->next = pNewRow; + return (void *)pNewRow; +} + +void SELFSIZINGTABLE::setCell(void *row, void *col, ...) +{ + ROW *pRow = (ROW *)row; + COLDESC *pColDesc = (COLDESC *)col; + va_list ap; + va_start(ap, col); + + char *pszData = new char[COLUMN_WIDTH_MAX]; + int cbData = RTStrPrintfV(pszData, COLUMN_WIDTH_MAX, pColDesc->pszFmt, ap); + COLDATA *pColData = pRow->colDataListhead.next; + while (pColData) + { + if (pColData->pColDesc == pColDesc) + { + pColData->pszData = pszData; + pColData->cbData = cbData; + break; + } + pColData = pColData->next; + } +} + +void SELFSIZINGTABLE::displayTable() +{ + /* Determine max cell (and column header) length for each column */ + + COLDESC *pColDesc = colDescListhead.next; + while (pColDesc) + { + pColDesc->cbWidestDataInCol = pColDesc->hdrLen; + pColDesc = pColDesc->next; + } + ROW *pRow = rowListhead.next; + while(pRow) + { + COLDATA *pColData = pRow->colDataListhead.next; + while (pColData) + { + pColDesc = pColData->pColDesc; + if (pColData->cbData > pColDesc->cbWidestDataInCol) + pColDesc->cbWidestDataInCol = pColData->cbData;; + pColData = pColData->next; + } + pRow = pRow->next; + } + + /* Display col headers based on actual column size w/alignment & padding */ + pColDesc = colDescListhead.next; + while (pColDesc) + { + uint8_t colWidth = pColDesc->cbWidestDataInCol; + char colHdr[colWidth + 1], *pszColHdr = (char *)colHdr; + switch (pColDesc->alignment) + { + case RIGHT: + RTStrPrintf(pszColHdr, colWidth + 1, "%*s", colWidth, pColDesc->pszHdr); + break; + case LEFT: + RTStrPrintf(pszColHdr, colWidth + 1, "%-*s", colWidth, pColDesc->pszHdr); + break; + case CENTER: + int cbPad = (colWidth - pColDesc->hdrLen) / 2; + RTStrPrintf(pszColHdr, colWidth + 1, "%*s%s%*s", cbPad, "", pColDesc->pszHdr, cbPad, ""); + } + RTPrintf(ANSI_BOLD ANSI_RED); + uint8_t cbPad = pColDesc->cbPadRightOpt ? pColDesc->cbPadRightOpt : cbDefaultColPadding; + RTPrintf("%s%*s", pszColHdr, cbPad, " "); + RTPrintf(ANSI_RESET); + pColDesc = pColDesc->next; + } + RTPrintf("\n"); + /* + * Display each of the column data items for the row + */ + pRow = rowListhead.next; + while(pRow) + { + COLDATA *pColData = pRow->colDataListhead.next; + while (pColData) + { pColDesc = pColData->pColDesc; + uint8_t colWidth = pColDesc->cbWidestDataInCol; + char aCell[colWidth + 1]; + switch (pColDesc->alignment) + { + case RIGHT: + RTStrPrintf(aCell, colWidth + 1, "%*s", colWidth, pColData->pszData); + break; + case LEFT: + RTStrPrintf(aCell, colWidth + 1, "%-*s", colWidth, pColData->pszData); + break; + case CENTER: + int cbPad = (colWidth - pColData->cbData) / 2; + RTStrPrintf(aCell, colWidth + 1, "%*s%s%*s", cbPad, "", pColData->pszData, cbPad, ""); + } + uint8_t cbPad = pColDesc->cbPadRightOpt ? pColDesc->cbPadRightOpt : this->cbDefaultColPadding; + RTPrintf("%s%*s", aCell, cbPad, " "); + pColData = pColData->next; + } + RTPrintf("\n"); + pRow = pRow->next; + } +} +#endif /* !VBOX_INCLUDED_SRC_vboximg_mount_SelfSizingTable_h */ diff --git a/src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp b/src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp new file mode 100644 index 00000000..8fdce614 --- /dev/null +++ b/src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp @@ -0,0 +1,1523 @@ +/* $Id: vboximg-mount.cpp $ */ +/** @file + * vboximg-mount - Disk Image Flattening FUSE Program. + */ + +/* + * Copyright (C) 2009-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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ + +#define LOG_GROUP LOG_GROUP_DEFAULT /** @todo log group */ + +#define FUSE_USE_VERSION 27 +#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined(RT_OS_FEEBSD) +# define UNIX_DERIVATIVE +#endif +#define MAX_READERS (INT32_MAX / 32) +#include <fuse.h> +#ifdef UNIX_DERIVATIVE +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <libgen.h> +#include <unistd.h> +#include <math.h> +#include <cstdarg> +#include <sys/stat.h> +#endif +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_LINUX) +# include <sys/param.h> +# undef PVM /* Blasted old BSD mess still hanging around darwin. */ +#endif +#ifdef RT_OS_LINUX +# include <linux/fs.h> +# include <linux/hdreg.h> +#endif +#include <VirtualBox_XPCOM.h> +#include <VBox/com/VirtualBox.h> +#include <VBox/vd.h> +#include <VBox/vd-ifs.h> +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/NativeEventQueue.h> +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/errorprint.h> +#include <VBox/vd-plugin.h> +#include <iprt/initterm.h> +#include <iprt/assert.h> +#include <iprt/message.h> +#include <iprt/critsect.h> +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/types.h> +#include <iprt/path.h> +#include <iprt/utf16.h> +#include <iprt/base64.h> + +#include "vboximg-mount.h" +#include "vboximgCrypto.h" +#include "vboximgMedia.h" +#include "SelfSizingTable.h" +#include "vboximgOpts.h" + +using namespace com; + +enum { + USAGE_FLAG, +}; + +enum { PARTITION_TABLE_MBR = 1, PARTITION_TABLE_GPT = 2 }; + +#define VBOX_EXTPACK "Oracle VM VirtualBox Extension Pack" +#define GPT_PTABLE_SIZE 32 * BLOCKSIZE /** Max size we to read for GPT partition table */ +#define MBR_PARTITIONS_MAX 4 /** Fixed number of partitions in Master Boot Record */ +#define BASENAME_MAX 256 /** Maximum name for the basename of a path (for RTStrNLen()*/ +#define VBOXIMG_PARTITION_MAX 256 /** How much storage to allocate to store partition info */ +#define PARTITION_NAME_MAX 72 /** Maximum partition name size (accomodates GPT partition name) */ +#define BLOCKSIZE 512 /** Commonly used disk block size */ +#define DOS_BOOT_RECORD_SIGNATURE 0xaa55 /** MBR and EBR (partition table) signature [EOT boundary] */ +#define NULL_BOOT_RECORD_SIGNATURE 0x0000 /** MBR or EBR null signature value */ +#define MAX_UUID_LEN 256 /** Max length of a UUID */ +#define LBA(n) (n * BLOCKSIZE) +#define VD_SECTOR_SIZE 512 /** Virtual disk sector/blocksize */ +#define VD_SECTOR_MASK (VD_SECTOR_SIZE - 1) /** Masks off a blocks worth of data */ +#define VD_SECTOR_OUT_OF_BOUNDS_MASK (~UINT64_C(VD_SECTOR_MASK)) /** Masks the overflow of a blocks worth of data */ +#define VERBOSE g_vboximgOpts.fVerbose +#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100) + +#define PARTTYPE_IS_NULL(parType) ((uint8_t)parType == 0x00) +#define PARTTYPE_IS_GPT(parType) ((uint8_t)parType == 0xee) +#define PARTTYPE_IS_EXT(parType) (( (uint8_t)parType) == 0x05 /* Extended */ \ + || ((uint8_t)parType) == 0x0f /* W95 Extended (LBA) */ \ + || ((uint8_t)parType) == 0x85) /* Linux Extended */ + +#define SAFENULL(strPtr) (strPtr ? strPtr : "") +#define CSTR(arg) Utf8Str(arg).c_str() /* Converts XPCOM string type to C string type */ + +static struct fuse_operations g_vboximgOps; /** FUSE structure that defines allowed ops for this FS */ + +/* Global variables */ + +static PVDISK g_pVDisk; /** Handle for Virtual Disk in contet */ +static char *g_pszDiskUuid; /** UUID of image (if known, otherwise NULL) */ +static off_t g_vDiskOffset; /** Biases r/w from start of VD */ +static off_t g_vDiskSize; /** Limits r/w length for VD */ +static int32_t g_cReaders; /** Number of readers for VD */ +static int32_t g_cWriters; /** Number of writers for VD */ +static RTFOFF g_cbEntireVDisk; /** Size of VD */ +static PVDINTERFACE g_pVdIfs; /** @todo Remove when VD I/O becomes threadsafe */ +static VDINTERFACETHREADSYNC g_VDIfThreadSync; /** @todo Remove when VD I/O becomes threadsafe */ +static RTCRITSECT g_vdioLock; /** @todo Remove when VD I/O becomes threadsafe */ +static uint16_t g_lastPartNbr; /** Last partition number found in MBR + EBR chain */ +static bool g_fGPT; /** True if GPT type partition table was found */ +static char *g_pszImageName; /** Base filename for current VD image */ +static char *g_pszImagePath; /** Full path to current VD image */ +static char *g_pszBaseImagePath; /** Base image known after parsing */ +static char *g_pszBaseImageName; /** Base image known after parsing */ +static uint32_t g_cImages; /** Number of images in diff chain */ + +/* Table entry containing partition info parsed out of GPT or MBR and EBR chain of specified VD */ + +typedef struct +{ + int idxPartition; /** partition number */ + char *pszName; + off_t offPartition; /** partition offset from start of disk, in bytes */ + uint64_t cbPartition; /** partition size in bytes */ + uint8_t fBootable; /** partition bootable */ + union + { + uint8_t legacy; /** partition type MBR/EBR */ + uint128_t gptGuidTypeSpecifier; /** partition type GPT */ + } partitionType; /** uint8_t for MBR/EBR (legacy) and GUID for GPT */ + union + { + MBRPARTITIONENTRY mbrEntry; /** MBR (also EBR partition entry) */ + GPTPARTITIONENTRY gptEntry; /** GPT partition entry */ + } partitionEntry; +} PARTITIONINFO; + +PARTITIONINFO g_aParsedPartitionInfo[VBOXIMG_PARTITION_MAX + 1]; /* Note: Element 0 reserved for EntireDisk partitionEntry */ + +VBOXIMGOPTS g_vboximgOpts; + +#define OPTION(fmt, pos, val) { fmt, offsetof(struct vboximgOpts, pos), val } + +static struct fuse_opt vboximgOptDefs[] = { + OPTION("--image=%s", pszImageUuidOrPath, 0), + OPTION("-i %s", pszImageUuidOrPath, 0), + OPTION("--rw", fRW, 1), + OPTION("--root", fAllowRoot, 0), + OPTION("--vm=%s", pszVm, 0), + OPTION("--partition=%d", idxPartition, 1), + OPTION("-p %d", idxPartition, 1), + OPTION("--offset=%d", offset, 1), + OPTION("-o %d", offset, 1), + OPTION("--size=%d", size, 1), + OPTION("-s %d", size, 1), + OPTION("-l", fList, 1), + OPTION("--list", fList, 1), + OPTION("--verbose", fVerbose, 1), + OPTION("-v", fVerbose, 1), + OPTION("--wide", fWide, 1), + OPTION("-w", fWide, 1), + OPTION("-lv", fVerboseList, 1), + OPTION("-vl", fVerboseList, 1), + OPTION("-lw", fWideList, 1), + OPTION("-wl", fWideList, 1), + OPTION("-h", fBriefUsage, 1), + FUSE_OPT_KEY("--help", USAGE_FLAG), + FUSE_OPT_KEY("-vm", FUSE_OPT_KEY_NONOPT), + FUSE_OPT_END +}; + +typedef struct IMAGELIST +{ + struct IMAGELIST *next; + struct IMAGELIST *prev; + ComPtr<IToken> pLockToken; + bool fWriteable; + ComPtr<IMedium> pImage; + Bstr pImageName; + Bstr pImagePath; +} IMAGELIST; + +IMAGELIST listHeadLockList; /* flink & blink intentionally left NULL */ + +static void +briefUsage() +{ + RTPrintf("usage: vboximg-mount [options] <mount point directory path>\n\n" + "vboximg-mount options:\n\n" + " [ { -i | --image= } <specifier> ] VirtualBox disk base or snapshot image,\n" + " specified by UUID, or fully-qualified path\n" + "\n" + " [ { -l | --list } ] If --image specified, list its partitions,\n" + " otherwise, list registered VMs and their\n" + " attached virtual HDD disk media. In verbose\n" + " mode, VM/media list will be long format,\n" + " i.e. including snapshot images and paths.\n" + "\n" + " [ { -w | --wide } ] List media in wide / tabular format\n" + " (reduces vertical scrolling but requires\n" + " wider than standard 80 column window\n)" + "\n" + " [ --vm=UUID ] Restrict media list to specified vm.\n" + "\n" + " [ { -p | --partition= } <part #> ] Expose only specified partition via FUSE.\n" + "\n" + " [ { -o | --offset= } <byte #> ] Bias disk I/O by offset from disk start.\n" + " (incompatible with -p, --partition)\n" + "\n" + " [ { -s | --size=<bytes> } ] Specify size of mounted disk.\n" + " (incompatible with -p, --partition)\n" + "\n" + " [ --rw ] Make image writeable (default = readonly)\n" + "\n" + " [ --root ] Same as -o allow_root.\n" + "\n" + " [ { -v | --verbose } ] Log extra information.\n" + "\n" + " [ -o opt[,opt...]] FUSE mount options.\n" + "\n" + " [ { -h | -? } ] Display short usage info (no FUSE options).\n" + " [ --help ] Display long usage info (incl. FUSE opts).\n\n" + ); + RTPrintf("\n" + "vboximg-mount is a utility to make VirtualBox disk images available to the host\n" + "operating system in a root or non-root accessible way. The user determines the\n" + "historical representation of the disk by choosing either the base image or a\n" + "snapshot, to establish the desired level of currency of the mounted disk.\n" + "\n" + "The disk image is mounted through this utility inside a FUSE-based filesystem\n" + "that overlays the user-provided mount point. The FUSE filesystem presents a\n" + "a directory that contains two files: an HDD pseudo device node and a symbolic\n" + "link. The device node is named 'vhdd' and is the access point to the synthesized\n" + "state of the virtual disk. It is the entity that can be mounted or otherwise\n" + "accessed through the host OS. The symbolic link is given the same name as the\n" + "base image, as determined from '--image' option argument. The link equates\n" + "to the specified image's location (path).\n" + "\n" + "If the user provides a base image UUID/path with the --image option, only\n" + "the base image will be exposed via vhdd, disregarding any snapshots.\n" + "Alternatively, if a snapshot (e.g. disk differencing image) is provided,\n" + "the chain of snapshots is calculated from that \"leaf\" snapshot\n" + "to the base image and the whole chain of images is merged to form the exposed\n" + "state of the FUSE-mounted disk.\n" + "\n" + + ); +} + +static int +vboximgOptHandler(void *data, const char *arg, int optKey, struct fuse_args *outargs) +{ + NOREF(data); + NOREF(arg); + NOREF(optKey); + NOREF(outargs); + + /* + * Apparently this handler is only called for arguments FUSE can't parse, + * and arguments that don't result in variable assignment such as "USAGE" + * In this impl. that's always deemed a parsing error. + */ + if (*arg != '-') /* could be user's mount point */ + return 1; + + return -1; +} + +/** @copydoc fuse_operations::open */ +static int vboximgOp_open(const char *pszPath, struct fuse_file_info *pInfo) +{ + RT_NOREF(pszPath, pInfo);; + LogFlowFunc(("pszPath=%s\n", pszPath)); + uint32_t notsup = 0; + int rc = 0; + +#ifdef UNIX_DERIVATIVE +# ifdef RT_OS_DARWIN + notsup = O_APPEND | O_NONBLOCK | O_SYMLINK | O_NOCTTY | O_SHLOCK | O_EXLOCK | + O_ASYNC | O_CREAT | O_TRUNC | O_EXCL | O_EVTONLY; +# elif defined(RT_OS_LINUX) + notsup = O_APPEND | O_ASYNC | O_DIRECT | O_NOATIME | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK; + /* | O_LARGEFILE | O_SYNC | ? */ +# elif defined(RT_OS_FREEBSD) + notsup = O_APPEND | O_ASYNC | O_DIRECT | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK; + /* | O_LARGEFILE | O_SYNC | ? */ +# endif +#else +# error "Port me" +#endif + +if (pInfo->flags & notsup) + rc -EINVAL; + +#ifdef UNIX_DERIVATIVE + if ((pInfo->flags & O_ACCMODE) == O_ACCMODE) + rc = -EINVAL; +# ifdef O_DIRECTORY + if (pInfo->flags & O_DIRECTORY) + rc = -ENOTDIR; +# endif +#endif + + if (RT_FAILURE(rc)) + { + LogFlowFunc(("rc=%d \"%s\"\n", rc, pszPath)); + return rc; + } + + int fWriteable = (pInfo->flags & O_ACCMODE) == O_WRONLY + || (pInfo->flags & O_ACCMODE) == O_RDWR; + if (g_cWriters) + rc = -ETXTBSY; + else + { + if (fWriteable) + g_cWriters++; + else + { + if (g_cReaders + 1 > MAX_READERS) + rc = -EMLINK; + else + g_cReaders++; + } + } + LogFlowFunc(("rc=%d \"%s\"\n", rc, pszPath)); + return rc; + +} + + +/** @todo Remove when VD I/O becomes threadsafe */ +static DECLCALLBACK(int) vboximgThreadStartRead(void *pvUser) +{ + PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser; + return RTCritSectEnter(vdioLock); +} + +static DECLCALLBACK(int) vboximgThreadFinishRead(void *pvUser) +{ + PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser; + return RTCritSectLeave(vdioLock); +} + +static DECLCALLBACK(int) vboximgThreadStartWrite(void *pvUser) +{ + PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser; + return RTCritSectEnter(vdioLock); +} + +static DECLCALLBACK(int) vboximgThreadFinishWrite(void *pvUser) +{ + PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser; + return RTCritSectLeave(vdioLock); +} +/** @todo (end of to do section) */ + +/** @copydoc fuse_operations::release */ +static int vboximgOp_release(const char *pszPath, struct fuse_file_info *pInfo) +{ + NOREF(pszPath); + + LogFlowFunc(("pszPath=%s\n", pszPath)); + + if ( (pInfo->flags & O_ACCMODE) == O_WRONLY + || (pInfo->flags & O_ACCMODE) == O_RDWR) + { + g_cWriters--; + Assert(g_cWriters >= 0); + } + else if ((pInfo->flags & O_ACCMODE) == O_RDONLY) + { + g_cReaders--; + Assert(g_cReaders >= 0); + } + else + AssertFailed(); + + LogFlowFunc(("\"%s\"\n", pszPath)); + return 0; +} + +/** + * VD read Sanitizer taking care of unaligned accesses. + * + * @return VBox bootIndicator code. + * @param pDisk VD disk container. + * @param off Offset to start reading from. + * @param pvBuf Pointer to the buffer to read into. + * @param cbRead Amount of bytes to read. + */ +static int vdReadSanitizer(PVDISK pDisk, uint64_t off, void *pvBuf, size_t cbRead) +{ + int rc; + + uint64_t const cbMisalignmentOfStart = off & VD_SECTOR_MASK; + uint64_t const cbMisalignmentOfEnd = (off + cbRead) & VD_SECTOR_MASK; + + if (cbMisalignmentOfStart + cbMisalignmentOfEnd == 0) /* perfectly aligned request; just read it and done */ + rc = VDRead(pDisk, off, pvBuf, cbRead); + else + { + uint8_t *pbBuf = (uint8_t *)pvBuf; + uint8_t abBuf[VD_SECTOR_SIZE]; + + /* If offset not @ sector boundary, read whole sector, then copy unaligned + * bytes (requested by user), only up to sector boundary, into user's buffer + */ + if (cbMisalignmentOfStart) + { + rc = VDRead(pDisk, off - cbMisalignmentOfStart, abBuf, VD_SECTOR_SIZE); + if (RT_SUCCESS(rc)) + { + size_t const cbPartial = RT_MIN(VD_SECTOR_SIZE - cbMisalignmentOfStart, cbRead); + memcpy(pbBuf, &abBuf[cbMisalignmentOfStart], cbPartial); + pbBuf += cbPartial; + off += cbPartial; /* Beginning of next sector or EOD */ + cbRead -= cbPartial; /* # left to read */ + } + } + else /* user's offset already aligned, did nothing */ + rc = VINF_SUCCESS; + + /* Read remaining aligned sectors, deferring any tail-skewed bytes */ + if (RT_SUCCESS(rc) && cbRead >= VD_SECTOR_SIZE) + { + Assert(!(off % VD_SECTOR_SIZE)); + + size_t cbPartial = cbRead - cbMisalignmentOfEnd; + Assert(!(cbPartial % VD_SECTOR_SIZE)); + rc = VDRead(pDisk, off, pbBuf, cbPartial); + if (RT_SUCCESS(rc)) + { + pbBuf += cbPartial; + off += cbPartial; + cbRead -= cbPartial; + } + } + + /* Unaligned buffered read of tail. */ + if (RT_SUCCESS(rc) && cbRead) + { + Assert(cbRead == cbMisalignmentOfEnd); + Assert(cbRead < VD_SECTOR_SIZE); + Assert(!(off % VD_SECTOR_SIZE)); + + rc = VDRead(pDisk, off, abBuf, VD_SECTOR_SIZE); + if (RT_SUCCESS(rc)) + memcpy(pbBuf, abBuf, cbRead); + } + } + + if (RT_FAILURE(rc)) + { + int sysrc = -RTErrConvertToErrno(rc); + LogFlowFunc(("error: %s (vbox err: %d)\n", strerror(sysrc), rc)); + rc = sysrc; + } + return cbRead; +} + +/** + * VD write Sanitizer taking care of unaligned accesses. + * + * @return VBox bootIndicator code. + * @param pDisk VD disk container. + * @param off Offset to start writing to. + * @param pvSrc Pointer to the buffer to read from. + * @param cbWrite Amount of bytes to write. + */ +static int vdWriteSanitizer(PVDISK pDisk, uint64_t off, const void *pvSrc, size_t cbWrite) +{ + uint8_t const *pbSrc = (uint8_t const *)pvSrc; + uint8_t abBuf[4096]; + int rc; + int cbRemaining = cbWrite; + + /* + * Take direct route if the request is sector aligned. + */ + uint64_t const cbMisalignmentOfStart = off & VD_SECTOR_MASK; + size_t const cbMisalignmentOfEnd = (off + cbWrite) & VD_SECTOR_MASK; + if (!cbMisalignmentOfStart && !cbMisalignmentOfEnd) + { + rc = VDWrite(pDisk, off, pbSrc, cbWrite); + do + { + size_t cbThisWrite = RT_MIN(cbWrite, sizeof(abBuf)); + rc = VDWrite(pDisk, off, memcpy(abBuf, pbSrc, cbThisWrite), cbThisWrite); + if (RT_SUCCESS(rc)) + { + pbSrc += cbThisWrite; + off += cbThisWrite; + cbRemaining -= cbThisWrite; + } + else + break; + } while (cbRemaining > 0); + } + else + { + /* + * Unaligned buffered read+write of head. Aligns the offset. + */ + if (cbMisalignmentOfStart) + { + rc = VDRead(pDisk, off - cbMisalignmentOfStart, abBuf, VD_SECTOR_SIZE); + if (RT_SUCCESS(rc)) + { + size_t const cbPartial = RT_MIN(VD_SECTOR_SIZE - cbMisalignmentOfStart, cbWrite); + memcpy(&abBuf[cbMisalignmentOfStart], pbSrc, cbPartial); + rc = VDWrite(pDisk, off - cbMisalignmentOfStart, abBuf, VD_SECTOR_SIZE); + if (RT_SUCCESS(rc)) + { + pbSrc += cbPartial; + off += cbPartial; + cbRemaining -= cbPartial; + } + } + } + else + rc = VINF_SUCCESS; + + /* + * Aligned direct write. + */ + if (RT_SUCCESS(rc) && cbWrite >= VD_SECTOR_SIZE) + { + Assert(!(off % VD_SECTOR_SIZE)); + size_t cbPartial = cbWrite - cbMisalignmentOfEnd; + Assert(!(cbPartial % VD_SECTOR_SIZE)); + rc = VDWrite(pDisk, off, pbSrc, cbPartial); + if (RT_SUCCESS(rc)) + { + pbSrc += cbPartial; + off += cbPartial; + cbRemaining -= cbPartial; + } + } + + /* + * Unaligned buffered read + write of tail. + */ + if ( RT_SUCCESS(rc) && cbWrite > 0) + { + Assert(cbWrite == cbMisalignmentOfEnd); + Assert(cbWrite < VD_SECTOR_SIZE); + Assert(!(off % VD_SECTOR_SIZE)); + rc = VDRead(pDisk, off, abBuf, VD_SECTOR_SIZE); + if (RT_SUCCESS(rc)) + { + memcpy(abBuf, pbSrc, cbWrite); + rc = VDWrite(pDisk, off, abBuf, VD_SECTOR_SIZE); + } + } + } + if (RT_FAILURE(rc)) + { + int sysrc = -RTErrConvertToErrno(rc); + LogFlowFunc(("error: %s (vbox err: %d)\n", strerror(sysrc), rc)); + return sysrc; + } + return cbWrite - cbRemaining; +} + + +/** @copydoc fuse_operations::read */ +static int vboximgOp_read(const char *pszPath, char *pbBuf, size_t cbBuf, + off_t offset, struct fuse_file_info *pInfo) +{ + NOREF(pszPath); + NOREF(pInfo); + + LogFlowFunc(("my offset=%#llx size=%#zx path=\"%s\"\n", (uint64_t)offset, cbBuf, pszPath)); + + AssertReturn(offset >= 0, -EINVAL); + AssertReturn((int)cbBuf >= 0, -EINVAL); + AssertReturn((unsigned)cbBuf == cbBuf, -EINVAL); + + AssertReturn(offset + g_vDiskOffset >= 0, -EINVAL); + int64_t adjOff = offset + g_vDiskOffset; + + int rc = 0; + if ((off_t)(adjOff + cbBuf) < adjOff) + rc = -EINVAL; + else if (adjOff >= g_vDiskSize) + return 0; + else if (!cbBuf) + return 0; + + if (rc >= 0) + rc = vdReadSanitizer(g_pVDisk, adjOff, pbBuf, cbBuf); + if (rc < 0) + LogFlowFunc(("%s\n", strerror(rc))); + return rc; +} + +/** @copydoc fuse_operations::write */ +static int vboximgOp_write(const char *pszPath, const char *pbBuf, size_t cbBuf, + off_t offset, struct fuse_file_info *pInfo) +{ + NOREF(pszPath); + NOREF(pInfo); + + LogFlowFunc(("offset=%#llx size=%#zx path=\"%s\"\n", (uint64_t)offset, cbBuf, pszPath)); + + AssertReturn(offset >= 0, -EINVAL); + AssertReturn((int)cbBuf >= 0, -EINVAL); + AssertReturn((unsigned)cbBuf == cbBuf, -EINVAL); + AssertReturn(offset + g_vDiskOffset >= 0, -EINVAL); + int64_t adjOff = offset + g_vDiskOffset; + + int rc = 0; + if (!g_vboximgOpts.fRW) { + LogFlowFunc(("WARNING: vboximg-mount (FUSE FS) --rw option not specified\n" + " (write operation ignored w/o error!)\n")); + return cbBuf; + } else if ((off_t)(adjOff + cbBuf) < adjOff) + rc = -EINVAL; + else if (offset >= g_vDiskSize) + return 0; + else if (!cbBuf) + return 0; + + if (rc >= 0) + rc = vdWriteSanitizer(g_pVDisk, adjOff, pbBuf, cbBuf); + if (rc < 0) + LogFlowFunc(("%s\n", strerror(rc))); + + return rc; +} + +/** @copydoc fuse_operations::getattr */ +static int +vboximgOp_getattr(const char *pszPath, struct stat *stbuf) +{ + int rc = 0; + + LogFlowFunc(("pszPath=%s, stat(\"%s\")\n", pszPath, g_pszImagePath)); + + memset(stbuf, 0, sizeof(struct stat)); + + if (RTStrCmp(pszPath, "/") == 0) + { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } + else if (RTStrCmp(pszPath + 1, "vhdd") == 0) + { + rc = stat(g_pszImagePath, stbuf); + if (rc < 0) + return rc; + /* + * st_size represents the size of the FUSE FS-mounted portion of the disk. + * By default it is the whole disk, but can be a partition or specified + * (or overridden) directly by the { -s | --size } option on the command line. + */ + stbuf->st_size = g_vDiskSize; + stbuf->st_nlink = 1; + } + else if (RTStrNCmp(pszPath + 1, g_pszImageName, strlen(g_pszImageName)) == 0) + { + /* When the disk is partitioned, the symbolic link named from `basename` of + * resolved path to VBox disk image, has appended to it formatted text + * representing the offset range of the partition. + * + * $ vboximg-mount -i /stroll/along/the/path/simple_fixed_disk.vdi -p 1 /mnt/tmpdir + * $ ls /mnt/tmpdir + * simple_fixed_disk.vdi[20480:2013244928] vhdd + */ + rc = stat(g_pszImagePath, stbuf); + if (rc < 0) + return rc; + stbuf->st_size = 0; + stbuf->st_mode = S_IFLNK | 0444; + stbuf->st_nlink = 1; + stbuf->st_uid = 0; + stbuf->st_gid = 0; + } else + rc = -ENOENT; + + return rc; +} + +/** @copydoc fuse_operations::readdir */ +static int +vboximgOp_readdir(const char *pszPath, void *pvBuf, fuse_fill_dir_t pfnFiller, + off_t offset, struct fuse_file_info *pInfo) + +{ + NOREF(offset); + NOREF(pInfo); + + if (RTStrCmp(pszPath, "/") != 0) + return -ENOENT; + + /* + * mandatory '.', '..', ... + */ + pfnFiller(pvBuf, ".", NULL, 0); + pfnFiller(pvBuf, "..", NULL, 0); + + /* + * Create FUSE FS dir entry that is depicted here (and exposed via stat()) as + * a symbolic link back to the resolved path to the VBox virtual disk image, + * whose symlink name is basename that path. This is a convenience so anyone + * listing the dir can figure out easily what the vhdd FUSE node entry + * represents. + */ + + if (g_vDiskOffset == 0 && (g_vDiskSize == 0 || g_vDiskSize == g_cbEntireVDisk)) + pfnFiller(pvBuf, g_pszImageName, NULL, 0); + else + { + char tmp[BASENAME_MAX]; + RTStrPrintf(tmp, sizeof (tmp), "%s[%jd:%jd]", g_pszImageName, g_vDiskOffset, g_vDiskSize); + pfnFiller(pvBuf, tmp, NULL, 0); + } + /* + * Create entry named "vhdd", which getattr() will describe as a + * regular file, and thus will go through the open/release/read/write vectors + * to access the VirtualBox image as processed by the IRPT VD API. + */ + pfnFiller(pvBuf, "vhdd", NULL, 0); + return 0; +} + +/** @copydoc fuse_operations::readlink */ +static int +vboximgOp_readlink(const char *pszPath, char *buf, size_t size) +{ + NOREF(pszPath); + RTStrCopy(buf, size, g_pszImagePath); + return 0; +} + +uint8_t +parsePartitionTable(void) +{ + MBR_t mbr; + EBR_t ebr; + PTH_t parTblHdr; + + ASSERT(sizeof (mbr) == 512); + ASSERT(sizeof (ebr) == 512); + /* + * First entry describes entire disk as a single entity + */ + g_aParsedPartitionInfo[0].idxPartition = 0; + g_aParsedPartitionInfo[0].offPartition = 0; + g_aParsedPartitionInfo[0].cbPartition = VDGetSize(g_pVDisk, 0); + g_aParsedPartitionInfo[0].pszName = RTStrDup("EntireDisk"); + + /* + * Currently only DOS partitioned disks are supported. Ensure this one conforms + */ + int rc = vdReadSanitizer(g_pVDisk, 0, &mbr, sizeof (mbr)); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Error reading MBR block from disk\n"); + + if (mbr.signature == NULL_BOOT_RECORD_SIGNATURE) + return RTMsgErrorExitFailure("Unprt disk (null MBR signature)\n"); + + if (mbr.signature != DOS_BOOT_RECORD_SIGNATURE) + return RTMsgErrorExitFailure("Invalid MBR found on image with signature 0x%04hX\n", + mbr.signature); + /* + * Parse the four physical partition entires in the MBR (any one, and only one, can be an EBR) + */ + int idxEbrPartitionInMbr = 0; + for (int idxPartition = 1; + idxPartition <= MBR_PARTITIONS_MAX; + idxPartition++) + { + MBRPARTITIONENTRY *pMbrPartitionEntry = + &g_aParsedPartitionInfo[idxPartition].partitionEntry.mbrEntry;; + memcpy (pMbrPartitionEntry, &mbr.partitionEntry[idxPartition - 1], sizeof (MBRPARTITIONENTRY)); + + if (PARTTYPE_IS_NULL(pMbrPartitionEntry->type)) + continue; + + if (PARTTYPE_IS_EXT(pMbrPartitionEntry->type)) + { + if (idxEbrPartitionInMbr) + return RTMsgErrorExitFailure("Multiple EBRs found found in MBR\n"); + idxEbrPartitionInMbr = idxPartition; + } + + PARTITIONINFO *ppi = &g_aParsedPartitionInfo[idxPartition]; + + ppi->idxPartition = idxPartition; + ppi->offPartition = (off_t) pMbrPartitionEntry->partitionLba * BLOCKSIZE; + ppi->cbPartition = (off_t) pMbrPartitionEntry->partitionBlkCnt * BLOCKSIZE; + ppi->fBootable = pMbrPartitionEntry->bootIndicator == 0x80; + ppi->partitionType.legacy = pMbrPartitionEntry->type; + + g_lastPartNbr = idxPartition; + + if (PARTTYPE_IS_GPT(pMbrPartitionEntry->type)) + { + g_fGPT = true; + break; + } + } + + if (g_fGPT) + { + g_lastPartNbr = 2; /* from the 'protective MBR' */ + + rc = vdReadSanitizer(g_pVDisk, LBA(1), &parTblHdr, sizeof (parTblHdr)); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Error reading Partition Table Header (LBA 1) from disk\n"); + + uint8_t *pTblBuf = (uint8_t *)RTMemAlloc(GPT_PTABLE_SIZE); + + if (!pTblBuf) + return RTMsgErrorExitFailure("Out of memory\n"); + + rc = vdReadSanitizer(g_pVDisk, LBA(2), pTblBuf, GPT_PTABLE_SIZE); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Error reading Partition Table blocks from disk\n"); + + uint32_t cEntries = parTblHdr.cPartitionEntries; + uint32_t cbEntry = parTblHdr.cbPartitionEntry; + if (cEntries * cbEntry > GPT_PTABLE_SIZE) + { + RTPrintf("Partition entries exceed GPT table read from disk (pruning!)\n"); + while (cEntries * cbEntry > GPT_PTABLE_SIZE && cEntries > 0) + --cEntries; + } + uint8_t *pEntryRaw = pTblBuf; + for (uint32_t i = 0; i < cEntries; i++) + { + GPTPARTITIONENTRY *pEntry = (GPTPARTITIONENTRY *)pEntryRaw; + PARTITIONINFO *ppi = &g_aParsedPartitionInfo[g_lastPartNbr]; + memcpy(&(ppi->partitionEntry).gptEntry, pEntry, sizeof(GPTPARTITIONENTRY)); + if (!pEntry->firstLba) + break; + ppi->offPartition = pEntry->firstLba * BLOCKSIZE; + ppi->cbPartition = (pEntry->lastLba - pEntry->firstLba) * BLOCKSIZE; + ppi->fBootable = pEntry->attrFlags & (1 << GPT_LEGACY_BIOS_BOOTABLE); + ppi->partitionType.gptGuidTypeSpecifier = pEntry->partitionTypeGuid; + size_t cwName = sizeof (pEntry->partitionName) / 2; + RTUtf16LittleToUtf8Ex((PRTUTF16)pEntry->partitionName, RTSTR_MAX, &ppi->pszName, cwName, NULL); + ppi->idxPartition = g_lastPartNbr++; + pEntryRaw += cbEntry; + } + return PARTITION_TABLE_GPT; + } + + /* + * Starting with EBR located in MBR, walk EBR chain to parse the logical partition entries + */ + if (idxEbrPartitionInMbr) + { + uint32_t firstEbrLba + = g_aParsedPartitionInfo[idxEbrPartitionInMbr].partitionEntry.mbrEntry.partitionLba; + off_t firstEbrOffset = (off_t)firstEbrLba * BLOCKSIZE; + off_t chainedEbrOffset = 0; + + if (!firstEbrLba) + return RTMsgErrorExitFailure("Inconsistency for logical partition start. Aborting\n"); + + for (int idxPartition = 5; + idxPartition <= VBOXIMG_PARTITION_MAX; + idxPartition++) + { + + off_t currentEbrOffset = firstEbrOffset + chainedEbrOffset; + vdReadSanitizer(g_pVDisk, currentEbrOffset, &ebr, sizeof (ebr)); + + if (ebr.signature != DOS_BOOT_RECORD_SIGNATURE) + return RTMsgErrorExitFailure("Invalid EBR found on image with signature 0x%04hX\n", + ebr.signature); + + MBRPARTITIONENTRY *pEbrPartitionEntry = + &g_aParsedPartitionInfo[idxPartition].partitionEntry.mbrEntry; /* EBR entry struct same as MBR */ + memcpy(pEbrPartitionEntry, &ebr.partitionEntry, sizeof (MBRPARTITIONENTRY)); + + if (pEbrPartitionEntry->type == NULL_BOOT_RECORD_SIGNATURE) + return RTMsgErrorExitFailure("Logical partition with type 0 encountered"); + + if (!pEbrPartitionEntry->partitionLba) + return RTMsgErrorExitFailure("Logical partition invalid partition start offset (LBA) encountered"); + + PARTITIONINFO *ppi = &g_aParsedPartitionInfo[idxPartition]; + ppi->idxPartition = idxPartition; + ppi->offPartition = currentEbrOffset + (off_t)pEbrPartitionEntry->partitionLba * BLOCKSIZE; + ppi->cbPartition = (off_t)pEbrPartitionEntry->partitionBlkCnt * BLOCKSIZE; + ppi->fBootable = pEbrPartitionEntry->bootIndicator == 0x80; + ppi->partitionType.legacy = pEbrPartitionEntry->type; + + g_lastPartNbr = idxPartition; + + if (ebr.chainingPartitionEntry.type == 0) /* end of chain */ + break; + + if (!PARTTYPE_IS_EXT(ebr.chainingPartitionEntry.type)) + return RTMsgErrorExitFailure("Logical partition chain broken"); + + chainedEbrOffset = ebr.chainingPartitionEntry.partitionLba * BLOCKSIZE; + } + } + return PARTITION_TABLE_MBR; +} + +const char *getClassicPartitionDesc(uint8_t type) +{ + for (uint32_t i = 0; i < sizeof (g_partitionDescTable) / sizeof (struct PartitionDesc); i++) + { + if (g_partitionDescTable[i].type == type) + return g_partitionDescTable[i].desc; + } + return "????"; +} + +void +displayGptPartitionTable(void) +{ + + RTPrintf( "Virtual disk image:\n\n"); + RTPrintf(" Base: %s\n", g_pszBaseImagePath); + if (g_cImages > 1) + RTPrintf(" Diff: %s\n", g_pszImagePath); + if (g_pszDiskUuid) + RTPrintf(" UUID: %s\n\n", g_pszDiskUuid); + + void *colBoot = NULL; + + SELFSIZINGTABLE tbl(2); + + /* Note: Omitting partition name column because type/UUID seems suffcient */ + void *colPartNbr = tbl.addCol("#", "%3d", 1); + + /* If none of the partitions supports legacy BIOS boot, don't show that column */ + for (int idxPartition = 2; idxPartition <= g_lastPartNbr; idxPartition++) + if (g_aParsedPartitionInfo[idxPartition].fBootable) { + colBoot = tbl.addCol("Boot", "%c ", 1); + break; + } + + void *colStart = tbl.addCol("Start", "%lld", 1); + void *colSectors = tbl.addCol("Sectors", "%lld", -1, 2); + void *colSize = tbl.addCol("Size", "%s", 1); + void *colOffset = tbl.addCol("Offset", "%lld", 1); + void *colType = tbl.addCol("Type", "%s", -1, 2); + +#if 0 /* need to see how other OSes w/GPT use 'Name' field, right now 'Type' seems to suffice */ + void *colName = tbl.addCol("Name", "%s", -1); */ +#endif + + for (int idxPartition = 2; idxPartition <= g_lastPartNbr; idxPartition++) + { + PARTITIONINFO *ppi = &g_aParsedPartitionInfo[idxPartition]; + if (ppi->idxPartition) + { + char abGuid[GUID_STRING_LENGTH * 2]; + RTStrPrintf(abGuid, sizeof(abGuid), "%RTuuid", &ppi->partitionType.gptGuidTypeSpecifier); + + char *pszPartitionTypeDesc = NULL; + for (uint32_t i = 0; i < sizeof(g_gptPartitionTypes) / sizeof(GPTPARTITIONTYPE); i++) + if (RTStrNICmp(abGuid, g_gptPartitionTypes[i].gptPartitionUuid, GUID_STRING_LENGTH) == 0) + { + pszPartitionTypeDesc = (char *)g_gptPartitionTypes[i].gptPartitionTypeDesc; + break; + } + + if (!pszPartitionTypeDesc) + RTPrintf("Couldn't find GPT partitiontype for GUID: %s\n", abGuid); + + void *row = tbl.addRow(); + tbl.setCell(row, colPartNbr, idxPartition - 1); + if (colBoot) + tbl.setCell(row, colBoot, ppi->fBootable ? '*' : ' '); + tbl.setCell(row, colStart, ppi->offPartition / BLOCKSIZE); + tbl.setCell(row, colSectors, ppi->cbPartition / BLOCKSIZE); + tbl.setCell(row, colSize, vboximgScaledSize(ppi->cbPartition)); + tbl.setCell(row, colOffset, ppi->offPartition); + tbl.setCell(row, colType, SAFENULL(pszPartitionTypeDesc)); + +#if 0 /* see comment for stubbed-out 'Name' column definition above */ + tbl.setCell(row, colName, ppi->pszName); +#endif + + } + } + tbl.displayTable(); + RTPrintf ("\n"); +} + +void +displayLegacyPartitionTable(void) +{ + /* + * Partition table is most readable and concise when headers and columns + * are adapted to the actual data, to avoid insufficient or excessive whitespace. + */ + + RTPrintf( "Virtual disk image:\n\n"); + RTPrintf(" Base: %s\n", g_pszBaseImagePath); + if (g_cImages > 1) + RTPrintf(" Diff: %s\n", g_pszImagePath); + if (g_pszDiskUuid) + RTPrintf(" UUID: %s\n\n", g_pszDiskUuid); + + SELFSIZINGTABLE tbl(2); + + void *colPartition = tbl.addCol("Partition", "%s(%d)", -1); + void *colBoot = tbl.addCol("Boot", "%c ", 1); + void *colStart = tbl.addCol("Start", "%lld", 1); + void *colSectors = tbl.addCol("Sectors", "%lld", -1, 2); + void *colSize = tbl.addCol("Size", "%s", 1); + void *colOffset = tbl.addCol("Offset", "%lld", 1); + void *colId = tbl.addCol("Id", "%2x", 1); + void *colType = tbl.addCol("Type", "%s", -1, 2); + + for (int idxPartition = 1; idxPartition <= g_lastPartNbr; idxPartition++) + { + PARTITIONINFO *p = &g_aParsedPartitionInfo[idxPartition]; + if (p->idxPartition) + { + void *row = tbl.addRow(); + tbl.setCell(row, colPartition, g_pszBaseImageName, idxPartition); + tbl.setCell(row, colBoot, p->fBootable ? '*' : ' '); + tbl.setCell(row, colStart, p->offPartition / BLOCKSIZE); + tbl.setCell(row, colSectors, p->cbPartition / BLOCKSIZE); + tbl.setCell(row, colSize, vboximgScaledSize(p->cbPartition)); + tbl.setCell(row, colOffset, p->offPartition); + tbl.setCell(row, colId, p->partitionType.legacy); + tbl.setCell(row, colType, getClassicPartitionDesc((p->partitionType).legacy)); + } + } + tbl.displayTable(); + RTPrintf ("\n"); +} + +int +main(int argc, char **argv) +{ + + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("RTR3InitExe failed, rc=%Rrc\n", rc); + + rc = VDInit(); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("VDInit failed, rc=%Rrc\n", rc); + + memset(&g_vboximgOps, 0, sizeof(g_vboximgOps)); + g_vboximgOps.open = vboximgOp_open; + g_vboximgOps.read = vboximgOp_read; + g_vboximgOps.write = vboximgOp_write; + g_vboximgOps.getattr = vboximgOp_getattr; + g_vboximgOps.release = vboximgOp_release; + g_vboximgOps.readdir = vboximgOp_readdir; + g_vboximgOps.readlink = vboximgOp_readlink; + + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + memset(&g_vboximgOpts, 0, sizeof(g_vboximgOpts)); + + g_vboximgOpts.idxPartition = -1; + + rc = fuse_opt_parse(&args, &g_vboximgOpts, vboximgOptDefs, vboximgOptHandler); + if (rc < 0 || argc < 2 || RTStrCmp(argv[1], "-?" ) == 0 || g_vboximgOpts.fBriefUsage) + { + briefUsage(); + return 0; + } + + if (g_vboximgOpts.fAllowRoot) + fuse_opt_add_arg(&args, "-oallow_root"); + + /* + * FUSE doesn't seem to like combining options with one hyphen, as traditional UNIX + * command line utilities allow. The following flags, fWideList and fVerboseList, + * and their respective option definitions give the appearance of combined opts, + * so that -vl, -lv, -wl, -lw options are allowed, since those in particular would + * tend to conveniently facilitate some of the most common use cases. + */ + if (g_vboximgOpts.fWideList) + { + g_vboximgOpts.fWide = true; + g_vboximgOpts.fList = true; + } + if (g_vboximgOpts.fVerboseList) + { + g_vboximgOpts.fVerbose = true; + g_vboximgOpts.fList = true; + } + if (g_vboximgOpts.fAllowRoot) + fuse_opt_add_arg(&args, "-oallow_root"); + + /* + * Initialize COM. + */ + using namespace com; + HRESULT hrc = com::Initialize(); + if (FAILED(hrc)) + { +# ifdef VBOX_WITH_XPCOM + if (hrc == NS_ERROR_FILE_ACCESS_DENIED) + { + char szHome[RTPATH_MAX] = ""; + com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome)); + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome); + } +# endif + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize COM! (hrc=%Rhrc)", hrc); + } + + /* + * Get the remote VirtualBox object and create a local session object. + */ + ComPtr<IVirtualBoxClient> pVirtualBoxClient; + ComPtr<IVirtualBox> pVirtualBox; + + hrc = pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient); + if (SUCCEEDED(hrc)) + hrc = pVirtualBoxClient->COMGETTER(VirtualBox)(pVirtualBox.asOutParam()); + + if (FAILED(hrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to get IVirtualBox object! (hrc=%Rhrc)", hrc); + + if (g_vboximgOpts.fList && g_vboximgOpts.pszImageUuidOrPath == NULL) + { + vboximgListVMs(pVirtualBox); + return VINF_SUCCESS; + } + + Bstr pMediumUuid; + ComPtr<IMedium> pVDiskMedium = NULL; + char *pszFormat; + VDTYPE enmType; + + /* + * Open chain of images from what is provided on command line, to base image + */ + if (g_vboximgOpts.pszImageUuidOrPath) + { + /* compiler was too fussy about access mode's data type in conditional expr, so... */ + if (g_vboximgOpts.fRW) + CHECK_ERROR(pVirtualBox, OpenMedium(Bstr(g_vboximgOpts.pszImageUuidOrPath).raw(), DeviceType_HardDisk, + AccessMode_ReadWrite, false /* forceNewUuid */, pVDiskMedium.asOutParam())); + + else + CHECK_ERROR(pVirtualBox, OpenMedium(Bstr(g_vboximgOpts.pszImageUuidOrPath).raw(), DeviceType_HardDisk, + AccessMode_ReadOnly, false /* forceNewUuid */, pVDiskMedium.asOutParam())); + + if (FAILED(rc)) + return RTMsgErrorExitFailure("\nCould't find specified VirtualBox base or snapshot disk image:\n%s", + g_vboximgOpts.pszImageUuidOrPath); + + + CHECK_ERROR(pVDiskMedium, COMGETTER(Id)(pMediumUuid.asOutParam())); + g_pszDiskUuid = RTStrDup((char *)CSTR(pMediumUuid)); + + /* + * Lock & cache the disk image media chain (from leaf to base). + * Only leaf can be rw (and only if media is being mounted in non-default writable (rw) mode) + * + * Note: Failure to acquire lock is intentionally fatal (e.g. program termination) + */ + + if (VERBOSE) + RTPrintf("\nAttempting to lock medium chain from leaf image to base image\n"); + + bool fLeaf = true; + g_cImages = 0; + + do + { + ++g_cImages; + IMAGELIST *pNewEntry= new IMAGELIST(); + pNewEntry->pImage = pVDiskMedium; + CHECK_ERROR(pVDiskMedium, COMGETTER(Name)((pNewEntry->pImageName).asOutParam())); + CHECK_ERROR(pVDiskMedium, COMGETTER(Location)((pNewEntry->pImagePath).asOutParam())); + + if (VERBOSE) + RTPrintf(" %s", CSTR(pNewEntry->pImageName)); + + if (fLeaf && g_vboximgOpts.fRW) + { + if (VERBOSE) + RTPrintf(" ... Locking for write\n"); + CHECK_ERROR_RET(pVDiskMedium, LockWrite((pNewEntry->pLockToken).asOutParam()), rc); + pNewEntry->fWriteable = true; + } + else + { + if (VERBOSE) + RTPrintf(" ... Locking for read\n"); + CHECK_ERROR_RET(pVDiskMedium, LockRead((pNewEntry->pLockToken).asOutParam()), rc); + } + + IMAGELIST *pCurImageEntry = &listHeadLockList; + while (pCurImageEntry->next) + pCurImageEntry = pCurImageEntry->next; + pCurImageEntry->next = pNewEntry; + pNewEntry->prev = pCurImageEntry; + listHeadLockList.prev = pNewEntry; + + CHECK_ERROR(pVDiskMedium, COMGETTER(Parent)(pVDiskMedium.asOutParam())); + fLeaf = false; + } + while(pVDiskMedium); + } + + ComPtr<IMedium> pVDiskBaseMedium = listHeadLockList.prev->pImage; + Bstr pVDiskBaseImagePath = listHeadLockList.prev->pImagePath; + Bstr pVDiskBaseImageName = listHeadLockList.prev->pImageName; + + g_pszBaseImagePath = RTStrDup((char *)CSTR(pVDiskBaseImagePath)); + g_pszBaseImageName = RTStrDup((char *)CSTR(pVDiskBaseImageName)); + + g_pszImagePath = RTStrDup((char *)CSTR(listHeadLockList.next->pImagePath)); + g_pszImageName = RTStrDup((char *)CSTR(listHeadLockList.next->pImageName)); + + /* + * Attempt to VDOpen media (base and any snapshots), handling encryption, + * if that property is set for base media + */ + Bstr pBase64EncodedKeyStore; + + rc = pVDiskBaseMedium->GetProperty(Bstr("CRYPT/KeyStore").raw(), pBase64EncodedKeyStore.asOutParam()); + if (SUCCEEDED(rc) && strlen(CSTR(pBase64EncodedKeyStore)) != 0) + { + RTPrintf("\nvboximgMount: Encrypted disks not supported in this version\n\n"); + return -1; + } + + +/* ***************** BEGIN IFDEF'D (STUBBED-OUT) CODE ************** */ +/* vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ + +#if 0 /* The following encrypted disk related code is stubbed out until it can be finished. + * What is here is an attempt to port the VBoxSVC specific code in i_openForIO to + * a client's proximity. It is supplemented by code in vboximgCrypto.cpp and + * vboximageCrypt.h that was lifed from SecretKeyStore.cpp along with the setup + * task function. + * + * The ultimate solution may be to use a simpler but less efficient COM interface, + * or to use VD encryption interfaces and key containers entirely. The keystore + * handling/filter approach that is here may be a bumbling hybrid approach + * that is broken (trying to bridge incompatible disk encryption mechanisms) or otherwise + * doesn't make sense. */ + + Bstr pKeyId; + ComPtr<IExtPackManager> pExtPackManager; + ComPtr<IExtPack> pExtPack; + com::SafeIfaceArray<IExtPackPlugIn> pExtPackPlugIns; + + if (SUCCEEDED(rc)) + { + RTPrintf("Got GetProperty(\"CRYPT/KeyStore\") = %s\n", CSTR(pBase64EncodedKeyStore)); + if (strlen(CSTR(pBase64EncodedKeyStore)) == 0) + return RTMsgErrorExitFailure("Image '%s' is configured for encryption but " + "there is no key store to retrieve the password from", CSTR(pVDiskBaseImageName)); + + SecretKeyStore keyStore(false); + RTBase64Decode(CSTR(pBase64EncodedKeyStore), &keyStore, sizeof (SecretKeyStore), NULL, NULL); + + rc = pVDiskBaseMedium->GetProperty(Bstr("CRYPT/KeyId").raw(), pKeyId.asOutParam()); + if (SUCCEEDED(rc) && strlen(CSTR(pKeyId)) == 0) + return RTMsgErrorExitFailure("Image '%s' is configured for encryption but " + "doesn't have a key identifier set", CSTR(pVDiskBaseImageName)); + + RTPrintf(" key id: %s\n", CSTR(pKeyId)); + +#ifndef VBOX_WITH_EXTPACK + return RTMsgErrorExitFailure( + "Encryption is not supported because extension pack support is not built in"); +#endif + + CHECK_ERROR(pVirtualBox, COMGETTER(ExtensionPackManager)(pExtPackManager.asOutParam())); + BOOL fExtPackUsable; + CHECK_ERROR(pExtPackManager, IsExtPackUsable((PRUnichar *)VBOX_EXTPACK, &fExtPackUsable)); + if (fExtPackUsable) + { + /* Load the PlugIn */ + + CHECK_ERROR(pExtPackManager, Find((PRUnichar *)VBOX_EXTPACK, pExtPack.asOutParam())); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure( + "Encryption is not supported because the extension pack '%s' is missing", + VBOX_EXTPACK); + + CHECK_ERROR(pExtPack, COMGETTER(PlugIns)(ComSafeArrayAsOutParam(pExtPackPlugIns))); + + Bstr pPlugInPath; + size_t iPlugIn; + for (iPlugIn = 0; iPlugIn < pExtPackPlugIns.size(); iPlugIn++) + { + Bstr pPlugInName; + CHECK_ERROR(pExtPackPlugIns[iPlugIn], COMGETTER(Name)(pPlugInName.asOutParam())); + if (RTStrCmp(CSTR(pPlugInName), "VDPlugInCrypt") == 0) + { + CHECK_ERROR(pExtPackPlugIns[iPlugIn], COMGETTER(ModulePath)(pPlugInPath.asOutParam())); + break; + } + } + if (iPlugIn == pExtPackPlugIns.size()) + return RTMsgErrorExitFailure("Encryption is not supported because the extension pack '%s' " + "is missing the encryption PlugIn (old extension pack installed?)", VBOX_EXTPACK); + + rc = VDPluginLoadFromFilename(CSTR(pPlugInPath)); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Retrieving encryption settings of the image failed " + "because the encryption PlugIn could not be loaded\n"); + } + + SecretKey *pKey = NULL; + rc = keyStore.retainSecretKey(Utf8Str(pKeyId), &pKey); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure( + "Failed to retrieve the secret key with ID \"%s\" from the store (%Rrc)", + CSTR(pKeyId), rc); + + VDISKCRYPTOSETTINGS vdiskCryptoSettings, *pVDiskCryptoSettings = &vdiskCryptoSettings; + + vboxImageCryptoSetup(pVDiskCryptoSettings, NULL, + (const char *)CSTR(pBase64EncodedKeyStore), (const char *)pKey->getKeyBuffer(), false); + + rc = VDFilterAdd(g_pVDisk, "CRYPT", VD_FILTER_FLAGS_DEFAULT, pVDiskCryptoSettings->vdFilterIfaces); + keyStore.releaseSecretKey(Utf8Str(pKeyId)); + + if (rc == VERR_VD_PASSWORD_INCORRECT) + return RTMsgErrorExitFailure("The password to decrypt the image is incorrect"); + + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to load the decryption filter: %Rrc", rc); + } +#endif + +/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ +/* **************** END IFDEF'D (STUBBED-OUT) CODE ***************** */ + + rc = RTCritSectInit(&g_vdioLock); + if (RT_SUCCESS(rc)) + { + g_VDIfThreadSync.pfnStartRead = vboximgThreadStartRead; + g_VDIfThreadSync.pfnFinishRead = vboximgThreadFinishRead; + g_VDIfThreadSync.pfnStartWrite = vboximgThreadStartWrite; + g_VDIfThreadSync.pfnFinishWrite = vboximgThreadFinishWrite; + rc = VDInterfaceAdd(&g_VDIfThreadSync.Core, "vboximg_ThreadSync", VDINTERFACETYPE_THREADSYNC, + &g_vdioLock, sizeof(VDINTERFACETHREADSYNC), &g_pVdIfs); + } + else + return RTMsgErrorExitFailure("ERROR: Failed to create critsects " + "for virtual disk I/O, rc=%Rrc\n", rc); + + /* + * Create HDD container to open base image and differencing images into + */ + rc = VDGetFormat(NULL /* pVDIIfsDisk */, NULL /* pVDIIfsImage*/, + CSTR(pVDiskBaseImagePath), &pszFormat, &enmType); + + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("VDGetFormat(,,%s,,) " + "failed (during HDD container creation), rc=%Rrc\n", g_pszImagePath, rc); + + if (VERBOSE) + RTPrintf("\nCreating container for base image of format %s\n", pszFormat); + + g_pVDisk = NULL; + rc = VDCreate(g_pVdIfs, enmType, &g_pVDisk); + if ((rc)) + return RTMsgErrorExitFailure("ERROR: Couldn't create virtual disk container\n"); + + /* Open all virtual disk media from leaf snapshot (if any) to base image*/ + + if (VERBOSE) + RTPrintf("\nOpening medium chain\n"); + + IMAGELIST *pCurMedium = listHeadLockList.prev; /* point to base image */ + while (pCurMedium != &listHeadLockList) + { + if (VERBOSE) + RTPrintf(" Open: %s\n", CSTR(pCurMedium->pImagePath)); + + rc = VDOpen(g_pVDisk, + CSTR(pszFormat), + CSTR(pCurMedium->pImagePath), + pCurMedium->fWriteable, + g_pVdIfs); + + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Could not open the medium storage unit '%s' %Rrc", + CSTR(pCurMedium->pImagePath), rc); + + pCurMedium = pCurMedium->prev; + } + + g_cReaders = VDIsReadOnly(g_pVDisk) ? INT32_MAX / 2 : 0; + g_cWriters = 0; + g_cbEntireVDisk = VDGetSize(g_pVDisk, 0 /* base */); + + if (g_vboximgOpts.fList) + { + if (g_pVDisk == NULL) + return RTMsgErrorExitFailure("No valid --image to list partitions from\n"); + + RTPrintf("\n"); + rc = parsePartitionTable(); + switch(rc) + { + case PARTITION_TABLE_MBR: + displayLegacyPartitionTable(); + break; + case PARTITION_TABLE_GPT: + displayGptPartitionTable(); + break; + default: + return rc; + } + return 0; + } + + if (g_vboximgOpts.idxPartition >= 0) + { + if (g_vboximgOpts.offset) + return RTMsgErrorExitFailure("--offset and --partition are mutually exclusive options\n"); + + if (g_vboximgOpts.size) + return RTMsgErrorExitFailure("--size and --partition are mutually exclusive options\n"); + + /* + * --partition option specified. That will set the global offset and limit + * honored by the disk read and write sanitizers to constrain operations + * to within the specified partion based on an initial parsing of the MBR + */ + rc = parsePartitionTable(); + if (rc < 0) + return RTMsgErrorExitFailure("Error parsing disk MBR/Partition table\n"); + int partNbr = g_vboximgOpts.idxPartition; + + if (partNbr < 0 || partNbr > g_lastPartNbr) + return RTMsgErrorExitFailure("Non-valid partition number specified\n"); + if (partNbr == 0) + { + g_vDiskOffset = 0; + g_vDiskSize = VDGetSize(g_pVDisk, 0); + if (VERBOSE) + RTPrintf("\nPartition 0 specified - Whole disk will be accessible\n"); + } else { + int fFoundPartition = false; + for (int i = 1; i < g_lastPartNbr + 1; i++) + { + /* If GPT, display vboximg's representation of partition table starts at partition 2 + * but the table is displayed calling it partition 1, because the protective MBR + * record is relatively pointless to display or reference in this context */ + if (g_aParsedPartitionInfo[i].idxPartition == partNbr + (g_fGPT ? 1 : 0)) + { + fFoundPartition = true; + g_vDiskOffset = g_aParsedPartitionInfo[i].offPartition; + g_vDiskSize = g_vDiskOffset + g_aParsedPartitionInfo[i].cbPartition; + if (VERBOSE) + RTPrintf("\nPartition %d specified. Only sectors %llu to %llu of disk will be accessible\n", + g_vboximgOpts.idxPartition, g_vDiskOffset / BLOCKSIZE, g_vDiskSize / BLOCKSIZE); + } + } + if (!fFoundPartition) + return RTMsgErrorExitFailure("Couldn't find partition %d in partition table\n", partNbr); + } + } else { + if (g_vboximgOpts.offset) { + if (g_vboximgOpts.offset < 0 || g_vboximgOpts.offset + g_vboximgOpts.size > g_cbEntireVDisk) + return RTMsgErrorExitFailure("User specified offset out of range of virtual disk\n"); + + if (VERBOSE) + RTPrintf("Setting r/w bias (offset) to user requested value for sector %llu\n", g_vDiskOffset / BLOCKSIZE); + + g_vDiskOffset = g_vboximgOpts.offset; + } + if (g_vboximgOpts.size) { + if (g_vboximgOpts.size < 0 || g_vboximgOpts.offset + g_vboximgOpts.size > g_cbEntireVDisk) + return RTMsgErrorExitFailure("User specified size out of range of virtual disk\n"); + + if (VERBOSE) + RTPrintf("Setting r/w size limit to user requested value %llu\n", g_vDiskSize / BLOCKSIZE); + + g_vDiskSize = g_vboximgOpts.size; + } + } + if (g_vDiskSize == 0) + g_vDiskSize = g_cbEntireVDisk - g_vDiskOffset; + + /* + * Hand control over to libfuse. + */ + if (VERBOSE) + RTPrintf("\nvboximg-mount: Going into background...\n"); + + rc = fuse_main(args.argc, args.argv, &g_vboximgOps, NULL); + + int rc2 = VDClose(g_pVDisk, false /* fDelete */); + AssertRC(rc2); + RTPrintf("vboximg-mount: fuse_main -> %d\n", rc); + return rc; +} + diff --git a/src/VBox/ImageMounter/vboximg-mount/vboximg-mount.h b/src/VBox/ImageMounter/vboximg-mount/vboximg-mount.h new file mode 100644 index 00000000..d3593f56 --- /dev/null +++ b/src/VBox/ImageMounter/vboximg-mount/vboximg-mount.h @@ -0,0 +1,297 @@ +/* $Id: vboximg-mount.h $ */ +/** @file + * vboximg-mount header file + */ + +/* + * Copyright (C) 2018-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. + */ + +#ifndef VBOX_INCLUDED_SRC_vboximg_mount_vboximg_mount_h +#define VBOX_INCLUDED_SRC_vboximg_mount_vboximg_mount_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define BOOTSTRAP_CODE_AREA_SIZE 446 /** Impl. specific MBR & EBR block not relevant for our use */ +#define GPT_PARTITION_ENTRY_NAME_SIZE 36 /** 72 bytes for GPT partition entry name, but treated as 36 UTF-16 */ +#define GUID_STRING_LENGTH 36 + +#pragma pack(push,1) /**** THE FOLLOWING STRUCTURES MUST NOT BE PADDED BY THE COMPILER ****/ + +typedef struct PartitionEntry /* Pos & size critical (See http://en.wikipedia.org/wiki/Master_boot_record) */ +{ + uint8_t bootIndicator; /** 0x80 = bootable, 0x00 = non-bootable */ + uint8_t firstHead; /** CHS address of first absolute Head from MBR entry */ + uint8_t firstSector; /** CHS address of first absolute Sector from MBR entry */ + uint8_t firstCyl; /** CHS address of first absolute Cylinder from MBR entry*/ + uint8_t type; /** partition type */ + uint8_t lastHead; /** CHS address of last absolute Head from MBR entry */ + uint8_t lastSector; /** CHS address of last absolute Sector from MBR entry*/ + uint8_t lastCyl; /** CHS address of last absolute Cylinder from MBR entry */ + uint32_t partitionLba; /** LBA of first sector in the partition */ + uint32_t partitionBlkCnt; /** partition block count (little endian) */ +} MBRPARTITIONENTRY; + +typedef struct MasterBootRecord /* Pos & size critical (See http://en.wikipedia.org/wiki/Master_boot_record) */ +{ + char bootstrapCodeArea[BOOTSTRAP_CODE_AREA_SIZE]; + MBRPARTITIONENTRY partitionEntry[4]; + uint16_t signature; +} MBR_t; /* _t for type to avoid conflict with definition in vd.h */ + +typedef struct ExtendedBootRecord /* Pos & size critical (See http://en.wikipedia.org/wiki/Extended_boot_record) */ +{ + char unused[BOOTSTRAP_CODE_AREA_SIZE]; + MBRPARTITIONENTRY partitionEntry; /** EBR-relative offset to 1st sector of partition */ + MBRPARTITIONENTRY chainingPartitionEntry; /** EBR-relative offset to next EBR */ + MBRPARTITIONENTRY unusedPartitionEntry3; /** Unused for Logical Partitions table entries (EBR) */ + MBRPARTITIONENTRY unusedPartitionEntry4; /** Unused for Logical Partitions table entries (EBR) */ + uint16_t signature; /** Boot Record signature */ +} EBR_t; /* _t for type to avoid possible future conflict with vd.h */ + +typedef struct gptPartitionTableHeader /* Pos and size critical (See https://en.wikipedia.org/wiki/GUID_Partition_Table) */ +{ + uint64_t signature; /** Equiv values: "EFI PART", 0x5452415020494645ULL little-endian */ + uint32_t revision; /** GPT 1.0 would be 0x00,0x00,x01,0x00 */ + uint32_t cbHeader; /** Typically 0x5C,0x00,0x00,0x00 (e.g. 92, little-endian) */ + uint32_t crc32OfHeader; /** From [0:<header size>], little endian, zero while being calculated */ + uint32_t MBZreserved1; /** MBZ */ + uint64_t headerLba; /** Location of this header copy */ + uint64_t backupLba; /** Location of the other header copy */ + uint64_t firstUsableLba; /** Ending LBA of primary partition table, +1 */ + uint64_t lastUsableLba; /** Starting LBA of secondary partition tabel, -1 */ + uint128_t diskUuid; /** Disk UUID */ + uint64_t partitionEntriesLba; /** Starting lba of partition entreis array (always 2 in primary copy) */ + uint32_t cPartitionEntries; /** Number of partitions in the array */ + uint32_t cbPartitionEntry; /** Size of a single partition entry (usually 0x80, e.g. 128) */ + uint32_t crc32OfPartitionArray; /** CRC32/zlib of partition array, little-endian */ + uint8_t MBZreserved2[]; /** Zeros until end of block, 420 bytes for 512 block but can be more */ +} PTH_t; /*_t for type, to avoid possible future conflict with vd.h */ + +typedef struct gptPartitionEntry +{ + uint128_t partitionTypeGuid; /** Partition type UUID (RFC4122: 128 bits, big endian, network byte order) */ + uint128_t uniquePartitionGuid; /** UUID unique to this partition */ + uint64_t firstLba; /** First LBA of partition (little-endian) */ + uint64_t lastLba; /** Last LBA of partition (inclusive, usually odd) */ + uint64_t attrFlags; /** Attribute flags */ + uint16_t partitionName[GPT_PARTITION_ENTRY_NAME_SIZE]; /** Partition Name, 72-bytes (e.g. 36 UTF-16LE code units) */ +} GPTPARTITIONENTRY; + +/* GPT partition attributes */ + +#define GPT_ATTR_REQUIRED_BY_PLATORM 0 /* bit number */ +#define GPT_ATTR_EFI_SHOULD_IGNORE 1 /* bit number */ +#define GPT_LEGACY_BIOS_BOOTABLE 2 /* bit number */ +#define GPT_ATTR_PARTITION_READONLY 60 /* bit number */ +#define GPT_ATTR_SHADOW_COPY 61 /* bit number */ +#define GPT_ATTR_HIDDEN 62 /* bit number */ +#define GPT_ATTR_NO_DRIVE_LETTER 63 /* bit number */ + +#pragma pack(pop) /**** End of packed data declarations ****/ + +struct PartitionDesc +{ + uint8_t type; + const char *desc; +} g_partitionDescTable[] = { + { 0x00, "Empty" }, { 0x01, "FAT12" }, { 0x02, "XENIX" }, { 0x03, "XENIX" }, { 0x04, "FAT16 <32M" }, { 0x05, "Extended" }, + { 0x06, "FAT16" }, { 0x07, "HPFS; NTFS; exFAT; WinCE; QNX" }, { 0x08, "AIX; DOS 3.x; OS/2; QNX 1.x" }, + { 0x09, "AIX; Coherent" }, { 0x0a, "OS/2 Boot Mgr; Coherent" }, { 0x0b, "W95 FAT32" }, { 0x0c, "W95 FAT32 (LBA)" }, + { 0x0e, "W95 FAT16 (LBA)" }, { 0x0f, "W95 Ext'd (LBA)" }, { 0x10, "OPUS" }, { 0x11, "FAT12" }, { 0x12, "Diag. & FW; Service; Compaq; EISA; AST" }, + { 0x14, "FAT16; MAVERICK; OS/2" }, { 0x16, "FAT16" }, { 0x17, "HPFS; NTFS; exFAT; IFS" }, + { 0x18, "AST" }, { 0x1b, "OS/2 BootMgr" }, { 0x1c, "OS/2 BootMgr FAT16/LBA; ASUS Recovery FAT32/LBA" }, + { 0x1e, "OS/2 BootMgr Ext. LBA" }, { 0x20, "Windows Mobile" }, { 0x21, "HP Vol. Expansion; FSo2" }, { 0x22, "FSo2 ext. part" }, + { 0x23, "Windows Mobile "}, { 0x24, "NEC" }, {0x25, "Windows Mobile" }, { 0x27, "NTFS; MirOS, RooterBoot" }, + { 0x2a, "AtheOS" }, { 0x2b, "Syllable OS" }, { 0x32, "NOS" }, { 0x35, "JFS" }, { 0x38, "Plan; THEOS" }, + { 0x39, "Plan; THEOS" }, { 0x3c, "PartitionMagic" }, { 0x40, "Venix; PICK" }, { 0x41, "PPC; Linux; SFS" }, { 0x42, "Linux; Win 2000" }, + { 0x43, "Linux" }, { 0x44, "Wildfile" }, { 0x45, "Priam; Boot-US; EMUEL/ELAN (L2)" }, { 0x46, "EMUEL/ELAN (L2)" }, + { 0x47, "EMUEL/ELAN (L2)" }, {0x48, "EMUEL/ELAN" }, { 0x4a, "AdaOS; ALFS/THIN" }, { 0x4c, "ETH Oberon" }, { 0x4d, "QNX4.x Primary" }, + { 0x4e, "QNX4.x 2nd part " }, { 0x4f, "QNX4.x 3rd part; ETH Oberon boot " }, { 0x50, "OnTrack; LynxOS; Novel" }, + { 0x51, "OnTrack" }, { 0x52, "CP/M; System V/AT, V/386" }, { 0x53, "OnTrack" }, { 0x54, "OnTrack" }, { 0x55, "EZ-Drive" }, + { 0x56, "AT&T; EZ-Drive; VFeature" }, {0x57, "DrivePro" }, { 0x5c, "Priam" }, { 0x61, "SpeedStor" }, + { 0x63, "GNU; SCO Unix; ISC; UnixWare; SYSV/386; ix; MtXinu BSD 4.3 on Mach" }, { 0x64, "Netware, SpeedStor; PC-ARMOUR" }, + { 0x65, "Netware" }, { 0x66, "Netware" }, { 0x67, "Netware" }, { 0x68, "Netware" }, { 0x69, "Netware" }, { 0x70, "DiskSecure" }, + { 0x72, "APTI FAT12 (CHS, SFN)" }, { 0x75, "PC/IX" }, { 0x77, "VNDI, M2FS, M2CS" }, { 0x78, "XOSL bootloader" }, { 0x79, "APTI FAT16 (LBA, SFN)" }, + { 0x7a, "APTI FAT16 (LBA, SFN)" }, { 0x7b, "APTI FAT16 (LBA, SFN)" }, { 0x7c, "APTI FAT16 (LBA, SFN)" }, { 0x7d, "APTI FAT16 (LBA, SFN)" }, + { 0x7e, "PrimoCache" }, { 0x7f, "Alt OS Dev. Partition Std." }, { 0x80, "MINIX (old)" }, { 0x81, "Minix" }, + { 0x82, "Linux swap; Solaris; PRIMOS" }, { 0x83, "Linux" }, { 0x84, "OS/2; Rapid Start Tech." }, { 0x85, "Linux Ext." }, + { 0x86, "NTFS" }, { 0x87, "NTFS" }, { 0x88, "Linux" }, { 0x8e, "Linux" }, {0x90, "Free FDISK" }, { 0x91, "Free FDISK" }, + { 0x92, "Free FDISK" }, { 0x93, "Amoeba" }, { 0x94, "Amoeba" }, { 0x95, "EXOPC" }, { 0x96, "CHRP" }, { 0x97, "Free FDISK" }, + { 0x98, "ROM-DOS; Free FDISK" }, { 0x9a, "Free FDISK" }, { 0x9b, "Free FDISK" }, + { 0x9e, "VSTa; ForthOS" }, { 0x9f, "BSD/OS / BSDI" }, { 0xa0, "Phoenix; IBM; Toshiba; Sony" }, { 0xa1, "HP Volume Expansion; Phoenix; NEC" }, + { 0xa5, "FreeBSD" }, { 0xa6, "OpenBSD" }, { 0xa7, "NeXTSTEP" }, { 0xa8, "Darwin; macOS" }, { 0xa9, "NetBSD" }, { 0xab, "Darwin; GO! OS" }, + { 0xad, "RISC OS" }, { 0xae, "ShagOS"}, { 0xaf, "HFS / ShagOS" }, { 0xb0, "Boot-Star" }, {0xb1, "HP Vol. Expansion; QNX 6.x" }, + { 0xb2, "QNX 6.x" }, { 0xB3, "HP Vol. Expansion; QNX 6.x" }, { 0xb7, "BSDI; Win NT 4 Server" }, { 0xb8, "BSDI" }, + { 0xbb, "BootWizard; OS Selector; Acronis True Image; Win NT 4 Server" }, { 0xbc, "Acronis" }, { 0xbe, "Solaris" }, { 0xbf, "Solaris" }, + { 0xc0, "DR-DOS; Multiuser DOS; REAL/32"}, { 0xc1, "DRDOS/sec" }, { 0xc2, "Power Boot" }, { 0xc3, "Power Boot" }, { 0xc4, "DRDOS/sec" }, + { 0xc6, "DRDOS/sec" }, { 0xc7, "Syrinx; Win NT Server" }, { 0xcb, "Win NT 4 Server" }, { 0xcc, "DR-DOS 7.0x; Win NT 4 Server" }, + { 0xcd, "CTOS" }, { 0xce, "DR-DOS 7.0x" }, { 0xcf, "DR-DOS 7.0x" }, { 0xd1, "Multiuser DOS" }, { 0xd4, "Multiuser DOS" }, + { 0xd5, "Multiuser DOS" }, { 0xd6, "Multiuser DOS" }, { 0xd8, "CP/M-86" }, { 0xda, "Non-FS data; Powercopy Backup" }, + { 0xdb, "CP/M; Concurrent DOS; CTOS; D800; DRMK " }, { 0xdd, "CTOS" }, { 0xde, "Dell" }, { 0xdf, "BootIt" }, { 0xe0, "Aviion" }, + { 0xe1, "SpeedStor" }, { 0xe2, "SpeedStor"}, { 0xe3, "SpeedStor" }, { 0xe4, "SpeedStor" }, { 0xe5, "Tandy MS-DOS" }, { 0xe6, "SpeedStor" }, + { 0xe8, "LUKS" }, { 0xea, "Rufus" }, { 0xeb, "BeOS" }, { 0xec, "SkyOS" }, { 0xed, "EFS; Sprytix" }, { 0xed, "EFS" }, + { 0xee, "GPT" }, { 0xef, "EFI" }, { 0xf0, "Linux; PA-RISC" }, { 0xf1, "SpeedStor" }, { 0xf2, "Sperry MS-DOS 3.x" }, + { 0xf3, "SpeedStor" }, { 0xf4, "SpeedStor; Prologue" }, { 0xf5, "Prologue" }, { 0xf6, "SpeedStor" }, { 0xf7, "O.S.G.; X1" }, + { 0xf9, "Linux" }, { 0xfa, "Bochs" }, { 0xfb, "VMware" }, { 0xfc, "VMware" }, { 0xfd, "Linux; FreeDOS" }, + { 0xfe, "SpeedStor; LANstep; PS/2; Win NT; Linux" }, { 0xff, "Xenis; BBT" }, +}; + +typedef struct GptPartitionTypeTable +{ + const char *gptPartitionUuid; + const char *osType; + const char *gptPartitionTypeDesc; +} GPTPARTITIONTYPE; + +GPTPARTITIONTYPE g_gptPartitionTypes[] = +{ + { "00000000-0000-0000-0000-000000000000", "", "Unused" }, + { "024DEE41-33E7-11D3-9D69-0008C781F39F", "", "MBR scheme" }, + { "C12A7328-F81F-11D2-BA4B-00A0C93EC93B", "", "EFI System" }, + { "21686148-6449-6E6F-744E-656564454649", "", "BIOS boot" }, + { "D3BFE2DE-3DAF-11DF-BA40-E3A556D89593", "", "Intel Fast Flash (iFFS)" }, + { "F4019732-066E-4E12-8273-346C5641494F", "", "Sony boot" }, + { "BFBFAFE7-A34F-448A-9A5B-6213EB736C22", "", "Lenovo boot" }, + { "E3C9E316-0B5C-4DB8-817D-F92DF00215AE", "Windows", "Microsoft Reserved" }, + { "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", "Windows", "Basic data" }, + { "5808C8AA-7E8F-42E0-85D2-E1E90434CFB3", "Windows", "Logical Disk Manager (LDM) metadata" }, + { "AF9B60A0-1431-4F62-BC68-3311714A69AD", "Windows", "Logical Disk Manager (LDM) data" }, + { "DE94BBA4-06D1-4D40-A16A-BFD50179D6AC", "Windows", "Windows Recovery Environment" }, + { "37AFFC90-EF7D-4E96-91C3-2D7AE055B174", "Windows", "IBM General Parallel File System (GPFS)" }, + { "E75CAF8F-F680-4CEE-AFA3-B001E56EFC2D", "Windows", "Storage Spaces" }, + { "75894C1E-3AEB-11D3-B7C1-7B03A0000000", "HP-UX", "Data" }, + { "E2A1E728-32E3-11D6-A682-7B03A0000000", "HP-UX", "Service" }, + { "0FC63DAF-8483-4772-8E79-3D69D8477DE4", "Linux", "Filesystem Data" }, + { "A19D880F-05FC-4D3B-A006-743F0F84911E", "Linux", "RAID" }, + { "44479540-F297-41B2-9AF7-D131D5F0458A", "Linux", "Root (x86)" }, + { "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709", "Linux", "Root (x86-64)" }, + { "69DAD710-2CE4-4E3C-B16C-21A1D49ABED3", "Linux", "Root (32-bit ARM)" }, + { "B921B045-1DF0-41C3-AF44-4C6F280D3FAE", "Linux", "Root (64-bit ARM/AArch64)" }, + { "A2A0D0EB-E5B9-3344-87C0-68B6B72699C7", "Linux", "Data" }, + { "AF3DC60F-8384-7247-8E79-3D69D8477DE4", "Linux", "Data" }, + { "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F", "Linux", "Swap" }, + { "E6D6D379-F507-44C2-A23C-238F2A3DF928", "Linux", "LVM" }, + { "933AC7E1-2EB4-4F13-B844-0E14E2AEF915", "Linux", "/home" }, + { "3B8F8425-20E0-4F3B-907F-1A25A76F98E8", "Linux", "/srv " }, + { "7FFEC5C9-2D00-49B7-8941-3EA10A5586B7", "Linux", "Plain dm-crypt" }, + { "CA7D7CCB-63ED-4C53-861C-1742536059CC", "Linux", "LUKS" }, + { "8DA63339-0007-60C0-C436-083AC8230908", "Linux", "Reserved" }, + { "83BD6B9D-7F41-11DC-BE0B-001560B84F0F", "FreeBSD", "Boot" }, + { "516E7CB4-6ECF-11D6-8FF8-00022D09712B", "FreeBSD", "Data" }, + { "516E7CB5-6ECF-11D6-8FF8-00022D09712B", "FreeBSD", "Swap" }, + { "516E7CB6-6ECF-11D6-8FF8-00022D09712B", "FreeBSD", "Unix File System (UFS)" }, + { "516E7CB8-6ECF-11D6-8FF8-00022D09712B", "FreeBSD", "Vinum volume manager" }, + { "516E7CBA-6ECF-11D6-8FF8-00022D09712B", "FreeBSD", "ZFS" }, + { "48465300-0000-11AA-AA11-00306543ECAC", "macOS Darwin", "Hierarchical File System Plus (HFS+)" }, + { "7C3457EF-0000-11AA-AA11-00306543ECAC", "macOS Darwin", "Apple APFS" }, + { "55465300-0000-11AA-AA11-00306543ECAC", "macOS Darwin", "Apple UFS container" }, + { "6A898CC3-1DD2-11B2-99A6-080020736631", "macOS Darwin", "ZFS" }, + { "52414944-0000-11AA-AA11-00306543ECAC", "macOS Darwin", "Apple RAID" }, + { "52414944-5F4F-11AA-AA11-00306543ECAC", "macOS Darwin", "Apple RAID , offline" }, + { "426F6F74-0000-11AA-AA11-00306543ECAC", "macOS Darwin", "Apple Boot (Recovery HD)" }, + { "4C616265-6C00-11AA-AA11-00306543ECAC", "macOS Darwin", "Apple Label" }, + { "5265636F-7665-11AA-AA11-00306543ECAC", "macOS Darwin", "Apple TV Recovery" }, + { "53746F72-6167-11AA-AA11-00306543ECAC", "macOS Darwin", "Apple Core Storage (i.e. Lion FileVault)" }, + { "B6FA30DA-92D2-4A9A-96F1-871EC6486200", "macOS Darwin", "SoftRAID_Status" }, + { "2E313465-19B9-463F-8126-8A7993773801", "macOS Darwin", "SoftRAID_Scratch" }, + { "FA709C7E-65B1-4593-BFD5-E71D61DE9B02", "macOS Darwin", "SoftRAID_Volume" }, + { "BBBA6DF5-F46F-4A89-8F59-8765B2727503", "macOS Darwin", "SoftRAID_Cache" }, + { "6A82CB45-1DD2-11B2-99A6-080020736631", "Solaris illumos", "Boot" }, + { "6A85CF4D-1DD2-11B2-99A6-080020736631", "Solaris illumos", "Root" }, + { "6A87C46F-1DD2-11B2-99A6-080020736631", "Solaris illumos", "Swap" }, + { "6A8B642B-1DD2-11B2-99A6-080020736631", "Solaris illumos", "Backup" }, + { "6A898CC3-1DD2-11B2-99A6-080020736631", "Solaris illumos", "/usr" }, + { "6A8EF2E9-1DD2-11B2-99A6-080020736631", "Solaris illumos", "/var" }, + { "6A90BA39-1DD2-11B2-99A6-080020736631", "Solaris illumos", "/home" }, + { "6A9283A5-1DD2-11B2-99A6-080020736631", "Solaris illumos", "Alternate sector" }, + { "6A945A3B-1DD2-11B2-99A6-080020736631", "Solaris illumos", "Reserved" }, + { "6A9630D1-1DD2-11B2-99A6-080020736631", "Solaris illumos", "Reserved" }, + { "6A980767-1DD2-11B2-99A6-080020736631", "Solaris illumos", "Reserved"}, + { "6A96237F-1DD2-11B2-99A6-080020736631", "Solaris illumos", "Reserved" }, + { "6A8D2AC7-1DD2-11B2-99A6-080020736631", "Solaris illumos", "Reserved" }, + { "49F48D32-B10E-11DC-B99B-0019D1879648", "NetBSD", "Swap" }, + { "49F48D5A-B10E-11DC-B99B-0019D1879648", "NetBSD", "FFS" }, + { "49F48D82-B10E-11DC-B99B-0019D1879648", "NetBSD", "LFS" }, + { "49F48DAA-B10E-11DC-B99B-0019D1879648", "NetBSD", "RAID" }, + { "2DB519C4-B10F-11DC-B99B-0019D1879648", "NetBSD", "Concatenated" }, + { "2DB519EC-B10F-11DC-B99B-0019D1879648", "NetBSD", "Encrypted" }, + { "FE3A2A5D-4F32-41A7-B725-ACCC3285A309", "Chrome OS", "kernel" }, + { "3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC", "Chrome OS", "rootfs" }, + { "2E0A753D-9E48-43B0-8337-B15192CB1B5E", "Chrome OS", "future use" }, + { "5DFBF5F4-2848-4BAC-AA5E-0D9A20B745A6", "Container Linux", "/usr (coreos-usr)" }, + { "3884DD41-8582-4404-B9A8-E9B84F2DF50E", "Container Linux", "Resizable rootfs (coreos-resize)" }, + { "C95DC21A-DF0E-4340-8D7B-26CBFA9A03E0", "Container Linux", "OEM customizations (coreos-reserved)" }, + { "BE9067B9-EA49-4F15-B4F6-F36F8C9E1818", "Container Linux", "Root filesystem on RAID (coreos-root-raid)" }, + { "42465331-3BA3-10F1-802A-4861696B7521", "Haiku", "BFS" }, + { "85D5E45E-237C-11E1-B4B3-E89A8F7FC3A7", "MidnightBSD", "Boot" }, + { "85D5E45A-237C-11E1-B4B3-E89A8F7FC3A7", "MidnightBSD", "Data" }, + { "85D5E45B-237C-11E1-B4B3-E89A8F7FC3A7", "MidnightBSD", "Swap" }, + { "0394EF8B-237E-11E1-B4B3-E89A8F7FC3A7", "MidnightBSD", "Unix File System (UFS)" }, + { "85D5E45C-237C-11E1-B4B3-E89A8F7FC3A7", "MidnightBSD", "Vinum volume manager" }, + { "85D5E45D-237C-11E1-B4B3-E89A8F7FC3A7", "MidnightBSD", "ZFS" }, + { "45B0969E-9B03-4F30-B4C6-B4B80CEFF106", "Ceph", "Journal" }, + { "45B0969E-9B03-4F30-B4C6-5EC00CEFF106", "Ceph", "dm-crypt journal" }, + { "4FBD7E29-9D25-41B8-AFD0-062C0CEFF05D", "Ceph", "OSD" }, + { "4FBD7E29-9D25-41B8-AFD0-5EC00CEFF05D", "Ceph", "dm-crypt OSD" }, + { "89C57F98-2FE5-4DC0-89C1-F3AD0CEFF2BE", "Ceph", "Disk in creation" }, + { "89C57F98-2FE5-4DC0-89C1-5EC00CEFF2BE", "Ceph", "dm-crypt disk in creation" }, + { "CAFECAFE-9B03-4F30-B4C6-B4B80CEFF106", "Ceph", "Block" }, + { "30CD0809-C2B2-499C-8879-2D6B78529876", "Ceph", "Block DB" }, + { "5CE17FCE-4087-4169-B7FF-056CC58473F9", "Ceph", "Block write-ahead log" }, + { "FB3AABF9-D25F-47CC-BF5E-721D1816496B", "Ceph", "Lockbox for dm-crypt keys" }, + { "4FBD7E29-8AE0-4982-BF9D-5A8D867AF560", "Ceph", "Multipath OSD" }, + { "45B0969E-8AE0-4982-BF9D-5A8D867AF560", "Ceph", "Multipath journal" }, + { "CAFECAFE-8AE0-4982-BF9D-5A8D867AF560", "Ceph", "Multipath block" }, + { "7F4A666A-16F3-47A2-8445-152EF4D03F6C", "Ceph", "Multipath block" }, + { "EC6D6385-E346-45DC-BE91-DA2A7C8B3261", "Ceph", "Multipath block DB" }, + { "01B41E1B-002A-453C-9F17-88793989FF8F", "Ceph", "Multipath block write-ahead log" }, + { "CAFECAFE-9B03-4F30-B4C6-5EC00CEFF106", "Ceph", "dm-crypt block" }, + { "93B0052D-02D9-4D8A-A43B-33A3EE4DFBC3", "Ceph", "dm-crypt block DB" }, + { "306E8683-4FE2-4330-B7C0-00A917C16966", "Ceph", "dm-crypt block write-ahead log" }, + { "45B0969E-9B03-4F30-B4C6-35865CEFF106", "Ceph", "dm-crypt LUKS journal" }, + { "CAFECAFE-9B03-4F30-B4C6-35865CEFF106", "Ceph", "dm-crypt LUKS block" }, + { "166418DA-C469-4022-ADF4-B30AFD37F176", "Ceph", "dm-crypt LUKS block DB" }, + { "86A32090-3647-40B9-BBBD-38D8C573AA86", "Ceph", "dm-crypt LUKS block write-ahead log" }, + { "4FBD7E29-9D25-41B8-AFD0-35865CEFF05D", "Ceph", "dm-crypt LUKS OSD" }, + { "824CC7A0-36A8-11E3-890A-952519AD3F61", "OpenBSD", "Data" }, + { "CEF5A9AD-73BC-4601-89F3-CDEEEEE321A1", "QNX", "Power-safe (QNX6) file system[45]" }, + { "C91818F9-8025-47AF-89D2-F030D7000C2C", "Plan 9" }, + { "9D275380-40AD-11DB-BF97-000C2911D1B8", "VMware ESX", "vmkcore (coredump )" }, + { "AA31E02A-400F-11DB-9590-000C2911D1B8", "VMware ESX", "VMFS filesystem" }, + { "9198EFFC-31C0-11DB-8F78-000C2911D1B8", "VMware ESX", "VMware Reserved" }, + { "2568845D-2332-4675-BC39-8FA5A4748D15", "Android-IA", "Bootloader" }, + { "114EAFFE-1552-4022-B26E-9B053604CF84", "Android-IA", "Bootloader2" }, + { "49A4D17F-93A3-45C1-A0DE-F50B2EBE2599", "Android-IA", "Boot" }, + { "4177C722-9E92-4AAB-8644-43502BFD5506", "Android-IA", "Recovery" }, + { "EF32A33B-A409-486C-9141-9FFB711F6266", "Android-IA", "Misc" }, + { "20AC26BE-20B7-11E3-84C5-6CFDB94711E9", "Android-IA", "Metadata" }, + { "38F428E6-D326-425D-9140-6E0EA133647C", "Android-IA", "System" }, + { "A893EF21-E428-470A-9E55-0668FD91A2D9", "Android-IA", "Cache" }, + { "DC76DDA9-5AC1-491C-AF42-A82591580C0D", "Android-IA", "Data" }, + { "EBC597D0-2053-4B15-8B64-E0AAC75F4DB1", "Android-IA", "Persistent" }, + { "C5A0AEEC-13EA-11E5-A1B1-001E67CA0C3C", "Android-IA", "Vendor" }, + { "BD59408B-4514-490D-BF12-9878D963F378", "Android-IA", "Config" }, + { "8F68CC74-C5E5-48DA-BE91-A0C8C15E9C80", "Android-IA", "Factory" }, + { "9FDAA6EF-4B3F-40D2-BA8D-BFF16BFB887B", "Android-IA", "Factory (alt)[50]" }, + { "767941D0-2085-11E3-AD3B-6CFDB94711E9", "Android-IA", "Fastboot / Tertiary" }, + { "AC6D7924-EB71-4DF8-B48D-E267B27148FF", "Android-IA", "OEM" }, + { "19A710A2-B3CA-11E4-B026-10604B889DCF", "Android 6.0+ ARM", "Android Meta" }, + { "193D1EA4-B3CA-11E4-B075-10604B889DCF", "Android 6.0+ ARM", "Android EXT" }, + { "7412F7D5-A156-4B13-81DC-867174929325", "ONIE", "Boot" }, + { "D4E6E2CD-4469-46F3-B5CB-1BFF57AFC149", "ONIE", "Config" }, + { "9E1A2D38-C612-4316-AA26-8B49521E5A8B", "PowerPC", "PReP boot" }, + { "BC13C2FF-59E6-4262-A352-B275FD6F7172", "freedesktop.org", "Shared boot loader configuration" }, + { "734E5AFE-F61A-11E6-BC64-92361F002671", "Atari TOS", "Basic data (GEM, BGM, F32)" }, +}; + +#endif /* !VBOX_INCLUDED_SRC_vboximg_mount_vboximg_mount_h */ diff --git a/src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.cpp b/src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.cpp new file mode 100644 index 00000000..c1836501 --- /dev/null +++ b/src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.cpp @@ -0,0 +1,412 @@ +/* $Id: vboximgCrypto.cpp $ */ + +/** @file + * vboximgCypto.cpp - Disk Image Flattening FUSE Program. + */ + +/* + * Copyright (C) 2009-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. + */ + +#include <iprt/cdefs.h> +#include <VBox/err.h> +#include <VBox/settings.h> +#include <VBox/vd.h> +#include "vboximgCrypto.h" +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/memsafer.h> + +/* + * Apparently there is a more COM:: oriented (but less efficient?) approach to dealing + * with the keystore and disk encryption, which will need to be investigated. Keeping + * all this duplicated code in a separate file until the ideal approach is determined. + */ +SecretKey::SecretKey(const uint8_t *pbKey, size_t cbKey, bool fKeyBufNonPageable) +{ + m_cRefs = 0; + m_fRemoveOnSuspend = false; + m_cUsers = 0; + m_cbKey = cbKey; + + int rc = RTMemSaferAllocZEx((void **)&this->m_pbKey, cbKey, + fKeyBufNonPageable ? RTMEMSAFER_F_REQUIRE_NOT_PAGABLE : 0); + if (RT_SUCCESS(rc)) + { + memcpy(this->m_pbKey, pbKey, cbKey); + + /* Scramble content to make retrieving the key more difficult. */ + rc = RTMemSaferScramble(this->m_pbKey, cbKey); + } + else + throw rc; +} + +SecretKey::~SecretKey() +{ + Assert(!m_cRefs); + + RTMemSaferFree(m_pbKey, m_cbKey); + m_cRefs = 0; + m_pbKey = NULL; + m_cbKey = 0; + m_fRemoveOnSuspend = false; + m_cUsers = 0; +} + +uint32_t SecretKey::retain() +{ + uint32_t cRefs = ASMAtomicIncU32(&m_cRefs); + if (cRefs == 1) + { + int rc = RTMemSaferUnscramble(m_pbKey, m_cbKey); + AssertRC(rc); + } + + return cRefs; +} + +uint32_t SecretKey::release() +{ + uint32_t cRefs = ASMAtomicDecU32(&m_cRefs); + if (!cRefs) + { + int rc = RTMemSaferScramble(m_pbKey, m_cbKey); + AssertRC(rc); + } + + return cRefs; +} + +uint32_t SecretKey::refCount() +{ + return m_cRefs; +} + +int SecretKey::setUsers(uint32_t cUsers) +{ + m_cUsers = cUsers; + return VINF_SUCCESS; +} + +uint32_t SecretKey::getUsers() +{ + return m_cUsers; +} + +int SecretKey::setRemoveOnSuspend(bool fRemoveOnSuspend) +{ + m_fRemoveOnSuspend = fRemoveOnSuspend; + return VINF_SUCCESS; +} + +bool SecretKey::getRemoveOnSuspend() +{ + return m_fRemoveOnSuspend; +} + +const void *SecretKey::getKeyBuffer() +{ + AssertReturn(m_cRefs > 0, NULL); + return m_pbKey; +} + +size_t SecretKey::getKeySize() +{ + return m_cbKey; +} + +SecretKeyStore::SecretKeyStore(bool fKeyBufNonPageable) +{ + m_fKeyBufNonPageable = fKeyBufNonPageable; +} + +SecretKeyStore::~SecretKeyStore() +{ + int rc = deleteAllSecretKeys(false /* fSuspend */, true /* fForce */); + AssertRC(rc); +} + +int SecretKeyStore::addSecretKey(const com::Utf8Str &strKeyId, const uint8_t *pbKey, size_t cbKey) +{ + /* Check that the ID is not existing already. */ + SecretKeyMap::const_iterator it = m_mapSecretKeys.find(strKeyId); + if (it != m_mapSecretKeys.end()) + return VERR_ALREADY_EXISTS; + + SecretKey *pKey = NULL; + try + { + pKey = new SecretKey(pbKey, cbKey, m_fKeyBufNonPageable); + + m_mapSecretKeys.insert(std::make_pair(strKeyId, pKey)); + } + catch (int rc) + { + return rc; + } + catch (std::bad_alloc &) + { + if (pKey) + delete pKey; + return VERR_NO_MEMORY; + } + + return VINF_SUCCESS; +} + +int SecretKeyStore::deleteSecretKey(const com::Utf8Str &strKeyId) +{ + SecretKeyMap::iterator it = m_mapSecretKeys.find(strKeyId); + if (it == m_mapSecretKeys.end()) + return VERR_NOT_FOUND; + + SecretKey *pKey = it->second; + if (pKey->refCount() != 0) + return VERR_RESOURCE_IN_USE; + + m_mapSecretKeys.erase(it); + delete pKey; + + return VINF_SUCCESS; +} + +int SecretKeyStore::retainSecretKey(const com::Utf8Str &strKeyId, SecretKey **ppKey) +{ + SecretKeyMap::const_iterator it = m_mapSecretKeys.find(strKeyId); + if (it == m_mapSecretKeys.end()) + return VERR_NOT_FOUND; + + SecretKey *pKey = it->second; + pKey->retain(); + + *ppKey = pKey; + + return VINF_SUCCESS; +} + +int SecretKeyStore::releaseSecretKey(const com::Utf8Str &strKeyId) +{ + SecretKeyMap::const_iterator it = m_mapSecretKeys.find(strKeyId); + if (it == m_mapSecretKeys.end()) + return VERR_NOT_FOUND; + + SecretKey *pKey = it->second; + pKey->release(); + return VINF_SUCCESS; +} + +int SecretKeyStore::deleteAllSecretKeys(bool fSuspend, bool fForce) +{ + /* First check whether a key is still in use. */ + if (!fForce) + { + for (SecretKeyMap::iterator it = m_mapSecretKeys.begin(); + it != m_mapSecretKeys.end(); + ++it) + { + SecretKey *pKey = it->second; + if ( pKey->refCount() + && ( ( pKey->getRemoveOnSuspend() + && fSuspend) + || !fSuspend)) + return VERR_RESOURCE_IN_USE; + } + } + + SecretKeyMap::iterator it = m_mapSecretKeys.begin(); + while (it != m_mapSecretKeys.end()) + { + SecretKey *pKey = it->second; + if ( pKey->getRemoveOnSuspend() + || !fSuspend) + { + AssertMsg(!pKey->refCount(), ("No one should access the stored key at this point anymore!\n")); + delete pKey; + SecretKeyMap::iterator itNext = it; + ++itNext; + m_mapSecretKeys.erase(it); + it = itNext; + } + else + ++it; + } + + return VINF_SUCCESS; +} + +void vboxImageCryptoSetup(VDISKCRYPTOSETTINGS *pSettings, const char *pszCipher, + const char *pszKeyStore, const char *pszPassword, + bool fCreateKeyStore) +{ + pSettings->pszCipher = pszCipher; + pSettings->pszPassword = pszPassword; + pSettings->pszKeyStoreLoad = pszKeyStore; + pSettings->fCreateKeyStore = fCreateKeyStore; + pSettings->pbDek = NULL; + pSettings->cbDek = 0; + pSettings->vdFilterIfaces = NULL; + + pSettings->vdIfCfg.pfnAreKeysValid = vboximgVdCryptoConfigAreKeysValid; + pSettings->vdIfCfg.pfnQuerySize = vboximgVdCryptoConfigQuerySize; + pSettings->vdIfCfg.pfnQuery = vboximgVdCryptoConfigQuery; + pSettings->vdIfCfg.pfnQueryBytes = NULL; + + pSettings->vdIfCrypto.pfnKeyRetain = vboximgVdCryptoKeyRetain; + pSettings->vdIfCrypto.pfnKeyRelease = vboximgVdCryptoKeyRelease; + pSettings->vdIfCrypto.pfnKeyStorePasswordRetain = vboximgVdCryptoKeyStorePasswordRetain; + pSettings->vdIfCrypto.pfnKeyStorePasswordRelease = vboximgVdCryptoKeyStorePasswordRelease; + pSettings->vdIfCrypto.pfnKeyStoreSave = vboximgVdCryptoKeyStoreSave; + pSettings->vdIfCrypto.pfnKeyStoreReturnParameters = vboximgVdCryptoKeyStoreReturnParameters; + + int rc = VDInterfaceAdd(&pSettings->vdIfCfg.Core, + "vboximgVdInterfaceCfgCrypto", + VDINTERFACETYPE_CONFIG, pSettings, + sizeof(VDINTERFACECONFIG), &pSettings->vdFilterIfaces); + AssertRC(rc); + + rc = VDInterfaceAdd(&pSettings->vdIfCrypto.Core, + "vboximgVdInterfaceCrypto", + VDINTERFACETYPE_CRYPTO, pSettings, + sizeof(VDINTERFACECRYPTO), &pSettings->vdFilterIfaces); + AssertRC(rc); +} + +DECLCALLBACK(bool) vboximgVdCryptoConfigAreKeysValid(void *pvUser, const char *pszzValid) +{ + /* Just return always true here. */ + NOREF(pvUser); + NOREF(pszzValid); + return true; +} + +DECLCALLBACK(int) vboximgVdCryptoConfigQuerySize(void *pvUser, const char *pszName, size_t *pcbValue) +{ + VDISKCRYPTOSETTINGS *pSettings = (VDISKCRYPTOSETTINGS *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + AssertReturn(VALID_PTR(pcbValue), VERR_INVALID_POINTER); + + size_t cbValue = 0; + if (!strcmp(pszName, "Algorithm")) + cbValue = strlen(pSettings->pszCipher) + 1; + else if (!strcmp(pszName, "KeyId")) + cbValue = sizeof("irrelevant"); + else if (!strcmp(pszName, "KeyStore")) + { + if (!pSettings->pszKeyStoreLoad) + return VERR_CFGM_VALUE_NOT_FOUND; + cbValue = strlen(pSettings->pszKeyStoreLoad) + 1; + } + else if (!strcmp(pszName, "CreateKeyStore")) + cbValue = 2; /* Single digit + terminator. */ + else + return VERR_CFGM_VALUE_NOT_FOUND; + + *pcbValue = cbValue + 1 /* include terminator */; + + return VINF_SUCCESS; +} + +DECLCALLBACK(int) vboximgVdCryptoConfigQuery(void *pvUser, const char *pszName, + char *pszValue, size_t cchValue) +{ + VDISKCRYPTOSETTINGS *pSettings = (VDISKCRYPTOSETTINGS *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + AssertReturn(VALID_PTR(pszValue), VERR_INVALID_POINTER); + + const char *psz = NULL; + if (!strcmp(pszName, "Algorithm")) + psz = pSettings->pszCipher; + else if (!strcmp(pszName, "KeyId")) + psz = "irrelevant"; + else if (!strcmp(pszName, "KeyStore")) + psz = pSettings->pszKeyStoreLoad; + else if (!strcmp(pszName, "CreateKeyStore")) + { + if (pSettings->fCreateKeyStore) + psz = "1"; + else + psz = "0"; + } + else + return VERR_CFGM_VALUE_NOT_FOUND; + + size_t cch = strlen(psz); + if (cch >= cchValue) + return VERR_CFGM_NOT_ENOUGH_SPACE; + + memcpy(pszValue, psz, cch + 1); + return VINF_SUCCESS; +} + +DECLCALLBACK(int) vboximgVdCryptoKeyRetain(void *pvUser, const char *pszId, + const uint8_t **ppbKey, size_t *pcbKey) +{ + VDISKCRYPTOSETTINGS *pSettings = (VDISKCRYPTOSETTINGS *)pvUser; + NOREF(pszId); + NOREF(ppbKey); + NOREF(pcbKey); + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + AssertMsgFailedReturn(("This method should not be called here!\n"), VERR_INVALID_STATE); +} + +DECLCALLBACK(int) vboximgVdCryptoKeyRelease(void *pvUser, const char *pszId) +{ + VDISKCRYPTOSETTINGS *pSettings = (VDISKCRYPTOSETTINGS *)pvUser; + NOREF(pszId); + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + AssertMsgFailedReturn(("This method should not be called here!\n"), VERR_INVALID_STATE); +} + +DECLCALLBACK(int) vboximgVdCryptoKeyStorePasswordRetain(void *pvUser, const char *pszId, const char **ppszPassword) +{ + VDISKCRYPTOSETTINGS *pSettings = (VDISKCRYPTOSETTINGS *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + + NOREF(pszId); + *ppszPassword = pSettings->pszPassword; + return VINF_SUCCESS; +} + +DECLCALLBACK(int) vboximgVdCryptoKeyStorePasswordRelease(void *pvUser, const char *pszId) +{ + VDISKCRYPTOSETTINGS *pSettings = (VDISKCRYPTOSETTINGS *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + NOREF(pszId); + return VINF_SUCCESS; +} + +DECLCALLBACK(int) vboximgVdCryptoKeyStoreSave(void *pvUser, const void *pvKeyStore, size_t cbKeyStore) +{ + VDISKCRYPTOSETTINGS *pSettings = (VDISKCRYPTOSETTINGS *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + + pSettings->pszKeyStore = (char *)RTMemAllocZ(cbKeyStore); + if (!pSettings->pszKeyStore) + return VERR_NO_MEMORY; + + memcpy(pSettings->pszKeyStore, pvKeyStore, cbKeyStore); + return VINF_SUCCESS; +} + +DECLCALLBACK(int) vboximgVdCryptoKeyStoreReturnParameters(void *pvUser, const char *pszCipher, + const uint8_t *pbDek, size_t cbDek) +{ + VDISKCRYPTOSETTINGS *pSettings = (VDISKCRYPTOSETTINGS *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + + pSettings->pszCipherReturned = RTStrDup(pszCipher); + pSettings->pbDek = pbDek; + pSettings->cbDek = cbDek; + + return pSettings->pszCipherReturned ? VINF_SUCCESS : VERR_NO_MEMORY; +} diff --git a/src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.h b/src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.h new file mode 100644 index 00000000..597535fd --- /dev/null +++ b/src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.h @@ -0,0 +1,255 @@ +/* $Id: vboximgCrypto.h $ */ + +/** @file + * vboximgCrypto.h + */ + +/* + * Copyright (C) 2008-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. + */ + +#ifndef VBOX_INCLUDED_SRC_vboximg_mount_vboximgCrypto_h +#define VBOX_INCLUDED_SRC_vboximg_mount_vboximgCrypto_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <map> + +/* + * Settings for a crypto filter instance. + */ +typedef struct VDiskCryptoSettings +{ + VDiskCryptoSettings() + : fCreateKeyStore(false), + pszPassword(NULL), + pszKeyStore(NULL), + pszKeyStoreLoad(NULL), + pbDek(NULL), + cbDek(0), + pszCipher(NULL), + pszCipherReturned(NULL) + { } + + bool fCreateKeyStore; + const char *pszPassword; + char *pszKeyStore; + const char *pszKeyStoreLoad; + + const uint8_t *pbDek; + size_t cbDek; + const char *pszCipher; + + /** The cipher returned by the crypto filter. */ + char *pszCipherReturned; + + PVDINTERFACE vdFilterIfaces; + + VDINTERFACECONFIG vdIfCfg; + VDINTERFACECRYPTO vdIfCrypto; +} VDISKCRYPTOSETTINGS; + + +class SecretKey +{ + public: + + /** + * Constructor for a secret key. + * + * @param pbKey The key buffer. + * @param cbKey Size of the key. + * @param fKeyBufNonPageable Flag whether the key buffer should be non pageable. + */ + SecretKey(const uint8_t *pbKey, size_t cbKey, bool fKeyBufNonPageable); + + /** + * Secret key destructor. + */ + ~SecretKey(); + + /** + * Increments the reference counter of the key. + * + * @returns The new reference count. + */ + uint32_t retain(); + + /** + * Releases a reference of the key. + * If the reference counter reaches 0 the key buffer might be protected + * against further access or the data will become scrambled. + * + * @returns The new reference count. + */ + uint32_t release(); + + /** + * Returns the reference count of the secret key. + */ + uint32_t refCount(); + + /** + * Sets the possible number of users for this key. + * + * @returns VBox status code. + * @param cUsers The possible number of user for this key. + */ + int setUsers(uint32_t cUsers); + + /** + * Returns the possible amount of users. + * + * @returns Possible amount of users. + */ + uint32_t getUsers(); + + /** + * Sets the remove on suspend flag. + * + * @returns VBox status code. + * @param fRemoveOnSuspend Flag whether to remove the key on host suspend. + */ + int setRemoveOnSuspend(bool fRemoveOnSuspend); + + /** + * Returns whether the key should be destroyed on suspend. + */ + bool getRemoveOnSuspend(); + + /** + * Returns the buffer to the key. + */ + const void *getKeyBuffer(); + + /** + * Returns the size of the key. + */ + size_t getKeySize(); + + private: + /** Reference counter of the key. */ + volatile uint32_t m_cRefs; + /** Key material. */ + uint8_t *m_pbKey; + /** Size of the key in bytes. */ + size_t m_cbKey; + /** Flag whether to remove the key on suspend. */ + bool m_fRemoveOnSuspend; + /** Number of entities which will use this key. */ + uint32_t m_cUsers; +}; + +class SecretKeyStore +{ + public: + + /** + * Constructor for a secret key store. + * + * @param fKeyBufNonPageable Flag whether the key buffer is required to + * be non pageable. + */ + SecretKeyStore(bool fKeyBufNonPageable); + + /** + * Destructor of a secret key store. This will free all stored secret keys + * inluding the key buffers. Make sure there no one accesses one of the keys + * stored. + */ + ~SecretKeyStore(); + + /** + * Add a secret key to the store. + * + * @returns VBox status code. + * @param strKeyId The key identifier. + * @param pbKey The key to store. + * @param cbKey Size of the key. + */ + int addSecretKey(const com::Utf8Str &strKeyId, const uint8_t *pbKey, size_t cbKey); + + /** + * Deletes a key from the key store associated with the given identifier. + * + * @returns VBox status code. + * @param strKeyId The key identifier. + */ + int deleteSecretKey(const com::Utf8Str &strKeyId); + + /** + * Returns the secret key object associated with the given identifier. + * This increments the reference counter of the secret key object. + * + * @returns VBox status code. + * @param strKeyId The key identifier. + * @param ppKey Where to store the secret key object on success. + */ + int retainSecretKey(const com::Utf8Str &strKeyId, SecretKey **ppKey); + + /** + * Releases a reference to the secret key object. + * + * @returns VBox status code. + * @param strKeyId The key identifier. + */ + int releaseSecretKey(const com::Utf8Str &strKeyId); + + /** + * Deletes all secret keys from the key store. + * + * @returns VBox status code. + * @param fSuspend Flag whether to delete only keys which are + * marked for deletion during a suspend. + * @param fForce Flag whether to force deletion if some keys + * are still in use. Otherwise an error is returned. + */ + int deleteAllSecretKeys(bool fSuspend, bool fForce); + + private: + + typedef std::map<com::Utf8Str, SecretKey *> SecretKeyMap; + + /** The map to map key identifers to secret keys. */ + SecretKeyMap m_mapSecretKeys; + /** Flag whether key buffers should be non pagable. */ + bool m_fKeyBufNonPageable; +}; + +void vboxImageCryptoSetup(VDISKCRYPTOSETTINGS *pSettings, const char *pszCipher, + const char *pszKeyStore, const char *pszPassword, + bool fCreateKeyStore); + +DECLCALLBACK(bool) vboximgVdCryptoConfigAreKeysValid(void *pvUser, const char *pszzValid); + +DECLCALLBACK(int) vboximgVdCryptoConfigQuerySize(void *pvUser, const char *pszName, size_t *pcbValue); + +DECLCALLBACK(int) vboximgVdCryptoConfigQuery(void *pvUser, const char *pszName, + char *pszValue, size_t cchValue); + +DECLCALLBACK(int) vboximgVdCryptoKeyRetain(void *pvUser, const char *pszId, + const uint8_t **ppbKey, size_t *pcbKey); + +DECLCALLBACK(int) vboximgVdCryptoKeyRelease(void *pvUser, const char *pszId); + +DECLCALLBACK(int) vboximgVdCryptoKeyStorePasswordRetain(void *pvUser, const char *pszId, const char **ppszPassword); + +DECLCALLBACK(int) vboximgVdCryptoKeyStorePasswordRelease(void *pvUser, const char *pszId); + +DECLCALLBACK(int) vboximgVdCryptoKeyStoreSave(void *pvUser, const void *pvKeyStore, size_t cbKeyStore); + +DECLCALLBACK(int) vboximgVdCryptoKeyStoreReturnParameters(void *pvUser, const char *pszCipher, + const uint8_t *pbDek, size_t cbDek); + + +#endif /* !VBOX_INCLUDED_SRC_vboximg_mount_vboximgCrypto_h */ + diff --git a/src/VBox/ImageMounter/vboximg-mount/vboximgMedia.cpp b/src/VBox/ImageMounter/vboximg-mount/vboximgMedia.cpp new file mode 100644 index 00000000..7e2071ec --- /dev/null +++ b/src/VBox/ImageMounter/vboximg-mount/vboximgMedia.cpp @@ -0,0 +1,381 @@ +/* $Id: vboximgMedia.cpp $ */ +/** @file + * vboximgMedia.cpp - Disk Image Flattening FUSE Program. + */ + +/* + * Copyright (C) 2009-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. + */ + +#include <VirtualBox_XPCOM.h> +#include <VBox/com/VirtualBox.h> +#include <VBox/vd.h> +#include <VBox/vd-ifs.h> +#include <VBox/log.h> +#include <iprt/errcore.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/NativeEventQueue.h> +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/errorprint.h> +#include <VBox/vd-plugin.h> +#include <iprt/initterm.h> +#include <iprt/assert.h> +#include <iprt/message.h> +#include <iprt/critsect.h> +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/types.h> +#include <iprt/path.h> +#include <iprt/utf16.h> +#include <math.h> +#include "vboximgOpts.h" + +using namespace com; + +extern VBOXIMGOPTS g_vboximgOpts; + +#define SAFENULL(strPtr) (strPtr ? strPtr : "") /** Makes null harmless to print */ +#define CSTR(arg) Utf8Str(arg).c_str() /** Converts XPCOM string type to C string type */ +#define MAX_UUID_LEN 256 /** Max length of a UUID */ +#define VM_MAX_NAME 32 /** Max length of VM name we handle */ + +typedef struct MEDIUMINFO +{ + char *pszName; + char *pszUuid; + char *pszBaseUuid; + char *pszPath; + char *pszDescription; + char *pszState; + char *pszType; + char *pszFormat; + bool fSnapshot; + PRInt64 cbSize; + MediumType_T type; + MediumState_T state; + ~MEDIUMINFO() + { + RTMemFree(pszName); + RTMemFree(pszUuid); + RTMemFree(pszPath); + RTMemFree(pszDescription); + RTMemFree(pszFormat); + } +} MEDIUMINFO; + + +char *vboximgScaledSize(size_t size) +{ + uint64_t exp = log2((double)size); + char scaledMagnitude = ((char []){ ' ', 'K', 'M', 'G', 'T', 'P' })[exp / 10]; + /* This workaround is because IPRT RT*Printf* funcs don't handle floating point format specifiers */ + double cbScaled = (double)size / pow(2, (double)(((uint64_t)(exp / 10)) * 10)); + uint64_t intPart = cbScaled; + uint64_t fracPart = (cbScaled - (double)intPart) * 10; + char tmp[256]; + RTStrPrintf(tmp, sizeof (tmp), "%d.%d%c", intPart, fracPart, scaledMagnitude); + return RTStrDup(tmp); +} + +static int +getMediumInfo(IMachine *pMachine, IMedium *pMedium, MEDIUMINFO **ppMediumInfo) +{ + NOREF(pMachine); + int rc; + MEDIUMINFO *info = new MEDIUMINFO(); + *ppMediumInfo = info; + + Bstr name; + Bstr uuid; + Bstr baseUuid; + Bstr path; + Bstr description; + Bstr format; + PRInt64 *pSize = &info->cbSize; + ComPtr<IMedium> pBase; + MediumType_T *pType = &info->type; + MediumState_T *pState = &info->state; + + *pState = MediumState_NotCreated; + + CHECK_ERROR(pMedium, RefreshState(pState)); + CHECK_ERROR(pMedium, COMGETTER(Id)(uuid.asOutParam())); + CHECK_ERROR(pMedium, COMGETTER(Base)(pBase.asOutParam())); + CHECK_ERROR(pBase, COMGETTER(Id)(baseUuid.asOutParam())); + + CHECK_ERROR(pMedium, COMGETTER(State)(pState)); + + CHECK_ERROR(pMedium, COMGETTER(Location)(path.asOutParam())); + CHECK_ERROR(pMedium, COMGETTER(Format)(format.asOutParam())); + CHECK_ERROR(pMedium, COMGETTER(Type)(pType)); + CHECK_ERROR(pMedium, COMGETTER(Size)(pSize)); + + info->pszUuid = RTStrDup((char *)CSTR(uuid)); + info->pszBaseUuid = RTStrDup((char *)CSTR(baseUuid)); + info->pszPath = RTStrDup((char *)CSTR(path)); + info->pszFormat = RTStrDup((char *)CSTR(format)); + info->fSnapshot = RTStrCmp(CSTR(uuid), CSTR(baseUuid)) != 0; + + if (info->fSnapshot) + { + /** @todo Determine the VM snapshot this and set name and description + * to the snapshot name/description + */ + CHECK_ERROR(pMedium, COMGETTER(Name)(name.asOutParam())); + CHECK_ERROR(pMedium, COMGETTER(Description)(description.asOutParam())); + } + else + { + CHECK_ERROR(pMedium, COMGETTER(Name)(name.asOutParam())); + CHECK_ERROR(pMedium, COMGETTER(Description)(description.asOutParam())); + } + + info->pszName = RTStrDup((char *)CSTR(name)); + info->pszDescription = RTStrDup((char *)CSTR(description)); + + switch(*pType) + { + case MediumType_Normal: + info->pszType = (char *)"normal"; + break; + case MediumType_Immutable: + info->pszType = (char *)"immutable"; + break; + case MediumType_Writethrough: + info->pszType = (char *)"writethrough"; + break; + case MediumType_Shareable: + info->pszType = (char *)"shareable"; + break; + case MediumType_Readonly: + info->pszType = (char *)"readonly"; + break; + case MediumType_MultiAttach: + info->pszType = (char *)"multiattach"; + break; + default: + info->pszType = (char *)"?"; + } + + switch(*pState) + { + case MediumState_NotCreated: + info->pszState = (char *)"uncreated"; + break; + case MediumState_Created: + info->pszState = (char *)"created"; + break; + case MediumState_LockedRead: + info->pszState = (char *)"rlock"; + break; + case MediumState_LockedWrite: + info->pszState = (char *)"wlock"; + break; + case MediumState_Inaccessible: + info->pszState = (char *)"no access"; + break; + case MediumState_Creating: + info->pszState = (char *)"creating"; + break; + case MediumState_Deleting: + info->pszState = (char *)"deleting"; + break; + default: + info->pszState = (char *)"?"; + } + return VINF_SUCCESS; +} + +static void displayMediumInfo(MEDIUMINFO *pInfo, int nestLevel, bool fLast) +{ + + char *cbScaled = vboximgScaledSize(pInfo->cbSize); + int cPad = nestLevel * 2; + if (g_vboximgOpts.fWide && !g_vboximgOpts.fVerbose) + { + RTPrintf("%3s %-*s %7s %-9s %9s %-*s %s\n", + !fLast ? (pInfo->fSnapshot ? " | " : " +-") : (pInfo->fSnapshot ? " " : " +-"), + VM_MAX_NAME, pInfo->fSnapshot ? "+- <snapshot>" : pInfo->pszName, + cbScaled, + pInfo->pszFormat, + pInfo->pszState, + cPad, "", pInfo->pszUuid); + RTMemFree(cbScaled); + } + else + { + if (!pInfo->fSnapshot) + { + RTPrintf(" Image: %s\n", pInfo->pszName); + if (pInfo->pszDescription && RTStrNLen(pInfo->pszDescription, 256) > 0) + RTPrintf("Desc: %s\n", pInfo->pszDescription); + RTPrintf(" UUID: %s\n", pInfo->pszUuid); + if (g_vboximgOpts.fVerbose) + { + RTPrintf(" Path: %s\n", pInfo->pszPath); + RTPrintf(" Format: %s\n", pInfo->pszFormat); + RTPrintf(" Size: %s\n", cbScaled); + RTPrintf(" State: %s\n", pInfo->pszState); + RTPrintf(" Type: %s\n", pInfo->pszType); + } + RTPrintf("\n"); + } + else + { + RTPrintf(" Snapshot: %s\n", pInfo->pszUuid); + if (g_vboximgOpts.fVerbose) + { + RTPrintf(" Name: %s\n", pInfo->pszName); + RTPrintf(" Desc: %s\n", pInfo->pszDescription); + } + RTPrintf(" Size: %s\n", cbScaled); + if (g_vboximgOpts.fVerbose) + RTPrintf(" Path: %s\n", pInfo->pszPath); + RTPrintf("\n"); + } + } +} + +static int vboximgListBranch(IMachine *pMachine, IMedium *pMedium, uint8_t nestLevel, bool fLast) +{ + int rc; + MEDIUMINFO *pMediumInfo; + rc = getMediumInfo(pMachine, pMedium, &pMediumInfo); + if (FAILED(rc)) + return rc; + displayMediumInfo(pMediumInfo, nestLevel, fLast); + com::SafeIfaceArray<IMedium> pChildren; + CHECK_ERROR_RET(pMedium, COMGETTER(Children)(ComSafeArrayAsOutParam(pChildren)), rc); + for (size_t i = 0; i < pChildren.size(); i++) + vboximgListBranch(pMachine, pChildren[i], nestLevel + 1, fLast); + delete pMediumInfo; + return VINF_SUCCESS; +} + +static int +listMedia(IVirtualBox *pVirtualBox, IMachine *pMachine, char *vmName, char *vmUuid) +{ + + NOREF(pVirtualBox); + NOREF(vmName); + NOREF(vmUuid); + + int rc = 0; + com::SafeIfaceArray<IMediumAttachment> pMediumAttachments; + + CHECK_ERROR(pMachine, COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(pMediumAttachments))); + for (size_t i = 0; i < pMediumAttachments.size(); i++) + { + bool fLast = (i == pMediumAttachments.size() - 1); + DeviceType_T deviceType; + + CHECK_ERROR(pMediumAttachments[i], COMGETTER(Type)(&deviceType)); + if (deviceType != DeviceType_HardDisk) + continue; + + ComPtr<IMedium> pMedium; + CHECK_ERROR(pMediumAttachments[i], COMGETTER(Medium)(pMedium.asOutParam())); + + ComPtr<IMedium> pBase; + CHECK_ERROR(pMedium, COMGETTER(Base)(pBase.asOutParam())); + if (g_vboximgOpts.fWide && !g_vboximgOpts.fVerbose) + RTPrintf(" |\n"); + else + RTPrintf("\n"); + rc = vboximgListBranch(pMachine, pBase, 0, fLast); + if (FAILED(rc)) + { + RTPrintf("vboximgListBranch failed %d\n", rc); + return rc; + } + + } + return VINF_SUCCESS; +} +/** + * Display all registered VMs on the screen with some information about each + * + * @param virtualBox VirtualBox instance object. + */ +int +vboximgListVMs(IVirtualBox *pVirtualBox) +{ + HRESULT rc = 0; + com::SafeIfaceArray<IMachine> pMachines; + CHECK_ERROR(pVirtualBox, COMGETTER(Machines)(ComSafeArrayAsOutParam(pMachines))); + if (g_vboximgOpts.fWide) + { + RTPrintf("\n"); + RTPrintf("VM Image Size Type State UUID (hierarchy)\n"); + } + for (size_t i = 0; i < pMachines.size(); ++i) + { + ComPtr<IMachine> pMachine = pMachines[i]; + if (pMachine) + { + BOOL fAccessible; + CHECK_ERROR(pMachines[i], COMGETTER(Accessible)(&fAccessible)); + if (fAccessible) + { + Bstr machineName; + Bstr machineUuid; + Bstr description; + Bstr machineLocation; + + CHECK_ERROR(pMachine, COMGETTER(Name)(machineName.asOutParam())); + CHECK_ERROR(pMachine, COMGETTER(Id)(machineUuid.asOutParam())); + CHECK_ERROR(pMachine, COMGETTER(Description)(description.asOutParam())); + CHECK_ERROR(pMachine, COMGETTER(SettingsFilePath)(machineLocation.asOutParam())); + + + if ( g_vboximgOpts.pszVm == NULL + || RTStrNCmp(CSTR(machineUuid), g_vboximgOpts.pszVm, MAX_UUID_LEN) == 0 + || RTStrNCmp((const char *)machineName.raw(), g_vboximgOpts.pszVm, MAX_UUID_LEN) == 0) + { + if (g_vboximgOpts.fVerbose) + { + RTPrintf("-----------------------------------------------------------------\n"); + RTPrintf("VM Name: \"%s\"\n", CSTR(machineName)); + RTPrintf("UUID: %s\n", CSTR(machineUuid)); + if (*description.raw() != '\0') + RTPrintf("Desc: %s\n", CSTR(description)); + RTPrintf("Path: %s\n", CSTR(machineLocation)); + } + else + { + if (g_vboximgOpts.fWide & !g_vboximgOpts.fVerbose) + { + RTPrintf("----------------------------------------------------------------- " + "------------------------------------\n"); + RTPrintf("%-*s %*s %s\n", VM_MAX_NAME, CSTR(machineName), 33, "", CSTR(machineUuid)); + } + else + { + RTPrintf("-----------------------------------------------------------------\n"); + RTPrintf("VM: %s\n", CSTR(machineName)); + RTPrintf("UUID: %s\n", CSTR(machineUuid)); + } + } + rc = listMedia(pVirtualBox, pMachine, + RTStrDup(CSTR(machineName)), RTStrDup(CSTR(machineUuid))); + RTPrintf("\n"); + } + } + } + } + return rc; +} diff --git a/src/VBox/ImageMounter/vboximg-mount/vboximgMedia.h b/src/VBox/ImageMounter/vboximg-mount/vboximgMedia.h new file mode 100644 index 00000000..55561e70 --- /dev/null +++ b/src/VBox/ImageMounter/vboximg-mount/vboximgMedia.h @@ -0,0 +1,40 @@ +/* $Id: vboximgMedia.h $ */ + +/** @file + * vboximgMedia.h + */ + +/* + * Copyright (C) 2008-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. + */ + +#ifndef VBOX_INCLUDED_SRC_vboximg_mount_vboximgMedia_h +#define VBOX_INCLUDED_SRC_vboximg_mount_vboximgMedia_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +typedef struct MEDIUMINFO +{ + char *name; + char *uuid; + char *location; + char *description; + char *state; + char *size; + char *format; + int ro; +} MEDIUMINFO; + +int vboximgListVMs(IVirtualBox *pVirtualBox); +char *vboximgScaledSize(size_t size); + +#endif /* !VBOX_INCLUDED_SRC_vboximg_mount_vboximgMedia_h */ diff --git a/src/VBox/ImageMounter/vboximg-mount/vboximgOpts.h b/src/VBox/ImageMounter/vboximg-mount/vboximgOpts.h new file mode 100644 index 00000000..ad729fe4 --- /dev/null +++ b/src/VBox/ImageMounter/vboximg-mount/vboximgOpts.h @@ -0,0 +1,46 @@ + +/* $Id: vboximgOpts.h $ */ + +/** @file + * vboximgOpts.h + */ + +/* + * Copyright (C) 2008-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. + */ + +#ifndef VBOX_INCLUDED_SRC_vboximg_mount_vboximgOpts_h +#define VBOX_INCLUDED_SRC_vboximg_mount_vboximgOpts_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +typedef struct vboximgOpts { + char *pszVm; /** optional VM UUID */ + char *pszImageUuidOrPath; /** Virtual Disk image UUID or path */ + int32_t idxPartition; /** Number of partition to constrain FUSE based FS to (optional) 0 - whole disk*/ + int32_t offset; /** Offset to base virtual disk reads and writes from (altnerative to partition) */ + int32_t size; /** Size of accessible disk region, starting at offset, default = offset 0 */ + uint32_t fListMediaLong; /** Flag to list virtual disks of all known VMs */ + uint32_t fVerboseList; /** FUSE parsing doesn't understand combined flags (-lv, -vl), so we kludge it */ + uint32_t fWideList; /** FUSE parsing doesn't understand combined flags,(-lw, -wl) so we kludge it */ + uint32_t fList; /** Flag to list virtual disks of all known VMs */ + uint32_t fListParts; /** Flag to summarily list partitions associated with pszImage */ + uint32_t fAllowRoot; /** Flag to allow root to access this FUSE FS */ + uint32_t fRW; /** Flag to allow changes to FUSE-mounted Virtual Disk image */ + uint32_t fWide; /** Flag to use wide-format list mode */ + uint32_t fBriefUsage; /** Flag to display only FS-specific program usage options */ + uint32_t fVerbose; /** Add more info to lists and operations */ +} VBOXIMGOPTS; + + +#endif /* !VBOX_INCLUDED_SRC_vboximg_mount_vboximgOpts_h */ |