1 /* Copyright (C) 1995-2021 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).  */
29 union 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
39 typedef union semun semctl_arg_t;
40 #else
41 # include <struct_kernel_semid64_ds.h>
42 
43 union 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.  */
55 union semun64
56 {
57   int val;
58   struct __semid64_ds *buf;
59   unsigned short int *array;
60   struct seminfo *__buf;
61 };
62 # endif
63 
64 static void
semid64_to_ksemid64(const struct __semid64_ds * semid64,struct kernel_semid64_ds * ksemid)65 semid64_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 
76 static void
ksemid64_to_semid64(const struct kernel_semid64_ds * ksemid,struct __semid64_ds * semid64)77 ksemid64_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 
88 static union ksemun64
semun64_to_ksemun64(int cmd,union semun64 semun64,struct kernel_semid64_ds * buf)89 semun64_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 
117 typedef union ksemun64 semctl_arg_t;
118 #endif
119 
120 static int
semctl_syscall(int semid,int semnum,int cmd,semctl_arg_t arg)121 semctl_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 
137 int
__semctl64(int semid,int semnum,int cmd,...)138 __semctl64 (int semid, int semnum, int cmd, ...)
139 {
140   union semun64 arg64 = { 0 };
141   va_list ap;
142 
143   /* Get the argument only if required.  */
144   switch (cmd)
145     {
146     case SETVAL:        /* arg.val */
147     case GETALL:        /* arg.array */
148     case SETALL:
149     case IPC_STAT:      /* arg.buf */
150     case IPC_SET:
151     case SEM_STAT:
152     case SEM_STAT_ANY:
153     case IPC_INFO:      /* arg.__buf */
154     case SEM_INFO:
155       va_start (ap, cmd);
156       arg64 = va_arg (ap, union semun64);
157       va_end (ap);
158       break;
159     case IPC_RMID:      /* arg ignored.  */
160     case GETNCNT:
161     case GETPID:
162     case GETVAL:
163     case GETZCNT:
164       break;
165     default:
166       __set_errno (EINVAL);
167       return -1;
168     }
169 
170 #if __IPC_TIME64
171   struct kernel_semid64_ds ksemid;
172   union ksemun64 ksemun = semun64_to_ksemun64 (cmd, arg64, &ksemid);
173 # ifdef __ASSUME_SYSVIPC_BROKEN_MODE_T
174   if (cmd == IPC_SET)
175     ksemid.sem_perm.mode *= 0x10000U;
176 # endif
177   union ksemun64 arg = ksemun;
178 #else
179   union semun arg = arg64;
180 #endif
181 
182   int ret = semctl_syscall (semid, semnum, cmd, arg);
183   if (ret < 0)
184     return ret;
185 
186   switch (cmd)
187     {
188     case IPC_STAT:
189     case SEM_STAT:
190     case SEM_STAT_ANY:
191 #ifdef __ASSUME_SYSVIPC_BROKEN_MODE_T
192       arg.buf->sem_perm.mode >>= 16;
193 #else
194       /* Old Linux kernel versions might not clear the mode padding.  */
195       if (sizeof ((struct semid_ds){0}.sem_perm.mode)
196 	  != sizeof (__kernel_mode_t))
197 	arg.buf->sem_perm.mode &= 0xFFFF;
198 #endif
199 
200 #if __IPC_TIME64
201       ksemid64_to_semid64 (arg.buf, arg64.buf);
202 #endif
203     }
204 
205   return ret;
206 }
207 #if __TIMESIZE != 64
libc_hidden_def(__semctl64)208 libc_hidden_def (__semctl64)
209 
210 
211 /* The 64-bit time_t semid_ds version might have a different layout and
212    internal field alignment.  */
213 
214 static void
215 semid_to_semid64 (struct __semid64_ds *ds64, const struct semid_ds *ds)
216 {
217   ds64->sem_perm  = ds->sem_perm;
218   ds64->sem_otime = ds->sem_otime
219 		    | ((__time64_t) ds->__sem_otime_high << 32);
220   ds64->sem_ctime = ds->sem_ctime
221 		    | ((__time64_t) ds->__sem_ctime_high << 32);
222   ds64->sem_nsems = ds->sem_nsems;
223 }
224 
225 static void
semid64_to_semid(struct semid_ds * ds,const struct __semid64_ds * ds64)226 semid64_to_semid (struct semid_ds *ds, const struct __semid64_ds *ds64)
227 {
228   ds->sem_perm         = ds64->sem_perm;
229   ds->sem_otime        = ds64->sem_otime;
230   ds->__sem_otime_high = 0;
231   ds->sem_ctime        = ds64->sem_ctime;
232   ds->__sem_ctime_high = 0;
233   ds->sem_nsems        = ds64->sem_nsems;
234 }
235 
236 static union semun64
semun_to_semun64(int cmd,union semun semun,struct __semid64_ds * semid64)237 semun_to_semun64 (int cmd, union semun semun, struct __semid64_ds *semid64)
238 {
239   union semun64 r = { 0 };
240   switch (cmd)
241     {
242     case SETVAL:
243       r.val = semun.val;
244       break;
245     case GETALL:
246     case SETALL:
247       r.array = semun.array;
248       break;
249     case SEM_STAT:
250     case SEM_STAT_ANY:
251     case IPC_STAT:
252     case IPC_SET:
253       r.buf = semid64;
254       semid_to_semid64 (r.buf, semun.buf);
255       break;
256     case IPC_INFO:
257     case SEM_INFO:
258       r.__buf = semun.__buf;
259       break;
260     }
261   return r;
262 }
263 
264 int
__semctl(int semid,int semnum,int cmd,...)265 __semctl (int semid, int semnum, int cmd, ...)
266 {
267   union semun arg = { 0 };
268 
269   va_list ap;
270 
271   /* Get the argument only if required.  */
272   switch (cmd)
273     {
274     case SETVAL:        /* arg.val */
275     case GETALL:        /* arg.array */
276     case SETALL:
277     case IPC_STAT:      /* arg.buf */
278     case IPC_SET:
279     case SEM_STAT:
280     case SEM_STAT_ANY:
281     case IPC_INFO:      /* arg.__buf */
282     case SEM_INFO:
283       va_start (ap, cmd);
284       arg = va_arg (ap, union semun);
285       va_end (ap);
286       break;
287     /* __semctl64 handles non-supported commands.  */
288     }
289 
290   struct __semid64_ds semid64;
291   union semun64 arg64 = semun_to_semun64 (cmd, arg, &semid64);
292 
293   int ret = __semctl64 (semid, semnum, cmd, arg64);
294   if (ret < 0)
295     return ret;
296 
297   switch (cmd)
298     {
299     case IPC_STAT:
300     case SEM_STAT:
301     case SEM_STAT_ANY:
302       semid64_to_semid (arg.buf, arg64.buf);
303     }
304 
305   return ret;
306 }
307 #endif
308 
309 #ifndef DEFAULT_VERSION
310 # ifndef __ASSUME_SYSVIPC_BROKEN_MODE_T
311 #  define DEFAULT_VERSION GLIBC_2_2
312 # else
313 #  define DEFAULT_VERSION GLIBC_2_31
314 # endif
315 #endif
316 versioned_symbol (libc, __semctl, semctl, DEFAULT_VERSION);
317 
318 #if defined __ASSUME_SYSVIPC_BROKEN_MODE_T \
319     && SHLIB_COMPAT (libc, GLIBC_2_2, GLIBC_2_31)
320 int
321 attribute_compat_text_section
__semctl_mode16(int semid,int semnum,int cmd,...)322 __semctl_mode16 (int semid, int semnum, int cmd, ...)
323 {
324   semctl_arg_t arg = { 0 };
325   va_list ap;
326 
327   /* Get the argument only if required.  */
328   switch (cmd)
329     {
330     case SETVAL:        /* arg.val */
331     case GETALL:        /* arg.array */
332     case SETALL:
333     case IPC_STAT:      /* arg.buf */
334     case IPC_SET:
335     case SEM_STAT:
336     case SEM_STAT_ANY:
337     case IPC_INFO:      /* arg.__buf */
338     case SEM_INFO:
339       va_start (ap, cmd);
340       arg = va_arg (ap, semctl_arg_t);
341       va_end (ap);
342       break;
343     }
344 
345   return semctl_syscall (semid, semnum, cmd, arg);
346 }
347 compat_symbol (libc, __semctl_mode16, semctl, GLIBC_2_2);
348 #endif
349 
350 #if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_2)
351 /* Since semctl use a variadic argument for semid_ds there is not need to
352    define and tie the compatibility symbol to the old 'union semun'
353    definition.  */
354 int
355 attribute_compat_text_section
__old_semctl(int semid,int semnum,int cmd,...)356 __old_semctl (int semid, int semnum, int cmd, ...)
357 {
358   union semun arg = { 0 };
359   va_list ap;
360 
361   /* Get the argument only if required.  */
362   switch (cmd)
363     {
364     case SETVAL:        /* arg.val */
365     case GETALL:        /* arg.array */
366     case SETALL:
367     case IPC_STAT:      /* arg.buf */
368     case IPC_SET:
369     case SEM_STAT:
370     case SEM_STAT_ANY:
371     case IPC_INFO:      /* arg.__buf */
372     case SEM_INFO:
373       va_start (ap, cmd);
374       arg = va_arg (ap, union semun);
375       va_end (ap);
376       break;
377     }
378 
379 #if defined __ASSUME_DIRECT_SYSVIPC_SYSCALLS \
380     && !defined __ASSUME_SYSVIPC_DEFAULT_IPC_64
381  /* For architectures that have wire-up semctl but also have __IPC_64 to a
382     value different than default (0x0) it means the compat symbol used the
383     __NR_ipc syscall.  */
384   return INLINE_SYSCALL_CALL (semctl, semid, semnum, cmd, arg.array);
385 # else
386   return INLINE_SYSCALL_CALL (ipc, IPCOP_semctl, semid, semnum, cmd,
387 			      SEMCTL_ARG_ADDRESS (arg));
388 # endif
389 }
390 compat_symbol (libc, __old_semctl, semctl, GLIBC_2_0);
391 #endif
392