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