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