xref: /illumos-gate/usr/src/lib/libpkg/common/pkgexecv.c (revision 2983dda76a6d296fdb560c88114fe41caad1b84f)
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 /*
23  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
29 
30 
31 #include <stdio.h>
32 #include <errno.h>
33 #include <string.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <signal.h>
37 #include <wait.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <fcntl.h>
41 #include <pwd.h>
42 #include <grp.h>
43 #include "pkglib.h"
44 #include "pkglibmsgs.h"
45 #include "pkglocale.h"
46 
47 /* global environment inherited by this process */
48 extern char	**environ;
49 
50 /* dstream.c */
51 extern int	ds_curpartcnt;
52 extern int	ds_close(int pkgendflg);
53 
54 /*
55  * global internal (private) variables
56  */
57 
58 /* received signal count - bumped with hooked signals are caught */
59 
60 static int	sig_received = 0;
61 
62 /*
63  * Name:	sig_trap
64  * Description:	hooked up to signal counts number of signals received
65  * Arguments:	a_signo - [RO, *RO] - (int)
66  *			Integer representing the signal received; see signal(3c)
67  * Returns:	<void>
68  */
69 
70 static void
71 sig_trap(int a_signo)
72 {
73 	sig_received++;
74 }
75 
76 /*
77  * Name:	pkgexecv
78  * Description:	Asynchronously execute a package command in a separate process
79  *		and return results - the subprocess MUST arm it's own SIGINT
80  *		and SIGHUP signals and must return a standard package command
81  *		exit code (see returns below)
82  *		Only another package command (such as pkginstall, pkgremove,
83  *		etc.) may be called via this interface. No files are closed
84  *		because open files are passed across to certain commands using
85  *		either implicit agreements between the two (yuk!) or by using
86  *		the '-p' option which passes a string of digits, some of which
87  *		represent open file descriptors passed through this interface!
88  * Arguments:	filein - [RO, *RO] - (char *)
89  *			Pointer to string representing the name of the file to
90  *			use for the package commands's stdin
91  *			== (char *)NULL or == "" - the current stdin
92  *			is used for the new package command process
93  *		fileout - [RO, *RO] - (char *)
94  *			Pointer to string representing the name of the file to
95  *			use for the package commands's stdout and stderr
96  *			== (char *)NULL or == "" - the current stdout/stderr
97  *			is used for the new package command process
98  *		uname - [RO, *RO] - (char *)
99  *			Pointer to string representing the user name to execute
100  *			the package command as - the user name is looked up
101  *			using the ncgrpw:cpwnam() interface
102  *			== (char *)NULL or == "" - the user name of the current
103  *			process is used for the new package command process
104  *		gname - [RO, *RO] - (char *)
105  *			Pointer to string representing the group name to execute
106  *			the package command as - the group name is looked up
107  *			using the ncgrpw:cgrnam() interface
108  *			== (char *)NULL or == "" - the group name of the current
109  *			process is used for the new package command process
110  *		arg - [RO, *RO] - (char **)
111  *			Pointer to array of character pointers representing the
112  *			arguments to pass to the package command - the array is
113  *			terminated with a pointer to (char *)NULL
114  * Returns:	int
115  *			== 99 - exec() of package command failed
116  *			== -1 - fork failed or other fatal error during
117  *				execution of the package command
118  *			otherwise - exit code from package command:
119  *			0 - successful
120  *			1 - package operation failed (fatal error)
121  *			2 - non-fatal error (warning)
122  *			3 - operation interrupted (including SIGINT/SIGHUP)
123  *			4 - admin settings prevented operation
124  *			5 - administration required and -n was specified
125  *			IN addition:
126  *			10 is added to the return code if reboot after the
127  *				installation of all packages is required
128  *			20 is added to the return code if immediate reboot
129  *				after installation of this package is required
130  */
131 
132 int
133 pkgexecv(char *filein, char *fileout, char *uname, char *gname, char *arg[])
134 {
135 	int			exit_no;
136 	int			n;
137 	int			status;
138 	pid_t			pid;
139 	pid_t			waitstat;
140 	struct group		*grp;
141 	struct passwd		*pwp;
142 	struct sigaction	nact;
143 	struct sigaction	oact;
144 	void			(*funcSighup)();
145 	void			(*funcSigint)();
146 
147 	/* flush standard i/o before creating new process */
148 
149 	(void) fflush(stdout);
150 	(void) fflush(stderr);
151 
152 	/*
153 	 * hold SIGINT/SIGHUP signals and reset signal received counter;
154 	 * after the vfork() the parent and child need to setup their respective
155 	 * interrupt handling and release the hold on the signals
156 	 */
157 
158 	(void) sighold(SIGINT);
159 	(void) sighold(SIGHUP);
160 
161 	sig_received = 0;
162 
163 	/*
164 	 * create new process to execute command in;
165 	 * vfork() is being used to avoid duplicating the parents
166 	 * memory space - this means that the child process may
167 	 * not modify any of the parents memory including the
168 	 * standard i/o descriptors - all the child can do is
169 	 * adjust interrupts and open files as a prelude to a
170 	 * call to exec().
171 	 */
172 
173 	pid = vfork();
174 
175 	if (pid < 0) {
176 		/*
177 		 * *************************************************************
178 		 * fork failed!
179 		 * *************************************************************
180 		 */
181 
182 		progerr(pkg_gt(ERR_FORK_FAILED), errno, strerror(errno));
183 
184 		/* release hold on signals */
185 
186 		(void) sigrelse(SIGHUP);
187 		(void) sigrelse(SIGINT);
188 
189 		return (-1);
190 	}
191 
192 	if (pid > 0) {
193 		/*
194 		 * *************************************************************
195 		 * This is the forking (parent) process
196 		 * *************************************************************
197 		 */
198 
199 		/* close datastream if any portion read */
200 
201 		if (ds_curpartcnt >= 0) {
202 			if (ds_close(0) != 0) {
203 				/* kill child process */
204 
205 				(void) sigsend(P_PID, pid, SIGKILL);
206 
207 				/* release hold on signals */
208 
209 				(void) sigrelse(SIGHUP);
210 				(void) sigrelse(SIGINT);
211 
212 				return (-1);
213 			}
214 		}
215 
216 		/*
217 		 * setup signal handlers for SIGINT and SIGHUP and release hold
218 		 */
219 
220 		/* hook SIGINT to sig_trap() */
221 
222 		nact.sa_handler = sig_trap;
223 		nact.sa_flags = SA_RESTART;
224 		(void) sigemptyset(&nact.sa_mask);
225 
226 		if (sigaction(SIGINT, &nact, &oact) < 0) {
227 			funcSigint = SIG_DFL;
228 		} else {
229 			funcSigint = oact.sa_handler;
230 		}
231 
232 		/* hook SIGHUP to sig_trap() */
233 
234 		nact.sa_handler = sig_trap;
235 		nact.sa_flags = SA_RESTART;
236 		(void) sigemptyset(&nact.sa_mask);
237 
238 		if (sigaction(SIGHUP, &nact, &oact) < 0) {
239 			funcSighup = SIG_DFL;
240 		} else {
241 			funcSighup = oact.sa_handler;
242 		}
243 
244 		/* release hold on signals */
245 
246 		(void) sigrelse(SIGHUP);
247 		(void) sigrelse(SIGINT);
248 
249 		/*
250 		 * wait for the process to exit, reap child exit status
251 		 */
252 
253 		for (;;) {
254 			status = 0;
255 			waitstat = waitpid(pid, (int *)&status, 0);
256 			if (waitstat < 0) {
257 				/* waitpid returned error */
258 				if (errno == EAGAIN) {
259 					/* try again */
260 					continue;
261 				}
262 				if (errno == EINTR) {
263 					continue;
264 				}
265 				/* error from waitpid: bail */
266 				break;
267 			} else if (waitstat == pid) {
268 				/* child exit status available */
269 				break;
270 			}
271 		}
272 
273 		/*
274 		 * reset signal handlers
275 		 */
276 
277 		/* reset SIGINT */
278 
279 		nact.sa_handler = funcSigint;
280 		nact.sa_flags = SA_RESTART;
281 		(void) sigemptyset(&nact.sa_mask);
282 
283 		(void) sigaction(SIGINT, &nact, (struct sigaction *)NULL);
284 
285 		/* reset SIGHUP */
286 
287 		nact.sa_handler = funcSighup;
288 		nact.sa_flags = SA_RESTART;
289 		(void) sigemptyset(&nact.sa_mask);
290 
291 		(void) sigaction(SIGHUP, &nact, (struct sigaction *)NULL);
292 
293 		/* error if child process does not match */
294 
295 		if (waitstat != pid) {
296 			progerr(pkg_gt(ERR_WAIT_FAILED), pid, waitstat, status,
297 				errno, strerror(errno));
298 			return (-1);
299 		}
300 
301 		/*
302 		 * determine final exit code:
303 		 * - if signal received, then return interrupted (3)
304 		 * - if child exit status is available, return exit child status
305 		 * - otherwise return error (-1)
306 		 */
307 
308 		if (sig_received != 0) {
309 			exit_no = 3;	/* interrupted */
310 		} else if (WIFEXITED(status)) {
311 			exit_no = WEXITSTATUS(status);
312 		} else {
313 			exit_no = -1;	/* exec() or other process error */
314 		}
315 
316 		return (exit_no);
317 	}
318 
319 	/*
320 	 * *********************************************************************
321 	 * This is the forked (child) process
322 	 * *********************************************************************
323 	 */
324 
325 	/* reset all signals to default */
326 
327 	for (n = 0; n < NSIG; n++) {
328 		(void) sigset(n, SIG_DFL);
329 	}
330 
331 	/* release hold on signals held by parent before fork() */
332 
333 	(void) sigrelse(SIGHUP);
334 	(void) sigrelse(SIGINT);
335 
336 	/*
337 	 * The caller wants to have stdin connected to filein.
338 	 */
339 
340 	if (filein && *filein) {
341 		/*
342 		 * If input is supposed to be connected to /dev/tty
343 		 */
344 		if (strncmp(filein, "/dev/tty", 8) == 0) {
345 			/*
346 			 * If stdin is connected to a tty device.
347 			 */
348 			if (isatty(STDIN_FILENO)) {
349 				/*
350 				 * Reopen it to /dev/tty.
351 				 */
352 				n = open(filein, O_RDONLY);
353 				if (n >= 0) {
354 					(void) dup2(n, STDIN_FILENO);
355 				}
356 			}
357 		} else {
358 			/*
359 			 * If we did not want to be connected to /dev/tty, we
360 			 * connect input to the requested file no questions.
361 			 */
362 			n = open(filein, O_RDONLY);
363 			if (n >= 0) {
364 				(void) dup2(n, STDIN_FILENO);
365 			}
366 		}
367 	}
368 
369 	/*
370 	 * The caller wants to have stdout and stderr connected to fileout.
371 	 * If "fileout" is "/dev/tty" then reconnect stdout to "/dev/tty"
372 	 * only if /dev/tty is not already associated with "a tty".
373 	 */
374 
375 	if (fileout && *fileout) {
376 		/*
377 		 * If output is supposed to be connected to /dev/tty
378 		 */
379 		if (strncmp(fileout, "/dev/tty", 8) == 0) {
380 			/*
381 			 * If stdout is connected to a tty device.
382 			 */
383 			if (isatty(STDOUT_FILENO)) {
384 				/*
385 				 * Reopen it to /dev/tty if /dev/tty available.
386 				 */
387 				n = open(fileout, O_WRONLY);
388 				if (n >= 0) {
389 					/*
390 					 * /dev/tty is available - close the
391 					 * current standard output stream, and
392 					 * reopen it on /dev/tty
393 					 */
394 					(void) dup2(n, STDOUT_FILENO);
395 				}
396 			}
397 			/*
398 			 * not connected to tty device - probably redirect to
399 			 * file - preserve existing output device
400 			 */
401 		} else {
402 			/*
403 			 * If we did not want to be connected to /dev/tty, we
404 			 * connect output to the requested file no questions.
405 			 */
406 			/* LINTED O_CREAT without O_EXCL specified in call to */
407 			n = open(fileout, O_WRONLY|O_CREAT|O_APPEND, 0666);
408 			if (n >= 0) {
409 				(void) dup2(n, STDOUT_FILENO);
410 			}
411 		}
412 
413 		/*
414 		 * Dup stderr from stdout.
415 		 */
416 
417 		(void) dup2(STDOUT_FILENO, STDERR_FILENO);
418 	}
419 
420 	/*
421 	 * do NOT close all file descriptors except stdio
422 	 * file descriptors are passed in to some subcommands
423 	 * (see dstream:ds_getinfo() and dstream:ds_putinfo())
424 	 */
425 
426 	/* set group/user i.d. if requested */
427 
428 	if (gname && *gname && (grp = cgrnam(gname)) != NULL) {
429 		if (setgid(grp->gr_gid) == -1) {
430 			progerr(pkg_gt(ERR_SETGID), grp->gr_gid);
431 		}
432 	}
433 	if (uname && *uname && (pwp = cpwnam(uname)) != NULL) {
434 		if (setuid(pwp->pw_uid) == -1) {
435 			progerr(pkg_gt(ERR_SETUID), pwp->pw_uid);
436 		}
437 	}
438 
439 	/* execute target executable */
440 
441 	(void) execve(arg[0], arg, environ);
442 	progerr(pkg_gt(ERR_EX_FAIL), arg[0], errno);
443 	_exit(99);
444 	/*NOTREACHED*/
445 }
446