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 */
69static 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 */
76int
77my_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 */
92void
93redirect_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 */
148int
149doshell(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 */
240static Boolean
241exec_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 */
313int
314doexec(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 */
433Boolean
434await(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 */
525void
526sh_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