diff mbox series

[RFC,13/14] linux: Add fallback for 64-bit time_t SO_TIMESTAMP{NS}

Message ID 20200908145738.640039-13-adhemerval.zanella@linaro.org
State New
Headers show
Series [v2,01/14] linux: Simplify clock_getres | expand

Commit Message

Adhemerval Zanella Sept. 8, 2020, 2:57 p.m. UTC
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(-)
diff mbox series

Patch

diff --git a/include/sys/socket.h b/include/sys/socket.h
index 0e39dd2a3a..c551c8fa87 100644
--- a/include/sys/socket.h
+++ b/include/sys/socket.h
@@ -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
diff --git a/sysdeps/unix/sysv/linux/getsockopt.c b/sysdeps/unix/sysv/linux/getsockopt.c
index 5dce0e29ee..0089a177b6 100644
--- a/sysdeps/unix/sysv/linux/getsockopt.c
+++ b/sysdeps/unix/sysv/linux/getsockopt.c
@@ -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;
diff --git a/sysdeps/unix/sysv/linux/recvmsg.c b/sysdeps/unix/sysv/linux/recvmsg.c
index a86d502922..c6d1d10b05 100644
--- a/sysdeps/unix/sysv/linux/recvmsg.c
+++ b/sysdeps/unix/sysv/linux/recvmsg.c
@@ -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)
diff --git a/sysdeps/unix/sysv/linux/setsockopt.c b/sysdeps/unix/sysv/linux/setsockopt.c
index ebc32d788c..6aabe8bbde 100644
--- a/sysdeps/unix/sysv/linux/setsockopt.c
+++ b/sysdeps/unix/sysv/linux/setsockopt.c
@@ -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;