Use full path to dlinklist.h in includes.
[obnox/samba/samba-obnox.git] / source3 / lib / unix_msg / unix_msg.c
index e32a4d80bcd275d92caa6dfddd83a1973686ae4c..cb648ede97f9e6e77b6851ecb5efbd856901e37e 100644 (file)
 #include "system/select.h"
 #include "system/time.h"
 #include "system/network.h"
-#include "dlinklist.h"
+#include "lib/util/dlinklist.h"
 #include "pthreadpool/pthreadpool.h"
+#include "lib/util/iov_buf.h"
+#include "lib/msghdr.h"
 #include <fcntl.h>
 
 /*
@@ -42,10 +44,6 @@ struct unix_dgram_msg {
        int sock;
        ssize_t sent;
        int sys_errno;
-       size_t num_fds;
-       int *fds;
-       size_t buflen;
-       uint8_t buf[];
 };
 
 struct unix_dgram_send_queue {
@@ -78,7 +76,6 @@ struct unix_dgram_ctx {
        char path[];
 };
 
-static ssize_t iov_buflen(const struct iovec *iov, int iovlen);
 static void unix_dgram_recv_handler(struct poll_watch *w, int fd, short events,
                                    void *private_data);
 
@@ -138,6 +135,22 @@ static int prepare_socket(int sock)
        return prepare_socket_cloexec(sock);
 }
 
+static size_t unix_dgram_msg_size(void)
+{
+       size_t msgsize = sizeof(struct unix_dgram_msg);
+       msgsize = (msgsize + 15) & ~15; /* align to 16 */
+       return msgsize;
+}
+
+static struct msghdr_buf *unix_dgram_msghdr(struct unix_dgram_msg *msg)
+{
+       /*
+        * Not portable in C99, but "msg" is aligned and so is
+        * unix_dgram_msg_size()
+        */
+       return (struct msghdr_buf *)(((char *)msg) + unix_dgram_msg_size());
+}
+
 static void close_fd_array(int *fds, size_t num_fds)
 {
        size_t i;
@@ -152,6 +165,18 @@ static void close_fd_array(int *fds, size_t num_fds)
        }
 }
 
+static void close_fd_array_dgram_msg(struct unix_dgram_msg *dmsg)
+{
+       struct msghdr_buf *hdr = unix_dgram_msghdr(dmsg);
+       struct msghdr *msg = msghdr_buf_msghdr(hdr);
+       size_t num_fds = msghdr_extract_fds(msg, NULL, 0);
+       int fds[num_fds];
+
+       msghdr_extract_fds(msg, fds, num_fds);
+
+       close_fd_array(fds, num_fds);
+}
+
 static int unix_dgram_init(const struct sockaddr_un *addr, size_t max_msg,
                           const struct poll_funcs *ev_funcs,
                           void (*recv_callback)(struct unix_dgram_ctx *ctx,
@@ -247,12 +272,8 @@ static void unix_dgram_recv_handler(struct poll_watch *w, int fd, short events,
        int flags = 0;
        struct msghdr msg;
        struct iovec iov;
-#ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL
-       char buf[CMSG_SPACE(sizeof(int)*INT8_MAX)] = { 0, };
-       struct cmsghdr *cmsg;
-#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */
-       int *fds = NULL;
-       size_t i, num_fds = 0;
+       size_t bufsize = msghdr_prep_recv_fds(NULL, NULL, 0, INT8_MAX);
+       uint8_t buf[bufsize];
 
        iov = (struct iovec) {
                .iov_base = (void *)ctx->recv_buf,
@@ -262,12 +283,10 @@ static void unix_dgram_recv_handler(struct poll_watch *w, int fd, short events,
        msg = (struct msghdr) {
                .msg_iov = &iov,
                .msg_iovlen = 1,
-#ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL
-               .msg_control = buf,
-               .msg_controllen = sizeof(buf),
-#endif
        };
 
+       msghdr_prep_recv_fds(&msg, buf, bufsize, INT8_MAX);
+
 #ifdef MSG_CMSG_CLOEXEC
        flags |= MSG_CMSG_CLOEXEC;
 #endif
@@ -289,43 +308,26 @@ static void unix_dgram_recv_handler(struct poll_watch *w, int fd, short events,
                return;
        }
 
-#ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL
-       for(cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
-           cmsg = CMSG_NXTHDR(&msg, cmsg))
        {
-               void *data = CMSG_DATA(cmsg);
+               size_t num_fds = msghdr_extract_fds(&msg, NULL, 0);
+               int fds[num_fds];
+               int i;
 
-               if (cmsg->cmsg_type != SCM_RIGHTS) {
-                       continue;
-               }
-               if (cmsg->cmsg_level != SOL_SOCKET) {
-                       continue;
-               }
-
-               fds = (int *)data;
-               num_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof (int);
-               break;
-       }
-#endif
+               msghdr_extract_fds(&msg, fds, num_fds);
 
-       for (i = 0; i < num_fds; i++) {
-               int err;
+               for (i = 0; i < num_fds; i++) {
+                       int err;
 
-               err = prepare_socket_cloexec(fds[i]);
-               if (err != 0) {
-                       goto cleanup_fds;
+                       err = prepare_socket_cloexec(fds[i]);
+                       if (err != 0) {
+                               close_fd_array(fds, num_fds);
+                               num_fds = 0;
+                       }
                }
-       }
-
-       ctx->recv_callback(ctx, ctx->recv_buf, received,
-                          fds, num_fds, ctx->private_data);
-       return;
-
-cleanup_fds:
-       close_fd_array(fds, num_fds);
 
-       ctx->recv_callback(ctx, ctx->recv_buf, received,
-                          NULL, 0, ctx->private_data);
+               ctx->recv_callback(ctx, ctx->recv_buf, received,
+                                  fds, num_fds, ctx->private_data);
+       }
 }
 
 static void unix_dgram_job_finished(struct poll_watch *w, int fd, short events,
@@ -423,7 +425,7 @@ static void unix_dgram_send_queue_free(struct unix_dgram_send_queue *q)
                struct unix_dgram_msg *msg;
                msg = q->msgs;
                DLIST_REMOVE(q->msgs, msg);
-               close_fd_array(msg->fds, msg->num_fds);
+               close_fd_array_dgram_msg(msg);
                free(msg);
        }
        close(q->sock);
@@ -445,24 +447,17 @@ static struct unix_dgram_send_queue *find_send_queue(
 }
 
 static int queue_msg(struct unix_dgram_send_queue *q,
-                    const struct iovec *iov, int iovlen,
+                    const struct iovec *iov, int iovcnt,
                     const int *fds, size_t num_fds)
 {
        struct unix_dgram_msg *msg;
-       ssize_t buflen;
-       size_t msglen;
-       size_t fds_size = sizeof(int) * num_fds;
+       struct msghdr_buf *hdr;
+       size_t msglen, needed;
+       ssize_t msghdrlen;
        int fds_copy[MIN(num_fds, INT8_MAX)];
-       size_t fds_padding = 0;
-       int i;
-       size_t tmp;
-       int ret = -1;
+       int i, ret;
 
-       if (num_fds > INT8_MAX) {
-               return EINVAL;
-       }
-
-       for (i = 0; i < num_fds; i++) {
+       for (i=0; i<num_fds; i++) {
                fds_copy[i] = -1;
        }
 
@@ -474,68 +469,34 @@ static int queue_msg(struct unix_dgram_send_queue *q,
                }
        }
 
-       buflen = iov_buflen(iov, iovlen);
-       if (buflen == -1) {
-               goto invalid;
-       }
+       msglen = unix_dgram_msg_size();
 
-       msglen = offsetof(struct unix_dgram_msg, buf);
-       tmp = msglen + buflen;
-       if ((tmp < msglen) || (tmp < buflen)) {
-               /* overflow */
-               goto invalid;
+       msghdrlen = msghdr_copy(NULL, 0, NULL, 0, iov, iovcnt,
+                               fds_copy, num_fds);
+       if (msghdrlen == -1) {
+               ret = EMSGSIZE;
+               goto fail;
        }
-       msglen = tmp;
 
-       if (num_fds > 0) {
-               const size_t fds_align = sizeof(int) - 1;
-
-               tmp = msglen + fds_align;
-               if ((tmp < msglen) || (tmp < fds_align)) {
-                       /* overflow */
-                       goto invalid;
-               }
-               tmp &= ~fds_align;
-
-               fds_padding = tmp - msglen;
-               msglen = tmp;
-
-               tmp = msglen + fds_size;
-               if ((tmp < msglen) || (tmp < fds_size)) {
-                       /* overflow */
-                       goto invalid;
-               }
-               msglen = tmp;
+       needed = msglen + msghdrlen;
+       if (needed < msglen) {
+               ret = EMSGSIZE;
+               goto fail;
        }
 
-       msg = malloc(msglen);
+       msg = malloc(needed);
        if (msg == NULL) {
                ret = ENOMEM;
                goto fail;
        }
-       msg->buflen = buflen;
-       msg->sock = q->sock;
-
-       buflen = 0;
-       for (i=0; i<iovlen; i++) {
-               memcpy(&msg->buf[buflen], iov[i].iov_base, iov[i].iov_len);
-               buflen += iov[i].iov_len;
-       }
+       hdr = unix_dgram_msghdr(msg);
 
-       msg->num_fds = num_fds;
-       if (msg->num_fds > 0) {
-               void *fds_ptr = (void *)&msg->buf[buflen+fds_padding];
-               memcpy(fds_ptr, fds_copy, fds_size);
-               msg->fds = (int *)fds_ptr;
-       } else {
-               msg->fds = NULL;
-       }
+       msg->sock = q->sock;
+       msghdr_copy(hdr, msghdrlen, NULL, 0, iov, iovcnt,
+                   fds_copy, num_fds);
 
        DLIST_ADD_END(q->msgs, msg, struct unix_dgram_msg);
        return 0;
-
-invalid:
-       ret = EINVAL;
 fail:
        close_fd_array(fds_copy, num_fds);
        return ret;
@@ -544,43 +505,16 @@ fail:
 static void unix_dgram_send_job(void *private_data)
 {
        struct unix_dgram_msg *dmsg = private_data;
-       struct iovec iov = {
-               .iov_base = (void *)dmsg->buf,
-               .iov_len = dmsg->buflen,
-       };
-       struct msghdr msg = {
-               .msg_iov = &iov,
-               .msg_iovlen = 1,
-       };
-#ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL
-       struct cmsghdr *cmsg;
-       size_t fds_size = sizeof(int) * dmsg->num_fds;
-       size_t cmsg_len = CMSG_LEN(fds_size);
-       size_t cmsg_space = CMSG_SPACE(fds_size);
-       char cmsg_buf[cmsg_space];
-
-       if (dmsg->num_fds > 0) {
-               void *fdptr;
-
-               memset(cmsg_buf, 0, cmsg_space);
-
-               msg.msg_control = cmsg_buf;
-               msg.msg_controllen = cmsg_space;
-               cmsg = CMSG_FIRSTHDR(&msg);
-               cmsg->cmsg_level = SOL_SOCKET;
-               cmsg->cmsg_type = SCM_RIGHTS;
-               cmsg->cmsg_len = cmsg_len;
-               fdptr = CMSG_DATA(cmsg);
-               memcpy(fdptr, dmsg->fds, fds_size);
-               msg.msg_controllen = cmsg->cmsg_len;
-       }
-#endif /*  HAVE_STRUCT_MSGHDR_MSG_CONTROL */
 
        do {
-               dmsg->sent = sendmsg(dmsg->sock, &msg, 0);
+               struct msghdr_buf *hdr = unix_dgram_msghdr(dmsg);
+               struct msghdr *msg = msghdr_buf_msghdr(hdr);
+               dmsg->sent = sendmsg(dmsg->sock, msg, 0);
        } while ((dmsg->sent == -1) && (errno == EINTR));
 
-       close_fd_array(dmsg->fds, dmsg->num_fds);
+       if (dmsg->sent == -1) {
+               dmsg->sys_errno = errno;
+       }
 }
 
 static void unix_dgram_job_finished(struct poll_watch *w, int fd, short events,
@@ -609,7 +543,7 @@ static void unix_dgram_job_finished(struct poll_watch *w, int fd, short events,
 
        msg = q->msgs;
        DLIST_REMOVE(q->msgs, msg);
-       close_fd_array(msg->fds, msg->num_fds);
+       close_fd_array_dgram_msg(msg);
        free(msg);
 
        if (q->msgs != NULL) {
@@ -630,13 +564,7 @@ static int unix_dgram_send(struct unix_dgram_ctx *ctx,
 {
        struct unix_dgram_send_queue *q;
        struct msghdr msg;
-#ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL
-       struct cmsghdr *cmsg;
-       size_t fds_size = sizeof(int) * num_fds;
-       size_t cmsg_len = CMSG_LEN(fds_size);
-       size_t cmsg_space = CMSG_SPACE(fds_size);
-       char cmsg_buf[cmsg_space];
-#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */
+       ssize_t fdlen;
        int ret;
        int i;
 
@@ -644,11 +572,11 @@ static int unix_dgram_send(struct unix_dgram_ctx *ctx,
                return EINVAL;
        }
 
-#ifndef HAVE_STRUCT_MSGHDR_MSG_CONTROL
+#if !defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && !defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
        if (num_fds > 0) {
                return ENOSYS;
        }
-#endif /* ! HAVE_STRUCT_MSGHDR_MSG_CONTROL */
+#endif
 
        for (i = 0; i < num_fds; i++) {
                /*
@@ -691,29 +619,29 @@ static int unix_dgram_send(struct unix_dgram_ctx *ctx,
                .msg_iov = discard_const_p(struct iovec, iov),
                .msg_iovlen = iovlen
        };
-#ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL
-       if (num_fds > 0) {
-               void *fdptr;
 
-               memset(cmsg_buf, 0, cmsg_space);
+       fdlen = msghdr_prep_fds(&msg, NULL, 0, fds, num_fds);
+       if (fdlen == -1) {
+               return EINVAL;
+       }
 
-               msg.msg_control = cmsg_buf;
-               msg.msg_controllen = cmsg_space;
-               cmsg = CMSG_FIRSTHDR(&msg);
-               cmsg->cmsg_level = SOL_SOCKET;
-               cmsg->cmsg_type = SCM_RIGHTS;
-               cmsg->cmsg_len = cmsg_len;
-               fdptr = CMSG_DATA(cmsg);
-               memcpy(fdptr, fds, fds_size);
-               msg.msg_controllen = cmsg->cmsg_len;
+       {
+               uint8_t buf[fdlen];
+               msghdr_prep_fds(&msg, buf, fdlen, fds, num_fds);
+
+               ret = sendmsg(ctx->sock, &msg, 0);
        }
-#endif /*  HAVE_STRUCT_MSGHDR_MSG_CONTROL */
 
-       ret = sendmsg(ctx->sock, &msg, 0);
        if (ret >= 0) {
                return 0;
        }
-       if ((errno != EWOULDBLOCK) && (errno != EAGAIN) && (errno != EINTR)) {
+       if ((errno != EWOULDBLOCK) &&
+           (errno != EAGAIN) &&
+#ifdef ENOBUFS
+           /* FreeBSD can give this for large messages */
+           (errno != ENOBUFS) &&
+#endif
+           (errno != EINTR)) {
                return errno;
        }
 
@@ -756,13 +684,13 @@ static int unix_dgram_free(struct unix_dgram_ctx *ctx)
 
        ctx->ev_funcs->watch_free(ctx->sock_read_watch);
 
+       close(ctx->sock);
        if (getpid() == ctx->created_pid) {
                /* If we created it, unlink. Otherwise someone else might
                 * still have it open */
                unlink(ctx->path);
        }
 
-       close(ctx->sock);
        free(ctx->recv_buf);
        free(ctx);
        return 0;
@@ -816,7 +744,7 @@ static void unix_msg_recv(struct unix_dgram_ctx *dgram_ctx,
 
 int unix_msg_init(const struct sockaddr_un *addr,
                  const struct poll_funcs *ev_funcs,
-                 size_t fragment_len, uint64_t cookie,
+                 size_t fragment_len,
                  void (*recv_callback)(struct unix_msg_ctx *ctx,
                                        uint8_t *msg, size_t msg_len,
                                        int *fds, size_t num_fds,
@@ -834,7 +762,7 @@ int unix_msg_init(const struct sockaddr_un *addr,
 
        *ctx = (struct unix_msg_ctx) {
                .fragment_len = fragment_len,
-               .cookie = cookie,
+               .cookie = 1,
                .recv_callback = recv_callback,
                .private_data = private_data
        };
@@ -851,7 +779,8 @@ int unix_msg_init(const struct sockaddr_un *addr,
 }
 
 int unix_msg_send(struct unix_msg_ctx *ctx, const struct sockaddr_un *dst,
-                 const struct iovec *iov, int iovlen)
+                 const struct iovec *iov, int iovlen,
+                 const int *fds, size_t num_fds)
 {
        ssize_t msglen;
        size_t sent;
@@ -869,6 +798,10 @@ int unix_msg_send(struct unix_msg_ctx *ctx, const struct sockaddr_un *dst,
                return EINVAL;
        }
 
+       if (num_fds > INT8_MAX) {
+               return EINVAL;
+       }
+
        if (msglen <= (ctx->fragment_len - sizeof(uint64_t))) {
                uint64_t cookie = 0;
 
@@ -880,7 +813,7 @@ int unix_msg_send(struct unix_msg_ctx *ctx, const struct sockaddr_un *dst,
                }
 
                return unix_dgram_send(ctx->dgram, dst, iov_copy, iovlen+1,
-                                      NULL, 0);
+                                      fds, num_fds);
        }
 
        hdr = (struct unix_msg_hdr) {
@@ -936,8 +869,19 @@ int unix_msg_send(struct unix_msg_ctx *ctx, const struct sockaddr_un *dst,
                }
                sent += (fragment_len - sizeof(ctx->cookie) - sizeof(hdr));
 
-               ret = unix_dgram_send(ctx->dgram, dst, iov_copy, iov_index,
-                                     NULL, 0);
+               /*
+                * only the last fragment should pass the fd array.
+                * That simplifies the receiver a lot.
+                */
+               if (sent < msglen) {
+                       ret = unix_dgram_send(ctx->dgram, dst,
+                                             iov_copy, iov_index,
+                                             NULL, 0);
+               } else {
+                       ret = unix_dgram_send(ctx->dgram, dst,
+                                             iov_copy, iov_index,
+                                             fds, num_fds);
+               }
                if (ret != 0) {
                        break;
                }
@@ -963,22 +907,22 @@ static void unix_msg_recv(struct unix_dgram_ctx *dgram_ctx,
        uint64_t cookie;
 
        if (buflen < sizeof(cookie)) {
-               close_fd_array(fds, num_fds);
-               return;
+               goto close_fds;
        }
+
        memcpy(&cookie, buf, sizeof(cookie));
 
        buf += sizeof(cookie);
        buflen -= sizeof(cookie);
 
        if (cookie == 0) {
-               ctx->recv_callback(ctx, buf, buflen, fds, num_fds, ctx->private_data);
+               ctx->recv_callback(ctx, buf, buflen, fds, num_fds,
+                                  ctx->private_data);
                return;
        }
 
        if (buflen < sizeof(hdr)) {
-               close_fd_array(fds, num_fds);
-               return;
+               goto close_fds;
        }
        memcpy(&hdr, buf, sizeof(hdr));
 
@@ -1001,8 +945,7 @@ static void unix_msg_recv(struct unix_dgram_ctx *dgram_ctx,
        if (msg == NULL) {
                msg = malloc(offsetof(struct unix_msg, buf) + hdr.msglen);
                if (msg == NULL) {
-                       close_fd_array(fds, num_fds);
-                       return;
+                       goto close_fds;
                }
                *msg = (struct unix_msg) {
                        .msglen = hdr.msglen,
@@ -1015,21 +958,24 @@ static void unix_msg_recv(struct unix_dgram_ctx *dgram_ctx,
 
        space = msg->msglen - msg->received;
        if (buflen > space) {
-               close_fd_array(fds, num_fds);
-               return;
+               goto close_fds;
        }
 
        memcpy(msg->buf + msg->received, buf, buflen);
        msg->received += buflen;
 
        if (msg->received < msg->msglen) {
-               close_fd_array(fds, num_fds);
-               return;
+               goto close_fds;
        }
 
        DLIST_REMOVE(ctx->msgs, msg);
-       ctx->recv_callback(ctx, msg->buf, msg->msglen, fds, num_fds, ctx->private_data);
+       ctx->recv_callback(ctx, msg->buf, msg->msglen, fds, num_fds,
+                          ctx->private_data);
        free(msg);
+       return;
+
+close_fds:
+       close_fd_array(fds, num_fds);
 }
 
 int unix_msg_free(struct unix_msg_ctx *ctx)
@@ -1050,21 +996,3 @@ int unix_msg_free(struct unix_msg_ctx *ctx)
        free(ctx);
        return 0;
 }
-
-static ssize_t iov_buflen(const struct iovec *iov, int iovlen)
-{
-       size_t buflen = 0;
-       int i;
-
-       for (i=0; i<iovlen; i++) {
-               size_t thislen = iov[i].iov_len;
-               size_t tmp = buflen + thislen;
-
-               if ((tmp < buflen) || (tmp < thislen)) {
-                       /* overflow */
-                       return -1;
-               }
-               buflen = tmp;
-       }
-       return buflen;
-}