In userland, fix printf(-like) calls without literal format and no args.
[dragonfly.git] / usr.sbin / installer / libinstaller / commands.c
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", command, "r");
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 }