+int
+sendmsg_x(struct proc *p, struct sendmsg_x_args *uap, user_ssize_t *retval)
+{
+ int error = 0;
+ struct user_msghdr_x *user_msg_x = NULL;
+ struct uio **uiop = NULL;
+ struct socket *so;
+ u_int i;
+ struct sockaddr *to = NULL;
+ user_ssize_t len_before = 0, len_after;
+ int need_drop = 0;
+ size_t size_of_msghdr;
+ void *umsgp = NULL;
+ u_int uiocnt;
+ int has_addr_or_ctl = 0;
+
+ KERNEL_DEBUG(DBG_FNC_SENDMSG_X | DBG_FUNC_START, 0, 0, 0, 0, 0);
+
+ error = file_socket(uap->s, &so);
+ if (error) {
+ goto out;
+ }
+ need_drop = 1;
+ if (so == NULL) {
+ error = EBADF;
+ goto out;
+ }
+
+ /*
+ * Input parameter range check
+ */
+ if (uap->cnt == 0 || uap->cnt > UIO_MAXIOV) {
+ error = EINVAL;
+ goto out;
+ }
+ /*
+ * Clip to max currently allowed
+ */
+ if (uap->cnt > somaxsendmsgx)
+ uap->cnt = somaxsendmsgx;
+
+ user_msg_x = _MALLOC(uap->cnt * sizeof(struct user_msghdr_x),
+ M_TEMP, M_WAITOK | M_ZERO);
+ if (user_msg_x == NULL) {
+ DBG_PRINTF("%s _MALLOC() user_msg_x failed\n", __func__);
+ error = ENOMEM;
+ goto out;
+ }
+ uiop = _MALLOC(uap->cnt * sizeof(struct uio *),
+ M_TEMP, M_WAITOK | M_ZERO);
+ if (uiop == NULL) {
+ DBG_PRINTF("%s _MALLOC() uiop failed\n", __func__);
+ error = ENOMEM;
+ goto out;
+ }
+
+ size_of_msghdr = IS_64BIT_PROCESS(p) ?
+ sizeof(struct user64_msghdr_x) : sizeof(struct user32_msghdr_x);
+
+ umsgp = _MALLOC(uap->cnt * size_of_msghdr,
+ M_TEMP, M_WAITOK | M_ZERO);
+ if (umsgp == NULL) {
+ printf("%s _MALLOC() user_msg_x failed\n", __func__);
+ error = ENOMEM;
+ goto out;
+ }
+ error = copyin(uap->msgp, umsgp, uap->cnt * size_of_msghdr);
+ if (error) {
+ DBG_PRINTF("%s copyin() failed\n", __func__);
+ goto out;
+ }
+ error = internalize_user_msghdr_array(umsgp,
+ IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32,
+ UIO_WRITE, uap->cnt, user_msg_x, uiop);
+ if (error) {
+ DBG_PRINTF("%s copyin_user_msghdr_array() failed\n", __func__);
+ goto out;
+ }
+ /*
+ * Make sure the size of each message iovec and
+ * the aggregate size of all the iovec is valid
+ */
+ if (uio_array_is_valid(uiop, uap->cnt) == 0) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /*
+ * Sanity check on passed arguments
+ */
+ for (i = 0; i < uap->cnt; i++) {
+ struct user_msghdr_x *mp = user_msg_x + i;
+
+ /*
+ * No flags on send message
+ */
+ if (mp->msg_flags != 0) {
+ error = EINVAL;
+ goto out;
+ }
+ /*
+ * No support for address or ancillary data (yet)
+ */
+ if (mp->msg_name != USER_ADDR_NULL || mp->msg_namelen != 0)
+ has_addr_or_ctl = 1;
+
+ if (mp->msg_control != USER_ADDR_NULL ||
+ mp->msg_controllen != 0)
+ has_addr_or_ctl = 1;
+
+#if CONFIG_MACF_SOCKET_SUBSET
+ /*
+ * We check the state without holding the socket lock;
+ * if a race condition occurs, it would simply result
+ * in an extra call to the MAC check function.
+ *
+ * Note: The following check is never true taken with the
+ * current limitation that we do not accept to pass an address,
+ * this is effectively placeholder code. If we add support for
+ * addresses, we will have to check every address.
+ */
+ if (to != NULL &&
+ !(so->so_state & SS_DEFUNCT) &&
+ (error = mac_socket_check_send(kauth_cred_get(), so, to))
+ != 0)
+ goto out;
+#endif /* MAC_SOCKET_SUBSET */
+ }
+
+ len_before = uio_array_resid(uiop, uap->cnt);
+
+ /*
+ * Feed list of packets at once only for connected socket without
+ * control message
+ */
+ if (so->so_proto->pr_usrreqs->pru_sosend_list !=
+ pru_sosend_list_notsupp &&
+ has_addr_or_ctl == 0 && somaxsendmsgx == 0) {
+ error = so->so_proto->pr_usrreqs->pru_sosend_list(so, uiop,
+ uap->cnt, uap->flags);
+ } else {
+ for (i = 0; i < uap->cnt; i++) {
+ struct user_msghdr_x *mp = user_msg_x + i;
+ struct user_msghdr user_msg;
+ uio_t auio = uiop[i];
+ int32_t tmpval;
+
+ user_msg.msg_flags = mp->msg_flags;
+ user_msg.msg_controllen = mp->msg_controllen;
+ user_msg.msg_control = mp->msg_control;
+ user_msg.msg_iovlen = mp->msg_iovlen;
+ user_msg.msg_iov = mp->msg_iov;
+ user_msg.msg_namelen = mp->msg_namelen;
+ user_msg.msg_name = mp->msg_name;
+
+ error = sendit(p, so, &user_msg, auio, uap->flags,
+ &tmpval);
+ if (error != 0)
+ break;
+ }
+ }
+ len_after = uio_array_resid(uiop, uap->cnt);
+
+ VERIFY(len_after <= len_before);
+
+ if (error != 0) {
+ if (len_after != len_before && (error == ERESTART ||
+ error == EINTR || error == EWOULDBLOCK ||
+ error == ENOBUFS))
+ error = 0;
+ /* Generation of SIGPIPE can be controlled per socket */
+ if (error == EPIPE && !(so->so_flags & SOF_NOSIGPIPE))
+ psignal(p, SIGPIPE);
+ }
+ if (error == 0) {
+ uiocnt = externalize_user_msghdr_array(umsgp,
+ IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32,
+ UIO_WRITE, uap->cnt, user_msg_x, uiop);
+
+ *retval = (int)(uiocnt);
+ }
+out:
+ if (need_drop)
+ file_drop(uap->s);
+ if (umsgp != NULL)
+ _FREE(umsgp, M_TEMP);
+ if (uiop != NULL) {
+ free_uio_array(uiop, uap->cnt);
+ _FREE(uiop, M_TEMP);
+ }
+ if (user_msg_x != NULL)
+ _FREE(user_msg_x, M_TEMP);
+
+ KERNEL_DEBUG(DBG_FNC_SENDMSG_X | DBG_FUNC_END, error, 0, 0, 0, 0);
+
+ return (error);
+}
+
+
+static int
+copyout_sa(struct sockaddr *fromsa, user_addr_t name, socklen_t *namelen)
+{
+ int error = 0;
+ socklen_t sa_len = 0;
+ ssize_t len;
+
+ len = *namelen;
+ if (len <= 0 || fromsa == 0) {
+ len = 0;
+ } else {
+#ifndef MIN
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+#endif
+ sa_len = fromsa->sa_len;
+ len = MIN((unsigned int)len, sa_len);
+ error = copyout(fromsa, name, (unsigned)len);
+ if (error)
+ goto out;
+ }
+ *namelen = sa_len;
+out:
+ return (0);
+}
+
+static int
+copyout_control(struct proc *p, struct mbuf *m, user_addr_t control,
+ socklen_t *controllen, int *flags)
+{
+ int error = 0;
+ ssize_t len;
+ user_addr_t ctlbuf;
+
+ len = *controllen;
+ *controllen = 0;
+ ctlbuf = control;
+
+ while (m && len > 0) {
+ unsigned int tocopy;
+ struct cmsghdr *cp = mtod(m, struct cmsghdr *);
+ int cp_size = CMSG_ALIGN(cp->cmsg_len);
+ int buflen = m->m_len;
+
+ while (buflen > 0 && len > 0) {
+ /*
+ * SCM_TIMESTAMP hack because struct timeval has a
+ * different size for 32 bits and 64 bits processes
+ */
+ if (cp->cmsg_level == SOL_SOCKET && cp->cmsg_type == SCM_TIMESTAMP) {
+ unsigned char tmp_buffer[CMSG_SPACE(sizeof(struct user64_timeval))];
+ struct cmsghdr *tmp_cp = (struct cmsghdr *)(void *)tmp_buffer;
+ int tmp_space;
+ struct timeval *tv = (struct timeval *)(void *)CMSG_DATA(cp);
+
+ tmp_cp->cmsg_level = SOL_SOCKET;
+ tmp_cp->cmsg_type = SCM_TIMESTAMP;
+
+ if (proc_is64bit(p)) {
+ struct user64_timeval *tv64 = (struct user64_timeval *)(void *)CMSG_DATA(tmp_cp);
+
+ tv64->tv_sec = tv->tv_sec;
+ tv64->tv_usec = tv->tv_usec;
+
+ tmp_cp->cmsg_len = CMSG_LEN(sizeof(struct user64_timeval));
+ tmp_space = CMSG_SPACE(sizeof(struct user64_timeval));
+ } else {
+ struct user32_timeval *tv32 = (struct user32_timeval *)(void *)CMSG_DATA(tmp_cp);
+
+ tv32->tv_sec = tv->tv_sec;
+ tv32->tv_usec = tv->tv_usec;
+
+ tmp_cp->cmsg_len = CMSG_LEN(sizeof(struct user32_timeval));
+ tmp_space = CMSG_SPACE(sizeof(struct user32_timeval));
+ }
+ if (len >= tmp_space) {
+ tocopy = tmp_space;
+ } else {
+ *flags |= MSG_CTRUNC;
+ tocopy = len;
+ }
+ error = copyout(tmp_buffer, ctlbuf, tocopy);
+ if (error)
+ goto out;
+ } else {
+ if (cp_size > buflen) {
+ panic("cp_size > buflen, something"
+ "wrong with alignment!");
+ }
+ if (len >= cp_size) {
+ tocopy = cp_size;
+ } else {
+ *flags |= MSG_CTRUNC;
+ tocopy = len;
+ }
+ error = copyout((caddr_t) cp, ctlbuf, tocopy);
+ if (error)
+ goto out;
+ }
+
+ ctlbuf += tocopy;
+ len -= tocopy;
+
+ buflen -= cp_size;
+ cp = (struct cmsghdr *)(void *)
+ ((unsigned char *) cp + cp_size);
+ cp_size = CMSG_ALIGN(cp->cmsg_len);
+ }
+
+ m = m->m_next;
+ }
+ *controllen = ctlbuf - control;
+out:
+ return (error);
+}
+
+/*
+ * Returns: 0 Success
+ * ENOTSOCK
+ * EINVAL
+ * EBADF
+ * EACCES Mandatory Access Control failure
+ * copyout:EFAULT
+ * fp_lookup:EBADF
+ * <pru_soreceive>:ENOBUFS
+ * <pru_soreceive>:ENOTCONN
+ * <pru_soreceive>:EWOULDBLOCK