1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <spawn.h>
18
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <gtest/gtest.h>
22
23 #include "SignalUtils.h"
24 #include "utils.h"
25
26 #include <android-base/file.h>
27 #include <android-base/strings.h>
28
29 // Old versions of glibc didn't have POSIX_SPAWN_SETSID.
30 #if __GLIBC__
31 # if !defined(POSIX_SPAWN_SETSID)
32 # define POSIX_SPAWN_SETSID 0
33 # endif
34 #endif
35
TEST(spawn,posix_spawnattr_init_posix_spawnattr_destroy)36 TEST(spawn, posix_spawnattr_init_posix_spawnattr_destroy) {
37 posix_spawnattr_t sa;
38 ASSERT_EQ(0, posix_spawnattr_init(&sa));
39 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
40 }
41
TEST(spawn,posix_spawnattr_setflags_EINVAL)42 TEST(spawn, posix_spawnattr_setflags_EINVAL) {
43 posix_spawnattr_t sa;
44 ASSERT_EQ(0, posix_spawnattr_init(&sa));
45 ASSERT_EQ(EINVAL, posix_spawnattr_setflags(&sa, ~0));
46 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
47 }
48
TEST(spawn,posix_spawnattr_setflags_posix_spawnattr_getflags)49 TEST(spawn, posix_spawnattr_setflags_posix_spawnattr_getflags) {
50 posix_spawnattr_t sa;
51 ASSERT_EQ(0, posix_spawnattr_init(&sa));
52
53 ASSERT_EQ(0, posix_spawnattr_setflags(&sa, POSIX_SPAWN_RESETIDS));
54 short flags;
55 ASSERT_EQ(0, posix_spawnattr_getflags(&sa, &flags));
56 ASSERT_EQ(POSIX_SPAWN_RESETIDS, flags);
57
58 constexpr short all_flags = POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF |
59 POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSCHEDPARAM |
60 POSIX_SPAWN_SETSCHEDULER | POSIX_SPAWN_USEVFORK | POSIX_SPAWN_SETSID;
61 ASSERT_EQ(0, posix_spawnattr_setflags(&sa, all_flags));
62 ASSERT_EQ(0, posix_spawnattr_getflags(&sa, &flags));
63 ASSERT_EQ(all_flags, flags);
64
65 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
66 }
67
TEST(spawn,posix_spawnattr_setpgroup_posix_spawnattr_getpgroup)68 TEST(spawn, posix_spawnattr_setpgroup_posix_spawnattr_getpgroup) {
69 posix_spawnattr_t sa;
70 ASSERT_EQ(0, posix_spawnattr_init(&sa));
71
72 ASSERT_EQ(0, posix_spawnattr_setpgroup(&sa, 123));
73 pid_t g;
74 ASSERT_EQ(0, posix_spawnattr_getpgroup(&sa, &g));
75 ASSERT_EQ(123, g);
76
77 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
78 }
79
TEST(spawn,posix_spawnattr_setsigmask_posix_spawnattr_getsigmask)80 TEST(spawn, posix_spawnattr_setsigmask_posix_spawnattr_getsigmask) {
81 posix_spawnattr_t sa;
82 ASSERT_EQ(0, posix_spawnattr_init(&sa));
83
84 sigset_t sigs;
85 ASSERT_EQ(0, posix_spawnattr_getsigmask(&sa, &sigs));
86 ASSERT_FALSE(sigismember(&sigs, SIGALRM));
87
88 sigset_t just_SIGALRM;
89 sigemptyset(&just_SIGALRM);
90 sigaddset(&just_SIGALRM, SIGALRM);
91 ASSERT_EQ(0, posix_spawnattr_setsigmask(&sa, &just_SIGALRM));
92
93 ASSERT_EQ(0, posix_spawnattr_getsigmask(&sa, &sigs));
94 ASSERT_TRUE(sigismember(&sigs, SIGALRM));
95
96 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
97 }
98
TEST(spawn,posix_spawnattr_setsigmask64_posix_spawnattr_getsigmask64)99 TEST(spawn, posix_spawnattr_setsigmask64_posix_spawnattr_getsigmask64) {
100 posix_spawnattr_t sa;
101 ASSERT_EQ(0, posix_spawnattr_init(&sa));
102
103 sigset64_t sigs;
104 ASSERT_EQ(0, posix_spawnattr_getsigmask64(&sa, &sigs));
105 ASSERT_FALSE(sigismember64(&sigs, SIGRTMIN));
106
107 sigset64_t just_SIGRTMIN;
108 sigemptyset64(&just_SIGRTMIN);
109 sigaddset64(&just_SIGRTMIN, SIGRTMIN);
110 ASSERT_EQ(0, posix_spawnattr_setsigmask64(&sa, &just_SIGRTMIN));
111
112 ASSERT_EQ(0, posix_spawnattr_getsigmask64(&sa, &sigs));
113 ASSERT_TRUE(sigismember64(&sigs, SIGRTMIN));
114
115 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
116 }
117
TEST(spawn,posix_spawnattr_setsigdefault_posix_spawnattr_getsigdefault)118 TEST(spawn, posix_spawnattr_setsigdefault_posix_spawnattr_getsigdefault) {
119 posix_spawnattr_t sa;
120 ASSERT_EQ(0, posix_spawnattr_init(&sa));
121
122 sigset_t sigs;
123 ASSERT_EQ(0, posix_spawnattr_getsigdefault(&sa, &sigs));
124 ASSERT_FALSE(sigismember(&sigs, SIGALRM));
125
126 sigset_t just_SIGALRM;
127 sigemptyset(&just_SIGALRM);
128 sigaddset(&just_SIGALRM, SIGALRM);
129 ASSERT_EQ(0, posix_spawnattr_setsigdefault(&sa, &just_SIGALRM));
130
131 ASSERT_EQ(0, posix_spawnattr_getsigdefault(&sa, &sigs));
132 ASSERT_TRUE(sigismember(&sigs, SIGALRM));
133
134 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
135 }
136
TEST(spawn,posix_spawnattr_setsigdefault64_posix_spawnattr_getsigdefault64)137 TEST(spawn, posix_spawnattr_setsigdefault64_posix_spawnattr_getsigdefault64) {
138 posix_spawnattr_t sa;
139 ASSERT_EQ(0, posix_spawnattr_init(&sa));
140
141 sigset64_t sigs;
142 ASSERT_EQ(0, posix_spawnattr_getsigdefault64(&sa, &sigs));
143 ASSERT_FALSE(sigismember64(&sigs, SIGRTMIN));
144
145 sigset64_t just_SIGRTMIN;
146 sigemptyset64(&just_SIGRTMIN);
147 sigaddset64(&just_SIGRTMIN, SIGRTMIN);
148 ASSERT_EQ(0, posix_spawnattr_setsigdefault64(&sa, &just_SIGRTMIN));
149
150 ASSERT_EQ(0, posix_spawnattr_getsigdefault64(&sa, &sigs));
151 ASSERT_TRUE(sigismember64(&sigs, SIGRTMIN));
152
153 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
154 }
155
TEST(spawn,posix_spawnattr_setsschedparam_posix_spawnattr_getsschedparam)156 TEST(spawn, posix_spawnattr_setsschedparam_posix_spawnattr_getsschedparam) {
157 posix_spawnattr_t sa;
158 ASSERT_EQ(0, posix_spawnattr_init(&sa));
159
160 sched_param sp;
161 ASSERT_EQ(0, posix_spawnattr_getschedparam(&sa, &sp));
162 ASSERT_EQ(0, sp.sched_priority);
163
164 sched_param sp123 = { .sched_priority = 123 };
165 ASSERT_EQ(0, posix_spawnattr_setschedparam(&sa, &sp123));
166
167 ASSERT_EQ(0, posix_spawnattr_getschedparam(&sa, &sp));
168 ASSERT_EQ(123, sp.sched_priority);
169
170 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
171 }
172
TEST(spawn,posix_spawnattr_setschedpolicy_posix_spawnattr_getschedpolicy)173 TEST(spawn, posix_spawnattr_setschedpolicy_posix_spawnattr_getschedpolicy) {
174 posix_spawnattr_t sa;
175 ASSERT_EQ(0, posix_spawnattr_init(&sa));
176
177 int p;
178 ASSERT_EQ(0, posix_spawnattr_getschedpolicy(&sa, &p));
179 ASSERT_EQ(0, p);
180
181 ASSERT_EQ(0, posix_spawnattr_setschedpolicy(&sa, SCHED_FIFO));
182
183 ASSERT_EQ(0, posix_spawnattr_getschedpolicy(&sa, &p));
184 ASSERT_EQ(SCHED_FIFO, p);
185
186 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
187 }
188
TEST(spawn,posix_spawn)189 TEST(spawn, posix_spawn) {
190 ExecTestHelper eth;
191 eth.SetArgs({BIN_DIR "true", nullptr});
192 pid_t pid;
193 ASSERT_EQ(0, posix_spawn(&pid, eth.GetArg0(), nullptr, nullptr, eth.GetArgs(), nullptr));
194 AssertChildExited(pid, 0);
195 }
196
TEST(spawn,posix_spawn_not_found)197 TEST(spawn, posix_spawn_not_found) {
198 ExecTestHelper eth;
199 eth.SetArgs({"true", nullptr});
200 pid_t pid;
201 ASSERT_EQ(0, posix_spawn(&pid, eth.GetArg0(), nullptr, nullptr, eth.GetArgs(), nullptr));
202 AssertChildExited(pid, 127);
203 }
204
TEST(spawn,posix_spawnp)205 TEST(spawn, posix_spawnp) {
206 ExecTestHelper eth;
207 eth.SetArgs({"true", nullptr});
208 pid_t pid;
209 ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), nullptr, nullptr, eth.GetArgs(), nullptr));
210 AssertChildExited(pid, 0);
211 }
212
TEST(spawn,posix_spawnp_not_found)213 TEST(spawn, posix_spawnp_not_found) {
214 ExecTestHelper eth;
215 eth.SetArgs({"does-not-exist", nullptr});
216 pid_t pid;
217 ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), nullptr, nullptr, eth.GetArgs(), nullptr));
218 AssertChildExited(pid, 127);
219 }
220
TEST(spawn,posix_spawn_environment)221 TEST(spawn, posix_spawn_environment) {
222 ExecTestHelper eth;
223 eth.SetArgs({"sh", "-c", "exit $posix_spawn_environment_test", nullptr});
224 eth.SetEnv({"posix_spawn_environment_test=66", nullptr});
225 pid_t pid;
226 ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), nullptr, nullptr, eth.GetArgs(), eth.GetEnv()));
227 AssertChildExited(pid, 66);
228 }
229
TEST(spawn,posix_spawn_file_actions)230 TEST(spawn, posix_spawn_file_actions) {
231 int fds[2];
232 ASSERT_NE(-1, pipe(fds));
233
234 posix_spawn_file_actions_t fa;
235 ASSERT_EQ(0, posix_spawn_file_actions_init(&fa));
236
237 ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[0]));
238 ASSERT_EQ(0, posix_spawn_file_actions_adddup2(&fa, fds[1], 1));
239 ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[1]));
240 // Check that close(2) failures are ignored by closing the same fd again.
241 ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[1]));
242 ASSERT_EQ(0, posix_spawn_file_actions_addopen(&fa, 56, "/proc/version", O_RDONLY, 0));
243
244 ExecTestHelper eth;
245 eth.SetArgs({"ls", "-l", "/proc/self/fd", nullptr});
246 pid_t pid;
247 ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), &fa, nullptr, eth.GetArgs(), eth.GetEnv()));
248 ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa));
249
250 ASSERT_EQ(0, close(fds[1]));
251 std::string content;
252 ASSERT_TRUE(android::base::ReadFdToString(fds[0], &content));
253 ASSERT_EQ(0, close(fds[0]));
254
255 AssertChildExited(pid, 0);
256
257 // We'll know the dup2 worked if we see any ls(1) output in our pipe.
258 // The open we can check manually...
259 bool open_to_fd_56_worked = false;
260 for (const auto& line : android::base::Split(content, "\n")) {
261 if (line.find(" 56 -> /proc/version") != std::string::npos) open_to_fd_56_worked = true;
262 }
263 ASSERT_TRUE(open_to_fd_56_worked);
264 }
265
CatFileToString(posix_spawnattr_t * sa,const char * path,std::string * content)266 static void CatFileToString(posix_spawnattr_t* sa, const char* path, std::string* content) {
267 int fds[2];
268 ASSERT_NE(-1, pipe(fds));
269
270 posix_spawn_file_actions_t fa;
271 ASSERT_EQ(0, posix_spawn_file_actions_init(&fa));
272 ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[0]));
273 ASSERT_EQ(0, posix_spawn_file_actions_adddup2(&fa, fds[1], 1));
274 ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[1]));
275
276 ExecTestHelper eth;
277 eth.SetArgs({"cat", path, nullptr});
278 pid_t pid;
279 ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), &fa, sa, eth.GetArgs(), nullptr));
280 ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa));
281
282 ASSERT_EQ(0, close(fds[1]));
283 ASSERT_TRUE(android::base::ReadFdToString(fds[0], content));
284 ASSERT_EQ(0, close(fds[0]));
285 AssertChildExited(pid, 0);
286 }
287
288 struct ProcStat {
289 pid_t pid;
290 pid_t ppid;
291 pid_t pgrp;
292 pid_t sid;
293 };
294
GetChildStat(posix_spawnattr_t * sa,ProcStat * ps)295 static void GetChildStat(posix_spawnattr_t* sa, ProcStat* ps) {
296 std::string content;
297 CatFileToString(sa, "/proc/self/stat", &content);
298
299 ASSERT_EQ(4, sscanf(content.c_str(), "%d (cat) %*c %d %d %d", &ps->pid, &ps->ppid, &ps->pgrp,
300 &ps->sid));
301
302 ASSERT_EQ(getpid(), ps->ppid);
303 }
304
305 struct ProcStatus {
306 uint64_t sigblk;
307 uint64_t sigign;
308 };
309
GetChildStatus(posix_spawnattr_t * sa,ProcStatus * ps)310 static void GetChildStatus(posix_spawnattr_t* sa, ProcStatus* ps) {
311 std::string content;
312 CatFileToString(sa, "/proc/self/status", &content);
313
314 bool saw_blk = false;
315 bool saw_ign = false;
316 for (const auto& line : android::base::Split(content, "\n")) {
317 if (sscanf(line.c_str(), "SigBlk: %" SCNx64, &ps->sigblk) == 1) saw_blk = true;
318 if (sscanf(line.c_str(), "SigIgn: %" SCNx64, &ps->sigign) == 1) saw_ign = true;
319 }
320 ASSERT_TRUE(saw_blk);
321 ASSERT_TRUE(saw_ign);
322 }
323
TEST(spawn,posix_spawn_POSIX_SPAWN_SETSID_clear)324 TEST(spawn, posix_spawn_POSIX_SPAWN_SETSID_clear) {
325 pid_t parent_sid = getsid(0);
326
327 posix_spawnattr_t sa;
328 ASSERT_EQ(0, posix_spawnattr_init(&sa));
329 ASSERT_EQ(0, posix_spawnattr_setflags(&sa, 0));
330
331 ProcStat ps = {};
332 GetChildStat(&sa, &ps);
333 ASSERT_EQ(parent_sid, ps.sid);
334 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
335 }
336
TEST(spawn,posix_spawn_POSIX_SPAWN_SETSID_set)337 TEST(spawn, posix_spawn_POSIX_SPAWN_SETSID_set) {
338 pid_t parent_sid = getsid(0);
339
340 posix_spawnattr_t sa;
341 ASSERT_EQ(0, posix_spawnattr_init(&sa));
342 ASSERT_EQ(0, posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETSID));
343
344 ProcStat ps = {};
345 GetChildStat(&sa, &ps);
346 ASSERT_NE(parent_sid, ps.sid);
347 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
348 }
349
TEST(spawn,posix_spawn_POSIX_SPAWN_SETPGROUP_clear)350 TEST(spawn, posix_spawn_POSIX_SPAWN_SETPGROUP_clear) {
351 pid_t parent_pgrp = getpgrp();
352
353 posix_spawnattr_t sa;
354 ASSERT_EQ(0, posix_spawnattr_init(&sa));
355 ASSERT_EQ(0, posix_spawnattr_setflags(&sa, 0));
356
357 ProcStat ps = {};
358 GetChildStat(&sa, &ps);
359 ASSERT_EQ(parent_pgrp, ps.pgrp);
360 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
361 }
362
TEST(spawn,posix_spawn_POSIX_SPAWN_SETPGROUP_set)363 TEST(spawn, posix_spawn_POSIX_SPAWN_SETPGROUP_set) {
364 pid_t parent_pgrp = getpgrp();
365
366 posix_spawnattr_t sa;
367 ASSERT_EQ(0, posix_spawnattr_init(&sa));
368 ASSERT_EQ(0, posix_spawnattr_setpgroup(&sa, 0));
369 ASSERT_EQ(0, posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETPGROUP));
370
371 ProcStat ps = {};
372 GetChildStat(&sa, &ps);
373 ASSERT_NE(parent_pgrp, ps.pgrp);
374 // Setting pgid 0 means "the same as the caller's pid".
375 ASSERT_EQ(ps.pid, ps.pgrp);
376 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
377 }
378
TEST(spawn,posix_spawn_POSIX_SPAWN_SETSIGMASK)379 TEST(spawn, posix_spawn_POSIX_SPAWN_SETSIGMASK) {
380 // Block SIGBUS in the parent...
381 sigset_t just_SIGBUS;
382 sigemptyset(&just_SIGBUS);
383 sigaddset(&just_SIGBUS, SIGBUS);
384 ASSERT_EQ(0, sigprocmask(SIG_BLOCK, &just_SIGBUS, nullptr));
385
386 posix_spawnattr_t sa;
387 ASSERT_EQ(0, posix_spawnattr_init(&sa));
388
389 // Ask for only SIGALRM to be blocked in the child...
390 sigset_t just_SIGALRM;
391 sigemptyset(&just_SIGALRM);
392 sigaddset(&just_SIGALRM, SIGALRM);
393 ASSERT_EQ(0, posix_spawnattr_setsigmask(&sa, &just_SIGALRM));
394 ASSERT_EQ(0, posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETSIGMASK));
395
396 // Check that's what happens...
397 ProcStatus ps = {};
398 GetChildStatus(&sa, &ps);
399
400 // TIMER_SIGNAL should also be blocked.
401 uint64_t expected_blocked = 0;
402 SignalSetAdd(&expected_blocked, SIGALRM);
403 SignalSetAdd(&expected_blocked, __SIGRTMIN + 0);
404 EXPECT_EQ(expected_blocked, ps.sigblk);
405
406 EXPECT_EQ(static_cast<uint64_t>(0), ps.sigign);
407
408 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
409 }
410
TEST(spawn,posix_spawn_POSIX_SPAWN_SETSIGDEF)411 TEST(spawn, posix_spawn_POSIX_SPAWN_SETSIGDEF) {
412 // Ignore SIGALRM and SIGCONT in the parent...
413 ASSERT_NE(SIG_ERR, signal(SIGALRM, SIG_IGN));
414 ASSERT_NE(SIG_ERR, signal(SIGCONT, SIG_IGN));
415
416 posix_spawnattr_t sa;
417 ASSERT_EQ(0, posix_spawnattr_init(&sa));
418
419 // Ask for SIGALRM to be defaulted in the child...
420 sigset_t just_SIGALRM;
421 sigemptyset(&just_SIGALRM);
422 sigaddset(&just_SIGALRM, SIGALRM);
423
424 ASSERT_EQ(0, posix_spawnattr_setsigdefault(&sa, &just_SIGALRM));
425 ASSERT_EQ(0, posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETSIGDEF));
426
427 // Check that's what happens...
428 ProcStatus ps = {};
429 GetChildStatus(&sa, &ps);
430
431 // TIMER_SIGNAL should be blocked.
432 uint64_t expected_blocked = 0;
433 SignalSetAdd(&expected_blocked, __SIGRTMIN + 0);
434 EXPECT_EQ(expected_blocked, ps.sigblk);
435
436 uint64_t expected_ignored = 0;
437 SignalSetAdd(&expected_ignored, SIGCONT);
438 EXPECT_EQ(expected_ignored, ps.sigign);
439
440 ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
441 }
442
TEST(spawn,signal_stress)443 TEST(spawn, signal_stress) {
444 // Ensure that posix_spawn doesn't restore the caller's signal mask in the
445 // child without first defaulting any caught signals (http://b/68707996).
446 static pid_t parent = getpid();
447
448 setpgid(0, 0);
449 signal(SIGRTMIN, SIG_IGN);
450
451 pid_t pid = fork();
452 ASSERT_NE(-1, pid);
453
454 if (pid == 0) {
455 for (size_t i = 0; i < 1024; ++i) {
456 kill(0, SIGRTMIN);
457 usleep(10);
458 }
459 _exit(99);
460 }
461
462 // We test both with and without attributes, because they used to be
463 // different codepaths. We also test with an empty `sigdefault` set.
464 posix_spawnattr_t attr1;
465 posix_spawnattr_init(&attr1);
466
467 sigset_t empty_mask = {};
468 posix_spawnattr_t attr2;
469 posix_spawnattr_init(&attr2);
470 posix_spawnattr_setflags(&attr2, POSIX_SPAWN_SETSIGDEF);
471 posix_spawnattr_setsigdefault(&attr2, &empty_mask);
472
473 posix_spawnattr_t* attrs[] = { nullptr, &attr1, &attr2 };
474
475 // We use a real-time signal because that's a tricky case for LP32
476 // because our sigset_t was too small.
477 ScopedSignalHandler ssh(SIGRTMIN, [](int) { ASSERT_EQ(getpid(), parent); });
478
479 const size_t pid_count = 128;
480 pid_t spawned_pids[pid_count];
481
482 ExecTestHelper eth;
483 eth.SetArgs({"true", nullptr});
484 for (size_t i = 0; i < pid_count; ++i) {
485 pid_t spawned_pid;
486 ASSERT_EQ(0, posix_spawn(&spawned_pid, "true", nullptr, attrs[i % 3], eth.GetArgs(), nullptr));
487 spawned_pids[i] = spawned_pid;
488 }
489
490 for (pid_t spawned_pid : spawned_pids) {
491 ASSERT_EQ(spawned_pid, TEMP_FAILURE_RETRY(waitpid(spawned_pid, nullptr, 0)));
492 }
493
494 AssertChildExited(pid, 99);
495 }
496