From: Daniel P. Berrange <berrange@xxxxxxxxxx> * docs/internals/command.html.in: New file. * docs/Makefile.am: Build new docs. * docs/subsite.xsl: New glue file. * docs/internals.html.in, docs/sitemap.html.in: Update glue. --- v2: document commands added in v2. docs/Makefile.am | 11 +- docs/internals.html.in | 9 + docs/internals/command.html.in | 550 ++++++++++++++++++++++++++++++++++++++++ docs/sitemap.html.in | 4 + docs/subsite.xsl | 25 ++ 5 files changed, 598 insertions(+), 1 deletions(-) create mode 100644 docs/internals/command.html.in create mode 100644 docs/subsite.xsl diff --git a/docs/Makefile.am b/docs/Makefile.am index b28e04e..ce0b391 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -60,7 +60,8 @@ gif = \ architecture.gif \ node.gif -dot_html_in = $(notdir $(wildcard $(srcdir)/*.html.in)) todo.html.in +dot_html_in = $(notdir $(wildcard $(srcdir)/*.html.in)) todo.html.in \ + $(patsubst $(srcdir)/%,%,$(wildcard $(srcdir)/internals/*.html.in)) dot_html = $(dot_html_in:%.html.in=%.html) patches = $(wildcard api_extension/*.patch) @@ -113,6 +114,14 @@ todo: %.png: %.fig convert -rotate 90 $< $@ +internals/%.html.tmp: internals/%.html.in subsite.xsl page.xsl sitemap.html.in + @if [ -x $(XSLTPROC) ] ; then \ + echo "Generating $@"; \ + name=`echo $@ | sed -e 's/.tmp//'`; \ + $(XSLTPROC) --stringparam pagename $$name --nonet --html \ + $(top_srcdir)/docs/subsite.xsl $< > $@ \ + || { rm $@ && exit 1; }; fi + %.html.tmp: %.html.in site.xsl page.xsl sitemap.html.in @if [ -x $(XSLTPROC) ] ; then \ echo "Generating $@"; \ diff --git a/docs/internals.html.in b/docs/internals.html.in index d39098e..dc88eab 100644 --- a/docs/internals.html.in +++ b/docs/internals.html.in @@ -7,5 +7,14 @@ internals, adding new public APIs, new hypervisor drivers or extending the libvirtd daemon code. </p> + + <ul> + <li>Introduction to basic rules and guidelines for <a href="hacking.html">hacking<a> + on libvirt code</li> + <li>Guide to adding <a href="api_extension.html">public APIs<a></li> + <li>Approach for <a href="internals/command.html">spawning commands</a> from + libvirt driver code</li> + </ul> + </body> </html> diff --git a/docs/internals/command.html.in b/docs/internals/command.html.in new file mode 100644 index 0000000..c4fa800 --- /dev/null +++ b/docs/internals/command.html.in @@ -0,0 +1,550 @@ +<html> + <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 name="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 name="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/command.h</code> + header which can be imported using <code>#include "command.h"</code> + </p> + + <h3><a name="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> + virCommandPtr 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 name="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 a 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>virCommandPtr</code> object: + </p> + +<pre> + const char *const args[] = { + "/usr/bin/dnsmasq", + "--strict-order", "--except-interface", + "lo", "--domain", "localdomain", NULL + }; + virCommandPtr cmd1 = virCommandNewArgs(cmd, args); + virCommandPtr cmd2 = virCommandNewArgList("/usr/bin/dnsmasq", + "--domain", "localdomain", NULL); +</pre> + + <h3><a name="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 name="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. + </p> + + <h3><a name="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 name="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 + either virCommandRun or virCommandFree. + </p> + +<pre> + int sharedfd = open("cmd.log", "w+"); + int childfd = open("conf.txt", "r"); + virCommandPreserveFD(cmd, sharedfd); + virCommandTransferFD(cmd, childfd); + 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. 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: + </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 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: + </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. It is the callers responsibility + to free these buffers. + </p> + + <h3><a name="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 name="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 name="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); + if (string) + VIR_DEBUG("about to run %s", string); + VIR_FREE(string); + if (virCommandRun(cmd) < 0) + return -1; +</pre> + + <h3><a name="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 occured 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 + </p> + +<pre> + int status; + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (WEXITSTATUS(status) ...) { + ...do stuff... + } +</pre> + + <h3><a name="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> + + + <h3><a name="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>virCommandPtr</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 forcably killed and cleaned up (via waitpid). + </p> + + <h2><a name="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) { + int ret; + char *path; + virCommandPtr cmd; + + ret = virBuildPath(&path, LIBVIRT_HOOK_DIR, drvstr); + if ((ret < 0) || (path == NULL)) { + virHookReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to build path for %s hook"), + drvstr); + return -1; + } + + cmd = virCommandNew(path); + VIR_FREE(path); + + virCommandAddEnvPassCommon(cmd); + + virCommandAddArgList(cmd, id, opstr, subopstr, extra, NULL); + + virCommandSetInputBuffer(cmd, input); + + ret = virCommandRun(cmd, NULL); + + virCommandFree(cmd); + + return ret; +} +</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/sitemap.html.in b/docs/sitemap.html.in index 692da29..7db59a1 100644 --- a/docs/sitemap.html.in +++ b/docs/sitemap.html.in @@ -260,6 +260,10 @@ <a href="api_extension.html">API extensions</a> <span>Adding new public libvirt APIs</span> </li> + <li> + <a href="internals/command.html">Spawning commands</a> + <span>Spawning commands from libvirt driver code</span> + </li> </ul> </li> <li> diff --git a/docs/subsite.xsl b/docs/subsite.xsl new file mode 100644 index 0000000..108d0d8 --- /dev/null +++ b/docs/subsite.xsl @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<xsl:stylesheet + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="xsl exsl" + version="1.0"> + + <xsl:import href="page.xsl"/> + + <xsl:output + method="xml" + encoding="UTF-8" + indent="yes" + doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" + doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/> + + <xsl:variable name="href_base" select="'../'"/> + + <xsl:template match="/"> + <xsl:apply-templates select="." mode="page"> + <xsl:with-param name="pagename" select="$pagename"/> + </xsl:apply-templates> + </xsl:template> + +</xsl:stylesheet> -- 1.7.3.2 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list