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