Patchwork [1/4] linux-user: implement device mapper ioctls

login
register
mail settings
Submitter Alexander Graf
Date Jan. 31, 2012, 9:49 p.m.
Message ID <1328046591-1641-2-git-send-email-agraf@suse.de>
Download mbox | patch
Permalink /patch/138880/
State New
Headers show

Comments

Alexander Graf - Jan. 31, 2012, 9:49 p.m.
This patch implements all ioctls currently implemented by device mapper,
enabling us to run dmsetup and kpartx inside of linux-user.

Signed-off-by: Alexander Graf <agraf@suse.de>
---
 linux-user/ioctls.h        |   33 +++++++
 linux-user/syscall.c       |  226 ++++++++++++++++++++++++++++++++++++++++++++
 linux-user/syscall_defs.h  |   18 ++++
 linux-user/syscall_types.h |   36 +++++++
 4 files changed, 313 insertions(+), 0 deletions(-)
Alexander Graf - Feb. 6, 2012, 3:06 p.m.
On 31.01.2012, at 22:49, Alexander Graf wrote:

> This patch implements all ioctls currently implemented by device mapper,
> enabling us to run dmsetup and kpartx inside of linux-user.

Hi Alasdair,

Could you please have a quick glimpse through this wrapper and check that I handled the ioctls correctly?

Thanks!


Alex

> 
> Signed-off-by: Alexander Graf <agraf@suse.de>
> ---
> linux-user/ioctls.h        |   33 +++++++
> linux-user/syscall.c       |  226 ++++++++++++++++++++++++++++++++++++++++++++
> linux-user/syscall_defs.h  |   18 ++++
> linux-user/syscall_types.h |   36 +++++++
> 4 files changed, 313 insertions(+), 0 deletions(-)
> 
> diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h
> index 6514502..a9d333a 100644
> --- a/linux-user/ioctls.h
> +++ b/linux-user/ioctls.h
> @@ -345,3 +345,36 @@
>   IOCTL(VT_SETMODE, IOC_RW, MK_PTR(MK_STRUCT(STRUCT_vt_mode)))
>   IOCTL(VT_RELDISP, 0, TYPE_INT)
>   IOCTL(VT_DISALLOCATE, 0, TYPE_INT)
> +
> +  IOCTL(DM_VERSION, IOC_RW, MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_REMOVE_ALL,   IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_LIST_DEVICES, IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_DEV_CREATE,   IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_DEV_REMOVE,   IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_DEV_RENAME,   IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_DEV_SUSPEND,  IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_DEV_STATUS,   IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_DEV_WAIT,     IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_TABLE_LOAD,   IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_TABLE_CLEAR,  IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_TABLE_DEPS,   IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_TABLE_STATUS, IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_LIST_VERSIONS,IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_TARGET_MSG,   IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +  IOCTL_SPECIAL(DM_DEV_SET_GEOMETRY, IOC_RW, do_ioctl_dm,
> +                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
> +
> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
> index 2bf9e7e..27a3131 100644
> --- a/linux-user/syscall.c
> +++ b/linux-user/syscall.c
> @@ -95,6 +95,7 @@ int __clone2(int (*fn)(void *), void *child_stack_base,
> #endif
> #include <linux/fb.h>
> #include <linux/vt.h>
> +#include <linux/dm-ioctl.h>
> #include "linux_loop.h"
> #include "cpu-uname.h"
> 
> @@ -3317,6 +3318,231 @@ static abi_long do_ioctl_ifconf(const IOCTLEntry *ie, uint8_t *buf_temp,
>     return ret;
> }
> 
> +static abi_long do_ioctl_dm(const IOCTLEntry *ie, uint8_t *buf_temp, int fd,
> +                            abi_long cmd, abi_long arg)
> +{
> +    void *argptr;
> +    struct dm_ioctl *host_dm;
> +    abi_long guest_data;
> +    uint32_t guest_data_size;
> +    int target_size;
> +    const argtype *arg_type = ie->arg_type;
> +    abi_long ret;
> +    void *big_buf = NULL;
> +    char *host_data;
> +
> +    arg_type++;
> +    target_size = thunk_type_size(arg_type, 0);
> +    argptr = lock_user(VERIFY_READ, arg, target_size, 1);
> +    if (!argptr) {
> +        ret = -TARGET_EFAULT;
> +        goto out;
> +    }
> +    thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST);
> +    unlock_user(argptr, arg, 0);
> +
> +    /* buf_temp is too small, so fetch things into a bigger buffer */
> +    big_buf = g_malloc0(((struct dm_ioctl*)buf_temp)->data_size * 2);
> +    memcpy(big_buf, buf_temp, target_size);
> +    buf_temp = big_buf;
> +    host_dm = big_buf;
> +
> +    guest_data = arg + host_dm->data_start;
> +    if ((guest_data - arg) < 0) {
> +        ret = -EINVAL;
> +        goto out;
> +    }
> +    guest_data_size = host_dm->data_size - host_dm->data_start;
> +    host_data = (char*)host_dm + host_dm->data_start;
> +
> +    argptr = lock_user(VERIFY_READ, guest_data, guest_data_size, 1);
> +    switch (ie->host_cmd) {
> +    case DM_REMOVE_ALL:
> +    case DM_LIST_DEVICES:
> +    case DM_DEV_CREATE:
> +    case DM_DEV_REMOVE:
> +    case DM_DEV_SUSPEND:
> +    case DM_DEV_STATUS:
> +    case DM_DEV_WAIT:
> +    case DM_TABLE_STATUS:
> +    case DM_TABLE_CLEAR:
> +    case DM_TABLE_DEPS:
> +    case DM_LIST_VERSIONS:
> +        /* no input data */
> +        break;
> +    case DM_DEV_RENAME:
> +    case DM_DEV_SET_GEOMETRY:
> +        /* data contains only strings */
> +        memcpy(host_data, argptr, guest_data_size);
> +        break;
> +    case DM_TARGET_MSG:
> +        memcpy(host_data, argptr, guest_data_size);
> +        *(uint64_t*)host_data = tswap64(*(uint64_t*)argptr);
> +        break;
> +    case DM_TABLE_LOAD:
> +    {
> +        void *gspec = argptr;
> +        void *cur_data = host_data;
> +        const argtype arg_type[] = { MK_STRUCT(STRUCT_dm_target_spec) };
> +        int spec_size = thunk_type_size(arg_type, 0);
> +        int i;
> +
> +        for (i = 0; i < host_dm->target_count; i++) {
> +            struct dm_target_spec *spec = cur_data;
> +            uint32_t next;
> +            int slen;
> +
> +            thunk_convert(spec, gspec, arg_type, THUNK_HOST);
> +            slen = strlen((char*)gspec + spec_size) + 1;
> +            next = spec->next;
> +            spec->next = sizeof(*spec) + slen;
> +            strcpy((char*)&spec[1], gspec + spec_size);
> +            gspec += next;
> +            cur_data += spec->next;
> +        }
> +        break;
> +    }
> +    default:
> +        ret = -TARGET_EINVAL;
> +        goto out;
> +    }
> +    unlock_user(argptr, guest_data, 0);
> +
> +    ret = get_errno(ioctl(fd, ie->host_cmd, buf_temp));
> +    if (!is_error(ret)) {
> +        guest_data = arg + host_dm->data_start;
> +        guest_data_size = host_dm->data_size - host_dm->data_start;
> +        argptr = lock_user(VERIFY_WRITE, guest_data, guest_data_size, 0);
> +        switch (ie->host_cmd) {
> +        case DM_REMOVE_ALL:
> +        case DM_DEV_CREATE:
> +        case DM_DEV_REMOVE:
> +        case DM_DEV_RENAME:
> +        case DM_DEV_SUSPEND:
> +        case DM_DEV_STATUS:
> +        case DM_TABLE_LOAD:
> +        case DM_TABLE_CLEAR:
> +        case DM_TARGET_MSG:
> +        case DM_DEV_SET_GEOMETRY:
> +            /* no return data */
> +            break;
> +        case DM_LIST_DEVICES:
> +        {
> +            struct dm_name_list *nl = (void*)host_dm + host_dm->data_start;
> +            uint32_t remaining_data = guest_data_size;
> +            void *cur_data = argptr;
> +            const argtype arg_type[] = { MK_STRUCT(STRUCT_dm_name_list) };
> +            int nl_size = 12; /* can't use thunk_size due to alignment */
> +
> +            while (1) {
> +                uint32_t next = nl->next;
> +                if (next) {
> +                    nl->next = nl_size + (strlen(nl->name) + 1);
> +                }
> +                if (remaining_data < nl->next) {
> +                    host_dm->flags |= DM_BUFFER_FULL_FLAG;
> +                    break;
> +                }
> +                thunk_convert(cur_data, nl, arg_type, THUNK_TARGET);
> +                strcpy(cur_data + nl_size, nl->name);
> +                cur_data += nl->next;
> +                remaining_data -= nl->next;
> +                if (!next) {
> +                    break;
> +                }
> +                nl = (void*)nl + next;
> +            }
> +            break;
> +        }
> +        case DM_DEV_WAIT:
> +        case DM_TABLE_STATUS:
> +        {
> +            struct dm_target_spec *spec = (void*)host_dm + host_dm->data_start;
> +            void *cur_data = argptr;
> +            const argtype arg_type[] = { MK_STRUCT(STRUCT_dm_target_spec) };
> +            int spec_size = thunk_type_size(arg_type, 0);
> +            int i;
> +
> +            for (i = 0; i < host_dm->target_count; i++) {
> +                uint32_t next = spec->next;
> +                int slen = strlen((char*)&spec[1]) + 1;
> +                spec->next = (cur_data - argptr) + spec_size + slen;
> +                if (guest_data_size < spec->next) {
> +                    host_dm->flags |= DM_BUFFER_FULL_FLAG;
> +                    break;
> +                }
> +                thunk_convert(cur_data, spec, arg_type, THUNK_TARGET);
> +                strcpy(cur_data + spec_size, (char*)&spec[1]);
> +                cur_data = argptr + spec->next;
> +                spec = (void*)host_dm + host_dm->data_start + next;
> +            }
> +            break;
> +        }
> +        case DM_TABLE_DEPS:
> +        {
> +            void *hdata = (void*)host_dm + host_dm->data_start;
> +            int count = *(uint32_t*)hdata;
> +            uint64_t *hdev = hdata + 8;
> +            uint64_t *gdev = argptr + 8;
> +            int i;
> +
> +            *(uint32_t*)argptr = tswap32(count);
> +            for (i = 0; i < count; i++) {
> +                *gdev = tswap64(*hdev);
> +                gdev++;
> +                hdev++;
> +            }
> +            break;
> +        }
> +        case DM_LIST_VERSIONS:
> +        {
> +            struct dm_target_versions *vers = (void*)host_dm + host_dm->data_start;
> +            uint32_t remaining_data = guest_data_size;
> +            void *cur_data = argptr;
> +            const argtype arg_type[] = { MK_STRUCT(STRUCT_dm_target_versions) };
> +            int vers_size = thunk_type_size(arg_type, 0);
> +
> +            while (1) {
> +                uint32_t next = vers->next;
> +                if (next) {
> +                    vers->next = vers_size + (strlen(vers->name) + 1);
> +                }
> +                if (remaining_data < vers->next) {
> +                    host_dm->flags |= DM_BUFFER_FULL_FLAG;
> +                    break;
> +                }
> +                thunk_convert(cur_data, vers, arg_type, THUNK_TARGET);
> +                strcpy(cur_data + vers_size, vers->name);
> +                cur_data += vers->next;
> +                remaining_data -= vers->next;
> +                if (!next) {
> +                    break;
> +                }
> +                vers = (void*)vers + next;
> +            }
> +            break;
> +        }
> +        default:
> +            ret = -TARGET_EINVAL;
> +            goto out;
> +        }
> +        unlock_user(argptr, guest_data, guest_data_size);
> +
> +        argptr = lock_user(VERIFY_WRITE, arg, target_size, 0);
> +        if (!argptr) {
> +            ret = -TARGET_EFAULT;
> +            goto out;
> +        }
> +        thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET);
> +        unlock_user(argptr, arg, target_size);
> +    }
> +out:
> +    if (big_buf) {
> +        free(big_buf);
> +    }
> +    return ret;
> +}
> +
> static IOCTLEntry ioctl_entries[] = {
> #define IOCTL(cmd, access, ...) \
>     { TARGET_ ## cmd, cmd, #cmd, access, 0, {  __VA_ARGS__ } },
> diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h
> index 2857805..f5e0c41 100644
> --- a/linux-user/syscall_defs.h
> +++ b/linux-user/syscall_defs.h
> @@ -989,6 +989,24 @@ struct target_pollfd {
> #define TARGET_VT_RELDISP             0x5605
> #define TARGET_VT_DISALLOCATE         0x5608
> 
> +/* device mapper */
> +#define TARGET_DM_VERSION             TARGET_IOWRU(0xfd, 0x00)
> +#define TARGET_DM_REMOVE_ALL          TARGET_IOWRU(0xfd, 0x01)
> +#define TARGET_DM_LIST_DEVICES        TARGET_IOWRU(0xfd, 0x02)
> +#define TARGET_DM_DEV_CREATE          TARGET_IOWRU(0xfd, 0x03)
> +#define TARGET_DM_DEV_REMOVE          TARGET_IOWRU(0xfd, 0x04)
> +#define TARGET_DM_DEV_RENAME          TARGET_IOWRU(0xfd, 0x05)
> +#define TARGET_DM_DEV_SUSPEND         TARGET_IOWRU(0xfd, 0x06)
> +#define TARGET_DM_DEV_STATUS          TARGET_IOWRU(0xfd, 0x07)
> +#define TARGET_DM_DEV_WAIT            TARGET_IOWRU(0xfd, 0x08)
> +#define TARGET_DM_TABLE_LOAD          TARGET_IOWRU(0xfd, 0x09)
> +#define TARGET_DM_TABLE_CLEAR         TARGET_IOWRU(0xfd, 0x0a)
> +#define TARGET_DM_TABLE_DEPS          TARGET_IOWRU(0xfd, 0x0b)
> +#define TARGET_DM_TABLE_STATUS        TARGET_IOWRU(0xfd, 0x0c)
> +#define TARGET_DM_LIST_VERSIONS       TARGET_IOWRU(0xfd, 0x0d)
> +#define TARGET_DM_TARGET_MSG          TARGET_IOWRU(0xfd, 0x0e)
> +#define TARGET_DM_DEV_SET_GEOMETRY    TARGET_IOWRU(0xfd, 0x0f)
> +
> /* from asm/termbits.h */
> 
> #define TARGET_NCC 8
> diff --git a/linux-user/syscall_types.h b/linux-user/syscall_types.h
> index c370125..fb8c9c9 100644
> --- a/linux-user/syscall_types.h
> +++ b/linux-user/syscall_types.h
> @@ -186,6 +186,42 @@ STRUCT(vt_mode,
>        TYPE_SHORT, /* acqsig */
>        TYPE_SHORT) /* frsig  */
> 
> +STRUCT(dm_ioctl,
> +       MK_ARRAY(TYPE_INT, 3), /* version */
> +       TYPE_INT, /* data_size */
> +       TYPE_INT, /* data_start */
> +       TYPE_INT, /* target_count*/
> +       TYPE_INT, /* open_count */
> +       TYPE_INT, /* flags */
> +       TYPE_INT, /* event_nr */
> +       TYPE_INT, /* padding */
> +       TYPE_ULONGLONG, /* dev */
> +       MK_ARRAY(TYPE_CHAR, 128), /* name */
> +       MK_ARRAY(TYPE_CHAR, 129), /* uuid */
> +       MK_ARRAY(TYPE_CHAR, 7)) /* data */
> +
> +STRUCT(dm_target_spec,
> +       TYPE_ULONGLONG, /* sector_start */
> +       TYPE_ULONGLONG, /* length */
> +       TYPE_INT, /* status */
> +       TYPE_INT, /* next */
> +       MK_ARRAY(TYPE_CHAR, 16)) /* target_type */
> +
> +STRUCT(dm_target_deps,
> +       TYPE_INT, /* count */
> +       TYPE_INT) /* padding */
> +
> +STRUCT(dm_name_list,
> +       TYPE_ULONGLONG, /* dev */
> +       TYPE_INT) /* next */
> +
> +STRUCT(dm_target_versions,
> +       TYPE_INT, /* next */
> +       MK_ARRAY(TYPE_INT, 3)) /* version*/
> +
> +STRUCT(dm_target_msg,
> +       TYPE_ULONGLONG) /* sector */
> +
> STRUCT(fiemap_extent,
>        TYPE_ULONGLONG, /* fe_logical */
>        TYPE_ULONGLONG, /* fe_physical */
> -- 
> 1.6.0.2
> 
>

Patch

diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h
index 6514502..a9d333a 100644
--- a/linux-user/ioctls.h
+++ b/linux-user/ioctls.h
@@ -345,3 +345,36 @@ 
   IOCTL(VT_SETMODE, IOC_RW, MK_PTR(MK_STRUCT(STRUCT_vt_mode)))
   IOCTL(VT_RELDISP, 0, TYPE_INT)
   IOCTL(VT_DISALLOCATE, 0, TYPE_INT)
+
+  IOCTL(DM_VERSION, IOC_RW, MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_REMOVE_ALL,   IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_LIST_DEVICES, IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_DEV_CREATE,   IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_DEV_REMOVE,   IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_DEV_RENAME,   IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_DEV_SUSPEND,  IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_DEV_STATUS,   IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_DEV_WAIT,     IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_TABLE_LOAD,   IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_TABLE_CLEAR,  IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_TABLE_DEPS,   IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_TABLE_STATUS, IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_LIST_VERSIONS,IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_TARGET_MSG,   IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+  IOCTL_SPECIAL(DM_DEV_SET_GEOMETRY, IOC_RW, do_ioctl_dm,
+                MK_PTR(MK_STRUCT(STRUCT_dm_ioctl)))
+
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 2bf9e7e..27a3131 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -95,6 +95,7 @@  int __clone2(int (*fn)(void *), void *child_stack_base,
 #endif
 #include <linux/fb.h>
 #include <linux/vt.h>
+#include <linux/dm-ioctl.h>
 #include "linux_loop.h"
 #include "cpu-uname.h"
 
@@ -3317,6 +3318,231 @@  static abi_long do_ioctl_ifconf(const IOCTLEntry *ie, uint8_t *buf_temp,
     return ret;
 }
 
+static abi_long do_ioctl_dm(const IOCTLEntry *ie, uint8_t *buf_temp, int fd,
+                            abi_long cmd, abi_long arg)
+{
+    void *argptr;
+    struct dm_ioctl *host_dm;
+    abi_long guest_data;
+    uint32_t guest_data_size;
+    int target_size;
+    const argtype *arg_type = ie->arg_type;
+    abi_long ret;
+    void *big_buf = NULL;
+    char *host_data;
+
+    arg_type++;
+    target_size = thunk_type_size(arg_type, 0);
+    argptr = lock_user(VERIFY_READ, arg, target_size, 1);
+    if (!argptr) {
+        ret = -TARGET_EFAULT;
+        goto out;
+    }
+    thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST);
+    unlock_user(argptr, arg, 0);
+
+    /* buf_temp is too small, so fetch things into a bigger buffer */
+    big_buf = g_malloc0(((struct dm_ioctl*)buf_temp)->data_size * 2);
+    memcpy(big_buf, buf_temp, target_size);
+    buf_temp = big_buf;
+    host_dm = big_buf;
+
+    guest_data = arg + host_dm->data_start;
+    if ((guest_data - arg) < 0) {
+        ret = -EINVAL;
+        goto out;
+    }
+    guest_data_size = host_dm->data_size - host_dm->data_start;
+    host_data = (char*)host_dm + host_dm->data_start;
+
+    argptr = lock_user(VERIFY_READ, guest_data, guest_data_size, 1);
+    switch (ie->host_cmd) {
+    case DM_REMOVE_ALL:
+    case DM_LIST_DEVICES:
+    case DM_DEV_CREATE:
+    case DM_DEV_REMOVE:
+    case DM_DEV_SUSPEND:
+    case DM_DEV_STATUS:
+    case DM_DEV_WAIT:
+    case DM_TABLE_STATUS:
+    case DM_TABLE_CLEAR:
+    case DM_TABLE_DEPS:
+    case DM_LIST_VERSIONS:
+        /* no input data */
+        break;
+    case DM_DEV_RENAME:
+    case DM_DEV_SET_GEOMETRY:
+        /* data contains only strings */
+        memcpy(host_data, argptr, guest_data_size);
+        break;
+    case DM_TARGET_MSG:
+        memcpy(host_data, argptr, guest_data_size);
+        *(uint64_t*)host_data = tswap64(*(uint64_t*)argptr);
+        break;
+    case DM_TABLE_LOAD:
+    {
+        void *gspec = argptr;
+        void *cur_data = host_data;
+        const argtype arg_type[] = { MK_STRUCT(STRUCT_dm_target_spec) };
+        int spec_size = thunk_type_size(arg_type, 0);
+        int i;
+
+        for (i = 0; i < host_dm->target_count; i++) {
+            struct dm_target_spec *spec = cur_data;
+            uint32_t next;
+            int slen;
+
+            thunk_convert(spec, gspec, arg_type, THUNK_HOST);
+            slen = strlen((char*)gspec + spec_size) + 1;
+            next = spec->next;
+            spec->next = sizeof(*spec) + slen;
+            strcpy((char*)&spec[1], gspec + spec_size);
+            gspec += next;
+            cur_data += spec->next;
+        }
+        break;
+    }
+    default:
+        ret = -TARGET_EINVAL;
+        goto out;
+    }
+    unlock_user(argptr, guest_data, 0);
+
+    ret = get_errno(ioctl(fd, ie->host_cmd, buf_temp));
+    if (!is_error(ret)) {
+        guest_data = arg + host_dm->data_start;
+        guest_data_size = host_dm->data_size - host_dm->data_start;
+        argptr = lock_user(VERIFY_WRITE, guest_data, guest_data_size, 0);
+        switch (ie->host_cmd) {
+        case DM_REMOVE_ALL:
+        case DM_DEV_CREATE:
+        case DM_DEV_REMOVE:
+        case DM_DEV_RENAME:
+        case DM_DEV_SUSPEND:
+        case DM_DEV_STATUS:
+        case DM_TABLE_LOAD:
+        case DM_TABLE_CLEAR:
+        case DM_TARGET_MSG:
+        case DM_DEV_SET_GEOMETRY:
+            /* no return data */
+            break;
+        case DM_LIST_DEVICES:
+        {
+            struct dm_name_list *nl = (void*)host_dm + host_dm->data_start;
+            uint32_t remaining_data = guest_data_size;
+            void *cur_data = argptr;
+            const argtype arg_type[] = { MK_STRUCT(STRUCT_dm_name_list) };
+            int nl_size = 12; /* can't use thunk_size due to alignment */
+
+            while (1) {
+                uint32_t next = nl->next;
+                if (next) {
+                    nl->next = nl_size + (strlen(nl->name) + 1);
+                }
+                if (remaining_data < nl->next) {
+                    host_dm->flags |= DM_BUFFER_FULL_FLAG;
+                    break;
+                }
+                thunk_convert(cur_data, nl, arg_type, THUNK_TARGET);
+                strcpy(cur_data + nl_size, nl->name);
+                cur_data += nl->next;
+                remaining_data -= nl->next;
+                if (!next) {
+                    break;
+                }
+                nl = (void*)nl + next;
+            }
+            break;
+        }
+        case DM_DEV_WAIT:
+        case DM_TABLE_STATUS:
+        {
+            struct dm_target_spec *spec = (void*)host_dm + host_dm->data_start;
+            void *cur_data = argptr;
+            const argtype arg_type[] = { MK_STRUCT(STRUCT_dm_target_spec) };
+            int spec_size = thunk_type_size(arg_type, 0);
+            int i;
+
+            for (i = 0; i < host_dm->target_count; i++) {
+                uint32_t next = spec->next;
+                int slen = strlen((char*)&spec[1]) + 1;
+                spec->next = (cur_data - argptr) + spec_size + slen;
+                if (guest_data_size < spec->next) {
+                    host_dm->flags |= DM_BUFFER_FULL_FLAG;
+                    break;
+                }
+                thunk_convert(cur_data, spec, arg_type, THUNK_TARGET);
+                strcpy(cur_data + spec_size, (char*)&spec[1]);
+                cur_data = argptr + spec->next;
+                spec = (void*)host_dm + host_dm->data_start + next;
+            }
+            break;
+        }
+        case DM_TABLE_DEPS:
+        {
+            void *hdata = (void*)host_dm + host_dm->data_start;
+            int count = *(uint32_t*)hdata;
+            uint64_t *hdev = hdata + 8;
+            uint64_t *gdev = argptr + 8;
+            int i;
+
+            *(uint32_t*)argptr = tswap32(count);
+            for (i = 0; i < count; i++) {
+                *gdev = tswap64(*hdev);
+                gdev++;
+                hdev++;
+            }
+            break;
+        }
+        case DM_LIST_VERSIONS:
+        {
+            struct dm_target_versions *vers = (void*)host_dm + host_dm->data_start;
+            uint32_t remaining_data = guest_data_size;
+            void *cur_data = argptr;
+            const argtype arg_type[] = { MK_STRUCT(STRUCT_dm_target_versions) };
+            int vers_size = thunk_type_size(arg_type, 0);
+
+            while (1) {
+                uint32_t next = vers->next;
+                if (next) {
+                    vers->next = vers_size + (strlen(vers->name) + 1);
+                }
+                if (remaining_data < vers->next) {
+                    host_dm->flags |= DM_BUFFER_FULL_FLAG;
+                    break;
+                }
+                thunk_convert(cur_data, vers, arg_type, THUNK_TARGET);
+                strcpy(cur_data + vers_size, vers->name);
+                cur_data += vers->next;
+                remaining_data -= vers->next;
+                if (!next) {
+                    break;
+                }
+                vers = (void*)vers + next;
+            }
+            break;
+        }
+        default:
+            ret = -TARGET_EINVAL;
+            goto out;
+        }
+        unlock_user(argptr, guest_data, guest_data_size);
+
+        argptr = lock_user(VERIFY_WRITE, arg, target_size, 0);
+        if (!argptr) {
+            ret = -TARGET_EFAULT;
+            goto out;
+        }
+        thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET);
+        unlock_user(argptr, arg, target_size);
+    }
+out:
+    if (big_buf) {
+        free(big_buf);
+    }
+    return ret;
+}
+
 static IOCTLEntry ioctl_entries[] = {
 #define IOCTL(cmd, access, ...) \
     { TARGET_ ## cmd, cmd, #cmd, access, 0, {  __VA_ARGS__ } },
diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h
index 2857805..f5e0c41 100644
--- a/linux-user/syscall_defs.h
+++ b/linux-user/syscall_defs.h
@@ -989,6 +989,24 @@  struct target_pollfd {
 #define TARGET_VT_RELDISP             0x5605
 #define TARGET_VT_DISALLOCATE         0x5608
 
+/* device mapper */
+#define TARGET_DM_VERSION             TARGET_IOWRU(0xfd, 0x00)
+#define TARGET_DM_REMOVE_ALL          TARGET_IOWRU(0xfd, 0x01)
+#define TARGET_DM_LIST_DEVICES        TARGET_IOWRU(0xfd, 0x02)
+#define TARGET_DM_DEV_CREATE          TARGET_IOWRU(0xfd, 0x03)
+#define TARGET_DM_DEV_REMOVE          TARGET_IOWRU(0xfd, 0x04)
+#define TARGET_DM_DEV_RENAME          TARGET_IOWRU(0xfd, 0x05)
+#define TARGET_DM_DEV_SUSPEND         TARGET_IOWRU(0xfd, 0x06)
+#define TARGET_DM_DEV_STATUS          TARGET_IOWRU(0xfd, 0x07)
+#define TARGET_DM_DEV_WAIT            TARGET_IOWRU(0xfd, 0x08)
+#define TARGET_DM_TABLE_LOAD          TARGET_IOWRU(0xfd, 0x09)
+#define TARGET_DM_TABLE_CLEAR         TARGET_IOWRU(0xfd, 0x0a)
+#define TARGET_DM_TABLE_DEPS          TARGET_IOWRU(0xfd, 0x0b)
+#define TARGET_DM_TABLE_STATUS        TARGET_IOWRU(0xfd, 0x0c)
+#define TARGET_DM_LIST_VERSIONS       TARGET_IOWRU(0xfd, 0x0d)
+#define TARGET_DM_TARGET_MSG          TARGET_IOWRU(0xfd, 0x0e)
+#define TARGET_DM_DEV_SET_GEOMETRY    TARGET_IOWRU(0xfd, 0x0f)
+
 /* from asm/termbits.h */
 
 #define TARGET_NCC 8
diff --git a/linux-user/syscall_types.h b/linux-user/syscall_types.h
index c370125..fb8c9c9 100644
--- a/linux-user/syscall_types.h
+++ b/linux-user/syscall_types.h
@@ -186,6 +186,42 @@  STRUCT(vt_mode,
        TYPE_SHORT, /* acqsig */
        TYPE_SHORT) /* frsig  */
 
+STRUCT(dm_ioctl,
+       MK_ARRAY(TYPE_INT, 3), /* version */
+       TYPE_INT, /* data_size */
+       TYPE_INT, /* data_start */
+       TYPE_INT, /* target_count*/
+       TYPE_INT, /* open_count */
+       TYPE_INT, /* flags */
+       TYPE_INT, /* event_nr */
+       TYPE_INT, /* padding */
+       TYPE_ULONGLONG, /* dev */
+       MK_ARRAY(TYPE_CHAR, 128), /* name */
+       MK_ARRAY(TYPE_CHAR, 129), /* uuid */
+       MK_ARRAY(TYPE_CHAR, 7)) /* data */
+
+STRUCT(dm_target_spec,
+       TYPE_ULONGLONG, /* sector_start */
+       TYPE_ULONGLONG, /* length */
+       TYPE_INT, /* status */
+       TYPE_INT, /* next */
+       MK_ARRAY(TYPE_CHAR, 16)) /* target_type */
+
+STRUCT(dm_target_deps,
+       TYPE_INT, /* count */
+       TYPE_INT) /* padding */
+
+STRUCT(dm_name_list,
+       TYPE_ULONGLONG, /* dev */
+       TYPE_INT) /* next */
+
+STRUCT(dm_target_versions,
+       TYPE_INT, /* next */
+       MK_ARRAY(TYPE_INT, 3)) /* version*/
+
+STRUCT(dm_target_msg,
+       TYPE_ULONGLONG) /* sector */
+
 STRUCT(fiemap_extent,
        TYPE_ULONGLONG, /* fe_logical */
        TYPE_ULONGLONG, /* fe_physical */