1
/* Original author: h2o on forums.virtualbox.org *
2
* http://forums.virtualbox.org/viewtopic.php?p=59275 *
3
* Reworked with Terry Ellison *
4
* vdfuse.c - tool for mounting VDI/VMDK/VHD files *
6
* This program is free software: you can redistribute it and/or modify *
7
* it under the terms of the GNU General Public License as published by *
8
* the Free Software Foundation, either version 2 of the License, or *
9
* (at your option) any later version. *
11
* This program is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU General Public License for more details. *
16
* You should have received a copy of the GNU General Public License *
17
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
19
/* VERSION 01 TE 14 Feb 09 Initial release
20
* 02 TE 15 Feb 09 Use the stat on the container file as the basis for any getattr on
21
* the partiton pseudo-files
22
* 03 TE 16 Feb 09 Bug corrections from Gavin, plus EntireDisc/Partition interlock.
23
* 04 TE 23 Mar 09 Another Bug correction from Gavin, plus addition of printPartition.
24
* 05 09 May 09 Change sizes from size_t to uint64_t (size_t is 32-bit on 32-bit systems)
25
* Fix _FILE_OFFSET_BITS
26
* Option for older VBox
31
* This Fuse module uses the VBox access library to open a VBox supported VD image file and mount
32
* it as a Fuse file system. The mount point contains a flat directory with the following files
36
* Note that each file should only be opened once and opening EntireDisk should locks out
37
* the other files. However, since file close isn't passed to the fuse utilities, I can only
38
* enforce this in a brute fashion: If you open EntireDisk then all further I/O to
39
* the PartitionN files will fail. If open any PartitionN file then all further I/O to EntireDisk
40
* will fail. Hence in practice you can only access on or the other within a single mount.
42
* This code is structured in the following sections:
43
* * The main(argc, argv) routine including validation of arguments and call to fuse_main
44
* * MBR and EBR parsing routines
45
* * The Fuse callback routines for destroy ,flush ,getattr ,open, read, readdir, write
47
* For further details on how this all works see http://fuse.sourceforge.net/
49
* VirtualBox provided an API to enable you to manipulate VD image files programmatically.
50
* This is documented in the embedded source comments. See for further details
51
* http://www.virtualbox.org/svn/vbox/trunk/include/VBox/VBoxHDD-new.h
53
* To compile this you need to pull (wget) the VBox OSE source from http://www.virtualbox.org/downloads.
54
* Set the environment variable VBOX_INCLUDE to the include directory within this tree
56
* gcc vdfuse.c -o vdfuse `pkg-config --cflags --libs fuse` \
57
* -l:/usr/lib/virtualbox/VBoxDD.so -Wl,-rpath,/usr/lib/virtualbox \
58
* -pthread -I$VBOX_INCLUDE
61
#define FUSE_USE_VERSION 26
62
#define _FILE_OFFSET_BITS 64
75
#define UNUSED __attribute__ ((unused))
82
#define UNALLOCATED -1
83
#define GETOPT_ARGS "rgvawt:f:dh?"
84
#define HOSTPARTITION_MAX 100
88
#define PARTTYPE_IS_EXTENDED(x) ((x) == 0x05 || (x) == 0x0f || (x) == 0x85)
91
void usageAndExit (char *optFormat, ...);
92
void vbprintf (const char *format, ...);
93
void vdErrorCallback(void *pvUser, int rc, const char *file, unsigned iLine, const char *function, const char *format, va_list va);
94
void initialisePartitionTable(void);
95
int findPartition (const char* filename);
96
int detectDiskType (char **disktype, char *filename);
97
static int VD_open (const char *c, struct fuse_file_info *i);
98
static int VD_release (const char *name, struct fuse_file_info *fi);
99
static int VD_read (const char *c, char *out, size_t len, off_t offset, struct fuse_file_info *i UNUSED);
100
static int VD_write (const char *c, const char *in, size_t len, off_t offset, struct fuse_file_info *i UNUSED);
101
static int VD_flush (const char *p, struct fuse_file_info *i UNUSED);
102
static int VD_readdir (const char *p, void *buf, fuse_fill_dir_t filler, off_t offset UNUSED, struct fuse_file_info *i UNUSED);
103
static int VD_getattr (const char *p, struct stat *stbuf);
104
void VD_destroy (void *u);
106
// Note that this abstraction layer was initially here to allow a version of this to be compiled with
107
// Version 1.x using the VBox/VBoxHDD.h interface, but since this has been withdrawn for 2.x and I can
108
// no longer test this, I've decided to remove this old API code, but I have left the abstraction in
109
// I also used the ...testcase/tstVD.cpp as a coding template to work out how to call the VD routines.
111
// if you don't have VBoxHDD.h, run 'svn co http://www.virtualbox.org/svn/vbox/trunk/include'
112
// if your VirtualBox version is older than 2.1, add -DOLDVBOXHDD to your gcc flags
113
#ifdef OLDVBOXHDD // < v2.1
114
#include <VBox/VBoxHDD-new.h>
116
#include <VBox/VBoxHDD.h>
118
#define DISKread(o,b,s) VDRead (hdDisk,o,b,s);
119
#define DISKwrite(o,b,s) VDWrite (hdDisk,o,b,s);
120
#define DISKclose VDClose(hdDisk, 0)
121
#define DISKsize VDGetSize(hdDisk, 0)
122
#define DISKflush VDFlush(hdDisk)
123
#define DISKopen(t,i) if (RT_FAILURE(VDInterfaceAdd(&vdError, "VD Error", VDINTERFACETYPE_ERROR, \
124
&vdErrorCallbacks, NULL, &pVDifs))) \
125
usageAndExit("invalid initialisation of VD interface"); \
126
if (RT_FAILURE(VDCreate(&vdError, &hdDisk))) usageAndExit("invalid initialisation of VD interface"); \
127
if (RT_FAILURE(VDOpen(hdDisk,t , i, readonly ? VD_OPEN_FLAGS_READONLY : VD_OPEN_FLAGS_NORMAL, NULL))) \
128
usageAndExit("opening vbox image failed"); \
131
PVDINTERFACE pVDifs = NULL;
133
VDINTERFACEERROR vdErrorCallbacks = {
134
.cbSize = sizeof(VDINTERFACEERROR),
135
.enmInterface = VDINTERFACETYPE_ERROR,
136
.pfnError = vdErrorCallback };
138
// Partition table information
140
typedef struct { // See http://en.wikipedia.org/wiki/Master_boot_record
141
uint8_t status; // status[7] (0x80 = bootable, 0x00 = non-bootable,other = invalid[8])
142
// ** CHS address of first block **
143
uint8_t shead; // first head
144
uint8_t ssector; // first sector is in bits 5-0; bits 9-8 of cylinder are in bits 7-6
145
uint8_t sbits; // first bits 7-0 of cylinder
146
uint8_t type; // partition type
147
// ** CHS address of last block **
148
uint8_t ehead; // last head
149
uint8_t esector; // last sector is in bits 5-0; bits 9-8 of cylinder are in bits 7-6
150
uint8_t ebits; // last bits 7-0 of cylinder
151
uint32_t offset; // LBA of first sector in the partition
152
uint32_t size; // number of blocks in partition, in little-endian format
156
char name[PNAMESIZE+1];// name of partition
157
off_t offset; // offset into disk in bytes
158
uint64_t size; // size of partiton in bytes
159
int no; // partition number
160
MBRentry descriptor; // copy of MBR / EBR descriptor that defines the partion
163
typedef struct { // See http://en.wikipedia.org/wiki/Extended_boot_record for details
166
MBRentry fill1, fill2;
170
Partition partitionTable[HOSTPARTITION_MAX+1]; // Note the partitionTable[0] is reserved for the EntireDisk descriptor
171
static int lastPartition = 0;
173
static struct fuse_operations fuseOperations = {
174
.readdir = VD_readdir,
175
.getattr = VD_getattr,
177
.release = VD_release,
181
.destroy = VD_destroy
184
static struct fuse_args fuseArgs = FUSE_ARGS_INIT (0, NULL);
186
static struct stat VDfile_stat;
188
static int verbose = 0;
189
static int readonly = 0;
190
static int allowall = 0; // allow all users to read from disk
191
static int allowallw = 0; // allow all users to write to disk
192
static uid_t myuid = 0;
193
static gid_t mygid = 0;
194
static char *processName;
195
static int entireDiskOpened = 0;
196
static int partitionOpened = 0;
197
static int opened = 0; // how many opened instances are there
200
//====================================================================================================
201
// Main routine including validation
202
//====================================================================================================
204
int main (int argc, char **argv) {
205
char *diskType = "auto";
206
char *imagefilename = NULL;
207
char *mountpoint = NULL;
213
extern int optind, optopt;
216
// *** Parse the command line options ***
218
processName = argv[0];
220
while ((c = getopt(argc, argv, GETOPT_ARGS)) != -1) {
222
case 'r' : readonly = 1; break;
223
case 'g' : foreground = 1; break;
224
case 'v' : verbose = 1; break;
225
case 'a' : allowall = 1; break;
226
case 'w' : allowall = 1; allowallw = 1; break;
227
case 't' : diskType = (char *) optarg; break; // ignored if OLDAPI
228
case 'f' : imagefilename = (char *)optarg; break;
229
case 'd' : foreground = 1; debug = 1; break;
230
case 'h' : usageAndExit(NULL);
231
case '?' : usageAndExit("Unknown option");
235
// *** Validate the command line ***
237
if (argc != optind+1) usageAndExit("a single mountpoint must be specified");
238
mountpoint = argv[optind];
239
if (!mountpoint) usageAndExit("no mountpoint specified");
240
if (!imagefilename) usageAndExit("no image chosen");
241
if (stat(imagefilename, &VDfile_stat)<0) usageAndExit("cannot access imagefile");
242
if (access (imagefilename, F_OK | R_OK | ((!readonly) ? W_OK : 0))
243
> 0) usageAndExit("cannot access imagefile");
245
#define IS_TYPE(s) (strcmp (s, diskType) == 0)
246
if ( !(IS_TYPE("auto") || IS_TYPE("VDI" ) || IS_TYPE("VMDK") || IS_TYPE("VHD" ) ||
247
IS_TYPE("auto")) ) usageAndExit("invalid disk type specified");
248
if (strcmp ("auto", diskType) == 0 && detectDiskType (&diskType, imagefilename) < 0) return 1;
251
// *** Open the VDI, parse the MBR + EBRs and connect to the fuse service ***
253
DISKopen(diskType, imagefilename);
255
initialisePartitionTable();
260
fuse_opt_add_arg (&fuseArgs, "vdfuse");
263
char fsname[strlen(imagefilename) + 12];
264
strcpy (fsname, "-ofsname=\0");
265
strcat (fsname, imagefilename);
266
fuse_opt_add_arg (&fuseArgs, fsname);
269
fuse_opt_add_arg (&fuseArgs, "-osubtype=vdfuse");
270
fuse_opt_add_arg (&fuseArgs, "-o");
271
fuse_opt_add_arg (&fuseArgs, (allowall) ? "allow_other" : "allow_root");
272
if (foreground) fuse_opt_add_arg (&fuseArgs, "-f");
273
if (debug) fuse_opt_add_arg (&fuseArgs, "-d");
274
fuse_opt_add_arg (&fuseArgs, mountpoint);
276
return fuse_main (fuseArgs.argc, fuseArgs.argv, &fuseOperations
277
#if FUSE_USE_VERSION >= 26
282
//====================================================================================================
283
// Miscellaneous output utilities
284
//====================================================================================================
286
void usageAndExit(char *optFormat, ... ) {
288
if (optFormat !=NULL) {
289
fputs ("\nERROR: ", stderr);
290
va_start (ap, optFormat); vfprintf (stderr, optFormat, ap); va_end (ap);
291
fputs ("\n\n", stderr);
293
// ---------!---------!---------!---------!---------!---------!---------!---------!
295
"DESCRIPTION: This Fuse module uses the VirtualBox access library to open a \n"
296
"VirtualBox supported VD image file and mount it as a Fuse file system. The\n"
297
"mount point contains a flat directory containing the files EntireDisk,\n"
298
"Partition1 .. PartitionN. These can then be loop mounted to access the\n"
299
"underlying file systems\n\n"
300
"USAGE: %s [options] -f image-file mountpoint\n"
304
"\t-t\tspecify type (VDI, VMDK, VHD, or raw; default: auto)\n"
306
"\t-f\tVDimage file\n"
307
"\t-a\tallow all users to read disk\n"
308
"\t-w\tallow all users to read and write to disk\n"
309
"\t-g\trun in foreground\n"
312
"NOTE: you must add the line \"user_allow_other\" (without quotes)\n"
313
"to /etc/fuse.confand set proper permissions on /etc/fuse.conf\n"
314
"for this to work.\n", processName);
318
void vbprintf (const char *format, ...) {
320
if (!verbose) return;
321
va_start (ap, format); vprintf (format, ap); va_end (ap);
322
fputs ("\n", stdout);
326
void vdErrorCallback(void *pvUser UNUSED, int rc, const char *file, unsigned iLine, const char *function, const char *format, va_list va) {
327
fprintf(stderr, "\nVD CallbackError rc %d at %s:%u (%s): ", rc, file, iLine, function);
328
vfprintf(stderr, format, va);
333
//====================================================================================================
334
// MBR + EBR parsing routine
335
//====================================================================================================
337
// This code is algorithmically based on partRead in VBoxInternalManage.cpp plus the Wikipedia articles
338
// on MBR and EBR. As in partRead, a statically allocated partition list is used same to keep things
339
// simple (but up to a max 100 partitions :lol:). Note than unlike partRead, this doesn't resort the
342
void initialisePartitionTable(void) {
343
uint16_t MBRsignature;
344
int entendedFlag = UNALLOCATED;
347
memset(partitionTable, 0, sizeof(partitionTable));
348
for (i=0; i <= (signed) (sizeof(partitionTable)/sizeof(Partition)) ; i++) partitionTable[i].no = UNALLOCATED;
350
partitionTable[0].no = 0;
351
partitionTable[0].offset = 0;
352
partitionTable[0].size = DISKsize;
353
strcpy(partitionTable[0].name, "EntireDisk");
355
// Check that this is unformated or a DOS partitioned disk. Sorry but other formats not supported.
357
DISKread(MBR_START + 4 * sizeof(MBRentry), &MBRsignature, sizeof (MBRsignature));
358
if (MBRsignature == 0x0000) return; // an unformated disk is allowed but only EntireDisk is defined
359
if (MBRsignature != 0xaa55) usageAndExit("Invalid MBR found on image with signature 0x%04hX", MBRsignature);
362
// Process the four physical partition entires in the MBR
364
for (i = 1; i <= 4; i++) {
365
Partition *p = partitionTable + i;
366
// MBRentry *m = &(p->descriptor);
367
DISKread (MBR_START + (i-1) * sizeof(MBRentry), &(p->descriptor), sizeof(MBRentry));
368
if ((p->descriptor).type == 0) continue;
369
if (PARTTYPE_IS_EXTENDED((p->descriptor).type)) {
370
if (entendedFlag != UNALLOCATED) usageAndExit("More than one extended partition in MBR");
375
p->offset = (off_t)((p->descriptor).offset) * BLOCKSIZE;
376
p->size = (off_t)((p->descriptor).size) * BLOCKSIZE;
380
// Now chain down any EBRs to process the logical partition entries
382
if (entendedFlag != UNALLOCATED) {
384
off_t uStart = (off_t)((partitionTable[entendedFlag].descriptor).offset) * BLOCKSIZE;
387
if (!uStart) usageAndExit("Inconsistency for logical partition start. Aborting\n");
389
for (i = 5; i <= HOSTPARTITION_MAX; i++) {
391
Partition *p = partitionTable + i;
393
DISKread (uStart + uOffset + EBR_START, &ebr, sizeof(ebr));
395
if (ebr.signature != 0xaa55) usageAndExit("Invalid EBR signature found on image");
396
if ((ebr.descriptor).type == 0) usageAndExit("Logical partition with type 0 encountered");
397
if (!((ebr.descriptor).offset)) usageAndExit("Logical partition invalid partition start offset encountered");
399
p->descriptor = ebr.descriptor;
402
p->offset = uStart + uOffset + (off_t)((ebr.descriptor).offset) * BLOCKSIZE;
403
p->size = (off_t)((ebr.descriptor).size) * BLOCKSIZE;
405
if (ebr.chain.type == 0) break;
406
if (!PARTTYPE_IS_EXTENDED(ebr.chain.type)) usageAndExit("Logical partition chain broken");
407
uOffset = (ebr.chain).offset;
411
// Now print out the partition table
413
vbprintf( "Partition Size Offset\n"
414
"========= ==== ======\n");
415
for (i = 1; i <= lastPartition; i++) {
416
Partition *p = partitionTable + i;
417
if (p->no != UNALLOCATED) {
418
sprintf(p->name, "Partition%d", i);
419
vbprintf ( "%-14s %-13lld %-13lld", p->name, p->offset, p->size );
425
int findPartition (const char* filename) {
426
// Use a dumb serial search since there are typically less than 3 entries
428
register Partition *p = partitionTable;
429
for (i = 0; i <= lastPartition; i++, p++) {
430
if (p->no != UNALLOCATED && strcmp(filename+1, p->name) == 0) return i;
435
// detects type of virtual image
436
int detectDiskType (char **disktype, char *filename) {
438
int fd = open (filename, O_RDONLY);
439
read (fd, buf, sizeof (buf));
441
if (strncmp (buf, "connectix", 8) == 0) *disktype = "VHD";
442
else if (strncmp (buf, "VMDK", 4) == 0) *disktype = "VMDK";
443
else if (strncmp (buf, "KDMV", 4) == 0) *disktype = "VMDK";
444
else if (strncmp (buf, "<<<", 3) == 0) *disktype = "VDI";
445
else usageAndExit("cannot autodetect disk type");
447
vbprintf ("disktype is %s", *disktype);
452
//====================================================================================================
453
// Fuse Callback Routines
454
//====================================================================================================
456
// in alphetic order to help find them: destroy ,flush ,getattr ,open, read, readdir, write
458
pthread_mutex_t disk_mutex = PTHREAD_MUTEX_INITIALIZER;
459
pthread_mutex_t part_mutex = PTHREAD_MUTEX_INITIALIZER;
461
void VD_destroy (void *u UNUSED) {
462
// called when the fuse filesystem is umounted
463
vbprintf ("destroy");
467
int VD_flush(const char *p, struct fuse_file_info *i UNUSED) {
468
vbprintf ("flush: %s", p);
473
static int VD_getattr (const char *p, struct stat *stbuf) {
474
vbprintf ("getattr: %s", p);
475
int isFileRoot = (strcmp ("/", p) == 0);
476
int n = findPartition(p);
478
if (!isFileRoot && n == -1) return -ENOENT;
480
// Use the container file's stat return as the basis. However since partitions cannot
481
// be created by creating files, there is no write access to the directory. I also
482
// treat group access the same as other.
484
memcpy (stbuf, &VDfile_stat, sizeof (struct stat));
487
stbuf->st_mode = S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP;
488
if (allowall) stbuf->st_mode |= S_IROTH;
490
stbuf->st_blocks = 2;
492
stbuf->st_mode = S_IFREG | S_IRUSR | S_IWUSR;
493
if (allowall) stbuf->st_mode |= S_IRGRP | S_IROTH;
494
if (allowallw) stbuf->st_mode |= S_IWGRP | S_IWOTH;
495
stbuf->st_size = partitionTable[n].size;
496
stbuf->st_blocks = (stbuf->st_size + BLOCKSIZE - 1) / BLOCKSIZE;
499
stbuf->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
507
static int VD_open(const char *cName, struct fuse_file_info *i) {
508
vbprintf ("open: %s, %lld, 0X%08lX ", cName, i->fh, i->flags );
509
int n = findPartition(cName);
511
(entireDiskOpened && n > 0) ||
512
(partitionOpened && n == 0) ) return -ENOENT;
514
((i->flags & (O_WRONLY | O_RDWR)) != 0)) return -EROFS;
516
if (n == 0) entireDiskOpened = 1;
517
else partitionOpened = 1;
519
pthread_mutex_lock (&part_mutex);
521
pthread_mutex_unlock (&part_mutex);
526
static int VD_release (const char *name, struct fuse_file_info *fi) {
528
vbprintf ("release: %s", name);
530
pthread_mutex_lock (&part_mutex);
533
initialisePartitionTable ();
534
entireDiskOpened = 0;
537
pthread_mutex_unlock (&part_mutex);
542
static int VD_read (const char *c, char *out, size_t len, off_t offset, struct fuse_file_info *i UNUSED) {
543
vbprintf ("read: %s, offset=%lld, length=%d", c, offset, len);
544
int n = findPartition(c);
545
if (n<0) return -ENOENT;
546
if ((n == 0) ? partitionOpened : entireDiskOpened) return -EIO;
548
Partition *p = &(partitionTable[n]);
549
// if (offset >= p->size) return 0;
550
// if (offset + len> p->size) len = p->size - offset;
551
if ((uint64_t)offset >= p->size) return 0;
552
if ((uint64_t)(offset + len) > p->size) len = p->size - offset;
554
pthread_mutex_lock (&disk_mutex);
555
int ret = DISKread(offset + p->offset, out, len);
556
pthread_mutex_unlock (&disk_mutex);
558
return VBOX_SUCCESS(ret) ? (signed) len : -EIO;
561
static int VD_readdir (const char *p, void *buf, fuse_fill_dir_t filler, off_t offset UNUSED, struct fuse_file_info *i UNUSED){
563
vbprintf ("readdir");
564
if (strcmp ("/", p) != 0) return -ENOENT;
565
filler (buf, ".", NULL, 0);
566
filler (buf, "..", NULL, 0);
567
for (n = 0; n <= lastPartition; n++) {
568
Partition *p = partitionTable + n;
569
if (p->no != UNALLOCATED) filler(buf, p->name, NULL, 0);
574
static int VD_write (const char *c, const char *in, size_t len, off_t offset, struct fuse_file_info *i UNUSED) {
575
vbprintf ("write: %s, offset=%lld, length=%d", c, offset, len);
576
int n = findPartition(c);
577
if (n<0) return -ENOENT;
578
if ((n == 0) ? partitionOpened : entireDiskOpened) return -EIO;
579
Partition *p = &(partitionTable[n]);
580
if ((uint64_t)offset >= p->size) return 0;
581
if ((uint64_t)(offset + len) > p->size) len = p->size - offset;
583
pthread_mutex_lock (&disk_mutex);
584
int ret = DISKwrite(offset + p->offset, in, len);
585
pthread_mutex_unlock (&disk_mutex);
587
return VBOX_SUCCESS(ret) ? (signed) len : -EIO;