1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2026 Oxide Computer Company
14 */
15
16 /*
17 * Tests for posix_spawn attributes: SETSIGDEF, SETSIGMASK, SETSIGIGN_NP,
18 * NOSIGCHLD_NP, and NOEXECERR_NP.
19 *
20 * Signal-related tests spawn the posix_spawn_child helper as necessary.
21 */
22
23 #include <err.h>
24 #include <stdlib.h>
25 #include <spawn.h>
26 #include <stdio.h>
27 #include <stdbool.h>
28 #include <unistd.h>
29 #include <sys/sysmacros.h>
30 #include <sys/ccompile.h>
31 #include <sys/debug.h>
32 #include <wait.h>
33 #include <string.h>
34 #include <fcntl.h>
35 #include <limits.h>
36 #include <errno.h>
37 #include <libgen.h>
38 #include <signal.h>
39
40 #include "posix_spawn_common.h"
41
42 typedef struct spawn_attr_test {
43 const char *sat_name;
44 bool (*sat_func)(struct spawn_attr_test *);
45 short sat_flags;
46 } spawn_attr_test_t;
47
48 static char posix_spawn_child_path[PATH_MAX];
49
50 /*
51 * SETSIGDEF tests: verify that signals are reset to SIG_DFL in the child.
52 */
53 static bool
setsigdef_test(spawn_attr_test_t * test)54 setsigdef_test(spawn_attr_test_t *test)
55 {
56 const char *desc = test->sat_name;
57 int pipes[2];
58 posix_spawn_file_actions_t acts;
59 posix_spawnattr_t attr;
60 sigset_t sigdef;
61 struct sigaction sa;
62 spawn_sig_result_t res[2];
63 ssize_t n;
64 bool bret = true;
65 char sig1_str[12], sig2_str[12];
66 char *argv[] = { posix_spawn_child_path, "sigs", sig1_str, sig2_str,
67 NULL };
68
69 (void) snprintf(sig1_str, sizeof (sig1_str), "%d", SIGUSR1);
70 (void) snprintf(sig2_str, sizeof (sig2_str), "%d", SIGUSR2);
71
72 /*
73 * Set SIGUSR1 and SIGUSR2 to SIG_IGN in the parent. Without
74 * SETSIGDEF, the child would inherit SIG_IGN.
75 */
76 (void) memset(&sa, 0, sizeof (sa));
77 sa.sa_handler = SIG_IGN;
78 VERIFY0(sigaction(SIGUSR1, &sa, NULL));
79 VERIFY0(sigaction(SIGUSR2, &sa, NULL));
80
81 posix_spawn_pipe_setup(&acts, pipes);
82
83 VERIFY0(posix_spawnattr_init(&attr));
84 VERIFY0(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF));
85 (void) sigemptyset(&sigdef);
86 (void) sigaddset(&sigdef, SIGUSR1);
87 (void) sigaddset(&sigdef, SIGUSR2);
88 VERIFY0(posix_spawnattr_setsigdefault(&attr, &sigdef));
89
90 if (!posix_spawn_run_child(desc, posix_spawn_child_path,
91 &acts, &attr, argv)) {
92 bret = false;
93 goto out;
94 }
95
96 n = read(pipes[0], res, sizeof (res));
97 if (n != sizeof (res)) {
98 warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
99 bret = false;
100 goto out;
101 }
102
103 if (res[0].ssr_disp != 0) {
104 warnx("TEST FAILED: %s: "
105 "SIGUSR1 disposition is %d, expected SIG_DFL (0)",
106 desc, res[0].ssr_disp);
107 bret = false;
108 }
109
110 if (res[1].ssr_disp != 0) {
111 warnx("TEST FAILED: %s: "
112 "SIGUSR2 disposition is %d, expected SIG_DFL (0)",
113 desc, res[1].ssr_disp);
114 bret = false;
115 }
116
117 out:
118 sa.sa_handler = SIG_DFL;
119 VERIFY0(sigaction(SIGUSR1, &sa, NULL));
120 VERIFY0(sigaction(SIGUSR2, &sa, NULL));
121
122 VERIFY0(posix_spawnattr_destroy(&attr));
123 VERIFY0(posix_spawn_file_actions_destroy(&acts));
124 VERIFY0(close(pipes[1]));
125 VERIFY0(close(pipes[0]));
126
127 return (bret);
128 }
129
130 /*
131 * SETSIGDEF with an empty set: no signals should be changed.
132 * Parent ignores SIGUSR1; child should still see SIG_IGN.
133 */
134 static bool
setsigdef_empty_test(spawn_attr_test_t * test)135 setsigdef_empty_test(spawn_attr_test_t *test)
136 {
137 const char *desc = test->sat_name;
138 int pipes[2];
139 posix_spawn_file_actions_t acts;
140 posix_spawnattr_t attr;
141 sigset_t sigdef;
142 struct sigaction sa;
143 spawn_sig_result_t res;
144 ssize_t n;
145 bool bret = true;
146 char sig_str[12];
147 char *argv[] = { posix_spawn_child_path, "sigs", sig_str, NULL };
148
149 (void) snprintf(sig_str, sizeof (sig_str), "%d", SIGUSR1);
150
151 (void) memset(&sa, 0, sizeof (sa));
152 sa.sa_handler = SIG_IGN;
153 VERIFY0(sigaction(SIGUSR1, &sa, NULL));
154
155 posix_spawn_pipe_setup(&acts, pipes);
156
157 VERIFY0(posix_spawnattr_init(&attr));
158 VERIFY0(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF));
159 (void) sigemptyset(&sigdef);
160 VERIFY0(posix_spawnattr_setsigdefault(&attr, &sigdef));
161
162 if (!posix_spawn_run_child(desc, posix_spawn_child_path,
163 &acts, &attr, argv)) {
164 bret = false;
165 goto out;
166 }
167
168 n = read(pipes[0], &res, sizeof (res));
169 if (n != sizeof (res)) {
170 warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
171 bret = false;
172 goto out;
173 }
174
175 if (res.ssr_disp != 1) {
176 warnx("TEST FAILED: %s: "
177 "SIGUSR1 disposition is %d, expected SIG_IGN (1)",
178 desc, res.ssr_disp);
179 bret = false;
180 }
181
182 out:
183 sa.sa_handler = SIG_DFL;
184 VERIFY0(sigaction(SIGUSR1, &sa, NULL));
185
186 VERIFY0(posix_spawnattr_destroy(&attr));
187 VERIFY0(posix_spawn_file_actions_destroy(&acts));
188 VERIFY0(close(pipes[1]));
189 VERIFY0(close(pipes[0]));
190
191 return (bret);
192 }
193
194 /*
195 * SETSIGMASK: set the child's signal mask to block SIGUSR1 and SIGUSR2.
196 */
197 static bool
setsigmask_block_test(spawn_attr_test_t * test)198 setsigmask_block_test(spawn_attr_test_t *test)
199 {
200 const char *desc = test->sat_name;
201 int pipes[2];
202 posix_spawn_file_actions_t acts;
203 posix_spawnattr_t attr;
204 sigset_t newmask;
205 sigset_t childmask;
206 ssize_t n;
207 bool bret = true;
208 char *argv[] = { posix_spawn_child_path, "sigmask", NULL };
209
210 posix_spawn_pipe_setup(&acts, pipes);
211
212 VERIFY0(posix_spawnattr_init(&attr));
213 VERIFY0(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK));
214
215 (void) sigemptyset(&newmask);
216 (void) sigaddset(&newmask, SIGUSR1);
217 (void) sigaddset(&newmask, SIGUSR2);
218 VERIFY0(posix_spawnattr_setsigmask(&attr, &newmask));
219
220 if (!posix_spawn_run_child(desc, posix_spawn_child_path,
221 &acts, &attr, argv)) {
222 bret = false;
223 goto out;
224 }
225
226 n = read(pipes[0], &childmask, sizeof (childmask));
227 if (n != sizeof (childmask)) {
228 warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
229 bret = false;
230 goto out;
231 }
232
233 if (!sigismember(&childmask, SIGUSR1)) {
234 warnx("TEST FAILED: %s: SIGUSR1 not in child's signal mask",
235 desc);
236 bret = false;
237 }
238
239 if (!sigismember(&childmask, SIGUSR2)) {
240 warnx("TEST FAILED: %s: SIGUSR2 not in child's signal mask",
241 desc);
242 bret = false;
243 }
244
245 out:
246 VERIFY0(posix_spawnattr_destroy(&attr));
247 VERIFY0(posix_spawn_file_actions_destroy(&acts));
248 VERIFY0(close(pipes[1]));
249 VERIFY0(close(pipes[0]));
250
251 return (bret);
252 }
253
254 /*
255 * SETSIGMASK: parent blocks SIGUSR1, set child mask to empty. The child
256 * should have an empty signal mask regardless of the parent's mask.
257 */
258 static bool
setsigmask_empty_test(spawn_attr_test_t * test)259 setsigmask_empty_test(spawn_attr_test_t *test)
260 {
261 const char *desc = test->sat_name;
262 int pipes[2];
263 posix_spawn_file_actions_t acts;
264 posix_spawnattr_t attr;
265 sigset_t newmask, parentmask;
266 sigset_t childmask;
267 ssize_t n;
268 bool bret = true;
269 char *argv[] = { posix_spawn_child_path, "sigmask", NULL };
270
271 (void) sigemptyset(&newmask);
272 (void) sigaddset(&newmask, SIGUSR1);
273 VERIFY0(sigprocmask(SIG_BLOCK, &newmask, &parentmask));
274
275 posix_spawn_pipe_setup(&acts, pipes);
276
277 VERIFY0(posix_spawnattr_init(&attr));
278 VERIFY0(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK));
279 (void) sigemptyset(&newmask);
280 VERIFY0(posix_spawnattr_setsigmask(&attr, &newmask));
281
282 if (!posix_spawn_run_child(desc, posix_spawn_child_path,
283 &acts, &attr, argv)) {
284 bret = false;
285 goto out;
286 }
287
288 n = read(pipes[0], &childmask, sizeof (childmask));
289 if (n != sizeof (childmask)) {
290 warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
291 bret = false;
292 goto out;
293 }
294
295 if (sigismember(&childmask, SIGUSR1)) {
296 warnx("TEST FAILED: %s: "
297 "SIGUSR1 unexpectedly in child's signal mask", desc);
298 bret = false;
299 }
300
301 out:
302 VERIFY0(sigprocmask(SIG_SETMASK, &parentmask, NULL));
303 VERIFY0(posix_spawnattr_destroy(&attr));
304 VERIFY0(posix_spawn_file_actions_destroy(&acts));
305 VERIFY0(close(pipes[1]));
306 VERIFY0(close(pipes[0]));
307
308 return (bret);
309 }
310
311 /*
312 * SETSIGIGN_NP: set SIGUSR1 to SIG_IGN in the child. The parent has
313 * SIG_DFL for SIGUSR1.
314 */
315 static bool
setsigign_test(spawn_attr_test_t * test)316 setsigign_test(spawn_attr_test_t *test)
317 {
318 const char *desc = test->sat_name;
319 int pipes[2];
320 posix_spawn_file_actions_t acts;
321 posix_spawnattr_t attr;
322 sigset_t sigign;
323 spawn_sig_result_t res;
324 ssize_t n;
325 bool bret = true;
326 char sig_str[12];
327 char *argv[] = { posix_spawn_child_path, "sigs", sig_str, NULL };
328
329 (void) snprintf(sig_str, sizeof (sig_str), "%d", SIGUSR1);
330
331 posix_spawn_pipe_setup(&acts, pipes);
332
333 VERIFY0(posix_spawnattr_init(&attr));
334 VERIFY0(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGIGN_NP));
335 (void) sigemptyset(&sigign);
336 (void) sigaddset(&sigign, SIGUSR1);
337 VERIFY0(posix_spawnattr_setsigignore_np(&attr, &sigign));
338
339 if (!posix_spawn_run_child(desc, posix_spawn_child_path,
340 &acts, &attr, argv)) {
341 bret = false;
342 goto out;
343 }
344
345 n = read(pipes[0], &res, sizeof (res));
346 if (n != sizeof (res)) {
347 warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
348 bret = false;
349 goto out;
350 }
351
352 if (res.ssr_disp != 1) {
353 warnx("TEST FAILED: %s: "
354 "SIGUSR1 disposition is %d, expected SIG_IGN (1)",
355 desc, res.ssr_disp);
356 bret = false;
357 }
358
359 out:
360 VERIFY0(posix_spawnattr_destroy(&attr));
361 VERIFY0(posix_spawn_file_actions_destroy(&acts));
362 VERIFY0(close(pipes[1]));
363 VERIFY0(close(pipes[0]));
364
365 return (bret);
366 }
367
368 /*
369 * SETSIGIGN_NP + SETSIGDEF on the same signal. SETSIGIGN_NP is processed
370 * before SETSIGDEF, so SETSIGDEF wins and the child should see SIG_DFL.
371 * The two variants set up the ignore and default sets in different order to
372 * verify that the result is independent of the API call sequence.
373 */
374 static bool
sigign_then_sigdef_test(spawn_attr_test_t * test)375 sigign_then_sigdef_test(spawn_attr_test_t *test)
376 {
377 const char *desc = test->sat_name;
378 int pipes[2];
379 posix_spawn_file_actions_t acts;
380 posix_spawnattr_t attr;
381 sigset_t sigign, sigdef;
382 spawn_sig_result_t res;
383 ssize_t n;
384 bool bret = true;
385 char sig_str[12];
386 char *argv[] = { posix_spawn_child_path, "sigs", sig_str, NULL };
387
388 (void) snprintf(sig_str, sizeof (sig_str), "%d", SIGUSR1);
389
390 posix_spawn_pipe_setup(&acts, pipes);
391
392 VERIFY0(posix_spawnattr_init(&attr));
393 VERIFY0(posix_spawnattr_setflags(&attr,
394 POSIX_SPAWN_SETSIGIGN_NP | POSIX_SPAWN_SETSIGDEF));
395
396 (void) sigemptyset(&sigign);
397 (void) sigaddset(&sigign, SIGUSR1);
398 VERIFY0(posix_spawnattr_setsigignore_np(&attr, &sigign));
399
400 (void) sigemptyset(&sigdef);
401 (void) sigaddset(&sigdef, SIGUSR1);
402 VERIFY0(posix_spawnattr_setsigdefault(&attr, &sigdef));
403
404 if (!posix_spawn_run_child(desc, posix_spawn_child_path,
405 &acts, &attr, argv)) {
406 bret = false;
407 goto out;
408 }
409
410 n = read(pipes[0], &res, sizeof (res));
411 if (n != sizeof (res)) {
412 warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
413 bret = false;
414 goto out;
415 }
416
417 if (res.ssr_disp != 0) {
418 warnx("TEST FAILED: %s: "
419 "SIGUSR1 disposition is %d, expected SIG_DFL (0)",
420 desc, res.ssr_disp);
421 bret = false;
422 }
423
424 out:
425 VERIFY0(posix_spawnattr_destroy(&attr));
426 VERIFY0(posix_spawn_file_actions_destroy(&acts));
427 VERIFY0(close(pipes[1]));
428 VERIFY0(close(pipes[0]));
429
430 return (bret);
431 }
432
433 /*
434 * Same as sigign_then_sigdef_test but set up the sigdefault set before the
435 * sigignore set, proving the result doesn't depend on API call order.
436 */
437 static bool
sigdef_then_sigign_test(spawn_attr_test_t * test)438 sigdef_then_sigign_test(spawn_attr_test_t *test)
439 {
440 const char *desc = test->sat_name;
441 int pipes[2];
442 posix_spawn_file_actions_t acts;
443 posix_spawnattr_t attr;
444 sigset_t sigign, sigdef;
445 spawn_sig_result_t res;
446 ssize_t n;
447 bool bret = true;
448 char sig_str[12];
449 char *argv[] = { posix_spawn_child_path, "sigs", sig_str, NULL };
450
451 (void) snprintf(sig_str, sizeof (sig_str), "%d", SIGUSR1);
452
453 posix_spawn_pipe_setup(&acts, pipes);
454
455 VERIFY0(posix_spawnattr_init(&attr));
456 VERIFY0(posix_spawnattr_setflags(&attr,
457 POSIX_SPAWN_SETSIGIGN_NP | POSIX_SPAWN_SETSIGDEF));
458
459 (void) sigemptyset(&sigdef);
460 (void) sigaddset(&sigdef, SIGUSR1);
461 VERIFY0(posix_spawnattr_setsigdefault(&attr, &sigdef));
462
463 (void) sigemptyset(&sigign);
464 (void) sigaddset(&sigign, SIGUSR1);
465 VERIFY0(posix_spawnattr_setsigignore_np(&attr, &sigign));
466
467 if (!posix_spawn_run_child(desc, posix_spawn_child_path,
468 &acts, &attr, argv)) {
469 bret = false;
470 goto out;
471 }
472
473 n = read(pipes[0], &res, sizeof (res));
474 if (n != sizeof (res)) {
475 warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
476 bret = false;
477 goto out;
478 }
479
480 if (res.ssr_disp != 0) {
481 warnx("TEST FAILED: %s: "
482 "SIGUSR1 disposition is %d, expected SIG_DFL (0)",
483 desc, res.ssr_disp);
484 bret = false;
485 }
486
487 out:
488 VERIFY0(posix_spawnattr_destroy(&attr));
489 VERIFY0(posix_spawn_file_actions_destroy(&acts));
490 VERIFY0(close(pipes[1]));
491 VERIFY0(close(pipes[0]));
492
493 return (bret);
494 }
495
496 /*
497 * Flag used to detect SIGCHLD delivery.
498 */
499 static volatile sig_atomic_t sigchld_received = 0;
500
501 static void
sigchld_handler(int sig __unused)502 sigchld_handler(int sig __unused)
503 {
504 sigchld_received = 1;
505 }
506
507 /*
508 * NOSIGCHLD_NP test. When sat_flags includes POSIX_SPAWN_NOSIGCHLD_NP,
509 * verify that SIGCHLD is suppressed. Otherwise verify it is delivered.
510 */
511 static bool
nosigchld_test(spawn_attr_test_t * test)512 nosigchld_test(spawn_attr_test_t *test)
513 {
514 const char *desc = test->sat_name;
515 bool use_flag = (test->sat_flags & POSIX_SPAWN_NOSIGCHLD_NP) != 0;
516 posix_spawn_file_actions_t acts;
517 posix_spawnattr_t attr;
518 struct sigaction sa, oldsa;
519 sigset_t mask, oldmask;
520 pid_t pid;
521 siginfo_t sig;
522 int ret;
523 bool bret = true;
524
525 char *true_path = "/usr/bin/true";
526 char *argv[] = { true_path, NULL };
527
528 (void) memset(&sa, 0, sizeof (sa));
529 sa.sa_handler = sigchld_handler;
530 sa.sa_flags = SA_NOCLDSTOP;
531 VERIFY0(sigaction(SIGCHLD, &sa, &oldsa));
532
533 (void) sigemptyset(&mask);
534 (void) sigaddset(&mask, SIGCHLD);
535 VERIFY0(sigprocmask(SIG_UNBLOCK, &mask, &oldmask));
536
537 sigchld_received = 0;
538
539 if ((ret = posix_spawn_file_actions_init(&acts)) != 0) {
540 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: "
541 "file_actions_init");
542 }
543
544 VERIFY0(posix_spawnattr_init(&attr));
545 if (use_flag) {
546 VERIFY0(posix_spawnattr_setflags(&attr,
547 POSIX_SPAWN_NOSIGCHLD_NP));
548 }
549
550 ret = posix_spawn(&pid, true_path, &acts, &attr, argv, NULL);
551 if (ret != 0) {
552 warnx("TEST FAILED: %s: posix_spawn failed with %s",
553 desc, strerrorname_np(ret));
554 bret = false;
555 goto out;
556 }
557
558 /* waitid() may be interrupted by SIGCHLD. Retry on EINTR */
559 while (waitid(P_PID, pid, &sig, WEXITED) != 0) {
560 if (errno == EINTR)
561 continue;
562 err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: waitid", desc);
563 }
564
565 if (use_flag) {
566 if (sigchld_received != 0) {
567 warnx("TEST FAILED: %s: "
568 "SIGCHLD was delivered despite NOSIGCHLD_NP flag",
569 desc);
570 bret = false;
571 }
572 } else {
573 if (sigchld_received == 0) {
574 warnx("TEST FAILED: %s: SIGCHLD was NOT delivered",
575 desc);
576 bret = false;
577 }
578 }
579
580 out:
581 VERIFY0(sigaction(SIGCHLD, &oldsa, NULL));
582 VERIFY0(sigprocmask(SIG_SETMASK, &oldmask, NULL));
583 VERIFY0(posix_spawnattr_destroy(&attr));
584 VERIFY0(posix_spawn_file_actions_destroy(&acts));
585
586 return (bret);
587 }
588
589 /*
590 * NOEXECERR_NP test. When sat_flags includes POSIX_SPAWN_NOEXECERR_NP,
591 * posix_spawn should return 0 and the child exits 127. Otherwise
592 * posix_spawn should return ENOENT directly.
593 */
594 static bool
noexecerr_test(spawn_attr_test_t * test)595 noexecerr_test(spawn_attr_test_t *test)
596 {
597 const char *desc = test->sat_name;
598 bool use_flag = (test->sat_flags & POSIX_SPAWN_NOEXECERR_NP) != 0;
599 posix_spawn_file_actions_t acts;
600 posix_spawnattr_t attr;
601 pid_t pid;
602 int ret;
603 bool bret = true;
604
605 char *bad_path = "/devices/nonexistent/path";
606 char *argv[] = { bad_path, NULL };
607
608 if ((ret = posix_spawn_file_actions_init(&acts)) != 0) {
609 errc(EXIT_FAILURE, ret,
610 "INTERNAL TEST FAILURE: file_actions_init");
611 }
612
613 VERIFY0(posix_spawnattr_init(&attr));
614 if (use_flag) {
615 VERIFY0(posix_spawnattr_setflags(&attr,
616 POSIX_SPAWN_NOEXECERR_NP));
617 }
618
619 ret = posix_spawn(&pid, bad_path, &acts, &attr, argv, NULL);
620
621 if (use_flag) {
622 siginfo_t sig;
623
624 if (ret != 0) {
625 warnx("TEST FAILED: %s: "
626 "posix_spawn returned %s, expected 0",
627 desc, strerrorname_np(ret));
628 bret = false;
629 goto out;
630 }
631
632 if (waitid(P_PID, pid, &sig, WEXITED) != 0) {
633 err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: waitid",
634 desc);
635 }
636
637 if (sig.si_code != CLD_EXITED) {
638 warnx("TEST FAILED: %s: "
639 "child did not exit normally: si_code: %d",
640 desc, sig.si_code);
641 bret = false;
642 goto out;
643 }
644
645 if (sig.si_status != 127) {
646 warnx("TEST FAILED: %s: "
647 "child exited with status %d, expected 127",
648 desc, sig.si_status);
649 bret = false;
650 }
651 } else {
652 if (ret == 0) {
653 siginfo_t sig;
654
655 warnx("TEST FAILED: %s: "
656 "posix_spawn returned 0, expected an error", desc);
657 (void) waitid(P_PID, pid, &sig, WEXITED);
658 bret = false;
659 goto out;
660 }
661
662 if (ret != ENOENT) {
663 warnx("TEST FAILED: %s: "
664 "posix_spawn returned %s, expected ENOENT",
665 desc, strerrorname_np(ret));
666 bret = false;
667 }
668 }
669
670 out:
671 VERIFY0(posix_spawnattr_destroy(&attr));
672 VERIFY0(posix_spawn_file_actions_destroy(&acts));
673
674 return (bret);
675 }
676
677 /*
678 * Test that posix_spawnattr_setflags rejects invalid flag bits.
679 */
680 static bool
bad_flags_test(spawn_attr_test_t * test)681 bad_flags_test(spawn_attr_test_t *test)
682 {
683 posix_spawnattr_t attr;
684 int ret;
685
686 VERIFY0(posix_spawnattr_init(&attr));
687
688 ret = posix_spawnattr_setflags(&attr, (short)0x8000);
689 if (ret != EINVAL) {
690 warnx("TEST FAILED: %s: "
691 "setflags(0x8000) returned %s, expected EINVAL",
692 test->sat_name, strerrorname_np(ret));
693 VERIFY0(posix_spawnattr_destroy(&attr));
694 return (false);
695 }
696
697 VERIFY0(posix_spawnattr_destroy(&attr));
698
699 return (true);
700 }
701
702 /*
703 * Test that setflags rejects a value that has valid bits mixed with invalid
704 * bits.
705 */
706 static bool
bad_flags_mixed_test(spawn_attr_test_t * test)707 bad_flags_mixed_test(spawn_attr_test_t *test)
708 {
709 posix_spawnattr_t attr;
710 int ret;
711
712 VERIFY0(posix_spawnattr_init(&attr));
713
714 ret = posix_spawnattr_setflags(&attr,
715 POSIX_SPAWN_SETSIGDEF | (short)0x8000);
716 if (ret != EINVAL) {
717 warnx("TEST FAILED: %s: "
718 "setflags(SETSIGDEF|0x8000) returned %s, expected EINVAL",
719 test->sat_name, strerrorname_np(ret));
720 VERIFY0(posix_spawnattr_destroy(&attr));
721 return (false);
722 }
723
724 VERIFY0(posix_spawnattr_destroy(&attr));
725
726 return (true);
727 }
728
729 /*
730 * Verify that caught signals in the parent are reset to SIG_DFL in the child
731 * after exec, per POSIX: "Signals set to be caught by the calling process
732 * image are set to the default action in the new process image."
733 *
734 * This does NOT use SETSIGDEF; the reset is a property of exec(2) itself.
735 */
736 static void
sigusr1_nop(int sig __unused)737 sigusr1_nop(int sig __unused)
738 {
739 }
740
741 static bool
caught_handler_reset_test(spawn_attr_test_t * test)742 caught_handler_reset_test(spawn_attr_test_t *test)
743 {
744 const char *desc = test->sat_name;
745 int pipes[2];
746 posix_spawn_file_actions_t acts;
747 struct sigaction sa, oldsa;
748 spawn_sig_result_t res;
749 ssize_t n;
750 bool bret = true;
751 char sig_str[12];
752 char *argv[] = { posix_spawn_child_path, "sigs", sig_str, NULL };
753
754 (void) snprintf(sig_str, sizeof (sig_str), "%d", SIGUSR1);
755
756 /*
757 * Install a real signal handler (not SIG_IGN or SIG_DFL). After
758 * exec the child must see SIG_DFL since caught handlers cannot
759 * survive exec.
760 */
761 (void) memset(&sa, 0, sizeof (sa));
762 sa.sa_handler = sigusr1_nop;
763 VERIFY0(sigaction(SIGUSR1, &sa, &oldsa));
764
765 posix_spawn_pipe_setup(&acts, pipes);
766
767 if (!posix_spawn_run_child(desc, posix_spawn_child_path,
768 &acts, NULL, argv)) {
769 bret = false;
770 goto out;
771 }
772
773 n = read(pipes[0], &res, sizeof (res));
774 if (n != sizeof (res)) {
775 warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
776 bret = false;
777 goto out;
778 }
779
780 if (res.ssr_disp != 0) {
781 warnx("TEST FAILED: %s: "
782 "SIGUSR1 disposition is %d, expected SIG_DFL (0)",
783 desc, res.ssr_disp);
784 bret = false;
785 }
786
787 out:
788 VERIFY0(sigaction(SIGUSR1, &oldsa, NULL));
789
790 VERIFY0(posix_spawn_file_actions_destroy(&acts));
791 VERIFY0(close(pipes[1]));
792 VERIFY0(close(pipes[0]));
793
794 return (bret);
795 }
796
797 static spawn_attr_test_t tests[] = {
798 { .sat_name = "SETSIGDEF: SIGUSR1+SIGUSR2 reset to SIG_DFL",
799 .sat_func = setsigdef_test },
800 { .sat_name = "SETSIGDEF: empty set, no change",
801 .sat_func = setsigdef_empty_test },
802 { .sat_name = "SETSIGMASK: block SIGUSR1+SIGUSR2",
803 .sat_func = setsigmask_block_test },
804 { .sat_name = "SETSIGMASK: parent blocks SIGUSR1, child empty",
805 .sat_func = setsigmask_empty_test },
806 { .sat_name = "SETSIGIGN_NP: SIGUSR1 set to SIG_IGN",
807 .sat_func = setsigign_test },
808 { .sat_name = "SETSIGIGN_NP + SETSIGDEF: SETSIGDEF wins (ign first)",
809 .sat_func = sigign_then_sigdef_test },
810 { .sat_name = "SETSIGIGN_NP + SETSIGDEF: SETSIGDEF wins (def first)",
811 .sat_func = sigdef_then_sigign_test },
812 { .sat_name = "exec resets caught handler to SIG_DFL",
813 .sat_func = caught_handler_reset_test },
814 { .sat_name = "NOSIGCHLD_NP: no SIGCHLD delivered",
815 .sat_func = nosigchld_test,
816 .sat_flags = POSIX_SPAWN_NOSIGCHLD_NP },
817 { .sat_name = "NOSIGCHLD_NP control: SIGCHLD delivered normally",
818 .sat_func = nosigchld_test },
819 { .sat_name = "NOEXECERR_NP: bad path returns 0, child exits 127",
820 .sat_func = noexecerr_test,
821 .sat_flags = POSIX_SPAWN_NOEXECERR_NP },
822 { .sat_name = "NOEXECERR_NP control: bad path returns error",
823 .sat_func = noexecerr_test },
824 { .sat_name = "setflags: reject invalid flag bits",
825 .sat_func = bad_flags_test },
826 { .sat_name = "setflags: reject valid+invalid flag mix",
827 .sat_func = bad_flags_mixed_test },
828 };
829
830 int
main(void)831 main(void)
832 {
833 const char *helpers[] = { POSIX_SPAWN_CHILD_HELPERS };
834 int ret = EXIT_SUCCESS;
835
836 for (size_t h = 0; h < ARRAY_SIZE(helpers); h++) {
837 posix_spawn_find_helper(posix_spawn_child_path,
838 sizeof (posix_spawn_child_path), helpers[h]);
839 (void) printf("--- child helper: %s ---\n", helpers[h]);
840
841 for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
842 if (tests[i].sat_func(&tests[i])) {
843 (void) printf("TEST PASSED: %s\n",
844 tests[i].sat_name);
845 } else {
846 ret = EXIT_FAILURE;
847 }
848 }
849 }
850
851 if (ret == EXIT_SUCCESS)
852 (void) printf("All tests passed successfully!\n");
853
854 return (ret);
855 }
856