xref: /illumos-gate/usr/src/lib/libpkg/common/runcmd.c (revision 3970c098)
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 2009 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 
32 #include <stdio.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <strings.h>
36 #include <signal.h>
37 #include <fcntl.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <wait.h>
41 #include <sys/types.h>
42 #include "pkglib.h"
43 #include "pkglocale.h"
44 #include "pkglibmsgs.h"
45 
46 #ifndef _STDARG_H
47 #include "stdarg.h"
48 #endif
49 
50 /*
51  * Private definitions
52  */
53 
54 /* Maximum number of arguments to pkg_ExecCmdList */
55 
56 #define	MAX_EXEC_CMD_ARGS	100
57 
58 /* Size of buffer increments when reading from pipe */
59 
60 #define	PIPE_BUFFER_INCREMENT	256
61 
62 static char	errfile[L_tmpnam+1];
63 
64 /*
65  * Public Methods
66  */
67 
68 
69 void
rpterr(void)70 rpterr(void)
71 {
72 	FILE	*fp;
73 	int	c;
74 
75 	if (errfile[0]) {
76 		if (fp = fopen(errfile, "r")) {
77 			while ((c = getc(fp)) != EOF)
78 				(void) putc(c, stderr);
79 			(void) fclose(fp);
80 		}
81 		(void) unlink(errfile);
82 		errfile[0] = '\0';
83 	}
84 }
85 
86 void
ecleanup(void)87 ecleanup(void)
88 {
89 	if (errfile[0]) {
90 		(void) unlink(errfile);
91 		errfile[0] = '\0';
92 	}
93 }
94 
95 int
esystem(char * cmd,int ifd,int ofd)96 esystem(char *cmd, int ifd, int ofd)
97 {
98 	char	*perrfile;
99 	int	status = 0;
100 	pid_t	pid;
101 
102 	perrfile = tmpnam(NULL);
103 	if (perrfile == NULL) {
104 		progerr(
105 		    pkg_gt("unable to create temp error file, errno=%d"),
106 		    errno);
107 		return (-1);
108 	}
109 	(void) strlcpy(errfile, perrfile, sizeof (errfile));
110 
111 	/* flush standard i/o before creating new process */
112 
113 	(void) fflush(stderr);
114 	(void) fflush(stdout);
115 
116 	/*
117 	 * create new process to execute command in;
118 	 * vfork() is being used to avoid duplicating the parents
119 	 * memory space - this means that the child process may
120 	 * not modify any of the parents memory including the
121 	 * standard i/o descriptors - all the child can do is
122 	 * adjust interrupts and open files as a prelude to a
123 	 * call to exec().
124 	 */
125 
126 	pid = vfork();
127 	if (pid == 0) {
128 		/*
129 		 * this is the child process
130 		 */
131 		int	i;
132 
133 		/* reset any signals to default */
134 
135 		for (i = 0; i < NSIG; i++) {
136 			(void) sigset(i, SIG_DFL);
137 		}
138 
139 		if (ifd > 0) {
140 			(void) dup2(ifd, STDIN_FILENO);
141 		}
142 
143 		if (ofd >= 0 && ofd != STDOUT_FILENO) {
144 			(void) dup2(ofd, STDOUT_FILENO);
145 		}
146 
147 		i = open(errfile, O_WRONLY|O_CREAT|O_TRUNC, 0666);
148 		if (i >= 0) {
149 			(void) dup2(i, STDERR_FILENO);
150 		}
151 
152 		/* Close all open files except standard i/o */
153 
154 		closefrom(3);
155 
156 		/* execute target executable */
157 
158 		(void) execl("/sbin/sh", "/sbin/sh", "-c", cmd, NULL);
159 		progerr(pkg_gt("exec of <%s> failed, errno=%d"), cmd, errno);
160 		_exit(99);
161 	} else if (pid < 0) {
162 		/* fork failed! */
163 
164 		logerr(pkg_gt("bad vfork(), errno=%d"), errno);
165 		return (-1);
166 	}
167 
168 	/*
169 	 * this is the parent process
170 	 */
171 
172 	(void) sighold(SIGINT);
173 	pid = waitpid(pid, &status, 0);
174 	(void) sigrelse(SIGINT);
175 
176 	if (pid < 0) {
177 		return (-1); /* probably interrupted */
178 	}
179 
180 	switch (status & 0177) {
181 		case 0:
182 		case 0177:
183 			status = status >> 8;
184 			/*FALLTHROUGH*/
185 
186 		default:
187 			/* terminated by a signal */
188 			status = status & 0177;
189 	}
190 
191 	if (status == 0) {
192 		ecleanup();
193 	}
194 
195 	return (status);
196 }
197 
198 FILE *
epopen(char * cmd,char * mode)199 epopen(char *cmd, char *mode)
200 {
201 	char	*buffer, *perrfile;
202 	FILE	*pp;
203 	size_t	len;
204 	size_t	alen;
205 
206 	if (errfile[0]) {
207 		/* cleanup previous errfile */
208 		(void) unlink(errfile);
209 	}
210 
211 	perrfile = tmpnam(NULL);
212 	if (perrfile == NULL) {
213 		progerr(
214 		    pkg_gt("unable to create temp error file, errno=%d"),
215 		    errno);
216 		return ((FILE *)0);
217 	}
218 
219 	if (strlcpy(errfile, perrfile, sizeof (errfile)) > sizeof (errfile)) {
220 		progerr(pkg_gt("file name max length %d; name is too long: %s"),
221 		    sizeof (errfile), perrfile);
222 		return (NULL);
223 	}
224 
225 	len = strlen(cmd)+6+strlen(errfile);
226 	buffer = (char *)calloc(len, sizeof (char));
227 	if (buffer == NULL) {
228 		progerr(pkg_gt("no memory in epopen(), errno=%d"), errno);
229 		return (NULL);
230 	}
231 
232 	if (strchr(cmd, '|')) {
233 		alen = snprintf(buffer, len, "(%s) 2>%s", cmd, errfile);
234 	} else {
235 		alen = snprintf(buffer, len, "%s 2>%s", cmd, errfile);
236 	}
237 
238 	if (alen > len) {
239 		progerr(pkg_gt("command max length %d; cmd is too long: %s"),
240 		    len, cmd);
241 		return (NULL);
242 	}
243 
244 	pp = popen(buffer, mode);
245 
246 	free(buffer);
247 	return (pp);
248 }
249 
250 int
epclose(FILE * pp)251 epclose(FILE *pp)
252 {
253 	int n;
254 
255 	n = pclose(pp);
256 	if (n == 0)
257 		ecleanup();
258 	return (n);
259 }
260 
261 /*
262  * Name:	e_ExecCmdArray
263  * Synopsis:	Execute Unix command and return results
264  * Description:	Execute a Unix command and return results and status
265  * Arguments:
266  *		r_status - [RO, *RW] - (int *)
267  *			Return (exit) status from Unix command:
268  *			== -1 : child terminated with a signal
269  *			!= -1 : lower 8-bit value child passed to exit()
270  *		r_results - [RO, *RW] - (char **)
271  *			Any output generated by the Unix command to stdout
272  *			and to stderr
273  *			== (char *)NULL if no output generated
274  *		a_inputFile - [RO, *RO] - (char *)
275  *			Pointer to character string representing file to be
276  *			used as "standard input" for the command.
277  *			== (char *)NULL to use "/dev/null" as standard input
278  *		a_cmd - [RO, *RO] - (char *)
279  *			Pointer to character string representing the full path
280  *			of the Unix command to execute
281  *		char **a_args - [RO, *RO] - (char **)
282  *			List of character strings representing the arguments
283  *			to be passed to the Unix command. The list must be
284  *			terminated with an element that is (char *)NULL
285  * Returns:	int
286  *			== 0 - Command executed
287  *				Look at r_status for results of Unix command
288  *			!= 0 - problems executing command
289  *				r_status and r_results have no meaning;
290  *				r_status will be -1
291  *				r_results will be NULL
292  * NOTE:	Any results returned is placed in new storage for the
293  *		calling method. The caller must use 'free' to dispose
294  *		of the storage once the results are no longer needed.
295  * NOTE:	If 0 is returned, 'r_status' must be queried to
296  *		determine the results of the Unix command.
297  * NOTE:	The system "errno" value from immediately after waitpid() call
298  *		is preserved for the calling method to use to determine
299  *		the system reason why the operation failed.
300  */
301 
302 int
e_ExecCmdArray(int * r_status,char ** r_results,char * a_inputFile,char * a_cmd,char ** a_args)303 e_ExecCmdArray(int *r_status, char **r_results,
304     char *a_inputFile, char *a_cmd, char **a_args)
305 {
306 	char		*buffer;
307 	int		bufferIndex;
308 	int		bufferSize;
309 	int		ipipe[2] = {0, 0};
310 	pid_t		pid;
311 	pid_t		resultPid;
312 	int		status;
313 	int		lerrno;
314 	int		stdinfile = -1;
315 
316 	/* reset return results buffer pointer */
317 
318 	if (r_results != (char **)NULL) {
319 		*r_results = (char *)NULL;
320 	}
321 
322 	*r_status = -1;
323 
324 	/*
325 	 * See if command exists
326 	 */
327 
328 	if (access(a_cmd, F_OK|X_OK) != 0) {
329 		return (-1);
330 	}
331 
332 	/*
333 	 * See if input file exists
334 	 */
335 
336 	if (a_inputFile != (char *)NULL) {
337 		stdinfile = open(a_inputFile, O_RDONLY);
338 	} else {
339 		stdinfile = open("/dev/null", O_RDONLY); /* stdin = /dev/null */
340 	}
341 
342 	if (stdinfile < 0) {
343 		return (-1);
344 	}
345 
346 	/*
347 	 * Create a pipe to be used to capture the command output
348 	 */
349 
350 	if (pipe(ipipe) != 0) {
351 		(void) close(stdinfile);
352 		return (-1);
353 	}
354 
355 
356 	bufferSize = PIPE_BUFFER_INCREMENT;
357 	bufferIndex = 0;
358 	buffer = calloc(1, bufferSize);
359 	if (buffer == (char *)NULL) {
360 		(void) close(stdinfile);
361 		return (-1);
362 	}
363 
364 	/* flush standard i/o before creating new process */
365 
366 	(void) fflush(stderr);
367 	(void) fflush(stdout);
368 
369 	/*
370 	 * create new process to execute command in;
371 	 * vfork() is being used to avoid duplicating the parents
372 	 * memory space - this means that the child process may
373 	 * not modify any of the parents memory including the
374 	 * standard i/o descriptors - all the child can do is
375 	 * adjust interrupts and open files as a prelude to a
376 	 * call to exec().
377 	 */
378 
379 	pid = vfork();
380 
381 	if (pid == 0) {
382 		/*
383 		 * This is the forked (child) process ======================
384 		 */
385 
386 		int	i;
387 
388 		/* reset any signals to default */
389 
390 		for (i = 0; i < NSIG; i++) {
391 			(void) sigset(i, SIG_DFL);
392 		}
393 
394 		/* assign stdin, stdout, stderr as appropriate */
395 
396 		(void) dup2(stdinfile, STDIN_FILENO);
397 		(void) close(ipipe[0]);		/* close out pipe reader side */
398 		(void) dup2(ipipe[1], STDOUT_FILENO);
399 		(void) dup2(ipipe[1], STDERR_FILENO);
400 
401 		/* Close all open files except standard i/o */
402 
403 		closefrom(3);
404 
405 		/* execute target executable */
406 
407 		(void) execvp(a_cmd, a_args);
408 		perror(a_cmd);	/* Emit error msg - ends up in callers buffer */
409 		_exit(0x00FE);
410 	}
411 
412 	/*
413 	 * This is the forking (parent) process ====================
414 	 */
415 
416 	(void) close(stdinfile);
417 	(void) close(ipipe[1]);		/* Close write side of pipe */
418 
419 	/*
420 	 * Spin reading data from the child into the buffer - when the read eofs
421 	 * the child has exited
422 	 */
423 
424 	for (;;) {
425 		ssize_t	bytesRead;
426 
427 		/* read as much child data as there is available buffer space */
428 
429 		bytesRead = read(ipipe[0], buffer + bufferIndex,
430 		    bufferSize - bufferIndex);
431 
432 		/* break out of read loop if end-of-file encountered */
433 
434 		if (bytesRead == 0) {
435 			break;
436 		}
437 
438 		/* if error, continue if recoverable, else break out of loop */
439 
440 		if (bytesRead == -1) {
441 			/* try again: EAGAIN - insufficient resources */
442 
443 			if (errno == EAGAIN) {
444 				continue;
445 			}
446 
447 			/* try again: EINTR - interrupted system call */
448 
449 			if (errno == EINTR) {
450 				continue;
451 			}
452 
453 			/* break out of loop - error not recoverable */
454 			break;
455 		}
456 
457 		/* at least 1 byte read: expand buffer if at end */
458 
459 		bufferIndex += bytesRead;
460 		if (bufferIndex >= bufferSize) {
461 			buffer = realloc(buffer,
462 			    bufferSize += PIPE_BUFFER_INCREMENT);
463 			(void) memset(buffer + bufferIndex, 0,
464 			    bufferSize - bufferIndex);
465 		}
466 	}
467 
468 	(void) close(ipipe[0]);		/* Close read side of pipe */
469 
470 	/* Get subprocess exit status */
471 
472 	for (;;) {
473 		resultPid = waitpid(pid, &status, 0L);
474 		lerrno = (resultPid == -1 ? errno : 0);
475 
476 		/* break loop if child process status reaped */
477 
478 		if (resultPid != -1) {
479 			break;
480 		}
481 
482 		/* break loop if not interrupted out of waitpid */
483 
484 		if (errno != EINTR) {
485 			break;
486 		}
487 	}
488 
489 	/*
490 	 * If the child process terminated due to a call to exit(), then
491 	 * set results equal to the 8-bit exit status of the child process;
492 	 * otherwise, set the exit status to "-1" indicating that the child
493 	 * exited via a signal.
494 	 */
495 
496 	*r_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
497 
498 	/* return appropriate output */
499 
500 	if (!*buffer) {
501 		/* No contents in output buffer - discard */
502 		free(buffer);
503 	} else if (r_results == (char **)NULL) {
504 		/* Not requested to return results - discard */
505 		free(buffer);
506 	} else {
507 		/* have output and request to return: pass to calling method */
508 		*r_results = buffer;
509 	}
510 
511 	errno = lerrno;
512 	return (resultPid == -1 ? -1 : 0);
513 }
514 
515 /*
516  * Name:	e_ExecCmdList
517  * Synopsis:	Execute Unix command and return results
518  * Description:	Execute a Unix command and return results and status
519  * Arguments:
520  *		r_status - [RO, *RW] - (int *)
521  *			Return (exit) status from Unix command
522  *		r_results - [RO, *RW] - (char **)
523  *			Any output generated by the Unix command to stdout
524  *			and to stderr
525  *			== (char *)NULL if no output generated
526  *		a_inputFile - [RO, *RO] - (char *)
527  *			Pointer to character string representing file to be
528  *			used as "standard input" for the command.
529  *			== (char *)NULL to use "/dev/null" as standard input
530  *		a_cmd - [RO, *RO] - (char *)
531  *			Pointer to character string representing the full path
532  *			of the Unix command to execute
533  *		... - [RO] (?)
534  *			Zero or more arguments to the Unix command
535  *			The argument list must be ended with (void *)NULL
536  * Returns:	int
537  *			== 0 - Command executed
538  *				Look at r_status for results of Unix command
539  *			!= 0 - problems executing command
540  *				r_status and r_results have no meaning
541  * NOTE:	Any results returned is placed in new storage for the
542  *		calling method. The caller must use 'free' to dispose
543  *		of the storage once the results are no longer needed.
544  * NOTE:	If LU_SUCCESS is returned, 'r_status' must be queried to
545  *		determine the results of the Unix command.
546  */
547 
548 int
e_ExecCmdList(int * r_status,char ** r_results,char * a_inputFile,char * a_cmd,...)549 e_ExecCmdList(int *r_status, char **r_results,
550     char *a_inputFile, char *a_cmd, ...)
551 {
552 	va_list		ap;		/* references variable argument list */
553 	char		*array[MAX_EXEC_CMD_ARGS+1];
554 	int		argno = 0;
555 
556 	/*
557 	 * Create argument array for exec system call
558 	 */
559 
560 	bzero(array, sizeof (array));
561 
562 	va_start(ap, a_cmd);	/* Begin variable argument processing */
563 
564 	for (argno = 0; argno < MAX_EXEC_CMD_ARGS; argno++) {
565 		array[argno] = va_arg(ap, char *);
566 		if (array[argno] == (char *)NULL) {
567 			break;
568 		}
569 	}
570 
571 	va_end(ap);
572 	return (e_ExecCmdArray(r_status, r_results, a_inputFile, a_cmd, array));
573 }
574