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