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 */
30
31
32 /*
33 * dosys.cc
34 *
35 * Execute one commandline
36 */
37
38 /*
39 * Included files
40 */
41 #include <sys/wait.h> /* WIFEXITED(status) */
42 #include <alloca.h> /* alloca() */
43
44 #include <stdio.h> /* errno */
45 #include <errno.h> /* errno */
46 #include <fcntl.h> /* open() */
47 #include <mksh/dosys.h>
48 #include <mksh/macro.h> /* getvar() */
49 #include <mksh/misc.h> /* getmem(), fatal_mksh(), errmsg() */
50 #include <sys/signal.h> /* SIG_DFL */
51 #include <sys/stat.h> /* open() */
52 #include <sys/wait.h> /* wait() */
53 #include <ulimit.h> /* ulimit() */
54 #include <unistd.h> /* close(), dup2() */
55 #include <stdlib.h> /* closefrom() */
56 #include <libintl.h>
57
58 /*
59 * typedefs & structs
60 */
61
62 /*
63 * Static variables
64 */
65
66 /*
67 * File table of contents
68 */
69 static Boolean exec_vp(char *name, char **argv, char **envp, Boolean ignore_error, pathpt vroot_path);
70
71 /*
72 * Workaround for NFS bug. Sometimes, when running 'open' on a remote
73 * dmake server, it fails with "Stale NFS file handle" error.
74 * The second attempt seems to work.
75 */
76 int
my_open(const char * path,int oflag,mode_t mode)77 my_open(const char *path, int oflag, mode_t mode) {
78 int res = open(path, oflag, mode);
79 if (res < 0 && (errno == ESTALE || errno == EAGAIN)) {
80 /* Stale NFS file handle. Try again */
81 res = open(path, oflag, mode);
82 }
83 return res;
84 }
85
86 /*
87 * void
88 * redirect_io(char *stdout_file, char *stderr_file)
89 *
90 * Redirects stdout and stderr for a child mksh process.
91 */
92 void
redirect_io(char * stdout_file,char * stderr_file)93 redirect_io(char *stdout_file, char *stderr_file)
94 {
95 int i;
96
97 (void) closefrom(3);
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
doshell(wchar_t * command,Boolean ignore_error,char * stdout_file,char * stderr_file,int nice_prio)149 doshell(wchar_t *command, 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 Name shell = getvar(shell_name);
157 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
exec_vp(char * name,char ** argv,char ** envp,Boolean ignore_error,pathpt vroot_path)241 exec_vp(char *name, char **argv, char **envp, Boolean ignore_error, pathpt vroot_path)
242 {
243 Name shell = getvar(shell_name);
244 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
doexec(wchar_t * command,Boolean ignore_error,char * stdout_file,char * stderr_file,pathpt vroot_path,int nice_prio)314 doexec(wchar_t *command, 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 char **p;
321 wchar_t *q;
322 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
await(Boolean ignore_error,Boolean silent_error,Name target,wchar_t * command,pid_t running_pid,void * xdrs_p,int job_msg_id)434 await(Boolean ignore_error, 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 pid_t pid;
442 struct stat stat_buff;
443 int termination_signal;
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 if (!silent_error) {
482 if (exit_status != 0) {
483 (void) fprintf(stdout,
484 gettext("*** Error code %d"),
485 exit_status);
486 } else {
487 (void) fprintf(stdout,
488 gettext("*** Signal %d"),
489 termination_signal);
490 if (core_dumped) {
491 (void) fprintf(stdout,
492 gettext(" - core dumped"));
493 }
494 }
495 if (ignore_error) {
496 (void) fprintf(stdout,
497 gettext(" (ignored)"));
498 }
499 (void) fprintf(stdout, "\n");
500 (void) fflush(stdout);
501 }
502
503 #ifdef PRINT_EXIT_STATUS
504 warning_mksh("I'm in await(), returning failed.");
505 #endif
506
507 return failed;
508 }
509
510 /*
511 * sh_command2string(command, destination)
512 *
513 * Run one sh command and capture the output from it.
514 *
515 * Return value:
516 *
517 * Parameters:
518 * command The command to run
519 * destination Where to deposit the output from the command
520 *
521 * Static variables used:
522 *
523 * Global variables used:
524 */
525 void
sh_command2string(String command,String destination)526 sh_command2string(String command, String destination)
527 {
528 FILE *fd;
529 int chr;
530 int status;
531 Boolean command_generated_output = false;
532
533 command->text.p = NULL;
534 WCSTOMBS(mbs_buffer, command->buffer.start);
535 if ((fd = popen(mbs_buffer, "r")) == NULL) {
536 WCSTOMBS(mbs_buffer, command->buffer.start);
537 fatal_mksh(gettext("Could not run command `%s' for :sh transformation"),
538 mbs_buffer);
539 }
540 while ((chr = getc(fd)) != EOF) {
541 if (chr == (int) newline_char) {
542 chr = (int) space_char;
543 }
544 command_generated_output = true;
545 append_char(chr, destination);
546 }
547
548 /*
549 * We don't want to keep the last LINE_FEED since usually
550 * the output of the 'sh:' command is used to evaluate
551 * some MACRO. ( /bin/sh and other shell add a line feed
552 * to the output so that the prompt appear in the right place.
553 * We don't need that
554 */
555 if (command_generated_output){
556 if ( *(destination->text.p-1) == (int) space_char) {
557 * (-- destination->text.p) = '\0';
558 }
559 } else {
560 /*
561 * If the command didn't generate any output,
562 * set the buffer to a null string.
563 */
564 *(destination->text.p) = '\0';
565 }
566
567 status = pclose(fd);
568 if (status != 0) {
569 WCSTOMBS(mbs_buffer, command->buffer.start);
570 fatal_mksh(gettext("The command `%s' returned status `%d'"),
571 mbs_buffer,
572 WEXITSTATUS(status));
573 }
574 }
575
576
577