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 2024 Oxide Computer Company
14 */
15
16 /*
17 * This tests various behaviors of execvpe to try and verify that it is working
18 * as expected.
19 */
20
21 #include <stdlib.h>
22 #include <err.h>
23 #include <unistd.h>
24 #include <stdbool.h>
25 #include <errno.h>
26 #include <sys/sysmacros.h>
27 #include <sys/fork.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <stdio.h>
31
32 typedef struct {
33 const char *et_desc;
34 const char *et_prog;
35 const char *et_path;
36 bool et_pass;
37 int et_errno;
38 } execvpe_test_t;
39
40 static const execvpe_test_t execvpe_tests[] = {
41 { .et_desc = "execute ls on default path", .et_prog = "ls",
42 .et_path = NULL, .et_pass = true },
43 { .et_desc = "execute ls on specified path (1)", .et_prog = "ls",
44 .et_path = "/usr/bin", .et_pass = true },
45 { .et_desc = "execute ls on specified path (2)", .et_prog = "ls",
46 .et_path = "/usr/lib/fm/topo/maps:/enoent:/usr/bin",
47 .et_pass = true },
48 { .et_desc = "fail to find ls on path", .et_prog = "ls",
49 .et_path = "/usr/lib/mdb/raw", .et_pass = false,
50 .et_errno = ENOENT },
51 { .et_desc = "fail to find program on default path",
52 .et_prog = "()theroadgoeseveronandon?!@#$%,downfromthedoor",
53 .et_path = NULL, .et_pass = false, .et_errno = ENOENT },
54 { .et_desc = "shell executes script without #!",
55 .et_prog = "execvpe-script",
56 .et_path = "/opt/os-tests/tests/execvpe", .et_pass = true },
57 { .et_desc = "properly fail with non-executable file",
58 .et_prog = "execvpe-noperm",
59 .et_path = "/opt/os-tests/tests/execvpe", .et_pass = false,
60 .et_errno = EACCES },
61 { .et_desc = "absolute path works if not in PATH",
62 .et_prog = "/usr/bin/true", .et_path = "/usr/lib", .et_pass = true }
63 };
64
65 static bool
execvpe_test_one(const execvpe_test_t * test)66 execvpe_test_one(const execvpe_test_t *test)
67 {
68 pid_t pid, wpid;
69 int stat;
70 const char *envp[4];
71 const char *argv[2];
72
73 if (test->et_path != NULL) {
74 if (setenv("PATH", test->et_path, 1) != 0) {
75 err(EXIT_FAILURE, "TEST FAILED: %s: fatal error: "
76 "failed to set PATH", test->et_desc);
77 }
78 } else {
79 if (unsetenv("PATH") != 0) {
80 err(EXIT_FAILURE, "TEST FAILED: %s: fatal error: "
81 "failed to unset PATH", test->et_desc);
82 }
83 }
84
85 envp[0] = "PATH=/this/should/not/interfere:/with/the/test";
86 envp[1] = "EXECVPE_TEST=Keep it secret, keep it safe!";
87 envp[2] = "WHOAMI=gandalf";
88 envp[3] = NULL;
89
90 argv[0] = test->et_prog;
91 argv[1] = NULL;
92
93 pid = forkx(FORK_NOSIGCHLD | FORK_WAITPID);
94 if (pid == 0) {
95 int ret = EXIT_SUCCESS, e;
96 (void) execvpe(test->et_prog, (char * const *)argv,
97 (char *const *)envp);
98 e = errno;
99 if (test->et_pass) {
100 warnc(e, "TEST FAILED: %s: expected execvpe success, "
101 "but no such luck", test->et_desc);
102 ret = EXIT_FAILURE;
103 } else if (test->et_errno != e) {
104 warnx("TEST FAILED: %s: execvpe failed with errno %d, "
105 "expected %d", test->et_desc, e, test->et_errno);
106 ret = EXIT_FAILURE;
107 }
108 _exit(ret);
109 }
110
111 wpid = waitpid(pid, &stat, 0);
112 if (wpid != pid) {
113 errx(EXIT_FAILURE, "TEST FAILED: %s: encountered fatal error "
114 "waitpid returned wrong pid: %" _PRIdID ", expected "
115 "%" _PRIdID, test->et_desc, wpid, pid);
116 }
117
118 if (stat == EXIT_SUCCESS) {
119 (void) printf("TEST PASSED: %s\n", test->et_desc);
120 return (true);
121 }
122
123 return (false);
124 }
125
126 int
main(void)127 main(void)
128 {
129 int ret = EXIT_SUCCESS;
130
131 for (size_t i = 0; i < ARRAY_SIZE(execvpe_tests); i++) {
132 if (!execvpe_test_one(&execvpe_tests[i]))
133 ret = EXIT_FAILURE;
134 }
135
136 if (ret == EXIT_SUCCESS) {
137 (void) printf("All tests passed successfully!\n");
138 }
139
140 return (ret);
141 }
142