/*
 * virdbus.c: helper for using DBus
 *
 * Copyright (C) 2012-2014 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 "virdbuspriv.h"
#include "viralloc.h"
#include "virerror.h"
#include "virlog.h"
#include "virthread.h"
#include "virstring.h"
#include "virprobe.h"

#define VIR_FROM_THIS VIR_FROM_DBUS

VIR_LOG_INIT("util.dbus");

#ifdef WITH_DBUS

static bool sharedBus = true;
static DBusConnection *systembus;
static DBusConnection *sessionbus;
static virOnceControl systemonce = VIR_ONCE_CONTROL_INITIALIZER;
static virOnceControl sessiononce = VIR_ONCE_CONTROL_INITIALIZER;
static DBusError systemdbuserr;
static DBusError sessiondbuserr;

static dbus_bool_t virDBusAddWatch(DBusWatch *watch, void *data);
static void virDBusRemoveWatch(DBusWatch *watch, void *data);
static void virDBusToggleWatch(DBusWatch *watch, void *data);

void virDBusSetSharedBus(bool shared)
{
    sharedBus = shared;
}

static DBusConnection *virDBusBusInit(DBusBusType type, DBusError *dbuserr)
{
    DBusConnection *bus;

    /* Allocate and initialize a new HAL context */
    dbus_connection_set_change_sigpipe(FALSE);
    dbus_threads_init_default();

    dbus_error_init(dbuserr);
    bus = sharedBus ?
        dbus_bus_get(type, dbuserr) :
        dbus_bus_get_private(type, dbuserr);
    if (!bus)
        return NULL;

    dbus_connection_set_exit_on_disconnect(bus, FALSE);

    /* Register dbus watch callbacks */
    if (!dbus_connection_set_watch_functions(bus,
                                             virDBusAddWatch,
                                             virDBusRemoveWatch,
                                             virDBusToggleWatch,
                                             bus, NULL)) {
        return NULL;
    }
    return bus;
}

static void virDBusSystemBusInit(void)
{
    systembus = virDBusBusInit(DBUS_BUS_SYSTEM, &systemdbuserr);
}

static DBusConnection *
virDBusGetSystemBusInternal(void)
{
    if (virOnce(&systemonce, virDBusSystemBusInit) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Unable to run one time DBus initializer"));
        return NULL;
    }

    return systembus;
}


DBusConnection *
virDBusGetSystemBus(void)
{
    DBusConnection *bus;

    if (!(bus = virDBusGetSystemBusInternal())) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unable to get DBus system bus connection: %s"),
                       systemdbuserr.message ? systemdbuserr.message : "watch setup failed");
        return NULL;
    }

    return bus;
}


/**
 * virDBusHasSystemBus:
 *
 * Check if dbus system bus is running. This does not
 * imply that we have a connection. DBus might be running
 * and refusing connections due to its client limit. The
 * latter must be treated as a fatal error.
 *
 * Return false if dbus is not available, true if probably available.
 */
bool
virDBusHasSystemBus(void)
{
    if (virDBusGetSystemBusInternal())
        return true;

    if (systemdbuserr.name &&
        (STREQ(systemdbuserr.name, "org.freedesktop.DBus.Error.FileNotFound") ||
         STREQ(systemdbuserr.name, "org.freedesktop.DBus.Error.NoServer"))) {
        VIR_DEBUG("System DBus not available: %s", NULLSTR(systemdbuserr.message));
        return false;
    }
    return true;
}


void virDBusCloseSystemBus(void)
{
    if (systembus && !sharedBus) {
        dbus_connection_close(systembus);
        systembus = NULL;
    }
}

static void virDBusSessionBusInit(void)
{
    sessionbus = virDBusBusInit(DBUS_BUS_SESSION, &sessiondbuserr);
}

DBusConnection *virDBusGetSessionBus(void)
{
    if (virOnce(&sessiononce, virDBusSessionBusInit) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Unable to run one time DBus initializer"));
        return NULL;
    }

    if (!sessionbus) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unable to get DBus session bus connection: %s"),
                       sessiondbuserr.message ? sessiondbuserr.message : "watch setup failed");
        return NULL;
    }

    return sessionbus;
}

struct virDBusWatch
{
    int watch;
    DBusConnection *bus;
};

static void virDBusWatchCallback(int fdatch ATTRIBUTE_UNUSED,
                                 int fd ATTRIBUTE_UNUSED,
                                 int events, void *opaque)
{
    DBusWatch *watch = opaque;
    struct virDBusWatch *info;
    int dbus_flags = 0;

    info = dbus_watch_get_data(watch);

    if (events & VIR_EVENT_HANDLE_READABLE)
        dbus_flags |= DBUS_WATCH_READABLE;
    if (events & VIR_EVENT_HANDLE_WRITABLE)
        dbus_flags |= DBUS_WATCH_WRITABLE;
    if (events & VIR_EVENT_HANDLE_ERROR)
        dbus_flags |= DBUS_WATCH_ERROR;
    if (events & VIR_EVENT_HANDLE_HANGUP)
        dbus_flags |= DBUS_WATCH_HANGUP;

    (void)dbus_watch_handle(watch, dbus_flags);

    while (dbus_connection_dispatch(info->bus) == DBUS_DISPATCH_DATA_REMAINS)
        /* keep dispatching while data remains */;
}


static int virDBusTranslateWatchFlags(int dbus_flags)
{
    unsigned int flags = 0;
    if (dbus_flags & DBUS_WATCH_READABLE)
        flags |= VIR_EVENT_HANDLE_READABLE;
    if (dbus_flags & DBUS_WATCH_WRITABLE)
        flags |= VIR_EVENT_HANDLE_WRITABLE;
    if (dbus_flags & DBUS_WATCH_ERROR)
        flags |= VIR_EVENT_HANDLE_ERROR;
    if (dbus_flags & DBUS_WATCH_HANGUP)
        flags |= VIR_EVENT_HANDLE_HANGUP;
    return flags;
}


static void virDBusWatchFree(void *data)
{
    struct virDBusWatch *info = data;
    VIR_FREE(info);
}

static dbus_bool_t virDBusAddWatch(DBusWatch *watch,
                                   void *data)
{
    int flags = 0;
    int fd;
    struct virDBusWatch *info;

    if (VIR_ALLOC(info) < 0)
        return 0;

    if (dbus_watch_get_enabled(watch))
        flags = virDBusTranslateWatchFlags(dbus_watch_get_flags(watch));

# if HAVE_DBUS_WATCH_GET_UNIX_FD
    fd = dbus_watch_get_unix_fd(watch);
# else
    fd = dbus_watch_get_fd(watch);
# endif
    dbus_watch_set_data(watch, info, virDBusWatchFree);
    info->bus = (DBusConnection *)data;
    info->watch = virEventAddHandle(fd, flags,
                                    virDBusWatchCallback,
                                    watch, NULL);
    if (info->watch < 0) {
        dbus_watch_set_data(watch, NULL, NULL);
        return 0;
    }

    return 1;
}


static void virDBusRemoveWatch(DBusWatch *watch,
                               void *data ATTRIBUTE_UNUSED)
{
    struct virDBusWatch *info;

    info = dbus_watch_get_data(watch);

    (void)virEventRemoveHandle(info->watch);
}


static void virDBusToggleWatch(DBusWatch *watch,
                               void *data ATTRIBUTE_UNUSED)
{
    int flags = 0;
    struct virDBusWatch *info;

    if (dbus_watch_get_enabled(watch))
        flags = virDBusTranslateWatchFlags(dbus_watch_get_flags(watch));

    info = dbus_watch_get_data(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,
};

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 *skiplen,
                               size_t *siglen)
{
    if (virDBusIsBasicType(*s) || *s == DBUS_TYPE_VARIANT) {
        *skiplen = *siglen = 1;
        return 0;
    }

    if (*s == DBUS_TYPE_ARRAY) {
        size_t skiplencont;
        size_t siglencont;
        bool arrayref = false;

        if (arrayDepth >= VIR_DBUS_TYPE_STACK_MAX_DEPTH) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Signature '%s' too deeply nested"),
                           s);
            return -1;
        }

        if (*(s + 1) == '&') {
            arrayref = true;
            s++;
        }

        if (virDBusSignatureLengthInternal(s + 1,
                                           true,
                                           arrayDepth + 1,
                                           structDepth,
                                           &skiplencont,
                                           &siglencont) < 0)
            return -1;

        *skiplen = skiplencont + 1;
        *siglen = siglencont + 1;
        if (arrayref)
            (*skiplen)++;
        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;
        }

        *skiplen = *siglen = 2;

        while (*p != DBUS_STRUCT_END_CHAR) {
            size_t skiplencont;
            size_t siglencont;

            if (virDBusSignatureLengthInternal(p,
                                               false,
                                               arrayDepth,
                                               structDepth + 1,
                                               &skiplencont,
                                               &siglencont) < 0)
                return -1;

            p += skiplencont;
            *skiplen += skiplencont;
            *siglen += siglencont;
        }

        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;
        }

        *skiplen = *siglen = 2;

        while (*p != DBUS_DICT_ENTRY_END_CHAR) {
            size_t skiplencont;
            size_t siglencont;

            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,
                                               &skiplencont,
                                               &siglencont) < 0)
                return -1;

            p += skiplencont;
            *skiplen += skiplencont;
            *siglen += siglencont;
            n++;
        }

        if (n != 2) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Dict entry in signature '%s' is wrong size"),
                           s);
            return -1;
        }

        return 0;
    }

    virReportError(VIR_ERR_INTERNAL_ERROR,
                   _("Unexpected signature '%s'"), s);
    return -1;
}


static int virDBusSignatureLength(const char *s, size_t *skiplen, size_t *siglen)
{
    return virDBusSignatureLengthInternal(s, true, 0, 0, skiplen, siglen);
}


static char *virDBusCopyContainerSignature(const char *sig,
                                           size_t *skiplen,
                                           size_t *siglen)
{
    size_t i, j;
    char *contsig;
    bool isGroup;

    isGroup = (sig[0] == DBUS_STRUCT_BEGIN_CHAR ||
               sig[0] == DBUS_DICT_ENTRY_BEGIN_CHAR);

    if (virDBusSignatureLength(isGroup ? sig : sig + 1, skiplen, siglen) < 0)
        return NULL;

    if (VIR_ALLOC_N(contsig, *siglen + 1) < 0)
        return NULL;

    for (i = 0, j = 0; i < *skiplen && j < *siglen; i++) {
        if (sig[i + 1] == '&')
            continue;
        contsig[j] = sig[i + 1];
        j++;
    }
    contsig[*siglen] = '\0';
    VIR_DEBUG("Extracted '%s' from '%s'", contsig, sig);
    return contsig;
}


/* 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 types='%s' nstruct=%zu narray=%zd",
              types, nstruct, (ssize_t)narray);
    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 types='%s' nstruct=%zu narray=%zd",
              *types, *nstruct, (ssize_t)*narray);
    VIR_SHRINK_N(*stack, *nstack, 1);

    return 0;
}


static void virDBusTypeStackFree(virDBusTypeStack **stack,
                                 size_t *nstack)
{
    size_t i;

    if (!*stack)
        return;

    /* 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);
}


static bool
virDBusIsAllowedRefType(const char *sig)
{
    if (*sig == '{') {
        if (strlen(sig) != 4)
            return false;
        if (!virDBusIsBasicType(sig[1]) ||
            !virDBusIsBasicType(sig[2]) ||
            sig[1] != sig[2])
            return false;
        if (sig[3] != '}')
            return false;
    } else {
        if (strlen(sig) != 1)
            return false;
        if (!virDBusIsBasicType(sig[0]))
            return false;
    }
    return true;
}


# define SET_NEXT_VAL(dbustype, vargtype, arrtype, sigtype, fmt)        \
    do {                                                                \
        dbustype x;                                                     \
        if (arrayref) {                                                 \
            arrtype valarray = arrayptr;                                \
            x = (dbustype)*valarray;                                    \
            valarray++;                                                 \
            arrayptr = valarray;                                        \
        } else {                                                        \
            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;
    bool arrayref = false;
    void *arrayptr = NULL;
    virDBusTypeStack *stack = NULL;
    size_t nstack = 0;
    size_t siglen;
    size_t skiplen;
    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 nstack=%zu narray=%zd nstruct=%zu types='%s'",
                  nstack, (ssize_t)narray, nstruct, types);
        if (narray == 0 ||
            (narray == (size_t)-1 &&
             nstruct == 0)) {
            DBusMessageIter *thisiter = iter;
            if (*types != '}') {
                VIR_DEBUG("Reset array ref");
                arrayref = false;
                arrayptr = NULL;
            }
            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, unsigned char *, *t, "%d");
            break;

        case DBUS_TYPE_BOOLEAN:
            SET_NEXT_VAL(dbus_bool_t, int, bool *, *t, "%d");
            break;

        case DBUS_TYPE_INT16:
            SET_NEXT_VAL(dbus_int16_t, int, short *, *t, "%d");
            break;

        case DBUS_TYPE_UINT16:
            SET_NEXT_VAL(dbus_uint16_t, unsigned int, unsigned short *,
                         *t, "%d");
            break;

        case DBUS_TYPE_INT32:
            SET_NEXT_VAL(dbus_int32_t, int, int *, *t, "%d");
            break;

        case DBUS_TYPE_UINT32:
            SET_NEXT_VAL(dbus_uint32_t, unsigned int, unsigned int *,
                         *t, "%u");
            break;

        case DBUS_TYPE_INT64:
            SET_NEXT_VAL(dbus_int64_t, long long, long long *, *t, "%lld");
            break;

        case DBUS_TYPE_UINT64:
            SET_NEXT_VAL(dbus_uint64_t, unsigned long long,
                         unsigned long long *, *t, "%llu");
            break;

        case DBUS_TYPE_DOUBLE:
            SET_NEXT_VAL(double, double, double *, *t, "%lf");
            break;

        case DBUS_TYPE_STRING:
        case DBUS_TYPE_OBJECT_PATH:
        case DBUS_TYPE_SIGNATURE:
            SET_NEXT_VAL(char *, char *, char **, *t, "%s");
            break;

        case DBUS_TYPE_ARRAY:
            arrayptr = NULL;
            if (t[1] == '&') {
                VIR_DEBUG("Got array ref");
                t++;
                types++;
                nstruct--;
                arrayref = true;
            } else {
                VIR_DEBUG("Got array non-ref");
                arrayref = false;
            }

            if (!(contsig = virDBusCopyContainerSignature(t, &skiplen, &siglen)))
                goto cleanup;

            if (arrayref && !virDBusIsAllowedRefType(contsig)) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("Got array ref but '%s' is not a single basic type "
                                 "or dict with matching key+value type"),
                               contsig);
                goto cleanup;
            }

            if (narray == (size_t)-1) {
                types += skiplen;
                nstruct -= skiplen;
            }

            if (VIR_ALLOC(newiter) < 0)
                goto cleanup;
            VIR_DEBUG("Contsig '%s' skip='%zu' len='%zu'", contsig, skiplen, siglen);
            if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
                                                  contsig, newiter))
                goto cleanup;
            if (virDBusTypeStackPush(&stack, &nstack,
                                     iter, types,
                                     nstruct, narray) < 0) {
                VIR_FREE(newiter);
                goto cleanup;
            }
            VIR_FREE(contsig);
            iter = newiter;
            newiter = NULL;
            types = t + 1;
            nstruct = skiplen;
            narray = (size_t)va_arg(args, int);
            if (arrayref)
                arrayptr = va_arg(args, void *);
            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) {
                VIR_FREE(newiter);
                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 (!(contsig = virDBusCopyContainerSignature(t, &skiplen, &siglen)))
                goto cleanup;

            if (VIR_ALLOC(newiter) < 0)
                goto cleanup;
            VIR_DEBUG("Contsig '%s' skip='%zu' len='%zu'", contsig, skiplen, 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 += skiplen - 1;
                nstruct -= skiplen - 1;
            }

            if (virDBusTypeStackPush(&stack, &nstack,
                                     iter, types,
                                     nstruct, narray) < 0) {
                VIR_FREE(newiter);
                goto cleanup;
            }
            VIR_FREE(contsig);
            iter = newiter;
            newiter = NULL;
            types = t + 1;
            nstruct = skiplen - 2;
            narray = (size_t)-1;

            break;

        default:
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Unknown type '%x' in signature '%s'"),
                           (int)*t, types);
            goto cleanup;
        }
    }

    ret = 0;

 cleanup:
    while (nstack > 0) {
        DBusMessageIter *thisiter = iter;
        VIR_DEBUG("Popping iter=%p", iter);
        ignore_value(virDBusTypeStackPop(&stack, &nstack, &iter,
                                         &types, &nstruct, &narray));
        VIR_DEBUG("Popped iter=%p", iter);

        if (thisiter != rootiter)
            VIR_FREE(thisiter);
    }

    virDBusTypeStackFree(&stack, &nstack);
    VIR_FREE(contsig);
    VIR_FREE(newiter);
    return ret;
}
# undef SET_NEXT_VAL


# define GET_NEXT_VAL(dbustype, member, vargtype, fmt)                  \
    do {                                                                \
        DBusBasicValue v;                                               \
        dbustype *x = (dbustype *)&v.member;                            \
        vargtype *y;                                                    \
        if (arrayref) {                                                 \
            VIR_DEBUG("Use arrayref");                                  \
            vargtype **xptrptr = arrayptr;                              \
            if (VIR_EXPAND_N(*xptrptr, *narrayptr, 1) < 0)              \
                goto cleanup;                                           \
            y = (*xptrptr + (*narrayptr - 1));                          \
            VIR_DEBUG("Expanded to %zu", *narrayptr);                   \
        } else {                                                        \
            y = va_arg(args, vargtype *);                               \
        }                                                               \
        dbus_message_iter_get_basic(iter, x);                           \
        *y = *x;                                                        \
        VIR_DEBUG("Read basic type '" #dbustype "' varg '" #vargtype    \
                  "' val '" fmt "'", (vargtype)*y);                     \
    } while (0)


static int
virDBusMessageIterDecode(DBusMessageIter *rootiter,
                         const char *types,
                         va_list args)
{
    int ret = -1;
    size_t narray;
    size_t nstruct;
    bool arrayref = false;
    void *arrayptr = NULL;
    size_t *narrayptr = 0;
    virDBusTypeStack *stack = NULL;
    size_t nstack = 0;
    size_t skiplen;
    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 nstack=%zu narray=%zd nstruct=%zu type='%s'",
                  nstack, (ssize_t)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 (strchr(types, '}') == NULL) {
                arrayref = false;
                arrayptr = NULL;
                VIR_DEBUG("Clear array ref flag");
            }
            if (thisiter != rootiter)
                VIR_FREE(thisiter);
            if (arrayref) {
                if (!dbus_message_iter_has_next(iter))
                    narray = 0;
                else
                    narray = 1;
                VIR_DEBUG("Pop set narray=%zd", (ssize_t)narray);
            }
            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) {
            if (!arrayref)
                narray--;
        } else {
            types++;
            nstruct--;
        }

        switch (*t) {
        case DBUS_TYPE_BYTE:
            GET_NEXT_VAL(unsigned char, byt, unsigned char, "%d");
            break;

        case DBUS_TYPE_BOOLEAN:
            GET_NEXT_VAL(dbus_bool_t, bool_val, bool, "%d");
            break;

        case DBUS_TYPE_INT16:
            GET_NEXT_VAL(dbus_int16_t, i16, short, "%d");
            break;

        case DBUS_TYPE_UINT16:
            GET_NEXT_VAL(dbus_uint16_t, u16, unsigned short, "%d");
            break;

        case DBUS_TYPE_INT32:
            GET_NEXT_VAL(dbus_uint32_t, i32, int, "%d");
            break;

        case DBUS_TYPE_UINT32:
            GET_NEXT_VAL(dbus_uint32_t, u32, unsigned int, "%u");
            break;

        case DBUS_TYPE_INT64:
            GET_NEXT_VAL(dbus_uint64_t, i64, long long, "%lld");
            break;

        case DBUS_TYPE_UINT64:
            GET_NEXT_VAL(dbus_uint64_t, u64, unsigned long long, "%llu");
            break;

        case DBUS_TYPE_DOUBLE:
            GET_NEXT_VAL(double, dbl, double, "%lf");
            break;

        case DBUS_TYPE_STRING:
        case DBUS_TYPE_OBJECT_PATH:
        case DBUS_TYPE_SIGNATURE:
            do {
                char **x;
                if (arrayref) {
                    char ***xptrptr = arrayptr;
                    if (VIR_EXPAND_N(*xptrptr, *narrayptr, 1) < 0)
                        goto cleanup;
                    x = (char **)(*xptrptr + (*narrayptr - 1));
                    VIR_DEBUG("Expanded to %zu", *narrayptr);
                } else {
                    x = (char **)va_arg(args, char **);
                }
                char *s;
                dbus_message_iter_get_basic(iter, &s);
                if (VIR_STRDUP(*x, s) < 0)
                    goto cleanup;
                VIR_DEBUG("Read basic type 'char *' varg 'char **'"
                          "' val '%s'", *x);
            } while (0);
            break;

        case DBUS_TYPE_ARRAY:
            arrayptr = NULL;
            if (t[1] == '&') {
                VIR_DEBUG("Got array ref");
                t++;
                types++;
                nstruct--;
                arrayref = true;
            } else {
                VIR_DEBUG("Got array non-ref");
                arrayref = false;
            }

            advanceiter = false;
            if (!(contsig = virDBusCopyContainerSignature(t, &skiplen, &siglen)))
                goto cleanup;

            if (arrayref && !virDBusIsAllowedRefType(contsig)) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("Got array ref but '%s' is not a single basic type / dict"),
                               contsig);
                goto cleanup;
            }

            if (narray == (size_t)-1) {
                types += skiplen;
                nstruct -= skiplen;
            }

            if (VIR_ALLOC(newiter) < 0)
                goto cleanup;
            VIR_DEBUG("Array contsig='%s' skip=%'zu' len='%zu' types='%s'",
                      contsig, skiplen, 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 = skiplen;
            if (arrayref) {
                narrayptr = va_arg(args, size_t *);
                arrayptr = va_arg(args, void *);
                *narrayptr = 0;
                *(char **)arrayptr = NULL;
            } else {
                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 (!(contsig = virDBusCopyContainerSignature(t, &skiplen, &siglen)))
                goto cleanup;

            if (VIR_ALLOC(newiter) < 0)
                goto cleanup;
            VIR_DEBUG("Dict/struct contsig='%s' skip='%zu' len='%zu' types='%s'",
                      contsig, skiplen, siglen, types);
            dbus_message_iter_recurse(iter, newiter);
            if (narray == (size_t)-1) {
                types += skiplen - 1;
                nstruct -= skiplen - 1;
            }

            if (virDBusTypeStackPush(&stack, &nstack,
                                     iter, types,
                                     nstruct, narray) < 0)
                goto cleanup;
            VIR_FREE(contsig);
            iter = newiter;
            newiter = NULL;
            types = t + 1;
            nstruct = skiplen - 2;
            narray = (size_t)-1;

            break;

        default:
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Unknown type '%c' in signature '%s'"),
                           *t, types);
            goto cleanup;
        }

        VIR_DEBUG("After nstack=%zu narray=%zd nstruct=%zu types='%s'",
                  nstack, (ssize_t)narray, nstruct, types);

        if (arrayref) {
            if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_INVALID) {
                narray = 0;
            } else {
                if (advanceiter)
                    dbus_message_iter_next(iter);
                if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_INVALID) {
                    narray = 0;
                } else {
                    narray = 1;
                }
            }
            VIR_DEBUG("Set narray=%zd", (ssize_t)narray);
        } else {
            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

/**
 * virDBusCreateMethodV:
 * @call: pointer to be filled with a method call message
 * @destination: bus identifier of the target service
 * @path: object path of the target service
 * @iface: the interface of the object
 * @member: the name of the method in the interface
 * @types: type signature for following method arguments
 * @args: method arguments
 *
 * This creates a DBus method call message and saves a
 * pointer to it in @call. The @destination, @path, @iface
 * 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 method
 * can be later invoked using virDBusCall.
 *
 * 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)
 *
 * At this time, there is no support for Unix fd's ('h'), which only
 * newer DBus supports.
 *
 * 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 additional variadic args,
 *   one for each element of the array.
 *
 * - Array reference: when 'a' appears in a type signature,
 *   followed by '&', this signifies an array passed by
 *   reference.
 *
 *   Array references may only be used when the
 *   element values are basic types, or a dict
 *   entry where both keys and values are using
 *   the same basic type.
 *
 *   The first variadic arg for an array, is an 'int'
 *   specifying the number of elements in the array.
 *   When the element is a basic type, the second
 *   variadic arg is a pointer to an array containing
 *   the element values. When the element is a dict
 *   entry, the second variadic arg is a pointer to
 *   an array containing the dict keys, and the
 *   third variadic arg is a pointer to an array
 *   containing the dict values.
 *
 * - 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 virDBusCreateMethodV(DBusMessage **call,
                         const char *destination,
                         const char *path,
                         const char *iface,
                         const char *member,
                         const char *types,
                         va_list args)
{
    int ret = -1;

    if (!(*call = dbus_message_new_method_call(destination,
                                               path,
                                               iface,
                                               member))) {
        virReportOOMError();
        goto cleanup;
    }

    if (virDBusMessageEncodeArgs(*call, types, args) < 0) {
        virDBusMessageUnref(*call);
        *call = NULL;
        goto cleanup;
    }

    ret = 0;
 cleanup:
    return ret;
}


/**
 * virDBusCreateMethod:
 * @call: pointer to be filled with a method call message
 * @destination: bus identifier of the target service
 * @path: object path of the target service
 * @iface: the interface of the object
 * @member: the name of the method in the interface
 * @types: type signature for following method arguments
 * @...: method arguments
 *
 * See virDBusCreateMethodV for a description of the
 * behaviour of this method.
 */
int virDBusCreateMethod(DBusMessage **call,
                        const char *destination,
                        const char *path,
                        const char *iface,
                        const char *member,
                        const char *types, ...)
{
    va_list args;
    int ret;

    va_start(args, types);
    ret = virDBusCreateMethodV(call, destination, path,
                               iface, member, types, args);
    va_end(args);

    return ret;
}


/**
 * virDBusCreateReplyV:
 * @reply: pointer to be filled with a method reply message
 * @types: type signature for following method arguments
 * @args: method arguments
 *
 * This creates a DBus method reply message and saves a
 * pointer to it in @reply.
 *
 * The @types parameter is a DBus signature describing
 * the method call parameters which will be provided
 * as variadic args. See virDBusCreateMethodV for a
 * description of this parameter.
 */
int virDBusCreateReplyV(DBusMessage **reply,
                        const char *types,
                        va_list args)
{
    int ret = -1;

    if (!(*reply = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN))) {
        virReportOOMError();
        goto cleanup;
    }

    if (virDBusMessageEncodeArgs(*reply, types, args) < 0) {
        virDBusMessageUnref(*reply);
        *reply = NULL;
        goto cleanup;
    }

    ret = 0;
 cleanup:
    return ret;
}


/**
 * virDBusCreateReply:
 * @reply: pointer to be filled with a method reply message
 * @types: type signature for following method arguments
 * @...: method arguments
 *
 * See virDBusCreateReplyV for a description of the
 * behaviour of this method.
 */
int virDBusCreateReply(DBusMessage **reply,
                       const char *types, ...)
{
    va_list args;
    int ret;

    va_start(args, types);
    ret = virDBusCreateReplyV(reply, types, args);
    va_end(args);

    return ret;
}


/**
 * virDBusCall:
 * @conn: a DBus connection
 * @call: pointer to a message to send
 * @replyout: pointer to receive reply message, or NULL
 * @error: pointer to receive error message
 *
 * This invokes a method encoded in @call on a remote
 * service on the DBus bus @conn. 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.
 *
 * If @error is NULL then a libvirt error will be raised
 * when a DBus error is received and the return value will
 * be -1. If @error is non-NULL then any DBus error will
 * be saved into that object and the return value will
 * be 0.
 *
 * Returns 0 on success, or -1 upon error
 */
static int
virDBusCall(DBusConnection *conn,
            DBusMessage *call,
            DBusMessage **replyout,
            virErrorPtr error)

{
    DBusMessage *reply = NULL;
    DBusError localerror;
    int ret = -1;
    const char *iface, *member, *path, *dest;

    dbus_error_init(&localerror);
    if (error)
        memset(error, 0, sizeof(*error));

    iface = dbus_message_get_interface(call);
    member = dbus_message_get_member(call);
    path = dbus_message_get_path(call);
    dest = dbus_message_get_destination(call);

    PROBE(DBUS_METHOD_CALL,
          "'%s.%s' on '%s' at '%s'",
          iface, member, path, dest);

    if (!(reply = dbus_connection_send_with_reply_and_block(conn,
                                                            call,
                                                            VIR_DBUS_METHOD_CALL_TIMEOUT_MILLIS,
                                                            &localerror))) {
        PROBE(DBUS_METHOD_ERROR,
              "'%s.%s' on '%s' at '%s' error %s: %s",
              iface, member, path, dest,
              localerror.name,
              localerror.message);
        if (error) {
            error->level = VIR_ERR_ERROR;
            error->code = VIR_ERR_DBUS_SERVICE;
            error->domain = VIR_FROM_DBUS;
            if (VIR_STRDUP(error->message, localerror.message) < 0)
                goto cleanup;
            if (VIR_STRDUP(error->str1, localerror.name) < 0)
                goto cleanup;
            ret = 0;
        } else {
            virReportError(VIR_ERR_DBUS_SERVICE, _("%s: %s"), member,
                localerror.message ? localerror.message : _("unknown error"));
        }
        goto cleanup;
    }

    PROBE(DBUS_METHOD_REPLY,
          "'%s.%s' on '%s' at '%s'",
          iface, member, path, dest);

    ret = 0;

 cleanup:
    if (ret < 0 && error)
        virResetError(error);
    dbus_error_free(&localerror);
    if (reply) {
        if (ret == 0 && replyout)
            *replyout = reply;
        else
            virDBusMessageUnref(reply);
    }
    return ret;
}


/**
 * 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
 * @iface: 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, @iface
 * 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. See virDBusCreateMethodV for a
 * description of this parameter.
 *
 *
 * If @error is NULL then a libvirt error will be raised
 * when a DBus error is received and the return value will
 * be -1. If @error is non-NULL then any DBus error will
 * be saved into that object and the return value will
 * be 0. If an error occurs while encoding method args
 * the return value will always be -1 regardless of whether
 * @error is set.
 *
 * Returns 0 on success, or -1 upon error
 */
int virDBusCallMethod(DBusConnection *conn,
                      DBusMessage **replyout,
                      virErrorPtr error,
                      const char *destination,
                      const char *path,
                      const char *iface,
                      const char *member,
                      const char *types, ...)
{
    DBusMessage *call = NULL;
    int ret = -1;
    va_list args;

    va_start(args, types);
    ret = virDBusCreateMethodV(&call, destination, path,
                               iface, member, types, args);
    va_end(args);
    if (ret < 0)
        goto cleanup;

    ret = virDBusCall(conn, call, replyout, error);

 cleanup:
    virDBusMessageUnref(call);
    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);

    return ret;
}

static int virDBusIsServiceInList(const char *listMethod, const char *name)
{
    DBusConnection *conn;
    DBusMessage *reply = NULL;
    DBusMessageIter iter, sub;
    int ret = -1;

    if (!virDBusHasSystemBus())
        return -2;

    if (!(conn = virDBusGetSystemBus()))
        return -1;

    if (virDBusCallMethod(conn,
                          &reply,
                          NULL,
                          "org.freedesktop.DBus",
                          "/org/freedesktop/DBus",
                          "org.freedesktop.DBus",
                          listMethod,
                          NULL) < 0)
        return ret;

    if (!dbus_message_iter_init(reply, &iter) ||
        dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Reply message incorrect"));
        goto cleanup;
    }

    ret = -2;
    dbus_message_iter_recurse(&iter, &sub);
    while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
        const char *service = NULL;

        dbus_message_iter_get_basic(&sub, &service);
        dbus_message_iter_next(&sub);

        if (STREQ(service, name)) {
            ret = 0;
            break;
        }
    }

 cleanup:
    virDBusMessageUnref(reply);
    return ret;
}

/**
 * virDBusIsServiceEnabled:
 * @name: service name
 *
 * Returns 0 if service is available, -1 on fatal error, or -2 if service is not available
 */
int virDBusIsServiceEnabled(const char *name)
{
    int ret = virDBusIsServiceInList("ListActivatableNames", name);

    VIR_DEBUG("Service %s is %s", name, ret ? "unavailable" : "available");

    return ret;
}

/**
 * virDBusIsServiceRegistered
 * @name: service name
 *
 * Retruns 0 if service is registered, -1 on fatal error, or -2 if service is not registered
 */
int virDBusIsServiceRegistered(const char *name)
{
    int ret = virDBusIsServiceInList("ListNames", name);

    VIR_DEBUG("Service %s is %s", name, ret ? "not registered" : "registered");

    return ret;
}

void virDBusMessageUnref(DBusMessage *msg)
{
    if (msg)
        dbus_message_unref(msg);
}

#else /* ! WITH_DBUS */
void virDBusSetSharedBus(bool shared ATTRIBUTE_UNUSED)
{
    /* nothing */
}

DBusConnection *virDBusGetSystemBus(void)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("DBus support not compiled into this binary"));
    return NULL;
}


bool
virDBusHasSystemBus(void)
{
    VIR_DEBUG("DBus support not compiled into this binary");
    return false;
}

void virDBusCloseSystemBus(void)
{
    /* nothing */
}

DBusConnection *virDBusGetSessionBus(void)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("DBus support not compiled into this binary"));
    return NULL;
}

int virDBusCreateMethod(DBusMessage **call ATTRIBUTE_UNUSED,
                        const char *destination ATTRIBUTE_UNUSED,
                        const char *path ATTRIBUTE_UNUSED,
                        const char *iface 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 virDBusCreateMethodV(DBusMessage **call ATTRIBUTE_UNUSED,
                         const char *destination ATTRIBUTE_UNUSED,
                         const char *path ATTRIBUTE_UNUSED,
                         const char *iface ATTRIBUTE_UNUSED,
                         const char *member ATTRIBUTE_UNUSED,
                         const char *types ATTRIBUTE_UNUSED,
                         va_list args ATTRIBUTE_UNUSED)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("DBus support not compiled into this binary"));
    return -1;
}

int virDBusCreateReplyV(DBusMessage **reply ATTRIBUTE_UNUSED,
                        const char *types ATTRIBUTE_UNUSED,
                        va_list args ATTRIBUTE_UNUSED)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("DBus support not compiled into this binary"));
    return -1;
}

int virDBusCreateReply(DBusMessage **reply ATTRIBUTE_UNUSED,
                       const char *types ATTRIBUTE_UNUSED, ...)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("DBus support not compiled into this binary"));
    return -1;
}

int virDBusCallMethod(DBusConnection *conn ATTRIBUTE_UNUSED,
                      DBusMessage **reply ATTRIBUTE_UNUSED,
                      virErrorPtr error ATTRIBUTE_UNUSED,
                      const char *destination ATTRIBUTE_UNUSED,
                      const char *path ATTRIBUTE_UNUSED,
                      const char *iface 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;
}

int virDBusMessageEncode(DBusMessage* msg ATTRIBUTE_UNUSED,
                         const char *types ATTRIBUTE_UNUSED,
                         ...)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("DBus support not compiled into this binary"));
    return -1;
}

int virDBusMessageDecode(DBusMessage* msg ATTRIBUTE_UNUSED,
                         const char *types ATTRIBUTE_UNUSED,
                         ...)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("DBus support not compiled into this binary"));
    return -1;
}

int virDBusIsServiceEnabled(const char *name ATTRIBUTE_UNUSED)
{
    VIR_DEBUG("DBus support not compiled into this binary");
    return -2;
}

int virDBusIsServiceRegistered(const char *name ATTRIBUTE_UNUSED)
{
    VIR_DEBUG("DBus support not compiled into this binary");
    return -2;
}

void virDBusMessageUnref(DBusMessage *msg ATTRIBUTE_UNUSED)
{
    /* nothing */
}
#endif /* ! WITH_DBUS */

bool virDBusErrorIsUnknownMethod(virErrorPtr err)
{
    return err->domain == VIR_FROM_DBUS &&
        err->code == VIR_ERR_DBUS_SERVICE &&
        err->level == VIR_ERR_ERROR &&
        STREQ_NULLABLE("org.freedesktop.DBus.Error.UnknownMethod",
                       err->str1);
}
