1313227Sngie/* $NetBSD: t_fileactions.c,v 1.6 2017/01/10 22:36:29 christos Exp $ */ 2272343Sngie 3272343Sngie/*- 4272343Sngie * Copyright (c) 2012 The NetBSD Foundation, Inc. 5272343Sngie * All rights reserved. 6272343Sngie * 7272343Sngie * This code is derived from software contributed to The NetBSD Foundation 8272343Sngie * by Charles Zhang <charles@NetBSD.org> and 9272343Sngie * Martin Husemann <martin@NetBSD.org>. 10272343Sngie * 11272343Sngie * Redistribution and use in source and binary forms, with or without 12272343Sngie * modification, are permitted provided that the following conditions 13272343Sngie * are met: 14272343Sngie * 1. Redistributions of source code must retain the above copyright 15272343Sngie * notice, this list of conditions and the following disclaimer. 16272343Sngie * 2. Redistributions in binary form must reproduce the above copyright 17272343Sngie * notice, this list of conditions and the following disclaimer in the 18272343Sngie * documentation and/or other materials provided with the distribution. 19272343Sngie * 20272343Sngie * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21272343Sngie * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22272343Sngie * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23272343Sngie * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24272343Sngie * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25272343Sngie * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26272343Sngie * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27272343Sngie * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28272343Sngie * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29272343Sngie * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30272343Sngie * POSSIBILITY OF SUCH DAMAGE. 31272343Sngie */ 32272343Sngie 33272343Sngie 34313227Sngie#include <atf-c.h> 35313227Sngie 36313227Sngie#include <sys/wait.h> 37272914Sngie#include <sys/stat.h> 38313227Sngie 39272343Sngie#include <stdio.h> 40272343Sngie#include <stdlib.h> 41272343Sngie#include <string.h> 42272343Sngie#include <errno.h> 43272343Sngie#include <fcntl.h> 44272343Sngie#include <spawn.h> 45272343Sngie#include <unistd.h> 46272343Sngie 47272343Sngie 48272343SngieATF_TC(t_spawn_openmode); 49272343Sngie 50272343SngieATF_TC_HEAD(t_spawn_openmode, tc) 51272343Sngie{ 52272343Sngie atf_tc_set_md_var(tc, "descr", 53272343Sngie "Test the proper handling of 'mode' for 'open' fileactions"); 54272343Sngie atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 55272343Sngie} 56272343Sngie 57272343Sngiestatic off_t 58272343Sngiefilesize(const char * restrict fname) 59272343Sngie{ 60272343Sngie struct stat st; 61272343Sngie int err; 62272343Sngie 63272343Sngie err = stat(fname, &st); 64272343Sngie ATF_REQUIRE(err == 0); 65272343Sngie return st.st_size; 66272343Sngie} 67272343Sngie 68272343Sngie#define TESTFILE "./the_input_data" 69272343Sngie#define CHECKFILE "./the_output_data" 70272343Sngie#define TESTCONTENT "marry has a little lamb" 71272343Sngie 72272343Sngiestatic void 73272343Sngiemake_testfile(const char *restrict file) 74272343Sngie{ 75272343Sngie FILE *f; 76272343Sngie size_t written; 77272343Sngie 78272343Sngie f = fopen(file, "w"); 79272343Sngie ATF_REQUIRE(f != NULL); 80272343Sngie written = fwrite(TESTCONTENT, 1, strlen(TESTCONTENT), f); 81272343Sngie fclose(f); 82272343Sngie ATF_REQUIRE(written == strlen(TESTCONTENT)); 83272343Sngie} 84272343Sngie 85272343Sngiestatic void 86272343Sngieempty_outfile(const char *restrict filename) 87272343Sngie{ 88272343Sngie FILE *f; 89272343Sngie 90272343Sngie f = fopen(filename, "w"); 91272343Sngie ATF_REQUIRE(f != NULL); 92272343Sngie fclose(f); 93272343Sngie} 94272343Sngie 95272343SngieATF_TC_BODY(t_spawn_openmode, tc) 96272343Sngie{ 97272343Sngie int status, err; 98272343Sngie pid_t pid; 99272343Sngie size_t insize, outsize; 100272343Sngie char * const args[2] = { __UNCONST("cat"), NULL }; 101272343Sngie posix_spawn_file_actions_t fa; 102272343Sngie 103272343Sngie /* 104272343Sngie * try a "cat < testfile > checkfile" 105272343Sngie */ 106272343Sngie make_testfile(TESTFILE); 107272343Sngie unlink(CHECKFILE); 108272343Sngie 109272343Sngie posix_spawn_file_actions_init(&fa); 110272343Sngie posix_spawn_file_actions_addopen(&fa, fileno(stdin), 111272343Sngie TESTFILE, O_RDONLY, 0); 112272343Sngie posix_spawn_file_actions_addopen(&fa, fileno(stdout), 113272343Sngie CHECKFILE, O_WRONLY|O_CREAT, 0600); 114272343Sngie err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 115272343Sngie posix_spawn_file_actions_destroy(&fa); 116272343Sngie 117272343Sngie ATF_REQUIRE(err == 0); 118272343Sngie 119272343Sngie /* ok, wait for the child to finish */ 120272343Sngie waitpid(pid, &status, 0); 121272343Sngie ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 122272343Sngie 123272343Sngie /* now check that input and output have the same size */ 124272343Sngie insize = filesize(TESTFILE); 125272343Sngie outsize = filesize(CHECKFILE); 126272343Sngie ATF_REQUIRE(insize == strlen(TESTCONTENT)); 127272343Sngie ATF_REQUIRE(insize == outsize); 128272343Sngie 129272343Sngie /* 130272343Sngie * try a "cat < testfile >> checkfile" 131272343Sngie */ 132272343Sngie make_testfile(TESTFILE); 133272343Sngie make_testfile(CHECKFILE); 134272343Sngie 135272343Sngie posix_spawn_file_actions_init(&fa); 136272343Sngie posix_spawn_file_actions_addopen(&fa, fileno(stdin), 137272343Sngie TESTFILE, O_RDONLY, 0); 138272343Sngie posix_spawn_file_actions_addopen(&fa, fileno(stdout), 139272343Sngie CHECKFILE, O_WRONLY|O_APPEND, 0); 140272343Sngie err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 141272343Sngie posix_spawn_file_actions_destroy(&fa); 142272343Sngie 143272343Sngie ATF_REQUIRE(err == 0); 144272343Sngie 145272343Sngie /* ok, wait for the child to finish */ 146272343Sngie waitpid(pid, &status, 0); 147272343Sngie ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 148272343Sngie 149272343Sngie /* now check that output is twice as long as input */ 150272343Sngie insize = filesize(TESTFILE); 151272343Sngie outsize = filesize(CHECKFILE); 152272343Sngie ATF_REQUIRE(insize == strlen(TESTCONTENT)); 153272343Sngie ATF_REQUIRE(insize*2 == outsize); 154272343Sngie 155272343Sngie /* 156272343Sngie * try a "cat < testfile > checkfile" with input and output swapped 157272343Sngie */ 158272343Sngie make_testfile(TESTFILE); 159272343Sngie empty_outfile(CHECKFILE); 160272343Sngie 161272343Sngie posix_spawn_file_actions_init(&fa); 162272343Sngie posix_spawn_file_actions_addopen(&fa, fileno(stdout), 163272343Sngie TESTFILE, O_RDONLY, 0); 164272343Sngie posix_spawn_file_actions_addopen(&fa, fileno(stdin), 165272343Sngie CHECKFILE, O_WRONLY, 0); 166272343Sngie err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 167272343Sngie posix_spawn_file_actions_destroy(&fa); 168272343Sngie 169272343Sngie ATF_REQUIRE(err == 0); 170272343Sngie 171272343Sngie /* ok, wait for the child to finish */ 172272343Sngie waitpid(pid, &status, 0); 173272343Sngie ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_FAILURE); 174272343Sngie 175272343Sngie /* now check that input and output are still the same size */ 176272343Sngie insize = filesize(TESTFILE); 177272343Sngie outsize = filesize(CHECKFILE); 178272343Sngie ATF_REQUIRE(insize == strlen(TESTCONTENT)); 179272343Sngie ATF_REQUIRE(outsize == 0); 180272343Sngie} 181272343Sngie 182272343SngieATF_TC(t_spawn_reopen); 183272343Sngie 184272343SngieATF_TC_HEAD(t_spawn_reopen, tc) 185272343Sngie{ 186272343Sngie atf_tc_set_md_var(tc, "descr", 187272343Sngie "an open filehandle can be replaced by a 'open' fileaction"); 188272343Sngie atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 189272343Sngie} 190272343Sngie 191272343SngieATF_TC_BODY(t_spawn_reopen, tc) 192272343Sngie{ 193272343Sngie int status, err; 194272343Sngie pid_t pid; 195272343Sngie char * const args[2] = { __UNCONST("cat"), NULL }; 196272343Sngie posix_spawn_file_actions_t fa; 197272343Sngie 198272343Sngie /* 199272343Sngie * make sure stdin is open in the parent 200272343Sngie */ 201272343Sngie freopen("/dev/zero", "r", stdin); 202272343Sngie /* 203272343Sngie * now request an open for this fd again in the child 204272343Sngie */ 205272343Sngie posix_spawn_file_actions_init(&fa); 206272343Sngie posix_spawn_file_actions_addopen(&fa, fileno(stdin), 207272343Sngie "/dev/null", O_RDONLY, 0); 208272343Sngie err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 209272343Sngie posix_spawn_file_actions_destroy(&fa); 210272343Sngie 211272343Sngie ATF_REQUIRE(err == 0); 212272343Sngie 213272343Sngie waitpid(pid, &status, 0); 214272343Sngie ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 215272343Sngie} 216272343Sngie 217272343SngieATF_TC(t_spawn_open_nonexistent); 218272343Sngie 219272343SngieATF_TC_HEAD(t_spawn_open_nonexistent, tc) 220272343Sngie{ 221272343Sngie atf_tc_set_md_var(tc, "descr", 222272343Sngie "posix_spawn fails when a file to open does not exist"); 223272343Sngie atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 224272343Sngie} 225272343Sngie 226272343SngieATF_TC_BODY(t_spawn_open_nonexistent, tc) 227272343Sngie{ 228272343Sngie int err, status; 229272343Sngie pid_t pid; 230272343Sngie char * const args[2] = { __UNCONST("cat"), NULL }; 231272343Sngie posix_spawn_file_actions_t fa; 232272343Sngie 233272343Sngie posix_spawn_file_actions_init(&fa); 234272343Sngie posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, 235272343Sngie "./non/ex/ist/ent", O_RDONLY, 0); 236272343Sngie err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 237272343Sngie if (err == 0) { 238272343Sngie /* 239272343Sngie * The child has been created - it should fail and 240272343Sngie * return exit code 127 241272343Sngie */ 242272343Sngie waitpid(pid, &status, 0); 243272343Sngie ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 127); 244272343Sngie } else { 245272343Sngie /* 246272343Sngie * The error has been noticed early enough, no child has 247272343Sngie * been run 248272343Sngie */ 249272343Sngie ATF_REQUIRE(err == ENOENT); 250272343Sngie } 251272343Sngie posix_spawn_file_actions_destroy(&fa); 252272343Sngie} 253272343Sngie 254274626Sngie#ifdef __NetBSD__ 255272343SngieATF_TC(t_spawn_open_nonexistent_diag); 256272343Sngie 257272343SngieATF_TC_HEAD(t_spawn_open_nonexistent_diag, tc) 258272343Sngie{ 259272343Sngie atf_tc_set_md_var(tc, "descr", 260272343Sngie "posix_spawn fails when a file to open does not exist " 261272343Sngie "and delivers proper diagnostic"); 262272343Sngie atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 263272343Sngie} 264272343Sngie 265272343SngieATF_TC_BODY(t_spawn_open_nonexistent_diag, tc) 266272343Sngie{ 267272343Sngie int err; 268272343Sngie pid_t pid; 269272343Sngie char * const args[2] = { __UNCONST("cat"), NULL }; 270272343Sngie posix_spawnattr_t attr; 271272343Sngie posix_spawn_file_actions_t fa; 272272343Sngie 273272343Sngie posix_spawnattr_init(&attr); 274272343Sngie /* 275272343Sngie * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that 276272343Sngie * will cause a "proper" return value from posix_spawn(2) 277272343Sngie * instead of a (potential) success there and a 127 exit 278272343Sngie * status from the child process (c.f. the non-diag variant 279272343Sngie * of this test). 280272343Sngie */ 281272343Sngie posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR); 282272343Sngie posix_spawn_file_actions_init(&fa); 283272343Sngie posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, 284272343Sngie "./non/ex/ist/ent", O_RDONLY, 0); 285272343Sngie err = posix_spawn(&pid, "/bin/cat", &fa, &attr, args, NULL); 286272343Sngie ATF_REQUIRE(err == ENOENT); 287272343Sngie posix_spawn_file_actions_destroy(&fa); 288272343Sngie posix_spawnattr_destroy(&attr); 289272343Sngie} 290272914Sngie#endif 291272343Sngie 292272343SngieATF_TC(t_spawn_fileactions); 293272343Sngie 294272343SngieATF_TC_HEAD(t_spawn_fileactions, tc) 295272343Sngie{ 296272343Sngie atf_tc_set_md_var(tc, "descr", 297272343Sngie "Tests various complex fileactions"); 298272343Sngie} 299272343Sngie 300272343SngieATF_TC_BODY(t_spawn_fileactions, tc) 301272343Sngie{ 302272343Sngie int fd1, fd2, fd3, status, err; 303272343Sngie pid_t pid; 304272343Sngie char * const args[2] = { __UNCONST("h_fileactions"), NULL }; 305272343Sngie char helper[FILENAME_MAX]; 306272343Sngie posix_spawn_file_actions_t fa; 307272343Sngie 308272343Sngie posix_spawn_file_actions_init(&fa); 309272343Sngie 310272343Sngie closefrom(fileno(stderr)+1); 311272343Sngie 312272343Sngie fd1 = open("/dev/null", O_RDONLY); 313272343Sngie ATF_REQUIRE(fd1 == 3); 314272343Sngie 315272343Sngie fd2 = open("/dev/null", O_WRONLY, O_CLOEXEC); 316272343Sngie ATF_REQUIRE(fd2 == 4); 317272343Sngie 318272343Sngie fd3 = open("/dev/null", O_WRONLY); 319272343Sngie ATF_REQUIRE(fd3 == 5); 320272343Sngie 321272343Sngie posix_spawn_file_actions_addclose(&fa, fd1); 322272343Sngie posix_spawn_file_actions_addopen(&fa, 6, "/dev/null", O_RDWR, 0); 323272343Sngie posix_spawn_file_actions_adddup2(&fa, 1, 7); 324272343Sngie 325272343Sngie snprintf(helper, sizeof helper, "%s/h_fileactions", 326272343Sngie atf_tc_get_config_var(tc, "srcdir")); 327272343Sngie err = posix_spawn(&pid, helper, &fa, NULL, args, NULL); 328272343Sngie posix_spawn_file_actions_destroy(&fa); 329272343Sngie 330272343Sngie ATF_REQUIRE(err == 0); 331272343Sngie 332272343Sngie waitpid(pid, &status, 0); 333272343Sngie ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 334272343Sngie} 335272343Sngie 336272343SngieATF_TC(t_spawn_empty_fileactions); 337272343Sngie 338272343SngieATF_TC_HEAD(t_spawn_empty_fileactions, tc) 339272343Sngie{ 340272343Sngie atf_tc_set_md_var(tc, "descr", 341272343Sngie "posix_spawn with empty fileactions (PR kern/46038)"); 342272343Sngie atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 343272343Sngie} 344272343Sngie 345272343SngieATF_TC_BODY(t_spawn_empty_fileactions, tc) 346272343Sngie{ 347272343Sngie int status, err; 348272343Sngie pid_t pid; 349272343Sngie char * const args[2] = { __UNCONST("cat"), NULL }; 350272343Sngie posix_spawn_file_actions_t fa; 351272343Sngie size_t insize, outsize; 352272343Sngie 353272343Sngie /* 354272343Sngie * try a "cat < testfile > checkfile", but set up stdin/stdout 355272343Sngie * already in the parent and pass empty file actions to the child. 356272343Sngie */ 357272343Sngie make_testfile(TESTFILE); 358272343Sngie unlink(CHECKFILE); 359272343Sngie 360272343Sngie freopen(TESTFILE, "r", stdin); 361272343Sngie freopen(CHECKFILE, "w", stdout); 362272343Sngie 363272343Sngie posix_spawn_file_actions_init(&fa); 364272343Sngie err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 365272343Sngie posix_spawn_file_actions_destroy(&fa); 366272343Sngie 367272343Sngie ATF_REQUIRE(err == 0); 368272343Sngie 369272343Sngie /* ok, wait for the child to finish */ 370272343Sngie waitpid(pid, &status, 0); 371272343Sngie ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); 372272343Sngie 373272343Sngie /* now check that input and output have the same size */ 374272343Sngie insize = filesize(TESTFILE); 375272343Sngie outsize = filesize(CHECKFILE); 376272343Sngie ATF_REQUIRE(insize == strlen(TESTCONTENT)); 377272343Sngie ATF_REQUIRE(insize == outsize); 378272343Sngie} 379272343Sngie 380272343SngieATF_TP_ADD_TCS(tp) 381272343Sngie{ 382272343Sngie ATF_TP_ADD_TC(tp, t_spawn_fileactions); 383272343Sngie ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent); 384274626Sngie#ifdef __NetBSD__ 385272343Sngie ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent_diag); 386272914Sngie#endif 387272343Sngie ATF_TP_ADD_TC(tp, t_spawn_reopen); 388272343Sngie ATF_TP_ADD_TC(tp, t_spawn_openmode); 389272343Sngie ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions); 390272343Sngie 391272343Sngie return atf_no_error(); 392272343Sngie} 393