Fix problem that xl cannot create PV guest with qcow/qcow2 disk image by using
pygrub. Have discussed with upstream, but the work is something related to both
qemu and xen upstream, making it accepted may take a long time. Submit first and
will be replaced with upstream patches if it is accepted.
http://xen.1045712.n5.nabble.com/xl-create-PV-guest-with-qcow-qcow2-disk-images-fail-td4909399.html

Signed-off-by: Chunyan Liu <cyliu@suse.com>

--- a/tools/libxl/libxl.c
+++ b/tools/libxl/libxl.c
@@ -1072,11 +1072,131 @@ int libxl_device_disk_del(libxl_ctx *ctx
     return rc;
 }
 
+static int libxl_forkexec(libxl_ctx *ctx, char *arg0, char **args)
+{
+    pid_t pid;
+    int status;
+
+    pid = libxl_fork(ctx);
+    if (pid < 0)
+        return -1;
+    else if (pid == 0){
+        libxl__exec(-1, -1, -1, arg0, args);
+        exit(127);
+    }
+    while (waitpid(pid, &status, 0) < 0) {
+        if (errno != EINTR) {
+            status = -1;
+            break;
+        }
+    }
+
+    return status;
+}
+
+#ifdef __linux__
+static int is_nbd_used(int minor)
+{
+    FILE *proc;
+    int NBDMAJOR = 43;
+    char buf[BUFSIZ];
+    int find = 0;
+
+    proc = fopen("/proc/partitions", "r");
+    if (proc != NULL) {
+        while (fgets(buf, sizeof(buf), proc)) {
+            int m, n;
+            unsigned long long sz;
+            char name[16];
+            char *pname = name;
+            char *end;
+
+            if (sscanf(buf, " %d %d %llu %128[^\n ]",
+                    &m, &n, &sz, name) != 4)
+                continue;
+            if (m != NBDMAJOR)
+                continue;
+            if (strncmp(name, "nbd", 3))
+                continue;
+            pname += 3;
+            n = strtol(pname, &end, 10);
+            if (end && end != pname && *end == '\0' && n == minor) {
+                find = 1;
+                break;
+            }
+        }
+        fclose(proc);
+    }
+
+    return find;
+}
+
+static int find_free_nbd_minor(void)
+{
+    int i;
+    int nbds_max = 16;
+    int minor = -1;
+
+    for (i = 0; i < nbds_max; i++) {
+        if (!is_nbd_used(i)) {
+            minor = i;
+            break;
+        }
+    }
+
+    return minor;
+}
+
+static char * nbd_mount_disk(libxl__gc *gc, libxl_device_disk *disk)
+{
+    libxl_ctx *ctx = libxl__gc_owner(gc);
+    int n = -1;
+    char *nbd_dev = NULL;
+    char *args[] = {"qemu-nbd","-c",NULL,NULL,NULL};
+    char *ret = NULL;
+
+    n = find_free_nbd_minor();
+    if (n >= 0) {
+        int i = 0;
+        int retry = 3;
+ 
+        nbd_dev = libxl__sprintf(gc, "/dev/nbd%d", n);
+        args[2] = libxl__sprintf(gc, "%s", nbd_dev);
+        args[3] = libxl__sprintf(gc, "%s", disk->pdev_path);
+        libxl_forkexec(ctx, args[0], args);
+ 
+        /*check connection*/
+        while (i < retry) {
+            if (is_nbd_used(n)) {
+                ret = strdup(nbd_dev);
+                break;
+            }
+            i++;
+            sleep(1);
+        }
+    }
+
+    return ret;
+}
+
+static int nbd_unmount_disk(libxl__gc *gc, char *diskpath) {
+    libxl_ctx *ctx = libxl__gc_owner(gc);
+    char *args[] = {"qemu-nbd","-d",NULL,NULL};
+
+    args[2] = libxl__sprintf(gc, "%s", diskpath);
+    if (libxl_forkexec(ctx, args[0], args))
+        return 0;
+    else
+        return ERROR_FAIL;
+}
+#endif
+
 char * libxl_device_disk_local_attach(libxl_ctx *ctx, libxl_device_disk *disk)
 {
     libxl__gc gc = LIBXL_INIT_GC(ctx);
     const char *dev = NULL;
     char *ret = NULL;
+    char *mdev = NULL;
 
     switch (disk->backend) {
         case DISK_BACKEND_PHY: 
@@ -1118,8 +1238,17 @@ char * libxl_device_disk_local_attach(li
             }
         case DISK_BACKEND_QDISK: 
             if (disk->format != DISK_FORMAT_RAW) {
-                LIBXL__LOG(ctx, LIBXL__LOG_ERROR, "cannot locally attach a qdisk "
-                    "image if the format is not raw");
+#ifdef __linux__
+                LIBXL__LOG(ctx, LIBXL__LOG_DEBUG, "attaching a non-raw qdisk image to domain 0\n");
+                mdev = nbd_mount_disk(&gc, disk);
+                if (mdev)
+                    dev = mdev;
+                else
+                    LIBXL__LOG(ctx, LIBXL__LOG_ERROR, "fail to mount image with qemu-nbd");
+#else       
+                LIBXL__LOG(ctx, LIBXL__LOG_ERROR, "cannot locally"
+                            " attach a qdisk image if the format is not raw");
+#endif
                 break;
             }
             LIBXL__LOG(ctx, LIBXL__LOG_DEBUG, "attaching qdisk %s to domain 0\n",
@@ -1135,18 +1264,37 @@ char * libxl_device_disk_local_attach(li
 
     if (dev != NULL)
         ret = strdup(dev);
+    free(mdev);
     libxl__free_all(&gc);
     return ret;
 }
 
-int libxl_device_disk_local_detach(libxl_ctx *ctx, libxl_device_disk *disk)
+int libxl_device_disk_local_detach(libxl_ctx *ctx, libxl_device_disk *disk, char *diskpath)
 {
-    /* Nothing to do for PHYSTYPE_PHY. */
+#ifdef __linux__
+    libxl__gc gc = LIBXL_INIT_GC(ctx);
+    int ret;
 
-    /*
-     * For other device types assume that the blktap2 process is
-     * needed by the soon to be started domain and do nothing.
-     */
+    switch (disk->backend) {
+        case DISK_BACKEND_QDISK:
+            if (disk->format != DISK_FORMAT_RAW) {
+                LIBXL__LOG(ctx, LIBXL__LOG_DEBUG, "Locally detach a non-raw "
+                    "qdisk image");
+                ret = nbd_unmount_disk(&gc, diskpath);
+                return ret;
+            }
+        default:
+            /* Nothing to do for PHYSTYPE_PHY. */
+
+            /*
+            * For other device types assume that the blktap2 process is
+            * needed by the soon to be started domain and do nothing.
+            */
+            break;
+    }
+
+    libxl__free_all(&gc);
+#endif
 
     return 0;
 }
--- a/tools/libxl/libxl.h
+++ b/tools/libxl/libxl.h
@@ -429,7 +429,7 @@ int libxl_cdrom_insert(libxl_ctx *ctx, u
  * Make a disk available in this domain. Returns path to a device.
  */
 char * libxl_device_disk_local_attach(libxl_ctx *ctx, libxl_device_disk *disk);
-int libxl_device_disk_local_detach(libxl_ctx *ctx, libxl_device_disk *disk);
+int libxl_device_disk_local_detach(libxl_ctx *ctx, libxl_device_disk *disk, char *diskpath);
 
 int libxl_device_nic_init(libxl_device_nic *nic, int dev_num);
 int libxl_device_nic_add(libxl_ctx *ctx, uint32_t domid, libxl_device_nic *nic);
--- a/tools/libxl/libxl_bootloader.c
+++ b/tools/libxl/libxl_bootloader.c
@@ -419,7 +419,7 @@ int libxl_run_bootloader(libxl_ctx *ctx,
     rc = 0;
 out_close:
     if (diskpath) {
-        libxl_device_disk_local_detach(ctx, disk);
+        libxl_device_disk_local_detach(ctx, disk, diskpath);
         free(diskpath);
     }
     if (fifo_fd > -1)
