mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
See upstream bug report:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99193
(cherry picked from commit 4af1c631a2)
475 lines
12 KiB
C
475 lines
12 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.
|
|
*/
|
|
|
|
/**
|
|
* This file contains a number of useful functions for running
|
|
* external commands and capturing their output.
|
|
*/
|
|
|
|
#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
|
|
|
|
/**
|
|
* Run a command. Optionally capture stdout and stderr as strings.
|
|
*
|
|
* Returns C<0> if the command ran successfully, or C<-1> if there was
|
|
* any error.
|
|
*
|
|
* For a description of the C<flags> see C<commandrvf>.
|
|
*
|
|
* There is also a macro C<command(out,err,name,...)> which calls
|
|
* C<commandf> with C<flags=0>.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99193
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wanalyzer-mismatching-deallocation"
|
|
/**
|
|
* Same as C<command>, but we allow the status code from the
|
|
* subcommand to be non-zero, and return that status code.
|
|
*
|
|
* We still return C<-1> if there was some other error.
|
|
*
|
|
* There is also a macro C<commandr(out,err,name,...)> which calls
|
|
* C<commandrf> with C<flags=0>.
|
|
*/
|
|
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;
|
|
}
|
|
#pragma GCC diagnostic pop
|
|
|
|
/**
|
|
* Same as C<command>, but passing in an argv array.
|
|
*
|
|
* There is also a macro C<commandv(out,err,argv)> which calls
|
|
* C<commandvf> with C<flags=0>.
|
|
*/
|
|
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 L<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 C<stdoutput> is not C<NULL>, then C<*stdoutput> will return the
|
|
* stdout of the command as a string.
|
|
*
|
|
* If C<stderror> is not C<NULL>, then C<*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
|
|
* C<reply_with_error>.
|
|
*
|
|
* Flags are:
|
|
*
|
|
* =over 4
|
|
*
|
|
* =item C<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 C<*stderror>
|
|
* buffer. If using this flag, you should pass C<stdoutput=NULL>
|
|
* because nothing could ever be captured in that buffer.
|
|
*
|
|
* =item C<COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN>
|
|
*
|
|
* For running external commands on chrooted files correctly (see
|
|
* L<https://bugzilla.redhat.com/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 F<daemon/hexdump.c> for an
|
|
* example of usage.
|
|
*
|
|
* =back
|
|
*
|
|
* There is also a macro C<commandrv(out,err,argv)> which calls
|
|
* C<commandrvf> with C<flags=0>.
|
|
*/
|
|
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];
|
|
const unsigned flag_copy_stdin =
|
|
flags & COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN;
|
|
const int flag_copy_fd = (int) (flags & COMMAND_FLAG_FD_MASK);
|
|
const 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;
|
|
}
|