1 /* $NetBSD: t_fileactions.c,v 1.3 2012/02/20 12:22:40 martin 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 33 34 #include <atf-c.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <errno.h> 39 #include <fcntl.h> 40 #include <spawn.h> 41 #include <unistd.h> 42 #include <sys/wait.h> 43 44 45 ATF_TC(t_spawn_openmode); 46 47 ATF_TC_HEAD(t_spawn_openmode, tc) 48 { 49 atf_tc_set_md_var(tc, "descr", 50 "Test the proper handling of 'mode' for 'open' fileactions"); 51 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 52 } 53 54 static off_t 55 filesize(const char * restrict fname) 56 { 57 struct stat st; 58 int err; 59 60 err = stat(fname, &st); 61 ATF_REQUIRE(err == 0); 62 return st.st_size; 63 } 64 65 #define TESTFILE "./the_input_data" 66 #define CHECKFILE "./the_output_data" 67 #define TESTCONTENT "marry has a little lamb" 68 69 static void 70 make_testfile(const char *restrict file) 71 { 72 FILE *f; 73 size_t written; 74 75 f = fopen(file, "w"); 76 ATF_REQUIRE(f != NULL); 77 written = fwrite(TESTCONTENT, 1, strlen(TESTCONTENT), f); 78 fclose(f); 79 ATF_REQUIRE(written == strlen(TESTCONTENT)); 80 } 81 82 static void 83 empty_outfile(const char *restrict filename) 84 { 85 FILE *f; 86 87 f = fopen(filename, "w"); 88 ATF_REQUIRE(f != NULL); 89 fclose(f); 90 } 91 92 ATF_TC_BODY(t_spawn_openmode, tc) 93 { 94 int status, err; 95 pid_t pid; 96 size_t insize, outsize; 97 char * const args[2] = { __UNCONST("cat"), NULL }; 98 posix_spawn_file_actions_t fa; 99 100 /* 101 * try a "cat < testfile > checkfile" 102 */ 103 make_testfile(TESTFILE); 104 unlink(CHECKFILE); 105 106 posix_spawn_file_actions_init(&fa); 107 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 108 TESTFILE, O_RDONLY, 0); 109 posix_spawn_file_actions_addopen(&fa, fileno(stdout), 110 CHECKFILE, O_WRONLY|O_CREAT, 0600); 111 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 112 posix_spawn_file_actions_destroy(&fa); 113 114 ATF_REQUIRE(err == 0); 115 116 /* ok, wait for the child to finish */ 117 waitpid(pid, &status, 0); 118 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 119 120 /* now check that input and output have the same size */ 121 insize = filesize(TESTFILE); 122 outsize = filesize(CHECKFILE); 123 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 124 ATF_REQUIRE(insize == outsize); 125 126 /* 127 * try a "cat < testfile >> checkfile" 128 */ 129 make_testfile(TESTFILE); 130 make_testfile(CHECKFILE); 131 132 posix_spawn_file_actions_init(&fa); 133 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 134 TESTFILE, O_RDONLY, 0); 135 posix_spawn_file_actions_addopen(&fa, fileno(stdout), 136 CHECKFILE, O_WRONLY|O_APPEND, 0); 137 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 138 posix_spawn_file_actions_destroy(&fa); 139 140 ATF_REQUIRE(err == 0); 141 142 /* ok, wait for the child to finish */ 143 waitpid(pid, &status, 0); 144 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 145 146 /* now check that output is twice as long as input */ 147 insize = filesize(TESTFILE); 148 outsize = filesize(CHECKFILE); 149 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 150 ATF_REQUIRE(insize*2 == outsize); 151 152 /* 153 * try a "cat < testfile > checkfile" with input and output swapped 154 */ 155 make_testfile(TESTFILE); 156 empty_outfile(CHECKFILE); 157 158 posix_spawn_file_actions_init(&fa); 159 posix_spawn_file_actions_addopen(&fa, fileno(stdout), 160 TESTFILE, O_RDONLY, 0); 161 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 162 CHECKFILE, O_WRONLY, 0); 163 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 164 posix_spawn_file_actions_destroy(&fa); 165 166 ATF_REQUIRE(err == 0); 167 168 /* ok, wait for the child to finish */ 169 waitpid(pid, &status, 0); 170 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_FAILURE); 171 172 /* now check that input and output are still the same size */ 173 insize = filesize(TESTFILE); 174 outsize = filesize(CHECKFILE); 175 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 176 ATF_REQUIRE(outsize == 0); 177 } 178 179 ATF_TC(t_spawn_reopen); 180 181 ATF_TC_HEAD(t_spawn_reopen, tc) 182 { 183 atf_tc_set_md_var(tc, "descr", 184 "an open filehandle can be replaced by a 'open' fileaction"); 185 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 186 } 187 188 ATF_TC_BODY(t_spawn_reopen, tc) 189 { 190 int status, err; 191 pid_t pid; 192 char * const args[2] = { __UNCONST("cat"), NULL }; 193 posix_spawn_file_actions_t fa; 194 195 /* 196 * make sure stdin is open in the parent 197 */ 198 freopen("/dev/zero", "r", stdin); 199 /* 200 * now request an open for this fd again in the child 201 */ 202 posix_spawn_file_actions_init(&fa); 203 posix_spawn_file_actions_addopen(&fa, fileno(stdin), 204 "/dev/null", O_RDONLY, 0); 205 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 206 posix_spawn_file_actions_destroy(&fa); 207 208 ATF_REQUIRE(err == 0); 209 210 waitpid(pid, &status, 0); 211 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 212 } 213 214 ATF_TC(t_spawn_open_nonexistent); 215 216 ATF_TC_HEAD(t_spawn_open_nonexistent, tc) 217 { 218 atf_tc_set_md_var(tc, "descr", 219 "posix_spawn fails when a file to open does not exist"); 220 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 221 } 222 223 ATF_TC_BODY(t_spawn_open_nonexistent, tc) 224 { 225 int err; 226 pid_t pid; 227 char * const args[2] = { __UNCONST("cat"), NULL }; 228 posix_spawn_file_actions_t fa; 229 230 posix_spawn_file_actions_init(&fa); 231 posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, 232 "./non/ex/ist/ent", O_RDONLY, 0); 233 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 234 ATF_REQUIRE(err == ENOENT); 235 posix_spawn_file_actions_destroy(&fa); 236 } 237 238 ATF_TC(t_spawn_fileactions); 239 240 ATF_TC_HEAD(t_spawn_fileactions, tc) 241 { 242 atf_tc_set_md_var(tc, "descr", 243 "Tests various complex fileactions"); 244 } 245 246 ATF_TC_BODY(t_spawn_fileactions, tc) 247 { 248 int fd1, fd2, fd3, status, err; 249 pid_t pid; 250 char * const args[2] = { __UNCONST("h_fileactions"), NULL }; 251 char helper[FILENAME_MAX]; 252 posix_spawn_file_actions_t fa; 253 254 posix_spawn_file_actions_init(&fa); 255 256 closefrom(fileno(stderr)+1); 257 258 fd1 = open("/dev/null", O_RDONLY); 259 ATF_REQUIRE(fd1 == 3); 260 261 fd2 = open("/dev/null", O_WRONLY, O_CLOEXEC); 262 ATF_REQUIRE(fd2 == 4); 263 264 fd3 = open("/dev/null", O_WRONLY); 265 ATF_REQUIRE(fd3 == 5); 266 267 posix_spawn_file_actions_addclose(&fa, fd1); 268 posix_spawn_file_actions_addopen(&fa, 6, "/dev/null", O_RDWR, 0); 269 posix_spawn_file_actions_adddup2(&fa, 1, 7); 270 271 snprintf(helper, sizeof helper, "%s/h_fileactions", 272 atf_tc_get_config_var(tc, "srcdir")); 273 err = posix_spawn(&pid, helper, &fa, NULL, args, NULL); 274 posix_spawn_file_actions_destroy(&fa); 275 276 ATF_REQUIRE(err == 0); 277 278 waitpid(pid, &status, 0); 279 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 280 } 281 282 ATF_TC(t_spawn_empty_fileactions); 283 284 ATF_TC_HEAD(t_spawn_empty_fileactions, tc) 285 { 286 atf_tc_set_md_var(tc, "descr", 287 "posix_spawn with empty fileactions (PR kern/46038)"); 288 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 289 } 290 291 ATF_TC_BODY(t_spawn_empty_fileactions, tc) 292 { 293 int status, err; 294 pid_t pid; 295 char * const args[2] = { __UNCONST("cat"), NULL }; 296 posix_spawn_file_actions_t fa; 297 size_t insize, outsize; 298 299 /* 300 * try a "cat < testfile > checkfile", but set up stdin/stdout 301 * already in the parent and pass empty file actions to the child. 302 */ 303 make_testfile(TESTFILE); 304 unlink(CHECKFILE); 305 306 freopen(TESTFILE, "r", stdin); 307 freopen(CHECKFILE, "w", stdout); 308 309 posix_spawn_file_actions_init(&fa); 310 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 311 posix_spawn_file_actions_destroy(&fa); 312 313 ATF_REQUIRE(err == 0); 314 315 /* ok, wait for the child to finish */ 316 waitpid(pid, &status, 0); 317 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 318 319 /* now check that input and output have the same size */ 320 insize = filesize(TESTFILE); 321 outsize = filesize(CHECKFILE); 322 ATF_REQUIRE(insize == strlen(TESTCONTENT)); 323 ATF_REQUIRE(insize == outsize); 324 } 325 326 ATF_TP_ADD_TCS(tp) 327 { 328 ATF_TP_ADD_TC(tp, t_spawn_fileactions); 329 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent); 330 ATF_TP_ADD_TC(tp, t_spawn_reopen); 331 ATF_TP_ADD_TC(tp, t_spawn_openmode); 332 ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions); 333 334 return atf_no_error(); 335 } 336