command: properly diagnose process exit via signal

Child processes don't always reach _exit(); if they die from a
signal, then any messages should still be accurate.  Most users
either expect a 0 status (thankfully, if status==0, then
WIFEXITED(status) is true and WEXITSTATUS(status)==0 for all
known platforms) or were filtering on WIFEXITED before printing
a status, but a few were missing this check.  Additionally,
nwfilter_ebiptables_driver was making an assumption that works
on Linux (where WEXITSTATUS shifts and WTERMSIG just masks)
but fails on other platforms (where WEXITSTATUS just masks and
WTERMSIG shifts).

* src/util/command.h (virCommandTranslateStatus): New helper.
* src/libvirt_private.syms (command.h): Export it.
* src/util/command.c (virCommandTranslateStatus): New function.
(virCommandWait): Use it to also diagnose status from signals.
* src/security/security_apparmor.c (load_profile): Likewise.
* src/storage/storage_backend.c
(virStorageBackendQEMUImgBackingFormat): Likewise.
* src/util/util.c (virExecDaemonize, virRunWithHook)
(virFileOperation, virDirCreate): Likewise.
* daemon/remote.c (remoteDispatchAuthPolkit): Likewise.
* src/nwfilter/nwfilter_ebiptables_driver.c (ebiptablesExecCLI):
Likewise.
This commit is contained in:
Eric Blake 2011-03-22 11:55:45 -06:00
parent 6ef1f6c2a1
commit 208a044a54
8 changed files with 89 additions and 44 deletions

View File

@ -60,6 +60,7 @@
#include "uuid.h" #include "uuid.h"
#include "network.h" #include "network.h"
#include "libvirt/libvirt-qemu.h" #include "libvirt/libvirt-qemu.h"
#include "command.h"
#define VIR_FROM_THIS VIR_FROM_REMOTE #define VIR_FROM_THIS VIR_FROM_REMOTE
#define REMOTE_DEBUG(fmt, ...) VIR_DEBUG(fmt, __VA_ARGS__) #define REMOTE_DEBUG(fmt, ...) VIR_DEBUG(fmt, __VA_ARGS__)
@ -4368,8 +4369,10 @@ remoteDispatchAuthPolkit (struct qemud_server *server,
goto authfail; goto authfail;
} }
if (status != 0) { if (status != 0) {
VIR_ERROR(_("Policy kit denied action %s from pid %d, uid %d, result: %d"), char *tmp = virCommandTranslateStatus(status);
action, callerPid, callerUid, status); VIR_ERROR(_("Policy kit denied action %s from pid %d, uid %d: %s"),
action, callerPid, callerUid, NULLSTR(tmp));
VIR_FREE(tmp);
goto authdeny; goto authdeny;
} }
PROBE(CLIENT_AUTH_ALLOW, "fd=%d, auth=%d, username=%s", PROBE(CLIENT_AUTH_ALLOW, "fd=%d, auth=%d, username=%s",

View File

@ -123,6 +123,7 @@ virCommandSetPreExecHook;
virCommandSetWorkingDirectory; virCommandSetWorkingDirectory;
virCommandToString; virCommandToString;
virCommandTransferFD; virCommandTransferFD;
virCommandTranslateStatus;
virCommandWait; virCommandWait;
virCommandWriteArgLog; virCommandWriteArgLog;

View File

@ -1,6 +1,7 @@
/* /*
* nwfilter_ebiptables_driver.c: driver for ebtables/iptables on tap devices * nwfilter_ebiptables_driver.c: driver for ebtables/iptables on tap devices
* *
* Copyright (C) 2011 Red Hat, Inc.
* Copyright (C) 2010 IBM Corp. * Copyright (C) 2010 IBM Corp.
* Copyright (C) 2010 Stefan Berger * Copyright (C) 2010 Stefan Berger
* *
@ -2558,7 +2559,7 @@ err_exit:
* ebiptablesExecCLI: * ebiptablesExecCLI:
* @buf : pointer to virBuffer containing the string with the commands to * @buf : pointer to virBuffer containing the string with the commands to
* execute. * execute.
* @status: Pointer to an integer for returning the status of the * @status: Pointer to an integer for returning the WEXITSTATUS of the
* commands executed via the script the was run. * commands executed via the script the was run.
* *
* Returns 0 in case of success, != 0 in case of an error. The returned * Returns 0 in case of success, != 0 in case of an error. The returned
@ -2587,7 +2588,7 @@ ebiptablesExecCLI(virBufferPtr buf,
cmds = virBufferContentAndReset(buf); cmds = virBufferContentAndReset(buf);
VIR_DEBUG("%s", cmds); VIR_DEBUG("%s", NULLSTR(cmds));
if (!cmds) if (!cmds)
return 0; return 0;
@ -2606,9 +2607,16 @@ ebiptablesExecCLI(virBufferPtr buf,
virMutexUnlock(&execCLIMutex); virMutexUnlock(&execCLIMutex);
*status >>= 8; if (rc == 0) {
if (WIFEXITED(*status)) {
*status = WEXITSTATUS(*status);
} else {
rc = -1;
*status = 1;
}
}
VIR_DEBUG("rc = %d, status = %d",rc, *status); VIR_DEBUG("rc = %d, status = %d", rc, *status);
unlink(filename); unlink(filename);

View File

@ -1,5 +1,6 @@
/* /*
* AppArmor security driver for libvirt * AppArmor security driver for libvirt
* Copyright (C) 2011 Red Hat, Inc.
* Copyright (C) 2009-2010 Canonical Ltd. * Copyright (C) 2009-2010 Canonical Ltd.
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
@ -36,6 +37,7 @@
#include "hostusb.h" #include "hostusb.h"
#include "files.h" #include "files.h"
#include "configmake.h" #include "configmake.h"
#include "command.h"
#define VIR_FROM_THIS VIR_FROM_SECURITY #define VIR_FROM_THIS VIR_FROM_SECURITY
#define SECURITY_APPARMOR_VOID_DOI "0" #define SECURITY_APPARMOR_VOID_DOI "0"
@ -217,15 +219,19 @@ load_profile(virSecurityManagerPtr mgr,
VIR_FORCE_CLOSE(pipefd[1]); VIR_FORCE_CLOSE(pipefd[1]);
rc = 0; rc = 0;
rewait: while ((ret = waitpid(child, &status, 0)) < 0 && errno == EINTR);
if (waitpid(child, &status, 0) != child) { if (ret < 0) {
if (errno == EINTR) virReportSystemError(errno,
goto rewait; _("Failed to reap virt-aa-helper pid %lu"),
(unsigned long)child);
rc = -1;
} else if (status) {
char *str = virCommandTranslateStatus(status);
virSecurityReportError(VIR_ERR_INTERNAL_ERROR, virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("Unexpected exit status from virt-aa-helper " _("Unexpected status from virt-aa-helper "
"%d pid %lu"), "pid %lu: %s"),
WEXITSTATUS(status), (unsigned long)child); (unsigned long)child, NULLSTR(str));
VIR_FREE(str);
rc = -1; rc = -1;
} }

View File

@ -56,6 +56,7 @@
#include "storage_backend.h" #include "storage_backend.h"
#include "logging.h" #include "logging.h"
#include "files.h" #include "files.h"
#include "command.h"
#if WITH_STORAGE_LVM #if WITH_STORAGE_LVM
# include "storage_backend_logical.h" # include "storage_backend_logical.h"
@ -631,18 +632,13 @@ static int virStorageBackendQEMUImgBackingFormat(const char *qemuimg)
cleanup: cleanup:
VIR_FREE(help); VIR_FREE(help);
VIR_FORCE_CLOSE(newstdout); VIR_FORCE_CLOSE(newstdout);
rewait:
if (child) { if (child) {
if (waitpid(child, &status, 0) != child) { while (waitpid(child, &status, 0) == -1 && errno == EINTR);
if (errno == EINTR) if (status) {
goto rewait; tmp = virCommandTranslateStatus(status);
VIR_WARN("Unexpected status, qemu probably failed: %s",
VIR_ERROR(_("Unexpected exit status from qemu %d pid %lu"), NULLSTR(tmp));
WEXITSTATUS(status), (unsigned long)child); VIR_FREE(tmp);
}
if (WEXITSTATUS(status) != 0) {
VIR_WARN("Unexpected exit status '%d', qemu probably failed",
WEXITSTATUS(status));
} }
} }

View File

@ -797,6 +797,26 @@ virCommandToString(virCommandPtr cmd)
} }
/*
* Translate an exit status into a malloc'd string. Generic helper
* for virCommandRun and virCommandWait status argument, as well as
* raw waitpid and older virRun status.
*/
char *
virCommandTranslateStatus(int status)
{
char *buf;
if (WIFEXITED(status)) {
virAsprintf(&buf, _("exit status %d"), WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
virAsprintf(&buf, _("fatal signal %d"), WTERMSIG(status));
} else {
virAsprintf(&buf, _("invalid value %d"), status);
}
return buf;
}
/* /*
* Manage input and output to the child process. * Manage input and output to the child process.
*/ */
@ -958,6 +978,7 @@ virCommandRun(virCommandPtr cmd, int *exitstatus)
struct stat st; struct stat st;
bool string_io; bool string_io;
bool async_io = false; bool async_io = false;
char *str;
if (!cmd ||cmd->has_error == ENOMEM) { if (!cmd ||cmd->has_error == ENOMEM) {
virReportOOMError(); virReportOOMError();
@ -1048,9 +1069,14 @@ virCommandRun(virCommandPtr cmd, int *exitstatus)
if (virCommandWait(cmd, exitstatus) < 0) if (virCommandWait(cmd, exitstatus) < 0)
ret = -1; ret = -1;
VIR_DEBUG("Result stdout: '%s' stderr: '%s'", str = (exitstatus ? virCommandTranslateStatus(*exitstatus)
: (char *) "status 0");
VIR_DEBUG("Result %s, stdout: '%s' stderr: '%s'",
NULLSTR(str),
cmd->outbuf ? NULLSTR(*cmd->outbuf) : "(null)", cmd->outbuf ? NULLSTR(*cmd->outbuf) : "(null)",
cmd->errbuf ? NULLSTR(*cmd->errbuf) : "(null)"); cmd->errbuf ? NULLSTR(*cmd->errbuf) : "(null)");
if (exitstatus)
VIR_FREE(str);
/* Reset any capturing, in case caller runs /* Reset any capturing, in case caller runs
* this identical command again */ * this identical command again */
@ -1230,10 +1256,12 @@ virCommandWait(virCommandPtr cmd, int *exitstatus)
if (exitstatus == NULL) { if (exitstatus == NULL) {
if (status != 0) { if (status != 0) {
char *str = virCommandToString(cmd); char *str = virCommandToString(cmd);
char *st = virCommandTranslateStatus(status);
virCommandError(VIR_ERR_INTERNAL_ERROR, virCommandError(VIR_ERR_INTERNAL_ERROR,
_("Child process (%s) exited with status %d."), _("Child process (%s) status unexpected: %s"),
str ? str : cmd->args[0], WEXITSTATUS(status)); str ? str : cmd->args[0], NULLSTR(st));
VIR_FREE(str); VIR_FREE(str);
VIR_FREE(st);
return -1; return -1;
} }
} else { } else {

View File

@ -1,7 +1,7 @@
/* /*
* command.h: Child command execution * command.h: Child command execution
* *
* Copyright (C) 2010 Red Hat, Inc. * Copyright (C) 2010-2011 Red Hat, Inc.
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
@ -242,6 +242,12 @@ void virCommandWriteArgLog(virCommandPtr cmd,
*/ */
char *virCommandToString(virCommandPtr cmd) ATTRIBUTE_RETURN_CHECK; char *virCommandToString(virCommandPtr cmd) ATTRIBUTE_RETURN_CHECK;
/*
* Translate an exit status into a malloc'd string.
*/
char *virCommandTranslateStatus(int exitstatus) ATTRIBUTE_RETURN_CHECK;
/* /*
* Run the command and wait for completion. * Run the command and wait for completion.
* Returns -1 on any error executing the * Returns -1 on any error executing the

View File

@ -76,6 +76,7 @@
#include "threads.h" #include "threads.h"
#include "verify.h" #include "verify.h"
#include "files.h" #include "files.h"
#include "command.h"
#ifndef NSIG #ifndef NSIG
# define NSIG 32 # define NSIG 32
@ -832,9 +833,11 @@ int virExecDaemonize(const char *const*argv,
errno == EINTR); errno == EINTR);
if (childstat != 0) { if (childstat != 0) {
char *str = virCommandTranslateStatus(childstat);
virUtilError(VIR_ERR_INTERNAL_ERROR, virUtilError(VIR_ERR_INTERNAL_ERROR,
_("Intermediate daemon process exited with status %d."), _("Intermediate daemon process status unexpected: %s"),
WEXITSTATUS(childstat)); NULLSTR(str));
VIR_FREE(str);
ret = -2; ret = -2;
} }
@ -903,13 +906,12 @@ virRunWithHook(const char *const*argv,
if (status == NULL) { if (status == NULL) {
errno = EINVAL; errno = EINVAL;
if (WIFEXITED(exitstatus) && WEXITSTATUS(exitstatus) != 0) { if (exitstatus) {
char *str = virCommandTranslateStatus(exitstatus);
virUtilError(VIR_ERR_INTERNAL_ERROR, virUtilError(VIR_ERR_INTERNAL_ERROR,
_("'%s' exited with non-zero status %d and " _("'%s' exited unexpectedly: %s"),
"signal %d: %s"), argv_str, argv_str, NULLSTR(str));
WIFEXITED(exitstatus) ? WEXITSTATUS(exitstatus) : 0, VIR_FREE(str);
WIFSIGNALED(exitstatus) ? WTERMSIG(exitstatus) : 0,
(errbuf ? errbuf : ""));
goto error; goto error;
} }
} else { } else {
@ -1510,8 +1512,7 @@ int virFileOperation(const char *path, int openflags, mode_t mode,
path); path);
goto parenterror; goto parenterror;
} }
ret = -WEXITSTATUS(status); if (!WIFEXITED(status) || (ret = -WEXITSTATUS(status)) == -EACCES) {
if (!WIFEXITED(status) || (ret == -EACCES)) {
/* fall back to the simpler method, which works better in /* fall back to the simpler method, which works better in
* some cases */ * some cases */
return virFileOperationNoFork(path, openflags, mode, uid, gid, return virFileOperationNoFork(path, openflags, mode, uid, gid,
@ -1630,15 +1631,11 @@ int virDirCreate(const char *path, mode_t mode,
path); path);
goto parenterror; goto parenterror;
} }
ret = -WEXITSTATUS(status); if (!WIFEXITED(status) || (ret = -WEXITSTATUS(status)) == -EACCES) {
if (!WIFEXITED(status) || (ret == -EACCES)) {
/* fall back to the simpler method, which works better in /* fall back to the simpler method, which works better in
* some cases */ * some cases */
return virDirCreateNoFork(path, mode, uid, gid, flags); return virDirCreateNoFork(path, mode, uid, gid, flags);
} }
if (ret < 0) {
goto parenterror;
}
parenterror: parenterror:
return ret; return ret;
} }