engines/exec: Code cleanup to remove leaks
[fio.git] / engines / exec.c
CommitLineData
b50590bc
EV
1/*
2 * Exec engine
3 *
4 * Doesn't transfer any data, merely run 3rd party tools
5 *
6 */
7#include "../fio.h"
8#include "../optgroup.h"
9#include <signal.h>
10
11struct exec_options {
12 void *pad;
13 char *program;
14 char *arguments;
15 int grace_time;
16 unsigned int std_redirect;
17 pid_t pid;
18};
19
20static struct fio_option options[] = {
21 {
65739ddf
JA
22 .name = "program",
23 .lname = "Program",
24 .type = FIO_OPT_STR_STORE,
25 .off1 = offsetof(struct exec_options, program),
26 .help = "Program to execute",
27 .category = FIO_OPT_C_ENGINE,
28 .group = FIO_OPT_G_INVALID,
29 },
b50590bc 30 {
65739ddf
JA
31 .name = "arguments",
32 .lname = "Arguments",
33 .type = FIO_OPT_STR_STORE,
34 .off1 = offsetof(struct exec_options, arguments),
35 .help = "Arguments to pass",
36 .category = FIO_OPT_C_ENGINE,
37 .group = FIO_OPT_G_INVALID,
38 },
b50590bc 39 {
65739ddf
JA
40 .name = "grace_time",
41 .lname = "Grace time",
42 .type = FIO_OPT_INT,
43 .minval = 0,
44 .def = "1",
45 .off1 = offsetof(struct exec_options, grace_time),
46 .help = "Grace time before sending a SIGKILL",
47 .category = FIO_OPT_C_ENGINE,
48 .group = FIO_OPT_G_INVALID,
49 },
b50590bc 50 {
65739ddf
JA
51 .name = "std_redirect",
52 .lname = "Std redirect",
53 .type = FIO_OPT_BOOL,
54 .def = "1",
55 .off1 = offsetof(struct exec_options, std_redirect),
56 .help = "Redirect stdout & stderr to files",
57 .category = FIO_OPT_C_ENGINE,
58 .group = FIO_OPT_G_INVALID,
59 },
b50590bc 60 {
65739ddf
JA
61 .name = NULL,
62 },
b50590bc
EV
63};
64
65char *str_replace(char *orig, const char *rep, const char *with)
66{
67 /*
65739ddf
JA
68 * Replace a substring by another.
69 *
70 * Returns the new string if occurences were found
71 * Returns orig if no occurence is found
b50590bc
EV
72 */
73 char *result, *insert, *tmp;
74 int len_rep, len_with, len_front, count;
75
65739ddf 76 /* sanity checks and initialization */
b50590bc
EV
77 if (!orig || !rep)
78 return orig;
79
80 len_rep = strlen(rep);
81 if (len_rep == 0)
82 return orig;
83
84 if (!with)
85 with = "";
86 len_with = strlen(with);
87
88 insert = orig;
89 for (count = 0; (tmp = strstr(insert, rep)); ++count) {
90 insert = tmp + len_rep;
91 }
92
93 tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1);
94
95 if (!result)
96 return orig;
97
98 while (count--) {
99 insert = strstr(orig, rep);
100 len_front = insert - orig;
101 tmp = strncpy(tmp, orig, len_front) + len_front;
102 tmp = strcpy(tmp, with) + len_with;
103 orig += len_front + len_rep;
104 }
105 strcpy(tmp, orig);
106 return result;
107}
108
109char *expand_variables(struct thread_options *o, char *arguments)
110{
111 char str[16];
112 char *expanded_runtime, *expanded_name;
113 snprintf(str, sizeof(str), "%lld", o->timeout / 1000000);
114
115 /* %r is replaced by the runtime in seconds */
116 expanded_runtime = str_replace(arguments, "%r", str);
117
118 /* %n is replaced by the name of the running job */
119 expanded_name = str_replace(expanded_runtime, "%n", o->name);
120
568b5ec6 121 free(expanded_runtime);
b50590bc
EV
122 return expanded_name;
123}
124
125static int exec_background(struct thread_options *o, struct exec_options *eo)
126{
127 char *outfilename = NULL, *errfilename = NULL;
128 int outfd = 0, errfd = 0;
129 pid_t pid;
130 char *expanded_arguments = NULL;
131 /* For the arguments splitting */
132 char **arguments_array = NULL;
133 char *p;
134 char *exec_cmd = NULL;
135 size_t arguments_nb_items = 0, q;
136
137 if (asprintf(&outfilename, "%s.stdout", o->name) < 0)
138 return -1;
139
140 if (asprintf(&errfilename, "%s.stderr", o->name) < 0) {
141 free(outfilename);
142 return -1;
143 }
144
145 /* If we have variables in the arguments, let's expand them */
146 expanded_arguments = expand_variables(o, eo->arguments);
147
148 if (eo->std_redirect) {
149 log_info("%s : Saving output of %s %s : stdout=%s stderr=%s\n",
150 o->name, eo->program, expanded_arguments, outfilename,
151 errfilename);
152
153 /* Creating the stderr & stdout output files */
154 outfd = open(outfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
568b5ec6 155 if (outfd < 0) {
b50590bc
EV
156 log_err("fio: cannot open output file %s : %s\n",
157 outfilename, strerror(errno));
158 free(outfilename);
159 free(errfilename);
568b5ec6 160 free(expanded_arguments);
b50590bc
EV
161 return -1;
162 }
163
164 errfd = open(errfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
568b5ec6 165 if (errfd < 0) {
b50590bc
EV
166 log_err("fio: cannot open output file %s : %s\n",
167 errfilename, strerror(errno));
168 free(outfilename);
169 free(errfilename);
568b5ec6
EV
170 free(expanded_arguments);
171 close(outfd);
b50590bc
EV
172 return -1;
173 }
174 } else {
175 log_info("%s : Running %s %s\n",
176 o->name, eo->program, expanded_arguments);
177 }
178
179 pid = fork();
180
181 /* We are on the control thread (parent side of the fork */
182 if (pid > 0) {
183 eo->pid = pid;
184 if (eo->std_redirect) {
185 /* The output file is for the client side of the fork */
186 close(outfd);
187 close(errfd);
188 free(outfilename);
189 free(errfilename);
190 }
568b5ec6 191 free(expanded_arguments);
b50590bc
EV
192 return 0;
193 }
194
195 /* If the fork failed */
196 if (pid < 0) {
197 log_err("fio: forking failed %s \n", strerror(errno));
198 if (eo->std_redirect) {
199 close(outfd);
200 close(errfd);
201 free(outfilename);
202 free(errfilename);
203 }
568b5ec6 204 free(expanded_arguments);
b50590bc
EV
205 return -1;
206 }
207
208 /* We are in the worker (child side of the fork) */
209 if (pid == 0) {
210 if (eo->std_redirect) {
65739ddf
JA
211 /* replace stdout by the output file we create */
212 dup2(outfd, 1);
213 /* replace stderr by the output file we create */
214 dup2(errfd, 2);
b50590bc
EV
215 close(outfd);
216 close(errfd);
217 free(outfilename);
218 free(errfilename);
219 }
220
65739ddf
JA
221 /*
222 * Let's split the command line into a null terminated array to
223 * be passed to the exec'd program.
224 * But don't asprintf expanded_arguments if NULL as it would be
225 * converted to a '(null)' argument, while we want no arguments
226 * at all.
227 */
b50590bc 228 if (expanded_arguments != NULL) {
568b5ec6
EV
229 if (asprintf(&exec_cmd, "%s %s", eo->program, expanded_arguments) < 0) {
230 free(expanded_arguments);
b50590bc 231 return -1;
568b5ec6 232 }
b50590bc
EV
233 } else {
234 if (asprintf(&exec_cmd, "%s", eo->program) < 0)
235 return -1;
236 }
237
65739ddf
JA
238 /*
239 * Let's build an argv array to based on the program name and
240 * arguments
241 */
b50590bc
EV
242 p = exec_cmd;
243 for (;;) {
244 p += strspn(p, " ");
245
246 if (!(q = strcspn(p, " ")))
247 break;
248
249 if (q) {
250 arguments_array =
251 realloc(arguments_array,
252 (arguments_nb_items +
253 1) * sizeof(char *));
254 arguments_array[arguments_nb_items] =
255 malloc(q + 1);
256 strncpy(arguments_array[arguments_nb_items], p,
257 q);
258 arguments_array[arguments_nb_items][q] = 0;
259 arguments_nb_items++;
260 p += q;
261 }
262 }
263
264 /* Adding a null-terminated item to close the list */
265 arguments_array =
266 realloc(arguments_array,
267 (arguments_nb_items + 1) * sizeof(char *));
268 arguments_array[arguments_nb_items] = NULL;
269
65739ddf
JA
270 /*
271 * Replace the fio program from the child fork by the target
272 * program
273 */
b50590bc
EV
274 execvp(arguments_array[0], arguments_array);
275 }
65739ddf 276 /* We never reach this place */
568b5ec6
EV
277 /* Let's free the malloc'ed structures to make static checkers happy */
278 if (expanded_arguments)
279 free(expanded_arguments);
280 if (arguments_array)
281 free(arguments_array);
b50590bc
EV
282 return 0;
283}
284
285static enum fio_q_status
286fio_exec_queue(struct thread_data *td, struct io_u fio_unused * io_u)
287{
288 struct thread_options *o = &td->o;
289 struct exec_options *eo = td->eo;
290
291 /* Let's execute the program the first time we get queued */
292 if (eo->pid == -1) {
293 exec_background(o, eo);
294 } else {
65739ddf
JA
295 /*
296 * The program is running in background, let's check on a
297 * regular basis
298 * if the time is over and if we need to stop the tool
299 */
b50590bc
EV
300 usleep(o->thinktime);
301 if (utime_since_now(&td->start) > o->timeout) {
302 /* Let's stop the child */
303 kill(eo->pid, SIGTERM);
65739ddf
JA
304 /*
305 * Let's give grace_time (1 sec by default) to the 3rd
306 * party tool to stop
307 */
b50590bc
EV
308 sleep(eo->grace_time);
309 }
310 }
311
312 return FIO_Q_COMPLETED;
313}
314
315static int fio_exec_init(struct thread_data *td)
316{
317 struct thread_options *o = &td->o;
318 struct exec_options *eo = td->eo;
319 int td_previous_state;
320
321 eo->pid = -1;
322
323 if (!eo->program) {
324 td_vmsg(td, EINVAL,
325 "no program is defined, it is mandatory to define one",
326 "exec");
327 return 1;
328 }
329
330 log_info("%s : program=%s, arguments=%s\n",
331 td->o.name, eo->program, eo->arguments);
332
333 /* Saving the current thread state */
334 td_previous_state = td->runstate;
335
65739ddf
JA
336 /*
337 * Reporting that we are preparing the engine
b50590bc
EV
338 * This is useful as the qsort() calibration takes time
339 * This prevents the job from starting before init is completed
340 */
341 td_set_runstate(td, TD_SETTING_UP);
342
343 /*
344 * set thinktime_sleep and thinktime_spin appropriately
345 */
346 o->thinktime_blocks = 1;
347 o->thinktime_blocks_type = THINKTIME_BLOCKS_TYPE_COMPLETE;
348 o->thinktime_spin = 0;
65739ddf
JA
349 /* 50ms pause when waiting for the program to complete */
350 o->thinktime = 50000;
b50590bc
EV
351
352 o->nr_files = o->open_files = 1;
353
354 /* Let's restore the previous state. */
355 td_set_runstate(td, td_previous_state);
356 return 0;
357}
358
359static void fio_exec_cleanup(struct thread_data *td)
360{
361 struct exec_options *eo = td->eo;
362 /* Send a sigkill to ensure the job is well terminated */
363 if (eo->pid > 0)
364 kill(eo->pid, SIGKILL);
365}
366
367static int
368fio_exec_open(struct thread_data fio_unused * td,
369 struct fio_file fio_unused * f)
370{
371 return 0;
372}
373
374static struct ioengine_ops ioengine = {
375 .name = "exec",
376 .version = FIO_IOOPS_VERSION,
377 .queue = fio_exec_queue,
378 .init = fio_exec_init,
379 .cleanup = fio_exec_cleanup,
380 .open_file = fio_exec_open,
381 .flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_NOIO,
382 .options = options,
383 .option_struct_size = sizeof(struct exec_options),
384};
385
386static void fio_init fio_exec_register(void)
387{
388 register_ioengine(&ioengine);
389}
390
391static void fio_exit fio_exec_unregister(void)
392{
393 unregister_ioengine(&ioengine);
394}