summaryrefslogtreecommitdiffstats
path: root/src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp')
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp1414
1 files changed, 1414 insertions, 0 deletions
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..8e36358d
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp
@@ -0,0 +1,1414 @@
+/* $Id: vboximg-mount.cpp $ */
+/** @file
+ * vboximg-mount - Disk Image Flattening FUSE Program.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+
+#define LOG_GROUP LOG_GROUP_DEFAULT /** @todo log group */
+
+#define RTTIME_INCL_TIMESPEC
+#define FUSE_USE_VERSION 27
+#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)
+# define UNIX_DERIVATIVE
+#endif
+#define MAX_READERS (INT32_MAX / 32)
+#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>
+#include <sys/time.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 <iprt/vfs.h>
+#include <iprt/dvm.h>
+#include <iprt/time.h>
+
+#include "fuse.h"
+#include "vboximgCrypto.h"
+#include "vboximgMedia.h"
+#include "SelfSizingTable.h"
+#include "vboximgOpts.h"
+
+using namespace com;
+
+enum {
+ USAGE_FLAG,
+};
+
+#if !defined(S_ISTXT) && defined(S_ISVTX)
+# define S_ISTXT (S_ISVTX)
+#endif
+
+#define VBOX_EXTPACK "Oracle VM VirtualBox Extension Pack"
+#define VERBOSE g_vboximgOpts.fVerbose
+
+#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 */
+
+/**
+ * Volume data.
+ */
+typedef struct VBOXIMGMOUNTVOL
+{
+ /** The volume handle. */
+ RTDVMVOLUME hVol;
+ /** The VFS file associated with the volume. */
+ RTVFSFILE hVfsFileVol;
+ /** Handle to the VFS root if supported and specified. */
+ RTVFS hVfsRoot;
+ /** Handle to the root directory. */
+ RTVFSDIR hVfsDirRoot;
+} VBOXIMGMOUNTVOL;
+/** Pointer to a volume data structure. */
+typedef VBOXIMGMOUNTVOL *PVBOXIMGMOUNTVOL;
+
+/* Global variables */
+static RTVFSFILE g_hVfsFileDisk = NIL_RTVFSFILE; /** Disk as VFS file handle. */
+static uint32_t g_cbSector; /** Disk sector size. */
+static RTDVM g_hDvmMgr; /** Handle to the volume manager. */
+static char *g_pszDiskUuid; /** UUID of image (if known, otherwise NULL) */
+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 char *g_pszImageName = NULL; /** 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 */
+
+/** Pointer to the detected volumes. */
+static PVBOXIMGMOUNTVOL g_paVolumes;
+/** Number of detected volumes. */
+static uint32_t g_cVolumes;
+
+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, 1),
+ OPTION("--vm %s", pszVm, 0),
+ OPTION("-l", fList, 1),
+ OPTION("--list", fList, 1),
+ OPTION("-g", fGstFs, 1),
+ OPTION("--guest-filesystem", fGstFs, 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 */
+
+
+
+/** @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) */
+
+
+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 image or snapshot,\n"
+ " specified by UUID or 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"
+ " [ { -g | --guest-filesystem } ] Exposes supported guest filesystems directly\n"
+ " in the mounted directory without the need\n"
+ " for a filesystem driver on the host\n"
+ "\n"
+ " [ --vm UUID ] Restrict media list to specified vm.\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"
+ " [ { --help | -h | -? } ] Display this usage information.\n"
+ );
+ RTPrintf("\n"
+ "vboximg-mount is a utility to make VirtualBox disk images available to the host\n"
+ "operating system for privileged or non-priviliged access. Any version of the\n"
+ "disk can be mounted from its available history of snapshots.\n"
+ "\n"
+ "If the user specifies a base image identifier using the --image option, only\n"
+ "the base image will be mounted, disregarding any snapshots. Alternatively,\n"
+ "if a snapshot is specified, the state of the FUSE-mounted virtual disk\n"
+ "is synthesized from the implied chain of snapshots, including the base image.\n"
+ "\n"
+ "The virtual disk is exposed as a device node within a FUSE-based filesystem\n"
+ "that overlays the user-provided mount point. The FUSE filesystem consists of a\n"
+ "directory containing a number of files and possibly other directories:"
+ " * vhdd: Provides access to the raw disk image data as a flat image\n"
+ " * vol<id>: Provides access to individual volumes on the accessed disk image\n"
+ " * fs<id>: Provides access to a supported filesystem without the need for a"
+ " host filesystem driver\n"
+ "\n"
+ "The directory will also contain a symbolic link which has the same basename(1)\n"
+ "as the virtual disk base image and points to the location of the\n"
+ "virtual disk base image.\n"
+ "\n"
+ );
+}
+
+static int
+vboximgOptHandler(void *data, const char *arg, int optKey, struct fuse_args *outargs)
+{
+ RT_NOREF(data);
+ RT_NOREF(arg);
+ RT_NOREF(optKey);
+ RT_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;
+}
+
+
+/**
+ * Queries the VFS object handle from the given path.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_NOT_FOUND if the object denoted by the path couldn't be found.
+ * @param pszPath The path.
+ * @param phVfsObj Where to store the handle to the VFS object on success.
+ */
+static int vboxImgMntVfsObjQueryFromPath(const char *pszPath, PRTVFSOBJ phVfsObj)
+{
+ PRTPATHSPLIT pPathSplit = NULL;
+ int rc = RTPathSplitA(pszPath, &pPathSplit, RTPATH_STR_F_STYLE_HOST);
+ if (RT_SUCCESS(rc))
+ {
+ if ( RTPATH_PROP_HAS_ROOT_SPEC(pPathSplit->fProps)
+ && pPathSplit->cComps >= 2)
+ {
+ /* Skip the root specifier and start with the component coming afterwards. */
+ if ( !RTStrCmp(pPathSplit->apszComps[1], "vhdd")
+ && g_hVfsFileDisk != NIL_RTVFSFILE)
+ *phVfsObj = RTVfsObjFromFile(g_hVfsFileDisk);
+ else if (!RTStrNCmp(pPathSplit->apszComps[1], "vol", sizeof("vol") - 1))
+ {
+ /* Retrieve the accessed volume and return the stat data. */
+ uint32_t idxVol;
+ int vrc = RTStrToUInt32Full(&pPathSplit->apszComps[1][3], 10, &idxVol);
+ if ( vrc == VINF_SUCCESS
+ && idxVol < g_cVolumes
+ && g_paVolumes[idxVol].hVfsFileVol != NIL_RTVFSFILE)
+ *phVfsObj = RTVfsObjFromFile(g_paVolumes[idxVol].hVfsFileVol);
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (!RTStrNCmp(pPathSplit->apszComps[1], "fs", sizeof("fs") - 1))
+ {
+ /* Retrieve the accessed volume and return the stat data. */
+ uint32_t idxVol;
+ int vrc = RTStrToUInt32Full(&pPathSplit->apszComps[1][2], 10, &idxVol);
+ if ( vrc == VINF_SUCCESS
+ && idxVol < g_cVolumes
+ && g_paVolumes[idxVol].hVfsDirRoot != NIL_RTVFSDIR)
+ *phVfsObj = RTVfsObjFromDir(g_paVolumes[idxVol].hVfsDirRoot);
+ else
+ rc = VERR_NOT_FOUND;
+
+ /* Is an object inside the guest filesystem requested? */
+ if (pPathSplit->cComps > 2)
+ {
+ PRTPATHSPLIT pPathSplitVfs = (PRTPATHSPLIT)RTMemTmpAllocZ(RT_UOFFSETOF_DYN(RTPATHSPLIT, apszComps[pPathSplit->cComps - 1]));
+ if (RT_LIKELY(pPathSplitVfs))
+ {
+ pPathSplitVfs->cComps = pPathSplit->cComps - 1;
+ pPathSplitVfs->fProps = pPathSplit->fProps;
+ pPathSplitVfs->cchPath = pPathSplit->cchPath - strlen(pPathSplit->apszComps[1]) - 1;
+ pPathSplitVfs->cbNeeded = pPathSplit->cbNeeded;
+ pPathSplitVfs->pszSuffix = pPathSplit->pszSuffix;
+ pPathSplitVfs->apszComps[0] = pPathSplit->apszComps[0];
+ for (uint32_t i = 1; i < pPathSplitVfs->cComps; i++)
+ pPathSplitVfs->apszComps[i] = pPathSplit->apszComps[i + 1];
+
+ /* Reassemble the path. */
+ char *pszPathVfs = (char *)RTMemTmpAllocZ(pPathSplitVfs->cbNeeded);
+ if (RT_LIKELY(pszPathVfs))
+ {
+ rc = RTPathSplitReassemble(pPathSplitVfs, RTPATH_STR_F_STYLE_HOST, pszPathVfs, pPathSplitVfs->cbNeeded);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsObjOpen(g_paVolumes[idxVol].hVfsRoot, pszPathVfs,
+ RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_CREATE_NOTHING | RTPATH_F_ON_LINK,
+ phVfsObj);
+ }
+ RTMemTmpFree(pszPathVfs);
+ }
+
+ RTMemTmpFree(pPathSplitVfs);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ RTPathSplitFree(pPathSplit);
+ }
+
+ return rc;
+}
+
+
+/** @copydoc fuse_operations::open */
+static int vboximgOp_open(const char *pszPath, struct fuse_file_info *pInfo)
+{
+ LogFlowFunc(("pszPath=%s\n", pszPath));
+ int rc = 0;
+
+ RTVFSOBJ hVfsObj;
+ int vrc = vboxImgMntVfsObjQueryFromPath(pszPath, &hVfsObj);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t fNotSup = 0;
+
+#ifdef UNIX_DERIVATIVE
+# ifdef RT_OS_DARWIN
+ fNotSup = 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)
+ fNotSup = O_APPEND | O_ASYNC | O_DIRECT | O_NOATIME | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK;
+ /* | O_LARGEFILE | O_SYNC | ? */
+# elif defined(RT_OS_FREEBSD)
+ fNotSup = 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 & fNotSup))
+ {
+#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 (!rc)
+ {
+ pInfo->fh = (uintptr_t)hVfsObj;
+ return 0;
+ }
+ }
+ else
+ rc = -EINVAL;
+
+ RTVfsObjRelease(hVfsObj);
+ }
+ else
+ rc = -RTErrConvertToErrno(vrc);
+
+ LogFlowFunc(("rc=%d \"%s\"\n", rc, pszPath));
+ return rc;
+
+}
+
+/** @copydoc fuse_operations::release */
+static int vboximgOp_release(const char *pszPath, struct fuse_file_info *pInfo)
+{
+ RT_NOREF(pszPath);
+
+ LogFlowFunc(("pszPath=%s\n", pszPath));
+
+ RTVFSOBJ hVfsObj = (RTVFSOBJ)(uintptr_t)pInfo->fh;
+ RTVfsObjRelease(hVfsObj);
+
+ LogFlowFunc(("\"%s\"\n", pszPath));
+ return 0;
+}
+
+
+/** @copydoc fuse_operations::read */
+static int vboximgOp_read(const char *pszPath, char *pbBuf, size_t cbBuf,
+ off_t offset, struct fuse_file_info *pInfo)
+{
+ RT_NOREF(pszPath);
+
+ 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);
+
+ int rc = 0;
+ RTVFSOBJ hVfsObj = (RTVFSOBJ)(uintptr_t)pInfo->fh;
+ switch (RTVfsObjGetType(hVfsObj))
+ {
+ case RTVFSOBJTYPE_FILE:
+ {
+ size_t cbRead = 0;
+ RTVFSFILE hVfsFile = RTVfsObjToFile(hVfsObj);
+ int vrc = RTVfsFileReadAt(hVfsFile, offset, pbBuf, cbBuf, &cbRead);
+ if (cbRead)
+ rc = cbRead;
+ else if (vrc == VINF_EOF)
+ rc = -RTErrConvertToErrno(VERR_EOF);
+ RTVfsFileRelease(hVfsFile);
+ break;
+ }
+ default:
+ rc = -EINVAL;
+ }
+
+ 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)
+{
+ RT_NOREF(pszPath);
+ RT_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);
+
+ if (!g_vboximgOpts.fRW)
+ {
+ LogFlowFunc(("WARNING: vboximg-mount (FUSE FS) --rw option not specified\n"
+ " (write operation ignored w/o error!)\n"));
+ return cbBuf;
+ }
+
+ int rc = 0;
+ RTVFSOBJ hVfsObj = (RTVFSOBJ)(uintptr_t)pInfo->fh;
+ switch (RTVfsObjGetType(hVfsObj))
+ {
+ case RTVFSOBJTYPE_FILE:
+ {
+ size_t cbWritten = 0;
+ RTVFSFILE hVfsFile = RTVfsObjToFile(hVfsObj);
+ int vrc = RTVfsFileWriteAt(hVfsFile, offset, pbBuf, cbBuf, &cbWritten);
+ if (cbWritten)
+ rc = cbWritten;
+ else if (vrc == VINF_EOF)
+ rc = -RTErrConvertToErrno(VERR_EOF);
+ RTVfsFileRelease(hVfsFile);
+ break;
+ }
+ default:
+ rc = -EINVAL;
+ }
+
+ 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 ( g_pszImageName
+ && 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
+ {
+ /* Query the VFS object and fill in the data. */
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ int vrc = vboxImgMntVfsObjQueryFromPath(pszPath, &hVfsObj);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFSOBJINFO ObjInfo;
+
+ vrc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(vrc))
+ {
+ stbuf->st_size = ObjInfo.cbObject;
+ stbuf->st_nlink = 1;
+ stbuf->st_uid = 0;
+ stbuf->st_gid = 0;
+
+#ifdef RT_OS_DARWIN
+ RTTimeSpecGetTimespec(&ObjInfo.AccessTime, &stbuf->st_atimespec);
+ RTTimeSpecGetTimespec(&ObjInfo.ModificationTime, &stbuf->st_mtimespec);
+ RTTimeSpecGetTimespec(&ObjInfo.ChangeTime, &stbuf->st_ctimespec);
+ RTTimeSpecGetTimespec(&ObjInfo.BirthTime, &stbuf->st_birthtimespec);
+#else
+ RTTimeSpecGetTimespec(&ObjInfo.AccessTime, &stbuf->st_atim);
+ RTTimeSpecGetTimespec(&ObjInfo.ModificationTime, &stbuf->st_mtim);
+ RTTimeSpecGetTimespec(&ObjInfo.ChangeTime, &stbuf->st_ctim);
+#endif
+
+ switch (ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FIFO:
+ {
+ stbuf->st_mode = S_IFIFO;
+ break;
+ }
+ case RTFS_TYPE_DEV_CHAR:
+ {
+ stbuf->st_mode = S_IFCHR;
+ break;
+ }
+ case RTFS_TYPE_DIRECTORY:
+ {
+ stbuf->st_mode = S_IFDIR;
+ stbuf->st_nlink = 2;
+ break;
+ }
+ case RTFS_TYPE_DEV_BLOCK:
+ {
+ stbuf->st_mode = S_IFBLK;
+ break;
+ }
+ case RTFS_TYPE_FILE:
+ {
+ stbuf->st_mode = S_IFREG;
+ break;
+ }
+ case RTFS_TYPE_SYMLINK:
+ {
+ stbuf->st_mode = S_IFLNK;
+ break;
+ }
+ case RTFS_TYPE_SOCKET:
+ {
+ stbuf->st_mode = S_IFSOCK;
+ break;
+ }
+#if 0 /* Not existing on Linux. */
+ case RTFS_TYPE_WHITEOUT:
+ {
+ stbuf->st_mode = S_IFWHT;
+ break;
+ }
+#endif
+ default:
+ stbuf->st_mode = 0;
+ }
+
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_ISUID)
+ stbuf->st_mode |= S_ISUID;
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_ISGID)
+ stbuf->st_mode |= S_ISGID;
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_ISTXT)
+ stbuf->st_mode |= S_ISTXT;
+
+ /* Owner permissions. */
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_IRUSR)
+ stbuf->st_mode |= S_IRUSR;
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_IWUSR)
+ stbuf->st_mode |= S_IWUSR;
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_IXUSR)
+ stbuf->st_mode |= S_IXUSR;
+
+ /* Group permissions. */
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_IRGRP)
+ stbuf->st_mode |= S_IRGRP;
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_IWGRP)
+ stbuf->st_mode |= S_IWGRP;
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_IXGRP)
+ stbuf->st_mode |= S_IXGRP;
+
+ /* Other permissions. */
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_IROTH)
+ stbuf->st_mode |= S_IROTH;
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_IWOTH)
+ stbuf->st_mode |= S_IWOTH;
+ if (ObjInfo.Attr.fMode & RTFS_UNIX_IXOTH)
+ stbuf->st_mode |= S_IXOTH;
+
+ if (ObjInfo.Attr.enmAdditional == RTFSOBJATTRADD_UNIX)
+ {
+ stbuf->st_uid = ObjInfo.Attr.u.Unix.uid;
+ stbuf->st_gid = ObjInfo.Attr.u.Unix.gid;
+ stbuf->st_nlink = ObjInfo.Attr.u.Unix.cHardlinks;
+ stbuf->st_ino = ObjInfo.Attr.u.Unix.INodeId;
+ stbuf->st_dev = ObjInfo.Attr.u.Unix.INodeIdDevice;
+ /*stbuf->st_flags = ObjInfo.Attr.u.Unix.fFlags;*/ /* Not existing on Linux. */
+ /*stbuf->st_gen = ObjInfo.Attr.u.Unix.GenerationId;*/ /* Not existing on Linux. */
+ stbuf->st_rdev = ObjInfo.Attr.u.Unix.Device;
+ }
+ }
+
+ RTVfsObjRelease(hVfsObj);
+ }
+ else if (vrc == VERR_NOT_FOUND)
+ rc = -ENOENT;
+ else
+ rc = -RTErrConvertToErrno(vrc);
+ }
+
+ 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)
+{
+ RT_NOREF(offset);
+ RT_NOREF(pInfo);
+
+ int rc = 0;
+
+ /* Special root directory handling?. */
+ if (!RTStrCmp(pszPath, "/"))
+ {
+ /*
+ * mandatory '.', '..', ...
+ */
+ pfnFiller(pvBuf, ".", NULL, 0);
+ pfnFiller(pvBuf, "..", NULL, 0);
+
+ if (g_pszImageName)
+ {
+ /*
+ * 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.
+ */
+ pfnFiller(pvBuf, g_pszImageName, NULL, 0);
+ }
+
+ if (g_hVfsFileDisk != NIL_RTVFSFILE)
+ {
+ /*
+ * Create entry named "vhdd" denoting the whole disk, 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);
+ }
+
+ /* Create entries for the individual volumes. */
+ for (uint32_t i = 0; i < g_cVolumes; i++)
+ {
+ char tmp[64];
+ if (g_paVolumes[i].hVfsFileVol != NIL_RTVFSFILE)
+ {
+ RTStrPrintf(tmp, sizeof (tmp), "vol%u", i);
+ pfnFiller(pvBuf, tmp, NULL, 0);
+ }
+
+ if (g_paVolumes[i].hVfsRoot != NIL_RTVFS)
+ {
+ RTStrPrintf(tmp, sizeof (tmp), "fs%u", i);
+ pfnFiller(pvBuf, tmp, NULL, 0);
+ }
+ }
+ }
+ else
+ {
+ /* Query the VFS object and fill in the data. */
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ int vrc = vboxImgMntVfsObjQueryFromPath(pszPath, &hVfsObj);
+ if (RT_SUCCESS(vrc))
+ {
+ switch (RTVfsObjGetType(hVfsObj))
+ {
+ case RTVFSOBJTYPE_DIR:
+ {
+ RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj);
+ RTDIRENTRYEX DirEntry;
+
+ vrc = RTVfsDirRewind(hVfsDir); AssertRC(vrc);
+ vrc = RTVfsDirReadEx(hVfsDir, &DirEntry, NULL, RTFSOBJATTRADD_NOTHING);
+ while (RT_SUCCESS(vrc))
+ {
+ pfnFiller(pvBuf, DirEntry.szName, NULL, 0);
+ vrc = RTVfsDirReadEx(hVfsDir, &DirEntry, NULL, RTFSOBJATTRADD_NOTHING);
+ }
+
+ RTVfsDirRelease(hVfsDir);
+ break;
+ }
+ default:
+ rc = -EINVAL;
+ }
+
+ RTVfsObjRelease(hVfsObj);
+ }
+ else
+ rc = -RTErrConvertToErrno(vrc);
+ }
+
+ return rc;
+}
+
+/** @copydoc fuse_operations::readlink */
+static int vboximgOp_readlink(const char *pszPath, char *buf, size_t size)
+{
+ RT_NOREF(pszPath);
+ RTStrCopy(buf, size, g_pszImagePath);
+ return 0;
+}
+
+
+/**
+ * Displays the list of volumes on the opened image.
+ */
+static void vboxImgMntVolumesDisplay(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 *colType = tbl.addCol("Type", "%s", -1, 2);
+
+ for (uint32_t i = 0; i < g_cVolumes; i++)
+ {
+ PVBOXIMGMOUNTVOL pVol = &g_paVolumes[i];
+ uint64_t fVolFlags = RTDvmVolumeGetFlags(pVol->hVol);
+ uint64_t cbVol = RTDvmVolumeGetSize(pVol->hVol);
+ RTDVMVOLTYPE enmType = RTDvmVolumeGetType(pVol->hVol);
+ uint64_t offStart = 0;
+ uint64_t offEnd = 0;
+
+ if (fVolFlags & DVMVOLUME_F_CONTIGUOUS)
+ {
+ int rc = RTDvmVolumeQueryRange(pVol->hVol, &offStart, &offEnd);
+ AssertRC(rc);
+ }
+
+ void *row = tbl.addRow();
+ tbl.setCell(row, colPartition, g_pszBaseImageName, i);
+ tbl.setCell(row, colBoot, (fVolFlags & DVMVOLUME_FLAGS_BOOTABLE) ? '*' : ' ');
+ tbl.setCell(row, colStart, offStart / g_cbSector);
+ tbl.setCell(row, colSectors, cbVol / g_cbSector);
+ tbl.setCell(row, colSize, vboximgScaledSize(cbVol));
+ tbl.setCell(row, colOffset, offStart);
+ tbl.setCell(row, colType, RTDvmVolumeTypeGetDescr(enmType));
+ }
+ tbl.displayTable();
+ RTPrintf ("\n");
+}
+
+
+/**
+ * Sets up the volumes for the disk.
+ *
+ * @returns IPRT status code.
+ */
+static int vboxImgMntVolumesSetup(void)
+{
+ g_cVolumes = 0;
+ g_paVolumes = NULL;
+
+ int rc = RTDvmCreate(&g_hDvmMgr, g_hVfsFileDisk, g_cbSector, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDvmMapOpen(g_hDvmMgr);
+ if (RT_SUCCESS(rc))
+ {
+ g_cVolumes = RTDvmMapGetValidVolumes(g_hDvmMgr);
+ if ( g_cVolumes != UINT32_MAX
+ && g_cVolumes > 0)
+ {
+ g_paVolumes = (PVBOXIMGMOUNTVOL)RTMemAllocZ(g_cVolumes * sizeof(VBOXIMGMOUNTVOL));
+ if (RT_LIKELY(g_paVolumes))
+ {
+ g_paVolumes[0].hVfsRoot = NIL_RTVFS;
+
+ rc = RTDvmMapQueryFirstVolume(g_hDvmMgr, &g_paVolumes[0].hVol);
+ if (RT_SUCCESS(rc))
+ rc = RTDvmVolumeCreateVfsFile(g_paVolumes[0].hVol,
+ RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE,
+ &g_paVolumes[0].hVfsFileVol);
+
+ for (uint32_t i = 1; i < g_cVolumes && RT_SUCCESS(rc); i++)
+ {
+ g_paVolumes[i].hVfsRoot = NIL_RTVFS;
+ rc = RTDvmMapQueryNextVolume(g_hDvmMgr, g_paVolumes[i-1].hVol, &g_paVolumes[i].hVol);
+ if (RT_SUCCESS(rc))
+ rc = RTDvmVolumeCreateVfsFile(g_paVolumes[i].hVol,
+ RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE,
+ &g_paVolumes[i].hVfsFileVol);
+ }
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTMemFree(g_paVolumes);
+ g_paVolumes = NULL;
+ g_cVolumes = 0;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (g_cVolumes == UINT32_MAX)
+ {
+ g_cVolumes = 0;
+ rc = VERR_INTERNAL_ERROR;
+ }
+
+ RTDvmRelease(g_hDvmMgr);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+static int vboxImgMntImageSetup(struct fuse_args *args)
+{
+ /*
+ * 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;
+ }
+
+ if (!g_vboximgOpts.pszImageUuidOrPath)
+ return RTMsgErrorExitFailure("A image UUID or path needs to be provided using the --image/-i option\n");
+
+ 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(hrc))
+ 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()), hrc);
+ pNewEntry->fWriteable = true;
+ }
+ else
+ {
+ if (VERBOSE)
+ RTPrintf(" ... Locking for read\n");
+ CHECK_ERROR_RET(pVDiskMedium, LockRead((pNewEntry->pLockToken).asOutParam()), hrc);
+ }
+
+ 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;
+
+ hrc = pVDiskBaseMedium->GetProperty(Bstr("CRYPT/KeyStore").raw(), pBase64EncodedKeyStore.asOutParam());
+ if (SUCCEEDED(hrc) && 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 ***************** */
+
+ int vrc = RTCritSectInit(&g_vdioLock);
+ if (RT_SUCCESS(vrc))
+ {
+ g_VDIfThreadSync.pfnStartRead = vboximgThreadStartRead;
+ g_VDIfThreadSync.pfnFinishRead = vboximgThreadFinishRead;
+ g_VDIfThreadSync.pfnStartWrite = vboximgThreadStartWrite;
+ g_VDIfThreadSync.pfnFinishWrite = vboximgThreadFinishWrite;
+ vrc = 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", vrc);
+
+ /*
+ * Create HDD container to open base image and differencing images into
+ */
+ vrc = VDGetFormat(NULL /* pVDIIfsDisk */, NULL /* pVDIIfsImage*/,
+ CSTR(pVDiskBaseImagePath), VDTYPE_INVALID, &pszFormat, &enmType);
+
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExitFailure("VDGetFormat(,,%s,,) "
+ "failed (during HDD container creation), rc=%Rrc\n", g_pszImagePath, vrc);
+
+ if (VERBOSE)
+ RTPrintf("\nCreating container for base image of format %s\n", pszFormat);
+
+ PVDISK pVDisk = NULL;
+ vrc = VDCreate(g_pVdIfs, enmType, &pVDisk);
+ if (RT_FAILURE(vrc))
+ 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));
+
+ vrc = VDOpen(pVDisk,
+ pszFormat,
+ CSTR(pCurMedium->pImagePath),
+ pCurMedium->fWriteable ? 0 : VD_OPEN_FLAGS_READONLY,
+ g_pVdIfs);
+
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExitFailure("Could not open the medium storage unit '%s' %Rrc",
+ CSTR(pCurMedium->pImagePath), vrc);
+
+ pCurMedium = pCurMedium->prev;
+ }
+
+ RTStrFree(pszFormat);
+
+ /* Create the VFS file to use for the disk image access. */
+ vrc = VDCreateVfsFileFromDisk(pVDisk, VD_VFSFILE_DESTROY_ON_RELEASE, &g_hVfsFileDisk);
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExitFailure("Error creating VFS file wrapper for disk image\n");
+
+ g_cbSector = VDGetSectorSize(pVDisk, VD_LAST_IMAGE);
+
+ vrc = vboxImgMntVolumesSetup();
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExitFailure("Error parsing volumes on disk\n");
+
+ if (g_vboximgOpts.fList)
+ {
+ if (g_hVfsFileDisk == NIL_RTVFSFILE)
+ return RTMsgErrorExitFailure("No valid --image to list partitions from\n");
+
+ RTPrintf("\n");
+ vboxImgMntVolumesDisplay();
+ return VINF_SUCCESS; /** @todo r=andy Re-visit this. */
+ }
+
+ /* Try to "mount" supported filesystems inside the disk image if specified. */
+ if (g_vboximgOpts.fGstFs)
+ {
+ for (uint32_t i = 0; i < g_cVolumes; i++)
+ {
+ vrc = RTVfsMountVol(g_paVolumes[i].hVfsFileVol,
+ g_vboximgOpts.fRW ? 0 : RTVFSMNT_F_READ_ONLY,
+ &g_paVolumes[i].hVfsRoot,
+ NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsOpenRoot(g_paVolumes[i].hVfsRoot, &g_paVolumes[i].hVfsDirRoot);
+ if (RT_FAILURE(vrc))
+ {
+ RTPrintf("\nvboximg-mount: Failed to access filesystem on volume %u, ignoring\n", i);
+ RTVfsRelease(g_paVolumes[i].hVfsRoot);
+ g_paVolumes[i].hVfsRoot = NIL_RTVFS;
+ }
+ }
+ else
+ RTPrintf("\nvboximg-mount: Failed to access filesystem on volume %u, ignoring\n", i);
+ }
+ }
+
+ /*
+ * Hand control over to libfuse.
+ */
+ if (VERBOSE)
+ RTPrintf("\nvboximg-mount: Going into background...\n");
+
+ int rc = fuse_main_real(args->argc, args->argv, &g_vboximgOps, sizeof(g_vboximgOps), NULL);
+ RTPrintf("vboximg-mount: fuse_main -> %d\n", rc);
+
+ int rc2 = RTVfsFileRelease(g_hVfsFileDisk);
+ AssertRC(rc2);
+
+ return vrc;
+}
+
+
+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);
+
+ rc = RTFuseLoadLib();
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to load the fuse library, 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));
+
+ 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");
+
+ if ( !g_vboximgOpts.pszImageUuidOrPath
+ || !RTVfsChainIsSpec(g_vboximgOpts.pszImageUuidOrPath))
+ return vboxImgMntImageSetup(&args);
+
+ /* Mount the VFS chain. */
+ RTVFSOBJ hVfsObj;
+ rc = RTVfsChainOpenObj(g_vboximgOpts.pszImageUuidOrPath, RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_CREATE_NOTHING | RTPATH_F_ON_LINK,
+ &hVfsObj, NULL, NULL);
+ if ( RT_SUCCESS(rc)
+ && RTVfsObjGetType(hVfsObj) == RTVFSOBJTYPE_VFS)
+ {
+ g_paVolumes = (PVBOXIMGMOUNTVOL)RTMemAllocZ(sizeof(*g_paVolumes));
+ if (RT_LIKELY(g_paVolumes))
+ {
+ g_cVolumes = 1;
+ g_paVolumes[0].hVfsRoot = RTVfsObjToVfs(hVfsObj);
+ g_paVolumes[0].hVfsFileVol = NIL_RTVFSFILE;
+ RTVfsObjRelease(hVfsObj);
+
+ rc = RTVfsOpenRoot(g_paVolumes[0].hVfsRoot, &g_paVolumes[0].hVfsDirRoot);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Hand control over to libfuse.
+ */
+ if (VERBOSE)
+ RTPrintf("\nvboximg-mount: Going into background...\n");
+
+ rc = fuse_main_real(args.argc, args.argv, &g_vboximgOps, sizeof(g_vboximgOps), NULL);
+ RTVfsDirRelease(g_paVolumes[0].hVfsDirRoot);
+ RTVfsRelease(g_paVolumes[0].hVfsRoot);
+ }
+
+ RTMemFree(g_paVolumes);
+ g_paVolumes = NULL;
+ g_cVolumes = 0;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTVfsObjRelease(hVfsObj);
+ }
+
+ return rc;
+}
+