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