Files
libguestfs/lib/command.c
Richard W.M. Jones f2fe422909 lib: command: If command fails, exit with 126 or 127 as defined by POSIX.
If running the external command fails in "argv mode" (ie. when
not using the shell), then exit with either 126 or 127 as defined
by POSIX.

This is mostly the same as what bash does, see
execute_cmd.c:shell_execve in the bash sources.

Note: saving errno around perror(3) if necessary, otherwise you will
see different behaviour between verbose and non-verbose mode.  In
non-verbose mode, perror(3) tried to print to a closed file
descriptor, failing and overwriting errno.  That took some time to
debug.
2017-11-16 12:55:19 +00:00

1062 lines
25 KiB
C

/* 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
*/
/**
* A wrapper for running external commands, loosely based on libvirt's
* C<virCommand> interface.
*
* In outline to use this interface you must:
*
* =over 4
*
* =item 1.
*
* Create a new command handle:
*
* struct command *cmd;
* cmd = guestfs_int_new_command (g);
*
* =item 2.
*
* I<Either> add arguments:
*
* guestfs_int_cmd_add_arg (cmd, "qemu-img");
* guestfs_int_cmd_add_arg (cmd, "info");
* guestfs_int_cmd_add_arg (cmd, filename);
*
* (B<NB:> You don't need to add a C<NULL> argument at the end.)
*
* =item 3.
*
* I<Or> construct a command using a mix of quoted and unquoted
* strings. (This is useful for L<system(3)>/C<popen("r")>-style
* shell commands, with the added safety of allowing args to be quoted
* properly).
*
* guestfs_int_cmd_add_string_unquoted (cmd, "qemu-img info ");
* guestfs_int_cmd_add_string_quoted (cmd, filename);
*
* =item 4.
*
* Set various flags, such as whether you want to capture
* errors in the regular libguestfs error log.
*
* =item 5.
*
* Run the command. This is what does the L<fork(2)> call, optionally
* loops over the output, and then does a L<waitpid(3)> and returns the
* exit status of the command.
*
* r = guestfs_int_cmd_run (cmd);
* if (r == -1)
* // error
* // else test r using the WIF* functions
*
* =item 6.
*
* Close the handle:
*
* guestfs_int_cmd_close (cmd);
*
* (or use C<CLEANUP_CMD_CLOSE>).
*
* =back
*/
#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/select.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#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 child_rlimits {
struct child_rlimits *next;
int resource;
long limit;
};
struct command
{
guestfs_h *g;
enum command_style style;
union {
/* COMMAND_STYLE_EXECV */
struct stringsbuf 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;
/* When using the pipe_* APIs, stderr is pointed to a temporary file. */
char *error_file;
/* Close file descriptors (defaults to true). */
bool close_files;
/* 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;
/* Optional child setup callback. */
cmd_child_callback child_callback;
void *child_callback_data;
/* Optional child limits. */
struct child_rlimits *child_rlimits;
};
/**
* Create a new command handle.
*/
struct command *
guestfs_int_new_command (guestfs_h *g)
{
struct command *cmd;
cmd = safe_calloc (g, 1, sizeof *cmd);
cmd->g = g;
cmd->capture_errors = true;
cmd->close_files = true;
cmd->errorfd = -1;
cmd->outfd = -1;
return cmd;
}
static void
add_arg_no_strdup (struct command *cmd, char *arg)
{
assert (cmd->style != COMMAND_STYLE_SYSTEM);
cmd->style = COMMAND_STYLE_EXECV;
guestfs_int_add_string_nodup (cmd->g, &cmd->argv, arg);
}
static void
add_arg (struct command *cmd, const char *arg)
{
assert (arg != NULL);
add_arg_no_strdup (cmd, safe_strdup (cmd->g, arg));
}
/**
* Add single arg (for C<execv>-style command execution).
*/
void
guestfs_int_cmd_add_arg (struct command *cmd, const char *arg)
{
add_arg (cmd, arg);
}
/**
* Add single arg (for C<execv>-style command execution)
* using a L<printf(3)>-style format string.
*/
void
guestfs_int_cmd_add_arg_format (struct command *cmd, const char *fs, ...)
{
va_list args;
char *arg;
int err;
va_start (args, fs);
err = vasprintf (&arg, fs, args);
va_end (args);
if (err < 0)
cmd->g->abort_cb ();
add_arg_no_strdup (cmd, arg);
}
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;
}
/**
* Add a string (for L<system(3)>-style command execution).
*
* This variant adds the strings without quoting them, which is
* dangerous if the string contains untrusted content.
*/
void
guestfs_int_cmd_add_string_unquoted (struct command *cmd, const char *str)
{
add_string (cmd, str, strlen (str));
}
/**
* Add a string (for L<system(3)>-style command execution).
*
* The string is 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 L<system(3)>-style command
* string.
*/
void
guestfs_int_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 C<CMD_STDOUT_FLAG_LINE_BUFFER> (the default),
* then the callback is called line by line on the output. If there
* is a trailing C<\n> then it is automatically removed before the
* callback is called. The line buffer is C<\0>-terminated.
*
* If flags contains C<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 I<not> C<\0>-terminated, so you need to may
* attention to the length field in the callback.
*
* If flags contains C<CMD_STDOUT_FLAG_WHOLE_BUFFER>, then the
* callback is called exactly once, with the entire buffer. Note in
* this case the buffer is I<not> C<\0>-terminated, so you need to may
* attention to the length field in the callback.
*/
void
guestfs_int_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 C<2E<gt>&1> to the end of the command. This
* is incompatible with the C<capture_errors> flag, because it doesn't
* make sense to combine them.
*/
void
guestfs_int_cmd_set_stderr_to_stdout (struct command *cmd)
{
cmd->stderr_to_stdout = true;
}
/**
* Clear the C<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_int_cmd_clear_capture_errors (struct command *cmd)
{
cmd->capture_errors = false;
}
/**
* Don't close file descriptors after the fork.
*
* XXX Should allow single fds to be sent to child process.
*/
void
guestfs_int_cmd_clear_close_files (struct command *cmd)
{
cmd->close_files = false;
}
/**
* Set a function to be executed in the child, right before the
* execution. Can be used to setup the child, for example changing
* its current directory.
*/
void
guestfs_int_cmd_set_child_callback (struct command *cmd,
cmd_child_callback child_callback,
void *data)
{
cmd->child_callback = child_callback;
cmd->child_callback_data = data;
}
/**
* Set up child rlimits, in case the process we are running could
* consume lots of space or time.
*/
void
guestfs_int_cmd_set_child_rlimit (struct command *cmd, int resource, long limit)
{
struct child_rlimits *p;
p = safe_malloc (cmd->g, sizeof *p);
p->resource = resource;
p->limit = limit;
p->next = cmd->child_rlimits;
cmd->child_rlimits = p;
}
/**
* Finish off the command by either C<NULL>-terminating the argv array
* or adding a terminating C<\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:
guestfs_int_end_stringsbuf (cmd->g, &cmd->argv);
break;
case COMMAND_STYLE_SYSTEM:
add_string (cmd, "\0", 1);
break;
case COMMAND_STYLE_NOT_SELECTED:
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.argv[0]);
last = cmd->argv.size-1; /* omit final NULL pointer */
for (i = 1; i < last; ++i) {
if (i < last-1 &&
cmd->argv.argv[i][0] == '-' && cmd->argv.argv[i+1][0] != '-') {
debug (cmd->g, "command: run: \\ %s %s",
cmd->argv.argv[i], cmd->argv.argv[i+1]);
i++;
}
else
debug (cmd->g, "command: run: \\ %s", cmd->argv.argv[i]);
}
break;
case COMMAND_STYLE_SYSTEM:
debug (cmd->g, "command: run: %s", cmd->string.str);
break;
case COMMAND_STYLE_NOT_SELECTED:
abort ();
}
}
static void run_child (struct command *cmd) __attribute__((noreturn));
static int
run_command (struct command *cmd)
{
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);
run_child (cmd);
/*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;
}
static void
run_child (struct command *cmd)
{
struct sigaction sa;
int i, err, fd, max_fd, r;
char status_string[80];
#ifdef HAVE_SETRLIMIT
struct child_rlimits *child_rlimit;
struct rlimit rlimit;
#endif
/* 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);
if (cmd->close_files) {
/* 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);
if (cmd->child_callback) {
if (cmd->child_callback (cmd->g, cmd->child_callback_data) == -1)
_exit (EXIT_FAILURE);
}
#ifdef HAVE_SETRLIMIT
for (child_rlimit = cmd->child_rlimits;
child_rlimit != NULL;
child_rlimit = child_rlimit->next) {
rlimit.rlim_cur = rlimit.rlim_max = child_rlimit->limit;
if (setrlimit (child_rlimit->resource, &rlimit) == -1) {
/* EPERM means we're trying to raise the limit (ie. the limit is
* already more restrictive than what we want), so ignore it.
*/
if (errno != EPERM) {
perror ("setrlimit");
_exit (EXIT_FAILURE);
}
}
}
#endif /* HAVE_SETRLIMIT */
/* NB: If the main process (which we have forked a copy of) uses
* more heap than the RLIMIT_AS we set above, then any call to
* malloc or any extension of the stack will fail with ENOMEM or
* SIGSEGV respectively. Luckily we only use RLIMIT_AS followed by
* execvp below, so we get away with it, but adding any code here
* could cause a failure.
*
* There is a regression test for this. See:
* tests/regressions/test-big-heap.c
*/
/* Run the command. */
switch (cmd->style) {
case COMMAND_STYLE_EXECV:
execvp (cmd->argv.argv[0], cmd->argv.argv);
err = errno;
perror (cmd->argv.argv[0]);
/* These error codes are defined in POSIX and meant to be the
* same as the shell.
*/
_exit (err == ENOENT ? 127 : 126);
case COMMAND_STYLE_SYSTEM:
r = system (cmd->string.str);
if (r == -1) {
perror ("system");
_exit (EXIT_FAILURE);
}
if (WIFEXITED (r))
_exit (WEXITSTATUS (r));
fprintf (stderr, "%s\n",
guestfs_int_exit_status_to_string (r, cmd->string.str,
status_string,
sizeof status_string));
_exit (EXIT_FAILURE);
case COMMAND_STYLE_NOT_SELECTED:
abort ();
}
/*NOTREACHED*/
abort ();
}
/**
* 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;
CLEANUP_FREE char *buf = safe_malloc (cmd->g, 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, BUFSIZ);
if (n > 0)
guestfs_int_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, BUFSIZ);
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 (guestfs_int_waitpid (cmd->g, cmd->pid, &status, "command") == -1)
return -1;
cmd->pid = 0;
return status;
}
/**
* Fork, run the command, loop over the output, and waitpid.
*
* Returns the exit status. Test it using C<WIF*> macros.
*
* On error: Calls C<error> and returns C<-1>.
*/
int
guestfs_int_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);
}
/**
* Fork and run the command, but don't wait. Roughly equivalent to
* S<C<popen (..., "r"|"w")>>.
*
* Returns the file descriptor of the pipe, connected to stdout
* (C<"r">) or stdin (C<"w">) of the child process.
*
* After reading/writing to this pipe, call
* C<guestfs_int_cmd_pipe_wait> to wait for the status of the child.
*
* Errors from the subcommand cannot be captured to the error log
* using this interface. Instead the caller should call
* C<guestfs_int_cmd_get_pipe_errors> (after
* C<guestfs_int_cmd_pipe_wait> returns an error).
*/
int
guestfs_int_cmd_pipe_run (struct command *cmd, const char *mode)
{
int fd[2] = { -1, -1 };
int errfd = -1;
int r_mode;
int ret;
finish_command (cmd);
if (cmd->g->verbose)
debug_command (cmd);
/* Various options cannot be used here. */
assert (!cmd->capture_errors);
assert (!cmd->stdout_callback);
assert (!cmd->stderr_to_stdout);
if (STREQ (mode, "r")) r_mode = 1;
else if (STREQ (mode, "w")) r_mode = 0;
else abort ();
if (pipe2 (fd, O_CLOEXEC) == -1) {
perrorf (cmd->g, "pipe2");
goto error;
}
/* We can't easily capture errors from the child process, so instead
* we write them into a temporary file and provide a separate
* function for the caller to read the error messages.
*/
cmd->error_file = guestfs_int_make_temp_path (cmd->g, "cmderr", "txt");
if (!cmd->error_file)
goto error;
errfd = open (cmd->error_file,
O_WRONLY|O_CREAT|O_NOCTTY|O_TRUNC|O_CLOEXEC, 0600);
if (errfd == -1) {
perrorf (cmd->g, "open: %s", cmd->error_file);
goto error;
}
cmd->pid = fork ();
if (cmd->pid == -1) {
perrorf (cmd->g, "fork");
goto error;
}
/* Parent. */
if (cmd->pid > 0) {
close (errfd);
errfd = -1;
if (r_mode) {
close (fd[1]);
ret = fd[0];
}
else {
close (fd[0]);
ret = fd[1];
}
return ret;
}
/* Child. */
dup2 (errfd, 2);
close (errfd);
if (r_mode) {
close (fd[0]);
dup2 (fd[1], 1);
close (fd[1]);
}
else {
close (fd[1]);
dup2 (fd[0], 0);
close (fd[0]);
}
run_child (cmd);
/*NOTREACHED*/
error:
if (errfd >= 0)
close (errfd);
if (fd[0] >= 0)
close (fd[0]);
if (fd[1] >= 0)
close (fd[1]);
return -1;
}
/**
* Wait for a subprocess created by C<guestfs_int_cmd_pipe_run> to
* finish. On error (eg. failed syscall) this returns C<-1> and sets
* the error. If the subcommand fails, then use C<WIF*> macros to
* check this, and call C<guestfs_int_cmd_get_pipe_errors> to read
* the error messages printed by the child.
*/
int
guestfs_int_cmd_pipe_wait (struct command *cmd)
{
return wait_command (cmd);
}
/**
* Read the error messages printed by the child. The caller must free
* the returned buffer after use.
*/
char *
guestfs_int_cmd_get_pipe_errors (struct command *cmd)
{
char *ret;
size_t len;
assert (cmd->error_file != NULL);
if (guestfs_int_read_whole_file (cmd->g, cmd->error_file, &ret, NULL) == -1)
return NULL;
/* If the file ends with \n characters, trim them. */
len = strlen (ret);
while (len > 0 && ret[len-1] == '\n') {
ret[len-1] = '\0';
len--;
}
return ret;
}
/**
* Close the C<cmd> object and free all resources.
*/
void
guestfs_int_cmd_close (struct command *cmd)
{
struct child_rlimits *child_rlimit, *child_rlimit_next;
if (!cmd)
return;
switch (cmd->style) {
case COMMAND_STYLE_NOT_SELECTED:
/* nothing */
break;
case COMMAND_STYLE_EXECV:
guestfs_int_free_stringsbuf (&cmd->argv);
break;
case COMMAND_STYLE_SYSTEM:
free (cmd->string.str);
break;
}
if (cmd->error_file != NULL) {
unlink (cmd->error_file);
free (cmd->error_file);
}
if (cmd->errorfd >= 0)
close (cmd->errorfd);
if (cmd->outfd >= 0)
close (cmd->outfd);
free (cmd->outbuf.buffer);
if (cmd->pid > 0)
guestfs_int_waitpid_noerror (cmd->pid);
for (child_rlimit = cmd->child_rlimits; child_rlimit != NULL;
child_rlimit = child_rlimit_next) {
child_rlimit_next = child_rlimit->next;
free (child_rlimit);
}
free (cmd);
}
void
guestfs_int_cleanup_cmd_close (struct command **ptr)
{
guestfs_int_cmd_close (*ptr);
}
/**
* 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);
}