On Thu, Apr 14, 2011 at 02:30:53PM +0200, Michal Privoznik wrote: > This patch adds support for domain lifecycle notification support > over SNMP traps. SNMP subagent monitors any domain events and when > something interesting happens, it sends a trap. > > Monitoring is done in a joinable thread using polling (used > domain-events example from libvirt) so we won't block the agent itself. > > Some debug info can be printed out by setting LIBVIRT_SNMP_VERBOSE > environment variable. > --- > INSTALL.1st | 9 +- > configure.ac | 17 +- > libvirt-snmp.spec.in | 7 +- > src/Makefile.am | 21 ++- > src/README.txt | 2 + > src/event_poll.c | 724 ++++++++++++++++++++++++++++++++++++++++++++ > src/event_poll.h | 132 ++++++++ > src/ignore-value.h | 64 ++++ > src/internal.h | 267 ++++++++++++++++ > src/libvirtNotifications.c | 16 +- > src/libvirtNotifications.h | 16 +- > src/libvirtSnmp.c | 154 +++++++++- > src/memory.c | 313 +++++++++++++++++++ > src/memory.h | 212 +++++++++++++ > src/threads.c | 251 +++++++++++++++ > src/threads.h | 101 ++++++ > src/util.c | 105 +++++++ > src/util.h | 38 +++ > 18 files changed, 2433 insertions(+), 16 deletions(-) > create mode 100644 src/event_poll.c > create mode 100644 src/event_poll.h > create mode 100644 src/ignore-value.h > create mode 100644 src/internal.h > create mode 100644 src/memory.c > create mode 100644 src/memory.h > create mode 100644 src/threads.c > create mode 100644 src/threads.h > create mode 100644 src/util.c > create mode 100644 src/util.h > > diff --git a/INSTALL.1st b/INSTALL.1st > index 31345d8..c439bf3 100644 > --- a/INSTALL.1st > +++ b/INSTALL.1st > @@ -15,14 +15,17 @@ Now it's time for make: > make > su -c "make install" > > -This compile all sources producing runable SNMP subagent > -libvirtMib_subagent, which is installed right after. > +This compiles all source producing a runnable SNMP subagent, > +libvirtMib_subagent, which is installed afterward. > But before we run it, we need to edit /etc/snmp/snmpd.conf > -so it contains this two lines: > +so it contains these four lines: > > rwcommunity public > master agentx > > +trap2sink localhost > +trapcommunity public > + > and then restart snmpd: > /etc/init.d/snmpd restart > > diff --git a/configure.ac b/configure.ac > index dcab0ae..d12f540 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -1,4 +1,4 @@ > -AC_INIT([libvirt-snmp],[0.0.1],[libvir-list@xxxxxxxxxx],[],[http://libvirt.org]) > +AC_INIT([libvirt-snmp],[0.0.2],[libvir-list@xxxxxxxxxx],[],[http://libvirt.org]) > AM_INIT_AUTOMAKE([-Wall -Werror]) > AC_CONFIG_HEADERS([config.h]) > > @@ -32,6 +32,14 @@ fi > AC_SUBST([LIBVIRT_CFLAGS]) > AC_SUBST([LIBVIRT_LIBS]) > > +dnl do we have old libvirt? > +AC_CHECK_LIB([virt], [virEventRunDefaultImpl], [old=0], [old=1]) > +if test $old = 1 ; then > + AC_DEFINE_UNQUOTED([LIBVIRT_OLD], ["$old"], [we are using old libvirt > + which does not have new event api]) > +fi > +AM_CONDITIONAL([LIBVIRT_OLD], [test $old = 1]) > + > SNMP_CONFIG="net-snmp-config" > SNMP_CFLAGS="" > SNMP_LIBS="" > @@ -86,5 +94,12 @@ fi > > AC_SUBST([MIB_DIR]) > > +dnl pthread > +PTHREAD_LIBS= > +AC_CHECK_HEADERS(pthread.h, [], [AC_MSG_ERROR([pthread.h required])]) > +AC_CHECK_LIB(pthread, pthread_create, [PTHREAD_LIBS="-lpthread"]) > + > +AC_SUBST([PTHREAD_LIBS]) > + > AC_OUTPUT(Makefile src/Makefile docs/Makefile libvirt-snmp.spec) > > diff --git a/libvirt-snmp.spec.in b/libvirt-snmp.spec.in > index bbc5602..293c375 100644 > --- a/libvirt-snmp.spec.in > +++ b/libvirt-snmp.spec.in > @@ -1,6 +1,6 @@ > Name: libvirt-snmp > Version: @VERSION@ > -Release: 3%{?dist}%{?extra_release} > +Release: 1%{?dist}%{?extra_release} > Summary: SNMP functionality for libvirt > > Group: Development/Libraries > @@ -36,8 +36,11 @@ make install DESTDIR=$RPM_BUILD_ROOT > > > %changelog > +* Wed Mar 23 2011 Michal Privoznik <mprivozn@xxxxxxxxxx> 0.0.2-1 > +- add SNMP trap/notification support > + > * Fri Mar 11 2011 Michal Privoznik <mprivozn@xxxxxxxxxx> 0.0.1-3 > -- remove LIBVIRT-MIB.txt from %doc > +- remove LIBVIRT-MIB.txt from doc > > * Wed Mar 9 2011 Michal Privoznik <mprivozn@xxxxxxxxxx> 0.0.1-2 > - resolve licensing conflicts > diff --git a/src/Makefile.am b/src/Makefile.am > index dcd463a..1a60c91 100644 > --- a/src/Makefile.am > +++ b/src/Makefile.am > @@ -9,8 +9,23 @@ AM_CFLAGS = \ > > AM_LDFLAGS = \ > $(COVERAGE_LDFLAGS) \ > + $(PTHREAD_LIBS) \ > $(SNMP_LIBS) > > +LIBVIRT_OLD_SRCS = \ > + threads.c \ > + event_poll.c \ > + memory.c \ > + util.c > + > +LIBVIRT_OLD_HDRS = \ > + internal.h \ > + ignore-value.h \ > + threads.h \ > + event_poll.h \ > + memory.h \ > + util.h > + > USER_SRCS = \ > libvirtGuestTable_data_get.c \ > libvirtGuestTable_data_set.c \ > @@ -43,10 +58,14 @@ HDRS = \ > libvirtMib_subagent_SOURCES=${SRCS} ${HDRS} > libvirtMib_subagent_LDFLAGS=${AM_LDFLAGS} > > +if LIBVIRT_OLD > +libvirtMib_subagent_SOURCES+=${LIBVIRT_OLD_SRCS} ${LIBVIRT_OLD_HDRS} > +endif > + > EXTRA_DIST = LIBVIRT-MIB.txt > > install-data-local: > - $(MKDIR_P) "$(DESTDIR)$(MIB_DIR)" > + test -z "$(DESTDIR)$(MIB_DIR)" || @mkdir_p@ "$(DESTDIR)$(MIB_DIR)" > $(INSTALL_DATA) "$(srcdir)/LIBVIRT-MIB.txt" \ > "$(DESTDIR)$(MIB_DIR)/LIBVIRT-MIB.txt" > > diff --git a/src/README.txt b/src/README.txt > index 6d010f6..5e9823a 100644 > --- a/src/README.txt > +++ b/src/README.txt > @@ -47,6 +47,8 @@ $ make > 2. use following /etc/snmp/snmpd.conf: > rwcommunity public > master agentx > +trap2sink localhost > +trapcommunity public > > 3. service snmpd start > > diff --git a/src/event_poll.c b/src/event_poll.c > new file mode 100644 > index 0000000..f8c4a8b > --- /dev/null > +++ b/src/event_poll.c > @@ -0,0 +1,724 @@ > +/* > + * event.c: event loop for monitoring file handles > + * > + * Copyright (C) 2007, 2010-2011 Red Hat, Inc. > + * Copyright (C) 2007 Daniel P. Berrange > + * > + * This library is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * This library 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 > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with this library; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> > + */ > + > +#include <config.h> > + > +#include <stdlib.h> > +#include <string.h> > +#include <poll.h> > +#include <sys/time.h> > +#include <errno.h> > +#include <unistd.h> > + > +#include "threads.h" > +#include "event_poll.h" > +#include "memory.h" > +#include "util.h" > +#include "ignore-value.h" > + > +#define EVENT_DEBUG(fmt, ...) VIR_DEBUG(fmt, __VA_ARGS__) > + > +static int virEventPollInterruptLocked(void); > + > +/* State for a single file handle being monitored */ > +struct virEventPollHandle { > + int watch; > + int fd; > + int events; > + virEventHandleCallback cb; > + virFreeCallback ff; > + void *opaque; > + int deleted; > +}; > + > +/* State for a single timer being generated */ > +struct virEventPollTimeout { > + int timer; > + int frequency; > + unsigned long long expiresAt; > + virEventTimeoutCallback cb; > + virFreeCallback ff; > + void *opaque; > + int deleted; > +}; > + > +/* Allocate extra slots for virEventPollHandle/virEventPollTimeout > + records in this multiple */ > +#define EVENT_ALLOC_EXTENT 10 > + > +/* State for the main event loop */ > +struct virEventPollLoop { > + virMutex lock; > + int running; > + virThread leader; > + int wakeupfd[2]; > + size_t handlesCount; > + size_t handlesAlloc; > + struct virEventPollHandle *handles; > + size_t timeoutsCount; > + size_t timeoutsAlloc; > + struct virEventPollTimeout *timeouts; > +}; > + > +/* Only have one event loop */ > +static struct virEventPollLoop eventLoop; > + > +/* Unique ID for the next FD watch to be registered */ > +static int nextWatch = 1; > + > +/* Unique ID for the next timer to be registered */ > +static int nextTimer = 1; > + > +/* > + * Register a callback for monitoring file handle events. > + * NB, it *must* be safe to call this from within a callback > + * For this reason we only ever append to existing list. > + */ > +int virEventPollAddHandle(int fd, int events, > + virEventHandleCallback cb, > + void *opaque, > + virFreeCallback ff) { > + int watch; > + EVENT_DEBUG("Add handle fd=%d events=%d cb=%p opaque=%p", fd, events, cb, opaque); > + virMutexLock(&eventLoop.lock); > + if (eventLoop.handlesCount == eventLoop.handlesAlloc) { > + EVENT_DEBUG("Used %zu handle slots, adding at least %d more", > + eventLoop.handlesAlloc, EVENT_ALLOC_EXTENT); > + if (VIR_RESIZE_N(eventLoop.handles, eventLoop.handlesAlloc, > + eventLoop.handlesCount, EVENT_ALLOC_EXTENT) < 0) { > + virMutexUnlock(&eventLoop.lock); > + return -1; > + } > + } > + > + watch = nextWatch++; > + > + eventLoop.handles[eventLoop.handlesCount].watch = watch; > + eventLoop.handles[eventLoop.handlesCount].fd = fd; > + eventLoop.handles[eventLoop.handlesCount].events = > + virEventPollToNativeEvents(events); > + eventLoop.handles[eventLoop.handlesCount].cb = cb; > + eventLoop.handles[eventLoop.handlesCount].ff = ff; > + eventLoop.handles[eventLoop.handlesCount].opaque = opaque; > + eventLoop.handles[eventLoop.handlesCount].deleted = 0; > + > + eventLoop.handlesCount++; > + > + virEventPollInterruptLocked(); > + virMutexUnlock(&eventLoop.lock); > + > + return watch; > +} > + > +void virEventPollUpdateHandle(int watch, int events) { > + int i; > + EVENT_DEBUG("Update handle w=%d e=%d", watch, events); > + > + if (watch <= 0) { > + VIR_WARN("Ignoring invalid update watch %d", watch); > + return; > + } > + > + virMutexLock(&eventLoop.lock); > + for (i = 0 ; i < eventLoop.handlesCount ; i++) { > + if (eventLoop.handles[i].watch == watch) { > + eventLoop.handles[i].events = > + virEventPollToNativeEvents(events); > + virEventPollInterruptLocked(); > + break; > + } > + } > + virMutexUnlock(&eventLoop.lock); > +} > + > +/* > + * Unregister a callback from a file handle > + * NB, it *must* be safe to call this from within a callback > + * For this reason we only ever set a flag in the existing list. > + * Actual deletion will be done out-of-band > + */ > +int virEventPollRemoveHandle(int watch) { > + int i; > + EVENT_DEBUG("Remove handle w=%d", watch); > + > + if (watch <= 0) { > + VIR_WARN("Ignoring invalid remove watch %d", watch); > + return -1; > + } > + > + virMutexLock(&eventLoop.lock); > + for (i = 0 ; i < eventLoop.handlesCount ; i++) { > + if (eventLoop.handles[i].deleted) > + continue; > + > + if (eventLoop.handles[i].watch == watch) { > + EVENT_DEBUG("mark delete %d %d", i, eventLoop.handles[i].fd); > + eventLoop.handles[i].deleted = 1; > + virEventPollInterruptLocked(); > + virMutexUnlock(&eventLoop.lock); > + return 0; > + } > + } > + virMutexUnlock(&eventLoop.lock); > + return -1; > +} > + > + > +/* > + * Register a callback for a timer event > + * NB, it *must* be safe to call this from within a callback > + * For this reason we only ever append to existing list. > + */ > +int virEventPollAddTimeout(int frequency, > + virEventTimeoutCallback cb, > + void *opaque, > + virFreeCallback ff) { > + struct timeval now; > + int ret; > + EVENT_DEBUG("Adding timer %d with %d ms freq", nextTimer, frequency); > + if (gettimeofday(&now, NULL) < 0) { > + return -1; > + } > + > + virMutexLock(&eventLoop.lock); > + if (eventLoop.timeoutsCount == eventLoop.timeoutsAlloc) { > + EVENT_DEBUG("Used %zu timeout slots, adding at least %d more", > + eventLoop.timeoutsAlloc, EVENT_ALLOC_EXTENT); > + if (VIR_RESIZE_N(eventLoop.timeouts, eventLoop.timeoutsAlloc, > + eventLoop.timeoutsCount, EVENT_ALLOC_EXTENT) < 0) { > + virMutexUnlock(&eventLoop.lock); > + return -1; > + } > + } > + > + eventLoop.timeouts[eventLoop.timeoutsCount].timer = nextTimer++; > + eventLoop.timeouts[eventLoop.timeoutsCount].frequency = frequency; > + eventLoop.timeouts[eventLoop.timeoutsCount].cb = cb; > + eventLoop.timeouts[eventLoop.timeoutsCount].ff = ff; > + eventLoop.timeouts[eventLoop.timeoutsCount].opaque = opaque; > + eventLoop.timeouts[eventLoop.timeoutsCount].deleted = 0; > + eventLoop.timeouts[eventLoop.timeoutsCount].expiresAt = > + frequency >= 0 ? frequency + > + (((unsigned long long)now.tv_sec)*1000) + > + (((unsigned long long)now.tv_usec)/1000) : 0; > + > + eventLoop.timeoutsCount++; > + ret = nextTimer-1; > + virEventPollInterruptLocked(); > + virMutexUnlock(&eventLoop.lock); > + return ret; > +} > + > +void virEventPollUpdateTimeout(int timer, int frequency) { > + struct timeval tv; > + int i; > + EVENT_DEBUG("Updating timer %d timeout with %d ms freq", timer, frequency); > + > + if (timer <= 0) { > + VIR_WARN("Ignoring invalid update timer %d", timer); > + return; > + } > + > + if (gettimeofday(&tv, NULL) < 0) { > + return; > + } > + > + virMutexLock(&eventLoop.lock); > + for (i = 0 ; i < eventLoop.timeoutsCount ; i++) { > + if (eventLoop.timeouts[i].timer == timer) { > + eventLoop.timeouts[i].frequency = frequency; > + eventLoop.timeouts[i].expiresAt = > + frequency >= 0 ? frequency + > + (((unsigned long long)tv.tv_sec)*1000) + > + (((unsigned long long)tv.tv_usec)/1000) : 0; > + virEventPollInterruptLocked(); > + break; > + } > + } > + virMutexUnlock(&eventLoop.lock); > +} > + > +/* > + * Unregister a callback for a timer > + * NB, it *must* be safe to call this from within a callback > + * For this reason we only ever set a flag in the existing list. > + * Actual deletion will be done out-of-band > + */ > +int virEventPollRemoveTimeout(int timer) { > + int i; > + EVENT_DEBUG("Remove timer %d", timer); > + > + if (timer <= 0) { > + VIR_WARN("Ignoring invalid remove timer %d", timer); > + return -1; > + } > + > + virMutexLock(&eventLoop.lock); > + for (i = 0 ; i < eventLoop.timeoutsCount ; i++) { > + if (eventLoop.timeouts[i].deleted) > + continue; > + > + if (eventLoop.timeouts[i].timer == timer) { > + eventLoop.timeouts[i].deleted = 1; > + virEventPollInterruptLocked(); > + virMutexUnlock(&eventLoop.lock); > + return 0; > + } > + } > + virMutexUnlock(&eventLoop.lock); > + return -1; > +} > + > +/* Iterates over all registered timeouts and determine which > + * will be the first to expire. > + * @timeout: filled with expiry time of soonest timer, or -1 if > + * no timeout is pending > + * returns: 0 on success, -1 on error > + */ > +static int virEventPollCalculateTimeout(int *timeout) { > + unsigned long long then = 0; > + int i; > + EVENT_DEBUG("Calculate expiry of %zu timers", eventLoop.timeoutsCount); > + /* Figure out if we need a timeout */ > + for (i = 0 ; i < eventLoop.timeoutsCount ; i++) { > + if (eventLoop.timeouts[i].frequency < 0) > + continue; > + > + EVENT_DEBUG("Got a timeout scheduled for %llu", eventLoop.timeouts[i].expiresAt); > + if (then == 0 || > + eventLoop.timeouts[i].expiresAt < then) > + then = eventLoop.timeouts[i].expiresAt; > + } > + > + /* Calculate how long we should wait for a timeout if needed */ > + if (then > 0) { > + struct timeval tv; > + > + if (gettimeofday(&tv, NULL) < 0) { > + perror("Unable to get current time"); > + return -1; > + } > + > + *timeout = then - > + ((((unsigned long long)tv.tv_sec)*1000) + > + (((unsigned long long)tv.tv_usec)/1000)); > + > + if (*timeout < 0) > + *timeout = 0; > + } else { > + *timeout = -1; > + } > + > + EVENT_DEBUG("Timeout at %llu due in %d ms", then, *timeout); > + > + return 0; > +} > + > +/* > + * Allocate a pollfd array containing data for all registered > + * file handles. The caller must free the returned data struct > + * returns: the pollfd array, or NULL on error > + */ > +static struct pollfd *virEventPollMakePollFDs(int *nfds) { > + struct pollfd *fds; > + int i; > + > + *nfds = 0; > + for (i = 0 ; i < eventLoop.handlesCount ; i++) { > + if (eventLoop.handles[i].events && !eventLoop.handles[i].deleted) > + (*nfds)++; > + } > + > + /* Setup the poll file handle data structs */ > + if (VIR_ALLOC_N(fds, *nfds) < 0) { > + perror("unable to allocate memory"); > + return NULL; > + } > + > + *nfds = 0; > + for (i = 0 ; i < eventLoop.handlesCount ; i++) { > + EVENT_DEBUG("Prepare n=%d w=%d, f=%d e=%d d=%d", i, > + eventLoop.handles[i].watch, > + eventLoop.handles[i].fd, > + eventLoop.handles[i].events, > + eventLoop.handles[i].deleted); > + if (!eventLoop.handles[i].events || eventLoop.handles[i].deleted) > + continue; > + fds[*nfds].fd = eventLoop.handles[i].fd; > + fds[*nfds].events = eventLoop.handles[i].events; > + fds[*nfds].revents = 0; > + (*nfds)++; > + //EVENT_DEBUG("Wait for %d %d", eventLoop.handles[i].fd, eventLoop.handles[i].events); > + } > + > + return fds; > +} > + > + > +/* > + * Iterate over all timers and determine if any have expired. > + * Invoke the user supplied callback for each timer whose > + * expiry time is met, and schedule the next timeout. Does > + * not try to 'catch up' on time if the actual expiry time > + * was later than the requested time. > + * > + * This method must cope with new timers being registered > + * by a callback, and must skip any timers marked as deleted. > + * > + * Returns 0 upon success, -1 if an error occurred > + */ > +static int virEventPollDispatchTimeouts(void) { > + struct timeval tv; > + unsigned long long now; > + int i; > + /* Save this now - it may be changed during dispatch */ > + int ntimeouts = eventLoop.timeoutsCount; > + VIR_DEBUG("Dispatch %d", ntimeouts); > + > + if (gettimeofday(&tv, NULL) < 0) { > + perror("Unable to get current time"); > + return -1; > + } > + now = (((unsigned long long)tv.tv_sec)*1000) + > + (((unsigned long long)tv.tv_usec)/1000); > + > + for (i = 0 ; i < ntimeouts ; i++) { > + if (eventLoop.timeouts[i].deleted || eventLoop.timeouts[i].frequency < 0) > + continue; > + > + /* Add 20ms fuzz so we don't pointlessly spin doing > + * <10ms sleeps, particularly on kernels with low HZ > + * it is fine that a timer expires 20ms earlier than > + * requested > + */ > + if (eventLoop.timeouts[i].expiresAt <= (now+20)) { > + virEventTimeoutCallback cb = eventLoop.timeouts[i].cb; > + int timer = eventLoop.timeouts[i].timer; > + void *opaque = eventLoop.timeouts[i].opaque; > + eventLoop.timeouts[i].expiresAt = > + now + eventLoop.timeouts[i].frequency; > + > + virMutexUnlock(&eventLoop.lock); > + (cb)(timer, opaque); > + virMutexLock(&eventLoop.lock); > + } > + } > + return 0; > +} > + > + > +/* Iterate over all file handles and dispatch any which > + * have pending events listed in the poll() data. Invoke > + * the user supplied callback for each handle which has > + * pending events > + * > + * This method must cope with new handles being registered > + * by a callback, and must skip any handles marked as deleted. > + * > + * Returns 0 upon success, -1 if an error occurred > + */ > +static int virEventPollDispatchHandles(int nfds, struct pollfd *fds) { > + int i, n; > + VIR_DEBUG("Dispatch %d", nfds); > + > + /* NB, use nfds not eventLoop.handlesCount, because new > + * fds might be added on end of list, and they're not > + * in the fds array we've got */ > + for (i = 0, n = 0 ; n < nfds && i < eventLoop.handlesCount ; n++) { > + while ((eventLoop.handles[i].fd != fds[n].fd || > + eventLoop.handles[i].events == 0) && > + i < eventLoop.handlesCount) { > + i++; > + } > + if (i == eventLoop.handlesCount) > + break; > + > + VIR_DEBUG("i=%d w=%d", i, eventLoop.handles[i].watch); > + if (eventLoop.handles[i].deleted) { > + EVENT_DEBUG("Skip deleted n=%d w=%d f=%d", i, > + eventLoop.handles[i].watch, eventLoop.handles[i].fd); > + continue; > + } > + > + if (fds[n].revents) { > + virEventHandleCallback cb = eventLoop.handles[i].cb; > + int watch = eventLoop.handles[i].watch; > + void *opaque = eventLoop.handles[i].opaque; > + int hEvents = virEventPollFromNativeEvents(fds[n].revents); > + EVENT_DEBUG("Dispatch n=%d f=%d w=%d e=%d %p", i, > + fds[n].fd, watch, fds[n].revents, opaque); > + virMutexUnlock(&eventLoop.lock); > + (cb)(watch, fds[n].fd, hEvents, opaque); > + virMutexLock(&eventLoop.lock); > + } > + } > + > + return 0; > +} > + > + > +/* Used post dispatch to actually remove any timers that > + * were previously marked as deleted. This asynchronous > + * cleanup is needed to make dispatch re-entrant safe. > + */ > +static void virEventPollCleanupTimeouts(void) { > + int i; > + size_t gap; > + VIR_DEBUG("Cleanup %zu", eventLoop.timeoutsCount); > + > + /* Remove deleted entries, shuffling down remaining > + * entries as needed to form contiguous series > + */ > + for (i = 0 ; i < eventLoop.timeoutsCount ; ) { > + if (!eventLoop.timeouts[i].deleted) { > + i++; > + continue; > + } > + > + EVENT_DEBUG("Purging timeout %d with id %d", i, > + eventLoop.timeouts[i].timer); > + if (eventLoop.timeouts[i].ff) { > + virFreeCallback ff = eventLoop.timeouts[i].ff; > + void *opaque = eventLoop.timeouts[i].opaque; > + virMutexUnlock(&eventLoop.lock); > + ff(opaque); > + virMutexLock(&eventLoop.lock); > + } > + > + if ((i+1) < eventLoop.timeoutsCount) { > + memmove(eventLoop.timeouts+i, > + eventLoop.timeouts+i+1, > + sizeof(struct virEventPollTimeout)*(eventLoop.timeoutsCount > + -(i+1))); > + } > + eventLoop.timeoutsCount--; > + } > + > + /* Release some memory if we've got a big chunk free */ > + gap = eventLoop.timeoutsAlloc - eventLoop.timeoutsCount; > + if (eventLoop.timeoutsCount == 0 || > + (gap > eventLoop.timeoutsCount && gap > EVENT_ALLOC_EXTENT)) { > + EVENT_DEBUG("Found %zu out of %zu timeout slots used, releasing %zu", > + eventLoop.timeoutsCount, eventLoop.timeoutsAlloc, gap); > + VIR_SHRINK_N(eventLoop.timeouts, eventLoop.timeoutsAlloc, gap); > + } > +} > + > +/* Used post dispatch to actually remove any handles that > + * were previously marked as deleted. This asynchronous > + * cleanup is needed to make dispatch re-entrant safe. > + */ > +static void virEventPollCleanupHandles(void) { > + int i; > + size_t gap; > + VIR_DEBUG("Cleanup %zu", eventLoop.handlesCount); > + > + /* Remove deleted entries, shuffling down remaining > + * entries as needed to form contiguous series > + */ > + for (i = 0 ; i < eventLoop.handlesCount ; ) { > + if (!eventLoop.handles[i].deleted) { > + i++; > + continue; > + } > + > + if (eventLoop.handles[i].ff) { > + virFreeCallback ff = eventLoop.handles[i].ff; > + void *opaque = eventLoop.handles[i].opaque; > + virMutexUnlock(&eventLoop.lock); > + ff(opaque); > + virMutexLock(&eventLoop.lock); > + } > + > + if ((i+1) < eventLoop.handlesCount) { > + memmove(eventLoop.handles+i, > + eventLoop.handles+i+1, > + sizeof(struct virEventPollHandle)*(eventLoop.handlesCount > + -(i+1))); > + } > + eventLoop.handlesCount--; > + } > + > + /* Release some memory if we've got a big chunk free */ > + gap = eventLoop.handlesAlloc - eventLoop.handlesCount; > + if (eventLoop.handlesCount == 0 || > + (gap > eventLoop.handlesCount && gap > EVENT_ALLOC_EXTENT)) { > + EVENT_DEBUG("Found %zu out of %zu handles slots used, releasing %zu", > + eventLoop.handlesCount, eventLoop.handlesAlloc, gap); > + VIR_SHRINK_N(eventLoop.handles, eventLoop.handlesAlloc, gap); > + } > +} > + > +/* > + * Run a single iteration of the event loop, blocking until > + * at least one file handle has an event, or a timer expires > + */ > +int virEventPollRunOnce(void) { > + struct pollfd *fds = NULL; > + int ret, timeout, nfds; > + > + virMutexLock(&eventLoop.lock); > + eventLoop.running = 1; > + virThreadSelf(&eventLoop.leader); > + > + virEventPollCleanupTimeouts(); > + virEventPollCleanupHandles(); > + > + if (!(fds = virEventPollMakePollFDs(&nfds)) || > + virEventPollCalculateTimeout(&timeout) < 0) > + goto error; > + > + virMutexUnlock(&eventLoop.lock); > + > + retry: > + EVENT_DEBUG("Poll on %d handles %p timeout %d", nfds, fds, timeout); > + ret = poll(fds, nfds, timeout); > + if (ret < 0) { > + EVENT_DEBUG("Poll got error event %d", errno); > + if (errno == EINTR) { > + goto retry; > + } > + perror("Unable to poll on file handles"); > + goto error_unlocked; > + } > + EVENT_DEBUG("Poll got %d event(s)", ret); > + > + virMutexLock(&eventLoop.lock); > + if (virEventPollDispatchTimeouts() < 0) > + goto error; > + > + if (ret > 0 && > + virEventPollDispatchHandles(nfds, fds) < 0) > + goto error; > + > + virEventPollCleanupTimeouts(); > + virEventPollCleanupHandles(); > + > + eventLoop.running = 0; > + virMutexUnlock(&eventLoop.lock); > + VIR_FREE(fds); > + return 0; > + > +error: > + virMutexUnlock(&eventLoop.lock); > +error_unlocked: > + VIR_FREE(fds); > + return -1; > +} > + > + > +static void virEventPollHandleWakeup(int watch ATTRIBUTE_UNUSED, > + int fd, > + int events ATTRIBUTE_UNUSED, > + void *opaque ATTRIBUTE_UNUSED) > +{ > + char c; > + virMutexLock(&eventLoop.lock); > + ignore_value(saferead(fd, &c, sizeof(c))); > + virMutexUnlock(&eventLoop.lock); > +} > + > +int virEventPollInit(void) > +{ > + if (virMutexInit(&eventLoop.lock) < 0) { > + perror("Unable to initialize mutex"); > + return -1; > + } > + > + if (pipe(eventLoop.wakeupfd) < 0 || > + virSetNonBlock(eventLoop.wakeupfd[0]) < 0 || > + virSetNonBlock(eventLoop.wakeupfd[1]) < 0 || > + virSetCloseExec(eventLoop.wakeupfd[0]) < 0 || > + virSetCloseExec(eventLoop.wakeupfd[1]) < 0) { > + perror("Unable to setup wakeup pipe"); > + return -1; > + } > + > + if (virEventPollAddHandle(eventLoop.wakeupfd[0], > + VIR_EVENT_HANDLE_READABLE, > + virEventPollHandleWakeup, NULL, NULL) < 0) { > + fprintf(stderr, "Unable to add handle %d to event loop", > + eventLoop.wakeupfd[0]); > + return -1; > + } > + > + return 0; > +} > + > +static int virEventPollInterruptLocked(void) > +{ > + char c = '\0'; > + > + if (!eventLoop.running || > + virThreadIsSelf(&eventLoop.leader)) { > + VIR_DEBUG("Skip interrupt, %d %d", eventLoop.running, > + virThreadID(&eventLoop.leader)); > + return 0; > + } > + > + VIR_DEBUG0("Interrupting"); > + if (safewrite(eventLoop.wakeupfd[1], &c, sizeof(c)) != sizeof(c)) > + return -1; > + return 0; > +} > + > +int virEventPollInterrupt(void) > +{ > + int ret; > + virMutexLock(&eventLoop.lock); > + ret = virEventPollInterruptLocked(); > + virMutexUnlock(&eventLoop.lock); > + return ret; > +} > + > +int > +virEventPollToNativeEvents(int events) > +{ > + int ret = 0; > + if(events & VIR_EVENT_HANDLE_READABLE) > + ret |= POLLIN; > + if(events & VIR_EVENT_HANDLE_WRITABLE) > + ret |= POLLOUT; > + if(events & VIR_EVENT_HANDLE_ERROR) > + ret |= POLLERR; > + if(events & VIR_EVENT_HANDLE_HANGUP) > + ret |= POLLHUP; > + return ret; > +} > + > +int > +virEventPollFromNativeEvents(int events) > +{ > + int ret = 0; > + if(events & POLLIN) > + ret |= VIR_EVENT_HANDLE_READABLE; > + if(events & POLLOUT) > + ret |= VIR_EVENT_HANDLE_WRITABLE; > + if(events & POLLERR) > + ret |= VIR_EVENT_HANDLE_ERROR; > + if(events & POLLNVAL) /* Treat NVAL as error, since libvirt doesn't distinguish */ > + ret |= VIR_EVENT_HANDLE_ERROR; > + if(events & POLLHUP) > + ret |= VIR_EVENT_HANDLE_HANGUP; > + return ret; > +} > diff --git a/src/event_poll.h b/src/event_poll.h > new file mode 100644 > index 0000000..4ab3789 > --- /dev/null > +++ b/src/event_poll.h > @@ -0,0 +1,132 @@ > +/* > + * event.h: event loop for monitoring file handles > + * > + * Copyright (C) 2007 Daniel P. Berrange > + * Copyright (C) 2007 Red Hat, Inc. > + * > + * This library is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * This library 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 > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with this library; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> > + */ > + > +#ifndef __VIR_EVENT_POLL_H__ > +# define __VIR_EVENT_POLL_H__ > + > +# include "internal.h" > + > +/** > + * virEventPollAddHandle: register a callback for monitoring file handle events > + * > + * @fd: file handle to monitor for events > + * @events: bitset of events to watch from POLLnnn constants > + * @cb: callback to invoke when an event occurs > + * @opaque: user data to pass to callback > + * > + * returns -1 if the file handle cannot be registered, 0 upon success > + */ > +int virEventPollAddHandle(int fd, int events, > + virEventHandleCallback cb, > + void *opaque, > + virFreeCallback ff); > + > +/** > + * virEventPollUpdateHandle: change event set for a monitored file handle > + * > + * @watch: watch whose handle to update > + * @events: bitset of events to watch from POLLnnn constants > + * > + * Will not fail if fd exists > + */ > +void virEventPollUpdateHandle(int watch, int events); > + > +/** > + * virEventPollRemoveHandle: unregister a callback from a file handle > + * > + * @watch: watch whose handle to remove > + * > + * returns -1 if the file handle was not registered, 0 upon success > + */ > +int virEventPollRemoveHandle(int watch); > + > +/** > + * virEventPollAddTimeout: register a callback for a timer event > + * > + * @frequency: time between events in milliseconds > + * @cb: callback to invoke when an event occurs > + * @opaque: user data to pass to callback > + * > + * Setting frequency to -1 will disable the timer. Setting the frequency > + * to zero will cause it to fire on every event loop iteration. > + * > + * returns -1 if the file handle cannot be registered, a positive > + * integer timer id upon success > + */ > +int virEventPollAddTimeout(int frequency, > + virEventTimeoutCallback cb, > + void *opaque, > + virFreeCallback ff); > + > +/** > + * virEventPollUpdateTimeout: change frequency for a timer > + * > + * @timer: timer id to change > + * @frequency: time between events in milliseconds > + * > + * Setting frequency to -1 will disable the timer. Setting the frequency > + * to zero will cause it to fire on every event loop iteration. > + * > + * Will not fail if timer exists > + */ > +void virEventPollUpdateTimeout(int timer, int frequency); > + > +/** > + * virEventPollRemoveTimeout: unregister a callback for a timer > + * > + * @timer: the timer id to remove > + * > + * returns -1 if the timer was not registered, 0 upon success > + */ > +int virEventPollRemoveTimeout(int timer); > + > +/** > + * virEventPollInit: Initialize the event loop > + * > + * returns -1 if initialization failed > + */ > +int virEventPollInit(void); > + > +/** > + * virEventPollRunOnce: run a single iteration of the event loop. > + * > + * Blocks the caller until at least one file handle has an > + * event or the first timer expires. > + * > + * returns -1 if the event monitoring failed > + */ > +int virEventPollRunOnce(void); > + > +int virEventPollFromNativeEvents(int events); > +int virEventPollToNativeEvents(int events); > + > + > +/** > + * virEventPollInterrupt: wakeup any thread waiting in poll() > + * > + * return -1 if wakup failed > + */ > +int virEventPollInterrupt(void); > + > + > +#endif /* __VIRTD_EVENT_H__ */ > diff --git a/src/ignore-value.h b/src/ignore-value.h > new file mode 100644 > index 0000000..0df1c01 > --- /dev/null > +++ b/src/ignore-value.h > @@ -0,0 +1,64 @@ > +/* -*- buffer-read-only: t -*- vi: set ro: */ > +/* DO NOT EDIT! GENERATED AUTOMATICALLY! */ > +/* ignore a function return without a compiler warning > + > + Copyright (C) 2008-2011 Free Software Foundation, Inc. > + > + This program is free software: you can redistribute it and/or modify > + it under the terms of the GNU Lesser General Public License as published by > + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. > + > + You should have received a copy of the GNU Lesser General Public License > + along with this program. If not, see <http://www.gnu.org/licenses/>. */ > + > +/* Written by Jim Meyering, Eric Blake and PÃdraig Brady. */ > + > +/* Use "ignore_value" to avoid a warning when using a function declared with > + gcc's warn_unused_result attribute, but for which you really do want to > + ignore the result. Traditionally, people have used a "(void)" cast to > + indicate that a function's return value is deliberately unused. However, > + if the function is declared with __attribute__((warn_unused_result)), > + gcc issues a warning even with the cast. > + > + Caution: most of the time, you really should heed gcc's warning, and > + check the return value. However, in those exceptional cases in which > + you're sure you know what you're doing, use this function. > + > + For the record, here's one of the ignorable warnings: > + "copy.c:233: warning: ignoring return value of 'fchown', > + declared with attribute warn_unused_result". */ > + > +#ifndef _GL_IGNORE_VALUE_H > +# define _GL_IGNORE_VALUE_H > + > +# ifndef _GL_ATTRIBUTE_DEPRECATED > +/* The __attribute__((__deprecated__)) feature > + is available in gcc versions 3.1 and newer. */ > +# if __GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 1) > +# define _GL_ATTRIBUTE_DEPRECATED /* empty */ > +# else > +# define _GL_ATTRIBUTE_DEPRECATED __attribute__ ((__deprecated__)) > +# endif > +# endif > + > +/* The __attribute__((__warn_unused_result__)) feature > + is available in gcc versions 3.4 and newer, > + while the typeof feature has been available since 2.7 at least. */ > +# if __GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) > +# define ignore_value(x) ((void) (x)) > +# else > +# define ignore_value(x) (({ __typeof__ (x) __x = (x); (void) __x; })) > +# endif > + > +/* ignore_value works for scalars, pointers and aggregates; > + deprecate ignore_ptr. */ > +static inline void _GL_ATTRIBUTE_DEPRECATED > +ignore_ptr (void *p) { (void) p; } /* deprecated: use ignore_value */ > + > +#endif > diff --git a/src/internal.h b/src/internal.h > new file mode 100644 > index 0000000..eaa6d70 > --- /dev/null > +++ b/src/internal.h > @@ -0,0 +1,267 @@ > +/* > + * internal.h: internal definitions just used by code from the library > + * > + * Copy: Copyright (C) 2005-2006, 2010-2011 Red Hat, Inc. > + * > + * See libvirt's COPYING.LIB for the License of this software > + * > + */ > + > +#ifndef __INTERNAL_H__ > +# define __INTERNAL_H__ > + > +# include <stdio.h> > +# include <stdlib.h> > +# include <stdbool.h> > +# include <stddef.h> > +# include <errno.h> > +# include <string.h> > +# include <libvirt/libvirt.h> > +# include <libvirt/virterror.h> Since you have included libvirt.h > +/** > + * virEventHandleCallback: > + * > + * @watch: watch on which the event occurred > + * @fd: file handle on which the event occurred > + * @events: bitset of events from virEventHandleType constants > + * @opaque: user data registered with handle > + * > + * Callback for receiving file handle events. The callback will > + * be invoked once for each event which is pending. > + */ > +typedef void (*virEventHandleCallback)(int watch, int fd, int events, void *opaque); > + > +/** > + * virEventAddHandleFunc: > + * @fd: file descriptor to listen on > + * @event: bitset of events on which to fire the callback > + * @cb: the callback to be called when an event occurrs > + * @opaque: user data to pass to the callback > + * @ff: the callback invoked to free opaque data blob > + * > + * Part of the EventImpl, this callback Adds a file handle callback to > + * listen for specific events. The same file handle can be registered > + * multiple times provided the requested event sets are non-overlapping > + * > + * If the opaque user data requires free'ing when the handle > + * is unregistered, then a 2nd callback can be supplied for > + * this purpose. > + * > + * Returns a handle watch number to be used for updating > + * and unregistering for events > + */ > +typedef int (*virEventAddHandleFunc)(int fd, int event, > + virEventHandleCallback cb, > + void *opaque, > + virFreeCallback ff); > + > +/** > + * virEventUpdateHandleFunc: > + * @watch: file descriptor watch to modify > + * @event: new events to listen on > + * > + * Part of the EventImpl, this user-provided callback is notified when > + * events to listen on change > + */ > +typedef void (*virEventUpdateHandleFunc)(int watch, int event); > + > +/** > + * virEventRemoveHandleFunc: > + * @watch: file descriptor watch to stop listening on > + * > + * Part of the EventImpl, this user-provided callback is notified when > + * an fd is no longer being listened on. > + * > + * If a virEventHandleFreeFunc was supplied when the handle was > + * registered, it will be invoked some time during, or after this > + * function call, when it is safe to release the user data. > + */ > +typedef int (*virEventRemoveHandleFunc)(int watch); > + > +/** > + * virEventTimeoutCallback: > + * > + * @timer: timer id emitting the event > + * @opaque: user data registered with handle > + * > + * callback for receiving timer events > + */ > +typedef void (*virEventTimeoutCallback)(int timer, void *opaque); > + > +/** > + * virEventAddTimeoutFunc: > + * @timeout: The timeout to monitor > + * @cb: the callback to call when timeout has expired > + * @opaque: user data to pass to the callback > + * @ff: the callback invoked to free opaque data blob > + * > + * Part of the EventImpl, this user-defined callback handles adding an > + * event timeout. > + * > + * If the opaque user data requires free'ing when the handle > + * is unregistered, then a 2nd callback can be supplied for > + * this purpose. > + * > + * Returns a timer value > + */ > +typedef int (*virEventAddTimeoutFunc)(int timeout, > + virEventTimeoutCallback cb, > + void *opaque, > + virFreeCallback ff); > + > +/** > + * virEventUpdateTimeoutFunc: > + * @timer: the timer to modify > + * @timeout: the new timeout value > + * > + * Part of the EventImpl, this user-defined callback updates an > + * event timeout. > + */ > +typedef void (*virEventUpdateTimeoutFunc)(int timer, int timeout); > + > +/** > + * virEventRemoveTimeoutFunc: > + * @timer: the timer to remove > + * > + * Part of the EventImpl, this user-defined callback removes a timer > + * > + * If a virEventTimeoutFreeFunc was supplied when the handle was > + * registered, it will be invoked some time during, or after this > + * function call, when it is safe to release the user data. > + * > + * Returns 0 on success, -1 on failure > + */ > +typedef int (*virEventRemoveTimeoutFunc)(int timer); > + > +void virEventRegisterImpl(virEventAddHandleFunc addHandle, > + virEventUpdateHandleFunc updateHandle, > + virEventRemoveHandleFunc removeHandle, > + virEventAddTimeoutFunc addTimeout, > + virEventUpdateTimeoutFunc updateTimeout, > + virEventRemoveTimeoutFunc removeTimeout); > + > +int virEventRegisterDefaultImpl(void); > +int virEventRunDefaultImpl(void); You don't need any of these functions or typedefs. ACK if those are removed. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :| -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list