VirtualBox

source: vbox/trunk/src/VBox/ImageMounter/vboximg-mount/vboximg-mount.cpp@ 78352

Last change on this file since 78352 was 77143, checked in by vboxsync, 6 years ago

Fixed a dyslexia-like transposition of words (but don't have dyslexia).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.1 KB
Line 
1/* $Id: vboximg-mount.cpp 77143 2019-02-02 00:22:10Z vboxsync $ */
2/** @file
3 * vboximg-mount - Disk Image Flattening FUSE Program.
4 */
5
6/*
7 * Copyright (C) 2009-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22
23#define LOG_GROUP LOG_GROUP_DEFAULT /** @todo log group */
24
25#define FUSE_USE_VERSION 27
26#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined(RT_OS_FEEBSD)
27# define UNIX_DERIVATIVE
28#endif
29#define MAX_READERS (INT32_MAX / 32)
30#include <fuse.h>
31#ifdef UNIX_DERIVATIVE
32#include <errno.h>
33#include <fcntl.h>
34#include <stdlib.h>
35#include <libgen.h>
36#include <unistd.h>
37#include <math.h>
38#include <cstdarg>
39#include <sys/stat.h>
40#endif
41#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_LINUX)
42# include <sys/param.h>
43# undef PVM /* Blasted old BSD mess still hanging around darwin. */
44#endif
45#ifdef RT_OS_LINUX
46# include <linux/fs.h>
47# include <linux/hdreg.h>
48#endif
49#include <VirtualBox_XPCOM.h>
50#include <VBox/com/VirtualBox.h>
51#include <VBox/vd.h>
52#include <VBox/vd-ifs.h>
53#include <VBox/log.h>
54#include <VBox/err.h>
55#include <VBox/com/ErrorInfo.h>
56#include <VBox/com/NativeEventQueue.h>
57#include <VBox/com/com.h>
58#include <VBox/com/string.h>
59#include <VBox/com/Guid.h>
60#include <VBox/com/array.h>
61#include <VBox/com/errorprint.h>
62#include <VBox/vd-plugin.h>
63#include <iprt/initterm.h>
64#include <iprt/assert.h>
65#include <iprt/message.h>
66#include <iprt/critsect.h>
67#include <iprt/asm.h>
68#include <iprt/mem.h>
69#include <iprt/string.h>
70#include <iprt/initterm.h>
71#include <iprt/stream.h>
72#include <iprt/types.h>
73#include <iprt/path.h>
74#include <iprt/utf16.h>
75#include <iprt/base64.h>
76
77#include "vboximg-mount.h"
78#include "vboximgCrypto.h"
79#include "vboximgMedia.h"
80#include "SelfSizingTable.h"
81#include "vboximgOpts.h"
82
83using namespace com;
84
85enum {
86 USAGE_FLAG,
87};
88
89enum { PARTITION_TABLE_MBR = 1, PARTITION_TABLE_GPT = 2 };
90
91#define VBOX_EXTPACK "Oracle VM VirtualBox Extension Pack"
92#define GPT_PTABLE_SIZE 32 * BLOCKSIZE /** Max size we to read for GPT partition table */
93#define MBR_PARTITIONS_MAX 4 /** Fixed number of partitions in Master Boot Record */
94#define BASENAME_MAX 256 /** Maximum name for the basename of a path (for RTStrNLen()*/
95#define VBOXIMG_PARTITION_MAX 256 /** How much storage to allocate to store partition info */
96#define PARTITION_NAME_MAX 72 /** Maximum partition name size (accomodates GPT partition name) */
97#define BLOCKSIZE 512 /** Commonly used disk block size */
98#define DOS_BOOT_RECORD_SIGNATURE 0xaa55 /** MBR and EBR (partition table) signature [EOT boundary] */
99#define NULL_BOOT_RECORD_SIGNATURE 0x0000 /** MBR or EBR null signature value */
100#define MAX_UUID_LEN 256 /** Max length of a UUID */
101#define LBA(n) (n * BLOCKSIZE)
102#define VD_SECTOR_SIZE 512 /** Virtual disk sector/blocksize */
103#define VD_SECTOR_MASK (VD_SECTOR_SIZE - 1) /** Masks off a blocks worth of data */
104#define VD_SECTOR_OUT_OF_BOUNDS_MASK (~UINT64_C(VD_SECTOR_MASK)) /** Masks the overflow of a blocks worth of data */
105#define VERBOSE g_vboximgOpts.fVerbose
106#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)
107
108#define PARTTYPE_IS_NULL(parType) ((uint8_t)parType == 0x00)
109#define PARTTYPE_IS_GPT(parType) ((uint8_t)parType == 0xee)
110#define PARTTYPE_IS_EXT(parType) (( (uint8_t)parType) == 0x05 /* Extended */ \
111 || ((uint8_t)parType) == 0x0f /* W95 Extended (LBA) */ \
112 || ((uint8_t)parType) == 0x85) /* Linux Extended */
113
114#define SAFENULL(strPtr) (strPtr ? strPtr : "")
115#define CSTR(arg) Utf8Str(arg).c_str() /* Converts XPCOM string type to C string type */
116
117static struct fuse_operations g_vboximgOps; /** FUSE structure that defines allowed ops for this FS */
118
119/* Global variables */
120
121static PVDISK g_pVDisk; /** Handle for Virtual Disk in contet */
122static char *g_pszDiskUuid; /** UUID of image (if known, otherwise NULL) */
123static off_t g_vDiskOffset; /** Biases r/w from start of VD */
124static off_t g_vDiskSize; /** Limits r/w length for VD */
125static int32_t g_cReaders; /** Number of readers for VD */
126static int32_t g_cWriters; /** Number of writers for VD */
127static RTFOFF g_cbEntireVDisk; /** Size of VD */
128static PVDINTERFACE g_pVdIfs; /** @todo Remove when VD I/O becomes threadsafe */
129static VDINTERFACETHREADSYNC g_VDIfThreadSync; /** @todo Remove when VD I/O becomes threadsafe */
130static RTCRITSECT g_vdioLock; /** @todo Remove when VD I/O becomes threadsafe */
131static uint16_t g_lastPartNbr; /** Last partition number found in MBR + EBR chain */
132static bool g_fGPT; /** True if GPT type partition table was found */
133static char *g_pszImageName; /** Base filename for current VD image */
134static char *g_pszImagePath; /** Full path to current VD image */
135static char *g_pszBaseImagePath; /** Base image known after parsing */
136static char *g_pszBaseImageName; /** Base image known after parsing */
137static uint32_t g_cImages; /** Number of images in diff chain */
138
139/* Table entry containing partition info parsed out of GPT or MBR and EBR chain of specified VD */
140
141typedef struct
142{
143 int idxPartition; /** partition number */
144 char *pszName;
145 off_t offPartition; /** partition offset from start of disk, in bytes */
146 uint64_t cbPartition; /** partition size in bytes */
147 uint8_t fBootable; /** partition bootable */
148 union
149 {
150 uint8_t legacy; /** partition type MBR/EBR */
151 uint128_t gptGuidTypeSpecifier; /** partition type GPT */
152 } partitionType; /** uint8_t for MBR/EBR (legacy) and GUID for GPT */
153 union
154 {
155 MBRPARTITIONENTRY mbrEntry; /** MBR (also EBR partition entry) */
156 GPTPARTITIONENTRY gptEntry; /** GPT partition entry */
157 } partitionEntry;
158} PARTITIONINFO;
159
160PARTITIONINFO g_aParsedPartitionInfo[VBOXIMG_PARTITION_MAX + 1]; /* Note: Element 0 reserved for EntireDisk partitionEntry */
161
162VBOXIMGOPTS g_vboximgOpts;
163
164#define OPTION(fmt, pos, val) { fmt, offsetof(struct vboximgOpts, pos), val }
165
166static struct fuse_opt vboximgOptDefs[] = {
167 OPTION("--image %s", pszImageUuidOrPath, 0),
168 OPTION("-i %s", pszImageUuidOrPath, 0),
169 OPTION("--rw", fRW, 1),
170 OPTION("--root", fAllowRoot, 0),
171 OPTION("--vm %s", pszVm, 0),
172 OPTION("--partition %d", idxPartition, 1),
173 OPTION("-p %d", idxPartition, 1),
174 OPTION("--offset %d", offset, 1),
175 OPTION("-o %d", offset, 1),
176 OPTION("--size %d", size, 1),
177 OPTION("-s %d", size, 1),
178 OPTION("-l", fList, 1),
179 OPTION("--list", fList, 1),
180 OPTION("--verbose", fVerbose, 1),
181 OPTION("-v", fVerbose, 1),
182 OPTION("--wide", fWide, 1),
183 OPTION("-w", fWide, 1),
184 OPTION("-lv", fVerboseList, 1),
185 OPTION("-vl", fVerboseList, 1),
186 OPTION("-lw", fWideList, 1),
187 OPTION("-wl", fWideList, 1),
188 OPTION("-h", fBriefUsage, 1),
189 FUSE_OPT_KEY("--help", USAGE_FLAG),
190 FUSE_OPT_KEY("-vm", FUSE_OPT_KEY_NONOPT),
191 FUSE_OPT_END
192};
193
194typedef struct IMAGELIST
195{
196 struct IMAGELIST *next;
197 struct IMAGELIST *prev;
198 ComPtr<IToken> pLockToken;
199 bool fWriteable;
200 ComPtr<IMedium> pImage;
201 Bstr pImageName;
202 Bstr pImagePath;
203} IMAGELIST;
204
205IMAGELIST listHeadLockList; /* flink & blink intentionally left NULL */
206
207static void
208briefUsage()
209{
210 RTPrintf("usage: vboximg-mount [options] <mount point directory path>\n\n"
211 "vboximg-mount options:\n\n"
212 " [ { -i | --image } <specifier> ] VirtualBox disk base image or snapshot,\n"
213 " specified by UUID or path\n"
214 "\n"
215 " [ { -l | --list } ] If --image specified, list its partitions,\n"
216 " otherwise, list registered VMs and their\n"
217 " attached virtual HDD disk media. In verbose\n"
218 " mode, VM/media list will be long format,\n"
219 " i.e. including snapshot images and paths.\n"
220 "\n"
221 " [ { -w | --wide } ] List media in wide / tabular format\n"
222 " (reduces vertical scrolling but requires\n"
223 " wider than standard 80 column window)\n"
224 "\n"
225 " [ --vm UUID ] Restrict media list to specified vm.\n"
226 "\n"
227 " [ { -p | --partition } <part #> ] Expose only specified partition via FUSE.\n"
228 "\n"
229 " [ { -o | --offset } <byte #> ] Bias disk I/O by offset from disk start.\n"
230 " (incompatible with -p, --partition)\n"
231 "\n"
232 " [ { -s | --size <bytes> } ] Specify size of mounted disk.\n"
233 " (incompatible with -p, --partition)\n"
234 "\n"
235 " [ --rw ] Make image writeable (default = readonly)\n"
236 "\n"
237 " [ --root ] Same as -o allow_root.\n"
238 "\n"
239 " [ { -v | --verbose } ] Log extra information.\n"
240 "\n"
241 " [ -o opt[,opt...]] FUSE mount options.\n"
242 "\n"
243 " [ { --help | -h | -? } ] Display this usage information.\n"
244 );
245 RTPrintf("\n"
246 "vboximg-mount is a utility to make VirtualBox disk images available to the host\n"
247 "operating system for privileged or non-priviliged access. Any version of the\n"
248 "disk can be mounted from its available history of snapshots.\n"
249 "\n"
250 "If the user specifies a base image identifier using the --image option, only\n"
251 "the base image will be mounted, disregarding any snapshots. Alternatively,\n"
252 "if a snapshot is specified, the state of the FUSE-mounted virtual disk\n"
253 "is synthesized from the implied chain of snapshots, including the base image.\n"
254 "\n"
255 "The virtual disk is exposed as a device node within a FUSE-based filesystem\n"
256 "that overlays the user-provided mount point. The FUSE filesystem consists of a\n"
257 "directory containing two files: A pseudo HDD device node and a symbolic\n"
258 "link. The device node, named 'vhdd', is the access point to the virtual disk\n"
259 "(i.e. the OS-mountable raw binary). The symbolic link has the same basename(1)\n"
260 "as the virtual disk base image and points to the location of the\n"
261 "virtual disk base image. If the --partition, --offset, or --size\n"
262 "options are provided, the boundaries of the FUSE-mounted subsection of the\n"
263 "virtual disk will be described as a numeric range in brackets appended to the\n"
264 "symbolic link name.\n"
265 "\n"
266 );
267}
268
269static int
270vboximgOptHandler(void *data, const char *arg, int optKey, struct fuse_args *outargs)
271{
272 NOREF(data);
273 NOREF(arg);
274 NOREF(optKey);
275 NOREF(outargs);
276
277 /*
278 * Apparently this handler is only called for arguments FUSE can't parse,
279 * and arguments that don't result in variable assignment such as "USAGE"
280 * In this impl. that's always deemed a parsing error.
281 */
282 if (*arg != '-') /* could be user's mount point */
283 return 1;
284
285 return -1;
286}
287
288/** @copydoc fuse_operations::open */
289static int vboximgOp_open(const char *pszPath, struct fuse_file_info *pInfo)
290{
291 RT_NOREF(pszPath, pInfo);;
292 LogFlowFunc(("pszPath=%s\n", pszPath));
293 uint32_t notsup = 0;
294 int rc = 0;
295
296#ifdef UNIX_DERIVATIVE
297# ifdef RT_OS_DARWIN
298 notsup = O_APPEND | O_NONBLOCK | O_SYMLINK | O_NOCTTY | O_SHLOCK | O_EXLOCK |
299 O_ASYNC | O_CREAT | O_TRUNC | O_EXCL | O_EVTONLY;
300# elif defined(RT_OS_LINUX)
301 notsup = O_APPEND | O_ASYNC | O_DIRECT | O_NOATIME | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK;
302 /* | O_LARGEFILE | O_SYNC | ? */
303# elif defined(RT_OS_FREEBSD)
304 notsup = O_APPEND | O_ASYNC | O_DIRECT | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK;
305 /* | O_LARGEFILE | O_SYNC | ? */
306# endif
307#else
308# error "Port me"
309#endif
310
311if (pInfo->flags & notsup)
312 rc -EINVAL;
313
314#ifdef UNIX_DERIVATIVE
315 if ((pInfo->flags & O_ACCMODE) == O_ACCMODE)
316 rc = -EINVAL;
317# ifdef O_DIRECTORY
318 if (pInfo->flags & O_DIRECTORY)
319 rc = -ENOTDIR;
320# endif
321#endif
322
323 if (RT_FAILURE(rc))
324 {
325 LogFlowFunc(("rc=%d \"%s\"\n", rc, pszPath));
326 return rc;
327 }
328
329 int fWriteable = (pInfo->flags & O_ACCMODE) == O_WRONLY
330 || (pInfo->flags & O_ACCMODE) == O_RDWR;
331 if (g_cWriters)
332 rc = -ETXTBSY;
333 else
334 {
335 if (fWriteable)
336 g_cWriters++;
337 else
338 {
339 if (g_cReaders + 1 > MAX_READERS)
340 rc = -EMLINK;
341 else
342 g_cReaders++;
343 }
344 }
345 LogFlowFunc(("rc=%d \"%s\"\n", rc, pszPath));
346 return rc;
347
348}
349
350
351/** @todo Remove when VD I/O becomes threadsafe */
352static DECLCALLBACK(int) vboximgThreadStartRead(void *pvUser)
353{
354 PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser;
355 return RTCritSectEnter(vdioLock);
356}
357
358static DECLCALLBACK(int) vboximgThreadFinishRead(void *pvUser)
359{
360 PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser;
361 return RTCritSectLeave(vdioLock);
362}
363
364static DECLCALLBACK(int) vboximgThreadStartWrite(void *pvUser)
365{
366 PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser;
367 return RTCritSectEnter(vdioLock);
368}
369
370static DECLCALLBACK(int) vboximgThreadFinishWrite(void *pvUser)
371{
372 PRTCRITSECT vdioLock = (PRTCRITSECT)pvUser;
373 return RTCritSectLeave(vdioLock);
374}
375/** @todo (end of to do section) */
376
377/** @copydoc fuse_operations::release */
378static int vboximgOp_release(const char *pszPath, struct fuse_file_info *pInfo)
379{
380 NOREF(pszPath);
381
382 LogFlowFunc(("pszPath=%s\n", pszPath));
383
384 if ( (pInfo->flags & O_ACCMODE) == O_WRONLY
385 || (pInfo->flags & O_ACCMODE) == O_RDWR)
386 {
387 g_cWriters--;
388 Assert(g_cWriters >= 0);
389 }
390 else if ((pInfo->flags & O_ACCMODE) == O_RDONLY)
391 {
392 g_cReaders--;
393 Assert(g_cReaders >= 0);
394 }
395 else
396 AssertFailed();
397
398 LogFlowFunc(("\"%s\"\n", pszPath));
399 return 0;
400}
401
402/**
403 * VD read Sanitizer taking care of unaligned accesses.
404 *
405 * @return VBox bootIndicator code.
406 * @param pDisk VD disk container.
407 * @param off Offset to start reading from.
408 * @param pvBuf Pointer to the buffer to read into.
409 * @param cbRead Amount of bytes to read.
410 */
411static int vdReadSanitizer(PVDISK pDisk, uint64_t off, void *pvBuf, size_t cbRead)
412{
413 int rc;
414
415 uint64_t const cbMisalignmentOfStart = off & VD_SECTOR_MASK;
416 uint64_t const cbMisalignmentOfEnd = (off + cbRead) & VD_SECTOR_MASK;
417
418 if (cbMisalignmentOfStart + cbMisalignmentOfEnd == 0) /* perfectly aligned request; just read it and done */
419 rc = VDRead(pDisk, off, pvBuf, cbRead);
420 else
421 {
422 uint8_t *pbBuf = (uint8_t *)pvBuf;
423 uint8_t abBuf[VD_SECTOR_SIZE];
424
425 /* If offset not @ sector boundary, read whole sector, then copy unaligned
426 * bytes (requested by user), only up to sector boundary, into user's buffer
427 */
428 if (cbMisalignmentOfStart)
429 {
430 rc = VDRead(pDisk, off - cbMisalignmentOfStart, abBuf, VD_SECTOR_SIZE);
431 if (RT_SUCCESS(rc))
432 {
433 size_t const cbPartial = RT_MIN(VD_SECTOR_SIZE - cbMisalignmentOfStart, cbRead);
434 memcpy(pbBuf, &abBuf[cbMisalignmentOfStart], cbPartial);
435 pbBuf += cbPartial;
436 off += cbPartial; /* Beginning of next sector or EOD */
437 cbRead -= cbPartial; /* # left to read */
438 }
439 }
440 else /* user's offset already aligned, did nothing */
441 rc = VINF_SUCCESS;
442
443 /* Read remaining aligned sectors, deferring any tail-skewed bytes */
444 if (RT_SUCCESS(rc) && cbRead >= VD_SECTOR_SIZE)
445 {
446 Assert(!(off % VD_SECTOR_SIZE));
447
448 size_t cbPartial = cbRead - cbMisalignmentOfEnd;
449 Assert(!(cbPartial % VD_SECTOR_SIZE));
450 rc = VDRead(pDisk, off, pbBuf, cbPartial);
451 if (RT_SUCCESS(rc))
452 {
453 pbBuf += cbPartial;
454 off += cbPartial;
455 cbRead -= cbPartial;
456 }
457 }
458
459 /* Unaligned buffered read of tail. */
460 if (RT_SUCCESS(rc) && cbRead)
461 {
462 Assert(cbRead == cbMisalignmentOfEnd);
463 Assert(cbRead < VD_SECTOR_SIZE);
464 Assert(!(off % VD_SECTOR_SIZE));
465
466 rc = VDRead(pDisk, off, abBuf, VD_SECTOR_SIZE);
467 if (RT_SUCCESS(rc))
468 memcpy(pbBuf, abBuf, cbRead);
469 }
470 }
471
472 if (RT_FAILURE(rc))
473 {
474 int sysrc = -RTErrConvertToErrno(rc);
475 LogFlowFunc(("error: %s (vbox err: %d)\n", strerror(sysrc), rc));
476 rc = sysrc;
477 }
478 return cbRead;
479}
480
481/**
482 * VD write Sanitizer taking care of unaligned accesses.
483 *
484 * @return VBox bootIndicator code.
485 * @param pDisk VD disk container.
486 * @param off Offset to start writing to.
487 * @param pvSrc Pointer to the buffer to read from.
488 * @param cbWrite Amount of bytes to write.
489 */
490static int vdWriteSanitizer(PVDISK pDisk, uint64_t off, const void *pvSrc, size_t cbWrite)
491{
492 uint8_t const *pbSrc = (uint8_t const *)pvSrc;
493 uint8_t abBuf[4096];
494 int rc;
495 int cbRemaining = cbWrite;
496
497 /*
498 * Take direct route if the request is sector aligned.
499 */
500 uint64_t const cbMisalignmentOfStart = off & VD_SECTOR_MASK;
501 size_t const cbMisalignmentOfEnd = (off + cbWrite) & VD_SECTOR_MASK;
502 if (!cbMisalignmentOfStart && !cbMisalignmentOfEnd)
503 {
504 rc = VDWrite(pDisk, off, pbSrc, cbWrite);
505 do
506 {
507 size_t cbThisWrite = RT_MIN(cbWrite, sizeof(abBuf));
508 rc = VDWrite(pDisk, off, memcpy(abBuf, pbSrc, cbThisWrite), cbThisWrite);
509 if (RT_SUCCESS(rc))
510 {
511 pbSrc += cbThisWrite;
512 off += cbThisWrite;
513 cbRemaining -= cbThisWrite;
514 }
515 else
516 break;
517 } while (cbRemaining > 0);
518 }
519 else
520 {
521 /*
522 * Unaligned buffered read+write of head. Aligns the offset.
523 */
524 if (cbMisalignmentOfStart)
525 {
526 rc = VDRead(pDisk, off - cbMisalignmentOfStart, abBuf, VD_SECTOR_SIZE);
527 if (RT_SUCCESS(rc))
528 {
529 size_t const cbPartial = RT_MIN(VD_SECTOR_SIZE - cbMisalignmentOfStart, cbWrite);
530 memcpy(&abBuf[cbMisalignmentOfStart], pbSrc, cbPartial);
531 rc = VDWrite(pDisk, off - cbMisalignmentOfStart, abBuf, VD_SECTOR_SIZE);
532 if (RT_SUCCESS(rc))
533 {
534 pbSrc += cbPartial;
535 off += cbPartial;
536 cbRemaining -= cbPartial;
537 }
538 }
539 }
540 else
541 rc = VINF_SUCCESS;
542
543 /*
544 * Aligned direct write.
545 */
546 if (RT_SUCCESS(rc) && cbWrite >= VD_SECTOR_SIZE)
547 {
548 Assert(!(off % VD_SECTOR_SIZE));
549 size_t cbPartial = cbWrite - cbMisalignmentOfEnd;
550 Assert(!(cbPartial % VD_SECTOR_SIZE));
551 rc = VDWrite(pDisk, off, pbSrc, cbPartial);
552 if (RT_SUCCESS(rc))
553 {
554 pbSrc += cbPartial;
555 off += cbPartial;
556 cbRemaining -= cbPartial;
557 }
558 }
559
560 /*
561 * Unaligned buffered read + write of tail.
562 */
563 if ( RT_SUCCESS(rc) && cbWrite > 0)
564 {
565 Assert(cbWrite == cbMisalignmentOfEnd);
566 Assert(cbWrite < VD_SECTOR_SIZE);
567 Assert(!(off % VD_SECTOR_SIZE));
568 rc = VDRead(pDisk, off, abBuf, VD_SECTOR_SIZE);
569 if (RT_SUCCESS(rc))
570 {
571 memcpy(abBuf, pbSrc, cbWrite);
572 rc = VDWrite(pDisk, off, abBuf, VD_SECTOR_SIZE);
573 }
574 }
575 }
576 if (RT_FAILURE(rc))
577 {
578 int sysrc = -RTErrConvertToErrno(rc);
579 LogFlowFunc(("error: %s (vbox err: %d)\n", strerror(sysrc), rc));
580 return sysrc;
581 }
582 return cbWrite - cbRemaining;
583}
584
585
586/** @copydoc fuse_operations::read */
587static int vboximgOp_read(const char *pszPath, char *pbBuf, size_t cbBuf,
588 off_t offset, struct fuse_file_info *pInfo)
589{
590 NOREF(pszPath);
591 NOREF(pInfo);
592
593 LogFlowFunc(("my offset=%#llx size=%#zx path=\"%s\"\n", (uint64_t)offset, cbBuf, pszPath));
594
595 AssertReturn(offset >= 0, -EINVAL);
596 AssertReturn((int)cbBuf >= 0, -EINVAL);
597 AssertReturn((unsigned)cbBuf == cbBuf, -EINVAL);
598
599 AssertReturn(offset + g_vDiskOffset >= 0, -EINVAL);
600 int64_t adjOff = offset + g_vDiskOffset;
601
602 int rc = 0;
603 if ((off_t)(adjOff + cbBuf) < adjOff)
604 rc = -EINVAL;
605 else if (adjOff >= g_vDiskSize)
606 return 0;
607 else if (!cbBuf)
608 return 0;
609
610 if (rc >= 0)
611 rc = vdReadSanitizer(g_pVDisk, adjOff, pbBuf, cbBuf);
612 if (rc < 0)
613 LogFlowFunc(("%s\n", strerror(rc)));
614 return rc;
615}
616
617/** @copydoc fuse_operations::write */
618static int vboximgOp_write(const char *pszPath, const char *pbBuf, size_t cbBuf,
619 off_t offset, struct fuse_file_info *pInfo)
620{
621 NOREF(pszPath);
622 NOREF(pInfo);
623
624 LogFlowFunc(("offset=%#llx size=%#zx path=\"%s\"\n", (uint64_t)offset, cbBuf, pszPath));
625
626 AssertReturn(offset >= 0, -EINVAL);
627 AssertReturn((int)cbBuf >= 0, -EINVAL);
628 AssertReturn((unsigned)cbBuf == cbBuf, -EINVAL);
629 AssertReturn(offset + g_vDiskOffset >= 0, -EINVAL);
630 int64_t adjOff = offset + g_vDiskOffset;
631
632 int rc = 0;
633 if (!g_vboximgOpts.fRW) {
634 LogFlowFunc(("WARNING: vboximg-mount (FUSE FS) --rw option not specified\n"
635 " (write operation ignored w/o error!)\n"));
636 return cbBuf;
637 } else if ((off_t)(adjOff + cbBuf) < adjOff)
638 rc = -EINVAL;
639 else if (offset >= g_vDiskSize)
640 return 0;
641 else if (!cbBuf)
642 return 0;
643
644 if (rc >= 0)
645 rc = vdWriteSanitizer(g_pVDisk, adjOff, pbBuf, cbBuf);
646 if (rc < 0)
647 LogFlowFunc(("%s\n", strerror(rc)));
648
649 return rc;
650}
651
652/** @copydoc fuse_operations::getattr */
653static int
654vboximgOp_getattr(const char *pszPath, struct stat *stbuf)
655{
656 int rc = 0;
657
658 LogFlowFunc(("pszPath=%s, stat(\"%s\")\n", pszPath, g_pszImagePath));
659
660 memset(stbuf, 0, sizeof(struct stat));
661
662 if (RTStrCmp(pszPath, "/") == 0)
663 {
664 stbuf->st_mode = S_IFDIR | 0755;
665 stbuf->st_nlink = 2;
666 }
667 else if (RTStrCmp(pszPath + 1, "vhdd") == 0)
668 {
669 rc = stat(g_pszImagePath, stbuf);
670 if (rc < 0)
671 return rc;
672 /*
673 * st_size represents the size of the FUSE FS-mounted portion of the disk.
674 * By default it is the whole disk, but can be a partition or specified
675 * (or overridden) directly by the { -s | --size } option on the command line.
676 */
677 stbuf->st_size = g_vDiskSize;
678 stbuf->st_nlink = 1;
679 }
680 else if (RTStrNCmp(pszPath + 1, g_pszImageName, strlen(g_pszImageName)) == 0)
681 {
682 /* When the disk is partitioned, the symbolic link named from `basename` of
683 * resolved path to VBox disk image, has appended to it formatted text
684 * representing the offset range of the partition.
685 *
686 * $ vboximg-mount -i /stroll/along/the/path/simple_fixed_disk.vdi -p 1 /mnt/tmpdir
687 * $ ls /mnt/tmpdir
688 * simple_fixed_disk.vdi[20480:2013244928] vhdd
689 */
690 rc = stat(g_pszImagePath, stbuf);
691 if (rc < 0)
692 return rc;
693 stbuf->st_size = 0;
694 stbuf->st_mode = S_IFLNK | 0444;
695 stbuf->st_nlink = 1;
696 stbuf->st_uid = 0;
697 stbuf->st_gid = 0;
698 } else
699 rc = -ENOENT;
700
701 return rc;
702}
703
704/** @copydoc fuse_operations::readdir */
705static int
706vboximgOp_readdir(const char *pszPath, void *pvBuf, fuse_fill_dir_t pfnFiller,
707 off_t offset, struct fuse_file_info *pInfo)
708
709{
710 NOREF(offset);
711 NOREF(pInfo);
712
713 if (RTStrCmp(pszPath, "/") != 0)
714 return -ENOENT;
715
716 /*
717 * mandatory '.', '..', ...
718 */
719 pfnFiller(pvBuf, ".", NULL, 0);
720 pfnFiller(pvBuf, "..", NULL, 0);
721
722 /*
723 * Create FUSE FS dir entry that is depicted here (and exposed via stat()) as
724 * a symbolic link back to the resolved path to the VBox virtual disk image,
725 * whose symlink name is basename that path. This is a convenience so anyone
726 * listing the dir can figure out easily what the vhdd FUSE node entry
727 * represents.
728 */
729
730 if (g_vDiskOffset == 0 && (g_vDiskSize == 0 || g_vDiskSize == g_cbEntireVDisk))
731 pfnFiller(pvBuf, g_pszImageName, NULL, 0);
732 else
733 {
734 char tmp[BASENAME_MAX];
735 RTStrPrintf(tmp, sizeof (tmp), "%s[%jd:%jd]", g_pszImageName, g_vDiskOffset, g_vDiskSize);
736 pfnFiller(pvBuf, tmp, NULL, 0);
737 }
738 /*
739 * Create entry named "vhdd", which getattr() will describe as a
740 * regular file, and thus will go through the open/release/read/write vectors
741 * to access the VirtualBox image as processed by the IRPT VD API.
742 */
743 pfnFiller(pvBuf, "vhdd", NULL, 0);
744 return 0;
745}
746
747/** @copydoc fuse_operations::readlink */
748static int
749vboximgOp_readlink(const char *pszPath, char *buf, size_t size)
750{
751 NOREF(pszPath);
752 RTStrCopy(buf, size, g_pszImagePath);
753 return 0;
754}
755
756uint8_t
757parsePartitionTable(void)
758{
759 MBR_t mbr;
760 EBR_t ebr;
761 PTH_t parTblHdr;
762
763 ASSERT(sizeof (mbr) == 512);
764 ASSERT(sizeof (ebr) == 512);
765 /*
766 * First entry describes entire disk as a single entity
767 */
768 g_aParsedPartitionInfo[0].idxPartition = 0;
769 g_aParsedPartitionInfo[0].offPartition = 0;
770 g_aParsedPartitionInfo[0].cbPartition = VDGetSize(g_pVDisk, 0);
771 g_aParsedPartitionInfo[0].pszName = RTStrDup("EntireDisk");
772
773 /*
774 * Currently only DOS partitioned disks are supported. Ensure this one conforms
775 */
776 int rc = vdReadSanitizer(g_pVDisk, 0, &mbr, sizeof (mbr));
777 if (RT_FAILURE(rc))
778 return RTMsgErrorExitFailure("Error reading MBR block from disk\n");
779
780 if (mbr.signature == NULL_BOOT_RECORD_SIGNATURE)
781 return RTMsgErrorExitFailure("Unprt disk (null MBR signature)\n");
782
783 if (mbr.signature != DOS_BOOT_RECORD_SIGNATURE)
784 return RTMsgErrorExitFailure("Invalid MBR found on image with signature 0x%04hX\n",
785 mbr.signature);
786 /*
787 * Parse the four physical partition entires in the MBR (any one, and only one, can be an EBR)
788 */
789 int idxEbrPartitionInMbr = 0;
790 for (int idxPartition = 1;
791 idxPartition <= MBR_PARTITIONS_MAX;
792 idxPartition++)
793 {
794 MBRPARTITIONENTRY *pMbrPartitionEntry =
795 &g_aParsedPartitionInfo[idxPartition].partitionEntry.mbrEntry;;
796 memcpy (pMbrPartitionEntry, &mbr.partitionEntry[idxPartition - 1], sizeof (MBRPARTITIONENTRY));
797
798 if (PARTTYPE_IS_NULL(pMbrPartitionEntry->type))
799 continue;
800
801 if (PARTTYPE_IS_EXT(pMbrPartitionEntry->type))
802 {
803 if (idxEbrPartitionInMbr)
804 return RTMsgErrorExitFailure("Multiple EBRs found found in MBR\n");
805 idxEbrPartitionInMbr = idxPartition;
806 }
807
808 PARTITIONINFO *ppi = &g_aParsedPartitionInfo[idxPartition];
809
810 ppi->idxPartition = idxPartition;
811 ppi->offPartition = (off_t) pMbrPartitionEntry->partitionLba * BLOCKSIZE;
812 ppi->cbPartition = (off_t) pMbrPartitionEntry->partitionBlkCnt * BLOCKSIZE;
813 ppi->fBootable = pMbrPartitionEntry->bootIndicator == 0x80;
814 ppi->partitionType.legacy = pMbrPartitionEntry->type;
815
816 g_lastPartNbr = idxPartition;
817
818 if (PARTTYPE_IS_GPT(pMbrPartitionEntry->type))
819 {
820 g_fGPT = true;
821 break;
822 }
823 }
824
825 if (g_fGPT)
826 {
827 g_lastPartNbr = 2; /* from the 'protective MBR' */
828
829 rc = vdReadSanitizer(g_pVDisk, LBA(1), &parTblHdr, sizeof (parTblHdr));
830 if (RT_FAILURE(rc))
831 return RTMsgErrorExitFailure("Error reading Partition Table Header (LBA 1) from disk\n");
832
833 uint8_t *pTblBuf = (uint8_t *)RTMemAlloc(GPT_PTABLE_SIZE);
834
835 if (!pTblBuf)
836 return RTMsgErrorExitFailure("Out of memory\n");
837
838 rc = vdReadSanitizer(g_pVDisk, LBA(2), pTblBuf, GPT_PTABLE_SIZE);
839 if (RT_FAILURE(rc))
840 return RTMsgErrorExitFailure("Error reading Partition Table blocks from disk\n");
841
842 uint32_t cEntries = parTblHdr.cPartitionEntries;
843 uint32_t cbEntry = parTblHdr.cbPartitionEntry;
844 if (cEntries * cbEntry > GPT_PTABLE_SIZE)
845 {
846 RTPrintf("Partition entries exceed GPT table read from disk (pruning!)\n");
847 while (cEntries * cbEntry > GPT_PTABLE_SIZE && cEntries > 0)
848 --cEntries;
849 }
850 uint8_t *pEntryRaw = pTblBuf;
851 for (uint32_t i = 0; i < cEntries; i++)
852 {
853 GPTPARTITIONENTRY *pEntry = (GPTPARTITIONENTRY *)pEntryRaw;
854 PARTITIONINFO *ppi = &g_aParsedPartitionInfo[g_lastPartNbr];
855 memcpy(&(ppi->partitionEntry).gptEntry, pEntry, sizeof(GPTPARTITIONENTRY));
856 if (!pEntry->firstLba)
857 break;
858 ppi->offPartition = pEntry->firstLba * BLOCKSIZE;
859 ppi->cbPartition = (pEntry->lastLba - pEntry->firstLba) * BLOCKSIZE;
860 ppi->fBootable = pEntry->attrFlags & (1 << GPT_LEGACY_BIOS_BOOTABLE);
861 ppi->partitionType.gptGuidTypeSpecifier = pEntry->partitionTypeGuid;
862 size_t cwName = sizeof (pEntry->partitionName) / 2;
863 RTUtf16LittleToUtf8Ex((PRTUTF16)pEntry->partitionName, RTSTR_MAX, &ppi->pszName, cwName, NULL);
864 ppi->idxPartition = g_lastPartNbr++;
865 pEntryRaw += cbEntry;
866 }
867 return PARTITION_TABLE_GPT;
868 }
869
870 /*
871 * Starting with EBR located in MBR, walk EBR chain to parse the logical partition entries
872 */
873 if (idxEbrPartitionInMbr)
874 {
875 uint32_t firstEbrLba
876 = g_aParsedPartitionInfo[idxEbrPartitionInMbr].partitionEntry.mbrEntry.partitionLba;
877 off_t firstEbrOffset = (off_t)firstEbrLba * BLOCKSIZE;
878 off_t chainedEbrOffset = 0;
879
880 if (!firstEbrLba)
881 return RTMsgErrorExitFailure("Inconsistency for logical partition start. Aborting\n");
882
883 for (int idxPartition = 5;
884 idxPartition <= VBOXIMG_PARTITION_MAX;
885 idxPartition++)
886 {
887
888 off_t currentEbrOffset = firstEbrOffset + chainedEbrOffset;
889 vdReadSanitizer(g_pVDisk, currentEbrOffset, &ebr, sizeof (ebr));
890
891 if (ebr.signature != DOS_BOOT_RECORD_SIGNATURE)
892 return RTMsgErrorExitFailure("Invalid EBR found on image with signature 0x%04hX\n",
893 ebr.signature);
894
895 MBRPARTITIONENTRY *pEbrPartitionEntry =
896 &g_aParsedPartitionInfo[idxPartition].partitionEntry.mbrEntry; /* EBR entry struct same as MBR */
897 memcpy(pEbrPartitionEntry, &ebr.partitionEntry, sizeof (MBRPARTITIONENTRY));
898
899 if (pEbrPartitionEntry->type == NULL_BOOT_RECORD_SIGNATURE)
900 return RTMsgErrorExitFailure("Logical partition with type 0 encountered");
901
902 if (!pEbrPartitionEntry->partitionLba)
903 return RTMsgErrorExitFailure("Logical partition invalid partition start offset (LBA) encountered");
904
905 PARTITIONINFO *ppi = &g_aParsedPartitionInfo[idxPartition];
906 ppi->idxPartition = idxPartition;
907 ppi->offPartition = currentEbrOffset + (off_t)pEbrPartitionEntry->partitionLba * BLOCKSIZE;
908 ppi->cbPartition = (off_t)pEbrPartitionEntry->partitionBlkCnt * BLOCKSIZE;
909 ppi->fBootable = pEbrPartitionEntry->bootIndicator == 0x80;
910 ppi->partitionType.legacy = pEbrPartitionEntry->type;
911
912 g_lastPartNbr = idxPartition;
913
914 if (ebr.chainingPartitionEntry.type == 0) /* end of chain */
915 break;
916
917 if (!PARTTYPE_IS_EXT(ebr.chainingPartitionEntry.type))
918 return RTMsgErrorExitFailure("Logical partition chain broken");
919
920 chainedEbrOffset = ebr.chainingPartitionEntry.partitionLba * BLOCKSIZE;
921 }
922 }
923 return PARTITION_TABLE_MBR;
924}
925
926const char *getClassicPartitionDesc(uint8_t type)
927{
928 for (uint32_t i = 0; i < sizeof (g_partitionDescTable) / sizeof (struct PartitionDesc); i++)
929 {
930 if (g_partitionDescTable[i].type == type)
931 return g_partitionDescTable[i].desc;
932 }
933 return "????";
934}
935
936void
937displayGptPartitionTable(void)
938{
939
940 RTPrintf( "Virtual disk image:\n\n");
941 RTPrintf(" Base: %s\n", g_pszBaseImagePath);
942 if (g_cImages > 1)
943 RTPrintf(" Diff: %s\n", g_pszImagePath);
944 if (g_pszDiskUuid)
945 RTPrintf(" UUID: %s\n\n", g_pszDiskUuid);
946
947 void *colBoot = NULL;
948
949 SELFSIZINGTABLE tbl(2);
950
951 /* Note: Omitting partition name column because type/UUID seems suffcient */
952 void *colPartNbr = tbl.addCol("#", "%3d", 1);
953
954 /* If none of the partitions supports legacy BIOS boot, don't show that column */
955 for (int idxPartition = 2; idxPartition <= g_lastPartNbr; idxPartition++)
956 if (g_aParsedPartitionInfo[idxPartition].fBootable) {
957 colBoot = tbl.addCol("Boot", "%c ", 1);
958 break;
959 }
960
961 void *colStart = tbl.addCol("Start", "%lld", 1);
962 void *colSectors = tbl.addCol("Sectors", "%lld", -1, 2);
963 void *colSize = tbl.addCol("Size", "%s", 1);
964 void *colOffset = tbl.addCol("Offset", "%lld", 1);
965 void *colType = tbl.addCol("Type", "%s", -1, 2);
966
967#if 0 /* need to see how other OSes w/GPT use 'Name' field, right now 'Type' seems to suffice */
968 void *colName = tbl.addCol("Name", "%s", -1); */
969#endif
970
971 for (int idxPartition = 2; idxPartition <= g_lastPartNbr; idxPartition++)
972 {
973 PARTITIONINFO *ppi = &g_aParsedPartitionInfo[idxPartition];
974 if (ppi->idxPartition)
975 {
976 char abGuid[GUID_STRING_LENGTH * 2];
977 RTStrPrintf(abGuid, sizeof(abGuid), "%RTuuid", &ppi->partitionType.gptGuidTypeSpecifier);
978
979 char *pszPartitionTypeDesc = NULL;
980 for (uint32_t i = 0; i < sizeof(g_gptPartitionTypes) / sizeof(GPTPARTITIONTYPE); i++)
981 if (RTStrNICmp(abGuid, g_gptPartitionTypes[i].gptPartitionUuid, GUID_STRING_LENGTH) == 0)
982 {
983 pszPartitionTypeDesc = (char *)g_gptPartitionTypes[i].gptPartitionTypeDesc;
984 break;
985 }
986
987 if (!pszPartitionTypeDesc)
988 RTPrintf("Couldn't find GPT partitiontype for GUID: %s\n", abGuid);
989
990 void *row = tbl.addRow();
991 tbl.setCell(row, colPartNbr, idxPartition - 1);
992 if (colBoot)
993 tbl.setCell(row, colBoot, ppi->fBootable ? '*' : ' ');
994 tbl.setCell(row, colStart, ppi->offPartition / BLOCKSIZE);
995 tbl.setCell(row, colSectors, ppi->cbPartition / BLOCKSIZE);
996 tbl.setCell(row, colSize, vboximgScaledSize(ppi->cbPartition));
997 tbl.setCell(row, colOffset, ppi->offPartition);
998 tbl.setCell(row, colType, SAFENULL(pszPartitionTypeDesc));
999
1000#if 0 /* see comment for stubbed-out 'Name' column definition above */
1001 tbl.setCell(row, colName, ppi->pszName);
1002#endif
1003
1004 }
1005 }
1006 tbl.displayTable();
1007 RTPrintf ("\n");
1008}
1009
1010void
1011displayLegacyPartitionTable(void)
1012{
1013 /*
1014 * Partition table is most readable and concise when headers and columns
1015 * are adapted to the actual data, to avoid insufficient or excessive whitespace.
1016 */
1017
1018 RTPrintf( "Virtual disk image:\n\n");
1019 RTPrintf(" Base: %s\n", g_pszBaseImagePath);
1020 if (g_cImages > 1)
1021 RTPrintf(" Diff: %s\n", g_pszImagePath);
1022 if (g_pszDiskUuid)
1023 RTPrintf(" UUID: %s\n\n", g_pszDiskUuid);
1024
1025 SELFSIZINGTABLE tbl(2);
1026
1027 void *colPartition = tbl.addCol("Partition", "%s(%d)", -1);
1028 void *colBoot = tbl.addCol("Boot", "%c ", 1);
1029 void *colStart = tbl.addCol("Start", "%lld", 1);
1030 void *colSectors = tbl.addCol("Sectors", "%lld", -1, 2);
1031 void *colSize = tbl.addCol("Size", "%s", 1);
1032 void *colOffset = tbl.addCol("Offset", "%lld", 1);
1033 void *colId = tbl.addCol("Id", "%2x", 1);
1034 void *colType = tbl.addCol("Type", "%s", -1, 2);
1035
1036 for (int idxPartition = 1; idxPartition <= g_lastPartNbr; idxPartition++)
1037 {
1038 PARTITIONINFO *p = &g_aParsedPartitionInfo[idxPartition];
1039 if (p->idxPartition)
1040 {
1041 void *row = tbl.addRow();
1042 tbl.setCell(row, colPartition, g_pszBaseImageName, idxPartition);
1043 tbl.setCell(row, colBoot, p->fBootable ? '*' : ' ');
1044 tbl.setCell(row, colStart, p->offPartition / BLOCKSIZE);
1045 tbl.setCell(row, colSectors, p->cbPartition / BLOCKSIZE);
1046 tbl.setCell(row, colSize, vboximgScaledSize(p->cbPartition));
1047 tbl.setCell(row, colOffset, p->offPartition);
1048 tbl.setCell(row, colId, p->partitionType.legacy);
1049 tbl.setCell(row, colType, getClassicPartitionDesc((p->partitionType).legacy));
1050 }
1051 }
1052 tbl.displayTable();
1053 RTPrintf ("\n");
1054}
1055
1056int
1057main(int argc, char **argv)
1058{
1059
1060 int rc = RTR3InitExe(argc, &argv, 0);
1061 if (RT_FAILURE(rc))
1062 return RTMsgErrorExitFailure("RTR3InitExe failed, rc=%Rrc\n", rc);
1063
1064 rc = VDInit();
1065 if (RT_FAILURE(rc))
1066 return RTMsgErrorExitFailure("VDInit failed, rc=%Rrc\n", rc);
1067
1068 memset(&g_vboximgOps, 0, sizeof(g_vboximgOps));
1069 g_vboximgOps.open = vboximgOp_open;
1070 g_vboximgOps.read = vboximgOp_read;
1071 g_vboximgOps.write = vboximgOp_write;
1072 g_vboximgOps.getattr = vboximgOp_getattr;
1073 g_vboximgOps.release = vboximgOp_release;
1074 g_vboximgOps.readdir = vboximgOp_readdir;
1075 g_vboximgOps.readlink = vboximgOp_readlink;
1076
1077 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
1078 memset(&g_vboximgOpts, 0, sizeof(g_vboximgOpts));
1079
1080 g_vboximgOpts.idxPartition = -1;
1081
1082 rc = fuse_opt_parse(&args, &g_vboximgOpts, vboximgOptDefs, vboximgOptHandler);
1083 if (rc < 0 || argc < 2 || RTStrCmp(argv[1], "-?" ) == 0 || g_vboximgOpts.fBriefUsage)
1084 {
1085 briefUsage();
1086 return 0;
1087 }
1088
1089 if (g_vboximgOpts.fAllowRoot)
1090 fuse_opt_add_arg(&args, "-oallow_root");
1091
1092 /*
1093 * FUSE doesn't seem to like combining options with one hyphen, as traditional UNIX
1094 * command line utilities allow. The following flags, fWideList and fVerboseList,
1095 * and their respective option definitions give the appearance of combined opts,
1096 * so that -vl, -lv, -wl, -lw options are allowed, since those in particular would
1097 * tend to conveniently facilitate some of the most common use cases.
1098 */
1099 if (g_vboximgOpts.fWideList)
1100 {
1101 g_vboximgOpts.fWide = true;
1102 g_vboximgOpts.fList = true;
1103 }
1104 if (g_vboximgOpts.fVerboseList)
1105 {
1106 g_vboximgOpts.fVerbose = true;
1107 g_vboximgOpts.fList = true;
1108 }
1109 if (g_vboximgOpts.fAllowRoot)
1110 fuse_opt_add_arg(&args, "-oallow_root");
1111
1112 /*
1113 * Initialize COM.
1114 */
1115 using namespace com;
1116 HRESULT hrc = com::Initialize();
1117 if (FAILED(hrc))
1118 {
1119# ifdef VBOX_WITH_XPCOM
1120 if (hrc == NS_ERROR_FILE_ACCESS_DENIED)
1121 {
1122 char szHome[RTPATH_MAX] = "";
1123 com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome));
1124 return RTMsgErrorExit(RTEXITCODE_FAILURE,
1125 "Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome);
1126 }
1127# endif
1128 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize COM! (hrc=%Rhrc)", hrc);
1129 }
1130
1131 /*
1132 * Get the remote VirtualBox object and create a local session object.
1133 */
1134 ComPtr<IVirtualBoxClient> pVirtualBoxClient;
1135 ComPtr<IVirtualBox> pVirtualBox;
1136
1137 hrc = pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
1138 if (SUCCEEDED(hrc))
1139 hrc = pVirtualBoxClient->COMGETTER(VirtualBox)(pVirtualBox.asOutParam());
1140
1141 if (FAILED(hrc))
1142 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to get IVirtualBox object! (hrc=%Rhrc)", hrc);
1143
1144 if (g_vboximgOpts.fList && g_vboximgOpts.pszImageUuidOrPath == NULL)
1145 {
1146 vboximgListVMs(pVirtualBox);
1147 return VINF_SUCCESS;
1148 }
1149
1150 Bstr pMediumUuid;
1151 ComPtr<IMedium> pVDiskMedium = NULL;
1152 char *pszFormat;
1153 VDTYPE enmType;
1154
1155 /*
1156 * Open chain of images from what is provided on command line, to base image
1157 */
1158 if (g_vboximgOpts.pszImageUuidOrPath)
1159 {
1160 /* compiler was too fussy about access mode's data type in conditional expr, so... */
1161 if (g_vboximgOpts.fRW)
1162 CHECK_ERROR(pVirtualBox, OpenMedium(Bstr(g_vboximgOpts.pszImageUuidOrPath).raw(), DeviceType_HardDisk,
1163 AccessMode_ReadWrite, false /* forceNewUuid */, pVDiskMedium.asOutParam()));
1164
1165 else
1166 CHECK_ERROR(pVirtualBox, OpenMedium(Bstr(g_vboximgOpts.pszImageUuidOrPath).raw(), DeviceType_HardDisk,
1167 AccessMode_ReadOnly, false /* forceNewUuid */, pVDiskMedium.asOutParam()));
1168
1169 if (FAILED(rc))
1170 return RTMsgErrorExitFailure("\nCould't find specified VirtualBox base or snapshot disk image:\n%s",
1171 g_vboximgOpts.pszImageUuidOrPath);
1172
1173
1174 CHECK_ERROR(pVDiskMedium, COMGETTER(Id)(pMediumUuid.asOutParam()));
1175 g_pszDiskUuid = RTStrDup((char *)CSTR(pMediumUuid));
1176
1177 /*
1178 * Lock & cache the disk image media chain (from leaf to base).
1179 * Only leaf can be rw (and only if media is being mounted in non-default writable (rw) mode)
1180 *
1181 * Note: Failure to acquire lock is intentionally fatal (e.g. program termination)
1182 */
1183
1184 if (VERBOSE)
1185 RTPrintf("\nAttempting to lock medium chain from leaf image to base image\n");
1186
1187 bool fLeaf = true;
1188 g_cImages = 0;
1189
1190 do
1191 {
1192 ++g_cImages;
1193 IMAGELIST *pNewEntry= new IMAGELIST();
1194 pNewEntry->pImage = pVDiskMedium;
1195 CHECK_ERROR(pVDiskMedium, COMGETTER(Name)((pNewEntry->pImageName).asOutParam()));
1196 CHECK_ERROR(pVDiskMedium, COMGETTER(Location)((pNewEntry->pImagePath).asOutParam()));
1197
1198 if (VERBOSE)
1199 RTPrintf(" %s", CSTR(pNewEntry->pImageName));
1200
1201 if (fLeaf && g_vboximgOpts.fRW)
1202 {
1203 if (VERBOSE)
1204 RTPrintf(" ... Locking for write\n");
1205 CHECK_ERROR_RET(pVDiskMedium, LockWrite((pNewEntry->pLockToken).asOutParam()), rc);
1206 pNewEntry->fWriteable = true;
1207 }
1208 else
1209 {
1210 if (VERBOSE)
1211 RTPrintf(" ... Locking for read\n");
1212 CHECK_ERROR_RET(pVDiskMedium, LockRead((pNewEntry->pLockToken).asOutParam()), rc);
1213 }
1214
1215 IMAGELIST *pCurImageEntry = &listHeadLockList;
1216 while (pCurImageEntry->next)
1217 pCurImageEntry = pCurImageEntry->next;
1218 pCurImageEntry->next = pNewEntry;
1219 pNewEntry->prev = pCurImageEntry;
1220 listHeadLockList.prev = pNewEntry;
1221
1222 CHECK_ERROR(pVDiskMedium, COMGETTER(Parent)(pVDiskMedium.asOutParam()));
1223 fLeaf = false;
1224 }
1225 while(pVDiskMedium);
1226 }
1227
1228 ComPtr<IMedium> pVDiskBaseMedium = listHeadLockList.prev->pImage;
1229 Bstr pVDiskBaseImagePath = listHeadLockList.prev->pImagePath;
1230 Bstr pVDiskBaseImageName = listHeadLockList.prev->pImageName;
1231
1232 g_pszBaseImagePath = RTStrDup((char *)CSTR(pVDiskBaseImagePath));
1233 g_pszBaseImageName = RTStrDup((char *)CSTR(pVDiskBaseImageName));
1234
1235 g_pszImagePath = RTStrDup((char *)CSTR(listHeadLockList.next->pImagePath));
1236 g_pszImageName = RTStrDup((char *)CSTR(listHeadLockList.next->pImageName));
1237
1238 /*
1239 * Attempt to VDOpen media (base and any snapshots), handling encryption,
1240 * if that property is set for base media
1241 */
1242 Bstr pBase64EncodedKeyStore;
1243
1244 rc = pVDiskBaseMedium->GetProperty(Bstr("CRYPT/KeyStore").raw(), pBase64EncodedKeyStore.asOutParam());
1245 if (SUCCEEDED(rc) && strlen(CSTR(pBase64EncodedKeyStore)) != 0)
1246 {
1247 RTPrintf("\nvboximgMount: Encrypted disks not supported in this version\n\n");
1248 return -1;
1249 }
1250
1251
1252/* ***************** BEGIN IFDEF'D (STUBBED-OUT) CODE ************** */
1253/* vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */
1254
1255#if 0 /* The following encrypted disk related code is stubbed out until it can be finished.
1256 * What is here is an attempt to port the VBoxSVC specific code in i_openForIO to
1257 * a client's proximity. It is supplemented by code in vboximgCrypto.cpp and
1258 * vboximageCrypt.h that was lifed from SecretKeyStore.cpp along with the setup
1259 * task function.
1260 *
1261 * The ultimate solution may be to use a simpler but less efficient COM interface,
1262 * or to use VD encryption interfaces and key containers entirely. The keystore
1263 * handling/filter approach that is here may be a bumbling hybrid approach
1264 * that is broken (trying to bridge incompatible disk encryption mechanisms) or otherwise
1265 * doesn't make sense. */
1266
1267 Bstr pKeyId;
1268 ComPtr<IExtPackManager> pExtPackManager;
1269 ComPtr<IExtPack> pExtPack;
1270 com::SafeIfaceArray<IExtPackPlugIn> pExtPackPlugIns;
1271
1272 if (SUCCEEDED(rc))
1273 {
1274 RTPrintf("Got GetProperty(\"CRYPT/KeyStore\") = %s\n", CSTR(pBase64EncodedKeyStore));
1275 if (strlen(CSTR(pBase64EncodedKeyStore)) == 0)
1276 return RTMsgErrorExitFailure("Image '%s' is configured for encryption but "
1277 "there is no key store to retrieve the password from", CSTR(pVDiskBaseImageName));
1278
1279 SecretKeyStore keyStore(false);
1280 RTBase64Decode(CSTR(pBase64EncodedKeyStore), &keyStore, sizeof (SecretKeyStore), NULL, NULL);
1281
1282 rc = pVDiskBaseMedium->GetProperty(Bstr("CRYPT/KeyId").raw(), pKeyId.asOutParam());
1283 if (SUCCEEDED(rc) && strlen(CSTR(pKeyId)) == 0)
1284 return RTMsgErrorExitFailure("Image '%s' is configured for encryption but "
1285 "doesn't have a key identifier set", CSTR(pVDiskBaseImageName));
1286
1287 RTPrintf(" key id: %s\n", CSTR(pKeyId));
1288
1289#ifndef VBOX_WITH_EXTPACK
1290 return RTMsgErrorExitFailure(
1291 "Encryption is not supported because extension pack support is not built in");
1292#endif
1293
1294 CHECK_ERROR(pVirtualBox, COMGETTER(ExtensionPackManager)(pExtPackManager.asOutParam()));
1295 BOOL fExtPackUsable;
1296 CHECK_ERROR(pExtPackManager, IsExtPackUsable((PRUnichar *)VBOX_EXTPACK, &fExtPackUsable));
1297 if (fExtPackUsable)
1298 {
1299 /* Load the PlugIn */
1300
1301 CHECK_ERROR(pExtPackManager, Find((PRUnichar *)VBOX_EXTPACK, pExtPack.asOutParam()));
1302 if (RT_FAILURE(rc))
1303 return RTMsgErrorExitFailure(
1304 "Encryption is not supported because the extension pack '%s' is missing",
1305 VBOX_EXTPACK);
1306
1307 CHECK_ERROR(pExtPack, COMGETTER(PlugIns)(ComSafeArrayAsOutParam(pExtPackPlugIns)));
1308
1309 Bstr pPlugInPath;
1310 size_t iPlugIn;
1311 for (iPlugIn = 0; iPlugIn < pExtPackPlugIns.size(); iPlugIn++)
1312 {
1313 Bstr pPlugInName;
1314 CHECK_ERROR(pExtPackPlugIns[iPlugIn], COMGETTER(Name)(pPlugInName.asOutParam()));
1315 if (RTStrCmp(CSTR(pPlugInName), "VDPlugInCrypt") == 0)
1316 {
1317 CHECK_ERROR(pExtPackPlugIns[iPlugIn], COMGETTER(ModulePath)(pPlugInPath.asOutParam()));
1318 break;
1319 }
1320 }
1321 if (iPlugIn == pExtPackPlugIns.size())
1322 return RTMsgErrorExitFailure("Encryption is not supported because the extension pack '%s' "
1323 "is missing the encryption PlugIn (old extension pack installed?)", VBOX_EXTPACK);
1324
1325 rc = VDPluginLoadFromFilename(CSTR(pPlugInPath));
1326 if (RT_FAILURE(rc))
1327 return RTMsgErrorExitFailure("Retrieving encryption settings of the image failed "
1328 "because the encryption PlugIn could not be loaded\n");
1329 }
1330
1331 SecretKey *pKey = NULL;
1332 rc = keyStore.retainSecretKey(Utf8Str(pKeyId), &pKey);
1333 if (RT_FAILURE(rc))
1334 return RTMsgErrorExitFailure(
1335 "Failed to retrieve the secret key with ID \"%s\" from the store (%Rrc)",
1336 CSTR(pKeyId), rc);
1337
1338 VDISKCRYPTOSETTINGS vdiskCryptoSettings, *pVDiskCryptoSettings = &vdiskCryptoSettings;
1339
1340 vboxImageCryptoSetup(pVDiskCryptoSettings, NULL,
1341 (const char *)CSTR(pBase64EncodedKeyStore), (const char *)pKey->getKeyBuffer(), false);
1342
1343 rc = VDFilterAdd(g_pVDisk, "CRYPT", VD_FILTER_FLAGS_DEFAULT, pVDiskCryptoSettings->vdFilterIfaces);
1344 keyStore.releaseSecretKey(Utf8Str(pKeyId));
1345
1346 if (rc == VERR_VD_PASSWORD_INCORRECT)
1347 return RTMsgErrorExitFailure("The password to decrypt the image is incorrect");
1348
1349 if (RT_FAILURE(rc))
1350 return RTMsgErrorExitFailure("Failed to load the decryption filter: %Rrc", rc);
1351 }
1352#endif
1353
1354/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
1355/* **************** END IFDEF'D (STUBBED-OUT) CODE ***************** */
1356
1357 rc = RTCritSectInit(&g_vdioLock);
1358 if (RT_SUCCESS(rc))
1359 {
1360 g_VDIfThreadSync.pfnStartRead = vboximgThreadStartRead;
1361 g_VDIfThreadSync.pfnFinishRead = vboximgThreadFinishRead;
1362 g_VDIfThreadSync.pfnStartWrite = vboximgThreadStartWrite;
1363 g_VDIfThreadSync.pfnFinishWrite = vboximgThreadFinishWrite;
1364 rc = VDInterfaceAdd(&g_VDIfThreadSync.Core, "vboximg_ThreadSync", VDINTERFACETYPE_THREADSYNC,
1365 &g_vdioLock, sizeof(VDINTERFACETHREADSYNC), &g_pVdIfs);
1366 }
1367 else
1368 return RTMsgErrorExitFailure("ERROR: Failed to create critsects "
1369 "for virtual disk I/O, rc=%Rrc\n", rc);
1370
1371 /*
1372 * Create HDD container to open base image and differencing images into
1373 */
1374 rc = VDGetFormat(NULL /* pVDIIfsDisk */, NULL /* pVDIIfsImage*/,
1375 CSTR(pVDiskBaseImagePath), &pszFormat, &enmType);
1376
1377 if (RT_FAILURE(rc))
1378 return RTMsgErrorExitFailure("VDGetFormat(,,%s,,) "
1379 "failed (during HDD container creation), rc=%Rrc\n", g_pszImagePath, rc);
1380
1381 if (VERBOSE)
1382 RTPrintf("\nCreating container for base image of format %s\n", pszFormat);
1383
1384 g_pVDisk = NULL;
1385 rc = VDCreate(g_pVdIfs, enmType, &g_pVDisk);
1386 if ((rc))
1387 return RTMsgErrorExitFailure("ERROR: Couldn't create virtual disk container\n");
1388
1389 /* Open all virtual disk media from leaf snapshot (if any) to base image*/
1390
1391 if (VERBOSE)
1392 RTPrintf("\nOpening medium chain\n");
1393
1394 IMAGELIST *pCurMedium = listHeadLockList.prev; /* point to base image */
1395 while (pCurMedium != &listHeadLockList)
1396 {
1397 if (VERBOSE)
1398 RTPrintf(" Open: %s\n", CSTR(pCurMedium->pImagePath));
1399
1400 rc = VDOpen(g_pVDisk,
1401 CSTR(pszFormat),
1402 CSTR(pCurMedium->pImagePath),
1403 pCurMedium->fWriteable,
1404 g_pVdIfs);
1405
1406 if (RT_FAILURE(rc))
1407 return RTMsgErrorExitFailure("Could not open the medium storage unit '%s' %Rrc",
1408 CSTR(pCurMedium->pImagePath), rc);
1409
1410 pCurMedium = pCurMedium->prev;
1411 }
1412
1413 g_cReaders = VDIsReadOnly(g_pVDisk) ? INT32_MAX / 2 : 0;
1414 g_cWriters = 0;
1415 g_cbEntireVDisk = VDGetSize(g_pVDisk, 0 /* base */);
1416
1417 if (g_vboximgOpts.fList)
1418 {
1419 if (g_pVDisk == NULL)
1420 return RTMsgErrorExitFailure("No valid --image to list partitions from\n");
1421
1422 RTPrintf("\n");
1423 rc = parsePartitionTable();
1424 switch(rc)
1425 {
1426 case PARTITION_TABLE_MBR:
1427 displayLegacyPartitionTable();
1428 break;
1429 case PARTITION_TABLE_GPT:
1430 displayGptPartitionTable();
1431 break;
1432 default:
1433 return rc;
1434 }
1435 return 0;
1436 }
1437
1438 if (g_vboximgOpts.idxPartition >= 0)
1439 {
1440 if (g_vboximgOpts.offset)
1441 return RTMsgErrorExitFailure("--offset and --partition are mutually exclusive options\n");
1442
1443 if (g_vboximgOpts.size)
1444 return RTMsgErrorExitFailure("--size and --partition are mutually exclusive options\n");
1445
1446 /*
1447 * --partition option specified. That will set the global offset and limit
1448 * honored by the disk read and write sanitizers to constrain operations
1449 * to within the specified partion based on an initial parsing of the MBR
1450 */
1451 rc = parsePartitionTable();
1452 if (rc < 0)
1453 return RTMsgErrorExitFailure("Error parsing disk MBR/Partition table\n");
1454 int partNbr = g_vboximgOpts.idxPartition;
1455
1456 if (partNbr < 0 || partNbr > g_lastPartNbr)
1457 return RTMsgErrorExitFailure("Non-valid partition number specified\n");
1458 if (partNbr == 0)
1459 {
1460 g_vDiskOffset = 0;
1461 g_vDiskSize = VDGetSize(g_pVDisk, 0);
1462 if (VERBOSE)
1463 RTPrintf("\nPartition 0 specified - Whole disk will be accessible\n");
1464 } else {
1465 int fFoundPartition = false;
1466 for (int i = 1; i < g_lastPartNbr + 1; i++)
1467 {
1468 /* If GPT, display vboximg's representation of partition table starts at partition 2
1469 * but the table is displayed calling it partition 1, because the protective MBR
1470 * record is relatively pointless to display or reference in this context */
1471 if (g_aParsedPartitionInfo[i].idxPartition == partNbr + (g_fGPT ? 1 : 0))
1472 {
1473 fFoundPartition = true;
1474 g_vDiskOffset = g_aParsedPartitionInfo[i].offPartition;
1475 g_vDiskSize = g_vDiskOffset + g_aParsedPartitionInfo[i].cbPartition;
1476 if (VERBOSE)
1477 RTPrintf("\nPartition %d specified. Only sectors %llu to %llu of disk will be accessible\n",
1478 g_vboximgOpts.idxPartition, g_vDiskOffset / BLOCKSIZE, g_vDiskSize / BLOCKSIZE);
1479 }
1480 }
1481 if (!fFoundPartition)
1482 return RTMsgErrorExitFailure("Couldn't find partition %d in partition table\n", partNbr);
1483 }
1484 } else {
1485 if (g_vboximgOpts.offset) {
1486 if (g_vboximgOpts.offset < 0 || g_vboximgOpts.offset + g_vboximgOpts.size > g_cbEntireVDisk)
1487 return RTMsgErrorExitFailure("User specified offset out of range of virtual disk\n");
1488
1489 if (VERBOSE)
1490 RTPrintf("Setting r/w bias (offset) to user requested value for sector %llu\n", g_vDiskOffset / BLOCKSIZE);
1491
1492 g_vDiskOffset = g_vboximgOpts.offset;
1493 }
1494 if (g_vboximgOpts.size) {
1495 if (g_vboximgOpts.size < 0 || g_vboximgOpts.offset + g_vboximgOpts.size > g_cbEntireVDisk)
1496 return RTMsgErrorExitFailure("User specified size out of range of virtual disk\n");
1497
1498 if (VERBOSE)
1499 RTPrintf("Setting r/w size limit to user requested value %llu\n", g_vDiskSize / BLOCKSIZE);
1500
1501 g_vDiskSize = g_vboximgOpts.size;
1502 }
1503 }
1504 if (g_vDiskSize == 0)
1505 g_vDiskSize = g_cbEntireVDisk - g_vDiskOffset;
1506
1507 /*
1508 * Hand control over to libfuse.
1509 */
1510 if (VERBOSE)
1511 RTPrintf("\nvboximg-mount: Going into background...\n");
1512
1513 rc = fuse_main(args.argc, args.argv, &g_vboximgOps, NULL);
1514
1515 int rc2 = VDClose(g_pVDisk, false /* fDelete */);
1516 AssertRC(rc2);
1517 RTPrintf("vboximg-mount: fuse_main -> %d\n", rc);
1518 return rc;
1519}
1520
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette