This patch adds the pass-fd QMP command using the QAPI framework.
Like the getfd command, it is used to pass a file descriptor via
SCM_RIGHTS and associate it with a name.  However, the pass-fd
command also returns the received file descriptor, which is a
difference in behavior from the getfd command, which returns
nothing.  pass-fd also supports a boolean "force" argument that
can be used to specify that an existing file descriptor is
associated with the specified name, and that it should become a
copy of the newly passed file descriptor.

The closefd command can be used to close a file descriptor that was
passed with the pass-fd command.

Note that when using getfd or pass-fd, there are some commands
(e.g. migrate with fd:name) that implicitly close the named fd.
When this is not the case, closefd must be used to avoid fd leaks.

Signed-off-by: Corey Bryant <cor...@linux.vnet.ibm.com>
---
v2:
 -Introduce new QMP command to pass/return fd (lcapitul...@redhat.com)
 -Use passfd as command name (berra...@redhat.com)

v3:
 -Use pass-fd as command name (lcapitul...@redhat.com)
 -Fail pass-fd if fdname already exists (lcapitul...@redhat.com)
 -Add notes to QMP command describing behavior in more detail
  (lcapitul...@redhat.com, ebl...@redhat.com)
 -Add note about fd leakage (ebl...@redhat.com)

v4:
 -Don't return same error class twice (lcapitul...@redhat.com)
 -Share code for qmp_gefd and qmp_pass_fd (lcapitul...@redhat.com)
 -Update qmp_closefd to call monitor_get_fd
 -Add support for "force" argument to pass-fd (ebl...@redhat.com)

 dump.c           |    3 +-
 migration-fd.c   |    2 +-
 monitor.c        |   96 +++++++++++++++++++++++++++++++++++-------------------
 monitor.h        |    2 +-
 net.c            |    6 ++--
 qapi-schema.json |   36 ++++++++++++++++++++
 qmp-commands.hx  |   42 +++++++++++++++++++++++-
 7 files changed, 146 insertions(+), 41 deletions(-)

diff --git a/dump.c b/dump.c
index 4412d7a..2fd8ced 100644
--- a/dump.c
+++ b/dump.c
@@ -836,9 +836,8 @@ void qmp_dump_guest_memory(bool paging, const char *file, 
bool has_begin,
 
 #if !defined(WIN32)
     if (strstart(file, "fd:", &p)) {
-        fd = monitor_get_fd(cur_mon, p);
+        fd = monitor_get_fd(cur_mon, p, errp);
         if (fd == -1) {
-            error_set(errp, QERR_FD_NOT_FOUND, p);
             return;
         }
     }
diff --git a/migration-fd.c b/migration-fd.c
index 50138ed..7335167 100644
--- a/migration-fd.c
+++ b/migration-fd.c
@@ -75,7 +75,7 @@ static int fd_close(MigrationState *s)
 
 int fd_start_outgoing_migration(MigrationState *s, const char *fdname)
 {
-    s->fd = monitor_get_fd(cur_mon, fdname);
+    s->fd = monitor_get_fd(cur_mon, fdname, NULL);
     if (s->fd == -1) {
         DPRINTF("fd_migration: invalid file descriptor identifier\n");
         goto err_after_get_fd;
diff --git a/monitor.c b/monitor.c
index 1a7f7e7..3433c06 100644
--- a/monitor.c
+++ b/monitor.c
@@ -814,7 +814,7 @@ static int add_graphics_client(Monitor *mon, const QDict 
*qdict, QObject **ret_d
     CharDriverState *s;
 
     if (strcmp(protocol, "spice") == 0) {
-        int fd = monitor_get_fd(mon, fdname);
+        int fd = monitor_get_fd(mon, fdname, NULL);
         int skipauth = qdict_get_try_bool(qdict, "skipauth", 0);
         int tls = qdict_get_try_bool(qdict, "tls", 0);
         if (!using_spice) {
@@ -828,18 +828,18 @@ static int add_graphics_client(Monitor *mon, const QDict 
*qdict, QObject **ret_d
         return 0;
 #ifdef CONFIG_VNC
     } else if (strcmp(protocol, "vnc") == 0) {
-       int fd = monitor_get_fd(mon, fdname);
+        int fd = monitor_get_fd(mon, fdname, NULL);
         int skipauth = qdict_get_try_bool(qdict, "skipauth", 0);
-       vnc_display_add_client(NULL, fd, skipauth);
-       return 0;
+        vnc_display_add_client(NULL, fd, skipauth);
+        return 0;
 #endif
     } else if ((s = qemu_chr_find(protocol)) != NULL) {
-       int fd = monitor_get_fd(mon, fdname);
-       if (qemu_chr_add_client(s, fd) < 0) {
-           qerror_report(QERR_ADD_CLIENT_FAILED);
-           return -1;
-       }
-       return 0;
+        int fd = monitor_get_fd(mon, fdname, NULL);
+        if (qemu_chr_add_client(s, fd) < 0) {
+            qerror_report(QERR_ADD_CLIENT_FAILED);
+            return -1;
+        }
+        return 0;
     }
 
     qerror_report(QERR_INVALID_PARAMETER, "protocol");
@@ -2182,57 +2182,69 @@ static void do_inject_mce(Monitor *mon, const QDict 
*qdict)
 }
 #endif
 
-void qmp_getfd(const char *fdname, Error **errp)
+static int monitor_put_fd(Monitor *mon, const char *fdname, bool replace,
+                          bool copy, Error **errp)
 {
     mon_fd_t *monfd;
     int fd;
 
-    fd = qemu_chr_fe_get_msgfd(cur_mon->chr);
+    fd = qemu_chr_fe_get_msgfd(mon->chr);
     if (fd == -1) {
         error_set(errp, QERR_FD_NOT_SUPPLIED);
-        return;
+        return -1;
     }
 
     if (qemu_isdigit(fdname[0])) {
         error_set(errp, QERR_INVALID_PARAMETER_VALUE, "fdname",
                   "a name not starting with a digit");
-        return;
+        return -1;
     }
 
-    QLIST_FOREACH(monfd, &cur_mon->fds, next) {
+    QLIST_FOREACH(monfd, &mon->fds, next) {
         if (strcmp(monfd->name, fdname) != 0) {
             continue;
         }
 
-        close(monfd->fd);
-        monfd->fd = fd;
-        return;
+        if (replace) {
+            /* the existing fd is replaced with the new fd */
+            close(monfd->fd);
+            monfd->fd = fd;
+            return fd;
+        }
+
+        if (copy) {
+            /* the existing fd becomes a copy of the new fd */
+            if (dup2(fd, monfd->fd) == -1) {
+                error_set(errp, QERR_UNDEFINED_ERROR);
+                return -1;
+            }
+            close(fd);
+            return monfd->fd;
+        }
+
+        error_set(errp, QERR_INVALID_PARAMETER, "fdname");
+        return -1;
+    }
+
+    if (copy) {
+        error_set(errp, QERR_INVALID_PARAMETER, "fdname");
+        return -1;
     }
 
     monfd = g_malloc0(sizeof(mon_fd_t));
     monfd->name = g_strdup(fdname);
     monfd->fd = fd;
 
-    QLIST_INSERT_HEAD(&cur_mon->fds, monfd, next);
+    QLIST_INSERT_HEAD(&mon->fds, monfd, next);
+    return fd;
 }
 
 void qmp_closefd(const char *fdname, Error **errp)
 {
-    mon_fd_t *monfd;
-
-    QLIST_FOREACH(monfd, &cur_mon->fds, next) {
-        if (strcmp(monfd->name, fdname) != 0) {
-            continue;
-        }
-
-        QLIST_REMOVE(monfd, next);
-        close(monfd->fd);
-        g_free(monfd->name);
-        g_free(monfd);
-        return;
+    int fd = monitor_get_fd(cur_mon, fdname, errp);
+    if (fd != -1) {
+        close(fd);
     }
-
-    error_set(errp, QERR_FD_NOT_FOUND, fdname);
 }
 
 static void do_loadvm(Monitor *mon, const QDict *qdict)
@@ -2247,7 +2259,7 @@ static void do_loadvm(Monitor *mon, const QDict *qdict)
     }
 }
 
-int monitor_get_fd(Monitor *mon, const char *fdname)
+int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp)
 {
     mon_fd_t *monfd;
 
@@ -2268,9 +2280,25 @@ int monitor_get_fd(Monitor *mon, const char *fdname)
         return fd;
     }
 
+    error_set(errp, QERR_FD_NOT_FOUND, fdname);
     return -1;
 }
 
+int64_t qmp_pass_fd(const char *fdname, bool has_force, bool force,
+                    Error **errp)
+{
+    bool replace = false;
+    bool copy = has_force ? force : false;
+    return monitor_put_fd(cur_mon, fdname, replace, copy, errp);
+}
+
+void qmp_getfd(const char *fdname, Error **errp)
+{
+    bool replace = true;
+    bool copy = false;
+    monitor_put_fd(cur_mon, fdname, replace, copy, errp);
+}
+
 /* mon_cmds and info_cmds would be sorted at runtime */
 static mon_cmd_t mon_cmds[] = {
 #include "hmp-commands.h"
diff --git a/monitor.h b/monitor.h
index cd1d878..8fa515d 100644
--- a/monitor.h
+++ b/monitor.h
@@ -63,7 +63,7 @@ int monitor_read_block_device_key(Monitor *mon, const char 
*device,
                                   BlockDriverCompletionFunc *completion_cb,
                                   void *opaque);
 
-int monitor_get_fd(Monitor *mon, const char *fdname);
+int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp);
 
 void monitor_vprintf(Monitor *mon, const char *fmt, va_list ap)
     GCC_FMT_ATTR(2, 0);
diff --git a/net.c b/net.c
index 4aa416c..28860b8 100644
--- a/net.c
+++ b/net.c
@@ -730,12 +730,14 @@ int qemu_find_nic_model(NICInfo *nd, const char * const 
*models,
 int net_handle_fd_param(Monitor *mon, const char *param)
 {
     int fd;
+    Error *local_err = NULL;
 
     if (!qemu_isdigit(param[0]) && mon) {
 
-        fd = monitor_get_fd(mon, param);
+        fd = monitor_get_fd(mon, param, &local_err);
         if (fd == -1) {
-            error_report("No file descriptor named %s found", param);
+            qerror_report_err(local_err);
+            error_free(local_err);
             return -1;
         }
     } else {
diff --git a/qapi-schema.json b/qapi-schema.json
index 26a6b84..2ac1261 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1864,6 +1864,41 @@
 { 'command': 'netdev_del', 'data': {'id': 'str'} }
 
 ##
+# @pass-fd:
+#
+# Pass a file descriptor via SCM rights and assign it a name
+#
+# @fdname: file descriptor name
+#
+# @force: #optional true specifies that an existing file descriptor is
+#         associated with @fdname, and that it should become a copy of the
+#         newly passed file descriptor.
+#
+# Returns: The QEMU file descriptor that is assigned to @fdname
+#          If file descriptor was not received, FdNotSupplied
+#          If @fdname is not valid, InvalidParameterValue
+#          If @fdname already exists, and @force is not true, InvalidParameter
+#          If @fdname does not already exist, and @force is true,
+#          InvalidParameter
+#          If @force fails to copy the passed file descriptor,
+#          UndefinedError
+#
+# Since: 1.2.0
+#
+# Notes: If @fdname already exists, and @force is not true, the
+#        command will fail.
+#
+#        If @fdname already exists, and @force is true, the value of
+#        the existing file descriptor is returned when the command is
+#        successful.
+#
+#        The 'closefd' command can be used to explicitly close the
+#        file descriptor when it is no longer needed.
+##
+{ 'command': 'pass-fd', 'data': {'fdname': 'str', '*force': 'bool'},
+  'returns': 'int' }
+
+##
 # @getfd:
 #
 # Receive a file descriptor via SCM rights and assign it a name
@@ -1879,6 +1914,7 @@
 # Notes: If @fdname already exists, the file descriptor assigned to
 #        it will be closed and replaced by the received file
 #        descriptor.
+#
 #        The 'closefd' command can be used to explicitly close the
 #        file descriptor when it is no longer needed.
 ##
diff --git a/qmp-commands.hx b/qmp-commands.hx
index e3cf3c5..7e3c07e 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -869,9 +869,49 @@ Example:
 EQMP
 
     {
+        .name       = "pass-fd",
+        .args_type  = "fdname:s,force:b?",
+        .params     = "pass-fd fdname force",
+        .help       = "pass a file descriptor via SCM rights and assign it a 
name",
+        .mhandler.cmd_new = qmp_marshal_input_pass_fd,
+    },
+
+SQMP
+pass-fd
+-------
+
+Pass a file descriptor via SCM rights and assign it a name.
+
+Arguments:
+
+- "fdname": file descriptor name (json-string)
+- "force": true specifies that an existing file descriptor is associated
+           with "fdname", and that it should become a copy of the newly
+           passed file descriptor. (json-bool, optional)
+
+Return a json-int with the QEMU file descriptor that is assigned to "fdname".
+
+Example:
+
+-> { "execute": "pass-fd", "arguments": { "fdname": "fd1" } }
+<- { "return": 42 }
+
+Notes:
+
+(1) If the name specified by the "fdname" argument already exists, and
+    "force" is not true, the command will fail.
+(2) If the name specified by the "fdname" argument already exists, and
+    "force" is true, the value of the existing file descriptor is
+    returned when the command is successful.
+(3) The 'closefd' command can be used to explicitly close the file
+    descriptor when it is no longer needed.
+
+EQMP
+
+    {
         .name       = "getfd",
         .args_type  = "fdname:s",
-        .params     = "getfd name",
+        .params     = "getfd fdname",
         .help       = "receive a file descriptor via SCM rights and assign it 
a name",
         .mhandler.cmd_new = qmp_marshal_input_getfd,
     },
-- 
1.7.10.2


Reply via email to