diff options
author | Moriah Waterland <Moriah.Waterland@Sun.COM> | 2009-06-03 20:16:25 -0600 |
---|---|---|
committer | Moriah Waterland <Moriah.Waterland@Sun.COM> | 2009-06-03 20:16:25 -0600 |
commit | 5c51f1241dbbdf2656d0e10011981411ed0c9673 (patch) | |
tree | 0f30a2e38fe4e5d53a5a67264ba548577d82a86f /usr/src/lib/libpkg/common/runcmd.c | |
parent | 2b79d384d32b4ea1e278466cd9b0f3bb56daae22 (diff) | |
download | illumos-gate-5c51f1241dbbdf2656d0e10011981411ed0c9673.tar.gz |
6739234 move SVR4 packaging to ONNV gate
Diffstat (limited to 'usr/src/lib/libpkg/common/runcmd.c')
-rw-r--r-- | usr/src/lib/libpkg/common/runcmd.c | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/usr/src/lib/libpkg/common/runcmd.c b/usr/src/lib/libpkg/common/runcmd.c new file mode 100644 index 0000000000..945673737e --- /dev/null +++ b/usr/src/lib/libpkg/common/runcmd.c @@ -0,0 +1,808 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <strings.h> +#include <signal.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <wait.h> +#include <sys/types.h> +#include "pkglib.h" +#include "pkglocale.h" +#include "pkglibmsgs.h" + +#ifndef _STDARG_H +#include "stdarg.h" +#endif + +/* + * Private definitions + */ + +/* Maximum number of arguments to pkg_ExecCmdList */ + +#define MAX_EXEC_CMD_ARGS 100 + +/* Size of buffer increments when reading from pipe */ + +#define PIPE_BUFFER_INCREMENT 256 + +static char errfile[L_tmpnam+1]; + +/* + * This is the "argument array" definition that is returned by e_new_args and is + * used by e_add_args, e_free_args, etc. + */ + +struct _argArray_t { + long _aaNumArgs; /* number of arguments set */ + long _aaMaxArgs; /* number of arguments allocated */ + char **_aaArgs; /* actual arguments */ +}; + +typedef struct _argArray_t argArray_t; + +/* + * Private Methods + */ +static void e_free_args(argArray_t *a_args); +static argArray_t *e_new_args(int initialCount); +/*PRINTFLIKE2*/ +static boolean_t e_add_arg(argArray_t *a_args, char *a_format, ...); +static int e_get_argc(argArray_t *a_args); +static char **e_get_argv(argArray_t *a_args); + + +/* + * Public Methods + */ + + +void +rpterr(void) +{ + FILE *fp; + int c; + + if (errfile[0]) { + if (fp = fopen(errfile, "r")) { + while ((c = getc(fp)) != EOF) + putc(c, stderr); + (void) fclose(fp); + } + (void) unlink(errfile); + errfile[0] = '\0'; + } +} + +void +ecleanup(void) +{ + if (errfile[0]) { + (void) unlink(errfile); + errfile[0] = NULL; + } +} + +int +esystem(char *cmd, int ifd, int ofd) +{ + char *perrfile; + int status = 0; + pid_t pid; + + perrfile = tmpnam(NULL); + if (perrfile == NULL) { + progerr( + pkg_gt("unable to create temp error file, errno=%d"), + errno); + return (-1); + } + (void) strlcpy(errfile, perrfile, sizeof (errfile)); + + /* flush standard i/o before creating new process */ + + (void) fflush(stderr); + (void) fflush(stdout); + + /* + * create new process to execute command in; + * vfork() is being used to avoid duplicating the parents + * memory space - this means that the child process may + * not modify any of the parents memory including the + * standard i/o descriptors - all the child can do is + * adjust interrupts and open files as a prelude to a + * call to exec(). + */ + + pid = vfork(); + if (pid == 0) { + /* + * this is the child process + */ + int i; + + /* reset any signals to default */ + + for (i = 0; i < NSIG; i++) { + (void) sigset(i, SIG_DFL); + } + + if (ifd > 0) { + (void) dup2(ifd, STDIN_FILENO); + } + + if (ofd >= 0 && ofd != STDOUT_FILENO) { + (void) dup2(ofd, STDOUT_FILENO); + } + + i = open(errfile, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (i >= 0) { + dup2(i, STDERR_FILENO); + } + + /* Close all open files except standard i/o */ + + closefrom(3); + + /* execute target executable */ + + execl("/sbin/sh", "/sbin/sh", "-c", cmd, NULL); + progerr(pkg_gt("exec of <%s> failed, errno=%d"), cmd, errno); + _exit(99); + } else if (pid < 0) { + /* fork failed! */ + + logerr(pkg_gt("bad vfork(), errno=%d"), errno); + return (-1); + } + + /* + * this is the parent process + */ + + sighold(SIGINT); + pid = waitpid(pid, &status, 0); + sigrelse(SIGINT); + + if (pid < 0) { + return (-1); /* probably interrupted */ + } + + switch (status & 0177) { + case 0: + case 0177: + status = status >> 8; + /*FALLTHROUGH*/ + + default: + /* terminated by a signal */ + status = status & 0177; + } + + if (status == 0) { + ecleanup(); + } + + return (status); +} + +FILE * +epopen(char *cmd, char *mode) +{ + char *buffer, *perrfile; + FILE *pp; + size_t len; + size_t alen; + + if (errfile[0]) { + /* cleanup previous errfile */ + unlink(errfile); + } + + perrfile = tmpnam(NULL); + if (perrfile == NULL) { + progerr( + pkg_gt("unable to create temp error file, errno=%d"), + errno); + return ((FILE *)0); + } + + if (strlcpy(errfile, perrfile, sizeof (errfile)) > sizeof (errfile)) { + progerr(pkg_gt("file name max length %d; name is too long: %s"), + sizeof (errfile), perrfile); + return ((FILE *)0); + } + + len = strlen(cmd)+6+strlen(errfile); + buffer = (char *)calloc(len, sizeof (char)); + if (buffer == NULL) { + progerr(pkg_gt("no memory in epopen(), errno=%d"), errno); + return ((FILE *)0); + } + + if (strchr(cmd, '|')) { + alen = snprintf(buffer, len, "(%s) 2>%s", cmd, errfile); + } else { + alen = snprintf(buffer, len, "%s 2>%s", cmd, errfile); + } + + if (alen > len) { + progerr(pkg_gt("command max length %d; cmd is too long: %s"), + len, cmd); + return ((FILE *)0); + } + + pp = popen(buffer, mode); + + free(buffer); + return (pp); +} + +int +epclose(FILE *pp) +{ + int n; + + n = pclose(pp); + if (n == 0) + ecleanup(); + return (n); +} + +/* + * Name: e_ExecCmdArray + * Synopsis: Execute Unix command and return results + * Description: Execute a Unix command and return results and status + * Arguments: + * r_status - [RO, *RW] - (int *) + * Return (exit) status from Unix command: + * == -1 : child terminated with a signal + * != -1 : lower 8-bit value child passed to exit() + * r_results - [RO, *RW] - (char **) + * Any output generated by the Unix command to stdout + * and to stderr + * == (char *)NULL if no output generated + * a_inputFile - [RO, *RO] - (char *) + * Pointer to character string representing file to be + * used as "standard input" for the command. + * == (char *)NULL to use "/dev/null" as standard input + * a_cmd - [RO, *RO] - (char *) + * Pointer to character string representing the full path + * of the Unix command to execute + * char **a_args - [RO, *RO] - (char **) + * List of character strings representing the arguments + * to be passed to the Unix command. The list must be + * terminated with an element that is (char *)NULL + * Returns: int + * == 0 - Command executed + * Look at r_status for results of Unix command + * != 0 - problems executing command + * r_status and r_results have no meaning; + * r_status will be -1 + * r_results will be NULL + * NOTE: Any results returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the results are no longer needed. + * NOTE: If 0 is returned, 'r_status' must be queried to + * determine the results of the Unix command. + * NOTE: The system "errno" value from immediately after waitpid() call + * is preserved for the calling method to use to determine + * the system reason why the operation failed. + */ + +int +e_ExecCmdArray(int *r_status, char **r_results, + char *a_inputFile, char *a_cmd, char **a_args) +{ + char *buffer; + int bufferIndex; + int bufferSize; + int ipipe[2] = {0, 0}; + pid_t pid; + pid_t resultPid; + int status; + int lerrno; + int stdinfile = -1; + + /* reset return results buffer pointer */ + + if (r_results != (char **)NULL) { + *r_results = (char *)NULL; + } + + *r_status = -1; + + /* + * See if command exists + */ + + if (access(a_cmd, F_OK|X_OK) != 0) { + return (-1); + } + + /* + * See if input file exists + */ + + if (a_inputFile != (char *)NULL) { + stdinfile = open(a_inputFile, O_RDONLY); + } else { + stdinfile = open("/dev/null", O_RDONLY); /* stdin = /dev/null */ + } + + if (stdinfile < 0) { + return (-1); + } + + /* + * Create a pipe to be used to capture the command output + */ + + if (pipe(ipipe) != 0) { + (void) close(stdinfile); + return (-1); + } + + + bufferSize = PIPE_BUFFER_INCREMENT; + bufferIndex = 0; + buffer = calloc(1, bufferSize); + if (buffer == (char *)NULL) { + (void) close(stdinfile); + return (-1); + } + + /* flush standard i/o before creating new process */ + + (void) fflush(stderr); + (void) fflush(stdout); + + /* + * create new process to execute command in; + * vfork() is being used to avoid duplicating the parents + * memory space - this means that the child process may + * not modify any of the parents memory including the + * standard i/o descriptors - all the child can do is + * adjust interrupts and open files as a prelude to a + * call to exec(). + */ + + pid = vfork(); + + if (pid == 0) { + /* + * This is the forked (child) process ====================== + */ + + int i; + + /* reset any signals to default */ + + for (i = 0; i < NSIG; i++) { + (void) sigset(i, SIG_DFL); + } + + /* assign stdin, stdout, stderr as appropriate */ + + (void) dup2(stdinfile, STDIN_FILENO); + (void) close(ipipe[0]); /* close out pipe reader side */ + (void) dup2(ipipe[1], STDOUT_FILENO); + (void) dup2(ipipe[1], STDERR_FILENO); + + /* Close all open files except standard i/o */ + + closefrom(3); + + /* execute target executable */ + + (void) execvp(a_cmd, a_args); + perror(a_cmd); /* Emit error msg - ends up in callers buffer */ + _exit(0x00FE); + } + + /* + * This is the forking (parent) process ==================== + */ + + (void) close(stdinfile); + (void) close(ipipe[1]); /* Close write side of pipe */ + + /* + * Spin reading data from the child into the buffer - when the read eofs + * the child has exited + */ + + for (;;) { + ssize_t bytesRead; + + /* read as much child data as there is available buffer space */ + + bytesRead = read(ipipe[0], buffer + bufferIndex, + bufferSize - bufferIndex); + + /* break out of read loop if end-of-file encountered */ + + if (bytesRead == 0) { + break; + } + + /* if error, continue if recoverable, else break out of loop */ + + if (bytesRead == -1) { + /* try again: EAGAIN - insufficient resources */ + + if (errno == EAGAIN) { + continue; + } + + /* try again: EINTR - interrupted system call */ + + if (errno == EINTR) { + continue; + } + + /* break out of loop - error not recoverable */ + break; + } + + /* at least 1 byte read: expand buffer if at end */ + + bufferIndex += bytesRead; + if (bufferIndex >= bufferSize) { + buffer = realloc(buffer, + bufferSize += PIPE_BUFFER_INCREMENT); + (void) memset(buffer + bufferIndex, 0, + bufferSize - bufferIndex); + } + } + + (void) close(ipipe[0]); /* Close read side of pipe */ + + /* Get subprocess exit status */ + + for (;;) { + resultPid = waitpid(pid, &status, 0L); + lerrno = (resultPid == -1 ? errno : 0); + + /* break loop if child process status reaped */ + + if (resultPid != -1) { + break; + } + + /* break loop if not interrupted out of waitpid */ + + if (errno != EINTR) { + break; + } + } + + /* + * If the child process terminated due to a call to exit(), then + * set results equal to the 8-bit exit status of the child process; + * otherwise, set the exit status to "-1" indicating that the child + * exited via a signal. + */ + + *r_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + + /* return appropriate output */ + + if (!*buffer) { + /* No contents in output buffer - discard */ + free(buffer); + } else if (r_results == (char **)NULL) { + /* Not requested to return results - discard */ + free(buffer); + } else { + /* have output and request to return: pass to calling method */ + *r_results = buffer; + } + + errno = lerrno; + return (resultPid == -1 ? -1 : 0); +} + +/* + * Name: e_ExecCmdList + * Synopsis: Execute Unix command and return results + * Description: Execute a Unix command and return results and status + * Arguments: + * r_status - [RO, *RW] - (int *) + * Return (exit) status from Unix command + * r_results - [RO, *RW] - (char **) + * Any output generated by the Unix command to stdout + * and to stderr + * == (char *)NULL if no output generated + * a_inputFile - [RO, *RO] - (char *) + * Pointer to character string representing file to be + * used as "standard input" for the command. + * == (char *)NULL to use "/dev/null" as standard input + * a_cmd - [RO, *RO] - (char *) + * Pointer to character string representing the full path + * of the Unix command to execute + * ... - [RO] (?) + * Zero or more arguments to the Unix command + * The argument list must be ended with (void *)NULL + * Returns: int + * == 0 - Command executed + * Look at r_status for results of Unix command + * != 0 - problems executing command + * r_status and r_results have no meaning + * NOTE: Any results returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the results are no longer needed. + * NOTE: If LU_SUCCESS is returned, 'r_status' must be queried to + * determine the results of the Unix command. + */ + +int +e_ExecCmdList(int *r_status, char **r_results, + char *a_inputFile, char *a_cmd, ...) +{ + va_list ap; /* references variable argument list */ + char *array[MAX_EXEC_CMD_ARGS+1]; + int argno = 0; + + /* + * Create argument array for exec system call + */ + + bzero(array, sizeof (array)); + + va_start(ap, a_cmd); /* Begin variable argument processing */ + + for (argno = 0; argno < MAX_EXEC_CMD_ARGS; argno++) { + array[argno] = va_arg(ap, char *); + if (array[argno] == (char *)NULL) { + break; + } + } + + va_end(ap); + return (e_ExecCmdArray(r_status, r_results, a_inputFile, + a_cmd, array)); +} + +/* + * Name: e_new_args + * Description: create a new argument array for use in exec() calls + * Arguments: initialCount - [RO, *RO] - (int) + * Initial number of elements to populate the + * argument array with - use best guess + * Returns: argArray_t * + * Pointer to argument array that can be used in other + * functions that accept it as an argument + * == (argArray_t *)NULL - error + * NOTE: you must call e_free_args() when the returned argument array is + * no longer needed so that all storage used can be freed up. + */ + +argArray_t * +e_new_args(int initialCount) +{ + argArray_t *aa; + + /* allocate new argument array structure */ + + aa = (argArray_t *)calloc(1, sizeof (argArray_t)); + if (aa == (argArray_t *)NULL) { + progerr(ERR_MALLOC, strerror(errno), sizeof (argArray_t), + "<argArray_t>"); + return ((argArray_t *)NULL); + } + + /* allocate initial argument array */ + + aa->_aaArgs = (char **)calloc(initialCount+1, sizeof (char *)); + if (aa->_aaArgs == (char **)NULL) { + progerr(ERR_MALLOC, strerror(errno), + (initialCount+1)*sizeof (char *), "<char **>"); + return ((argArray_t *)NULL); + } + + /* initialize argument indexes */ + + aa->_aaNumArgs = 0; + aa->_aaMaxArgs = initialCount; + + return (aa); +} + +/* + * Name: e_add_arg + * Description: add new argument to argument array for use in exec() calls + * Arguments: a_args - [RO, *RW] - (argArray_t *) + * Pointer to argument array (previously allocated via + * a call to e_new_args) to add the argument to + * a_format - [RO, *RO] - (char *) + * Pointer to "printf" style format argument + * ... - [RO, *RO] - (varies) + * Arguments as appropriate for format statement + * Returns: boolean_t + * B_TRUE - success + * B_FALSE - failure + * Examples: + * - to add an argument that specifies a file descriptor: + * int fd; + * e_add_arg(aa, "/proc/self/fd/%d", fd); + * - to add a flag or other known text: + * e_add_arg(aa, "-s") + * - to add random text: + * char *random_text; + * e_add_arg(aa, "%s", random_text); + */ + +/*PRINTFLIKE2*/ +boolean_t +e_add_arg(argArray_t *a_args, char *a_format, ...) +{ + char *rstr = (char *)NULL; + char bfr[MAX_CANON]; + size_t vres = 0; + va_list ap; + + /* + * double argument array if array is full + */ + + if (a_args->_aaNumArgs >= a_args->_aaMaxArgs) { + int newMax; + char **newArgs; + + newMax = a_args->_aaMaxArgs * 2; + newArgs = (char **)realloc(a_args->_aaArgs, + (newMax+1) * sizeof (char *)); + if (newArgs == (char **)NULL) { + progerr(ERR_MALLOC, strerror(errno), + ((newMax+1) * sizeof (char *)), "<char **>"); + return (B_FALSE); + } + a_args->_aaArgs = newArgs; + a_args->_aaMaxArgs = newMax; + } + + /* determine size of argument to add to list */ + + va_start(ap, a_format); + vres = vsnprintf(bfr, sizeof (bfr), a_format, ap); + va_end(ap); + + /* if it fit in the built in buffer, use that */ + if (vres < sizeof (bfr)) { + /* dup text already generated in bfr */ + rstr = strdup(bfr); + if (rstr == (char *)NULL) { + progerr(ERR_MALLOC, strerror(errno), vres+2, + "<char *>"); + return (B_FALSE); + } + } else { + /* allocate space for argument to add */ + + rstr = (char *)malloc(vres+2); + if (rstr == (char *)NULL) { + progerr(ERR_MALLOC, strerror(errno), vres+2, + "<char *>"); + return (B_FALSE); + } + + /* generate argument to add */ + + va_start(ap, a_format); + vres = vsnprintf(rstr, vres+1, a_format, ap); + va_end(ap); + } + + /* add argument to the end of the argument array */ + + a_args->_aaArgs[a_args->_aaNumArgs++] = rstr; + a_args->_aaArgs[a_args->_aaNumArgs] = (char *)NULL; + + return (B_TRUE); +} + +/* + * Name: e_get_argv + * Description: return (char **)argv pointer from argument array + * Arguments: a_args - [RO, *RW] - (argArray_t *) + * Pointer to argument array (previously allocated via + * a call to e_new_args) to return argv pointer for + * Returns: char ** + * Pointer to (char **)argv pointer suitable for use + * in an exec*() call + * NOTE: the actual character array is always terminated with a (char *)NULL + */ + +char ** +e_get_argv(argArray_t *a_args) +{ + return (a_args->_aaArgs); +} + +/* + * Name: e_get_argc + * Description: return (int) argc count from argument array + * Arguments: a_args - [RO, *RW] - (argArray_t *) + * Pointer to argument array (previously allocated via + * a call to e_new_args) to return argc count for + * Returns: int + * Count of the number of arguments in the argument array + * suitable for use in an exec*() call + */ + +int +e_get_argc(argArray_t *a_args) +{ + return (a_args->_aaNumArgs); +} + +/* + * Name: e_free_args + * Description: free all storage contained in an argument array previously + * allocated by a call to e_new_args + * Arguments: a_args - [RO, *RW] - (argArray_t *) + * Pointer to argument array (previously allocated via + * a call to e_new_args) to free + * Returns: void + * NOTE: preserves errno (usually called right after e_execCmd*()) + */ + +void +e_free_args(argArray_t *a_args) +{ + int i; + int lerrno = errno; + + /* free all arguments in the argument array */ + + for (i = (a_args->_aaNumArgs-1); i >= 0; i--) { + (void) free(a_args->_aaArgs[i]); + a_args->_aaArgs[i] = (char *)NULL; + } + + /* free argument array */ + + (void) free(a_args->_aaArgs); + + /* free argument array structure */ + + (void) free(a_args); + + /* restore errno */ + + errno = lerrno; +} |