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