[libvirt] [PATCH] qemu: Report errors from iohelper

Currently, we use iohelper when saving/restoring a domain. However, if there's some kind of error (like I/O) it is not propagated to libvirt. Since it is not qemu who is doing the actual write() it will not get error. The iohelper does. Therefore we should check for iohelper errors as it makes libvirt more user friendly. --- diff to v1: -incorporate the event loop to read iohelper's stderr src/libvirt_private.syms | 1 + src/qemu/qemu_driver.c | 7 ++- src/util/virfile.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/virfile.h | 2 + 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 699c9a3..68440d0 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1332,6 +1332,7 @@ virDomainListSnapshots; virFileLoopDeviceAssociate; virFileClose; virFileDirectFdFlag; +virFileWrapperFdCatchError; virFileWrapperFdClose; virFileWrapperFdFree; virFileWrapperFdNew; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8af316f..c8e97cf 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -2945,6 +2945,7 @@ endjob: if (rc < 0) VIR_WARN("Unable to resume guest CPUs after save failure"); } + virFileWrapperFdCatchError(wrapperFd); } if (qemuDomainObjEndAsyncJob(driver, vm) == 0) vm = NULL; @@ -3282,9 +3283,11 @@ doCoreDump(struct qemud_driver *driver, cleanup: VIR_FORCE_CLOSE(fd); - virFileWrapperFdFree(wrapperFd); - if (ret != 0) + if (ret != 0) { + virFileWrapperFdCatchError(wrapperFd); unlink(path); + } + virFileWrapperFdFree(wrapperFd); return ret; } diff --git a/src/util/virfile.c b/src/util/virfile.c index 5b00ead..36b3420 100644 --- a/src/util/virfile.c +++ b/src/util/virfile.c @@ -135,10 +135,58 @@ virFileDirectFdFlag(void) * read-write is not supported, just a single direction. */ struct _virFileWrapperFd { virCommandPtr cmd; /* Child iohelper process to do the I/O. */ + int err_fd; /* FD to read stderr of @cmd */ + char *err_msg; /* stderr of @cmd */ + size_t err_msg_len; /* strlen of err_msg so we don't + have to compute it every time */ + int err_watch; /* ID of watch in the event loop */ }; #ifndef WIN32 /** + * virFileWrapperFdReadStdErr: + * @watch: watch ID + * @fd: the read end of pipe to iohelper's stderr + * @events: an OR-ed set of events which occurred on @fd + * @opaque: virFileWrapperFdPtr + * + * This is a callback to our eventloop which will read iohelper's + * stderr, reallocate @opaque->err_msg and copy data. + */ +static void +virFileWrapperFdReadStdErr(int watch ATTRIBUTE_UNUSED, + int fd, int events, void *opaque) +{ + virFileWrapperFdPtr wfd = (virFileWrapperFdPtr) opaque; + char ebuf[1024]; + ssize_t nread; + + if (events & VIR_EVENT_HANDLE_READABLE) { + while ((nread = saferead(fd, ebuf, sizeof(ebuf)))) { + if (nread < 0) { + if (errno != EAGAIN) + virReportSystemError(errno, "%s", + _("unable to read iohelper's stderr")); + break; + } + + if (VIR_REALLOC_N(wfd->err_msg, wfd->err_msg_len + nread + 1) < 0) { + virReportOOMError(); + return; + } + memcpy(wfd->err_msg + wfd->err_msg_len, ebuf, nread); + wfd->err_msg_len += nread; + wfd->err_msg[wfd->err_msg_len] = '\0'; + } + } + + if (events & VIR_EVENT_HANDLE_HANGUP) { + virEventRemoveHandle(watch); + wfd->err_watch = -1; + } +} + +/** * virFileWrapperFdNew: * @fd: pointer to fd to wrap * @name: name of fd, for diagnostics @@ -197,6 +245,8 @@ virFileWrapperFdNew(int *fd, const char *name, unsigned int flags) return NULL; } + ret->err_watch = -1; + mode = fcntl(*fd, F_GETFL); if (mode < 0) { @@ -229,9 +279,36 @@ virFileWrapperFdNew(int *fd, const char *name, unsigned int flags) virCommandAddArg(ret->cmd, "0"); } + /* In order to catch iohelper stderr, we must: + * - pass a FD to virCommand (-1 to auto-allocate one) + * - change iohelper's env so virLog functions print to stderr + */ + ret->err_fd = -1; + virCommandSetErrorFD(ret->cmd, &ret->err_fd); + virCommandAddEnvPair(ret->cmd, "LIBVIRT_LOG_OUTPUTS", "1:stderr"); + if (virCommandRunAsync(ret->cmd, NULL) < 0) goto error; + /* deliberately don't use virCommandNonblockingFDs here as it is all or + * nothing. And we want iohelper's stdin to block. */ + if (virSetNonBlock(ret->err_fd) < 0) { + virReportSystemError(errno, "%s", + _("Failed to set non-blocking " + "file descriptor flag")); + goto error; + } + + if ((ret->err_watch = virEventAddHandle(ret->err_fd, + VIR_EVENT_HANDLE_READABLE, + virFileWrapperFdReadStdErr, + ret, NULL)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to register iohelper's " + "stderr FD in the eventloop")); + goto error; + } + if (VIR_CLOSE(pipefd[!output]) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("unable to close pipe")); goto error; @@ -280,6 +357,21 @@ virFileWrapperFdClose(virFileWrapperFdPtr wfd) return virCommandWait(wfd->cmd, NULL); } + +/** + * virFileWrapperFdCatchError: + * @wfd: fd wrapper, or NULL + * + * If iohelper reported any error VIR_WARN() about it. + */ +void +virFileWrapperFdCatchError(virFileWrapperFdPtr wfd) +{ + if (wfd->err_msg) + VIR_WARN("iohelper reports: %s", wfd->err_msg); +} + + /** * virFileWrapperFdFree: * @wfd: fd wrapper, or NULL @@ -295,6 +387,11 @@ virFileWrapperFdFree(virFileWrapperFdPtr wfd) if (!wfd) return; + VIR_FORCE_CLOSE(wfd->err_fd); + if (wfd->err_watch != -1) + virEventRemoveHandle(wfd->err_watch); + VIR_FREE(wfd->err_msg); + virCommandFree(wfd->cmd); VIR_FREE(wfd); } diff --git a/src/util/virfile.h b/src/util/virfile.h index c885b73..80daf86 100644 --- a/src/util/virfile.h +++ b/src/util/virfile.h @@ -90,6 +90,8 @@ int virFileWrapperFdClose(virFileWrapperFdPtr dfd); void virFileWrapperFdFree(virFileWrapperFdPtr dfd); +void virFileWrapperFdCatchError(virFileWrapperFdPtr dfd); + int virFileLock(int fd, bool shared, off_t start, off_t len); int virFileUnlock(int fd, off_t start, off_t len); -- 1.7.8.6

On 24.10.2012 17:49, Michal Privoznik wrote:
Currently, we use iohelper when saving/restoring a domain. However, if there's some kind of error (like I/O) it is not propagated to libvirt. Since it is not qemu who is doing the actual write() it will not get error. The iohelper does. Therefore we should check for iohelper errors as it makes libvirt more user friendly. ---
diff to v1: -incorporate the event loop to read iohelper's stderr
Ugrh. s/PATCH/PATCH v2/ in $SUBJ obviously.

On 10/24/2012 09:49 AM, Michal Privoznik wrote:
Currently, we use iohelper when saving/restoring a domain. However, if there's some kind of error (like I/O) it is not propagated to libvirt. Since it is not qemu who is doing the actual write() it will not get error. The iohelper does. Therefore we should check for iohelper errors as it makes libvirt more user friendly. ---
diff to v1: -incorporate the event loop to read iohelper's stderr @@ -3282,9 +3283,11 @@ doCoreDump(struct qemud_driver *driver,
+ /* deliberately don't use virCommandNonblockingFDs here as it is all or + * nothing. And we want iohelper's stdin to block. */ + if (virSetNonBlock(ret->err_fd) < 0) {
This comment is confusing, since you say we don't use nonblocking and then immediately set our side of stderr to nonblocking. Maybe you meant to say that we leave helper's stdin or stdout alone (default blocking) because they are not tied to our event loop, but set our side of stderr to be nonblocking so we can read it from our event loop. ACK once you fix up that comment. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 25.10.2012 23:56, Eric Blake wrote:
On 10/24/2012 09:49 AM, Michal Privoznik wrote:
Currently, we use iohelper when saving/restoring a domain. However, if there's some kind of error (like I/O) it is not propagated to libvirt. Since it is not qemu who is doing the actual write() it will not get error. The iohelper does. Therefore we should check for iohelper errors as it makes libvirt more user friendly. ---
diff to v1: -incorporate the event loop to read iohelper's stderr @@ -3282,9 +3283,11 @@ doCoreDump(struct qemud_driver *driver,
+ /* deliberately don't use virCommandNonblockingFDs here as it is all or + * nothing. And we want iohelper's stdin to block. */ + if (virSetNonBlock(ret->err_fd) < 0) {
This comment is confusing, since you say we don't use nonblocking and then immediately set our side of stderr to nonblocking. Maybe you meant to say that we leave helper's stdin or stdout alone (default blocking) because they are not tied to our event loop, but set our side of stderr to be nonblocking so we can read it from our event loop.
ACK once you fix up that comment.
Thanks, pushed with this: diff --git a/src/util/virfile.c b/src/util/virfile.c index 36b3420..9593151 100644 --- a/src/util/virfile.c +++ b/src/util/virfile.c @@ -291,7 +291,9 @@ virFileWrapperFdNew(int *fd, const char *name, unsigned int flags) goto error; /* deliberately don't use virCommandNonblockingFDs here as it is all or - * nothing. And we want iohelper's stdin to block. */ + * nothing. And we want iohelper's stdin and stdout to block (default). + * However, stderr is read within event loop and therefore it must be + * nonblocking.*/ if (virSetNonBlock(ret->err_fd) < 0) { virReportSystemError(errno, "%s", _("Failed to set non-blocking "

On 10/29/2012 10:08 AM, Michal Privoznik wrote:
On 25.10.2012 23:56, Eric Blake wrote:
On 10/24/2012 09:49 AM, Michal Privoznik wrote:
Currently, we use iohelper when saving/restoring a domain. However, if there's some kind of error (like I/O) it is not propagated to libvirt. Since it is not qemu who is doing the actual write() it will not get error. The iohelper does. Therefore we should check for iohelper errors as it makes libvirt more user friendly. ---
ACK once you fix up that comment.
Thanks, pushed with this:
diff --git a/src/util/virfile.c b/src/util/virfile.c index 36b3420..9593151 100644 --- a/src/util/virfile.c +++ b/src/util/virfile.c @@ -291,7 +291,9 @@ virFileWrapperFdNew(int *fd, const char *name, unsigned int flags) goto error;
/* deliberately don't use virCommandNonblockingFDs here as it is all or - * nothing. And we want iohelper's stdin to block. */ + * nothing. And we want iohelper's stdin and stdout to block (default). + * However, stderr is read within event loop and therefore it must be + * nonblocking.*/
Shoot, now I'm getting link failures: CCLD libvirt_iohelper ./.libs/libvirt_util.a(libvirt_util_la-event_poll.o): In function `virEventPollAddHandle': /home/dummy/libvirt/src/util/event_poll.c:136: undefined reference to `libvirt_event_poll_add_handle_semaphore' ... ./.libs/libvirt_util.a(libvirt_util_la-event_poll.o):(.note.stapsdt+0x24): undefined reference to `libvirt_event_poll_add_handle_semaphore' Something in this patch make libvirt_iohelper now drag in new link dependencies that aren't being met. But since you didn't see this in your testing, it's got to be some difference in the configure results for which devel libraries were found on your platform vs. mine. I'm trying to narrow down what might be the culprit, but the ".note.stapsdt" portion of the error message makes me think it has to do with systemtap support. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
participants (2)
-
Eric Blake
-
Michal Privoznik