From ee11729d7f06197fd9770b3389118e2772b515a8 Mon Sep 17 00:00:00 2001
From: Eric Blake
@@ -266,9 +266,16 @@ With this, both file descriptors sharedfd and childfd in the current process remain open as the same file descriptors in the child. Meanwhile, after the child is spawned, sharedfd remains - open in the parent, while childfd is closed. For stdin/out/err - it is usually necessary to map a file handle. To attach file - descriptor 7 in the current process to stdin in the child: + open in the parent, while childfd is closed. + + ++ For stdin/out/err it is sometimes necessary to map a file + handle. If a mapped file handle is a pipe fed or consumed by + the caller, then the caller should use virCommandDaemonize or + virCommandRunAsync rather than virCommandRun to avoid deadlock + (mapping a regular file is okay with virCommandRun). To attach + file descriptor 7 in the current process to stdin in the child:
@@ -322,11 +329,21 @@Feeding & capturing strings to/from the child
- Often dealing with file handles for stdin/out/err - is unnecessarily complex. It is possible to specify - a string buffer to act as the data source for the - child's stdin, if there are no embedded NUL bytes, - and if the command will be run with virCommandRun: + Often dealing with file handles for stdin/out/err is + unnecessarily complex; an alternative is to let virCommandRun + perform the I/O and interact via string buffers. Use of a buffer + only works with virCommandRun, and cannot be mixed with pipe + file descriptors. That is, the choice is generally between + managing all I/O in the caller (any fds not specified are tied + to /dev/null), or letting virCommandRun manage all I/O via + strings (unspecified stdin is tied to /dev/null, and unspecified + output streams get logged but are otherwise discarded). +
+ ++ It is possible to specify a string buffer to act as the data + source for the child's stdin, if there are no embedded NUL + bytes, and if the command will be run with virCommandRun:
diff --git a/src/util/command.c b/src/util/command.c index c0520ec7e0..3dfd33d6fe 100644 --- a/src/util/command.c +++ b/src/util/command.c @@ -25,6 +25,7 @@ #include#include #include +#include #include #include "command.h" @@ -872,6 +873,9 @@ virCommandRun(virCommandPtr cmd, int *exitstatus) char *outbuf = NULL; char *errbuf = NULL; int infd[2]; + struct stat st; + bool string_io; + bool async_io = false; if (!cmd ||cmd->has_error == ENOMEM) { virReportOOMError(); @@ -883,6 +887,35 @@ virCommandRun(virCommandPtr cmd, int *exitstatus) 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) { + virCommandError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot mix caller fds with blocking execution")); + return -1; + } + } else { + if ((cmd->flags & VIR_EXEC_DAEMON) && string_io) { + virCommandError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot mix string I/O with daemon")); + return -1; + } + } + /* If we have an input buffer, we need * a pipe to feed the data to the child */ if (cmd->inbuf) { @@ -921,7 +954,7 @@ virCommandRun(virCommandPtr cmd, int *exitstatus) return -1; } - if (cmd->inbuf || cmd->outbuf || cmd->errbuf) + if (string_io) ret = virCommandProcessIO(cmd); if (virCommandWait(cmd, exitstatus) < 0) @@ -1001,6 +1034,15 @@ virCommandRunAsync(virCommandPtr cmd, pid_t *pid) return -1; } + /* Buffer management can only be requested via virCommandRun. */ + if ((cmd->inbuf && cmd->infd == -1) || + (cmd->outbuf && cmd->outfdptr != &cmd->outfd) || + (cmd->errbuf && cmd->errfdptr != &cmd->errfd)) { + virCommandError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot mix string I/O with asynchronous command")); + return -1; + } + if (cmd->pid != -1) { virCommandError(VIR_ERR_INTERNAL_ERROR, _("command is already running as pid %d"),