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