* docs/internals/command.html.in: Better documentation of buffer
vs. fd considerations.
* src/util/command.c (virCommandRunAsync): Reject raw execution
with string io.
(virCommandRun): Reject execution with user-specified fds not
visiting a regular file.
---
Perhaps we need to relax the fstat check to permit block devices
in addition to regular files; but that can be a later patch if needed.
docs/internals/command.html.in | 35 ++++++++++++++++++++++++++---------
src/util/command.c | 37 ++++++++++++++++++++++++++++++++++++-
2 files changed, 62 insertions(+), 10 deletions(-)
diff --git a/docs/internals/command.html.in b/docs/internals/command.html.in
index c4fa800..259e68e 100644
--- a/docs/internals/command.html.in
+++ b/docs/internals/command.html.in
@@ -250,7 +250,7 @@
allow an open file handle to be passed into the child, while
controlling whether that handle remains open in the parent or
guaranteeing that the handle will be closed in the parent after
- either virCommandRun or virCommandFree.
+ virCommandRun, virCommandRunAsync, or virCommandFree.
</p>
<pre>
@@ -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.
+ </p>
+
+ <p>
+ 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:
</p>
<pre>
@@ -322,11 +329,21 @@
<h3><a name="buffers">Feeding & capturing strings
to/from the child</a></h3>
<p>
- 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).
+ </p>
+
+ <p>
+ 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:
</p>
<pre>
diff --git a/src/util/command.c b/src/util/command.c
index c0520ec..d1d8f6d 100644
--- a/src/util/command.c
+++ b/src/util/command.c
@@ -25,6 +25,7 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
+#include <sys/stat.h>
#include <sys/wait.h>
#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,28 @@ 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 ? (!(cmd->flags & VIR_EXEC_DAEMON) || string_io)
+ : ((cmd->flags & VIR_EXEC_DAEMON) && string_io)) {
+ virCommandError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("invalid use of command API"));
+ return -1;
+ }
+
/* If we have an input buffer, we need
* a pipe to feed the data to the child */
if (cmd->inbuf) {
@@ -921,7 +947,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 +1027,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",
+ _("invalid use of command API"));
+ return -1;
+ }
+
if (cmd->pid != -1) {
virCommandError(VIR_ERR_INTERNAL_ERROR,
_("command is already running as pid %d"),
--
1.7.3.2