From d94860d7e8357b70d2a301f381fc41aca49c4369 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Fri, 4 Dec 2015 16:28:08 +0000 Subject: [PATCH] daemon: Split out command* functions and CLEANUP_* macros. This allows the command* functions and CLEANUP_* macros to be used independently from the daemon. --- daemon/Makefile.am | 2 + daemon/cleanups.c | 80 +++++++++ daemon/cleanups.h | 47 +++++ daemon/command.c | 436 +++++++++++++++++++++++++++++++++++++++++++++ daemon/command.h | 41 +++++ daemon/daemon.h | 47 +---- daemon/guestfsd.c | 392 ---------------------------------------- po/POTFILES | 2 + 8 files changed, 611 insertions(+), 436 deletions(-) create mode 100644 daemon/cleanups.c create mode 100644 daemon/cleanups.h create mode 100644 daemon/command.c create mode 100644 daemon/command.h diff --git a/daemon/Makefile.am b/daemon/Makefile.am index c1a33e30a..a4e3df753 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -96,6 +96,8 @@ guestfsd_SOURCES = \ cap.c \ checksum.c \ cmp.c \ + command.c \ + command.h \ compress.c \ copy.c \ cpio.c \ diff --git a/daemon/cleanups.c b/daemon/cleanups.c new file mode 100644 index 000000000..5293ff23d --- /dev/null +++ b/daemon/cleanups.c @@ -0,0 +1,80 @@ +/* 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 + +#include +#include +#include + +#include + +#include "cleanups.h" + +/* Use by the CLEANUP_* macros. Do not call these directly. */ +void +cleanup_free (void *ptr) +{ + free (* (void **) ptr); +} + +extern void free_strings (char **argv); + +void +cleanup_free_string_list (void *ptr) +{ + free_strings (* (char ***) ptr); +} + +void +cleanup_unlink_free (void *ptr) +{ + char *filename = * (char **) ptr; + + if (filename) { + unlink (filename); + free (filename); + } +} + +void +cleanup_close (void *ptr) +{ + int fd = * (int *) ptr; + + if (fd >= 0) + close (fd); +} + +void +cleanup_aug_close (void *ptr) +{ + augeas *aug = * (augeas **) ptr; + + if (aug != NULL) + aug_close (aug); +} + +struct stringsbuf; +extern void free_stringsbuf (struct stringsbuf *sb); + +void +cleanup_free_stringsbuf (void *ptr) +{ + free_stringsbuf ((struct stringsbuf *) ptr); +} diff --git a/daemon/cleanups.h b/daemon/cleanups.h new file mode 100644 index 000000000..1a56ead8d --- /dev/null +++ b/daemon/cleanups.h @@ -0,0 +1,47 @@ +/* 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. + */ + +#ifndef GUESTFSD_CLEANUPS_H +#define GUESTFSD_CLEANUPS_H + +/* Used by the CLEANUP_* macros. */ +extern void cleanup_free (void *ptr); +extern void cleanup_free_string_list (void *ptr); +extern void cleanup_unlink_free (void *ptr); +extern void cleanup_close (void *ptr); +extern void cleanup_aug_close (void *ptr); +extern void cleanup_free_stringsbuf (void *ptr); + +#ifdef HAVE_ATTRIBUTE_CLEANUP +#define CLEANUP_FREE __attribute__((cleanup(cleanup_free))) +#define CLEANUP_FREE_STRING_LIST \ + __attribute__((cleanup(cleanup_free_string_list))) +#define CLEANUP_UNLINK_FREE __attribute__((cleanup(cleanup_unlink_free))) +#define CLEANUP_CLOSE __attribute__((cleanup(cleanup_close))) +#define CLEANUP_AUG_CLOSE __attribute__((cleanup(cleanup_aug_close))) +#define CLEANUP_FREE_STRINGSBUF __attribute__((cleanup(cleanup_free_stringsbuf))) +#else +#define CLEANUP_FREE +#define CLEANUP_FREE_STRING_LIST +#define CLEANUP_UNLINK_FREE +#define CLEANUP_CLOSE +#define CLEANUP_AUG_CLOSE +#define CLEANUP_FREE_STRINGSBUF +#endif + +#endif /* GUESTFSD_CLEANUPS_H */ diff --git a/daemon/command.c b/daemon/command.c new file mode 100644 index 000000000..3e757aa31 --- /dev/null +++ b/daemon/command.c @@ -0,0 +1,436 @@ +/* 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ignore-value.h" + +#include "command.h" +#include "cleanups.h" + +extern int verbose; + +extern const char *sysroot; +extern size_t sysroot_len; + +#ifndef MAX +# define MAX(a,b) ((a)>(b)?(a):(b)) +#endif + +/* 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); + 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" : "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 (!(flags & COMMAND_FLAG_FOLD_STDOUT_ON_STDERR)) { + 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; +} diff --git a/daemon/command.h b/daemon/command.h new file mode 100644 index 000000000..60f32b883 --- /dev/null +++ b/daemon/command.h @@ -0,0 +1,41 @@ +/* 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. + */ + +#ifndef GUESTFSD_COMMAND_H +#define GUESTFSD_COMMAND_H + +#define command(out,err,name,...) commandf((out),(err),0,(name),__VA_ARGS__) +#define commandr(out,err,name,...) commandrf((out),(err),0,(name),__VA_ARGS__) +#define commandv(out,err,argv) commandvf((out),(err),0,(argv)) +#define commandrv(out,err,argv) commandrvf((out),(err),0,(argv)) + +#define COMMAND_FLAG_FD_MASK 0x0000ffff +#define COMMAND_FLAG_FOLD_STDOUT_ON_STDERR 0x00010000 +#define COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN 0x00020000 +#define COMMAND_FLAG_DO_CHROOT 0x00040000 + +extern int commandf (char **stdoutput, char **stderror, unsigned flags, + const char *name, ...) __attribute__((sentinel)); +extern int commandrf (char **stdoutput, char **stderror, unsigned flags, + const char *name, ...) __attribute__((sentinel)); +extern int commandvf (char **stdoutput, char **stderror, unsigned flags, + char const *const *argv); +extern int commandrvf (char **stdoutput, char **stderror, unsigned flags, + char const* const *argv); + +#endif /* GUESTFSD_COMMAND_H */ diff --git a/daemon/daemon.h b/daemon/daemon.h index 7c9091772..994b47a3a 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -32,6 +32,9 @@ #include "guestfs-internal-all.h" +#include "cleanups.h" +#include "command.h" + /* Mountables */ typedef struct { @@ -117,28 +120,9 @@ extern char **split_lines (char *str); extern char **empty_list (void); -#define command(out,err,name,...) commandf((out),(err),0,(name),__VA_ARGS__) -#define commandr(out,err,name,...) commandrf((out),(err),0,(name),__VA_ARGS__) -#define commandv(out,err,argv) commandvf((out),(err),0,(argv)) -#define commandrv(out,err,argv) commandrvf((out),(err),0,(argv)) - #define __external_command __attribute__((__section__(".guestfsd_ext_cmds"))) #define GUESTFSD_EXT_CMD(___ext_cmd_var, ___ext_cmd_str) static const char ___ext_cmd_var[] __external_command = #___ext_cmd_str -#define COMMAND_FLAG_FD_MASK 0x0000ffff -#define COMMAND_FLAG_FOLD_STDOUT_ON_STDERR 0x00010000 -#define COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN 0x00020000 -#define COMMAND_FLAG_DO_CHROOT 0x00040000 - -extern int commandf (char **stdoutput, char **stderror, unsigned flags, - const char *name, ...) __attribute__((sentinel)); -extern int commandrf (char **stdoutput, char **stderror, unsigned flags, - const char *name, ...) __attribute__((sentinel)); -extern int commandvf (char **stdoutput, char **stderror, unsigned flags, - char const *const *argv); -extern int commandrvf (char **stdoutput, char **stderror, unsigned flags, - char const* const *argv); - extern int is_power_of_2 (unsigned long v); extern void trim (char *str); @@ -157,14 +141,6 @@ extern char *get_random_uuid (void); extern int asprintf_nowarn (char **strp, const char *fmt, ...); -/* Use by the CLEANUP_* macros. */ -extern void cleanup_free (void *ptr); -extern void cleanup_free_string_list (void *ptr); -extern void cleanup_unlink_free (void *ptr); -extern void cleanup_close (void *ptr); -extern void cleanup_aug_close (void *ptr); -extern void cleanup_free_stringsbuf (void *ptr); - /*-- in names.c (auto-generated) --*/ extern const char *function_names[]; @@ -452,21 +428,4 @@ is_zero (const char *buffer, size_t size) } \ } while (0) -#ifdef HAVE_ATTRIBUTE_CLEANUP -#define CLEANUP_FREE __attribute__((cleanup(cleanup_free))) -#define CLEANUP_FREE_STRING_LIST \ - __attribute__((cleanup(cleanup_free_string_list))) -#define CLEANUP_UNLINK_FREE __attribute__((cleanup(cleanup_unlink_free))) -#define CLEANUP_CLOSE __attribute__((cleanup(cleanup_close))) -#define CLEANUP_AUG_CLOSE __attribute__((cleanup(cleanup_aug_close))) -#define CLEANUP_FREE_STRINGSBUF __attribute__((cleanup(cleanup_free_stringsbuf))) -#else -#define CLEANUP_FREE -#define CLEANUP_FREE_STRING_LIST -#define CLEANUP_UNLINK_FREE -#define CLEANUP_CLOSE -#define CLEANUP_AUG_CLOSE -#define CLEANUP_FREE_STRINGSBUF -#endif - #endif /* GUESTFSD_DAEMON_H */ diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c index b31353461..00f53c6d0 100644 --- a/daemon/guestfsd.c +++ b/daemon/guestfsd.c @@ -68,10 +68,6 @@ GUESTFSD_EXT_CMD(str_uuidgen, uuidgen); # define O_CLOEXEC 0 #endif -/* For improved readability dealing with pipe arrays */ -#define PIPE_READ 0 -#define PIPE_WRITE 1 - /* If root device is an ext2 filesystem, this is the major and minor. * This is so we can ignore this device from the point of view of the * user, eg. in guestfs_list_devices and many other places. @@ -752,394 +748,6 @@ join_strings (const char *separator, char *const *argv) return r; } -/* 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); - 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" : "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 (!(flags & COMMAND_FLAG_FOLD_STDOUT_ON_STDERR)) { - 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; -} - /* Split an output string into a NULL-terminated list of lines, * wrapped into a stringsbuf. * Typically this is used where we have run an external command diff --git a/po/POTFILES b/po/POTFILES index 9719afd59..aca174deb 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -25,7 +25,9 @@ daemon/blockdev.c daemon/btrfs.c daemon/cap.c daemon/checksum.c +daemon/cleanups.c daemon/cmp.c +daemon/command.c daemon/compress.c daemon/copy.c daemon/cpio.c