xref: /illumos-gate/usr/src/test/libc-tests/tests/posix_spawn/posix_spawn_pipe_np.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_pipe_np(3C), which spawns "sh -c <cmd>" and
18  * returns a pipe fd for reading from or writing to the child.
19  */
20 
21 #include <err.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <limits.h>
25 #include <spawn.h>
26 #include <stdbool.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <wait.h>
32 #include <sys/sysmacros.h>
33 #include <sys/debug.h>
34 
35 #include "posix_spawn_common.h"
36 
37 typedef struct spawn_pipe_test {
38 	const char	*spt_name;
39 	bool		(*spt_func)(struct spawn_pipe_test *);
40 } spawn_pipe_test_t;
41 
42 /*
43  * Read from a child process: "sh -c echo hello". Verify we get "hello\n".
44  */
45 static bool
read_from_child_test(spawn_pipe_test_t * test)46 read_from_child_test(spawn_pipe_test_t *test)
47 {
48 	const char *desc = test->spt_name;
49 	posix_spawn_file_actions_t fact;
50 	posix_spawnattr_t attr;
51 	pid_t pid;
52 	int fd, ret;
53 	char buf[64];
54 	ssize_t n;
55 	siginfo_t sig;
56 
57 	VERIFY0(posix_spawn_file_actions_init(&fact));
58 	VERIFY0(posix_spawnattr_init(&attr));
59 
60 	ret = posix_spawn_pipe_np(&pid, &fd, "echo hello", B_FALSE,
61 	    &fact, &attr);
62 	if (ret != 0) {
63 		warnx("TEST FAILED: %s: posix_spawn_pipe_np failed with %s",
64 		    desc, strerrorname_np(ret));
65 		VERIFY0(posix_spawn_file_actions_destroy(&fact));
66 		VERIFY0(posix_spawnattr_destroy(&attr));
67 		return (false);
68 	}
69 
70 	n = read(fd, buf, sizeof (buf) - 1);
71 	(void) close(fd);
72 
73 	if (waitid(P_PID, pid, &sig, WEXITED) != 0)
74 		err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: waitid", desc);
75 
76 	if (n < 0) {
77 		warn("TEST FAILED: %s: read from pipe failed", desc);
78 		VERIFY0(posix_spawn_file_actions_destroy(&fact));
79 		VERIFY0(posix_spawnattr_destroy(&attr));
80 		return (false);
81 	}
82 
83 	buf[n] = '\0';
84 
85 	if (strcmp(buf, "hello\n") != 0) {
86 		warnx("TEST FAILED: %s: expected 'hello\\n', got '%s'",
87 		    desc, buf);
88 		VERIFY0(posix_spawn_file_actions_destroy(&fact));
89 		VERIFY0(posix_spawnattr_destroy(&attr));
90 		return (false);
91 	}
92 
93 	VERIFY0(posix_spawn_file_actions_destroy(&fact));
94 	VERIFY0(posix_spawnattr_destroy(&attr));
95 
96 	return (sig.si_code == CLD_EXITED && sig.si_status == 0);
97 }
98 
99 /*
100  * Write to a child process: pipe data to "cat", capture its output via a
101  * temporary file. Verify the data round-trips.
102  */
103 static bool
write_to_child_test(spawn_pipe_test_t * test)104 write_to_child_test(spawn_pipe_test_t *test)
105 {
106 	const char *desc = test->spt_name;
107 	posix_spawn_file_actions_t fact;
108 	posix_spawnattr_t attr;
109 	pid_t pid;
110 	int fd, ret;
111 	siginfo_t sig;
112 	bool bret = true;
113 	char tmpfile[] = "/tmp/posix_spawn_pipe_np.XXXXXX";
114 	char cmd[PATH_MAX];
115 	int tmpfd;
116 	char buf[64];
117 	ssize_t n;
118 	const char *msg = "test data\n";
119 
120 	tmpfd = mkstemp(tmpfile);
121 	if (tmpfd == -1) {
122 		warn("TEST FAILED: %s: mkstemp failed", desc);
123 		return (false);
124 	}
125 	(void) close(tmpfd);
126 
127 	(void) snprintf(cmd, sizeof (cmd), "cat > %s", tmpfile);
128 
129 	VERIFY0(posix_spawn_file_actions_init(&fact));
130 	VERIFY0(posix_spawnattr_init(&attr));
131 
132 	ret = posix_spawn_pipe_np(&pid, &fd, cmd, B_TRUE, &fact, &attr);
133 	if (ret != 0) {
134 		warnx("TEST FAILED: %s: posix_spawn_pipe_np failed with %s",
135 		    desc, strerrorname_np(ret));
136 		bret = false;
137 		goto out;
138 	}
139 
140 	if (write(fd, msg, strlen(msg)) != (ssize_t)strlen(msg)) {
141 		warn("TEST FAILED: %s: write to pipe failed", desc);
142 		bret = false;
143 	}
144 	(void) close(fd);
145 
146 	if (waitid(P_PID, pid, &sig, WEXITED) != 0)
147 		err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: waitid", desc);
148 
149 	if (sig.si_code != CLD_EXITED || sig.si_status != 0) {
150 		warnx("TEST FAILED: %s: child did not exit cleanly", desc);
151 		bret = false;
152 		goto out;
153 	}
154 
155 	/* Read back from the temporary file and verify. */
156 	tmpfd = open(tmpfile, O_RDONLY);
157 	if (tmpfd == -1) {
158 		warn("TEST FAILED: %s: open(%s) failed", desc, tmpfile);
159 		bret = false;
160 		goto out;
161 	}
162 
163 	n = read(tmpfd, buf, sizeof (buf) - 1);
164 	(void) close(tmpfd);
165 
166 	if (n < 0) {
167 		warn("TEST FAILED: %s: read from tmpfile failed", desc);
168 		bret = false;
169 		goto out;
170 	}
171 
172 	buf[n] = '\0';
173 	if (strcmp(buf, msg) != 0) {
174 		warnx("TEST FAILED: %s: expected '%s', got '%s'",
175 		    desc, msg, buf);
176 		bret = false;
177 	}
178 
179 out:
180 	(void) unlink(tmpfile);
181 	VERIFY0(posix_spawn_file_actions_destroy(&fact));
182 	VERIFY0(posix_spawnattr_destroy(&attr));
183 
184 	return (bret);
185 }
186 
187 /*
188  * Error case: spawn a command that doesn't exist.
189  */
190 static bool
bad_cmd_test(spawn_pipe_test_t * test)191 bad_cmd_test(spawn_pipe_test_t *test)
192 {
193 	const char *desc = test->spt_name;
194 	posix_spawn_file_actions_t fact;
195 	posix_spawnattr_t attr;
196 	pid_t pid;
197 	int fd, ret;
198 	siginfo_t sig;
199 
200 	VERIFY0(posix_spawn_file_actions_init(&fact));
201 	VERIFY0(posix_spawnattr_init(&attr));
202 
203 	/*
204 	 * posix_spawn_pipe_np spawns "sh -c <cmd>", so even a bad command
205 	 * will successfully spawn sh. The shell itself will report the error
206 	 * via a non-zero exit status.
207 	 */
208 	ret = posix_spawn_pipe_np(&pid, &fd, "/nonexistent/cmd", B_FALSE,
209 	    &fact, &attr);
210 	if (ret != 0) {
211 		warnx("TEST FAILED: %s: posix_spawn_pipe_np failed with %s, "
212 		    "expected success (sh spawns)", desc, strerrorname_np(ret));
213 		VERIFY0(posix_spawn_file_actions_destroy(&fact));
214 		VERIFY0(posix_spawnattr_destroy(&attr));
215 		return (false);
216 	}
217 
218 	(void) close(fd);
219 
220 	if (waitid(P_PID, pid, &sig, WEXITED) != 0)
221 		err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: waitid", desc);
222 
223 	VERIFY0(posix_spawn_file_actions_destroy(&fact));
224 	VERIFY0(posix_spawnattr_destroy(&attr));
225 
226 	if (sig.si_code != CLD_EXITED || sig.si_status == 0) {
227 		warnx("TEST FAILED: %s: expected non-zero exit from shell",
228 		    desc);
229 		return (false);
230 	}
231 
232 	return (true);
233 }
234 
235 static spawn_pipe_test_t tests[] = {
236 	{ .spt_name = "pipe_np: read from child (echo)",
237 	    .spt_func = read_from_child_test },
238 	{ .spt_name = "pipe_np: write to child (cat)",
239 	    .spt_func = write_to_child_test },
240 	{ .spt_name = "pipe_np: bad command exits non-zero",
241 	    .spt_func = bad_cmd_test },
242 };
243 
244 int
main(void)245 main(void)
246 {
247 	int ret = EXIT_SUCCESS;
248 
249 	for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
250 		if (tests[i].spt_func(&tests[i]))
251 			(void) printf("TEST PASSED: %s\n", tests[i].spt_name);
252 		else
253 			ret = EXIT_FAILURE;
254 	}
255 
256 	if (ret == EXIT_SUCCESS)
257 		(void) printf("All tests passed successfully!\n");
258 
259 	return (ret);
260 }
261