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 
11 struct 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 
20 static struct fio_option options[] = {
21 	{
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 	},
30 	{
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 	},
39 	{
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 	},
50 	{
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 	},
60 	{
61 		.name = NULL,
62 	},
63 };
64 
str_replace(char * orig,const char * rep,const char * with)65 char *str_replace(char *orig, const char *rep, const char *with)
66 {
67 	/*
68 	 * Replace a substring by another.
69 	 *
70 	 * Returns the new string if occurences were found
71 	 * Returns orig if no occurence is found
72 	 */
73 	char *result, *insert, *tmp;
74 	int len_rep, len_with, len_front, count;
75 
76 	/* sanity checks and initialization */
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 
expand_variables(struct thread_options * o,char * arguments)109 char *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 
121 	free(expanded_runtime);
122 	return expanded_name;
123 }
124 
exec_background(struct thread_options * o,struct exec_options * eo)125 static 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);
155 		if (outfd < 0) {
156 			log_err("fio: cannot open output file %s : %s\n",
157 				outfilename, strerror(errno));
158 			free(outfilename);
159 			free(errfilename);
160 			free(expanded_arguments);
161 			return -1;
162 		}
163 
164 		errfd = open(errfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
165 		if (errfd < 0) {
166 			log_err("fio: cannot open output file %s : %s\n",
167 				errfilename, strerror(errno));
168 			free(outfilename);
169 			free(errfilename);
170 			free(expanded_arguments);
171 			close(outfd);
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 		}
191 		free(expanded_arguments);
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 		}
204 		free(expanded_arguments);
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) {
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);
215 			close(outfd);
216 			close(errfd);
217 			free(outfilename);
218 			free(errfilename);
219 		}
220 
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 		 */
228 		if (expanded_arguments != NULL) {
229 			if (asprintf(&exec_cmd, "%s %s", eo->program, expanded_arguments) < 0) {
230 				free(expanded_arguments);
231 				return -1;
232 			}
233 		} else {
234 			if (asprintf(&exec_cmd, "%s", eo->program) < 0)
235 				return -1;
236 		}
237 
238 		/*
239 		 * Let's build an argv array to based on the program name and
240 		 * arguments
241 		 */
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 
270 		/*
271 		 * Replace the fio program from the child fork by the target
272 		 * program
273 		 */
274 		execvp(arguments_array[0], arguments_array);
275 	}
276 	/* We never reach this place */
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);
282 	return 0;
283 }
284 
285 static enum fio_q_status
fio_exec_queue(struct thread_data * td,struct io_u fio_unused * io_u)286 fio_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 {
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 		 */
300 		usleep(o->thinktime);
301 		if (utime_since_now(&td->start) > o->timeout) {
302 			/* Let's stop the child */
303 			kill(eo->pid, SIGTERM);
304 			/*
305 			 * Let's give grace_time (1 sec by default) to the 3rd
306 			 * party tool to stop
307 			 */
308 			sleep(eo->grace_time);
309 		}
310 	}
311 
312 	return FIO_Q_COMPLETED;
313 }
314 
fio_exec_init(struct thread_data * td)315 static 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 
336 	/*
337 	 * Reporting that we are preparing the engine
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;
349 	/* 50ms pause when waiting for the program to complete */
350 	o->thinktime = 50000;
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 
fio_exec_cleanup(struct thread_data * td)359 static 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 
367 static int
fio_exec_open(struct thread_data fio_unused * td,struct fio_file fio_unused * f)368 fio_exec_open(struct thread_data fio_unused * td,
369 	      struct fio_file fio_unused * f)
370 {
371 	return 0;
372 }
373 
374 static 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 
fio_exec_register(void)386 static void fio_init fio_exec_register(void)
387 {
388 	register_ioengine(&ioengine);
389 }
390 
fio_exec_unregister(void)391 static void fio_exit fio_exec_unregister(void)
392 {
393 	unregister_ioengine(&ioengine);
394 }
395