hook recvmmsg up to SO_TIMESTAMP[NS] fallback for pre-time64 kernels
authorRich Felker <dalias@aerifal.cx>
Wed, 18 Dec 2019 04:00:24 +0000 (23:00 -0500)
committerRich Felker <dalias@aerifal.cx>
Wed, 18 Dec 2019 04:00:24 +0000 (23:00 -0500)
always try the time64 syscall first since we can use its success to
conclude that no conversion is needed (any setsockopt for the
timestamp options would have succeeded without need for fallbacks).
otherwise, we have to remember the original controllen for each
msghdr, requiring O(vlen) space, so vlen must be bounded. linux clamps
it to IOV_MAX for sendmmsg only (not recvmmsg), but doing the same for
recvmmsg is not unreasonable, especially since the limitation will
only apply to old kernels.

we could optimize to avoid trying SYS_recvmmsg_time64 first if all
msghdrs have controllen zero, or support unlimited vlen by looping and
emulating the timeout logic, but I'm not inclined to do complex and
error-prone optimizations on a function that has so many underlying
problems it should really never be used.

src/network/recvmmsg.c
src/network/recvmsg.c

index d5dc6b51cb8be7b93a99179734ede4d2ad7362cb..2978e2f64f34ed07a1a7c65bfd910bf72f63b668 100644 (file)
@@ -8,6 +8,8 @@
 #define IS32BIT(x) !((x)+0x80000000ULL>>32)
 #define CLAMP(x) (int)(IS32BIT(x) ? (x) : 0x7fffffffU+((0ULL+(x))>>63))
 
+hidden void __convert_scm_timestamps(struct msghdr *, socklen_t);
+
 int recvmmsg(int fd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags, struct timespec *timeout)
 {
 #if LONG_MAX > INT_MAX
@@ -19,14 +21,18 @@ int recvmmsg(int fd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int fla
 #ifdef SYS_recvmmsg_time64
        time_t s = timeout ? timeout->tv_sec : 0;
        long ns = timeout ? timeout->tv_nsec : 0;
-       int r = -ENOSYS;
-       if (SYS_recvmmsg == SYS_recvmmsg_time64 || !IS32BIT(s))
-               r = __syscall_cp(SYS_recvmmsg_time64, fd, msgvec, vlen, flags,
+       int r = __syscall_cp(SYS_recvmmsg_time64, fd, msgvec, vlen, flags,
                        timeout ? ((long long[]){s, ns}) : 0);
        if (SYS_recvmmsg == SYS_recvmmsg_time64 || r!=-ENOSYS)
                return __syscall_ret(r);
-       return syscall_cp(SYS_recvmmsg, fd, msgvec, vlen, flags,
+       if (vlen > IOV_MAX) vlen = IOV_MAX;
+       socklen_t csize[vlen];
+       for (int i=0; i<vlen; i++) csize[i] = msgvec[i].msg_hdr.msg_controllen;
+       r = __syscall_cp(SYS_recvmmsg, fd, msgvec, vlen, flags,
                timeout ? ((long[]){CLAMP(s), ns}) : 0);
+       for (int i=0; i<r; i++)
+               __convert_scm_timestamps(&msgvec[i].msg_hdr, csize[i]);
+       return __syscall_ret(r);
 #else
        return syscall_cp(SYS_recvmmsg, fd, msgvec, vlen, flags, timeout);
 #endif
index 5ce37e738f727db834e9e1643019e0a85a24df1f..03641625e8af79aea0aed96d6daa7d549a2c7efc 100644 (file)
@@ -5,7 +5,9 @@
 #include <string.h>
 #include "syscall.h"
 
-static void convert_scm_timestamps(struct msghdr *msg, socklen_t csize)
+hidden void __convert_scm_timestamps(struct msghdr *, socklen_t);
+
+void __convert_scm_timestamps(struct msghdr *msg, socklen_t csize)
 {
        if (SCM_TIMESTAMP == SCM_TIMESTAMP_OLD) return;
        if (!msg->msg_control || !msg->msg_controllen) return;
@@ -58,7 +60,7 @@ ssize_t recvmsg(int fd, struct msghdr *msg, int flags)
        }
 #endif
        r = socketcall_cp(recvmsg, fd, msg, flags, 0, 0, 0);
-       if (r >= 0) convert_scm_timestamps(msg, orig_controllen);
+       if (r >= 0) __convert_scm_timestamps(msg, orig_controllen);
 #if LONG_MAX > INT_MAX
        if (orig) *orig = h;
 #endif