mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-01-18 18:45:16 +00:00
command: enforce fd vs. buffer considerations
* 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.
This commit is contained in:
parent
ea8389dd3d
commit
ee11729d7f
@ -250,7 +250,7 @@
|
|||||||
allow an open file handle to be passed into the child, while
|
allow an open file handle to be passed into the child, while
|
||||||
controlling whether that handle remains open in the parent or
|
controlling whether that handle remains open in the parent or
|
||||||
guaranteeing that the handle will be closed in the parent after
|
guaranteeing that the handle will be closed in the parent after
|
||||||
either virCommandRun or virCommandFree.
|
virCommandRun, virCommandRunAsync, or virCommandFree.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
@ -266,9 +266,16 @@
|
|||||||
With this, both file descriptors sharedfd and childfd in the
|
With this, both file descriptors sharedfd and childfd in the
|
||||||
current process remain open as the same file descriptors in the
|
current process remain open as the same file descriptors in the
|
||||||
child. Meanwhile, after the child is spawned, sharedfd remains
|
child. Meanwhile, after the child is spawned, sharedfd remains
|
||||||
open in the parent, while childfd is closed. For stdin/out/err
|
open in the parent, while childfd is closed.
|
||||||
it is usually necessary to map a file handle. To attach file
|
</p>
|
||||||
descriptor 7 in the current process to stdin in the child:
|
|
||||||
|
<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>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
@ -322,11 +329,21 @@
|
|||||||
<h3><a name="buffers">Feeding & capturing strings to/from the child</a></h3>
|
<h3><a name="buffers">Feeding & capturing strings to/from the child</a></h3>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Often dealing with file handles for stdin/out/err
|
Often dealing with file handles for stdin/out/err is
|
||||||
is unnecessarily complex. It is possible to specify
|
unnecessarily complex; an alternative is to let virCommandRun
|
||||||
a string buffer to act as the data source for the
|
perform the I/O and interact via string buffers. Use of a buffer
|
||||||
child's stdin, if there are no embedded NUL bytes,
|
only works with virCommandRun, and cannot be mixed with pipe
|
||||||
and if the command will be run with virCommandRun:
|
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>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
@ -872,6 +873,9 @@ virCommandRun(virCommandPtr cmd, int *exitstatus)
|
|||||||
char *outbuf = NULL;
|
char *outbuf = NULL;
|
||||||
char *errbuf = NULL;
|
char *errbuf = NULL;
|
||||||
int infd[2];
|
int infd[2];
|
||||||
|
struct stat st;
|
||||||
|
bool string_io;
|
||||||
|
bool async_io = false;
|
||||||
|
|
||||||
if (!cmd ||cmd->has_error == ENOMEM) {
|
if (!cmd ||cmd->has_error == ENOMEM) {
|
||||||
virReportOOMError();
|
virReportOOMError();
|
||||||
@ -883,6 +887,35 @@ virCommandRun(virCommandPtr cmd, int *exitstatus)
|
|||||||
return -1;
|
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
|
/* If we have an input buffer, we need
|
||||||
* a pipe to feed the data to the child */
|
* a pipe to feed the data to the child */
|
||||||
if (cmd->inbuf) {
|
if (cmd->inbuf) {
|
||||||
@ -921,7 +954,7 @@ virCommandRun(virCommandPtr cmd, int *exitstatus)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cmd->inbuf || cmd->outbuf || cmd->errbuf)
|
if (string_io)
|
||||||
ret = virCommandProcessIO(cmd);
|
ret = virCommandProcessIO(cmd);
|
||||||
|
|
||||||
if (virCommandWait(cmd, exitstatus) < 0)
|
if (virCommandWait(cmd, exitstatus) < 0)
|
||||||
@ -1001,6 +1034,15 @@ virCommandRunAsync(virCommandPtr cmd, pid_t *pid)
|
|||||||
return -1;
|
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) {
|
if (cmd->pid != -1) {
|
||||||
virCommandError(VIR_ERR_INTERNAL_ERROR,
|
virCommandError(VIR_ERR_INTERNAL_ERROR,
|
||||||
_("command is already running as pid %d"),
|
_("command is already running as pid %d"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user