Files
libguestfs/daemon/command.c
Pino Toscano d2a36f8cbe daemon: improve debugging for "stdout on stderr" flag
When the COMMAND_FLAG_FOLD_STDOUT_ON_STDERR flag is passed to
command*(), indicate that as stdout=e in debugging message.
2016-01-26 11:21:46 +01:00

436 lines
11 KiB
C

/* libguestfs - the guestfsd daemon
* Copyright (C) 2009-2015 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <error.h>
#include <errno.h>
#include "ignore-value.h"
#include "guestfs-internal-all.h"
#include "command.h"
#include "cleanups.h"
extern int verbose;
extern const char *sysroot;
extern size_t sysroot_len;
/* For improved readability dealing with pipe arrays */
#define PIPE_READ 0
#define PIPE_WRITE 1
/* Easy ways to run external commands. For full documentation, see
* 'commandrvf' below.
*/
int
commandf (char **stdoutput, char **stderror, unsigned flags,
const char *name, ...)
{
va_list args;
/* NB: Mustn't free the strings which are on the stack. */
CLEANUP_FREE const char **argv = NULL;
char *s;
size_t i;
int r;
/* Collect the command line arguments into an array. */
i = 2;
argv = malloc (sizeof (char *) * i);
if (argv == NULL) {
perror ("malloc");
return -1;
}
argv[0] = (char *) name;
argv[1] = NULL;
va_start (args, name);
while ((s = va_arg (args, char *)) != NULL) {
const char **p = realloc (argv, sizeof (char *) * (++i));
if (p == NULL) {
perror ("realloc");
va_end (args);
return -1;
}
argv = p;
argv[i-2] = s;
argv[i-1] = NULL;
}
va_end (args);
r = commandvf (stdoutput, stderror, flags, (const char * const*) argv);
return r;
}
/* Same as 'command', but we allow the status code from the
* subcommand to be non-zero, and return that status code.
* We still return -1 if there was some other error.
*/
int
commandrf (char **stdoutput, char **stderror, unsigned flags,
const char *name, ...)
{
va_list args;
CLEANUP_FREE const char **argv = NULL;
char *s;
int i, r;
/* Collect the command line arguments into an array. */
i = 2;
argv = malloc (sizeof (char *) * i);
if (argv == NULL) {
perror ("malloc");
return -1;
}
argv[0] = (char *) name;
argv[1] = NULL;
va_start (args, name);
while ((s = va_arg (args, char *)) != NULL) {
const char **p = realloc (argv, sizeof (char *) * (++i));
if (p == NULL) {
perror ("realloc");
va_end (args);
return -1;
}
argv = p;
argv[i-2] = s;
argv[i-1] = NULL;
}
va_end (args);
r = commandrvf (stdoutput, stderror, flags, argv);
return r;
}
/* Same as 'command', but passing an argv. */
int
commandvf (char **stdoutput, char **stderror, unsigned flags,
char const *const *argv)
{
int r;
r = commandrvf (stdoutput, stderror, flags, (void *) argv);
if (r == 0)
return 0;
else
return -1;
}
/* This is a more sane version of 'system(3)' for running external
* commands. It uses fork/execvp, so we don't need to worry about
* quoting of parameters, and it allows us to capture any error
* messages in a buffer.
*
* If stdoutput is not NULL, then *stdoutput will return the stdout
* of the command.
*
* If stderror is not NULL, then *stderror will return the stderr
* of the command. If there is a final \n character, it is removed
* so you can use the error string directly in a call to
* reply_with_error.
*
* Flags:
*
* COMMAND_FLAG_FOLD_STDOUT_ON_STDERR: For broken external commands
* that send error messages to stdout (hello, parted) but that don't
* have any useful stdout information, use this flag to capture the
* error messages in the *stderror buffer. If using this flag,
* you should pass stdoutput as NULL because nothing could ever be
* captured in that buffer.
*
* COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN: For running external
* commands on chrooted files correctly (see RHBZ#579608) specifying
* this flag causes another process to be forked which chroots into
* sysroot and just copies the input file to stdin of the specified
* command. The file descriptor is ORed with the flags, and that file
* descriptor is always closed by this function. See hexdump.c for an
* example of usage.
*/
int
commandrvf (char **stdoutput, char **stderror, unsigned flags,
char const* const *argv)
{
size_t so_size = 0, se_size = 0;
int so_fd[2], se_fd[2];
unsigned flag_copy_stdin = flags & COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN;
int flag_copy_fd = (int) (flags & COMMAND_FLAG_FD_MASK);
unsigned flag_out_on_err = flags & COMMAND_FLAG_FOLD_STDOUT_ON_STDERR;
pid_t pid;
int r, quit, i;
fd_set rset, rset2;
char buf[256];
char *p;
if (stdoutput) *stdoutput = NULL;
if (stderror) *stderror = NULL;
if (verbose) {
printf ("commandrvf: stdout=%s stderr=%s flags=0x%x\n",
stdoutput ? "y" : flag_out_on_err ? "e" : "n",
stderror ? "y" : "n", flags);
fputs ("commandrvf: ", stdout);
fputs (argv[0], stdout);
for (i = 1; argv[i] != NULL; ++i) {
char quote;
/* Do simple (and incorrect) quoting of the debug output. Real
* quoting is not necessary because we use execvp to run the
* command below.
*/
if (strchr (argv[i], '\''))
quote = '"';
else if (strchr (argv[i], '"'))
quote = '\'';
else if (strchr (argv[i], ' '))
quote = '"';
else
quote = 0;
putchar (' ');
if (quote) putchar (quote);
fputs (argv[i], stdout);
if (quote) putchar (quote);
}
putchar ('\n');
}
/* Note: abort is used in a few places along the error paths early
* in this function. This is because (a) cleaning up correctly is
* very complex at these places and (b) abort is used when a
* resource problems is indicated which would be due to much more
* serious issues - eg. memory or file descriptor leaks. We
* wouldn't expect fork(2) or pipe(2) to fail in normal
* circumstances.
*/
if (pipe (so_fd) == -1 || pipe (se_fd) == -1) {
error (0, errno, "pipe");
abort ();
}
pid = fork ();
if (pid == -1) {
error (0, errno, "fork");
abort ();
}
if (pid == 0) { /* Child process running the command. */
signal (SIGALRM, SIG_DFL);
signal (SIGPIPE, SIG_DFL);
close (0);
if (flag_copy_stdin) {
if (dup2 (flag_copy_fd, STDIN_FILENO) == -1) {
perror ("dup2/flag_copy_fd");
_exit (EXIT_FAILURE);
}
} else {
/* Set stdin to /dev/null. */
if (open ("/dev/null", O_RDONLY) == -1) {
perror ("open: /dev/null");
_exit (EXIT_FAILURE);
}
}
close (so_fd[PIPE_READ]);
close (se_fd[PIPE_READ]);
if (!flag_out_on_err) {
if (dup2 (so_fd[PIPE_WRITE], STDOUT_FILENO) == -1) {
perror ("dup2/so_fd[PIPE_WRITE]");
_exit (EXIT_FAILURE);
}
} else {
if (dup2 (se_fd[PIPE_WRITE], STDOUT_FILENO) == -1) {
perror ("dup2/se_fd[PIPE_WRITE]");
_exit (EXIT_FAILURE);
}
}
if (dup2 (se_fd[PIPE_WRITE], STDERR_FILENO) == -1) {
perror ("dup2/se_fd[PIPE_WRITE]");
_exit (EXIT_FAILURE);
}
close (so_fd[PIPE_WRITE]);
close (se_fd[PIPE_WRITE]);
if (flags & COMMAND_FLAG_DO_CHROOT && sysroot_len > 0) {
if (chroot (sysroot) == -1) {
perror ("chroot in sysroot");
_exit (EXIT_FAILURE);
}
}
if (chdir ("/") == -1) {
perror ("chdir");
_exit (EXIT_FAILURE);
}
execvp (argv[0], (void *) argv);
perror (argv[0]);
_exit (EXIT_FAILURE);
}
/* Parent process. */
close (so_fd[PIPE_WRITE]);
close (se_fd[PIPE_WRITE]);
FD_ZERO (&rset);
FD_SET (so_fd[PIPE_READ], &rset);
FD_SET (se_fd[PIPE_READ], &rset);
quit = 0;
while (quit < 2) {
again:
rset2 = rset;
r = select (MAX (so_fd[PIPE_READ], se_fd[PIPE_READ]) + 1, &rset2,
NULL, NULL, NULL);
if (r == -1) {
if (errno == EINTR)
goto again;
perror ("select");
quit:
if (stdoutput) {
free (*stdoutput);
*stdoutput = NULL;
}
if (stderror) {
free (*stderror);
/* Need to return non-NULL *stderror here since most callers
* will try to print and then free the err string.
* Unfortunately recovery from strdup failure here is not
* possible.
*/
*stderror = strdup ("error running external command, "
"see debug output for details");
}
close (so_fd[PIPE_READ]);
close (se_fd[PIPE_READ]);
if (flag_copy_stdin) close (flag_copy_fd);
waitpid (pid, NULL, 0);
return -1;
}
if (FD_ISSET (so_fd[PIPE_READ], &rset2)) { /* something on stdout */
r = read (so_fd[PIPE_READ], buf, sizeof buf);
if (r == -1) {
perror ("read");
goto quit;
}
if (r == 0) { FD_CLR (so_fd[PIPE_READ], &rset); quit++; }
if (r > 0 && stdoutput) {
so_size += r;
p = realloc (*stdoutput, so_size);
if (p == NULL) {
perror ("realloc");
goto quit;
}
*stdoutput = p;
memcpy (*stdoutput + so_size - r, buf, r);
}
}
if (FD_ISSET (se_fd[PIPE_READ], &rset2)) { /* something on stderr */
r = read (se_fd[PIPE_READ], buf, sizeof buf);
if (r == -1) {
perror ("read");
goto quit;
}
if (r == 0) { FD_CLR (se_fd[PIPE_READ], &rset); quit++; }
if (r > 0) {
if (verbose)
ignore_value (write (STDERR_FILENO, buf, r));
if (stderror) {
se_size += r;
p = realloc (*stderror, se_size);
if (p == NULL) {
perror ("realloc");
goto quit;
}
*stderror = p;
memcpy (*stderror + se_size - r, buf, r);
}
}
}
}
close (so_fd[PIPE_READ]);
close (se_fd[PIPE_READ]);
/* Make sure the output buffers are \0-terminated. Also remove any
* trailing \n characters from the error buffer (not from stdout).
*/
if (stdoutput) {
void *q = realloc (*stdoutput, so_size+1);
if (q == NULL) {
perror ("realloc");
free (*stdoutput);
}
*stdoutput = q;
if (*stdoutput)
(*stdoutput)[so_size] = '\0';
}
if (stderror) {
void *q = realloc (*stderror, se_size+1);
if (q == NULL) {
perror ("realloc");
free (*stderror);
}
*stderror = q;
if (*stderror) {
(*stderror)[se_size] = '\0';
while (se_size > 0 && (*stderror)[se_size-1] == '\n') {
se_size--;
(*stderror)[se_size] = '\0';
}
}
}
if (flag_copy_stdin && close (flag_copy_fd) == -1) {
perror ("close");
return -1;
}
/* Get the exit status of the command. */
if (waitpid (pid, &r, 0) != pid) {
perror ("waitpid");
return -1;
}
if (WIFEXITED (r)) {
return WEXITSTATUS (r);
} else
return -1;
}