1 /*
2  * Copyright (C) 2017-2021 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * This code is a complete clean re-write of the stress tool by
19  * Colin Ian King <colin.king@canonical.com> and attempts to be
20  * backwardly compatible with the stress tool by Amos Waterland
21  * <apw@rossby.metr.ou.edu> but has more stress tests and more
22  * functionality.
23  *
24  */
25 #include "stress-ng.h"
26 
27 #define MAX_ARGS	(64)
28 #define RUN_SEQUENTIAL	(0x01)
29 #define RUN_PARALLEL	(0x02)
30 
31 #define ISBLANK(ch)	isblank((int)(ch))
32 
33 /*
34  *  stress_chop()
35  *	chop off end of line that matches char ch
36  */
stress_chop(char * str,const char ch)37 static inline void stress_chop(char *str, const char ch)
38 {
39 	char *ptr = strchr(str, ch);
40 
41 	if (ptr)
42 		*ptr = '\0';
43 }
44 
45 /*
46  *  stress_parse_run()
47  *	parse the special job file "run" command
48  *	that informs stress-ng to run the job file
49  *	stressors sequentially or in parallel
50  */
stress_parse_run(const char * jobfile,int argc,char ** argv,uint32_t * flag)51 static int stress_parse_run(
52 	const char *jobfile,
53 	int argc,
54 	char **argv,
55 	uint32_t *flag)
56 {
57 	if (argc < 3)
58 		return 0;
59 	if (strcmp(argv[1], "run"))
60 		return 0;
61 
62 	if (!strcmp(argv[2], "sequential") ||
63 	    !strcmp(argv[2], "sequentially") ||
64 	    !strcmp(argv[2], "seq")) {
65 		if (*flag & RUN_PARALLEL)
66 			goto err;
67 		*flag |= RUN_SEQUENTIAL;
68 		g_opt_flags |= OPT_FLAGS_SEQUENTIAL;
69 		return 1;
70 	}
71 	if (!strcmp(argv[2], "parallel") ||
72 	    !strcmp(argv[2], "par") ||
73 	    !strcmp(argv[2], "together")) {
74 		if (*flag & RUN_SEQUENTIAL)
75 			goto err;
76 		*flag |= RUN_PARALLEL;
77 		g_opt_flags &= ~OPT_FLAGS_SEQUENTIAL;
78 		g_opt_flags |= OPT_FLAGS_ALL;
79 		return 1;
80 	}
81 err:
82 	(void)fprintf(stderr, "Cannot have both run sequential "
83 		"and run parallel in jobfile %s\n",
84 		jobfile);
85 	return -1;
86 }
87 
88 /*
89  *  stress_parse_error()
90  *	generic job error message
91  */
stress_parse_error(const uint32_t lineno,const char * line)92 static void stress_parse_error(
93 	const uint32_t lineno,
94 	const char *line)
95 {
96 	(void)fprintf(stderr, "error in line %" PRIu32 ": '%s'\n",
97 		lineno, line);
98 }
99 
100 /*
101  *  stress_parse_jobfile()
102  *	parse a jobfile, turn job commands into
103  *	individual stress-ng options
104  */
stress_parse_jobfile(const int argc,char ** argv,const char * jobfile)105 int stress_parse_jobfile(
106 	const int argc,
107 	char **argv,
108 	const char *jobfile)
109 {
110 	NOCLOBBER FILE *fp;
111 	char buf[4096];
112 	char *new_argv[MAX_ARGS];
113 	char txt[sizeof(buf)];
114 	int ret;
115 	uint32_t flag;
116 	static uint32_t lineno;
117 
118 
119 	if (!jobfile) {
120 		if (optind >= argc)
121 			return 0;
122 		fp = fopen(argv[optind], "r");
123 		if (!fp)
124 			return 0;
125 		optind++;
126 	} else {
127 		fp = fopen(jobfile, "r");
128 	}
129 	if (!fp) {
130 		(void)fprintf(stderr, "Cannot open jobfile '%s'\n", jobfile);
131 		return -1;
132 	}
133 
134 	if (setjmp(g_error_env) == 1) {
135 		stress_parse_error(lineno, txt);
136 		ret = -1;
137 		goto err;
138 	}
139 
140 	flag = 0;
141 	ret = -1;
142 
143 	while (fgets(buf, sizeof(buf), fp)) {
144 		char *ptr = buf;
145 		int new_argc = 1;
146 
147 		(void)memset(new_argv, 0, sizeof(new_argv));
148 		new_argv[0] = argv[0];
149 		lineno++;
150 
151 		/* remove \n */
152 		stress_chop(buf, '\n');
153 		(void)shim_strlcpy(txt, buf, sizeof(txt) - 1);
154 
155 		/* remove comments */
156 		stress_chop(buf, '#');
157 
158 		if (!*ptr)
159 			continue;
160 
161 		/* skip leading blanks */
162 		while (ISBLANK(*ptr))
163 			ptr++;
164 
165 		while (new_argc < MAX_ARGS && *ptr) {
166 			new_argv[new_argc++] = ptr;
167 
168 			/* eat up chars until eos or blank */
169 			while (*ptr && !ISBLANK(*ptr))
170 				ptr++;
171 
172 			if (!*ptr)
173 				break;
174 			*ptr++ = '\0';
175 
176 			/* skip over blanks */
177 			while (ISBLANK(*ptr))
178 				ptr++;
179 		}
180 
181 		/* managed to get any tokens? */
182 		if (new_argc > 1) {
183 			const size_t len = strlen(new_argv[1]) + 3;
184 			char tmp[len];
185 			int rc;
186 
187 			/* Must check for --job -h option! */
188 			if (!strcmp(new_argv[1], "job") ||
189 			    !strcmp(new_argv[1], "j")) {
190 				(void)fprintf(stderr, "Cannot read job file in from a job script!\n");
191 				goto err;
192 			}
193 
194 			/* Check for job run option */
195 			rc = stress_parse_run(jobfile, new_argc, new_argv, &flag);
196 			if (rc < 0) {
197 				ret = -1;
198 				stress_parse_error(lineno, txt);
199 				goto err;
200 			} else if (rc == 1) {
201 				continue;
202 			}
203 
204 			/* prepend -- to command to make them into stress-ng options */
205 			(void)snprintf(tmp, len, "--%s", new_argv[1]);
206 			new_argv[1] = tmp;
207 			if (stress_parse_opts(new_argc, new_argv, true) != EXIT_SUCCESS) {
208 				stress_parse_error(lineno, txt);
209 				ret = -1;
210 				goto err;
211 			}
212 			new_argv[1] = NULL;
213 		}
214 	}
215 	ret = 0;
216 err:
217 	(void)fclose(fp);
218 
219 	return ret;
220 }
221