The constant value will be changed for __TIMESIZE=64, so binaries built
with 64-bit time support might fail to work properly on old kernels.
Both {get,set}sockopt will retry the syscall with the old constant
values and the timeout value adjusted when kernel returns ENOTPROTOPT.
The recvmsg handling is more complicated because it requires check the
returned kernel control message and make some convertions. For
!__ASSUME_TIME64_SYSCALLS it converts the first 32-bit time SO_TIMESTAMP
or SO_TIMESTAMPNS and appends it to the control buffer if has extra
space or returns MSG_CTRUNC otherwise. The 32-bit time field is kept
as-is.
Calls with __TIMESIZE=32 will see the converted 64-bit time control
messages as spurious control message of unknown type. Calls with
__TIMESIZE=64 running on pre-time64 kernels will see the original
message as a spurious control ones of unknown typ while running on
kernel with native 64-bit time support will only see the time64 version
of the control message.
Checked on x86_64-linux-gnu and i686-linux-gnu (on 5.4 and on 4.15
kernel).
---
include/sys/socket.h | 9 +++
sysdeps/unix/sysv/linux/getsockopt.c | 12 ++++
sysdeps/unix/sysv/linux/recvmsg.c | 93 ++++++++++++++++++++++++++--
sysdeps/unix/sysv/linux/setsockopt.c | 12 ++++
4 files changed, 121 insertions(+), 5 deletions(-)
@@ -162,6 +162,15 @@ libc_hidden_proto (__libc_sa_len)
# define SA_LEN(_x) __libc_sa_len((_x)->sa_family)
#endif
+/* Used on y2038 emulation on 64-bit time_t binaries running on older
+ kernel without 64-bit time_t support. */
+#define SCM_TIMESTAMP_OLD SO_TIMESTAMP_OLD
+#define SCM_TIMESTAMPNS_OLD SO_TIMESTAMPNS_OLD
+#define SCM_TIMESTAMPING_OLD SO_TIMESTAMPING_OLD
+#define SCM_TIMESTAMP_NEW SO_TIMESTAMP_NEW
+#define SCM_TIMESTAMPNS_NEW SO_TIMESTAMPNS_NEW
+#define SCM_TIMESTAMPING_NEW SO_TIMESTAMPING_NEW
+
libc_hidden_proto (__cmsg_nxthdr)
#endif
@@ -72,6 +72,18 @@ getsockopt32 (int fd, int level, int optname, void *optval,
*tv64 = valid_timeval32_to_timeval64 (tv32);
*len = sizeof (*tv64);
}
+ break;
+
+ case SO_TIMESTAMP_NEW:
+ case SO_TIMESTAMPNS_NEW:
+ {
+ if (optname == SO_TIMESTAMP_NEW)
+ optname = SO_TIMESTAMP_OLD;
+ if (optname == SO_TIMESTAMPNS_NEW)
+ optname = SO_TIMESTAMPNS_OLD;
+ r = getsockopt_syscall (fd, level, optname, optval, len);
+ }
+ break;
}
return r;
@@ -21,14 +21,97 @@
#include <socketcall.h>
#include <shlib-compat.h>
+#ifndef __ASSUME_TIME64_SYSCALLS
+/* It converts the first SO_TIMESTAMP or SO_TIMESTAMPNS with 32-bit time and
+ appends it to the control buffer. The 32-bit time field is kept as-is.
+
+ Calls with __TIMESIZE=32 will see the converted 64-bit time control
+ messages as spurious control message of unknown type.
+
+ Calls with __TIMESIZE=64 running on pre-time64 kernels will see the
+ original message as a spurious control ones of unknown typ while running
+ on kernel with native 64-bit time support will only see the time64 version
+ of the control message. */
+static void
+convert_scm_timestamps (struct msghdr *msg, socklen_t msgsize)
+{
+ if (msg->msg_control == NULL || msg->msg_controllen == 0)
+ return;
+
+ /* The returnted control message format for SO_TIMESTAMP_NEW is a
+ 'struct __kernel_sock_timeval' while for SO_TIMESTAMPNS_NEW is a
+ 'struct __kernel_timespec'. In both case it is essentially two
+ uint64_t members. */
+ uint64_t tvts[2];
+
+ struct cmsghdr *cmsg, *last = NULL;
+ int type = 0;
+
+ for (cmsg = CMSG_FIRSTHDR (msg);
+ cmsg != NULL;
+ cmsg = CMSG_NXTHDR (msg, cmsg))
+ {
+ if (cmsg->cmsg_level != SOL_SOCKET)
+ continue;
+
+ switch (cmsg->cmsg_type)
+ {
+ case SCM_TIMESTAMP_OLD:
+ if (type != 0)
+ break;
+ type = SCM_TIMESTAMP_NEW;
+ goto common;
+
+ case SCM_TIMESTAMPNS_OLD:
+ type = SCM_TIMESTAMPNS_NEW;
+
+ /* fallthrough */
+ common:
+ memcpy (tvts, CMSG_DATA (cmsg), sizeof (tvts));
+ break;
+ }
+
+ last = cmsg;
+ }
+
+ if (last == NULL || type == 0)
+ return;
+
+ if (CMSG_SPACE (sizeof tvts) > msgsize - msg->msg_controllen)
+ {
+ msg->msg_flags |= MSG_CTRUNC;
+ return;
+ }
+
+ msg->msg_controllen += CMSG_SPACE (sizeof tvts);
+ cmsg = CMSG_NXTHDR(msg, last);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = type;
+ cmsg->cmsg_len = CMSG_LEN (sizeof tvts);
+ memcpy (CMSG_DATA (cmsg), tvts, sizeof tvts);
+}
+#endif
+
ssize_t
__libc_recvmsg (int fd, struct msghdr *msg, int flags)
{
-# ifdef __ASSUME_RECVMSG_SYSCALL
- return SYSCALL_CANCEL (recvmsg, fd, msg, flags);
-# else
- return SOCKETCALL_CANCEL (recvmsg, fd, msg, flags);
-# endif
+ ssize_t r;
+#ifndef __ASSUME_TIME64_SYSCALLS
+ socklen_t orig_controllen = msg->msg_controllen;
+#endif
+
+#ifdef __ASSUME_RECVMSG_SYSCALL
+ r = SYSCALL_CANCEL (recvmsg, fd, msg, flags);
+#else
+ r = SOCKETCALL_CANCEL (recvmsg, fd, msg, flags);
+#endif
+
+#ifndef __ASSUME_TIME64_SYSCALLS
+ if (r >= 0)
+ convert_scm_timestamps (msg, orig_controllen);
+#endif
+
+ return r;
}
weak_alias (__libc_recvmsg, recvmsg)
weak_alias (__libc_recvmsg, __recvmsg)
@@ -74,6 +74,18 @@ setsockopt32 (int fd, int level, int optname, const void *optval,
r = setsockopt_syscall (fd, level, optname, &tv32, sizeof (tv32));
}
+ break;
+
+ case SO_TIMESTAMP_NEW:
+ case SO_TIMESTAMPNS_NEW:
+ {
+ if (optname == SO_TIMESTAMP_NEW)
+ optname = SO_TIMESTAMP_OLD;
+ if (optname == SO_TIMESTAMPNS_NEW)
+ optname = SO_TIMESTAMPNS_OLD;
+ r = setsockopt_syscall (fd, level, optname, NULL, 0);
+ }
+ break;
}
return r;