[libvirt] [libvirt-snmp][PATCH v5 0/4] Add SNMP trap/notification support

The fifth version. In case we build against libvirt older than 0.9.0 (which have old event API), we need to include some sources from libvirt git. So we are able to run even on older RHELs, like 5.6. Those files were copied to our git and modified very slighty. Finally, we have a proof-of-concept implementation of SNMP trap. Two new files were first generated using mib2c then edited, so be careful when re-generating them. Destination of traps is defined in snmpd.conf (trap2sink), and to receive them snmptrapd must be configured and up. The idea behind is - create a thread in which we poll for domain events. Once we receive event notification, we just fire up trap sending. Michal Privoznik (4): Add notification-type object to libvirt MIB Add libvirt error handling function Create functions to fill in and send notification packets. Add SNMP trap/notification support. INSTALL.1st | 9 +- configure.ac | 17 +- libvirt-snmp.spec.in | 7 +- src/LIBVIRT-MIB.txt | 9 + src/Makefile.am | 25 ++- src/README.txt | 9 +- src/event_poll.c | 724 ++++++++++++++++++++++++++++++++++++++++++++ src/event_poll.h | 132 ++++++++ src/ignore-value.h | 64 ++++ src/internal.h | 267 ++++++++++++++++ src/libvirtNotifications.c | 122 ++++++++ src/libvirtNotifications.h | 33 ++ src/libvirtSnmp.c | 154 +++++++++- src/libvirtSnmpError.c | 34 ++ src/libvirtSnmpError.h | 31 ++ src/memory.c | 313 +++++++++++++++++++ src/memory.h | 212 +++++++++++++ src/threads.c | 251 +++++++++++++++ src/threads.h | 101 ++++++ src/util.c | 105 +++++++ src/util.h | 38 +++ 21 files changed, 2643 insertions(+), 14 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/libvirtNotifications.c create mode 100644 src/libvirtNotifications.h create mode 100644 src/libvirtSnmpError.c create mode 100644 src/libvirtSnmpError.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 -- 1.7.4.2

--- src/LIBVIRT-MIB.txt | 9 +++++++++ 1 files changed, 9 insertions(+), 0 deletions(-) diff --git a/src/LIBVIRT-MIB.txt b/src/LIBVIRT-MIB.txt index 607d441..932dec9 100644 --- a/src/LIBVIRT-MIB.txt +++ b/src/LIBVIRT-MIB.txt @@ -201,5 +201,14 @@ libvirtGuestGroup OBJECT-GROUP guests." ::= { libvirtGroups 1 } +libvirtGuestNotif NOTIFICATION-TYPE + STATUS current + OBJECTS { libvirtGuestName, + libvirtGuestUUID, + libvirtGuestState, + libvirtGuestRowStatus } + DESCRIPTION + "Guest lifecycle notification." + ::= { libvirtNotifications 1 } END -- 1.7.4.2

--- src/Makefile.am | 2 ++ src/libvirtSnmpError.c | 34 ++++++++++++++++++++++++++++++++++ src/libvirtSnmpError.h | 31 +++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 0 deletions(-) create mode 100644 src/libvirtSnmpError.c create mode 100644 src/libvirtSnmpError.h diff --git a/src/Makefile.am b/src/Makefile.am index c781e23..57b74c8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,12 +15,14 @@ USER_SRCS = \ libvirtGuestTable_data_get.c \ libvirtGuestTable_data_set.c \ libvirtGuestTable_data_access.c \ + libvirtSnmpError.c \ libvirtSnmp.c USER_HDRS = \ libvirtGuestTable_data_get.h \ libvirtGuestTable_data_set.h \ libvirtGuestTable_data_access.h \ + libvirtSnmpError.h \ libvirtSnmp.h SRCS = \ diff --git a/src/libvirtSnmpError.c b/src/libvirtSnmpError.c new file mode 100644 index 0000000..f5d546d --- /dev/null +++ b/src/libvirtSnmpError.c @@ -0,0 +1,34 @@ +/* + * libvirtSnmpError.c: libvirt error handling function + * + * Copyright (C) 2011 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Michal Privoznik <mprivozn@redhat.com> + */ + +#include "libvirtSnmpError.h" +#include <stdio.h> + +/** + * Print libvirt error + * @msg Error message + */ +void printLibvirtError(const char *msg) { + virErrorPtr err = virGetLastError(); + + fprintf(stderr, "%s: %s", msg, err ? err->message : "Unknown error"); +} diff --git a/src/libvirtSnmpError.h b/src/libvirtSnmpError.h new file mode 100644 index 0000000..518cc03 --- /dev/null +++ b/src/libvirtSnmpError.h @@ -0,0 +1,31 @@ +/* + * libvirtSnmpError.h: libvirt error handling function + * + * Copyright (C) 2011 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Michal Privoznik <mprivozn@redhat.com> + */ + +#ifndef __VIR_SNMP_ERROR_H__ +#define __VIR_SNMP_ERROR_H__ + +#include <libvirt/libvirt.h> +#include <libvirt/virterror.h> + +extern void printLibvirtError(const char *msg); + +#endif -- 1.7.4.2

Before sending snmp notification we need to fill in useful data, like domain status, UUID, etc. --- src/Makefile.am | 2 + src/README.txt | 7 ++- src/libvirtNotifications.c | 110 ++++++++++++++++++++++++++++++++++++++++++++ src/libvirtNotifications.h | 21 ++++++++ 4 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 src/libvirtNotifications.c create mode 100644 src/libvirtNotifications.h diff --git a/src/Makefile.am b/src/Makefile.am index 57b74c8..dcd463a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -16,6 +16,7 @@ USER_SRCS = \ libvirtGuestTable_data_set.c \ libvirtGuestTable_data_access.c \ libvirtSnmpError.c \ + libvirtNotifications.c \ libvirtSnmp.c USER_HDRS = \ @@ -23,6 +24,7 @@ USER_HDRS = \ libvirtGuestTable_data_set.h \ libvirtGuestTable_data_access.h \ libvirtSnmpError.h \ + libvirtNotifications.h \ libvirtSnmp.h SRCS = \ diff --git a/src/README.txt b/src/README.txt index 22dd2af..6d010f6 100644 --- a/src/README.txt +++ b/src/README.txt @@ -31,8 +31,11 @@ libvirtGuestTable* - Generated by this command: 'MIBDIRS="+." MIBS="+LIBVIRT-MIB" mib2c libvirtMIB'. - It asks few questions and produces the mentioned files. - libvirtGuestTable.h, libvirtGuestTable.c, libvirtGuestTable_data_access.c, libvirtGuestTable_data_get.c and libvirtGuestTable_data_set.c are manually edited to set correct data structures and their handling. It's nicely documented, see libvirtGuestTable-README-FIRST.txt and libvirtGuestTable-README-libvirtGuestTable.txt + TODOs in the code. It took me ~2 hours to update these files (but I've done this several times for different MIBs, so I know what to do :). Do not touch the rest of the files! -- I've done everything necessary for simple read-write access. -- This code must be regenerated when the definition of libvirtGuestTable in the MIB file is changed! There is some automatic merging tool, which merges my changes with newly generated code, but I wouldn't rely on it. + +libvirtNotifications.[c|h] +- The SNMP trap (notification) stuff +- Generated by this command: 'MIBDIRS="+." MIBS="+LIBVIRT-MIB" mib2c -c mib2c.notify.conf libvirtNotifications' +- and slightly modified (especially filling up trap variables which are going to be send). Usage (tested on Fedora 14 and RHEL6) diff --git a/src/libvirtNotifications.c b/src/libvirtNotifications.c new file mode 100644 index 0000000..60fc44b --- /dev/null +++ b/src/libvirtNotifications.c @@ -0,0 +1,110 @@ +/* + * libvirtNotifications.c: Fill in trap packets + * + * Copyright (C) 2011 Red Hat, Inc. + * + * See COPYING for the license of this software + * + * Michal Privoznik <mprivozn@redhat.com> + */ + +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-includes.h> +#include <net-snmp/agent/net-snmp-agent-includes.h> +#include <string.h> +#include "libvirtNotifications.h" +#include "libvirtGuestTable_enums.h" +#include "libvirtSnmpError.h" + +static const oid snmptrap_oid[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 }; + +int +send_libvirtGuestNotif_trap(virDomainPtr dom) +{ + netsnmp_variable_list *var_list = NULL; + const oid libvirtGuestNotif_oid[] = { 1, 3, 6, 1, 4, 1, 12345, 0, 1 }; + const oid libvirtGuestName_oid[] = + { 1, 3, 6, 1, 4, 1, 12345, 1, 1, 1, 2, 0 }; + const oid libvirtGuestUUID_oid[] = + { 1, 3, 6, 1, 4, 1, 12345, 1, 1, 1, 1, 1 }; + const oid libvirtGuestState_oid[] = + { 1, 3, 6, 1, 4, 1, 12345, 1, 1, 1, 3, 2 }; + const oid libvirtGuestRowStatus_oid[] = + { 1, 3, 6, 1, 4, 1, 12345, 1, 1, 1, 9, 3 }; + + + const char *domName = virDomainGetName(dom); + unsigned char domUUID[VIR_UUID_BUFLEN]; + virDomainInfo info; + int rowstatus = ROWSTATUS_ACTIVE; + + if (virDomainGetUUID(dom, domUUID) < 0) { + printLibvirtError("Failed to get domain UUID"); + return SNMP_ERR_GENERR; + } + + if (virDomainGetInfo(dom, &info) < 0) { + printLibvirtError("Failed to get domain info"); + return SNMP_ERR_GENERR; + } + + /* + * If domain is shutting down, row in libvirtGuestTable will + * not be accessible anymore. + */ + switch (info.state) { + case VIR_DOMAIN_SHUTDOWN: + case VIR_DOMAIN_SHUTOFF: + case VIR_DOMAIN_CRASHED: + rowstatus = ROWSTATUS_NOTINSERVICE; + break; + + default: + rowstatus = ROWSTATUS_ACTIVE; + break; + }; + + /* + * Set the snmpTrapOid.0 value + */ + snmp_varlist_add_variable(&var_list, + snmptrap_oid, OID_LENGTH(snmptrap_oid), + ASN_OBJECT_ID, + libvirtGuestNotif_oid, + sizeof(libvirtGuestNotif_oid)); + + /* + * Add any objects from the trap definition + */ + snmp_varlist_add_variable(&var_list, + libvirtGuestName_oid, + OID_LENGTH(libvirtGuestName_oid), + ASN_OCTET_STR, domName, strlen(domName)); + snmp_varlist_add_variable(&var_list, + libvirtGuestUUID_oid, + OID_LENGTH(libvirtGuestUUID_oid), + ASN_OCTET_STR, domUUID, sizeof(domUUID)); + snmp_varlist_add_variable(&var_list, + libvirtGuestState_oid, + OID_LENGTH(libvirtGuestState_oid), + ASN_INTEGER, + (u_char *) & info.state, sizeof(info.state)); + snmp_varlist_add_variable(&var_list, + libvirtGuestRowStatus_oid, + OID_LENGTH(libvirtGuestRowStatus_oid), + ASN_INTEGER, + (u_char *) & rowstatus, sizeof(rowstatus)); + + /* + * Add any extra (optional) objects here + */ + + /* + * Send the trap to the list of configured destinations + * and clean up + */ + send_v2trap(var_list); + snmp_free_varbind(var_list); + + return SNMP_ERR_NOERROR; +} diff --git a/src/libvirtNotifications.h b/src/libvirtNotifications.h new file mode 100644 index 0000000..84eefc3 --- /dev/null +++ b/src/libvirtNotifications.h @@ -0,0 +1,21 @@ +/* + * libvirtNotifications.h: Fill in trap packets + * + * Copyright (C) 2011 Red Hat, Inc. + * + * See COPYING for the license of this software + * + * Michal Privoznik <mprivozn@redhat.com> + */ + +#ifndef LIBVIRTNOTIFICATIONS_H +#define LIBVIRTNOTIFICATIONS_H + +#include "libvirtSnmp.h" + +/* + * function declarations + */ +int send_libvirtGuestNotif_trap(virDomainPtr dom); + +#endif /* LIBVIRTNOTIFICATIONS_H */ -- 1.7.4.2

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@redhat.com],[],[http://libvirt.org]) +AC_INIT([libvirt-snmp],[0.0.2],[libvir-list@redhat.com],[],[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@redhat.com> 0.0.2-1 +- add SNMP trap/notification support + * Fri Mar 11 2011 Michal Privoznik <mprivozn@redhat.com> 0.0.1-3 -- remove LIBVIRT-MIB.txt from %doc +- remove LIBVIRT-MIB.txt from doc * Wed Mar 9 2011 Michal Privoznik <mprivozn@redhat.com> 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@redhat.com> + */ + +#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@redhat.com> + */ + +#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> + +/* C99 uses __func__. __FUNCTION__ is legacy. */ +# ifndef __GNUC__ +# define __FUNCTION__ __func__ +# endif + +# ifdef __GNUC__ + +# ifndef __GNUC_PREREQ +# if defined __GNUC__ && defined __GNUC_MINOR__ +# define __GNUC_PREREQ(maj, min) \ + ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +# else +# define __GNUC_PREREQ(maj,min) 0 +# endif + +/* Work around broken limits.h on debian etch */ +# if defined _GCC_LIMITS_H_ && ! defined ULLONG_MAX +# define ULLONG_MAX ULONG_LONG_MAX +# endif + +# endif /* __GNUC__ */ + +/** + * ATTRIBUTE_UNUSED: + * + * Macro to flag conciously unused parameters to functions + */ +# ifndef ATTRIBUTE_UNUSED +# define ATTRIBUTE_UNUSED __attribute__((__unused__)) +# endif + +/** + * ATTRIBUTE_SENTINEL: + * + * Macro to check for NULL-terminated varargs lists + */ +# ifndef ATTRIBUTE_SENTINEL +# if __GNUC_PREREQ (4, 0) +# define ATTRIBUTE_SENTINEL __attribute__((__sentinel__)) +# else +# define ATTRIBUTE_SENTINEL +# endif +# endif + +/** + * ATTRIBUTE_FMT_PRINTF + * + * Macro used to check printf like functions, if compiling + * with gcc. + * + * We use gnulib which guarentees we always have GNU style + * printf format specifiers even on broken Win32 platforms + * hence we have to force 'gnu_printf' for new GCC + */ +# ifndef ATTRIBUTE_FMT_PRINTF +# if __GNUC_PREREQ (4, 4) +# define ATTRIBUTE_FMT_PRINTF(fmtpos,argpos) __attribute__((__format__ (gnu_printf, fmtpos,argpos))) +# else +# define ATTRIBUTE_FMT_PRINTF(fmtpos,argpos) __attribute__((__format__ (printf, fmtpos,argpos))) +# endif +# endif + +# ifndef ATTRIBUTE_RETURN_CHECK +# if __GNUC_PREREQ (3, 4) +# define ATTRIBUTE_RETURN_CHECK __attribute__((__warn_unused_result__)) +# else +# define ATTRIBUTE_RETURN_CHECK +# endif +# endif + +/** + * ATTRIBUTE_PACKED + * + * force a structure to be packed, i.e. not following architecture and + * compiler best alignments for its sub components. It's needed for example + * for the network filetering code when defining the content of raw + * ethernet packets. + * Others compiler than gcc may use something different e.g. #pragma pack(1) + */ +# ifndef ATTRIBUTE_PACKED +# if __GNUC_PREREQ (3, 3) +# define ATTRIBUTE_PACKED __attribute__((packed)) +# else +# error "Need an __attribute__((packed)) equivalent" +# endif +# endif + +# ifndef ATTRIBUTE_NONNULL +# if __GNUC_PREREQ (3, 3) +# define ATTRIBUTE_NONNULL(m) __attribute__((__nonnull__(m))) +# else +# define ATTRIBUTE_NONNULL(m) +# endif +# endif + +# else +# ifndef ATTRIBUTE_UNUSED +# define ATTRIBUTE_UNUSED +# endif +# ifndef ATTRIBUTE_FMT_PRINTF +# define ATTRIBUTE_FMT_PRINTF(...) +# endif +# ifndef ATTRIBUTE_RETURN_CHECK +# define ATTRIBUTE_RETURN_CHECK +# endif +# endif /* __GNUC__ */ + +/* + * Events Implementation + */ + +/** + * 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); + +extern int verbose; + +#define VIR_DEBUG0(fmt) if (verbose) fprintf(stderr, "%s:%d :: " fmt "\n", \ + __func__, __LINE__) +#define VIR_DEBUG(fmt, ...) if (verbose) fprintf(stderr, "%s:%d: " fmt "\n", \ + __func__, __LINE__, __VA_ARGS__) +#define VIR_WARN(fmt, ...) if (verbose) fprintf(stderr, "%s:%d: " fmt "\n", \ + __func__, __LINE__, __VA_ARGS__) + +#endif diff --git a/src/libvirtNotifications.c b/src/libvirtNotifications.c index 60fc44b..52c7ae4 100644 --- a/src/libvirtNotifications.c +++ b/src/libvirtNotifications.c @@ -3,9 +3,21 @@ * * Copyright (C) 2011 Red Hat, Inc. * - * See COPYING for the license of this software + * 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. * - * Michal Privoznik <mprivozn@redhat.com> + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Michal Privoznik <mprivozn@redhat.com> */ #include <net-snmp/net-snmp-config.h> diff --git a/src/libvirtNotifications.h b/src/libvirtNotifications.h index 84eefc3..0be8fdc 100644 --- a/src/libvirtNotifications.h +++ b/src/libvirtNotifications.h @@ -3,9 +3,21 @@ * * Copyright (C) 2011 Red Hat, Inc. * - * See COPYING for the license of this software + * 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. * - * Michal Privoznik <mprivozn@redhat.com> + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Michal Privoznik <mprivozn@redhat.com> */ #ifndef LIBVIRTNOTIFICATIONS_H diff --git a/src/libvirtSnmp.c b/src/libvirtSnmp.c index dd1bd33..7e56737 100644 --- a/src/libvirtSnmp.c +++ b/src/libvirtSnmp.c @@ -20,14 +20,63 @@ * Author: Michal Privoznik <mprivozn@redhat.com> */ +#include <config.h> + #include <stdio.h> #include <stdlib.h> +#include <sys/types.h> +#include <sys/poll.h> +#include <pthread.h> +#include <signal.h> #include "libvirtSnmp.h" -/* include our MIB structures*/ -#include "libvirtGuestTable.h" - +#include "libvirtGuestTable.h" /* include our MIB structures*/ +#include "libvirtNotifications.h" +#ifdef LIBVIRT_OLD +# include "event_poll.h" +#endif + +#define DEBUG0(fmt) if (verbose) printf("%s:%d :: " fmt "\n", \ + __func__, __LINE__) +#define DEBUG(fmt, ...) if (verbose) printf("%s:%d: " fmt "\n", \ + __func__, __LINE__, __VA_ARGS__) +#define STREQ(a,b) (strcmp(a,b) == 0) + +#ifndef ATTRIBUTE_UNUSED +#define ATTRIBUTE_UNUSED __attribute__((__unused__)) +#endif + +int verbose = 0; virConnectPtr conn; +int callbackRet = -1; +int run = 1; +pthread_t poll_thread; + +static int +domainEventCallback(virConnectPtr conn ATTRIBUTE_UNUSED, + virDomainPtr dom, int event, int detail, + void *opaque ATTRIBUTE_UNUSED) +{ + DEBUG("%s EVENT: Domain %s(%d) %d %d\n", __func__, + virDomainGetName(dom), virDomainGetID(dom), event, detail); + + send_libvirtGuestNotif_trap(dom); + return 0; +} + +static void +myFreeFunc(void *opaque) +{ + if (opaque) + free(opaque); +} + +/* Signal trap function */ +static void +stop(int sig) +{ + run = 0; +} static void showError(virConnectPtr conn) @@ -188,10 +237,95 @@ out: return ret; } +/* Polling thread function */ +void * +pollingThreadFunc(void *foo) { + while (run) { +#ifdef LIBVIRT_OLD + if (virEventPollRunOnce() < 0) { +#else + if (virEventRunDefaultImpl() < 0) { +#endif + showError(conn); + pthread_exit(NULL); + } + } + return NULL; +} + +/* Function to register domain lifecycle events collection */ +int +libvirtRegisterEvents(virConnectPtr conn) { + struct sigaction action_stop; + pthread_attr_t thread_attr; + + memset(&action_stop, 0, sizeof action_stop); + + action_stop.sa_handler = stop; + + sigaction(SIGTERM, &action_stop, NULL); + sigaction(SIGINT, &action_stop, NULL); + + DEBUG0("Registering domain event callback"); + + callbackRet = virConnectDomainEventRegisterAny(conn, NULL, + VIR_DOMAIN_EVENT_ID_LIFECYCLE, + VIR_DOMAIN_EVENT_CALLBACK + (domainEventCallback), + NULL, myFreeFunc); + + if (callbackRet == -1) + return -1; + + /* we need a thread to poll for events */ + pthread_attr_init(&thread_attr); + pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_JOINABLE); + + if (pthread_create + (&poll_thread, &thread_attr, pollingThreadFunc, NULL)) + return -1; + + pthread_attr_destroy(&thread_attr); + + return 0; +} + +/* Unregister domain events collection */ +int +libvirtUnregisterEvents(virConnectPtr conn) +{ + void *status; + + pthread_join(poll_thread, &status); + virConnectDomainEventDeregisterAny(conn, callbackRet); + callbackRet = -1; + return 0; +} + int libvirtSnmpInit(void) { - /* virConnectOpenAuth is called here with all default parameters, - * except, possibly, the URI of the hypervisor. */ + char *verbose_env = getenv("LIBVIRT_SNMP_VERBOSE"); + + verbose = verbose_env != NULL; + + /* if we don't already have registered callback */ + if (callbackRet == -1) { +#ifdef LIBVIRT_OLD + if (virEventPollInit() < 0) + return -1; + + virEventRegisterImpl( + virEventPollAddHandle, + virEventPollUpdateHandle, + virEventPollRemoveHandle, + virEventPollAddTimeout, + virEventPollUpdateTimeout, + virEventPollRemoveTimeout); +#else + virEventRegisterDefaultImpl(); +#endif + } + /* TODO: configure the URI */ /* Use libvirt env variable LIBVIRT_DEFAULT_URI by default*/ conn = virConnectOpenAuth(NULL, virConnectAuthPtrDefault, 0); @@ -201,11 +335,21 @@ int libvirtSnmpInit(void) showError(conn); return -1; } + + if ((callbackRet == -1) && libvirtRegisterEvents(conn)) { + printf("Unable to register domain events\n"); + return -1; + } + return 0; } void libvirtSnmpShutdown(void) { + if (libvirtUnregisterEvents(conn)) { + printf("Failed to unregister domain events\n"); + } + if (0 != virConnectClose(conn)) { printf("Failed to disconnect from hypervisor\n"); showError(conn); diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..bfa32a8 --- /dev/null +++ b/src/memory.c @@ -0,0 +1,313 @@ +/* + * memory.c: safer memory allocation + * + * Copyright (C) 2010-2011 Red Hat, Inc. + * Copyright (C) 2008 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 + * + */ + +#include <config.h> +#include <stdlib.h> + +#include "memory.h" +#include "ignore-value.h" + + +#if TEST_OOM +static int testMallocNext = 0; +static int testMallocFailFirst = 0; +static int testMallocFailLast = 0; +static void (*testMallocHook)(int, void*) = NULL; +static void *testMallocHookData = NULL; + +void virAllocTestInit(void) +{ + testMallocNext = 1; + testMallocFailFirst = 0; + testMallocFailLast = 0; +} + +int virAllocTestCount(void) +{ + return testMallocNext - 1; +} + +void virAllocTestHook(void (*func)(int, void*), void *data) +{ + testMallocHook = func; + testMallocHookData = data; +} + +void virAllocTestOOM(int n, int m) +{ + testMallocNext = 1; + testMallocFailFirst = n; + testMallocFailLast = n + m - 1; +} + +static int virAllocTestFail(void) +{ + int fail = 0; + if (testMallocNext == 0) + return 0; + + fail = + testMallocNext >= testMallocFailFirst && + testMallocNext <= testMallocFailLast; + + if (fail && testMallocHook) + (testMallocHook)(testMallocNext, testMallocHookData); + + testMallocNext++; + return fail; +} +#endif + + +/** + * virAlloc: + * @ptrptr: pointer to pointer for address of allocated memory + * @size: number of bytes to allocate + * + * Allocate 'size' bytes of memory. Return the address of the + * allocated memory in 'ptrptr'. The newly allocated memory is + * filled with zeros. + * + * Returns -1 on failure to allocate, zero on success + */ +int virAlloc(void *ptrptr, size_t size) +{ +#if TEST_OOM + if (virAllocTestFail()) { + *(void **)ptrptr = NULL; + return -1; + } +#endif + + *(void **)ptrptr = calloc(1, size); + if (*(void **)ptrptr == NULL) + return -1; + return 0; +} + +/** + * virAllocN: + * @ptrptr: pointer to pointer for address of allocated memory + * @size: number of bytes to allocate + * @count: number of elements to allocate + * + * Allocate an array of memory 'count' elements long, + * each with 'size' bytes. Return the address of the + * allocated memory in 'ptrptr'. The newly allocated + * memory is filled with zeros. + * + * Returns -1 on failure to allocate, zero on success + */ +int virAllocN(void *ptrptr, size_t size, size_t count) +{ +#if TEST_OOM + if (virAllocTestFail()) { + *(void **)ptrptr = NULL; + return -1; + } +#endif + + *(void**)ptrptr = calloc(count, size); + if (*(void**)ptrptr == NULL) + return -1; + return 0; +} + +/** + * virReallocN: + * @ptrptr: pointer to pointer for address of allocated memory + * @size: number of bytes to allocate + * @count: number of elements in array + * + * Resize the block of memory in 'ptrptr' to be an array of + * 'count' elements, each 'size' bytes in length. Update 'ptrptr' + * with the address of the newly allocated memory. On failure, + * 'ptrptr' is not changed and still points to the original memory + * block. Any newly allocated memory in 'ptrptr' is uninitialized. + * + * Returns -1 on failure to allocate, zero on success + */ +int virReallocN(void *ptrptr, size_t size, size_t count) +{ + void *tmp; +#if TEST_OOM + if (virAllocTestFail()) + return -1; +#endif + + if (xalloc_oversized(count, size)) { + errno = ENOMEM; + return -1; + } + tmp = realloc(*(void**)ptrptr, size * count); + if (!tmp && (size * count)) + return -1; + *(void**)ptrptr = tmp; + return 0; +} + +/** + * virExpandN: + * @ptrptr: pointer to pointer for address of allocated memory + * @size: number of bytes per element + * @countptr: pointer to number of elements in array + * @add: number of elements to add + * + * Resize the block of memory in 'ptrptr' to be an array of + * '*countptr' + 'add' elements, each 'size' bytes in length. + * Update 'ptrptr' and 'countptr' with the details of the newly + * allocated memory. On failure, 'ptrptr' and 'countptr' are not + * changed. Any newly allocated memory in 'ptrptr' is zero-filled. + * + * Returns -1 on failure to allocate, zero on success + */ +int virExpandN(void *ptrptr, size_t size, size_t *countptr, size_t add) +{ + int ret; + + if (*countptr + add < *countptr) { + errno = ENOMEM; + return -1; + } + ret = virReallocN(ptrptr, size, *countptr + add); + if (ret == 0) { + memset(*(char **)ptrptr + (size * *countptr), 0, size * add); + *countptr += add; + } + return ret; +} + +/** + * virResizeN: + * @ptrptr: pointer to pointer for address of allocated memory + * @size: number of bytes per element + * @allocptr: pointer to number of elements allocated in array + * @count: number of elements currently used in array + * @add: minimum number of additional elements to support in array + * + * If 'count' + 'add' is larger than '*allocptr', then resize the + * block of memory in 'ptrptr' to be an array of at least 'count' + + * 'add' elements, each 'size' bytes in length. Update 'ptrptr' and + * 'allocptr' with the details of the newly allocated memory. On + * failure, 'ptrptr' and 'allocptr' are not changed. Any newly + * allocated memory in 'ptrptr' is zero-filled. + * + * Returns -1 on failure to allocate, zero on success + */ +int virResizeN(void *ptrptr, size_t size, size_t *allocptr, size_t count, + size_t add) +{ + size_t delta; + + if (count + add < count) { + errno = ENOMEM; + return -1; + } + if (count + add <= *allocptr) + return 0; + + delta = count + add - *allocptr; + if (delta < *allocptr / 2) + delta = *allocptr / 2; + return virExpandN(ptrptr, size, allocptr, delta); +} + +/** + * virShrinkN: + * @ptrptr: pointer to pointer for address of allocated memory + * @size: number of bytes per element + * @countptr: pointer to number of elements in array + * @toremove: number of elements to remove + * + * Resize the block of memory in 'ptrptr' to be an array of + * '*countptr' - 'toremove' elements, each 'size' bytes in length. + * Update 'ptrptr' and 'countptr' with the details of the newly + * allocated memory. If 'toremove' is larger than 'countptr', free + * the entire array. + */ +void virShrinkN(void *ptrptr, size_t size, size_t *countptr, size_t toremove) +{ + if (toremove < *countptr) + ignore_value(virReallocN(ptrptr, size, *countptr -= toremove)); + else { + virFree(ptrptr); + *countptr = 0; + } +} + + +/** + * Vir_Alloc_Var: + * @ptrptr: pointer to hold address of allocated memory + * @struct_size: size of initial struct + * @element_size: size of array elements + * @count: number of array elements to allocate + * + * Allocate struct_size bytes plus an array of 'count' elements, each + * of size element_size. This sort of allocation is useful for + * receiving the data of certain ioctls and other APIs which return a + * struct in which the last element is an array of undefined length. + * The caller of this type of API is expected to know the length of + * the array that will be returned and allocate a suitable buffer to + * contain the returned data. C99 refers to these variable length + * objects as structs containing flexible array members. + * + * Returns -1 on failure, 0 on success + */ +int virAllocVar(void *ptrptr, size_t struct_size, size_t element_size, size_t count) +{ + size_t alloc_size = 0; + +#if TEST_OOM + if (virAllocTestFail()) + return -1; +#endif + + if (VIR_ALLOC_VAR_OVERSIZED(struct_size, count, element_size)) { + errno = ENOMEM; + return -1; + } + + alloc_size = struct_size + (element_size * count); + *(void **)ptrptr = calloc(1, alloc_size); + if (*(void **)ptrptr == NULL) + return -1; + return 0; +} + + +/** + * virFree: + * @ptrptr: pointer to pointer for address of memory to be freed + * + * Release the chunk of memory in the pointer pointed to by + * the 'ptrptr' variable. After release, 'ptrptr' will be + * updated to point to NULL. + */ +void virFree(void *ptrptr) +{ + int save_errno = errno; + + free(*(void**)ptrptr); + *(void**)ptrptr = NULL; + errno = save_errno; +} diff --git a/src/memory.h b/src/memory.h new file mode 100644 index 0000000..66b4c42 --- /dev/null +++ b/src/memory.h @@ -0,0 +1,212 @@ +/* + * memory.c: safer memory allocation + * + * Copyright (C) 2010 Red Hat, Inc. + * Copyright (C) 2008 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 + * + */ + + +#ifndef __VIR_MEMORY_H_ +# define __VIR_MEMORY_H_ + +# include "internal.h" + +/* Return 1 if an array of N objects, each of size S, cannot exist due + to size arithmetic overflow. S must be positive and N must be + nonnegative. This is a macro, not an inline function, so that it + works correctly even when SIZE_MAX < N. + + By gnulib convention, SIZE_MAX represents overflow in size + calculations, so the conservative dividend to use here is + SIZE_MAX - 1, since SIZE_MAX might represent an overflowed value. + However, malloc (SIZE_MAX) fails on all known hosts where + sizeof (ptrdiff_t) <= sizeof (size_t), so do not bother to test for + exactly-SIZE_MAX allocations on such hosts; this avoids a test and + branch when S is known to be 1. */ +# ifndef xalloc_oversized +# define xalloc_oversized(n, s) \ + ((size_t) (sizeof (ptrdiff_t) <= sizeof (size_t) ? -1 : -2) / (s) < (n)) +# endif + + + +/* Don't call these directly - use the macros below */ +int virAlloc(void *ptrptr, size_t size) ATTRIBUTE_RETURN_CHECK + ATTRIBUTE_NONNULL(1); +int virAllocN(void *ptrptr, size_t size, size_t count) ATTRIBUTE_RETURN_CHECK + ATTRIBUTE_NONNULL(1); +int virReallocN(void *ptrptr, size_t size, size_t count) ATTRIBUTE_RETURN_CHECK + ATTRIBUTE_NONNULL(1); +int virExpandN(void *ptrptr, size_t size, size_t *count, size_t add) + ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); +int virResizeN(void *ptrptr, size_t size, size_t *alloc, size_t count, + size_t desired) + ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); +void virShrinkN(void *ptrptr, size_t size, size_t *count, size_t toremove) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); +int virAllocVar(void *ptrptr, + size_t struct_size, + size_t element_size, + size_t count) ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1); +void virFree(void *ptrptr) ATTRIBUTE_NONNULL(1); + +/** + * VIR_ALLOC: + * @ptr: pointer to hold address of allocated memory + * + * Allocate sizeof(*ptr) bytes of memory and store + * the address of allocated memory in 'ptr'. Fill the + * newly allocated memory with zeros. + * + * Returns -1 on failure, 0 on success + */ +# define VIR_ALLOC(ptr) virAlloc(&(ptr), sizeof(*(ptr))) + +/** + * VIR_ALLOC_N: + * @ptr: pointer to hold address of allocated memory + * @count: number of elements to allocate + * + * Allocate an array of 'count' elements, each sizeof(*ptr) + * bytes long and store the address of allocated memory in + * 'ptr'. Fill the newly allocated memory with zeros. + * + * Returns -1 on failure, 0 on success + */ +# define VIR_ALLOC_N(ptr, count) virAllocN(&(ptr), sizeof(*(ptr)), (count)) + +/** + * VIR_REALLOC_N: + * @ptr: pointer to hold address of allocated memory + * @count: number of elements to allocate + * + * Re-allocate an array of 'count' elements, each sizeof(*ptr) + * bytes long and store the address of allocated memory in + * 'ptr'. If 'ptr' grew, the added memory is uninitialized. + * + * Returns -1 on failure, 0 on success + */ +# define VIR_REALLOC_N(ptr, count) virReallocN(&(ptr), sizeof(*(ptr)), (count)) + +/** + * VIR_EXPAND_N: + * @ptr: pointer to hold address of allocated memory + * @count: variable tracking number of elements currently allocated + * @add: number of elements to add + * + * Re-allocate an array of 'count' elements, each sizeof(*ptr) + * bytes long, to be 'count' + 'add' elements long, then store the + * address of allocated memory in 'ptr' and the new size in 'count'. + * The new elements are filled with zero. + * + * Returns -1 on failure, 0 on success + */ +# define VIR_EXPAND_N(ptr, count, add) \ + virExpandN(&(ptr), sizeof(*(ptr)), &(count), add) + +/** + * VIR_RESIZE_N: + * @ptr: pointer to hold address of allocated memory + * @alloc: variable tracking number of elements currently allocated + * @count: number of elements currently in use + * @add: minimum number of elements to additionally support + * + * Blindly using VIR_EXPAND_N(array, alloc, 1) in a loop scales + * quadratically, because every iteration must copy contents from + * all prior iterations. But amortized linear scaling can be achieved + * by tracking allocation size separately from the number of used + * elements, and growing geometrically only as needed. + * + * If 'count' + 'add' is larger than 'alloc', then geometrically reallocate + * the array of 'alloc' elements, each sizeof(*ptr) bytes long, and store + * the address of allocated memory in 'ptr' and the new size in 'alloc'. + * The new elements are filled with zero. + * + * Returns -1 on failure, 0 on success + */ +# define VIR_RESIZE_N(ptr, alloc, count, add) \ + virResizeN(&(ptr), sizeof(*(ptr)), &(alloc), count, add) + +/** + * VIR_SHRINK_N: + * @ptr: pointer to hold address of allocated memory + * @count: variable tracking number of elements currently allocated + * @remove: number of elements to remove + * + * Re-allocate an array of 'count' elements, each sizeof(*ptr) + * bytes long, to be 'count' - 'remove' elements long, then store the + * address of allocated memory in 'ptr' and the new size in 'count'. + * If 'count' <= 'remove', the entire array is freed. + * + * No return value. + */ +# define VIR_SHRINK_N(ptr, count, remove) \ + virShrinkN(&(ptr), sizeof(*(ptr)), &(count), remove) + +/* + * VIR_ALLOC_VAR_OVERSIZED: + * @M: size of base structure + * @N: number of array elements in trailing array + * @S: size of trailing array elements + * + * Check to make sure that the requested allocation will not cause + * arithmetic overflow in the allocation size. The check is + * essentially the same as that in gnulib's xalloc_oversized. + */ +# define VIR_ALLOC_VAR_OVERSIZED(M, N, S) ((((size_t)-1) - (M)) / (S) < (N)) + +/** + * VIR_ALLOC_VAR: + * @ptr: pointer to hold address of allocated memory + * @type: element type of trailing array + * @count: number of array elements to allocate + * + * Allocate sizeof(*ptr) bytes plus an array of 'count' elements, each + * sizeof('type'). This sort of allocation is useful for receiving + * the data of certain ioctls and other APIs which return a struct in + * which the last element is an array of undefined length. The caller + * of this type of API is expected to know the length of the array + * that will be returned and allocate a suitable buffer to contain the + * returned data. C99 refers to these variable length objects as + * structs containing flexible array members. + + * Returns -1 on failure, 0 on success + */ +# define VIR_ALLOC_VAR(ptr, type, count) \ + virAllocVar(&(ptr), sizeof(*(ptr)), sizeof(type), (count)) + +/** + * VIR_FREE: + * @ptr: pointer holding address to be freed + * + * Free the memory stored in 'ptr' and update to point + * to NULL. + */ +# define VIR_FREE(ptr) virFree(&(ptr)) + + +# if TEST_OOM +void virAllocTestInit(void); +int virAllocTestCount(void); +void virAllocTestOOM(int n, int m); +void virAllocTestHook(void (*func)(int, void*), void *data); +# endif + + + +#endif /* __VIR_MEMORY_H_ */ diff --git a/src/threads.c b/src/threads.c new file mode 100644 index 0000000..8987173 --- /dev/null +++ b/src/threads.c @@ -0,0 +1,251 @@ +/* + * threads.c: basic thread synchronization primitives + * + * Copyright (C) 2009-2010 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 + * + */ + +#include <config.h> + +#include <unistd.h> +#include <inttypes.h> +#if HAVE_SYS_SYSCALL_H +# include <sys/syscall.h> +#endif + +#include "threads.h" +#include "memory.h" + + +/* Nothing special required for pthreads */ +int virThreadInitialize(void) +{ + return 0; +} + +void virThreadOnExit(void) +{ +} + + +int virMutexInit(virMutexPtr m) +{ + int ret; + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + ret = pthread_mutex_init(&m->lock, &attr); + pthread_mutexattr_destroy(&attr); + if (ret != 0) { + errno = ret; + return -1; + } + return 0; +} + +int virMutexInitRecursive(virMutexPtr m) +{ + int ret; + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + ret = pthread_mutex_init(&m->lock, &attr); + pthread_mutexattr_destroy(&attr); + if (ret != 0) { + errno = ret; + return -1; + } + return 0; +} + +void virMutexDestroy(virMutexPtr m) +{ + pthread_mutex_destroy(&m->lock); +} + +void virMutexLock(virMutexPtr m){ + pthread_mutex_lock(&m->lock); +} + +void virMutexUnlock(virMutexPtr m) +{ + pthread_mutex_unlock(&m->lock); +} + + +int virCondInit(virCondPtr c) +{ + int ret; + if ((ret = pthread_cond_init(&c->cond, NULL)) != 0) { + errno = ret; + return -1; + } + return 0; +} + +int virCondDestroy(virCondPtr c) +{ + int ret; + if ((ret = pthread_cond_destroy(&c->cond)) != 0) { + errno = ret; + return -1; + } + return 0; +} + +int virCondWait(virCondPtr c, virMutexPtr m) +{ + int ret; + if ((ret = pthread_cond_wait(&c->cond, &m->lock)) != 0) { + errno = ret; + return -1; + } + return 0; +} + +int virCondWaitUntil(virCondPtr c, virMutexPtr m, unsigned long long whenms) +{ + int ret; + struct timespec ts; + + ts.tv_sec = whenms / 1000; + ts.tv_nsec = (whenms % 1000) * 1000; + + if ((ret = pthread_cond_timedwait(&c->cond, &m->lock, &ts)) != 0) { + errno = ret; + return -1; + } + return 0; +} + +void virCondSignal(virCondPtr c) +{ + pthread_cond_signal(&c->cond); +} + +void virCondBroadcast(virCondPtr c) +{ + pthread_cond_broadcast(&c->cond); +} + +struct virThreadArgs { + virThreadFunc func; + void *opaque; +}; + +static void *virThreadHelper(void *data) +{ + struct virThreadArgs *args = data; + args->func(args->opaque); + VIR_FREE(args); + return NULL; +} + +int virThreadCreate(virThreadPtr thread, + bool joinable, + virThreadFunc func, + void *opaque) +{ + struct virThreadArgs *args; + pthread_attr_t attr; + int ret = -1; + int err; + + if ((err = pthread_attr_init(&attr)) != 0) + goto cleanup; + if (VIR_ALLOC(args) < 0) { + err = ENOMEM; + goto cleanup; + } + + args->func = func; + args->opaque = opaque; + + if (!joinable) + pthread_attr_setdetachstate(&attr, 1); + + err = pthread_create(&thread->thread, &attr, virThreadHelper, args); + if (err != 0) { + VIR_FREE(args); + goto cleanup; + } + /* New thread owns 'args' in success case, so don't free */ + + ret = 0; +cleanup: + pthread_attr_destroy(&attr); + if (ret < 0) + errno = err; + return ret; +} + +void virThreadSelf(virThreadPtr thread) +{ + thread->thread = pthread_self(); +} + +bool virThreadIsSelf(virThreadPtr thread) +{ + return pthread_equal(pthread_self(), thread->thread) ? true : false; +} + +/* For debugging use only; this result is not guaranteed unique on BSD + * systems when pthread_t is a 64-bit pointer. */ +int virThreadSelfID(void) +{ +#if defined(HAVE_SYS_SYSCALL_H) && defined(SYS_gettid) + pid_t tid; + tid = syscall(SYS_gettid); + return (int)tid; +#else + return (int)pthread_self(); +#endif +} + +/* For debugging use only; this result is not guaranteed unique on BSD + * systems when pthread_t is a 64-bit pointer, nor does it match the + * thread id of virThreadSelfID on Linux. */ +int virThreadID(virThreadPtr thread) +{ + return (int)(uintptr_t)thread->thread; +} + +void virThreadJoin(virThreadPtr thread) +{ + pthread_join(thread->thread, NULL); +} + +int virThreadLocalInit(virThreadLocalPtr l, + virThreadLocalCleanup c) +{ + int ret; + if ((ret = pthread_key_create(&l->key, c)) != 0) { + errno = ret; + return -1; + } + return 0; +} + +void *virThreadLocalGet(virThreadLocalPtr l) +{ + return pthread_getspecific(l->key); +} + +void virThreadLocalSet(virThreadLocalPtr l, void *val) +{ + pthread_setspecific(l->key, val); +} diff --git a/src/threads.h b/src/threads.h new file mode 100644 index 0000000..ae81273 --- /dev/null +++ b/src/threads.h @@ -0,0 +1,101 @@ +/* + * threads.h: basic thread synchronization primitives + * + * Copyright (C) 2009-2010 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 + * + */ + +#ifndef __THREADS_H_ +# define __THREADS_H_ + +# include "internal.h" +# include <pthread.h> + +typedef struct virMutex virMutex; +typedef virMutex *virMutexPtr; + +typedef struct virCond virCond; +typedef virCond *virCondPtr; + +typedef struct virThreadLocal virThreadLocal; +typedef virThreadLocal *virThreadLocalPtr; + +typedef struct virThread virThread; +typedef virThread *virThreadPtr; + + +int virThreadInitialize(void) ATTRIBUTE_RETURN_CHECK; +void virThreadOnExit(void); + +typedef void (*virThreadFunc)(void *opaque); + +int virThreadCreate(virThreadPtr thread, + bool joinable, + virThreadFunc func, + void *opaque) ATTRIBUTE_RETURN_CHECK; +void virThreadSelf(virThreadPtr thread); +bool virThreadIsSelf(virThreadPtr thread); +void virThreadJoin(virThreadPtr thread); + +/* These next two functions are for debugging only, since they are not + * guaranteed to give unique values for distinct threads on all + * architectures, nor are the two functions guaranteed to give the same + * value for the same thread. */ +int virThreadSelfID(void); +int virThreadID(virThreadPtr thread); + +int virMutexInit(virMutexPtr m) ATTRIBUTE_RETURN_CHECK; +int virMutexInitRecursive(virMutexPtr m) ATTRIBUTE_RETURN_CHECK; +void virMutexDestroy(virMutexPtr m); + +void virMutexLock(virMutexPtr m); +void virMutexUnlock(virMutexPtr m); + + + +int virCondInit(virCondPtr c) ATTRIBUTE_RETURN_CHECK; +int virCondDestroy(virCondPtr c) ATTRIBUTE_RETURN_CHECK; + +int virCondWait(virCondPtr c, virMutexPtr m) ATTRIBUTE_RETURN_CHECK; +int virCondWaitUntil(virCondPtr c, virMutexPtr m, unsigned long long whenms) ATTRIBUTE_RETURN_CHECK; +void virCondSignal(virCondPtr c); +void virCondBroadcast(virCondPtr c); + + +typedef void (*virThreadLocalCleanup)(void *); +int virThreadLocalInit(virThreadLocalPtr l, + virThreadLocalCleanup c) ATTRIBUTE_RETURN_CHECK; +void *virThreadLocalGet(virThreadLocalPtr l); +void virThreadLocalSet(virThreadLocalPtr l, void*); + +struct virMutex { + pthread_mutex_t lock; +}; + +struct virCond { + pthread_cond_t cond; +}; + +struct virThread { + pthread_t thread; +}; + +struct virThreadLocal { + pthread_key_t key; +}; + +#endif diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..30d4e47 --- /dev/null +++ b/src/util.c @@ -0,0 +1,105 @@ +/* + * utils.c: common, generic utility functions + * + * Copyright (C) 2006-2011 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * Copyright (C) 2006, 2007 Binary Karma + * Copyright (C) 2006 Shuveb Hussain + * + * 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@redhat.com> + * File created Jul 18, 2007 - Shuveb Hussain <shuveb@binarykarma.com> + */ + +#include "util.h" +#include <unistd.h> +#include <fcntl.h> + +/* Like read(), but restarts after EINTR */ +ssize_t +saferead(int fd, void *buf, size_t count) +{ + size_t nread = 0; + while (count > 0) { + ssize_t r = read(fd, buf, count); + if (r < 0 && errno == EINTR) + continue; + if (r < 0) + return r; + if (r == 0) + return nread; + buf = (char *)buf + r; + count -= r; + nread += r; + } + return nread; +} + +/* Like write(), but restarts after EINTR */ +ssize_t +safewrite(int fd, const void *buf, size_t count) +{ + size_t nwritten = 0; + while (count > 0) { + ssize_t r = write(fd, buf, count); + + if (r < 0 && errno == EINTR) + continue; + if (r < 0) + return r; + if (r == 0) + return nwritten; + buf = (const char *)buf + r; + count -= r; + nwritten += r; + } + return nwritten; +} + +int virSetBlocking(int fd, bool blocking) { + int flags; + if ((flags = fcntl(fd, F_GETFL)) < 0) + return -1; + if (blocking) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + if ((fcntl(fd, F_SETFL, flags)) < 0) + return -1; + return 0; +} + +int virSetNonBlock(int fd) { + return virSetBlocking(fd, false); +} + +int virSetCloseExec(int fd) +{ + return virSetInherit(fd, false); +} + +int virSetInherit(int fd, bool inherit) { + int flags; + if ((flags = fcntl(fd, F_GETFD)) < 0) + return -1; + if (inherit) + flags &= ~FD_CLOEXEC; + else + flags |= FD_CLOEXEC; + if ((fcntl(fd, F_SETFD, flags)) < 0) + return -1; + return 0; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..a3d289e --- /dev/null +++ b/src/util.h @@ -0,0 +1,38 @@ +/* + * utils.h: common, generic utility functions + * + * Copyright (C) 2010-2011 Red Hat, Inc. + * Copyright (C) 2006, 2007 Binary Karma + * Copyright (C) 2006 Shuveb Hussain + * + * 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 + * + * File created Jul 18, 2007 - Shuveb Hussain <shuveb@binarykarma.com> + */ + +#ifndef __VIR_UTIL_H__ +#define __VIR_UTIL_H__ + +#include "internal.h" + +int virSetBlocking(int fd, bool blocking) ATTRIBUTE_RETURN_CHECK; +int virSetNonBlock(int fd) ATTRIBUTE_RETURN_CHECK; +int virSetInherit(int fd, bool inherit) ATTRIBUTE_RETURN_CHECK; +int virSetCloseExec(int fd) ATTRIBUTE_RETURN_CHECK; + +ssize_t saferead(int fd, void *buf, size_t count) ATTRIBUTE_RETURN_CHECK; +ssize_t safewrite(int fd, const void *buf, size_t count) ATTRIBUTE_RETURN_CHECK; + +#endif -- 1.7.4.2

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@redhat.com],[],[http://libvirt.org]) +AC_INIT([libvirt-snmp],[0.0.2],[libvir-list@redhat.com],[],[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@redhat.com> 0.0.2-1 +- add SNMP trap/notification support + * Fri Mar 11 2011 Michal Privoznik <mprivozn@redhat.com> 0.0.1-3 -- remove LIBVIRT-MIB.txt from %doc +- remove LIBVIRT-MIB.txt from doc
* Wed Mar 9 2011 Michal Privoznik <mprivozn@redhat.com> 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@redhat.com> + */ + +#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@redhat.com> + */ + +#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 :|
participants (2)
-
Daniel P. Berrange
-
Michal Privoznik