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
37 enum cmd_op {
38 OP_GE, /* >= */
39 OP_GT, /* > */
40 OP_LE, /* <= */
41 OP_LT, /* < */
42 OP_EQ, /* == */
43 OP_NE, /* != */
44 };
45
46
tst_cmd_fds_(void (cleanup_fn)(void),const char * const argv[],int stdout_fd,int stderr_fd,enum tst_cmd_flags flags)47 int tst_cmd_fds_(void (cleanup_fn)(void),
48 const char *const argv[],
49 int stdout_fd,
50 int stderr_fd,
51 enum tst_cmd_flags flags)
52 {
53 int rc;
54
55 if (argv == NULL || argv[0] == NULL) {
56 tst_brkm(TBROK, cleanup_fn,
57 "argument list is empty at %s:%d", __FILE__, __LINE__);
58 return -1;
59 }
60
61 /*
62 * The tst_sig() install poisoned signal handlers for all signals the
63 * test is not expected to get.
64 *
65 * So we temporarily disable the handler for sigchild we get after our
66 * child exits so that we don't have to disable it in each test that
67 * uses this interface.
68 */
69 void *old_handler = signal(SIGCHLD, SIG_DFL);
70
71 char path[PATH_MAX];
72
73 if (tst_get_path(argv[0], path, sizeof(path))) {
74 if (flags & TST_CMD_TCONF_ON_MISSING)
75 tst_brkm(TCONF, cleanup_fn, "Couldn't find '%s' in $PATH at %s:%d", argv[0],
76 __FILE__, __LINE__);
77 else
78 return 255;
79 }
80
81 pid_t pid = vfork();
82 if (pid == -1) {
83 tst_brkm(TBROK | TERRNO, cleanup_fn, "vfork failed at %s:%d",
84 __FILE__, __LINE__);
85 return -1;
86 }
87 if (!pid) {
88 /* redirecting stdout and stderr if needed */
89 if (stdout_fd != -1) {
90 close(STDOUT_FILENO);
91 dup2(stdout_fd, STDOUT_FILENO);
92 }
93
94 if (stderr_fd != -1) {
95 close(STDERR_FILENO);
96 dup2(stderr_fd, STDERR_FILENO);
97 }
98
99 execvp(argv[0], (char *const *)argv);
100 _exit(254);
101 }
102
103 int ret = -1;
104 if (waitpid(pid, &ret, 0) != pid) {
105 tst_brkm(TBROK | TERRNO, cleanup_fn, "waitpid failed at %s:%d",
106 __FILE__, __LINE__);
107 return -1;
108 }
109
110 signal(SIGCHLD, old_handler);
111
112 if (!WIFEXITED(ret)) {
113 tst_brkm(TBROK, cleanup_fn, "failed to exec cmd '%s' at %s:%d",
114 argv[0], __FILE__, __LINE__);
115 return -1;
116 }
117
118 rc = WEXITSTATUS(ret);
119
120 if (!(flags & TST_CMD_PASS_RETVAL) && rc) {
121 tst_brkm(TBROK, cleanup_fn,
122 "'%s' exited with a non-zero code %d at %s:%d",
123 argv[0], rc, __FILE__, __LINE__);
124 return -1;
125 }
126
127 return rc;
128 }
129
tst_cmd_(void (cleanup_fn)(void),const char * const argv[],const char * stdout_path,const char * stderr_path,enum tst_cmd_flags flags)130 int tst_cmd_(void (cleanup_fn)(void),
131 const char *const argv[],
132 const char *stdout_path,
133 const char *stderr_path,
134 enum tst_cmd_flags flags)
135 {
136 int stdout_fd = -1;
137 int stderr_fd = -1;
138 int rc;
139
140 if (stdout_path != NULL) {
141 stdout_fd = open(stdout_path,
142 OPEN_FLAGS, OPEN_MODE);
143
144 if (stdout_fd == -1)
145 tst_resm(TWARN | TERRNO,
146 "open() on %s failed at %s:%d",
147 stdout_path, __FILE__, __LINE__);
148 }
149
150 if (stderr_path != NULL) {
151 stderr_fd = open(stderr_path,
152 OPEN_FLAGS, OPEN_MODE);
153
154 if (stderr_fd == -1)
155 tst_resm(TWARN | TERRNO,
156 "open() on %s failed at %s:%d",
157 stderr_path, __FILE__, __LINE__);
158 }
159
160 rc = tst_cmd_fds(cleanup_fn, argv, stdout_fd, stderr_fd, flags);
161
162 if ((stdout_fd != -1) && (close(stdout_fd) == -1))
163 tst_resm(TWARN | TERRNO,
164 "close() on %s failed at %s:%d",
165 stdout_path, __FILE__, __LINE__);
166
167 if ((stderr_fd != -1) && (close(stderr_fd) == -1))
168 tst_resm(TWARN | TERRNO,
169 "close() on %s failed at %s:%d",
170 stderr_path, __FILE__, __LINE__);
171
172 return rc;
173 }
174
tst_system(const char * command)175 int tst_system(const char *command)
176 {
177 int ret = 0;
178
179 /*
180 *Temporarily disable SIGCHLD of user defined handler, so the
181 *system(3) function will not cause unexpected SIGCHLD signal
182 *callback function for test cases.
183 */
184 void *old_handler = signal(SIGCHLD, SIG_DFL);
185
186 ret = system(command);
187
188 signal(SIGCHLD, old_handler);
189 return ret;
190 }
191
mkfs_ext4_version_parser(void)192 static int mkfs_ext4_version_parser(void)
193 {
194 FILE *f;
195 int rc, major, minor, patch;
196
197 f = popen("mkfs.ext4 -V 2>&1", "r");
198 if (!f) {
199 tst_resm(TWARN, "Could not run mkfs.ext4 -V 2>&1 cmd");
200 return -1;
201 }
202
203 rc = fscanf(f, "mke2fs %d.%d.%d", &major, &minor, &patch);
204 pclose(f);
205 if (rc != 3) {
206 tst_resm(TWARN, "Unable to parse mkfs.ext4 version");
207 return -1;
208 }
209
210 return major * 10000 + minor * 100 + patch;
211 }
212
mkfs_ext4_version_table_get(char * version)213 static int mkfs_ext4_version_table_get(char *version)
214 {
215 int major, minor, patch;
216 int len;
217
218 if (sscanf(version, "%u.%u.%u %n", &major, &minor, &patch, &len) != 3) {
219 tst_resm(TWARN, "Illegal version(%s), should use format like 1.43.0", version);
220 return -1;
221 }
222
223 if (len != (int)strlen(version)) {
224 tst_resm(TWARN, "Grabage after version");
225 return -1;
226 }
227
228 return major * 10000 + minor * 100 + patch;
229 }
230
231 static struct version_parser {
232 const char *cmd;
233 int (*parser)(void);
234 int (*table_get)(char *version);
235 } version_parsers[] = {
236 {"mkfs.ext4", mkfs_ext4_version_parser, mkfs_ext4_version_table_get},
237 {},
238 };
239
tst_check_cmd(const char * cmd)240 void tst_check_cmd(const char *cmd)
241 {
242 struct version_parser *p;
243 char *cmd_token, *op_token, *version_token, *next, *str;
244 char path[PATH_MAX];
245 char parser_cmd[100];
246 int ver_parser, ver_get;
247 int op_flag = 0;
248
249 strcpy(parser_cmd, cmd);
250
251 cmd_token = strtok_r(parser_cmd, " ", &next);
252 op_token = strtok_r(NULL, " ", &next);
253 version_token = strtok_r(NULL, " ", &next);
254 str = strtok_r(NULL, " ", &next);
255
256 if (tst_get_path(cmd_token, path, sizeof(path)))
257 tst_brkm(TCONF, NULL, "Couldn't find '%s' in $PATH", cmd_token);
258
259 if (!op_token)
260 return;
261
262 if (!strcmp(op_token, ">="))
263 op_flag = OP_GE;
264 else if (!strcmp(op_token, ">"))
265 op_flag = OP_GT;
266 else if (!strcmp(op_token, "<="))
267 op_flag = OP_LE;
268 else if (!strcmp(op_token, "<"))
269 op_flag = OP_LT;
270 else if (!strcmp(op_token, "=="))
271 op_flag = OP_EQ;
272 else if (!strcmp(op_token, "!="))
273 op_flag = OP_NE;
274 else
275 tst_brkm(TCONF, NULL, "Invalid op(%s)", op_token);
276
277 if (!version_token || str) {
278 tst_brkm(TCONF, NULL,
279 "Illegal format(%s), should use format like mkfs.ext4 >= 1.43.0",
280 cmd);
281 }
282
283 for (p = &version_parsers[0]; p->cmd; p++) {
284 if (!strcmp(p->cmd, cmd_token)) {
285 tst_resm(TINFO, "Parsing %s version", p->cmd);
286 break;
287 }
288 }
289
290 if (!p->cmd) {
291 tst_brkm(TBROK, NULL, "No version parser for %s implemented!",
292 cmd_token);
293 }
294
295 ver_parser = p->parser();
296 if (ver_parser < 0)
297 tst_brkm(TBROK, NULL, "Failed to parse %s version", p->cmd);
298
299 ver_get = p->table_get(version_token);
300 if (ver_get < 0)
301 tst_brkm(TBROK, NULL, "Failed to get %s version", p->cmd);
302
303 switch (op_flag) {
304 case OP_GE:
305 if (ver_parser < ver_get) {
306 tst_brkm(TCONF, NULL, "%s required >= %d, but got %d, "
307 "the version is required in order run the test.",
308 cmd, ver_get, ver_parser);
309 }
310 break;
311 case OP_GT:
312 if (ver_parser <= ver_get) {
313 tst_brkm(TCONF, NULL, "%s required > %d, but got %d, "
314 "the version is required in order run the test.",
315 cmd, ver_get, ver_parser);
316 }
317 break;
318 case OP_LE:
319 if (ver_parser > ver_get) {
320 tst_brkm(TCONF, NULL, "%s required <= %d, but got %d, "
321 "the version is required in order run the test.",
322 cmd, ver_get, ver_parser);
323 }
324 break;
325 case OP_LT:
326 if (ver_parser >= ver_get) {
327 tst_brkm(TCONF, NULL, "%s required < %d, but got %d, "
328 "the version is required in order run the test.",
329 cmd, ver_get, ver_parser);
330 }
331 break;
332 case OP_EQ:
333 if (ver_parser != ver_get) {
334 tst_brkm(TCONF, NULL, "%s required == %d, but got %d, "
335 "the version is required in order run the test.",
336 cmd, ver_get, ver_parser);
337 }
338 break;
339 case OP_NE:
340 if (ver_parser == ver_get) {
341 tst_brkm(TCONF, NULL, "%s required != %d, but got %d, "
342 "the version is required in order run the test.",
343 cmd, ver_get, ver_parser);
344 }
345 break;
346 }
347 }
348