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