summaryrefslogtreecommitdiff
path: root/sys/compat/linux/common/linux_socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/compat/linux/common/linux_socket.c')
-rw-r--r--sys/compat/linux/common/linux_socket.c328
1 files changed, 147 insertions, 181 deletions
diff --git a/sys/compat/linux/common/linux_socket.c b/sys/compat/linux/common/linux_socket.c
index e3e854a7255..a77014fd56e 100644
--- a/sys/compat/linux/common/linux_socket.c
+++ b/sys/compat/linux/common/linux_socket.c
@@ -1,4 +1,4 @@
-/* $NetBSD: linux_socket.c,v 1.75 2007/06/06 17:08:27 rjs Exp $ */
+/* $NetBSD: linux_socket.c,v 1.76 2007/07/01 18:45:36 dsl Exp $ */
/*-
* Copyright (c) 1995, 1998 The NetBSD Foundation, Inc.
@@ -42,7 +42,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: linux_socket.c,v 1.75 2007/06/06 17:08:27 rjs Exp $");
+__KERNEL_RCSID(0, "$NetBSD: linux_socket.c,v 1.76 2007/07/01 18:45:36 dsl Exp $");
#if defined(_KERNEL_OPT)
#include "opt_ktrace.h"
@@ -459,7 +459,8 @@ linux_sys_sendmsg(l, v, retval)
* Handle cmsg if there is any.
*/
if (CMSG_FIRSTHDR(&msg)) {
- struct cmsghdr cmsg, *cc;
+ struct linux_cmsghdr l_cmsg, *l_cc;
+ struct cmsghdr *cmsg;
ssize_t resid = msg.msg_controllen;
size_t clen, cidx = 0, cspace;
@@ -467,17 +468,17 @@ linux_sys_sendmsg(l, v, retval)
clen = MLEN;
control = mtod(ctl_mbuf, void *);
- cc = CMSG_FIRSTHDR(&msg);
+ l_cc = LINUX_CMSG_FIRSTHDR(&msg);
do {
- error = copyin(cc, &cmsg, sizeof(cmsg));
+ error = copyin(l_cc, &l_cmsg, sizeof(l_cmsg));
if (error)
goto done;
/*
* Sanity check the control message length.
*/
- if (cmsg.cmsg_len > resid
- || cmsg.cmsg_len < sizeof(struct cmsghdr)) {
+ if (l_cmsg.cmsg_len > resid
+ || l_cmsg.cmsg_len < sizeof l_cmsg) {
error = EINVAL;
goto done;
}
@@ -486,13 +487,13 @@ linux_sys_sendmsg(l, v, retval)
* Refuse unsupported control messages, and
* translate fields as appropriate.
*/
- switch (cmsg.cmsg_level) {
+ switch (l_cmsg.cmsg_level) {
case LINUX_SOL_SOCKET:
/* It only differs on some archs */
if (LINUX_SOL_SOCKET != SOL_SOCKET)
- cmsg.cmsg_level = SOL_SOCKET;
+ l_cmsg.cmsg_level = SOL_SOCKET;
- switch(cmsg.cmsg_type) {
+ switch(l_cmsg.cmsg_type) {
case LINUX_SCM_RIGHTS:
/* Linux SCM_RIGHTS is same as NetBSD */
break;
@@ -508,7 +509,7 @@ linux_sys_sendmsg(l, v, retval)
break;
}
- cspace = CMSG_SPACE(cmsg.cmsg_len - sizeof(cmsg));
+ cspace = CMSG_SPACE(l_cmsg.cmsg_len - sizeof(l_cmsg));
/* Check the buffer is big enough */
if (__predict_false(cidx + cspace > clen)) {
@@ -532,22 +533,25 @@ linux_sys_sendmsg(l, v, retval)
}
/* Copy header */
- memcpy(&control[cidx], &cmsg, sizeof(cmsg));
+ cmsg = (void *)&control[cidx];
+ cmsg->cmsg_len = l_cmsg.cmsg_len + LINUX_CMSG_ALIGN_DELTA;
+ cmsg->cmsg_level = l_cmsg.cmsg_level;
+ cmsg->cmsg_type = l_cmsg.cmsg_type;
/* Zero are between header and data */
- memset(&control[cidx+sizeof(cmsg)], 0,
+ memset(cmsg + 1, 0,
CMSG_ALIGN(sizeof(cmsg)) - sizeof(cmsg));
/* Copyin the data */
- error = copyin(LINUX_CMSG_DATA(cc),
+ error = copyin(LINUX_CMSG_DATA(l_cc),
CMSG_DATA(control),
- cmsg.cmsg_len - sizeof(cmsg));
+ l_cmsg.cmsg_len - sizeof(l_cmsg));
if (error)
goto done;
- resid -= cspace;
+ resid -= LINUX_CMSG_ALIGN(l_cmsg.cmsg_len);
cidx += cspace;
- } while ((cc = LINUX_CMSG_NXTHDR(&msg, cc)) && resid > 0);
+ } while ((l_cc = LINUX_CMSG_NXTHDR(&msg, l_cc)) && resid > 0);
/* If we allocated a buffer, attach to mbuf */
if (cidx > MLEN) {
@@ -607,6 +611,98 @@ linux_sys_recvfrom(l, v, retval)
return (0);
}
+static int
+linux_copyout_msg_control(struct lwp *l, struct msghdr *mp, struct mbuf *control)
+{
+ int dlen, error = 0;
+ struct cmsghdr *cmsg;
+ struct linux_cmsghdr linux_cmsg;
+ struct mbuf *m;
+ char *q, *q_end;
+
+ if (mp->msg_controllen <= 0 || control == 0) {
+ mp->msg_controllen = 0;
+ free_control_mbuf(l, control, control);
+ return 0;
+ }
+
+ q = (char *)mp->msg_control;
+ q_end = q + mp->msg_controllen;
+
+ for (m = control; m != NULL; ) {
+ cmsg = mtod(m, struct cmsghdr *);
+
+ /*
+ * Fixup cmsg. We handle two things:
+ * 0. different sizeof cmsg_len.
+ * 1. different values for level/type on some archs
+ * 2. different alignment of CMSG_DATA on some archs
+ */
+ linux_cmsg.cmsg_len = cmsg->cmsg_len - LINUX_CMSG_ALIGN_DELTA;
+ linux_cmsg.cmsg_level = cmsg->cmsg_level;
+ linux_cmsg.cmsg_type = cmsg->cmsg_type;
+
+ dlen = q_end - q;
+ if (linux_cmsg.cmsg_len > dlen) {
+ /* Not enough room for the parameter */
+ dlen -= sizeof linux_cmsg;
+ if (dlen <= 0)
+ /* Discard if header wont fit */
+ break;
+ mp->msg_flags |= MSG_CTRUNC;
+ if (linux_cmsg.cmsg_level == SOL_SOCKET
+ && linux_cmsg.cmsg_type == SCM_RIGHTS)
+ /* Do not truncate me ... */
+ break;
+ } else
+ dlen = linux_cmsg.cmsg_len - sizeof linux_cmsg;
+
+ switch (linux_cmsg.cmsg_level) {
+ case SOL_SOCKET:
+ linux_cmsg.cmsg_level = LINUX_SOL_SOCKET;
+ switch (linux_cmsg.cmsg_type) {
+ case SCM_RIGHTS:
+ /* Linux SCM_RIGHTS is same as NetBSD */
+ break;
+
+ default:
+ /* other types not supported */
+ error = EINVAL;
+ goto done;
+ }
+ /* machine dependant ! */
+ break;
+ default:
+ /* pray and leave intact */
+ break;
+ }
+
+ /* There can be padding between the header and data... */
+ error = copyout(&linux_cmsg, q, sizeof *cmsg);
+ if (error != 0) {
+ error = copyout(CCMSG_DATA(cmsg), q + sizeof linux_cmsg,
+ dlen);
+ }
+ if (error != 0) {
+ /* We must free all the SCM_RIGHTS */
+ m = control;
+ break;
+ }
+ m = m->m_next;
+ if (m == NULL || q + LINUX_CMSG_ALIGN(dlen) > q_end) {
+ q += dlen;
+ break;
+ }
+ q += LINUX_CMSG_ALIGN(dlen);
+ }
+
+ done:
+ free_control_mbuf(l, control, m);
+
+ mp->msg_controllen = q - (char *)mp->msg_control;
+ return error;
+}
+
int
linux_sys_recvmsg(l, v, retval)
struct lwp *l;
@@ -620,177 +716,47 @@ linux_sys_recvmsg(l, v, retval)
} */ *uap = v;
struct msghdr msg;
int error;
- struct sys_recvmsg_args bsa;
- int lflags;
- u_int8_t *ocontrol = NULL; /* XXX: gcc */
- socklen_t ocontrollen = 0;
-
- /*
- * Data alignment is different on some architectures. If control
- * message is expected, we must arrange for the control message
- * to be initially put elsewhere, and copy to target place
- * with Linux alignment.
- */
- if (LINUX_CMSG_ALIGNDIFF) {
- error = copyin(SCARG(uap, msg), &msg, sizeof(msg));
- if (error)
- return (error);
-
- if (CMSG_FIRSTHDR(&msg)) {
- void *sg;
-
- /* Need to fit within stackgap */
- if (msg.msg_controllen > STACKGAPLEN/2) {
- /* Sorry guys! */
- return (EINVAL);
- }
-
- sg = stackgap_init(l->l_proc, STACKGAPLEN/3);
-
- ocontrol = msg.msg_control;
- ocontrollen = msg.msg_controllen;
-
- /* space for at least one message's worth align */
- msg.msg_controllen += CMSG_ALIGN(1);
-
- msg.msg_control = stackgap_alloc(l->l_proc, &sg,
- msg.msg_controllen);
- if (!msg.msg_control)
- return (ENOMEM);
-
- /*
- * Okay to overwrite the original structure, it's
- * supposed to be writable.
- */
- error = copyout(&msg, SCARG(uap, msg), sizeof(msg));
- if (error)
- return (error);
- }
- }
-
- SCARG(&bsa, s) = SCARG(uap, s);
- SCARG(&bsa, msg) = SCARG(uap, msg);
- SCARG(&bsa, flags) = linux_to_bsd_msg_flags(SCARG(uap, flags));
-
- if (SCARG(&bsa, flags) < 0) {
- /* Some unsupported flag */
- return (EINVAL);
- }
-
- if ((error = sys_recvmsg(l, &bsa, retval)))
- goto done;
+ struct mbuf *from, *control;
- /* Fixup sockaddr */
error = copyin(SCARG(uap, msg), &msg, sizeof(msg));
if (error)
- goto done;
-
- if (msg.msg_name && msg.msg_namelen > 2) {
- if ((error = linux_sa_put(msg.msg_name)))
- goto done;
- }
+ return (error);
- /* Fixup msg flags */
- lflags = bsd_to_linux_msg_flags(msg.msg_flags);
- if (lflags < 0) {
- /* Some flag unsupported by Linux */
- error = EINVAL;
- goto done;
+ msg.msg_flags = linux_to_bsd_msg_flags(SCARG(uap, flags));
+ if (msg.msg_flags < 0) {
+ /* Some unsupported flag */
+ return (EINVAL);
}
- error = copyout(&lflags, (u_int8_t *) SCARG(uap, msg) +
- offsetof(struct msghdr, msg_flags), sizeof(lflags));
- if (error)
- goto done;
-
- /*
- * Fixup cmsg. We handle two things:
- * 1. different values for level/type on some archs
- * 2. different alignment of CMSG_DATA on some archs
- */
- if (CMSG_FIRSTHDR(&msg)) {
- struct cmsghdr cmsg, *cc;
- int changed = 0;
- size_t resid = ocontrollen;
-
- cc = CMSG_FIRSTHDR(&msg);
- do {
- error = copyin(cc, &cmsg, sizeof(cmsg));
- if (error)
- goto done;
-
- switch (cmsg.cmsg_level) {
- case SOL_SOCKET:
- if (SOL_SOCKET != LINUX_SOL_SOCKET) {
- cmsg.cmsg_level = LINUX_SOL_SOCKET;
- changed = 1;
- }
-
- switch (cmsg.cmsg_type) {
- case SCM_RIGHTS:
- /* Linux SCM_RIGHTS is same as NetBSD */
- break;
-
- default:
- /* other types not supported */
- error = EINVAL;
- goto done;
- }
- default:
- /* pray and leave intact */
- break;
- }
-
- if (LINUX_CMSG_ALIGNDIFF) {
- int i;
- u_int8_t d, *sd, *td;
-
- /*
- * Sanity check.
- */
- if (cmsg.cmsg_len > resid
- || cmsg.cmsg_len < sizeof(cmsg)) {
- error = EINVAL;
- goto done;
- }
-
- /*
- * Need to copy the cmsg from scratch area
- * to the original place, converting data
- * alignment from NetBSD to Linux one.
- */
- error = copyout(&cmsg, ocontrol, sizeof(cmsg));
- if (error)
- goto done;
- /* zero pad */
-#if 0
- for(i=0; i < LINUX_CMSG_ALIGN(sizeof(cmsg)) - sizeof(cmsg); i++) {
- copyout("",&ocontrol[sizeof(cmsg)+i],1);
- }
-#endif
-
- sd = CMSG_DATA(cc);
- td = LINUX_CMSG_DATA(ocontrol);
-
- /* This is not particularily effective, but ..*/
- d = '\0';
- for(i=0; i < cmsg.cmsg_len - sizeof(cmsg); i++){
- copyin(sd++, &d, 1);
- copyout(&d, td++, 1);
- }
-
- resid -= (td - ocontrol);
- ocontrol = td;
- } else if (changed) {
- /* Update cmsghdr in-place */
- error = copyout(&cmsg, cc, sizeof(cmsg));
- if (error)
- goto done;
- changed = 0;
- }
- } while((cc = CMSG_NXTHDR(&msg, cc)));
+ msg.msg_flags |= MSG_IOVUSRSPACE;
+
+ error = do_sys_recvmsg(l, SCARG(uap, s), &msg, &from,
+ msg.msg_control != NULL ? &control : NULL, retval);
+ if (error != 0)
+ return error;
+
+ if (msg.msg_control != NULL)
+ error = linux_copyout_msg_control(l, &msg, control);
+
+ if (error == 0 && from != 0) {
+ mtod(from, struct osockaddr *)->sa_family =
+ bsd_to_linux_domain(mtod(from, struct sockaddr *)->sa_family);
+ error = copyout_sockname(msg.msg_name, &msg.msg_namelen, 0,
+ from);
+ } else
+ msg.msg_namelen = 0;
+
+ if (from != NULL)
+ m_free(from);
+
+ if (error == 0) {
+ msg.msg_flags = bsd_to_linux_msg_flags(msg.msg_flags);
+ if (msg.msg_flags < 0)
+ /* Some flag unsupported by Linux */
+ error = EINVAL;
+ else
+ error = copyout(&msg, SCARG(uap, msg), sizeof(msg));
}
-done:
return (error);
}