summaryrefslogtreecommitdiffstats
path: root/src/VBox/ImageMounter
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/ImageMounter')
-rw-r--r--src/VBox/ImageMounter/Makefile.kmk36
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/.scm-settings30
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/Makefile.kmk55
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/SelfSizingTable.h323
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/fuse-calls.h67
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/fuse.cpp35
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/fuse.h185
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp1414
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.cpp422
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.h265
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximgMedia.cpp409
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximgMedia.h50
-rw-r--r--src/VBox/ImageMounter/vboximg-mount/vboximgOpts.h54
13 files changed, 3345 insertions, 0 deletions
diff --git a/src/VBox/ImageMounter/Makefile.kmk b/src/VBox/ImageMounter/Makefile.kmk
new file mode 100644
index 00000000..b1a15b20
--- /dev/null
+++ b/src/VBox/ImageMounter/Makefile.kmk
@@ -0,0 +1,36 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the image mounting utilities.
+#
+
+#
+# Copyright (C) 2018-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
+#
+
+SUB_DEPTH = ../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+if defined(VBOX_WITH_VBOXIMGMOUNT) && "$(intersects $(KBUILD_TARGET_ARCH), $(VBOX_SUPPORTED_HOST_ARCHS))" != ""
+ include $(PATH_SUB_CURRENT)/vboximg-mount/Makefile.kmk
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/ImageMounter/vboximg-mount/.scm-settings b/src/VBox/ImageMounter/vboximg-mount/.scm-settings
new file mode 100644
index 00000000..5f622189
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/.scm-settings
@@ -0,0 +1,30 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for the includes.
+#
+
+#
+# Copyright (C) 2010-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
+#
+
+
+# Some headers actually shouldn't have include guards:
+/fuse-calls.h: --no-fix-header-guards
diff --git a/src/VBox/ImageMounter/vboximg-mount/Makefile.kmk b/src/VBox/ImageMounter/vboximg-mount/Makefile.kmk
new file mode 100644
index 00000000..e3f2f648
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/Makefile.kmk
@@ -0,0 +1,55 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the vboximg-mount Program.
+
+#
+# Copyright (C) 2006-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
+#
+
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# 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 \
+ fuse.cpp
+
+vboximg-mount_LIBS = \
+ $(LIB_DDU) \
+ $(LIB_RUNTIME)
+
+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..58f25769
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/SelfSizingTable.h
@@ -0,0 +1,323 @@
+/* $Id: SelfSizingTable.h $ */
+/** @file
+ * vboxraw header file
+ */
+
+/*
+ * Copyright (C) 2018-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
+ */
+
+/* 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/fuse-calls.h b/src/VBox/ImageMounter/vboximg-mount/fuse-calls.h
new file mode 100644
index 00000000..e7a09823
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/fuse-calls.h
@@ -0,0 +1,67 @@
+/** @file
+ * Stubs for dynamically loading libfuse/libosxfuse and the symbols which are needed by
+ * VirtualBox.
+ */
+
+/*
+ * Copyright (C) 2019-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
+ */
+/** The file name of the fuse library */
+#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_SOLARIS)
+# define RT_RUNTIME_LOADER_LIB_NAME "libfuse.so.2"
+#elif defined(RT_OS_DARWIN)
+# define RT_RUNTIME_LOADER_LIB_NAME "libosxfuse.dylib"
+#else
+# error "Unsupported host OS, manual work required"
+#endif
+
+/** The name of the loader function */
+#define RT_RUNTIME_LOADER_FUNCTION RTFuseLoadLib
+
+/** The following are the symbols which we need from the fuse library. */
+#define RT_RUNTIME_LOADER_INSERT_SYMBOLS \
+ RT_PROXY_STUB(fuse_main_real, int, (int argc, char **argv, struct fuse_operations *fuse_ops, size_t op_size, void *pv), \
+ (argc, argv, fuse_ops, op_size, pv)) \
+ RT_PROXY_STUB(fuse_opt_parse, int, (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc), \
+ (args, data, opts, proc)) \
+ RT_PROXY_STUB(fuse_opt_add_arg, int, (struct fuse_args *args, const char *arg), \
+ (args, arg)) \
+ RT_PROXY_STUB(fuse_opt_free_args, void, (struct fuse_args *args), \
+ (args))
+
+#ifdef VBOX_FUSE_GENERATE_HEADER
+# define RT_RUNTIME_LOADER_GENERATE_HEADER
+# define RT_RUNTIME_LOADER_GENERATE_DECLS
+# include <iprt/runtime-loader.h>
+# undef RT_RUNTIME_LOADER_GENERATE_HEADER
+# undef RT_RUNTIME_LOADER_GENERATE_DECLS
+
+#elif defined(VBOX_FUSE_GENERATE_BODY)
+# define RT_RUNTIME_LOADER_GENERATE_BODY_STUBS
+# include <iprt/runtime-loader.h>
+# undef RT_RUNTIME_LOADER_GENERATE_BODY_STUBS
+
+#else
+# error This file should only be included to generate stubs for loading the Fuse library at runtime
+#endif
+
+#undef RT_RUNTIME_LOADER_LIB_NAME
+#undef RT_RUNTIME_LOADER_INSERT_SYMBOLS
diff --git a/src/VBox/ImageMounter/vboximg-mount/fuse.cpp b/src/VBox/ImageMounter/vboximg-mount/fuse.cpp
new file mode 100644
index 00000000..06e76efc
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/fuse.cpp
@@ -0,0 +1,35 @@
+/* $Id: fuse.cpp $ */
+/** @file
+ *
+ * Module to dynamically load libfuse and load all symbols
+ * which are needed by vboximg-mount.
+ */
+
+/*
+ * Copyright (C) 2019-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
+ */
+
+#define LOG_GROUP RTLOGGROUP_DEFAULT
+#include "fuse.h"
+
+/* Declarations of the functions that we need from libfuse */
+#define VBOX_FUSE_GENERATE_BODY
+#include "fuse-calls.h"
diff --git a/src/VBox/ImageMounter/vboximg-mount/fuse.h b/src/VBox/ImageMounter/vboximg-mount/fuse.h
new file mode 100644
index 00000000..a9ffd37b
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/fuse.h
@@ -0,0 +1,185 @@
+/** @file
+ * Module to dynamically load libfuse/libosxfuse and load all symbols which are needed by
+ * vboximg-mount.
+ */
+
+/*
+ * Copyright (C) 2019-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
+ * --------------------------------------------------------------------
+ *
+ * This code is based on and contains parts of:
+ *
+ * libfuse
+ *
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+ *
+ * This program can be distributed under the terms of the GNU LGPLv2.
+ * See the file COPYING.LIB.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_vboximg_mount_fuse_h
+#define VBOX_INCLUDED_SRC_vboximg_mount_fuse_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/types.h>
+#include <iprt/stdarg.h>
+
+RT_C_DECLS_BEGIN
+
+/* Forward declaration of stat buffer. */
+struct stat;
+
+/**
+ * Fuse option structure.
+ */
+typedef struct fuse_opt
+{
+ /** Argument template with optional parameter formatting. */
+ const char *pszTempl;
+ /** Offset where the parameter is stored inside the data struct passed to fuse_opt_parse(). */
+ unsigned long uOffset;
+ /** The value to set the variable to if the template has no argument format. */
+ int uVal;
+} fuse_opt;
+
+#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+#define FUSE_OPT_END { NULL, 0, 0 }
+#define FUSE_OPT_KEY_NONOPT -2
+
+/**
+ * Fuse argument vector.
+ */
+typedef struct fuse_args
+{
+ int argc;
+ char **argv;
+ int allocated;
+} fuse_args;
+
+#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
+
+/**
+ * Fuse file info structure - for us only the fh member is of interest for now.
+ */
+typedef struct fuse_file_info
+{
+ int flags;
+ unsigned long fh_old;
+ int writepage;
+ unsigned int oth_flags;
+ uint64_t fh;
+ uint64_t lock_owner;
+} fuse_file_info;
+
+/** Option processing function. */
+typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs);
+
+/** Directory entry filler function. */
+typedef int (*fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off);
+
+
+/**
+ * Fuse FS callback table implementing the filesystem functionality.
+ *
+ * Only required methods are filled out, others are stubbed (change as required).
+ */
+typedef struct fuse_operations
+{
+ int (*getattr) (const char *pszPath, struct stat *pStatBuf);
+ int (*readlink) (const char *pszPath, char *pbBuf, size_t cbBuf);
+ PFNRT getdir;
+ PFNRT mknod;
+ PFNRT mkdir;
+ PFNRT unlink;
+ PFNRT rmdir;
+ PFNRT symlink;
+ PFNRT rename;
+ PFNRT link;
+ PFNRT chmod;
+ PFNRT chown;
+ PFNRT truncate;
+ PFNRT utime;
+ int (*open) (const char *pszPath, struct fuse_file_info *pInfo);
+ int (*read) (const char *pszPath, char *pbBuf, size_t cbBuf, off_t off, struct fuse_file_info *pInfo);
+ int (*write) (const char *pszPath, const char *pbBuf, size_t cbBuf, off_t off, struct fuse_file_info *pInfo);
+ PFNRT statfs;
+ PFNRT flush;
+ int (*release) (const char *pszPath, struct fuse_file_info *pInfo);
+ PFNRT fsync;
+ PFNRT setxattr; /* OSXFuse has a different parameter layout. */
+ PFNRT getxattr; /* OSXFuse has a different parameter layout. */
+ PFNRT listxattr;
+ PFNRT removexattr;
+ int (*opendir) (const char *pszPath, struct fuse_file_info *pInfo);
+ int (*readdir) (const char *pszPath, void *pvBuf, fuse_fill_dir_t pfnFiller, off_t offset, struct fuse_file_info *pInfo);
+ PFNRT releasedir;
+ PFNRT fsyncdir;
+ PFNRT init;
+ PFNRT destroy;
+ PFNRT access;
+ PFNRT create;
+ PFNRT ftruncate;
+ PFNRT fgettatr;
+ PFNRT lock;
+ PFNRT utimens;
+ PFNRT bmap;
+ unsigned int flag_null_path_ok: 1;
+ unsigned int flag_nopath: 1;
+ unsigned int flag_utime_omit_ok: 1;
+ unsigned int flag_reserved: 20;
+ PFNRT ioctl;
+ PFNRT poll;
+ PFNRT write_buf;
+ PFNRT read_buf;
+ PFNRT flock;
+ PFNRT fallocate;
+#ifdef RT_OS_DARWIN
+ PFNRT rsvd00;
+ PFNRT rsvd01;
+ PFNRT rsvd02;
+ PFNRT statfs_x;
+ PFNRT setvolname;
+ PFNRT exchange;
+ PFNRT getxtimes;
+ PFNRT setbkuptime;
+ PFNRT setchgtime;
+ PFNRT setcrtime;
+ PFNRT chflags;
+ PFNRT setattr_x;
+ PFNRT fsetattr_x;
+#endif
+} fuse_operations;
+
+/* Declarations of the functions that we need from libfuse */
+#define VBOX_FUSE_GENERATE_HEADER
+
+#include "fuse-calls.h"
+
+#undef VBOX_FUSE_GENERATE_HEADER
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_vboximg_mount_fuse_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..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;
+}
+
diff --git a/src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.cpp b/src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.cpp
new file mode 100644
index 00000000..0ac487b5
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.cpp
@@ -0,0 +1,422 @@
+/* $Id: vboximgCrypto.cpp $ */
+
+/** @file
+ * vboximgCypto.cpp - 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
+ */
+
+#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);
+ AssertPtrReturn(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);
+ AssertPtrReturn(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..49f44262
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/vboximgCrypto.h
@@ -0,0 +1,265 @@
+/* $Id: vboximgCrypto.h $ */
+
+/** @file
+ * vboximgCrypto.h
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+#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..bb49867c
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/vboximgMedia.cpp
@@ -0,0 +1,409 @@
+/* $Id: vboximgMedia.cpp $ */
+/** @file
+ * vboximgMedia.cpp - 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
+ */
+
+#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 = 0;
+ if (size > 0)
+ 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)
+{
+ RT_NOREF(pMachine);
+
+ 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;
+
+ HRESULT hrc;
+
+ 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 *pszSzScaled = 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,
+ pszSzScaled,
+ pInfo->pszFormat,
+ pInfo->pszState,
+ cPad, "", pInfo->pszUuid);
+ }
+ 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", pszSzScaled);
+ 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", pszSzScaled);
+ if (g_vboximgOpts.fVerbose)
+ RTPrintf(" Path: %s\n", pInfo->pszPath);
+ RTPrintf("\n");
+ }
+ }
+ RTMemFree(pszSzScaled);
+}
+
+static int vboximgListBranch(IMachine *pMachine, IMedium *pMedium, uint8_t nestLevel, bool fLast)
+{
+ MEDIUMINFO *pMediumInfo;
+ int vrc = getMediumInfo(pMachine, pMedium, &pMediumInfo);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ displayMediumInfo(pMediumInfo, nestLevel, fLast);
+
+ HRESULT hrc;
+ com::SafeIfaceArray<IMedium> pChildren;
+ CHECK_ERROR_RET(pMedium, COMGETTER(Children)(ComSafeArrayAsOutParam(pChildren)), VERR_NOT_FOUND); /** @todo r=andy Find a better 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)
+{
+ RT_NOREF(pVirtualBox);
+ RT_NOREF(vmName);
+ RT_NOREF(vmUuid);
+
+ int vrc = VINF_SUCCESS;
+
+ com::SafeIfaceArray<IMediumAttachment> pMediumAttachments;
+
+ HRESULT hrc;
+ 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");
+
+ vrc = vboximgListBranch(pMachine, pBase, 0, fLast);
+ if (RT_FAILURE(vrc))
+ {
+ RTPrintf("vboximgListBranch failed with %Rrc\n", vrc);
+ break;
+ }
+ }
+
+ return vrc;
+}
+/**
+ * Display all registered VMs on the screen with some information about each
+ *
+ * @param virtualBox VirtualBox instance object.
+ */
+int vboximgListVMs(IVirtualBox *pVirtualBox)
+{
+ HRESULT hrc;
+ 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");
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ 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));
+ }
+ }
+
+ int vrc2 = listMedia(pVirtualBox, pMachine,
+ RTStrDup(CSTR(machineName)), RTStrDup(CSTR(machineUuid)));
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ RTPrintf("\n");
+ }
+ }
+ }
+ }
+
+ return vrc;
+}
+
diff --git a/src/VBox/ImageMounter/vboximg-mount/vboximgMedia.h b/src/VBox/ImageMounter/vboximg-mount/vboximgMedia.h
new file mode 100644
index 00000000..67ee4036
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/vboximgMedia.h
@@ -0,0 +1,50 @@
+/* $Id: vboximgMedia.h $ */
+
+/** @file
+ * vboximgMedia.h
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+#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..348ac337
--- /dev/null
+++ b/src/VBox/ImageMounter/vboximg-mount/vboximgOpts.h
@@ -0,0 +1,54 @@
+
+/* $Id: vboximgOpts.h $ */
+
+/** @file
+ * vboximgOpts.h
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+#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 */
+ 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 fGstFs; /** Flag to try to exposes supported filesystems directly in the mountpoint inside a subdirectory */
+ 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 */