VirtualBox

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

Last change on this file since 76585 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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