summaryrefslogtreecommitdiffstats
path: root/src/VBox/ImageMounter
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/ImageMounter')
-rw-r--r--src/VBox/ImageMounter/Makefile.kmk26
-rw-r--r--src/VBox/ImageMounter/VBoxFUSE/Makefile.kmk57
-rw-r--r--src/VBox/ImageMounter/VBoxFUSE/VBoxFUSE.cpp1476
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/Makefile.kmk65
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/SelfSizingTable.h313
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp1523
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximg-mount.h297
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.cpp412
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.h255
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximgMedia.cpp381
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximgMedia.h40
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximgOpts.h46
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 */