Print this page
7127 remove -Wno-missing-braces from Makefile.uts
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/uts/common/syscall/sem.c
+++ new/usr/src/uts/common/syscall/sem.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21 /*
22 22 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
23 23 * Use is subject to license terms.
24 24 */
25 25
26 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
27 27 /* All Rights Reserved */
28 28
29 29 /*
30 30 * Inter-Process Communication Semaphore Facility.
31 31 *
32 32 * See os/ipc.c for a description of common IPC functionality.
33 33 *
34 34 * Resource controls
35 35 * -----------------
36 36 *
37 37 * Control: zone.max-sem-ids (rc_zone_semmni)
38 38 * Description: Maximum number of semaphore ids allowed a zone.
39 39 *
40 40 * When semget() is used to allocate a semaphore set, one id is
41 41 * allocated. If the id allocation doesn't succeed, semget() fails
42 42 * and errno is set to ENOSPC. Upon successful semctl(, IPC_RMID)
43 43 * the id is deallocated.
44 44 *
45 45 * Control: project.max-sem-ids (rc_project_semmni)
46 46 * Description: Maximum number of semaphore ids allowed a project.
47 47 *
48 48 * When semget() is used to allocate a semaphore set, one id is
49 49 * allocated. If the id allocation doesn't succeed, semget() fails
50 50 * and errno is set to ENOSPC. Upon successful semctl(, IPC_RMID)
51 51 * the id is deallocated.
52 52 *
53 53 * Control: process.max-sem-nsems (rc_process_semmsl)
54 54 * Description: Maximum number of semaphores allowed per semaphore set.
55 55 *
56 56 * When semget() is used to allocate a semaphore set, the size of the
57 57 * set is compared with this limit. If the number of semaphores
58 58 * exceeds the limit, semget() fails and errno is set to EINVAL.
59 59 *
60 60 * Control: process.max-sem-ops (rc_process_semopm)
61 61 * Description: Maximum number of semaphore operations allowed per
62 62 * semop call.
63 63 *
64 64 * When semget() successfully allocates a semaphore set, the minimum
65 65 * enforced value of this limit is used to initialize the
66 66 * "system-imposed maximum" number of operations a semop() call for
67 67 * this set can perform.
68 68 *
69 69 * Undo structures
70 70 * ---------------
71 71 *
72 72 * Removing the undo structure tunables involved a serious redesign of
73 73 * how they were implemented. There is now one undo structure for
74 74 * every process/semaphore array combination (lazily allocated, of
75 75 * course), and each is equal in size to the semaphore it corresponds
76 76 * to. To avoid scalability and performance problems, the undo
77 77 * structures are stored in two places: a per-process AVL tree sorted
78 78 * by ksemid pointer (p_semacct, protected by p_lock) and an unsorted
79 79 * per-semaphore linked list (sem_undos, protected by the semaphore's
80 80 * ID lock). The former is used by semop, where a lookup is performed
81 81 * once and cached if SEM_UNDO is specified for any of the operations,
82 82 * and at process exit where the undoable operations are rolled back.
83 83 * The latter is used when removing the semaphore, so the undo
84 84 * structures can be removed from the appropriate processes' trees.
85 85 *
86 86 * The undo structure itself contains pointers to the ksemid and proc
87 87 * to which it corresponds, a list node, an AVL node, and an array of
88 88 * adjust-on-exit (AOE) values. When an undo structure is allocated it
89 89 * is immediately added to both the process's tree and the semaphore's
90 90 * list. Lastly, the reference count on the semaphore is increased.
91 91 *
92 92 * Avoiding a lock ordering violation between p_lock and the ID lock,
93 93 * wont to occur when there is a race between a process exiting and the
94 94 * removal of a semaphore, mandates the delicate dance that exists
95 95 * between semexit and sem_rmid.
96 96 *
97 97 * sem_rmid, holding the ID lock, iterates through all undo structures
98 98 * and for each takes the appropriate process's p_lock and checks to
99 99 * see if p_semacct is NULL. If it is, it skips that undo structure
100 100 * and continues to the next. Otherwise, it removes the undo structure
101 101 * from both the AVL tree and the semaphore's list, and releases the
102 102 * hold that the undo structure had on the semaphore.
103 103 *
104 104 * The important other half of this is semexit, which will immediately
105 105 * take p_lock, obtain the AVL pointer, clear p_semacct, and drop
106 106 * p_lock. From this point on it is semexit's responsibility to clean
107 107 * up all undo structures found in the tree -- a coexecuting sem_rmid
108 108 * will see the NULL p_semacct and skip that undo structure. It walks
109 109 * the AVL tree (using avl_destroy_nodes) and for each undo structure
110 110 * takes the appropriate semaphore's ID lock (always legal since the
111 111 * undo structure has a hold on the semaphore), updates all semaphores
112 112 * with non-zero AOE values, and removes the structure from the
113 113 * semaphore's list. It then drops the structure's reference on the
114 114 * semaphore, drops the ID lock, and frees the undo structure.
115 115 */
116 116
117 117 #include <sys/types.h>
118 118 #include <sys/t_lock.h>
119 119 #include <sys/param.h>
120 120 #include <sys/systm.h>
121 121 #include <sys/sysmacros.h>
122 122 #include <sys/cred.h>
123 123 #include <sys/vmem.h>
124 124 #include <sys/kmem.h>
125 125 #include <sys/errno.h>
126 126 #include <sys/time.h>
127 127 #include <sys/ipc.h>
128 128 #include <sys/ipc_impl.h>
129 129 #include <sys/sem.h>
130 130 #include <sys/sem_impl.h>
131 131 #include <sys/user.h>
132 132 #include <sys/proc.h>
133 133 #include <sys/cpuvar.h>
134 134 #include <sys/debug.h>
135 135 #include <sys/var.h>
136 136 #include <sys/cmn_err.h>
137 137 #include <sys/modctl.h>
138 138 #include <sys/syscall.h>
139 139 #include <sys/avl.h>
140 140 #include <sys/list.h>
141 141 #include <sys/zone.h>
142 142
143 143 #include <c2/audit.h>
144 144
145 145 extern rctl_hndl_t rc_zone_semmni;
146 146 extern rctl_hndl_t rc_project_semmni;
147 147 extern rctl_hndl_t rc_process_semmsl;
148 148 extern rctl_hndl_t rc_process_semopm;
149 149 static ipc_service_t *sem_svc;
150 150 static zone_key_t sem_zone_key;
151 151
152 152 /*
153 153 * The following tunables are obsolete. Though for compatibility we
154 154 * still read and interpret seminfo_semmsl, seminfo_semopm and
155 155 * seminfo_semmni (see os/project.c and os/rctl_proc.c), the preferred
156 156 * mechanism for administrating the IPC Semaphore facility is through
157 157 * the resource controls described at the top of this file.
158 158 */
159 159 int seminfo_semaem = 16384; /* (obsolete) */
160 160 int seminfo_semmap = 10; /* (obsolete) */
161 161 int seminfo_semmni = 10; /* (obsolete) */
162 162 int seminfo_semmns = 60; /* (obsolete) */
163 163 int seminfo_semmnu = 30; /* (obsolete) */
164 164 int seminfo_semmsl = 25; /* (obsolete) */
165 165 int seminfo_semopm = 10; /* (obsolete) */
166 166 int seminfo_semume = 10; /* (obsolete) */
167 167 int seminfo_semusz = 96; /* (obsolete) */
168 168 int seminfo_semvmx = 32767; /* (obsolete) */
169 169
170 170 #define SEM_MAXUCOPS 4096 /* max # of unchecked ops per semop call */
171 171 #define SEM_UNDOSZ(n) (sizeof (struct sem_undo) + (n - 1) * sizeof (int))
172 172
173 173 static int semsys(int opcode, uintptr_t a0, uintptr_t a1,
174 174 uintptr_t a2, uintptr_t a3);
175 175 static void sem_dtor(kipc_perm_t *);
176 176 static void sem_rmid(kipc_perm_t *);
177 177 static void sem_remove_zone(zoneid_t, void *);
178 178
179 179 static struct sysent ipcsem_sysent = {
180 180 5,
181 181 SE_NOUNLOAD | SE_ARGC | SE_32RVAL1,
182 182 semsys
183 183 };
184 184
185 185 /*
186 186 * Module linkage information for the kernel.
187 187 */
188 188 static struct modlsys modlsys = {
189 189 &mod_syscallops, "System V semaphore facility", &ipcsem_sysent
↓ open down ↓ |
189 lines elided |
↑ open up ↑ |
190 190 };
191 191
192 192 #ifdef _SYSCALL32_IMPL
193 193 static struct modlsys modlsys32 = {
194 194 &mod_syscallops32, "32-bit System V semaphore facility", &ipcsem_sysent
195 195 };
196 196 #endif
197 197
198 198 static struct modlinkage modlinkage = {
199 199 MODREV_1,
200 - &modlsys,
200 + { &modlsys,
201 201 #ifdef _SYSCALL32_IMPL
202 - &modlsys32,
202 + &modlsys32,
203 203 #endif
204 - NULL
204 + NULL
205 + }
205 206 };
206 207
207 208
208 209 int
209 210 _init(void)
210 211 {
211 212 int result;
212 213
213 214 sem_svc = ipcs_create("semids", rc_project_semmni, rc_zone_semmni,
214 215 sizeof (ksemid_t), sem_dtor, sem_rmid, AT_IPC_SEM,
215 216 offsetof(ipc_rqty_t, ipcq_semmni));
216 217 zone_key_create(&sem_zone_key, NULL, sem_remove_zone, NULL);
217 218
218 219 if ((result = mod_install(&modlinkage)) == 0)
219 220 return (0);
220 221
221 222 (void) zone_key_delete(sem_zone_key);
222 223 ipcs_destroy(sem_svc);
223 224
224 225 return (result);
225 226 }
226 227
227 228 int
228 229 _fini(void)
229 230 {
230 231 return (EBUSY);
231 232 }
232 233
233 234 int
234 235 _info(struct modinfo *modinfop)
235 236 {
236 237 return (mod_info(&modlinkage, modinfop));
237 238 }
238 239
239 240 static void
240 241 sem_dtor(kipc_perm_t *perm)
241 242 {
242 243 ksemid_t *sp = (ksemid_t *)perm;
243 244
244 245 kmem_free(sp->sem_base,
245 246 P2ROUNDUP(sp->sem_nsems * sizeof (struct sem), 64));
246 247 list_destroy(&sp->sem_undos);
247 248 }
248 249
249 250 /*
250 251 * sem_undo_add - Create or update adjust on exit entry.
251 252 */
252 253 static int
253 254 sem_undo_add(short val, ushort_t num, struct sem_undo *undo)
254 255 {
255 256 int newval = undo->un_aoe[num] - val;
256 257
257 258 if (newval > USHRT_MAX || newval < -USHRT_MAX)
258 259 return (ERANGE);
259 260 undo->un_aoe[num] = newval;
260 261
261 262 return (0);
262 263 }
263 264
264 265 /*
265 266 * sem_undo_clear - clears all undo entries for specified semaphores
266 267 *
267 268 * Used when semaphores are reset by SETVAL or SETALL.
268 269 */
269 270 static void
270 271 sem_undo_clear(ksemid_t *sp, ushort_t low, ushort_t high)
271 272 {
272 273 struct sem_undo *undo;
273 274 int i;
274 275
275 276 ASSERT(low <= high);
276 277 ASSERT(high < sp->sem_nsems);
277 278
278 279 for (undo = list_head(&sp->sem_undos); undo;
279 280 undo = list_next(&sp->sem_undos, undo))
280 281 for (i = low; i <= high; i++)
281 282 undo->un_aoe[i] = 0;
282 283 }
283 284
284 285 /*
285 286 * sem_rollback - roll back work done so far if unable to complete operation
286 287 */
287 288 static void
288 289 sem_rollback(ksemid_t *sp, struct sembuf *op, int n, struct sem_undo *undo)
289 290 {
290 291 struct sem *semp; /* semaphore ptr */
291 292
292 293 for (op += n - 1; n--; op--) {
293 294 if (op->sem_op == 0)
294 295 continue;
295 296 semp = &sp->sem_base[op->sem_num];
296 297 semp->semval -= op->sem_op;
297 298 if (op->sem_flg & SEM_UNDO) {
298 299 ASSERT(undo != NULL);
299 300 (void) sem_undo_add(-op->sem_op, op->sem_num, undo);
300 301 }
301 302 }
302 303 }
303 304
304 305 static void
305 306 sem_rmid(kipc_perm_t *perm)
306 307 {
307 308 ksemid_t *sp = (ksemid_t *)perm;
308 309 struct sem *semp;
309 310 struct sem_undo *undo;
310 311 size_t size = SEM_UNDOSZ(sp->sem_nsems);
311 312 int i;
312 313
313 314 /*LINTED*/
314 315 while (undo = list_head(&sp->sem_undos)) {
315 316 list_remove(&sp->sem_undos, undo);
316 317 mutex_enter(&undo->un_proc->p_lock);
317 318 if (undo->un_proc->p_semacct == NULL) {
318 319 mutex_exit(&undo->un_proc->p_lock);
319 320 continue;
320 321 }
321 322 avl_remove(undo->un_proc->p_semacct, undo);
322 323 mutex_exit(&undo->un_proc->p_lock);
323 324 kmem_free(undo, size);
324 325 ipc_rele_locked(sem_svc, (kipc_perm_t *)sp);
325 326 }
326 327
327 328 for (i = 0; i < sp->sem_nsems; i++) {
328 329 semp = &sp->sem_base[i];
329 330 semp->semval = semp->sempid = 0;
330 331 if (semp->semncnt) {
331 332 cv_broadcast(&semp->semncnt_cv);
332 333 semp->semncnt = 0;
333 334 }
334 335 if (semp->semzcnt) {
335 336 cv_broadcast(&semp->semzcnt_cv);
336 337 semp->semzcnt = 0;
337 338 }
338 339 }
339 340 }
340 341
341 342 /*
342 343 * semctl - Semctl system call.
343 344 */
344 345 static int
345 346 semctl(int semid, uint_t semnum, int cmd, uintptr_t arg)
346 347 {
347 348 ksemid_t *sp; /* ptr to semaphore header */
348 349 struct sem *p; /* ptr to semaphore */
349 350 unsigned int i; /* loop control */
350 351 ushort_t *vals, *vp;
351 352 size_t vsize = 0;
352 353 int error = 0;
353 354 int retval = 0;
354 355 struct cred *cr;
355 356 kmutex_t *lock;
356 357 model_t mdl = get_udatamodel();
357 358 STRUCT_DECL(semid_ds, sid);
358 359 struct semid_ds64 ds64;
359 360
360 361 STRUCT_INIT(sid, mdl);
361 362 cr = CRED();
362 363
363 364 /*
364 365 * Perform pre- or non-lookup actions (e.g. copyins, RMID).
365 366 */
366 367 switch (cmd) {
367 368 case IPC_SET:
368 369 if (copyin((void *)arg, STRUCT_BUF(sid), STRUCT_SIZE(sid)))
369 370 return (set_errno(EFAULT));
370 371 break;
371 372
372 373 case IPC_SET64:
373 374 if (copyin((void *)arg, &ds64, sizeof (struct semid_ds64)))
374 375 return (set_errno(EFAULT));
375 376 break;
376 377
377 378 case SETALL:
378 379 if ((lock = ipc_lookup(sem_svc, semid,
379 380 (kipc_perm_t **)&sp)) == NULL)
380 381 return (set_errno(EINVAL));
381 382 vsize = sp->sem_nsems * sizeof (*vals);
382 383 mutex_exit(lock);
383 384
384 385 /* allocate space to hold all semaphore values */
385 386 vals = kmem_alloc(vsize, KM_SLEEP);
386 387
387 388 if (copyin((void *)arg, vals, vsize)) {
388 389 kmem_free(vals, vsize);
389 390 return (set_errno(EFAULT));
390 391 }
391 392 break;
392 393
393 394 case IPC_RMID:
394 395 if (error = ipc_rmid(sem_svc, semid, cr))
395 396 return (set_errno(error));
396 397 return (0);
397 398 }
398 399
399 400 if ((lock = ipc_lookup(sem_svc, semid, (kipc_perm_t **)&sp)) == NULL) {
400 401 if (vsize != 0)
401 402 kmem_free(vals, vsize);
402 403 return (set_errno(EINVAL));
403 404 }
404 405 switch (cmd) {
405 406 /* Set ownership and permissions. */
406 407 case IPC_SET:
407 408
408 409 if (error = ipcperm_set(sem_svc, cr, &sp->sem_perm,
409 410 &STRUCT_BUF(sid)->sem_perm, mdl)) {
410 411 mutex_exit(lock);
411 412 return (set_errno(error));
412 413 }
413 414 sp->sem_ctime = gethrestime_sec();
414 415 mutex_exit(lock);
415 416 return (0);
416 417
417 418 /* Get semaphore data structure. */
418 419 case IPC_STAT:
419 420
420 421 if (error = ipcperm_access(&sp->sem_perm, SEM_R, cr)) {
421 422 mutex_exit(lock);
422 423 return (set_errno(error));
423 424 }
424 425
425 426 ipcperm_stat(&STRUCT_BUF(sid)->sem_perm, &sp->sem_perm, mdl);
426 427 STRUCT_FSETP(sid, sem_base, NULL); /* kernel addr */
427 428 STRUCT_FSET(sid, sem_nsems, sp->sem_nsems);
428 429 STRUCT_FSET(sid, sem_otime, sp->sem_otime);
429 430 STRUCT_FSET(sid, sem_ctime, sp->sem_ctime);
430 431 STRUCT_FSET(sid, sem_binary, sp->sem_binary);
431 432 mutex_exit(lock);
432 433
433 434 if (copyout(STRUCT_BUF(sid), (void *)arg, STRUCT_SIZE(sid)))
434 435 return (set_errno(EFAULT));
435 436 return (0);
436 437
437 438 case IPC_SET64:
438 439
439 440 if (error = ipcperm_set64(sem_svc, cr, &sp->sem_perm,
440 441 &ds64.semx_perm)) {
441 442 mutex_exit(lock);
442 443 return (set_errno(error));
443 444 }
444 445 sp->sem_ctime = gethrestime_sec();
445 446 mutex_exit(lock);
446 447 return (0);
447 448
448 449 case IPC_STAT64:
449 450
450 451 ipcperm_stat64(&ds64.semx_perm, &sp->sem_perm);
451 452 ds64.semx_nsems = sp->sem_nsems;
452 453 ds64.semx_otime = sp->sem_otime;
453 454 ds64.semx_ctime = sp->sem_ctime;
454 455
455 456 mutex_exit(lock);
456 457 if (copyout(&ds64, (void *)arg, sizeof (struct semid_ds64)))
457 458 return (set_errno(EFAULT));
458 459
459 460 return (0);
460 461
461 462 /* Get # of processes sleeping for greater semval. */
462 463 case GETNCNT:
463 464 if (error = ipcperm_access(&sp->sem_perm, SEM_R, cr)) {
464 465 mutex_exit(lock);
465 466 return (set_errno(error));
466 467 }
467 468 if (semnum >= sp->sem_nsems) {
468 469 mutex_exit(lock);
469 470 return (set_errno(EINVAL));
470 471 }
471 472 retval = sp->sem_base[semnum].semncnt;
472 473 mutex_exit(lock);
473 474 return (retval);
474 475
475 476 /* Get pid of last process to operate on semaphore. */
476 477 case GETPID:
477 478 if (error = ipcperm_access(&sp->sem_perm, SEM_R, cr)) {
478 479 mutex_exit(lock);
479 480 return (set_errno(error));
480 481 }
481 482 if (semnum >= sp->sem_nsems) {
482 483 mutex_exit(lock);
483 484 return (set_errno(EINVAL));
484 485 }
485 486 retval = sp->sem_base[semnum].sempid;
486 487 mutex_exit(lock);
487 488 return (retval);
488 489
489 490 /* Get semval of one semaphore. */
490 491 case GETVAL:
491 492 if (error = ipcperm_access(&sp->sem_perm, SEM_R, cr)) {
492 493 mutex_exit(lock);
493 494 return (set_errno(error));
494 495 }
495 496 if (semnum >= sp->sem_nsems) {
496 497 mutex_exit(lock);
497 498 return (set_errno(EINVAL));
498 499 }
499 500 retval = sp->sem_base[semnum].semval;
500 501 mutex_exit(lock);
501 502 return (retval);
502 503
503 504 /* Get all semvals in set. */
504 505 case GETALL:
505 506 if (error = ipcperm_access(&sp->sem_perm, SEM_R, cr)) {
506 507 mutex_exit(lock);
507 508 return (set_errno(error));
508 509 }
509 510
510 511 /* allocate space to hold all semaphore values */
511 512 vsize = sp->sem_nsems * sizeof (*vals);
512 513 vals = vp = kmem_alloc(vsize, KM_SLEEP);
513 514
514 515 for (i = sp->sem_nsems, p = sp->sem_base; i--; p++, vp++)
515 516 bcopy(&p->semval, vp, sizeof (p->semval));
516 517
517 518 mutex_exit(lock);
518 519
519 520 if (copyout((void *)vals, (void *)arg, vsize)) {
520 521 kmem_free(vals, vsize);
521 522 return (set_errno(EFAULT));
522 523 }
523 524
524 525 kmem_free(vals, vsize);
525 526 return (0);
526 527
527 528 /* Get # of processes sleeping for semval to become zero. */
528 529 case GETZCNT:
529 530 if (error = ipcperm_access(&sp->sem_perm, SEM_R, cr)) {
530 531 mutex_exit(lock);
531 532 return (set_errno(error));
532 533 }
533 534 if (semnum >= sp->sem_nsems) {
534 535 mutex_exit(lock);
535 536 return (set_errno(EINVAL));
536 537 }
537 538 retval = sp->sem_base[semnum].semzcnt;
538 539 mutex_exit(lock);
539 540 return (retval);
540 541
541 542 /* Set semval of one semaphore. */
542 543 case SETVAL:
543 544 if (error = ipcperm_access(&sp->sem_perm, SEM_A, cr)) {
544 545 mutex_exit(lock);
545 546 return (set_errno(error));
546 547 }
547 548 if (semnum >= sp->sem_nsems) {
548 549 mutex_exit(lock);
549 550 return (set_errno(EINVAL));
550 551 }
551 552 if ((uint_t)arg > USHRT_MAX) {
552 553 mutex_exit(lock);
553 554 return (set_errno(ERANGE));
554 555 }
555 556 p = &sp->sem_base[semnum];
556 557 if ((p->semval = (ushort_t)arg) != 0) {
557 558 if (p->semncnt) {
558 559 cv_broadcast(&p->semncnt_cv);
559 560 }
560 561 } else if (p->semzcnt) {
561 562 cv_broadcast(&p->semzcnt_cv);
562 563 }
563 564 p->sempid = curproc->p_pid;
564 565 sem_undo_clear(sp, (ushort_t)semnum, (ushort_t)semnum);
565 566 mutex_exit(lock);
566 567 return (0);
567 568
568 569 /* Set semvals of all semaphores in set. */
569 570 case SETALL:
570 571 /* Check if semaphore set has been deleted and reallocated. */
571 572 if (sp->sem_nsems * sizeof (*vals) != vsize) {
572 573 error = set_errno(EINVAL);
573 574 goto seterr;
574 575 }
575 576 if (error = ipcperm_access(&sp->sem_perm, SEM_A, cr)) {
576 577 error = set_errno(error);
577 578 goto seterr;
578 579 }
579 580 sem_undo_clear(sp, 0, sp->sem_nsems - 1);
580 581 for (i = 0, p = sp->sem_base; i < sp->sem_nsems;
581 582 (p++)->sempid = curproc->p_pid) {
582 583 if ((p->semval = vals[i++]) != 0) {
583 584 if (p->semncnt) {
584 585 cv_broadcast(&p->semncnt_cv);
585 586 }
586 587 } else if (p->semzcnt) {
587 588 cv_broadcast(&p->semzcnt_cv);
588 589 }
589 590 }
590 591 seterr:
591 592 mutex_exit(lock);
592 593 kmem_free(vals, vsize);
593 594 return (error);
594 595
595 596 default:
596 597 mutex_exit(lock);
597 598 return (set_errno(EINVAL));
598 599 }
599 600
600 601 /* NOTREACHED */
601 602 }
602 603
603 604 /*
604 605 * semexit - Called by exit() to clean up on process exit.
605 606 */
606 607 void
607 608 semexit(proc_t *pp)
608 609 {
609 610 avl_tree_t *tree;
610 611 struct sem_undo *undo;
611 612 void *cookie = NULL;
612 613
613 614 mutex_enter(&pp->p_lock);
614 615 tree = pp->p_semacct;
615 616 pp->p_semacct = NULL;
616 617 mutex_exit(&pp->p_lock);
617 618
618 619 while (undo = avl_destroy_nodes(tree, &cookie)) {
619 620 ksemid_t *sp = undo->un_sp;
620 621 size_t size = SEM_UNDOSZ(sp->sem_nsems);
621 622 int i;
622 623
623 624 (void) ipc_lock(sem_svc, sp->sem_perm.ipc_id);
624 625 if (!IPC_FREE(&sp->sem_perm)) {
625 626 for (i = 0; i < sp->sem_nsems; i++) {
626 627 int adj = undo->un_aoe[i];
627 628 if (adj) {
628 629 struct sem *semp = &sp->sem_base[i];
629 630 int v = (int)semp->semval + adj;
630 631
631 632 if (v < 0 || v > USHRT_MAX)
632 633 continue;
633 634 semp->semval = (ushort_t)v;
634 635 if (v == 0 && semp->semzcnt)
635 636 cv_broadcast(&semp->semzcnt_cv);
636 637 if (adj > 0 && semp->semncnt)
637 638 cv_broadcast(&semp->semncnt_cv);
638 639 }
639 640 }
640 641 list_remove(&sp->sem_undos, undo);
641 642 }
642 643 ipc_rele(sem_svc, (kipc_perm_t *)sp);
643 644 kmem_free(undo, size);
644 645 }
645 646
646 647 avl_destroy(tree);
647 648 kmem_free(tree, sizeof (avl_tree_t));
648 649 }
649 650
650 651 /*
651 652 * Remove all semaphores associated with a given zone. Called by
652 653 * zone_shutdown when the zone is halted.
653 654 */
654 655 /*ARGSUSED1*/
655 656 static void
656 657 sem_remove_zone(zoneid_t zoneid, void *arg)
657 658 {
658 659 ipc_remove_zone(sem_svc, zoneid);
659 660 }
660 661
661 662 /*
662 663 * semget - Semget system call.
663 664 */
664 665 static int
665 666 semget(key_t key, int nsems, int semflg)
666 667 {
667 668 ksemid_t *sp;
668 669 kmutex_t *lock;
669 670 int id, error;
670 671 proc_t *pp = curproc;
671 672
672 673 top:
673 674 if (error = ipc_get(sem_svc, key, semflg, (kipc_perm_t **)&sp, &lock))
674 675 return (set_errno(error));
675 676
676 677 if (!IPC_FREE(&sp->sem_perm)) {
677 678 /*
678 679 * A semaphore with the requested key exists.
679 680 */
680 681 if (!((nsems >= 0) && (nsems <= sp->sem_nsems))) {
681 682 mutex_exit(lock);
682 683 return (set_errno(EINVAL));
683 684 }
684 685 } else {
685 686 /*
686 687 * This is a new semaphore set. Finish initialization.
687 688 */
688 689 if (nsems <= 0 || (rctl_test(rc_process_semmsl, pp->p_rctls, pp,
689 690 nsems, RCA_SAFE) & RCT_DENY)) {
690 691 mutex_exit(lock);
691 692 mutex_exit(&pp->p_lock);
692 693 ipc_cleanup(sem_svc, (kipc_perm_t *)sp);
693 694 return (set_errno(EINVAL));
694 695 }
695 696 mutex_exit(lock);
696 697 mutex_exit(&pp->p_lock);
697 698
698 699 /*
699 700 * We round the allocation up to coherency granularity
700 701 * so that multiple semaphore allocations won't result
701 702 * in the false sharing of their sem structures.
702 703 */
703 704 sp->sem_base =
704 705 kmem_zalloc(P2ROUNDUP(nsems * sizeof (struct sem), 64),
705 706 KM_SLEEP);
706 707 sp->sem_binary = (nsems == 1);
707 708 sp->sem_nsems = (ushort_t)nsems;
708 709 sp->sem_ctime = gethrestime_sec();
709 710 sp->sem_otime = 0;
710 711 list_create(&sp->sem_undos, sizeof (struct sem_undo),
711 712 offsetof(struct sem_undo, un_list));
712 713
713 714 if (error = ipc_commit_begin(sem_svc, key, semflg,
714 715 (kipc_perm_t *)sp)) {
715 716 if (error == EAGAIN)
716 717 goto top;
717 718 return (set_errno(error));
718 719 }
719 720 sp->sem_maxops =
720 721 rctl_enforced_value(rc_process_semopm, pp->p_rctls, pp);
721 722 if (rctl_test(rc_process_semmsl, pp->p_rctls, pp, nsems,
722 723 RCA_SAFE) & RCT_DENY) {
723 724 ipc_cleanup(sem_svc, (kipc_perm_t *)sp);
724 725 return (set_errno(EINVAL));
725 726 }
726 727 lock = ipc_commit_end(sem_svc, &sp->sem_perm);
727 728 }
728 729
729 730 if (AU_AUDITING())
730 731 audit_ipcget(AT_IPC_SEM, (void *)sp);
731 732
732 733 id = sp->sem_perm.ipc_id;
733 734 mutex_exit(lock);
734 735 return (id);
735 736 }
736 737
737 738 /*
738 739 * semids system call.
739 740 */
740 741 static int
741 742 semids(int *buf, uint_t nids, uint_t *pnids)
742 743 {
743 744 int error;
744 745
745 746 if (error = ipc_ids(sem_svc, buf, nids, pnids))
746 747 return (set_errno(error));
747 748
748 749 return (0);
749 750 }
750 751
751 752
752 753 /*
753 754 * Helper function for semop - copies in the provided timespec and
754 755 * computes the absolute future time after which we must return.
755 756 */
756 757 static int
757 758 compute_timeout(timespec_t **tsp, timespec_t *ts, timespec_t *now,
758 759 timespec_t *timeout)
759 760 {
760 761 model_t datamodel = get_udatamodel();
761 762
762 763 if (datamodel == DATAMODEL_NATIVE) {
763 764 if (copyin(timeout, ts, sizeof (timespec_t)))
764 765 return (EFAULT);
765 766 } else {
766 767 timespec32_t ts32;
767 768
768 769 if (copyin(timeout, &ts32, sizeof (timespec32_t)))
769 770 return (EFAULT);
770 771 TIMESPEC32_TO_TIMESPEC(ts, &ts32)
771 772 }
772 773
773 774 if (itimerspecfix(ts))
774 775 return (EINVAL);
775 776
776 777 /*
777 778 * Convert the timespec value into absolute time.
778 779 */
779 780 timespecadd(ts, now);
780 781 *tsp = ts;
781 782
782 783 return (0);
783 784 }
784 785
785 786 /*
786 787 * Undo structure comparator. We sort based on ksemid_t pointer.
787 788 */
788 789 static int
789 790 sem_undo_compar(const void *x, const void *y)
790 791 {
791 792 struct sem_undo *undo1 = (struct sem_undo *)x;
792 793 struct sem_undo *undo2 = (struct sem_undo *)y;
793 794
794 795 if (undo1->un_sp < undo2->un_sp)
795 796 return (-1);
796 797 if (undo1->un_sp > undo2->un_sp)
797 798 return (1);
798 799 return (0);
799 800 }
800 801
801 802 /*
802 803 * Helper function for semop - creates an undo structure and adds it to
803 804 * the process's avl tree and the semaphore's list.
804 805 */
805 806 static int
806 807 sem_undo_alloc(proc_t *pp, ksemid_t *sp, kmutex_t **lock,
807 808 struct sem_undo *template, struct sem_undo **un)
808 809 {
809 810 size_t size;
810 811 struct sem_undo *undo;
811 812 avl_tree_t *tree = NULL;
812 813 avl_index_t where;
813 814
814 815 mutex_exit(*lock);
815 816
816 817 size = SEM_UNDOSZ(sp->sem_nsems);
817 818 undo = kmem_zalloc(size, KM_SLEEP);
818 819 undo->un_proc = pp;
819 820 undo->un_sp = sp;
820 821
821 822 if (pp->p_semacct == NULL)
822 823 tree = kmem_alloc(sizeof (avl_tree_t), KM_SLEEP);
823 824
824 825 *lock = ipc_lock(sem_svc, sp->sem_perm.ipc_id);
825 826 if (IPC_FREE(&sp->sem_perm)) {
826 827 kmem_free(undo, size);
827 828 if (tree)
828 829 kmem_free(tree, sizeof (avl_tree_t));
829 830 return (EIDRM);
830 831 }
831 832
832 833 mutex_enter(&pp->p_lock);
833 834 if (tree) {
834 835 if (pp->p_semacct == NULL) {
835 836 avl_create(tree, sem_undo_compar,
836 837 sizeof (struct sem_undo),
837 838 offsetof(struct sem_undo, un_avl));
838 839 pp->p_semacct = tree;
839 840 } else {
840 841 kmem_free(tree, sizeof (avl_tree_t));
841 842 }
842 843 }
843 844
844 845 if (*un = avl_find(pp->p_semacct, template, &where)) {
845 846 mutex_exit(&pp->p_lock);
846 847 kmem_free(undo, size);
847 848 } else {
848 849 *un = undo;
849 850 avl_insert(pp->p_semacct, undo, where);
850 851 mutex_exit(&pp->p_lock);
851 852 list_insert_head(&sp->sem_undos, undo);
852 853 ipc_hold(sem_svc, (kipc_perm_t *)sp);
853 854 }
854 855
855 856
856 857 return (0);
857 858 }
858 859
859 860 /*
860 861 * semop - Semop system call.
861 862 */
862 863 static int
863 864 semop(int semid, struct sembuf *sops, size_t nsops, timespec_t *timeout)
864 865 {
865 866 ksemid_t *sp = NULL;
866 867 kmutex_t *lock;
867 868 struct sembuf *op; /* ptr to operation */
868 869 int i; /* loop control */
869 870 struct sem *semp; /* ptr to semaphore */
870 871 int error = 0;
871 872 struct sembuf *uops; /* ptr to copy of user ops */
872 873 struct sembuf x_sem; /* avoid kmem_alloc's */
873 874 timespec_t now, ts, *tsp = NULL;
874 875 int timecheck = 0;
875 876 int cvres, needundo, mode;
876 877 struct sem_undo *undo;
877 878 proc_t *pp = curproc;
878 879 int held = 0;
879 880
880 881 CPU_STATS_ADDQ(CPU, sys, sema, 1); /* bump semaphore op count */
881 882
882 883 /*
883 884 * To avoid the cost of copying in 'timeout' in the common
884 885 * case, we could only grab the time here and defer the copyin
885 886 * and associated computations until we are about to block.
886 887 *
887 888 * The down side to this is that we would then have to spin
888 889 * some goto top nonsense to avoid the copyin behind the semid
889 890 * lock. As a common use of timed semaphores is as an explicit
890 891 * blocking mechanism, this could incur a greater penalty.
891 892 *
892 893 * If we eventually decide that this would be a wise route to
893 894 * take, the deferrable functionality is completely contained
894 895 * in 'compute_timeout', and the interface is defined such that
895 896 * we can legally not validate 'timeout' if it is unused.
896 897 */
897 898 if (timeout != NULL) {
898 899 timecheck = timechanged;
899 900 gethrestime(&now);
900 901 if (error = compute_timeout(&tsp, &ts, &now, timeout))
901 902 return (set_errno(error));
902 903 }
903 904
904 905 /*
905 906 * Allocate space to hold the vector of semaphore ops. If
906 907 * there is only 1 operation we use a preallocated buffer on
907 908 * the stack for speed.
908 909 *
909 910 * Since we don't want to allow the user to allocate an
910 911 * arbitrary amount of kernel memory, we need to check against
911 912 * the number of operations allowed by the semaphore. We only
912 913 * bother doing this if the number of operations is larger than
913 914 * SEM_MAXUCOPS.
914 915 */
915 916 if (nsops == 1)
916 917 uops = &x_sem;
917 918 else if (nsops == 0)
918 919 return (0);
919 920 else if (nsops <= SEM_MAXUCOPS)
920 921 uops = kmem_alloc(nsops * sizeof (*uops), KM_SLEEP);
921 922
922 923 if (nsops > SEM_MAXUCOPS) {
923 924 if ((lock = ipc_lookup(sem_svc, semid,
924 925 (kipc_perm_t **)&sp)) == NULL)
925 926 return (set_errno(EFAULT));
926 927
927 928 if (nsops > sp->sem_maxops) {
928 929 mutex_exit(lock);
929 930 return (set_errno(E2BIG));
930 931 }
931 932 held = 1;
932 933 ipc_hold(sem_svc, (kipc_perm_t *)sp);
933 934 mutex_exit(lock);
934 935
935 936 uops = kmem_alloc(nsops * sizeof (*uops), KM_SLEEP);
936 937 if (copyin(sops, uops, nsops * sizeof (*op))) {
937 938 error = EFAULT;
938 939 (void) ipc_lock(sem_svc, sp->sem_perm.ipc_id);
939 940 goto semoperr;
940 941 }
941 942
942 943 lock = ipc_lock(sem_svc, sp->sem_perm.ipc_id);
943 944 if (IPC_FREE(&sp->sem_perm)) {
944 945 error = EIDRM;
945 946 goto semoperr;
946 947 }
947 948 } else {
948 949 /*
949 950 * This could be interleaved with the above code, but
950 951 * keeping them separate improves readability.
951 952 */
952 953 if (copyin(sops, uops, nsops * sizeof (*op))) {
953 954 error = EFAULT;
954 955 goto semoperr_unlocked;
955 956 }
956 957
957 958 if ((lock = ipc_lookup(sem_svc, semid,
958 959 (kipc_perm_t **)&sp)) == NULL) {
959 960 error = EINVAL;
960 961 goto semoperr_unlocked;
961 962 }
962 963
963 964 if (nsops > sp->sem_maxops) {
964 965 error = E2BIG;
965 966 goto semoperr;
966 967 }
967 968 }
968 969
969 970 /*
970 971 * Scan all operations. Verify that sem #s are in range and
971 972 * this process is allowed the requested operations. If any
972 973 * operations are marked SEM_UNDO, find (or allocate) the undo
973 974 * structure for this process and semaphore.
974 975 */
975 976 needundo = 0;
976 977 mode = 0;
977 978 for (i = 0, op = uops; i++ < nsops; op++) {
978 979 mode |= op->sem_op ? SEM_A : SEM_R;
979 980 if (op->sem_num >= sp->sem_nsems) {
980 981 error = EFBIG;
981 982 goto semoperr;
982 983 }
983 984 if ((op->sem_flg & SEM_UNDO) && op->sem_op)
984 985 needundo = 1;
985 986 }
986 987 if (error = ipcperm_access(&sp->sem_perm, mode, CRED()))
987 988 goto semoperr;
988 989
989 990 if (needundo) {
990 991 struct sem_undo template;
991 992
992 993 template.un_sp = sp;
993 994 mutex_enter(&pp->p_lock);
994 995 if (pp->p_semacct)
995 996 undo = avl_find(pp->p_semacct, &template, NULL);
996 997 else
997 998 undo = NULL;
998 999 mutex_exit(&pp->p_lock);
999 1000 if (undo == NULL) {
1000 1001 if (!held) {
1001 1002 held = 1;
1002 1003 ipc_hold(sem_svc, (kipc_perm_t *)sp);
1003 1004 }
1004 1005 if (error = sem_undo_alloc(pp, sp, &lock, &template,
1005 1006 &undo))
1006 1007 goto semoperr;
1007 1008
1008 1009 /* sem_undo_alloc unlocks the semaphore */
1009 1010 if (error = ipcperm_access(&sp->sem_perm, mode, CRED()))
1010 1011 goto semoperr;
1011 1012 }
1012 1013 }
1013 1014
1014 1015 check:
1015 1016 /*
1016 1017 * Loop waiting for the operations to be satisfied atomically.
1017 1018 * Actually, do the operations and undo them if a wait is needed
1018 1019 * or an error is detected.
1019 1020 */
1020 1021 for (i = 0; i < nsops; i++) {
1021 1022 op = &uops[i];
1022 1023 semp = &sp->sem_base[op->sem_num];
1023 1024
1024 1025 /*
1025 1026 * Raise the semaphore (i.e. sema_v)
1026 1027 */
1027 1028 if (op->sem_op > 0) {
1028 1029 if (op->sem_op + (int)semp->semval > USHRT_MAX ||
1029 1030 ((op->sem_flg & SEM_UNDO) &&
1030 1031 (error = sem_undo_add(op->sem_op, op->sem_num,
1031 1032 undo)))) {
1032 1033 if (i)
1033 1034 sem_rollback(sp, uops, i, undo);
1034 1035 if (error == 0)
1035 1036 error = ERANGE;
1036 1037 goto semoperr;
1037 1038 }
1038 1039 semp->semval += op->sem_op;
1039 1040 /*
1040 1041 * If we are only incrementing the semaphore value
1041 1042 * by one on a binary semaphore, we can cv_signal.
1042 1043 */
1043 1044 if (semp->semncnt) {
1044 1045 if (op->sem_op == 1 && sp->sem_binary)
1045 1046 cv_signal(&semp->semncnt_cv);
1046 1047 else
1047 1048 cv_broadcast(&semp->semncnt_cv);
1048 1049 }
1049 1050 if (semp->semzcnt && !semp->semval)
1050 1051 cv_broadcast(&semp->semzcnt_cv);
1051 1052 continue;
1052 1053 }
1053 1054
1054 1055 /*
1055 1056 * Lower the semaphore (i.e. sema_p)
1056 1057 */
1057 1058 if (op->sem_op < 0) {
1058 1059 if (semp->semval >= (unsigned)(-op->sem_op)) {
1059 1060 if ((op->sem_flg & SEM_UNDO) &&
1060 1061 (error = sem_undo_add(op->sem_op,
1061 1062 op->sem_num, undo))) {
1062 1063 if (i)
1063 1064 sem_rollback(sp, uops, i, undo);
1064 1065 goto semoperr;
1065 1066 }
1066 1067 semp->semval += op->sem_op;
1067 1068 if (semp->semzcnt && !semp->semval)
1068 1069 cv_broadcast(&semp->semzcnt_cv);
1069 1070 continue;
1070 1071 }
1071 1072 if (i)
1072 1073 sem_rollback(sp, uops, i, undo);
1073 1074 if (op->sem_flg & IPC_NOWAIT) {
1074 1075 error = EAGAIN;
1075 1076 goto semoperr;
1076 1077 }
1077 1078
1078 1079 /*
1079 1080 * Mark the semaphore set as not a binary type
1080 1081 * if we are decrementing the value by more than 1.
1081 1082 *
1082 1083 * V operations will resort to cv_broadcast
1083 1084 * for this set because there are too many weird
1084 1085 * cases that have to be caught.
1085 1086 */
1086 1087 if (op->sem_op < -1)
1087 1088 sp->sem_binary = 0;
1088 1089 if (!held) {
1089 1090 held = 1;
1090 1091 ipc_hold(sem_svc, (kipc_perm_t *)sp);
1091 1092 }
1092 1093 semp->semncnt++;
1093 1094 cvres = cv_waituntil_sig(&semp->semncnt_cv, lock,
1094 1095 tsp, timecheck);
1095 1096 lock = ipc_relock(sem_svc, sp->sem_perm.ipc_id, lock);
1096 1097
1097 1098 if (!IPC_FREE(&sp->sem_perm)) {
1098 1099 ASSERT(semp->semncnt != 0);
1099 1100 semp->semncnt--;
1100 1101 if (cvres > 0) /* normal wakeup */
1101 1102 goto check;
1102 1103 }
1103 1104
1104 1105 /* EINTR or EAGAIN overrides EIDRM */
1105 1106 if (cvres == 0)
1106 1107 error = EINTR;
1107 1108 else if (cvres < 0)
1108 1109 error = EAGAIN;
1109 1110 else
1110 1111 error = EIDRM;
1111 1112 goto semoperr;
1112 1113 }
1113 1114
1114 1115 /*
1115 1116 * Wait for zero value
1116 1117 */
1117 1118 if (semp->semval) {
1118 1119 if (i)
1119 1120 sem_rollback(sp, uops, i, undo);
1120 1121 if (op->sem_flg & IPC_NOWAIT) {
1121 1122 error = EAGAIN;
1122 1123 goto semoperr;
1123 1124 }
1124 1125
1125 1126 if (!held) {
1126 1127 held = 1;
1127 1128 ipc_hold(sem_svc, (kipc_perm_t *)sp);
1128 1129 }
1129 1130 semp->semzcnt++;
1130 1131 cvres = cv_waituntil_sig(&semp->semzcnt_cv, lock,
1131 1132 tsp, timecheck);
1132 1133 lock = ipc_relock(sem_svc, sp->sem_perm.ipc_id, lock);
1133 1134
1134 1135 /*
1135 1136 * Don't touch semp if the semaphores have been removed.
1136 1137 */
1137 1138 if (!IPC_FREE(&sp->sem_perm)) {
1138 1139 ASSERT(semp->semzcnt != 0);
1139 1140 semp->semzcnt--;
1140 1141 if (cvres > 0) /* normal wakeup */
1141 1142 goto check;
1142 1143 }
1143 1144
1144 1145 /* EINTR or EAGAIN overrides EIDRM */
1145 1146 if (cvres == 0)
1146 1147 error = EINTR;
1147 1148 else if (cvres < 0)
1148 1149 error = EAGAIN;
1149 1150 else
1150 1151 error = EIDRM;
1151 1152 goto semoperr;
1152 1153 }
1153 1154 }
1154 1155
1155 1156 /* All operations succeeded. Update sempid for accessed semaphores. */
1156 1157 for (i = 0, op = uops; i++ < nsops;
1157 1158 sp->sem_base[(op++)->sem_num].sempid = pp->p_pid)
1158 1159 ;
1159 1160 sp->sem_otime = gethrestime_sec();
1160 1161 if (held)
1161 1162 ipc_rele(sem_svc, (kipc_perm_t *)sp);
1162 1163 else
1163 1164 mutex_exit(lock);
1164 1165
1165 1166 /* Before leaving, deallocate the buffer that held the user semops */
1166 1167 if (nsops != 1)
1167 1168 kmem_free(uops, sizeof (*uops) * nsops);
1168 1169 return (0);
1169 1170
1170 1171 /*
1171 1172 * Error return labels
1172 1173 */
1173 1174 semoperr:
1174 1175 if (held)
1175 1176 ipc_rele(sem_svc, (kipc_perm_t *)sp);
1176 1177 else
1177 1178 mutex_exit(lock);
1178 1179
1179 1180 semoperr_unlocked:
1180 1181
1181 1182 /* Before leaving, deallocate the buffer that held the user semops */
1182 1183 if (nsops != 1)
1183 1184 kmem_free(uops, sizeof (*uops) * nsops);
1184 1185 return (set_errno(error));
1185 1186 }
1186 1187
1187 1188 /*
1188 1189 * semsys - System entry point for semctl, semget, and semop system calls.
1189 1190 */
1190 1191 static int
1191 1192 semsys(int opcode, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4)
1192 1193 {
1193 1194 int error;
1194 1195
1195 1196 switch (opcode) {
1196 1197 case SEMCTL:
1197 1198 error = semctl((int)a1, (uint_t)a2, (int)a3, a4);
1198 1199 break;
1199 1200 case SEMGET:
1200 1201 error = semget((key_t)a1, (int)a2, (int)a3);
1201 1202 break;
1202 1203 case SEMOP:
1203 1204 error = semop((int)a1, (struct sembuf *)a2, (size_t)a3, 0);
1204 1205 break;
1205 1206 case SEMIDS:
1206 1207 error = semids((int *)a1, (uint_t)a2, (uint_t *)a3);
1207 1208 break;
1208 1209 case SEMTIMEDOP:
1209 1210 error = semop((int)a1, (struct sembuf *)a2, (size_t)a3,
1210 1211 (timespec_t *)a4);
1211 1212 break;
1212 1213 default:
1213 1214 error = set_errno(EINVAL);
1214 1215 break;
1215 1216 }
1216 1217 return (error);
1217 1218 }
↓ open down ↓ |
1003 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX