#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <inttypes.h>

#define VIR_ENUM_SENTINELS

#include <libvirt/libvirt.h>
#include <libvirt/virterror.h>

#define G_N_ELEMENTS(Array) (sizeof(Array) / sizeof(*(Array)))
#define STREQ(a, b) (strcmp(a, b) == 0)
#define NULLSTR(s) ((s) ? (s) : "<null>")

#if (4 < __GNUC__ + (6 <= __GNUC_MINOR__) \
     && (201112L <= __STDC_VERSION__  || !defined __STRICT_ANSI__) \
     && !defined __cplusplus)
# define G_STATIC_ASSERT(cond) _Static_assert(cond, "verify (" #cond ")")
#else
# define G_STATIC_ASSERT(cond)
#endif

#ifndef G_GNUC_UNUSED
# define G_GNUC_UNUSED __attribute__((__unused__))
#endif

int run = 1;

/* Callback functions */
static void
connectClose(virConnectPtr conn G_GNUC_UNUSED,
             int reason,
             void *opaque G_GNUC_UNUSED)
{
    run = 0;

    switch ((virConnectCloseReason) reason) {
    case VIR_CONNECT_CLOSE_REASON_ERROR:
        fprintf(stderr, "Connection closed due to I/O error\n");
        return;

    case VIR_CONNECT_CLOSE_REASON_EOF:
        fprintf(stderr, "Connection closed due to end of file\n");
        return;

    case VIR_CONNECT_CLOSE_REASON_KEEPALIVE:
        fprintf(stderr, "Connection closed due to keepalive timeout\n");
        return;

    case VIR_CONNECT_CLOSE_REASON_CLIENT:
        fprintf(stderr, "Connection closed due to client request\n");
        return;

    case VIR_CONNECT_CLOSE_REASON_LAST:
        break;
    };

    fprintf(stderr, "Connection closed due to unknown reason\n");
}


static const char *
eventToString(int event)
{
    switch ((virDomainEventType) event) {
        case VIR_DOMAIN_EVENT_DEFINED:
            return "Defined";

        case VIR_DOMAIN_EVENT_UNDEFINED:
            return "Undefined";

        case VIR_DOMAIN_EVENT_STARTED:
            return "Started";

        case VIR_DOMAIN_EVENT_SUSPENDED:
            return "Suspended";

        case VIR_DOMAIN_EVENT_RESUMED:
            return "Resumed";

        case VIR_DOMAIN_EVENT_STOPPED:
            return "Stopped";

        case VIR_DOMAIN_EVENT_SHUTDOWN:
            return "Shutdown";

        case VIR_DOMAIN_EVENT_PMSUSPENDED:
            return "PMSuspended";

        case VIR_DOMAIN_EVENT_CRASHED:
            return "Crashed";

        case VIR_DOMAIN_EVENT_LAST:
            break;
    }

    return "unknown";
}


static const char *
eventDetailToString(int event,
                    int detail)
{
    switch ((virDomainEventType) event) {
        case VIR_DOMAIN_EVENT_DEFINED:
            switch ((virDomainEventDefinedDetailType) detail) {
            case VIR_DOMAIN_EVENT_DEFINED_ADDED:
                return "Added";

            case VIR_DOMAIN_EVENT_DEFINED_UPDATED:
                return "Updated";

            case VIR_DOMAIN_EVENT_DEFINED_RENAMED:
                return "Renamed";

            case  VIR_DOMAIN_EVENT_DEFINED_FROM_SNAPSHOT:
                return "Snapshot";

            case VIR_DOMAIN_EVENT_DEFINED_LAST:
                break;
            }
            break;

        case VIR_DOMAIN_EVENT_UNDEFINED:
            switch ((virDomainEventUndefinedDetailType) detail) {
            case VIR_DOMAIN_EVENT_UNDEFINED_REMOVED:
                return "Removed";

            case VIR_DOMAIN_EVENT_UNDEFINED_RENAMED:
                return "Renamed";

            case VIR_DOMAIN_EVENT_UNDEFINED_LAST:
                break;
            }
            break;

        case VIR_DOMAIN_EVENT_STARTED:
            switch ((virDomainEventStartedDetailType) detail) {
            case VIR_DOMAIN_EVENT_STARTED_BOOTED:
                return "Booted";

            case VIR_DOMAIN_EVENT_STARTED_MIGRATED:
                return "Migrated";

            case VIR_DOMAIN_EVENT_STARTED_RESTORED:
                return "Restored";

            case VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT:
                return "Snapshot";

            case VIR_DOMAIN_EVENT_STARTED_WAKEUP:
                return "Event wakeup";

            case VIR_DOMAIN_EVENT_STARTED_LAST:
                break;
            }
            break;

        case VIR_DOMAIN_EVENT_SUSPENDED:
            switch ((virDomainEventSuspendedDetailType) detail) {
            case VIR_DOMAIN_EVENT_SUSPENDED_PAUSED:
                return "Paused";

            case VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED:
                return "Migrated";

            case VIR_DOMAIN_EVENT_SUSPENDED_IOERROR:
                return "I/O Error";

            case VIR_DOMAIN_EVENT_SUSPENDED_WATCHDOG:
                return "Watchdog";

            case VIR_DOMAIN_EVENT_SUSPENDED_RESTORED:
                return "Restored";

            case VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT:
                return "Snapshot";

            case VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR:
                return "API error";

            case VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY:
                return "Post-copy";

            case VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED:
                return "Post-copy Error";

            case VIR_DOMAIN_EVENT_SUSPENDED_LAST:
                break;
            }
            break;

        case VIR_DOMAIN_EVENT_RESUMED:
            switch ((virDomainEventResumedDetailType) detail) {
            case VIR_DOMAIN_EVENT_RESUMED_UNPAUSED:
                return "Unpaused";

            case VIR_DOMAIN_EVENT_RESUMED_MIGRATED:
                return "Migrated";

            case VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT:
                return "Snapshot";

            case VIR_DOMAIN_EVENT_RESUMED_POSTCOPY:
                return "Post-copy";

            case VIR_DOMAIN_EVENT_RESUMED_LAST:
                break;
            }
            break;

        case VIR_DOMAIN_EVENT_STOPPED:
            switch ((virDomainEventStoppedDetailType) detail) {
            case VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN:
                return "Shutdown";

            case VIR_DOMAIN_EVENT_STOPPED_DESTROYED:
                return "Destroyed";

            case VIR_DOMAIN_EVENT_STOPPED_CRASHED:
                return "Crashed";

            case VIR_DOMAIN_EVENT_STOPPED_MIGRATED:
                return "Migrated";

            case VIR_DOMAIN_EVENT_STOPPED_SAVED:
                return "Saved";

            case VIR_DOMAIN_EVENT_STOPPED_FAILED:
                return "Failed";

            case VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT:
                return "Snapshot";

            case VIR_DOMAIN_EVENT_STOPPED_LAST:
                break;
            }
            break;

        case VIR_DOMAIN_EVENT_SHUTDOWN:
            switch ((virDomainEventShutdownDetailType) detail) {
            case VIR_DOMAIN_EVENT_SHUTDOWN_FINISHED:
                return "Finished";

            case VIR_DOMAIN_EVENT_SHUTDOWN_GUEST:
                return "Guest request";

            case VIR_DOMAIN_EVENT_SHUTDOWN_HOST:
                return "Host request";

            case VIR_DOMAIN_EVENT_SHUTDOWN_LAST:
                break;
            }
            break;

        case VIR_DOMAIN_EVENT_PMSUSPENDED:
            switch ((virDomainEventPMSuspendedDetailType) detail) {
            case VIR_DOMAIN_EVENT_PMSUSPENDED_MEMORY:
                return "Memory";

            case VIR_DOMAIN_EVENT_PMSUSPENDED_DISK:
                return "Disk";

            case VIR_DOMAIN_EVENT_PMSUSPENDED_LAST:
                break;
            }
            break;

        case VIR_DOMAIN_EVENT_CRASHED:
           switch ((virDomainEventCrashedDetailType) detail) {
           case VIR_DOMAIN_EVENT_CRASHED_PANICKED:
               return "Panicked";

           case VIR_DOMAIN_EVENT_CRASHED_CRASHLOADED:
               return "Crashloaded";

           case VIR_DOMAIN_EVENT_CRASHED_LAST:
               break;
           }
           break;

        case VIR_DOMAIN_EVENT_LAST:
           break;
    }

    return "unknown";
}


static const char *
networkEventToString(int event)
{
    switch ((virNetworkEventLifecycleType) event) {
        case VIR_NETWORK_EVENT_DEFINED:
            return "Defined";

        case VIR_NETWORK_EVENT_UNDEFINED:
            return "Undefined";

        case VIR_NETWORK_EVENT_STARTED:
            return "Started";

        case VIR_NETWORK_EVENT_STOPPED:
            return "Stopped";

        case VIR_NETWORK_EVENT_LAST:
            break;
    }

    return "unknown";
}


static const char *
guestAgentLifecycleEventStateToString(int event)
{
    switch ((virConnectDomainEventAgentLifecycleState) event) {
    case VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_DISCONNECTED:
        return "Disconnected";

    case VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_CONNECTED:
        return "Connected";

    case VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_LAST:
        break;
    }

    return "unknown";
}


static const char *
guestAgentLifecycleEventReasonToString(int event)
{
    switch ((virConnectDomainEventAgentLifecycleReason) event) {
    case VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_UNKNOWN:
        return "Unknown";

    case VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_DOMAIN_STARTED:
        return "Domain started";

    case VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_CHANNEL:
        return "Channel event";

    case VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_LAST:
        break;
    }

    return "unknown";
}

static const char *
storagePoolEventToString(int event)
{
    switch ((virStoragePoolEventLifecycleType) event) {
        case VIR_STORAGE_POOL_EVENT_DEFINED:
            return "Defined";
        case VIR_STORAGE_POOL_EVENT_UNDEFINED:
            return "Undefined";
        case VIR_STORAGE_POOL_EVENT_STARTED:
            return "Started";
        case VIR_STORAGE_POOL_EVENT_STOPPED:
            return "Stopped";
        case VIR_STORAGE_POOL_EVENT_CREATED:
            return "Created";
        case VIR_STORAGE_POOL_EVENT_DELETED:
            return "Deleted";
        case VIR_STORAGE_POOL_EVENT_LAST:
            break;
    }
    return "unknown";
}

static const char *
nodeDeviceEventToString(int event)
{
    switch ((virNodeDeviceEventLifecycleType) event) {
        case VIR_NODE_DEVICE_EVENT_CREATED:
            return "Created";
        case VIR_NODE_DEVICE_EVENT_DELETED:
            return "Deleted";
        case VIR_NODE_DEVICE_EVENT_DEFINED:
            return "Defined";
        case VIR_NODE_DEVICE_EVENT_UNDEFINED:
            return "Undefined";
        case VIR_NODE_DEVICE_EVENT_LAST:
            break;
    }
    return "unknown";
}


static const char *
secretEventToString(int event)
{
    switch ((virSecretEventLifecycleType) event) {
        case VIR_SECRET_EVENT_DEFINED:
            return "Defined";

        case VIR_SECRET_EVENT_UNDEFINED:
            return "Undefined";

        case VIR_SECRET_EVENT_LAST:
            break;
    }

    return "unknown";
}


static int
myDomainEventCallback1(virConnectPtr conn G_GNUC_UNUSED,
                       virDomainPtr dom,
                       int event,
                       int detail,
                       void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) %s %s\n", __func__, virDomainGetName(dom),
           virDomainGetID(dom), eventToString(event),
           eventDetailToString(event, detail));
    return 0;
}


static int
myDomainEventCallback2(virConnectPtr conn G_GNUC_UNUSED,
                       virDomainPtr dom,
                       int event,
                       int detail,
                       void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) %s %s\n", __func__, virDomainGetName(dom),
           virDomainGetID(dom), eventToString(event),
           eventDetailToString(event, detail));
    return 0;
}


static int
myDomainEventRebootCallback(virConnectPtr conn G_GNUC_UNUSED,
                            virDomainPtr dom,
                            void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) rebooted\n", __func__, virDomainGetName(dom),
           virDomainGetID(dom));

    return 0;
}


static int
myDomainEventRTCChangeCallback(virConnectPtr conn G_GNUC_UNUSED,
                               virDomainPtr dom,
                               long long offset,
                               void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) rtc change %" PRIdMAX "\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom),
           (intmax_t)offset);

    return 0;
}


static int
myDomainEventBalloonChangeCallback(virConnectPtr conn G_GNUC_UNUSED,
                                   virDomainPtr dom,
                                   unsigned long long actual,
                                   void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) balloon change %" PRIuMAX "KB\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom), (uintmax_t)actual);

    return 0;
}


static int
myDomainEventWatchdogCallback(virConnectPtr conn G_GNUC_UNUSED,
                              virDomainPtr dom,
                              int action,
                              void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) watchdog action=%d\n", __func__,
           virDomainGetName(dom), virDomainGetID(dom), action);

    return 0;
}


static int
myDomainEventIOErrorCallback(virConnectPtr conn G_GNUC_UNUSED,
                             virDomainPtr dom,
                             const char *srcPath,
                             const char *devAlias,
                             int action,
                             void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) io error path=%s alias=%s action=%d\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom),
           srcPath, devAlias, action);

    return 0;
}


static int
myDomainEventIOErrorReasonCallback(virConnectPtr conn G_GNUC_UNUSED,
                                   virDomainPtr dom,
                                   const char *srcPath,
                                   const char *devAlias,
                                   int action,
                                   const char *reason,
                                   void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) io error (reason) path=%s alias=%s "
           "action=%d reason=%s\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom),
           srcPath, devAlias, action, reason);

    return 0;
}


static const char *
graphicsPhaseToStr(int phase)
{
    switch ((virDomainEventGraphicsPhase) phase) {
    case VIR_DOMAIN_EVENT_GRAPHICS_CONNECT:
        return "connected";

    case VIR_DOMAIN_EVENT_GRAPHICS_INITIALIZE:
        return "initialized";

    case VIR_DOMAIN_EVENT_GRAPHICS_DISCONNECT:
        return "disconnected";

    case VIR_DOMAIN_EVENT_GRAPHICS_LAST:
        break;
    }

    return "unknown";
}


static int
myDomainEventGraphicsCallback(virConnectPtr conn G_GNUC_UNUSED,
                              virDomainPtr dom,
                              int phase,
                              virDomainEventGraphicsAddressPtr local,
                              virDomainEventGraphicsAddressPtr remote,
                              const char *authScheme,
                              virDomainEventGraphicsSubjectPtr subject,
                              void *opaque G_GNUC_UNUSED)
{
    size_t i;
    printf("%s EVENT: Domain %s(%d) graphics ", __func__, virDomainGetName(dom),
           virDomainGetID(dom));

    printf("%s ", graphicsPhaseToStr(phase));

    printf("local: family=%d node=%s service=%s ",
           local->family, local->node, local->service);
    printf("remote: family=%d node=%s service=%s ",
           remote->family, remote->node, remote->service);

    printf("auth: %s ", authScheme);
    for (i = 0; i < subject->nidentity; i++) {
        printf(" identity: %s=%s",
               subject->identities[i].type,
               subject->identities[i].name);
    }
    printf("\n");

    return 0;
}


static int
myDomainEventControlErrorCallback(virConnectPtr conn G_GNUC_UNUSED,
                                  virDomainPtr dom,
                                  void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) control error\n", __func__,
           virDomainGetName(dom), virDomainGetID(dom));

    return 0;
}

static const char *
diskChangeReasonToStr(int reason)
{
    switch ((virConnectDomainEventDiskChangeReason) reason) {
    case VIR_DOMAIN_EVENT_DISK_CHANGE_MISSING_ON_START:
        return "disk empty due to startupPolicy";

    case VIR_DOMAIN_EVENT_DISK_DROP_MISSING_ON_START:
        return "disk dropped due to startupPolicy";

    case VIR_DOMAIN_EVENT_DISK_CHANGE_LAST:
        break;
    }

    return "unknown";
}


static int
myDomainEventDiskChangeCallback(virConnectPtr conn G_GNUC_UNUSED,
                                virDomainPtr dom,
                                const char *oldSrcPath,
                                const char *newSrcPath,
                                const char *devAlias,
                                int reason,
                                void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) disk change oldSrcPath: %s newSrcPath: %s "
           "devAlias: %s reason: %s\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom),
           oldSrcPath, newSrcPath, devAlias, diskChangeReasonToStr(reason));
    return 0;
}

static const char *
trayChangeReasonToStr(int reason)
{
    switch ((virDomainEventTrayChangeReason) reason) {
    case VIR_DOMAIN_EVENT_TRAY_CHANGE_OPEN:
        return "open";

    case VIR_DOMAIN_EVENT_TRAY_CHANGE_CLOSE:
        return "close";

    case VIR_DOMAIN_EVENT_TRAY_CHANGE_LAST:
        break;
    }

    return "unknown";
};


static int
myDomainEventTrayChangeCallback(virConnectPtr conn G_GNUC_UNUSED,
                                virDomainPtr dom,
                                const char *devAlias,
                                int reason,
                                void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) removable disk's tray change devAlias: %s "
           "reason: %s\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom),
           devAlias, trayChangeReasonToStr(reason));
    return 0;
}


static int
myDomainEventPMWakeupCallback(virConnectPtr conn G_GNUC_UNUSED,
                              virDomainPtr dom,
                              int reason G_GNUC_UNUSED,
                              void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) system pmwakeup\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom));
    return 0;
}


static int
myDomainEventPMSuspendCallback(virConnectPtr conn G_GNUC_UNUSED,
                               virDomainPtr dom,
                               int reason G_GNUC_UNUSED,
                               void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) system pmsuspend\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom));
    return 0;
}


static int
myDomainEventPMSuspendDiskCallback(virConnectPtr conn G_GNUC_UNUSED,
                                   virDomainPtr dom,
                                   int reason G_GNUC_UNUSED,
                                   void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) system pmsuspend-disk\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom));
    return 0;
}


static int
myDomainEventDeviceRemovedCallback(virConnectPtr conn G_GNUC_UNUSED,
                                   virDomainPtr dom,
                                   const char *devAlias,
                                   void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) device removed: %s\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom), devAlias);
    return 0;
}


static int
myNetworkEventCallback(virConnectPtr conn G_GNUC_UNUSED,
                       virNetworkPtr dom,
                       int event,
                       int detail,
                       void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Network %s %s %d\n", __func__, virNetworkGetName(dom),
           networkEventToString(event), detail);
    return 0;
}

static int
myStoragePoolEventCallback(virConnectPtr conn G_GNUC_UNUSED,
                           virStoragePoolPtr pool,
                           int event,
                           int detail,
                           void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Storage pool %s %s %d\n", __func__,
           virStoragePoolGetName(pool),
           storagePoolEventToString(event),
           detail);
    return 0;
}


static int
myStoragePoolEventRefreshCallback(virConnectPtr conn G_GNUC_UNUSED,
                                  virStoragePoolPtr pool,
                                  void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Storage pool %s refresh\n", __func__,
           virStoragePoolGetName(pool));
    return 0;
}


static int
myNodeDeviceEventCallback(virConnectPtr conn G_GNUC_UNUSED,
                          virNodeDevicePtr dev,
                          int event,
                          int detail,
                          void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Node device %s %s %d\n", __func__,
           virNodeDeviceGetName(dev),
           nodeDeviceEventToString(event),
           detail);
    return 0;
}


static int
myNodeDeviceEventUpdateCallback(virConnectPtr conn G_GNUC_UNUSED,
                                virNodeDevicePtr dev,
                                void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Node device %s update\n", __func__,
           virNodeDeviceGetName(dev));
    return 0;
}


static int
mySecretEventCallback(virConnectPtr conn G_GNUC_UNUSED,
                      virSecretPtr secret,
                      int event,
                      int detail,
                      void *opaque G_GNUC_UNUSED)
{
    char uuid[VIR_UUID_STRING_BUFLEN];
    virSecretGetUUIDString(secret, uuid);
    printf("%s EVENT: Secret %s %s %d\n", __func__,
           uuid,
           secretEventToString(event),
           detail);
    return 0;
}


static int
mySecretEventValueChanged(virConnectPtr conn G_GNUC_UNUSED,
                          virSecretPtr secret,
                          void *opaque G_GNUC_UNUSED)
{
    char uuid[VIR_UUID_STRING_BUFLEN];
    virSecretGetUUIDString(secret, uuid);
    printf("%s EVENT: Secret %s\n", __func__, uuid);
    return 0;
}


static void
eventTypedParamsPrint(virTypedParameterPtr params,
                      int nparams)
{
    size_t i;

    for (i = 0; i < nparams; i++) {
        switch (params[i].type) {
        case VIR_TYPED_PARAM_INT:
            printf("\t%s: %d\n", params[i].field, params[i].value.i);
            break;
        case VIR_TYPED_PARAM_UINT:
            printf("\t%s: %u\n", params[i].field, params[i].value.ui);
            break;
        case VIR_TYPED_PARAM_LLONG:
            printf("\t%s: %" PRId64 "\n", params[i].field,
                   (int64_t) params[i].value.l);
            break;
        case VIR_TYPED_PARAM_ULLONG:
            printf("\t%s: %" PRIu64 "\n", params[i].field,
                   (uint64_t) params[i].value.ul);
            break;
        case VIR_TYPED_PARAM_DOUBLE:
            printf("\t%s: %g\n", params[i].field, params[i].value.d);
            break;
        case VIR_TYPED_PARAM_BOOLEAN:
            printf("\t%s: %d\n", params[i].field, params[i].value.b);
            break;
        case VIR_TYPED_PARAM_STRING:
            printf("\t%s: %s\n", params[i].field, params[i].value.s);
            break;
        default:
            printf("\t%s: unknown type\n", params[i].field);
        }
    }
}


static int
myDomainEventTunableCallback(virConnectPtr conn G_GNUC_UNUSED,
                             virDomainPtr dom,
                             virTypedParameterPtr params,
                             int nparams,
                             void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) tunable updated:\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom));

    eventTypedParamsPrint(params, nparams);

    return 0;
}


static int
myDomainEventAgentLifecycleCallback(virConnectPtr conn G_GNUC_UNUSED,
                                    virDomainPtr dom,
                                    int state,
                                    int reason,
                                    void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) guest agent state changed: %s reason: %s\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom),
           guestAgentLifecycleEventStateToString(state),
           guestAgentLifecycleEventReasonToString(reason));

    return 0;
}


static int
myDomainEventDeviceAddedCallback(virConnectPtr conn G_GNUC_UNUSED,
                                 virDomainPtr dom,
                                 const char *devAlias,
                                 void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) device added: %s\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom), devAlias);
    return 0;
}


static const char *
blockJobTypeToStr(int type)
{
    switch ((virDomainBlockJobType) type) {
    case VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN:
    case VIR_DOMAIN_BLOCK_JOB_TYPE_LAST:
        break;

    case VIR_DOMAIN_BLOCK_JOB_TYPE_PULL:
        return "block pull";

    case VIR_DOMAIN_BLOCK_JOB_TYPE_COPY:
        return "block copy";

    case VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT:
        return "block commit";

    case VIR_DOMAIN_BLOCK_JOB_TYPE_ACTIVE_COMMIT:
        return "active layer block commit";

    case VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP:
        return "backup";
    }

    return "unknown";
}


static const char *
blockJobStatusToStr(int status)
{
    switch ((virConnectDomainEventBlockJobStatus) status) {
    case VIR_DOMAIN_BLOCK_JOB_COMPLETED:
        return "completed";

    case VIR_DOMAIN_BLOCK_JOB_FAILED:
        return "failed";

    case VIR_DOMAIN_BLOCK_JOB_CANCELED:
        return "cancelled";

    case VIR_DOMAIN_BLOCK_JOB_READY:
        return "ready";

    case VIR_DOMAIN_BLOCK_JOB_LAST:
        break;
    }

    return "unknown";
}


static int
myDomainEventBlockJobCallback(virConnectPtr conn G_GNUC_UNUSED,
                              virDomainPtr dom,
                              const char *disk,
                              int type,
                              int status,
                              void *opaque)
{
    const char *eventName = opaque;

    printf("%s EVENT: Domain %s(%d) block job callback '%s' disk '%s', "
           "type '%s' status '%s'\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom), eventName,
           disk, blockJobTypeToStr(type), blockJobStatusToStr(status));
    return 0;
}


static int
myDomainEventBlockThresholdCallback(virConnectPtr conn G_GNUC_UNUSED,
                                    virDomainPtr dom,
                                    const char *dev,
                                    const char *path,
                                    unsigned long long threshold,
                                    unsigned long long excess,
                                    void *opaque G_GNUC_UNUSED)
{
    /* Casts to uint64_t to work around mingw not knowing %lld */
    printf("%s EVENT: Domain %s(%d) block threshold callback dev '%s'(%s), "
           "threshold: '%" PRIu64 "', excess: '%" PRIu64 "'\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom),
           dev, NULLSTR(path), (uint64_t)threshold, (uint64_t)excess);
    return 0;
}


static int
myDomainEventMemoryFailureCallback(virConnectPtr conn G_GNUC_UNUSED,
                                   virDomainPtr dom,
                                   int recipient,
                                   int action,
                                   unsigned int flags,
                                   void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) memory failure: recipient '%d', "
           "aciont '%d', flags '%d'\n", __func__, virDomainGetName(dom),
           virDomainGetID(dom), recipient, action, flags);
    return 0;
}


static int
myDomainEventMigrationIterationCallback(virConnectPtr conn G_GNUC_UNUSED,
                                        virDomainPtr dom,
                                        int iteration,
                                        void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) migration iteration '%d'\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom), iteration);
    return 0;
}


static int
myDomainEventJobCompletedCallback(virConnectPtr conn G_GNUC_UNUSED,
                                  virDomainPtr dom,
                                  virTypedParameterPtr params,
                                  int nparams,
                                  void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) job completed:\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom));

    eventTypedParamsPrint(params, nparams);

    return 0;
}


static int
myDomainEventDeviceRemovalFailedCallback(virConnectPtr conn G_GNUC_UNUSED,
                                         virDomainPtr dom,
                                         const char *devAlias,
                                         void *opaque G_GNUC_UNUSED)
{
    printf("%s EVENT: Domain %s(%d) device removal failed: %s\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom), devAlias);
    return 0;
}


static const char *
metadataTypeToStr(int status)
{
    switch ((virDomainMetadataType) status) {
    case VIR_DOMAIN_METADATA_DESCRIPTION:
        return "description";

    case VIR_DOMAIN_METADATA_TITLE:
        return "title";

    case VIR_DOMAIN_METADATA_ELEMENT:
        return "element";

    case VIR_DOMAIN_METADATA_LAST:
        break;
    }

    return "unknown";
}

static int
myDomainEventMetadataChangeCallback(virConnectPtr conn G_GNUC_UNUSED,
                                    virDomainPtr dom,
                                    int type,
                                    const char *nsuri,
                                    void *opaque G_GNUC_UNUSED)
{
    const char *typestr = metadataTypeToStr(type);
    printf("%s EVENT: Domain %s(%d) metadata type: %s (%s)\n",
           __func__, virDomainGetName(dom), virDomainGetID(dom), typestr, nsuri ? nsuri : "n/a");
    return 0;
}



static void
myFreeFunc(void *opaque)
{
    char *str = opaque;
    printf("%s: Freeing [%s]\n", __func__, str);
    free(str);
}


/* main test functions */
static void
stop(int sig)
{
    printf("Exiting on signal %d\n", sig);
    run = 0;
}


struct domainEventData {
    int event;
    int id;
    virConnectDomainEventGenericCallback cb;
    const char *name;
};


#define DOMAIN_EVENT(event, callback) \
    {event, -1, VIR_DOMAIN_EVENT_CALLBACK(callback), #event}

struct domainEventData domainEvents[] = {
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_LIFECYCLE, myDomainEventCallback2),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_REBOOT, myDomainEventRebootCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_RTC_CHANGE, myDomainEventRTCChangeCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_WATCHDOG, myDomainEventWatchdogCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_IO_ERROR, myDomainEventIOErrorCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_GRAPHICS, myDomainEventGraphicsCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON, myDomainEventIOErrorReasonCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_CONTROL_ERROR, myDomainEventControlErrorCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_BLOCK_JOB, myDomainEventBlockJobCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_DISK_CHANGE, myDomainEventDiskChangeCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_TRAY_CHANGE, myDomainEventTrayChangeCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_PMWAKEUP, myDomainEventPMWakeupCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_PMSUSPEND, myDomainEventPMSuspendCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_BALLOON_CHANGE, myDomainEventBalloonChangeCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_PMSUSPEND_DISK, myDomainEventPMSuspendDiskCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED, myDomainEventDeviceRemovedCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_BLOCK_JOB_2, myDomainEventBlockJobCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_TUNABLE, myDomainEventTunableCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_AGENT_LIFECYCLE, myDomainEventAgentLifecycleCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_DEVICE_ADDED, myDomainEventDeviceAddedCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_MIGRATION_ITERATION, myDomainEventMigrationIterationCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_JOB_COMPLETED, myDomainEventJobCompletedCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED, myDomainEventDeviceRemovalFailedCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_METADATA_CHANGE, myDomainEventMetadataChangeCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_BLOCK_THRESHOLD, myDomainEventBlockThresholdCallback),
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_MEMORY_FAILURE, myDomainEventMemoryFailureCallback),
};

struct storagePoolEventData {
    int event;
    int id;
    virConnectStoragePoolEventGenericCallback cb;
    const char *name;
};

#define STORAGE_POOL_EVENT(event, callback) \
    {event, -1, VIR_STORAGE_POOL_EVENT_CALLBACK(callback), #event}

struct storagePoolEventData storagePoolEvents[] = {
    STORAGE_POOL_EVENT(VIR_STORAGE_POOL_EVENT_ID_LIFECYCLE, myStoragePoolEventCallback),
    STORAGE_POOL_EVENT(VIR_STORAGE_POOL_EVENT_ID_REFRESH, myStoragePoolEventRefreshCallback),
};

struct nodeDeviceEventData {
    int event;
    int id;
    virConnectNodeDeviceEventGenericCallback cb;
    const char *name;
};

#define NODE_DEVICE_EVENT(event, callback) \
    {event, -1, VIR_NODE_DEVICE_EVENT_CALLBACK(callback), #event}

struct nodeDeviceEventData nodeDeviceEvents[] = {
    NODE_DEVICE_EVENT(VIR_NODE_DEVICE_EVENT_ID_LIFECYCLE, myNodeDeviceEventCallback),
    NODE_DEVICE_EVENT(VIR_NODE_DEVICE_EVENT_ID_UPDATE, myNodeDeviceEventUpdateCallback),
};

struct secretEventData {
    int event;
    int id;
    virConnectSecretEventGenericCallback cb;
    const char *name;
};

#define SECRET_EVENT(event, callback) \
    {event, -1, VIR_SECRET_EVENT_CALLBACK(callback), #event}

struct secretEventData secretEvents[] = {
    SECRET_EVENT(VIR_SECRET_EVENT_ID_LIFECYCLE, mySecretEventCallback),
    SECRET_EVENT(VIR_SECRET_EVENT_ID_VALUE_CHANGED, mySecretEventValueChanged),
};

/* make sure that the events are kept in sync */
G_STATIC_ASSERT(G_N_ELEMENTS(domainEvents) == VIR_DOMAIN_EVENT_ID_LAST);
G_STATIC_ASSERT(G_N_ELEMENTS(storagePoolEvents) == VIR_STORAGE_POOL_EVENT_ID_LAST);
G_STATIC_ASSERT(G_N_ELEMENTS(nodeDeviceEvents) == VIR_NODE_DEVICE_EVENT_ID_LAST);
G_STATIC_ASSERT(G_N_ELEMENTS(secretEvents) == VIR_SECRET_EVENT_ID_LAST);

int
main(int argc, char **argv)
{
    int ret = EXIT_FAILURE;
    virConnectPtr dconn = NULL;
    int callback1ret = -1;
    int callback16ret = -1;
    size_t i;

    if (argc > 1 && STREQ(argv[1], "--help")) {
        printf("%s uri\n", argv[0]);
        goto cleanup;
    }

    if (virInitialize() < 0) {
        fprintf(stderr, "Failed to initialize libvirt");
        goto cleanup;
    }

    if (virEventRegisterDefaultImpl() < 0) {
        fprintf(stderr, "Failed to register event implementation: %s\n",
                virGetLastErrorMessage());
        goto cleanup;
    }

    dconn = virConnectOpenAuth(argc > 1 ? argv[1] : NULL,
                               virConnectAuthPtrDefault,
                               VIR_CONNECT_RO);
    if (!dconn) {
        printf("error opening\n");
        goto cleanup;
    }

    if (virConnectRegisterCloseCallback(dconn,
                                        connectClose, NULL, NULL) < 0) {
        fprintf(stderr, "Unable to register close callback\n");
        goto cleanup;
    }

    /* The ideal program would use sigaction to set this handler, but
     * this way is portable to mingw. */
    signal(SIGTERM, stop);
    signal(SIGINT, stop);

    printf("Registering event callbacks\n");

    callback1ret = virConnectDomainEventRegister(dconn, myDomainEventCallback1,
                                                 strdup("callback 1"), myFreeFunc);

    /* register common domain callbacks */
    for (i = 0; i < G_N_ELEMENTS(domainEvents); i++) {
        struct domainEventData *event = domainEvents + i;

        event->id = virConnectDomainEventRegisterAny(dconn, NULL,
                                                     event->event,
                                                     event->cb,
                                                     strdup(event->name),
                                                     myFreeFunc);

        if (event->id < 0) {
            fprintf(stderr, "Failed to register event '%s'\n", event->name);
            goto cleanup;
        }
    }

    callback16ret = virConnectNetworkEventRegisterAny(dconn,
                                                      NULL,
                                                      VIR_NETWORK_EVENT_ID_LIFECYCLE,
                                                      VIR_NETWORK_EVENT_CALLBACK(myNetworkEventCallback),
                                                      strdup("net callback"), myFreeFunc);

    /* register common storage pool callbacks */
    for (i = 0; i < G_N_ELEMENTS(storagePoolEvents); i++) {
        struct storagePoolEventData *event = storagePoolEvents + i;

        event->id = virConnectStoragePoolEventRegisterAny(dconn, NULL,
                                                          event->event,
                                                          event->cb,
                                                          strdup(event->name),
                                                          myFreeFunc);

        if (event->id < 0) {
            fprintf(stderr, "Failed to register event '%s'\n", event->name);
            goto cleanup;
        }
    }

    /* register common node device callbacks */
    for (i = 0; i < G_N_ELEMENTS(nodeDeviceEvents); i++) {
        struct nodeDeviceEventData *event = nodeDeviceEvents + i;

        event->id = virConnectNodeDeviceEventRegisterAny(dconn, NULL,
                                                         event->event,
                                                         event->cb,
                                                         strdup(event->name),
                                                         myFreeFunc);

        if (event->id < 0) {
            fprintf(stderr, "Failed to register event '%s'\n", event->name);
            goto cleanup;
        }
    }

    /* register common secret callbacks */
    for (i = 0; i < G_N_ELEMENTS(secretEvents); i++) {
        struct secretEventData *event = secretEvents + i;

        event->id = virConnectSecretEventRegisterAny(dconn, NULL,
                                                     event->event,
                                                     event->cb,
                                                     strdup(event->name),
                                                     myFreeFunc);

        if (event->id < 0) {
            fprintf(stderr, "Failed to register event '%s'\n", event->name);
            goto cleanup;
        }
    }

    if ((callback1ret == -1) ||
        (callback16ret == -1))
        goto cleanup;

    if (virConnectSetKeepAlive(dconn, 5, 3) < 0) {
        fprintf(stderr, "Failed to start keepalive protocol: %s\n",
                virGetLastErrorMessage());
        run = 0;
    }

    while (run) {
        if (virEventRunDefaultImpl() < 0) {
            fprintf(stderr, "Failed to run event loop: %s\n",
                    virGetLastErrorMessage());
        }
    }

    printf("Deregistering event callbacks\n");
    virConnectDomainEventDeregister(dconn, myDomainEventCallback1);
    virConnectNetworkEventDeregisterAny(dconn, callback16ret);


    printf("Deregistering domain event callbacks\n");
    for (i = 0; i < G_N_ELEMENTS(domainEvents); i++) {
        if (domainEvents[i].id > 0)
            virConnectDomainEventDeregisterAny(dconn, domainEvents[i].id);
    }


    printf("Deregistering storage pool event callbacks\n");
    for (i = 0; i < G_N_ELEMENTS(storagePoolEvents); i++) {
        if (storagePoolEvents[i].id > 0)
            virConnectStoragePoolEventDeregisterAny(dconn, storagePoolEvents[i].id);
    }


    printf("Deregistering node device event callbacks\n");
    for (i = 0; i < G_N_ELEMENTS(nodeDeviceEvents); i++) {
        if (nodeDeviceEvents[i].id > 0)
            virConnectNodeDeviceEventDeregisterAny(dconn, nodeDeviceEvents[i].id);
    }

    printf("Deregistering secret event callbacks\n");
    for (i = 0; i < G_N_ELEMENTS(secretEvents); i++) {
        if (secretEvents[i].id > 0)
            virConnectSecretEventDeregisterAny(dconn, secretEvents[i].id);
    }


    virConnectUnregisterCloseCallback(dconn, connectClose);
    ret = EXIT_SUCCESS;


 cleanup:
    if (dconn) {
        printf("Closing connection: ");
        if (virConnectClose(dconn) < 0)
            printf("failed\n");
        printf("done\n");
    }

    return ret;
}
