diff --git a/Makefile b/Makefile
index 57c354d..d5a1dae 100644
--- a/Makefile
+++ b/Makefile
@@ -126,10 +126,12 @@ bt-host.o: QEMU_CFLAGS += $(BLUEZ_CFLAGS)
 
 ######################################################################
 
-qemu-img.o: qemu-img-cmds.h
+qemu-img.o: qemu-img.h qemu-img-cmds.h
 qemu-img.o qemu-tool.o qemu-nbd.o qemu-io.o: $(GENERATED_HEADERS)
 
-qemu-img$(EXESUF): qemu-img.o qemu-tool.o $(block-obj-y) $(qobject-obj-y)
+qemu-img-map.o: QEMU_CFLAGS += $(FUSE_CFLAGS) $(BLKID_CFLAGS)
+
+qemu-img$(EXESUF): $(qemu-img-y) $(block-obj-y) $(qobject-obj-y)
 
 qemu-nbd$(EXESUF): qemu-nbd.o qemu-tool.o $(block-obj-y) $(qobject-obj-y)
 
diff --git a/Makefile.objs b/Makefile.objs
index 281f7a6..8a651d2 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -207,3 +207,9 @@ libdis-$(CONFIG_PPC_DIS) += ppc-dis.o
 libdis-$(CONFIG_S390_DIS) += s390-dis.o
 libdis-$(CONFIG_SH4_DIS) += sh4-dis.o
 libdis-$(CONFIG_SPARC_DIS) += sparc-dis.o
+
+######################################################################
+# qemu-img
+
+qemu-img-y = qemu-img.o qemu-tool.o
+qemu-img-$(CONFIG_FUSE) += qemu-img-map.o
diff --git a/configure b/configure
index 6bc40a3..c84aaa9 100755
--- a/configure
+++ b/configure
@@ -263,6 +263,7 @@ vnc_tls=""
 vnc_sasl=""
 xen=""
 linux_aio=""
+fuse=""
 
 gprof="no"
 debug_tcg="no"
@@ -639,6 +640,10 @@ for opt do
   ;;
   --enable-linux-aio) linux_aio="yes"
   ;;
+  --disable-fuse) fuse="no"
+  ;;
+  --enable-fuse) fuse="yes"
+  ;;
   --enable-io-thread) io_thread="yes"
   ;;
   --disable-blobs) blobs="no"
@@ -801,6 +806,8 @@ echo "  --disable-vde            disable support for vde network"
 echo "  --enable-vde             enable support for vde network"
 echo "  --disable-linux-aio      disable Linux AIO support"
 echo "  --enable-linux-aio       enable Linux AIO support"
+echo "  --disable-fuse           disable support for FUSE in qemu-img"
+echo "  --enable-fuse            enable support for FUSE in qemu-img"
 echo "  --enable-io-thread       enable IO thread"
 echo "  --disable-blobs          disable installing provided firmware blobs"
 echo "  --kerneldir=PATH         look for kernel includes in PATH"
@@ -1586,6 +1593,44 @@ EOF
   fi
 fi
 
+##########################################
+# FUSE libraries probe
+if test "$fuse" != "no" ; then
+  fuse_cflags=`pkg-config --cflags fuse 2> /dev/null`
+  fuse_libs=`pkg-config --libs fuse 2> /dev/null`
+  cat > $TMPC << EOF
+#include <fuse.h>
+int main(int argc, const char *argv[])
+{
+    return fuse_main(argc, argv, NULL);
+}
+EOF
+  if compile_prog "$fuse_cflags" "$fuse_libs" ; then
+    fuse=yes
+    libs_tools="$fuse_libs $libs_tools"
+  else
+    if test "$fuse" = "yes" ; then
+      feature_not_found "FUSE"
+    fi
+    fuse=no
+  fi
+fi
+
+##########################################
+# blkid_partlist probe
+blkid_cflags=`pkg-config --cflags blkid 2> /dev/null`
+blkid_libs=`pkg-config --libs blkid 2> /dev/null`
+cat > $TMPC <<EOF
+#include <blkid.h>
+int main(void) { blkid_partlist ls; return 0; }
+EOF
+blkid_partlist=no
+if compile_prog "$blkid_cflags" "$blkid_libs" ; then
+  blkid_partlist=yes
+  libs_tools="$blkid_libs $libs_tools"
+fi
+
+
 #
 # Check for xxxat() functions when we are building linux-user
 # emulator.  This is done because older glibc versions don't
@@ -1962,6 +2007,8 @@ echo "PIE user targets  $user_pie"
 echo "vde support       $vde"
 echo "IO thread         $io_thread"
 echo "Linux AIO support $linux_aio"
+echo "FUSE support      $fuse"
+echo "partlist support  $blkid_partlist"
 echo "Install blobs     $blobs"
 echo "KVM support       $kvm"
 echo "fdt support       $fdt"
@@ -2183,6 +2230,14 @@ fi
 if test "$fdatasync" = "yes" ; then
   echo "CONFIG_FDATASYNC=y" >> $config_host_mak
 fi
+if test "$fuse" = "yes" ; then
+  echo "CONFIG_FUSE=y" >> $config_host_mak
+  echo "FUSE_CFLAGS=$fuse_cflags" >> $config_host_mak
+fi
+if test "$blkid_partlist" = "yes" ; then
+  echo "CONFIG_BLKID_PARTLIST=y" >> $config_host_mak
+  echo "BLKID_CFLAGS=$blkid_cflags" >> $config_host_mak
+fi
 
 # XXX: suppress that
 if [ "$bsd" = "yes" ] ; then
diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
index f96876a..94c6e66 100644
--- a/qemu-img-cmds.hx
+++ b/qemu-img-cmds.hx
@@ -49,5 +49,16 @@ DEF("rebase", img_rebase,
     "rebase [-f fmt] [-u] -b backing_file [-F backing_fmt] filename")
 STEXI
 @item rebase [-f @var{fmt}] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename}
+ETEXI
+
+#ifdef CONFIG_FUSE
+DEF("map", img_map,
+    "map [-f fmt] [<FUSE options>] filename mountpoint")
+#endif
+STEXI
+@item map [@var{FUSE options}] @var{filename} @var{mountpoint}
+ETEXI
+
+STEXI
 @end table
 ETEXI
diff --git a/qemu-img-map.c b/qemu-img-map.c
new file mode 100644
index 0000000..cd6bbf4
--- /dev/null
+++ b/qemu-img-map.c
@@ -0,0 +1,438 @@
+/*
+ * QEMU disk image utility
+ *
+ * Copyright (c) 2010 Jan Kiszka
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qemu-img.h"
+#include "qemu-option.h"
+#include "osdep.h"
+#include "block_int.h"
+#include <stdio.h>
+#include <getopt.h>
+#include <pthread.h>
+#include <signal.h>
+
+#define FUSE_USE_VERSION 28
+#include <fuse.h>
+
+#ifdef CONFIG_LINUX
+#include <linux/fs.h>
+#endif
+
+#define ENTRY_INVALID   1
+#define ENTRY_DIRTY     2
+
+#define ENTRY_PATH_MAX  16
+
+struct map_entry {
+    struct map_entry *next;
+    const char       *path;
+    size_t           size;
+    off_t            offset;
+    unsigned int     use_counter;
+    unsigned int     flags;
+};
+
+static struct stat img_stat;
+static BlockDriverState *img_bs;
+static struct map_entry disk_entry = { .path = "/disk" };
+static char *disk_path;
+
+#ifdef CONFIG_BLKID_PARTLIST
+
+#include <blkid.h>
+
+static pthread_t reader_thread;
+static sigset_t wakeup_sigset;
+static pthread_mutex_t entry_lock = PTHREAD_MUTEX_INITIALIZER;
+static struct map_entry *last_entry = &disk_entry;
+
+static void *partition_reader(void *unused)
+{
+    struct map_entry *entry;
+    blkid_partition par;
+    blkid_partlist ls;
+    blkid_probe pr;
+    int nparts, i;
+    char *path;
+
+    while (sigwaitinfo(&wakeup_sigset, NULL) >= 0) {
+        pr = blkid_new_probe_from_filename(disk_path);
+        if (!pr) {
+            continue;
+        }
+
+        ls = blkid_probe_get_partitions(pr);
+        if (!ls) {
+            blkid_free_probe(pr);
+            continue;
+        }
+
+        nparts = blkid_partlist_numof_partitions(ls);
+
+        for (i = 0; i < nparts; i++) {
+            entry = calloc(1, sizeof(*entry));
+            if (!entry) {
+                continue;
+            }
+            path = malloc(ENTRY_PATH_MAX);
+            if (!path) {
+                free(entry);
+                continue;
+            }
+
+            par = blkid_partlist_get_partition(ls, i);
+
+            snprintf(path, ENTRY_PATH_MAX, "/partition%d",
+                     blkid_partition_get_partno(par));
+            entry->path = path;
+            entry->size = blkid_partition_get_size(par) * BDRV_SECTOR_SIZE;
+            entry->offset = blkid_partition_get_start(par) * BDRV_SECTOR_SIZE;
+
+            pthread_mutex_lock(&entry_lock);
+
+            last_entry->next = entry;
+            last_entry = entry;
+
+            pthread_mutex_unlock(&entry_lock);
+        }
+
+        blkid_free_probe(pr);
+    }
+
+    return NULL;
+}
+
+static void update_partitions(void)
+{
+    struct map_entry *entry = disk_entry.next;
+    struct map_entry *old;
+
+    /* release old partions */
+    pthread_mutex_lock(&entry_lock);
+
+    while (entry) {
+        old = entry;
+        entry = entry->next;
+        if (old->use_counter == 0) {
+            free((void *)old->path);
+            free(old);
+        } else {
+            old->flags = ENTRY_INVALID;
+        }
+    }
+
+    disk_entry.next = NULL;
+    last_entry = &disk_entry;
+
+    disk_entry.flags &= ~ENTRY_DIRTY;
+
+    pthread_mutex_unlock(&entry_lock);
+
+    /* kick off partition table scan */
+    pthread_kill(reader_thread, SIGUSR1);
+}
+
+static void init_reader_thread(void)
+{
+    sigemptyset(&wakeup_sigset);
+    sigaddset(&wakeup_sigset, SIGUSR1);
+    sigprocmask(SIG_BLOCK, &wakeup_sigset, NULL);
+
+    if (pthread_create(&reader_thread, NULL, partition_reader, NULL)) {
+        error("Could not spawn partition reader thread");
+    }
+}
+
+#else /* !CONFIG_BLKID_PARTLIST */
+
+static inline void update_partitions(void) { }
+static inline void init_reader_thread(void) { }
+
+#endif /* !CONFIG_BLKID_PARTLIST */
+
+static struct map_entry *find_map_entry(const char *path)
+{
+    struct map_entry *entry = &disk_entry;
+
+    do {
+        if (strcmp(entry->path, path) == 0) {
+            break;
+        }
+        entry = entry->next;
+    } while (entry);
+
+    return entry;
+}
+
+static void *map_init(struct fuse_conn_info *conn)
+{
+    init_reader_thread();
+    update_partitions();
+    return NULL;
+}
+
+static int map_getattr(const char *path, struct stat *stbuf)
+{
+    struct map_entry *entry;
+    int res = 0;
+
+    memset(stbuf, 0, sizeof(struct stat));
+    stbuf->st_uid   = img_stat.st_uid;
+    stbuf->st_gid   = img_stat.st_gid;
+    stbuf->st_atime = img_stat.st_atime;
+    stbuf->st_mtime = img_stat.st_mtime;
+    stbuf->st_ctime = img_stat.st_ctime;
+
+    if (strcmp(path, "/") == 0) {
+        stbuf->st_mode = S_IFDIR | 0111 | img_stat.st_mode;
+        stbuf->st_nlink = 2;
+    } else {
+        entry = find_map_entry(path);
+        if (entry) {
+            stbuf->st_mode = S_IFREG | img_stat.st_mode;
+            stbuf->st_nlink = 1;
+            stbuf->st_size = entry->size;
+        } else {
+            res = -ENOENT;
+        }
+    }
+
+    return res;
+}
+
+static int map_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+                       off_t offset, struct fuse_file_info *fi)
+{
+    struct map_entry *entry;
+
+    if (strcmp(path, "/") != 0) {
+        return -ENOENT;
+    }
+    filler(buf, ".", NULL, 0);
+    filler(buf, "..", NULL, 0);
+    for (entry = &disk_entry; entry; entry = entry->next) {
+        filler(buf, entry->path+1, NULL, 0);
+    }
+
+    return 0;
+}
+
+static int map_open(const char *path, struct fuse_file_info *fi)
+{
+    struct map_entry *entry = find_map_entry(path);
+
+    if (!entry) {
+        return -ENOENT;
+    }
+
+    entry->use_counter++;
+    fi->fh = (uint64_t)entry;
+
+    return 0;
+}
+
+static int map_release(const char *path, struct fuse_file_info *fi)
+{
+    struct map_entry *entry = (struct map_entry *)fi->fh;
+
+    entry->use_counter--;
+
+    if (entry == &disk_entry && entry->flags & ENTRY_DIRTY) {
+        update_partitions();
+    }
+    if (entry->flags & ENTRY_INVALID && entry->use_counter == 0) {
+        free((void *)entry->path);
+        free(entry);
+    }
+
+    return 0;
+}
+
+static int map_read(const char *path, char *buf, size_t size, off_t offset,
+                    struct fuse_file_info *fi)
+{
+    struct map_entry *entry = (struct map_entry *)fi->fh;
+    int err;
+
+    if (entry->flags & ENTRY_INVALID) {
+        return -ENOENT;
+    }
+
+    if (offset + size > entry->size) {
+        size = entry->size - offset;
+    }
+
+    err = bdrv_read(img_bs, (entry->offset + offset) / BDRV_SECTOR_SIZE,
+                    (uint8_t*)buf, size / BDRV_SECTOR_SIZE);
+    if (err) {
+        return err;
+    }
+
+    return size;
+}
+
+static int map_write(const char *path, const char *buf, size_t size,
+                     off_t offset, struct fuse_file_info *fi)
+{
+    struct map_entry *entry = (struct map_entry *)fi->fh;
+    int err;
+
+    if (entry->flags & ENTRY_INVALID) {
+        return -ENOENT;
+    }
+
+    err = bdrv_write(img_bs, (entry->offset + offset) / BDRV_SECTOR_SIZE,
+                     (uint8_t*)buf, size / BDRV_SECTOR_SIZE);
+    if (err) {
+        return err;
+    }
+
+    entry->flags |= ENTRY_DIRTY;
+
+    return size;
+}
+
+#if FUSE_VERSION >= 28
+static int map_ioctl(const char *path, int cmd, void *arg,
+                     struct fuse_file_info *fi, unsigned int flags, void *data)
+{
+    struct map_entry *entry = (struct map_entry *)fi->fh;
+
+    if (entry->flags & ENTRY_INVALID) {
+        return -ENOENT;
+    }
+
+    switch (cmd) {
+#ifdef CONFIG_LINUX
+    case BLKGETSIZE64:
+        *(uint64_t *)data = entry->size;
+        return 0;
+#endif /* CONFIG_LINUX */
+    default:
+        return -ENOTTY;
+    }
+}
+#endif /* FUSE_VERSION >= 28 */
+
+static struct fuse_operations map_ops = {
+    .init    = map_init,
+    .getattr = map_getattr,
+    .readdir = map_readdir,
+    .open    = map_open,
+    .release = map_release,
+    .read    = map_read,
+    .write   = map_write,
+#if FUSE_VERSION >= 28
+    .ioctl   = map_ioctl,
+#endif
+};
+
+static void QEMU_NORETURN map_help(struct fuse_args *args)
+{
+    printf("usage: qemu-img map [-F fmt] [FUSE options] filename mountpoint\n"
+           "\ngeneral options:\n"
+           "    -o opt,[opt...]         mount options\n"
+           "    -h    --help            print help\n"
+           "    -V    --version         print version\n"
+           "\nqemu-img options:\n"
+           "    -F fmt                  image format\n\n");
+    fuse_opt_add_arg(args, "-ho");
+    fuse_main(args->argc, args->argv, &map_ops, NULL);
+    exit(1);
+}
+
+int img_map(int argc, char **argv)
+{
+    struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+    const char *filename = NULL;
+    const char *fmt = NULL;
+    const char *mountpoint;
+    char *fs_name;
+    uint64_t size;
+
+    fuse_opt_add_arg(&args, argv[0]);
+    fuse_opt_add_arg(&args, "-o");
+    fuse_opt_add_arg(&args, "subtype=qemu-img-map");
+
+    /* block layer is not thread-safe */
+    fuse_opt_add_arg(&args, "-s");
+
+    for (;;) {
+        static const struct option long_opts[] = {
+            { "--help", 0, NULL, 'h' },
+            { "--version", 0, NULL, 'v' },
+            { NULL, 0, NULL, 0 }
+        };
+        int c;
+
+        c = getopt_long(argc, argv, "F:dfsho:", long_opts, NULL);
+        if (c < 0) {
+            break;
+        }
+        switch (c) {
+        case 'h':
+            map_help(&args);
+            break;
+        case 'F':
+            fmt = optarg;
+            break;
+        case 'o':
+            fuse_opt_add_arg(&args, "-o");
+            fuse_opt_add_arg(&args, optarg);
+            break;
+        case 'd':
+            fuse_opt_add_arg(&args, "-d");
+            break;
+        case 'f':
+            fuse_opt_add_arg(&args, "-f");
+            break;
+        default:
+            /* ignore -s, we enforce it anyway */
+            break;
+        }
+    }
+    if (optind + 1 >= argc) {
+        map_help(&args);
+    }
+
+    filename = argv[optind++];
+
+    size = strlen(filename) + 8;
+    fs_name = malloc(size);
+    if (!fs_name) {
+        error("Not enough memory");
+    }
+    snprintf(fs_name, size, "fsname=%s", filename);
+    fuse_opt_insert_arg(&args, 1, "-o");
+    fuse_opt_insert_arg(&args, 2, fs_name);
+    free(fs_name);
+
+    mountpoint = argv[optind];
+    fuse_opt_add_arg(&args, mountpoint);
+
+    size = strlen(mountpoint) + strlen(disk_entry.path) + 1;
+    disk_path = malloc(size);
+    if (!disk_path) {
+        error("Not enough memory");
+    }
+    snprintf(disk_path, size, "%s%s", mountpoint, disk_entry.path);
+
+    if (stat(filename, &img_stat) < 0) {
+        perror("Unable to process image file");
+        exit(1);
+    }
+    img_stat.st_mode &= S_IRWXU | S_IRWXG | S_IRWXO;
+
+    img_bs = bdrv_new_open(filename, fmt, 0);
+    if (!img_bs) {
+        error("Could not open '%s'", filename);
+    }
+    bdrv_get_geometry(img_bs, &size);
+    disk_entry.size = size * BDRV_SECTOR_SIZE;
+
+    return fuse_main(args.argc, args.argv, &map_ops, NULL);
+}
diff --git a/qemu-img.c b/qemu-img.c
index 9b28664..28b8427 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -21,7 +21,7 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-#include "qemu-common.h"
+#include "qemu-img.h"
 #include "qemu-option.h"
 #include "osdep.h"
 #include "block_int.h"
@@ -39,7 +39,7 @@ typedef struct img_cmd_t {
 /* Default to cache=writeback as data integrity is not important for qemu-tcg. */
 #define BRDV_O_FLAGS BDRV_O_CACHE_WB
 
-static void QEMU_NORETURN error(const char *fmt, ...)
+void QEMU_NORETURN error(const char *fmt, ...)
 {
     va_list ap;
     va_start(ap, fmt);
@@ -97,6 +97,9 @@ static void help(void)
     printf("%s\nSupported formats:", help_msg);
     bdrv_iterate_format(format_print, NULL);
     printf("\n");
+#ifdef CONFIG_FUSE
+    printf("\nInvoke 'qemu-img map --help' to list FUSE options.\n");
+#endif
     exit(1);
 }
 
@@ -188,9 +191,9 @@ static int read_password(char *buf, int buf_size)
 }
 #endif
 
-static BlockDriverState *bdrv_new_open(const char *filename,
-                                       const char *fmt,
-                                       int readonly)
+BlockDriverState *bdrv_new_open(const char *filename,
+                                const char *fmt,
+                                int readonly)
 {
     BlockDriverState *bs;
     BlockDriver *drv;
diff --git a/qemu-img.h b/qemu-img.h
new file mode 100644
index 0000000..1bf0f27
--- /dev/null
+++ b/qemu-img.h
@@ -0,0 +1,13 @@
+#ifndef QEMU_IMG_H
+#define QEMU_IMG_H
+
+#include "qemu-common.h"
+
+void QEMU_NORETURN error(const char *fmt, ...);
+BlockDriverState *bdrv_new_open(const char *filename,
+                                const char *fmt,
+                                int readonly);
+
+int img_map(int argc, char **argv);
+
+#endif
diff --git a/qemu-img.texi b/qemu-img.texi
index ac97854..a85f454 100644
--- a/qemu-img.texi
+++ b/qemu-img.texi
@@ -106,6 +106,16 @@ they are displayed too.
 @item snapshot [-l | -a @var{snapshot} | -c @var{snapshot} | -d @var{snapshot} ] @var{filename}
 
 List, apply, create or delete snapshots in image @var{filename}.
+
+@item map [-F @var{fmt}] [@var{FUSE options}] @var{filename} @var{mountpoint}
+
+Make a disk image accessible via pseudo devices under @var{mountpoint}. This
+command will expose the whole raw image as well as individual partitions, the
+latter depending on the parsing capabilies of libblkid. The exposed disk
+device file can be passed to partitioning tools, and any device file containing
+a valid filesystem can be loop-back mounted to access its content (e.g. via
+mountlo without any root privileges). For the full list of FUSE-related
+options, invoke @code{qemu-img map --help}.
 @end table
 
 Supported image file formats:
