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