[libvirt] [PATCH v3 8/9] rpc: pass listen FD to the daemon being started

Martin Kletzander mkletzan at redhat.com
Wed Jul 23 14:27:12 UTC 2014


This eliminates the need for active waiting.

Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=927369

Signed-off-by: Martin Kletzander <mkletzan at redhat.com>
---
 src/rpc/virnetsocket.c | 102 ++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 83 insertions(+), 19 deletions(-)

diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c
index a94b2bc..46be541 100644
--- a/src/rpc/virnetsocket.c
+++ b/src/rpc/virnetsocket.c
@@ -1,7 +1,7 @@
 /*
  * virnetsocket.c: generic network socket handling
  *
- * Copyright (C) 2006-2013 Red Hat, Inc.
+ * Copyright (C) 2006-2014 Red Hat, Inc.
  * Copyright (C) 2006 Daniel P. Berrange
  *
  * This library is free software; you can redistribute it and/or
@@ -121,7 +121,7 @@ VIR_ONCE_GLOBAL_INIT(virNetSocket)


 #ifndef WIN32
-static int virNetSocketForkDaemon(const char *binary)
+static int virNetSocketForkDaemon(const char *binary, int passfd)
 {
     int ret;
     virCommandPtr cmd = virCommandNewArgList(binary,
@@ -134,6 +134,10 @@ static int virNetSocketForkDaemon(const char *binary)
     virCommandAddEnvPassBlockSUID(cmd, "XDG_RUNTIME_DIR", NULL);
     virCommandClearCaps(cmd);
     virCommandDaemonize(cmd);
+    if (passfd) {
+        virCommandPassFD(cmd, passfd, VIR_COMMAND_PASS_FD_CLOSE_PARENT);
+        virCommandPassListenFDs(cmd);
+    }
     ret = virCommandRun(cmd, NULL);
     virCommandFree(cmd);
     return ret;
@@ -540,10 +544,11 @@ int virNetSocketNewConnectUNIX(const char *path,
                                const char *binary,
                                virNetSocketPtr *retsock)
 {
+    char *buf = NULL;
+    int errfd[2] = { -1, -1 };
+    int fd, passfd;
     virSocketAddr localAddr;
     virSocketAddr remoteAddr;
-    int fd;
-    int retries = 0;

     memset(&localAddr, 0, sizeof(localAddr));
     memset(&remoteAddr, 0, sizeof(remoteAddr));
@@ -569,28 +574,82 @@ int virNetSocketNewConnectUNIX(const char *path,
     if (remoteAddr.data.un.sun_path[0] == '@')
         remoteAddr.data.un.sun_path[0] = '\0';

- retry:
-    if (connect(fd, &remoteAddr.data.sa, remoteAddr.len) < 0) {
-        if ((errno == ECONNREFUSED ||
-             errno == ENOENT) &&
-            spawnDaemon && retries < 20) {
-            VIR_DEBUG("Connection refused for %s, trying to spawn %s",
-                      path, binary);
-            if (retries == 0 &&
-                virNetSocketForkDaemon(binary) < 0)
-                goto error;
+    if (spawnDaemon) {
+        int err = 0;
+        int rv = -1;
+        int status = 0;
+        pid_t pid = 0;

-            retries++;
-            usleep(1000 * 100 * retries);
-            goto retry;
+        if ((passfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+            virReportSystemError(errno, "%s", _("Failed to create socket"));
+            goto error;
         }

-        virReportSystemError(errno,
-                             _("Failed to connect socket to '%s'"),
+        if (pipe2(errfd, O_CLOEXEC) < 0) {
+            virReportSystemError(errno, "%s",
+                                 _("Cannot create pipe for child"));
+            goto error;
+        }
+
+        /*
+         * We have to fork() here, because umask() is set
+         * per-process, chmod() is racy and fchmod() has undefined
+         * behaviour on sockets according to POSIX, so it doesn't
+         * work outside Linux.
+         */
+
+        if ((pid = virFork()) < 0)
+            goto error;
+
+        if (pid == 0) {
+            VIR_FORCE_CLOSE(errfd[0]);
+
+            umask(0077);
+            rv = bind(passfd, &remoteAddr.data.sa, remoteAddr.len);
+            if (rv < 0) {
+                ignore_value(safewrite(errfd[1], &errno, sizeof(int)));
+            }
+            VIR_FORCE_CLOSE(errfd[1]);
+            _exit(rv < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+        }
+
+        VIR_FORCE_CLOSE(errfd[1]);
+        rv = virProcessWait(pid, &status, false);
+        ignore_value(saferead(errfd[0], &err, sizeof(int)));
+        VIR_FORCE_CLOSE(errfd[0]);
+
+        if (rv < 0 || status != EXIT_SUCCESS) {
+            if (err) {
+                virReportSystemError(err,
+                                     _("Failed to bind socket to %s "
+                                       "in child process"),
+                                     path);
+            } else {
+                virReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("Failed to bind socket to %s "
+                                 "in child process"),
+                               path);
+            }
+            goto error;
+        }
+
+        if (listen(passfd, 0) < 0) {
+            virReportSystemError(errno, "%s",
+                                 _("Failed to listen on socket that's about "
+                                   "to be passed to the daemon"));
+            goto error;
+        }
+    }
+
+    if (connect(fd, &remoteAddr.data.sa, remoteAddr.len) < 0) {
+        virReportSystemError(errno, _("Failed to connect socket to '%s'"),
                              path);
         goto error;
     }

+    if (spawnDaemon && virNetSocketForkDaemon(binary, passfd) < 0)
+        goto error;
+
     localAddr.len = sizeof(localAddr.data);
     if (getsockname(fd, &localAddr.data.sa, &localAddr.len) < 0) {
         virReportSystemError(errno, "%s", _("Unable to get local socket name"));
@@ -603,7 +662,12 @@ int virNetSocketNewConnectUNIX(const char *path,
     return 0;

  error:
+    VIR_FREE(buf);
     VIR_FORCE_CLOSE(fd);
+    VIR_FORCE_CLOSE(errfd[0]);
+    VIR_FORCE_CLOSE(errfd[1]);
+    if (spawnDaemon)
+        unlink(path);
     return -1;
 }
 #else
-- 
2.0.2




More information about the libvir-list mailing list