[libvirt] [PATCH 0/2] Integration with systemd-machined

From: "Daniel P. Berrange" <berrange@redhat.com> This includes a v2 of a previously submitted patch https://www.redhat.com/archives/libvir-list/2013-July/msg01084.html This posting includes a second patch to actually wrap the systemd call Changes since that first posting - Use 'int' instead of 'size_t' for the ARRAY var-arg, since a plain number "3" does not promote to 'size_t' when passed through variadic args, only to 'int'. Using 'int' thus avoids need to cast every time. - Handle DBus error replies - Make sure building --without-dbus works Daniel P. Berrange (2): Introduce virDBusCallMethod & virDBusMessageRead methods Add API for calling systemd-machined's DBus API .gitignore | 2 + include/libvirt/virterror.h | 2 + src/Makefile.am | 1 + src/libvirt_private.syms | 8 + src/util/virdbus.c | 966 +++++++++++++++++++++++++++++++++++++++++++- src/util/virdbus.h | 11 + src/util/virdbuspriv.h | 45 +++ src/util/virerror.c | 7 + src/util/virerror.h | 11 + src/util/virsystemd.c | 139 +++++++ src/util/virsystemd.h | 36 ++ tests/Makefile.am | 29 ++ tests/testutils.h | 2 + tests/virdbustest.c | 389 ++++++++++++++++++ tests/virsystemdmock.c | 77 ++++ tests/virsystemdtest.c | 131 ++++++ 16 files changed, 1855 insertions(+), 1 deletion(-) create mode 100644 src/util/virdbuspriv.h create mode 100644 src/util/virsystemd.c create mode 100644 src/util/virsystemd.h create mode 100644 tests/virdbustest.c create mode 100644 tests/virsystemdmock.c create mode 100644 tests/virsystemdtest.c -- 1.8.1.4

From: "Daniel P. Berrange" <berrange@redhat.com> 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 <berrange@redhat.com> --- .gitignore | 1 + src/libvirt_private.syms | 4 + src/util/virdbus.c | 966 ++++++++++++++++++++++++++++++++++++++++++++++- src/util/virdbus.h | 11 + src/util/virdbuspriv.h | 45 +++ tests/Makefile.am | 13 + tests/virdbustest.c | 389 +++++++++++++++++++ 7 files changed, 1428 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 542424d..0128264 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1270,8 +1270,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..dde2a24 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,947 @@ 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, 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) \ + 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, 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; +} + +/** + * @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@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, + 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; + } + + 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 +1179,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 a73e293..c04fd10 100644 --- a/src/util/virdbus.h +++ b/src/util/virdbus.h @@ -27,10 +27,21 @@ # include <dbus/dbus.h> # 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 0000000..a4ff655 --- /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 + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef __VIR_DBUS_PRIV_H__ +# define __VIR_DBUS_PRIV_H__ + +# include "virdbus.h" + +# include <stdarg.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..1748ed1 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 @@ -637,6 +642,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 0000000..39cdaf4 --- /dev/null +++ b/tests/virdbustest.c @@ -0,0 +1,389 @@ +/* + * 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@redhat.com> + */ + +#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"; + 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; + 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"; + 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) -- 1.8.1.4

On 07/18/2013 07:27 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
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 <berrange@redhat.com>
Failed to build. util/virdbus.c:1119:9: error: implicit declaration of function 'virReportDBusServiceError' [-Werror=implicit-function-declaration] virReportDBusServiceError(error.message ? error.message : "unknown error", ^ util/virdbus.c:1119:9: error: nested extern declaration of 'virReportDBusServiceError' [-Werror=nested-externs]
--- .gitignore | 1 + src/libvirt_private.syms | 4 + src/util/virdbus.c | 966 ++++++++++++++++++++++++++++++++++++++++++++++- src/util/virdbus.h | 11 + src/util/virdbuspriv.h | 45 +++ tests/Makefile.am | 13 + tests/virdbustest.c | 389 +++++++++++++++++++ 7 files changed, 1428 insertions(+), 1 deletion(-) create mode 100644 src/util/virdbuspriv.h create mode 100644 tests/virdbustest.c
+++ b/.gitignore @@ -191,6 +191,7 @@ /tests/virbitmaptest /tests/virbuftest /tests/vircgrouptest +/tests/virdbustest
Yay - an added test is reassuring (although I didn't actually run it, due to the compile error).
+ + if (virDBusSignatureLengthInternal(s + 1, true, arrayDepth+1, structDepth, &t) < 0)
Inconsistent spacing around '+'.
+ + while (*p != DBUS_STRUCT_END_CHAR) { + size_t t; + + if (virDBusSignatureLengthInternal(p, false, arrayDepth, structDepth+1, &t) < 0)
and again, probably also a long line worth wrapping.
+ + if (virDBusSignatureLengthInternal(p, false, arrayDepth, structDepth+1, &t) < 0)
Recurring theme on '+'; I'll quit pointing them out.
+ +/* 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
d/docode/decode/
+ * in a single stackframe. We hence implement our own + * home-grown stack in an array. */
Is it also possible to use va_copy, or is that risking stack explosion because we'd have to copy the list for each recursion?
+/** + * @conn: a DBus connection
Missing the method name 'virDBusCallMethod:' before the parameters.
+ * @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
+ * '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
Technically, it's NUL-terminated (terminated by the one-byte character NUL, not by the 4/8 byte pointer NULL), but we're inconsistent on this so I don't care if you leave it.
+ * Example signatures, with their corresponding variadic + * args:
Thanks; this helps.
+ * + * - "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")
Wow, that adds up fast.
+ ret = -1; + + if (!(reply = dbus_connection_send_with_reply_and_block(conn, + call, + 30 * 1000,
Worth a symbolic name for this magic number?
+ +/** + * 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 *'.
Are variants/structs/dicts allowed on decoding? If so, an example of how to interleave intermediate types alongside pointers for receiving contents may make sense.
+++ b/tests/virdbustest.c +#include "testutils.h" + +#define VERIFY(t, a, b, f) \
These names are a bit cryptic, would it hurt to make them words?
+ + if (!(msg = dbus_message_new_method_call("org.libvirt.test", + "/org/libvirt/test", + "org.libvirt.test.astrochicken", + "cluck"))) {
bawk bawk b-gaawkk :)
+ + if (virDBusMessageDecode(msg, + "svs", + &out_str1, + "i", &out_int32, + &out_str2) < 0) {
Ah, so you CAN pass a variant to decode. I guess DBus is smart enough to error out if any type signature fails to match the actual message being decoded (that is, if I pass ["v", "i", &out_int32], but the message was encoded with ["v", "s", "hello"], then I get a proper message about type mismatch rather than some random integer which happens to represent 32-bits of the pointer value of "hello")? In fact, it is worth a negative test, to prove that we handle mismatch in the client's arguments vs. the message being decoded?
+ + if (virDBusMessageDecode(msg, + "sais", + &out_str1, + 3, &out_int32a, &out_int32b, &out_int32c, + &out_str2) < 0) {
Likewise, what happens if our array is too small or too large compared to what we are decoding? Too small is probably an error; does too large work and just leave the extra pointers unmodified? Overall, it looks pretty clean; I'm okay if you just post an interdiff for the change you make to fix compilation instead of a full-blown v2. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Thu, Jul 18, 2013 at 04:33:06PM -0600, Eric Blake wrote:
On 07/18/2013 07:27 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
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 <berrange@redhat.com>
Failed to build.
util/virdbus.c:1119:9: error: implicit declaration of function 'virReportDBusServiceError' [-Werror=implicit-function-declaration] virReportDBusServiceError(error.message ? error.message : "unknown error", ^ util/virdbus.c:1119:9: error: nested extern declaration of 'virReportDBusServiceError' [-Werror=nested-externs]
Opps, yes, stuff slipped into the following patch.
+ + if (virDBusSignatureLengthInternal(s + 1, true, arrayDepth+1, structDepth, &t) < 0)
Inconsistent spacing around '+'.
+ + while (*p != DBUS_STRUCT_END_CHAR) { + size_t t; + + if (virDBusSignatureLengthInternal(p, false, arrayDepth, structDepth+1, &t) < 0)
and again, probably also a long line worth wrapping.
+ + if (virDBusSignatureLengthInternal(p, false, arrayDepth, structDepth+1, &t) < 0)
Recurring theme on '+'; I'll quit pointing them out.
All changed.
+ +/* 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
d/docode/decode/
+ * in a single stackframe. We hence implement our own + * home-grown stack in an array. */
Is it also possible to use va_copy, or is that risking stack explosion because we'd have to copy the list for each recursion?
To be honest, this code is hard enough to understand without trying to change the way systemd developers wrote it. So I just took the approach of following their design without trying to improve it myself, since it'd be more likely that I'd screw it up :-)
+ +/** + * 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 *'.
Are variants/structs/dicts allowed on decoding? If so, an example of how to interleave intermediate types alongside pointers for receiving contents may make sense.
Yes, with the limitation that you must know exactly the nesting you are expecting to get back. ie must know the array length, must know the variant sub-type, etc. If you have unpredictable complex types then you'll have to use dbus apis the hard way. Fortunately this is mostly a problem for DBus servers, clients typically only need to deal with very simple out parameters.
+ + if (virDBusMessageDecode(msg, + "svs", + &out_str1, + "i", &out_int32, + &out_str2) < 0) {
Ah, so you CAN pass a variant to decode. I guess DBus is smart enough to error out if any type signature fails to match the actual message being decoded (that is, if I pass ["v", "i", &out_int32], but the message was encoded with ["v", "s", "hello"], then I get a proper message about type mismatch rather than some random integer which happens to represent 32-bits of the pointer value of "hello")? In fact, it is worth a negative test, to prove that we handle mismatch in the client's arguments vs. the message being decoded?
I'll see about doing some negative tests.
+ + if (virDBusMessageDecode(msg, + "sais", + &out_str1, + 3, &out_int32a, &out_int32b, &out_int32c, + &out_str2) < 0) {
Likewise, what happens if our array is too small or too large compared to what we are decoding? Too small is probably an error; does too large work and just leave the extra pointers unmodified?
As above, it just won't work - you need to know the size upfront.
Overall, it looks pretty clean; I'm okay if you just post an interdiff for the change you make to fix compilation instead of a full-blown v2.
Oddly I can't get the test failure you did. Can you test this updated patch below where I've added more debugging and send the output of LIBVIRT_DEBUG=1 VIR_TEST_DEBUG=1 ./virdbustest Daniel
From d920df83f4966a088d6bb88b27b5ce11b546db2c Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" <berrange@redhat.com> 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 <berrange@redhat.com> --- .gitignore | 1 + include/libvirt/virterror.h | 1 + src/libvirt_private.syms | 4 + src/util/virdbus.c | 985 +++++++++++++++++++++++++++++++++++++++++++- 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, 1469 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/include/libvirt/virterror.h b/include/libvirt/virterror.h index 5f78856..3ef73a1 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 542424d..0128264 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1270,8 +1270,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..47648e9 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,966 @@ 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; + } 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 +1198,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 a73e293..c04fd10 100644 --- a/src/util/virdbus.h +++ b/src/util/virdbus.h @@ -27,10 +27,21 @@ # include <dbus/dbus.h> # 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 0000000..a4ff655 --- /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 + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef __VIR_DBUS_PRIV_H__ +# define __VIR_DBUS_PRIV_H__ + +# include "virdbus.h" + +# include <stdarg.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/src/util/virerror.c b/src/util/virerror.c index ce3ab85..b8572da 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 332a5eb..6ea456b 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 4c49151..1748ed1 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 @@ -637,6 +642,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 0000000..9506f86 --- /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 + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#include <config.h> + +#include <stdlib.h> + +#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; + 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"; + 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; + 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"; + 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) -- 1.8.1.4 -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 07/18/2013 07:27 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
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 <berrange@redhat.com> ---
+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 +};
This fails to build on RHEL 6.4: CC libvirt_util_la-virdbus.lo util/virdbus.c:242: error: 'DBUS_TYPE_UNIX_FD' undeclared here (not in a function) cc1: warnings being treated as errors DBUS_TYPE_UNIX_FD was added sometime after dbus-devel-1.2.24 and before 1.6.12; we'll have to make the code conditional and error out if a client tries to pass an fd when using an older version of dbus. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Tue, Jul 23, 2013 at 05:02:54PM -0600, Eric Blake wrote:
On 07/18/2013 07:27 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
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 <berrange@redhat.com> ---
+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 +};
This fails to build on RHEL 6.4:
CC libvirt_util_la-virdbus.lo util/virdbus.c:242: error: 'DBUS_TYPE_UNIX_FD' undeclared here (not in a function) cc1: warnings being treated as errors
DBUS_TYPE_UNIX_FD was added sometime after dbus-devel-1.2.24 and before 1.6.12; we'll have to make the code conditional and error out if a client tries to pass an fd when using an older version of dbus.
Ok, I'll add an #ifdef Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

From: "Daniel P. Berrange" <berrange@redhat.com> To register virtual machines and containers with systemd-machined, and thus have cgroups auto-created, we need to talk over DBus. This is somewhat tedious code, so introduce a dedicated function to isolate the DBus call in one place. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- .gitignore | 1 + include/libvirt/virterror.h | 2 + src/Makefile.am | 1 + src/libvirt_private.syms | 4 ++ src/util/virerror.c | 7 +++ src/util/virerror.h | 11 ++++ src/util/virsystemd.c | 139 ++++++++++++++++++++++++++++++++++++++++++++ src/util/virsystemd.h | 36 ++++++++++++ tests/Makefile.am | 22 ++++++- tests/testutils.h | 2 + tests/virsystemdmock.c | 77 ++++++++++++++++++++++++ tests/virsystemdtest.c | 131 +++++++++++++++++++++++++++++++++++++++++ 12 files changed, 430 insertions(+), 3 deletions(-) create mode 100644 src/util/virsystemd.c create mode 100644 src/util/virsystemd.h create mode 100644 tests/virsystemdmock.c create mode 100644 tests/virsystemdtest.c diff --git a/.gitignore b/.gitignore index 851c6e4..4c79de3 100644 --- a/.gitignore +++ b/.gitignore @@ -204,6 +204,7 @@ /tests/virshtest /tests/virstoragetest /tests/virstringtest +/tests/virsystemdtest /tests/virtimetest /tests/viruritest /tests/vmx2xmltest diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index 5f78856..c1960c8 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -119,6 +119,7 @@ typedef enum { VIR_FROM_CGROUP = 54, /* Error from cgroups */ VIR_FROM_ACCESS = 55, /* Error from access control manager */ + VIR_FROM_SYSTEMD = 56, /* Error from systemd code */ # ifdef VIR_ENUM_SENTINELS VIR_ERR_DOMAIN_LAST @@ -294,6 +295,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/Makefile.am b/src/Makefile.am index d9e703f..e4d05a0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -131,6 +131,7 @@ UTIL_SOURCES = \ util/virstoragefile.c util/virstoragefile.h \ util/virstring.h util/virstring.c \ util/virsysinfo.c util/virsysinfo.h \ + util/virsystemd.c util/virsystemd.h \ util/virthread.c util/virthread.h \ util/virthreadpthread.h \ util/virthreadwin32.h \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0128264..a9b65fd 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1911,6 +1911,10 @@ virSysinfoRead; virSysinfoSetup; +# util/virsystemd.h +virSystemdCreateMachine; + + # util/virthread.h virCondBroadcast; virCondDestroy; diff --git a/src/util/virerror.c b/src/util/virerror.c index ce3ab85..36d256b 100644 --- a/src/util/virerror.c +++ b/src/util/virerror.c @@ -123,6 +123,7 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST, "Cgroup", "Access Manager", /* 55 */ + "Systemd", ) @@ -1243,6 +1244,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 332a5eb..6ea456b 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/src/util/virsystemd.c b/src/util/virsystemd.c new file mode 100644 index 0000000..25165a3 --- /dev/null +++ b/src/util/virsystemd.c @@ -0,0 +1,139 @@ +/* + * virsystemd.c: helpers for using systemd APIs + * + * 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/>. + * + */ + +#include <config.h> + +#include "virsystemd.h" +#include "virdbus.h" +#include "virstring.h" +#include "viralloc.h" +#include "virutil.h" + +#define VIR_FROM_THIS VIR_FROM_SYSTEMD + +/** + * virSystemdCreateMachine: + * @name: driver unique name of the machine + * @drivername: name of the virt driver + * @privileged: whether driver is running privileged or per user + * @uuid: globally unique UUID of the machine + * @rootdir: root directory of machine filesystem + * @pidleader: PID of the leader process + * @slice: name of the slice to place the machine in + */ +int virSystemdCreateMachine(const char *name, + const char *drivername, + bool privileged, + const unsigned char *uuid, + const char *rootdir, + pid_t pidleader, + bool iscontainer, + const char *slice) +{ + int ret = -1; + DBusConnection *conn; + char *machinename = NULL; + char *creatorname = NULL; + char *username = NULL; + + if (!(conn = virDBusGetSystemBus())) + return -1; + + if (privileged) { + if (virAsprintf(&machinename, "%s-%s", drivername, name) < 0) + goto cleanup; + } else { + if (!(username = virGetUserName(geteuid()))) + goto cleanup; + if (virAsprintf(&machinename, "%s-%s-%s", username, drivername, name) < 0) + goto cleanup; + } + + if (virAsprintf(&creatorname, "libvirt-%s", drivername) < 0) + goto cleanup; + + /* + * The systemd DBus API we're invoking has the + * following signature + * + * CreateMachine(in s name, + * in ay id, + * in s service, + * in s class, + * in u leader, + * in s root_directory, + * in a(sv) scope_properties, + * out o path); + * + * @name a host unique name for the machine. shows up + * in 'ps' listing & similar + * + * @id: a UUID of the machine, ideally matching /etc/machine-id + * for containers + * + * @service: identifier of the client ie "libvirt-lxc" + * + * @class: either the string "container" or "vm" depending + * on the type of machine + * + * @leader: main PID of the machine, either the host emulator + * process, or the 'init' PID of the container + * + * @root_directory: the root directory of the container, if + * this is known & visible in the host filesystem, or empty string + * + * @scope_properties:an array (not a dict!) of properties that are + * passed on to PID 1 when creating a scope unit for your machine. + * Will allow initial settings for the cgroup & similar. + * + * @path: a bus path returned for the machine object created, to + * allow further API calls to be made against the object. + */ + + if (virDBusCallMethod(conn, + NULL, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CreateMachine", + "sayssusa(sv)", + machinename, + 16, + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15], + creatorname, + iscontainer ? "container" : "vm", + (unsigned int)pidleader, + rootdir ? rootdir : "", + 1, "Slice","s", + slice ? slice : "") < 0) + goto cleanup; + + ret = 0; + +cleanup: + VIR_FREE(username); + VIR_FREE(creatorname); + VIR_FREE(machinename); + return ret; +} diff --git a/src/util/virsystemd.h b/src/util/virsystemd.h new file mode 100644 index 0000000..5bee3db --- /dev/null +++ b/src/util/virsystemd.h @@ -0,0 +1,36 @@ +/* + * virsystemd.h: helpers for using systemd APIs + * + * 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/>. + * + */ + +#ifndef __VIR_SYSTEMD_H__ +# define __VIR_SYSTEMD_H__ + +# include "internal.h" + +int virSystemdCreateMachine(const char *name, + const char *drivername, + bool privileged, + const unsigned char *uuid, + const char *rootdir, + pid_t pidleader, + bool iscontainer, + const char *slice); + +#endif /* __VIR_SYSTEMD_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 1748ed1..be6347a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -129,10 +129,10 @@ test_programs = virshtest sockettest \ $(NULL) if WITH_DBUS -test_programs += virdbustest +test_programs += virdbustest \ + virsystemdtest endif - if WITH_GNUTLS test_programs += virnettlscontexttest endif @@ -281,6 +281,10 @@ if WITH_QEMU test_libraries += libqemumonitortestutils.la endif +if WITH_DBUS +test_libraries += virsystemdmock.la +endif + if WITH_TESTS noinst_PROGRAMS = $(test_programs) $(test_helpers) noinst_LTLIBRARIES = $(test_libraries) @@ -647,8 +651,20 @@ virdbustest_SOURCES = \ virdbustest.c testutils.h testutils.c virdbustest_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) virdbustest_LDADD = $(LDADDS) + +virsystemdtest_SOURCES = \ + virsystemdtest.c testutils.h testutils.c +virsystemdtest_CFLAGS = $(AM_CFLAGS) +virsystemdtest_LDADD = $(LDADDS) + +virsystemdmock_la_SOURCES = \ + virsystemdmock.c +virsystemdmock_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) +virsystemdmock_la_LDFLAGS = -module -avoid-version \ + -rpath /evil/libtool/hack/to/force/shared/lib/creation + else -EXTRA_DIST += virdbustest.c +EXTRA_DIST += virdbustest.c virsystemdtest.c virsystemdmock.c endif viruritest_SOURCES = \ diff --git a/tests/testutils.h b/tests/testutils.h index 3647487..bf5c701 100644 --- a/tests/testutils.h +++ b/tests/testutils.h @@ -25,6 +25,8 @@ # include <stdio.h> # include "viralloc.h" +# include "virfile.h" +# include "virstring.h" # define EXIT_AM_SKIP 77 /* tell Automake we're skipping a test */ # define EXIT_AM_HARDFAIL 99 /* tell Automake that the framework is broken */ diff --git a/tests/virsystemdmock.c b/tests/virsystemdmock.c new file mode 100644 index 0000000..5f9cce6 --- /dev/null +++ b/tests/virsystemdmock.c @@ -0,0 +1,77 @@ +/* + * 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@redhat.com> + */ + +#include <config.h> + +#include "internal.h" + +#include <stdlib.h> + +#include <dbus/dbus.h> + +void dbus_connection_set_change_sigpipe(dbus_bool_t will_modify_sigpipe ATTRIBUTE_UNUSED) +{ +} + +dbus_bool_t dbus_threads_init_default(void) +{ + return 1; +} + +DBusConnection *dbus_bus_get(DBusBusType type ATTRIBUTE_UNUSED, + DBusError *error ATTRIBUTE_UNUSED) +{ + return (DBusConnection *)0x1; +} + +void dbus_connection_set_exit_on_disconnect(DBusConnection *connection ATTRIBUTE_UNUSED, + dbus_bool_t exit_on_disconnect ATTRIBUTE_UNUSED) +{ +} + + +dbus_bool_t dbus_connection_set_watch_functions(DBusConnection *connection ATTRIBUTE_UNUSED, + DBusAddWatchFunction add_function ATTRIBUTE_UNUSED, + DBusRemoveWatchFunction remove_function ATTRIBUTE_UNUSED, + DBusWatchToggledFunction toggled_function ATTRIBUTE_UNUSED, + void *data ATTRIBUTE_UNUSED, + DBusFreeFunction free_data_function ATTRIBUTE_UNUSED) +{ + return 1; +} + +DBusMessage *dbus_connection_send_with_reply_and_block(DBusConnection *connection ATTRIBUTE_UNUSED, + DBusMessage *message, + int timeout_milliseconds ATTRIBUTE_UNUSED, + DBusError *error ATTRIBUTE_UNUSED) +{ + DBusMessage *reply; + + dbus_message_set_serial(message, 7); + + if (getenv("FAIL_NO_SERVICE")) + reply = dbus_message_new_error(message, + "org.freedesktop.DBus.Error.ServiceUnknown", + "The name org.freedesktop.machine1 was not provided by any .service files"); + else + reply = dbus_message_new_method_return(message); + + return reply; +} diff --git a/tests/virsystemdtest.c b/tests/virsystemdtest.c new file mode 100644 index 0000000..3992722 --- /dev/null +++ b/tests/virsystemdtest.c @@ -0,0 +1,131 @@ +/* + * 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@redhat.com> + */ + +#include <config.h> + +#include <stdlib.h> + +#include "virsystemd.h" +#include "virlog.h" +#include "testutils.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +static int testCreateContainer(const void *opaque ATTRIBUTE_UNUSED) +{ + unsigned char uuid[VIR_UUID_BUFLEN] = { + 1, 1, 1, 1, + 2, 2, 2, 2, + 3, 3, 3, 3, + 4, 4, 4, 4 + }; + if (virSystemdCreateMachine("demo", + "lxc", + true, + uuid, + "/proc/123/root", + 123, + true, + "highpriority.slice") < 0) { + fprintf(stderr, "%s", "Failed to create LXC machine\n"); + return -1; + } + + return 0; +} + +static int testCreateMachine(const void *opaque ATTRIBUTE_UNUSED) +{ + unsigned char uuid[VIR_UUID_BUFLEN] = { + 1, 1, 1, 1, + 2, 2, 2, 2, + 3, 3, 3, 3, + 4, 4, 4, 4 + }; + if (virSystemdCreateMachine("demo", + "qemu", + false, + uuid, + NULL, + 123, + false, + NULL) < 0) { + fprintf(stderr, "%s", "Failed to create KVM machine\n"); + return -1; + } + + return 0; +} + +static int testCreateNoSystemd(const void *opaque ATTRIBUTE_UNUSED) +{ + unsigned char uuid[VIR_UUID_BUFLEN] = { + 1, 1, 1, 1, + 2, 2, 2, 2, + 3, 3, 3, 3, + 4, 4, 4, 4 + }; + + setenv("FAIL_NO_SERVICE", "1", 1); + + if (virSystemdCreateMachine("demo", + "qemu", + true, + uuid, + NULL, + 123, + false, + NULL) == 0) { + fprintf(stderr, "%s", "Unexpected create machine success\n"); + return -1; + } + + virErrorPtr err = virGetLastError(); + + if (!err) { + fprintf(stderr, "No error raised"); + return -1; + } + + if (err->code == VIR_ERR_DBUS_SERVICE && + STREQ(err->str2, "org.freedesktop.DBus.Error.ServiceUnknown")) + return 0; + + fprintf(stderr, "Unexpected error code %d / message %s\n", + err->code, err->str2); + return -1; +} + +static int +mymain(void) +{ + int ret = 0; + + if (virtTestRun("Test create container ", 1, testCreateContainer, NULL) < 0) + ret = -1; + if (virtTestRun("Test create machine ", 1, testCreateMachine, NULL) < 0) + ret = -1; + if (virtTestRun("Test create nosystemd ", 1, testCreateNoSystemd, NULL) < 0) + ret = -1; + + return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/virsystemdmock.so") -- 1.8.1.4

On 07/18/2013 07:27 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
To register virtual machines and containers with systemd-machined, and thus have cgroups auto-created, we need to talk over DBus. This is somewhat tedious code, so introduce a dedicated function to isolate the DBus call in one place.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> ---
Fails 'make syntax-check': GEN bracket-spacing-check tests/virsystemdmock.c:74: reply = dbus_message_new_method_return(message); maint.mk: incorrect whitespace, see HACKING for rules but that's a false positive against 'return(foo)' - looks like I'll have to tweak that.
+++ b/src/util/virerror.h @@ -145,6 +145,17 @@ void virReportSystemErrorFull(int domcode, 0, 0, \ (fmt), __VA_ARGS__)
+# define virReportDBusServiceError(message, name) \
Ah, this hunk probably belongs in 1/2. In fact, it fixes compilation, so I was able to run 'make check', but the test from 1/2 fails: TEST: virdbustest Failed to round-trip byte '0' to '200' !..Failed to round-trip byte '0' to '200' !. 5 FAIL
+ /* + * The systemd DBus API we're invoking has the + * following signature + * + * CreateMachine(in s name, + * in ay id, + * in s service, + * in s class, + * in u leader, + * in s root_directory, + * in a(sv) scope_properties, + * out o path);
+ + if (virDBusCallMethod(conn, + NULL, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CreateMachine", + "sayssusa(sv)", + machinename, + 16, + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15], + creatorname, + iscontainer ? "container" : "vm", + (unsigned int)pidleader, + rootdir ? rootdir : "", + 1, "Slice","s",
Space after ','
+ slice ? slice : "") < 0)
Looks like the correct call translation. ACK if you can fix the problems. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Thu, Jul 18, 2013 at 05:11:45PM -0600, Eric Blake wrote:
Ah, this hunk probably belongs in 1/2. In fact, it fixes compilation, so I was able to run 'make check', but the test from 1/2 fails:
TEST: virdbustest Failed to round-trip byte '0' to '200' !..Failed to round-trip byte '0' to '200' !. 5 FAIL
This turned out to be caused by the use of the 'bool' type. This is not the same size as dbus_bool_t which is defined to be same as an int. So I changed the test to use a plain int Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
participants (2)
-
Daniel P. Berrange
-
Eric Blake