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 = wcslen(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 = wcslen(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 = wcslen(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 579