Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:medozas74
vdfuse
vdfuse-v82a.c
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File vdfuse-v82a.c of Package vdfuse
/* Original author: h2o on forums.virtualbox.org * * http://forums.virtualbox.org/viewtopic.php?p=59275 * * Reworked with Terry Ellison * * vdfuse.c - tool for mounting VDI/VMDK/VHD files * * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* VERSION 01 TE 14 Feb 09 Initial release * 02 TE 15 Feb 09 Use the stat on the container file as the basis for any getattr on * the partiton pseudo-files * 03 TE 16 Feb 09 Bug corrections from Gavin, plus EntireDisc/Partition interlock. * 04 TE 23 Mar 09 Another Bug correction from Gavin, plus addition of printPartition. * 05 09 May 09 Change sizes from size_t to uint64_t (size_t is 32-bit on 32-bit systems) * Fix _FILE_OFFSET_BITS * Option for older VBox * 07 22 Feb 10 VBOX_SUCCESS macro was removed, replace with RT_SUCCESS * 08 27 Jun 10 Support for snapshots through frontend (-s option) * * * DESCRIPTION * * This Fuse module uses the VBox access library to open a VBox supported VD image file and mount * it as a Fuse file system. The mount point contains a flat directory with the following files * * EntireDisk * * PartitionN * * Note that each file should only be opened once and opening EntireDisk should locks out * the other files. However, since file close isn't passed to the fuse utilities, I can only * enforce this in a brute fashion: If you open EntireDisk then all further I/O to * the PartitionN files will fail. If open any PartitionN file then all further I/O to EntireDisk * will fail. Hence in practice you can only access on or the other within a single mount. * * This code is structured in the following sections: * * The main(argc, argv) routine including validation of arguments and call to fuse_main * * MBR and EBR parsing routines * * The Fuse callback routines for destroy ,flush ,getattr ,open, read, readdir, write * * For further details on how this all works see http://fuse.sourceforge.net/ * * VirtualBox provided an API to enable you to manipulate VD image files programmatically. * This is documented in the embedded source comments. See for further details * http://www.virtualbox.org/svn/vbox/trunk/include/VBox/VBoxHDD-new.h * * To compile this you need to pull (wget) the VBox OSE source from http://www.virtualbox.org/downloads. * Set the environment variable VBOX_INCLUDE to the include directory within this tree * * gcc vdfuse.c -o vdfuse `pkg-config --cflags --libs fuse` \ * -l:/usr/lib/virtualbox/VBoxDD.so -Wl,-rpath,/usr/lib/virtualbox \ * -pthread -I$VBOX_INCLUDE * */ #define FUSE_USE_VERSION 26 #define _FILE_OFFSET_BITS 64 #include <limits.h> #include <fuse.h> #include <errno.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <unistd.h> #include <pthread.h> #ifdef __GNUC__ #define UNUSED __attribute__ ((unused)) #else #define UNUSED #endif #define IN_RING3 #define BLOCKSIZE 512 #define UNALLOCATED -1 #define GETOPT_ARGS "rgvawt:s:f:dh?" #define HOSTPARTITION_MAX 100 #define DIFFERENCING_MAX 100 #define PNAMESIZE 15 #define MBR_START 446 #define EBR_START 446 #define PARTTYPE_IS_EXTENDED(x) ((x) == 0x05 || (x) == 0x0f || (x) == 0x85) void usageAndExit (char *optFormat, ...); void vbprintf (const char *format, ...); void vdErrorCallback(void *pvUser, int rc, const char *file, unsigned iLine, const char *function, const char *format, va_list va); void initialisePartitionTable(void); int findPartition (const char* filename); int detectDiskType (char **disktype, char *filename); static int VD_open (const char *c, struct fuse_file_info *i); static int VD_release (const char *name, struct fuse_file_info *fi); static int VD_read (const char *c, char *out, size_t len, off_t offset, struct fuse_file_info *i UNUSED); static int VD_write (const char *c, const char *in, size_t len, off_t offset, struct fuse_file_info *i UNUSED); static int VD_flush (const char *p, struct fuse_file_info *i UNUSED); static int VD_readdir (const char *p, void *buf, fuse_fill_dir_t filler, off_t offset UNUSED, struct fuse_file_info *i UNUSED); static int VD_getattr (const char *p, struct stat *stbuf); void VD_destroy (void *u); // if you don't have VBoxHDD.h, run 'svn co http://www.virtualbox.org/svn/vbox/trunk/include' // if your VirtualBox version is older than 4.0, add -DOLDVBOXHDD to your gcc flags #ifdef OLDVBOXHDD // < v4.0 #include <VBox/VBoxHDD.h> #else // >= v4.0 #include <VBox/vd.h> #endif #define DISKread(o,b,s) VDRead (hdDisk,o,b,s); #define DISKwrite(o,b,s) VDWrite (hdDisk,o,b,s); #define DISKclose VDCloseAll(hdDisk) #define DISKsize VDGetSize(hdDisk, 0) #define DISKflush VDFlush(hdDisk) #define DISKopen(t,i) \ if (RT_FAILURE(VDOpen(hdDisk,t , i, readonly ? VD_OPEN_FLAGS_READONLY : VD_OPEN_FLAGS_NORMAL, NULL))) \ usageAndExit("opening vbox image failed"); PVBOXHDD hdDisk; PVDINTERFACE pVDifs = NULL; VDINTERFACE vdError; VDINTERFACEERROR vdErrorCallbacks = { .cbSize = sizeof(VDINTERFACEERROR), .enmInterface = VDINTERFACETYPE_ERROR, .pfnError = vdErrorCallback }; // Partition table information typedef struct { // See http://en.wikipedia.org/wiki/Master_boot_record uint8_t status; // status[7] (0x80 = bootable, 0x00 = non-bootable,other = invalid[8]) // ** CHS address of first block ** uint8_t shead; // first head uint8_t ssector; // first sector is in bits 5-0; bits 9-8 of cylinder are in bits 7-6 uint8_t sbits; // first bits 7-0 of cylinder uint8_t type; // partition type // ** CHS address of last block ** uint8_t ehead; // last head uint8_t esector; // last sector is in bits 5-0; bits 9-8 of cylinder are in bits 7-6 uint8_t ebits; // last bits 7-0 of cylinder uint32_t offset; // LBA of first sector in the partition uint32_t size; // number of blocks in partition, in little-endian format } MBRentry; typedef struct { char name[PNAMESIZE+1];// name of partition off_t offset; // offset into disk in bytes uint64_t size; // size of partiton in bytes int no; // partition number MBRentry descriptor; // copy of MBR / EBR descriptor that defines the partion } Partition; #pragma pack( push ) #pragma pack( 1 ) typedef struct { char fill[ MBR_START ]; MBRentry descriptor[ 4 ]; uint16_t signature; } MBRblock; typedef struct { // See http://en.wikipedia.org/wiki/Extended_boot_record for details char fill[ EBR_START ]; MBRentry descriptor; MBRentry chain; MBRentry fill1, fill2; uint16_t signature; } EBRentry; #pragma pack( pop ) Partition partitionTable[HOSTPARTITION_MAX+1]; // Note the partitionTable[0] is reserved for the EntireDisk descriptor static int lastPartition = 0; static struct fuse_operations fuseOperations = { .readdir = VD_readdir, .getattr = VD_getattr, .open = VD_open, .release = VD_release, .read = VD_read, .write = VD_write, .flush = VD_flush, .destroy = VD_destroy }; static struct fuse_args fuseArgs = FUSE_ARGS_INIT (0, NULL); static struct stat VDfile_stat; static int verbose = 0; static int readonly = 0; static int allowall = 0; // allow all users to read from disk static int allowallw = 0; // allow all users to write to disk static uid_t myuid = 0; static gid_t mygid = 0; static char *processName; static int entireDiskOpened = 0; static int partitionOpened = 0; static int opened = 0; // how many opened instances are there // //==================================================================================================== // Main routine including validation //==================================================================================================== int main (int argc, char **argv) { char *diskType = "auto"; char *imagefilename = NULL; char *mountpoint = NULL; int debug = 0; int foreground = 0; char c; int i; char *differencing[DIFFERENCING_MAX]; int differencingLen = 0; extern char *optarg; extern int optind, optopt; // // *** Parse the command line options *** // processName = argv[0]; while ((c = getopt(argc, argv, GETOPT_ARGS)) != -1) { switch(c) { case 'r' : readonly = 1; break; case 'g' : foreground = 1; break; case 'v' : verbose = 1; break; case 'a' : allowall = 1; break; case 'w' : allowall = 1; allowallw = 1; break; case 't' : diskType = (char *) optarg; break; // ignored if OLDAPI case 's' : if (differencingLen == DIFFERENCING_MAX) usageAndExit("Too many differencing disks"); differencing[differencingLen++] = (char *)optarg; break; case 'f' : imagefilename = (char *)optarg; break; case 'd' : foreground = 1; debug = 1; break; case 'h' : usageAndExit(NULL); case '?' : usageAndExit("Unknown option"); } } // // *** Validate the command line *** // if (argc != optind+1) usageAndExit("a single mountpoint must be specified"); mountpoint = argv[optind]; if (!mountpoint) usageAndExit("no mountpoint specified"); if (!imagefilename) usageAndExit("no image chosen"); if (stat(imagefilename, &VDfile_stat)<0) usageAndExit("cannot access imagefile"); if (access (imagefilename, F_OK | R_OK | ((!readonly) ? W_OK : 0)) < 0) usageAndExit("cannot access imagefile"); for (i = 0; i < differencingLen; i++) if (access(differencing[i], F_OK | R_OK | ((readonly) ? 0 : W_OK)) < 0) usageAndExit("cannot access differencing imagefile %s", differencing[i]); #define IS_TYPE(s) (strcmp (s, diskType) == 0) if ( !(IS_TYPE("auto") || IS_TYPE("VDI" ) || IS_TYPE("VMDK") || IS_TYPE("VHD" ) || IS_TYPE("auto")) ) usageAndExit("invalid disk type specified"); if (strcmp ("auto", diskType) == 0 && detectDiskType (&diskType, imagefilename) < 0) return 1; // // *** Open the VDI, parse the MBR + EBRs and connect to the fuse service *** // if (RT_FAILURE(VDInterfaceAdd(&vdError, "VD Error", VDINTERFACETYPE_ERROR, &vdErrorCallbacks, NULL, &pVDifs))) usageAndExit("invalid initialisation of VD interface"); if (RT_FAILURE(VDCreate(&vdError, VDTYPE_HDD, &hdDisk))) usageAndExit("invalid initialisation of VD interface"); DISKopen(diskType, imagefilename); for (i = 0; i < differencingLen; i++) { char *diffType; char *diffFilename = differencing[i]; detectDiskType (&diffType, diffFilename); DISKopen (diffType, diffFilename); } initialisePartitionTable(); myuid = geteuid (); mygid = getegid (); fuse_opt_add_arg (&fuseArgs, "vdfuse"); { char fsname[strlen(imagefilename) + 12]; strcpy (fsname, "-ofsname=\0"); strcat (fsname, imagefilename); fuse_opt_add_arg (&fuseArgs, fsname); } fuse_opt_add_arg (&fuseArgs, "-osubtype=vdfuse"); fuse_opt_add_arg (&fuseArgs, "-o"); fuse_opt_add_arg (&fuseArgs, (allowall) ? "allow_other" : "allow_root"); if (foreground) fuse_opt_add_arg (&fuseArgs, "-f"); if (debug) fuse_opt_add_arg (&fuseArgs, "-d"); fuse_opt_add_arg (&fuseArgs, mountpoint); return fuse_main (fuseArgs.argc, fuseArgs.argv, &fuseOperations #if FUSE_USE_VERSION >= 26 , NULL #endif ); } //==================================================================================================== // Miscellaneous output utilities //==================================================================================================== void usageAndExit(char *optFormat, ... ) { va_list ap; if (optFormat !=NULL) { fputs ("\nERROR: ", stderr); va_start (ap, optFormat); vfprintf (stderr, optFormat, ap); va_end (ap); fputs ("\n\n", stderr); } // ---------!---------!---------!---------!---------!---------!---------!---------! fprintf (stderr, "DESCRIPTION: This Fuse module uses the VirtualBox access library to open a \n" "VirtualBox supported VD image file and mount it as a Fuse file system. The\n" "mount point contains a flat directory containing the files EntireDisk,\n" "Partition1 .. PartitionN. These can then be loop mounted to access the\n" "underlying file systems\n\n" "USAGE: %s [options] -f image-file mountpoint\n" "\t-h\thelp\n" "\t-r\treadonly\n" #ifndef OLDAPI "\t-t\tspecify type (VDI, VMDK, VHD, or raw; default: auto)\n" #endif "\t-f\tVDimage file\n" // "\t-s\tdifferencing disk files\n" // prevent misuse "\t-a\tallow all users to read disk\n" "\t-w\tallow all users to read and write to disk\n" "\t-g\trun in foreground\n" "\t-v\tverbose\n" "\t-d\tdebug\n\n" "NOTE: you must add the line \"user_allow_other\" (without quotes)\n" "to /etc/fuse.confand set proper permissions on /etc/fuse.conf\n" "for this to work.\n", processName); exit(1); } void vbprintf (const char *format, ...) { va_list ap; if (!verbose) return; va_start (ap, format); vprintf (format, ap); va_end (ap); fputs ("\n", stdout); fflush (stdout); } void vdErrorCallback(void *pvUser UNUSED, int rc, const char *file, unsigned iLine, const char *function, const char *format, va_list va) { fprintf(stderr, "\nVD CallbackError rc %d at %s:%u (%s): ", rc, file, iLine, function); vfprintf(stderr, format, va); fputs("\n", stderr); } //==================================================================================================== // MBR + EBR parsing routine //==================================================================================================== // // This code is algorithmically based on partRead in VBoxInternalManage.cpp plus the Wikipedia articles // on MBR and EBR. As in partRead, a statically allocated partition list is used same to keep things // simple (but up to a max 100 partitions :lol:). Note than unlike partRead, this doesn't resort the // partitions. // //int VDRead(PVBOXHDD pDisk, uint64_t uOffset, void *pvBuf, size_t cbRead, int ii ); void initialisePartitionTable(void) { //uint16_t MBRsignature; int entendedFlag = UNALLOCATED; int i; MBRblock mbrb; memset(partitionTable, 0, sizeof(partitionTable)); for (i=0; i <= (signed) (sizeof(partitionTable)/sizeof(Partition)) ; i++) partitionTable[i].no = UNALLOCATED; partitionTable[0].no = 0; partitionTable[0].offset = 0; partitionTable[0].size = DISKsize; strcpy(partitionTable[0].name, "EntireDisk"); // // Check that this is unformated or a DOS partitioned disk. Sorry but other formats not supported. // DISKread(0, &mbrb, sizeof (mbrb) ); if (mbrb.signature == 0x0000) return; // an unformated disk is allowed but only EntireDisk is defined if (mbrb.signature != 0xaa55) usageAndExit("Invalid MBR found on image with signature 0x%04hX", mbrb.signature); // // Process the four physical partition entires in the MBR // for (i = 1; i <= 4; i++) { Partition *p = partitionTable + i; // MBRentry *m = &(p->descriptor); //DISKread (MBR_START + (i-1) * sizeof(MBRentry), &(p->descriptor), sizeof(MBRentry)); memcpy( &(p->descriptor), &mbrb.descriptor[ i - 1 ], sizeof( MBRentry ) ); if ((p->descriptor).type == 0) continue; if (PARTTYPE_IS_EXTENDED((p->descriptor).type)) { if (entendedFlag != UNALLOCATED) usageAndExit("More than one extended partition in MBR"); entendedFlag = i; } else { lastPartition = i; p->no = i; p->offset = (off_t)((p->descriptor).offset) * BLOCKSIZE; p->size = (off_t)((p->descriptor).size) * BLOCKSIZE; } } // // Now chain down any EBRs to process the logical partition entries // if (entendedFlag != UNALLOCATED) { EBRentry ebr; off_t uStart = (off_t)((partitionTable[entendedFlag].descriptor).offset) * BLOCKSIZE; off_t uOffset = 0; if (!uStart) usageAndExit("Inconsistency for logical partition start. Aborting\n"); for (i = 5; i <= HOSTPARTITION_MAX; i++) { lastPartition++; Partition *p = partitionTable + i; DISKread (uStart + uOffset, &ebr, sizeof(ebr)); if (ebr.signature != 0xaa55) usageAndExit("Invalid EBR signature found on image"); if ((ebr.descriptor).type == 0) usageAndExit("Logical partition with type 0 encountered"); if (!((ebr.descriptor).offset)) usageAndExit("Logical partition invalid partition start offset encountered"); p->descriptor = ebr.descriptor; p->no = i; lastPartition = i; p->offset = uStart + uOffset + (off_t)((ebr.descriptor).offset) * BLOCKSIZE; p->size = (off_t)((ebr.descriptor).size) * BLOCKSIZE; if (ebr.chain.type == 0) break; if (!PARTTYPE_IS_EXTENDED(ebr.chain.type)) usageAndExit("Logical partition chain broken"); uOffset = (off_t)((ebr.chain).offset) * BLOCKSIZE; } } // // Now print out the partition table // vbprintf( "Partition Size Offset\n" "========= ==== ======\n"); for (i = 1; i <= lastPartition; i++) { Partition *p = partitionTable + i; if (p->no != UNALLOCATED) { sprintf(p->name, "Partition%d", i); vbprintf ( "%-14s %-13lld %-13lld", p->name, p->offset, p->size ); } } vbprintf( "\n" ); } int findPartition (const char* filename) { // Use a dumb serial search since there are typically less than 3 entries int i; register Partition *p = partitionTable; for (i = 0; i <= lastPartition; i++, p++) { if (p->no != UNALLOCATED && strcmp(filename+1, p->name) == 0) return i; } return -1; } // detects type of virtual image int detectDiskType (char **disktype, char *filename) { char buf[8]; int fd = open (filename, O_RDONLY); read (fd, buf, sizeof (buf)); if (strncmp (buf, "conectix", 8) == 0) *disktype = "VHD"; else if (strncmp (buf, "VMDK", 4) == 0) *disktype = "VMDK"; else if (strncmp (buf, "KDMV", 4) == 0) *disktype = "VMDK"; else if (strncmp (buf, "<<<", 3) == 0) *disktype = "VDI"; else usageAndExit("cannot autodetect disk type of %s", filename); vbprintf ("disktype is %s", *disktype); close(fd); return 0; } //==================================================================================================== // Fuse Callback Routines //==================================================================================================== // // in alphetic order to help find them: destroy ,flush ,getattr ,open, read, readdir, write pthread_mutex_t disk_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t part_mutex = PTHREAD_MUTEX_INITIALIZER; void VD_destroy (void *u UNUSED) { // called when the fuse filesystem is umounted vbprintf ("destroy"); DISKclose; } int VD_flush(const char *p, struct fuse_file_info *i UNUSED) { vbprintf ("flush: %s", p); DISKflush; return 0; } static int VD_getattr (const char *p, struct stat *stbuf) { vbprintf ("getattr: %s", p); int isFileRoot = (strcmp ("/", p) == 0); int n = findPartition(p); if (!isFileRoot && n == -1) return -ENOENT; // Use the container file's stat return as the basis. However since partitions cannot // be created by creating files, there is no write access to the directory. I also // treat group access the same as other. memcpy (stbuf, &VDfile_stat, sizeof (struct stat)); if (isFileRoot) { stbuf->st_mode = S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP; if (allowall) stbuf->st_mode |= S_IROTH; stbuf->st_size = 0; stbuf->st_blocks = 2; } else { stbuf->st_mode = S_IFREG | S_IRUSR | S_IWUSR; if (allowall) stbuf->st_mode |= S_IRGRP | S_IROTH; if (allowallw) stbuf->st_mode |= S_IWGRP | S_IWOTH; stbuf->st_size = partitionTable[n].size; stbuf->st_blocks = (stbuf->st_size + BLOCKSIZE - 1) / BLOCKSIZE; } if (readonly) { stbuf->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); } stbuf->st_nlink = 1; return 0; } static int VD_open(const char *cName, struct fuse_file_info *i) { vbprintf ("open: %s, %lld, 0X%08lX ", cName, i->fh, i->flags ); int n = findPartition(cName); if ( (n == -1) || (entireDiskOpened && n > 0) || (partitionOpened && n == 0) ) return -ENOENT; if ( readonly && ((i->flags & (O_WRONLY | O_RDWR)) != 0)) return -EROFS; if (n == 0) entireDiskOpened = 1; else partitionOpened = 1; pthread_mutex_lock (&part_mutex); opened++; pthread_mutex_unlock (&part_mutex); return 0; } static int VD_release (const char *name, struct fuse_file_info *fi) { (void) fi; vbprintf ("release: %s", name); pthread_mutex_lock (&part_mutex); opened--; if (opened == 0) { initialisePartitionTable (); entireDiskOpened = 0; partitionOpened = 0; } pthread_mutex_unlock (&part_mutex); return 0; } static int VD_read (const char *c, char *out, size_t len, off_t offset, struct fuse_file_info *i UNUSED) { vbprintf ("read: %s, offset=%lld, length=%d", c, offset, len); int n = findPartition(c); if (n<0) return -ENOENT; if ((n == 0) ? partitionOpened : entireDiskOpened) return -EIO; Partition *p = &(partitionTable[n]); // if (offset >= p->size) return 0; // if (offset + len> p->size) len = p->size - offset; if ((uint64_t)offset >= p->size) return 0; if ((uint64_t)(offset + len) > p->size) len = p->size - offset; pthread_mutex_lock (&disk_mutex); int ret = DISKread(offset + p->offset, out, len); pthread_mutex_unlock (&disk_mutex); return RT_SUCCESS(ret) ? (signed) len : -EIO; } static int VD_readdir (const char *p, void *buf, fuse_fill_dir_t filler, off_t offset UNUSED, struct fuse_file_info *i UNUSED){ int n; vbprintf ("readdir"); if (strcmp ("/", p) != 0) return -ENOENT; filler (buf, ".", NULL, 0); filler (buf, "..", NULL, 0); for (n = 0; n <= lastPartition; n++) { Partition *p = partitionTable + n; if (p->no != UNALLOCATED) filler(buf, p->name, NULL, 0); } return 0; } static int VD_write (const char *c, const char *in, size_t len, off_t offset, struct fuse_file_info *i UNUSED) { vbprintf ("write: %s, offset=%lld, length=%d", c, offset, len); int n = findPartition(c); if (n<0) return -ENOENT; if ((n == 0) ? partitionOpened : entireDiskOpened) return -EIO; Partition *p = &(partitionTable[n]); if ((uint64_t)offset >= p->size) return 0; if ((uint64_t)(offset + len) > p->size) len = p->size - offset; pthread_mutex_lock (&disk_mutex); int ret = DISKwrite(offset + p->offset, in, len); pthread_mutex_unlock (&disk_mutex); return RT_SUCCESS(ret) ? (signed) len : -EIO; }
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor