From b769aac7fdffa0b03d54ade692a043ae00fade1e Mon Sep 17 00:00:00 2001
From: Chunyan Liu <cyliu@suse.com>
Date: Wed, 2 Apr 2014 01:15:00 -0600
Subject: [PATCH] qemu-img: add 'nocow' option

Add 'nocow' create option so that users could set nocow flag to newly created
images, which could solve performance issues on btrfs.

Btrfs has terrible performance when hosting VM images, even more when the guest
in those VM are also using btrfs as file system. One way to mitigate this bad
performance is to turn off COW attributes on VM files (since having copy on
write for this kind of data is not useful).

Signed-off-by: Chunyan Liu <cyliu@suse.com>
[BR: Implements FATE#315125]
Signed-off-by: Bruce Rogers <brogers@suse.com>
---
 block/cow.c               |  7 +++++++
 block/qcow.c              |  7 +++++++
 block/qcow2.c             |  7 +++++++
 block/qed.c               |  7 +++++++
 block/raw-posix.c         | 26 ++++++++++++++++++++++++++
 block/raw_bsd.c           |  5 +++++
 block/vdi.c               | 30 ++++++++++++++++++++++++++++++
 block/vhdx.c              |  7 +++++++
 block/vmdk.c              | 14 +++++++++++---
 block/vpc.c               | 30 ++++++++++++++++++++++++++++++
 include/block/block_int.h |  1 +
 11 files changed, 138 insertions(+), 3 deletions(-)

diff --git a/block/cow.c b/block/cow.c
index 30deb88..f5fb068 100644
--- a/block/cow.c
+++ b/block/cow.c
@@ -341,6 +341,8 @@ static int cow_create(const char *filename, QEMUOptionParameter *options,
             image_sectors = options->value.n / 512;
         } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) {
             image_filename = options->value.s;
+        } else if (!strcmp(options->name, BLOCK_OPT_NOCOW)) {
+            break;
         }
         options++;
     }
@@ -404,6 +406,11 @@ static QEMUOptionParameter cow_create_options[] = {
         .type = OPT_STRING,
         .help = "File name of a base image"
     },
+    {
+        .name = BLOCK_OPT_NOCOW,
+        .type = OPT_FLAG,
+        .help = "No copy-on-write"
+    },
     { NULL }
 };
 
diff --git a/block/qcow.c b/block/qcow.c
index c0a3b89..adddd07 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -713,6 +713,8 @@ static int qcow_create(const char *filename, QEMUOptionParameter *options,
             backing_file = options->value.s;
         } else if (!strcmp(options->name, BLOCK_OPT_ENCRYPT)) {
             flags |= options->value.n ? BLOCK_FLAG_ENCRYPT : 0;
+        } else if (!strcmp(options->name, BLOCK_OPT_NOCOW)) {
+            break;
         }
         options++;
     }
@@ -928,6 +930,11 @@ static QEMUOptionParameter qcow_create_options[] = {
         .type = OPT_FLAG,
         .help = "Encrypt the image"
     },
+    {
+        .name = BLOCK_OPT_NOCOW,
+        .type = OPT_FLAG,
+        .help = "No copy-on-write"
+    },
     { NULL }
 };
 
diff --git a/block/qcow2.c b/block/qcow2.c
index 57c353d..3f033eb 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1811,6 +1811,8 @@ static int qcow2_create(const char *filename, QEMUOptionParameter *options,
             }
         } else if (!strcmp(options->name, BLOCK_OPT_LAZY_REFCOUNTS)) {
             flags |= options->value.n ? BLOCK_FLAG_LAZY_REFCOUNTS : 0;
+        } else if (!strcmp(options->name, BLOCK_OPT_NOCOW)) {
+            break;
         }
         options++;
     }
@@ -2364,6 +2366,11 @@ static QEMUOptionParameter qcow2_create_options[] = {
         .type = OPT_FLAG,
         .help = "Postpone refcount updates",
     },
+    {
+        .name = BLOCK_OPT_NOCOW,
+        .type = OPT_FLAG,
+        .help = "No copy-on-write"
+    },
     { NULL }
 };
 
diff --git a/block/qed.c b/block/qed.c
index 3bd9db9..8aa1bae 100644
--- a/block/qed.c
+++ b/block/qed.c
@@ -645,6 +645,8 @@ static int bdrv_qed_create(const char *filename, QEMUOptionParameter *options,
             if (options->value.n) {
                 table_size = options->value.n;
             }
+        } else if (!strcmp(options->name, BLOCK_OPT_NOCOW)) {
+            break;
         }
         options++;
     }
@@ -1616,6 +1618,11 @@ static QEMUOptionParameter qed_create_options[] = {
         .type = OPT_SIZE,
         .help = "L1/L2 table size (in clusters)"
     },
+    {
+        .name = BLOCK_OPT_NOCOW,
+        .type = OPT_FLAG,
+        .help = "No copy-on-write"
+    },
     { /* end of list */ }
 };
 
diff --git a/block/raw-posix.c b/block/raw-posix.c
index 1688e16..9e97266 100644
--- a/block/raw-posix.c
+++ b/block/raw-posix.c
@@ -55,6 +55,9 @@
 #include <linux/cdrom.h>
 #include <linux/fd.h>
 #include <linux/fs.h>
+#ifndef FS_NOCOW_FL
+#define FS_NOCOW_FL                     0x00800000 /* Do not cow file */
+#endif
 #endif
 #ifdef CONFIG_FIEMAP
 #include <linux/fiemap.h>
@@ -1240,6 +1243,7 @@ static int raw_create(const char *filename, QEMUOptionParameter *options,
     int fd;
     int result = 0;
     int64_t total_size = 0;
+    bool nocow = false;
 
     strstart(filename, "file:", &filename);
 
@@ -1247,6 +1251,8 @@ static int raw_create(const char *filename, QEMUOptionParameter *options,
     while (options && options->name) {
         if (!strcmp(options->name, BLOCK_OPT_SIZE)) {
             total_size = options->value.n / BDRV_SECTOR_SIZE;
+        } else if (!strcmp(options->name, BLOCK_OPT_NOCOW)) {
+            nocow |= options->value.n;
         }
         options++;
     }
@@ -1257,6 +1263,21 @@ static int raw_create(const char *filename, QEMUOptionParameter *options,
         result = -errno;
         error_setg_errno(errp, -result, "Could not create file");
     } else {
+        if (nocow) {
+#ifdef __linux__
+            /* Set NOCOW flag to solve performance issue on fs like btrfs.
+             * This is an optimisation. The FS_IOC_SETFLAGS ioctl return value will
+             * be ignored since any failure of this operation should not block the
+             * left work.
+             */
+            int attr;
+            if (ioctl(fd, FS_IOC_GETFLAGS, &attr) == 0) {
+                attr |= FS_NOCOW_FL;
+                ioctl(fd, FS_IOC_SETFLAGS, &attr);
+            }
+#endif
+        }
+
         if (ftruncate(fd, total_size * BDRV_SECTOR_SIZE) != 0) {
             result = -errno;
             error_setg_errno(errp, -result, "Could not resize file");
@@ -1416,6 +1437,11 @@ static QEMUOptionParameter raw_create_options[] = {
         .type = OPT_SIZE,
         .help = "Virtual disk size"
     },
+    {
+        .name = BLOCK_OPT_NOCOW,
+        .type = OPT_FLAG,
+        .help = "No copy-on-write"
+    },
     { NULL }
 };
 
diff --git a/block/raw_bsd.c b/block/raw_bsd.c
index 01ea692..86954da 100644
--- a/block/raw_bsd.c
+++ b/block/raw_bsd.c
@@ -35,6 +35,11 @@ static QEMUOptionParameter raw_create_options[] = {
         .type = OPT_SIZE,
         .help = "Virtual disk size"
     },
+    {
+        .name = BLOCK_OPT_NOCOW,
+        .type = OPT_FLAG,
+        .help = "No copy-on-write"
+    },
     { 0 }
 };
 
diff --git a/block/vdi.c b/block/vdi.c
index 820cd37..4f0b64b 100644
--- a/block/vdi.c
+++ b/block/vdi.c
@@ -53,6 +53,13 @@
 #include "block/block_int.h"
 #include "qemu/module.h"
 #include "migration/migration.h"
+#ifdef __linux__
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#ifndef FS_NOCOW_FL
+#define FS_NOCOW_FL                     0x00800000 /* Do not cow file */
+#endif
+#endif
 
 #if defined(CONFIG_UUID)
 #include <uuid/uuid.h>
@@ -684,6 +691,7 @@ static int vdi_create(const char *filename, QEMUOptionParameter *options,
     VdiHeader header;
     size_t i;
     size_t bmap_size;
+    bool nocow = false;
 
     logout("\n");
 
@@ -704,6 +712,8 @@ static int vdi_create(const char *filename, QEMUOptionParameter *options,
                 image_type = VDI_TYPE_STATIC;
             }
 #endif
+        } else if (!strcmp(options->name, BLOCK_OPT_NOCOW)) {
+            nocow |= options->value.n;
         }
         options++;
     }
@@ -724,6 +734,21 @@ static int vdi_create(const char *filename, QEMUOptionParameter *options,
         goto exit;
     }
 
+    if (nocow) {
+#ifdef __linux__
+        /* Set NOCOW flag to solve performance issue on fs like btrfs.
+         * This is an optimisation. The FS_IOC_SETFLAGS ioctl return value will
+         * be ignored since any failure of this operation should not block the
+         * left work.
+         */
+        int attr;
+        if (ioctl(fd, FS_IOC_GETFLAGS, &attr) == 0) {
+            attr |= FS_NOCOW_FL;
+            ioctl(fd, FS_IOC_SETFLAGS, &attr);
+        }
+#endif
+    }
+
     /* We need enough blocks to store the given disk size,
        so always round up. */
     blocks = (bytes + block_size - 1) / block_size;
@@ -817,6 +842,11 @@ static QEMUOptionParameter vdi_create_options[] = {
         .help = "VDI static (pre-allocated) image"
     },
 #endif
+    {
+        .name = BLOCK_OPT_NOCOW,
+        .type = OPT_FLAG,
+        .help = "No copy-on-write"
+    },
     /* TODO: An additional option to set UUID values might be useful. */
     { NULL }
 };
diff --git a/block/vhdx.c b/block/vhdx.c
index 509baaf..14fc98b 100644
--- a/block/vhdx.c
+++ b/block/vhdx.c
@@ -1745,6 +1745,8 @@ static int vhdx_create(const char *filename, QEMUOptionParameter *options,
             type = options->value.s;
         } else if (!strcmp(options->name, VHDX_BLOCK_OPT_ZERO)) {
             use_zero_blocks = options->value.n != 0;
+        } else if (!strcmp(options->name, BLOCK_OPT_NOCOW)) {
+            break;
         }
         options++;
     }
@@ -1908,6 +1910,11 @@ static QEMUOptionParameter vhdx_create_options[] = {
         .type = OPT_FLAG,
         .help = "Force use of payload blocks of type 'ZERO'.  Non-standard."
     },
+    {
+        .name = BLOCK_OPT_NOCOW,
+        .type = OPT_FLAG,
+        .help = "No copy-on-write"
+    },
     { NULL }
 };
 
diff --git a/block/vmdk.c b/block/vmdk.c
index 59c468d..db0d23f 100644
--- a/block/vmdk.c
+++ b/block/vmdk.c
@@ -1515,7 +1515,7 @@ static int coroutine_fn vmdk_co_write_zeroes(BlockDriverState *bs,
 
 static int vmdk_create_extent(const char *filename, int64_t filesize,
                               bool flat, bool compress, bool zeroed_grain,
-                              Error **errp)
+                              Error **errp, QEMUOptionParameter *options)
 {
     int ret, i;
     BlockDriverState *bs = NULL;
@@ -1525,7 +1525,7 @@ static int vmdk_create_extent(const char *filename, int64_t filesize,
     uint32_t *gd_buf = NULL;
     int gd_buf_size;
 
-    ret = bdrv_create_file(filename, NULL, &local_err);
+    ret = bdrv_create_file(filename, options, &local_err);
     if (ret < 0) {
         error_propagate(errp, local_err);
         goto exit;
@@ -1746,6 +1746,8 @@ static int vmdk_create(const char *filename, QEMUOptionParameter *options,
             zeroed_grain |= options->value.n;
         } else if (!strcmp(options->name, BLOCK_OPT_SCSI)) {
             flags |= options->value.n ? BLOCK_FLAG_SCSI: 0;
+        } else if (!strcmp(options->name, BLOCK_OPT_NOCOW)) {
+            break;
         }
         options++;
     }
@@ -1839,7 +1841,8 @@ static int vmdk_create(const char *filename, QEMUOptionParameter *options,
                 path, desc_filename);
 
         if (vmdk_create_extent(ext_filename, size,
-                               flat, compress, zeroed_grain, errp)) {
+                               flat, compress, zeroed_grain, errp,
+                               options)) {
             ret = -EINVAL;
             goto exit;
         }
@@ -2103,6 +2106,11 @@ static QEMUOptionParameter vmdk_create_options[] = {
         .type = OPT_FLAG,
         .help = "SCSI image"
     },
+    {
+        .name = BLOCK_OPT_NOCOW,
+        .type = OPT_FLAG,
+        .help = "No copy-on-write"
+    },
     { NULL }
 };
 
diff --git a/block/vpc.c b/block/vpc.c
index 2e25f57..5d80dd7 100644
--- a/block/vpc.c
+++ b/block/vpc.c
@@ -29,6 +29,13 @@
 #if defined(CONFIG_UUID)
 #include <uuid/uuid.h>
 #endif
+#ifdef __linux__
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#ifndef FS_NOCOW_FL
+#define FS_NOCOW_FL                     0x00800000 /* Do not cow file */
+#endif
+#endif
 
 /**************************************************************/
 
@@ -751,6 +758,7 @@ static int vpc_create(const char *filename, QEMUOptionParameter *options,
     int64_t total_sectors;
     int64_t total_size;
     int disk_type;
+    bool nocow = false;
     int ret = -EIO;
 
     /* Read out options */
@@ -769,12 +777,29 @@ static int vpc_create(const char *filename, QEMUOptionParameter *options,
         disk_type = VHD_DYNAMIC;
     }
 
+    nocow |= get_option_parameter(options, BLOCK_OPT_NOCOW)->value.n;
+
     /* Create the file */
     fd = qemu_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
     if (fd < 0) {
         return -EIO;
     }
 
+    if (nocow) {
+#ifdef __linux__
+        /* Set NOCOW flag to solve performance issue on fs like btrfs.
+         * This is an optimisation. The FS_IOC_SETFLAGS ioctl return value will
+         * be ignored since any failure of this operation should not block the
+         * left work.
+         */
+        int attr;
+        if (ioctl(fd, FS_IOC_GETFLAGS, &attr) == 0) {
+            attr |= FS_NOCOW_FL;
+            ioctl(fd, FS_IOC_SETFLAGS, &attr);
+        }
+#endif
+    }
+
     /*
      * Calculate matching total_size and geometry. Increase the number of
      * sectors requested until we get enough (or fail). This ensures that
@@ -879,6 +904,11 @@ static QEMUOptionParameter vpc_create_options[] = {
             "Type of virtual hard disk format. Supported formats are "
             "{dynamic (default) | fixed} "
     },
+    {
+        .name = BLOCK_OPT_NOCOW,
+        .type = OPT_FLAG,
+        .help = "No copy-on-write"
+    },
     { NULL }
 };
 
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 0d4208f..a7989ab 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -56,6 +56,7 @@
 #define BLOCK_OPT_LAZY_REFCOUNTS    "lazy_refcounts"
 #define BLOCK_OPT_ADAPTER_TYPE      "adapter_type"
 #define BLOCK_OPT_REDUNDANCY        "redundancy"
+#define BLOCK_OPT_NOCOW             "nocow"
 
 typedef struct BdrvTrackedRequest {
     BlockDriverState *bs;
