Signed-off-by: Peter Krempa <pkrempa@xxxxxxxxxx> --- docs/docs.rst | 3 - docs/internals/command.html.in | 596 ------------------------------- docs/internals/meson.build | 1 - docs/kbase/index.rst | 3 + docs/kbase/internals/command.rst | 465 ++++++++++++++++++++++++ docs/kbase/internals/meson.build | 1 + 6 files changed, 469 insertions(+), 600 deletions(-) delete mode 100644 docs/internals/command.html.in create mode 100644 docs/kbase/internals/command.rst diff --git a/docs/docs.rst b/docs/docs.rst index 299c26d09b..a92c7c26ab 100644 --- a/docs/docs.rst +++ b/docs/docs.rst @@ -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 diff --git a/docs/internals/command.html.in b/docs/internals/command.html.in deleted file mode 100644 index d9f53933c6..0000000000 --- a/docs/internals/command.html.in +++ /dev/null @@ -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 & 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 & 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 & 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) < 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, &outfd); -virCommandSetErrorFD(cmd, &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, &outfd); -virCommandSetErrorFD(cmd, &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 & 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, &output); -virCommandSetErrorBuffer(cmd, &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) < 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 & exit and then - check that its exit status is zero - </p> - -<pre> -if (virCommandRun(cmd, NULL) < 0) - return -1; -</pre> - - <p> - <strong>Note:</strong> if the command has been daemonized - this will only block & 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, &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... -} -</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, &pid) < 0) - return -1; - -... do something while pid is running ... - -int status; -if (virCommandWait(cmd, &status) < 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(&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> diff --git a/docs/internals/meson.build b/docs/internals/meson.build index 298a55dd88..e5f4bb0a4b 100644 --- a/docs/internals/meson.build +++ b/docs/internals/meson.build @@ -1,5 +1,4 @@ internals_in_files = [ - 'command', 'eventloop', 'locking', 'rpc', diff --git a/docs/kbase/index.rst b/docs/kbase/index.rst index c6748e8883..01ec5a070d 100644 --- a/docs/kbase/index.rst +++ b/docs/kbase/index.rst @@ -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 diff --git a/docs/kbase/internals/command.rst b/docs/kbase/internals/command.rst new file mode 100644 index 0000000000..738fb5930a --- /dev/null +++ b/docs/kbase/internals/command.rst @@ -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. diff --git a/docs/kbase/internals/meson.build b/docs/kbase/internals/meson.build index 923e262706..3486b21852 100644 --- a/docs/kbase/internals/meson.build +++ b/docs/kbase/internals/meson.build @@ -1,4 +1,5 @@ docs_kbase_internals_files = [ + 'command', 'incremental-backup', 'migration', ] -- 2.35.1