xref: /illumos-gate/usr/src/cmd/make/lib/mksh/dosys.cc (revision 356ba08c)
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(char *name, char **argv, char **envp, 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
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
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
148 doshell(wchar_t *command, 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 	Name			shell = getvar(shell_name);
156 	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
240 exec_vp(char *name, char **argv, char **envp, Boolean ignore_error, pathpt vroot_path)
241 {
242 	Name			shell = getvar(shell_name);
243 	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
313 doexec(wchar_t *command, 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 	char			**p;
320 	wchar_t			*q;
321 	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
433 await(Boolean ignore_error, 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 	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
527 sh_command2string(String command, String destination)
528 {
529 	FILE		*fd;
530 	int		chr;
531 	int		status;
532 	Boolean		command_generated_output = false;
533 
534 	command->text.p = NULL;
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