• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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