I plan to add 'virsh event' to virsh-domain.c and 'virsh net-event' to virsh-network.c; but as they will share quite a bit of common boilerplate, it's better to set that up now in virsh.c. * tools/virsh.h (_vshControl): Add fields. (vshEventStart, vshEventWait, vshEventDone, vshEventCleanup): New prototypes. * tools/virsh.c (vshEventFd, vshEventOldAction, vshEventInt) (vshEventTimeout): New helper variables and functions. (vshEventStart, vshEventWait, vshEventDone, vshEventCleanup): Implement new functions. (vshInit, vshDeinit, main): Manage event timeout. Signed-off-by: Eric Blake <eblake@xxxxxxxxxx> --- tools/virsh.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- tools/virsh.h | 13 +++++ 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/tools/virsh.c b/tools/virsh.c index 944c037..2d4aaff 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -42,6 +42,7 @@ #include <sys/stat.h> #include <inttypes.h> #include <strings.h> +#include <signal.h> #include <libxml/parser.h> #include <libxml/tree.h> @@ -85,6 +86,11 @@ #include "virsh-snapshot.h" #include "virsh-volume.h" +/* Gnulib doesn't guarantee SA_SIGINFO support. */ +#ifndef SA_SIGINFO +# define SA_SIGINFO 0 +#endif + static char *progname; static const vshCmdGrp cmdGroups[]; @@ -2435,6 +2441,149 @@ vshEventLoop(void *opaque) /* + * Helpers for waiting for a libvirt event. + */ + +/* We want to use SIGINT to cancel a wait; but as signal handlers + * don't have an opaque argument, we have to use static storage. */ +static int vshEventFd = -1; +static struct sigaction vshEventOldAction; + + +/* Signal handler installed in vshEventStart, removed in vshEventCleanup. */ +static void +vshEventInt(int sig ATTRIBUTE_UNUSED, + siginfo_t *siginfo ATTRIBUTE_UNUSED, + void *context ATTRIBUTE_UNUSED) +{ + char reason = VSH_EVENT_INTERRUPT; + if (vshEventFd >= 0) + ignore_value(safewrite(vshEventFd, &reason, 1)); +} + + +/* Event loop handler used to limit length of waiting for any other event. */ +static void +vshEventTimeout(int timer ATTRIBUTE_UNUSED, + void *opaque) +{ + vshControl *ctl = opaque; + char reason = VSH_EVENT_TIMEOUT; + + if (ctl->eventPipe[1] >= 0) + ignore_value(safewrite(ctl->eventPipe[1], &reason, 1)); +} + + +/** + * vshEventStart: + * @ctl virsh command struct + * @timeout_ms max wait time in milliseconds, or 0 for indefinite + * + * Set up a wait for a libvirt event. The wait can be canceled by + * SIGINT or by calling vshEventDone() in your event handler. If + * @timeout_ms is positive, the wait will also end if the timeout + * expires. Call vshEventWait() to block the main thread (the event + * handler runs in the event loop thread). When done (including if + * there was an error registering for an event), use vshEventCleanup() + * to quit waiting. Returns 0 on success, -1 on failure. */ +int +vshEventStart(vshControl *ctl, int timeout_ms) +{ + struct sigaction action; + + assert(ctl->eventPipe[0] == -1 && ctl->eventPipe[1] == -1 && + vshEventFd == -1 && ctl->eventTimerId >= 0); + if (pipe2(ctl->eventPipe, O_CLOEXEC) < 0) { + char ebuf[1024]; + + vshError(ctl, _("failed to create pipe: %s"), + virStrerror(errno, ebuf, sizeof(ebuf))); + return -1; + } + vshEventFd = ctl->eventPipe[1]; + + action.sa_sigaction = vshEventInt; + action.sa_flags = SA_SIGINFO; + sigemptyset(&action.sa_mask); + sigaction(SIGINT, &action, &vshEventOldAction); + + if (timeout_ms) + virEventUpdateTimeout(ctl->eventTimerId, timeout_ms); + + return 0; +} + + +/** + * vshEventDone: + * @ctl virsh command struct + * + * Call this from an event callback to let the main thread quit + * blocking on further events. + */ +void +vshEventDone(vshControl *ctl) +{ + char reason = VSH_EVENT_DONE; + + if (ctl->eventPipe[1] >= 0) + ignore_value(safewrite(ctl->eventPipe[1], &reason, 1)); +} + + +/** + * vshEventWait: + * @ctl virsh command struct + * + * Call this in the main thread after calling vshEventStart() then + * registering for one or more events. This call will block until + * SIGINT, the timeout registered at the start, or until one of your + * event handlers calls vshEventDone(). Returns an enum VSH_EVENT_* + * stating how the wait concluded, or -1 on error. + */ +int +vshEventWait(vshControl *ctl) +{ + char buf; + int rv; + + assert(ctl->eventPipe[0] >= 0); + while ((rv = read(ctl->eventPipe[0], &buf, 1)) < 0 && errno == EINTR); + if (rv != 1) { + char ebuf[1024]; + + if (!rv) + errno = EPIPE; + vshError(ctl, _("failed to determine loop exit status: %s"), + virStrerror(errno, ebuf, sizeof(ebuf))); + return -1; + } + return buf; +} + + +/** + * vshEventCleanup: + * @ctl virsh command struct + * + * Call at the end of any function that has used vshEventStart(), to + * tear down any remaining SIGINT or timeout handlers. + */ +void +vshEventCleanup(vshControl *ctl) +{ + if (vshEventFd >= 0) { + sigaction(SIGINT, &vshEventOldAction, NULL); + vshEventFd = -1; + } + VIR_FORCE_CLOSE(ctl->eventPipe[0]); + VIR_FORCE_CLOSE(ctl->eventPipe[1]); + virEventUpdateTimeout(ctl->eventTimerId, -1); +} + + +/* * Initialize debug settings. */ static void @@ -2490,6 +2639,10 @@ vshInit(vshControl *ctl) return false; ctl->eventLoopStarted = true; + if ((ctl->eventTimerId = virEventAddTimeout(-1, vshEventTimeout, ctl, + NULL)) < 0) + return false; + if (ctl->name) { vshReconnect(ctl); /* Connecting to a named connection must succeed, but we delay @@ -2933,6 +3086,9 @@ vshDeinit(vshControl *ctl) if (timer != -1) virEventRemoveTimeout(timer); + if (ctl->eventTimerId != -1) + virEventRemoveTimeout(ctl->eventTimerId); + ctl->eventLoopStarted = false; } @@ -3334,7 +3490,9 @@ main(int argc, char **argv) ctl->log_fd = -1; /* Initialize log file descriptor */ ctl->debug = VSH_DEBUG_DEFAULT; ctl->escapeChar = "^]"; /* Same default as telnet */ - + ctl->eventPipe[0] = -1; + ctl->eventPipe[1] = -1; + ctl->eventTimerId = -1; if (!setlocale(LC_ALL, "")) { perror("setlocale"); diff --git a/tools/virsh.h b/tools/virsh.h index 4ee2d72..62a1eed 100644 --- a/tools/virsh.h +++ b/tools/virsh.h @@ -242,6 +242,9 @@ struct _vshControl { virMutex lock; bool eventLoopStarted; bool quit; + int eventPipe[2]; /* Write-to-self pipe to end waiting for an + * event to occur */ + int eventTimerId; /* id of event loop timeout registration */ const char *escapeChar; /* String representation of console escape character */ @@ -369,6 +372,16 @@ int vshTTYRestore(vshControl *ctl); int vshTTYMakeRaw(vshControl *ctl, bool report_errors); bool vshTTYAvailable(vshControl *ctl); +/* waiting for events */ +enum { + VSH_EVENT_INTERRUPT, + VSH_EVENT_TIMEOUT, + VSH_EVENT_DONE, +}; +int vshEventStart(vshControl *ctl, int timeout_ms); +void vshEventDone(vshControl *ctl); +int vshEventWait(vshControl *ctl); +void vshEventCleanup(vshControl *ctl); /* allocation wrappers */ void *_vshMalloc(vshControl *ctl, size_t sz, const char *filename, int line); -- 1.8.5.3 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list