1 /* $NetBSD: t_fileactions.c,v 1.7 2021/11/07 15:46:20 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 2012 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Charles Zhang <charles@NetBSD.org> and 9 * Martin Husemann <martin@NetBSD.org>. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 #include <sys/cdefs.h> 33 __RCSID("$NetBSD: t_fileactions.c,v 1.7 2021/11/07 15:46:20 christos Exp $"); 34 35 36 #include <atf-c.h> 37 38 #include <sys/wait.h> 39 #include <sys/stat.h> 40 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <errno.h> 45 #include <fcntl.h> 46 #include <spawn.h> 47 #include <unistd.h> 48 49 #include "fa_spawn_utils.h" 50 51 52 ATF_TC(t_spawn_openmode); 53 54 ATF_TC_HEAD(t_spawn_openmode, tc) 55 { 56 atf_tc_set_md_var(tc, "descr", 57 "Test the proper handling of 'mode' for 'open' fileactions"); 58 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 59 } 60 61 #define TESTFILE "./the_input_data" 62 #define CHECKFILE "./the_output_data" 63 #define TESTCONTENT "marry has a little lamb" 64 65 static void 66 make_testfile(const char *restrict file) 67 { 68 FILE *f; 69 size_t written; 70 71 f = fopen(file, "w"); 72 ATF_REQUIRE(f != NULL); 73 written = fwrite(TESTCONTENT, 1, strlen(TESTCONTENT), f); 74 fclose(f); 75 ATF_REQUIRE(written == strlen(TESTCONTENT)); 76 } 77 78 ATF_TC_BODY(t_spawn_openmode, tc) 79 { 80 int status, err; 81 pid_t pid; 82 size_t insize, outsize; 83 char * const args[2] = { __UNCONST("cat"), NULL }; 84 posix_spawn_file_actions_t fa; 85 86 /* 87 * try a "cat < testfile > checkfile" 88 */ 89 make_testfile(TESTFILE); 90 unlink(CHECKFILE); 91 92 posix_spawn_file_actions_init(&fa); 93 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 94 TESTFILE, O_RDONLY, 0); 95 posix_spawn_file_actions_addopen(&fa, fileno(stdout), 96 CHECKFILE, O_WRONLY|O_CREAT, 0600); 97 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 98 posix_spawn_file_actions_destroy(&fa); 99 100 ATF_REQUIRE(err == 0); 101 102 /* ok, wait for the child to finish */ 103 waitpid(pid, &status, 0); 104 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 105 106 /* now check that input and output have the same size */ 107 insize = filesize(TESTFILE); 108 outsize = filesize(CHECKFILE); 109 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 110 ATF_REQUIRE(insize == outsize); 111 112 /* 113 * try a "cat < testfile >> checkfile" 114 */ 115 make_testfile(TESTFILE); 116 make_testfile(CHECKFILE); 117 118 posix_spawn_file_actions_init(&fa); 119 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 120 TESTFILE, O_RDONLY, 0); 121 posix_spawn_file_actions_addopen(&fa, fileno(stdout), 122 CHECKFILE, O_WRONLY|O_APPEND, 0); 123 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 124 posix_spawn_file_actions_destroy(&fa); 125 126 ATF_REQUIRE(err == 0); 127 128 /* ok, wait for the child to finish */ 129 waitpid(pid, &status, 0); 130 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 131 132 /* now check that output is twice as long as input */ 133 insize = filesize(TESTFILE); 134 outsize = filesize(CHECKFILE); 135 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 136 ATF_REQUIRE(insize*2 == outsize); 137 138 /* 139 * try a "cat < testfile > checkfile" with input and output swapped 140 */ 141 make_testfile(TESTFILE); 142 empty_outfile(CHECKFILE); 143 144 posix_spawn_file_actions_init(&fa); 145 posix_spawn_file_actions_addopen(&fa, fileno(stdout), 146 TESTFILE, O_RDONLY, 0); 147 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 148 CHECKFILE, O_WRONLY, 0); 149 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 150 posix_spawn_file_actions_destroy(&fa); 151 152 ATF_REQUIRE(err == 0); 153 154 /* ok, wait for the child to finish */ 155 waitpid(pid, &status, 0); 156 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_FAILURE); 157 158 /* now check that input and output are still the same size */ 159 insize = filesize(TESTFILE); 160 outsize = filesize(CHECKFILE); 161 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 162 ATF_REQUIRE(outsize == 0); 163 } 164 165 ATF_TC(t_spawn_reopen); 166 167 ATF_TC_HEAD(t_spawn_reopen, tc) 168 { 169 atf_tc_set_md_var(tc, "descr", 170 "an open filehandle can be replaced by a 'open' fileaction"); 171 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 172 } 173 174 ATF_TC_BODY(t_spawn_reopen, tc) 175 { 176 int status, err; 177 pid_t pid; 178 char * const args[2] = { __UNCONST("cat"), NULL }; 179 posix_spawn_file_actions_t fa; 180 181 /* 182 * make sure stdin is open in the parent 183 */ 184 freopen("/dev/zero", "r", stdin); 185 /* 186 * now request an open for this fd again in the child 187 */ 188 posix_spawn_file_actions_init(&fa); 189 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 190 "/dev/null", O_RDONLY, 0); 191 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 192 posix_spawn_file_actions_destroy(&fa); 193 194 ATF_REQUIRE(err == 0); 195 196 waitpid(pid, &status, 0); 197 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 198 } 199 200 ATF_TC(t_spawn_open_nonexistent); 201 202 ATF_TC_HEAD(t_spawn_open_nonexistent, tc) 203 { 204 atf_tc_set_md_var(tc, "descr", 205 "posix_spawn fails when a file to open does not exist"); 206 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 207 } 208 209 ATF_TC_BODY(t_spawn_open_nonexistent, tc) 210 { 211 int err, status; 212 pid_t pid; 213 char * const args[2] = { __UNCONST("cat"), NULL }; 214 posix_spawn_file_actions_t fa; 215 216 posix_spawn_file_actions_init(&fa); 217 posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, 218 "./non/ex/ist/ent", O_RDONLY, 0); 219 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 220 if (err == 0) { 221 /* 222 * The child has been created - it should fail and 223 * return exit code 127 224 */ 225 waitpid(pid, &status, 0); 226 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 127); 227 } else { 228 /* 229 * The error has been noticed early enough, no child has 230 * been run 231 */ 232 ATF_REQUIRE(err == ENOENT); 233 } 234 posix_spawn_file_actions_destroy(&fa); 235 } 236 237 ATF_TC(t_spawn_open_nonexistent_diag); 238 239 ATF_TC_HEAD(t_spawn_open_nonexistent_diag, tc) 240 { 241 atf_tc_set_md_var(tc, "descr", 242 "posix_spawn fails when a file to open does not exist " 243 "and delivers proper diagnostic"); 244 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 245 } 246 247 ATF_TC_BODY(t_spawn_open_nonexistent_diag, tc) 248 { 249 int err; 250 pid_t pid; 251 char * const args[2] = { __UNCONST("cat"), NULL }; 252 posix_spawnattr_t attr; 253 posix_spawn_file_actions_t fa; 254 255 posix_spawnattr_init(&attr); 256 /* 257 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that 258 * will cause a "proper" return value from posix_spawn(2) 259 * instead of a (potential) success there and a 127 exit 260 * status from the child process (c.f. the non-diag variant 261 * of this test). 262 */ 263 posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR); 264 posix_spawn_file_actions_init(&fa); 265 posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, 266 "./non/ex/ist/ent", O_RDONLY, 0); 267 err = posix_spawn(&pid, "/bin/cat", &fa, &attr, args, NULL); 268 ATF_REQUIRE(err == ENOENT); 269 posix_spawn_file_actions_destroy(&fa); 270 posix_spawnattr_destroy(&attr); 271 } 272 273 ATF_TC(t_spawn_fileactions); 274 275 ATF_TC_HEAD(t_spawn_fileactions, tc) 276 { 277 atf_tc_set_md_var(tc, "descr", 278 "Tests various complex fileactions"); 279 } 280 281 ATF_TC_BODY(t_spawn_fileactions, tc) 282 { 283 int fd1, fd2, fd3, status, err; 284 pid_t pid; 285 char * const args[2] = { __UNCONST("h_fileactions"), NULL }; 286 char helper[FILENAME_MAX]; 287 posix_spawn_file_actions_t fa; 288 289 posix_spawn_file_actions_init(&fa); 290 291 closefrom(fileno(stderr)+1); 292 293 fd1 = open("/dev/null", O_RDONLY); 294 ATF_REQUIRE(fd1 == 3); 295 296 fd2 = open("/dev/null", O_WRONLY, O_CLOEXEC); 297 ATF_REQUIRE(fd2 == 4); 298 299 fd3 = open("/dev/null", O_WRONLY); 300 ATF_REQUIRE(fd3 == 5); 301 302 posix_spawn_file_actions_addclose(&fa, fd1); 303 posix_spawn_file_actions_addopen(&fa, 6, "/dev/null", O_RDWR, 0); 304 posix_spawn_file_actions_adddup2(&fa, 1, 7); 305 306 snprintf(helper, sizeof helper, "%s/h_fileactions", 307 atf_tc_get_config_var(tc, "srcdir")); 308 err = posix_spawn(&pid, helper, &fa, NULL, args, NULL); 309 posix_spawn_file_actions_destroy(&fa); 310 311 ATF_REQUIRE(err == 0); 312 313 waitpid(pid, &status, 0); 314 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 315 } 316 317 ATF_TC(t_spawn_empty_fileactions); 318 319 ATF_TC_HEAD(t_spawn_empty_fileactions, tc) 320 { 321 atf_tc_set_md_var(tc, "descr", 322 "posix_spawn with empty fileactions (PR kern/46038)"); 323 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 324 } 325 326 ATF_TC_BODY(t_spawn_empty_fileactions, tc) 327 { 328 int status, err; 329 pid_t pid; 330 char * const args[2] = { __UNCONST("cat"), NULL }; 331 posix_spawn_file_actions_t fa; 332 size_t insize, outsize; 333 334 /* 335 * try a "cat < testfile > checkfile", but set up stdin/stdout 336 * already in the parent and pass empty file actions to the child. 337 */ 338 make_testfile(TESTFILE); 339 unlink(CHECKFILE); 340 341 freopen(TESTFILE, "r", stdin); 342 freopen(CHECKFILE, "w", stdout); 343 344 posix_spawn_file_actions_init(&fa); 345 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 346 posix_spawn_file_actions_destroy(&fa); 347 348 ATF_REQUIRE(err == 0); 349 350 /* ok, wait for the child to finish */ 351 waitpid(pid, &status, 0); 352 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 353 354 /* now check that input and output have the same size */ 355 insize = filesize(TESTFILE); 356 outsize = filesize(CHECKFILE); 357 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 358 ATF_REQUIRE(insize == outsize); 359 } 360 361 ATF_TP_ADD_TCS(tp) 362 { 363 ATF_TP_ADD_TC(tp, t_spawn_fileactions); 364 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent); 365 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent_diag); 366 ATF_TP_ADD_TC(tp, t_spawn_reopen); 367 ATF_TP_ADD_TC(tp, t_spawn_openmode); 368 ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions); 369 370 return atf_no_error(); 371 } 372