docs: Convert 'internals/command' to rst and move it to 'kbase/internals'

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
This commit is contained in:
Peter Krempa 2022-04-05 13:58:56 +02:00
parent a2a089c65e
commit 879546fdd4
6 changed files with 469 additions and 600 deletions

View File

@ -157,9 +157,6 @@ Project development
`Event loop and worker pool <internals/eventloop.html>`__
Libvirt's event loop and worker pool mode
`Spawning commands <internals/command.html>`__
Spawning commands from libvirt driver code
`RPC protocol & APIs <internals/rpc.html>`__
RPC protocol information and API / dispatch guide

View File

@ -1,596 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<h1>Spawning processes / commands from libvirt drivers</h1>
<ul id="toc"></ul>
<p>
This page describes the usage of libvirt APIs for
spawning processes / commands from libvirt drivers.
All code is required to use these APIs
</p>
<h2><a id="posix">Problems with standard POSIX APIs</a></h2>
<p>
The POSIX specification includes a number of APIs for
spawning processes / commands, but they suffer from
a number of flaws
</p>
<ul>
<li><code>fork+exec</code>: The lowest &amp; most flexible
level, but very hard to use correctly / safely. It
is easy to leak file descriptors, have unexpected
signal handler behaviour and not handle edge cases.
Furthermore, it is not portable to mingw.
</li>
<li><code>system</code>: Convenient if you don't care
about capturing command output, but has the serious
downside that the command string is interpreted by
the shell. This makes it very dangerous to use, because
improperly validated user input can lead to exploits
via shell meta characters.
</li>
<li><code>popen</code>: Inherits the flaws of
<code>system</code>, and has no option for bi-directional
communication.
</li>
<li><code>posix_spawn</code>: A half-way house between
simplicity of system() and the flexibility of fork+exec.
It does not allow for a couple of important features
though, such as running a hook between the fork+exec
stage, or closing all open file descriptors.</li>
</ul>
<p>
Due to the problems mentioned with each of these,
libvirt driver code <strong>must not use</strong> any
of the above APIs. Historically libvirt provided a
higher level API known as virExec. This was wrapper
around fork+exec, in a similar style to posix_spawn,
but with a few more features.
</p>
<p>
This wrapper still suffered from a number of problems.
Handling command cleanup via waitpid() is overly
complex &amp; error prone for most usage. Building up the
argv[] + env[] string arrays is quite cumbersome and
error prone, particularly wrt memory leak / OOM handling.
</p>
<h2><a id="api">The libvirt command execution API</a></h2>
<p>
There is now a high level API that provides a safe and
flexible way to spawn commands, which prevents the most
common errors &amp; is easy to code against. This
code is provided in the <code>src/util/vircommand.h</code>
header which can be imported using <code>#include "vircommand.h"</code>
</p>
<h3><a id="initial">Defining commands in libvirt</a></h3>
<p>
The first step is to declare what command is to be
executed. The command name can be either a fully
qualified path, or a bare command name. In the latter
case it will be resolved wrt the <code>$PATH</code>
environment variable.
</p>
<pre>
virCommand *cmd = virCommandNew("/usr/bin/dnsmasq");
</pre>
<p>
There is no need to check for allocation failure after
<code>virCommandNew</code>. This will be detected and
reported at a later time.
</p>
<h3><a id="args">Adding arguments to the command</a></h3>
<p>
There are a number of APIs for adding arguments to a
command. To add a direct string arg
</p>
<pre>
virCommandAddArg(cmd, "-strict-order");
</pre>
<p>
If an argument takes an attached value of the form
<code>-arg=val</code>, then this can be done using
</p>
<pre>
virCommandAddArgPair(cmd, "--conf-file", "/etc/dnsmasq.conf");
</pre>
<p>
If an argument needs to be formatted as if by
<code>printf</code>:
</p>
<pre>
virCommandAddArgFormat(cmd, "%d", count);
</pre>
<p>
To add an entire NULL terminated array of arguments in one go,
there are two options.
</p>
<pre>
const char *const args[] = {
"--strict-order", "--except-interface", "lo", NULL
};
virCommandAddArgSet(cmd, args);
virCommandAddArgList(cmd, "--domain", "localdomain", NULL);
</pre>
<p>
This can also be done at the time of initial construction of
the <code>virCommand *</code> object:
</p>
<pre>
const char *const args[] = {
"/usr/bin/dnsmasq",
"--strict-order", "--except-interface",
"lo", "--domain", "localdomain", NULL
};
virCommand *cmd1 = virCommandNewArgs(cmd, args);
virCommand *cmd2 = virCommandNewArgList("/usr/bin/dnsmasq",
"--domain", "localdomain", NULL);
</pre>
<h3><a id="env">Setting up the environment</a></h3>
<p>
By default a command will inherit all environment variables
from the current process. Generally this is not desirable
and a customized environment will be more suitable. Any
customization done via the following APIs will prevent
inheritance of any existing environment variables unless
explicitly allowed. The first step is usually to pass through
a small number of variables from the current process.
</p>
<pre>
virCommandAddEnvPassCommon(cmd);
</pre>
<p>
This has now set up a clean environment for the child, passing
through <code>PATH</code>, <code>LD_PRELOAD</code>,
<code>LD_LIBRARY_PATH</code>, <code>HOME</code>,
<code>USER</code>, <code>LOGNAME</code> and <code>TMPDIR</code>.
Furthermore it will explicitly set <code>LC_ALL=C</code> to
avoid unexpected localization of command output. Further
variables can be passed through from parent explicitly:
</p>
<pre>
virCommandAddEnvPass(cmd, "DISPLAY");
virCommandAddEnvPass(cmd, "XAUTHORITY");
</pre>
<p>
To define an environment variable in the child with an
separate key / value:
</p>
<pre>
virCommandAddEnvPair(cmd, "TERM", "xterm");
</pre>
<p>
If the key/value pair is pre-formatted in the right
format, it can be set directly
</p>
<pre>
virCommandAddEnvString(cmd, "TERM=xterm");
</pre>
<h3><a id="misc">Miscellaneous other options</a></h3>
<p>
Normally the spawned command will retain the current
process and process group as its parent. If the current
process dies, the child will then (usually) be terminated
too. If this cleanup is not desired, then the command
should be marked as daemonized:
</p>
<pre>
virCommandDaemonize(cmd);
</pre>
<p>
When daemonizing a command, the PID visible from the
caller will be that of the intermediate process, not
the actual damonized command. If the PID of the real
command is required then a pidfile can be requested
</p>
<pre>
virCommandSetPidFile(cmd, "/var/run/dnsmasq.pid");
</pre>
<p>
This PID file is guaranteed to be written before
the intermediate process exits. Moreover, the daemonized
process will inherit the FD of the opened and locked PID
file.
</p>
<h3><a id="privs">Reducing command privileges</a></h3>
<p>
Normally a command will inherit all privileges of
the current process. To restrict what a command can
do, it is possible to request that all its capabilities
are cleared. With this done it will only be able to
access resources for which it has explicit DAC permissions
</p>
<pre>
virCommandClearCaps(cmd);
</pre>
<h3><a id="fds">Managing file handles</a></h3>
<p>
To prevent unintended resource leaks to child processes, the
child defaults to closing all open file handles, and setting
stdin/out/err to <code>/dev/null</code>. It is possible to
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
virCommandRun, virCommandRunAsync, or virCommandFree.
</p>
<pre>
int sharedfd = open("cmd.log", "w+");
int childfd = open("conf.txt", "r");
virCommandPassFD(cmd, sharedfd, 0);
virCommandPassFD(cmd, childfd,
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
if (VIR_CLOSE(sharedfd) &lt; 0)
goto cleanup;
</pre>
<p>
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.
</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>
virCommandSetInputFD(cmd, 7);
</pre>
<p>
Equivalently to redirect stdout or stderr in the child,
pass in a pointer to the desired handle
</p>
<pre>
int outfd = open("out.log", "w+");
int errfd = open("err.log", "w+");
virCommandSetOutputFD(cmd, &amp;outfd);
virCommandSetErrorFD(cmd, &amp;errfd);
</pre>
<p>
Alternatively it is possible to request that a pipe be
created to fetch stdout/err in the parent, by initializing
the FD to -1.
</p>
<pre>
int outfd = -1;
int errfd = -1
virCommandSetOutputFD(cmd, &amp;outfd);
virCommandSetErrorFD(cmd, &amp;errfd);
</pre>
<p>
Once the command is running, <code>outfd</code>
and <code>errfd</code> will be initialized with
valid file handles that can be read from. It is
permissible to pass the same pointer for both outfd
and errfd, in which case both standard streams in
the child will share the same fd in the parent.
</p>
<p>
Normally, file descriptors opened to collect output from a child
process perform blocking I/O, but the parent process can request
non-blocking mode:
</p>
<pre>
virCommandNonblockingFDs(cmd);
</pre>
<h3><a id="buffers">Feeding &amp; capturing strings to/from the child</a></h3>
<p>
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>
const char *input = "Hello World\n";
virCommandSetInputBuffer(cmd, input);
</pre>
<p>
Similarly it is possible to request that the child's
stdout/err be redirected into a string buffer, if the
output is not expected to contain NUL bytes, and if
the command will be run with virCommandRun:
</p>
<pre>
char *output = NULL, *errors = NULL;
virCommandSetOutputBuffer(cmd, &amp;output);
virCommandSetErrorBuffer(cmd, &amp;errors);
</pre>
<p>
Once the command has finished executing, these buffers will
contain the output. Allocation is guaranteed if virCommandRun
or virCommandWait succeed (if there was no output, then the
buffer will contain an allocated empty string); if the command
failed, then the buffers usually contain a best-effort
allocation of collected information (however, on an
out-of-memory condition, the buffer may still be NULL). The
caller is responsible for freeing registered buffers, since the
buffers are designed to persist beyond virCommandFree. It
is possible to pass the same pointer to both
virCommandSetOutputBuffer and virCommandSetErrorBuffer, in which
case the child process interleaves output into a single string.
</p>
<h3><a id="directory">Setting working directory</a></h3>
<p>
Daemonized commands are always run with "/" as the current
working directory. All other commands default to running in the
same working directory as the parent process, but an alternate
directory can be specified:
</p>
<pre>
virCommandSetWorkingDirectory(cmd, LOCALSTATEDIR);
</pre>
<h3><a id="hooks">Any additional hooks</a></h3>
<p>
If anything else is needed, it is possible to request a hook
function that is called in the child after the fork, as the
last thing before changing directories, dropping capabilities,
and executing the new process. If hook(opaque) returns
non-zero, then the child process will not be run.
</p>
<pre>
virCommandSetPreExecHook(cmd, hook, opaque);
</pre>
<h3><a id="logging">Logging commands</a></h3>
<p>
Sometimes, it is desirable to log what command will be run, or
even to use virCommand solely for creation of a single
consolidated string without running anything.
</p>
<pre>
int logfd = ...;
char *timestamp = virTimestamp();
char *string = NULL;
dprintf(logfd, "%s: ", timestamp);
VIR_FREE(timestamp);
virCommandWriteArgLog(cmd, logfd);
string = virCommandToString(cmd, false);
if (string)
VIR_DEBUG("about to run %s", string);
VIR_FREE(string);
if (virCommandRun(cmd, NULL) &lt; 0)
return -1;
</pre>
<h3><a id="sync">Running commands synchronously</a></h3>
<p>
For most commands, the desired behaviour is to spawn
the command, wait for it to complete &amp; exit and then
check that its exit status is zero
</p>
<pre>
if (virCommandRun(cmd, NULL) &lt; 0)
return -1;
</pre>
<p>
<strong>Note:</strong> if the command has been daemonized
this will only block &amp; wait for the intermediate process,
not the real command. <code>virCommandRun</code> will
report on any errors that have occurred upon this point
with all previous API calls. If the command fails to
run, or exits with non-zero status an error will be
reported via normal libvirt error infrastructure. If a
non-zero exit status can represent a success condition,
it is possible to request the exit status and perform
that check manually instead of letting <code>virCommandRun</code>
raise the error. By default, the captured status is only
for a normal exit (death from a signal is treated as an error),
but a caller can use <code>virCommandRawStatus</code> to get
encoded status that includes any terminating signals.
</p>
<pre>
int status;
if (virCommandRun(cmd, &amp;status) &lt; 0)
return -1;
if (status == 1) {
...do stuff...
}
virCommandRawStatus(cmd2);
if (virCommandRun(cmd2, &amp;status) &lt; 0)
return -1;
if (WIFEXITED(status) &amp;&amp; WEXITSTATUS(status) == 1) {
...do stuff...
}
</pre>
<h3><a id="async">Running commands asynchronously</a></h3>
<p>
In certain complex scenarios, particularly special
I/O handling is required for the child's stdin/err/out
it will be necessary to run the command asynchronously
and wait for completion separately.
</p>
<pre>
pid_t pid;
if (virCommandRunAsync(cmd, &amp;pid) &lt; 0)
return -1;
... do something while pid is running ...
int status;
if (virCommandWait(cmd, &amp;status) &lt; 0)
return -1;
if (WEXITSTATUS(status)...) {
..do stuff..
}
</pre>
<p>
As with <code>virCommandRun</code>, the <code>status</code>
arg for <code>virCommandWait</code> can be omitted, in which
case it will validate that exit status is zero and raise an
error if not.
</p>
<p>
There are two approaches to child process cleanup, determined by
how long you want to keep the virCommand object in scope.
</p>
<p>1. If the virCommand object will outlast the child process,
then pass NULL for the pid argument, and the child process will
automatically be reaped at virCommandFree, unless you reap it
sooner via virCommandWait or virCommandAbort.
</p>
<p>2. If the child process must exist on at least one code path
after virCommandFree, then pass a pointer for the pid argument.
Later, to clean up the child, call virPidWait or virPidAbort.
Before virCommandFree, you can still use virCommandWait or
virCommandAbort to reap the process.
</p>
<h3><a id="release">Releasing resources</a></h3>
<p>
Once the command has been executed, or if execution
has been abandoned, it is necessary to release
resources associated with the <code>virCommand *</code>
object. This is done with:
</p>
<pre>
virCommandFree(cmd);
</pre>
<p>
There is no need to check if <code>cmd</code> is NULL
before calling <code>virCommandFree</code>. This scenario
is handled automatically. If the command is still running,
it will be forcibly killed and cleaned up (via waitpid).
</p>
<h2><a id="example">Complete examples</a></h2>
<p>
This shows a complete example usage of the APIs roughly
using the libvirt source src/util/hooks.c
</p>
<pre>
int runhook(const char *drvstr, const char *id,
const char *opstr, const char *subopstr,
const char *extra)
{
g_autofree char *path = NULL;
g_autoptr(virCommand) cmd = NULL;
virBuildPath(&amp;path, LIBVIRT_HOOK_DIR, drvstr);
cmd = virCommandNew(path);
virCommandAddEnvPassCommon(cmd);
virCommandAddArgList(cmd, id, opstr, subopstr, extra, NULL);
virCommandSetInputBuffer(cmd, input);
return virCommandRun(cmd, NULL);
}
</pre>
<p>
In this example, the command is being run synchronously.
A pre-formatted string is being fed to the command as
its stdin. The command takes four arguments, and has a
minimal set of environment variables passed down. In
this example, the code does not require any error checking.
All errors are reported by the <code>virCommandRun</code>
method, and the exit status from this is returned to
the caller to handle as desired.
</p>
</body>
</html>

View File

@ -1,5 +1,4 @@
internals_in_files = [
'command',
'eventloop',
'locking',
'rpc',

View File

@ -85,3 +85,6 @@ Internals
`VM migration internals <internals/migration.html>`__
VM migration implementation details, complementing the info in
`migration <../migration.html>`__
`Spawning commands <internals/command.html>`__
Spawning commands from libvirt driver code

View File

@ -0,0 +1,465 @@
==================================================
Spawning processes / commands from libvirt drivers
==================================================
.. contents::
This page describes the usage of libvirt APIs for spawning processes / commands
from libvirt drivers. All code is required to use these APIs
Problems with standard POSIX APIs
---------------------------------
The POSIX specification includes a number of APIs for spawning processes /
commands, but they suffer from a number of flaws
- ``fork+exec``: The lowest & most flexible level, but very hard to use
correctly / safely. It is easy to leak file descriptors, have unexpected
signal handler behaviour and not handle edge cases. Furthermore, it is not
portable to mingw.
- ``system``: Convenient if you don't care about capturing command output, but
has the serious downside that the command string is interpreted by the shell.
This makes it very dangerous to use, because improperly validated user input
can lead to exploits via shell meta characters.
- ``popen``: Inherits the flaws of ``system``, and has no option for
bi-directional communication.
- ``posix_spawn``: A half-way house between simplicity of system() and the
flexibility of fork+exec. It does not allow for a couple of important
features though, such as running a hook between the fork+exec stage, or
closing all open file descriptors.
Due to the problems mentioned with each of these, libvirt driver code **must not
use** any of the above APIs. Historically libvirt provided a higher level API
known as virExec. This was wrapper around fork+exec, in a similar style to
posix_spawn, but with a few more features.
This wrapper still suffered from a number of problems. Handling command cleanup
via waitpid() is overly complex & error prone for most usage. Building up the
argv[] + env[] string arrays is quite cumbersome and error prone, particularly
wrt memory leak / OOM handling.
The libvirt command execution API
---------------------------------
There is now a high level API that provides a safe and flexible way to spawn
commands, which prevents the most common errors & is easy to code against. This
code is provided in the ``src/util/vircommand.h`` header which can be imported
using ``#include "vircommand.h"``
Defining commands in libvirt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The first step is to declare what command is to be executed. The command name
can be either a fully qualified path, or a bare command name. In the latter case
it will be resolved wrt the ``$PATH`` environment variable.
::
virCommand *cmd = virCommandNew("/usr/bin/dnsmasq");
There is no need to check for allocation failure after ``virCommandNew``. This
will be detected and reported at a later time.
Adding arguments to the command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are a number of APIs for adding arguments to a command. To add a direct
string arg
::
virCommandAddArg(cmd, "-strict-order");
If an argument takes an attached value of the form ``-arg=val``, then this can
be done using
::
virCommandAddArgPair(cmd, "--conf-file", "/etc/dnsmasq.conf");
If an argument needs to be formatted as if by ``printf``:
::
virCommandAddArgFormat(cmd, "%d", count);
To add an entire NULL terminated array of arguments in one go, there are two
options.
::
const char *const args[] = {
"--strict-order", "--except-interface", "lo", NULL
};
virCommandAddArgSet(cmd, args);
virCommandAddArgList(cmd, "--domain", "localdomain", NULL);
This can also be done at the time of initial construction of the
``virCommand *`` object:
::
const char *const args[] = {
"/usr/bin/dnsmasq",
"--strict-order", "--except-interface",
"lo", "--domain", "localdomain", NULL
};
virCommand *cmd1 = virCommandNewArgs(cmd, args);
virCommand *cmd2 = virCommandNewArgList("/usr/bin/dnsmasq",
"--domain", "localdomain", NULL);
Setting up the environment
~~~~~~~~~~~~~~~~~~~~~~~~~~
By default a command will inherit all environment variables from the current
process. Generally this is not desirable and a customized environment will be
more suitable. Any customization done via the following APIs will prevent
inheritance of any existing environment variables unless explicitly allowed. The
first step is usually to pass through a small number of variables from the
current process.
::
virCommandAddEnvPassCommon(cmd);
This has now set up a clean environment for the child, passing through ``PATH``,
``LD_PRELOAD``, ``LD_LIBRARY_PATH``, ``HOME``, ``USER``, ``LOGNAME`` and
``TMPDIR``. Furthermore it will explicitly set ``LC_ALL=C`` to avoid unexpected
localization of command output. Further variables can be passed through from
parent explicitly:
::
virCommandAddEnvPass(cmd, "DISPLAY");
virCommandAddEnvPass(cmd, "XAUTHORITY");
To define an environment variable in the child with an separate key / value:
::
virCommandAddEnvPair(cmd, "TERM", "xterm");
If the key/value pair is pre-formatted in the right format, it can be set
directly
::
virCommandAddEnvString(cmd, "TERM=xterm");
Miscellaneous other options
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Normally the spawned command will retain the current process and process group
as its parent. If the current process dies, the child will then (usually) be
terminated too. If this cleanup is not desired, then the command should be
marked as daemonized:
::
virCommandDaemonize(cmd);
When daemonizing a command, the PID visible from the caller will be that of the
intermediate process, not the actual damonized command. If the PID of the real
command is required then a pidfile can be requested
::
virCommandSetPidFile(cmd, "/var/run/dnsmasq.pid");
This PID file is guaranteed to be written before the intermediate process exits.
Moreover, the daemonized process will inherit the FD of the opened and locked
PID file.
Reducing command privileges
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Normally a command will inherit all privileges of the current process. To
restrict what a command can do, it is possible to request that all its
capabilities are cleared. With this done it will only be able to access
resources for which it has explicit DAC permissions
::
virCommandClearCaps(cmd);
Managing file handles
~~~~~~~~~~~~~~~~~~~~~
To prevent unintended resource leaks to child processes, the child defaults to
closing all open file handles, and setting stdin/out/err to ``/dev/null``. It is
possible to 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 virCommandRun, virCommandRunAsync,
or virCommandFree.
::
int sharedfd = open("cmd.log", "w+");
int childfd = open("conf.txt", "r");
virCommandPassFD(cmd, sharedfd, 0);
virCommandPassFD(cmd, childfd,
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
if (VIR_CLOSE(sharedfd) < 0)
goto cleanup;
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 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:
::
virCommandSetInputFD(cmd, 7);
Equivalently to redirect stdout or stderr in the child, pass in a pointer to the
desired handle
::
int outfd = open("out.log", "w+");
int errfd = open("err.log", "w+");
virCommandSetOutputFD(cmd, &outfd);
virCommandSetErrorFD(cmd, &errfd);
Alternatively it is possible to request that a pipe be created to fetch
stdout/err in the parent, by initializing the FD to -1.
::
int outfd = -1;
int errfd = -1
virCommandSetOutputFD(cmd, &outfd);
virCommandSetErrorFD(cmd, &errfd);
Once the command is running, ``outfd`` and ``errfd`` will be initialized with
valid file handles that can be read from. It is permissible to pass the same
pointer for both outfd and errfd, in which case both standard streams in the
child will share the same fd in the parent.
Normally, file descriptors opened to collect output from a child process perform
blocking I/O, but the parent process can request non-blocking mode:
::
virCommandNonblockingFDs(cmd);
Feeding & capturing strings to/from the child
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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:
::
const char *input = "Hello World\n";
virCommandSetInputBuffer(cmd, input);
Similarly it is possible to request that the child's stdout/err be redirected
into a string buffer, if the output is not expected to contain NUL bytes, and if
the command will be run with virCommandRun:
::
char *output = NULL, *errors = NULL;
virCommandSetOutputBuffer(cmd, &output);
virCommandSetErrorBuffer(cmd, &errors);
Once the command has finished executing, these buffers will contain the output.
Allocation is guaranteed if virCommandRun or virCommandWait succeed (if there
was no output, then the buffer will contain an allocated empty string); if the
command failed, then the buffers usually contain a best-effort allocation of
collected information (however, on an out-of-memory condition, the buffer may
still be NULL). The caller is responsible for freeing registered buffers, since
the buffers are designed to persist beyond virCommandFree. It is possible to
pass the same pointer to both virCommandSetOutputBuffer and
virCommandSetErrorBuffer, in which case the child process interleaves output
into a single string.
Setting working directory
~~~~~~~~~~~~~~~~~~~~~~~~~
Daemonized commands are always run with "/" as the current working directory.
All other commands default to running in the same working directory as the
parent process, but an alternate directory can be specified:
::
virCommandSetWorkingDirectory(cmd, LOCALSTATEDIR);
Any additional hooks
~~~~~~~~~~~~~~~~~~~~
If anything else is needed, it is possible to request a hook function that is
called in the child after the fork, as the last thing before changing
directories, dropping capabilities, and executing the new process. If
hook(opaque) returns non-zero, then the child process will not be run.
::
virCommandSetPreExecHook(cmd, hook, opaque);
Logging commands
~~~~~~~~~~~~~~~~
Sometimes, it is desirable to log what command will be run, or even to use
virCommand solely for creation of a single consolidated string without running
anything.
::
int logfd = ...;
char *timestamp = virTimestamp();
char *string = NULL;
dprintf(logfd, "%s: ", timestamp);
VIR_FREE(timestamp);
virCommandWriteArgLog(cmd, logfd);
string = virCommandToString(cmd, false);
if (string)
VIR_DEBUG("about to run %s", string);
VIR_FREE(string);
if (virCommandRun(cmd, NULL) < 0)
return -1;
Running commands synchronously
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For most commands, the desired behaviour is to spawn the command, wait for it to
complete & exit and then check that its exit status is zero
::
if (virCommandRun(cmd, NULL) < 0)
return -1;
**Note:** if the command has been daemonized this will only block & wait for the
intermediate process, not the real command. ``virCommandRun`` will report on any
errors that have occurred upon this point with all previous API calls. If the
command fails to run, or exits with non-zero status an error will be reported
via normal libvirt error infrastructure. If a non-zero exit status can represent
a success condition, it is possible to request the exit status and perform that
check manually instead of letting ``virCommandRun`` raise the error. By default,
the captured status is only for a normal exit (death from a signal is treated as
an error), but a caller can use ``virCommandRawStatus`` to get encoded status
that includes any terminating signals.
::
int status;
if (virCommandRun(cmd, &status) < 0)
return -1;
if (status == 1) {
...do stuff...
}
virCommandRawStatus(cmd2);
if (virCommandRun(cmd2, &status) < 0)
return -1;
if (WIFEXITED(status) && WEXITSTATUS(status) == 1) {
...do stuff...
}
Running commands asynchronously
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In certain complex scenarios, particularly special I/O handling is required for
the child's stdin/err/out it will be necessary to run the command asynchronously
and wait for completion separately.
::
pid_t pid;
if (virCommandRunAsync(cmd, &pid) < 0)
return -1;
... do something while pid is running ...
int status;
if (virCommandWait(cmd, &status) < 0)
return -1;
if (WEXITSTATUS(status)...) {
..do stuff..
}
As with ``virCommandRun``, the ``status`` arg for ``virCommandWait`` can be
omitted, in which case it will validate that exit status is zero and raise an
error if not.
There are two approaches to child process cleanup, determined by how long you
want to keep the virCommand object in scope.
1. If the virCommand object will outlast the child process, then pass NULL for
the pid argument, and the child process will automatically be reaped at
virCommandFree, unless you reap it sooner via virCommandWait or virCommandAbort.
2. If the child process must exist on at least one code path after
virCommandFree, then pass a pointer for the pid argument. Later, to clean up the
child, call virPidWait or virPidAbort. Before virCommandFree, you can still use
virCommandWait or virCommandAbort to reap the process.
Releasing resources
~~~~~~~~~~~~~~~~~~~
Once the command has been executed, or if execution has been abandoned, it is
necessary to release resources associated with the ``virCommand *`` object. This
is done with:
::
virCommandFree(cmd);
There is no need to check if ``cmd`` is NULL before calling ``virCommandFree``.
This scenario is handled automatically. If the command is still running, it will
be forcibly killed and cleaned up (via waitpid).
Complete examples
-----------------
This shows a complete example usage of the APIs roughly using the libvirt source
src/util/hooks.c
::
int runhook(const char *drvstr, const char *id,
const char *opstr, const char *subopstr,
const char *extra)
{
g_autofree char *path = NULL;
g_autoptr(virCommand) cmd = NULL;
virBuildPath(&path, LIBVIRT_HOOK_DIR, drvstr);
cmd = virCommandNew(path);
virCommandAddEnvPassCommon(cmd);
virCommandAddArgList(cmd, id, opstr, subopstr, extra, NULL);
virCommandSetInputBuffer(cmd, input);
return virCommandRun(cmd, NULL);
}
In this example, the command is being run synchronously. A pre-formatted string
is being fed to the command as its stdin. The command takes four arguments, and
has a minimal set of environment variables passed down. In this example, the
code does not require any error checking. All errors are reported by the
``virCommandRun`` method, and the exit status from this is returned to the
caller to handle as desired.

View File

@ -1,4 +1,5 @@
docs_kbase_internals_files = [
'command',
'incremental-backup',
'migration',
]