1 /*
2  * Copyright (c)2004 The DragonFly Project.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  *   Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  *
11  *   Redistributions in binary form must reproduce the above copyright
12  *   notice, this list of conditions and the following disclaimer in
13  *   the documentation and/or other materials provided with the
14  *   distribution.
15  *
16  *   Neither the name of the DragonFly Project nor the names of its
17  *   contributors may be used to endorse or promote products derived
18  *   from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 /*
35  * commands.c
36  * Execute a queued series of commands, updating a DFUI progress bar
37  * as each is executed.
38  * $Id: commands.c,v 1.27 2005/03/12 04:32:14 cpressey Exp $
39  */
40 
41 #include <sys/time.h>
42 #include <sys/types.h>
43 
44 #include <libgen.h>
45 #include <signal.h>
46 #include <stdarg.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <unistd.h>
50 
51 #include "libaura/mem.h"
52 #include "libaura/buffer.h"
53 #include "libaura/popen.h"
54 
55 #include "libdfui/dfui.h"
56 
57 #define	NEEDS_COMMANDS_STRUCTURE_DEFINITIONS
58 #include "commands.h"
59 #undef	NEEDS_COMMANDS_STRUCTURE_DEFINITIONS
60 
61 #include "diskutil.h"
62 #include "functions.h"
63 #include "uiutil.h"
64 
65 /*
66  * Create a new queue of commands.
67  */
68 struct commands	*
69 commands_new(void)
70 {
71 	struct commands *cmds;
72 
73 	AURA_MALLOC(cmds, commands);
74 
75 	cmds->head = NULL;
76 	cmds->tail = NULL;
77 
78 	return(cmds);
79 }
80 
81 /*
82  * Add a new, empty command to an existing queue of commands.
83  */
84 static struct command *
85 command_new(struct commands *cmds)
86 {
87 	struct command *cmd;
88 
89 	AURA_MALLOC(cmd, command);
90 
91 	cmd->cmdline = NULL;
92 	cmd->desc = NULL;
93 	cmd->log_mode = COMMAND_LOG_VERBOSE;
94 	cmd->failure_mode = COMMAND_FAILURE_ABORT;
95 	cmd->tag = NULL;
96 	cmd->result = COMMAND_RESULT_NEVER_EXECUTED;
97 	cmd->output = NULL;
98 
99 	cmd->next = NULL;
100 	if (cmds->head == NULL)
101 		cmds->head = cmd;
102 	else
103 		cmds->tail->next = cmd;
104 
105 	cmd->prev = cmds->tail;
106 	cmds->tail = cmd;
107 
108 	return(cmd);
109 }
110 
111 /*
112  * Add a new shell command to an existing queue of commands.
113  * The command can be specified as a format string followed by
114  * any number of arguments, in the style of "sprintf".
115  */
116 struct command *
117 command_add(struct commands *cmds, const char *fmt, ...)
118 {
119 	va_list args;
120 	struct command *cmd;
121 
122 	cmd = command_new(cmds);
123 
124 	va_start(args, fmt);
125 	vasprintf(&cmd->cmdline, fmt, args);
126 	va_end(args);
127 
128 	return(cmd);
129 }
130 
131 /*
132  * Set the log mode of the given command.
133  * Valid log modes are:
134  *   COMMAND_LOG_SILENT - do not log anything at all
135  *   COMMAND_LOG_QUIET - only log command name and exit code, not output
136  *   COMMAND_LOG_VERBOSE - log everything
137  */
138 void
139 command_set_log_mode(struct command *cmd, int log_mode)
140 {
141 	cmd->log_mode = log_mode;
142 }
143 
144 /*
145  * Set the failure mode of the given command.
146  * Valid failure modes are:
147  *   COMMAND_FAILURE_IGNORE - ignore failures and carry on
148  *   COMMAND_FAILURE_WARN - issue a non-critical warning
149  *   COMMAND_FAILURE_ABORT - halt the command chain and ask the user
150  */
151 void
152 command_set_failure_mode(struct command *cmd, int failure_mode)
153 {
154 	cmd->failure_mode = failure_mode;
155 }
156 
157 /*
158  * Set the description of the given command.  If present, it will
159  * be displayed in the progress bar instead of the command line.
160  */
161 void
162 command_set_desc(struct command *cmd, const char *fmt, ...)
163 {
164 	va_list args;
165 
166 	if (cmd->desc != NULL)
167 		free(cmd->desc);
168 
169 	va_start(args, fmt);
170 	vasprintf(&cmd->desc, fmt, args);
171 	va_end(args);
172 }
173 
174 /*
175  * Set an arbitrary tag on the command.
176  */
177 void
178 command_set_tag(struct command *cmd, const char *fmt, ...)
179 {
180 	va_list args;
181 
182 	if (cmd->tag != NULL)
183 		free(cmd->tag);
184 
185 	va_start(args, fmt);
186 	vasprintf(&cmd->tag, fmt, args);
187 	va_end(args);
188 }
189 
190 struct command *
191 command_get_first(const struct commands *cmds)
192 {
193 	return(cmds->head);
194 }
195 
196 struct command *
197 command_get_next(const struct command *cmd)
198 {
199 	return(cmd->next);
200 }
201 
202 char *
203 command_get_cmdline(const struct command *cmd)
204 {
205 	return(cmd->cmdline);
206 }
207 
208 char *
209 command_get_tag(const struct command *cmd)
210 {
211 	return(cmd->tag);
212 }
213 
214 int
215 command_get_result(const struct command *cmd)
216 {
217 	return(cmd->result);
218 }
219 
220 /*
221  * Allow the user to view the command log.
222  */
223 void
224 view_command_log(struct i_fn_args *a)
225 {
226 	struct dfui_form *f;
227 	struct dfui_response *r;
228 	struct aura_buffer *error_log;
229 
230 	error_log = aura_buffer_new(1024);
231 	aura_buffer_cat_file(error_log, "%sinstall.log", a->tmp);
232 
233 	f = dfui_form_create(
234 	    "error_log",
235 	    "Error Log",
236 	    aura_buffer_buf(error_log),
237 	    "",
238 
239 	    "p",	"role", "informative",
240 	    "p",	"minimum_width", "72",
241 	    "p",	"monospaced", "true",
242 
243 	    "a",	"ok", "OK", "", "",
244 	    NULL);
245 
246 	if (!dfui_be_present(a->c, f, &r))
247 		abort_backend();
248 
249 	dfui_form_free(f);
250 	dfui_response_free(r);
251 
252 	aura_buffer_free(error_log);
253 }
254 
255 /*
256  * Preview a set of commands.
257  */
258 void
259 commands_preview(struct dfui_connection *c, const struct commands *cmds)
260 {
261 	struct command *cmd;
262 	struct aura_buffer *preview;
263 
264 	preview = aura_buffer_new(1024);
265 
266 	for (cmd = cmds->head; cmd != NULL; cmd = cmd->next) {
267 		aura_buffer_cat(preview, cmd->cmdline);
268 		aura_buffer_cat(preview, "\n");
269 	}
270 
271 	inform(c, "%s", aura_buffer_buf(preview));
272 
273 	aura_buffer_free(preview);
274 }
275 
276 /*
277  * The command chain executing engine proper follows.
278  */
279 
280 /*
281  * Read from the pipe that was opened to the executing commands
282  * and update the progress bar as data comes and (and/or as the
283  * read from the pipe times out.)
284  */
285 static int
286 pipe_loop(struct i_fn_args *a, struct dfui_progress *pr,
287 	  struct command *cmd, int *cancelled)
288 {
289 	FILE *cmdout = NULL;
290 	struct timeval tv = { 1, 0 };
291 	char cline[256];
292 	char *command;
293 	pid_t pid;
294 	fd_set r;
295 	int n;
296 
297 	asprintf(&command, "(%s) 2>&1 </dev/null", cmd->cmdline);
298 	fflush(stdout);
299 	cmdout = aura_popen("%s", "r", command);
300 	free(command);
301 
302 	if (cmdout == NULL) {
303 		i_log(a, "! could not aura_popen() command");
304 		return(COMMAND_RESULT_POPEN_ERR);
305 	}
306 	pid = aura_pgetpid(cmdout);
307 #ifdef DEBUG
308 	fprintf(stderr, "+ pid = %d\n", pid);
309 #endif
310 
311 	/*
312 	 * Loop, selecting on the command and a timeout.
313 	 */
314 	for (;;) {
315 		if (*cancelled)
316 			break;
317 		FD_ZERO(&r);
318 		FD_SET(fileno(cmdout), &r);
319 		n = select(fileno(cmdout) + 1, &r, NULL, NULL, &tv);
320 #ifdef DEBUG
321 		fprintf(stderr, "+ select() = %d\n", n);
322 #endif
323 		if (n < 0) {
324 			/* Error */
325 			i_log(a, "! select() failed\n");
326 			aura_pclose(cmdout);
327 			return(COMMAND_RESULT_SELECT_ERR);
328 		} else if (n == 0) {
329 			/* Timeout */
330 			if (!dfui_be_progress_update(a->c, pr, cancelled))
331 				abort_backend();
332 #ifdef DEBUG
333 			fprintf(stderr, "+ cancelled = %d\n", *cancelled);
334 #endif
335 		} else {
336 			/* Data came in */
337 			fgets(cline, 255, cmdout);
338 			while (strlen(cline) > 0 && cline[strlen(cline) - 1] == '\n')
339 				cline[strlen(cline) - 1] = '\0';
340 			if (feof(cmdout))
341 				break;
342 			if (!dfui_be_progress_update(a->c, pr, cancelled))
343 				abort_backend();
344 			if (cmd->log_mode == COMMAND_LOG_VERBOSE) {
345 				i_log(a, "| %s", cline);
346 			} else if (cmd->log_mode != COMMAND_LOG_SILENT) {
347 				fprintf(stderr, "| %s\n", cline);
348 			}
349 		}
350 	}
351 
352 	if (*cancelled) {
353 #ifdef DEBUG
354 		fprintf(stderr, "+ killing %d\n", pid);
355 #endif
356 		n = kill(pid, SIGTERM);
357 #ifdef DEBUG
358 		fprintf(stderr, "+ kill() = %d\n", n);
359 #endif
360 	}
361 
362 #ifdef DEBUG
363 	fprintf(stderr, "+ pclosing %d\n", fileno(cmdout));
364 #endif
365 	n = aura_pclose(cmdout) / 256;
366 #ifdef DEBUG
367 	fprintf(stderr, "+ pclose() = %d\n", n);
368 #endif
369 	return(n);
370 }
371 
372 /*
373  * Execute a single command.
374  * Return value is a COMMAND_RESULT_* constant, or
375  * a value from 0 to 255 to indicate the exit code
376  * from the utility.
377  */
378 static int
379 command_execute(struct i_fn_args *a, struct dfui_progress *pr,
380 		struct command *cmd)
381 {
382 	FILE *log = NULL;
383 	char *filename;
384 	int cancelled = 0, done = 0, report_done = 0;
385 
386 	if (cmd->desc != NULL)
387 		dfui_info_set_short_desc(dfui_progress_get_info(pr), cmd->desc);
388 	else
389 		dfui_info_set_short_desc(dfui_progress_get_info(pr), cmd->cmdline);
390 
391 	if (!dfui_be_progress_update(a->c, pr, &cancelled))
392 		  abort_backend();
393 
394 	while (!done) {
395 		asprintf(&filename, "%sinstall.log", a->tmp);
396 		log = fopen(filename, "a");
397 		free(filename);
398 
399 		if (cmd->log_mode != COMMAND_LOG_SILENT)
400 			i_log(a, ",-<<< Executing `%s'", cmd->cmdline);
401 		cmd->result = pipe_loop(a, pr, cmd, &cancelled);
402 		if (cmd->log_mode != COMMAND_LOG_SILENT)
403 			i_log(a, "`->>> Exit status: %d\n", cmd->result);
404 
405 		if (log != NULL)
406 			fclose(log);
407 
408 		if (cancelled) {
409 			if (!dfui_be_progress_end(a->c))
410 				abort_backend();
411 
412 			report_done = 0;
413 			while (!report_done) {
414 				switch (dfui_be_present_dialog(a->c, "Cancelled",
415 				    "View Log|Retry|Cancel|Skip",
416 				    "Execution of the command\n\n%s\n\n"
417 				    "was cancelled.",
418 				    cmd->cmdline)) {
419 				case 1:
420 					/* View Log */
421 					view_command_log(a);
422 					break;
423 				case 2:
424 					/* Retry */
425 					cancelled = 0;
426 					report_done = 1;
427 					break;
428 				case 3:
429 					/* Cancel */
430 					cmd->result = COMMAND_RESULT_CANCELLED;
431 					report_done = 1;
432 					done = 1;
433 					break;
434 				case 4:
435 					/* Skip */
436 					cmd->result = COMMAND_RESULT_SKIPPED;
437 					report_done = 1;
438 					done = 1;
439 					break;
440 				}
441 			}
442 
443 			if (!dfui_be_progress_begin(a->c, pr))
444 				abort_backend();
445 
446 		} else if (cmd->failure_mode == COMMAND_FAILURE_IGNORE) {
447 			cmd->result = 0;
448 			done = 1;
449 		} else if (cmd->result != 0 && cmd->failure_mode != COMMAND_FAILURE_WARN) {
450 			if (!dfui_be_progress_end(a->c))
451 				abort_backend();
452 
453 			report_done = 0;
454 			while (!report_done) {
455 				switch (dfui_be_present_dialog(a->c, "Command Failed!",
456 				    "View Log|Retry|Cancel|Skip",
457 				    "Execution of the command\n\n%s\n\n"
458 				    "FAILED with a return code of %d.",
459 				    cmd->cmdline, cmd->result)) {
460 				case 1:
461 					/* View Log */
462 					view_command_log(a);
463 					break;
464 				case 2:
465 					/* Retry */
466 					report_done = 1;
467 					break;
468 				case 3:
469 					/* Cancel */
470 					/* XXX need a better way to retain actual result */
471 					cmd->result = COMMAND_RESULT_CANCELLED;
472 					report_done = 1;
473 					done = 1;
474 					break;
475 				case 4:
476 					/* Skip */
477 					/* XXX need a better way to retain actual result */
478 					cmd->result = COMMAND_RESULT_SKIPPED;
479 					report_done = 1;
480 					done = 1;
481 					break;
482 				}
483 			}
484 
485 			if (!dfui_be_progress_begin(a->c, pr))
486 				abort_backend();
487 
488 		} else {
489 			done = 1;
490 		}
491 	}
492 
493 	return(cmd->result);
494 }
495 
496 /*
497  * Execute a series of external utility programs.
498  * Returns 1 if everything executed OK, 0 if one of the
499  * critical commands failed or if the user cancelled.
500  */
501 int
502 commands_execute(struct i_fn_args *a, struct commands *cmds)
503 {
504 	struct dfui_progress *pr;
505 	struct command *cmd;
506 	int i;
507 	int n = 0;
508 	int result = 0;
509 	int return_val = 1;
510 
511 	cmd = cmds->head;
512 	while (cmd != NULL) {
513 		n++;
514 		cmd = cmd->next;
515 	}
516 
517 	pr = dfui_progress_new(dfui_info_new(
518 	    "Executing Commands",
519 	    "Executing Commands",
520 	    ""),
521 	    0);
522 
523 	if (!dfui_be_progress_begin(a->c, pr))
524 		abort_backend();
525 
526 	i = 1;
527 	for (cmd = cmds->head; cmd != NULL; cmd = cmd->next, i++) {
528 		result = command_execute(a, pr, cmd);
529 		if (result == COMMAND_RESULT_CANCELLED) {
530 			return_val = 0;
531 			break;
532 		}
533 		if (result > 0 && result < 256) {
534 			return_val = 0;
535 			if (cmd->failure_mode == COMMAND_FAILURE_ABORT) {
536 				break;
537 			}
538 		}
539 		dfui_progress_set_amount(pr, (i * 100) / n);
540 	}
541 
542 	if (!dfui_be_progress_end(a->c))
543 		abort_backend();
544 
545 	dfui_progress_free(pr);
546 
547 	return(return_val);
548 }
549 
550 /*
551  * Free the memory allocated for a queue of commands.  This invalidates
552  * the pointer passed to it.
553  */
554 void
555 commands_free(struct commands *cmds)
556 {
557 	struct command *cmd, *next;
558 
559 	cmd = cmds->head;
560 	while (cmd != NULL) {
561 		next = cmd->next;
562 		if (cmd->cmdline != NULL)
563 			free(cmd->cmdline);
564 		if (cmd->desc != NULL)
565 			free(cmd->desc);
566 		if (cmd->tag != NULL)
567 			free(cmd->tag);
568 		AURA_FREE(cmd, command);
569 		cmd = next;
570 	}
571 	AURA_FREE(cmds, commands);
572 }
573