c5b0b3
From: Michal Privoznik <mprivozn@redhat.com>
c5b0b3
Date: Thu, 2 Apr 2015 14:41:17 +0200
c5b0b3
Subject: [PATCH] virNetSocketNewConnectUNIX: Use flocks when spawning a daemon
c5b0b3
c5b0b3
https://bugzilla.redhat.com/show_bug.cgi?id=1200149
c5b0b3
c5b0b3
Even though we have a mutex mechanism so that two clients don't spawn
c5b0b3
two daemons, it's not strong enough. It can happen that while one
c5b0b3
client is spawning the daemon, the other one fails to connect.
c5b0b3
Basically two possible errors can happen:
c5b0b3
c5b0b3
  error: Failed to connect socket to '/home/mprivozn/.cache/libvirt/libvirt-sock': Connection refused
c5b0b3
c5b0b3
or:
c5b0b3
c5b0b3
  error: Failed to connect socket to '/home/mprivozn/.cache/libvirt/libvirt-sock': No such file or directory
c5b0b3
c5b0b3
The problem in both cases is, the daemon is only starting up, while we
c5b0b3
are trying to connect (and fail). We should postpone the connecting
c5b0b3
phase until the daemon is started (by the other thread that is
c5b0b3
spawning it). In order to do that, create a file lock 'libvirt-lock'
c5b0b3
in the directory where session daemon would create its socket. So even
c5b0b3
when called from multiple processes, spawning a daemon will serialize
c5b0b3
on the file lock. So only the first to come will spawn the daemon.
c5b0b3
c5b0b3
Tested-by: Richard W. M. Jones <rjones@redhat.com>
c5b0b3
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
c5b0b3
(cherry picked from commit be78814ae07f092d9c4e71fd82dd1947aba2f029)
c5b0b3
---
c5b0b3
 src/rpc/virnetsocket.c | 164 +++++++++++++++++--------------------------------
c5b0b3
 1 file changed, 55 insertions(+), 109 deletions(-)
c5b0b3
c5b0b3
diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c
c5b0b3
index 6b019cc..b824285 100644
c5b0b3
--- a/src/rpc/virnetsocket.c
c5b0b3
+++ b/src/rpc/virnetsocket.c
c5b0b3
@@ -123,7 +123,7 @@ VIR_ONCE_GLOBAL_INIT(virNetSocket)
c5b0b3
 
c5b0b3
 
c5b0b3
 #ifndef WIN32
c5b0b3
-static int virNetSocketForkDaemon(const char *binary, int passfd)
c5b0b3
+static int virNetSocketForkDaemon(const char *binary)
c5b0b3
 {
c5b0b3
     int ret;
c5b0b3
     virCommandPtr cmd = virCommandNewArgList(binary,
c5b0b3
@@ -136,10 +136,6 @@ static int virNetSocketForkDaemon(const char *binary, int passfd)
c5b0b3
     virCommandAddEnvPassBlockSUID(cmd, "XDG_RUNTIME_DIR", NULL);
c5b0b3
     virCommandClearCaps(cmd);
c5b0b3
     virCommandDaemonize(cmd);
c5b0b3
-    if (passfd) {
c5b0b3
-        virCommandPassFD(cmd, passfd, VIR_COMMAND_PASS_FD_CLOSE_PARENT);
c5b0b3
-        virCommandPassListenFDs(cmd);
c5b0b3
-    }
c5b0b3
     ret = virCommandRun(cmd, NULL);
c5b0b3
     virCommandFree(cmd);
c5b0b3
     return ret;
c5b0b3
@@ -543,45 +539,26 @@ int virNetSocketNewConnectUNIX(const char *path,
c5b0b3
                                const char *binary,
c5b0b3
                                virNetSocketPtr *retsock)
c5b0b3
 {
c5b0b3
-    char *binname = NULL;
c5b0b3
-    char *pidpath = NULL;
c5b0b3
-    int fd, passfd = -1;
c5b0b3
-    int pidfd = -1;
c5b0b3
+    char *lockpath = NULL;
c5b0b3
+    int lockfd = -1;
c5b0b3
+    int fd = -1;
c5b0b3
+    int retries = 100;
c5b0b3
     virSocketAddr localAddr;
c5b0b3
     virSocketAddr remoteAddr;
c5b0b3
+    char *rundir = NULL;
c5b0b3
 
c5b0b3
     memset(&localAddr, 0, sizeof(localAddr));
c5b0b3
     memset(&remoteAddr, 0, sizeof(remoteAddr));
c5b0b3
 
c5b0b3
     remoteAddr.len = sizeof(remoteAddr.data.un);
c5b0b3
 
c5b0b3
-    if (spawnDaemon && !binary) {
c5b0b3
-        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
c5b0b3
-                       _("Auto-spawn of daemon requested, but no binary specified"));
c5b0b3
-        return -1;
c5b0b3
-    }
c5b0b3
-
c5b0b3
-    if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
c5b0b3
-        virReportSystemError(errno, "%s", _("Failed to create socket"));
c5b0b3
-        goto error;
c5b0b3
-    }
c5b0b3
-
c5b0b3
-    remoteAddr.data.un.sun_family = AF_UNIX;
c5b0b3
-    if (virStrcpyStatic(remoteAddr.data.un.sun_path, path) == NULL) {
c5b0b3
-        virReportSystemError(ENOMEM, _("Path %s too long for unix socket"), path);
c5b0b3
-        goto error;
c5b0b3
-    }
c5b0b3
-    if (remoteAddr.data.un.sun_path[0] == '@')
c5b0b3
-        remoteAddr.data.un.sun_path[0] = '\0';
c5b0b3
-
c5b0b3
- retry:
c5b0b3
-    if (connect(fd, &remoteAddr.data.sa, remoteAddr.len) < 0) {
c5b0b3
-        int status = 0;
c5b0b3
-        pid_t pid = 0;
c5b0b3
+    if (spawnDaemon) {
c5b0b3
+        const char *binname;
c5b0b3
 
c5b0b3
-        if (!spawnDaemon) {
c5b0b3
-            virReportSystemError(errno, _("Failed to connect socket to '%s'"),
c5b0b3
-                                 path);
c5b0b3
+        if (spawnDaemon && !binary) {
c5b0b3
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
c5b0b3
+                           _("Auto-spawn of daemon requested, "
c5b0b3
+                             "but no binary specified"));
c5b0b3
             goto error;
c5b0b3
         }
c5b0b3
 
c5b0b3
@@ -592,90 +569,63 @@ int virNetSocketNewConnectUNIX(const char *path,
c5b0b3
             goto error;
c5b0b3
         }
c5b0b3
 
c5b0b3
-        if (virPidFileConstructPath(false, NULL, binname, &pidpath) < 0)
c5b0b3
+        if (!(rundir = virGetUserRuntimeDirectory()))
c5b0b3
             goto error;
c5b0b3
 
c5b0b3
-        pidfd = virPidFileAcquirePath(pidpath, false, getpid());
c5b0b3
-        if (pidfd < 0) {
c5b0b3
-            /*
c5b0b3
-             * This can happen in a very rare case of two clients spawning two
c5b0b3
-             * daemons at the same time, and the error in the logs that gets
c5b0b3
-             * reset here can be a clue to some future debugging.
c5b0b3
-             */
c5b0b3
-            virResetLastError();
c5b0b3
-            spawnDaemon = false;
c5b0b3
-            goto retry;
c5b0b3
-        }
c5b0b3
-
c5b0b3
-        if ((passfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
c5b0b3
-            virReportSystemError(errno, "%s", _("Failed to create socket"));
c5b0b3
+        if (virFileMakePathWithMode(rundir, 0700) < 0) {
c5b0b3
+            virReportSystemError(errno,
c5b0b3
+                                 _("Cannot create user runtime directory '%s'"),
c5b0b3
+                                 rundir);
c5b0b3
             goto error;
c5b0b3
         }
c5b0b3
 
c5b0b3
-        /*
c5b0b3
-         * We already even acquired the pidfile, so no one else should be using
c5b0b3
-         * @path right now.  So we're OK to unlink it and paying attention to
c5b0b3
-         * the return value makes no real sense here.  Only if it's not an
c5b0b3
-         * abstract socket, of course.
c5b0b3
-         */
c5b0b3
-        if (path[0] != '@')
c5b0b3
-            unlink(path);
c5b0b3
-
c5b0b3
-        /*
c5b0b3
-         * We have to fork() here, because umask() is set per-process, chmod()
c5b0b3
-         * is racy and fchmod() has undefined behaviour on sockets according to
c5b0b3
-         * POSIX, so it doesn't work outside Linux.
c5b0b3
-         */
c5b0b3
-        if ((pid = virFork()) < 0)
c5b0b3
+        if (virAsprintf(&lockpath, "%s/%s.lock", rundir, binname) < 0)
c5b0b3
             goto error;
c5b0b3
 
c5b0b3
-        if (pid == 0) {
c5b0b3
-            umask(0077);
c5b0b3
-            if (bind(passfd, &remoteAddr.data.sa, remoteAddr.len) < 0)
c5b0b3
-                _exit(EXIT_FAILURE);
c5b0b3
-
c5b0b3
-            _exit(EXIT_SUCCESS);
c5b0b3
-        }
c5b0b3
-
c5b0b3
-        if (virProcessWait(pid, &status, false) < 0)
c5b0b3
+        if ((lockfd = open(lockpath, O_RDWR | O_CREAT, 0600)) < 0 ||
c5b0b3
+            virSetCloseExec(lockfd) < 0) {
c5b0b3
+            virReportSystemError(errno, _("Unable to create lock '%s'"), lockpath);
c5b0b3
             goto error;
c5b0b3
-
c5b0b3
-        if (status != EXIT_SUCCESS) {
c5b0b3
-            /*
c5b0b3
-             * OK, so the child failed to bind() the socket.  This may mean that
c5b0b3
-             * another daemon was starting at the same time and succeeded with
c5b0b3
-             * its bind() (even though it should not happen because we using a
c5b0b3
-             * pidfile for the race).  So we'll try connecting again, but this
c5b0b3
-             * time without spawning the daemon.
c5b0b3
-             */
c5b0b3
-            spawnDaemon = false;
c5b0b3
-            virPidFileDeletePath(pidpath);
c5b0b3
-            VIR_FORCE_CLOSE(pidfd);
c5b0b3
-            VIR_FORCE_CLOSE(passfd);
c5b0b3
-            goto retry;
c5b0b3
         }
c5b0b3
 
c5b0b3
-        if (listen(passfd, 0) < 0) {
c5b0b3
-            virReportSystemError(errno, "%s",
c5b0b3
-                                 _("Failed to listen on socket that's about "
c5b0b3
-                                   "to be passed to the daemon"));
c5b0b3
+        if (virFileLock(lockfd, false, 0, 1, true) < 0) {
c5b0b3
+            virReportSystemError(errno, _("Unable to lock '%s'"), lockpath);
c5b0b3
             goto error;
c5b0b3
         }
c5b0b3
+    }
c5b0b3
+
c5b0b3
+    if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
c5b0b3
+        virReportSystemError(errno, "%s", _("Failed to create socket"));
c5b0b3
+        goto error;
c5b0b3
+    }
c5b0b3
 
c5b0b3
-        if (connect(fd, &remoteAddr.data.sa, remoteAddr.len) < 0) {
c5b0b3
+    remoteAddr.data.un.sun_family = AF_UNIX;
c5b0b3
+    if (virStrcpyStatic(remoteAddr.data.un.sun_path, path) == NULL) {
c5b0b3
+        virReportSystemError(ENOMEM, _("Path %s too long for unix socket"), path);
c5b0b3
+        goto error;
c5b0b3
+    }
c5b0b3
+    if (remoteAddr.data.un.sun_path[0] == '@')
c5b0b3
+        remoteAddr.data.un.sun_path[0] = '\0';
c5b0b3
+
c5b0b3
+    while (retries &&
c5b0b3
+           connect(fd, &remoteAddr.data.sa, remoteAddr.len) < 0) {
c5b0b3
+        if (!(spawnDaemon && errno == ENOENT)) {
c5b0b3
             virReportSystemError(errno, _("Failed to connect socket to '%s'"),
c5b0b3
                                  path);
c5b0b3
             goto error;
c5b0b3
         }
c5b0b3
 
c5b0b3
-        /*
c5b0b3
-         * Do we need to eliminate the super-rare race here any more?  It would
c5b0b3
-         * need incorporating the following VIR_FORCE_CLOSE() into a
c5b0b3
-         * virCommandHook inside a virNetSocketForkDaemon().
c5b0b3
-         */
c5b0b3
-        VIR_FORCE_CLOSE(pidfd);
c5b0b3
-        if (virNetSocketForkDaemon(binary, passfd) < 0)
c5b0b3
+        if (virNetSocketForkDaemon(binary) < 0)
c5b0b3
             goto error;
c5b0b3
+
c5b0b3
+        retries--;
c5b0b3
+        usleep(5000);
c5b0b3
+    }
c5b0b3
+
c5b0b3
+    if (lockfd) {
c5b0b3
+        unlink(lockpath);
c5b0b3
+        VIR_FORCE_CLOSE(lockfd);
c5b0b3
+        VIR_FREE(lockpath);
c5b0b3
     }
c5b0b3
 
c5b0b3
     localAddr.len = sizeof(localAddr.data);
c5b0b3
@@ -687,19 +637,15 @@ int virNetSocketNewConnectUNIX(const char *path,
c5b0b3
     if (!(*retsock = virNetSocketNew(&localAddr, &remoteAddr, true, fd, -1, 0)))
c5b0b3
         goto error;
c5b0b3
 
c5b0b3
-    VIR_FREE(pidpath);
c5b0b3
-
c5b0b3
     return 0;
c5b0b3
 
c5b0b3
  error:
c5b0b3
-    if (pidfd >= 0)
c5b0b3
-        virPidFileDeletePath(pidpath);
c5b0b3
-    VIR_FREE(pidpath);
c5b0b3
+    if (lockfd)
c5b0b3
+        unlink(lockpath);
c5b0b3
+    VIR_FREE(lockpath);
c5b0b3
+    VIR_FREE(rundir);
c5b0b3
     VIR_FORCE_CLOSE(fd);
c5b0b3
-    VIR_FORCE_CLOSE(passfd);
c5b0b3
-    VIR_FORCE_CLOSE(pidfd);
c5b0b3
-    if (spawnDaemon)
c5b0b3
-        unlink(path);
c5b0b3
+    VIR_FORCE_CLOSE(lockfd);
c5b0b3
     return -1;
c5b0b3
 }
c5b0b3
 #else