/* 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 #include #include #include #include #include #include #include #include #include #include #include #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 see C. * * There is also a macro C which calls * C with C. */ 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, 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 which calls * C with C. */ 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, but passing in an argv array. * * There is also a macro C which calls * C with C. */ 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 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 is not C, then C<*stdoutput> will return the * stdout of the command as a string. * * If C is not C, 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. * * Flags are: * * =over 4 * * =item C * * 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 * because nothing could ever be captured in that buffer. * * =item C * * For running external commands on chrooted files correctly (see * L) 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 for an * example of usage. * * =back * * There is also a macro C which calls * C with C. */ 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; }