[RFC,12/14] linux: Add fallback for 64-bit time_t SO_{RCV,SND}TIMEO

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

Commit Message

Adhemerval Zanella Netto 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.

It also changes to SO_{RCV,SND}TIMEO to follow the uapi kernel values
where SO_{RCV,SND}TIMEO_OLD indicates pre 64-bit time support and
SO_{RCV,SND}TIMEO_NEW indicate time64 support.  It allows to refer to
constant independently of the time_t abi used (since kernel defines
SO_{RCV,SND}TIMEO depending of the time_t size).

The hppa, mips, powerpc, and sparc provides its own socket-constant.h,
which are also updated the the SO_{RCV,SND}TIMEO constants (they are
missed on 019d828669df966 patch).

Checked on x86_64-linux-gnu and i686-linux-gnu (on 5.4 and on 4.15
kernel).
---
 .../unix/sysv/linux/bits/socket-constants.h   | 19 ++---
 sysdeps/unix/sysv/linux/getsockopt.c          | 72 ++++++++++++++++--
 .../sysv/linux/hppa/bits/socket-constants.h   | 15 +++-
 .../sysv/linux/mips/bits/socket-constants.h   | 17 ++++-
 .../linux/powerpc/bits/socket-constants.h     | 19 ++++-
 sysdeps/unix/sysv/linux/setsockopt.c          | 76 +++++++++++++++++--
 .../sysv/linux/sparc/bits/socket-constants.h  | 17 ++++-
 7 files changed, 202 insertions(+), 33 deletions(-)
  

Patch

diff --git a/sysdeps/unix/sysv/linux/bits/socket-constants.h b/sysdeps/unix/sysv/linux/bits/socket-constants.h
index d02e1cbc7c..c7478651fb 100644
--- a/sysdeps/unix/sysv/linux/bits/socket-constants.h
+++ b/sysdeps/unix/sysv/linux/bits/socket-constants.h
@@ -32,19 +32,20 @@ 
 #define SO_OOBINLINE 10
 #define SO_RCVBUF 8
 #define SO_RCVLOWAT 18
-#if (__TIMESIZE == 64 && __WORDSIZE == 32 \
-     && (!defined __SYSCALL_WORDSIZE || __SYSCALL_WORDSIZE == 32))
-# define SO_RCVTIMEO 66
-#else
-# define SO_RCVTIMEO 20
-#endif
 #define SO_REUSEADDR 2
 #define SO_SNDBUF 7
 #define SO_SNDLOWAT 19
+#define SO_TYPE 3
+
+#define SO_RCVTIMEO_OLD 20
+#define SO_SNDTIMEO_OLD 21
+#define SO_RCVTIMEO_NEW 66
+#define SO_SNDTIMEO_NEW 67
 #if (__TIMESIZE == 64 && __WORDSIZE == 32 \
      && (!defined __SYSCALL_WORDSIZE || __SYSCALL_WORDSIZE == 32))
-# define SO_SNDTIMEO 67
+# define SO_RCVTIMEO SO_RCVTIMEO_NEW
+# define SO_SNDTIMEO SO_SNDTIMEO_NEW
 #else
-# define SO_SNDTIMEO 21
+# define SO_RCVTIMEO SO_RCVTIMEO_OLD
+# define SO_SNDTIMEO SO_SNDTIMEO_OLD
 #endif
-#define SO_TYPE 3
diff --git a/sysdeps/unix/sysv/linux/getsockopt.c b/sysdeps/unix/sysv/linux/getsockopt.c
index 11939660c1..5dce0e29ee 100644
--- a/sysdeps/unix/sysv/linux/getsockopt.c
+++ b/sysdeps/unix/sysv/linux/getsockopt.c
@@ -15,16 +15,20 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <errno.h>
-#include <signal.h>
 #include <sys/socket.h>
-
+/* The kernel header with SO_* constants is used as default for _GNU_SOURCE,
+   however the new constants that describe 64-bit time support were added
+   only on v5.1.  */
+#if !defined(SO_RCVTIMEO_NEW) || !defined(SO_RCVTIMEO_OLD)
+# include <bits/socket-constants.h>
+#endif
+#include <time.h>
+#include <sysdep.h>
 #include <socketcall.h>
-#include <kernel-features.h>
-#include <sys/syscall.h>
 
-int
-__getsockopt (int fd, int level, int optname, void *optval, socklen_t *len)
+static int
+getsockopt_syscall (int fd, int level, int optname, void *optval,
+		    socklen_t *len)
 {
 #ifdef __ASSUME_GETSOCKOPT_SYSCALL
   return INLINE_SYSCALL (getsockopt, 5, fd, level, optname, optval, len);
@@ -32,4 +36,58 @@  __getsockopt (int fd, int level, int optname, void *optval, socklen_t *len)
   return SOCKETCALL (getsockopt, fd, level, optname, optval, len);
 #endif
 }
+
+#ifndef __ASSUME_TIME64_SYSCALLS
+static int
+getsockopt32 (int fd, int level, int optname, void *optval,
+	      socklen_t *len)
+{
+  int r = -1;
+
+  if (level != SOL_SOCKET)
+    return r;
+
+  switch (optname)
+    {
+    case SO_RCVTIMEO_NEW:
+    case SO_SNDTIMEO_NEW:
+      {
+        if (*len < sizeof (struct __timeval64))
+	  {
+	    __set_errno (EINVAL);
+	    break;
+	  }
+
+	if (optname == SO_RCVTIMEO_NEW)
+	  optname = SO_RCVTIMEO_OLD;
+	if (optname == SO_SNDTIMEO_NEW)
+	  optname = SO_SNDTIMEO_OLD;
+
+	struct __timeval32 tv32;
+	r = getsockopt_syscall (fd, level, optname, &tv32,
+				(socklen_t[]) { sizeof tv32 });
+	if (r < 0)
+	  break;
+	struct __timeval64 *tv64 = (struct __timeval64 *) optval;
+	*tv64 = valid_timeval32_to_timeval64 (tv32);
+	*len = sizeof (*tv64);
+      }
+    }
+
+  return r;
+}
+#endif
+
+int
+__getsockopt (int fd, int level, int optname, void *optval, socklen_t *len)
+{
+  int r = getsockopt_syscall (fd, level, optname, optval, len);
+
+#ifndef __ASSUME_TIME64_SYSCALLS
+  if (r == -1 && errno == ENOPROTOOPT)
+    r = getsockopt32 (fd, level, optname, optval, len);
+#endif
+
+ return r;
+}
 weak_alias (__getsockopt, getsockopt)
diff --git a/sysdeps/unix/sysv/linux/hppa/bits/socket-constants.h b/sysdeps/unix/sysv/linux/hppa/bits/socket-constants.h
index fda7f95d44..eca5fe045c 100644
--- a/sysdeps/unix/sysv/linux/hppa/bits/socket-constants.h
+++ b/sysdeps/unix/sysv/linux/hppa/bits/socket-constants.h
@@ -30,9 +30,20 @@ 
 #define SO_OOBINLINE 256
 #define SO_RCVBUF 4098
 #define SO_RCVLOWAT 4100
-#define SO_RCVTIMEO 4102
 #define SO_REUSEADDR 4
 #define SO_SNDBUF 4097
 #define SO_SNDLOWAT 4099
-#define SO_SNDTIMEO 4101
 #define SO_TYPE 4104
+
+#define SO_RCVTIMEO_OLD 4102
+#define SO_SNDTIMEO_OLD 4101
+#define SO_RCVTIMEO_NEW 16448
+#define SO_SNDTIMEO_NEW 16449
+#if (__TIMESIZE == 64 && __WORDSIZE == 32 \
+     && (!defined __SYSCALL_WORDSIZE || __SYSCALL_WORDSIZE == 32))
+# define SO_RCVTIMEO SO_RCVTIMEO_NEW
+# define SO_SNDTIMEO SO_SNDTIMEO_NEW
+#else
+# define SO_RCVTIMEO SO_RCVTIMEO_OLD
+# define SO_SNDTIMEO SO_SNDTIMEO_OLD
+#endif
diff --git a/sysdeps/unix/sysv/linux/mips/bits/socket-constants.h b/sysdeps/unix/sysv/linux/mips/bits/socket-constants.h
index daa47c6c7c..961fad21b8 100644
--- a/sysdeps/unix/sysv/linux/mips/bits/socket-constants.h
+++ b/sysdeps/unix/sysv/linux/mips/bits/socket-constants.h
@@ -20,6 +20,8 @@ 
 # error "Never include <bits/socket-constants.h> directly; use <sys/socket.h> instead."
 #endif
 
+#include <bits/timesize.h>
+
 #define SOL_SOCKET 65535
 #define SO_ACCEPTCONN 4105
 #define SO_BROADCAST 32
@@ -30,9 +32,20 @@ 
 #define SO_OOBINLINE 256
 #define SO_RCVBUF 4098
 #define SO_RCVLOWAT 4100
-#define SO_RCVTIMEO 4102
 #define SO_REUSEADDR 4
 #define SO_SNDBUF 4097
 #define SO_SNDLOWAT 4099
-#define SO_SNDTIMEO 4101
 #define SO_TYPE 4104
+
+#define SO_RCVTIMEO_OLD 4100
+#define SO_SNDTIMEO_OLD 4101
+#define SO_RCVTIMEO_NEW 66
+#define SO_SNDTIMEO_NEW 67
+#if (__TIMESIZE == 64 && __WORDSIZE == 32 \
+     && (!defined __SYSCALL_WORDSIZE || __SYSCALL_WORDSIZE == 32))
+# define SO_RCVTIMEO SO_RCVTIMEO_NEW
+# define SO_SNDTIMEO SO_SNDTIMEO_NEW
+#else
+# define SO_RCVTIMEO SO_RCVTIMEO_OLD
+# define SO_SNDTIMEO SO_SNDTIMEO_OLD
+#endif
diff --git a/sysdeps/unix/sysv/linux/powerpc/bits/socket-constants.h b/sysdeps/unix/sysv/linux/powerpc/bits/socket-constants.h
index 77fc8b207e..d0ec3cb4fc 100644
--- a/sysdeps/unix/sysv/linux/powerpc/bits/socket-constants.h
+++ b/sysdeps/unix/sysv/linux/powerpc/bits/socket-constants.h
@@ -20,6 +20,8 @@ 
 # error "Never include <bits/socket-constants.h> directly; use <sys/socket.h> instead."
 #endif
 
+#include <bits/timesize.h>
+
 #define SOL_SOCKET 1
 #define SO_ACCEPTCONN 30
 #define SO_BROADCAST 6
@@ -30,9 +32,20 @@ 
 #define SO_OOBINLINE 10
 #define SO_RCVBUF 8
 #define SO_RCVLOWAT 16
-#define SO_RCVTIMEO 18
 #define SO_REUSEADDR 2
 #define SO_SNDBUF 7
-#define SO_SNDLOWAT 17
-#define SO_SNDTIMEO 19
+#define SO_SNDTIMEO 67
 #define SO_TYPE 3
+
+#define SO_RCVTIMEO_OLD 18
+#define SO_SNDTIMEO_OLD 19
+#define SO_RCVTIMEO_NEW 66
+#define SO_SNDTIMEO_NEW 67
+#if (__TIMESIZE == 64 && __WORDSIZE == 32 \
+     && (!defined __SYSCALL_WORDSIZE || __SYSCALL_WORDSIZE == 32))
+# define SO_RCVTIMEO SO_RCVTIMEO_NEW
+# define SO_SNDTIMEO SO_SNDTIMEO_NEW
+#else
+# define SO_RCVTIMEO SO_RCVTIMEO_OLD
+# define SO_SNDTIMEO SO_SNDTIMEO_OLD
+#endif
diff --git a/sysdeps/unix/sysv/linux/setsockopt.c b/sysdeps/unix/sysv/linux/setsockopt.c
index 20c0868783..ebc32d788c 100644
--- a/sysdeps/unix/sysv/linux/setsockopt.c
+++ b/sysdeps/unix/sysv/linux/setsockopt.c
@@ -15,21 +15,81 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <errno.h>
-#include <signal.h>
 #include <sys/socket.h>
-
+/* The kernel header with SO_* constants is used as default for _GNU_SOURCE,
+   however the new constants that describe 64-bit time support were added
+   only on v5.1.  */
+#if !defined(SO_RCVTIMEO_NEW) || !defined(SO_RCVTIMEO_OLD)
+# include <bits/socket-constants.h>
+#endif
+#include <time.h>
+#include <sysdep.h>
 #include <socketcall.h>
-#include <kernel-features.h>
-#include <sys/syscall.h>
 
-int
-setsockopt (int fd, int level, int optname, const void *optval, socklen_t len)
+static int
+setsockopt_syscall (int fd, int level, int optname, const void *optval,
+		    socklen_t len)
 {
 #ifdef __ASSUME_SETSOCKOPT_SYSCALL
-  return INLINE_SYSCALL (setsockopt, 5, fd, level, optname, optval, len);
+  return INLINE_SYSCALL_CALL (setsockopt, fd, level, optname, optval, len);
 #else
   return SOCKETCALL (setsockopt, fd, level, optname, optval, len);
 #endif
 }
+
+#ifndef __ASSUME_TIME64_SYSCALLS
+static int
+setsockopt32 (int fd, int level, int optname, const void *optval,
+	      socklen_t len)
+{
+  int r = -1;
+
+  if (level != SOL_SOCKET)
+    return r;
+
+  switch (optname)
+    {
+    case SO_RCVTIMEO_NEW:
+    case SO_SNDTIMEO_NEW:
+      {
+        if (len < sizeof (struct __timeval64))
+	  {
+	    __set_errno (EINVAL);
+	    break;
+	  }
+
+	struct __timeval64 *tv64 = (struct __timeval64 *) optval;
+	if (! in_time_t_range (tv64->tv_sec))
+	  {
+	    __set_errno (EOVERFLOW);
+	    break;
+	  }
+
+	if (optname == SO_RCVTIMEO_NEW)
+	  optname = SO_RCVTIMEO_OLD;
+	if (optname == SO_SNDTIMEO_NEW)
+	  optname = SO_SNDTIMEO_OLD;
+
+	struct __timeval32 tv32 = valid_timeval64_to_timeval32 (*tv64);
+
+	r = setsockopt_syscall (fd, level, optname, &tv32, sizeof (tv32));
+      }
+    }
+
+  return r;
+}
+#endif
+
+int
+setsockopt (int fd, int level, int optname, const void *optval, socklen_t len)
+{
+  int r = setsockopt_syscall (fd, level, optname, optval, len);
+
+#ifndef __ASSUME_TIME64_SYSCALLS
+  if (r == -1 && errno == ENOPROTOOPT)
+    r = setsockopt32 (fd, level, optname, optval, len);
+#endif
+
+  return r;
+}
 weak_alias (setsockopt, __setsockopt)
diff --git a/sysdeps/unix/sysv/linux/sparc/bits/socket-constants.h b/sysdeps/unix/sysv/linux/sparc/bits/socket-constants.h
index 6ef575e521..d4bf8f79d6 100644
--- a/sysdeps/unix/sysv/linux/sparc/bits/socket-constants.h
+++ b/sysdeps/unix/sysv/linux/sparc/bits/socket-constants.h
@@ -20,6 +20,8 @@ 
 # error "Never include <bits/socket-constants.h> directly; use <sys/socket.h> instead."
 #endif
 
+#include <bits/timesize.h>
+
 #define SOL_SOCKET 65535
 #define SO_ACCEPTCONN 32768
 #define SO_BROADCAST 32
@@ -30,9 +32,20 @@ 
 #define SO_OOBINLINE 256
 #define SO_RCVBUF 4098
 #define SO_RCVLOWAT 2048
-#define SO_RCVTIMEO 8192
 #define SO_REUSEADDR 4
 #define SO_SNDBUF 4097
 #define SO_SNDLOWAT 4096
-#define SO_SNDTIMEO 16384
 #define SO_TYPE 4104
+
+#define SO_RCVTIMEO_OLD 8192
+#define SO_SNDTIMEO_OLD 16384
+#define SO_RCVTIMEO_NEW 68
+#define SO_SNDTIMEO_NEW 69
+#if (__TIMESIZE == 64 && __WORDSIZE == 32 \
+     && (!defined __SYSCALL_WORDSIZE || __SYSCALL_WORDSIZE == 32))
+# define SO_RCVTIMEO SO_RCVTIMEO_NEW
+# define SO_SNDTIMEO SO_SNDTIMEO_NEW
+#else
+# define SO_RCVTIMEO SO_RCVTIMEO_OLD
+# define SO_SNDTIMEO SO_SNDTIMEO_OLD
+#endif