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, 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(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 } 573