1/* Copyright (C) 1995-2024 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <https://www.gnu.org/licenses/>. */
17
18#include <sys/sem.h>
19#include <stdarg.h>
20#include <ipc_priv.h>
21#include <sysdep.h>
22#include <shlib-compat.h>
23#include <linux/posix_types.h> /* For __kernel_mode_t. */
24
25/* The struct used to issue the syscall. For architectures that assume
26 64-bit time as default (!__ASSUME_TIME64_SYSCALLS) the syscall will
27 split the resulting 64-bit sem_{o,c}time in two fields (sem_{o,c}time
28 and __sem_{o,c}time_high). */
29union semun
30{
31 int val; /* value for SETVAL */
32 struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
33 unsigned short int *array; /* array for GETALL & SETALL */
34 struct seminfo *__buf; /* buffer for IPC_INFO */
35};
36
37#if __IPC_TIME64 == 0
38# define semun64 semun
39typedef union semun semctl_arg_t;
40#else
41# include <struct_kernel_semid64_ds.h>
42
43union ksemun64
44{
45 int val;
46 struct kernel_semid64_ds *buf;
47 unsigned short int *array;
48 struct seminfo *__buf;
49};
50
51# if __TIMESIZE == 64
52# define semun64 semun
53# else
54/* The struct used when __semctl64 is called. */
55union semun64
56{
57 int val;
58 struct __semid64_ds *buf;
59 unsigned short int *array;
60 struct seminfo *__buf;
61};
62# endif
63
64static void
65semid64_to_ksemid64 (const struct __semid64_ds *semid64,
66 struct kernel_semid64_ds *ksemid)
67{
68 ksemid->sem_perm = semid64->sem_perm;
69 ksemid->sem_otime = semid64->sem_otime;
70 ksemid->sem_otime_high = semid64->sem_otime >> 32;
71 ksemid->sem_ctime = semid64->sem_ctime;
72 ksemid->sem_ctime_high = semid64->sem_ctime >> 32;
73 ksemid->sem_nsems = semid64->sem_nsems;
74}
75
76static void
77ksemid64_to_semid64 (const struct kernel_semid64_ds *ksemid,
78 struct __semid64_ds *semid64)
79{
80 semid64->sem_perm = ksemid->sem_perm;
81 semid64->sem_otime = ksemid->sem_otime
82 | ((__time64_t) ksemid->sem_otime_high << 32);
83 semid64->sem_ctime = ksemid->sem_ctime
84 | ((__time64_t) ksemid->sem_ctime_high << 32);
85 semid64->sem_nsems = ksemid->sem_nsems;
86}
87
88static union ksemun64
89semun64_to_ksemun64 (int cmd, union semun64 semun64,
90 struct kernel_semid64_ds *buf)
91{
92 union ksemun64 r = { 0 };
93 switch (cmd)
94 {
95 case SETVAL:
96 r.val = semun64.val;
97 break;
98 case GETALL:
99 case SETALL:
100 r.array = semun64.array;
101 break;
102 case SEM_STAT:
103 case SEM_STAT_ANY:
104 case IPC_STAT:
105 case IPC_SET:
106 r.buf = buf;
107 semid64_to_ksemid64 (semun64.buf, r.buf);
108 break;
109 case IPC_INFO:
110 case SEM_INFO:
111 r.__buf = semun64.__buf;
112 break;
113 }
114 return r;
115}
116
117typedef union ksemun64 semctl_arg_t;
118#endif
119
120static int
121semctl_syscall (int semid, int semnum, int cmd, semctl_arg_t arg)
122{
123#ifdef __ASSUME_DIRECT_SYSVIPC_SYSCALLS
124 return INLINE_SYSCALL_CALL (semctl, semid, semnum, cmd | __IPC_64,
125 arg.array);
126#else
127 return INLINE_SYSCALL_CALL (ipc, IPCOP_semctl, semid, semnum, cmd | __IPC_64,
128 SEMCTL_ARG_ADDRESS (arg));
129#endif
130}
131
132/* POSIX states ipc_perm mode should have type of mode_t. */
133_Static_assert (sizeof ((struct semid_ds){0}.sem_perm.mode)
134 == sizeof (mode_t),
135 "sizeof (msqid_ds.msg_perm.mode) != sizeof (mode_t)");
136
137int
138__semctl64 (int semid, int semnum, int cmd, ...)
139{
140 union semun64 arg64 = { 0 };
141 va_list ap;
142
143 /* Some applications pass the __IPC_64 flag in cmd, to invoke
144 previously unsupported commands back when there was no EINVAL
145 error checking in glibc. Mask the flag for the switch statements
146 below. semctl_syscall adds back the __IPC_64 flag for the actual
147 system call. */
148 cmd &= ~__IPC_64;
149
150 /* Get the argument only if required. */
151 switch (cmd)
152 {
153 case SETVAL: /* arg.val */
154 case GETALL: /* arg.array */
155 case SETALL:
156 case IPC_STAT: /* arg.buf */
157 case IPC_SET:
158 case SEM_STAT:
159 case SEM_STAT_ANY:
160 case IPC_INFO: /* arg.__buf */
161 case SEM_INFO:
162 va_start (ap, cmd);
163 arg64 = va_arg (ap, union semun64);
164 va_end (ap);
165 break;
166 case IPC_RMID: /* arg ignored. */
167 case GETNCNT:
168 case GETPID:
169 case GETVAL:
170 case GETZCNT:
171 break;
172 default:
173 __set_errno (EINVAL);
174 return -1;
175 }
176
177#if __IPC_TIME64
178 struct kernel_semid64_ds ksemid;
179 union ksemun64 ksemun = semun64_to_ksemun64 (cmd, arg64, &ksemid);
180# ifdef __ASSUME_SYSVIPC_BROKEN_MODE_T
181 if (cmd == IPC_SET)
182 ksemid.sem_perm.mode *= 0x10000U;
183# endif
184 union ksemun64 arg = ksemun;
185#else
186 union semun arg = arg64;
187#endif
188
189 int ret = semctl_syscall (semid, semnum, cmd, arg);
190 if (ret < 0)
191 return ret;
192
193 switch (cmd)
194 {
195 case IPC_STAT:
196 case SEM_STAT:
197 case SEM_STAT_ANY:
198#ifdef __ASSUME_SYSVIPC_BROKEN_MODE_T
199 arg.buf->sem_perm.mode >>= 16;
200#else
201 /* Old Linux kernel versions might not clear the mode padding. */
202 if (sizeof ((struct semid_ds){0}.sem_perm.mode)
203 != sizeof (__kernel_mode_t))
204 arg.buf->sem_perm.mode &= 0xFFFF;
205#endif
206
207#if __IPC_TIME64
208 ksemid64_to_semid64 (arg.buf, arg64.buf);
209#endif
210 }
211
212 return ret;
213}
214#if __TIMESIZE != 64
215libc_hidden_def (__semctl64)
216
217
218/* The 64-bit time_t semid_ds version might have a different layout and
219 internal field alignment. */
220
221static void
222semid_to_semid64 (struct __semid64_ds *ds64, const struct semid_ds *ds)
223{
224 ds64->sem_perm = ds->sem_perm;
225 ds64->sem_otime = ds->sem_otime
226 | ((__time64_t) ds->__sem_otime_high << 32);
227 ds64->sem_ctime = ds->sem_ctime
228 | ((__time64_t) ds->__sem_ctime_high << 32);
229 ds64->sem_nsems = ds->sem_nsems;
230}
231
232static void
233semid64_to_semid (struct semid_ds *ds, const struct __semid64_ds *ds64)
234{
235 ds->sem_perm = ds64->sem_perm;
236 ds->sem_otime = ds64->sem_otime;
237 ds->__sem_otime_high = 0;
238 ds->sem_ctime = ds64->sem_ctime;
239 ds->__sem_ctime_high = 0;
240 ds->sem_nsems = ds64->sem_nsems;
241}
242
243static union semun64
244semun_to_semun64 (int cmd, union semun semun, struct __semid64_ds *semid64)
245{
246 union semun64 r = { 0 };
247 switch (cmd)
248 {
249 case SETVAL:
250 r.val = semun.val;
251 break;
252 case GETALL:
253 case SETALL:
254 r.array = semun.array;
255 break;
256 case SEM_STAT:
257 case SEM_STAT_ANY:
258 case IPC_STAT:
259 case IPC_SET:
260 r.buf = semid64;
261 semid_to_semid64 (r.buf, semun.buf);
262 break;
263 case IPC_INFO:
264 case SEM_INFO:
265 r.__buf = semun.__buf;
266 break;
267 }
268 return r;
269}
270
271int
272__semctl (int semid, int semnum, int cmd, ...)
273{
274 union semun arg = { 0 };
275
276 va_list ap;
277
278 /* Get the argument only if required. */
279 switch (cmd)
280 {
281 case SETVAL: /* arg.val */
282 case GETALL: /* arg.array */
283 case SETALL:
284 case IPC_STAT: /* arg.buf */
285 case IPC_SET:
286 case SEM_STAT:
287 case SEM_STAT_ANY:
288 case IPC_INFO: /* arg.__buf */
289 case SEM_INFO:
290 va_start (ap, cmd);
291 arg = va_arg (ap, union semun);
292 va_end (ap);
293 break;
294 /* __semctl64 handles non-supported commands. */
295 }
296
297 struct __semid64_ds semid64;
298 union semun64 arg64 = semun_to_semun64 (cmd, arg, &semid64);
299
300 int ret = __semctl64 (semid, semnum, cmd, arg64);
301 if (ret < 0)
302 return ret;
303
304 switch (cmd)
305 {
306 case IPC_STAT:
307 case SEM_STAT:
308 case SEM_STAT_ANY:
309 semid64_to_semid (arg.buf, arg64.buf);
310 }
311
312 return ret;
313}
314#endif
315
316#ifndef DEFAULT_VERSION
317# ifndef __ASSUME_SYSVIPC_BROKEN_MODE_T
318# define DEFAULT_VERSION GLIBC_2_2
319# else
320# define DEFAULT_VERSION GLIBC_2_31
321# endif
322#endif
323versioned_symbol (libc, __semctl, semctl, DEFAULT_VERSION);
324
325#if defined __ASSUME_SYSVIPC_BROKEN_MODE_T \
326 && SHLIB_COMPAT (libc, GLIBC_2_2, GLIBC_2_31)
327int
328attribute_compat_text_section
329__semctl_mode16 (int semid, int semnum, int cmd, ...)
330{
331 semctl_arg_t arg = { 0 };
332 va_list ap;
333
334 /* Get the argument only if required. */
335 switch (cmd)
336 {
337 case SETVAL: /* arg.val */
338 case GETALL: /* arg.array */
339 case SETALL:
340 case IPC_STAT: /* arg.buf */
341 case IPC_SET:
342 case SEM_STAT:
343 case SEM_STAT_ANY:
344 case IPC_INFO: /* arg.__buf */
345 case SEM_INFO:
346 va_start (ap, cmd);
347 arg = va_arg (ap, semctl_arg_t);
348 va_end (ap);
349 break;
350 }
351
352 return semctl_syscall (semid, semnum, cmd, arg);
353}
354compat_symbol (libc, __semctl_mode16, semctl, GLIBC_2_2);
355#endif
356
357#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_2)
358/* Since semctl use a variadic argument for semid_ds there is not need to
359 define and tie the compatibility symbol to the old 'union semun'
360 definition. */
361int
362attribute_compat_text_section
363__old_semctl (int semid, int semnum, int cmd, ...)
364{
365 union semun arg = { 0 };
366 va_list ap;
367
368 /* Get the argument only if required. */
369 switch (cmd)
370 {
371 case SETVAL: /* arg.val */
372 case GETALL: /* arg.array */
373 case SETALL:
374 case IPC_STAT: /* arg.buf */
375 case IPC_SET:
376 case SEM_STAT:
377 case SEM_STAT_ANY:
378 case IPC_INFO: /* arg.__buf */
379 case SEM_INFO:
380 va_start (ap, cmd);
381 arg = va_arg (ap, union semun);
382 va_end (ap);
383 break;
384 }
385
386#if defined __ASSUME_DIRECT_SYSVIPC_SYSCALLS \
387 && !defined __ASSUME_SYSVIPC_DEFAULT_IPC_64
388 /* For architectures that have wire-up semctl but also have __IPC_64 to a
389 value different than default (0x0) it means the compat symbol used the
390 __NR_ipc syscall. */
391 return INLINE_SYSCALL_CALL (semctl, semid, semnum, cmd, arg.array);
392# else
393 return INLINE_SYSCALL_CALL (ipc, IPCOP_semctl, semid, semnum, cmd,
394 SEMCTL_ARG_ADDRESS (arg));
395# endif
396}
397compat_symbol (libc, __old_semctl, semctl, GLIBC_2_0);
398#endif
399

source code of glibc/sysdeps/unix/sysv/linux/semctl.c