diff mbox

[3/3] Sample server that opens image files for QEMU

Message ID 1338815410-24890-4-git-send-email-coreyb@linux.vnet.ibm.com
State New
Headers show

Commit Message

Corey Bryant June 4, 2012, 1:10 p.m. UTC
This sample server opens image files and passes the fds to QEMU.  The
paths for two image files are passed as parameters, the first being
the boot image, and the second being an image to be hot-attached.  The
server will open the files and pass the fds to QEMU in one of two ways:

  1) Over the command line (using -drive file=/dev/fd/X) or
  2) Via the QMP monitor with the getfd command (using SCM_RIGHTS)
     followed by drive_add (using file=/dev/fd/X) and then
     device_add.

Usage:
  gcc -Wall -o test-fd-passing test-fd-passing.c -L/usr/local/lib -ljson
  ./test-fd-passing /path/hda.img /path/hdb.img

Note: This requires json-c and json-c-devel packages.

Signed-off-by: Corey Bryant <coreyb@linux.vnet.ibm.com>
---
 test-fd-passing.c |  321 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 321 insertions(+)
 create mode 100644 test-fd-passing.c

Comments

Kevin Wolf June 4, 2012, 3:01 p.m. UTC | #1
Am 04.06.2012 15:10, schrieb Corey Bryant:
> This sample server opens image files and passes the fds to QEMU.  The
> paths for two image files are passed as parameters, the first being
> the boot image, and the second being an image to be hot-attached.  The
> server will open the files and pass the fds to QEMU in one of two ways:
> 
>   1) Over the command line (using -drive file=/dev/fd/X) or
>   2) Via the QMP monitor with the getfd command (using SCM_RIGHTS)
>      followed by drive_add (using file=/dev/fd/X) and then
>      device_add.
> 
> Usage:
>   gcc -Wall -o test-fd-passing test-fd-passing.c -L/usr/local/lib -ljson
>   ./test-fd-passing /path/hda.img /path/hdb.img
> 
> Note: This requires json-c and json-c-devel packages.
> 
> Signed-off-by: Corey Bryant <coreyb@linux.vnet.ibm.com>
> ---
>  test-fd-passing.c |  321 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 321 insertions(+)
>  create mode 100644 test-fd-passing.c

Is this meant to be applied or just for reference?

Maybe we can make a proper test case out of it that runs during make
check? Would probably require that the json-c dependency is dropped,
though. Maybe we should rewrite it in Python, as we already have QMP
bindings for that (and there are already tests that use them).

Kevin
Corey Bryant June 4, 2012, 4:15 p.m. UTC | #2
On 06/04/2012 11:01 AM, Kevin Wolf wrote:
> Am 04.06.2012 15:10, schrieb Corey Bryant:
>> This sample server opens image files and passes the fds to QEMU.  The
>> paths for two image files are passed as parameters, the first being
>> the boot image, and the second being an image to be hot-attached.  The
>> server will open the files and pass the fds to QEMU in one of two ways:
>>
>>    1) Over the command line (using -drive file=/dev/fd/X) or
>>    2) Via the QMP monitor with the getfd command (using SCM_RIGHTS)
>>       followed by drive_add (using file=/dev/fd/X) and then
>>       device_add.
>>
>> Usage:
>>    gcc -Wall -o test-fd-passing test-fd-passing.c -L/usr/local/lib -ljson
>>    ./test-fd-passing /path/hda.img /path/hdb.img
>>
>> Note: This requires json-c and json-c-devel packages.
>>
>> Signed-off-by: Corey Bryant<coreyb@linux.vnet.ibm.com>
>> ---
>>   test-fd-passing.c |  321 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>>   1 file changed, 321 insertions(+)
>>   create mode 100644 test-fd-passing.c
>
> Is this meant to be applied or just for reference?
>

This was just for reference.  It was the majority of my code (unit test) 
so I figured I'd share it.  :)

> Maybe we can make a proper test case out of it that runs during make
> check? Would probably require that the json-c dependency is dropped,
> though. Maybe we should rewrite it in Python, as we already have QMP
> bindings for that (and there are already tests that use them).

I won't promise anything but if I get a chance to convert to a Python 
test I will.
diff mbox

Patch

diff --git a/test-fd-passing.c b/test-fd-passing.c
new file mode 100644
index 0000000..f3f06e6
--- /dev/null
+++ b/test-fd-passing.c
@@ -0,0 +1,321 @@ 
+/*
+ * QEMU fd passing test server
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ *  Corey Bryant      <coreyb@linux.vnet.ibm.com>
+ *  Stefan Hajnoczi   <stefanha@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ * gcc -Wall -o test-fd-passing test-fd-passing.c -L/usr/local/lib -ljson
+ * ./test-fd-passing /path/hda.img /path/hdb.img
+ *
+ * Note: This requires json-c and json-c-devel packages.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <spawn.h>
+#include <sys/un.h>
+#include <json/json.h>
+
+static int openMonitor(const char *mon_path)
+{
+    int i;
+    int rc;
+    struct sockaddr_un addr;
+    int fd = 0;
+
+    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+        perror("socket");
+        goto error;
+    }
+
+    memset(&addr, 0, sizeof(addr));
+    addr.sun_family = AF_UNIX;
+    strcpy(addr.sun_path, mon_path);
+
+    for (i = 0; i < 100; i++) {
+        rc = connect(fd, (struct sockaddr *) &addr, sizeof(addr));
+        if (rc == 0) {
+            break;
+        }
+        sleep(1);
+    }
+
+    if (rc != 0) {
+        fprintf(stderr, "unable to connect to monitor socket\n");
+        goto error;
+    }
+
+    return fd;
+
+error:
+    close(fd);
+    return -1;
+}
+
+static int writeQMPfd(int qmpfd, int fd, const char *str)
+{
+    int rc;
+    char *json_str = NULL;
+    struct json_object *json_obj;
+    struct msghdr msg;
+    struct iovec iov[1];
+    char control[CMSG_SPACE(sizeof(int))];
+    struct cmsghdr *cmsg;
+
+    fprintf(stderr, "write (fd=%d) %s\n", fd, str);
+
+    memset(&msg, 0, sizeof(msg));
+
+    json_obj = json_tokener_parse(str);
+    asprintf(&json_str, "%s\r\n", json_object_to_json_string(json_obj));
+    json_object_put(json_obj);
+
+    iov[0].iov_base = (void *)json_str;
+    iov[0].iov_len = strlen(json_str);
+
+    msg.msg_iov = iov;
+    msg.msg_iovlen = 1;
+
+    msg.msg_control = control;
+    msg.msg_controllen = sizeof(control);
+
+    cmsg = CMSG_FIRSTHDR(&msg);
+    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+    cmsg->cmsg_level = SOL_SOCKET;
+    cmsg->cmsg_type = SCM_RIGHTS;
+    memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+    do {
+        rc = sendmsg(qmpfd, &msg, 0);
+    } while (rc < 0 && errno == EINTR);
+
+    if (rc < 0) {
+        perror("sendmsg");
+    }
+
+    return rc;
+}
+
+static int writeQMP(int qmpfd, const char *str) {
+    int rc;
+    char *json_str = NULL;
+    struct json_object *json_obj;
+
+    fprintf(stderr, "write %s\n", str);
+
+    json_obj = json_tokener_parse(str);
+    asprintf(&json_str, "%s\r\n", json_object_to_json_string(json_obj));
+    json_object_put(json_obj);
+
+    rc = write(qmpfd, json_str, strlen(json_str));
+    if (rc < 0) {
+        perror("write");
+    }
+
+    return rc;
+}
+
+static char *readQMP(int qmpfd) {
+    int rc;
+    int i;
+    char *json_str;
+
+    json_str = calloc(1024, sizeof(char));
+    if (json_str == NULL) {
+        return NULL;
+    }
+
+    i = 0;
+    do {
+        sleep(1);
+        rc = read(qmpfd, json_str, 1024);
+        if (rc < 0) {
+            perror("read");
+            return NULL;
+        }
+        i++;
+    } while (i < 100 && rc == 0);
+
+    if (rc == 0) {
+        fprintf(stderr, "no data to read");
+        free(json_str);
+        return NULL;
+    }
+
+    fprintf(stderr, "read %s", json_str);
+
+    return json_str;
+}
+
+int main(int argc, char *argv[]) {
+    int rc = -1;
+    int qmpfd, bootfd, hotfd_host, hotfd_guest;
+    int flags = O_RDWR;
+    int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+    pid_t child_pid;
+    char *drive_str = NULL;;
+    char *drive_add_str = NULL;
+    char *device_add_str = NULL;
+    char *drive_add_qmp = NULL;
+    char *device_add_qmp = NULL;
+    char *json_str;
+    struct json_object *json_obj;
+
+    if (argc != 3) {
+        fprintf(stderr, "usage: %s <boot-image-file> <attach-image-file>\n",
+                argv[0]);
+        goto error;
+    }
+
+    /* open the image files */
+    bootfd = open(argv[1], flags, mode);
+    if (bootfd == -1) {
+        perror("open");
+        goto error;
+    }
+
+    hotfd_host = open(argv[2], flags, mode);
+    if (hotfd_host == -1) {
+        perror("open");
+        goto error;
+    }
+
+    /* use '-drive file=/dev/fd/X' on QEMU command line to open boot image */
+    asprintf(&drive_str, "file=/dev/fd/%d,if=none,id=drive-virtio-disk0",
+             bootfd);
+
+    char *child_argv[] = {
+        "qemu-system-x86_64",
+        "-enable-kvm",
+        "-m", "1024",
+        "-chardev", "socket,id=qmp,path=./qmp-sock,server",
+        "-mon", "chardev=qmp,mode=control,pretty=on",
+        "-drive", drive_str,
+        "-device",
+        "virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,id=virtio-disk0",
+        "-vnc", ":0",
+        NULL,
+    };
+
+    rc = posix_spawn(&child_pid, "/usr/local/bin/qemu-system-x86_64",
+                     NULL, NULL, child_argv, environ);
+    if (rc != 0) {
+        perror("posix_spawn\n");
+        goto error;
+    }
+
+    qmpfd = openMonitor("./qmp-sock");
+    if (qmpfd == -1) {
+        rc = -1;
+        goto error;
+    }
+
+    json_str = readQMP(qmpfd);
+    if (json_str == NULL) {
+        rc = -1;
+        goto error;
+    }
+    free(json_str);
+
+    rc = writeQMP(qmpfd, "{ \"execute\": \"qmp_capabilities\" }");
+    if (rc < 0) {
+        goto error;
+    }
+
+    json_str = readQMP(qmpfd);
+    if (json_str == NULL) {
+        rc = -1;
+        goto error;
+    }
+    free(json_str);
+
+    /* issue 'getfd' monitor command and get QEMU guest's fd in return */
+    rc = writeQMPfd(qmpfd, hotfd_host,
+         "{ \"execute\": \"getfd\", \"arguments\": { \"fdname\": \"fd1\" } }");
+    if (rc < 0) {
+        goto error;
+    }
+
+    json_str = readQMP(qmpfd);
+    if (json_str == NULL) {
+        rc = -1;
+        goto error;
+    }
+    json_obj = json_tokener_parse(json_str);
+    json_obj = json_object_object_get(json_obj, "return");
+    hotfd_guest = json_object_get_int(json_obj);
+    json_object_put(json_obj);
+    free(json_str);
+
+    /* issue 'drive_add' monitor command with guest's /dev/fd/X path */
+    asprintf(&drive_add_str, "drive_add data_drive file=/dev/fd/%d,%s",
+             hotfd_guest,
+             "if=none,id=drive-virtio-disk1,cache=writethrough\r\n");
+    asprintf(&drive_add_qmp,
+             "{ \"%s\": \"%s\", \"%s\": { \"%s\": \"%s\" } }",
+             "execute", "human-monitor-command", "arguments",
+             "command-line", drive_add_str);
+    rc = writeQMP(qmpfd, drive_add_qmp);
+    if (rc < 0) {
+        goto error;
+    }
+
+    json_str = readQMP(qmpfd);
+    if (json_str == NULL) {
+        rc = -1;
+        goto error;
+    }
+    free(json_str);
+
+    /* issue 'device_add' monitor command to add the disk drive */
+    asprintf(&device_add_str, "device_add virtio-blk-pci,bus=pci.0,%s",
+             "addr=0x6,drive=drive-virtio-disk1,id=virtio-disk1\r\n");
+    asprintf(&device_add_qmp,
+             "{ \"%s\": \"%s\", \"%s\": { \"%s\": \"%s\" } }",
+             "execute", "human-monitor-command", "arguments",
+             "command-line", device_add_str);
+    rc = writeQMP(qmpfd, device_add_qmp);
+    if (rc < 0) {
+        goto error;
+    }
+
+    json_str = readQMP(qmpfd);
+    if (json_str == NULL) {
+        rc = -1;
+        goto error;
+    }
+    free(json_str);
+
+    rc = 0;
+
+error:
+    if (drive_str) {
+        free(drive_str);
+    }
+    if (drive_add_str) {
+        free(drive_add_str);
+    }
+    if (device_add_str) {
+        free(device_add_str);
+    }
+    if (drive_add_qmp) {
+        free(drive_add_qmp);
+    }
+    if (device_add_qmp) {
+        free(device_add_qmp);
+    }
+    return rc;
+}