diff mbox

[3/7] linux-user: Implement FS_IOC_FIEMAP ioctl

Message ID 285da2b9a83353703d07e141fdb447e82944146c.1294433287.git.riku.voipio@nokia.com
State New
Headers show

Commit Message

Riku Voipio Jan. 7, 2011, 8:52 p.m. UTC
From: Peter Maydell <peter.maydell@linaro.org>

Implement the FS_IOC_FIEMAP ioctl using the new support for
custom handling of ioctls; this is needed because the struct
that is passed includes a variable-length array.

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Riku Voipio <riku.voipio@iki.fi>
---
 linux-user/ioctls.h        |    4 ++
 linux-user/syscall.c       |   88 ++++++++++++++++++++++++++++++++++++++++++++
 linux-user/syscall_defs.h  |    1 +
 linux-user/syscall_types.h |   16 ++++++++
 4 files changed, 109 insertions(+), 0 deletions(-)

Comments

Blue Swirl Jan. 9, 2011, 2:47 p.m. UTC | #1
On Fri, Jan 7, 2011 at 8:52 PM, Riku Voipio <riku.voipio@iki.fi> wrote:
> From: Peter Maydell <peter.maydell@linaro.org>
>
> Implement the FS_IOC_FIEMAP ioctl using the new support for
> custom handling of ioctls; this is needed because the struct
> that is passed includes a variable-length array.
>
> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
> Signed-off-by: Riku Voipio <riku.voipio@iki.fi>
> ---
>  linux-user/ioctls.h        |    4 ++
>  linux-user/syscall.c       |   88 ++++++++++++++++++++++++++++++++++++++++++++
>  linux-user/syscall_defs.h  |    1 +
>  linux-user/syscall_types.h |   16 ++++++++
>  4 files changed, 109 insertions(+), 0 deletions(-)
>
> diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h
> index 769e1bc..538e257 100644
> --- a/linux-user/ioctls.h
> +++ b/linux-user/ioctls.h
> @@ -76,6 +76,10 @@
>  #ifdef FIGETBSZ
>      IOCTL(FIGETBSZ, IOC_R, MK_PTR(TYPE_LONG))
>  #endif
> +#ifdef FS_IOC_FIEMAP
> +     IOCTL_SPECIAL(FS_IOC_FIEMAP, IOC_W | IOC_R, do_ioctl_fs_ioc_fiemap,
> +                   MK_PTR(MK_STRUCT(STRUCT_fiemap)))
> +#endif
>
>   IOCTL(SIOCATMARK, 0, TYPE_NULL)
>   IOCTL(SIOCADDRT, IOC_W, MK_PTR(MK_STRUCT(STRUCT_rtentry)))
> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
> index 970efe3..f10e17a 100644
> --- a/linux-user/syscall.c
> +++ b/linux-user/syscall.c
> @@ -83,6 +83,7 @@ int __clone2(int (*fn)(void *), void *child_stack_base,
>  #include <linux/kd.h>
>  #include <linux/mtio.h>
>  #include <linux/fs.h>
> +#include <linux/fiemap.h>

This fails if the file doesn't exist:
  CC    i386-linux-user/syscall.o
/src/qemu/linux-user/syscall.c:86:26: error: linux/fiemap.h: No such
file or directory
/src/qemu/linux-user/syscall.c: In function 'do_ioctl_fs_ioc_fiemap':
/src/qemu/linux-user/syscall.c:3025: error: dereferencing pointer to
incomplete type
/src/qemu/linux-user/syscall.c:3025: error: invalid application of
'sizeof' to incomplete type 'struct fiemap'
/src/qemu/linux-user/syscall.c:3025: error: invalid application of
'sizeof' to incomplete type 'struct fiemap_extent'
cc1: warnings being treated as errors
/src/qemu/linux-user/syscall.c:3025: error: division by zero
/src/qemu/linux-user/syscall.c:3029: error: dereferencing pointer to
incomplete type
/src/qemu/linux-user/syscall.c:3030: error: invalid application of
'sizeof' to incomplete type 'struct fiemap_extent'
/src/qemu/linux-user/syscall.c:3030: error: dereferencing pointer to
incomplete type
/src/qemu/linux-user/syscall.c:3040: error: invalid application of
'sizeof' to incomplete type 'struct fiemap'
/src/qemu/linux-user/syscall.c:3049: error: dereferencing pointer to
incomplete type
/src/qemu/linux-user/syscall.c:3050: error: dereferencing pointer to
incomplete type
/src/qemu/linux-user/syscall.c:3058: error: dereferencing pointer to
incomplete type
/src/qemu/linux-user/syscall.c:3061: error: dereferencing pointer to
incomplete type
/src/qemu/linux-user/syscall.c:3062: error: dereferencing pointer to
incomplete type

The fix is to introduce a feature check in configure.
diff mbox

Patch

diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h
index 769e1bc..538e257 100644
--- a/linux-user/ioctls.h
+++ b/linux-user/ioctls.h
@@ -76,6 +76,10 @@ 
 #ifdef FIGETBSZ
      IOCTL(FIGETBSZ, IOC_R, MK_PTR(TYPE_LONG))
 #endif
+#ifdef FS_IOC_FIEMAP
+     IOCTL_SPECIAL(FS_IOC_FIEMAP, IOC_W | IOC_R, do_ioctl_fs_ioc_fiemap,
+                   MK_PTR(MK_STRUCT(STRUCT_fiemap)))
+#endif
 
   IOCTL(SIOCATMARK, 0, TYPE_NULL)
   IOCTL(SIOCADDRT, IOC_W, MK_PTR(MK_STRUCT(STRUCT_rtentry)))
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 970efe3..f10e17a 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -83,6 +83,7 @@  int __clone2(int (*fn)(void *), void *child_stack_base,
 #include <linux/kd.h>
 #include <linux/mtio.h>
 #include <linux/fs.h>
+#include <linux/fiemap.h>
 #include <linux/fb.h>
 #include <linux/vt.h>
 #include "linux_loop.h"
@@ -2985,6 +2986,93 @@  struct IOCTLEntry {
 
 #define MAX_STRUCT_SIZE 4096
 
+/* So fiemap access checks don't overflow on 32 bit systems.
+ * This is very slightly smaller than the limit imposed by
+ * the underlying kernel.
+ */
+#define FIEMAP_MAX_EXTENTS ((UINT_MAX - sizeof(struct fiemap))  \
+                            / sizeof(struct fiemap_extent))
+
+static abi_long do_ioctl_fs_ioc_fiemap(const IOCTLEntry *ie, uint8_t *buf_temp,
+                                       int fd, abi_long cmd, abi_long arg)
+{
+    /* The parameter for this ioctl is a struct fiemap followed
+     * by an array of struct fiemap_extent whose size is set
+     * in fiemap->fm_extent_count. The array is filled in by the
+     * ioctl.
+     */
+    int target_size_in, target_size_out;
+    struct fiemap *fm;
+    const argtype *arg_type = ie->arg_type;
+    const argtype extent_arg_type[] = { MK_STRUCT(STRUCT_fiemap_extent) };
+    void *argptr, *p;
+    abi_long ret;
+    int i, extent_size = thunk_type_size(extent_arg_type, 0);
+    uint32_t outbufsz;
+    int free_fm = 0;
+
+    assert(arg_type[0] == TYPE_PTR);
+    assert(ie->access == IOC_RW);
+    arg_type++;
+    target_size_in = thunk_type_size(arg_type, 0);
+    argptr = lock_user(VERIFY_READ, arg, target_size_in, 1);
+    if (!argptr) {
+        return -TARGET_EFAULT;
+    }
+    thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST);
+    unlock_user(argptr, arg, 0);
+    fm = (struct fiemap *)buf_temp;
+    if (fm->fm_extent_count > FIEMAP_MAX_EXTENTS) {
+        return -TARGET_EINVAL;
+    }
+
+    outbufsz = sizeof (*fm) +
+        (sizeof(struct fiemap_extent) * fm->fm_extent_count);
+
+    if (outbufsz > MAX_STRUCT_SIZE) {
+        /* We can't fit all the extents into the fixed size buffer.
+         * Allocate one that is large enough and use it instead.
+         */
+        fm = malloc(outbufsz);
+        if (!fm) {
+            return -TARGET_ENOMEM;
+        }
+        memcpy(fm, buf_temp, sizeof(struct fiemap));
+        free_fm = 1;
+    }
+    ret = get_errno(ioctl(fd, ie->host_cmd, fm));
+    if (!is_error(ret)) {
+        target_size_out = target_size_in;
+        /* An extent_count of 0 means we were only counting the extents
+         * so there are no structs to copy
+         */
+        if (fm->fm_extent_count != 0) {
+            target_size_out += fm->fm_mapped_extents * extent_size;
+        }
+        argptr = lock_user(VERIFY_WRITE, arg, target_size_out, 0);
+        if (!argptr) {
+            ret = -TARGET_EFAULT;
+        } else {
+            /* Convert the struct fiemap */
+            thunk_convert(argptr, fm, arg_type, THUNK_TARGET);
+            if (fm->fm_extent_count != 0) {
+                p = argptr + target_size_in;
+                /* ...and then all the struct fiemap_extents */
+                for (i = 0; i < fm->fm_mapped_extents; i++) {
+                    thunk_convert(p, &fm->fm_extents[i], extent_arg_type,
+                                  THUNK_TARGET);
+                    p += extent_size;
+                }
+            }
+            unlock_user(argptr, arg, target_size_out);
+        }
+    }
+    if (free_fm) {
+        free(fm);
+    }
+    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 20c93d0..d02a9bf 100644
--- a/linux-user/syscall_defs.h
+++ b/linux-user/syscall_defs.h
@@ -783,6 +783,7 @@  struct target_pollfd {
 #define TARGET_BLKGETSIZE64 TARGET_IOR(0x12,114,sizeof(uint64_t)) /* return device size in bytes (u64 *arg) */
 #define TARGET_FIBMAP     TARGET_IO(0x00,1)  /* bmap access */
 #define TARGET_FIGETBSZ   TARGET_IO(0x00,2)  /* get the block size used for bmap */
+#define TARGET_FS_IOC_FIEMAP TARGET_IOWR('f',11,struct fiemap)
 
 /* cdrom commands */
 #define TARGET_CDROMPAUSE		0x5301 /* Pause Audio Operation */
diff --git a/linux-user/syscall_types.h b/linux-user/syscall_types.h
index 340dbd3..0e67cd8 100644
--- a/linux-user/syscall_types.h
+++ b/linux-user/syscall_types.h
@@ -165,3 +165,19 @@  STRUCT(vt_stat,
        TYPE_SHORT, /* v_active */
        TYPE_SHORT, /* v_signal */
        TYPE_SHORT) /* v_state */
+
+STRUCT(fiemap_extent,
+       TYPE_ULONGLONG, /* fe_logical */
+       TYPE_ULONGLONG, /* fe_physical */
+       TYPE_ULONGLONG, /* fe_length */
+       MK_ARRAY(TYPE_ULONGLONG, 2), /* fe_reserved64[2] */
+       TYPE_INT, /* fe_flags */
+       MK_ARRAY(TYPE_INT, 3)) /* fe_reserved[3] */
+
+STRUCT(fiemap,
+       TYPE_ULONGLONG, /* fm_start */
+       TYPE_ULONGLONG, /* fm_length */
+       TYPE_INT, /* fm_flags */
+       TYPE_INT, /* fm_mapped_extents */
+       TYPE_INT, /* fm_extent_count */
+       TYPE_INT) /* fm_reserved */