/* $Id: vboximg-mount.cpp $ */ /** @file * vboximg-mount - Disk Image Flattening FUSE Program. */ /* * Copyright (C) 2009-2019 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DEFAULT /** @todo log group */ #define FUSE_USE_VERSION 27 #if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined(RT_OS_FEEBSD) # define UNIX_DERIVATIVE #endif #define MAX_READERS (INT32_MAX / 32) #include #ifdef UNIX_DERIVATIVE #include #include #include #include #include #include #include #include #endif #if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_LINUX) # include # undef PVM /* Blasted old BSD mess still hanging around darwin. */ #endif #ifdef RT_OS_LINUX # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vboximg-mount.h" #include "vboximgCrypto.h" #include "vboximgMedia.h" #include "SelfSizingTable.h" #include "vboximgOpts.h" using namespace com; enum { USAGE_FLAG, }; enum { PARTITION_TABLE_MBR = 1, PARTITION_TABLE_GPT = 2 }; #define VBOX_EXTPACK "Oracle VM VirtualBox Extension Pack" #define GPT_PTABLE_SIZE 32 * BLOCKSIZE /** Max size we to read for GPT partition table */ #define MBR_PARTITIONS_MAX 4 /** Fixed number of partitions in Master Boot Record */ #define BASENAME_MAX 256 /** Maximum name for the basename of a path (for RTStrNLen()*/ #define VBOXIMG_PARTITION_MAX 256 /** How much storage to allocate to store partition info */ #define PARTITION_NAME_MAX 72 /** Maximum partition name size (accomodates GPT partition name) */ #define BLOCKSIZE 512 /** Commonly used disk block size */ #define DOS_BOOT_RECORD_SIGNATURE 0xaa55 /** MBR and EBR (partition table) signature [EOT boundary] */ #define NULL_BOOT_RECORD_SIGNATURE 0x0000 /** MBR or EBR null signature value */ #define MAX_UUID_LEN 256 /** Max length of a UUID */ #define LBA(n) (n * BLOCKSIZE) #define VD_SECTOR_SIZE 512 /** Virtual disk sector/blocksize */ #define VD_SECTOR_MASK (VD_SECTOR_SIZE - 1) /** Masks off a blocks worth of data */ #define VD_SECTOR_OUT_OF_BOUNDS_MASK (~UINT64_C(VD_SECTOR_MASK)) /** Masks the overflow of a blocks worth of data */ #define VERBOSE g_vboximgOpts.fVerbose #define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100) #define PARTTYPE_IS_NULL(parType) ((uint8_t)parType == 0x00) #define PARTTYPE_IS_GPT(parType) ((uint8_t)parType == 0xee) #define PARTTYPE_IS_EXT(parType) (( (uint8_t)parType) == 0x05 /* Extended */ \ || ((uint8_t)parType) == 0x0f /* W95 Extended (LBA) */ \ || ((uint8_t)parType) == 0x85) /* Linux Extended */ #define SAFENULL(strPtr) (strPtr ? strPtr : "") #define CSTR(arg) Utf8Str(arg).c_str() /* Converts XPCOM string type to C string type */ static struct fuse_operations g_vboximgOps; /** FUSE structure that defines allowed ops for this FS */ /* Global variables */ static PVDISK g_pVDisk; /** Handle for Virtual Disk in contet */ static char *g_pszDiskUuid; /** UUID of image (if known, otherwise NULL) */ static off_t g_vDiskOffset; /** Biases r/w from start of VD */ static off_t g_vDiskSize; /** Limits r/w length for VD */ static int32_t g_cReaders; /** Number of readers for VD */ static int32_t g_cWriters; /** Number of writers for VD */ static RTFOFF g_cbEntireVDisk; /** Size of VD */ static PVDINTERFACE g_pVdIfs; /** @todo Remove when VD I/O becomes threadsafe */ static VDINTERFACETHREADSYNC g_VDIfThreadSync; /** @todo Remove when VD I/O becomes threadsafe */ static RTCRITSECT g_vdioLock; /** @todo Remove when VD I/O becomes threadsafe */ static uint16_t g_lastPartNbr; /** Last partition number found in MBR + EBR chain */ static bool g_fGPT; /** True if GPT type partition table was found */ static char *g_pszImageName; /** Base filename for current VD image */ static char *g_pszImagePath; /** Full path to current VD image */ static char *g_pszBaseImagePath; /** Base image known after parsing */ static char *g_pszBaseImageName; /** Base image known after parsing */ static uint32_t g_cImages; /** Number of images in diff chain */ /* Table entry containing partition info parsed out of GPT or MBR and EBR chain of specified VD */ typedef struct { int idxPartition; /** partition number */ char *pszName; off_t offPartition; /** partition offset from start of disk, in bytes */ uint64_t cbPartition; /** partition size in bytes */ uint8_t fBootable; /** partition bootable */ union { uint8_t legacy; /** partition type MBR/EBR */ uint128_t gptGuidTypeSpecifier; /** partition type GPT */ } partitionType; /** uint8_t for MBR/EBR (legacy) and GUID for GPT */ union { MBRPARTITIONENTRY mbrEntry; /** MBR (also EBR partition entry) */ GPTPARTITIONENTRY gptEntry; /** GPT partition entry */ } partitionEntry; } PARTITIONINFO; PARTITIONINFO g_aParsedPartitionInfo[VBOXIMG_PARTITION_MAX + 1]; /* Note: Element 0 reserved for EntireDisk partitionEntry */ VBOXIMGOPTS g_vboximgOpts; #define OPTION(fmt, pos, val) { fmt, offsetof(struct vboximgOpts, pos), val } static struct fuse_opt vboximgOptDefs[] = { OPTION("--image=%s", pszImageUuidOrPath, 0), OPTION("-i %s", pszImageUuidOrPath, 0), OPTION("--rw", fRW, 1), OPTION("--root", fAllowRoot, 0), OPTION("--vm=%s", pszVm, 0), OPTION("--partition=%d", idxPartition, 1), OPTION("-p %d", idxPartition, 1), OPTION("--offset=%d", offset, 1), OPTION("-o %d", offset, 1), OPTION("--size=%d", size, 1), OPTION("-s %d", size, 1), OPTION("-l", fList, 1), OPTION("--list", fList, 1), OPTION("--verbose", fVerbose, 1), OPTION("-v", fVerbose, 1), OPTION("--wide", fWide, 1), OPTION("-w", fWide, 1), OPTION("-lv", fVerboseList, 1), OPTION("-vl", fVerboseList, 1), OPTION("-lw", fWideList, 1), OPTION("-wl", fWideList, 1), OPTION("-h", fBriefUsage, 1), FUSE_OPT_KEY("--help", USAGE_FLAG), FUSE_OPT_KEY("-vm", FUSE_OPT_KEY_NONOPT), FUSE_OPT_END }; typedef struct IMAGELIST { struct IMAGELIST *next; struct IMAGELIST *prev; ComPtr pLockToken; bool fWriteable; ComPtr pImage; Bstr pImageName; Bstr pImagePath; } IMAGELIST; IMAGELIST listHeadLockList; /* flink & blink intentionally left NULL */ static void briefUsage() { RTPrintf("usage: vboximg-mount [options] \n\n" "vboximg-mount options:\n\n" " [ { -i | --image= } ] VirtualBox disk base or snapshot image,\n" " specified by UUID, or fully-qualified path\n" "\n" " [ { -l | --list } ] If --image specified, list its partitions,\n" " otherwise, list registered VMs and their\n" " attached virtual HDD disk media. In verbose\n" " mode, VM/media list will be long format,\n" " i.e. including snapshot images and paths.\n" "\n" " [ { -w | --wide } ] List media in wide / tabular format\n" " (reduces vertical scrolling but requires\n" " wider than standard 80 column window\n)" "\n" " [ --vm=UUID ] Restrict media list to specified vm.\n" "\n" " [ { -p | --partition= } ] Expose only specified partition via FUSE.\n" "\n" " [ { -o | --offset= } ] Bias disk I/O by offset from disk start.\n" " (incompatible with -p, --partition)\n" "\n" " [ { -s | --size= } ] Specify size of mounted disk.\n" " (incompatible with -p, --partition)\n" "\n" " [ --rw ] Make image writeable (default = readonly)\n" "\n" " [ --root ] Same as -o allow_root.\n" "\n" " [ { -v | --verbose } ] Log extra information.\n" "\n" " [ -o opt[,opt...]] FUSE mount options.\n" "\n" " [ { -h | -? } ] Display short usage info (no FUSE options).\n" " [ --help ] Display long usage info (incl. FUSE opts).\n\n" ); RTPrintf("\n" "vboximg-mount is a utility to make VirtualBox disk images available to the host\n" "operating system in a root or non-root accessible way. The user determines the\n" "historical representation of the disk by choosing either the base image or a\n" "snapshot, to establish the desired level of currency of the mounted disk.\n" "\n" "The disk image is mounted through this utility inside a FUSE-based filesystem\n" "that overlays the user-provided mount point. The FUSE filesystem presents a\n" "a directory that contains two files: an HDD pseudo device node and a symbolic\n" "link. The device node is named 'vhdd' and is the access point to the synthesized\n" "state of the virtual disk. It is the entity that can be mounted or otherwise\n" "accessed through the host OS. The symbolic link is given the same name as the\n" "base image, as determined from '--image' option argument. The link equates\n" "to the specified image's location (path).\n" "\n" "If the user provides a base image UUID/path with the --image option, only\n" "the base image will be exposed via vhdd, disregarding any snapshots.\n" "Alternatively, if a snapshot (e.g. disk differencing image) is provided,\n" "the chain of snapshots is calculated from that \"leaf\" snapshot\n" "to the base image and the whole chain of images is merged to form the exposed\n" "state of the FUSE-mounted disk.\n" "\n" ); } static int vboximgOptHandler(void *data, const char *arg, int optKey, struct fuse_args *outargs) { NOREF(data); NOREF(arg); NOREF(optKey); NOREF(outargs); /* * Apparently this handler is only called for arguments FUSE can't parse, * and arguments that don't result in variable assignment such as "USAGE" * In this impl. that's always deemed a parsing error. */ if (*arg != '-') /* could be user's mount point */ return 1; return -1; } /** @copydoc fuse_operations::open */ static int vboximgOp_open(const char *pszPath, struct fuse_file_info *pInfo) { RT_NOREF(pszPath, pInfo);; LogFlowFunc(("pszPath=%s\n", pszPath)); uint32_t notsup = 0; int rc = 0; #ifdef UNIX_DERIVATIVE # ifdef RT_OS_DARWIN notsup = O_APPEND | O_NONBLOCK | O_SYMLINK | O_NOCTTY | O_SHLOCK | O_EXLOCK | O_ASYNC | O_CREAT | O_TRUNC | O_EXCL | O_EVTONLY; # elif defined(RT_OS_LINUX) notsup = O_APPEND | O_ASYNC | O_DIRECT | O_NOATIME | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK; /* | O_LARGEFILE | O_SYNC | ? */ # elif defined(RT_OS_FREEBSD) notsup = O_APPEND | O_ASYNC | O_DIRECT | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK; /* | O_LARGEFILE | O_SYNC | ? */ # endif #else # error "Port me" #endif if (pInfo->flags & notsup) rc -EINVAL; #ifdef UNIX_DERIVATIVE if ((pInfo->flags & O_ACCMODE) == O_ACCMODE) rc = -EINVAL; # ifdef O_DIRECTORY if (pInfo->flags & O_DIRECTORY) rc = -ENOTDIR; # endif #endif if (RT_FAILURE(rc)) { LogFlowFunc(("rc=%d \"%s\"\n", rc, pszPath)); return rc; } int fWriteable = (pInfo->flags & O_ACCMODE) == O_WRONLY || (pInfo->flags & O_ACCMODE) == O_RDWR; if (g_cWriters) rc = -ETXTBSY; else { if (fWriteable) g_cWriters++; else { if (g_cReaders + 1 > MAX_READERS) rc = -EMLINK; else g_cReaders++; } } LogFlowFunc(("rc=%d \"%s\"\n", rc, pszPath)); return rc; } /** @todo Remove when VD I/O becomes threadsafe */ static DECLCALLBACK(int) vboximgThreadStartRead(void *pvUser) { PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser; return RTCritSectEnter(vdioLock); } static DECLCALLBACK(int) vboximgThreadFinishRead(void *pvUser) { PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser; return RTCritSectLeave(vdioLock); } static DECLCALLBACK(int) vboximgThreadStartWrite(void *pvUser) { PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser; return RTCritSectEnter(vdioLock); } static DECLCALLBACK(int) vboximgThreadFinishWrite(void *pvUser) { PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser; return RTCritSectLeave(vdioLock); } /** @todo (end of to do section) */ /** @copydoc fuse_operations::release */ static int vboximgOp_release(const char *pszPath, struct fuse_file_info *pInfo) { NOREF(pszPath); LogFlowFunc(("pszPath=%s\n", pszPath)); if ( (pInfo->flags & O_ACCMODE) == O_WRONLY || (pInfo->flags & O_ACCMODE) == O_RDWR) { g_cWriters--; Assert(g_cWriters >= 0); } else if ((pInfo->flags & O_ACCMODE) == O_RDONLY) { g_cReaders--; Assert(g_cReaders >= 0); } else AssertFailed(); LogFlowFunc(("\"%s\"\n", pszPath)); return 0; } /** * VD read Sanitizer taking care of unaligned accesses. * * @return VBox bootIndicator code. * @param pDisk VD disk container. * @param off Offset to start reading from. * @param pvBuf Pointer to the buffer to read into. * @param cbRead Amount of bytes to read. */ static int vdReadSanitizer(PVDISK pDisk, uint64_t off, void *pvBuf, size_t cbRead) { int rc; uint64_t const cbMisalignmentOfStart = off & VD_SECTOR_MASK; uint64_t const cbMisalignmentOfEnd = (off + cbRead) & VD_SECTOR_MASK; if (cbMisalignmentOfStart + cbMisalignmentOfEnd == 0) /* perfectly aligned request; just read it and done */ rc = VDRead(pDisk, off, pvBuf, cbRead); else { uint8_t *pbBuf = (uint8_t *)pvBuf; uint8_t abBuf[VD_SECTOR_SIZE]; /* If offset not @ sector boundary, read whole sector, then copy unaligned * bytes (requested by user), only up to sector boundary, into user's buffer */ if (cbMisalignmentOfStart) { rc = VDRead(pDisk, off - cbMisalignmentOfStart, abBuf, VD_SECTOR_SIZE); if (RT_SUCCESS(rc)) { size_t const cbPartial = RT_MIN(VD_SECTOR_SIZE - cbMisalignmentOfStart, cbRead); memcpy(pbBuf, &abBuf[cbMisalignmentOfStart], cbPartial); pbBuf += cbPartial; off += cbPartial; /* Beginning of next sector or EOD */ cbRead -= cbPartial; /* # left to read */ } } else /* user's offset already aligned, did nothing */ rc = VINF_SUCCESS; /* Read remaining aligned sectors, deferring any tail-skewed bytes */ if (RT_SUCCESS(rc) && cbRead >= VD_SECTOR_SIZE) { Assert(!(off % VD_SECTOR_SIZE)); size_t cbPartial = cbRead - cbMisalignmentOfEnd; Assert(!(cbPartial % VD_SECTOR_SIZE)); rc = VDRead(pDisk, off, pbBuf, cbPartial); if (RT_SUCCESS(rc)) { pbBuf += cbPartial; off += cbPartial; cbRead -= cbPartial; } } /* Unaligned buffered read of tail. */ if (RT_SUCCESS(rc) && cbRead) { Assert(cbRead == cbMisalignmentOfEnd); Assert(cbRead < VD_SECTOR_SIZE); Assert(!(off % VD_SECTOR_SIZE)); rc = VDRead(pDisk, off, abBuf, VD_SECTOR_SIZE); if (RT_SUCCESS(rc)) memcpy(pbBuf, abBuf, cbRead); } } if (RT_FAILURE(rc)) { int sysrc = -RTErrConvertToErrno(rc); LogFlowFunc(("error: %s (vbox err: %d)\n", strerror(sysrc), rc)); rc = sysrc; } return cbRead; } /** * VD write Sanitizer taking care of unaligned accesses. * * @return VBox bootIndicator code. * @param pDisk VD disk container. * @param off Offset to start writing to. * @param pvSrc Pointer to the buffer to read from. * @param cbWrite Amount of bytes to write. */ static int vdWriteSanitizer(PVDISK pDisk, uint64_t off, const void *pvSrc, size_t cbWrite) { uint8_t const *pbSrc = (uint8_t const *)pvSrc; uint8_t abBuf[4096]; int rc; int cbRemaining = cbWrite; /* * Take direct route if the request is sector aligned. */ uint64_t const cbMisalignmentOfStart = off & VD_SECTOR_MASK; size_t const cbMisalignmentOfEnd = (off + cbWrite) & VD_SECTOR_MASK; if (!cbMisalignmentOfStart && !cbMisalignmentOfEnd) { rc = VDWrite(pDisk, off, pbSrc, cbWrite); do { size_t cbThisWrite = RT_MIN(cbWrite, sizeof(abBuf)); rc = VDWrite(pDisk, off, memcpy(abBuf, pbSrc, cbThisWrite), cbThisWrite); if (RT_SUCCESS(rc)) { pbSrc += cbThisWrite; off += cbThisWrite; cbRemaining -= cbThisWrite; } else break; } while (cbRemaining > 0); } else { /* * Unaligned buffered read+write of head. Aligns the offset. */ if (cbMisalignmentOfStart) { rc = VDRead(pDisk, off - cbMisalignmentOfStart, abBuf, VD_SECTOR_SIZE); if (RT_SUCCESS(rc)) { size_t const cbPartial = RT_MIN(VD_SECTOR_SIZE - cbMisalignmentOfStart, cbWrite); memcpy(&abBuf[cbMisalignmentOfStart], pbSrc, cbPartial); rc = VDWrite(pDisk, off - cbMisalignmentOfStart, abBuf, VD_SECTOR_SIZE); if (RT_SUCCESS(rc)) { pbSrc += cbPartial; off += cbPartial; cbRemaining -= cbPartial; } } } else rc = VINF_SUCCESS; /* * Aligned direct write. */ if (RT_SUCCESS(rc) && cbWrite >= VD_SECTOR_SIZE) { Assert(!(off % VD_SECTOR_SIZE)); size_t cbPartial = cbWrite - cbMisalignmentOfEnd; Assert(!(cbPartial % VD_SECTOR_SIZE)); rc = VDWrite(pDisk, off, pbSrc, cbPartial); if (RT_SUCCESS(rc)) { pbSrc += cbPartial; off += cbPartial; cbRemaining -= cbPartial; } } /* * Unaligned buffered read + write of tail. */ if ( RT_SUCCESS(rc) && cbWrite > 0) { Assert(cbWrite == cbMisalignmentOfEnd); Assert(cbWrite < VD_SECTOR_SIZE); Assert(!(off % VD_SECTOR_SIZE)); rc = VDRead(pDisk, off, abBuf, VD_SECTOR_SIZE); if (RT_SUCCESS(rc)) { memcpy(abBuf, pbSrc, cbWrite); rc = VDWrite(pDisk, off, abBuf, VD_SECTOR_SIZE); } } } if (RT_FAILURE(rc)) { int sysrc = -RTErrConvertToErrno(rc); LogFlowFunc(("error: %s (vbox err: %d)\n", strerror(sysrc), rc)); return sysrc; } return cbWrite - cbRemaining; } /** @copydoc fuse_operations::read */ static int vboximgOp_read(const char *pszPath, char *pbBuf, size_t cbBuf, off_t offset, struct fuse_file_info *pInfo) { NOREF(pszPath); NOREF(pInfo); LogFlowFunc(("my offset=%#llx size=%#zx path=\"%s\"\n", (uint64_t)offset, cbBuf, pszPath)); AssertReturn(offset >= 0, -EINVAL); AssertReturn((int)cbBuf >= 0, -EINVAL); AssertReturn((unsigned)cbBuf == cbBuf, -EINVAL); AssertReturn(offset + g_vDiskOffset >= 0, -EINVAL); int64_t adjOff = offset + g_vDiskOffset; int rc = 0; if ((off_t)(adjOff + cbBuf) < adjOff) rc = -EINVAL; else if (adjOff >= g_vDiskSize) return 0; else if (!cbBuf) return 0; if (rc >= 0) rc = vdReadSanitizer(g_pVDisk, adjOff, pbBuf, cbBuf); if (rc < 0) LogFlowFunc(("%s\n", strerror(rc))); return rc; } /** @copydoc fuse_operations::write */ static int vboximgOp_write(const char *pszPath, const char *pbBuf, size_t cbBuf, off_t offset, struct fuse_file_info *pInfo) { NOREF(pszPath); NOREF(pInfo); LogFlowFunc(("offset=%#llx size=%#zx path=\"%s\"\n", (uint64_t)offset, cbBuf, pszPath)); AssertReturn(offset >= 0, -EINVAL); AssertReturn((int)cbBuf >= 0, -EINVAL); AssertReturn((unsigned)cbBuf == cbBuf, -EINVAL); AssertReturn(offset + g_vDiskOffset >= 0, -EINVAL); int64_t adjOff = offset + g_vDiskOffset; int rc = 0; if (!g_vboximgOpts.fRW) { LogFlowFunc(("WARNING: vboximg-mount (FUSE FS) --rw option not specified\n" " (write operation ignored w/o error!)\n")); return cbBuf; } else if ((off_t)(adjOff + cbBuf) < adjOff) rc = -EINVAL; else if (offset >= g_vDiskSize) return 0; else if (!cbBuf) return 0; if (rc >= 0) rc = vdWriteSanitizer(g_pVDisk, adjOff, pbBuf, cbBuf); if (rc < 0) LogFlowFunc(("%s\n", strerror(rc))); return rc; } /** @copydoc fuse_operations::getattr */ static int vboximgOp_getattr(const char *pszPath, struct stat *stbuf) { int rc = 0; LogFlowFunc(("pszPath=%s, stat(\"%s\")\n", pszPath, g_pszImagePath)); memset(stbuf, 0, sizeof(struct stat)); if (RTStrCmp(pszPath, "/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (RTStrCmp(pszPath + 1, "vhdd") == 0) { rc = stat(g_pszImagePath, stbuf); if (rc < 0) return rc; /* * st_size represents the size of the FUSE FS-mounted portion of the disk. * By default it is the whole disk, but can be a partition or specified * (or overridden) directly by the { -s | --size } option on the command line. */ stbuf->st_size = g_vDiskSize; stbuf->st_nlink = 1; } else if (RTStrNCmp(pszPath + 1, g_pszImageName, strlen(g_pszImageName)) == 0) { /* When the disk is partitioned, the symbolic link named from `basename` of * resolved path to VBox disk image, has appended to it formatted text * representing the offset range of the partition. * * $ vboximg-mount -i /stroll/along/the/path/simple_fixed_disk.vdi -p 1 /mnt/tmpdir * $ ls /mnt/tmpdir * simple_fixed_disk.vdi[20480:2013244928] vhdd */ rc = stat(g_pszImagePath, stbuf); if (rc < 0) return rc; stbuf->st_size = 0; stbuf->st_mode = S_IFLNK | 0444; stbuf->st_nlink = 1; stbuf->st_uid = 0; stbuf->st_gid = 0; } else rc = -ENOENT; return rc; } /** @copydoc fuse_operations::readdir */ static int vboximgOp_readdir(const char *pszPath, void *pvBuf, fuse_fill_dir_t pfnFiller, off_t offset, struct fuse_file_info *pInfo) { NOREF(offset); NOREF(pInfo); if (RTStrCmp(pszPath, "/") != 0) return -ENOENT; /* * mandatory '.', '..', ... */ pfnFiller(pvBuf, ".", NULL, 0); pfnFiller(pvBuf, "..", NULL, 0); /* * Create FUSE FS dir entry that is depicted here (and exposed via stat()) as * a symbolic link back to the resolved path to the VBox virtual disk image, * whose symlink name is basename that path. This is a convenience so anyone * listing the dir can figure out easily what the vhdd FUSE node entry * represents. */ if (g_vDiskOffset == 0 && (g_vDiskSize == 0 || g_vDiskSize == g_cbEntireVDisk)) pfnFiller(pvBuf, g_pszImageName, NULL, 0); else { char tmp[BASENAME_MAX]; RTStrPrintf(tmp, sizeof (tmp), "%s[%jd:%jd]", g_pszImageName, g_vDiskOffset, g_vDiskSize); pfnFiller(pvBuf, tmp, NULL, 0); } /* * Create entry named "vhdd", which getattr() will describe as a * regular file, and thus will go through the open/release/read/write vectors * to access the VirtualBox image as processed by the IRPT VD API. */ pfnFiller(pvBuf, "vhdd", NULL, 0); return 0; } /** @copydoc fuse_operations::readlink */ static int vboximgOp_readlink(const char *pszPath, char *buf, size_t size) { NOREF(pszPath); RTStrCopy(buf, size, g_pszImagePath); return 0; } uint8_t parsePartitionTable(void) { MBR_t mbr; EBR_t ebr; PTH_t parTblHdr; ASSERT(sizeof (mbr) == 512); ASSERT(sizeof (ebr) == 512); /* * First entry describes entire disk as a single entity */ g_aParsedPartitionInfo[0].idxPartition = 0; g_aParsedPartitionInfo[0].offPartition = 0; g_aParsedPartitionInfo[0].cbPartition = VDGetSize(g_pVDisk, 0); g_aParsedPartitionInfo[0].pszName = RTStrDup("EntireDisk"); /* * Currently only DOS partitioned disks are supported. Ensure this one conforms */ int rc = vdReadSanitizer(g_pVDisk, 0, &mbr, sizeof (mbr)); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Error reading MBR block from disk\n"); if (mbr.signature == NULL_BOOT_RECORD_SIGNATURE) return RTMsgErrorExitFailure("Unprt disk (null MBR signature)\n"); if (mbr.signature != DOS_BOOT_RECORD_SIGNATURE) return RTMsgErrorExitFailure("Invalid MBR found on image with signature 0x%04hX\n", mbr.signature); /* * Parse the four physical partition entires in the MBR (any one, and only one, can be an EBR) */ int idxEbrPartitionInMbr = 0; for (int idxPartition = 1; idxPartition <= MBR_PARTITIONS_MAX; idxPartition++) { MBRPARTITIONENTRY *pMbrPartitionEntry = &g_aParsedPartitionInfo[idxPartition].partitionEntry.mbrEntry;; memcpy (pMbrPartitionEntry, &mbr.partitionEntry[idxPartition - 1], sizeof (MBRPARTITIONENTRY)); if (PARTTYPE_IS_NULL(pMbrPartitionEntry->type)) continue; if (PARTTYPE_IS_EXT(pMbrPartitionEntry->type)) { if (idxEbrPartitionInMbr) return RTMsgErrorExitFailure("Multiple EBRs found found in MBR\n"); idxEbrPartitionInMbr = idxPartition; } PARTITIONINFO *ppi = &g_aParsedPartitionInfo[idxPartition]; ppi->idxPartition = idxPartition; ppi->offPartition = (off_t) pMbrPartitionEntry->partitionLba * BLOCKSIZE; ppi->cbPartition = (off_t) pMbrPartitionEntry->partitionBlkCnt * BLOCKSIZE; ppi->fBootable = pMbrPartitionEntry->bootIndicator == 0x80; ppi->partitionType.legacy = pMbrPartitionEntry->type; g_lastPartNbr = idxPartition; if (PARTTYPE_IS_GPT(pMbrPartitionEntry->type)) { g_fGPT = true; break; } } if (g_fGPT) { g_lastPartNbr = 2; /* from the 'protective MBR' */ rc = vdReadSanitizer(g_pVDisk, LBA(1), &parTblHdr, sizeof (parTblHdr)); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Error reading Partition Table Header (LBA 1) from disk\n"); uint8_t *pTblBuf = (uint8_t *)RTMemAlloc(GPT_PTABLE_SIZE); if (!pTblBuf) return RTMsgErrorExitFailure("Out of memory\n"); rc = vdReadSanitizer(g_pVDisk, LBA(2), pTblBuf, GPT_PTABLE_SIZE); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Error reading Partition Table blocks from disk\n"); uint32_t cEntries = parTblHdr.cPartitionEntries; uint32_t cbEntry = parTblHdr.cbPartitionEntry; if (cEntries * cbEntry > GPT_PTABLE_SIZE) { RTPrintf("Partition entries exceed GPT table read from disk (pruning!)\n"); while (cEntries * cbEntry > GPT_PTABLE_SIZE && cEntries > 0) --cEntries; } uint8_t *pEntryRaw = pTblBuf; for (uint32_t i = 0; i < cEntries; i++) { GPTPARTITIONENTRY *pEntry = (GPTPARTITIONENTRY *)pEntryRaw; PARTITIONINFO *ppi = &g_aParsedPartitionInfo[g_lastPartNbr]; memcpy(&(ppi->partitionEntry).gptEntry, pEntry, sizeof(GPTPARTITIONENTRY)); if (!pEntry->firstLba) break; ppi->offPartition = pEntry->firstLba * BLOCKSIZE; ppi->cbPartition = (pEntry->lastLba - pEntry->firstLba) * BLOCKSIZE; ppi->fBootable = pEntry->attrFlags & (1 << GPT_LEGACY_BIOS_BOOTABLE); ppi->partitionType.gptGuidTypeSpecifier = pEntry->partitionTypeGuid; size_t cwName = sizeof (pEntry->partitionName) / 2; RTUtf16LittleToUtf8Ex((PRTUTF16)pEntry->partitionName, RTSTR_MAX, &ppi->pszName, cwName, NULL); ppi->idxPartition = g_lastPartNbr++; pEntryRaw += cbEntry; } return PARTITION_TABLE_GPT; } /* * Starting with EBR located in MBR, walk EBR chain to parse the logical partition entries */ if (idxEbrPartitionInMbr) { uint32_t firstEbrLba = g_aParsedPartitionInfo[idxEbrPartitionInMbr].partitionEntry.mbrEntry.partitionLba; off_t firstEbrOffset = (off_t)firstEbrLba * BLOCKSIZE; off_t chainedEbrOffset = 0; if (!firstEbrLba) return RTMsgErrorExitFailure("Inconsistency for logical partition start. Aborting\n"); for (int idxPartition = 5; idxPartition <= VBOXIMG_PARTITION_MAX; idxPartition++) { off_t currentEbrOffset = firstEbrOffset + chainedEbrOffset; vdReadSanitizer(g_pVDisk, currentEbrOffset, &ebr, sizeof (ebr)); if (ebr.signature != DOS_BOOT_RECORD_SIGNATURE) return RTMsgErrorExitFailure("Invalid EBR found on image with signature 0x%04hX\n", ebr.signature); MBRPARTITIONENTRY *pEbrPartitionEntry = &g_aParsedPartitionInfo[idxPartition].partitionEntry.mbrEntry; /* EBR entry struct same as MBR */ memcpy(pEbrPartitionEntry, &ebr.partitionEntry, sizeof (MBRPARTITIONENTRY)); if (pEbrPartitionEntry->type == NULL_BOOT_RECORD_SIGNATURE) return RTMsgErrorExitFailure("Logical partition with type 0 encountered"); if (!pEbrPartitionEntry->partitionLba) return RTMsgErrorExitFailure("Logical partition invalid partition start offset (LBA) encountered"); PARTITIONINFO *ppi = &g_aParsedPartitionInfo[idxPartition]; ppi->idxPartition = idxPartition; ppi->offPartition = currentEbrOffset + (off_t)pEbrPartitionEntry->partitionLba * BLOCKSIZE; ppi->cbPartition = (off_t)pEbrPartitionEntry->partitionBlkCnt * BLOCKSIZE; ppi->fBootable = pEbrPartitionEntry->bootIndicator == 0x80; ppi->partitionType.legacy = pEbrPartitionEntry->type; g_lastPartNbr = idxPartition; if (ebr.chainingPartitionEntry.type == 0) /* end of chain */ break; if (!PARTTYPE_IS_EXT(ebr.chainingPartitionEntry.type)) return RTMsgErrorExitFailure("Logical partition chain broken"); chainedEbrOffset = ebr.chainingPartitionEntry.partitionLba * BLOCKSIZE; } } return PARTITION_TABLE_MBR; } const char *getClassicPartitionDesc(uint8_t type) { for (uint32_t i = 0; i < sizeof (g_partitionDescTable) / sizeof (struct PartitionDesc); i++) { if (g_partitionDescTable[i].type == type) return g_partitionDescTable[i].desc; } return "????"; } void displayGptPartitionTable(void) { RTPrintf( "Virtual disk image:\n\n"); RTPrintf(" Base: %s\n", g_pszBaseImagePath); if (g_cImages > 1) RTPrintf(" Diff: %s\n", g_pszImagePath); if (g_pszDiskUuid) RTPrintf(" UUID: %s\n\n", g_pszDiskUuid); void *colBoot = NULL; SELFSIZINGTABLE tbl(2); /* Note: Omitting partition name column because type/UUID seems suffcient */ void *colPartNbr = tbl.addCol("#", "%3d", 1); /* If none of the partitions supports legacy BIOS boot, don't show that column */ for (int idxPartition = 2; idxPartition <= g_lastPartNbr; idxPartition++) if (g_aParsedPartitionInfo[idxPartition].fBootable) { colBoot = tbl.addCol("Boot", "%c ", 1); break; } void *colStart = tbl.addCol("Start", "%lld", 1); void *colSectors = tbl.addCol("Sectors", "%lld", -1, 2); void *colSize = tbl.addCol("Size", "%s", 1); void *colOffset = tbl.addCol("Offset", "%lld", 1); void *colType = tbl.addCol("Type", "%s", -1, 2); #if 0 /* need to see how other OSes w/GPT use 'Name' field, right now 'Type' seems to suffice */ void *colName = tbl.addCol("Name", "%s", -1); */ #endif for (int idxPartition = 2; idxPartition <= g_lastPartNbr; idxPartition++) { PARTITIONINFO *ppi = &g_aParsedPartitionInfo[idxPartition]; if (ppi->idxPartition) { char abGuid[GUID_STRING_LENGTH * 2]; RTStrPrintf(abGuid, sizeof(abGuid), "%RTuuid", &ppi->partitionType.gptGuidTypeSpecifier); char *pszPartitionTypeDesc = NULL; for (uint32_t i = 0; i < sizeof(g_gptPartitionTypes) / sizeof(GPTPARTITIONTYPE); i++) if (RTStrNICmp(abGuid, g_gptPartitionTypes[i].gptPartitionUuid, GUID_STRING_LENGTH) == 0) { pszPartitionTypeDesc = (char *)g_gptPartitionTypes[i].gptPartitionTypeDesc; break; } if (!pszPartitionTypeDesc) RTPrintf("Couldn't find GPT partitiontype for GUID: %s\n", abGuid); void *row = tbl.addRow(); tbl.setCell(row, colPartNbr, idxPartition - 1); if (colBoot) tbl.setCell(row, colBoot, ppi->fBootable ? '*' : ' '); tbl.setCell(row, colStart, ppi->offPartition / BLOCKSIZE); tbl.setCell(row, colSectors, ppi->cbPartition / BLOCKSIZE); tbl.setCell(row, colSize, vboximgScaledSize(ppi->cbPartition)); tbl.setCell(row, colOffset, ppi->offPartition); tbl.setCell(row, colType, SAFENULL(pszPartitionTypeDesc)); #if 0 /* see comment for stubbed-out 'Name' column definition above */ tbl.setCell(row, colName, ppi->pszName); #endif } } tbl.displayTable(); RTPrintf ("\n"); } void displayLegacyPartitionTable(void) { /* * Partition table is most readable and concise when headers and columns * are adapted to the actual data, to avoid insufficient or excessive whitespace. */ RTPrintf( "Virtual disk image:\n\n"); RTPrintf(" Base: %s\n", g_pszBaseImagePath); if (g_cImages > 1) RTPrintf(" Diff: %s\n", g_pszImagePath); if (g_pszDiskUuid) RTPrintf(" UUID: %s\n\n", g_pszDiskUuid); SELFSIZINGTABLE tbl(2); void *colPartition = tbl.addCol("Partition", "%s(%d)", -1); void *colBoot = tbl.addCol("Boot", "%c ", 1); void *colStart = tbl.addCol("Start", "%lld", 1); void *colSectors = tbl.addCol("Sectors", "%lld", -1, 2); void *colSize = tbl.addCol("Size", "%s", 1); void *colOffset = tbl.addCol("Offset", "%lld", 1); void *colId = tbl.addCol("Id", "%2x", 1); void *colType = tbl.addCol("Type", "%s", -1, 2); for (int idxPartition = 1; idxPartition <= g_lastPartNbr; idxPartition++) { PARTITIONINFO *p = &g_aParsedPartitionInfo[idxPartition]; if (p->idxPartition) { void *row = tbl.addRow(); tbl.setCell(row, colPartition, g_pszBaseImageName, idxPartition); tbl.setCell(row, colBoot, p->fBootable ? '*' : ' '); tbl.setCell(row, colStart, p->offPartition / BLOCKSIZE); tbl.setCell(row, colSectors, p->cbPartition / BLOCKSIZE); tbl.setCell(row, colSize, vboximgScaledSize(p->cbPartition)); tbl.setCell(row, colOffset, p->offPartition); tbl.setCell(row, colId, p->partitionType.legacy); tbl.setCell(row, colType, getClassicPartitionDesc((p->partitionType).legacy)); } } tbl.displayTable(); RTPrintf ("\n"); } int main(int argc, char **argv) { int rc = RTR3InitExe(argc, &argv, 0); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTR3InitExe failed, rc=%Rrc\n", rc); rc = VDInit(); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("VDInit failed, rc=%Rrc\n", rc); memset(&g_vboximgOps, 0, sizeof(g_vboximgOps)); g_vboximgOps.open = vboximgOp_open; g_vboximgOps.read = vboximgOp_read; g_vboximgOps.write = vboximgOp_write; g_vboximgOps.getattr = vboximgOp_getattr; g_vboximgOps.release = vboximgOp_release; g_vboximgOps.readdir = vboximgOp_readdir; g_vboximgOps.readlink = vboximgOp_readlink; struct fuse_args args = FUSE_ARGS_INIT(argc, argv); memset(&g_vboximgOpts, 0, sizeof(g_vboximgOpts)); g_vboximgOpts.idxPartition = -1; rc = fuse_opt_parse(&args, &g_vboximgOpts, vboximgOptDefs, vboximgOptHandler); if (rc < 0 || argc < 2 || RTStrCmp(argv[1], "-?" ) == 0 || g_vboximgOpts.fBriefUsage) { briefUsage(); return 0; } if (g_vboximgOpts.fAllowRoot) fuse_opt_add_arg(&args, "-oallow_root"); /* * FUSE doesn't seem to like combining options with one hyphen, as traditional UNIX * command line utilities allow. The following flags, fWideList and fVerboseList, * and their respective option definitions give the appearance of combined opts, * so that -vl, -lv, -wl, -lw options are allowed, since those in particular would * tend to conveniently facilitate some of the most common use cases. */ if (g_vboximgOpts.fWideList) { g_vboximgOpts.fWide = true; g_vboximgOpts.fList = true; } if (g_vboximgOpts.fVerboseList) { g_vboximgOpts.fVerbose = true; g_vboximgOpts.fList = true; } if (g_vboximgOpts.fAllowRoot) fuse_opt_add_arg(&args, "-oallow_root"); /* * Initialize COM. */ using namespace com; HRESULT hrc = com::Initialize(); if (FAILED(hrc)) { # ifdef VBOX_WITH_XPCOM if (hrc == NS_ERROR_FILE_ACCESS_DENIED) { char szHome[RTPATH_MAX] = ""; com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome)); return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome); } # endif return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize COM! (hrc=%Rhrc)", hrc); } /* * Get the remote VirtualBox object and create a local session object. */ ComPtr pVirtualBoxClient; ComPtr pVirtualBox; hrc = pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient); if (SUCCEEDED(hrc)) hrc = pVirtualBoxClient->COMGETTER(VirtualBox)(pVirtualBox.asOutParam()); if (FAILED(hrc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to get IVirtualBox object! (hrc=%Rhrc)", hrc); if (g_vboximgOpts.fList && g_vboximgOpts.pszImageUuidOrPath == NULL) { vboximgListVMs(pVirtualBox); return VINF_SUCCESS; } Bstr pMediumUuid; ComPtr pVDiskMedium = NULL; char *pszFormat; VDTYPE enmType; /* * Open chain of images from what is provided on command line, to base image */ if (g_vboximgOpts.pszImageUuidOrPath) { /* compiler was too fussy about access mode's data type in conditional expr, so... */ if (g_vboximgOpts.fRW) CHECK_ERROR(pVirtualBox, OpenMedium(Bstr(g_vboximgOpts.pszImageUuidOrPath).raw(), DeviceType_HardDisk, AccessMode_ReadWrite, false /* forceNewUuid */, pVDiskMedium.asOutParam())); else CHECK_ERROR(pVirtualBox, OpenMedium(Bstr(g_vboximgOpts.pszImageUuidOrPath).raw(), DeviceType_HardDisk, AccessMode_ReadOnly, false /* forceNewUuid */, pVDiskMedium.asOutParam())); if (FAILED(rc)) return RTMsgErrorExitFailure("\nCould't find specified VirtualBox base or snapshot disk image:\n%s", g_vboximgOpts.pszImageUuidOrPath); CHECK_ERROR(pVDiskMedium, COMGETTER(Id)(pMediumUuid.asOutParam())); g_pszDiskUuid = RTStrDup((char *)CSTR(pMediumUuid)); /* * Lock & cache the disk image media chain (from leaf to base). * Only leaf can be rw (and only if media is being mounted in non-default writable (rw) mode) * * Note: Failure to acquire lock is intentionally fatal (e.g. program termination) */ if (VERBOSE) RTPrintf("\nAttempting to lock medium chain from leaf image to base image\n"); bool fLeaf = true; g_cImages = 0; do { ++g_cImages; IMAGELIST *pNewEntry= new IMAGELIST(); pNewEntry->pImage = pVDiskMedium; CHECK_ERROR(pVDiskMedium, COMGETTER(Name)((pNewEntry->pImageName).asOutParam())); CHECK_ERROR(pVDiskMedium, COMGETTER(Location)((pNewEntry->pImagePath).asOutParam())); if (VERBOSE) RTPrintf(" %s", CSTR(pNewEntry->pImageName)); if (fLeaf && g_vboximgOpts.fRW) { if (VERBOSE) RTPrintf(" ... Locking for write\n"); CHECK_ERROR_RET(pVDiskMedium, LockWrite((pNewEntry->pLockToken).asOutParam()), rc); pNewEntry->fWriteable = true; } else { if (VERBOSE) RTPrintf(" ... Locking for read\n"); CHECK_ERROR_RET(pVDiskMedium, LockRead((pNewEntry->pLockToken).asOutParam()), rc); } IMAGELIST *pCurImageEntry = &listHeadLockList; while (pCurImageEntry->next) pCurImageEntry = pCurImageEntry->next; pCurImageEntry->next = pNewEntry; pNewEntry->prev = pCurImageEntry; listHeadLockList.prev = pNewEntry; CHECK_ERROR(pVDiskMedium, COMGETTER(Parent)(pVDiskMedium.asOutParam())); fLeaf = false; } while(pVDiskMedium); } ComPtr pVDiskBaseMedium = listHeadLockList.prev->pImage; Bstr pVDiskBaseImagePath = listHeadLockList.prev->pImagePath; Bstr pVDiskBaseImageName = listHeadLockList.prev->pImageName; g_pszBaseImagePath = RTStrDup((char *)CSTR(pVDiskBaseImagePath)); g_pszBaseImageName = RTStrDup((char *)CSTR(pVDiskBaseImageName)); g_pszImagePath = RTStrDup((char *)CSTR(listHeadLockList.next->pImagePath)); g_pszImageName = RTStrDup((char *)CSTR(listHeadLockList.next->pImageName)); /* * Attempt to VDOpen media (base and any snapshots), handling encryption, * if that property is set for base media */ Bstr pBase64EncodedKeyStore; rc = pVDiskBaseMedium->GetProperty(Bstr("CRYPT/KeyStore").raw(), pBase64EncodedKeyStore.asOutParam()); if (SUCCEEDED(rc) && strlen(CSTR(pBase64EncodedKeyStore)) != 0) { RTPrintf("\nvboximgMount: Encrypted disks not supported in this version\n\n"); return -1; } /* ***************** BEGIN IFDEF'D (STUBBED-OUT) CODE ************** */ /* vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ #if 0 /* The following encrypted disk related code is stubbed out until it can be finished. * What is here is an attempt to port the VBoxSVC specific code in i_openForIO to * a client's proximity. It is supplemented by code in vboximgCrypto.cpp and * vboximageCrypt.h that was lifed from SecretKeyStore.cpp along with the setup * task function. * * The ultimate solution may be to use a simpler but less efficient COM interface, * or to use VD encryption interfaces and key containers entirely. The keystore * handling/filter approach that is here may be a bumbling hybrid approach * that is broken (trying to bridge incompatible disk encryption mechanisms) or otherwise * doesn't make sense. */ Bstr pKeyId; ComPtr pExtPackManager; ComPtr pExtPack; com::SafeIfaceArray pExtPackPlugIns; if (SUCCEEDED(rc)) { RTPrintf("Got GetProperty(\"CRYPT/KeyStore\") = %s\n", CSTR(pBase64EncodedKeyStore)); if (strlen(CSTR(pBase64EncodedKeyStore)) == 0) return RTMsgErrorExitFailure("Image '%s' is configured for encryption but " "there is no key store to retrieve the password from", CSTR(pVDiskBaseImageName)); SecretKeyStore keyStore(false); RTBase64Decode(CSTR(pBase64EncodedKeyStore), &keyStore, sizeof (SecretKeyStore), NULL, NULL); rc = pVDiskBaseMedium->GetProperty(Bstr("CRYPT/KeyId").raw(), pKeyId.asOutParam()); if (SUCCEEDED(rc) && strlen(CSTR(pKeyId)) == 0) return RTMsgErrorExitFailure("Image '%s' is configured for encryption but " "doesn't have a key identifier set", CSTR(pVDiskBaseImageName)); RTPrintf(" key id: %s\n", CSTR(pKeyId)); #ifndef VBOX_WITH_EXTPACK return RTMsgErrorExitFailure( "Encryption is not supported because extension pack support is not built in"); #endif CHECK_ERROR(pVirtualBox, COMGETTER(ExtensionPackManager)(pExtPackManager.asOutParam())); BOOL fExtPackUsable; CHECK_ERROR(pExtPackManager, IsExtPackUsable((PRUnichar *)VBOX_EXTPACK, &fExtPackUsable)); if (fExtPackUsable) { /* Load the PlugIn */ CHECK_ERROR(pExtPackManager, Find((PRUnichar *)VBOX_EXTPACK, pExtPack.asOutParam())); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure( "Encryption is not supported because the extension pack '%s' is missing", VBOX_EXTPACK); CHECK_ERROR(pExtPack, COMGETTER(PlugIns)(ComSafeArrayAsOutParam(pExtPackPlugIns))); Bstr pPlugInPath; size_t iPlugIn; for (iPlugIn = 0; iPlugIn < pExtPackPlugIns.size(); iPlugIn++) { Bstr pPlugInName; CHECK_ERROR(pExtPackPlugIns[iPlugIn], COMGETTER(Name)(pPlugInName.asOutParam())); if (RTStrCmp(CSTR(pPlugInName), "VDPlugInCrypt") == 0) { CHECK_ERROR(pExtPackPlugIns[iPlugIn], COMGETTER(ModulePath)(pPlugInPath.asOutParam())); break; } } if (iPlugIn == pExtPackPlugIns.size()) return RTMsgErrorExitFailure("Encryption is not supported because the extension pack '%s' " "is missing the encryption PlugIn (old extension pack installed?)", VBOX_EXTPACK); rc = VDPluginLoadFromFilename(CSTR(pPlugInPath)); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Retrieving encryption settings of the image failed " "because the encryption PlugIn could not be loaded\n"); } SecretKey *pKey = NULL; rc = keyStore.retainSecretKey(Utf8Str(pKeyId), &pKey); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure( "Failed to retrieve the secret key with ID \"%s\" from the store (%Rrc)", CSTR(pKeyId), rc); VDISKCRYPTOSETTINGS vdiskCryptoSettings, *pVDiskCryptoSettings = &vdiskCryptoSettings; vboxImageCryptoSetup(pVDiskCryptoSettings, NULL, (const char *)CSTR(pBase64EncodedKeyStore), (const char *)pKey->getKeyBuffer(), false); rc = VDFilterAdd(g_pVDisk, "CRYPT", VD_FILTER_FLAGS_DEFAULT, pVDiskCryptoSettings->vdFilterIfaces); keyStore.releaseSecretKey(Utf8Str(pKeyId)); if (rc == VERR_VD_PASSWORD_INCORRECT) return RTMsgErrorExitFailure("The password to decrypt the image is incorrect"); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Failed to load the decryption filter: %Rrc", rc); } #endif /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ /* **************** END IFDEF'D (STUBBED-OUT) CODE ***************** */ rc = RTCritSectInit(&g_vdioLock); if (RT_SUCCESS(rc)) { g_VDIfThreadSync.pfnStartRead = vboximgThreadStartRead; g_VDIfThreadSync.pfnFinishRead = vboximgThreadFinishRead; g_VDIfThreadSync.pfnStartWrite = vboximgThreadStartWrite; g_VDIfThreadSync.pfnFinishWrite = vboximgThreadFinishWrite; rc = VDInterfaceAdd(&g_VDIfThreadSync.Core, "vboximg_ThreadSync", VDINTERFACETYPE_THREADSYNC, &g_vdioLock, sizeof(VDINTERFACETHREADSYNC), &g_pVdIfs); } else return RTMsgErrorExitFailure("ERROR: Failed to create critsects " "for virtual disk I/O, rc=%Rrc\n", rc); /* * Create HDD container to open base image and differencing images into */ rc = VDGetFormat(NULL /* pVDIIfsDisk */, NULL /* pVDIIfsImage*/, CSTR(pVDiskBaseImagePath), &pszFormat, &enmType); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("VDGetFormat(,,%s,,) " "failed (during HDD container creation), rc=%Rrc\n", g_pszImagePath, rc); if (VERBOSE) RTPrintf("\nCreating container for base image of format %s\n", pszFormat); g_pVDisk = NULL; rc = VDCreate(g_pVdIfs, enmType, &g_pVDisk); if ((rc)) return RTMsgErrorExitFailure("ERROR: Couldn't create virtual disk container\n"); /* Open all virtual disk media from leaf snapshot (if any) to base image*/ if (VERBOSE) RTPrintf("\nOpening medium chain\n"); IMAGELIST *pCurMedium = listHeadLockList.prev; /* point to base image */ while (pCurMedium != &listHeadLockList) { if (VERBOSE) RTPrintf(" Open: %s\n", CSTR(pCurMedium->pImagePath)); rc = VDOpen(g_pVDisk, CSTR(pszFormat), CSTR(pCurMedium->pImagePath), pCurMedium->fWriteable, g_pVdIfs); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Could not open the medium storage unit '%s' %Rrc", CSTR(pCurMedium->pImagePath), rc); pCurMedium = pCurMedium->prev; } g_cReaders = VDIsReadOnly(g_pVDisk) ? INT32_MAX / 2 : 0; g_cWriters = 0; g_cbEntireVDisk = VDGetSize(g_pVDisk, 0 /* base */); if (g_vboximgOpts.fList) { if (g_pVDisk == NULL) return RTMsgErrorExitFailure("No valid --image to list partitions from\n"); RTPrintf("\n"); rc = parsePartitionTable(); switch(rc) { case PARTITION_TABLE_MBR: displayLegacyPartitionTable(); break; case PARTITION_TABLE_GPT: displayGptPartitionTable(); break; default: return rc; } return 0; } if (g_vboximgOpts.idxPartition >= 0) { if (g_vboximgOpts.offset) return RTMsgErrorExitFailure("--offset and --partition are mutually exclusive options\n"); if (g_vboximgOpts.size) return RTMsgErrorExitFailure("--size and --partition are mutually exclusive options\n"); /* * --partition option specified. That will set the global offset and limit * honored by the disk read and write sanitizers to constrain operations * to within the specified partion based on an initial parsing of the MBR */ rc = parsePartitionTable(); if (rc < 0) return RTMsgErrorExitFailure("Error parsing disk MBR/Partition table\n"); int partNbr = g_vboximgOpts.idxPartition; if (partNbr < 0 || partNbr > g_lastPartNbr) return RTMsgErrorExitFailure("Non-valid partition number specified\n"); if (partNbr == 0) { g_vDiskOffset = 0; g_vDiskSize = VDGetSize(g_pVDisk, 0); if (VERBOSE) RTPrintf("\nPartition 0 specified - Whole disk will be accessible\n"); } else { int fFoundPartition = false; for (int i = 1; i < g_lastPartNbr + 1; i++) { /* If GPT, display vboximg's representation of partition table starts at partition 2 * but the table is displayed calling it partition 1, because the protective MBR * record is relatively pointless to display or reference in this context */ if (g_aParsedPartitionInfo[i].idxPartition == partNbr + (g_fGPT ? 1 : 0)) { fFoundPartition = true; g_vDiskOffset = g_aParsedPartitionInfo[i].offPartition; g_vDiskSize = g_vDiskOffset + g_aParsedPartitionInfo[i].cbPartition; if (VERBOSE) RTPrintf("\nPartition %d specified. Only sectors %llu to %llu of disk will be accessible\n", g_vboximgOpts.idxPartition, g_vDiskOffset / BLOCKSIZE, g_vDiskSize / BLOCKSIZE); } } if (!fFoundPartition) return RTMsgErrorExitFailure("Couldn't find partition %d in partition table\n", partNbr); } } else { if (g_vboximgOpts.offset) { if (g_vboximgOpts.offset < 0 || g_vboximgOpts.offset + g_vboximgOpts.size > g_cbEntireVDisk) return RTMsgErrorExitFailure("User specified offset out of range of virtual disk\n"); if (VERBOSE) RTPrintf("Setting r/w bias (offset) to user requested value for sector %llu\n", g_vDiskOffset / BLOCKSIZE); g_vDiskOffset = g_vboximgOpts.offset; } if (g_vboximgOpts.size) { if (g_vboximgOpts.size < 0 || g_vboximgOpts.offset + g_vboximgOpts.size > g_cbEntireVDisk) return RTMsgErrorExitFailure("User specified size out of range of virtual disk\n"); if (VERBOSE) RTPrintf("Setting r/w size limit to user requested value %llu\n", g_vDiskSize / BLOCKSIZE); g_vDiskSize = g_vboximgOpts.size; } } if (g_vDiskSize == 0) g_vDiskSize = g_cbEntireVDisk - g_vDiskOffset; /* * Hand control over to libfuse. */ if (VERBOSE) RTPrintf("\nvboximg-mount: Going into background...\n"); rc = fuse_main(args.argc, args.argv, &g_vboximgOps, NULL); int rc2 = VDClose(g_pVDisk, false /* fDelete */); AssertRC(rc2); RTPrintf("vboximg-mount: fuse_main -> %d\n", rc); return rc; }