1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 
  27 /*
  28  *      dosys.cc
  29  *
  30  *      Execute one commandline
  31  */
  32 
  33 /*
  34  * Included files
  35  */
  36 #include <sys/wait.h>                     /* WIFEXITED(status) */
  37 #include <alloca.h>               /* alloca() */
  38 
  39 #include <stdio.h>                /* errno */
  40 #include <errno.h>                /* errno */
  41 #include <fcntl.h>                /* open() */
  42 #include <mksh/dosys.h>
  43 #include <mksh/macro.h>           /* getvar() */
  44 #include <mksh/misc.h>            /* getmem(), fatal_mksh(), errmsg() */
  45 #include <sys/signal.h>           /* SIG_DFL */
  46 #include <sys/stat.h>             /* open() */
  47 #include <sys/wait.h>             /* wait() */
  48 #include <ulimit.h>               /* ulimit() */
  49 #include <unistd.h>               /* close(), dup2() */
  50 #include <libintl.h>
  51 
  52 /*
  53  * typedefs & structs
  54  */
  55 
  56 /*
  57  * Static variables
  58  */
  59 
  60 /*
  61  * File table of contents
  62  */
  63 static Boolean  exec_vp(register char *name, register char **argv, char **envp, register Boolean ignore_error, pathpt vroot_path);
  64 
  65 /*
  66  * Workaround for NFS bug. Sometimes, when running 'open' on a remote
  67  * dmake server, it fails with "Stale NFS file handle" error.
  68  * The second attempt seems to work.
  69  */
  70 int
  71 my_open(const char *path, int oflag, mode_t mode) {
  72         int res = open(path, oflag, mode);
  73         if (res < 0 && (errno == ESTALE || errno == EAGAIN)) {
  74                 /* Stale NFS file handle. Try again */
  75                 res = open(path, oflag, mode);
  76         }
  77         return res;
  78 }
  79 
  80 /*
  81  *      void
  82  *      redirect_io(char *stdout_file, char *stderr_file)
  83  *
  84  *      Redirects stdout and stderr for a child mksh process.
  85  */
  86 void
  87 redirect_io(char *stdout_file, char *stderr_file)
  88 {        
  89         long            descriptor_limit;
  90         int             i;
  91 
  92         if ((descriptor_limit = ulimit(UL_GDESLIM)) < 0) {
  93                 fatal_mksh(gettext("ulimit() failed: %s"), errmsg(errno));
  94         }
  95         for (i = 3; i < descriptor_limit; i++) {
  96                 (void) close(i);
  97         }
  98         if ((i = my_open(stdout_file,
  99                  O_WRONLY | O_CREAT | O_TRUNC | O_DSYNC,
 100                  S_IREAD | S_IWRITE)) < 0) {
 101                 fatal_mksh(gettext("Couldn't open standard out temp file `%s': %s"),
 102                       stdout_file,
 103                       errmsg(errno));
 104         } else {
 105                 if (dup2(i, 1) == -1) {
 106                         fatal_mksh("*** Error: dup2(3, 1) failed: %s",
 107                                 errmsg(errno));
 108                 }
 109                 close(i);
 110         }
 111         if (stderr_file == NULL) {
 112                 if (dup2(1, 2) == -1) {
 113                         fatal_mksh("*** Error: dup2(1, 2) failed: %s",
 114                                 errmsg(errno));
 115                 }
 116         } else if ((i = my_open(stderr_file,
 117                         O_WRONLY | O_CREAT | O_TRUNC | O_DSYNC,
 118                         S_IREAD | S_IWRITE)) < 0) {
 119                 fatal_mksh(gettext("Couldn't open standard error temp file `%s': %s"),
 120                       stderr_file,
 121                       errmsg(errno));
 122         } else {
 123                 if (dup2(i, 2) == -1) {
 124                         fatal_mksh("*** Error: dup2(3, 2) failed: %s",
 125                                 errmsg(errno));
 126                 }
 127                 close(i);
 128         }
 129 }
 130 
 131 /*
 132  *      doshell(command, ignore_error)
 133  *
 134  *      Used to run command lines that include shell meta-characters.
 135  *      The make macro SHELL is supposed to contain a path to the shell.
 136  *
 137  *      Return value:
 138  *                              The pid of the process we started
 139  *
 140  *      Parameters:
 141  *              command         The command to run
 142  *              ignore_error    Should we abort on error?
 143  *
 144  *      Global variables used:
 145  *              filter_stderr   If -X is on we redirect stderr
 146  *              shell_name      The Name "SHELL", used to get the path to shell
 147  */
 148 int
 149 doshell(wchar_t *command, register Boolean ignore_error, char *stdout_file, char *stderr_file, int nice_prio)
 150 {
 151         char                    *argv[6];
 152         int                     argv_index = 0;
 153         int                     cmd_argv_index;
 154         int                     length;
 155         char                    nice_prio_buf[MAXPATHLEN];
 156         register Name           shell = getvar(shell_name);
 157         register char           *shellname;
 158         char                    *tmp_mbs_buffer;
 159 
 160 
 161         if (IS_EQUAL(shell->string_mb, "")) {
 162                 shell = shell_name;
 163         }
 164         if ((shellname = strrchr(shell->string_mb, (int) slash_char)) == NULL) {
 165                 shellname = shell->string_mb;
 166         } else {
 167                 shellname++;
 168         }
 169 
 170         /*
 171          * Only prepend the /usr/bin/nice command to the original command
 172          * if the nice priority, nice_prio, is NOT zero (0).
 173          * Nice priorities can be a positive or a negative number.
 174          */
 175         if (nice_prio != 0) {
 176                 argv[argv_index++] = (char *)"nice";
 177                 (void) sprintf(nice_prio_buf, "-%d", nice_prio);
 178                 argv[argv_index++] = strdup(nice_prio_buf);
 179         }
 180         argv[argv_index++] = shellname;
 181         argv[argv_index++] = (char*)(ignore_error ? "-c" : "-ce");
 182         if ((length = wslen(command)) >= MAXPATHLEN) {
 183                 tmp_mbs_buffer = getmem((length * MB_LEN_MAX) + 1);
 184                 (void) wcstombs(tmp_mbs_buffer, command, (length * MB_LEN_MAX) + 1);
 185                 cmd_argv_index = argv_index;
 186                 argv[argv_index++] = strdup(tmp_mbs_buffer);
 187                 retmem_mb(tmp_mbs_buffer);
 188         } else {
 189                 WCSTOMBS(mbs_buffer, command);
 190                 cmd_argv_index = argv_index;
 191                 argv[argv_index++] = strdup(mbs_buffer);
 192         }
 193         argv[argv_index] = NULL;
 194         (void) fflush(stdout);
 195         if ((childPid = fork()) == 0) {
 196                 enable_interrupt((void (*) (int)) SIG_DFL);
 197 #if 0
 198                 if (filter_stderr) {
 199                         redirect_stderr();
 200                 }
 201 #endif
 202                 if (nice_prio != 0) {
 203                         (void) execve("/usr/bin/nice", argv, environ);
 204                         fatal_mksh(gettext("Could not load `/usr/bin/nice': %s"),
 205                               errmsg(errno));
 206                 } else {
 207                         (void) execve(shell->string_mb, argv, environ);
 208                         fatal_mksh(gettext("Could not load Shell from `%s': %s"),
 209                               shell->string_mb,
 210                               errmsg(errno));
 211                 }
 212         }
 213         if (childPid  == -1) {
 214                 fatal_mksh(gettext("fork failed: %s"),
 215                       errmsg(errno));
 216         }
 217         retmem_mb(argv[cmd_argv_index]);
 218         return childPid;
 219 }
 220 
 221 /*
 222  *      exec_vp(name, argv, envp, ignore_error)
 223  *
 224  *      Like execve, but does path search.
 225  *      This starts command when make invokes it directly (without a shell).
 226  *
 227  *      Return value:
 228  *                              Returns false if the exec failed
 229  *
 230  *      Parameters:
 231  *              name            The name of the command to run
 232  *              argv            Arguments for the command
 233  *              envp            The environment for it
 234  *              ignore_error    Should we abort on error?
 235  *
 236  *      Global variables used:
 237  *              shell_name      The Name "SHELL", used to get the path to shell
 238  *              vroot_path      The path used by the vroot package
 239  */
 240 static Boolean
 241 exec_vp(register char *name, register char **argv, char **envp, register Boolean ignore_error, pathpt vroot_path)
 242 {
 243         register Name           shell = getvar(shell_name);
 244         register char           *shellname;
 245         char                    *shargv[4];
 246         Name                    tmp_shell;
 247 
 248         if (IS_EQUAL(shell->string_mb, "")) {
 249                 shell = shell_name;
 250         }
 251 
 252         for (int i = 0; i < 5; i++) {
 253                 (void) execve_vroot(name,
 254                                     argv + 1,
 255                                     envp,
 256                                     vroot_path,
 257                                     VROOT_DEFAULT);
 258                 switch (errno) {
 259                 case ENOEXEC:
 260                 case ENOENT:
 261                         /* That failed. Let the shell handle it */
 262                         shellname = strrchr(shell->string_mb, (int) slash_char);
 263                         if (shellname == NULL) {
 264                                 shellname = shell->string_mb;
 265                         } else {
 266                                 shellname++;
 267                         }
 268                         shargv[0] = shellname;
 269                         shargv[1] = (char*)(ignore_error ? "-c" : "-ce");
 270                         shargv[2] = argv[0];
 271                         shargv[3] = NULL;
 272                         tmp_shell = getvar(shell_name);
 273                         if (IS_EQUAL(tmp_shell->string_mb, "")) {
 274                                 tmp_shell = shell_name;
 275                         }
 276                         (void) execve_vroot(tmp_shell->string_mb,
 277                                             shargv,
 278                                             envp,
 279                                             vroot_path,
 280                                             VROOT_DEFAULT);
 281                         return failed;
 282                 case ETXTBSY:
 283                         /*
 284                          * The program is busy (debugged?).
 285                          * Wait and then try again.
 286                          */
 287                         (void) sleep((unsigned) i);
 288                 case EAGAIN:
 289                         break;
 290                 default:
 291                         return failed;
 292                 }
 293         }
 294         return failed;
 295 }
 296 
 297 /*
 298  *      doexec(command, ignore_error)
 299  *
 300  *      Will scan an argument string and split it into words
 301  *      thus building an argument list that can be passed to exec_ve()
 302  *
 303  *      Return value:
 304  *                              The pid of the process started here
 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  */
 313 int
 314 doexec(register wchar_t *command, register Boolean ignore_error, char *stdout_file, char *stderr_file, pathpt vroot_path, int nice_prio)
 315 {
 316         int                     arg_count = 5;
 317         char                    **argv;
 318         int                     length;
 319         char                    nice_prio_buf[MAXPATHLEN];
 320         register char           **p;
 321         wchar_t                 *q;
 322         register wchar_t        *t;
 323         char                    *tmp_mbs_buffer;
 324 
 325         /*
 326          * Only prepend the /usr/bin/nice command to the original command
 327          * if the nice priority, nice_prio, is NOT zero (0).
 328          * Nice priorities can be a positive or a negative number.
 329          */
 330         if (nice_prio != 0) {
 331                 arg_count += 2;
 332         }
 333         for (t = command; *t != (int) nul_char; t++) {
 334                 if (iswspace(*t)) {
 335                         arg_count++;
 336                 }
 337         }
 338         argv = (char **)alloca(arg_count * (sizeof(char *)));
 339         /*
 340          * Reserve argv[0] for sh in case of exec_vp failure.
 341          * Don't worry about prepending /usr/bin/nice command to argv[0].
 342          * In fact, doing it may cause the sh command to fail!
 343          */
 344         p = &argv[1];
 345         if ((length = wslen(command)) >= MAXPATHLEN) {
 346                 tmp_mbs_buffer = getmem((length * MB_LEN_MAX) + 1);
 347                 (void) wcstombs(tmp_mbs_buffer, command, (length * MB_LEN_MAX) + 1);
 348                 argv[0] = strdup(tmp_mbs_buffer);
 349                 retmem_mb(tmp_mbs_buffer);
 350         } else {
 351                 WCSTOMBS(mbs_buffer, command);
 352                 argv[0] = strdup(mbs_buffer);
 353         }
 354 
 355         if (nice_prio != 0) {
 356                 *p++ = strdup("/usr/bin/nice");
 357                 (void) sprintf(nice_prio_buf, "-%d", nice_prio);
 358                 *p++ = strdup(nice_prio_buf);
 359         }
 360         /* Build list of argument words. */
 361         for (t = command; *t;) {
 362                 if (p >= &argv[arg_count]) {
 363                         /* This should never happen, right? */
 364                         WCSTOMBS(mbs_buffer, command);
 365                         fatal_mksh(gettext("Command `%s' has more than %d arguments"),
 366                               mbs_buffer,
 367                               arg_count);
 368                 }
 369                 q = t;
 370                 while (!iswspace(*t) && (*t != (int) nul_char)) {
 371                         t++;
 372                 }
 373                 if (*t) {
 374                         for (*t++ = (int) nul_char; iswspace(*t); t++);
 375                 }
 376                 if ((length = wslen(q)) >= MAXPATHLEN) {
 377                         tmp_mbs_buffer = getmem((length * MB_LEN_MAX) + 1);
 378                         (void) wcstombs(tmp_mbs_buffer, q, (length * MB_LEN_MAX) + 1);
 379                         *p++ = strdup(tmp_mbs_buffer);
 380                         retmem_mb(tmp_mbs_buffer);
 381                 } else {
 382                         WCSTOMBS(mbs_buffer, q);
 383                         *p++ = strdup(mbs_buffer);
 384                 }
 385         }
 386         *p = NULL;
 387 
 388         /* Then exec the command with that argument list. */
 389         (void) fflush(stdout);
 390         if ((childPid = fork()) == 0) {
 391                 enable_interrupt((void (*) (int)) SIG_DFL);
 392 #if 0
 393                 if (filter_stderr) {
 394                         redirect_stderr();
 395                 }
 396 #endif
 397                 (void) exec_vp(argv[1], argv, environ, ignore_error, vroot_path);
 398                 fatal_mksh(gettext("Cannot load command `%s': %s"), argv[1], errmsg(errno));
 399         }
 400         if (childPid  == -1) {
 401                 fatal_mksh(gettext("fork failed: %s"),
 402                       errmsg(errno));
 403         }
 404         for (int i = 0; argv[i] != NULL; i++) {
 405                 retmem_mb(argv[i]);
 406         }
 407         return childPid;
 408 }
 409 
 410 /*
 411  *      await(ignore_error, silent_error, target, command, running_pid)
 412  *
 413  *      Wait for one child process and analyzes
 414  *      the returned status when the child process terminates.
 415  *
 416  *      Return value:
 417  *                              Returns true if commands ran OK
 418  *
 419  *      Parameters:
 420  *              ignore_error    Should we abort on error?
 421  *              silent_error    Should error messages be suppressed for dmake?
 422  *              target          The target we are building, for error msgs
 423  *              command         The command we ran, for error msgs
 424  *              running_pid     The pid of the process we are waiting for
 425  *              
 426  *      Static variables used:
 427  *              filter_file     The fd for the filter file
 428  *              filter_file_name The name of the filter file
 429  *
 430  *      Global variables used:
 431  *              filter_stderr   Set if -X is on
 432  */
 433 Boolean
 434 await(register Boolean ignore_error, register Boolean silent_error, Name target, wchar_t *command, pid_t running_pid, void *xdrs_p, int job_msg_id)
 435 {
 436         int                     status;
 437         char                    *buffer;
 438         int                     core_dumped;
 439         int                     exit_status;
 440         FILE                    *outfp;
 441         register pid_t          pid;
 442         struct stat             stat_buff;
 443         int                     termination_signal;
 444         char                    tmp_buf[MAXPATHLEN];
 445 
 446         while ((pid = wait(&status)) != running_pid) {
 447                 if (pid == -1) {
 448                         fatal_mksh(gettext("wait() failed: %s"), errmsg(errno));
 449                 }
 450         }
 451         (void) fflush(stdout);
 452         (void) fflush(stderr);
 453 
 454         if (status == 0) {
 455 
 456 #ifdef PRINT_EXIT_STATUS
 457                 warning_mksh("I'm in await(), and status is 0.");
 458 #endif
 459 
 460                 return succeeded;
 461         }
 462 
 463 #ifdef PRINT_EXIT_STATUS
 464         warning_mksh("I'm in await(), and status is *NOT* 0.");
 465 #endif
 466 
 467 
 468         exit_status = WEXITSTATUS(status);
 469 
 470 #ifdef PRINT_EXIT_STATUS
 471         warning_mksh("I'm in await(), and exit_status is %d.", exit_status);
 472 #endif
 473 
 474         termination_signal = WTERMSIG(status);
 475         core_dumped = WCOREDUMP(status);
 476 
 477         /*
 478          * If the child returned an error, we now try to print a
 479          * nice message about it.
 480          */
 481         
 482         tmp_buf[0] = (int) nul_char;
 483         if (!silent_error) {
 484                 if (exit_status != 0) {
 485                         (void) fprintf(stdout,
 486                                        gettext("*** Error code %d"),
 487                                        exit_status);
 488                 } else {
 489                                 (void) fprintf(stdout,
 490                                                gettext("*** Signal %d"),
 491                                                termination_signal);
 492                         if (core_dumped) {
 493                                 (void) fprintf(stdout,
 494                                                gettext(" - core dumped"));
 495                         }
 496                 }
 497                 if (ignore_error) {
 498                         (void) fprintf(stdout,
 499                                        gettext(" (ignored)"));
 500                 }
 501                 (void) fprintf(stdout, "\n");
 502                 (void) fflush(stdout);
 503         }
 504 
 505 #ifdef PRINT_EXIT_STATUS
 506         warning_mksh("I'm in await(), returning failed.");
 507 #endif
 508 
 509         return failed;
 510 }
 511 
 512 /*
 513  *      sh_command2string(command, destination)
 514  *
 515  *      Run one sh command and capture the output from it.
 516  *
 517  *      Return value:
 518  *
 519  *      Parameters:
 520  *              command         The command to run
 521  *              destination     Where to deposit the output from the command
 522  *              
 523  *      Static variables used:
 524  *
 525  *      Global variables used:
 526  */
 527 void
 528 sh_command2string(register String command, register String destination)
 529 {
 530         register FILE           *fd;
 531         register int            chr;
 532         int                     status;
 533         Boolean                 command_generated_output = false;
 534 
 535         command->text.p = (int) nul_char;
 536         WCSTOMBS(mbs_buffer, command->buffer.start);
 537         if ((fd = popen(mbs_buffer, "r")) == NULL) {
 538                 WCSTOMBS(mbs_buffer, command->buffer.start);
 539                 fatal_mksh(gettext("Could not run command `%s' for :sh transformation"),
 540                       mbs_buffer);
 541         }
 542         while ((chr = getc(fd)) != EOF) {
 543                 if (chr == (int) newline_char) {
 544                         chr = (int) space_char;
 545                 }
 546                 command_generated_output = true;
 547                 append_char(chr, destination);
 548         }
 549 
 550         /*
 551          * We don't want to keep the last LINE_FEED since usually
 552          * the output of the 'sh:' command is used to evaluate
 553          * some MACRO. ( /bin/sh and other shell add a line feed
 554          * to the output so that the prompt appear in the right place.
 555          * We don't need that
 556          */
 557         if (command_generated_output){
 558                 if ( *(destination->text.p-1) == (int) space_char) {
 559                         * (-- destination->text.p) = '\0';
 560                 } 
 561         } else {
 562                 /*
 563                  * If the command didn't generate any output,
 564                  * set the buffer to a null string.
 565                  */
 566                 *(destination->text.p) = '\0';
 567         }
 568                         
 569         status = pclose(fd);
 570         if (status != 0) {
 571                 WCSTOMBS(mbs_buffer, command->buffer.start);
 572                 fatal_mksh(gettext("The command `%s' returned status `%d'"),
 573                       mbs_buffer,
 574                       WEXITSTATUS(status));
 575         }
 576 }
 577 
 578