1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 27 /* 28 * dosys.cc 29 * 30 * Execute one commandline 31 */ 32 33 /* 34 * Included files 35 */ 36 #include <sys/wait.h> /* WIFEXITED(status) */ 37 #include <avo/avo_alloca.h> /* alloca() */ 38 39 #if defined(TEAMWARE_MAKE_CMN) || defined(MAKETOOL) /* tolik */ 40 # include <avo/strings.h> /* AVO_STRDUP() */ 41 #if defined(DISTRIBUTED) 42 # include <dm/Avo_CmdOutput.h> 43 # include <rw/xdrstrea.h> 44 #endif 45 #endif 46 47 #include <stdio.h> /* errno */ 48 #include <errno.h> /* errno */ 49 #include <fcntl.h> /* open() */ 50 #include <mksh/dosys.h> 51 #include <mksh/macro.h> /* getvar() */ 52 #include <mksh/misc.h> /* getmem(), fatal_mksh(), errmsg() */ 53 #include <mksdmsi18n/mksdmsi18n.h> /* libmksdmsi18n_init() */ 54 #include <sys/signal.h> /* SIG_DFL */ 55 #include <sys/stat.h> /* open() */ 56 #include <sys/wait.h> /* wait() */ 57 #include <ulimit.h> /* ulimit() */ 58 #include <unistd.h> /* close(), dup2() */ 59 60 61 62 /* 63 * Defined macros 64 */ 65 #if defined(DISTRIBUTED) || defined(MAKETOOL) /* tolik */ 66 #define SEND_MTOOL_MSG(cmds) \ 67 if (send_mtool_msgs) { \ 68 cmds \ 69 } 70 #else 71 #define SEND_MTOOL_MSG(cmds) 72 #endif 73 74 /* 75 * typedefs & structs 76 */ 77 78 /* 79 * Static variables 80 */ 81 82 /* 83 * File table of contents 84 */ 85 static Boolean exec_vp(register char *name, register char **argv, char **envp, register Boolean ignore_error, pathpt vroot_path); 86 87 /* 88 * Workaround for NFS bug. Sometimes, when running 'open' on a remote 89 * dmake server, it fails with "Stale NFS file handle" error. 90 * The second attempt seems to work. 91 */ 92 int 93 my_open(const char *path, int oflag, mode_t mode) { 94 int res = open(path, oflag, mode); 95 if (res < 0 && (errno == ESTALE || errno == EAGAIN)) { 96 /* Stale NFS file handle. Try again */ 97 res = open(path, oflag, mode); 98 } 99 return res; 100 } 101 102 /* 103 * void 104 * redirect_io(char *stdout_file, char *stderr_file) 105 * 106 * Redirects stdout and stderr for a child mksh process. 107 */ 108 void 109 redirect_io(char *stdout_file, char *stderr_file) 110 { 111 long descriptor_limit; 112 int i; 113 114 if ((descriptor_limit = ulimit(UL_GDESLIM)) < 0) { 115 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 89, "ulimit() failed: %s"), errmsg(errno)); 116 } 117 for (i = 3; i < descriptor_limit; i++) { 118 (void) close(i); 119 } 120 if ((i = my_open(stdout_file, 121 O_WRONLY | O_CREAT | O_TRUNC | O_DSYNC, 122 S_IREAD | S_IWRITE)) < 0) { 123 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 90, "Couldn't open standard out temp file `%s': %s"), 124 stdout_file, 125 errmsg(errno)); 126 } else { 127 if (dup2(i, 1) == -1) { 128 fatal_mksh(NOCATGETS("*** Error: dup2(3, 1) failed: %s"), 129 errmsg(errno)); 130 } 131 close(i); 132 } 133 if (stderr_file == NULL) { 134 if (dup2(1, 2) == -1) { 135 fatal_mksh(NOCATGETS("*** Error: dup2(1, 2) failed: %s"), 136 errmsg(errno)); 137 } 138 } else if ((i = my_open(stderr_file, 139 O_WRONLY | O_CREAT | O_TRUNC | O_DSYNC, 140 S_IREAD | S_IWRITE)) < 0) { 141 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 91, "Couldn't open standard error temp file `%s': %s"), 142 stderr_file, 143 errmsg(errno)); 144 } else { 145 if (dup2(i, 2) == -1) { 146 fatal_mksh(NOCATGETS("*** Error: dup2(3, 2) failed: %s"), 147 errmsg(errno)); 148 } 149 close(i); 150 } 151 } 152 153 /* 154 * dosys_mksh(command, ignore_error, call_make, silent_error, target) 155 * 156 * Check if command string contains meta chars and dispatch to 157 * the proper routine for executing one command line. 158 * 159 * Return value: 160 * Indicates if the command execution failed 161 * 162 * Parameters: 163 * command The command to run 164 * ignore_error Should we abort when an error is seen? 165 * call_make Did command reference $(MAKE) ? 166 * silent_error Should error messages be suppressed for dmake? 167 * target Target we are building 168 * 169 * Global variables used: 170 * do_not_exec_rule Is -n on? 171 * working_on_targets We started processing real targets 172 */ 173 Doname 174 dosys_mksh(register Name command, register Boolean ignore_error, register Boolean call_make, Boolean silent_error, Boolean always_exec, Name target, Boolean redirect_out_err, char *stdout_file, char *stderr_file, pathpt vroot_path, int nice_prio) 175 { 176 register int length = command->hash.length; 177 register wchar_t *p; 178 register wchar_t *q; 179 register wchar_t *cmd_string; 180 struct stat before; 181 Doname result; 182 Boolean working_on_targets_mksh = true; 183 Wstring wcb(command); 184 p = wcb.get_string(); 185 cmd_string = p; 186 187 /* Strip spaces from head of command string */ 188 while (iswspace(*p)) { 189 p++, length--; 190 } 191 if (*p == (int) nul_char) { 192 return build_failed; 193 } 194 /* If we are faking it we just return */ 195 if (do_not_exec_rule && 196 working_on_targets_mksh && 197 !call_make && 198 !always_exec) { 199 return build_ok; 200 } 201 202 /* Copy string to make it OK to write it. */ 203 q = ALLOC_WC(length + 1); 204 (void) wscpy(q, p); 205 /* Write the state file iff this command uses make. */ 206 /* XXX - currently does not support recursive make's, $(MAKE)'s 207 if (call_make && command_changed) { 208 write_state_file(0, false); 209 } 210 (void) stat(make_state->string_mb, &before); 211 */ 212 /* 213 * Run command directly if it contains no shell meta chars, 214 * else run it using the shell. 215 */ 216 /* XXX - command->meta *may* not be set correctly */ 217 if (await(ignore_error, 218 silent_error, 219 target, 220 cmd_string, 221 command->meta ? 222 doshell(q, ignore_error, redirect_out_err, stdout_file, stderr_file, nice_prio) : 223 doexec(q, ignore_error, redirect_out_err, stdout_file, stderr_file, vroot_path, nice_prio), 224 false, 225 NULL, 226 -1)) { 227 228 #ifdef PRINT_EXIT_STATUS 229 warning_mksh(NOCATGETS("I'm in dosys_mksh(), and await() returned result of build_ok.")); 230 #endif 231 232 result = build_ok; 233 } else { 234 235 #ifdef PRINT_EXIT_STATUS 236 warning_mksh(NOCATGETS("I'm in dosys_mksh(), and await() returned result of build_failed.")); 237 #endif 238 239 result = build_failed; 240 } 241 retmem(q); 242 243 /* XXX - currently does not support recursive make's, $(MAKE)'s 244 if ((report_dependencies_level == 0) && 245 call_make) { 246 make_state->stat.time = (time_t)file_no_time; 247 (void)exists(make_state); 248 if (before.st_mtime == make_state->stat.time) { 249 return result; 250 } 251 makefile_type = reading_statefile; 252 if (read_trace_level > 1) { 253 trace_reader = true; 254 } 255 (void) read_simple_file(make_state, 256 false, 257 false, 258 false, 259 false, 260 false, 261 true); 262 trace_reader = false; 263 } 264 */ 265 return result; 266 } 267 268 /* 269 * doshell(command, ignore_error) 270 * 271 * Used to run command lines that include shell meta-characters. 272 * The make macro SHELL is supposed to contain a path to the shell. 273 * 274 * Return value: 275 * The pid of the process we started 276 * 277 * Parameters: 278 * command The command to run 279 * ignore_error Should we abort on error? 280 * 281 * Global variables used: 282 * filter_stderr If -X is on we redirect stderr 283 * shell_name The Name "SHELL", used to get the path to shell 284 */ 285 int 286 doshell(wchar_t *command, register Boolean ignore_error, Boolean redirect_out_err, char *stdout_file, char *stderr_file, int nice_prio) 287 { 288 char *argv[6]; 289 int argv_index = 0; 290 int cmd_argv_index; 291 int length; 292 char nice_prio_buf[MAXPATHLEN]; 293 register Name shell = getvar(shell_name); 294 register char *shellname; 295 char *tmp_mbs_buffer; 296 297 298 if (IS_EQUAL(shell->string_mb, "")) { 299 shell = shell_name; 300 } 301 if ((shellname = strrchr(shell->string_mb, (int) slash_char)) == NULL) { 302 shellname = shell->string_mb; 303 } else { 304 shellname++; 305 } 306 307 /* 308 * Only prepend the /usr/bin/nice command to the original command 309 * if the nice priority, nice_prio, is NOT zero (0). 310 * Nice priorities can be a positive or a negative number. 311 */ 312 if (nice_prio != 0) { 313 argv[argv_index++] = (char *)NOCATGETS("nice"); 314 (void) sprintf(nice_prio_buf, NOCATGETS("-%d"), nice_prio); 315 argv[argv_index++] = strdup(nice_prio_buf); 316 } 317 argv[argv_index++] = shellname; 318 argv[argv_index++] = (char*)(ignore_error ? NOCATGETS("-c") : NOCATGETS("-ce")); 319 if ((length = wslen(command)) >= MAXPATHLEN) { 320 tmp_mbs_buffer = getmem((length * MB_LEN_MAX) + 1); 321 (void) wcstombs(tmp_mbs_buffer, command, (length * MB_LEN_MAX) + 1); 322 cmd_argv_index = argv_index; 323 argv[argv_index++] = strdup(tmp_mbs_buffer); 324 retmem_mb(tmp_mbs_buffer); 325 } else { 326 WCSTOMBS(mbs_buffer, command); 327 cmd_argv_index = argv_index; 328 argv[argv_index++] = strdup(mbs_buffer); 329 } 330 argv[argv_index] = NULL; 331 (void) fflush(stdout); 332 if ((childPid = fork()) == 0) { 333 enable_interrupt((void (*) (int)) SIG_DFL); 334 if (redirect_out_err) { 335 redirect_io(stdout_file, stderr_file); 336 } 337 #if 0 338 if (filter_stderr) { 339 redirect_stderr(); 340 } 341 #endif 342 if (nice_prio != 0) { 343 (void) execve(NOCATGETS("/usr/bin/nice"), argv, environ); 344 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 92, "Could not load `/usr/bin/nice': %s"), 345 errmsg(errno)); 346 } else { 347 (void) execve(shell->string_mb, argv, environ); 348 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 93, "Could not load Shell from `%s': %s"), 349 shell->string_mb, 350 errmsg(errno)); 351 } 352 } 353 if (childPid == -1) { 354 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 94, "fork failed: %s"), 355 errmsg(errno)); 356 } 357 retmem_mb(argv[cmd_argv_index]); 358 return childPid; 359 } 360 361 /* 362 * exec_vp(name, argv, envp, ignore_error) 363 * 364 * Like execve, but does path search. 365 * This starts command when make invokes it directly (without a shell). 366 * 367 * Return value: 368 * Returns false if the exec failed 369 * 370 * Parameters: 371 * name The name of the command to run 372 * argv Arguments for the command 373 * envp The environment for it 374 * ignore_error Should we abort on error? 375 * 376 * Global variables used: 377 * shell_name The Name "SHELL", used to get the path to shell 378 * vroot_path The path used by the vroot package 379 */ 380 static Boolean 381 exec_vp(register char *name, register char **argv, char **envp, register Boolean ignore_error, pathpt vroot_path) 382 { 383 register Name shell = getvar(shell_name); 384 register char *shellname; 385 char *shargv[4]; 386 Name tmp_shell; 387 388 if (IS_EQUAL(shell->string_mb, "")) { 389 shell = shell_name; 390 } 391 392 for (int i = 0; i < 5; i++) { 393 (void) execve_vroot(name, 394 argv + 1, 395 envp, 396 vroot_path, 397 VROOT_DEFAULT); 398 switch (errno) { 399 case ENOEXEC: 400 case ENOENT: 401 /* That failed. Let the shell handle it */ 402 shellname = strrchr(shell->string_mb, (int) slash_char); 403 if (shellname == NULL) { 404 shellname = shell->string_mb; 405 } else { 406 shellname++; 407 } 408 shargv[0] = shellname; 409 shargv[1] = (char*)(ignore_error ? NOCATGETS("-c") : NOCATGETS("-ce")); 410 shargv[2] = argv[0]; 411 shargv[3] = NULL; 412 tmp_shell = getvar(shell_name); 413 if (IS_EQUAL(tmp_shell->string_mb, "")) { 414 tmp_shell = shell_name; 415 } 416 (void) execve_vroot(tmp_shell->string_mb, 417 shargv, 418 envp, 419 vroot_path, 420 VROOT_DEFAULT); 421 return failed; 422 case ETXTBSY: 423 /* 424 * The program is busy (debugged?). 425 * Wait and then try again. 426 */ 427 (void) sleep((unsigned) i); 428 case EAGAIN: 429 break; 430 default: 431 return failed; 432 } 433 } 434 return failed; 435 } 436 437 /* 438 * doexec(command, ignore_error) 439 * 440 * Will scan an argument string and split it into words 441 * thus building an argument list that can be passed to exec_ve() 442 * 443 * Return value: 444 * The pid of the process started here 445 * 446 * Parameters: 447 * command The command to run 448 * ignore_error Should we abort on error? 449 * 450 * Global variables used: 451 * filter_stderr If -X is on we redirect stderr 452 */ 453 int 454 doexec(register wchar_t *command, register Boolean ignore_error, Boolean redirect_out_err, char *stdout_file, char *stderr_file, pathpt vroot_path, int nice_prio) 455 { 456 int arg_count = 5; 457 char **argv; 458 int length; 459 char nice_prio_buf[MAXPATHLEN]; 460 register char **p; 461 wchar_t *q; 462 register wchar_t *t; 463 char *tmp_mbs_buffer; 464 465 /* 466 * Only prepend the /usr/bin/nice command to the original command 467 * if the nice priority, nice_prio, is NOT zero (0). 468 * Nice priorities can be a positive or a negative number. 469 */ 470 if (nice_prio != 0) { 471 arg_count += 2; 472 } 473 for (t = command; *t != (int) nul_char; t++) { 474 if (iswspace(*t)) { 475 arg_count++; 476 } 477 } 478 argv = (char **)alloca(arg_count * (sizeof(char *))); 479 /* 480 * Reserve argv[0] for sh in case of exec_vp failure. 481 * Don't worry about prepending /usr/bin/nice command to argv[0]. 482 * In fact, doing it may cause the sh command to fail! 483 */ 484 p = &argv[1]; 485 if ((length = wslen(command)) >= MAXPATHLEN) { 486 tmp_mbs_buffer = getmem((length * MB_LEN_MAX) + 1); 487 (void) wcstombs(tmp_mbs_buffer, command, (length * MB_LEN_MAX) + 1); 488 argv[0] = strdup(tmp_mbs_buffer); 489 retmem_mb(tmp_mbs_buffer); 490 } else { 491 WCSTOMBS(mbs_buffer, command); 492 argv[0] = strdup(mbs_buffer); 493 } 494 495 if (nice_prio != 0) { 496 *p++ = strdup(NOCATGETS("/usr/bin/nice")); 497 (void) sprintf(nice_prio_buf, NOCATGETS("-%d"), nice_prio); 498 *p++ = strdup(nice_prio_buf); 499 } 500 /* Build list of argument words. */ 501 for (t = command; *t;) { 502 if (p >= &argv[arg_count]) { 503 /* This should never happen, right? */ 504 WCSTOMBS(mbs_buffer, command); 505 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 95, "Command `%s' has more than %d arguments"), 506 mbs_buffer, 507 arg_count); 508 } 509 q = t; 510 while (!iswspace(*t) && (*t != (int) nul_char)) { 511 t++; 512 } 513 if (*t) { 514 for (*t++ = (int) nul_char; iswspace(*t); t++); 515 } 516 if ((length = wslen(q)) >= MAXPATHLEN) { 517 tmp_mbs_buffer = getmem((length * MB_LEN_MAX) + 1); 518 (void) wcstombs(tmp_mbs_buffer, q, (length * MB_LEN_MAX) + 1); 519 *p++ = strdup(tmp_mbs_buffer); 520 retmem_mb(tmp_mbs_buffer); 521 } else { 522 WCSTOMBS(mbs_buffer, q); 523 *p++ = strdup(mbs_buffer); 524 } 525 } 526 *p = NULL; 527 528 /* Then exec the command with that argument list. */ 529 (void) fflush(stdout); 530 if ((childPid = fork()) == 0) { 531 enable_interrupt((void (*) (int)) SIG_DFL); 532 if (redirect_out_err) { 533 redirect_io(stdout_file, stderr_file); 534 } 535 #if 0 536 if (filter_stderr) { 537 redirect_stderr(); 538 } 539 #endif 540 (void) exec_vp(argv[1], argv, environ, ignore_error, vroot_path); 541 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 96, "Cannot load command `%s': %s"), argv[1], errmsg(errno)); 542 } 543 if (childPid == -1) { 544 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 97, "fork failed: %s"), 545 errmsg(errno)); 546 } 547 for (int i = 0; argv[i] != NULL; i++) { 548 retmem_mb(argv[i]); 549 } 550 return childPid; 551 } 552 553 /* 554 * await(ignore_error, silent_error, target, command, running_pid) 555 * 556 * Wait for one child process and analyzes 557 * the returned status when the child process terminates. 558 * 559 * Return value: 560 * Returns true if commands ran OK 561 * 562 * Parameters: 563 * ignore_error Should we abort on error? 564 * silent_error Should error messages be suppressed for dmake? 565 * target The target we are building, for error msgs 566 * command The command we ran, for error msgs 567 * running_pid The pid of the process we are waiting for 568 * 569 * Static variables used: 570 * filter_file The fd for the filter file 571 * filter_file_name The name of the filter file 572 * 573 * Global variables used: 574 * filter_stderr Set if -X is on 575 */ 576 #if defined(DISTRIBUTED) || defined(MAKETOOL) /* tolik */ 577 Boolean 578 await(register Boolean ignore_error, register Boolean silent_error, Name target, wchar_t *command, pid_t running_pid, Boolean send_mtool_msgs, XDR *xdrs_p, int job_msg_id) 579 #else 580 Boolean 581 await(register Boolean ignore_error, register Boolean silent_error, Name target, wchar_t *command, pid_t running_pid, Boolean send_mtool_msgs, void *xdrs_p, int job_msg_id) 582 #endif 583 { 584 int status; 585 char *buffer; 586 int core_dumped; 587 int exit_status; 588 #if defined(DISTRIBUTED) || defined(MAKETOOL) /* tolik */ 589 Avo_CmdOutput *make_output_msg; 590 #endif 591 FILE *outfp; 592 register pid_t pid; 593 struct stat stat_buff; 594 int termination_signal; 595 char tmp_buf[MAXPATHLEN]; 596 #if defined(DISTRIBUTED) || defined(MAKETOOL) /* tolik */ 597 RWCollectable *xdr_msg; 598 #endif 599 600 while ((pid = wait(&status)) != running_pid) { 601 if (pid == -1) { 602 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 98, "wait() failed: %s"), errmsg(errno)); 603 } 604 } 605 (void) fflush(stdout); 606 (void) fflush(stderr); 607 608 if (status == 0) { 609 610 #ifdef PRINT_EXIT_STATUS 611 warning_mksh(NOCATGETS("I'm in await(), and status is 0.")); 612 #endif 613 614 return succeeded; 615 } 616 617 #ifdef PRINT_EXIT_STATUS 618 warning_mksh(NOCATGETS("I'm in await(), and status is *NOT* 0.")); 619 #endif 620 621 622 exit_status = WEXITSTATUS(status); 623 624 #ifdef PRINT_EXIT_STATUS 625 warning_mksh(NOCATGETS("I'm in await(), and exit_status is %d."), exit_status); 626 #endif 627 628 termination_signal = WTERMSIG(status); 629 core_dumped = WCOREDUMP(status); 630 631 /* 632 * If the child returned an error, we now try to print a 633 * nice message about it. 634 */ 635 SEND_MTOOL_MSG( 636 make_output_msg = new Avo_CmdOutput(); 637 (void) sprintf(tmp_buf, "%d", job_msg_id); 638 make_output_msg->appendOutput(AVO_STRDUP(tmp_buf)); 639 ); 640 641 tmp_buf[0] = (int) nul_char; 642 if (!silent_error) { 643 if (exit_status != 0) { 644 (void) fprintf(stdout, 645 catgets(libmksdmsi18n_catd, 1, 103, "*** Error code %d"), 646 exit_status); 647 SEND_MTOOL_MSG( 648 (void) sprintf(&tmp_buf[strlen(tmp_buf)], 649 catgets(libmksdmsi18n_catd, 1, 104, "*** Error code %d"), 650 exit_status); 651 ); 652 } else { 653 (void) fprintf(stdout, 654 catgets(libmksdmsi18n_catd, 1, 105, "*** Signal %d"), 655 termination_signal); 656 SEND_MTOOL_MSG( 657 (void) sprintf(&tmp_buf[strlen(tmp_buf)], 658 catgets(libmksdmsi18n_catd, 1, 106, "*** Signal %d"), 659 termination_signal); 660 ); 661 if (core_dumped) { 662 (void) fprintf(stdout, 663 catgets(libmksdmsi18n_catd, 1, 107, " - core dumped")); 664 SEND_MTOOL_MSG( 665 (void) sprintf(&tmp_buf[strlen(tmp_buf)], 666 catgets(libmksdmsi18n_catd, 1, 108, " - core dumped")); 667 ); 668 } 669 } 670 if (ignore_error) { 671 (void) fprintf(stdout, 672 catgets(libmksdmsi18n_catd, 1, 109, " (ignored)")); 673 SEND_MTOOL_MSG( 674 (void) sprintf(&tmp_buf[strlen(tmp_buf)], 675 catgets(libmksdmsi18n_catd, 1, 110, " (ignored)")); 676 ); 677 } 678 (void) fprintf(stdout, "\n"); 679 (void) fflush(stdout); 680 SEND_MTOOL_MSG( 681 make_output_msg->appendOutput(AVO_STRDUP(tmp_buf)); 682 ); 683 } 684 SEND_MTOOL_MSG( 685 xdr_msg = (RWCollectable*) make_output_msg; 686 xdr(xdrs_p, xdr_msg); 687 delete make_output_msg; 688 ); 689 690 #ifdef PRINT_EXIT_STATUS 691 warning_mksh(NOCATGETS("I'm in await(), returning failed.")); 692 #endif 693 694 return failed; 695 } 696 697 /* 698 * sh_command2string(command, destination) 699 * 700 * Run one sh command and capture the output from it. 701 * 702 * Return value: 703 * 704 * Parameters: 705 * command The command to run 706 * destination Where to deposit the output from the command 707 * 708 * Static variables used: 709 * 710 * Global variables used: 711 */ 712 void 713 sh_command2string(register String command, register String destination) 714 { 715 register FILE *fd; 716 register int chr; 717 int status; 718 Boolean command_generated_output = false; 719 720 command->text.p = (int) nul_char; 721 WCSTOMBS(mbs_buffer, command->buffer.start); 722 if ((fd = popen(mbs_buffer, "r")) == NULL) { 723 WCSTOMBS(mbs_buffer, command->buffer.start); 724 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 111, "Could not run command `%s' for :sh transformation"), 725 mbs_buffer); 726 } 727 while ((chr = getc(fd)) != EOF) { 728 if (chr == (int) newline_char) { 729 chr = (int) space_char; 730 } 731 command_generated_output = true; 732 append_char(chr, destination); 733 } 734 735 /* 736 * We don't want to keep the last LINE_FEED since usually 737 * the output of the 'sh:' command is used to evaluate 738 * some MACRO. ( /bin/sh and other shell add a line feed 739 * to the output so that the prompt appear in the right place. 740 * We don't need that 741 */ 742 if (command_generated_output){ 743 if ( *(destination->text.p-1) == (int) space_char) { 744 * (-- destination->text.p) = '\0'; 745 } 746 } else { 747 /* 748 * If the command didn't generate any output, 749 * set the buffer to a null string. 750 */ 751 *(destination->text.p) = '\0'; 752 } 753 754 status = pclose(fd); 755 if (status != 0) { 756 WCSTOMBS(mbs_buffer, command->buffer.start); 757 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 112, "The command `%s' returned status `%d'"), 758 mbs_buffer, 759 WEXITSTATUS(status)); 760 } 761 } 762 763