From 834c9c94592c18b67826bdda85ee50ffc31dae69 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Fri, 12 Jul 2013 11:13:04 +0100 Subject: [PATCH] Introduce virDBusCallMethod & virDBusMessageRead methods Doing DBus method calls using libdbus.so is tedious in the extreme. systemd developers came up with a nice high level API for DBus method calls (sd_bus_call_method). While systemd doesn't use libdbus.so, their API design can easily be ported to libdbus.so. This patch thus introduces methods virDBusCallMethod & virDBusMessageRead, which are based on the code used for sd_bus_call_method and sd_bus_message_read. This code in systemd is under the LGPLv2+, so we're license compatible. This code is probably pretty unintelligible unless you are familiar with the DBus type system. So I added some API docs trying to explain how to use them, as well as test cases to validate that I didn't screw up the adaptation from the original systemd code. Signed-off-by: Daniel P. Berrange --- .gitignore | 1 + include/libvirt/virterror.h | 1 + src/libvirt_private.syms | 4 + src/util/virdbus.c | 987 +++++++++++++++++++++++++++++++++++- src/util/virdbus.h | 11 + src/util/virdbuspriv.h | 45 ++ src/util/virerror.c | 6 + src/util/virerror.h | 11 + tests/Makefile.am | 13 + tests/virdbustest.c | 393 ++++++++++++++ 10 files changed, 1471 insertions(+), 1 deletion(-) create mode 100644 src/util/virdbuspriv.h create mode 100644 tests/virdbustest.c diff --git a/.gitignore b/.gitignore index 3efc2e4a5b..851c6e4008 100644 --- a/.gitignore +++ b/.gitignore @@ -191,6 +191,7 @@ /tests/virbitmaptest /tests/virbuftest /tests/vircgrouptest +/tests/virdbustest /tests/virdrivermoduletest /tests/virendiantest /tests/virhashtest diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index 5f78856d01..3ef73a1f46 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -294,6 +294,7 @@ typedef enum { VIR_ERR_RESOURCE_BUSY = 87, /* resource is already in use */ VIR_ERR_ACCESS_DENIED = 88, /* operation on the object/resource was denied */ + VIR_ERR_DBUS_SERVICE = 89, /* error from a dbus service */ } virErrorNumber; /** diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 7790ede6d5..b83074f4b0 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1282,8 +1282,12 @@ virConfWriteMem; # util/virdbus.h +virDBusCallMethod; virDBusGetSessionBus; virDBusGetSystemBus; +virDBusMessageDecode; +virDBusMessageEncode; +virDBusMessageRead; # util/virdnsmasq.h diff --git a/src/util/virdbus.c b/src/util/virdbus.c index 52b6ca9d16..ee99f7fbc4 100644 --- a/src/util/virdbus.c +++ b/src/util/virdbus.c @@ -21,11 +21,12 @@ #include -#include "virdbus.h" +#include "virdbuspriv.h" #include "viralloc.h" #include "virerror.h" #include "virlog.h" #include "virthread.h" +#include "virstring.h" #define VIR_FROM_THIS VIR_FROM_DBUS @@ -223,6 +224,968 @@ static void virDBusToggleWatch(DBusWatch *watch, (void)virEventUpdateHandle(info->watch, flags); } +# define VIR_DBUS_TYPE_STACK_MAX_DEPTH 32 + +static const char virDBusBasicTypes[] = { + DBUS_TYPE_BYTE, + DBUS_TYPE_BOOLEAN, + DBUS_TYPE_INT16, + DBUS_TYPE_UINT16, + DBUS_TYPE_INT32, + DBUS_TYPE_UINT32, + DBUS_TYPE_INT64, + DBUS_TYPE_UINT64, + DBUS_TYPE_DOUBLE, + DBUS_TYPE_STRING, + DBUS_TYPE_OBJECT_PATH, + DBUS_TYPE_SIGNATURE, + DBUS_TYPE_UNIX_FD +}; + +static bool virDBusIsBasicType(char c) { + return !!memchr(virDBusBasicTypes, c, ARRAY_CARDINALITY(virDBusBasicTypes)); +} + +/* + * All code related to virDBusMessageIterEncode and + * virDBusMessageIterDecode is derived from systemd + * bus_message_append_ap()/message_read_ap() in + * bus-message.c under the terms of the LGPLv2+ + */ +static int +virDBusSignatureLengthInternal(const char *s, + bool allowDict, + unsigned arrayDepth, + unsigned structDepth, + size_t *l) +{ + if (virDBusIsBasicType(*s) || *s == DBUS_TYPE_VARIANT) { + *l = 1; + return 0; + } + + if (*s == DBUS_TYPE_ARRAY) { + size_t t; + + if (arrayDepth >= VIR_DBUS_TYPE_STACK_MAX_DEPTH) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Signature '%s' too deeply nested"), + s); + return -1; + } + + if (virDBusSignatureLengthInternal(s + 1, + true, + arrayDepth + 1, + structDepth, + &t) < 0) + return -1; + + *l = t + 1; + return 0; + } + + if (*s == DBUS_STRUCT_BEGIN_CHAR) { + const char *p = s + 1; + + if (structDepth >= VIR_DBUS_TYPE_STACK_MAX_DEPTH) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Signature '%s' too deeply nested"), + s); + return -1; + } + + while (*p != DBUS_STRUCT_END_CHAR) { + size_t t; + + if (virDBusSignatureLengthInternal(p, + false, + arrayDepth, + structDepth + 1, + &t) < 0) + return -1; + + p += t; + } + + *l = p - s + 1; + return 0; + } + + if (*s == DBUS_DICT_ENTRY_BEGIN_CHAR && allowDict) { + const char *p = s + 1; + unsigned n = 0; + if (structDepth >= VIR_DBUS_TYPE_STACK_MAX_DEPTH) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Signature '%s' too deeply nested"), + s); + return -1; + } + + while (*p != DBUS_DICT_ENTRY_END_CHAR) { + size_t t; + + if (n == 0 && !virDBusIsBasicType(*p)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Dict entry in signature '%s' must be a basic type"), + s); + return -1; + } + + if (virDBusSignatureLengthInternal(p, + false, + arrayDepth, + structDepth + 1, + &t) < 0) + return -1; + + p += t; + n++; + } + + if (n != 2) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Dict entry in signature '%s' is wrong size"), + s); + return -1; + } + + *l = p - s + 1; + return 0; + } + + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unexpected signature '%s'"), s); + return -1; +} + + +static int virDBusSignatureLength(const char *s, size_t *l) +{ + return virDBusSignatureLengthInternal(s, true, 0, 0, l); +} + + + +/* Ideally, we'd just call ourselves recursively on every + * complex type. However, the state of a va_list that is + * passed to a function is undefined after that function + * returns. This means we need to decode the va_list linearly + * in a single stackframe. We hence implement our own + * home-grown stack in an array. */ + +typedef struct _virDBusTypeStack virDBusTypeStack; +struct _virDBusTypeStack { + const char *types; + size_t nstruct; + size_t narray; + DBusMessageIter *iter; +}; + +static int virDBusTypeStackPush(virDBusTypeStack **stack, + size_t *nstack, + DBusMessageIter *iter, + const char *types, + size_t nstruct, + size_t narray) +{ + if (*nstack >= VIR_DBUS_TYPE_STACK_MAX_DEPTH) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("DBus type too deeply nested")); + return -1; + } + + if (VIR_EXPAND_N(*stack, *nstack, 1) < 0) + return -1; + + (*stack)[(*nstack) - 1].iter = iter; + (*stack)[(*nstack) - 1].types = types; + (*stack)[(*nstack) - 1].nstruct = nstruct; + (*stack)[(*nstack) - 1].narray = narray; + VIR_DEBUG("Pushed '%s'", types); + return 0; +} + + +static int virDBusTypeStackPop(virDBusTypeStack **stack, + size_t *nstack, + DBusMessageIter **iter, + const char **types, + size_t *nstruct, + size_t *narray) +{ + if (*nstack == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("DBus type stack is empty")); + return -1; + } + + *iter = (*stack)[(*nstack) - 1].iter; + *types = (*stack)[(*nstack) - 1].types; + *nstruct = (*stack)[(*nstack) - 1].nstruct; + *narray = (*stack)[(*nstack) - 1].narray; + VIR_DEBUG("Popped '%s'", *types); + VIR_SHRINK_N(*stack, *nstack, 1); + + return 0; +} + + +static void virDBusTypeStackFree(virDBusTypeStack **stack, + size_t *nstack) +{ + size_t i; + /* The iter in the first level of the stack is the + * root iter which must not be freed + */ + for (i = 1; i < *nstack; i++) { + VIR_FREE((*stack)[i].iter); + } + VIR_FREE(*stack); +} + + +# define SET_NEXT_VAL(dbustype, vargtype, sigtype, fmt) \ + do { \ + dbustype x = (dbustype)va_arg(args, vargtype); \ + if (!dbus_message_iter_append_basic(iter, sigtype, &x)) { \ + virReportError(VIR_ERR_INTERNAL_ERROR, \ + _("Cannot append basic type %s"), #vargtype); \ + goto cleanup; \ + } \ + VIR_DEBUG("Appended basic type '" #dbustype "' varg '" #vargtype \ + "' sig '%c' val '" fmt "'", sigtype, (vargtype)x); \ + } while (0) + +static int +virDBusMessageIterEncode(DBusMessageIter *rootiter, + const char *types, + va_list args) +{ + int ret = -1; + size_t narray; + size_t nstruct; + virDBusTypeStack *stack = NULL; + size_t nstack = 0; + size_t siglen; + char *contsig = NULL; + const char *vsig; + DBusMessageIter *newiter = NULL; + DBusMessageIter *iter = rootiter; + + VIR_DEBUG("rootiter=%p types=%s", rootiter, types); + + if (!types) + return 0; + + narray = (size_t)-1; + nstruct = strlen(types); + + for (;;) { + const char *t; + + VIR_DEBUG("Loop stack=%zu array=%zu struct=%zu type='%s'", + nstack, narray, nstruct, types); + if (narray == 0 || + (narray == (size_t)-1 && + nstruct == 0)) { + DBusMessageIter *thisiter = iter; + VIR_DEBUG("Popping iter=%p", iter); + if (nstack == 0) + break; + if (virDBusTypeStackPop(&stack, &nstack, &iter, + &types, &nstruct, &narray) < 0) + goto cleanup; + VIR_DEBUG("Popped iter=%p", iter); + + if (!dbus_message_iter_close_container(iter, thisiter)) { + if (thisiter != rootiter) + VIR_FREE(thisiter); + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot close container iterator")); + goto cleanup; + } + if (thisiter != rootiter) + VIR_FREE(thisiter); + continue; + } + + t = types; + if (narray != (size_t)-1) { + narray--; + } else { + types++; + nstruct--; + } + + switch (*t) { + case DBUS_TYPE_BYTE: + SET_NEXT_VAL(unsigned char, int, *t, "%d"); + break; + + case DBUS_TYPE_BOOLEAN: + SET_NEXT_VAL(dbus_bool_t, int, *t, "%d"); + break; + + case DBUS_TYPE_INT16: + SET_NEXT_VAL(dbus_int16_t, int, *t, "%d"); + break; + + case DBUS_TYPE_UINT16: + SET_NEXT_VAL(dbus_uint16_t, unsigned int, *t, "%d"); + break; + + case DBUS_TYPE_INT32: + SET_NEXT_VAL(dbus_int32_t, int, *t, "%d"); + break; + + case DBUS_TYPE_UINT32: + SET_NEXT_VAL(dbus_uint32_t, unsigned int, *t, "%u"); + break; + + case DBUS_TYPE_INT64: + SET_NEXT_VAL(dbus_int64_t, long long, *t, "%lld"); + break; + + case DBUS_TYPE_UINT64: + SET_NEXT_VAL(dbus_uint64_t, unsigned long long, *t, "%llu"); + break; + + case DBUS_TYPE_DOUBLE: + SET_NEXT_VAL(double, double, *t, "%lf"); + break; + + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_SIGNATURE: + SET_NEXT_VAL(char *, char *, *t, "%s"); + break; + + case DBUS_TYPE_ARRAY: + if (virDBusSignatureLength(t + 1, &siglen) < 0) + goto cleanup; + + if (VIR_STRNDUP(contsig, t + 1, siglen) < 0) + goto cleanup; + + if (narray == (size_t)-1) { + types += siglen; + nstruct -= siglen; + } + + if (VIR_ALLOC(newiter) < 0) + goto cleanup; + VIR_DEBUG("Contsig '%s' '%zu'", contsig, siglen); + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + contsig, newiter)) + goto cleanup; + if (virDBusTypeStackPush(&stack, &nstack, + iter, types, + nstruct, narray) < 0) + goto cleanup; + VIR_FREE(contsig); + iter = newiter; + newiter = NULL; + types = t + 1; + nstruct = siglen; + narray = va_arg(args, int); + break; + + case DBUS_TYPE_VARIANT: + vsig = va_arg(args, const char *); + if (!vsig) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing variant type signature")); + goto cleanup; + } + if (VIR_ALLOC(newiter) < 0) + goto cleanup; + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, + vsig, newiter)) + goto cleanup; + if (virDBusTypeStackPush(&stack, &nstack, + iter, types, + nstruct, narray) < 0) + goto cleanup; + iter = newiter; + newiter = NULL; + types = vsig; + nstruct = strlen(types); + narray = (size_t)-1; + break; + + case DBUS_STRUCT_BEGIN_CHAR: + case DBUS_DICT_ENTRY_BEGIN_CHAR: + if (virDBusSignatureLength(t, &siglen) < 0) + goto cleanup; + + if (VIR_STRNDUP(contsig, t + 1, siglen - 1) < 0) + goto cleanup; + + if (VIR_ALLOC(newiter) < 0) + goto cleanup; + VIR_DEBUG("Contsig '%s' '%zu'", contsig, siglen); + if (!dbus_message_iter_open_container(iter, + *t == DBUS_STRUCT_BEGIN_CHAR ? + DBUS_TYPE_STRUCT : DBUS_TYPE_DICT_ENTRY, + NULL, newiter)) + goto cleanup; + if (narray == (size_t)-1) { + types += siglen - 1; + nstruct -= siglen - 1; + } + + if (virDBusTypeStackPush(&stack, &nstack, + iter, types, + nstruct, narray) < 0) + goto cleanup; + VIR_FREE(contsig); + iter = newiter; + newiter = NULL; + types = t + 1; + nstruct = siglen - 2; + narray = (size_t)-1; + + break; + + default: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unknown type in signature '%s'"), + types); + } + } + + ret = 0; + +cleanup: + virDBusTypeStackFree(&stack, &nstack); + VIR_FREE(contsig); + VIR_FREE(newiter); + return ret; +} +# undef SET_NEXT_VAL + + +# define GET_NEXT_VAL(dbustype, vargtype, fmt) \ + do { \ + dbustype *x = (dbustype *)va_arg(args, vargtype *); \ + dbus_message_iter_get_basic(iter, x); \ + VIR_DEBUG("Read basic type '" #dbustype "' varg '" #vargtype \ + "' val '" fmt "'", (vargtype)*x); \ + } while (0) + + +static int +virDBusMessageIterDecode(DBusMessageIter *rootiter, + const char *types, + va_list args) +{ + int ret = -1; + size_t narray; + size_t nstruct; + virDBusTypeStack *stack = NULL; + size_t nstack = 0; + size_t siglen; + char *contsig = NULL; + const char *vsig; + DBusMessageIter *newiter = NULL; + DBusMessageIter *iter = rootiter; + + VIR_DEBUG("rootiter=%p types=%s", rootiter, types); + + if (!types) + return 0; + + narray = (size_t)-1; + nstruct = strlen(types); + + for (;;) { + const char *t; + bool advanceiter = true; + + VIR_DEBUG("Loop stack=%zu array=%zu struct=%zu type='%s'", + nstack, narray, nstruct, types); + if (narray == 0 || + (narray == (size_t)-1 && + nstruct == 0)) { + DBusMessageIter *thisiter = iter; + VIR_DEBUG("Popping iter=%p", iter); + if (nstack == 0) + break; + if (virDBusTypeStackPop(&stack, &nstack, &iter, + &types, &nstruct, &narray) < 0) + goto cleanup; + VIR_DEBUG("Popped iter=%p types=%s", iter, types); + if (thisiter != rootiter) + VIR_FREE(thisiter); + if (!(narray == 0 || + (narray == (size_t)-1 && + nstruct == 0)) && + !dbus_message_iter_next(iter)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Not enough fields in message for signature")); + goto cleanup; + } + continue; + } + + t = types; + if (narray != (size_t)-1) { + narray--; + } else { + types++; + nstruct--; + } + + switch (*t) { + case DBUS_TYPE_BYTE: + GET_NEXT_VAL(unsigned char, int, "%d"); + break; + + case DBUS_TYPE_BOOLEAN: + GET_NEXT_VAL(dbus_bool_t, int, "%d"); + break; + + case DBUS_TYPE_INT16: + GET_NEXT_VAL(dbus_int16_t, int, "%d"); + break; + + case DBUS_TYPE_UINT16: + GET_NEXT_VAL(dbus_uint16_t, unsigned int, "%d"); + break; + + case DBUS_TYPE_INT32: + GET_NEXT_VAL(dbus_uint32_t, int, "%d"); + break; + + case DBUS_TYPE_UINT32: + GET_NEXT_VAL(dbus_uint32_t, unsigned int, "%u"); + break; + + case DBUS_TYPE_INT64: + GET_NEXT_VAL(dbus_uint64_t, long long, "%lld"); + break; + + case DBUS_TYPE_UINT64: + GET_NEXT_VAL(dbus_uint64_t, unsigned long long, "%llu"); + break; + + case DBUS_TYPE_DOUBLE: + GET_NEXT_VAL(double, double, "%lf"); + break; + + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_SIGNATURE: + do { + char **x = (char **)va_arg(args, char **); + char *s; + dbus_message_iter_get_basic(iter, &s); + if (VIR_STRDUP(*x, s) < 0) + goto cleanup; + VIR_DEBUG("Read basic type 'char *' varg 'char **'" + "' val '%s'", *x); + } while (0); + break; + + case DBUS_TYPE_ARRAY: + advanceiter = false; + if (virDBusSignatureLength(t + 1, &siglen) < 0) + goto cleanup; + + if (VIR_STRNDUP(contsig, t + 1, siglen) < 0) + goto cleanup; + + if (narray == (size_t)-1) { + types += siglen; + nstruct -= siglen; + } + + if (VIR_ALLOC(newiter) < 0) + goto cleanup; + VIR_DEBUG("Contsig '%s' '%zu' '%s'", contsig, siglen, types); + dbus_message_iter_recurse(iter, newiter); + if (virDBusTypeStackPush(&stack, &nstack, + iter, types, + nstruct, narray) < 0) + goto cleanup; + VIR_FREE(contsig); + iter = newiter; + newiter = NULL; + types = t + 1; + nstruct = siglen; + narray = va_arg(args, int); + break; + + case DBUS_TYPE_VARIANT: + advanceiter = false; + vsig = va_arg(args, const char *); + if (!vsig) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing variant type signature")); + goto cleanup; + } + if (VIR_ALLOC(newiter) < 0) + goto cleanup; + dbus_message_iter_recurse(iter, newiter); + if (virDBusTypeStackPush(&stack, &nstack, + iter, types, + nstruct, narray) < 0) { + VIR_DEBUG("Push failed"); + goto cleanup; + } + iter = newiter; + newiter = NULL; + types = vsig; + nstruct = strlen(types); + narray = (size_t)-1; + break; + + case DBUS_STRUCT_BEGIN_CHAR: + case DBUS_DICT_ENTRY_BEGIN_CHAR: + advanceiter = false; + if (virDBusSignatureLength(t, &siglen) < 0) + goto cleanup; + + if (VIR_STRNDUP(contsig, t + 1, siglen - 1) < 0) + goto cleanup; + + if (VIR_ALLOC(newiter) < 0) + goto cleanup; + VIR_DEBUG("Contsig '%s' '%zu'", contsig, siglen); + dbus_message_iter_recurse(iter, newiter); + if (narray == (size_t)-1) { + types += siglen - 1; + nstruct -= siglen - 1; + } + + if (virDBusTypeStackPush(&stack, &nstack, + iter, types, + nstruct, narray) < 0) + goto cleanup; + VIR_FREE(contsig); + iter = newiter; + newiter = NULL; + types = t + 1; + nstruct = siglen - 2; + narray = (size_t)-1; + + break; + + default: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unknown type in signature '%s'"), + types); + } + + VIR_DEBUG("After stack=%zu array=%zu struct=%zu type='%s'", + nstack, narray, nstruct, types); + if (advanceiter && + !(narray == 0 || + (narray == (size_t)-1 && + nstruct == 0)) && + !dbus_message_iter_next(iter)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Not enough fields in message for signature")); + goto cleanup; + } + } + + if (dbus_message_iter_has_next(iter)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Too many fields in message for signature")); + goto cleanup; + } + + ret = 0; + +cleanup: + virDBusTypeStackFree(&stack, &nstack); + VIR_FREE(contsig); + VIR_FREE(newiter); + return ret; +} +# undef GET_NEXT_VAL + +int +virDBusMessageEncodeArgs(DBusMessage* msg, + const char *types, + va_list args) +{ + DBusMessageIter iter; + int ret = -1; + + memset(&iter, 0, sizeof(iter)); + + dbus_message_iter_init_append(msg, &iter); + + ret = virDBusMessageIterEncode(&iter, types, args); + + return ret; +} + + +int virDBusMessageDecodeArgs(DBusMessage* msg, + const char *types, + va_list args) +{ + DBusMessageIter iter; + int ret = -1; + + if (!dbus_message_iter_init(msg, &iter)) { + if (*types != '\0') { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("No args present for signature %s"), + types); + } else { + ret = 0; + } + goto cleanup; + } + + ret = virDBusMessageIterDecode(&iter, types, args); + +cleanup: + return ret; +} + + +int virDBusMessageEncode(DBusMessage* msg, + const char *types, + ...) +{ + int ret; + va_list args; + va_start(args, types); + ret = virDBusMessageEncodeArgs(msg, types, args); + va_end(args); + return ret; +} + + +int virDBusMessageDecode(DBusMessage* msg, + const char *types, + ...) +{ + int ret; + va_list args; + va_start(args, types); + ret = virDBusMessageDecodeArgs(msg, types, args); + va_end(args); + return ret; +} + +# define VIR_DBUS_METHOD_CALL_TIMEOUT_MILLIS 30 * 1000 + +/** + * virDBusCallMethod: + * @conn: a DBus connection + * @replyout: pointer to receive reply message, or NULL + * @destination: bus identifier of the target service + * @path: object path of the target service + * @interface: the interface of the object + * @member: the name of the method in the interface + * @types: type signature for following method arguments + * @...: method arguments + * + * This invokes a method on a remote service on the + * DBus bus @conn. The @destination, @path, @interface + * and @member parameters identify the object method to + * be invoked. The optional @replyout parameter will be + * filled with any reply to the method call. The + * virDBusMethodReply method can be used to decode the + * return values. + * + * The @types parameter is a DBus signature describing + * the method call parameters which will be provided + * as variadic args. Each character in @types must + * correspond to one of the following DBus codes for + * basic types: + * + * 'y' - 8-bit byte, promoted to an 'int' + * 'b' - bool value, promoted to an 'int' + * 'n' - 16-bit signed integer, promoted to an 'int' + * 'q' - 16-bit unsigned integer, promoted to an 'int' + * 'i' - 32-bit signed integer, passed as an 'int' + * 'u' - 32-bit unsigned integer, passed as an 'int' + * 'x' - 64-bit signed integer, passed as a 'long long' + * 't' - 64-bit unsigned integer, passed as an 'unsigned long long' + * 'd' - 8-byte floating point, passed as a 'double' + * 's' - NUL-terminated string, in UTF-8 + * 'o' - NUL-terminated string, representing a valid object path + * 'g' - NUL-terminated string, representing a valid type signature + * + * or use one of the compound types + * + * 'a' - array of values + * 'v' - a variadic type. + * '(' - start of a struct + * ')' - end of a struct + * '{' - start of a dictionary entry (pair of types) + * '}' - start of a dictionary entry (pair of types) + * + * Passing values in variadic args for basic types is + * simple, the value is just passed directly using the + * corresponding C type listed against the type code + * above. Note how any integer value smaller than an + * 'int' is promoted to an 'int' by the C rules for + * variadic args. + * + * Passing values in variadic args for compound types + * requires a little further explanation. + * + * - Variant: the first arg is a string containing + * the type signature for the values to be stored + * inside the variant. This is then followed by + * the values corresponding to the type signature + * in the normal manner. + * + * - Array: when 'a' appears in a type signature, it + * must be followed by a single type describing the + * array element type. For example 'as' is an array + * of strings. 'a(is)' is an array of structs, each + * struct containing an int and a string. + * + * The first variadic arg for an array, is an 'int' + * specifying the number of elements in the array. + * This is then followed by the values for the array + * + * - Struct: when a '(' appears in a type signature, + * it must be followed by one or more types describing + * the elements in the array, terminated by a ')'. + * + * - Dict entry: when a '{' appears in a type signature it + * must be followed by exactly two types, one describing + * the type of the hash key, the other describing the + * type of the hash entry. The hash key type must be + * a basic type, not a compound type. + * + * Example signatures, with their corresponding variadic + * args: + * + * - "biiss" - some basic types + * + * (true, 7, 42, "hello", "world") + * + * - "as" - an array with a basic type element + * + * (3, "one", "two", "three") + * + * - "a(is)" - an array with a struct element + * + * (3, 1, "one", 2, "two", 3, "three") + * + * - "svs" - some basic types with a variant as an int + * + * ("hello", "i", 3, "world") + * + * - "svs" - some basic types with a variant as an array of ints + * + * ("hello", "ai", 4, 1, 2, 3, 4, "world") + * + * - "a{ss}" - a hash table (aka array + dict entry) + * + * (3, "title", "Mr", "forename", "Joe", "surname", "Bloggs") + * + * - "a{sv}" - a hash table (aka array + dict entry) + * + * (3, "email", "s", "joe@blogs.com", "age", "i", 35, + * "address", "as", 3, "Some house", "Some road", "some city") + */ + +int virDBusCallMethod(DBusConnection *conn, + DBusMessage **replyout, + const char *destination, + const char *path, + const char *interface, + const char *member, + const char *types, ...) +{ + DBusMessage *call = NULL; + DBusMessage *reply = NULL; + DBusError error; + int ret = -1; + va_list args; + + dbus_error_init(&error); + + if (!(call = dbus_message_new_method_call(destination, + path, + interface, + member))) { + virReportOOMError(); + goto cleanup; + } + + va_start(args, types); + ret = virDBusMessageEncodeArgs(call, types, args); + va_end(args); + if (ret < 0) + goto cleanup; + + ret = -1; + + if (!(reply = dbus_connection_send_with_reply_and_block(conn, + call, + VIR_DBUS_METHOD_CALL_TIMEOUT_MILLIS, + &error))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot send to %s.%s on path %s with interface %s: %s"), + destination, member, path, interface, NULLSTR(error.message)); + goto cleanup; + } + + if (dbus_set_error_from_message(&error, + reply)) { + virReportDBusServiceError(error.message ? error.message : "unknown error", + error.name); + goto cleanup; + } + + ret = 0; + +cleanup: + dbus_error_free(&error); + if (call) + dbus_message_unref(call); + if (reply) { + if (ret == 0 && replyout) + *replyout = reply; + else + dbus_message_unref(reply); + } + return ret; +} + + +/** + * virDBusMessageRead: + * @msg: the reply to decode + * @types: type signature for following return values + * @...: pointers in which to store return values + * + * The @types type signature is the same format as + * that used for the virDBusCallMethod. The difference + * is that each variadic parameter must be a pointer to + * be filled with the values. eg instead of passing an + * 'int', pass an 'int *'. + * + */ +int virDBusMessageRead(DBusMessage *msg, + const char *types, ...) +{ + va_list args; + int ret; + + va_start(args, types); + ret = virDBusMessageDecodeArgs(msg, types, args); + va_end(args); + + dbus_message_unref(msg); + return ret; +} + + #else /* ! WITH_DBUS */ DBusConnection *virDBusGetSystemBus(void) { @@ -237,4 +1200,26 @@ DBusConnection *virDBusGetSessionBus(void) "%s", _("DBus support not compiled into this binary")); return NULL; } + +int virDBusCallMethod(DBusConnection *conn ATTRIBUTE_UNUSED, + DBusMessage **reply ATTRIBUTE_UNUSED, + const char *destination ATTRIBUTE_UNUSED, + const char *path ATTRIBUTE_UNUSED, + const char *interface ATTRIBUTE_UNUSED, + const char *member ATTRIBUTE_UNUSED, + const char *types ATTRIBUTE_UNUSED, ...) +{ + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("DBus support not compiled into this binary")); + return -1; +} + +int virDBusMessageRead(DBusMessage *msg ATTRIBUTE_UNUSED, + const char *types ATTRIBUTE_UNUSED, ...) +{ + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("DBus support not compiled into this binary")); + return -1; +} + #endif /* ! WITH_DBUS */ diff --git a/src/util/virdbus.h b/src/util/virdbus.h index a73e293a83..c04fd104fb 100644 --- a/src/util/virdbus.h +++ b/src/util/virdbus.h @@ -27,10 +27,21 @@ # include # else # define DBusConnection void +# define DBusMessage void # endif # include "internal.h" DBusConnection *virDBusGetSystemBus(void); DBusConnection *virDBusGetSessionBus(void); +int virDBusCallMethod(DBusConnection *conn, + DBusMessage **reply, + const char *destination, + const char *path, + const char *interface, + const char *member, + const char *types, ...); +int virDBusMessageRead(DBusMessage *msg, + const char *types, ...); + #endif /* __VIR_DBUS_H__ */ diff --git a/src/util/virdbuspriv.h b/src/util/virdbuspriv.h new file mode 100644 index 0000000000..a4ff655a77 --- /dev/null +++ b/src/util/virdbuspriv.h @@ -0,0 +1,45 @@ +/* + * virdbuspriv.h: internal APIs for testing DBus code + * + * Copyright (C) 2012-2013 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, see + * . + * + */ + +#ifndef __VIR_DBUS_PRIV_H__ +# define __VIR_DBUS_PRIV_H__ + +# include "virdbus.h" + +# include + +int virDBusMessageEncodeArgs(DBusMessage* msg, + const char *types, + va_list args); + +int virDBusMessageDecodeArgs(DBusMessage* msg, + const char *types, + va_list args); + +int virDBusMessageEncode(DBusMessage* msg, + const char *types, + ...); + +int virDBusMessageDecode(DBusMessage* msg, + const char *types, + ...); + +#endif /* __VIR_DBUS_PRIV_H__ */ diff --git a/src/util/virerror.c b/src/util/virerror.c index ce3ab859f2..b8572daf61 100644 --- a/src/util/virerror.c +++ b/src/util/virerror.c @@ -1243,6 +1243,12 @@ virErrorMsg(virErrorNumber error, const char *info) else errmsg = _("access denied: %s"); break; + case VIR_ERR_DBUS_SERVICE: + if (info == NULL) + errmsg = _("error from service"); + else + errmsg = _("error from service: %s"); + break; } return errmsg; } diff --git a/src/util/virerror.h b/src/util/virerror.h index 332a5eb4fa..6ea456b8f5 100644 --- a/src/util/virerror.h +++ b/src/util/virerror.h @@ -145,6 +145,17 @@ void virReportSystemErrorFull(int domcode, 0, 0, \ (fmt), __VA_ARGS__) +# define virReportDBusServiceError(message, name) \ + virRaiseErrorFull(__FILE__, __FUNCTION__, __LINE__, \ + VIR_FROM_THIS, \ + VIR_ERR_DBUS_SERVICE, \ + VIR_ERR_ERROR, \ + __FUNCTION__, \ + name, \ + NULL, \ + 0, 0, \ + "%s", message); + void virReportOOMErrorFull(int domcode, const char *filename, const char *funcname, diff --git a/tests/Makefile.am b/tests/Makefile.am index 47926b1b29..f04aec6d45 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -128,6 +128,11 @@ test_programs = virshtest sockettest \ fchosttest \ $(NULL) +if WITH_DBUS +test_programs += virdbustest +endif + + if WITH_GNUTLS test_programs += virnettlscontexttest endif @@ -640,6 +645,14 @@ vircgroupmock_la_CFLAGS = $(AM_CFLAGS) vircgroupmock_la_LDFLAGS = -module -avoid-version \ -rpath /evil/libtool/hack/to/force/shared/lib/creation +if WITH_DBUS +virdbustest_SOURCES = \ + virdbustest.c testutils.h testutils.c +virdbustest_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) +virdbustest_LDADD = $(LDADDS) +else +EXTRA_DIST += virdbustest.c +endif viruritest_SOURCES = \ viruritest.c testutils.h testutils.c diff --git a/tests/virdbustest.c b/tests/virdbustest.c new file mode 100644 index 0000000000..e0547166e9 --- /dev/null +++ b/tests/virdbustest.c @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2013 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, see + * . + * + * Author: Daniel P. Berrange + */ + +#include + +#include + +#include "virdbuspriv.h" +#include "virlog.h" +#include "testutils.h" + +#define VERIFY(typname, valorig, valnew, fmt) \ + do { \ + VIR_DEBUG("Compare " typname " '" fmt "' to '" \ + fmt "'", valorig, valnew); \ + if (valorig != valnew) { \ + fprintf(stderr, "Failed to round-trip " typname " '" \ + fmt "' to '" fmt "'\n", valorig, valnew); \ + goto cleanup; \ + } \ + } while (0) + +#define VERIFY_STR(typname, valorig, valnew, fmt) \ + do { \ + VIR_DEBUG("Compare " typname " '" fmt "' to '" \ + fmt "'", valorig, valnew); \ + if (STRNEQ(valorig, valnew)) { \ + fprintf(stderr, "Failed to round-trip " typname " '" \ + fmt "' to '" fmt "'\n", valorig, valnew); \ + goto cleanup; \ + } \ + } while (0) + +static int testMessageSimple(const void *args ATTRIBUTE_UNUSED) +{ + DBusMessage *msg = NULL; + int ret = -1; + unsigned char in_byte = 200, out_byte = 0; + int in_bool = true, out_bool = false; + int in_int16 = 12000, out_int16 = 0; + unsigned int in_uint16 = 32000, out_uint16 = 0; + int in_int32 = 100000000, out_int32 = 0; + unsigned int in_uint32 = 200000000, out_uint32 = 0; + long long in_int64 = 1000000000000, out_int64 = 0; + unsigned long long in_uint64 = 2000000000000, out_uint64 = 0; + double in_double = 3.14159265359, out_double = 0;; + const char *in_string = "Hello World"; + char *out_string = NULL; + const char *in_objectpath = "/org/libvirt/test"; + char *out_objectpath = NULL; + const char *in_signature = "ybnqiuxtdsog"; + char *out_signature = NULL; + + if (!(msg = dbus_message_new_method_call("org.libvirt.test", + "/org/libvirt/test", + "org.libvirt.test.astrochicken", + "cluck"))) { + VIR_DEBUG("Failed to allocate method call"); + goto cleanup; + } + + if (virDBusMessageEncode(msg, + "ybnqiuxtdsog", + in_byte, in_bool, + in_int16, in_uint16, + in_int32, in_uint32, + in_int64, in_uint64, + in_double, in_string, + in_objectpath, in_signature) < 0) { + VIR_DEBUG("Failed to encode arguments"); + goto cleanup; + } + + if (virDBusMessageDecode(msg, + "ybnqiuxtdsog", + &out_byte, &out_bool, + &out_int16, &out_uint16, + &out_int32, &out_uint32, + &out_int64, &out_uint64, + &out_double, &out_string, + &out_objectpath, &out_signature) < 0) { + VIR_DEBUG("Failed to decode arguments"); + goto cleanup; + } + + VERIFY("byte", in_byte, out_byte, "%d"); + VERIFY("bool", in_bool, out_bool, "%d"); + VERIFY("int16", in_int16, out_int16, "%d"); + VERIFY("uint16", in_int16, out_int16, "%d"); + VERIFY("int32", in_int32, out_int32, "%d"); + VERIFY("uint32", in_int32, out_int32, "%d"); + VERIFY("int64", in_int64, out_int64, "%lld"); + VERIFY("uint64", in_int64, out_int64, "%lld"); + VERIFY("double", in_double, out_double, "%lf"); + VERIFY_STR("string", in_string, out_string, "%s"); + VERIFY_STR("objectpath", in_objectpath, out_objectpath, "%s"); + VERIFY_STR("signature", in_signature, out_signature, "%s"); + + ret = 0; + +cleanup: + VIR_FREE(out_string); + VIR_FREE(out_signature); + VIR_FREE(out_objectpath); + dbus_message_unref(msg); + return ret; +} + + +static int testMessageVariant(const void *args ATTRIBUTE_UNUSED) +{ + DBusMessage *msg = NULL; + int ret = -1; + const char *in_str1 = "Hello"; + int in_int32 = 100000000, out_int32 = 0; + const char *in_str2 = "World"; + char *out_str1 = NULL, *out_str2 = NULL; + + if (!(msg = dbus_message_new_method_call("org.libvirt.test", + "/org/libvirt/test", + "org.libvirt.test.astrochicken", + "cluck"))) { + VIR_DEBUG("Failed to allocate method call"); + goto cleanup; + } + + if (virDBusMessageEncode(msg, + "svs", + in_str1, + "i", in_int32, + in_str2) < 0) { + VIR_DEBUG("Failed to encode arguments"); + goto cleanup; + } + + if (virDBusMessageDecode(msg, + "svs", + &out_str1, + "i", &out_int32, + &out_str2) < 0) { + VIR_DEBUG("Failed to decode arguments"); + goto cleanup; + } + + + VERIFY_STR("str1", in_str1, out_str1, "%s"); + VERIFY("int32", in_int32, out_int32, "%d"); + VERIFY_STR("str2", in_str2, out_str2, "%s"); + + ret = 0; + +cleanup: + VIR_FREE(out_str1); + VIR_FREE(out_str2); + dbus_message_unref(msg); + return ret; +} + +static int testMessageArray(const void *args ATTRIBUTE_UNUSED) +{ + DBusMessage *msg = NULL; + int ret = -1; + const char *in_str1 = "Hello"; + int in_int32a = 1000000000, out_int32a = 0; + int in_int32b = 2000000000, out_int32b = 0; + int in_int32c = 3000000000, out_int32c = 0; + const char *in_str2 = "World"; + char *out_str1 = NULL, *out_str2 = NULL; + + if (!(msg = dbus_message_new_method_call("org.libvirt.test", + "/org/libvirt/test", + "org.libvirt.test.astrochicken", + "cluck"))) { + VIR_DEBUG("Failed to allocate method call"); + goto cleanup; + } + + if (virDBusMessageEncode(msg, + "sais", + in_str1, + (long long)3, in_int32a, in_int32b, in_int32c, + in_str2) < 0) { + VIR_DEBUG("Failed to encode arguments"); + goto cleanup; + } + + if (virDBusMessageDecode(msg, + "sais", + &out_str1, + 3, &out_int32a, &out_int32b, &out_int32c, + &out_str2) < 0) { + VIR_DEBUG("Failed to decode arguments"); + goto cleanup; + } + + + VERIFY_STR("str1", in_str1, out_str1, "%s"); + VERIFY("int32a", in_int32a, out_int32a, "%d"); + VERIFY("int32b", in_int32b, out_int32b, "%d"); + VERIFY("int32c", in_int32c, out_int32c, "%d"); + VERIFY_STR("str2", in_str2, out_str2, "%s"); + + ret = 0; + +cleanup: + VIR_FREE(out_str1); + VIR_FREE(out_str2); + dbus_message_unref(msg); + return ret; +} + +static int testMessageStruct(const void *args ATTRIBUTE_UNUSED) +{ + DBusMessage *msg = NULL; + int ret = -1; + unsigned char in_byte = 200, out_byte = 0; + int in_bool = true, out_bool = false; + int in_int16 = 12000, out_int16 = 0; + unsigned int in_uint16 = 32000, out_uint16 = 0; + int in_int32 = 100000000, out_int32 = 0; + unsigned int in_uint32 = 200000000, out_uint32 = 0; + long long in_int64 = 1000000000000, out_int64 = 0; + unsigned long long in_uint64 = 2000000000000, out_uint64 = 0; + double in_double = 3.14159265359, out_double = 0;; + const char *in_string = "Hello World"; + char *out_string = NULL; + const char *in_objectpath = "/org/libvirt/test"; + char *out_objectpath = NULL; + const char *in_signature = "ybnqiuxtdsog"; + char *out_signature = NULL; + + if (!(msg = dbus_message_new_method_call("org.libvirt.test", + "/org/libvirt/test", + "org.libvirt.test.astrochicken", + "cluck"))) { + VIR_DEBUG("Failed to allocate method call"); + goto cleanup; + } + + if (virDBusMessageEncode(msg, + "ybn(qiuxtds)og", + in_byte, in_bool, + in_int16, in_uint16, + in_int32, in_uint32, + in_int64, in_uint64, + in_double, in_string, + in_objectpath, in_signature) < 0) { + VIR_DEBUG("Failed to encode arguments"); + goto cleanup; + } + + if (virDBusMessageDecode(msg, + "ybn(qiuxtds)og", + &out_byte, &out_bool, + &out_int16, &out_uint16, + &out_int32, &out_uint32, + &out_int64, &out_uint64, + &out_double, &out_string, + &out_objectpath, &out_signature) < 0) { + VIR_DEBUG("Failed to decode arguments"); + goto cleanup; + } + + VERIFY("byte", in_byte, out_byte, "%d"); + VERIFY("bool", in_bool, out_bool, "%d"); + VERIFY("int16", in_int16, out_int16, "%d"); + VERIFY("uint16", in_int16, out_int16, "%d"); + VERIFY("int32", in_int32, out_int32, "%d"); + VERIFY("uint32", in_int32, out_int32, "%d"); + VERIFY("int64", in_int64, out_int64, "%lld"); + VERIFY("uint64", in_int64, out_int64, "%lld"); + VERIFY("double", in_double, out_double, "%lf"); + VERIFY_STR("string", in_string, out_string, "%s"); + VERIFY_STR("objectpath", in_objectpath, out_objectpath, "%s"); + VERIFY_STR("signature", in_signature, out_signature, "%s"); + + ret = 0; + +cleanup: + VIR_FREE(out_string); + VIR_FREE(out_signature); + VIR_FREE(out_objectpath); + dbus_message_unref(msg); + return ret; +} + + +static int testMessageDict(const void *args ATTRIBUTE_UNUSED) +{ + DBusMessage *msg = NULL; + int ret = -1; + const char *in_str1 = "Hello"; + int in_int32a = 100000000, out_int32a = 0; + const char *in_key1 = "turnover"; + int in_int32b = 200000000, out_int32b = 0; + const char *in_key2 = "revenue"; + int in_int32c = 300000000, out_int32c = 0; + const char *in_key3 = "debt"; + const char *in_str2 = "World"; + char *out_str1 = NULL, *out_str2 = NULL; + char *out_key1 = NULL, *out_key2 = NULL, *out_key3 = NULL; + + if (!(msg = dbus_message_new_method_call("org.libvirt.test", + "/org/libvirt/test", + "org.libvirt.test.astrochicken", + "cluck"))) { + VIR_DEBUG("Failed to allocate method call"); + goto cleanup; + } + + if (virDBusMessageEncode(msg, + "sa{si}s", + in_str1, + 3, + in_key1, in_int32a, + in_key2, in_int32b, + in_key3, in_int32c, + in_str2) < 0) { + VIR_DEBUG("Failed to encode arguments"); + goto cleanup; + } + + if (virDBusMessageDecode(msg, + "sa{si}s", + &out_str1, + 3, + &out_key1, &out_int32a, + &out_key2, &out_int32b, + &out_key3, &out_int32c, + &out_str2) < 0) { + VIR_DEBUG("Failed to decode arguments"); + goto cleanup; + } + + + VERIFY_STR("str1", in_str1, out_str1, "%s"); + VERIFY("int32a", in_int32a, out_int32a, "%d"); + VERIFY("int32b", in_int32b, out_int32b, "%d"); + VERIFY("int32c", in_int32c, out_int32c, "%d"); + VERIFY_STR("key1", in_key1, out_key1, "%s"); + VERIFY_STR("key1", in_key2, out_key2, "%s"); + VERIFY_STR("key1", in_key3, out_key3, "%s"); + VERIFY_STR("str2", in_str2, out_str2, "%s"); + + ret = 0; + +cleanup: + VIR_FREE(out_str1); + VIR_FREE(out_str2); + VIR_FREE(out_key1); + VIR_FREE(out_key2); + VIR_FREE(out_key3); + dbus_message_unref(msg); + return ret; +} + + +static int +mymain(void) +{ + int ret = 0; + + if (virtTestRun("Test message simple ", 1, testMessageSimple, NULL) < 0) + ret = -1; + if (virtTestRun("Test message variant ", 1, testMessageVariant, NULL) < 0) + ret = -1; + if (virtTestRun("Test message array ", 1, testMessageArray, NULL) < 0) + ret = -1; + if (virtTestRun("Test message struct ", 1, testMessageStruct, NULL) < 0) + ret = -1; + if (virtTestRun("Test message dict ", 1, testMessageDict, NULL) < 0) + ret = -1; + return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIRT_TEST_MAIN(mymain)