Merge branch 'mainstream'
[sliver-openvswitch.git] / lib / socket-util.c
index 7f34ea2..24fc6fe 100644 (file)
@@ -60,6 +60,10 @@ VLOG_DEFINE_THIS_MODULE(socket_util);
 #define O_DIRECTORY 0
 #endif
 
+/* Maximum length of the sun_path member in a struct sockaddr_un, excluding
+ * space for a null terminator. */
+#define MAX_UN_LEN (sizeof(((struct sockaddr_un *) 0)->sun_path) - 1)
+
 static int getsockopt_int(int fd, int level, int option, const char *optname,
                           int *valuep);
 
@@ -337,78 +341,161 @@ drain_fd(int fd, size_t n_packets)
     }
 }
 
-/* Stores in '*un' a sockaddr_un that refers to file 'name'.  Stores in
- * '*un_len' the size of the sockaddr_un. */
-static void
-make_sockaddr_un__(const char *name, struct sockaddr_un *un, socklen_t *un_len)
+/* Attempts to shorten 'name' by opening a file descriptor for the directory
+ * part of the name and indirecting through /proc/self/fd/<dirfd>/<basename>.
+ * On systems with Linux-like /proc, this works as long as <basename> isn't too
+ * long.
+ *
+ * On success, returns 0 and stores the short name in 'short_name' and a
+ * directory file descriptor to eventually be closed in '*dirfpd'. */
+static int
+shorten_name_via_proc(const char *name, char short_name[MAX_UN_LEN + 1],
+                      int *dirfdp)
 {
-    un->sun_family = AF_UNIX;
-    ovs_strzcpy(un->sun_path, name, sizeof un->sun_path);
-    *un_len = (offsetof(struct sockaddr_un, sun_path)
-                + strlen (un->sun_path) + 1);
+    char *dir, *base;
+    int dirfd;
+    int len;
+
+    if (!LINUX_DATAPATH) {
+        return ENAMETOOLONG;
+    }
+
+    dir = dir_name(name);
+    dirfd = open(dir, O_DIRECTORY | O_RDONLY);
+    if (dirfd < 0) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        int error = errno;
+
+        VLOG_WARN_RL(&rl, "%s: open failed (%s)", dir, ovs_strerror(error));
+        free(dir);
+
+        return error;
+    }
+    free(dir);
+
+    base = base_name(name);
+    len = snprintf(short_name, MAX_UN_LEN + 1,
+                   "/proc/self/fd/%d/%s", dirfd, base);
+    free(base);
+
+    if (len >= 0 && len <= MAX_UN_LEN) {
+        *dirfdp = dirfd;
+        return 0;
+    } else {
+        close(dirfd);
+        return ENAMETOOLONG;
+    }
+}
+
+/* Attempts to shorten 'name' by creating a symlink for the directory part of
+ * the name and indirecting through <symlink>/<basename>.  This works on
+ * systems that support symlinks, as long as <basename> isn't too long.
+ *
+ * On success, returns 0 and stores the short name in 'short_name' and the
+ * symbolic link to eventually delete in 'linkname'. */
+static int
+shorten_name_via_symlink(const char *name, char short_name[MAX_UN_LEN + 1],
+                         char linkname[MAX_UN_LEN + 1])
+{
+    char *abs, *dir, *base;
+    const char *tmpdir;
+    int error;
+    int i;
+
+    abs = abs_file_name(NULL, name);
+    dir = dir_name(abs);
+    base = base_name(abs);
+    free(abs);
+
+    tmpdir = getenv("TMPDIR");
+    if (tmpdir == NULL) {
+        tmpdir = "/tmp";
+    }
+
+    for (i = 0; i < 1000; i++) {
+        int len;
+
+        len = snprintf(linkname, MAX_UN_LEN + 1,
+                       "%s/ovs-un-c-%"PRIu32, tmpdir, random_uint32());
+        error = (len < 0 || len > MAX_UN_LEN ? ENAMETOOLONG
+                 : symlink(dir, linkname) ? errno
+                 : 0);
+        if (error != EEXIST) {
+            break;
+        }
+    }
+
+    if (!error) {
+        int len;
+
+        fatal_signal_add_file_to_unlink(linkname);
+
+        len = snprintf(short_name, MAX_UN_LEN + 1, "%s/%s", linkname, base);
+        if (len < 0 || len > MAX_UN_LEN) {
+            fatal_signal_unlink_file_now(linkname);
+            error = ENAMETOOLONG;
+        }
+    }
+
+    if (error) {
+        linkname[0] = '\0';
+    }
+    free(dir);
+    free(base);
+
+    return error;
 }
 
 /* Stores in '*un' a sockaddr_un that refers to file 'name'.  Stores in
  * '*un_len' the size of the sockaddr_un.
  *
- * Returns 0 on success, otherwise a positive errno value.  On success,
- * '*dirfdp' is either -1 or a nonnegative file descriptor that the caller
- * should close after using '*un' to bind or connect.  On failure, '*dirfdp' is
- * -1. */
+ * Returns 0 on success, otherwise a positive errno value.
+ *
+ * Uses '*dirfdp' and 'linkname' to store references to data when the caller no
+ * longer needs to use 'un'.  On success, freeing these references with
+ * free_sockaddr_un() is mandatory to avoid a leak; on failure, freeing them is
+ * unnecessary but harmless. */
 static int
 make_sockaddr_un(const char *name, struct sockaddr_un *un, socklen_t *un_len,
-                 int *dirfdp)
+                 int *dirfdp, char linkname[MAX_UN_LEN + 1])
 {
-    enum { MAX_UN_LEN = sizeof un->sun_path - 1 };
+    char short_name[MAX_UN_LEN + 1];
 
     *dirfdp = -1;
+    linkname[0] = '\0';
     if (strlen(name) > MAX_UN_LEN) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-
-        if (LINUX_DATAPATH) {
-            /* 'name' is too long to fit in a sockaddr_un, but we have a
-             * workaround for that on Linux: shorten it by opening a file
-             * descriptor for the directory part of the name and indirecting
-             * through /proc/self/fd/<dirfd>/<basename>. */
-            char *dir, *base;
-            char *short_name;
-            int dirfd;
-
-            dir = dir_name(name);
-            base = base_name(name);
-
-            dirfd = open(dir, O_DIRECTORY | O_RDONLY);
-            if (dirfd < 0) {
-                free(base);
-                free(dir);
-                return errno;
-            }
-
-            short_name = xasprintf("/proc/self/fd/%d/%s", dirfd, base);
-            free(dir);
-            free(base);
-
-            if (strlen(short_name) <= MAX_UN_LEN) {
-                make_sockaddr_un__(short_name, un, un_len);
-                free(short_name);
-                *dirfdp = dirfd;
-                return 0;
-            }
-            free(short_name);
-            close(dirfd);
+        /* 'name' is too long to fit in a sockaddr_un.  Try a workaround. */
+        int error = shorten_name_via_proc(name, short_name, dirfdp);
+        if (error == ENAMETOOLONG) {
+            error = shorten_name_via_symlink(name, short_name, linkname);
+        }
+        if (error) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
 
             VLOG_WARN_RL(&rl, "Unix socket name %s is longer than maximum "
-                         "%d bytes (even shortened)", name, MAX_UN_LEN);
-        } else {
-            /* 'name' is too long and we have no workaround. */
-            VLOG_WARN_RL(&rl, "Unix socket name %s is longer than maximum "
-                         "%d bytes", name, MAX_UN_LEN);
+                         "%"PRIuSIZE" bytes", name, MAX_UN_LEN);
+            return error;
         }
 
-        return ENAMETOOLONG;
-    } else {
-        make_sockaddr_un__(name, un, un_len);
-        return 0;
+        name = short_name;
+    }
+
+    un->sun_family = AF_UNIX;
+    ovs_strzcpy(un->sun_path, name, sizeof un->sun_path);
+    *un_len = (offsetof(struct sockaddr_un, sun_path)
+                + strlen (un->sun_path) + 1);
+    return 0;
+}
+
+/* Clean up after make_sockaddr_un(). */
+static void
+free_sockaddr_un(int dirfd, const char *linkname)
+{
+    if (dirfd >= 0) {
+        close(dirfd);
+    }
+    if (linkname[0]) {
+        fatal_signal_unlink_file_now(linkname);
     }
 }
 
@@ -453,6 +540,7 @@ make_unix_socket(int style, bool nonblock,
     }
 
     if (bind_path) {
+        char linkname[MAX_UN_LEN + 1];
         struct sockaddr_un un;
         socklen_t un_len;
         int dirfd;
@@ -463,32 +551,31 @@ make_unix_socket(int style, bool nonblock,
         }
         fatal_signal_add_file_to_unlink(bind_path);
 
-        error = make_sockaddr_un(bind_path, &un, &un_len, &dirfd);
+        error = make_sockaddr_un(bind_path, &un, &un_len, &dirfd, linkname);
         if (!error) {
             error = bind_unix_socket(fd, (struct sockaddr *) &un, un_len);
         }
-        if (dirfd >= 0) {
-            close(dirfd);
-        }
+        free_sockaddr_un(dirfd, linkname);
+
         if (error) {
             goto error;
         }
     }
 
     if (connect_path) {
+        char linkname[MAX_UN_LEN + 1];
         struct sockaddr_un un;
         socklen_t un_len;
         int dirfd;
 
-        error = make_sockaddr_un(connect_path, &un, &un_len, &dirfd);
+        error = make_sockaddr_un(connect_path, &un, &un_len, &dirfd, linkname);
         if (!error
             && connect(fd, (struct sockaddr*) &un, un_len)
             && errno != EINPROGRESS) {
             error = errno;
         }
-        if (dirfd >= 0) {
-            close(dirfd);
-        }
+        free_sockaddr_un(dirfd, linkname);
+
         if (error) {
             goto error;
         }
@@ -941,14 +1028,6 @@ xpipe_nonblocking(int fds[2])
     xset_nonblocking(fds[1]);
 }
 
-void
-xsocketpair(int domain, int type, int protocol, int fds[2])
-{
-    if (socketpair(domain, type, protocol, fds)) {
-        VLOG_FATAL("failed to create socketpair (%s)", ovs_strerror(errno));
-    }
-}
-
 static int
 getsockopt_int(int fd, int level, int option, const char *optname, int *valuep)
 {
@@ -963,7 +1042,7 @@ getsockopt_int(int fd, int level, int option, const char *optname, int *valuep)
         VLOG_ERR_RL(&rl, "getsockopt(%s): %s", optname, ovs_strerror(error));
     } else if (len != sizeof value) {
         error = EINVAL;
-        VLOG_ERR_RL(&rl, "getsockopt(%s): value is %u bytes (expected %zu)",
+        VLOG_ERR_RL(&rl, "getsockopt(%s): value is %u bytes (expected %"PRIuSIZE")",
                     optname, (unsigned int) len, sizeof value);
     } else {
         error = 0;
@@ -1109,252 +1188,6 @@ describe_fd(int fd)
     return ds_steal_cstr(&string);
 }
 
-/* Returns the total of the 'iov_len' members of the 'n_iovs' in 'iovs'.
- * The caller must ensure that the total does not exceed SIZE_MAX. */
-size_t
-iovec_len(const struct iovec iovs[], size_t n_iovs)
-{
-    size_t len = 0;
-    size_t i;
-
-    for (i = 0; i < n_iovs; i++) {
-        len += iovs[i].iov_len;
-    }
-    return len;
-}
-
-/* Returns true if all of the 'n_iovs' iovecs in 'iovs' have length zero. */
-bool
-iovec_is_empty(const struct iovec iovs[], size_t n_iovs)
-{
-    size_t i;
-
-    for (i = 0; i < n_iovs; i++) {
-        if (iovs[i].iov_len) {
-            return false;
-        }
-    }
-    return true;
-}
-
-/* Sends the 'n_iovs' iovecs of data in 'iovs' and the 'n_fds' file descriptors
- * in 'fds' on Unix domain socket 'sock'.  Returns the number of bytes
- * successfully sent or -1 if an error occurred.  On error, sets errno
- * appropriately.  */
-int
-send_iovec_and_fds(int sock,
-                   const struct iovec *iovs, size_t n_iovs,
-                   const int fds[], size_t n_fds)
-{
-    ovs_assert(sock >= 0);
-    if (n_fds > 0) {
-        union {
-            struct cmsghdr cm;
-            char control[CMSG_SPACE(SOUTIL_MAX_FDS * sizeof *fds)];
-        } cmsg;
-        struct msghdr msg;
-
-        ovs_assert(!iovec_is_empty(iovs, n_iovs));
-        ovs_assert(n_fds <= SOUTIL_MAX_FDS);
-
-        memset(&cmsg, 0, sizeof cmsg);
-        cmsg.cm.cmsg_len = CMSG_LEN(n_fds * sizeof *fds);
-        cmsg.cm.cmsg_level = SOL_SOCKET;
-        cmsg.cm.cmsg_type = SCM_RIGHTS;
-        memcpy(CMSG_DATA(&cmsg.cm), fds, n_fds * sizeof *fds);
-
-        msg.msg_name = NULL;
-        msg.msg_namelen = 0;
-        msg.msg_iov = CONST_CAST(struct iovec *, iovs);
-        msg.msg_iovlen = n_iovs;
-        msg.msg_control = &cmsg.cm;
-        msg.msg_controllen = CMSG_SPACE(n_fds * sizeof *fds);
-        msg.msg_flags = 0;
-
-        return sendmsg(sock, &msg, 0);
-    } else {
-        return writev(sock, iovs, n_iovs);
-    }
-}
-
-/* Sends the 'n_iovs' iovecs of data in 'iovs' and the 'n_fds' file descriptors
- * in 'fds' on Unix domain socket 'sock'.  If 'skip_bytes' is nonzero, then the
- * first 'skip_bytes' of data in the iovecs are not sent, and none of the file
- * descriptors are sent.  The function continues to retry sending until an
- * error (other than EINTR) occurs or all the data and fds are sent.
- *
- * Returns 0 if all the data and fds were successfully sent, otherwise a
- * positive errno value.  Regardless of success, stores the number of bytes
- * sent (always at least 'skip_bytes') in '*bytes_sent'.  (If at least one byte
- * is sent, then all the fds have been sent.)
- *
- * 'skip_bytes' must be less than or equal to iovec_len(iovs, n_iovs). */
-int
-send_iovec_and_fds_fully(int sock,
-                         const struct iovec iovs[], size_t n_iovs,
-                         const int fds[], size_t n_fds,
-                         size_t skip_bytes, size_t *bytes_sent)
-{
-    *bytes_sent = 0;
-    while (n_iovs > 0) {
-        int retval;
-
-        if (skip_bytes) {
-            retval = skip_bytes;
-            skip_bytes = 0;
-        } else if (!*bytes_sent) {
-            retval = send_iovec_and_fds(sock, iovs, n_iovs, fds, n_fds);
-        } else {
-            retval = writev(sock, iovs, n_iovs);
-        }
-
-        if (retval > 0) {
-            *bytes_sent += retval;
-            while (retval > 0) {
-                const uint8_t *base = iovs->iov_base;
-                size_t len = iovs->iov_len;
-
-                if (retval < len) {
-                    size_t sent;
-                    int error;
-
-                    error = write_fully(sock, base + retval, len - retval,
-                                        &sent);
-                    *bytes_sent += sent;
-                    retval += sent;
-                    if (error) {
-                        return error;
-                    }
-                }
-                retval -= len;
-                iovs++;
-                n_iovs--;
-            }
-        } else if (retval == 0) {
-            if (iovec_is_empty(iovs, n_iovs)) {
-                break;
-            }
-            VLOG_WARN("send returned 0");
-            return EPROTO;
-        } else if (errno != EINTR) {
-            return errno;
-        }
-    }
-
-    return 0;
-}
-
-/* Sends the 'n_iovs' iovecs of data in 'iovs' and the 'n_fds' file descriptors
- * in 'fds' on Unix domain socket 'sock'.  The function continues to retry
- * sending until an error (other than EAGAIN or EINTR) occurs or all the data
- * and fds are sent.  Upon EAGAIN, the function blocks until the socket is
- * ready for more data.
- *
- * Returns 0 if all the data and fds were successfully sent, otherwise a
- * positive errno value. */
-int
-send_iovec_and_fds_fully_block(int sock,
-                               const struct iovec iovs[], size_t n_iovs,
-                               const int fds[], size_t n_fds)
-{
-    size_t sent = 0;
-
-    for (;;) {
-        int error;
-
-        error = send_iovec_and_fds_fully(sock, iovs, n_iovs,
-                                         fds, n_fds, sent, &sent);
-        if (error != EAGAIN) {
-            return error;
-        }
-        poll_fd_wait(sock, POLLOUT);
-        poll_block();
-    }
-}
-
-/* Attempts to receive from Unix domain socket 'sock' up to 'size' bytes of
- * data into 'data' and up to SOUTIL_MAX_FDS file descriptors into 'fds'.
- *
- *      - Upon success, returns the number of bytes of data copied into 'data'
- *        and stores the number of received file descriptors into '*n_fdsp'.
- *
- *      - On failure, returns a negative errno value and stores 0 in
- *        '*n_fdsp'.
- *
- *      - On EOF, returns 0 and stores 0 in '*n_fdsp'. */
-int
-recv_data_and_fds(int sock,
-                  void *data, size_t size,
-                  int fds[SOUTIL_MAX_FDS], size_t *n_fdsp)
-{
-    union {
-        struct cmsghdr cm;
-        char control[CMSG_SPACE(SOUTIL_MAX_FDS * sizeof *fds)];
-    } cmsg;
-    struct msghdr msg;
-    int retval;
-    struct cmsghdr *p;
-    size_t i;
-
-    *n_fdsp = 0;
-
-    do {
-        struct iovec iov;
-
-        iov.iov_base = data;
-        iov.iov_len = size;
-
-        msg.msg_name = NULL;
-        msg.msg_namelen = 0;
-        msg.msg_iov = &iov;
-        msg.msg_iovlen = 1;
-        msg.msg_control = &cmsg.cm;
-        msg.msg_controllen = sizeof cmsg.control;
-        msg.msg_flags = 0;
-
-        retval = recvmsg(sock, &msg, 0);
-    } while (retval < 0 && errno == EINTR);
-    if (retval <= 0) {
-        return retval < 0 ? -errno : 0;
-    }
-
-    for (p = CMSG_FIRSTHDR(&msg); p; p = CMSG_NXTHDR(&msg, p)) {
-        if (p->cmsg_level != SOL_SOCKET || p->cmsg_type != SCM_RIGHTS) {
-            VLOG_ERR("unexpected control message %d:%d",
-                     p->cmsg_level, p->cmsg_type);
-            goto error;
-        } else if (*n_fdsp) {
-            VLOG_ERR("multiple SCM_RIGHTS received");
-            goto error;
-        } else {
-            size_t n_fds = (p->cmsg_len - CMSG_LEN(0)) / sizeof *fds;
-            const int *fds_data = ALIGNED_CAST(const int *, CMSG_DATA(p));
-
-            ovs_assert(n_fds > 0);
-            if (n_fds > SOUTIL_MAX_FDS) {
-                VLOG_ERR("%zu fds received but only %d supported",
-                         n_fds, SOUTIL_MAX_FDS);
-                for (i = 0; i < n_fds; i++) {
-                    close(fds_data[i]);
-                }
-                goto error;
-            }
-
-            *n_fdsp = n_fds;
-            memcpy(fds, fds_data, n_fds * sizeof *fds);
-        }
-    }
-
-    return retval;
-
-error:
-    for (i = 0; i < *n_fdsp; i++) {
-        close(fds[i]);
-    }
-    *n_fdsp = 0;
-    return EPROTO;
-}
-
 /* Calls ioctl() on an AF_INET sock, passing the specified 'command' and
  * 'arg'.  Returns 0 if successful, otherwise a positive errno value. */
 int