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
my_open(const char * path,int oflag,mode_t mode)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
redirect_io(char * stdout_file,char * stderr_file)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 *
nice_attr(posix_spawnattr_t * attr,int nice_prio)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
doshell(wchar_t * command,Boolean ignore_error,char * stdout_file,char * stderr_file,int nice_prio)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
spawn_vp(char * name,char ** argv,char ** envp,posix_spawnattr_t * attr,Boolean ignore_error,pathpt vroot_path)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
doexec(wchar_t * command,Boolean ignore_error,char * stdout_file,char * stderr_file,pathpt vroot_path,int nice_prio)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
await(Boolean ignore_error,Boolean silent_error,Name target,wchar_t * command,pid_t running_pid,void * xdrs_p,int job_msg_id)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
sh_command2string(String command,String destination)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