1 /*
2 * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved.
3 * Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it would be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Author: Alexey Kodanev <alexey.kodanev@oracle.com>
20 */
21
22 #include <stdio.h>
23 #include <errno.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <sys/wait.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <signal.h>
30 #include "test.h"
31 #include "tst_cmd.h"
32 #include "tst_private.h"
33
34 #define OPEN_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
35 #define OPEN_FLAGS (O_WRONLY | O_APPEND | O_CREAT)
36
tst_cmd_fds_(void (cleanup_fn)(void),const char * const argv[],int stdout_fd,int stderr_fd,enum tst_cmd_flags flags)37 int tst_cmd_fds_(void (cleanup_fn)(void),
38 const char *const argv[],
39 int stdout_fd,
40 int stderr_fd,
41 enum tst_cmd_flags flags)
42 {
43 int rc;
44
45 if (argv == NULL || argv[0] == NULL) {
46 tst_brkm(TBROK, cleanup_fn,
47 "argument list is empty at %s:%d", __FILE__, __LINE__);
48 return -1;
49 }
50
51 /*
52 * The tst_sig() install poisoned signal handlers for all signals the
53 * test is not expected to get.
54 *
55 * So we temporarily disable the handler for sigchild we get after our
56 * child exits so that we don't have to disable it in each test that
57 * uses this interface.
58 */
59 void *old_handler = signal(SIGCHLD, SIG_DFL);
60
61 char path[PATH_MAX];
62
63 if (tst_get_path(argv[0], path, sizeof(path))) {
64 if (flags & TST_CMD_TCONF_ON_MISSING)
65 tst_brkm(TCONF, cleanup_fn, "Couldn't find '%s' in $PATH at %s:%d", argv[0],
66 __FILE__, __LINE__);
67 else
68 return 255;
69 }
70
71 pid_t pid = vfork();
72 if (pid == -1) {
73 tst_brkm(TBROK | TERRNO, cleanup_fn, "vfork failed at %s:%d",
74 __FILE__, __LINE__);
75 return -1;
76 }
77 if (!pid) {
78 /* redirecting stdout and stderr if needed */
79 if (stdout_fd != -1) {
80 close(STDOUT_FILENO);
81 dup2(stdout_fd, STDOUT_FILENO);
82 }
83
84 if (stderr_fd != -1) {
85 close(STDERR_FILENO);
86 dup2(stderr_fd, STDERR_FILENO);
87 }
88
89 execvp(argv[0], (char *const *)argv);
90 _exit(254);
91 }
92
93 int ret = -1;
94 if (waitpid(pid, &ret, 0) != pid) {
95 tst_brkm(TBROK | TERRNO, cleanup_fn, "waitpid failed at %s:%d",
96 __FILE__, __LINE__);
97 return -1;
98 }
99
100 signal(SIGCHLD, old_handler);
101
102 if (!WIFEXITED(ret)) {
103 tst_brkm(TBROK, cleanup_fn, "failed to exec cmd '%s' at %s:%d",
104 argv[0], __FILE__, __LINE__);
105 return -1;
106 }
107
108 rc = WEXITSTATUS(ret);
109
110 if (!(flags & TST_CMD_PASS_RETVAL) && rc) {
111 tst_brkm(TBROK, cleanup_fn,
112 "'%s' exited with a non-zero code %d at %s:%d",
113 argv[0], rc, __FILE__, __LINE__);
114 return -1;
115 }
116
117 return rc;
118 }
119
tst_cmd_(void (cleanup_fn)(void),const char * const argv[],const char * stdout_path,const char * stderr_path,enum tst_cmd_flags flags)120 int tst_cmd_(void (cleanup_fn)(void),
121 const char *const argv[],
122 const char *stdout_path,
123 const char *stderr_path,
124 enum tst_cmd_flags flags)
125 {
126 int stdout_fd = -1;
127 int stderr_fd = -1;
128 int rc;
129
130 if (stdout_path != NULL) {
131 stdout_fd = open(stdout_path,
132 OPEN_FLAGS, OPEN_MODE);
133
134 if (stdout_fd == -1)
135 tst_resm(TWARN | TERRNO,
136 "open() on %s failed at %s:%d",
137 stdout_path, __FILE__, __LINE__);
138 }
139
140 if (stderr_path != NULL) {
141 stderr_fd = open(stderr_path,
142 OPEN_FLAGS, OPEN_MODE);
143
144 if (stderr_fd == -1)
145 tst_resm(TWARN | TERRNO,
146 "open() on %s failed at %s:%d",
147 stderr_path, __FILE__, __LINE__);
148 }
149
150 rc = tst_cmd_fds(cleanup_fn, argv, stdout_fd, stderr_fd, flags);
151
152 if ((stdout_fd != -1) && (close(stdout_fd) == -1))
153 tst_resm(TWARN | TERRNO,
154 "close() on %s failed at %s:%d",
155 stdout_path, __FILE__, __LINE__);
156
157 if ((stderr_fd != -1) && (close(stderr_fd) == -1))
158 tst_resm(TWARN | TERRNO,
159 "close() on %s failed at %s:%d",
160 stderr_path, __FILE__, __LINE__);
161
162 return rc;
163 }
164
tst_system(const char * command)165 int tst_system(const char *command)
166 {
167 int ret = 0;
168
169 /*
170 *Temporarily disable SIGCHLD of user defined handler, so the
171 *system(3) function will not cause unexpected SIGCHLD signal
172 *callback function for test cases.
173 */
174 void *old_handler = signal(SIGCHLD, SIG_DFL);
175
176 ret = system(command);
177
178 signal(SIGCHLD, old_handler);
179 return ret;
180 }
181
mkfs_ext4_version_parser(void)182 static int mkfs_ext4_version_parser(void)
183 {
184 FILE *f;
185 int rc, major, minor, patch;
186
187 f = popen("mkfs.ext4 -V 2>&1", "r");
188 if (!f) {
189 tst_resm(TWARN, "Could not run mkfs.ext4 -V 2>&1 cmd");
190 return -1;
191 }
192
193 rc = fscanf(f, "mke2fs %d.%d.%d", &major, &minor, &patch);
194 pclose(f);
195 if (rc != 3) {
196 tst_resm(TWARN, "Unable to parse mkfs.ext4 version");
197 return -1;
198 }
199
200 return major * 10000 + minor * 100 + patch;
201 }
202
mkfs_generic_version_table_get(char * version)203 static int mkfs_generic_version_table_get(char *version)
204 {
205 int major, minor, patch;
206 int len;
207
208 if (sscanf(version, "%u.%u.%u %n", &major, &minor, &patch, &len) != 3) {
209 tst_resm(TWARN, "Illegal version(%s), should use format like 1.43.0", version);
210 return -1;
211 }
212
213 if (len != (int)strlen(version)) {
214 tst_resm(TWARN, "Grabage after version");
215 return -1;
216 }
217
218 return major * 10000 + minor * 100 + patch;
219 }
220
mkfs_xfs_version_parser(void)221 static int mkfs_xfs_version_parser(void)
222 {
223 FILE *f;
224 int rc, major, minor, patch;
225
226 f = popen("mkfs.xfs -V 2>&1", "r");
227 if (!f) {
228 tst_resm(TWARN, "Could not run mkfs.xfs -V 2>&1 cmd");
229 return -1;
230 }
231
232 rc = fscanf(f, "mkfs.xfs version %d.%d.%d", &major, &minor, &patch);
233 pclose(f);
234 if (rc != 3) {
235 tst_resm(TWARN, "Unable to parse mkfs.xfs version");
236 return -1;
237 }
238
239 return major * 10000 + minor * 100 + patch;
240 }
241
242 static struct version_parser {
243 const char *cmd;
244 int (*parser)(void);
245 int (*table_get)(char *version);
246 } version_parsers[] = {
247 {"mkfs.ext4", mkfs_ext4_version_parser, mkfs_generic_version_table_get},
248 {"mkfs.xfs", mkfs_xfs_version_parser, mkfs_generic_version_table_get},
249 {},
250 };
251
tst_check_cmd(const char * cmd,const int brk_nosupp)252 int tst_check_cmd(const char *cmd, const int brk_nosupp)
253 {
254 struct version_parser *p;
255 char *cmd_token, *op_token, *version_token, *next, *str;
256 char path[PATH_MAX];
257 char parser_cmd[100];
258 int ver_parser, ver_get;
259
260 strcpy(parser_cmd, cmd);
261
262 cmd_token = strtok_r(parser_cmd, " ", &next);
263 op_token = strtok_r(NULL, " ", &next);
264 version_token = strtok_r(NULL, " ", &next);
265 str = strtok_r(NULL, " ", &next);
266
267 if (tst_get_path(cmd_token, path, sizeof(path)))
268 tst_brkm(TCONF, NULL, "Couldn't find '%s' in $PATH", cmd_token);
269
270 if (!op_token)
271 return 0;
272
273 if (!version_token || str) {
274 tst_brkm(TCONF, NULL,
275 "Illegal format(%s), should use format like mkfs.ext4 >= 1.43.0",
276 cmd);
277 }
278
279 for (p = &version_parsers[0]; p->cmd; p++) {
280 if (!strcmp(p->cmd, cmd_token)) {
281 tst_resm(TINFO, "Parsing %s version", p->cmd);
282 break;
283 }
284 }
285
286 if (!p->cmd) {
287 tst_brkm(TBROK, NULL, "No version parser for %s implemented!",
288 cmd_token);
289 }
290
291 ver_parser = p->parser();
292 if (ver_parser < 0)
293 tst_brkm(TBROK, NULL, "Failed to parse %s version", p->cmd);
294
295 ver_get = p->table_get(version_token);
296 if (ver_get < 0)
297 tst_brkm(TBROK, NULL, "Failed to get %s version", p->cmd);
298
299 if (!strcmp(op_token, ">=")) {
300 if (ver_parser < ver_get)
301 goto error;
302 } else if (!strcmp(op_token, ">")) {
303 if (ver_parser <= ver_get)
304 goto error;
305 } else if (!strcmp(op_token, "<=")) {
306 if (ver_parser > ver_get)
307 goto error;
308 } else if (!strcmp(op_token, "<")) {
309 if (ver_parser >= ver_get)
310 goto error;
311 } else if (!strcmp(op_token, "==")) {
312 if (ver_parser != ver_get)
313 goto error;
314 } else if (!strcmp(op_token, "!=")) {
315 if (ver_parser == ver_get)
316 goto error;
317 } else {
318 tst_brkm(TCONF, NULL, "Invalid op(%s)", op_token);
319 }
320
321 return 0;
322 error:
323 if (brk_nosupp) {
324 tst_brkm(TCONF, NULL, "%s requires %s %d, but got %d",
325 cmd, op_token, ver_get, ver_parser);
326 } else {
327 tst_resm(TCONF, "%s requires %s %d, but got %d",
328 cmd, op_token, ver_get, ver_parser);
329 }
330
331 return 1;
332 }
333