Implement futex wait scheme
[lttng-ust.git] / libust / lttng-ust-comm.c
index 3ee862a00871d267ebd1f4632881256ca6bb0537..a5f2b0b0069de494f0fb44dd14fdde78537002e6 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#define _LGPL_SOURCE
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/prctl.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <errno.h>
@@ -33,6 +36,7 @@
 #include <assert.h>
 #include <signal.h>
 #include <urcu/uatomic.h>
+#include <urcu/futex.h>
 
 #include <lttng-ust-comm.h>
 #include <ust/usterr-signal-safe.h>
@@ -308,82 +312,163 @@ void cleanup_sock_info(struct sock_info *sock_info)
        }
 }
 
+/*
+ * Using fork to set umask in the child process (not multi-thread safe).
+ * We deal with the shm_open vs ftruncate race (happening when the
+ * sessiond owns the shm and does not let everybody modify it, to ensure
+ * safety against shm_unlink) by simply letting the mmap fail and
+ * retrying after a few seconds.
+ * For global shm, everybody has rw access to it until the sessiond
+ * starts.
+ */
 static
-char *get_map_shm(struct sock_info *sock_info)
+int get_wait_shm(struct sock_info *sock_info, size_t mmap_size)
 {
-       size_t mmap_size = sysconf(_SC_PAGE_SIZE);
        int wait_shm_fd, ret;
-       char *wait_shm_mmap;
-       int mode;
-
-       mode = S_IRUSR | S_IRGRP;
-       if (sock_info->global)
-               mode |= S_IROTH;
+       pid_t pid;
 
        /*
-        * Get existing (read-only) shm, or open new shm.
-        * First try to open read-only.
+        * Try to open read-only.
         */
-       wait_shm_fd = shm_open(sock_info->wait_shm_path,
-                       O_RDONLY, mode);
-       if (wait_shm_fd >= 0)
-               goto got_shm;
-       /*
-        * Real-only open did not work. If it is because it did
-        * not exist, try creating it. Else it's a failure that
-        * prohibits using shm.
-        */
-       if (errno != ENOENT) {
-               ERR("Error opening shm %s", sock_info->wait_shm_path);
-               goto error;
-       }
-       wait_shm_fd = shm_open(sock_info->wait_shm_path,
-                       O_RDWR | O_CREAT | O_EXCL, mode | S_IWUSR);
-       if (wait_shm_fd >= 0)
-               goto created_shm;
-       if (errno != EEXIST) {
+       wait_shm_fd = shm_open(sock_info->wait_shm_path, O_RDONLY, 0);
+       if (wait_shm_fd >= 0) {
+               goto end;
+       } else if (wait_shm_fd < 0 && errno != ENOENT) {
+               /*
+                * Real-only open did not work, and it's not because the
+                * entry was not present. It's a failure that prohibits
+                * using shm.
+                */
                ERR("Error opening shm %s", sock_info->wait_shm_path);
-               goto error;
+               goto end;
        }
        /*
-        * If another process beat us to create the shm, we are
-        * pretty certain the shm is available for us in
-        * read-only mode.
+        * If the open failed because the file did not exist, try
+        * creating it ourself.
         */
-       wait_shm_fd = shm_open(sock_info->wait_shm_path,
-                       O_RDWR | O_CREAT | O_EXCL, mode);
-       if (wait_shm_fd >= 0)
-               goto got_shm;
-       else
-               goto error;
+       pid = fork();
+       if (pid > 0) {
+               int status;
 
-created_shm:
-       ret = ftruncate(wait_shm_fd, mmap_size);
-       if (ret) {
-               PERROR("ftruncate");
-               ret = close(wait_shm_fd);
+               /*
+                * Parent: wait for child to return, in which case the
+                * shared memory map will have been created.
+                */
+               pid = wait(&status);
+               if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+                       wait_shm_fd = -1;
+                       goto end;
+               }
+               /*
+                * Try to open read-only again after creation.
+                */
+               wait_shm_fd = shm_open(sock_info->wait_shm_path, O_RDONLY, 0);
+               if (wait_shm_fd < 0) {
+                       /*
+                        * Real-only open did not work. It's a failure
+                        * that prohibits using shm.
+                        */
+                       ERR("Error opening shm %s", sock_info->wait_shm_path);
+                       goto end;
+               }
+               goto end;
+       } else if (pid == 0) {
+               int create_mode;
+
+               /* Child */
+               create_mode = S_IRUSR | S_IWUSR | S_IRGRP;
+               if (sock_info->global)
+                       create_mode |= S_IROTH | S_IWGRP | S_IWOTH;
+               /*
+                * We're alone in a child process, so we can modify the
+                * process-wide umask.
+                */
+               umask(~create_mode);
+               /*
+                * Try creating shm (or get rw access).
+                * We don't do an exclusive open, because we allow other
+                * processes to create+ftruncate it concurrently.
+                */
+               wait_shm_fd = shm_open(sock_info->wait_shm_path,
+                               O_RDWR | O_CREAT, create_mode);
+               if (wait_shm_fd >= 0) {
+                       ret = ftruncate(wait_shm_fd, mmap_size);
+                       if (ret) {
+                               PERROR("ftruncate");
+                               exit(EXIT_FAILURE);
+                       }
+                       exit(EXIT_SUCCESS);
+               }
+               /*
+                * For local shm, we need to have rw access to accept
+                * opening it: this means the local sessiond will be
+                * able to wake us up. For global shm, we open it even
+                * if rw access is not granted, because the root.root
+                * sessiond will be able to override all rights and wake
+                * us up.
+                */
+               if (!sock_info->global && errno != EACCES) {
+                       ERR("Error opening shm %s", sock_info->wait_shm_path);
+                       exit(EXIT_FAILURE);
+               }
+               /*
+                * The shm exists, but we cannot open it RW. Report
+                * success.
+                */
+               exit(EXIT_SUCCESS);
+       } else {
+               return -1;
+       }
+end:
+       if (wait_shm_fd >= 0 && !sock_info->global) {
+               struct stat statbuf;
+
+               /*
+                * Ensure that our user is the owner of the shm file for
+                * local shm. If we do not own the file, it means our
+                * sessiond will not have access to wake us up (there is
+                * probably a rogue process trying to fake our
+                * sessiond). Fallback to polling method in this case.
+                */
+               ret = fstat(wait_shm_fd, &statbuf);
                if (ret) {
-                       ERR("Error closing fd");
+                       PERROR("fstat");
+                       goto error_close;
                }
-               wait_shm_fd = -1;
-               goto error;
+               if (statbuf.st_uid != getuid())
+                       goto error_close;
        }
-       /* Drop write access ASAP */
-       ret = chmod(sock_info->wait_shm_path, mode);
+       return wait_shm_fd;
+
+error_close:
+       ret = close(wait_shm_fd);
        if (ret) {
-               PERROR("chmod");
+               PERROR("Error closing fd");
        }
-got_shm:
-       wait_shm_mmap = mmap(NULL, mmap_size, PROT_READ,
-                 MAP_SHARED, wait_shm_fd, 0);
-       if (wait_shm_mmap == MAP_FAILED) {
-               PERROR("mmap");
+       return -1;
+}
+
+static
+char *get_map_shm(struct sock_info *sock_info)
+{
+       size_t mmap_size = sysconf(_SC_PAGE_SIZE);
+       int wait_shm_fd, ret;
+       char *wait_shm_mmap;
+
+       wait_shm_fd = get_wait_shm(sock_info, mmap_size);
+       if (wait_shm_fd < 0) {
                goto error;
        }
+       wait_shm_mmap = mmap(NULL, mmap_size, PROT_READ,
+                 MAP_SHARED, wait_shm_fd, 0);
        /* close shm fd immediately after taking the mmap reference */
        ret = close(wait_shm_fd);
        if (ret) {
-               ERR("Error closing fd");
+               PERROR("Error closing fd");
+       }
+       if (wait_shm_mmap == MAP_FAILED) {
+               DBG("mmap error (can be caused by race with sessiond). Fallback to poll mode.");
+               goto error;
        }
        return wait_shm_mmap;
 
@@ -394,6 +479,8 @@ error:
 static
 void wait_for_sessiond(struct sock_info *sock_info)
 {
+       int ret;
+
        ust_lock();
        if (lttng_ust_comm_should_quit) {
                goto quit;
@@ -406,9 +493,19 @@ void wait_for_sessiond(struct sock_info *sock_info)
        ust_unlock();
 
        DBG("Waiting for %s apps sessiond", sock_info->name);
-       /* Wait for futex wakeup TODO */
-       sleep(5);
-
+       /* Wait for futex wakeup */
+       if (uatomic_read((int32_t *) sock_info->wait_shm_mmap) == 0) {
+               ret = futex_async((int32_t *) sock_info->wait_shm_mmap,
+                       FUTEX_WAIT, 0, NULL, NULL, 0);
+               /*
+                * FIXME: Currently, futexes on read-only shm seems to
+                * EFAULT.
+                */
+               if (ret < 0) {
+                       PERROR("futex");
+                       sleep(5);
+               }
+       }
        return;
 
 quit:
This page took 0.027293 seconds and 4 git commands to generate.