$OpenBSD: patch-ext_socket_socket_c,v 1.2 2012/09/18 16:02:53 jeremy Exp $

Fix UnixSocket#recv_io on 64-bit archs, backported from ruby SVN
revisions 22141 and 22633.

--- ext/socket/socket.c.orig	Fri May 20 15:25:41 2011
+++ ext/socket/socket.c	Mon Sep 17 14:10:31 2012
@@ -2027,6 +2027,28 @@ unix_send_io(sock, val)
 #endif
 }
 
+#if defined(HAVE_RECVMSG) && FD_PASSING_BY_MSG_CONTROL
+void
+rsock_discard_cmsg_resource(struct msghdr *mh)
+{
+    struct cmsghdr *cmh;
+
+    if (mh->msg_controllen == 0)
+        return;
+
+    for (cmh = CMSG_FIRSTHDR(mh); cmh != NULL; cmh = CMSG_NXTHDR(mh, cmh)) {
+        if (cmh->cmsg_level == SOL_SOCKET && cmh->cmsg_type == SCM_RIGHTS) {
+            int *fdp = (int *)CMSG_DATA(cmh);
+            int *end = (int *)((char *)cmh + cmh->cmsg_len);
+            while (fdp < end) {
+                close(*fdp);
+                fdp++;
+            }
+        }
+    }
+}
+#endif
+
 #if defined(HAVE_RECVMSG) && (FD_PASSING_BY_MSG_CONTROL || FD_PASSING_BY_MSG_ACCRIGHTS)
 static void
 thread_read_select(fd)
@@ -2097,16 +2119,11 @@ unix_recv_io(argc, argv, sock)
 	rb_sys_fail("recvmsg(2)");
 
 #if FD_PASSING_BY_MSG_CONTROL
-    if (msg.msg_controllen != CMSG_SPACE(sizeof(int))) {
-      rb_raise(rb_eSocket,
-          "file descriptor was not passed (msg_controllen=%d, %d expected)",
-          msg.msg_controllen, CMSG_SPACE(sizeof(int)));
+    if (msg.msg_controllen < sizeof(struct cmsghdr)) {
+        rb_raise(rb_eSocket,
+                 "file descriptor was not passed (msg_controllen=%d smaller than sizeof(struct cmsghdr)=%d)",
+                 (int)msg.msg_controllen, (int)sizeof(struct cmsghdr));
     }
-    if (cmsg.hdr.cmsg_len != CMSG_LEN(sizeof(int))) {
-      rb_raise(rb_eSocket,
-          "file descriptor was not passed (cmsg_len=%d, %d expected)",
-          cmsg.hdr.cmsg_len, CMSG_LEN(sizeof(int)));
-    }
     if (cmsg.hdr.cmsg_level != SOL_SOCKET) {
       rb_raise(rb_eSocket,
           "file descriptor was not passed (cmsg_level=%d, %d expected)",
@@ -2116,6 +2133,22 @@ unix_recv_io(argc, argv, sock)
       rb_raise(rb_eSocket,
           "file descriptor was not passed (cmsg_type=%d, %d expected)",
           cmsg.hdr.cmsg_type, SCM_RIGHTS);
+    }
+    if (msg.msg_controllen < CMSG_LEN(sizeof(int))) {
+        rb_raise(rb_eSocket,
+		 "file descriptor was not passed (msg_controllen=%d smaller than CMSG_LEN(sizeof(int))=%d)",
+		 (int)msg.msg_controllen, (int)CMSG_LEN(sizeof(int)));
+    }
+    if (CMSG_SPACE(sizeof(int)) < msg.msg_controllen) {
+	rb_raise(rb_eSocket,
+		 "file descriptor was not passed (msg_controllen=%d bigger than CMSG_SPACE(sizeof(int))=%d)",
+                 (int)msg.msg_controllen, (int)CMSG_SPACE(sizeof(int)));
+    }
+    if (cmsg.hdr.cmsg_len != CMSG_LEN(sizeof(int))) {
+        rsock_discard_cmsg_resource(&msg);
+        rb_raise(rb_eSocket,
+                 "file descriptor was not passed (cmsg_len=%d, %d expected)",
+                 (int)cmsg.hdr.cmsg_len, (int)CMSG_LEN(sizeof(int)));
     }
 #else
     if (msg.msg_accrightslen != sizeof(fd)) {
