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
my_open(const char * path,int oflag,mode_t mode)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
redirect_io(char * stdout_file,char * stderr_file)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
doshell(wchar_t * command,register Boolean ignore_error,char * stdout_file,char * stderr_file,int nice_prio)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
exec_vp(register char * name,register char ** argv,char ** envp,register Boolean ignore_error,pathpt vroot_path)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
doexec(register wchar_t * command,register Boolean ignore_error,char * stdout_file,char * stderr_file,pathpt vroot_path,int nice_prio)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
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)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
sh_command2string(register String command,register String destination)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