diff options
Diffstat (limited to 'tools/libxl')
-rw-r--r-- | tools/libxl/Makefile | 2 | ||||
-rw-r--r-- | tools/libxl/libxl.c | 30 | ||||
-rw-r--r-- | tools/libxl/libxl.h | 6 | ||||
-rw-r--r-- | tools/libxl/libxl_event.c | 750 | ||||
-rw-r--r-- | tools/libxl/libxl_event.h | 205 | ||||
-rw-r--r-- | tools/libxl/libxl_internal.h | 277 |
6 files changed, 1267 insertions, 3 deletions
diff --git a/tools/libxl/Makefile b/tools/libxl/Makefile index 3c3661bfdd..b58c43ecb7 100644 --- a/tools/libxl/Makefile +++ b/tools/libxl/Makefile @@ -49,7 +49,7 @@ LIBXL_LIBS += -lyajl LIBXL_OBJS = flexarray.o libxl.o libxl_create.o libxl_dm.o libxl_pci.o \ libxl_dom.o libxl_exec.o libxl_xshelp.o libxl_device.o \ libxl_internal.o libxl_utils.o libxl_uuid.o libxl_json.o \ - libxl_qmp.o $(LIBXL_OBJS-y) + libxl_qmp.o libxl_event.o $(LIBXL_OBJS-y) LIBXL_OBJS += _libxl_types.o libxl_flask.o _libxl_types_internal.o $(LIBXL_OBJS): CFLAGS += $(CFLAGS_libxenctrl) $(CFLAGS_libxenguest) $(CFLAGS_libxenstore) $(CFLAGS_libblktapctl) diff --git a/tools/libxl/libxl.c b/tools/libxl/libxl.c index 5dd7412ca7..3e492f09e8 100644 --- a/tools/libxl/libxl.c +++ b/tools/libxl/libxl.c @@ -45,6 +45,16 @@ int libxl_ctx_alloc(libxl_ctx **pctx, int version, * only as an initialiser, not as an expression. */ memcpy(&ctx->lock, &mutex_value, sizeof(ctx->lock)); + ctx->osevent_hooks = 0; + + ctx->fd_rindex = 0; + LIBXL_LIST_INIT(&ctx->efds); + LIBXL_TAILQ_INIT(&ctx->etimes); + + ctx->watch_slots = 0; + LIBXL_SLIST_INIT(&ctx->watch_freeslots); + libxl__ev_fd_init(&ctx->watch_efd); + if ( stat(XENSTORE_PID_FILE, &stat_buf) != 0 ) { LIBXL__LOG_ERRNO(ctx, LIBXL__LOG_ERROR, "Is xenstore daemon running?\n" "failed to stat %s", XENSTORE_PID_FILE); @@ -79,9 +89,29 @@ int libxl_ctx_alloc(libxl_ctx **pctx, int version, int libxl_ctx_free(libxl_ctx *ctx) { if (!ctx) return 0; + + int i; + GC_INIT(ctx); + + /* Deregister all libxl__ev_KINDs: */ + + for (i = 0; i < ctx->watch_nslots; i++) + assert(!libxl__watch_slot_contents(gc, i)); + libxl__ev_fd_deregister(gc, &ctx->watch_efd); + + /* Now there should be no more events requested from the application: */ + + assert(LIBXL_LIST_EMPTY(&ctx->efds)); + assert(LIBXL_TAILQ_EMPTY(&ctx->etimes)); + if (ctx->xch) xc_interface_close(ctx->xch); libxl_version_info_dispose(&ctx->version_info); if (ctx->xsh) xs_daemon_close(ctx->xsh); + + free(ctx->fd_rindex); + free(ctx->watch_slots); + + GC_FREE; free(ctx); return 0; } diff --git a/tools/libxl/libxl.h b/tools/libxl/libxl.h index 723eac2ca1..b06772449d 100644 --- a/tools/libxl/libxl.h +++ b/tools/libxl/libxl.h @@ -137,6 +137,7 @@ #include <xen/sysctl.h> #include <libxl_uuid.h> +#include <_libxl_list.h> typedef uint8_t libxl_mac[6]; #define LIBXL_MAC_FMT "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" @@ -223,6 +224,9 @@ enum { ERROR_GUEST_TIMEDOUT = -8, ERROR_TIMEDOUT = -9, ERROR_NOPARAVIRT = -10, + ERROR_NOT_READY = -11, + ERROR_OSEVENT_REG_FAIL = -12, + ERROR_BUFFERFULL = -13, }; #define LIBXL_VERSION 0 @@ -648,6 +652,8 @@ const char *libxl_xenpaging_dir_path(void); /* misc */ int libxl_fd_set_cloexec(int fd); +#include <libxl_event.h> + #endif /* LIBXL_H */ /* diff --git a/tools/libxl/libxl_event.c b/tools/libxl/libxl_event.c new file mode 100644 index 0000000000..ec66340874 --- /dev/null +++ b/tools/libxl/libxl_event.c @@ -0,0 +1,750 @@ +/* + * Copyright (C) 2011 Citrix Ltd. + * + * 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; version 2.1 only. with the special + * exception on linking described in file LICENSE. + * + * 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. + */ +/* + * Internal event machinery for use by other parts of libxl + */ + +#include <poll.h> + +#include "libxl_internal.h" + +/* + * The counter osevent_in_hook is used to ensure that the application + * honours the reentrancy restriction documented in libxl_event.h. + * + * The application's registration hooks should be called ONLY via + * these macros, with the ctx locked. Likewise all the "occurred" + * entrypoints from the application should assert(!in_hook); + */ +#define OSEVENT_HOOK_INTERN(defval, hookname, ...) \ + (CTX->osevent_hooks \ + ? (CTX->osevent_in_hook++, \ + CTX->osevent_hooks->hookname(CTX->osevent_user, __VA_ARGS__), \ + CTX->osevent_in_hook--) \ + : defval) + +#define OSEVENT_HOOK(hookname,...) \ + OSEVENT_HOOK_INTERN(0, hookname, __VA_ARGS__) + +#define OSEVENT_HOOK_VOID(hookname,...) \ + OSEVENT_HOOK_INTERN((void)0, hookname, __VA_ARGS__) + +/* + * fd events + */ + +int libxl__ev_fd_register(libxl__gc *gc, libxl__ev_fd *ev, + libxl__ev_fd_callback *func, + int fd, short events) +{ + int rc; + + assert(fd >= 0); + + CTX_LOCK; + + rc = OSEVENT_HOOK(fd_register, fd, &ev->for_app_reg, events, ev); + if (rc) goto out; + + ev->fd = fd; + ev->events = events; + ev->func = func; + + LIBXL_LIST_INSERT_HEAD(&CTX->efds, ev, entry); + + rc = 0; + + out: + CTX_UNLOCK; + return rc; +} + +int libxl__ev_fd_modify(libxl__gc *gc, libxl__ev_fd *ev, short events) +{ + int rc; + + CTX_LOCK; + assert(libxl__ev_fd_isregistered(ev)); + + rc = OSEVENT_HOOK(fd_modify, ev->fd, &ev->for_app_reg, events); + if (rc) goto out; + + ev->events = events; + + rc = 0; + out: + CTX_UNLOCK; + return rc; +} + +void libxl__ev_fd_deregister(libxl__gc *gc, libxl__ev_fd *ev) +{ + CTX_LOCK; + + if (!libxl__ev_fd_isregistered(ev)) + goto out; + + OSEVENT_HOOK_VOID(fd_deregister, ev->fd, ev->for_app_reg); + LIBXL_LIST_REMOVE(ev, entry); + ev->fd = -1; + + out: + CTX_UNLOCK; +} + +/* + * timeouts + */ + + +int libxl__gettimeofday(libxl__gc *gc, struct timeval *now_r) +{ + int rc = gettimeofday(now_r, 0); + if (rc) { + LIBXL__LOG_ERRNO(CTX, LIBXL__LOG_ERROR, "gettimeofday failed"); + return ERROR_FAIL; + } + return 0; +} + +static int time_rel_to_abs(libxl__gc *gc, int ms, struct timeval *abs_out) +{ + int rc; + struct timeval additional = { + .tv_sec = ms / 1000, + .tv_usec = (ms % 1000) * 1000 + }; + struct timeval now; + + rc = libxl__gettimeofday(gc, &now); + if (rc) return rc; + + timeradd(&now, &additional, abs_out); + return 0; +} + +static void time_insert_finite(libxl__gc *gc, libxl__ev_time *ev) +{ + libxl__ev_time *evsearch; + LIBXL_TAILQ_INSERT_SORTED(&CTX->etimes, entry, ev, evsearch, /*empty*/, + timercmp(&ev->abs, &evsearch->abs, >)); + ev->infinite = 0; +} + +static int time_register_finite(libxl__gc *gc, libxl__ev_time *ev, + struct timeval abs) +{ + int rc; + + rc = OSEVENT_HOOK(timeout_register, &ev->for_app_reg, abs, ev); + if (rc) return rc; + + ev->infinite = 0; + ev->abs = abs; + time_insert_finite(gc, ev); + + return 0; +} + +static void time_deregister(libxl__gc *gc, libxl__ev_time *ev) +{ + if (!ev->infinite) { + OSEVENT_HOOK_VOID(timeout_deregister, &ev->for_app_reg); + LIBXL_TAILQ_REMOVE(&CTX->etimes, ev, entry); + } +} + + +int libxl__ev_time_register_abs(libxl__gc *gc, libxl__ev_time *ev, + libxl__ev_time_callback *func, + struct timeval abs) +{ + int rc; + + CTX_LOCK; + + rc = time_register_finite(gc, ev, abs); + if (rc) goto out; + + ev->func = func; + + rc = 0; + out: + CTX_UNLOCK; + return rc; +} + + +int libxl__ev_time_register_rel(libxl__gc *gc, libxl__ev_time *ev, + libxl__ev_time_callback *func, + int milliseconds /* as for poll(2) */) +{ + struct timeval abs; + int rc; + + CTX_LOCK; + + if (milliseconds < 0) { + ev->infinite = 1; + } else { + rc = time_rel_to_abs(gc, milliseconds, &abs); + if (rc) goto out; + + rc = time_register_finite(gc, ev, abs); + if (rc) goto out; + } + + ev->func = func; + rc = 0; + + out: + CTX_UNLOCK; + return rc; +} + +int libxl__ev_time_modify_abs(libxl__gc *gc, libxl__ev_time *ev, + struct timeval abs) +{ + int rc; + + CTX_LOCK; + + assert(libxl__ev_time_isregistered(ev)); + + if (ev->infinite) { + rc = time_register_finite(gc, ev, abs); + if (rc) goto out; + } else { + rc = OSEVENT_HOOK(timeout_modify, &ev->for_app_reg, abs); + if (rc) goto out; + + LIBXL_TAILQ_REMOVE(&CTX->etimes, ev, entry); + ev->abs = abs; + time_insert_finite(gc, ev); + } + + rc = 0; + out: + CTX_UNLOCK; + return rc; +} + +int libxl__ev_time_modify_rel(libxl__gc *gc, libxl__ev_time *ev, + int milliseconds) +{ + struct timeval abs; + int rc; + + CTX_LOCK; + + assert(libxl__ev_time_isregistered(ev)); + + if (milliseconds < 0) { + time_deregister(gc, ev); + ev->infinite = 1; + rc = 0; + goto out; + } + + rc = time_rel_to_abs(gc, milliseconds, &abs); + if (rc) goto out; + + rc = libxl__ev_time_modify_abs(gc, ev, abs); + if (rc) goto out; + + rc = 0; + out: + CTX_UNLOCK; + return rc; +} + +void libxl__ev_time_deregister(libxl__gc *gc, libxl__ev_time *ev) +{ + CTX_LOCK; + + if (!libxl__ev_time_isregistered(ev)) + goto out; + + time_deregister(gc, ev); + ev->func = 0; + + out: + CTX_UNLOCK; + return; +} + + +/* + * xenstore watches + */ + +libxl__ev_xswatch *libxl__watch_slot_contents(libxl__gc *gc, int slotnum) +{ + libxl__ev_watch_slot *slot = &CTX->watch_slots[slotnum]; + libxl__ev_watch_slot *slotcontents = LIBXL_SLIST_NEXT(slot, empty); + + if (slotcontents == NULL || + ((uintptr_t)slotcontents >= (uintptr_t)CTX->watch_slots && + (uintptr_t)slotcontents < (uintptr_t)(CTX->watch_slots + + CTX->watch_nslots))) + /* An empty slot has either a NULL pointer (end of the + * free list), or a pointer to another entry in the array. + * So we can do a bounds check to distinguish empty from + * full slots. + */ + /* We need to do the comparisons as uintptr_t because + * comparing pointers which are not in the same object is + * undefined behaviour; if the compiler managed to figure + * out that watch_slots[0..watch_nslots-1] is all of the + * whole array object it could prove that the above bounds + * check was always true if it was legal, and remove it! + * + * uintptr_t because even on a machine with signed + * pointers, objects do not cross zero; whereas on + * machines with unsigned pointers, they may cross + * 0x8bazillion. + */ + return NULL; + + /* see comment near libxl__ev_watch_slot definition */ + return (void*)slotcontents; +} + +static void libxl__set_watch_slot_contents(libxl__ev_watch_slot *slot, + libxl__ev_xswatch *w) +{ + /* we look a bit behind the curtain of LIBXL_SLIST, to explicitly + * assign to the pointer that's the next link. See the comment + * by the definition of libxl__ev_watch_slot */ + slot->empty.sle_next = (void*)w; +} + +static void watchfd_callback(libxl__egc *egc, libxl__ev_fd *ev, + int fd, short events, short revents) +{ + EGC_GC; + + for (;;) { + char **event = xs_check_watch(CTX->xsh); + if (!event) { + if (errno == EAGAIN) break; + if (errno == EINTR) continue; + LIBXL__EVENT_DISASTER(egc, "cannot check/read watches", errno, 0); + return; + } + + const char *epath = event[0]; + const char *token = event[1]; + int slotnum; + uint32_t counterval; + int rc = sscanf(token, "%d/%"SCNx32, &slotnum, &counterval); + if (rc != 2) { + LIBXL__LOG(CTX, LIBXL__LOG_ERROR, + "watch epath=%s token=%s: failed to parse token", + epath, token); + /* oh well */ + goto ignore; + } + if (slotnum < 0 || slotnum >= CTX->watch_nslots) { + /* perhaps in the future we will make the watchslots array shrink */ + LIBXL__LOG(CTX, LIBXL__LOG_DEBUG, "watch epath=%s token=%s:" + " slotnum %d out of range [0,%d>", + epath, token, slotnum, CTX->watch_nslots); + goto ignore; + } + + libxl__ev_xswatch *w = libxl__watch_slot_contents(gc, slotnum); + + if (!w) { + LIBXL__LOG(CTX, LIBXL__LOG_DEBUG, + "watch epath=%s token=%s: empty slot", + epath, token); + goto ignore; + } + + if (w->counterval != counterval) { + LIBXL__LOG(CTX, LIBXL__LOG_DEBUG, + "watch epath=%s token=%s: counter != %"PRIx32, + epath, token, w->counterval); + goto ignore; + } + + /* Now it's possible, though unlikely, that this was an event + * from a previous use of the same slot with the same counterval. + * + * In that case either: + * - the event path is a child of the watch path, in + * which case this watch would really have generated this + * event if it had been registered soon enough and we are + * OK to give this possibly-spurious event to the caller; or + * - it is not, in which case we must suppress it as the + * caller should not see events for unrelated paths. + * + * See also docs/misc/xenstore.txt. + */ + if (!xs_path_is_subpath(w->path, epath)) { + LIBXL__LOG(CTX, LIBXL__LOG_DEBUG, + "watch epath=%s token=%s: not child of wpath=%s", + epath, token, w->path); + goto ignore; + } + + /* At last, we have checked everything! */ + LIBXL__LOG(CTX, LIBXL__LOG_DEBUG, + "watch event: epath=%s token=%s wpath=%s w=%p", + epath, token, w->path, w); + w->callback(egc, w, w->path, epath); + + ignore: + free(event); + } +} + +static char *watch_token(libxl__gc *gc, int slotnum, uint32_t counterval) +{ + return libxl__sprintf(gc, "%d/%"PRIx32, slotnum, counterval); +} + +int libxl__ev_xswatch_register(libxl__gc *gc, libxl__ev_xswatch *w, + libxl__ev_xswatch_callback *func, + const char *path /* copied */) +{ + libxl__ev_watch_slot *use = NULL; + char *path_copy = NULL; + int rc; + + CTX_LOCK; + + if (!libxl__ev_fd_isregistered(&CTX->watch_efd)) { + rc = libxl__ev_fd_register(gc, &CTX->watch_efd, watchfd_callback, + xs_fileno(CTX->xsh), POLLIN); + if (rc) goto out_rc; + } + + if (LIBXL_SLIST_EMPTY(&CTX->watch_freeslots)) { + /* Free list is empty so there is not in fact a linked + * free list in the array and we can safely realloc it */ + int newarraysize = (CTX->watch_nslots + 1) << 2; + int i; + libxl__ev_watch_slot *newarray = + realloc(CTX->watch_slots, sizeof(*newarray) * newarraysize); + if (!newarray) goto out_nomem; + for (i = CTX->watch_nslots; i < newarraysize; i++) + LIBXL_SLIST_INSERT_HEAD(&CTX->watch_freeslots, + &newarray[i], empty); + CTX->watch_slots = newarray; + CTX->watch_nslots = newarraysize; + } + use = LIBXL_SLIST_FIRST(&CTX->watch_freeslots); + assert(use); + LIBXL_SLIST_REMOVE_HEAD(&CTX->watch_freeslots, empty); + + path_copy = strdup(path); + if (!path_copy) goto out_nomem; + + int slotnum = use - CTX->watch_slots; + w->counterval = CTX->watch_counter++; + + if (!xs_watch(CTX->xsh, path, watch_token(gc, slotnum, w->counterval))) { + LIBXL__LOG_ERRNOVAL(CTX, LIBXL__LOG_ERROR, errno, + "create watch for path %s", path); + rc = ERROR_FAIL; + goto out_rc; + } + + w->slotnum = slotnum; + w->path = path_copy; + w->callback = func; + libxl__set_watch_slot_contents(use, w); + + CTX_UNLOCK; + return 0; + + out_nomem: + rc = ERROR_NOMEM; + out_rc: + if (use) + LIBXL_SLIST_INSERT_HEAD(&CTX->watch_freeslots, use, empty); + if (path_copy) + free(path_copy); + CTX_UNLOCK; + return rc; +} + +void libxl__ev_xswatch_deregister(libxl__gc *gc, libxl__ev_xswatch *w) +{ + /* it is legal to deregister from within _callback */ + CTX_LOCK; + + if (w->slotnum >= 0) { + char *token = watch_token(gc, w->slotnum, w->counterval); + if (!xs_unwatch(CTX->xsh, w->path, token)) + /* Oh well, we will just get watch events forever more + * and ignore them. But we should complain to the log. */ + LIBXL__LOG_ERRNOVAL(CTX, LIBXL__LOG_ERROR, errno, + "remove watch for path %s", w->path); + + libxl__ev_watch_slot *slot = &CTX->watch_slots[w->slotnum]; + LIBXL_SLIST_INSERT_HEAD(&CTX->watch_freeslots, slot, empty); + w->slotnum = -1; + } + + free(w->path); + w->path = NULL; + + CTX_UNLOCK; +} + +/* + * osevent poll + */ + +int libxl_osevent_beforepoll(libxl_ctx *ctx, int *nfds_io, + struct pollfd *fds, int *timeout_upd, + struct timeval now) +{ + libxl__ev_fd *efd; + int rc; + + /* + * In order to be able to efficiently find the libxl__ev_fd + * for a struct poll during _afterpoll, we maintain a shadow + * data structure in CTX->fd_beforepolled: each slot in + * the fds array corresponds to a slot in fd_beforepolled. + */ + + GC_INIT(ctx); + CTX_LOCK; + + if (*nfds_io) { + /* + * As an optimisation, we don't touch fd_rindex + * if *nfds_io is zero on entry, since in that case the + * caller just wanted to know how big an array to give us. + * + * If !*nfds_io, the unconditional parts below are guaranteed + * not to mess with fd_rindex. + */ + + int maxfd = 0; + LIBXL_LIST_FOREACH(efd, &CTX->efds, entry) { + if (!efd->events) + continue; + if (efd->fd >= maxfd) + maxfd = efd->fd + 1; + } + /* make sure our array is as big as *nfds_io */ + if (CTX->fd_rindex_allocd < maxfd) { + assert(maxfd < INT_MAX / sizeof(int) / 2); + int *newarray = realloc(CTX->fd_rindex, sizeof(int) * maxfd); + if (!newarray) { rc = ERROR_NOMEM; goto out; } + memset(newarray + CTX->fd_rindex_allocd, 0, + sizeof(int) * (maxfd - CTX->fd_rindex_allocd)); + CTX->fd_rindex = newarray; + CTX->fd_rindex_allocd = maxfd; + } + } + + int used = 0; + LIBXL_LIST_FOREACH(efd, &CTX->efds, entry) { + if (!efd->events) + continue; + if (used < *nfds_io) { + fds[used].fd = efd->fd; + fds[used].events = efd->events; + fds[used].revents = 0; + assert(efd->fd < CTX->fd_rindex_allocd); + CTX->fd_rindex[efd->fd] = used; + } + used++; + } + rc = used <= *nfds_io ? 0 : ERROR_BUFFERFULL; + + *nfds_io = used; + + libxl__ev_time *etime = LIBXL_TAILQ_FIRST(&CTX->etimes); + if (etime) { + int our_timeout; + struct timeval rel; + static struct timeval zero; + + timersub(&etime->abs, &now, &rel); + + if (timercmp(&rel, &zero, <)) { + our_timeout = 0; + } else if (rel.tv_sec >= 2000000) { + our_timeout = 2000000000; + } else { + our_timeout = rel.tv_sec * 1000 + (rel.tv_usec + 999) / 1000; + } + if (*timeout_upd < 0 || our_timeout < *timeout_upd) + *timeout_upd = our_timeout; + } + + out: + CTX_UNLOCK; + GC_FREE; + return rc; +} + +static int afterpoll_check_fd(libxl_ctx *ctx, + const struct pollfd *fds, int nfds, + int fd, int events) + /* returns mask of events which were requested and occurred */ +{ + if (fd >= ctx->fd_rindex_allocd) + /* added after we went into poll, have to try again */ + return 0; + + int slot = ctx->fd_rindex[fd]; + + if (slot >= nfds) + /* stale slot entry; again, added afterwards */ + return 0; + + if (fds[slot].fd != fd) + /* again, stale slot entry */ + return 0; + + int revents = fds[slot].revents & events; + /* we mask in case requested events have changed */ + + return revents; +} + +void libxl_osevent_afterpoll(libxl_ctx *ctx, int nfds, const struct pollfd *fds, + struct timeval now) +{ + EGC_INIT(ctx); + CTX_LOCK; + libxl__ev_fd *efd; + + LIBXL_LIST_FOREACH(efd, &CTX->efds, entry) { + if (!efd->events) + continue; + + int revents = afterpoll_check_fd(CTX,fds,nfds, efd->fd,efd->events); + if (revents) + efd->func(egc, efd, efd->fd, efd->events, revents); + } + + for (;;) { + libxl__ev_time *etime = LIBXL_TAILQ_FIRST(&CTX->etimes); + if (!etime) + break; + + assert(!etime->infinite); + + if (timercmp(&etime->abs, &now, >)) + break; + + time_deregister(gc, etime); + + etime->func(egc, etime, &etime->abs); + } + + CTX_UNLOCK; + EGC_FREE; +} + + +/* + * osevent hook and callback machinery + */ + +void libxl_osevent_register_hooks(libxl_ctx *ctx, + const libxl_osevent_hooks *hooks, + void *user) +{ + GC_INIT(ctx); + CTX_LOCK; + ctx->osevent_hooks = hooks; + ctx->osevent_user = user; + CTX_UNLOCK; + GC_FREE; +} + + +void libxl_osevent_occurred_fd(libxl_ctx *ctx, void *for_libxl, + int fd, short events, short revents) +{ + libxl__ev_fd *ev = for_libxl; + + EGC_INIT(ctx); + CTX_LOCK; + assert(!CTX->osevent_in_hook); + + assert(fd == ev->fd); + revents &= ev->events; + if (revents) + ev->func(egc, ev, fd, ev->events, revents); + + CTX_UNLOCK; + EGC_FREE; +} + +void libxl_osevent_occurred_timeout(libxl_ctx *ctx, void *for_libxl) +{ + libxl__ev_time *ev = for_libxl; + + EGC_INIT(ctx); + CTX_LOCK; + assert(!CTX->osevent_in_hook); + + assert(!ev->infinite); + LIBXL_TAILQ_REMOVE(&CTX->etimes, ev, entry); + ev->func(egc, ev, &ev->abs); + + CTX_UNLOCK; + EGC_FREE; +} + +void libxl__event_disaster(libxl__egc *egc, const char *msg, int errnoval, + libxl_event_type type /* may be 0 */, + const char *file, int line, const char *func) +{ + EGC_GC; + + libxl__log(CTX, XTL_CRITICAL, errnoval, file, line, func, + "DISASTER in event loop: %s%s%s%s", + msg, + type ? " (relates to event type " : "", + type ? libxl_event_type_to_string(type) : "", + type ? ")" : ""); + + /* + * FIXME: This should call the "disaster" hook supplied to + * libxl_event_register_callbacks, which will be introduced in the + * next patch. + */ + + const char verybad[] = + "DISASTER in event loop not handled by libxl application"; + LIBXL__LOG(CTX, XTL_CRITICAL, verybad); + fprintf(stderr, "libxl: fatal error, exiting program: %s\n", verybad); + exit(-1); +} + +void libxl__egc_cleanup(libxl__egc *egc) +{ + libxl__free_all(&egc->gc); +} + +/* + * Local variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/tools/libxl/libxl_event.h b/tools/libxl/libxl_event.h new file mode 100644 index 0000000000..63ef65e2a0 --- /dev/null +++ b/tools/libxl/libxl_event.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2011 Citrix Ltd. + * Author Ian Jackson <ian.jackson@eu.citrix.com> + * + * 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; version 2.1 only. with the special + * exception on linking described in file LICENSE. + * + * 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. + */ + +#ifndef LIBXL_EVENT_H +#define LIBXL_EVENT_H + +#include <libxl.h> + + +/*======================================================================*/ + +/* + * OS event handling - passing low-level OS events to libxl + * + * Event-driven programs must use these facilities to allow libxl + * to become aware of readability/writeability of file descriptors + * and the occurrence of timeouts. + * + * There are two approaches available. The first is appropriate for + * simple programs handling reasonably small numbers of domains: + * + * for (;;) { + * libxl_osevent_beforepoll(...) + * poll(); + * libxl_osevent_afterpoll(...); + * for (;;) { + * r=libxl_event_check(...); + * if (r==LIBXL_NOT_READY) break; + * if (r) handle failure; + * do something with the event; + * } + * } + * + * The second approach uses libxl_osevent_register_hooks and is + * suitable for programs which are already using a callback-based + * event library. + * + * An application may freely mix the two styles of interaction. + * + * (Callers inside libxl may not call libxl_osevent_... functions.) + */ + +struct pollfd; + +/* The caller should provide beforepoll with some space for libxl's + * fds, and tell libxl how much space is available by setting *nfds_io. + * fds points to the start of this space (and fds may be a pointer into + * a larger array, for example, if the application has some fds of + * its own that it is interested in). + * + * On return *nfds_io will in any case have been updated by libxl + * according to how many fds libxl wants to poll on. + * + * If the space was sufficient, libxl fills in fds[0..<new + * *nfds_io>] suitably for poll(2), updates *timeout_upd if needed, + * and returns ok. + * + * If space was insufficient, fds[0..<old *nfds_io>] is undefined on + * return; *nfds_io on return will be greater than the value on + * entry; *timeout_upd may or may not have been updated; and + * libxl_osevent_beforepoll returns ERROR_BUFERFULL. In this case + * the application needs to make more space (enough space for + * *nfds_io struct pollfd) and then call beforepoll again, before + * entering poll(2). Typically this will involve calling realloc. + * + * The application may call beforepoll with fds==NULL and + * *nfds_io==0 in order to find out how much space is needed. + * + * *timeout_upd is as for poll(2): it's in milliseconds, and + * negative values mean no timeout (infinity). + * libxl_osevent_beforepoll will only reduce the timeout, naturally. + */ +int libxl_osevent_beforepoll(libxl_ctx *ctx, int *nfds_io, + struct pollfd *fds, int *timeout_upd, + struct timeval now); + +/* nfds and fds[0..nfds] must be from the most recent call to + * _beforepoll, as modified by poll. (It is therefore not possible + * to have multiple threads simultaneously polling using this + * interface.) + * + * This function actually performs all of the IO and other actions, + * and generates events (libxl_event), which are implied by either + * (a) the time of day or (b) both (i) the returned information from + * _beforepoll, and (ii) the results from poll specified in + * fds[0..nfds-1]. Generated events can then be retrieved by + * libxl_event_check. + */ +void libxl_osevent_afterpoll(libxl_ctx *ctx, int nfds, const struct pollfd *fds, + struct timeval now); + + +typedef struct libxl_osevent_hooks { + int (*fd_register)(void *user, int fd, void **for_app_registration_out, + short events, void *for_libxl); + int (*fd_modify)(void *user, int fd, void **for_app_registration_update, + short events); + void (*fd_deregister)(void *user, int fd, void *for_app_registration); + int (*timeout_register)(void *user, void **for_app_registration_out, + struct timeval abs, void *for_libxl); + int (*timeout_modify)(void *user, void **for_app_registration_update, + struct timeval abs); + void (*timeout_deregister)(void *user, void *for_app_registration); +} libxl_osevent_hooks; + +/* The application which calls register_fd_hooks promises to + * maintain a register of fds and timeouts that libxl is interested + * in, and make calls into libxl (libxl_osevent_occurred_*) + * when those fd events and timeouts occur. This is more efficient + * than _beforepoll/_afterpoll if there are many fds (which can + * happen if the same libxl application is managing many domains). + * + * For an fd event, events is as for poll(). register or modify may + * be called with events==0, in which case it must still work + * normally, just not generate any events. + * + * For a timeout event, milliseconds is as for poll(). + * Specifically, negative values of milliseconds mean NO TIMEOUT. + * This is used by libxl to temporarily disable a timeout. + * + * If the register or modify hook succeeds it may update + * *for_app_registration_out/_update and must then return 0. + * On entry to register, *for_app_registration_out is always NULL. + * + * A registration or modification hook may fail, in which case it + * must leave the registration state of the fd or timeout unchanged. + * It may then either return ERROR_OSEVENT_REG_FAIL or any positive + * int. The value returned will be passed up through libxl and + * eventually returned back to the application. When register + * fails, any value stored into *for_registration_out is ignored by + * libxl; when modify fails, any changed value stored into + * *for_registration_update is honoured by libxl and will be passed + * to future modify or deregister calls. + * + * libxl will only attempt to register one callback for any one fd. + * libxl will remember the value stored in *for_app_registration_out + * (or *for_app_registration_update) by a successful call to + * register (or modify), and pass it to subsequent calls to modify + * or deregister. + * + * register_fd_hooks may be called only once for each libxl_ctx. + * libxl may make calls to register/modify/deregister from within + * any libxl function (indeed, it will usually call register from + * register_event_hooks). Conversely, the application MUST NOT make + * the event occurrence calls (libxl_osevent_occurred_*) into libxl + * reentrantly from within libxl (for example, from within the + * register/modify functions). + * + * Lock hierarchy: the register/modify/deregister functions may be + * called with locks held. These locks (the "libxl internal locks") + * are inside the libxl_ctx. Therefore, if those register functions + * acquire any locks of their own ("caller register locks") outside + * libxl, to avoid deadlock one of the following must hold for each + * such caller register lock: + * (a) "acquire libxl internal locks before caller register lock": + * No libxl function may be called with the caller register + * lock held. + * (b) "acquire caller register lock before libxl internal locks": + * No libxl function may be called _without_ the caller + * register lock held. + * Of these we would normally recommend (a). + * + * The value *hooks is not copied and must outlast the libxl_ctx. + */ +void libxl_osevent_register_hooks(libxl_ctx *ctx, + const libxl_osevent_hooks *hooks, + void *user); + +/* It is NOT legal to call _occurred_ reentrantly within any libxl + * function. Specifically it is NOT legal to call it from within + * a register callback. Conversely, libxl MAY call register/deregister + * from within libxl_event_registered_call_*. + */ + +void libxl_osevent_occurred_fd(libxl_ctx *ctx, void *for_libxl, + int fd, short events, short revents); + +/* Implicitly, on entry to this function the timeout has been + * deregistered. If _occurred_timeout is called, libxl will not + * call timeout_deregister; if it wants to requeue the timeout it + * will call timeout_register again. + */ +void libxl_osevent_occurred_timeout(libxl_ctx *ctx, void *for_libxl); + +#endif + +/* + * Local variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/tools/libxl/libxl_internal.h b/tools/libxl/libxl_internal.h index 3c8da450f2..26a6147a33 100644 --- a/tools/libxl/libxl_internal.h +++ b/tools/libxl/libxl_internal.h @@ -35,6 +35,7 @@ #include <unistd.h> #include <sys/mman.h> +#include <sys/poll.h> #include <sys/select.h> #include <sys/stat.h> #include <sys/time.h> @@ -111,6 +112,71 @@ _hidden void libxl__log(libxl_ctx *ctx, xentoollog_level msglevel, int errnoval, /* these functions preserve errno (saving and restoring) */ +typedef struct libxl__gc libxl__gc; +typedef struct libxl__egc libxl__egc; + +typedef struct libxl__ev_fd libxl__ev_fd; +typedef void libxl__ev_fd_callback(libxl__egc *egc, libxl__ev_fd *ev, + int fd, short events, short revents); +struct libxl__ev_fd { + /* caller should include this in their own struct */ + /* read-only for caller, who may read only when registered: */ + int fd; + short events; + libxl__ev_fd_callback *func; + /* remainder is private for libxl__ev_fd... */ + LIBXL_LIST_ENTRY(libxl__ev_fd) entry; + void *for_app_reg; +}; + + +typedef struct libxl__ev_time libxl__ev_time; +typedef void libxl__ev_time_callback(libxl__egc *egc, libxl__ev_time *ev, + const struct timeval *requested_abs); +struct libxl__ev_time { + /* caller should include this in their own struct */ + /* read-only for caller, who may read only when registered: */ + libxl__ev_time_callback *func; + /* remainder is private for libxl__ev_time... */ + int infinite; /* not registered in list or with app if infinite */ + LIBXL_TAILQ_ENTRY(libxl__ev_time) entry; + struct timeval abs; + void *for_app_reg; +}; + +typedef struct libxl__ev_xswatch libxl__ev_xswatch; +typedef void libxl__ev_xswatch_callback(libxl__egc *egc, libxl__ev_xswatch*, + const char *watch_path, const char *event_path); +struct libxl__ev_xswatch { + /* caller should include this in their own struct */ + /* read-only for caller, who may read only when registered: */ + char *path; + libxl__ev_xswatch_callback *callback; + /* remainder is private for libxl__ev_xswatch... */ + int slotnum; /* registered iff slotnum >= 0 */ + uint32_t counterval; +}; + +/* + * An entry in the watch_slots table is either: + * 1. an entry in the free list, ie NULL or pointer to next free list entry + * 2. an pointer to a libxl__ev_xswatch + * + * But we don't want to use unions or type-punning because the + * compiler might "prove" that our code is wrong and misoptimise it. + * + * The rules say that all struct pointers have identical + * representation and alignment requirements (C99+TC1+TC2 6.2.5p26) so + * what we do is simply declare our array as containing only the free + * list pointers, and explicitly convert from and to our actual + * xswatch pointers when we store and retrieve them. + */ +typedef struct libxl__ev_watch_slot { + LIBXL_SLIST_ENTRY(struct libxl__ev_watch_slot) empty; +} libxl__ev_watch_slot; + +libxl__ev_xswatch *libxl__watch_slot_contents(libxl__gc *gc, int slotnum); + struct libxl__ctx { xentoollog_logger *lg; xc_interface *xch; @@ -129,6 +195,23 @@ struct libxl__ctx { * documented in the libxl public interface. */ + int osevent_in_hook; + const libxl_osevent_hooks *osevent_hooks; + void *osevent_user; + /* See the comment for OSEVENT_HOOK_INTERN in libxl_event.c + * for restrictions on the use of the osevent fields. */ + + int fd_rindex_allocd; + int *fd_rindex; /* see libxl_osevent_beforepoll */ + LIBXL_LIST_HEAD(, libxl__ev_fd) efds; + LIBXL_TAILQ_HEAD(, libxl__ev_time) etimes; + + libxl__ev_watch_slot *watch_slots; + int watch_nslots; + LIBXL_SLIST_HEAD(, libxl__ev_watch_slot) watch_freeslots; + uint32_t watch_counter; /* helps disambiguate slot reuse */ + libxl__ev_fd watch_efd; + /* for callers who reap children willy-nilly; caller must only * set this after libxl_init and before any other call - or * may leave them untouched */ @@ -159,12 +242,17 @@ typedef struct { #define PRINTF_ATTRIBUTE(x, y) __attribute__((format(printf, x, y))) -typedef struct { +struct libxl__gc { /* mini-GC */ int alloc_maxsize; void **alloc_ptrs; libxl_ctx *owner; -} libxl__gc; +}; + +struct libxl__egc { + /* for event-generating functions only */ + struct libxl__gc gc; +}; #define LIBXL_INIT_GC(gc,ctx) do{ \ (gc).alloc_maxsize = 0; \ @@ -236,6 +324,140 @@ _hidden bool libxl__xs_mkdir(libxl__gc *gc, xs_transaction_t t, _hidden char *libxl__xs_libxl_path(libxl__gc *gc, uint32_t domid); + +/* + * Event generation functions provided by the libxl event core to the + * rest of libxl. Implemented in terms of _beforepoll/_afterpoll + * and/or the fd registration machinery, as provided by the + * application. + * + * Semantics are similar to those of the fd and timeout registration + * functions provided to libxl_osevent_register_hooks. + * + * Non-0 returns from libxl__ev_{modify,deregister} have already been + * logged by the core and should be returned unmodified to libxl's + * caller; NB that they may be valid libxl error codes but they may + * also be positive numbers supplied by the caller. + * + * In each case, there is a libxl__ev_FOO structure which can be in + * one of three states: + * + * Undefined - Might contain anything. All-bits-zero is + * an undefined state. + * + * Idle - Struct contents are defined enough to pass to any + * libxl__ev_FOO function but not registered and + * callback will not be called. The struct does not + * contain references to any allocated resources so + * can be thrown away. + * + * Active - Request for events has been registered and events + * may be generated. _deregister must be called to + * reclaim resources. + * + * These functions are provided for each kind of event KIND: + * + * int libxl__ev_KIND_register(libxl__gc *gc, libxl__ev_KIND *GEN, + * libxl__ev_KIND_callback *FUNC, + * DETAILS); + * On entry *GEN must be in state Undefined or Idle. + * Returns a libxl error code; on error return *GEN is Idle. + * On successful return *GEN is Active and FUNC wil be + * called by the event machinery in future. FUNC will + * not be called from within the call to _register. + * FUNC will be called with the context locked (with CTX_LOCK). + * + * void libxl__ev_KIND_deregister(libxl__gc *gc, libxl__ev_KIND *GEN_upd); + * On entry *GEN must be in state Active or Idle. + * On return it is Idle. (Idempotent.) + * + * void libxl__ev_KIND_init(libxl__ev_KIND *GEN); + * Provided for initialising an Undefined KIND. + * On entry *GEN must be in state Idle or Undefined. + * On return it is Idle. (Idempotent.) + * + * int libxl__ev_KIND_isregistered(const libxl__ev_KIND *GEN); + * On entry *GEN must be Idle or Active. + * Returns nonzero if it is Active, zero otherwise. + * Cannot fail. + * + * int libxl__ev_KIND_modify(libxl__gc*, libxl__ev_KIND *GEN, + * DETAILS); + * Only provided for some kinds of generator. + * On entry *GEN must be Active and on return, whether successful + * or not, it will be Active. + * Returns a libxl error code; on error the modification + * is not effective. + * + * All of these functions are fully threadsafe and may be called by + * general code in libxl even from within event callback FUNCs. + * The ctx will be locked on entry to each FUNC and FUNC should not + * unlock it. + * + * Callers of libxl__ev_KIND_register must ensure that the + * registration is undone, with _deregister, in libxl_ctx_free. + */ + + +_hidden int libxl__ev_fd_register(libxl__gc*, libxl__ev_fd *ev_out, + libxl__ev_fd_callback*, + int fd, short events /* as for poll(2) */); +_hidden int libxl__ev_fd_modify(libxl__gc*, libxl__ev_fd *ev, + short events); +_hidden void libxl__ev_fd_deregister(libxl__gc*, libxl__ev_fd *ev); +static inline void libxl__ev_fd_init(libxl__ev_fd *efd) + { efd->fd = -1; } +static inline int libxl__ev_fd_isregistered(libxl__ev_fd *efd) + { return efd->fd >= 0; } + +_hidden int libxl__ev_time_register_rel(libxl__gc*, libxl__ev_time *ev_out, + libxl__ev_time_callback*, + int milliseconds /* as for poll(2) */); +_hidden int libxl__ev_time_register_abs(libxl__gc*, libxl__ev_time *ev_out, + libxl__ev_time_callback*, + struct timeval); +_hidden int libxl__ev_time_modify_rel(libxl__gc*, libxl__ev_time *ev, + int milliseconds /* as for poll(2) */); +_hidden int libxl__ev_time_modify_abs(libxl__gc*, libxl__ev_time *ev, + struct timeval); +_hidden void libxl__ev_time_deregister(libxl__gc*, libxl__ev_time *ev); +static inline void libxl__ev_time_init(libxl__ev_time *ev) + { ev->func = 0; } +static inline int libxl__ev_time_isregistered(libxl__ev_time *ev) + { return !!ev->func; } + + +_hidden int libxl__ev_xswatch_register(libxl__gc*, libxl__ev_xswatch *xsw_out, + libxl__ev_xswatch_callback*, + const char *path /* copied */); +_hidden void libxl__ev_xswatch_deregister(libxl__gc *gc, libxl__ev_xswatch*); + +static inline void libxl__ev_xswatch_init(libxl__ev_xswatch *xswatch_out) + { xswatch_out->slotnum = -1; } +static inline int libxl__ev_xswatch_isregistered(const libxl__ev_xswatch *xw) + { return xw->slotnum >= 0; } + + +/* + * In general, call this via the macro LIBXL__EVENT_DISASTER. + * + * Event-generating functions may call this if they might have wanted + * to generate an event (either an internal one ie a + * libxl__ev_FOO_callback or an application event), but are prevented + * from doing so due to eg lack of memory. + * + * NB that this function may return and the caller isn't supposed to + * then crash, although it may fail (and henceforth leave things in a + * state where many or all calls fail). + */ +_hidden void libxl__event_disaster(libxl__egc*, const char *msg, int errnoval, + libxl_event_type type /* may be 0 */, + const char *file, int line, + const char *func); +#define LIBXL__EVENT_DISASTER(egc, msg, errnoval, type) \ + libxl__event_disaster(egc, msg, errnoval, type, __FILE__,__LINE__,__func__) + + /* from xl_dom */ _hidden libxl_domain_type libxl__domain_type(libxl__gc *gc, uint32_t domid); _hidden int libxl__domain_shutdown_reason(libxl__gc *gc, uint32_t domid); @@ -602,6 +824,8 @@ _hidden int libxl__parse_mac(const char *s, libxl_mac mac); /* compare mac address @a and @b. 0 if the same, -ve if a<b and +ve if a>b */ _hidden int libxl__compare_macs(libxl_mac *a, libxl_mac *b); +_hidden int libxl__gettimeofday(libxl__gc *gc, struct timeval *now_r); + #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) @@ -739,6 +963,55 @@ libxl__device_model_version_running(libxl__gc *gc, uint32_t domid); /* + * Calling context and GC for event-generating functions: + * + * These are for use by parts of libxl which directly or indirectly + * call libxl__event_occurred. These contain a gc but also a list of + * deferred events. + * + * You should never need to initialise an egc unless you are part of + * the event machinery itself. Otherwise you will always be given an + * egc if you need one. Even functions which generate specific kinds + * of events don't need to - rather, they will be passed an egc into + * their own callback function and should just use the one they're + * given. + * + * Functions using LIBXL__INIT_EGC may *not* generally be called from + * within libxl, because libxl__egc_cleanup may call back into the + * application. This should be documented near the function + * prototype(s) for callers of LIBXL__INIT_EGC and EGC_INIT. You + * should in any case not find it necessary to call egc-creators from + * within libxl. + * + * For the same reason libxl__egc_cleanup (or EGC_FREE) must be called + * with the ctx *unlocked*. So the right pattern has the EGC_... + * macro calls on the outside of the CTX_... ones. + */ + +/* useful for all functions which take an egc: */ + +#define EGC_GC \ + libxl__gc *const gc = &egc->gc + +/* egc initialisation and destruction: */ + +#define LIBXL_INIT_EGC(egc,ctx) do{ \ + LIBXL_INIT_GC((egc).gc,ctx); \ + /* list of occurred events tbd */ \ + } while(0) + +_hidden void libxl__egc_cleanup(libxl__egc *egc); + +/* convenience macros: */ + +#define EGC_INIT(ctx) \ + libxl__egc egc[1]; LIBXL_INIT_EGC(egc[0],ctx); \ + EGC_GC + +#define EGC_FREE libxl__egc_cleanup(egc) + + +/* * Convenience macros. */ |