mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
lib: Add a new 'command' mini-library for running external commands.
This is a wrapper or mini-library for running external command, loosely based on libvirt's virCommand interface. Amongst the advantages are: - Can redirect errors into the error log (RHBZ#713678). - Can redirect output into a callback function. - Handles shell quoting properly. - Safely resets signal handlers, closes file descriptors, etc. - Single place where we can implement other improvements in future.
This commit is contained in:
@@ -218,6 +218,7 @@ ruby/ext/guestfs/_guestfs.c
|
||||
src/actions.c
|
||||
src/appliance.c
|
||||
src/bindtests.c
|
||||
src/command.c
|
||||
src/dbdump.c
|
||||
src/errnostring-gperf.c
|
||||
src/errnostring.c
|
||||
|
||||
@@ -125,6 +125,7 @@ libguestfs_la_SOURCES = \
|
||||
actions.c \
|
||||
appliance.c \
|
||||
bindtests.c \
|
||||
command.c \
|
||||
dbdump.c \
|
||||
events.c \
|
||||
file.c \
|
||||
|
||||
752
src/command.c
Normal file
752
src/command.c
Normal file
@@ -0,0 +1,752 @@
|
||||
/* libguestfs
|
||||
* Copyright (C) 2010-2012 Red Hat Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* Wrapper for running external command, loosely based on libvirt's
|
||||
* virCommand interface. Read the comments at the top of each
|
||||
* function for detailed information on how to use this interface. In
|
||||
* outline what you have to do is:
|
||||
*
|
||||
* (1) Create a new command handle:
|
||||
*
|
||||
* struct command *cmd;
|
||||
* cmd = guestfs___new_command (g);
|
||||
*
|
||||
* (2) EITHER add arguments:
|
||||
*
|
||||
* guestfs___cmd_add_arg (cmd, "qemu-img");
|
||||
* guestfs___cmd_add_arg (cmd, "info");
|
||||
* guestfs___cmd_add_arg (cmd, filename);
|
||||
*
|
||||
* NB: You don't need to add a NULL argument at the end.
|
||||
*
|
||||
* (3) OR construct a command using a mix of quoted and unquoted
|
||||
* strings. (This is useful for system(3)/popen("r")-style shell
|
||||
* commands, with the added safety of allowing args to be quoted
|
||||
* properly).
|
||||
*
|
||||
* guestfs___cmd_add_string_unquoted (cmd, "qemu-img info ");
|
||||
* guestfs___cmd_add_string_quoted (cmd, filename);
|
||||
*
|
||||
* (4) Set various flags, such as whether you want to capture
|
||||
* errors in the regular libguestfs error log.
|
||||
*
|
||||
* (5) Run the command. This is what does the fork call, optionally
|
||||
* loops over the output, and then does a waitpid and returns the
|
||||
* exit status of the command.
|
||||
*
|
||||
* r = guestfs___cmd_run (cmd);
|
||||
* if (r == -1)
|
||||
* // error
|
||||
* // else test r using the WIF* functions
|
||||
*
|
||||
* (6) Close the handle:
|
||||
*
|
||||
* guestfs___cmd_close (cmd);
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#include "guestfs.h"
|
||||
#include "guestfs-internal.h"
|
||||
|
||||
enum command_style {
|
||||
COMMAND_STYLE_NOT_SELECTED = 0,
|
||||
COMMAND_STYLE_EXECV = 1,
|
||||
COMMAND_STYLE_SYSTEM = 2
|
||||
};
|
||||
|
||||
struct command;
|
||||
|
||||
static void add_line_buffer (struct command *cmd, const char *buf, size_t len);
|
||||
static void close_line_buffer (struct command *cmd);
|
||||
static void add_unbuffered (struct command *cmd, const char *buf, size_t len);
|
||||
static void add_whole_buffer (struct command *cmd, const char *buf, size_t len);
|
||||
static void close_whole_buffer (struct command *cmd);
|
||||
|
||||
struct buffering {
|
||||
char *buffer;
|
||||
size_t len;
|
||||
void (*add_data) (struct command *cmd, const char *buf, size_t len);
|
||||
void (*close_data) (struct command *cmd);
|
||||
};
|
||||
|
||||
struct command
|
||||
{
|
||||
guestfs_h *g;
|
||||
|
||||
enum command_style style;
|
||||
union {
|
||||
/* COMMAND_STYLE_EXECV */
|
||||
struct {
|
||||
char **args;
|
||||
size_t len, alloc;
|
||||
} argv;
|
||||
/* COMMAND_STYLE_SYSTEM */
|
||||
struct {
|
||||
char *str;
|
||||
size_t len, alloc;
|
||||
} string;
|
||||
};
|
||||
|
||||
/* Capture errors to the error log (defaults to true). */
|
||||
bool capture_errors;
|
||||
int errorfd;
|
||||
|
||||
/* Supply a callback to receive stdout. */
|
||||
cmd_stdout_callback stdout_callback;
|
||||
void *stdout_data;
|
||||
int outfd;
|
||||
struct buffering outbuf;
|
||||
|
||||
/* For programs that send output to stderr. Hello qemu. */
|
||||
bool stderr_to_stdout;
|
||||
|
||||
/* PID of subprocess (if > 0). */
|
||||
pid_t pid;
|
||||
};
|
||||
|
||||
/* Create a new command handle. */
|
||||
struct command *
|
||||
guestfs___new_command (guestfs_h *g)
|
||||
{
|
||||
struct command *cmd;
|
||||
|
||||
cmd = safe_calloc (g, 1, sizeof *cmd);
|
||||
cmd->g = g;
|
||||
cmd->capture_errors = true;
|
||||
cmd->errorfd = -1;
|
||||
cmd->outfd = -1;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/* Add single arg (for execv-style command execution). */
|
||||
static void
|
||||
add_arg_no_strdup (struct command *cmd, char *arg)
|
||||
{
|
||||
assert (cmd->style != COMMAND_STYLE_SYSTEM);
|
||||
cmd->style = COMMAND_STYLE_EXECV;
|
||||
|
||||
if (cmd->argv.len >= cmd->argv.alloc) {
|
||||
if (cmd->argv.alloc == 0)
|
||||
cmd->argv.alloc = 16;
|
||||
else
|
||||
cmd->argv.alloc *= 2;
|
||||
cmd->argv.args = safe_realloc (cmd->g, cmd->argv.args,
|
||||
cmd->argv.alloc * sizeof (char *));
|
||||
}
|
||||
cmd->argv.args[cmd->argv.len] = arg;
|
||||
cmd->argv.len++;
|
||||
}
|
||||
|
||||
static void
|
||||
add_arg (struct command *cmd, const char *arg)
|
||||
{
|
||||
assert (arg != NULL);
|
||||
add_arg_no_strdup (cmd, safe_strdup (cmd->g, arg));
|
||||
}
|
||||
|
||||
void
|
||||
guestfs___cmd_add_arg (struct command *cmd, const char *arg)
|
||||
{
|
||||
add_arg (cmd, arg);
|
||||
}
|
||||
|
||||
void
|
||||
guestfs___cmd_add_arg_format (struct command *cmd, const char *fs, ...)
|
||||
{
|
||||
va_list args;
|
||||
char *arg;
|
||||
|
||||
va_start (args, fs);
|
||||
int err = vasprintf (&arg, fs, args);
|
||||
va_end (args);
|
||||
|
||||
if (err < 0)
|
||||
cmd->g->abort_cb ();
|
||||
|
||||
add_arg_no_strdup (cmd, arg);
|
||||
}
|
||||
|
||||
/* Add strings (for system(3)-style command execution). */
|
||||
static void
|
||||
add_string (struct command *cmd, const char *str, size_t len)
|
||||
{
|
||||
assert (cmd->style != COMMAND_STYLE_EXECV);
|
||||
cmd->style = COMMAND_STYLE_SYSTEM;
|
||||
|
||||
if (cmd->string.len >= cmd->string.alloc) {
|
||||
if (cmd->string.alloc == 0)
|
||||
cmd->string.alloc = 256;
|
||||
else
|
||||
cmd->string.alloc += MAX (cmd->string.alloc, len);
|
||||
cmd->string.str = safe_realloc (cmd->g, cmd->string.str, cmd->string.alloc);
|
||||
}
|
||||
|
||||
memcpy (&cmd->string.str[cmd->string.len], str, len);
|
||||
cmd->string.len += len;
|
||||
}
|
||||
|
||||
void
|
||||
guestfs___cmd_add_string_unquoted (struct command *cmd, const char *str)
|
||||
{
|
||||
add_string (cmd, str, strlen (str));
|
||||
}
|
||||
|
||||
/* Add a string enclosed in double quotes, with any special characters
|
||||
* within the string which need escaping done. This is used to add a
|
||||
* single argument to a system(3)-style command string.
|
||||
*/
|
||||
void
|
||||
guestfs___cmd_add_string_quoted (struct command *cmd, const char *str)
|
||||
{
|
||||
add_string (cmd, "\"", 1);
|
||||
|
||||
for (; *str; str++) {
|
||||
if (*str == '$' ||
|
||||
*str == '`' ||
|
||||
*str == '\\' ||
|
||||
*str == '"')
|
||||
add_string (cmd, "\\", 1);
|
||||
add_string (cmd, str, 1);
|
||||
}
|
||||
|
||||
add_string (cmd, "\"", 1);
|
||||
}
|
||||
|
||||
/* Set a callback which will capture stdout.
|
||||
*
|
||||
* If flags contains CMD_STDOUT_FLAG_LINE_BUFFER (the default), then
|
||||
* the callback is called line by line on the output. If there is a
|
||||
* trailing \n then it is automatically removed before the callback is
|
||||
* called. The line buffer is \0-terminated.
|
||||
*
|
||||
* If flags contains CMD_STDOUT_FLAG_UNBUFFERED, then buffers are
|
||||
* passed to the callback as it is received from the command. Note in
|
||||
* this case the buffer is NOT \0-terminated, so you need to may
|
||||
* attention to the length field in the callback.
|
||||
*
|
||||
* If flags contains CMD_STDOUT_FLAG_WHOLE_BUFFER, then the callback
|
||||
* is called exactly once, with the entire buffer. Note in this case
|
||||
* the buffer is NOT \0-terminated, so you need to may attention to
|
||||
* the length field in the callback.
|
||||
*/
|
||||
void
|
||||
guestfs___cmd_set_stdout_callback (struct command *cmd,
|
||||
cmd_stdout_callback stdout_callback,
|
||||
void *stdout_data, unsigned flags)
|
||||
{
|
||||
cmd->stdout_callback = stdout_callback;
|
||||
cmd->stdout_data = stdout_data;
|
||||
|
||||
/* Buffering mode. */
|
||||
if ((flags & 3) == CMD_STDOUT_FLAG_LINE_BUFFER) {
|
||||
cmd->outbuf.add_data = add_line_buffer;
|
||||
cmd->outbuf.close_data = close_line_buffer;
|
||||
}
|
||||
else if ((flags & 3) == CMD_STDOUT_FLAG_UNBUFFERED) {
|
||||
cmd->outbuf.add_data = add_unbuffered;
|
||||
cmd->outbuf.close_data = NULL;
|
||||
}
|
||||
else if ((flags & 3) == CMD_STDOUT_FLAG_WHOLE_BUFFER) {
|
||||
cmd->outbuf.add_data = add_whole_buffer;
|
||||
cmd->outbuf.close_data = close_whole_buffer;
|
||||
}
|
||||
else
|
||||
abort ();
|
||||
}
|
||||
|
||||
/* Equivalent to adding 2>&1 to the end of the command. This is
|
||||
* incompatible with the capture_errors flag, because it doesn't make
|
||||
* sense to combine them.
|
||||
*/
|
||||
void
|
||||
guestfs___cmd_set_stderr_to_stdout (struct command *cmd)
|
||||
{
|
||||
cmd->stderr_to_stdout = true;
|
||||
}
|
||||
|
||||
/* Clear the capture_errors flag. This means that any errors will go
|
||||
* to stderr, instead of being captured in the event log, and that is
|
||||
* usually undesirable.
|
||||
*/
|
||||
void
|
||||
guestfs___cmd_clear_capture_errors (struct command *cmd)
|
||||
{
|
||||
cmd->capture_errors = false;
|
||||
}
|
||||
|
||||
/* Finish off the command by either NULL-terminating the argv array or
|
||||
* adding a terminating \0 to the string, or die with an internal
|
||||
* error if no command has been added.
|
||||
*/
|
||||
static void
|
||||
finish_command (struct command *cmd)
|
||||
{
|
||||
switch (cmd->style) {
|
||||
case COMMAND_STYLE_EXECV:
|
||||
add_arg_no_strdup (cmd, NULL);
|
||||
break;
|
||||
|
||||
case COMMAND_STYLE_SYSTEM:
|
||||
add_string (cmd, "\0", 1);
|
||||
break;
|
||||
|
||||
case COMMAND_STYLE_NOT_SELECTED:
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
debug_command (struct command *cmd)
|
||||
{
|
||||
size_t i, last;
|
||||
|
||||
switch (cmd->style) {
|
||||
case COMMAND_STYLE_EXECV:
|
||||
debug (cmd->g, "command: run: %s", cmd->argv.args[0]);
|
||||
last = cmd->argv.len-1; /* omit final NULL pointer */
|
||||
for (i = 1; i < last; ++i) {
|
||||
if (i < last-1 &&
|
||||
cmd->argv.args[i][0] == '-' && cmd->argv.args[i+1][0] != '-') {
|
||||
debug (cmd->g, "command: run: \\ %s %s",
|
||||
cmd->argv.args[i], cmd->argv.args[i+1]);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
debug (cmd->g, "command: run: \\ %s", cmd->argv.args[i]);
|
||||
}
|
||||
break;
|
||||
|
||||
case COMMAND_STYLE_SYSTEM:
|
||||
debug (cmd->g, "command: run: %s", cmd->string.str);
|
||||
break;
|
||||
|
||||
case COMMAND_STYLE_NOT_SELECTED:
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
run_command (struct command *cmd)
|
||||
{
|
||||
struct sigaction sa;
|
||||
int i, fd, max_fd, r;
|
||||
int errorfd[2] = { -1, -1 };
|
||||
int outfd[2] = { -1, -1 };
|
||||
|
||||
/* Set up a pipe to capture command output and send it to the error log. */
|
||||
if (cmd->capture_errors) {
|
||||
if (pipe2 (errorfd, O_CLOEXEC) == -1) {
|
||||
perrorf (cmd->g, "pipe2");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set up a pipe to capture stdout for the callback. */
|
||||
if (cmd->stdout_callback) {
|
||||
if (pipe2 (outfd, O_CLOEXEC) == -1) {
|
||||
perrorf (cmd->g, "pipe2");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
cmd->pid = fork ();
|
||||
if (cmd->pid == -1) {
|
||||
perrorf (cmd->g, "fork");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* In parent, return to caller. */
|
||||
if (cmd->pid > 0) {
|
||||
if (cmd->capture_errors) {
|
||||
close (errorfd[1]);
|
||||
errorfd[1] = -1;
|
||||
cmd->errorfd = errorfd[0];
|
||||
errorfd[0] = -1;
|
||||
}
|
||||
|
||||
if (cmd->stdout_callback) {
|
||||
close (outfd[1]);
|
||||
outfd[1] = -1;
|
||||
cmd->outfd = outfd[0];
|
||||
outfd[0] = -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Child process. */
|
||||
if (cmd->capture_errors) {
|
||||
close (errorfd[0]);
|
||||
if (!cmd->stdout_callback)
|
||||
dup2 (errorfd[1], 1);
|
||||
dup2 (errorfd[1], 2);
|
||||
close (errorfd[1]);
|
||||
}
|
||||
|
||||
if (cmd->stdout_callback) {
|
||||
close (outfd[0]);
|
||||
dup2 (outfd[1], 1);
|
||||
close (outfd[1]);
|
||||
}
|
||||
|
||||
if (cmd->stderr_to_stdout)
|
||||
dup2 (1, 2);
|
||||
|
||||
/* Remove all signal handlers. See the justification here:
|
||||
* https://www.redhat.com/archives/libvir-list/2008-August/msg00303.html
|
||||
* We don't mask signal handlers yet, so this isn't completely
|
||||
* race-free, but better than not doing it at all.
|
||||
*/
|
||||
memset (&sa, 0, sizeof sa);
|
||||
sa.sa_handler = SIG_DFL;
|
||||
sa.sa_flags = 0;
|
||||
sigemptyset (&sa.sa_mask);
|
||||
for (i = 1; i < NSIG; ++i)
|
||||
sigaction (i, &sa, NULL);
|
||||
|
||||
/* Close all other file descriptors. This ensures that we don't
|
||||
* hold open (eg) pipes from the parent process.
|
||||
*/
|
||||
max_fd = sysconf (_SC_OPEN_MAX);
|
||||
if (max_fd == -1)
|
||||
max_fd = 1024;
|
||||
if (max_fd > 65536)
|
||||
max_fd = 65536; /* bound the amount of work we do here */
|
||||
for (fd = 3; fd < max_fd; ++fd)
|
||||
close (fd);
|
||||
|
||||
/* Clean up the environment. */
|
||||
setenv ("LC_ALL", "C", 1);
|
||||
|
||||
/* Set the umask for all subcommands to something sensible (RHBZ#610880). */
|
||||
umask (022);
|
||||
|
||||
/* Run the command. */
|
||||
switch (cmd->style) {
|
||||
case COMMAND_STYLE_EXECV:
|
||||
execvp (cmd->argv.args[0], cmd->argv.args);
|
||||
perror (cmd->argv.args[0]);
|
||||
_exit (EXIT_FAILURE);
|
||||
|
||||
case COMMAND_STYLE_SYSTEM:
|
||||
r = system (cmd->string.str);
|
||||
if (r == -1) {
|
||||
perror ("system");
|
||||
_exit (EXIT_FAILURE);
|
||||
}
|
||||
if (WIFEXITED (r))
|
||||
_exit (WEXITSTATUS (r));
|
||||
if (WIFSIGNALED (r)) {
|
||||
fprintf (stderr, "%s: received signal %d\n", cmd->string.str,
|
||||
WTERMSIG (r));
|
||||
_exit (EXIT_FAILURE);
|
||||
}
|
||||
if (WIFSTOPPED (r)) {
|
||||
fprintf (stderr, "%s: stopped by signal %d\n", cmd->string.str,
|
||||
WSTOPSIG (r));
|
||||
_exit (EXIT_FAILURE);
|
||||
}
|
||||
_exit (EXIT_FAILURE);
|
||||
|
||||
case COMMAND_STYLE_NOT_SELECTED:
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
/*NOTREACHED*/
|
||||
|
||||
error:
|
||||
if (errorfd[0] >= 0)
|
||||
close (errorfd[0]);
|
||||
if (errorfd[1] >= 0)
|
||||
close (errorfd[1]);
|
||||
if (outfd[0] >= 0)
|
||||
close (outfd[0]);
|
||||
if (outfd[1] >= 0)
|
||||
close (outfd[1]);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* The loop which reads errors and output and directs it either
|
||||
* to the log or to the stdout callback as appropriate.
|
||||
*/
|
||||
static int
|
||||
loop (struct command *cmd)
|
||||
{
|
||||
fd_set rset, rset2;
|
||||
int maxfd = -1, r;
|
||||
size_t nr_fds = 0;
|
||||
char buf[BUFSIZ];
|
||||
ssize_t n;
|
||||
|
||||
FD_ZERO (&rset);
|
||||
|
||||
if (cmd->errorfd >= 0) {
|
||||
FD_SET (cmd->errorfd, &rset);
|
||||
maxfd = MAX (cmd->errorfd, maxfd);
|
||||
nr_fds++;
|
||||
}
|
||||
|
||||
if (cmd->outfd >= 0) {
|
||||
FD_SET (cmd->outfd, &rset);
|
||||
maxfd = MAX (cmd->outfd, maxfd);
|
||||
nr_fds++;
|
||||
}
|
||||
|
||||
while (nr_fds > 0) {
|
||||
rset2 = rset;
|
||||
r = select (maxfd+1, &rset2, NULL, NULL, NULL);
|
||||
if (r == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
perrorf (cmd->g, "select");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cmd->errorfd >= 0 && FD_ISSET (cmd->errorfd, &rset2)) {
|
||||
/* Read output and send it to the log. */
|
||||
n = read (cmd->errorfd, buf, sizeof buf);
|
||||
if (n > 0)
|
||||
guestfs___call_callbacks_message (cmd->g, GUESTFS_EVENT_APPLIANCE,
|
||||
buf, n);
|
||||
else if (n == 0) {
|
||||
if (close (cmd->errorfd) == -1)
|
||||
perrorf (cmd->g, "close: errorfd");
|
||||
FD_CLR (cmd->errorfd, &rset);
|
||||
cmd->errorfd = -1;
|
||||
nr_fds--;
|
||||
}
|
||||
else if (n == -1) {
|
||||
perrorf (cmd->g, "read: errorfd");
|
||||
close (cmd->errorfd);
|
||||
FD_CLR (cmd->errorfd, &rset);
|
||||
cmd->errorfd = -1;
|
||||
nr_fds--;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd->outfd >= 0 && FD_ISSET (cmd->outfd, &rset2)) {
|
||||
/* Read the output, buffer it up to the end of the line, then
|
||||
* pass it to the callback.
|
||||
*/
|
||||
n = read (cmd->outfd, buf, sizeof buf);
|
||||
if (n > 0) {
|
||||
if (cmd->outbuf.add_data)
|
||||
cmd->outbuf.add_data (cmd, buf, n);
|
||||
}
|
||||
else if (n == 0) {
|
||||
if (cmd->outbuf.close_data)
|
||||
cmd->outbuf.close_data (cmd);
|
||||
if (close (cmd->outfd) == -1)
|
||||
perrorf (cmd->g, "close: outfd");
|
||||
FD_CLR (cmd->outfd, &rset);
|
||||
cmd->outfd = -1;
|
||||
nr_fds--;
|
||||
}
|
||||
else if (n == -1) {
|
||||
perrorf (cmd->g, "read: outfd");
|
||||
close (cmd->outfd);
|
||||
FD_CLR (cmd->outfd, &rset);
|
||||
cmd->outfd = -1;
|
||||
nr_fds--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
wait_command (struct command *cmd)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (waitpid (cmd->pid, &status, 0) == -1) {
|
||||
perrorf (cmd->g, "waitpid");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd->pid = 0;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Fork, run the command, loop over the output, and waitpid.
|
||||
*
|
||||
* Returns the exit status. Test it using WIF* macros.
|
||||
*
|
||||
* On error: Calls error(g) and returns -1.
|
||||
*/
|
||||
int
|
||||
guestfs___cmd_run (struct command *cmd)
|
||||
{
|
||||
finish_command (cmd);
|
||||
|
||||
if (cmd->g->verbose)
|
||||
debug_command (cmd);
|
||||
|
||||
if (run_command (cmd) == -1)
|
||||
return -1;
|
||||
|
||||
if (loop (cmd) == -1)
|
||||
return -1;
|
||||
|
||||
return wait_command (cmd);
|
||||
}
|
||||
|
||||
void
|
||||
guestfs___cmd_close (struct command *cmd)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
switch (cmd->style) {
|
||||
case COMMAND_STYLE_NOT_SELECTED:
|
||||
/* nothing */
|
||||
break;
|
||||
|
||||
case COMMAND_STYLE_EXECV:
|
||||
for (i = 0; i < cmd->argv.len; ++i)
|
||||
free (cmd->argv.args[i]);
|
||||
free (cmd->argv.args);
|
||||
break;
|
||||
|
||||
case COMMAND_STYLE_SYSTEM:
|
||||
free (cmd->string.str);
|
||||
break;
|
||||
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
|
||||
if (cmd->errorfd >= 0)
|
||||
close (cmd->errorfd);
|
||||
|
||||
if (cmd->outfd >= 0)
|
||||
close (cmd->outfd);
|
||||
|
||||
free (cmd->outbuf.buffer);
|
||||
|
||||
if (cmd->pid > 0)
|
||||
waitpid (cmd->pid, NULL, 0);
|
||||
|
||||
free (cmd);
|
||||
}
|
||||
|
||||
/* Deal with buffering stdout for the callback. */
|
||||
static void
|
||||
process_line_buffer (struct command *cmd, int closed)
|
||||
{
|
||||
guestfs_h *g = cmd->g;
|
||||
char *p;
|
||||
size_t len, newlen;
|
||||
|
||||
while (cmd->outbuf.len > 0) {
|
||||
/* Length of the next line. */
|
||||
p = strchr (cmd->outbuf.buffer, '\n');
|
||||
if (p != NULL) { /* Got a whole line. */
|
||||
len = p - cmd->outbuf.buffer;
|
||||
newlen = cmd->outbuf.len - len - 1;
|
||||
}
|
||||
else if (closed) { /* Consume rest of input even if no \n found. */
|
||||
len = cmd->outbuf.len;
|
||||
newlen = 0;
|
||||
}
|
||||
else /* Need to wait for more input. */
|
||||
break;
|
||||
|
||||
/* Call the callback with the next line. */
|
||||
cmd->outbuf.buffer[len] = '\0';
|
||||
cmd->stdout_callback (g, cmd->stdout_data, cmd->outbuf.buffer, len);
|
||||
|
||||
/* Remove the consumed line from the buffer. */
|
||||
cmd->outbuf.len = newlen;
|
||||
memmove (cmd->outbuf.buffer, cmd->outbuf.buffer + len + 1, newlen);
|
||||
|
||||
/* Keep the buffer \0 terminated. */
|
||||
cmd->outbuf.buffer[newlen] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_line_buffer (struct command *cmd, const char *buf, size_t len)
|
||||
{
|
||||
guestfs_h *g = cmd->g;
|
||||
size_t oldlen;
|
||||
|
||||
/* Append the new content to the end of the current buffer. Keep
|
||||
* the buffer \0 terminated to make things simple when processing
|
||||
* the buffer.
|
||||
*/
|
||||
oldlen = cmd->outbuf.len;
|
||||
cmd->outbuf.len += len;
|
||||
cmd->outbuf.buffer = safe_realloc (g, cmd->outbuf.buffer,
|
||||
cmd->outbuf.len + 1 /* for \0 */);
|
||||
memcpy (cmd->outbuf.buffer + oldlen, buf, len);
|
||||
cmd->outbuf.buffer[cmd->outbuf.len] = '\0';
|
||||
|
||||
process_line_buffer (cmd, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
close_line_buffer (struct command *cmd)
|
||||
{
|
||||
process_line_buffer (cmd, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
add_unbuffered (struct command *cmd, const char *buf, size_t len)
|
||||
{
|
||||
cmd->stdout_callback (cmd->g, cmd->stdout_data, buf, len);
|
||||
}
|
||||
|
||||
static void
|
||||
add_whole_buffer (struct command *cmd, const char *buf, size_t len)
|
||||
{
|
||||
guestfs_h *g = cmd->g;
|
||||
size_t oldlen;
|
||||
|
||||
/* Append the new content to the end of the current buffer. */
|
||||
oldlen = cmd->outbuf.len;
|
||||
cmd->outbuf.len += len;
|
||||
cmd->outbuf.buffer = safe_realloc (g, cmd->outbuf.buffer, cmd->outbuf.len);
|
||||
memcpy (cmd->outbuf.buffer + oldlen, buf, len);
|
||||
}
|
||||
|
||||
static void
|
||||
close_whole_buffer (struct command *cmd)
|
||||
{
|
||||
cmd->stdout_callback (cmd->g, cmd->stdout_data,
|
||||
cmd->outbuf.buffer, cmd->outbuf.len);
|
||||
}
|
||||
@@ -583,4 +583,22 @@ extern void guestfs___free_fuse (guestfs_h *g);
|
||||
extern virConnectPtr guestfs___open_libvirt_connection (guestfs_h *g, const char *uri, unsigned int flags);
|
||||
#endif
|
||||
|
||||
/* command.c */
|
||||
struct command;
|
||||
typedef void (*cmd_stdout_callback) (guestfs_h *g, void *data, const char *line, size_t len);
|
||||
extern struct command *guestfs___new_command (guestfs_h *g);
|
||||
extern void guestfs___cmd_add_arg (struct command *, const char *arg);
|
||||
extern void guestfs___cmd_add_arg_format (struct command *, const char *fs, ...)
|
||||
__attribute__((format (printf,2,3)));
|
||||
extern void guestfs___cmd_add_string_unquoted (struct command *, const char *str);
|
||||
extern void guestfs___cmd_add_string_quoted (struct command *, const char *str);
|
||||
extern void guestfs___cmd_set_stdout_callback (struct command *, cmd_stdout_callback stdout_callback, void *data, unsigned flags);
|
||||
#define CMD_STDOUT_FLAG_LINE_BUFFER 0
|
||||
#define CMD_STDOUT_FLAG_UNBUFFERED 1
|
||||
#define CMD_STDOUT_FLAG_WHOLE_BUFFER 2
|
||||
extern void guestfs___cmd_set_stderr_to_stdout (struct command *);
|
||||
extern void guestfs___cmd_clear_capture_errors (struct command *);
|
||||
extern int guestfs___cmd_run (struct command *);
|
||||
extern void guestfs___cmd_close (struct command *);
|
||||
|
||||
#endif /* GUESTFS_INTERNAL_H_ */
|
||||
|
||||
Reference in New Issue
Block a user