/* * 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. */ /* * Module: zones_exec.c * Group: libinstzones * Description: Provide "zones" execution interface for install * consolidation code * * Public Methods: * * z_ExecCmdArray - Execute a Unix command and return results and status * _zexec - run a command with arguments on a specified zone * _zexec_init_template - used by _zexec to establish contracts * _z_zone_exec - Execute a Unix command in a specified zone and return results * z_ExecCmdList - Execute a Unix command and return results and status */ /* * System includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * local includes */ #include "instzones_lib.h" #include "zones_strings.h" /* * Private structures */ /* * Library Function Prototypes */ /* * Local Function Prototypes */ /* * global internal (private) declarations */ /* * ***************************************************************************** * global external (public) functions * ***************************************************************************** */ /* * Name: z_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 z_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}; int lerrno; int status; int stdinfile = -1; pid_t pid; pid_t resultPid; /* entry assertions */ assert(r_status != NULL); assert(a_cmd != NULL); assert(*a_cmd != '\0'); assert(a_args != NULL); /* 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); } else if (pid == -1) { _z_program_error(ERR_FORK, strerror(errno)); *r_status = -1; return (-1); } /* * 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: _zexec * Description: run a command with arguments on a specified zone * Arguments: a_zoneName - pointer to string representing the name of the zone * to execute the specified command in * a_path - pointer to string representing the full path *in the * non-global zone named by a_zoneName* of the Unix command * to be executed * a_argv[] - Pointer to array of character strings representing * the arguments to be passed to the Unix command. The list * must be termianted with an element that is (char *)NULL * NOTE: a_argv[0] is the "command name" passed to the command * Returns: int * This function must be treated like a call to exec() * If the exec() is successful, the thread of control is * NOT returned, and the process will exit when completed. * If this function returns, it means the exec() could not * be done, or another fatal error occurred. */ int _zexec(const char *a_zoneName, const char *a_path, char *a_argv[]) { zoneid_t zoneid; zone_state_t st; char **new_env = { NULL }; priv_set_t *privset; /* entry assertions */ assert(a_zoneName != NULL); assert(*a_zoneName != '\0'); assert(a_path != NULL); assert(*a_path != '\0'); /* establish locale settings */ (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); /* can only be invoked from within the global zone */ if (getzoneid() != GLOBAL_ZONEID) { _z_program_error(ERR_ZEXEC_NOT_IN_GZ, a_zoneName); return (-1); } if (strcmp(a_zoneName, GLOBAL_ZONENAME) == 0) { _z_program_error(ERR_ZEXEC_GZUSED, a_zoneName); return (-1); } /* get the state of the specified zone */ if (zone_get_state((char *)a_zoneName, &st) != Z_OK) { _z_program_error(ERR_ZEXEC_BADZONE, a_zoneName); return (-1); } if (st < ZONE_STATE_INSTALLED) { _z_program_error(ERR_ZEXEC_BADSTATE, a_zoneName, zone_state_str(st)); return (-1); } if (st != ZONE_STATE_RUNNING && st != ZONE_STATE_MOUNTED) { _z_program_error(ERR_ZEXEC_NOTRUNNING, a_zoneName, zone_state_str(st)); return (-1); } /* * In both console and non-console cases, we require all privs. * In the console case, because we may need to startup zoneadmd. * In the non-console case in order to do zone_enter(2), zonept() * and other tasks. * * Future work: this solution is temporary. Ultimately, we need to * move to a flexible system which allows the global admin to * designate that a particular user can zlogin (and probably zlogin * -C) to a particular zone. This all-root business we have now is * quite sketchy. */ if ((privset = priv_allocset()) == NULL) { _z_program_error(ERR_ZEXEC_PRIV_ALLOCSET, a_zoneName, strerror(errno)); return (-1); } if (getppriv(PRIV_EFFECTIVE, privset) != 0) { _z_program_error(ERR_ZEXEC_GETPPRIV, a_zoneName, strerror(errno)); priv_freeset(privset); return (-1); } if (priv_isfullset(privset) == B_FALSE) { _z_program_error(ERR_ZEXEC_PRIVS, a_zoneName); priv_freeset(privset); return (-1); } priv_freeset(privset); if ((zoneid = getzoneidbyname(a_zoneName)) == -1) { _z_program_error(ERR_ZEXEC_NOZONEID, a_zoneName, strerror(errno)); return (-1); } if ((new_env = _zexec_prep_env()) == NULL) { _z_program_error(ERR_ZEXEC_ASSEMBLE, a_zoneName); return (-1); } /* * In case any of stdin, stdout or stderr are streams, * anchor them to prevent malicious I_POPs. * * Future work: use pipes to entirely eliminate FD leakage * into the zone. */ (void) ioctl(STDIN_FILENO, I_ANCHOR); (void) ioctl(STDOUT_FILENO, I_ANCHOR); (void) ioctl(STDERR_FILENO, I_ANCHOR); if (zone_enter(zoneid) == -1) { int lerrno = errno; _z_program_error(ERR_ZEXEC_ZONEENTER, a_zoneName, strerror(errno)); if (lerrno == EFAULT) { _z_program_error(ERR_ZEXEC_EFAULT, a_zoneName); } free(new_env); return (-1); } (void) execve(a_path, &a_argv[0], new_env); _z_program_error(ERR_ZEXEC_EXECFAILURE, a_zoneName, strerror(errno)); return (-1); } /* * Name: _zexec_init_template * Description: used by _zexec to establish contracts */ int _zexec_init_template(void) { int fd; int err = 0; fd = open64(CTFS_ROOT "/process/template", O_RDWR); if (fd == -1) { return (-1); } /* * zlogin doesn't do anything with the contract. * Deliver no events, don't inherit, and allow it to be orphaned. */ err |= ct_tmpl_set_critical(fd, 0); err |= ct_tmpl_set_informative(fd, 0); err |= ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR); err |= ct_pr_tmpl_set_param(fd, CT_PR_PGRPONLY | CT_PR_REGENT); if (err || ct_tmpl_activate(fd)) { (void) close(fd); return (-1); } return (fd); } /* * Helper routine for _zexec_prep_env below. */ char * _zexec_add_env(char *name, char *value) { size_t sz = strlen(name) + strlen(value) + 1; char *str; if ((str = malloc(sz)) == NULL) return (NULL); (void) snprintf(str, sz, "%s%s", name, value); return (str); } /* * Prepare envp array for exec'd process. */ char ** _zexec_prep_env() { int e = 0, size = 1; char **new_env, *estr; char *term = getenv("TERM"); size++; /* for $PATH */ if (term != NULL) size++; /* * In failsafe mode we set $HOME */ size++; /* * In failsafe mode we set $SHELL, since login won't be around to do it. */ size++; if ((new_env = malloc(sizeof (char *) * size)) == NULL) return (NULL); if ((estr = _zexec_add_env("PATH=", ZONE_DEF_PATH)) == NULL) { free(new_env); return (NULL); } new_env[e++] = estr; if (term != NULL) { if ((estr = _zexec_add_env("TERM=", term)) == NULL) { free(new_env); return (NULL); } new_env[e++] = estr; } if ((estr = _zexec_add_env("HOME=", "/")) == NULL) { free(new_env); return (NULL); } new_env[e++] = estr; if ((estr = _zexec_add_env("SHELL=", ZONE_FAILSAFESHELL)) == NULL) { free(new_env); return (NULL); } new_env[e++] = estr; new_env[e++] = NULL; return (new_env); } /* * Name: _z_zone_exec * Description: Execute a Unix command in a specified zone and return results * 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_path - [RO, *RO] - (char *) * Pointer to character string representing the full path * *in the non-global zone named by a_zoneName*of the Unix * command to be executed * char **a_args - [RO, *RO] - (char **) * List of character strings representing the arguments * to be passed to the Unix command. * NOTE: The list must be terminated with an element that * ----- is (char *)NULL * NOTE: a_argv[0] is the "command name" passed to the * ----- command executed in the specified non-global zone * a_zoneName - pointer to string representing the name of the zone * to execute the specified command in * a_fds - Pointer to array of integers representing file * descriptors to remain open during the call - all * file descriptors above STDERR_FILENO not in this * list will be closed. * 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 * The return (exit) code from the specified Unix command * Special return codes: * -1 : failure to exec process * -2 : could not create contract for greenline * -3 : fork() failed * -4 : could not open stdin source file * -5 : error from 'waitpid' other than EINTR * -6 : zones are not supported * -7 : interrupt received * NOTE: All file descriptores other than 0, 1 and 2 are closed except * for those file descriptors listed in the a_fds array. */ int _z_zone_exec(int *r_status, char **r_results, char *a_inputFile, char *a_path, char *a_argv[], const char *a_zoneName, int *a_fds) { struct sigaction nact; struct sigaction oact; char *buffer; char *thisZoneName; int bufferIndex; int bufferSize; int exit_no; int ipipe[2] = {0, 0}; int lerrno; int n; int status; int stdinfile = -1; int tmpl_fd; pid_t child_pid; pid_t result_pid; void (*funcSighup)(); void (*funcSigint)(); /* entry assertions */ assert(a_path != (char *)NULL); assert(*a_path != '\0'); assert(a_argv != (char **)NULL); assert(a_argv[0] != (char *)NULL); assert(*a_argv[0] != '\0'); assert(a_zoneName != (char *)NULL); /* * if requested to execute in current zone name, directly execute */ thisZoneName = z_get_zonename(); status = (strcmp(a_zoneName, thisZoneName) == 0); /* entry debugging info */ _z_echoDebug(DBG_ZONE_EXEC_CMD_ENTER, a_path, a_zoneName, thisZoneName); (void) free(thisZoneName); for (n = 0; a_argv[n]; n++) { _z_echoDebug(DBG_ARG, n, a_argv[n]); } /* if this zone, just exec the command directly */ if (status != 0) { return (z_ExecCmdArray(r_status, r_results, a_inputFile, a_path, a_argv)); } /* reset return results buffer pointer */ if (r_results != (char **)NULL) { *r_results = (char *)NULL; } *r_status = -1; /* -1 : failure to exec process */ /* if zones are not implemented, return TRUE */ if (!z_zones_are_implemented()) { return (-6); /* -6 : zones are not supported */ } if ((tmpl_fd = _zexec_init_template()) == -1) { _z_program_error(ERR_CANNOT_CREATE_CONTRACT, strerror(errno)); return (-2); /* -2 : cannot create greenline contract */ } /* * 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 (-4); /* -4 : could not open stdin source file */ } /* * 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); /* * hold SIGINT/SIGHUP signals and reset signal received counter; * after the fork1() the parent and child need to setup their respective * interrupt handling and release the hold on the signals */ (void) sighold(SIGINT); (void) sighold(SIGHUP); _z_global_data._z_SigReceived = 0; /* no signals received */ /* * fork off a new process to execute command in; * fork1() is used instead of vfork() so the child process can * perform operations that would modify the parent process if * vfork() were used */ child_pid = fork1(); if (child_pid < 0) { /* * ************************************************************* * fork failed! * ************************************************************* */ (void) ct_tmpl_clear(tmpl_fd); (void) close(tmpl_fd); (void) free(buffer); _z_program_error(ERR_FORK, strerror(errno)); /* release hold on signals */ (void) sigrelse(SIGHUP); (void) sigrelse(SIGINT); return (-3); /* -3 : fork() failed */ } if (child_pid == 0) { int i; /* * ************************************************************* * This is the forked (child) process * ************************************************************* */ (void) ct_tmpl_clear(tmpl_fd); (void) close(tmpl_fd); /* 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 file descriptors not in the a_fds list */ (void) fdwalk(&_z_close_file_descriptors, (void *)a_fds); /* release all held signals */ (void) sigrelse(SIGHUP); (void) sigrelse(SIGINT); /* execute command in the specified non-global zone */ _exit(_zexec(a_zoneName, a_path, a_argv)); } /* * ********************************************************************* * This is the forking (parent) process * ********************************************************************* */ /* register child process i.d. so signal handlers can pass signal on */ _z_global_data._z_ChildProcessId = child_pid; /* * setup signal handlers for SIGINT and SIGHUP and release hold */ /* hook SIGINT to _z_sig_trap() */ nact.sa_handler = _z_sig_trap; nact.sa_flags = SA_RESTART; (void) sigemptyset(&nact.sa_mask); if (sigaction(SIGINT, &nact, &oact) < 0) { funcSigint = SIG_DFL; } else { funcSigint = oact.sa_handler; } /* hook SIGHUP to _z_sig_trap() */ nact.sa_handler = _z_sig_trap; nact.sa_flags = SA_RESTART; (void) sigemptyset(&nact.sa_mask); if (sigaction(SIGHUP, &nact, &oact) < 0) { funcSighup = SIG_DFL; } else { funcSighup = oact.sa_handler; } /* release hold on signals */ (void) sigrelse(SIGHUP); (void) sigrelse(SIGINT); (void) ct_tmpl_clear(tmpl_fd); (void) close(tmpl_fd); (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 */ /* * wait for the process to exit, reap child exit status */ for (;;) { result_pid = waitpid(child_pid, &status, 0L); lerrno = (result_pid == -1 ? errno : 0); /* break loop if child process status reaped */ if (result_pid != -1) { break; } /* break loop if not interrupted out of waitpid */ if (errno != EINTR) { break; } } /* reset child process i.d. so signal handlers do not pass signals on */ _z_global_data._z_ChildProcessId = -1; /* * 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. */ if (WIFEXITED(status)) { *r_status = WEXITSTATUS(status); if ((_z_global_data._z_SigReceived != 0) && (*r_status == 0)) { *r_status = 1; } } else { *r_status = -1; /* -1 : failure to exec process */ } /* determine proper exit code */ if (result_pid == -1) { exit_no = -5; /* -5 : error from 'waitpid' other than EINTR */ } else if (_z_global_data._z_SigReceived != 0) { exit_no = -7; /* -7 : interrupt received */ } else { exit_no = 0; } /* 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; } /* * reset signal handlers */ /* reset SIGINT */ nact.sa_handler = funcSigint; nact.sa_flags = SA_RESTART; (void) sigemptyset(&nact.sa_mask); (void) sigaction(SIGINT, &nact, (struct sigaction *)NULL); /* reset SIGHUP */ nact.sa_handler = funcSighup; nact.sa_flags = SA_RESTART; (void) sigemptyset(&nact.sa_mask); (void) sigaction(SIGHUP, &nact, (struct sigaction *)NULL); /* * if signal received during command execution, interrupt * this process now. */ if (_z_global_data._z_SigReceived != 0) { (void) kill(getpid(), SIGINT); } /* set errno and return */ errno = lerrno; return (exit_no); } /* * Name: z_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. */ /*VARARGS*/ int z_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 (z_ExecCmdArray(r_status, r_results, a_inputFile, a_cmd, array)); }