xref: /illumos-gate/usr/src/test/libc-tests/tests/posix_spawn/posix_spawn_attr.c (revision 93d6c51de00648a982a50fbecc433b5482953fc7)
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