mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-25 23:25:24 +00:00
be00118d5d
Unfortunately, advisory record locking lose the lock if any fd refering
to the file is closed. There doesn't seem to be a way to preserve the
lock atomically. We could eventually retake the lock if low pidfilefd
is required.
This fixes processes being leaked, as they are not killed in
virPidFileForceCleanupPath() if the lock can be taken. Here also, we may
consider this is not good enough, as a process may leak by simply
closing the pidfilefd.
Fixes commit d146105f1e
("virCommand:
Actually acquire pidfile instead of just writing it")
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
3399 lines
90 KiB
C
3399 lines
90 KiB
C
/*
|
|
* vircommand.c: Child command execution
|
|
*
|
|
* Copyright (C) 2010-2014 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#ifndef WIN32
|
|
# include <poll.h>
|
|
#endif
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <sys/stat.h>
|
|
#ifndef WIN32
|
|
# include <sys/wait.h>
|
|
#endif
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#if WITH_CAPNG
|
|
# include <cap-ng.h>
|
|
#endif
|
|
|
|
#if defined(WITH_SECDRIVER_SELINUX)
|
|
# include <selinux/selinux.h>
|
|
#endif
|
|
#if defined(WITH_SECDRIVER_APPARMOR)
|
|
# include <sys/apparmor.h>
|
|
#endif
|
|
|
|
#define LIBVIRT_VIRCOMMANDPRIV_H_ALLOW
|
|
#include "viralloc.h"
|
|
#include "vircommandpriv.h"
|
|
#include "virerror.h"
|
|
#include "virutil.h"
|
|
#include "virlog.h"
|
|
#include "virfile.h"
|
|
#include "virpidfile.h"
|
|
#include "virprocess.h"
|
|
#include "virbuffer.h"
|
|
#include "virthread.h"
|
|
#include "virstring.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_NONE
|
|
|
|
VIR_LOG_INIT("util.command");
|
|
|
|
/* Flags for virExec */
|
|
enum {
|
|
VIR_EXEC_NONE = 0,
|
|
VIR_EXEC_NONBLOCK = (1 << 0),
|
|
VIR_EXEC_DAEMON = (1 << 1),
|
|
VIR_EXEC_CLEAR_CAPS = (1 << 2),
|
|
VIR_EXEC_RUN_SYNC = (1 << 3),
|
|
VIR_EXEC_ASYNC_IO = (1 << 4),
|
|
};
|
|
|
|
typedef struct _virCommandFD virCommandFD;
|
|
typedef virCommandFD *virCommandFDPtr;
|
|
|
|
struct _virCommandFD {
|
|
int fd;
|
|
unsigned int flags;
|
|
};
|
|
|
|
typedef struct _virCommandSendBuffer virCommandSendBuffer;
|
|
typedef virCommandSendBuffer *virCommandSendBufferPtr;
|
|
|
|
struct _virCommandSendBuffer {
|
|
int fd;
|
|
unsigned char *buffer;
|
|
size_t buflen;
|
|
off_t offset;
|
|
};
|
|
|
|
struct _virCommand {
|
|
int has_error; /* ENOMEM on allocation failure, -1 for anything else. */
|
|
|
|
char **args;
|
|
size_t nargs;
|
|
size_t maxargs;
|
|
|
|
char **env;
|
|
size_t nenv;
|
|
size_t maxenv;
|
|
|
|
char *pwd;
|
|
|
|
size_t npassfd;
|
|
virCommandFDPtr passfd;
|
|
|
|
unsigned int flags;
|
|
|
|
char *inbuf;
|
|
char **outbuf;
|
|
char **errbuf;
|
|
|
|
int infd;
|
|
int inpipe;
|
|
int outfd;
|
|
int errfd;
|
|
int *outfdptr;
|
|
int *errfdptr;
|
|
|
|
virThreadPtr asyncioThread;
|
|
|
|
bool handshake;
|
|
int handshakeWait[2];
|
|
int handshakeNotify[2];
|
|
|
|
virExecHook hook;
|
|
void *opaque;
|
|
|
|
pid_t pid;
|
|
char *pidfile;
|
|
bool reap;
|
|
bool rawStatus;
|
|
|
|
unsigned long long maxMemLock;
|
|
unsigned int maxProcesses;
|
|
unsigned int maxFiles;
|
|
bool setMaxCore;
|
|
unsigned long long maxCore;
|
|
|
|
uid_t uid;
|
|
gid_t gid;
|
|
unsigned long long capabilities;
|
|
#if defined(WITH_SECDRIVER_SELINUX)
|
|
char *seLinuxLabel;
|
|
#endif
|
|
#if defined(WITH_SECDRIVER_APPARMOR)
|
|
char *appArmorProfile;
|
|
#endif
|
|
int mask;
|
|
|
|
virCommandSendBufferPtr sendBuffers;
|
|
size_t numSendBuffers;
|
|
};
|
|
|
|
/* See virCommandSetDryRun for description for this variable */
|
|
static virBufferPtr dryRunBuffer;
|
|
static virCommandDryRunCallback dryRunCallback;
|
|
static void *dryRunOpaque;
|
|
#ifndef WIN32
|
|
static int dryRunStatus;
|
|
#endif /* !WIN32 */
|
|
|
|
/*
|
|
* virCommandFDIsSet:
|
|
* @cmd: pointer to virCommand
|
|
* @fd: file descriptor to query
|
|
*
|
|
* Check if FD is already in @set or not.
|
|
*
|
|
* Returns true if @set contains @fd,
|
|
* false otherwise.
|
|
*/
|
|
static bool
|
|
virCommandFDIsSet(virCommandPtr cmd,
|
|
int fd)
|
|
{
|
|
size_t i = 0;
|
|
if (!cmd)
|
|
return false;
|
|
|
|
while (i < cmd->npassfd)
|
|
if (cmd->passfd[i++].fd == fd)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* virCommandFDSet:
|
|
* @cmd: pointer to virCommand
|
|
* @fd: file descriptor to pass
|
|
* @flags: extra flags; binary-OR of virCommandPassFDFlags
|
|
*
|
|
* This is practically generalized implementation
|
|
* of FD_SET() as we do not want to be limited
|
|
* by FD_SETSIZE.
|
|
*
|
|
* Returns: 0 on success,
|
|
* -1 on usage error,
|
|
* ENOMEM on OOM
|
|
*/
|
|
static int
|
|
virCommandFDSet(virCommandPtr cmd,
|
|
int fd,
|
|
unsigned int flags)
|
|
{
|
|
if (!cmd || fd < 0)
|
|
return -1;
|
|
|
|
if (virCommandFDIsSet(cmd, fd))
|
|
return 0;
|
|
|
|
if (VIR_EXPAND_N(cmd->passfd, cmd->npassfd, 1) < 0)
|
|
return ENOMEM;
|
|
|
|
cmd->passfd[cmd->npassfd - 1].fd = fd;
|
|
cmd->passfd[cmd->npassfd - 1].flags = flags;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef WIN32
|
|
|
|
static void virDummyHandler(int sig G_GNUC_UNUSED)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* virFork:
|
|
*
|
|
* Wrapper around fork() that avoids various race/deadlock conditions.
|
|
*
|
|
* Like fork(), there are several return possibilities:
|
|
* 1. No child was created: the return is -1, errno is set, and an error
|
|
* message has been reported. The semantics of virWaitProcess() recognize
|
|
* this to avoid clobbering the error message from here.
|
|
* 2. This is the parent: the return is > 0. The parent can now attempt
|
|
* to interact with the child (but be aware that unlike raw fork(), the
|
|
* child may not return - some failures in the child result in this
|
|
* function calling _exit(EXIT_CANCELED) if the child cannot be set up
|
|
* correctly).
|
|
* 3. This is the child: the return is 0. If this happens, the parent
|
|
* is also guaranteed to return.
|
|
*/
|
|
pid_t
|
|
virFork(void)
|
|
{
|
|
sigset_t oldmask, newmask;
|
|
struct sigaction sig_action;
|
|
int saved_errno;
|
|
pid_t pid;
|
|
|
|
/*
|
|
* Need to block signals now, so that child process can safely
|
|
* kill off caller's signal handlers without a race.
|
|
*/
|
|
sigfillset(&newmask);
|
|
if (pthread_sigmask(SIG_SETMASK, &newmask, &oldmask) != 0) {
|
|
virReportSystemError(errno,
|
|
"%s", _("cannot block signals"));
|
|
return -1;
|
|
}
|
|
|
|
/* Ensure we hold the logging lock, to protect child processes
|
|
* from deadlocking on another thread's inherited mutex state */
|
|
virLogLock();
|
|
|
|
pid = fork();
|
|
saved_errno = errno; /* save for caller */
|
|
|
|
/* Unlock for both parent and child process */
|
|
virLogUnlock();
|
|
|
|
if (pid < 0) {
|
|
/* attempt to restore signal mask, but ignore failure, to
|
|
* avoid obscuring the fork failure */
|
|
ignore_value(pthread_sigmask(SIG_SETMASK, &oldmask, NULL));
|
|
virReportSystemError(saved_errno,
|
|
"%s", _("cannot fork child process"));
|
|
errno = saved_errno;
|
|
|
|
} else if (pid) {
|
|
/* parent process */
|
|
|
|
/* Restore our original signal mask now that the child is
|
|
* safely running. Only documented failures are EFAULT (not
|
|
* possible, since we are using just-grabbed mask) or EINVAL
|
|
* (not possible, since we are using correct arguments). */
|
|
ignore_value(pthread_sigmask(SIG_SETMASK, &oldmask, NULL));
|
|
|
|
} else {
|
|
/* child process */
|
|
|
|
int logprio;
|
|
size_t i;
|
|
|
|
/* Remove any error callback so errors in child now get sent
|
|
* to stderr where they stand a fighting chance of being seen
|
|
* and logged */
|
|
virSetErrorFunc(NULL, NULL);
|
|
virSetErrorLogPriorityFunc(NULL);
|
|
|
|
/* Make sure any hook logging is sent to stderr, since child
|
|
* process may close the logfile FDs */
|
|
logprio = virLogGetDefaultPriority();
|
|
virLogReset();
|
|
virLogSetDefaultPriority(logprio);
|
|
|
|
/* Clear out all signal handlers from parent so nothing
|
|
* unexpected can happen in our child once we unblock
|
|
* signals */
|
|
sig_action.sa_handler = SIG_DFL;
|
|
sig_action.sa_flags = 0;
|
|
sigemptyset(&sig_action.sa_mask);
|
|
|
|
for (i = 1; i < NSIG; i++) {
|
|
/* Only possible errors are EFAULT or EINVAL The former
|
|
* won't happen, the latter we expect, so no need to check
|
|
* return value */
|
|
ignore_value(sigaction(i, &sig_action, NULL));
|
|
}
|
|
|
|
/* Code that runs between fork & execve might trigger
|
|
* SIG_PIPE, so we must explicitly set that to a no-op
|
|
* handler. This handler will get reset to SIG_DFL when
|
|
* execve() runs
|
|
*/
|
|
sig_action.sa_handler = virDummyHandler;
|
|
ignore_value(sigaction(SIGPIPE, &sig_action, NULL));
|
|
|
|
/* Unmask all signals in child, since we've no idea what the
|
|
* caller's done with their signal mask and don't want to
|
|
* propagate that to children */
|
|
sigemptyset(&newmask);
|
|
if (pthread_sigmask(SIG_SETMASK, &newmask, NULL) != 0) {
|
|
virReportSystemError(errno, "%s", _("cannot unblock signals"));
|
|
virDispatchError(NULL);
|
|
_exit(EXIT_CANCELED);
|
|
}
|
|
}
|
|
return pid;
|
|
}
|
|
|
|
/*
|
|
* Ensure that *null is an fd visiting /dev/null. Return 0 on
|
|
* success, -1 on failure. Allows for lazy opening of shared
|
|
* /dev/null fd only as required.
|
|
*/
|
|
static int
|
|
getDevNull(int *null)
|
|
{
|
|
if (*null == -1 && (*null = open("/dev/null", O_RDWR|O_CLOEXEC)) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot open %s"),
|
|
"/dev/null");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Ensure that STD is an inheritable copy of FD. Return 0 on success,
|
|
* -1 on failure. */
|
|
static int
|
|
prepareStdFd(int fd, int std)
|
|
{
|
|
if (fd == std)
|
|
return virSetInherit(fd, true);
|
|
if (dup2(fd, std) != std)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* virCommandHandshakeChild:
|
|
*
|
|
* child side of handshake - called by child process in virExec() to
|
|
* indicate to parent that the child process has successfully
|
|
* completed its pre-exec initialization.
|
|
*/
|
|
static int
|
|
virCommandHandshakeChild(virCommandPtr cmd)
|
|
{
|
|
char c = '1';
|
|
int rv;
|
|
|
|
if (!cmd->handshake)
|
|
return true;
|
|
|
|
VIR_DEBUG("Notifying parent for handshake start on %d",
|
|
cmd->handshakeWait[1]);
|
|
if (safewrite(cmd->handshakeWait[1], &c, sizeof(c)) != sizeof(c)) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to notify parent process"));
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("Waiting on parent for handshake complete on %d",
|
|
cmd->handshakeNotify[0]);
|
|
if ((rv = saferead(cmd->handshakeNotify[0], &c,
|
|
sizeof(c))) != sizeof(c)) {
|
|
if (rv < 0)
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to wait on parent process"));
|
|
else
|
|
virReportSystemError(EIO, "%s",
|
|
_("libvirtd quit during handshake"));
|
|
return -1;
|
|
}
|
|
if (c != '1') {
|
|
virReportSystemError(EINVAL,
|
|
_("Unexpected confirm code '%c' from parent"),
|
|
c);
|
|
return -1;
|
|
}
|
|
VIR_FORCE_CLOSE(cmd->handshakeWait[1]);
|
|
VIR_FORCE_CLOSE(cmd->handshakeNotify[0]);
|
|
|
|
VIR_DEBUG("Handshake with parent is done");
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virExecCommon(virCommandPtr cmd, gid_t *groups, int ngroups)
|
|
{
|
|
if (cmd->uid != (uid_t)-1 || cmd->gid != (gid_t)-1 ||
|
|
cmd->capabilities || (cmd->flags & VIR_EXEC_CLEAR_CAPS)) {
|
|
VIR_DEBUG("Setting child uid:gid to %d:%d with caps %llx",
|
|
(int)cmd->uid, (int)cmd->gid, cmd->capabilities);
|
|
if (virSetUIDGIDWithCaps(cmd->uid, cmd->gid, groups, ngroups,
|
|
cmd->capabilities,
|
|
!!(cmd->flags & VIR_EXEC_CLEAR_CAPS)) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (cmd->pwd) {
|
|
VIR_DEBUG("Running child in %s", cmd->pwd);
|
|
if (chdir(cmd->pwd) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Unable to change to %s"), cmd->pwd);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
# ifdef __linux__
|
|
/* On Linux, we can utilize procfs and read the table of opened
|
|
* FDs and selectively close only those FDs we don't want to pass
|
|
* onto child process (well, the one we will exec soon since this
|
|
* is called from the child). */
|
|
static int
|
|
virCommandMassCloseGetFDsLinux(virCommandPtr cmd G_GNUC_UNUSED,
|
|
virBitmapPtr fds)
|
|
{
|
|
DIR *dp = NULL;
|
|
struct dirent *entry;
|
|
const char *dirName = "/proc/self/fd";
|
|
int rc;
|
|
int ret = -1;
|
|
|
|
if (virDirOpen(&dp, dirName) < 0)
|
|
return -1;
|
|
|
|
while ((rc = virDirRead(dp, &entry, dirName)) > 0) {
|
|
int fd;
|
|
|
|
if (virStrToLong_i(entry->d_name, NULL, 10, &fd) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unable to parse FD: %s"),
|
|
entry->d_name);
|
|
goto cleanup;
|
|
}
|
|
|
|
ignore_value(virBitmapSetBit(fds, fd));
|
|
}
|
|
|
|
if (rc < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
VIR_DIR_CLOSE(dp);
|
|
return ret;
|
|
}
|
|
|
|
# else /* !__linux__ */
|
|
|
|
static int
|
|
virCommandMassCloseGetFDsGeneric(virCommandPtr cmd G_GNUC_UNUSED,
|
|
virBitmapPtr fds)
|
|
{
|
|
virBitmapSetAll(fds);
|
|
return 0;
|
|
}
|
|
# endif /* !__linux__ */
|
|
|
|
# ifdef __FreeBSD__
|
|
|
|
static int
|
|
virCommandMassClose(virCommandPtr cmd,
|
|
int childin,
|
|
int childout,
|
|
int childerr)
|
|
{
|
|
int lastfd = -1;
|
|
int fd = -1;
|
|
size_t i;
|
|
|
|
/*
|
|
* Two phases of closing.
|
|
*
|
|
* The first (inefficient) phase iterates over FDs,
|
|
* preserving certain FDs we need to pass down, and
|
|
* closing others. The number of iterations is bounded
|
|
* to the number of the biggest FD we need to preserve.
|
|
*
|
|
* The second (speedy) phase uses closefrom() to cull
|
|
* all remaining FDs in the process.
|
|
*
|
|
* Usually the first phase will be fairly quick only
|
|
* processing a handful of low FD numbers, and thus using
|
|
* closefrom() is a massive win for high ulimit() NFILES
|
|
* values.
|
|
*/
|
|
lastfd = MAX(lastfd, childin);
|
|
lastfd = MAX(lastfd, childout);
|
|
lastfd = MAX(lastfd, childerr);
|
|
|
|
for (i = 0; i < cmd->npassfd; i++)
|
|
lastfd = MAX(lastfd, cmd->passfd[i].fd);
|
|
|
|
for (fd = 0; fd <= lastfd; fd++) {
|
|
if (fd == childin || fd == childout || fd == childerr)
|
|
continue;
|
|
if (!virCommandFDIsSet(cmd, fd)) {
|
|
int tmpfd = fd;
|
|
VIR_MASS_CLOSE(tmpfd);
|
|
} else if (virSetInherit(fd, true) < 0) {
|
|
virReportSystemError(errno, _("failed to preserve fd %d"), fd);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
closefrom(lastfd + 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
# else /* ! __FreeBSD__ */
|
|
|
|
static int
|
|
virCommandMassClose(virCommandPtr cmd,
|
|
int childin,
|
|
int childout,
|
|
int childerr)
|
|
{
|
|
g_autoptr(virBitmap) fds = NULL;
|
|
int openmax = sysconf(_SC_OPEN_MAX);
|
|
int fd = -1;
|
|
|
|
/* In general, it is not safe to call malloc() between fork() and exec()
|
|
* because the child might have forked at the worst possible time, i.e.
|
|
* when another thread was in malloc() and thus held its lock. That is to
|
|
* say, POSIX does not mandate malloc() to be async-safe. Fortunately,
|
|
* glibc developers are aware of this and made malloc() async-safe.
|
|
* Therefore we can safely allocate memory here (and transitively call
|
|
* opendir/readdir) without a deadlock. */
|
|
|
|
if (openmax < 0) {
|
|
virReportSystemError(errno, "%s", _("sysconf(_SC_OPEN_MAX) failed"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(fds = virBitmapNew(openmax)))
|
|
return -1;
|
|
|
|
# ifdef __linux__
|
|
if (virCommandMassCloseGetFDsLinux(cmd, fds) < 0)
|
|
return -1;
|
|
# else
|
|
if (virCommandMassCloseGetFDsGeneric(cmd, fds) < 0)
|
|
return -1;
|
|
# endif
|
|
|
|
fd = virBitmapNextSetBit(fds, 2);
|
|
for (; fd >= 0; fd = virBitmapNextSetBit(fds, fd)) {
|
|
if (fd == childin || fd == childout || fd == childerr)
|
|
continue;
|
|
if (!virCommandFDIsSet(cmd, fd)) {
|
|
int tmpfd = fd;
|
|
VIR_MASS_CLOSE(tmpfd);
|
|
} else if (virSetInherit(fd, true) < 0) {
|
|
virReportSystemError(errno, _("failed to preserve fd %d"), fd);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
# endif /* ! __FreeBSD__ */
|
|
|
|
/*
|
|
* virExec:
|
|
* @cmd virCommandPtr containing all information about the program to
|
|
* exec.
|
|
*/
|
|
static int
|
|
virExec(virCommandPtr cmd)
|
|
{
|
|
pid_t pid;
|
|
int null = -1;
|
|
int pipeout[2] = {-1, -1};
|
|
int pipeerr[2] = {-1, -1};
|
|
int pipesync[2] = {-1, -1};
|
|
int childin = cmd->infd;
|
|
int childout = -1;
|
|
int childerr = -1;
|
|
g_autofree char *binarystr = NULL;
|
|
const char *binary = NULL;
|
|
int ret;
|
|
g_autofree gid_t *groups = NULL;
|
|
int ngroups;
|
|
|
|
if (cmd->args[0][0] != '/') {
|
|
if (!(binary = binarystr = virFindFileInPath(cmd->args[0]))) {
|
|
virReportSystemError(ENOENT,
|
|
_("Cannot find '%s' in path"),
|
|
cmd->args[0]);
|
|
return -1;
|
|
}
|
|
} else {
|
|
binary = cmd->args[0];
|
|
}
|
|
|
|
if (childin < 0) {
|
|
if (getDevNull(&null) < 0)
|
|
goto cleanup;
|
|
childin = null;
|
|
}
|
|
|
|
if (cmd->outfdptr != NULL) {
|
|
if (*cmd->outfdptr == -1) {
|
|
if (virPipe(pipeout) < 0)
|
|
goto cleanup;
|
|
|
|
if ((cmd->flags & VIR_EXEC_NONBLOCK) &&
|
|
virSetNonBlock(pipeout[0]) == -1) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Failed to set non-blocking file descriptor flag"));
|
|
goto cleanup;
|
|
}
|
|
|
|
childout = pipeout[1];
|
|
} else {
|
|
childout = *cmd->outfdptr;
|
|
}
|
|
} else {
|
|
if (getDevNull(&null) < 0)
|
|
goto cleanup;
|
|
childout = null;
|
|
}
|
|
|
|
if (cmd->errfdptr != NULL) {
|
|
if (cmd->errfdptr == cmd->outfdptr) {
|
|
childerr = childout;
|
|
} else if (*cmd->errfdptr == -1) {
|
|
if (virPipe(pipeerr) < 0)
|
|
goto cleanup;
|
|
|
|
if ((cmd->flags & VIR_EXEC_NONBLOCK) &&
|
|
virSetNonBlock(pipeerr[0]) == -1) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Failed to set non-blocking file descriptor flag"));
|
|
goto cleanup;
|
|
}
|
|
|
|
childerr = pipeerr[1];
|
|
} else {
|
|
childerr = *cmd->errfdptr;
|
|
}
|
|
} else {
|
|
if (getDevNull(&null) < 0)
|
|
goto cleanup;
|
|
childerr = null;
|
|
}
|
|
|
|
if ((ngroups = virGetGroupList(cmd->uid, cmd->gid, &groups)) < 0)
|
|
goto cleanup;
|
|
|
|
pid = virFork();
|
|
|
|
if (pid < 0)
|
|
goto cleanup;
|
|
|
|
if (pid) { /* parent */
|
|
VIR_FORCE_CLOSE(null);
|
|
if (cmd->outfdptr && *cmd->outfdptr == -1) {
|
|
VIR_FORCE_CLOSE(pipeout[1]);
|
|
*cmd->outfdptr = pipeout[0];
|
|
}
|
|
if (cmd->errfdptr && *cmd->errfdptr == -1) {
|
|
VIR_FORCE_CLOSE(pipeerr[1]);
|
|
*cmd->errfdptr = pipeerr[0];
|
|
}
|
|
|
|
cmd->pid = pid;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* child */
|
|
|
|
if (cmd->mask)
|
|
umask(cmd->mask);
|
|
ret = EXIT_CANCELED;
|
|
|
|
if (virCommandMassClose(cmd, childin, childout, childerr) < 0)
|
|
goto fork_error;
|
|
|
|
if (prepareStdFd(childin, STDIN_FILENO) < 0) {
|
|
virReportSystemError(errno,
|
|
"%s", _("failed to setup stdin file handle"));
|
|
goto fork_error;
|
|
}
|
|
if (childout > 0 && prepareStdFd(childout, STDOUT_FILENO) < 0) {
|
|
virReportSystemError(errno,
|
|
"%s", _("failed to setup stdout file handle"));
|
|
goto fork_error;
|
|
}
|
|
if (childerr > 0 && prepareStdFd(childerr, STDERR_FILENO) < 0) {
|
|
virReportSystemError(errno,
|
|
"%s", _("failed to setup stderr file handle"));
|
|
goto fork_error;
|
|
}
|
|
|
|
if (childin != STDIN_FILENO && childin != null &&
|
|
childin != childerr && childin != childout)
|
|
VIR_FORCE_CLOSE(childin);
|
|
if (childout > STDERR_FILENO && childout != null && childout != childerr)
|
|
VIR_FORCE_CLOSE(childout);
|
|
if (childerr > STDERR_FILENO && childerr != null)
|
|
VIR_FORCE_CLOSE(childerr);
|
|
VIR_FORCE_CLOSE(null);
|
|
|
|
/* Initialize full logging for a while */
|
|
virLogSetFromEnv();
|
|
|
|
if (cmd->pidfile &&
|
|
virPipe(pipesync) < 0)
|
|
goto fork_error;
|
|
|
|
/* Daemonize as late as possible, so the parent process can detect
|
|
* the above errors with wait* */
|
|
if (cmd->flags & VIR_EXEC_DAEMON) {
|
|
char c;
|
|
|
|
if (setsid() < 0) {
|
|
virReportSystemError(errno,
|
|
"%s", _("cannot become session leader"));
|
|
goto fork_error;
|
|
}
|
|
|
|
if (chdir("/") < 0) {
|
|
virReportSystemError(errno,
|
|
"%s", _("cannot change to root directory"));
|
|
goto fork_error;
|
|
}
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
virReportSystemError(errno,
|
|
"%s", _("cannot fork child process"));
|
|
goto fork_error;
|
|
}
|
|
|
|
if (pid > 0) {
|
|
/* The parent expect us to have written the pid file before
|
|
* exiting. Wait here for the child to write it and signal us. */
|
|
if (cmd->pidfile &&
|
|
saferead(pipesync[0], &c, sizeof(c)) != sizeof(c)) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to wait for child process"));
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
_exit(EXIT_SUCCESS);
|
|
}
|
|
}
|
|
|
|
if (virProcessSetMaxMemLock(0, cmd->maxMemLock) < 0)
|
|
goto fork_error;
|
|
if (virProcessSetMaxProcesses(0, cmd->maxProcesses) < 0)
|
|
goto fork_error;
|
|
if (virProcessSetMaxFiles(0, cmd->maxFiles) < 0)
|
|
goto fork_error;
|
|
if (cmd->setMaxCore &&
|
|
virProcessSetMaxCoreSize(0, cmd->maxCore) < 0)
|
|
goto fork_error;
|
|
if (cmd->pidfile) {
|
|
int pidfilefd = -1;
|
|
char c;
|
|
|
|
pidfilefd = virPidFileAcquirePath(cmd->pidfile, false, getpid());
|
|
if (pidfilefd < 0)
|
|
goto fork_error;
|
|
if (virSetInherit(pidfilefd, true) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Cannot disable close-on-exec flag"));
|
|
goto fork_error;
|
|
}
|
|
|
|
c = '1';
|
|
if (safewrite(pipesync[1], &c, sizeof(c)) != sizeof(c)) {
|
|
virReportSystemError(errno, "%s", _("Unable to notify child process"));
|
|
goto fork_error;
|
|
}
|
|
VIR_FORCE_CLOSE(pipesync[0]);
|
|
VIR_FORCE_CLOSE(pipesync[1]);
|
|
|
|
/* pidfilefd is intentionally leaked. */
|
|
}
|
|
|
|
if (cmd->hook) {
|
|
VIR_DEBUG("Run hook %p %p", cmd->hook, cmd->opaque);
|
|
ret = cmd->hook(cmd->opaque);
|
|
VIR_DEBUG("Done hook %d", ret);
|
|
if (ret < 0)
|
|
goto fork_error;
|
|
}
|
|
|
|
# if defined(WITH_SECDRIVER_SELINUX)
|
|
if (cmd->seLinuxLabel) {
|
|
VIR_DEBUG("Setting child security label to %s", cmd->seLinuxLabel);
|
|
if (setexeccon_raw(cmd->seLinuxLabel) == -1) {
|
|
virReportSystemError(errno,
|
|
_("unable to set SELinux security context "
|
|
"'%s' for '%s'"),
|
|
cmd->seLinuxLabel, cmd->args[0]);
|
|
if (security_getenforce() == 1)
|
|
goto fork_error;
|
|
}
|
|
}
|
|
# endif
|
|
# if defined(WITH_SECDRIVER_APPARMOR)
|
|
if (cmd->appArmorProfile) {
|
|
VIR_DEBUG("Setting child AppArmor profile to %s", cmd->appArmorProfile);
|
|
if (aa_change_profile(cmd->appArmorProfile) < 0) {
|
|
virReportSystemError(errno,
|
|
_("unable to set AppArmor profile '%s' "
|
|
"for '%s'"),
|
|
cmd->appArmorProfile, cmd->args[0]);
|
|
goto fork_error;
|
|
}
|
|
}
|
|
# endif
|
|
|
|
if (virExecCommon(cmd, groups, ngroups) < 0)
|
|
goto fork_error;
|
|
|
|
if (virCommandHandshakeChild(cmd) < 0)
|
|
goto fork_error;
|
|
|
|
/* Close logging again to ensure no FDs leak to child */
|
|
virLogReset();
|
|
|
|
if (cmd->env)
|
|
execve(binary, cmd->args, cmd->env);
|
|
else
|
|
execv(binary, cmd->args);
|
|
|
|
ret = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
|
|
virReportSystemError(errno,
|
|
_("cannot execute binary %s"),
|
|
cmd->args[0]);
|
|
|
|
fork_error:
|
|
virDispatchError(NULL);
|
|
_exit(ret);
|
|
|
|
cleanup:
|
|
/* This is cleanup of parent process only - child
|
|
should never jump here on error */
|
|
|
|
/* NB we don't virReportError() on any failures here
|
|
because the code which jumped here already raised
|
|
an error condition which we must not overwrite */
|
|
VIR_FORCE_CLOSE(pipeerr[0]);
|
|
VIR_FORCE_CLOSE(pipeerr[1]);
|
|
VIR_FORCE_CLOSE(pipeout[0]);
|
|
VIR_FORCE_CLOSE(pipeout[1]);
|
|
VIR_FORCE_CLOSE(null);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* virRun:
|
|
* @argv NULL terminated argv to run
|
|
* @status optional variable to return exit status in
|
|
*
|
|
* Run a command without using the shell.
|
|
*
|
|
* If status is NULL, then return 0 if the command run and
|
|
* exited with 0 status; Otherwise return -1
|
|
*
|
|
* If status is not-NULL, then return 0 if the command ran.
|
|
* The status variable is filled with the command exit status
|
|
* and should be checked by caller for success. Return -1
|
|
* only if the command could not be run.
|
|
*/
|
|
int
|
|
virRun(const char *const*argv, int *status)
|
|
{
|
|
g_autoptr(virCommand) cmd = virCommandNewArgs(argv);
|
|
|
|
return virCommandRun(cmd, status);
|
|
}
|
|
|
|
#else /* WIN32 */
|
|
|
|
int
|
|
virRun(const char *const *argv G_GNUC_UNUSED,
|
|
int *status)
|
|
{
|
|
if (status)
|
|
*status = ENOTSUP;
|
|
else
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("virRun is not implemented for WIN32"));
|
|
return -1;
|
|
}
|
|
|
|
pid_t
|
|
virFork(void)
|
|
{
|
|
errno = ENOTSUP;
|
|
|
|
return -1;
|
|
}
|
|
|
|
#endif /* WIN32 */
|
|
|
|
|
|
/**
|
|
* virCommandNew:
|
|
* @binary: program to run
|
|
*
|
|
* Create a new command for named binary. If @binary is relative,
|
|
* it will be found via a PATH search of the parent's PATH (and not
|
|
* any altered PATH set by virCommandAddEnv* commands).
|
|
*/
|
|
virCommandPtr
|
|
virCommandNew(const char *binary)
|
|
{
|
|
const char *const args[] = { binary, NULL };
|
|
|
|
return virCommandNewArgs(args);
|
|
}
|
|
|
|
/**
|
|
* virCommandNewArgs:
|
|
* @args: array of arguments
|
|
*
|
|
* Create a new command with a NULL terminated
|
|
* set of args, taking binary from args[0]. More arguments can
|
|
* be added later. @args[0] is handled like @binary of virCommandNew.
|
|
*/
|
|
virCommandPtr
|
|
virCommandNewArgs(const char *const*args)
|
|
{
|
|
virCommandPtr cmd;
|
|
|
|
if (VIR_ALLOC(cmd) < 0)
|
|
return NULL;
|
|
|
|
cmd->handshakeWait[0] = -1;
|
|
cmd->handshakeWait[1] = -1;
|
|
cmd->handshakeNotify[0] = -1;
|
|
cmd->handshakeNotify[1] = -1;
|
|
|
|
cmd->infd = cmd->inpipe = cmd->outfd = cmd->errfd = -1;
|
|
cmd->pid = -1;
|
|
cmd->uid = -1;
|
|
cmd->gid = -1;
|
|
|
|
virCommandAddArgSet(cmd, args);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
* virCommandNewArgList:
|
|
* @binary: program to run
|
|
* @...: additional arguments
|
|
*
|
|
* Create a new command with a NULL terminated
|
|
* list of args, starting with the binary to run. More arguments can
|
|
* be added later. @binary is handled as in virCommandNew.
|
|
*/
|
|
virCommandPtr
|
|
virCommandNewArgList(const char *binary, ...)
|
|
{
|
|
virCommandPtr cmd;
|
|
va_list list;
|
|
|
|
va_start(list, binary);
|
|
cmd = virCommandNewVAList(binary, list);
|
|
va_end(list);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
* virCommandNewVAList:
|
|
* @binary: program to run
|
|
* @va_list: additional arguments
|
|
*
|
|
* Create a new command with a NULL terminated
|
|
* variable argument list. @binary is handled as in virCommandNew.
|
|
*/
|
|
virCommandPtr
|
|
virCommandNewVAList(const char *binary, va_list list)
|
|
{
|
|
virCommandPtr cmd = virCommandNew(binary);
|
|
const char *arg;
|
|
|
|
if (!cmd || cmd->has_error)
|
|
return cmd;
|
|
|
|
while ((arg = va_arg(list, const char *)) != NULL)
|
|
virCommandAddArg(cmd, arg);
|
|
return cmd;
|
|
}
|
|
|
|
|
|
#define VIR_COMMAND_MAYBE_CLOSE_FD(fd, flags) \
|
|
if ((fd > STDERR_FILENO) && \
|
|
(flags & VIR_COMMAND_PASS_FD_CLOSE_PARENT)) \
|
|
VIR_FORCE_CLOSE(fd)
|
|
|
|
/**
|
|
* virCommandPassFD:
|
|
* @cmd: the command to modify
|
|
* @fd: fd to reassign to the child
|
|
* @flags: extra flags; binary-OR of virCommandPassFDFlags
|
|
*
|
|
* Transfer the specified file descriptor to the child, instead
|
|
* of closing it on exec. @fd must not be one of the three
|
|
* standard streams.
|
|
*
|
|
* If the flag VIR_COMMAND_PASS_FD_CLOSE_PARENT is set then fd will
|
|
* be closed in the parent no later than Run/RunAsync/Free. The parent
|
|
* should cease using the @fd when this call completes
|
|
*/
|
|
void
|
|
virCommandPassFD(virCommandPtr cmd, int fd, unsigned int flags)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!cmd) {
|
|
VIR_COMMAND_MAYBE_CLOSE_FD(fd, flags);
|
|
return;
|
|
}
|
|
|
|
if (fd <= STDERR_FILENO) {
|
|
VIR_DEBUG("invalid fd %d", fd);
|
|
VIR_COMMAND_MAYBE_CLOSE_FD(fd, flags);
|
|
if (!cmd->has_error)
|
|
cmd->has_error = -1;
|
|
return;
|
|
}
|
|
|
|
if ((ret = virCommandFDSet(cmd, fd, flags)) != 0) {
|
|
if (!cmd->has_error)
|
|
cmd->has_error = ret;
|
|
VIR_DEBUG("cannot preserve %d", fd);
|
|
VIR_COMMAND_MAYBE_CLOSE_FD(fd, flags);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* virCommandPassFDGetFDIndex:
|
|
* @cmd: pointer to virCommand
|
|
* @fd: FD to get index of
|
|
*
|
|
* Determine the index of the FD in the transfer set.
|
|
*
|
|
* Returns index >= 0 if @set contains @fd,
|
|
* -1 otherwise.
|
|
*/
|
|
int
|
|
virCommandPassFDGetFDIndex(virCommandPtr cmd, int fd)
|
|
{
|
|
size_t i = 0;
|
|
|
|
if (!cmd || cmd->has_error)
|
|
return -1;
|
|
|
|
while (i < cmd->npassfd) {
|
|
if (cmd->passfd[i].fd == fd)
|
|
return i;
|
|
i++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* virCommandSetPidFile:
|
|
* @cmd: the command to modify
|
|
* @pidfile: filename to use
|
|
*
|
|
* Save the child PID in a pidfile. The pidfile will be populated before the
|
|
* exec of the child and the child will inherit opened and locked FD to the
|
|
* pidfile.
|
|
*/
|
|
void
|
|
virCommandSetPidFile(virCommandPtr cmd, const char *pidfile)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
VIR_FREE(cmd->pidfile);
|
|
cmd->pidfile = g_strdup(pidfile);
|
|
}
|
|
|
|
|
|
gid_t
|
|
virCommandGetGID(virCommandPtr cmd)
|
|
{
|
|
return cmd->gid;
|
|
}
|
|
|
|
|
|
uid_t
|
|
virCommandGetUID(virCommandPtr cmd)
|
|
{
|
|
return cmd->uid;
|
|
}
|
|
|
|
|
|
void
|
|
virCommandSetGID(virCommandPtr cmd, gid_t gid)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->gid = gid;
|
|
}
|
|
|
|
void
|
|
virCommandSetUID(virCommandPtr cmd, uid_t uid)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->uid = uid;
|
|
}
|
|
|
|
void
|
|
virCommandSetMaxMemLock(virCommandPtr cmd, unsigned long long bytes)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->maxMemLock = bytes;
|
|
}
|
|
|
|
void
|
|
virCommandSetMaxProcesses(virCommandPtr cmd, unsigned int procs)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->maxProcesses = procs;
|
|
}
|
|
|
|
void
|
|
virCommandSetMaxFiles(virCommandPtr cmd, unsigned int files)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->maxFiles = files;
|
|
}
|
|
|
|
void virCommandSetMaxCoreSize(virCommandPtr cmd, unsigned long long bytes)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->maxCore = bytes;
|
|
cmd->setMaxCore = true;
|
|
}
|
|
|
|
void virCommandSetUmask(virCommandPtr cmd, int mask)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->mask = mask;
|
|
}
|
|
|
|
/**
|
|
* virCommandClearCaps:
|
|
* @cmd: the command to modify
|
|
*
|
|
* Remove all capabilities from the child, after any hooks have been run.
|
|
*/
|
|
void
|
|
virCommandClearCaps(virCommandPtr cmd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->flags |= VIR_EXEC_CLEAR_CAPS;
|
|
}
|
|
|
|
/**
|
|
* virCommandAllowCap:
|
|
* @cmd: the command to modify
|
|
* @capability: what to allow
|
|
*
|
|
* Allow specific capabilities
|
|
*/
|
|
void
|
|
virCommandAllowCap(virCommandPtr cmd,
|
|
int capability)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->capabilities |= (1ULL << capability);
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandSetSELinuxLabel:
|
|
* @cmd: the command to modify
|
|
* @label: the SELinux label to use for the child process
|
|
*
|
|
* Saves a copy of @label to use when setting the SELinux context
|
|
* label (with setexeccon_raw()) after the child process has been
|
|
* started. If SELinux isn't compiled into libvirt, or if label is
|
|
* NULL, nothing will be done.
|
|
*/
|
|
void
|
|
virCommandSetSELinuxLabel(virCommandPtr cmd,
|
|
const char *label G_GNUC_UNUSED)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
#if defined(WITH_SECDRIVER_SELINUX)
|
|
VIR_FREE(cmd->seLinuxLabel);
|
|
cmd->seLinuxLabel = g_strdup(label);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandSetAppArmorProfile:
|
|
* @cmd: the command to modify
|
|
* @profile: the AppArmor profile to use
|
|
*
|
|
* Saves a copy of @profile to use when aa_change_profile() after the
|
|
* child process has been started. If AppArmor support isn't
|
|
* configured into libvirt, or if profile is NULL, nothing will be done.
|
|
*/
|
|
void
|
|
virCommandSetAppArmorProfile(virCommandPtr cmd,
|
|
const char *profile G_GNUC_UNUSED)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
#if defined(WITH_SECDRIVER_APPARMOR)
|
|
VIR_FREE(cmd->appArmorProfile);
|
|
cmd->appArmorProfile = g_strdup(profile);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandDaemonize:
|
|
* @cmd: the command to modify
|
|
*
|
|
* Daemonize the child process. The child will have a current working
|
|
* directory of /, and must be started with virCommandRun, which will
|
|
* complete as soon as the daemon grandchild has started.
|
|
*/
|
|
void
|
|
virCommandDaemonize(virCommandPtr cmd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->flags |= VIR_EXEC_DAEMON;
|
|
}
|
|
|
|
/**
|
|
* virCommandNonblockingFDs:
|
|
* @cmd: the command to modify
|
|
*
|
|
* Set FDs created by virCommandSetOutputFD and virCommandSetErrorFD
|
|
* as non-blocking in the parent.
|
|
*/
|
|
void
|
|
virCommandNonblockingFDs(virCommandPtr cmd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->flags |= VIR_EXEC_NONBLOCK;
|
|
}
|
|
|
|
/**
|
|
* virCommandRawStatus:
|
|
* @cmd: the command to modify
|
|
*
|
|
* Mark this command as returning raw exit status via virCommandRun() or
|
|
* virCommandWait() (caller must use WIFEXITED() and friends, and can
|
|
* detect death from signals) instead of the default of only allowing
|
|
* normal exit status (caller must not use WEXITSTATUS(), and death from
|
|
* signals returns -1).
|
|
*/
|
|
void
|
|
virCommandRawStatus(virCommandPtr cmd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->rawStatus = true;
|
|
}
|
|
|
|
/* Add an environment variable to the cmd->env list. 'env' is a
|
|
* string like "name=value". If the named environment variable is
|
|
* already set, then it is replaced in the list.
|
|
*/
|
|
static void
|
|
virCommandAddEnv(virCommandPtr cmd, char *env)
|
|
{
|
|
size_t namelen;
|
|
size_t i;
|
|
|
|
/* Search for the name in the existing environment. */
|
|
namelen = strcspn(env, "=");
|
|
for (i = 0; i < cmd->nenv; ++i) {
|
|
/* + 1 because we want to match the '=' character too. */
|
|
if (STREQLEN(cmd->env[i], env, namelen + 1)) {
|
|
VIR_FREE(cmd->env[i]);
|
|
cmd->env[i] = env;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Arg plus trailing NULL. */
|
|
if (VIR_RESIZE_N(cmd->env, cmd->maxenv, cmd->nenv, 1 + 1) < 0) {
|
|
VIR_FREE(env);
|
|
cmd->has_error = ENOMEM;
|
|
return;
|
|
}
|
|
|
|
cmd->env[cmd->nenv++] = env;
|
|
}
|
|
|
|
/**
|
|
* virCommandAddEnvFormat:
|
|
* @cmd: the command to modify
|
|
* @format: format of arguments, end result must be in name=value format
|
|
* @...: arguments to be formatted
|
|
*
|
|
* Add an environment variable to the child created by a printf-style format.
|
|
*/
|
|
void
|
|
virCommandAddEnvFormat(virCommandPtr cmd, const char *format, ...)
|
|
{
|
|
char *env;
|
|
va_list list;
|
|
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
va_start(list, format);
|
|
env = g_strdup_vprintf(format, list);
|
|
va_end(list);
|
|
|
|
virCommandAddEnv(cmd, env);
|
|
}
|
|
|
|
/**
|
|
* virCommandAddEnvPair:
|
|
* @cmd: the command to modify
|
|
* @name: variable name, must not contain =
|
|
* @value: value to assign to name
|
|
*
|
|
* Add an environment variable to the child
|
|
* using separate name & value strings
|
|
*/
|
|
void
|
|
virCommandAddEnvPair(virCommandPtr cmd, const char *name, const char *value)
|
|
{
|
|
virCommandAddEnvFormat(cmd, "%s=%s", name, value);
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandAddEnvString:
|
|
* @cmd: the command to modify
|
|
* @str: name=value format
|
|
*
|
|
* Add an environment variable to the child
|
|
* using a preformatted env string FOO=BAR
|
|
*/
|
|
void
|
|
virCommandAddEnvString(virCommandPtr cmd, const char *str)
|
|
{
|
|
char *env;
|
|
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
env = g_strdup(str);
|
|
|
|
virCommandAddEnv(cmd, env);
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandAddEnvBuffer:
|
|
* @cmd: the command to modify
|
|
* @buf: buffer that contains name=value string, which will be reset on return
|
|
*
|
|
* Convert a buffer containing preformatted name=value into an
|
|
* environment variable of the child.
|
|
* Correctly transfers memory errors or contents from buf to cmd.
|
|
*/
|
|
void
|
|
virCommandAddEnvBuffer(virCommandPtr cmd, virBufferPtr buf)
|
|
{
|
|
if (!cmd || cmd->has_error) {
|
|
virBufferFreeAndReset(buf);
|
|
return;
|
|
}
|
|
|
|
if (!virBufferUse(buf)) {
|
|
cmd->has_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
virCommandAddEnv(cmd, virBufferContentAndReset(buf));
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandAddEnvPass:
|
|
* @cmd: the command to modify
|
|
* @name: the name to look up in current environment
|
|
*
|
|
* Pass an environment variable to the child
|
|
* using current process' value
|
|
*/
|
|
void
|
|
virCommandAddEnvPass(virCommandPtr cmd, const char *name)
|
|
{
|
|
const char *value;
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
value = getenv(name);
|
|
if (value)
|
|
virCommandAddEnvPair(cmd, name, value);
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandAddEnvPassCommon:
|
|
* @cmd: the command to modify
|
|
*
|
|
* Set LC_ALL to C, and propagate other essential environment
|
|
* variables (such as PATH) from the parent process.
|
|
*/
|
|
void
|
|
virCommandAddEnvPassCommon(virCommandPtr cmd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (VIR_RESIZE_N(cmd->env, cmd->maxenv, cmd->nenv, 9) < 0) {
|
|
cmd->has_error = ENOMEM;
|
|
return;
|
|
}
|
|
|
|
virCommandAddEnvPair(cmd, "LC_ALL", "C");
|
|
|
|
virCommandAddEnvPass(cmd, "LD_PRELOAD");
|
|
virCommandAddEnvPass(cmd, "LD_LIBRARY_PATH");
|
|
virCommandAddEnvPass(cmd, "PATH");
|
|
virCommandAddEnvPass(cmd, "HOME");
|
|
virCommandAddEnvPass(cmd, "USER");
|
|
virCommandAddEnvPass(cmd, "LOGNAME");
|
|
virCommandAddEnvPass(cmd, "TMPDIR");
|
|
}
|
|
|
|
|
|
void
|
|
virCommandAddEnvXDG(virCommandPtr cmd, const char *baseDir)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (VIR_RESIZE_N(cmd->env, cmd->maxenv, cmd->nenv, 3) < 0) {
|
|
cmd->has_error = ENOMEM;
|
|
return;
|
|
}
|
|
|
|
virCommandAddEnvFormat(cmd, "XDG_DATA_HOME=%s/%s",
|
|
baseDir, ".local/share");
|
|
virCommandAddEnvFormat(cmd, "XDG_CACHE_HOME=%s/%s",
|
|
baseDir, ".cache");
|
|
virCommandAddEnvFormat(cmd, "XDG_CONFIG_HOME=%s/%s",
|
|
baseDir, ".config");
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandAddArg:
|
|
* @cmd: the command to modify
|
|
* @val: the argument to add
|
|
*
|
|
* Add a command line argument to the child
|
|
*/
|
|
void
|
|
virCommandAddArg(virCommandPtr cmd, const char *val)
|
|
{
|
|
char *arg;
|
|
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (val == NULL) {
|
|
cmd->has_error = EINVAL;
|
|
abort();
|
|
return;
|
|
}
|
|
|
|
arg = g_strdup(val);
|
|
|
|
/* Arg plus trailing NULL. */
|
|
if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, 1 + 1) < 0) {
|
|
VIR_FREE(arg);
|
|
cmd->has_error = ENOMEM;
|
|
return;
|
|
}
|
|
|
|
cmd->args[cmd->nargs++] = arg;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandAddArgBuffer:
|
|
* @cmd: the command to modify
|
|
* @buf: buffer that contains argument string, which will be reset on return
|
|
*
|
|
* Convert a buffer into a command line argument to the child.
|
|
* Correctly transfers memory errors or contents from buf to cmd.
|
|
*/
|
|
void
|
|
virCommandAddArgBuffer(virCommandPtr cmd, virBufferPtr buf)
|
|
{
|
|
if (!cmd || cmd->has_error) {
|
|
virBufferFreeAndReset(buf);
|
|
return;
|
|
}
|
|
|
|
/* Arg plus trailing NULL. */
|
|
if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, 1 + 1) < 0) {
|
|
cmd->has_error = ENOMEM;
|
|
virBufferFreeAndReset(buf);
|
|
return;
|
|
}
|
|
|
|
cmd->args[cmd->nargs] = virBufferContentAndReset(buf);
|
|
if (!cmd->args[cmd->nargs])
|
|
cmd->args[cmd->nargs] = g_strdup("");
|
|
cmd->nargs++;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandAddArgFormat:
|
|
* @cmd: the command to modify
|
|
* @format: format of arguments, end result must be in name=value format
|
|
* @...: arguments to be formatted
|
|
*
|
|
* Add a command line argument created by a printf-style format.
|
|
*/
|
|
void
|
|
virCommandAddArgFormat(virCommandPtr cmd, const char *format, ...)
|
|
{
|
|
char *arg;
|
|
va_list list;
|
|
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
va_start(list, format);
|
|
arg = g_strdup_vprintf(format, list);
|
|
va_end(list);
|
|
|
|
/* Arg plus trailing NULL. */
|
|
if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, 1 + 1) < 0) {
|
|
VIR_FREE(arg);
|
|
cmd->has_error = ENOMEM;
|
|
return;
|
|
}
|
|
|
|
cmd->args[cmd->nargs++] = arg;
|
|
}
|
|
|
|
/**
|
|
* virCommandAddArgPair:
|
|
* @cmd: the command to modify
|
|
* @name: left half of argument
|
|
* @value: right half of argument
|
|
*
|
|
* Add "NAME=VAL" as a single command line argument to the child
|
|
*/
|
|
void
|
|
virCommandAddArgPair(virCommandPtr cmd, const char *name, const char *val)
|
|
{
|
|
if (name == NULL || val == NULL) {
|
|
cmd->has_error = EINVAL;
|
|
return;
|
|
}
|
|
virCommandAddArgFormat(cmd, "%s=%s", name, val);
|
|
}
|
|
|
|
/**
|
|
* virCommandAddArgSet:
|
|
* @cmd: the command to modify
|
|
* @vals: array of arguments to add
|
|
*
|
|
* Add a NULL terminated list of args
|
|
*/
|
|
void
|
|
virCommandAddArgSet(virCommandPtr cmd, const char *const*vals)
|
|
{
|
|
int narg = 0;
|
|
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (vals[0] == NULL) {
|
|
cmd->has_error = EINVAL;
|
|
return;
|
|
}
|
|
|
|
while (vals[narg] != NULL)
|
|
narg++;
|
|
|
|
/* narg plus trailing NULL. */
|
|
if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, narg + 1) < 0) {
|
|
cmd->has_error = ENOMEM;
|
|
return;
|
|
}
|
|
|
|
narg = 0;
|
|
while (vals[narg] != NULL) {
|
|
char *arg;
|
|
|
|
arg = g_strdup(vals[narg++]);
|
|
cmd->args[cmd->nargs++] = arg;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* virCommandAddArgList:
|
|
* @cmd: the command to modify
|
|
* @...: list of arguments to add
|
|
*
|
|
* Add a NULL terminated list of args.
|
|
*/
|
|
void
|
|
virCommandAddArgList(virCommandPtr cmd, ...)
|
|
{
|
|
va_list list;
|
|
int narg = 0;
|
|
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
va_start(list, cmd);
|
|
while (va_arg(list, const char *) != NULL)
|
|
narg++;
|
|
va_end(list);
|
|
|
|
/* narg plus trailing NULL. */
|
|
if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, narg + 1) < 0) {
|
|
cmd->has_error = ENOMEM;
|
|
return;
|
|
}
|
|
|
|
va_start(list, cmd);
|
|
while (1) {
|
|
char *arg = va_arg(list, char *);
|
|
if (!arg)
|
|
break;
|
|
arg = g_strdup(arg);
|
|
cmd->args[cmd->nargs++] = arg;
|
|
}
|
|
va_end(list);
|
|
}
|
|
|
|
/**
|
|
* virCommandSetWorkingDirectory:
|
|
* @cmd: the command to modify
|
|
* @pwd: directory to use
|
|
*
|
|
* Set the working directory of a non-daemon child process, rather
|
|
* than the parent's working directory. Daemons automatically get /
|
|
* without using this call.
|
|
*/
|
|
void
|
|
virCommandSetWorkingDirectory(virCommandPtr cmd, const char *pwd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (cmd->pwd) {
|
|
cmd->has_error = -1;
|
|
VIR_DEBUG("cannot set directory twice");
|
|
} else {
|
|
cmd->pwd = g_strdup(pwd);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virCommandGetNumSendBuffers(virCommandPtr cmd)
|
|
{
|
|
return cmd->numSendBuffers;
|
|
}
|
|
|
|
|
|
static void
|
|
virCommandFreeSendBuffers(virCommandPtr cmd)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < virCommandGetNumSendBuffers(cmd); i++) {
|
|
VIR_FORCE_CLOSE(cmd->sendBuffers[i].fd);
|
|
VIR_FREE(cmd->sendBuffers[i].buffer);
|
|
}
|
|
VIR_FREE(cmd->sendBuffers);
|
|
}
|
|
|
|
|
|
#ifndef WIN32
|
|
/**
|
|
* virCommandSetSendBuffer
|
|
* @cmd: the command to modify
|
|
*
|
|
* Pass a buffer to virCommand that will be written into the
|
|
* given file descriptor. The buffer will be freed automatically
|
|
* and the file descriptor closed.
|
|
*/
|
|
int
|
|
virCommandSetSendBuffer(virCommandPtr cmd,
|
|
int fd,
|
|
unsigned char *buffer, size_t buflen)
|
|
{
|
|
size_t i;
|
|
|
|
if (!cmd || cmd->has_error)
|
|
return -1;
|
|
|
|
if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("fcntl failed to set O_NONBLOCK"));
|
|
cmd->has_error = errno;
|
|
return -1;
|
|
}
|
|
|
|
i = virCommandGetNumSendBuffers(cmd);
|
|
if (VIR_REALLOC_N(cmd->sendBuffers, i + 1) < 0) {
|
|
cmd->has_error = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
cmd->sendBuffers[i].fd = fd;
|
|
cmd->sendBuffers[i].buffer = buffer;
|
|
cmd->sendBuffers[i].buflen = buflen;
|
|
cmd->sendBuffers[i].offset = 0;
|
|
|
|
cmd->numSendBuffers++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCommandSendBuffersFillPollfd(virCommandPtr cmd,
|
|
struct pollfd *fds,
|
|
int startidx)
|
|
{
|
|
size_t i, j;
|
|
|
|
for (i = 0, j = 0; i < virCommandGetNumSendBuffers(cmd); i++) {
|
|
if (cmd->sendBuffers[i].fd >= 0) {
|
|
fds[startidx + j].fd = cmd->sendBuffers[i].fd;
|
|
fds[startidx + j].events = POLLOUT;
|
|
fds[startidx + j].revents = 0;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return j;
|
|
}
|
|
|
|
|
|
static int
|
|
virCommandSendBuffersHandlePoll(virCommandPtr cmd,
|
|
struct pollfd *fds)
|
|
{
|
|
size_t i;
|
|
ssize_t done;
|
|
|
|
for (i = 0; i < virCommandGetNumSendBuffers(cmd); i++) {
|
|
if (fds->fd == cmd->sendBuffers[i].fd)
|
|
break;
|
|
}
|
|
if (i == virCommandGetNumSendBuffers(cmd))
|
|
return 0;
|
|
|
|
done = write(fds->fd,
|
|
cmd->sendBuffers[i].buffer + cmd->sendBuffers[i].offset,
|
|
cmd->sendBuffers[i].buflen - cmd->sendBuffers[i].offset);
|
|
if (done < 0) {
|
|
if (errno == EPIPE) {
|
|
VIR_DEBUG("child closed PIPE early, ignoring EPIPE "
|
|
"on fd %d", cmd->sendBuffers[i].fd);
|
|
VIR_FORCE_CLOSE(cmd->sendBuffers[i].fd);
|
|
} else if (errno != EINTR && errno != EAGAIN) {
|
|
virReportSystemError(errno, "%s",
|
|
_("unable to write to child input"));
|
|
return -1;
|
|
}
|
|
} else {
|
|
cmd->sendBuffers[i].offset += done;
|
|
if (cmd->sendBuffers[i].offset == cmd->sendBuffers[i].buflen)
|
|
VIR_FORCE_CLOSE(cmd->sendBuffers[i].fd);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif /* !WIN32 */
|
|
|
|
|
|
/**
|
|
* virCommandSetInputBuffer:
|
|
* @cmd: the command to modify
|
|
* @inbuf: string to feed to stdin
|
|
*
|
|
* Feed the child's stdin from a string buffer. This requires the
|
|
* use of virCommandRun() or combination of virCommandDoAsyncIO and
|
|
* virCommandRunAsync. The buffer is forgotten after each @cmd run.
|
|
*/
|
|
void
|
|
virCommandSetInputBuffer(virCommandPtr cmd, const char *inbuf)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (cmd->infd != -1 || cmd->inbuf) {
|
|
cmd->has_error = -1;
|
|
VIR_DEBUG("cannot specify input twice");
|
|
return;
|
|
}
|
|
|
|
cmd->inbuf = g_strdup(inbuf);
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandSetOutputBuffer:
|
|
* @cmd: the command to modify
|
|
* @outbuf: address of variable to store malloced result buffer
|
|
*
|
|
* Capture the child's stdout to a string buffer. *outbuf is
|
|
* guaranteed to be allocated after successful virCommandRun or
|
|
* virCommandWait, and is best-effort allocated after failed
|
|
* virCommandRun or virCommandRunAsync; caller is responsible for
|
|
* freeing *outbuf. This requires the use of virCommandRun() or
|
|
* combination of virCommandDoAsyncIO and virCommandRunAsync. The
|
|
* buffer is forgotten after each @cmd run.
|
|
*/
|
|
void
|
|
virCommandSetOutputBuffer(virCommandPtr cmd, char **outbuf)
|
|
{
|
|
*outbuf = NULL;
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (cmd->outfdptr) {
|
|
cmd->has_error = -1;
|
|
VIR_DEBUG("cannot specify output twice");
|
|
return;
|
|
}
|
|
|
|
cmd->outbuf = outbuf;
|
|
cmd->outfdptr = &cmd->outfd;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandSetErrorBuffer:
|
|
* @cmd: the command to modify
|
|
* @errbuf: address of variable to store malloced result buffer
|
|
*
|
|
* Capture the child's stderr to a string buffer. *errbuf is
|
|
* guaranteed to be allocated after successful virCommandRun or
|
|
* virCommandWait, and is best-effort allocated after failed
|
|
* virCommandRun or virCommandRunAsync; caller is responsible for
|
|
* freeing *errbuf. It is possible to pass the same pointer as
|
|
* for virCommandSetOutputBuffer(), in which case the child
|
|
* process will interleave all output into a single string. This
|
|
* requires the use of virCommandRun() or combination of
|
|
* virCommandDoAsyncIO and virCommandRunAsync.The buffer is
|
|
* forgotten after each @cmd run.
|
|
*/
|
|
void
|
|
virCommandSetErrorBuffer(virCommandPtr cmd, char **errbuf)
|
|
{
|
|
*errbuf = NULL;
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (cmd->errfdptr) {
|
|
cmd->has_error = -1;
|
|
VIR_DEBUG("cannot specify stderr twice");
|
|
return;
|
|
}
|
|
|
|
cmd->errbuf = errbuf;
|
|
cmd->errfdptr = &cmd->errfd;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandSetInputFD:
|
|
* @cmd: the command to modify
|
|
* @infd: the descriptor to use
|
|
*
|
|
* Attach a file descriptor to the child's stdin
|
|
*/
|
|
void
|
|
virCommandSetInputFD(virCommandPtr cmd, int infd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (cmd->infd != -1 || cmd->inbuf) {
|
|
cmd->has_error = -1;
|
|
VIR_DEBUG("cannot specify input twice");
|
|
return;
|
|
}
|
|
if (infd < 0) {
|
|
cmd->has_error = -1;
|
|
VIR_DEBUG("cannot specify invalid input fd");
|
|
return;
|
|
}
|
|
|
|
cmd->infd = infd;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandSetOutputFD:
|
|
* @cmd: the command to modify
|
|
* @outfd: location of output fd
|
|
*
|
|
* Attach a file descriptor to the child's stdout. If *@outfd is -1 on
|
|
* entry, then a pipe will be created and returned in this variable when
|
|
* the child is run. Otherwise, *@outfd is used as the output.
|
|
*/
|
|
void
|
|
virCommandSetOutputFD(virCommandPtr cmd, int *outfd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (cmd->outfdptr) {
|
|
cmd->has_error = -1;
|
|
VIR_DEBUG("cannot specify output twice");
|
|
return;
|
|
}
|
|
|
|
cmd->outfdptr = outfd;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandSetErrorFD:
|
|
* @cmd: the command to modify
|
|
* @errfd: location of error fd
|
|
*
|
|
* Attach a file descriptor to the child's stderr. If *@errfd is -1 on
|
|
* entry, then a pipe will be created and returned in this variable when
|
|
* the child is run. Otherwise, *@errfd is used for error collection,
|
|
* and may be the same as outfd given to virCommandSetOutputFD().
|
|
*/
|
|
void
|
|
virCommandSetErrorFD(virCommandPtr cmd, int *errfd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (cmd->errfdptr) {
|
|
cmd->has_error = -1;
|
|
VIR_DEBUG("cannot specify stderr twice");
|
|
return;
|
|
}
|
|
|
|
cmd->errfdptr = errfd;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandSetPreExecHook:
|
|
* @cmd: the command to modify
|
|
* @hook: the hook to run
|
|
* @opaque: argument to pass to the hook
|
|
*
|
|
* Run HOOK(OPAQUE) in the child as the last thing before changing
|
|
* directories, dropping capabilities, and executing the new process.
|
|
* Force the child to fail if HOOK does not return zero.
|
|
*
|
|
* Since @hook runs in the child, it should be careful to avoid
|
|
* any functions that are not async-signal-safe.
|
|
*/
|
|
void
|
|
virCommandSetPreExecHook(virCommandPtr cmd, virExecHook hook, void *opaque)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (cmd->hook) {
|
|
cmd->has_error = -1;
|
|
VIR_DEBUG("cannot specify hook twice");
|
|
return;
|
|
}
|
|
cmd->hook = hook;
|
|
cmd->opaque = opaque;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandWriteArgLog:
|
|
* @cmd: the command to log
|
|
* @logfd: where to log the results
|
|
*
|
|
* Call after adding all arguments and environment settings, but before
|
|
* Run/RunAsync, to immediately output the environment and arguments of
|
|
* cmd to logfd. If virCommandRun cannot succeed (because of an
|
|
* out-of-memory condition while building cmd), nothing will be logged.
|
|
*/
|
|
void
|
|
virCommandWriteArgLog(virCommandPtr cmd, int logfd)
|
|
{
|
|
int ioError = 0;
|
|
size_t i;
|
|
|
|
/* Any errors will be reported later by virCommandRun, which means
|
|
* no command will be run, so there is nothing to log. */
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
for (i = 0; i < cmd->nenv; i++) {
|
|
if (safewrite(logfd, cmd->env[i], strlen(cmd->env[i])) < 0)
|
|
ioError = errno;
|
|
if (safewrite(logfd, " ", 1) < 0)
|
|
ioError = errno;
|
|
}
|
|
for (i = 0; i < cmd->nargs; i++) {
|
|
if (safewrite(logfd, cmd->args[i], strlen(cmd->args[i])) < 0)
|
|
ioError = errno;
|
|
if (safewrite(logfd, i == cmd->nargs - 1 ? "\n" : " ", 1) < 0)
|
|
ioError = errno;
|
|
}
|
|
|
|
if (ioError) {
|
|
VIR_WARN("Unable to write command %s args to logfile: %s",
|
|
cmd->args[0], g_strerror(ioError));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandToString:
|
|
* @cmd: the command to convert
|
|
* @linebreaks: true to break line after each env var or option
|
|
*
|
|
* Call after adding all arguments and environment settings, but
|
|
* before Run/RunAsync, to return a string representation of the
|
|
* environment and arguments of cmd, suitably quoted for pasting into
|
|
* a shell. If virCommandRun cannot succeed (because of an
|
|
* out-of-memory condition while building cmd), NULL will be returned.
|
|
* Caller is responsible for freeing the resulting string.
|
|
*/
|
|
char *
|
|
virCommandToString(virCommandPtr cmd, bool linebreaks)
|
|
{
|
|
size_t i;
|
|
virBuffer buf = VIR_BUFFER_INITIALIZER;
|
|
bool prevopt = false;
|
|
|
|
/* Cannot assume virCommandRun will be called; so report the error
|
|
* now. If virCommandRun is called, it will report the same error. */
|
|
if (!cmd ||cmd->has_error == ENOMEM) {
|
|
virReportOOMError();
|
|
return NULL;
|
|
}
|
|
if (cmd->has_error) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid use of command API"));
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < cmd->nenv; i++) {
|
|
/* In shell, a='b c' has a different meaning than 'a=b c', so
|
|
* we must determine where the '=' lives. */
|
|
char *eq = strchr(cmd->env[i], '=');
|
|
|
|
if (!eq) {
|
|
virBufferFreeAndReset(&buf);
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid use of command API"));
|
|
return NULL;
|
|
}
|
|
eq++;
|
|
virBufferAdd(&buf, cmd->env[i], eq - cmd->env[i]);
|
|
virBufferEscapeShell(&buf, eq);
|
|
virBufferAddChar(&buf, ' ');
|
|
if (linebreaks)
|
|
virBufferAddLit(&buf, "\\\n");
|
|
}
|
|
virBufferEscapeShell(&buf, cmd->args[0]);
|
|
for (i = 1; i < cmd->nargs; i++) {
|
|
virBufferAddChar(&buf, ' ');
|
|
if (linebreaks) {
|
|
/* Line break if this is a --arg or if
|
|
* the previous arg was a positional option
|
|
*/
|
|
if (cmd->args[i][0] == '-' ||
|
|
!prevopt)
|
|
virBufferAddLit(&buf, "\\\n");
|
|
}
|
|
virBufferEscapeShell(&buf, cmd->args[i]);
|
|
prevopt = (cmd->args[i][0] == '-');
|
|
}
|
|
|
|
return virBufferContentAndReset(&buf);
|
|
}
|
|
|
|
|
|
#ifndef WIN32
|
|
/*
|
|
* Manage input and output to the child process.
|
|
*/
|
|
static int
|
|
virCommandProcessIO(virCommandPtr cmd)
|
|
{
|
|
int outfd = -1, errfd = -1;
|
|
size_t inlen = 0, outlen = 0, errlen = 0;
|
|
size_t inoff = 0;
|
|
int ret = 0;
|
|
g_autofree struct pollfd *fds = NULL;
|
|
|
|
if (dryRunBuffer || dryRunCallback) {
|
|
VIR_DEBUG("Dry run requested, skipping I/O processing");
|
|
return 0;
|
|
}
|
|
|
|
/* With an input buffer, feed data to child
|
|
* via pipe */
|
|
if (cmd->inbuf)
|
|
inlen = strlen(cmd->inbuf);
|
|
|
|
/* With out/err buffer, the outfd/errfd have been filled with an
|
|
* FD for us. Guarantee an allocated string with partial results
|
|
* even if we encounter a later failure, as well as freeing any
|
|
* results accumulated over a prior run of the same command. */
|
|
if (cmd->outbuf) {
|
|
outfd = cmd->outfd;
|
|
VIR_FREE(*cmd->outbuf);
|
|
if (VIR_ALLOC_N(*cmd->outbuf, 1) < 0)
|
|
ret = -1;
|
|
}
|
|
if (cmd->errbuf) {
|
|
errfd = cmd->errfd;
|
|
VIR_FREE(*cmd->errbuf);
|
|
if (VIR_ALLOC_N(*cmd->errbuf, 1) < 0)
|
|
ret = -1;
|
|
}
|
|
if (ret == -1)
|
|
goto cleanup;
|
|
ret = -1;
|
|
|
|
if (VIR_ALLOC_N(fds, 3 + virCommandGetNumSendBuffers(cmd)) < 0)
|
|
goto cleanup;
|
|
|
|
for (;;) {
|
|
size_t i;
|
|
int nfds = 0;
|
|
|
|
if (cmd->inpipe != -1) {
|
|
fds[nfds].fd = cmd->inpipe;
|
|
fds[nfds].events = POLLOUT;
|
|
fds[nfds].revents = 0;
|
|
nfds++;
|
|
}
|
|
if (outfd != -1) {
|
|
fds[nfds].fd = outfd;
|
|
fds[nfds].events = POLLIN;
|
|
fds[nfds].revents = 0;
|
|
nfds++;
|
|
}
|
|
if (errfd != -1) {
|
|
fds[nfds].fd = errfd;
|
|
fds[nfds].events = POLLIN;
|
|
fds[nfds].revents = 0;
|
|
nfds++;
|
|
}
|
|
|
|
nfds += virCommandSendBuffersFillPollfd(cmd, fds, nfds);
|
|
|
|
if (nfds == 0)
|
|
break;
|
|
|
|
if (poll(fds, nfds, -1) < 0) {
|
|
if (errno == EAGAIN || errno == EINTR)
|
|
continue;
|
|
virReportSystemError(errno, "%s",
|
|
_("unable to poll on child"));
|
|
goto cleanup;
|
|
}
|
|
|
|
for (i = 0; i < nfds; i++) {
|
|
if (fds[i].revents & (POLLIN | POLLHUP | POLLERR) &&
|
|
(fds[i].fd == errfd || fds[i].fd == outfd)) {
|
|
char data[1024];
|
|
char **buf;
|
|
size_t *len;
|
|
int done;
|
|
if (fds[i].fd == outfd) {
|
|
buf = cmd->outbuf;
|
|
len = &outlen;
|
|
} else {
|
|
buf = cmd->errbuf;
|
|
len = &errlen;
|
|
}
|
|
/* Silence a false positive from clang. */
|
|
sa_assert(buf);
|
|
|
|
done = read(fds[i].fd, data, sizeof(data));
|
|
if (done < 0) {
|
|
if (errno != EINTR &&
|
|
errno != EAGAIN) {
|
|
virReportSystemError(errno, "%s",
|
|
(fds[i].fd == outfd) ?
|
|
_("unable to read child stdout") :
|
|
_("unable to read child stderr"));
|
|
goto cleanup;
|
|
}
|
|
} else if (done == 0) {
|
|
if (fds[i].fd == outfd)
|
|
outfd = -1;
|
|
else
|
|
errfd = -1;
|
|
} else {
|
|
if (VIR_REALLOC_N(*buf, *len + done + 1) < 0)
|
|
goto cleanup;
|
|
memcpy(*buf + *len, data, done);
|
|
*len += done;
|
|
}
|
|
}
|
|
|
|
if (fds[i].revents & (POLLOUT | POLLHUP | POLLERR) &&
|
|
fds[i].fd == cmd->inpipe) {
|
|
int done;
|
|
|
|
done = write(cmd->inpipe, cmd->inbuf + inoff,
|
|
inlen - inoff);
|
|
if (done < 0) {
|
|
if (errno == EPIPE) {
|
|
VIR_DEBUG("child closed stdin early, ignoring EPIPE "
|
|
"on fd %d", cmd->inpipe);
|
|
VIR_FORCE_CLOSE(cmd->inpipe);
|
|
} else if (errno != EINTR && errno != EAGAIN) {
|
|
virReportSystemError(errno, "%s",
|
|
_("unable to write to child input"));
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
inoff += done;
|
|
if (inoff == inlen)
|
|
VIR_FORCE_CLOSE(cmd->inpipe);
|
|
}
|
|
} else if (fds[i].revents & (POLLOUT | POLLHUP | POLLERR)) {
|
|
if (virCommandSendBuffersHandlePoll(cmd, &fds[i]) < 0)
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (cmd->outbuf && *cmd->outbuf)
|
|
(*cmd->outbuf)[outlen] = '\0';
|
|
if (cmd->errbuf && *cmd->errbuf)
|
|
(*cmd->errbuf)[errlen] = '\0';
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* virCommandExec:
|
|
* @cmd: command to run
|
|
* @groups: array of supplementary group IDs used for the command
|
|
* @ngroups: number of group IDs in @groups
|
|
*
|
|
* Exec the command, replacing the current process. Meant to be called
|
|
* in the hook after already forking / cloning, so does not attempt to
|
|
* daemonize or preserve any FDs.
|
|
*
|
|
* Returns -1 on any error executing the command.
|
|
* Will not return on success.
|
|
*/
|
|
int virCommandExec(virCommandPtr cmd, gid_t *groups, int ngroups)
|
|
{
|
|
if (!cmd ||cmd->has_error == ENOMEM) {
|
|
virReportOOMError();
|
|
return -1;
|
|
}
|
|
if (cmd->has_error) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid use of command API"));
|
|
return -1;
|
|
}
|
|
|
|
if (virExecCommon(cmd, groups, ngroups) < 0)
|
|
return -1;
|
|
|
|
execve(cmd->args[0], cmd->args, cmd->env);
|
|
|
|
virReportSystemError(errno,
|
|
_("cannot execute binary %s"),
|
|
cmd->args[0]);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandRun:
|
|
* @cmd: command to run
|
|
* @exitstatus: optional status collection
|
|
*
|
|
* Run the command and wait for completion.
|
|
* Returns -1 on any error executing the
|
|
* command. Returns 0 if the command executed,
|
|
* with the exit status set. If @exitstatus is NULL, then the
|
|
* child must exit with status 0 for this to succeed. By default,
|
|
* a non-NULL @exitstatus contains the normal exit status of the child
|
|
* (death from a signal is treated as execution error); but if
|
|
* virCommandRawStatus() was used, it instead contains the raw exit
|
|
* status that the caller must then decipher using WIFEXITED() and friends.
|
|
*/
|
|
int
|
|
virCommandRun(virCommandPtr cmd, int *exitstatus)
|
|
{
|
|
int ret = 0;
|
|
char *outbuf = NULL;
|
|
char *errbuf = NULL;
|
|
struct stat st;
|
|
bool string_io;
|
|
bool async_io = false;
|
|
char *str;
|
|
int tmpfd;
|
|
|
|
if (!cmd ||cmd->has_error == ENOMEM) {
|
|
virReportOOMError();
|
|
return -1;
|
|
}
|
|
if (cmd->has_error) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid use of command API"));
|
|
return -1;
|
|
}
|
|
|
|
/* Avoid deadlock, by requiring that any open fd not under our
|
|
* control must be visiting a regular file, or that we are
|
|
* daemonized and no string io is required. */
|
|
string_io = cmd->inbuf || cmd->outbuf || cmd->errbuf;
|
|
if (cmd->infd != -1 &&
|
|
(fstat(cmd->infd, &st) < 0 || !S_ISREG(st.st_mode)))
|
|
async_io = true;
|
|
if (cmd->outfdptr && cmd->outfdptr != &cmd->outfd &&
|
|
(*cmd->outfdptr == -1 ||
|
|
fstat(*cmd->outfdptr, &st) < 0 || !S_ISREG(st.st_mode)))
|
|
async_io = true;
|
|
if (cmd->errfdptr && cmd->errfdptr != &cmd->errfd &&
|
|
(*cmd->errfdptr == -1 ||
|
|
fstat(*cmd->errfdptr, &st) < 0 || !S_ISREG(st.st_mode)))
|
|
async_io = true;
|
|
if (async_io) {
|
|
if (!(cmd->flags & VIR_EXEC_DAEMON) || string_io) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot mix caller fds with blocking execution"));
|
|
return -1;
|
|
}
|
|
} else {
|
|
if ((cmd->flags & VIR_EXEC_DAEMON) && string_io) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot mix string I/O with daemon"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* If caller requested the same string for stdout and stderr, then
|
|
* merge those into one string. */
|
|
if (cmd->outbuf && cmd->outbuf == cmd->errbuf) {
|
|
cmd->errfdptr = &cmd->outfd;
|
|
cmd->errbuf = NULL;
|
|
}
|
|
|
|
/* If caller hasn't requested capture of stdout/err, then capture
|
|
* it ourselves so we can log it. But the intermediate child for
|
|
* a daemon has no expected output, and we don't want our
|
|
* capturing pipes passed on to the daemon grandchild.
|
|
*/
|
|
if (!(cmd->flags & VIR_EXEC_DAEMON)) {
|
|
if (!cmd->outfdptr) {
|
|
cmd->outfdptr = &cmd->outfd;
|
|
cmd->outbuf = &outbuf;
|
|
string_io = true;
|
|
}
|
|
if (!cmd->errfdptr) {
|
|
cmd->errfdptr = &cmd->errfd;
|
|
cmd->errbuf = &errbuf;
|
|
string_io = true;
|
|
}
|
|
}
|
|
|
|
cmd->flags |= VIR_EXEC_RUN_SYNC;
|
|
if (virCommandRunAsync(cmd, NULL) < 0) {
|
|
cmd->has_error = -1;
|
|
return -1;
|
|
}
|
|
|
|
if (string_io) {
|
|
VIR_FORCE_CLOSE(cmd->infd);
|
|
ret = virCommandProcessIO(cmd);
|
|
}
|
|
|
|
if (virCommandWait(cmd, exitstatus) < 0)
|
|
ret = -1;
|
|
|
|
str = (exitstatus ? virProcessTranslateStatus(*exitstatus)
|
|
: (char *) "status 0");
|
|
VIR_DEBUG("Result %s, stdout: '%s' stderr: '%s'",
|
|
NULLSTR(str),
|
|
cmd->outbuf ? NULLSTR(*cmd->outbuf) : "(null)",
|
|
cmd->errbuf ? NULLSTR(*cmd->errbuf) : "(null)");
|
|
if (exitstatus)
|
|
VIR_FREE(str);
|
|
|
|
/* Reset any capturing, in case caller runs
|
|
* this identical command again */
|
|
VIR_FORCE_CLOSE(cmd->inpipe);
|
|
if (cmd->outbuf == &outbuf) {
|
|
tmpfd = cmd->outfd;
|
|
if (VIR_CLOSE(cmd->outfd) < 0)
|
|
VIR_DEBUG("ignoring failed close on fd %d", tmpfd);
|
|
cmd->outfdptr = NULL;
|
|
cmd->outbuf = NULL;
|
|
VIR_FREE(outbuf);
|
|
}
|
|
if (cmd->errbuf == &errbuf) {
|
|
tmpfd = cmd->errfd;
|
|
if (VIR_CLOSE(cmd->errfd) < 0)
|
|
VIR_DEBUG("ignoring failed close on fd %d", tmpfd);
|
|
cmd->errfdptr = NULL;
|
|
cmd->errbuf = NULL;
|
|
VIR_FREE(errbuf);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
virCommandDoAsyncIOHelper(void *opaque)
|
|
{
|
|
virCommandPtr cmd = opaque;
|
|
if (virCommandProcessIO(cmd) < 0) {
|
|
/* If something went wrong, save errno or -1 */
|
|
cmd->has_error = errno ? errno : -1;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandRunAsync:
|
|
* @cmd: command to start
|
|
* @pid: optional variable to track child pid
|
|
*
|
|
* Run the command asynchronously
|
|
* Returns -1 on any error executing the
|
|
* command. Returns 0 if the command executed.
|
|
*
|
|
* There are two approaches to child process cleanup.
|
|
* 1. Use auto-cleanup, by passing NULL for pid. The child will be
|
|
* auto-reaped by virCommandFree, unless you reap it earlier via
|
|
* virCommandWait or virCommandAbort. Good for where cmd is in
|
|
* scope for the duration of the child process.
|
|
* 2. Use manual cleanup, by passing the address of a pid_t variable
|
|
* for pid. While cmd is still in scope, you may reap the child via
|
|
* virCommandWait or virCommandAbort. But after virCommandFree, if
|
|
* you have not yet reaped the child, then it continues to run until
|
|
* you call virProcessWait or virProcessAbort.
|
|
*/
|
|
int
|
|
virCommandRunAsync(virCommandPtr cmd, pid_t *pid)
|
|
{
|
|
int ret = -1;
|
|
g_autofree char *str = NULL;
|
|
size_t i;
|
|
bool synchronous = false;
|
|
int infd[2] = {-1, -1};
|
|
|
|
if (!cmd || cmd->has_error == ENOMEM) {
|
|
virReportOOMError();
|
|
return -1;
|
|
}
|
|
if (cmd->has_error) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid use of command API"));
|
|
return -1;
|
|
}
|
|
|
|
synchronous = cmd->flags & VIR_EXEC_RUN_SYNC;
|
|
cmd->flags &= ~VIR_EXEC_RUN_SYNC;
|
|
|
|
/* Buffer management can only be requested via virCommandRun or
|
|
* virCommandDoAsyncIO. */
|
|
if (cmd->inbuf && cmd->infd == -1 &&
|
|
(synchronous || cmd->flags & VIR_EXEC_ASYNC_IO)) {
|
|
if (virPipe(infd) < 0) {
|
|
cmd->has_error = -1;
|
|
return -1;
|
|
}
|
|
cmd->infd = infd[0];
|
|
cmd->inpipe = infd[1];
|
|
|
|
if (fcntl(cmd->inpipe, F_SETFL, O_NONBLOCK) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("fcntl failed to set O_NONBLOCK"));
|
|
cmd->has_error = -1;
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
} else if ((cmd->inbuf && cmd->infd == -1) ||
|
|
(cmd->outbuf && cmd->outfdptr != &cmd->outfd) ||
|
|
(cmd->errbuf && cmd->errfdptr != &cmd->errfd)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot mix string I/O with asynchronous command"));
|
|
return -1;
|
|
}
|
|
|
|
if (cmd->pid != -1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("command is already running as pid %lld"),
|
|
(long long) cmd->pid);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!synchronous && (cmd->flags & VIR_EXEC_DAEMON)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("daemonized command cannot use virCommandRunAsync"));
|
|
goto cleanup;
|
|
}
|
|
if (cmd->pwd && (cmd->flags & VIR_EXEC_DAEMON)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("daemonized command cannot set working directory %s"),
|
|
cmd->pwd);
|
|
goto cleanup;
|
|
}
|
|
if (cmd->pidfile && !(cmd->flags & VIR_EXEC_DAEMON)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("creation of pid file requires daemonized command"));
|
|
goto cleanup;
|
|
}
|
|
|
|
str = virCommandToString(cmd, false);
|
|
if (dryRunBuffer || dryRunCallback) {
|
|
dryRunStatus = 0;
|
|
if (!str) {
|
|
/* error already reported by virCommandToString */
|
|
goto cleanup;
|
|
}
|
|
|
|
if (dryRunBuffer) {
|
|
VIR_DEBUG("Dry run requested, appending stringified "
|
|
"command to dryRunBuffer=%p", dryRunBuffer);
|
|
virBufferAdd(dryRunBuffer, str, -1);
|
|
virBufferAddChar(dryRunBuffer, '\n');
|
|
}
|
|
if (dryRunCallback) {
|
|
dryRunCallback((const char *const*)cmd->args,
|
|
(const char *const*)cmd->env,
|
|
cmd->inbuf, cmd->outbuf, cmd->errbuf,
|
|
&dryRunStatus, dryRunOpaque);
|
|
}
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
VIR_DEBUG("About to run %s", str ? str : cmd->args[0]);
|
|
ret = virExec(cmd);
|
|
VIR_DEBUG("Command result %d, with PID %d",
|
|
ret, (int)cmd->pid);
|
|
|
|
for (i = 0; i < cmd->npassfd; i++) {
|
|
if (cmd->passfd[i].flags & VIR_COMMAND_PASS_FD_CLOSE_PARENT)
|
|
VIR_FORCE_CLOSE(cmd->passfd[i].fd);
|
|
}
|
|
cmd->npassfd = 0;
|
|
VIR_FREE(cmd->passfd);
|
|
|
|
if (ret == 0 && pid)
|
|
*pid = cmd->pid;
|
|
else
|
|
cmd->reap = true;
|
|
|
|
if (ret == 0 && cmd->flags & VIR_EXEC_ASYNC_IO) {
|
|
if (cmd->inbuf)
|
|
VIR_FORCE_CLOSE(cmd->infd);
|
|
/* clear any error so we can catch if the helper thread reports one */
|
|
cmd->has_error = 0;
|
|
if (VIR_ALLOC(cmd->asyncioThread) < 0 ||
|
|
virThreadCreateFull(cmd->asyncioThread, true,
|
|
virCommandDoAsyncIOHelper,
|
|
"cmd-async-io", false, cmd) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to create thread "
|
|
"to process command's IO"));
|
|
VIR_FREE(cmd->asyncioThread);
|
|
virCommandAbort(cmd);
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
if (ret < 0) {
|
|
VIR_FORCE_CLOSE(cmd->infd);
|
|
VIR_FORCE_CLOSE(cmd->inpipe);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandWait:
|
|
* @cmd: command to wait on
|
|
* @exitstatus: optional status collection
|
|
*
|
|
* Wait for the command previously started with virCommandRunAsync()
|
|
* to complete. Return -1 on any error waiting for
|
|
* completion. Returns 0 if the command
|
|
* finished with the exit status set. If @exitstatus is NULL, then the
|
|
* child must exit with status 0 for this to succeed. By default,
|
|
* a non-NULL @exitstatus contains the normal exit status of the child
|
|
* (death from a signal is treated as execution error); but if
|
|
* virCommandRawStatus() was used, it instead contains the raw exit
|
|
* status that the caller must then decipher using WIFEXITED() and friends.
|
|
*/
|
|
int
|
|
virCommandWait(virCommandPtr cmd, int *exitstatus)
|
|
{
|
|
int ret;
|
|
int status = 0;
|
|
|
|
if (!cmd ||cmd->has_error == ENOMEM) {
|
|
virReportOOMError();
|
|
return -1;
|
|
}
|
|
if (cmd->has_error) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid use of command API"));
|
|
return -1;
|
|
}
|
|
|
|
if (dryRunBuffer || dryRunCallback) {
|
|
VIR_DEBUG("Dry run requested, returning status %d",
|
|
dryRunStatus);
|
|
if (exitstatus)
|
|
*exitstatus = dryRunStatus;
|
|
else if (dryRunStatus)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
if (cmd->pid == -1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("command is not yet running"));
|
|
return -1;
|
|
}
|
|
|
|
/* If virProcessWait reaps pid but then returns failure because
|
|
* exitstatus was NULL, then a second virCommandWait would risk
|
|
* calling waitpid on an unrelated process. Besides, that error
|
|
* message is not as detailed as what we can provide. So, we
|
|
* guarantee that virProcessWait only fails due to failure to wait,
|
|
* and repeat the exitstatus check code ourselves. */
|
|
ret = virProcessWait(cmd->pid, &status, true);
|
|
if (cmd->flags & VIR_EXEC_ASYNC_IO) {
|
|
cmd->flags &= ~VIR_EXEC_ASYNC_IO;
|
|
virThreadJoin(cmd->asyncioThread);
|
|
VIR_FREE(cmd->asyncioThread);
|
|
VIR_FORCE_CLOSE(cmd->inpipe);
|
|
if (cmd->has_error) {
|
|
const char *msg = _("Error while processing command's IO");
|
|
if (cmd->has_error < 0)
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", msg);
|
|
else
|
|
virReportSystemError(cmd->has_error, "%s", msg);
|
|
ret = -1;
|
|
}
|
|
}
|
|
if (ret == 0) {
|
|
cmd->pid = -1;
|
|
cmd->reap = false;
|
|
if (exitstatus && (cmd->rawStatus || WIFEXITED(status))) {
|
|
*exitstatus = cmd->rawStatus ? status : WEXITSTATUS(status);
|
|
} else if (status) {
|
|
g_autofree char *str = virCommandToString(cmd, false);
|
|
g_autofree char *st = virProcessTranslateStatus(status);
|
|
bool haveErrMsg = cmd->errbuf && *cmd->errbuf && (*cmd->errbuf)[0];
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Child process (%s) unexpected %s%s%s"),
|
|
str ? str : cmd->args[0], NULLSTR(st),
|
|
haveErrMsg ? ": " : "",
|
|
haveErrMsg ? *cmd->errbuf : "");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandAbort:
|
|
* @cmd: command to abort
|
|
*
|
|
* Abort an async command if it is running, without issuing
|
|
* any errors or affecting errno. Designed for error paths
|
|
* where some but not all paths to the cleanup code might
|
|
* have started the child process.
|
|
*/
|
|
void
|
|
virCommandAbort(virCommandPtr cmd)
|
|
{
|
|
if (!cmd || cmd->pid == -1)
|
|
return;
|
|
virProcessAbort(cmd->pid);
|
|
cmd->pid = -1;
|
|
cmd->reap = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCommandRequireHandshake:
|
|
* @cmd: command to modify
|
|
*
|
|
* Request that the child perform a handshake with
|
|
* the parent when the hook function has completed
|
|
* execution. The child will not exec() until the
|
|
* parent has notified
|
|
*/
|
|
void virCommandRequireHandshake(virCommandPtr cmd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
if (cmd->handshake) {
|
|
cmd->has_error = -1;
|
|
VIR_DEBUG("Cannot require handshake twice");
|
|
return;
|
|
}
|
|
|
|
if (virPipeQuiet(cmd->handshakeWait) < 0) {
|
|
cmd->has_error = errno;
|
|
return;
|
|
}
|
|
if (virPipeQuiet(cmd->handshakeNotify) < 0) {
|
|
VIR_FORCE_CLOSE(cmd->handshakeWait[0]);
|
|
VIR_FORCE_CLOSE(cmd->handshakeWait[1]);
|
|
cmd->has_error = errno;
|
|
return;
|
|
}
|
|
|
|
VIR_DEBUG("Transfer handshake wait=%d notify=%d, "
|
|
"keep handshake wait=%d notify=%d",
|
|
cmd->handshakeWait[1], cmd->handshakeNotify[0],
|
|
cmd->handshakeWait[0], cmd->handshakeNotify[1]);
|
|
virCommandPassFD(cmd, cmd->handshakeWait[1],
|
|
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
|
|
virCommandPassFD(cmd, cmd->handshakeNotify[0],
|
|
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
|
|
cmd->handshake = true;
|
|
}
|
|
|
|
/**
|
|
* virCommandHandshakeWait:
|
|
* @cmd: command to wait on
|
|
*
|
|
* Wait for the child to complete execution of its
|
|
* hook function. To be called in the parent.
|
|
*/
|
|
int virCommandHandshakeWait(virCommandPtr cmd)
|
|
{
|
|
char c;
|
|
int rv;
|
|
if (!cmd ||cmd->has_error == ENOMEM) {
|
|
virReportOOMError();
|
|
return -1;
|
|
}
|
|
if (cmd->has_error || !cmd->handshake) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid use of command API"));
|
|
return -1;
|
|
}
|
|
|
|
if (cmd->handshakeWait[0] == -1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Handshake is already complete"));
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("Wait for handshake on %d", cmd->handshakeWait[0]);
|
|
if ((rv = saferead(cmd->handshakeWait[0], &c, sizeof(c))) != sizeof(c)) {
|
|
if (rv < 0)
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to wait for child process"));
|
|
else
|
|
virReportSystemError(EIO, "%s",
|
|
_("Child quit during startup handshake"));
|
|
VIR_FORCE_CLOSE(cmd->handshakeWait[0]);
|
|
return -1;
|
|
}
|
|
if (c != '1') {
|
|
g_autofree char *msg = NULL;
|
|
ssize_t len;
|
|
if (VIR_ALLOC_N(msg, 1024) < 0) {
|
|
VIR_FORCE_CLOSE(cmd->handshakeWait[0]);
|
|
return -1;
|
|
}
|
|
/* Close the handshakeNotify fd before trying to read anything
|
|
* further on the handshakeWait pipe; so that a child waiting
|
|
* on our acknowledgment will die rather than deadlock. */
|
|
VIR_FORCE_CLOSE(cmd->handshakeNotify[1]);
|
|
|
|
if ((len = saferead(cmd->handshakeWait[0], msg, 1024)) < 0) {
|
|
VIR_FORCE_CLOSE(cmd->handshakeWait[0]);
|
|
virReportSystemError(errno, "%s",
|
|
_("No error message from child failure"));
|
|
return -1;
|
|
}
|
|
VIR_FORCE_CLOSE(cmd->handshakeWait[0]);
|
|
msg[len-1] = '\0';
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", msg);
|
|
return -1;
|
|
}
|
|
VIR_FORCE_CLOSE(cmd->handshakeWait[0]);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* virCommandHandshakeNotify:
|
|
* @cmd: command to resume
|
|
*
|
|
* Notify the child that it is OK to exec() the
|
|
* real binary now. To be called in the parent.
|
|
*/
|
|
int virCommandHandshakeNotify(virCommandPtr cmd)
|
|
{
|
|
char c = '1';
|
|
if (!cmd ||cmd->has_error == ENOMEM) {
|
|
virReportOOMError();
|
|
return -1;
|
|
}
|
|
if (cmd->has_error || !cmd->handshake) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid use of command API"));
|
|
return -1;
|
|
}
|
|
|
|
if (cmd->handshakeNotify[1] == -1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Handshake is already complete"));
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("Notify handshake on %d", cmd->handshakeNotify[1]);
|
|
if (safewrite(cmd->handshakeNotify[1], &c, sizeof(c)) != sizeof(c)) {
|
|
virReportSystemError(errno, "%s", _("Unable to notify child process"));
|
|
VIR_FORCE_CLOSE(cmd->handshakeNotify[1]);
|
|
return -1;
|
|
}
|
|
VIR_FORCE_CLOSE(cmd->handshakeNotify[1]);
|
|
return 0;
|
|
}
|
|
#else /* WIN32 */
|
|
int
|
|
virCommandSetSendBuffer(virCommandPtr cmd,
|
|
int fd G_GNUC_UNUSED,
|
|
unsigned char *buffer G_GNUC_UNUSED,
|
|
size_t buflen G_GNUC_UNUSED)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return -1;
|
|
|
|
cmd->has_error = ENOTSUP;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virCommandExec(virCommandPtr cmd G_GNUC_UNUSED, gid_t *groups G_GNUC_UNUSED,
|
|
int ngroups G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Executing new processes is not supported on Win32 platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virCommandRun(virCommandPtr cmd G_GNUC_UNUSED, int *exitstatus G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Executing new processes is not supported on Win32 platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virCommandRunAsync(virCommandPtr cmd G_GNUC_UNUSED, pid_t *pid G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Executing new processes is not supported on Win32 platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virCommandWait(virCommandPtr cmd G_GNUC_UNUSED, int *exitstatus G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Executing new processes is not supported on Win32 platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
void
|
|
virCommandAbort(virCommandPtr cmd G_GNUC_UNUSED)
|
|
{
|
|
/* Mingw lacks WNOHANG and kill(). But since we haven't ported
|
|
* virExec to mingw yet, there's no process to be killed,
|
|
* making this implementation trivially correct for now :) */
|
|
}
|
|
|
|
|
|
void virCommandRequireHandshake(virCommandPtr cmd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->has_error = ENOSYS;
|
|
}
|
|
|
|
|
|
int virCommandHandshakeWait(virCommandPtr cmd G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Executing new processes is not supported on Win32 platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int virCommandHandshakeNotify(virCommandPtr cmd G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Executing new processes is not supported on Win32 platform"));
|
|
return -1;
|
|
}
|
|
#endif /* WIN32 */
|
|
|
|
|
|
/**
|
|
* virCommandFree:
|
|
* @cmd: optional command to free
|
|
*
|
|
* Release all resources. The only exception is that if you called
|
|
* virCommandRunAsync with a non-null pid, then the asynchronous child
|
|
* is not reaped, and you must call virProcessWait() or virProcessAbort() yourself.
|
|
*/
|
|
void
|
|
virCommandFree(virCommandPtr cmd)
|
|
{
|
|
size_t i;
|
|
if (!cmd)
|
|
return;
|
|
|
|
for (i = 0; i < cmd->npassfd; i++) {
|
|
if (cmd->passfd[i].flags & VIR_COMMAND_PASS_FD_CLOSE_PARENT)
|
|
VIR_FORCE_CLOSE(cmd->passfd[i].fd);
|
|
}
|
|
cmd->npassfd = 0;
|
|
VIR_FREE(cmd->passfd);
|
|
|
|
if (cmd->asyncioThread) {
|
|
virThreadJoin(cmd->asyncioThread);
|
|
VIR_FREE(cmd->asyncioThread);
|
|
}
|
|
VIR_FREE(cmd->inbuf);
|
|
VIR_FORCE_CLOSE(cmd->outfd);
|
|
VIR_FORCE_CLOSE(cmd->errfd);
|
|
|
|
for (i = 0; i < cmd->nargs; i++)
|
|
VIR_FREE(cmd->args[i]);
|
|
VIR_FREE(cmd->args);
|
|
|
|
for (i = 0; i < cmd->nenv; i++)
|
|
VIR_FREE(cmd->env[i]);
|
|
VIR_FREE(cmd->env);
|
|
|
|
VIR_FREE(cmd->pwd);
|
|
|
|
if (cmd->handshake) {
|
|
/* The other 2 fds in these arrays are closed
|
|
* due to use with virCommandPassFD
|
|
*/
|
|
VIR_FORCE_CLOSE(cmd->handshakeWait[0]);
|
|
VIR_FORCE_CLOSE(cmd->handshakeNotify[1]);
|
|
}
|
|
|
|
VIR_FREE(cmd->pidfile);
|
|
|
|
if (cmd->reap)
|
|
virCommandAbort(cmd);
|
|
|
|
#if defined(WITH_SECDRIVER_SELINUX)
|
|
VIR_FREE(cmd->seLinuxLabel);
|
|
#endif
|
|
#if defined(WITH_SECDRIVER_APPARMOR)
|
|
VIR_FREE(cmd->appArmorProfile);
|
|
#endif
|
|
|
|
virCommandFreeSendBuffers(cmd);
|
|
|
|
VIR_FREE(cmd);
|
|
}
|
|
|
|
/**
|
|
* virCommandDoAsyncIO:
|
|
* @cmd: command to do async IO on
|
|
*
|
|
* This requests asynchronous string IO on @cmd. It is useful in
|
|
* combination with virCommandRunAsync():
|
|
*
|
|
* g_autoptr(virCommand) cmd = virCommandNew*(...);
|
|
* g_autofree char *buf = NULL;
|
|
*
|
|
* ...
|
|
*
|
|
* virCommandSetOutputBuffer(cmd, &buf);
|
|
* virCommandDoAsyncIO(cmd);
|
|
*
|
|
* if (virCommandRunAsync(cmd, NULL) < 0)
|
|
* return;
|
|
*
|
|
* ...
|
|
*
|
|
* if (virCommandWait(cmd, NULL) < 0)
|
|
* return;
|
|
*
|
|
* // @buf now contains @cmd's stdout
|
|
* VIR_DEBUG("STDOUT: %s", NULLSTR(buf));
|
|
*
|
|
* ...
|
|
*
|
|
*
|
|
* The libvirt's event loop is used for handling stdios of @cmd.
|
|
* Since current implementation uses strlen to determine length
|
|
* of data to be written to @cmd's stdin, don't pass any binary
|
|
* data. If you want to re-run command, you need to call this and
|
|
* buffer setting functions (virCommandSet.*Buffer) prior each run.
|
|
*/
|
|
void
|
|
virCommandDoAsyncIO(virCommandPtr cmd)
|
|
{
|
|
if (!cmd || cmd->has_error)
|
|
return;
|
|
|
|
cmd->flags |= VIR_EXEC_ASYNC_IO | VIR_EXEC_NONBLOCK;
|
|
}
|
|
|
|
/**
|
|
* virCommandSetDryRun:
|
|
* @buf: buffer to store stringified commands
|
|
* @callback: callback to process input/output/args
|
|
*
|
|
* Sometimes it's desired to not actually run given command, but
|
|
* see its string representation without having to change the
|
|
* callee. Unit testing serves as a great example. In such cases,
|
|
* the callee constructs the command and calls it via
|
|
* virCommandRun* API. The virCommandSetDryRun allows you to
|
|
* modify this behavior: once called, every call to
|
|
* virCommandRun* results in command string representation being
|
|
* appended to @buf instead of being executed. If @callback is
|
|
* provided, then it is invoked with the argv, env and stdin
|
|
* data string for the command. It is expected to fill the stdout
|
|
* and stderr data strings and exit status variables.
|
|
*
|
|
* The strings stored in @buf are escaped for a shell and
|
|
* separated by a newline. For example:
|
|
*
|
|
* virBuffer buffer = VIR_BUFFER_INITIALIZER;
|
|
* virCommandSetDryRun(&buffer);
|
|
*
|
|
* virCommandPtr echocmd = virCommandNewArgList("/bin/echo", "Hello world", NULL);
|
|
* virCommandRun(echocmd, NULL);
|
|
*
|
|
* After this, the @buffer should contain:
|
|
*
|
|
* /bin/echo 'Hello world'\n
|
|
*
|
|
* To cancel this effect pass NULL for @buf and @callback.
|
|
*/
|
|
void
|
|
virCommandSetDryRun(virBufferPtr buf,
|
|
virCommandDryRunCallback cb,
|
|
void *opaque)
|
|
{
|
|
dryRunBuffer = buf;
|
|
dryRunCallback = cb;
|
|
dryRunOpaque = opaque;
|
|
}
|
|
|
|
#ifndef WIN32
|
|
/**
|
|
* virCommandRunRegex:
|
|
* @cmd: command to run
|
|
* @nregex: number of regexes to apply
|
|
* @regex: array of regexes to apply
|
|
* @nvars: array of numbers of variables each regex will produce
|
|
* @func: callback function that is called for every line of output,
|
|
* needs to return 0 on success
|
|
* @data: additional data that will be passed to the callback function
|
|
* @prefix: prefix that will be skipped at the beginning of each line
|
|
* @exitstatus: allows the caller to handle command run exit failures
|
|
*
|
|
* Run an external program.
|
|
*
|
|
* Read its output and apply a series of regexes to each line
|
|
* When the entire set of regexes has matched consecutively
|
|
* then run a callback passing in all the matches on the current line.
|
|
*
|
|
* Returns: 0 on success, -1 on memory allocation error, virCommandRun
|
|
* error or callback function error
|
|
*/
|
|
int
|
|
virCommandRunRegex(virCommandPtr cmd,
|
|
int nregex,
|
|
const char **regex,
|
|
int *nvars,
|
|
virCommandRunRegexFunc func,
|
|
void *data,
|
|
const char *prefix,
|
|
int *exitstatus)
|
|
{
|
|
GRegex **reg = NULL;
|
|
size_t i, j, k;
|
|
int totgroups = 0, ngroup = 0;
|
|
char **groups;
|
|
g_autofree char *outbuf = NULL;
|
|
VIR_AUTOSTRINGLIST lines = NULL;
|
|
int ret = -1;
|
|
|
|
/* Compile all regular expressions */
|
|
if (VIR_ALLOC_N(reg, nregex) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < nregex; i++) {
|
|
g_autoptr(GError) err = NULL;
|
|
reg[i] = g_regex_new(regex[i], G_REGEX_OPTIMIZE, 0, &err);
|
|
if (!reg[i]) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to compile regex %s"), err->message);
|
|
for (j = 0; j < i; j++)
|
|
g_regex_unref(reg[j]);
|
|
VIR_FREE(reg);
|
|
return -1;
|
|
}
|
|
|
|
totgroups += nvars[i];
|
|
}
|
|
|
|
/* Storage for matched variables */
|
|
if (VIR_ALLOC_N(groups, totgroups) < 0)
|
|
goto cleanup;
|
|
|
|
virCommandSetOutputBuffer(cmd, &outbuf);
|
|
if (virCommandRun(cmd, exitstatus) < 0)
|
|
goto cleanup;
|
|
|
|
if (!outbuf) {
|
|
/* no output */
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(lines = virStringSplit(outbuf, "\n", 0)))
|
|
goto cleanup;
|
|
|
|
for (k = 0; lines[k]; k++) {
|
|
g_autoptr(GMatchInfo) info = NULL;
|
|
const char *p = NULL;
|
|
|
|
/* ignore any command prefix */
|
|
if (prefix)
|
|
p = STRSKIP(lines[k], prefix);
|
|
if (!p)
|
|
p = lines[k];
|
|
|
|
ngroup = 0;
|
|
for (i = 0; i < nregex; i++) {
|
|
if (!(g_regex_match(reg[i], p, 0, &info)))
|
|
break;
|
|
|
|
/* NB match #0 is the full pattern, so we offset j by 1 */
|
|
for (j = 1; j <= nvars[i]; j++)
|
|
groups[ngroup++] = g_match_info_fetch(info, j);
|
|
}
|
|
/* We've matched on the last regex, so callback time */
|
|
if (i == nregex) {
|
|
if (((*func)(groups, data)) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
for (j = 0; j < ngroup; j++)
|
|
VIR_FREE(groups[j]);
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (groups) {
|
|
for (j = 0; j < totgroups; j++)
|
|
VIR_FREE(groups[j]);
|
|
VIR_FREE(groups);
|
|
}
|
|
|
|
for (i = 0; i < nregex; i++)
|
|
g_regex_unref(reg[i]);
|
|
|
|
VIR_FREE(reg);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Run an external program and read from its standard output
|
|
* a stream of tokens from IN_STREAM, applying FUNC to
|
|
* each successive sequence of N_COLUMNS tokens.
|
|
* If FUNC returns < 0, stop processing input and return -1.
|
|
* Return -1 if N_COLUMNS == 0.
|
|
* Return -1 upon memory allocation error.
|
|
* If the number of input tokens is not a multiple of N_COLUMNS,
|
|
* then the final FUNC call will specify a number smaller than N_COLUMNS.
|
|
* If there are no input tokens (empty input), call FUNC with N_COLUMNS == 0.
|
|
*/
|
|
int
|
|
virCommandRunNul(virCommandPtr cmd,
|
|
size_t n_columns,
|
|
virCommandRunNulFunc func,
|
|
void *data)
|
|
{
|
|
size_t n_tok = 0;
|
|
int fd = -1;
|
|
FILE *fp = NULL;
|
|
char **v;
|
|
int ret = -1;
|
|
size_t i;
|
|
|
|
if (n_columns == 0)
|
|
return -1;
|
|
|
|
if (VIR_ALLOC_N(v, n_columns) < 0)
|
|
return -1;
|
|
for (i = 0; i < n_columns; i++)
|
|
v[i] = NULL;
|
|
|
|
virCommandSetOutputFD(cmd, &fd);
|
|
if (virCommandRunAsync(cmd, NULL) < 0)
|
|
goto cleanup;
|
|
|
|
if ((fp = VIR_FDOPEN(fd, "r")) == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("cannot open file using fd"));
|
|
goto cleanup;
|
|
}
|
|
|
|
while (1) {
|
|
char *buf = NULL;
|
|
size_t buf_len = 0;
|
|
/* Be careful: even when it returns -1,
|
|
this use of getdelim allocates memory. */
|
|
ssize_t tok_len = getdelim(&buf, &buf_len, 0, fp);
|
|
v[n_tok] = buf;
|
|
if (tok_len < 0) {
|
|
/* Maybe EOF, maybe an error.
|
|
If n_tok > 0, then we know it's an error. */
|
|
if (n_tok && func(n_tok, v, data) < 0)
|
|
goto cleanup;
|
|
break;
|
|
}
|
|
++n_tok;
|
|
if (n_tok == n_columns) {
|
|
if (func(n_tok, v, data) < 0)
|
|
goto cleanup;
|
|
n_tok = 0;
|
|
for (i = 0; i < n_columns; i++)
|
|
VIR_FREE(v[i]);
|
|
}
|
|
}
|
|
|
|
if (feof(fp) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("read error on pipe"));
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = virCommandWait(cmd, NULL);
|
|
cleanup:
|
|
for (i = 0; i < n_columns; i++)
|
|
VIR_FREE(v[i]);
|
|
VIR_FREE(v);
|
|
|
|
VIR_FORCE_FCLOSE(fp);
|
|
VIR_FORCE_CLOSE(fd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#else /* WIN32 */
|
|
|
|
int
|
|
virCommandRunRegex(virCommandPtr cmd G_GNUC_UNUSED,
|
|
int nregex G_GNUC_UNUSED,
|
|
const char **regex G_GNUC_UNUSED,
|
|
int *nvars G_GNUC_UNUSED,
|
|
virCommandRunRegexFunc func G_GNUC_UNUSED,
|
|
void *data G_GNUC_UNUSED,
|
|
const char *prefix G_GNUC_UNUSED,
|
|
int *exitstatus G_GNUC_UNUSED)
|
|
{
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("%s not implemented on Win32"), __FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
virCommandRunNul(virCommandPtr cmd G_GNUC_UNUSED,
|
|
size_t n_columns G_GNUC_UNUSED,
|
|
virCommandRunNulFunc func G_GNUC_UNUSED,
|
|
void *data G_GNUC_UNUSED)
|
|
{
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("%s not implemented on Win32"), __FUNCTION__);
|
|
return -1;
|
|
}
|
|
#endif /* WIN32 */
|