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 *
commands_new(void)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 *
command_new(struct commands * cmds)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 *
command_add(struct commands * cmds,const char * fmt,...)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
command_set_log_mode(struct command * cmd,int log_mode)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
command_set_failure_mode(struct command * cmd,int failure_mode)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
command_set_desc(struct command * cmd,const char * fmt,...)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
command_set_tag(struct command * cmd,const char * fmt,...)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 *
command_get_first(const struct commands * cmds)191 command_get_first(const struct commands *cmds)
192 {
193 return(cmds->head);
194 }
195
196 struct command *
command_get_next(const struct command * cmd)197 command_get_next(const struct command *cmd)
198 {
199 return(cmd->next);
200 }
201
202 char *
command_get_cmdline(const struct command * cmd)203 command_get_cmdline(const struct command *cmd)
204 {
205 return(cmd->cmdline);
206 }
207
208 char *
command_get_tag(const struct command * cmd)209 command_get_tag(const struct command *cmd)
210 {
211 return(cmd->tag);
212 }
213
214 int
command_get_result(const struct command * cmd)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
view_command_log(struct i_fn_args * a)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
commands_preview(struct dfui_connection * c,const struct commands * cmds)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
pipe_loop(struct i_fn_args * a,struct dfui_progress * pr,struct command * cmd,int * cancelled)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
command_execute(struct i_fn_args * a,struct dfui_progress * pr,struct command * cmd)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
commands_execute(struct i_fn_args * a,struct commands * cmds)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
commands_free(struct commands * cmds)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