diff mbox

[02/10] qga: implement guest-pipe-open command

Message ID 1434733075-24240-3-git-send-email-den@openvz.org
State New
Headers show

Commit Message

Denis V. Lunev June 19, 2015, 4:57 p.m. UTC
From: Olga Krishtal <okrishtal@virtuozzo.com>

The command creates FIFO pair that can be used with existing file
read/write interfaces to communicate with processes spawned via the
forthcoming guest-file-exec interface.

Signed-off-by: Olga Krishtal <okrishtal@virtuozzo.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
Acked-by: Roman Kagan <rkagan@virtuozzo.com>
CC: Eric Blake <eblake@redhat.com>
CC: Michael Roth <mdroth@linux.vnet.ibm.com>
---
 qga/commands-posix.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++---
 qga/commands-win32.c |  8 ++++-
 qga/qapi-schema.json | 44 ++++++++++++++++++++++++
 3 files changed, 143 insertions(+), 5 deletions(-)

Comments

Eric Blake June 19, 2015, 5:30 p.m. UTC | #1
On 06/19/2015 10:57 AM, Denis V. Lunev wrote:
> From: Olga Krishtal <okrishtal@virtuozzo.com>
> 
> The command creates FIFO pair that can be used with existing file
> read/write interfaces to communicate with processes spawned via the
> forthcoming guest-file-exec interface.
> 
> Signed-off-by: Olga Krishtal <okrishtal@virtuozzo.com>
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> Acked-by: Roman Kagan <rkagan@virtuozzo.com>
> CC: Eric Blake <eblake@redhat.com>
> CC: Michael Roth <mdroth@linux.vnet.ibm.com>
> ---
>  qga/commands-posix.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++---
>  qga/commands-win32.c |  8 ++++-
>  qga/qapi-schema.json | 44 ++++++++++++++++++++++++
>  3 files changed, 143 insertions(+), 5 deletions(-)

Just an interface review at this point:

> +++ b/qga/qapi-schema.json
> @@ -215,12 +215,56 @@
>    'returns': 'int' }
>  
>  ##
> +# @GuestPipeMode
> +#
> +# An enumeration of pipe modes
> +# read: pipe is opened for writing data
> +# write: pipe is opened for reading data
> +#
> +# Since: 2.4
> +##
> +{ 'enum': 'GuestPipeMode',
> +  'data': [ 'read', 'write' ] }
> +
> +##
> +# @GuestPipeInfo
> +#
> +# Information about pipe.
> +#
> +# Since: 2.4
> +##
> +{ 'struct': 'GuestPipeInfo',
> +  'data': {'fd': 'int'} }

Missing a field of type GuestPipeMode?

> +
> +##
> +# @guest-pipe-open
> +#
> +# Open a pipe to in the guest to associated with a qga-spawned processes
> +# for communication.

Reads poorly.  Maybe:

Open a pipe in the guest for association with later qga-spawned processes.

> +#
> +# Returns: Guest file handle on success, as per guest-file-open. This
> +# handle is usable with the same interfaces as a handle returned by
> +# guest-file-open.
> +#
> +# Since: 2.4
> +##
> +{ 'command': 'guest-pipe-open',
> +  'data':    { 'mode': 'GuestPipeMode' },
> +  'returns': ['GuestPipeInfo'] }

I'm assuming the array will always contain two elements?  Would it be
any simpler to return a single dictionary of { 'read': 'int', 'write':
'int' } for naming the two fds directly, instead of having to parse
through [ { 'fd': 1 }, { 'fd': 2 } ] ?

> +
> +##
> +##
>  # @guest-file-close:
>  #
>  # Close an open file in the guest
>  #
>  # @handle: filehandle returned by guest-file-open
>  #
> +# Please note that closing the write side of a pipe will block until the read
> +# side is closed.  If you passed the read-side of the pipe to a qga-spawned
> +# process, make sure the process has exited before attempting to close the
> +# write side.
> +#
>  # Returns: Nothing on success.
>  #
>  # Since: 0.15.0
>
Eric Blake June 19, 2015, 5:34 p.m. UTC | #2
On 06/19/2015 10:57 AM, Denis V. Lunev wrote:
> From: Olga Krishtal <okrishtal@virtuozzo.com>
> 
> The command creates FIFO pair that can be used with existing file
> read/write interfaces to communicate with processes spawned via the
> forthcoming guest-file-exec interface.
> 
> Signed-off-by: Olga Krishtal <okrishtal@virtuozzo.com>
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> Acked-by: Roman Kagan <rkagan@virtuozzo.com>
> CC: Eric Blake <eblake@redhat.com>
> CC: Michael Roth <mdroth@linux.vnet.ibm.com>
> ---
>  qga/commands-posix.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++---
>  qga/commands-win32.c |  8 ++++-
>  qga/qapi-schema.json | 44 ++++++++++++++++++++++++
>  3 files changed, 143 insertions(+), 5 deletions(-)
> 

> +
> +    if (pipe(fd) != 0) {
> +        error_set_errno(errp, errno, QERR_QGA_COMMAND_FAILED, "pipe() failed");
> +        return NULL;
> +    }
> +
> +    this_end = (mode == GUEST_PIPE_MODE_WRITE);
> +    other_end = !this_end;
> +
> +    qemu_set_nonblock(fd[this_end]);
> +
> +    qemu_set_cloexec(fd[this_end]);
> +    qemu_set_cloexec(fd[other_end]);

Would it be better to create a named FIFO somewhere in the file system,
so that you can reopen the connection even if the qga daemon is
restarted?  By using just pipe(), your fds are rather ephemeral, but if
I understand correctly, the rest of the guest-file API tries to persist
across qga restart.
Denis V. Lunev June 19, 2015, 5:59 p.m. UTC | #3
On 19/06/15 20:34, Eric Blake wrote:
> On 06/19/2015 10:57 AM, Denis V. Lunev wrote:
>> From: Olga Krishtal <okrishtal@virtuozzo.com>
>>
>> The command creates FIFO pair that can be used with existing file
>> read/write interfaces to communicate with processes spawned via the
>> forthcoming guest-file-exec interface.
>>
>> Signed-off-by: Olga Krishtal <okrishtal@virtuozzo.com>
>> Signed-off-by: Denis V. Lunev <den@openvz.org>
>> Acked-by: Roman Kagan <rkagan@virtuozzo.com>
>> CC: Eric Blake <eblake@redhat.com>
>> CC: Michael Roth <mdroth@linux.vnet.ibm.com>
>> ---
>>   qga/commands-posix.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++---
>>   qga/commands-win32.c |  8 ++++-
>>   qga/qapi-schema.json | 44 ++++++++++++++++++++++++
>>   3 files changed, 143 insertions(+), 5 deletions(-)
>>
>> +
>> +    if (pipe(fd) != 0) {
>> +        error_set_errno(errp, errno, QERR_QGA_COMMAND_FAILED, "pipe() failed");
>> +        return NULL;
>> +    }
>> +
>> +    this_end = (mode == GUEST_PIPE_MODE_WRITE);
>> +    other_end = !this_end;
>> +
>> +    qemu_set_nonblock(fd[this_end]);
>> +
>> +    qemu_set_cloexec(fd[this_end]);
>> +    qemu_set_cloexec(fd[other_end]);
> Would it be better to create a named FIFO somewhere in the file system,
> so that you can reopen the connection even if the qga daemon is
> restarted?  By using just pipe(), your fds are rather ephemeral, but if
> I understand correctly, the rest of the guest-file API tries to persist
> across qga restart.
>
this will not help IMHO

Let us assume that we have a FIFO on a filesystem
and we have a process which uses this FIFO as
stdout. If QGA will exit that process will blown up with
SIGPIPE on any write attempt.

Den
Denis V. Lunev June 19, 2015, 6:05 p.m. UTC | #4
On 19/06/15 20:30, Eric Blake wrote:
> On 06/19/2015 10:57 AM, Denis V. Lunev wrote:
>> From: Olga Krishtal <okrishtal@virtuozzo.com>
>>
>> The command creates FIFO pair that can be used with existing file
>> read/write interfaces to communicate with processes spawned via the
>> forthcoming guest-file-exec interface.
>>
>> Signed-off-by: Olga Krishtal <okrishtal@virtuozzo.com>
>> Signed-off-by: Denis V. Lunev <den@openvz.org>
>> Acked-by: Roman Kagan <rkagan@virtuozzo.com>
>> CC: Eric Blake <eblake@redhat.com>
>> CC: Michael Roth <mdroth@linux.vnet.ibm.com>
>> ---
>>   qga/commands-posix.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++---
>>   qga/commands-win32.c |  8 ++++-
>>   qga/qapi-schema.json | 44 ++++++++++++++++++++++++
>>   3 files changed, 143 insertions(+), 5 deletions(-)
> Just an interface review at this point:
>
>> +++ b/qga/qapi-schema.json
>> @@ -215,12 +215,56 @@
>>     'returns': 'int' }
>>   
>>   ##
>> +# @GuestPipeMode
>> +#
>> +# An enumeration of pipe modes
>> +# read: pipe is opened for writing data
>> +# write: pipe is opened for reading data
>> +#
>> +# Since: 2.4
>> +##
>> +{ 'enum': 'GuestPipeMode',
>> +  'data': [ 'read', 'write' ] }
>> +
>> +##
>> +# @GuestPipeInfo
>> +#
>> +# Information about pipe.
>> +#
>> +# Since: 2.4
>> +##
>> +{ 'struct': 'GuestPipeInfo',
>> +  'data': {'fd': 'int'} }
> Missing a field of type GuestPipeMode?
yep, nice catch :)

>> +
>> +##
>> +# @guest-pipe-open
>> +#
>> +# Open a pipe to in the guest to associated with a qga-spawned processes
>> +# for communication.
> Reads poorly.  Maybe:
>
> Open a pipe in the guest for association with later qga-spawned processes.
ok

>> +#
>> +# Returns: Guest file handle on success, as per guest-file-open. This
>> +# handle is usable with the same interfaces as a handle returned by
>> +# guest-file-open.
>> +#
>> +# Since: 2.4
>> +##
>> +{ 'command': 'guest-pipe-open',
>> +  'data':    { 'mode': 'GuestPipeMode' },
>> +  'returns': ['GuestPipeInfo'] }
> I'm assuming the array will always contain two elements?  Would it be
> any simpler to return a single dictionary of { 'read': 'int', 'write':
> 'int' } for naming the two fds directly, instead of having to parse
> through [ { 'fd': 1 }, { 'fd': 2 } ] ?

this looks much better to me

>> +
>> +##
>> +##
>>   # @guest-file-close:
>>   #
>>   # Close an open file in the guest
>>   #
>>   # @handle: filehandle returned by guest-file-open
>>   #
>> +# Please note that closing the write side of a pipe will block until the read
>> +# side is closed.  If you passed the read-side of the pipe to a qga-spawned
>> +# process, make sure the process has exited before attempting to close the
>> +# write side.
>> +#
>>   # Returns: Nothing on success.
>>   #
>>   # Since: 0.15.0
>>
Denis V. Lunev June 23, 2015, 10:33 a.m. UTC | #5
On 19/06/15 20:30, Eric Blake wrote:
> On 06/19/2015 10:57 AM, Denis V. Lunev wrote:
>> From: Olga Krishtal <okrishtal@virtuozzo.com>
>>
>> The command creates FIFO pair that can be used with existing file
>> read/write interfaces to communicate with processes spawned via the
>> forthcoming guest-file-exec interface.
>>
>> Signed-off-by: Olga Krishtal <okrishtal@virtuozzo.com>
>> Signed-off-by: Denis V. Lunev <den@openvz.org>
>> Acked-by: Roman Kagan <rkagan@virtuozzo.com>
>> CC: Eric Blake <eblake@redhat.com>
>> CC: Michael Roth <mdroth@linux.vnet.ibm.com>
>> ---
>>   qga/commands-posix.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++---
>>   qga/commands-win32.c |  8 ++++-
>>   qga/qapi-schema.json | 44 ++++++++++++++++++++++++
>>   3 files changed, 143 insertions(+), 5 deletions(-)
> Just an interface review at this point:
>
>> +++ b/qga/qapi-schema.json
>> @@ -215,12 +215,56 @@
>>     'returns': 'int' }
>>   
>>   ##
>> +# @GuestPipeMode
>> +#
>> +# An enumeration of pipe modes
>> +# read: pipe is opened for writing data
>> +# write: pipe is opened for reading data
>> +#
>> +# Since: 2.4
>> +##
>> +{ 'enum': 'GuestPipeMode',
>> +  'data': [ 'read', 'write' ] }
>> +
>> +##
>> +# @GuestPipeInfo
>> +#
>> +# Information about pipe.
>> +#
>> +# Since: 2.4
>> +##
>> +{ 'struct': 'GuestPipeInfo',
>> +  'data': {'fd': 'int'} }
> Missing a field of type GuestPipeMode?
>
>> +
>> +##
>> +# @guest-pipe-open
>> +#
>> +# Open a pipe to in the guest to associated with a qga-spawned processes
>> +# for communication.
> Reads poorly.  Maybe:
>
> Open a pipe in the guest for association with later qga-spawned processes.
>
>> +#
>> +# Returns: Guest file handle on success, as per guest-file-open. This
>> +# handle is usable with the same interfaces as a handle returned by
>> +# guest-file-open.
>> +#
>> +# Since: 2.4
>> +##
>> +{ 'command': 'guest-pipe-open',
>> +  'data':    { 'mode': 'GuestPipeMode' },
>> +  'returns': ['GuestPipeInfo'] }
> I'm assuming the array will always contain two elements?  Would it be
> any simpler to return a single dictionary of { 'read': 'int', 'write':
> 'int' } for naming the two fds directly, instead of having to parse
> through [ { 'fd': 1 }, { 'fd': 2 } ] ?

Eric,

I have mistaken here in the previous letter. The idea
is that we return here only one int, which is qemu file
handle representing the pipe as an entity.

May be this should be declared in a different way to
allow future extendability. Can you advise on this?

Regards,
     Den
diff mbox

Patch

diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index d0f371b..a616996 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -212,6 +212,7 @@  void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
 typedef struct GuestFileHandle {
     uint64_t id;
     FILE *fh;
+    int pipe_other_end_fd; /* if set, it's a pipe fd of the other end. */
     QTAILQ_ENTRY(GuestFileHandle) next;
 } GuestFileHandle;
 
@@ -219,7 +220,8 @@  static struct {
     QTAILQ_HEAD(, GuestFileHandle) filehandles;
 } guest_file_state;
 
-static int64_t guest_file_handle_add(FILE *fh, Error **errp)
+static int64_t guest_file_handle_add(FILE *fh, int pipe_other_end_fd,
+                                     Error **errp)
 {
     GuestFileHandle *gfh;
     int64_t handle;
@@ -232,6 +234,7 @@  static int64_t guest_file_handle_add(FILE *fh, Error **errp)
     gfh = g_malloc0(sizeof(GuestFileHandle));
     gfh->id = handle;
     gfh->fh = fh;
+    gfh->pipe_other_end_fd = pipe_other_end_fd;
     QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
 
     return handle;
@@ -399,7 +402,7 @@  int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode,
      */
     qemu_set_nonblock(fileno(fh));
 
-    handle = guest_file_handle_add(fh, errp);
+    handle = guest_file_handle_add(fh, -1, errp);
     if (handle < 0) {
         fclose(fh);
         return -1;
@@ -409,6 +412,85 @@  int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode,
     return handle;
 }
 
+GuestPipeInfoList *qmp_guest_pipe_open(GuestPipeMode mode, Error **errp)
+{
+    FILE *f = NULL;
+    int fd[2], this_end, other_end;
+    int64_t handle;
+    GuestPipeInfoList *pipe_list;
+    GuestPipeInfo *pipe_inf;
+    const char *op_flag;
+
+    slog("guest-pipe-open called");
+
+    if (mode > 2) {
+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+                  "Only \"r\" or \"w\" are the valid modes to open a pipe");
+        return NULL;
+    }
+
+    if (pipe(fd) != 0) {
+        error_set_errno(errp, errno, QERR_QGA_COMMAND_FAILED, "pipe() failed");
+        return NULL;
+    }
+
+    this_end = (mode == GUEST_PIPE_MODE_WRITE);
+    other_end = !this_end;
+
+    qemu_set_nonblock(fd[this_end]);
+
+    qemu_set_cloexec(fd[this_end]);
+    qemu_set_cloexec(fd[other_end]);
+
+    if (mode == GUEST_PIPE_MODE_READ) {
+        op_flag = "r";
+    } else {
+        op_flag = "w";
+    }
+    f = fdopen(fd[this_end], op_flag);
+    if (f == NULL) {
+        error_set_errno(errp, errno, QERR_QGA_COMMAND_FAILED,
+                        "fdopen() failed to open pipe handle");
+        goto fail;
+    }
+
+    handle = guest_file_handle_add(f, fd[other_end], errp);
+    if (handle == -1) {
+        goto fail;
+    }
+
+    slog("guest-pipe-open: handle: %" PRId64, handle);
+
+    pipe_inf = g_malloc0(sizeof(*pipe_inf));
+    pipe_inf->fd = handle;
+    pipe_list = g_malloc0(sizeof(*pipe_list));
+    pipe_list->value = pipe_inf;
+    pipe_list->next = NULL;
+    return pipe_list;
+
+fail:
+    if (f != NULL) {
+        fclose(f);
+    } else {
+        close(fd[this_end]);
+    }
+    close(fd[other_end]);
+    return NULL;
+}
+
+static int guest_pipe_close_other_end(GuestFileHandle *gfh)
+{
+    if (gfh->pipe_other_end_fd != -1) {
+        if (close(gfh->pipe_other_end_fd) != 0) {
+            return 1;
+        }
+
+        gfh->pipe_other_end_fd = -1;
+    }
+
+    return 0;
+}
+
 void qmp_guest_file_close(int64_t handle, Error **errp)
 {
     GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
@@ -419,6 +501,11 @@  void qmp_guest_file_close(int64_t handle, Error **errp)
         return;
     }
 
+    if (guest_pipe_close_other_end(gfh) != 0) {
+        error_setg_errno(errp, errno, "failed to close pipe handle");
+        return;
+    }
+
     ret = fclose(gfh->fh);
     if (ret == EOF) {
         error_setg_errno(errp, errno, "failed to close handle");
@@ -2395,7 +2482,7 @@  GList *ga_command_blacklist_init(GList *blacklist)
             "guest-suspend-hybrid", "guest-network-get-interfaces",
             "guest-get-vcpus", "guest-set-vcpus",
             "guest-get-memory-blocks", "guest-set-memory-blocks",
-            "guest-get-memory-block-size", NULL};
+            "guest-get-memory-block-size", "guest-pipe-open", NULL};
         char **p = (char **)list;
 
         while (*p) {
@@ -2409,7 +2496,8 @@  GList *ga_command_blacklist_init(GList *blacklist)
         const char *list[] = {
             "guest-get-fsinfo", "guest-fsfreeze-status",
             "guest-fsfreeze-freeze", "guest-fsfreeze-freeze-list",
-            "guest-fsfreeze-thaw", "guest-get-fsinfo", NULL};
+            "guest-fsfreeze-thaw", "guest-get-fsinfo",
+            "guest-pipe-open", NULL};
         char **p = (char **)list;
 
         while (*p) {
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
index 3ef0549..685dd0f 100644
--- a/qga/commands-win32.c
+++ b/qga/commands-win32.c
@@ -154,6 +154,12 @@  int64_t qmp_guest_file_open(const char *path, bool has_mode,
     return fd;
 }
 
+GuestPipeInfoList *qmp_guest_pipe_open(GuestPipeMode mode, Error **errp)
+{
+    error_set(errp, QERR_UNSUPPORTED);
+    return NULL;
+}
+
 void qmp_guest_file_close(int64_t handle, Error **errp)
 {
     bool ret;
@@ -713,7 +719,7 @@  GList *ga_command_blacklist_init(GList *blacklist)
         "guest-get-memory-blocks", "guest-set-memory-blocks",
         "guest-get-memory-block-size",
         "guest-fsfreeze-freeze-list", "guest-get-fsinfo",
-        "guest-fstrim", NULL};
+        "guest-fstrim", "guest-pipe-open", NULL};
     char **p = (char **)list_unsupported;
 
     while (*p) {
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index b446dc7..8081213 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -215,12 +215,56 @@ 
   'returns': 'int' }
 
 ##
+# @GuestPipeMode
+#
+# An enumeration of pipe modes
+# read: pipe is opened for writing data
+# write: pipe is opened for reading data
+#
+# Since: 2.4
+##
+{ 'enum': 'GuestPipeMode',
+  'data': [ 'read', 'write' ] }
+
+##
+# @GuestPipeInfo
+#
+# Information about pipe.
+#
+# Since: 2.4
+##
+{ 'struct': 'GuestPipeInfo',
+  'data': {'fd': 'int'} }
+
+##
+# @guest-pipe-open
+#
+# Open a pipe to in the guest to associated with a qga-spawned processes
+# for communication.
+#
+# Returns: Guest file handle on success, as per guest-file-open. This
+# handle is usable with the same interfaces as a handle returned by
+# guest-file-open.
+#
+# Since: 2.4
+##
+{ 'command': 'guest-pipe-open',
+  'data':    { 'mode': 'GuestPipeMode' },
+  'returns': ['GuestPipeInfo'] }
+
+##
+##
 # @guest-file-close:
 #
 # Close an open file in the guest
 #
 # @handle: filehandle returned by guest-file-open
 #
+# Please note that closing the write side of a pipe will block until the read
+# side is closed.  If you passed the read-side of the pipe to a qga-spawned
+# process, make sure the process has exited before attempting to close the
+# write side.
+#
 # Returns: Nothing on success.
 #
 # Since: 0.15.0