daemon: Split out command* functions and CLEANUP_* macros.

This allows the command* functions and CLEANUP_* macros to be used
independently from the daemon.
This commit is contained in:
Richard W.M. Jones
2015-12-04 16:28:08 +00:00
parent b5066c67e0
commit d94860d7e8
8 changed files with 611 additions and 436 deletions

View File

@@ -96,6 +96,8 @@ guestfsd_SOURCES = \
cap.c \
checksum.c \
cmp.c \
command.c \
command.h \
compress.c \
copy.c \
cpio.c \

80
daemon/cleanups.c Normal file
View File

@@ -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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <augeas.h>
#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);
}

47
daemon/cleanups.h Normal file
View File

@@ -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 */

436
daemon/command.c Normal file
View File

@@ -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 <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 "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;
}

41
daemon/command.h Normal file
View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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

View File

@@ -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