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