used the new VBoxHDD-new.h api, added -DOLDAPI for compatibility, plugged small memory leak, added new option for choosing image type, prevent mapping non-existent partitions
Code: Select all
/* Original author: h2o on forums.virtualbox.org *
* http://forums.virtualbox.org/viewtopic.php?p=59275 *
* vdimount.c - tool for mounting VDI files */
/* *
* Available compile flags: *
* -DNO_VBOX_HDRS: don't use the VBox headers *
* (useful if you don't have them) *
* -DOLDAPI: use old VBoxHDD.h api (default uses VBoxHDD-new.h) *
* */
/* 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 3 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/>. */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <endian.h>
#include <errno.h>
#include <pthread.h>
#include <linux/nbd.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#warning "This program is obsolete, use the fuse-based version (vdfuse) instead"
#ifdef __GNUC__
#define UNUSED __attribute__ ((unused))
#else
#define UNUSED
#endif
// macro to convert uint64_t's to network order
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ntohll __bswap_64
#else
#define ntohll
#endif
#define htonll ntohll
#ifdef NO_VBOX_HDRS
#define VBOX_SUCCESS(i) (__builtin_expect (i >= 0, 1))
#define VBOX_FAILURE(i) (!VBOX_SUCCESS (i))
#endif
#ifdef OLDAPI
#ifdef NO_VBOX_HDRS
// from VBoxHDD.h
// (P)VDIDISK
struct VDIDISK;
typedef struct VDIDISK VDIDISK;
typedef VDIDISK *PVDIDISK;
// Flags
#define VDI_OPEN_FLAGS_NORMAL (0)
#define VDI_OPEN_FLAGS_READONLY (1)
// Functions
extern PVDIDISK VDIDiskCreate (void);
extern int VDIDiskOpenImage (PVDIDISK, const char *, unsigned);
extern void VDIDiskCloseImage (PVDIDISK);
extern int VDIDiskRead (PVDIDISK, uint64_t, void *, size_t);
extern int VDIDiskWrite (PVDIDISK, uint64_t, const void *, size_t);
extern uint64_t VDIDiskGetSize (PVDIDISK);
#else // NO_VBOX_HDRS
#define IN_RING3
#include <VBox/VBoxHDD.h>
#endif // NO_VBOX_HDRS
#else // OLDAPI
#ifdef NO_VBOX_HDRS
// from VBoxHDD-new.h
// VDINTERFACETYPE
typedef enum VDINTERFACETYPE
{
VDINTERFACETYPE_FIRST = 0,
VDINTERFACETYPE_ERROR = VDINTERFACETYPE_FIRST,
VDINTERFACETYPE_ASYNCIO,
VDINTERFACETYPE_PROGRESS,
VDINTERFACETYPE_CONFIG,
VDINTERFACETYPE_INVALID
} VDINTERFACETYPE;
// (P)VDINTERFACE
typedef struct VDINTERFACE
{
const char *pszInterfaceName;
uint32_t cbSize;
struct VDINTERFACE *pNext;
VDINTERFACETYPE enmInterface;
void *pvUser;
void *pCallbacks;
} VDINTERFACE, *PVDINTERFACE;
// (P)VBOXHDD
struct VBOXHDD;
typedef struct VBOXHDD VBOXHDD;
typedef VBOXHDD *PVBOXHDD;
// Flags
#define VD_OPEN_FLAGS_NORMAL 0
#define VD_OPEN_FLAGS_READONLY 1
// Functions
extern int VDCreate(PVDINTERFACE, PVBOXHDD *);
extern int VDOpen(PVBOXHDD, const char *, const char *, unsigned, PVDINTERFACE);
extern uint64_t VDGetSize(PVBOXHDD, unsigned);
extern int VDRead(PVBOXHDD, uint64_t, void *, size_t);
extern int VDWrite(PVBOXHDD, uint64_t, const void *, size_t);
extern int VDClose(PVBOXHDD, int /* should be bool */);
#else // NO_VBOX_HDRS
#define IN_RING3
#include <VBox/VBoxHDD-new.h>
#endif // NO_VBOX_HDRS
#endif // OLDAPI
#define SINSIZE sizeof (struct sockaddr_in)
// taken from cliserv.h in nbd package
#define INIT_PASSWD "NBDMAGIC"
#define BUFSIZE 1048576
#define NBD_FLAG_HAS_FLAGS (1)
#define NBD_FLAG_READ_ONLY (2)
uint64_t clientmagic = 0x00420281861253LL;
typedef struct
{
int pn; // partition #
int port; // port #
int sock; // socket #
struct sockaddr_in sin;
uint64_t offset; // offset into disk
uint64_t size; // size of partiton
} abinding;
// MBR entry structure
struct mbri
{
uint8_t status;
uint8_t shead;
uint8_t ssector;
uint8_t sbits;
uint8_t type;
uint8_t ehead;
uint8_t esector;
uint8_t ebits;
uint32_t offset;
uint32_t size;
};
// own implementation of GList from GLib
typedef struct _dlist dlist;
struct _dlist
{
void *data;
dlist *next;
dlist *prev;
};
// struct to pass args to server thread
struct serverarg
{
dlist *dl;
abinding *bn;
};
struct sigaction siga;
void *server_main (void *);
void cleanup (int);
void start_server (int);
int vread (abinding *, uint64_t, uint32_t, void *);
int vwrite (abinding *, uint64_t, uint32_t, const void *);
void timeout (int);
dlist *list_start (dlist *list)
{
while (list && list->prev)
list = list->prev;
return list;
}
dlist *list_end (dlist *list)
{
while (list && list->next)
list = list->next;
return list;
}
int list_len (dlist *list)
{
int i = 0;
list = list_start (list);
do
{
i++;
}
while (list && (list = list->next));
return i;
}
dlist *list_add (dlist *list, void *data)
{
if (list)
list = list_end (list);
dlist *newlist = malloc (sizeof (dlist));
newlist->data = data;
newlist->next = NULL;
newlist->prev = list;
if (list)
list->next = newlist;
return list_start (newlist);
}
dlist *list_del (dlist *item)
{
dlist *next = item->next;
dlist *prev = item->prev;
if (next)
next->prev = prev;
if (prev)
prev->next = next;
free (item);
if (next)
return list_start (next);
else if (prev)
return list_start (prev);
else
return NULL;
}
// printf to stderr
const char *errout_pre = "ERROR: ";
void errout (const char *format, ...)
{
char *tmpfmt = malloc (strlen (errout_pre) + strlen (format) + 2);
strcpy (tmpfmt, errout_pre);
strcat (tmpfmt, format);
strcat (tmpfmt, "\n");
va_list ap;
va_start (ap, format);
vfprintf (stderr, tmpfmt, ap);
va_end (ap);
free (tmpfmt);
fflush (stderr);
}
int verbose = 0;
// verbose printf
void vbprintf (const char *format, ...)
{
if (!verbose)
return;
va_list ap;
va_start (ap, format);
vprintf (format, ap);
va_end (ap);
fflush (stdout);
}
dlist *maplist;
dlist *thrs;
#ifdef OLDAPI
PVDIDISK vdidisk;
#else
VDINTERFACE dInterface;
PVBOXHDD dDisk;
#endif
int readonly;
#define BLOCKSIZE 512
pthread_mutex_t disk_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t sock_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t sock_mutex = PTHREAD_MUTEX_INITIALIZER;
struct sigaction blocksig;
struct sigaction freesig;
// valid backends
char *validBackends[] = {"VDI", "VMDK", "VHD", "raw"};
// adds partition->port mapping
int add_map (char *v)
{
char *eqi = strchr (v, '=');
if (!eqi)
{
errout ("invalid argument \"%s\"", v);
return -1;
}
char *pn = v;
char *port = eqi + 1;
*eqi = 0;
int pni;
if (strcmp (pn, "raw") == 0)
pni = -1;
else
pni = strtol (pn, 0, 10);
int porti = strtol (port, 0, 10);
if (pni == 0 || porti == 0)
{
errout ("invalid partition number %d or port number %d", pni, porti);
return -1;
}
abinding *binding = malloc (sizeof (abinding));
binding->pn = pni;
binding->port = porti;
maplist = list_add (maplist, binding);
return 0;
}
// processes logical partitions (does this work???)
int pnse (uint64_t offset)
{
int pn = 5;
while (offset)
{
char buf[512];
#ifdef OLDAPI
VDIDiskRead (vdidisk, offset, buf, 512);
#else
VDRead (dDisk, offset, buf, 512);
#endif
struct mbri *entry_a = (struct mbri *)(buf + 446);
struct mbri *entry_b = (struct mbri *)(buf + 462);
uint16_t *sig = (uint16_t *)(buf + 510);
if (*sig != 0xaa55)
{
errout ("invalid ebr signature at offset %llx", offset);
return -1;
}
uint64_t offset64 = (uint64_t)entry_a->offset;
offset64 *= BLOCKSIZE;
uint64_t size64 = (uint64_t)entry_a->size;
size64 *= BLOCKSIZE;
dlist *mm = maplist;
while (mm)
{
abinding *bn = mm->data;
if (bn->pn == pn)
{
bn->offset = offset64;
bn->size = size64;
}
mm = mm->next;
}
offset = entry_b->offset;
pn++;
}
return 0;
}
// processes partiton table
int pns ()
{
char dmbr[512];
#ifdef OLDAPI
VDIDiskRead (vdidisk, 0, dmbr, 512);
#else
VDRead (dDisk, 0, dmbr, 512);
#endif
struct mbri *pts = (struct mbri *)(dmbr + 446);
uint16_t *sig;
sig = (uint16_t *)(dmbr + 510);
if (*sig != 0xaa55)
{
errout ("invalid mbr signature");
return -1;
}
int i = 0;
dlist *mm = maplist;
abinding *bn;
while (i < 4)
{
int pn = i + 1;
uint64_t offset64 = (uint64_t)pts[i].offset;
offset64 *= BLOCKSIZE;
uint64_t size64 = (uint64_t)pts[i].size;
size64 *= BLOCKSIZE;
while (mm)
{
bn = mm->data;
if (bn->pn == pn)
{
bn->offset = offset64;
bn->size = size64;
}
mm = mm->next;
}
if (pts[i].type == 5)
{
uint64_t extpart = (uint64_t) pts[i].offset;
extpart = extpart * BLOCKSIZE;
if (pnse (extpart) < 0)
return -1;
}
i++;
}
mm = maplist;
while (mm)
{
bn = mm->data;
if (bn->pn == -1)
{
bn->offset = 0;
#ifdef OLDAPI
bn->size = VDIDiskGetSize (vdidisk);
#else
bn->size = VDGetSize (dDisk, 0);
#endif
break;
}
mm = mm->next;
}
return 0;
}
int main (int c, char **v)
{
if (c < 4)
{
printf ("Usage: %s [-r] [-v] [-d] [-t type] -f image-file partiton-number=port-number "
"[partition-#=port-#] [...]\n"
"\t-r readonly\n"
"\t-f path to image file\n"
"\t-d daemonize\n"
#ifndef OLDAPI
"\t-t image type (VDI, VMDK, VHD, raw) (default: VDI)\n"
#endif
"\t-v verbose\n"
"To use the entire disk use \"raw\" as the partition number.\n\n"
"Partition 1 is first partiton; partition 5 is first logical partition.\n"
"Example:\tMap partition 1 to port 2001, partition 2 to port 2002:\n"
"\t%s -f /path/to/image/file 1=2001 2=2002\n", v[0], v[0]);
return 255;
}
char *imagefilename = NULL;
#ifndef OLDAPI
char *diskType = "VDI";
#endif
// parse args
int dofork = 0;
v++;
while (*v)
{
if (strcmp (*v, "-r") == 0)
readonly = 1;
else if (strcmp (*v, "-f") == 0)
imagefilename = *++v;
else if (strcmp (*v, "-d") == 0)
dofork = 1;
else if (strcmp (*v, "-v") == 0)
verbose = 1;
#ifndef OLDAPI
else if (strcmp (*v, "-t") == 0)
diskType = *++v;
#endif
else
{
if (add_map (*v) < 0)
return 1;
}
v++;
}
if (!maplist)
{
errout ("no partion->port mappings specified");
return 1;
}
if (!imagefilename)
{
errout ("no image file specified");
return 1;
}
#ifndef OLDAPI
unsigned int di = 0;
int invalidBackend = 1;
while (di < (sizeof (validBackends) / sizeof (char *)))
{
if (strcmp (validBackends[di], diskType) == 0)
{
invalidBackend = 0;
break;
}
di++;
}
if (invalidBackend)
{
errout ("invalid disk type specified, valid types are VDI, VMDK, VHD, and raw.");
return 1;
}
#endif
// open disk
#ifdef OLDAPI
vdidisk = VDIDiskCreate ();
int imageopen = VDI_OPEN_FLAGS_NORMAL;
if (readonly)
imageopen = VDI_OPEN_FLAGS_READONLY;
int ret = VDIDiskOpenImage (vdidisk, imagefilename, imageopen);
#else
dInterface.pszInterfaceName = "What's an interface?";
dInterface.enmInterface = VDINTERFACETYPE_FIRST;
VDCreate (&dInterface, &dDisk);
int imageopen = VD_OPEN_FLAGS_NORMAL;
if (readonly)
imageopen = VD_OPEN_FLAGS_READONLY;
int ret = VDOpen (dDisk, diskType, imagefilename, imageopen, &dInterface);
#endif
if (VBOX_FAILURE (ret))
{
errout ("opening vbox image failed, errno %d", ret);
return 1;
}
vbprintf ("image opened\n");
if (pns () < 0)
return 1;
// setup poll structures
struct pollfd *pfd = malloc (list_len (maplist) * sizeof (struct pollfd));
int pfdl = 0;
// bind / listen sockets
dlist *mm = maplist;
int i = 0;
while (mm)
{
abinding *bn = mm->data;
bn->sock = socket (AF_INET, SOCK_STREAM, 0);
pfd[pfdl].fd = bn->sock;
pfd[pfdl].events = POLLIN;
pfdl++;
memset (&bn->sin, 0, SINSIZE);
bn->sin.sin_family = AF_INET;
bn->sin.sin_port = htons (bn->port);
bn->sin.sin_addr.s_addr = htonl (0x7f000001); /* 127.0.0.1 */
ret = bind (bn->sock, (struct sockaddr *)&bn->sin, SINSIZE);
if (ret < 0)
{
errout ("Failed binding socket: %m", errno);
return 1;
}
listen (bn->sock, 1024);
vbprintf ("Binding %d:\n\tPartition #: %d\n\tPort: %d\n\tOffset: %lld\n\tSize: %lld\n\n", i++, bn->pn, bn->port, bn->offset, bn->size);
if (!bn->size)
{
errout ("Size of binding %d is zero, either partition %d does not exist, or the partition table is corrupt/not valid.", i, bn->pn);
return 1;
}
mm = mm->next;
}
siga.sa_handler = cleanup;
sigfillset (&siga.sa_mask);
siga.sa_flags = 0;
sigaction (SIGTERM, &siga, 0);
sigaction (SIGINT, &siga, 0);
blocksig.sa_handler = SIG_IGN;
// daemonize
if (dofork)
{
verbose = 0;
int forkrc = fork ();
if (forkrc < 0)
errout ("failed to daemonize: %m", errno);
else if (forkrc)
return 0;
else
{
int devnull = open ("/dev/null", O_RDWR);
dup2 (devnull, 0);
dup2 (devnull, 1);
dup2 (devnull, 2);
close (devnull);
}
}
// MAIN LOOP
int po;
while (1)
{
po = poll (pfd, pfdl, 1000);
if (po > 0)
{
i = 0;
while (i < pfdl)
{
if (pfd[i].revents & POLLIN)
{
pthread_mutex_lock (&sock_mutex);
start_server (pfd[i].fd);
// Keep from starting multiple threads for one connection
pthread_cond_wait (&sock_cond, &sock_mutex);
pthread_mutex_unlock (&sock_mutex);
}
i++;
}
}
}
}
void start_server (int fd)
{
dlist *mm = maplist;
abinding *bn;
int notfound = 1;
while (mm)
{
bn = mm->data;
if (bn->sock == fd)
{
notfound = 0;
break;
}
mm = mm->next;
}
if (notfound)
return;
vbprintf ("starting server for partition #%d\n", bn->pn);
pthread_t *thr = malloc (sizeof (pthread_t));
thrs = list_add (thrs, thr);
struct serverarg *sarg = malloc (sizeof (struct serverarg));
sarg->dl = list_end (thrs);
sarg->bn = bn;
pthread_create (thr, NULL, server_main, (void *)sarg);
}
// SIGPIPE-safe write
int swrite (int fd, const void *buf, int count)
{
sigaction (SIGPIPE, &blocksig, &freesig);
int ret = write (fd, buf, count);
int oerrno = errno;
sigaction (SIGPIPE, &freesig, &blocksig);
errno = oerrno;
return ret;
}
// negotiate function (taken from nbd-server)
int negotiate (int fd, abinding *bn)
{
sigaction (SIGPIPE, &blocksig, &freesig);
if (write (fd, INIT_PASSWD, 8) < 0)
{
errout ("negotiate: cannot write INIT_PASSWD: %m", errno);
return -1;
}
uint64_t clientmagicout = htonll (clientmagic);
if (write (fd, &clientmagicout, sizeof (clientmagic)) < 0)
{
errout ("negotiate: cannot write clientmagic: %m", errno);
return -1;
}
uint64_t sizeout = htonll (bn->size);
if (write (fd, &sizeout, sizeof (sizeout)) < 0)
{
errout ("negotiate: cannot write size: %m", errno);
return -1;
}
uint32_t flags = NBD_FLAG_HAS_FLAGS;
if (readonly)
flags |= NBD_FLAG_READ_ONLY;
flags = htonl (flags);
if (write (fd, &flags, sizeof (flags)) < 0)
{
errout ("negotiate: cannot write flags: %m", errno);
return -1;
}
char *nas[124];
memset (nas, 0, 124);
if (write (fd, nas, 124) < 0)
{
errout ("negotiate: cannot write zeroes: %m", errno);
return -1;
}
sigaction (SIGPIPE, &freesig, &blocksig);
return 0;
}
// macro for sending error packet
#define SERR(fd,reply,code) \
{ \
reply.error = htonl (code); \
swrite (fd, &reply, sizeof (reply)); \
reply.error = 0; \
if (errno == EPIPE) \
break;\
}
void *server_main (void *sarg)
{
struct serverarg *sargs = sarg;
abinding *bn = sargs->bn;
pthread_mutex_lock (&sock_mutex);
int mycon = accept (bn->sock, 0, 0);
pthread_cond_broadcast (&sock_cond);
pthread_mutex_unlock (&sock_mutex);
if (mycon < 0)
{
errout ("failed to accept connection on socket %d: %m", bn->sock, errno);
goto out;
}
struct nbd_request request;
struct nbd_reply reply;
if (negotiate (mycon, bn) < 0)
goto out;
reply.magic = htonl (NBD_REPLY_MAGIC);
reply.error = 0;
while (1)
{
char buf[BUFSIZE];
int reqread = read (mycon, &request, sizeof (request));
if (reqread < 0)
goto out;
else if (reqread != sizeof (request))
{
errout ("bad packet: packet is %d bytes instead of %d", reqread, sizeof (request));
SERR (mycon, reply, EINVAL);
continue;
}
request.magic = ntohl (request.magic);
request.type = ntohl (request.type);
request.from = ntohll (request.from);
request.len = ntohl (request.len);
if (request.type == NBD_CMD_DISC)
{
vbprintf ("got disconnect command\n");
break;
}
if (request.magic != NBD_REQUEST_MAGIC)
{
errout ("invalid request magic");
SERR (mycon, reply, EINVAL);
continue;
}
if (request.len > BUFSIZE + sizeof (reply))
{
errout ("request too big");
SERR (mycon, reply, ENOBUFS);
continue;
}
memcpy (reply.handle, request.handle, sizeof (reply.handle));
if (request.from + request.len > bn->offset + bn->size)
{
errout ("request beyond end of device");
SERR (mycon, reply, EINVAL);
continue;
}
if (request.type == NBD_CMD_WRITE)
{
vbprintf ("got write command to write %d bytes at offset %lld on partition %d\n", request.len, request.from, bn->pn);
if (readonly)
{
errout ("attempted write on readonly map");
SERR (mycon, reply, EPERM);
continue;
}
struct pollfd pfd;
pfd.fd = mycon;
pfd.events = POLLIN;
if (poll (&pfd, 1, 5000) == 0)
{
errout ("read data timed out");
SERR (mycon, reply, ETIMEDOUT);
continue;
}
read (mycon, buf, request.len);
if (vwrite (bn, request.from, request.len, buf) < 0)
{
errout ("problem writing to disk");
SERR (mycon, reply, EIO);
continue;
}
swrite (mycon, &reply, sizeof (reply));
continue;
}
else if (request.type == NBD_CMD_READ)
{
vbprintf ("got read command to read %d bytes at offset %lld on partition %d\n", request.len, request.from, bn->pn);
if (vread (bn, request.from, request.len, buf + sizeof (reply)) < 0)
{
errout ("problem reading from disk");
SERR (mycon, reply, EIO);
continue;
}
memcpy (buf, &reply, sizeof (reply));
swrite (mycon, buf, request.len + sizeof (reply));
continue;
}
else
{
errout ("invalid command");
SERR (mycon, reply, EINVAL);
continue;
}
}
out:
close (mycon);
free (sargs->dl->data); // free pthread_t
thrs = list_del (sargs->dl); // free list item
free (sarg); // free server args
vbprintf ("closing server for partition #%d\n", bn->pn);
pthread_exit (0);
}
int vread (abinding *bn, uint64_t from, uint32_t len, void *buf)
{
pthread_mutex_lock (&disk_mutex);
#ifdef OLDAPI
int ret = VDIDiskRead (vdidisk, from + bn->offset, buf, len);
#else
int ret = VDRead (dDisk, from + bn->offset, buf, len);
#endif
pthread_mutex_unlock (&disk_mutex);
if (VBOX_SUCCESS (ret))
return 0;
else
return -1;
}
int vwrite (abinding *bn, uint64_t from, uint32_t len, const void *buf)
{
pthread_mutex_lock (&disk_mutex);
#ifdef OLDAPI
int ret = VDIDiskWrite (vdidisk, from + bn->offset, buf, len);
#else
int ret = VDWrite (dDisk, from + bn->offset, buf, len);
#endif
pthread_mutex_unlock (&disk_mutex);
if (VBOX_SUCCESS (ret))
return 0;
else
return -1;
}
// called on SIGINT / SIGTERM
void cleanup (int s UNUSED)
{
#ifdef OLDAPI
VDIDiskCloseImage (vdidisk);
#else
VDClose (dDisk, 0);
#endif
dlist *mm = maplist;
while (mm)
{
abinding *bn = mm->data;
close (bn->sock);
mm = mm->next;
}
exit (0);
}