From: "Daniel P. Berrange" <berrange@xxxxxxxxxx> 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. NB: this is being done as a pre-requisite to updating libvirt's cgroups code to talk to systemd via DBus. Signed-off-by: Daniel P. Berrange <berrange@xxxxxxxxxx> --- .gitignore | 1 + src/libvirt_private.syms | 4 + src/util/virdbus.c | 958 ++++++++++++++++++++++++++++++++++++++++++++++- src/util/virdbus.h | 11 + src/util/virdbuspriv.h | 43 +++ tests/Makefile.am | 6 + tests/virdbustest.c | 391 +++++++++++++++++++ 7 files changed, 1413 insertions(+), 1 deletion(-) create mode 100644 src/util/virdbuspriv.h create mode 100644 tests/virdbustest.c diff --git a/.gitignore b/.gitignore index 3efc2e4..851c6e4 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/src/libvirt_private.syms b/src/libvirt_private.syms index 0ab7632..f337637 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1269,8 +1269,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 52b6ca9..8c2c783 100644 --- a/src/util/virdbus.c +++ b/src/util/virdbus.c @@ -21,11 +21,12 @@ #include <config.h> -#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,940 @@ 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 docode 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) \ + 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; \ + } \ + } 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); + break; + + case DBUS_TYPE_BOOLEAN: + SET_NEXT_VAL(dbus_bool_t, int, *t); + break; + + case DBUS_TYPE_INT16: + SET_NEXT_VAL(dbus_int16_t, int, *t); + break; + + case DBUS_TYPE_UINT16: + SET_NEXT_VAL(dbus_uint16_t, unsigned int, *t); + break; + + case DBUS_TYPE_INT32: + SET_NEXT_VAL(dbus_int32_t, int, *t); + break; + + case DBUS_TYPE_UINT32: + SET_NEXT_VAL(dbus_uint32_t, unsigned int, *t); + break; + + case DBUS_TYPE_INT64: + SET_NEXT_VAL(dbus_int64_t, long long, *t); + break; + + case DBUS_TYPE_UINT64: + SET_NEXT_VAL(dbus_uint64_t, unsigned long long, *t); + break; + + case DBUS_TYPE_DOUBLE: + SET_NEXT_VAL(double, double, *t); + break; + + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_SIGNATURE: + SET_NEXT_VAL(char *, char *, *t); + 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, size_t); + 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) \ + do { \ + dbustype *x = (dbustype *)va_arg(args, vargtype *); \ + dbus_message_iter_get_basic(iter, 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); + break; + + case DBUS_TYPE_BOOLEAN: + GET_NEXT_VAL(dbus_bool_t, int); + break; + + case DBUS_TYPE_INT16: + GET_NEXT_VAL(dbus_int16_t, int); + break; + + case DBUS_TYPE_UINT16: + GET_NEXT_VAL(dbus_uint16_t, unsigned int); + break; + + case DBUS_TYPE_INT32: + GET_NEXT_VAL(dbus_uint32_t, int); + break; + + case DBUS_TYPE_UINT32: + GET_NEXT_VAL(dbus_uint32_t, unsigned int); + break; + + case DBUS_TYPE_INT64: + GET_NEXT_VAL(dbus_uint64_t, long long); + break; + + case DBUS_TYPE_UINT64: + GET_NEXT_VAL(dbus_uint64_t, unsigned long long); + break; + + case DBUS_TYPE_DOUBLE: + GET_NEXT_VAL(double, double); + 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; + } 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, size_t); + 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; +} + +/** + * @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' - NULL terminated string, in UTF-8 + * 'o' - NULL terminated string, representing a valid object path + * 'g' - NULL 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@xxxxxxxxx", "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, + 30 * 1000, + &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; + } + + 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 +1172,25 @@ DBusConnection *virDBusGetSessionBus(void) "%s", _("DBus support not compiled into this binary")); return NULL; } + +int virDBusCallMethod(DBusConnection *conn 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 a73e293..69a32d8 100644 --- a/src/util/virdbus.h +++ b/src/util/virdbus.h @@ -27,10 +27,21 @@ # include <dbus/dbus.h> # else # define DBusConnection void +# define DBusMesssage 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 0000000..751536f --- /dev/null +++ b/src/util/virdbuspriv.h @@ -0,0 +1,43 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef __VIR_DBUS_PRIV_H__ +# define __VIR_DBUS_PRIV_H__ + +# include "virdbus.h" + +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/tests/Makefile.am b/tests/Makefile.am index 4c49151..9eaa9d8 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -116,6 +116,7 @@ test_programs = virshtest sockettest \ virauthconfigtest \ virbitmaptest \ vircgrouptest \ + virdbustest \ virendiantest \ viridentitytest \ virkeycodetest \ @@ -637,6 +638,11 @@ vircgroupmock_la_CFLAGS = $(AM_CFLAGS) vircgroupmock_la_LDFLAGS = -module -avoid-version \ -rpath /evil/libtool/hack/to/force/shared/lib/creation +virdbustest_SOURCES = \ + virdbustest.c testutils.h testutils.c +virdbustest_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) +virdbustest_LDADD = $(LDADDS) + viruritest_SOURCES = \ viruritest.c testutils.h testutils.c diff --git a/tests/virdbustest.c b/tests/virdbustest.c new file mode 100644 index 0000000..13cd8cf --- /dev/null +++ b/tests/virdbustest.c @@ -0,0 +1,391 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#include <config.h> + +#include <stdlib.h> + +#include "virdbuspriv.h" +#include "virlog.h" +#include "testutils.h" + +#define VERIFY(t, a, b, f) \ + do { \ + VIR_DEBUG("Compare " t " '" f "' to '" f "'", a, b); \ + if (a != b) { \ + fprintf(stderr, "Failed to round-trip " t " '" f "' to '" f "'\n", a, b); \ + goto cleanup; \ + } \ + } while (0) + +#define VERIFY_STR(t, a, b, f) \ + do { \ + VIR_DEBUG("Compare " t " '" f "' to '" f "'", a, b); \ + if (STRNEQ(a, b)) { \ + fprintf(stderr, "Failed to round-trip " t " '" f "' to '" f "'\n", a, b); \ + 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; + bool 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"; + size_t arraylen = 3; + int in_int32a = 100000000, out_int32a = 0; + int in_int32b = 200000000, out_int32b = 0; + int in_int32c = 300000000, 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, + arraylen, in_int32a, in_int32b, in_int32c, + in_str2) < 0) { + VIR_DEBUG("Failed to encode arguments"); + goto cleanup; + } + + if (virDBusMessageDecode(msg, + "sais", + &out_str1, + arraylen, &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; + bool 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"; + size_t arraylen = 3; + 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, + arraylen, + 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, + arraylen, + &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) -- 1.8.1.4 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list