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 struct stat st; 33 char path[PATH_MAX], path2[PATH_MAX], *exec_path; 34 ssize_t len; 35 static const char * const devel_dirs[] = { 36 "./tools/perf/tests/shell", 37 "./tests/shell", 38 "./source/tests/shell" 39 }; 40 int fd; 41 char *p; 42 43 for (size_t i = 0; i < ARRAY_SIZE(devel_dirs); ++i) { 44 fd = open(devel_dirs[i], O_PATH); 45 46 if (fd >= 0) 47 return fd; 48 } 49 50 /* Use directory of executable */ 51 len = readlink("/proc/self/exe", path2, sizeof(path2) - 1); 52 if (len < 0) 53 return -1; 54 path2[len] = '\0'; 55 /* Follow another level of symlink if there */ 56 if (lstat(path2, &st) == 0 && (st.st_mode & S_IFMT) == S_IFLNK) { 57 scnprintf(path, sizeof(path), "%s", path2); 58 len = readlink(path, path2, sizeof(path2) - 1); 59 if (len < 0) 60 return -1; 61 path2[len] = '\0'; 62 } 63 /* Get directory */ 64 p = strrchr(path2, '/'); 65 if (p) 66 *p = 0; 67 scnprintf(path, sizeof(path), "%s/tests/shell", path2); 68 fd = open(path, O_PATH); 69 if (fd >= 0) 70 return fd; 71 scnprintf(path, sizeof(path), "%s/source/tests/shell", path2); 72 fd = open(path, O_PATH); 73 if (fd >= 0) 74 return fd; 75 76 /* Then installed path. */ 77 exec_path = get_argv_exec_path(); 78 scnprintf(path, sizeof(path), "%s/tests/shell", exec_path); 79 free(exec_path); 80 return open(path, O_PATH); 81 } 82 83 static char *shell_test__description(int dir_fd, const char *name) 84 { 85 struct io io; 86 char buf[128], *line = NULL; 87 size_t line_len = 0; 88 ssize_t len; 89 char *desc = NULL; 90 const char *spdx = "SPDX-License"; 91 92 io__init(&io, openat(dir_fd, name, O_RDONLY), buf, sizeof(buf)); 93 if (io.fd < 0) 94 return NULL; 95 96 while ((len = io__getline(&io, &line, &line_len)) > 0) { 97 char *p = line; 98 99 /* Skip leading whitespace */ 100 while (*p && isspace(*p)) 101 p++; 102 103 /* Must be a comment */ 104 if (*p != '#') 105 continue; 106 p++; 107 108 /* Skip shebang or SPDX lines */ 109 if (*p == '!' || (strstr(p, spdx) && strstr(p, "-Identifier:"))) 110 continue; 111 112 /* Skip whitespace after # */ 113 while (*p && isspace(*p)) 114 p++; 115 116 /* If we found non-empty text, this is the description! */ 117 if (*p && *p != '\n') { 118 char *end = p + strlen(p); 119 120 while (end > p && isspace(end[-1])) 121 end--; 122 *end = '\0'; 123 desc = strdup(p); 124 break; 125 } 126 } 127 free(line); 128 close(io.fd); 129 return desc; 130 } 131 132 /* Is this full file path a shell script */ 133 static bool is_shell_script(int dir_fd, const char *path) 134 { 135 const char *ext; 136 137 ext = strrchr(path, '.'); 138 if (!ext) 139 return false; 140 if (!strcmp(ext, ".sh")) { /* Has .sh extension */ 141 if (faccessat(dir_fd, path, R_OK | X_OK, 0) == 0) /* Is executable */ 142 return true; 143 } 144 return false; 145 } 146 147 /* Is this file in this dir a shell script (for test purposes) */ 148 static bool is_test_script(int dir_fd, const char *name) 149 { 150 return is_shell_script(dir_fd, name); 151 } 152 153 /* Duplicate a string and fall over and die if we run out of memory */ 154 static char *strdup_check(const char *str) 155 { 156 char *newstr; 157 158 newstr = strdup(str); 159 if (!newstr) { 160 pr_err("Out of memory while duplicating test script string\n"); 161 abort(); 162 } 163 return newstr; 164 } 165 166 static int shell_test__run(struct test_suite *test, int subtest __maybe_unused) 167 { 168 const char *file = test->priv; 169 int err; 170 char *cmd = NULL; 171 172 if (asprintf(&cmd, "%s%s", file, verbose ? " -v" : "") < 0) 173 return TEST_FAIL; 174 err = system(cmd); 175 free(cmd); 176 if (!err) 177 return TEST_OK; 178 179 return WEXITSTATUS(err) == 2 ? TEST_SKIP : TEST_FAIL; 180 } 181 182 static void append_script(int dir_fd, const char *name, char *desc, 183 struct test_suite ***result, 184 size_t *result_sz) 185 { 186 char filename[PATH_MAX], link[128]; 187 struct test_suite *test_suite, **result_tmp; 188 struct test_case *tests; 189 ssize_t len; 190 char *exclusive; 191 192 snprintf(link, sizeof(link), "/proc/%d/fd/%d", getpid(), dir_fd); 193 len = readlink(link, filename, sizeof(filename) - 1); 194 if (len < 0 || (size_t)len > sizeof(filename) - strlen(name) - 2) { 195 pr_err("Failed to readlink %s or path too long", link); 196 return; 197 } 198 filename[len++] = '/'; 199 strcpy(&filename[len], name); 200 201 tests = calloc(2, sizeof(*tests)); 202 if (!tests) { 203 pr_err("Out of memory while building script test suite list\n"); 204 return; 205 } 206 tests[0].name = strdup_check(name); 207 exclusive = strstr(desc, " (exclusive)"); 208 if (exclusive != NULL) { 209 tests[0].exclusive = true; 210 exclusive[0] = '\0'; 211 } 212 tests[0].desc = strdup_check(desc); 213 tests[0].run_case = shell_test__run; 214 test_suite = zalloc(sizeof(*test_suite)); 215 if (!test_suite) { 216 pr_err("Out of memory while building script test suite list\n"); 217 free(tests); 218 return; 219 } 220 test_suite->desc = desc; 221 test_suite->test_cases = tests; 222 test_suite->priv = strdup_check(filename); 223 /* Realloc is good enough, though we could realloc by chunks, not that 224 * anyone will ever measure performance here */ 225 result_tmp = realloc(*result, (*result_sz + 1) * sizeof(*result_tmp)); 226 if (result_tmp == NULL) { 227 pr_err("Out of memory while building script test suite list\n"); 228 free(tests); 229 free(test_suite); 230 return; 231 } 232 /* Add file to end and NULL terminate the struct array */ 233 *result = result_tmp; 234 (*result)[*result_sz] = test_suite; 235 (*result_sz)++; 236 } 237 238 static void append_scripts_in_dir(int dir_fd, 239 struct test_suite ***result, 240 size_t *result_sz) 241 { 242 struct dirent **entlist; 243 struct dirent *ent; 244 int n_dirs, i; 245 246 /* List files, sorted by alpha */ 247 n_dirs = scandirat(dir_fd, ".", &entlist, NULL, alphasort); 248 if (n_dirs == -1) 249 return; 250 for (i = 0; i < n_dirs && (ent = entlist[i]); i++) { 251 int fd; 252 253 if (ent->d_name[0] == '.') 254 continue; /* Skip hidden files */ 255 if (is_test_script(dir_fd, ent->d_name)) { /* It's a test */ 256 char *desc = shell_test__description(dir_fd, ent->d_name); 257 258 if (desc) /* It has a desc line - valid script */ 259 append_script(dir_fd, ent->d_name, desc, result, result_sz); 260 continue; 261 } 262 if (ent->d_type != DT_DIR) { 263 struct stat st; 264 265 if (ent->d_type != DT_UNKNOWN) 266 continue; 267 fstatat(dir_fd, ent->d_name, &st, 0); 268 if (!S_ISDIR(st.st_mode)) 269 continue; 270 } 271 if (strncmp(ent->d_name, "base_", 5) == 0) 272 continue; /* Skip scripts that have a separate driver. */ 273 fd = openat(dir_fd, ent->d_name, O_PATH); 274 append_scripts_in_dir(fd, result, result_sz); 275 close(fd); 276 } 277 for (i = 0; i < n_dirs; i++) /* Clean up */ 278 zfree(&entlist[i]); 279 free(entlist); 280 } 281 282 struct test_suite **create_script_test_suites(void) 283 { 284 struct test_suite **result = NULL, **result_tmp; 285 size_t result_sz = 0; 286 int dir_fd = shell_tests__dir_fd(); /* Walk dir */ 287 288 /* 289 * Append scripts if fd is good, otherwise return a NULL terminated zero 290 * length array. 291 */ 292 if (dir_fd >= 0) 293 append_scripts_in_dir(dir_fd, &result, &result_sz); 294 295 result_tmp = realloc(result, (result_sz + 1) * sizeof(*result_tmp)); 296 if (result_tmp == NULL) { 297 pr_err("Out of memory while building script test suite list\n"); 298 abort(); 299 } 300 /* NULL terminate the test suite array. */ 301 result = result_tmp; 302 result[result_sz] = NULL; 303 if (dir_fd >= 0) 304 close(dir_fd); 305 return result; 306 } 307