diff mbox series

[v9,2/2] sysv: linux: Pass 64-bit version of semctl syscall

Message ID 20200602204105.43866-3-alistair.francis@wdc.com
State Not Applicable
Headers show
Series Support semctl_syscall() for __TIMESIZE==64 | expand

Commit Message

Alistair Francis June 2, 2020, 8:41 p.m. UTC
The semctl_syscall() function passes a union semun to the kernel. The
union includes struct semid_ds as a member. On 32-bit architectures the
Linux kernel provides a *_high version of the 32-bit sem_otime and
sem_ctime values. These can be combined to get a 64-bit version of the
time.

This patch adjusts the struct semid_ds to support the *_high versions
of sem_otime and sem_ctime. For 32-bit systems with a 64-bit time_t
this can be used to get a 64-bit time from the two 32-bit values.

Due to alignment differences between 64-bit and 32-bit variables we
also need to set nsems to ensure it's correct.
---
 sysdeps/unix/sysv/linux/ipc_priv.h |  7 +++++
 sysdeps/unix/sysv/linux/semctl.c   | 50 +++++++++++++++++++++++++++---
 2 files changed, 53 insertions(+), 4 deletions(-)
diff mbox series

Patch

diff --git a/sysdeps/unix/sysv/linux/ipc_priv.h b/sysdeps/unix/sysv/linux/ipc_priv.h
index 15a6e683a4..ff20323337 100644
--- a/sysdeps/unix/sysv/linux/ipc_priv.h
+++ b/sysdeps/unix/sysv/linux/ipc_priv.h
@@ -43,6 +43,13 @@  struct __old_ipc_perm
   unsigned short int __seq;		/* Sequence number.  */
 };
 
+#if (__WORDSIZE == 32 && __TIMESIZE == 64 \
+     && (!defined __SYSCALL_WORDSIZE || __SYSCALL_WORDSIZE == 32))
+# define __IPC_TIME64 1
+#else
+# define __IPC_TIME64 0
+#endif
+
 #define SEMCTL_ARG_ADDRESS(__arg) &__arg.array
 
 #define MSGRCV_ARGS(__msgp, __msgtyp) \
diff --git a/sysdeps/unix/sysv/linux/semctl.c b/sysdeps/unix/sysv/linux/semctl.c
index 30571af49f..3ed2af89d1 100644
--- a/sysdeps/unix/sysv/linux/semctl.c
+++ b/sysdeps/unix/sysv/linux/semctl.c
@@ -22,6 +22,7 @@ 
 #include <sysdep.h>
 #include <shlib-compat.h>
 #include <errno.h>
+#include <struct__semid_ds32.h>
 #include <linux/posix_types.h>  /* For __kernel_mode_t.  */
 
 /* Define a `union semun' suitable for Linux here.  */
@@ -44,13 +45,54 @@  union semun
 static int
 semctl_syscall (int semid, int semnum, int cmd, union semun arg)
 {
+  int ret;
+#if __IPC_TIME64
+  /* A temporary buffer is used to avoid both an issue where the export
+     semid_ds might not follow the kernel's expected layout (due
+     to {o,c}time{_high} alignment in 64-bit time case) and the issue where
+     some kernel versions might not clear the high bits when returning
+     then {o,c}time{_high} information.  */
+  struct __semid_ds32 tmp;
+  struct semid_ds *orig;
+  bool restore = false;
+  if (cmd == IPC_STAT || cmd == SEM_STAT || cmd == SEM_STAT_ANY)
+    {
+      tmp = (struct __semid_ds32) {
+        .sem_perm  = arg.buf->sem_perm,
+        .sem_otime = arg.buf->sem_otime,
+        .sem_otime_high = arg.buf->sem_otime >> 32,
+        .sem_ctime = arg.buf->sem_ctime,
+        .sem_ctime_high = arg.buf->sem_ctime >> 32,
+        .sem_nsems = arg.buf->sem_nsems,
+      };
+      orig = arg.buf;
+      arg.buf = (struct semid_ds*) &tmp;
+      restore = true;
+    }
+#endif
+
 #ifdef __ASSUME_DIRECT_SYSVIPC_SYSCALLS
-  return INLINE_SYSCALL_CALL (semctl, semid, semnum, cmd | __IPC_64,
-			      arg.array);
+  ret = INLINE_SYSCALL_CALL (semctl, semid, semnum, cmd | __IPC_64,
+                             arg.array);
 #else
-  return INLINE_SYSCALL_CALL (ipc, IPCOP_semctl, semid, semnum, cmd | __IPC_64,
-			      SEMCTL_ARG_ADDRESS (arg));
+  ret = INLINE_SYSCALL_CALL (ipc, IPCOP_semctl, semid, semnum, cmd | __IPC_64,
+                             SEMCTL_ARG_ADDRESS (arg));
 #endif
+
+#if __IPC_TIME64
+  if (ret >= 0 && restore)
+    {
+      arg.buf = orig;
+      arg.buf->sem_perm  = tmp.sem_perm;
+      arg.buf->sem_otime = tmp.sem_otime
+                           | ((__time64_t) tmp.sem_otime_high << 32);
+      arg.buf->sem_ctime = tmp.sem_ctime
+                           | ((__time64_t) tmp.sem_ctime_high << 32);
+      arg.buf->sem_nsems = tmp.sem_nsems;
+    }
+#endif
+
+    return ret;
 }
 
 int