1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | #include <dirent.h> |
3 | #include <errno.h> |
4 | #include <fcntl.h> |
5 | #include <linux/ctype.h> |
6 | #include <linux/kernel.h> |
7 | #include <linux/string.h> |
8 | #include <linux/zalloc.h> |
9 | #include <string.h> |
10 | #include <stdlib.h> |
11 | #include <sys/types.h> |
12 | #include <unistd.h> |
13 | #include <subcmd/exec-cmd.h> |
14 | #include <subcmd/parse-options.h> |
15 | #include <sys/wait.h> |
16 | #include <sys/stat.h> |
17 | #include <api/io.h> |
18 | #include "builtin.h" |
19 | #include "tests-scripts.h" |
20 | #include "color.h" |
21 | #include "debug.h" |
22 | #include "hist.h" |
23 | #include "intlist.h" |
24 | #include "string2.h" |
25 | #include "symbol.h" |
26 | #include "tests.h" |
27 | #include "util/rlimit.h" |
28 | #include "util/util.h" |
29 | |
30 | static int shell_tests__dir_fd(void) |
31 | { |
32 | char path[PATH_MAX], *exec_path; |
33 | static const char * const devel_dirs[] = { "./tools/perf/tests/shell" , "./tests/shell" , }; |
34 | |
35 | for (size_t i = 0; i < ARRAY_SIZE(devel_dirs); ++i) { |
36 | int fd = open(devel_dirs[i], O_PATH); |
37 | |
38 | if (fd >= 0) |
39 | return fd; |
40 | } |
41 | |
42 | /* Then installed path. */ |
43 | exec_path = get_argv_exec_path(); |
44 | scnprintf(buf: path, size: sizeof(path), fmt: "%s/tests/shell" , exec_path); |
45 | free(exec_path); |
46 | return open(path, O_PATH); |
47 | } |
48 | |
49 | static char *shell_test__description(int dir_fd, const char *name) |
50 | { |
51 | struct io io; |
52 | char buf[128], desc[256]; |
53 | int ch, pos = 0; |
54 | |
55 | io__init(&io, openat(dir_fd, name, O_RDONLY), buf, sizeof(buf)); |
56 | if (io.fd < 0) |
57 | return NULL; |
58 | |
59 | /* Skip first line - should be #!/bin/sh Shebang */ |
60 | if (io__get_char(&io) != '#') |
61 | goto err_out; |
62 | if (io__get_char(&io) != '!') |
63 | goto err_out; |
64 | do { |
65 | ch = io__get_char(&io); |
66 | if (ch < 0) |
67 | goto err_out; |
68 | } while (ch != '\n'); |
69 | |
70 | do { |
71 | ch = io__get_char(&io); |
72 | if (ch < 0) |
73 | goto err_out; |
74 | } while (ch == '#' || isspace(ch)); |
75 | while (ch > 0 && ch != '\n') { |
76 | desc[pos++] = ch; |
77 | if (pos >= (int)sizeof(desc) - 1) |
78 | break; |
79 | ch = io__get_char(&io); |
80 | } |
81 | while (pos > 0 && isspace(desc[--pos])) |
82 | ; |
83 | desc[++pos] = '\0'; |
84 | close(io.fd); |
85 | return strdup(desc); |
86 | err_out: |
87 | close(io.fd); |
88 | return NULL; |
89 | } |
90 | |
91 | /* Is this full file path a shell script */ |
92 | static bool is_shell_script(int dir_fd, const char *path) |
93 | { |
94 | const char *ext; |
95 | |
96 | ext = strrchr(path, '.'); |
97 | if (!ext) |
98 | return false; |
99 | if (!strcmp(ext, ".sh" )) { /* Has .sh extension */ |
100 | if (faccessat(dir_fd, path, R_OK | X_OK, 0) == 0) /* Is executable */ |
101 | return true; |
102 | } |
103 | return false; |
104 | } |
105 | |
106 | /* Is this file in this dir a shell script (for test purposes) */ |
107 | static bool is_test_script(int dir_fd, const char *name) |
108 | { |
109 | return is_shell_script(dir_fd, path: name); |
110 | } |
111 | |
112 | /* Duplicate a string and fall over and die if we run out of memory */ |
113 | static char *strdup_check(const char *str) |
114 | { |
115 | char *newstr; |
116 | |
117 | newstr = strdup(str); |
118 | if (!newstr) { |
119 | pr_err("Out of memory while duplicating test script string\n" ); |
120 | abort(); |
121 | } |
122 | return newstr; |
123 | } |
124 | |
125 | static int shell_test__run(struct test_suite *test, int subtest __maybe_unused) |
126 | { |
127 | const char *file = test->priv; |
128 | int err; |
129 | char *cmd = NULL; |
130 | |
131 | if (asprintf(&cmd, "%s%s" , file, verbose ? " -v" : "" ) < 0) |
132 | return TEST_FAIL; |
133 | err = system(cmd); |
134 | free(cmd); |
135 | if (!err) |
136 | return TEST_OK; |
137 | |
138 | return WEXITSTATUS(err) == 2 ? TEST_SKIP : TEST_FAIL; |
139 | } |
140 | |
141 | static void append_script(int dir_fd, const char *name, char *desc, |
142 | struct test_suite ***result, |
143 | size_t *result_sz) |
144 | { |
145 | char filename[PATH_MAX], link[128]; |
146 | struct test_suite *test_suite, **result_tmp; |
147 | struct test_case *tests; |
148 | size_t len; |
149 | |
150 | snprintf(buf: link, size: sizeof(link), fmt: "/proc/%d/fd/%d" , getpid(), dir_fd); |
151 | len = readlink(link, filename, sizeof(filename)); |
152 | if (len < 0) { |
153 | pr_err("Failed to readlink %s" , link); |
154 | return; |
155 | } |
156 | filename[len++] = '/'; |
157 | strcpy(p: &filename[len], q: name); |
158 | |
159 | tests = calloc(2, sizeof(*tests)); |
160 | if (!tests) { |
161 | pr_err("Out of memory while building script test suite list\n" ); |
162 | return; |
163 | } |
164 | tests[0].name = strdup_check(str: name); |
165 | tests[0].desc = strdup_check(str: desc); |
166 | tests[0].run_case = shell_test__run; |
167 | |
168 | test_suite = zalloc(sizeof(*test_suite)); |
169 | if (!test_suite) { |
170 | pr_err("Out of memory while building script test suite list\n" ); |
171 | free(tests); |
172 | return; |
173 | } |
174 | test_suite->desc = desc; |
175 | test_suite->test_cases = tests; |
176 | test_suite->priv = strdup_check(str: filename); |
177 | /* Realloc is good enough, though we could realloc by chunks, not that |
178 | * anyone will ever measure performance here */ |
179 | result_tmp = realloc(*result, (*result_sz + 1) * sizeof(*result_tmp)); |
180 | if (result_tmp == NULL) { |
181 | pr_err("Out of memory while building script test suite list\n" ); |
182 | free(tests); |
183 | free(test_suite); |
184 | return; |
185 | } |
186 | /* Add file to end and NULL terminate the struct array */ |
187 | *result = result_tmp; |
188 | (*result)[*result_sz] = test_suite; |
189 | (*result_sz)++; |
190 | } |
191 | |
192 | static void append_scripts_in_dir(int dir_fd, |
193 | struct test_suite ***result, |
194 | size_t *result_sz) |
195 | { |
196 | struct dirent **entlist; |
197 | struct dirent *ent; |
198 | int n_dirs, i; |
199 | |
200 | /* List files, sorted by alpha */ |
201 | n_dirs = scandirat(dir_fd, "." , &entlist, NULL, alphasort); |
202 | if (n_dirs == -1) |
203 | return; |
204 | for (i = 0; i < n_dirs && (ent = entlist[i]); i++) { |
205 | int fd; |
206 | |
207 | if (ent->d_name[0] == '.') |
208 | continue; /* Skip hidden files */ |
209 | if (is_test_script(dir_fd, name: ent->d_name)) { /* It's a test */ |
210 | char *desc = shell_test__description(dir_fd, name: ent->d_name); |
211 | |
212 | if (desc) /* It has a desc line - valid script */ |
213 | append_script(dir_fd, name: ent->d_name, desc, result, result_sz); |
214 | continue; |
215 | } |
216 | if (ent->d_type != DT_DIR) { |
217 | struct stat st; |
218 | |
219 | if (ent->d_type != DT_UNKNOWN) |
220 | continue; |
221 | fstatat(dir_fd, ent->d_name, &st, 0); |
222 | if (!S_ISDIR(st.st_mode)) |
223 | continue; |
224 | } |
225 | fd = openat(dir_fd, ent->d_name, O_PATH); |
226 | append_scripts_in_dir(dir_fd: fd, result, result_sz); |
227 | } |
228 | for (i = 0; i < n_dirs; i++) /* Clean up */ |
229 | zfree(&entlist[i]); |
230 | free(entlist); |
231 | } |
232 | |
233 | struct test_suite **create_script_test_suites(void) |
234 | { |
235 | struct test_suite **result = NULL, **result_tmp; |
236 | size_t result_sz = 0; |
237 | int dir_fd = shell_tests__dir_fd(); /* Walk dir */ |
238 | |
239 | /* |
240 | * Append scripts if fd is good, otherwise return a NULL terminated zero |
241 | * length array. |
242 | */ |
243 | if (dir_fd >= 0) |
244 | append_scripts_in_dir(dir_fd, result: &result, result_sz: &result_sz); |
245 | |
246 | result_tmp = realloc(result, (result_sz + 1) * sizeof(*result_tmp)); |
247 | if (result_tmp == NULL) { |
248 | pr_err("Out of memory while building script test suite list\n" ); |
249 | abort(); |
250 | } |
251 | /* NULL terminate the test suite array. */ |
252 | result = result_tmp; |
253 | result[result_sz] = NULL; |
254 | if (dir_fd >= 0) |
255 | close(dir_fd); |
256 | return result; |
257 | } |
258 | |