/*
 * virsh-domain-monitor.c: Commands to monitor domain status
 *
 * Copyright (C) 2005, 2007-2016 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 "virsh-domain-monitor.h"
#include "virsh-util.h"

#include <libxml/parser.h>
#include <libxml/xpath.h>

#include "internal.h"
#include "conf/virdomainobjlist.h"
#include "viralloc.h"
#include "virmacaddr.h"
#include "virxml.h"
#include "virstring.h"
#include "vsh-table.h"
#include "virenum.h"

VIR_ENUM_DECL(virshDomainIOError);
VIR_ENUM_IMPL(virshDomainIOError,
              VIR_DOMAIN_DISK_ERROR_LAST,
              N_("no error"),
              N_("unspecified error"),
              N_("no space"),
);

static const char *
virshDomainIOErrorToString(int error)
{
    const char *str = virshDomainIOErrorTypeToString(error);
    return str ? _(str) : _("unknown error");
}

/* extract description or title from domain xml */
char *
virshGetDomainDescription(vshControl *ctl, virDomainPtr dom, bool title,
                          unsigned int flags)
{
    char *desc = NULL;
    g_autoptr(xmlDoc) doc = NULL;
    g_autoptr(xmlXPathContext) ctxt = NULL;
    int type;

    if (title)
        type = VIR_DOMAIN_METADATA_TITLE;
    else
        type = VIR_DOMAIN_METADATA_DESCRIPTION;

    if ((desc = virDomainGetMetadata(dom, type, NULL, flags))) {
        return desc;
    } else {
        int errCode = virGetLastErrorCode();

        if (errCode == VIR_ERR_NO_DOMAIN_METADATA) {
            desc = g_strdup("");
            vshResetLibvirtError();
            return desc;
        }

        if (errCode != VIR_ERR_NO_SUPPORT)
            return desc;
    }

    /* fall back to xml */
    if (virshDomainGetXMLFromDom(ctl, dom, flags, &doc, &ctxt) < 0)
        return NULL;

    if (title)
        desc = virXPathString("string(./title[1])", ctxt);
    else
        desc = virXPathString("string(./description[1])", ctxt);

    if (!desc)
        desc = g_strdup("");

    return desc;
}

VIR_ENUM_DECL(virshDomainControlState);
VIR_ENUM_IMPL(virshDomainControlState,
              VIR_DOMAIN_CONTROL_LAST,
              N_("ok"),
              N_("background job"),
              N_("occupied"),
              N_("error"),
);

static const char *
virshDomainControlStateToString(int state)
{
    const char *str = virshDomainControlStateTypeToString(state);
    return str ? _(str) : _("unknown");
}

VIR_ENUM_DECL(virshDomainControlErrorReason);
VIR_ENUM_IMPL(virshDomainControlErrorReason,
              VIR_DOMAIN_CONTROL_ERROR_REASON_LAST,
              "",
              N_("unknown"),
              N_("monitor failure"),
              N_("internal (locking) error"),
);

static const char *
virshDomainControlErrorReasonToString(int reason)
{
    const char *ret = virshDomainControlErrorReasonTypeToString(reason);
    return ret ? _(ret) : _("unknown");
}

VIR_ENUM_DECL(virshDomainState);
VIR_ENUM_IMPL(virshDomainState,
              VIR_DOMAIN_LAST,
              N_("no state"),
              N_("running"),
              N_("idle"),
              N_("paused"),
              N_("in shutdown"),
              N_("shut off"),
              N_("crashed"),
              N_("pmsuspended"),
);

static const char *
virshDomainStateToString(int state)
{
    const char *str = virshDomainStateTypeToString(state);
    return str ? _(str) : _("no state");
}

VIR_ENUM_DECL(virshDomainNostateReason);
VIR_ENUM_IMPL(virshDomainNostateReason,
              VIR_DOMAIN_NOSTATE_LAST,
              N_("unknown"),
);

VIR_ENUM_DECL(virshDomainRunningReason);
VIR_ENUM_IMPL(virshDomainRunningReason,
              VIR_DOMAIN_RUNNING_LAST,
              N_("unknown"),
              N_("booted"),
              N_("migrated"),
              N_("restored"),
              N_("from snapshot"),
              N_("unpaused"),
              N_("migration canceled"),
              N_("save canceled"),
              N_("event wakeup"),
              N_("crashed"),
              N_("post-copy"),
              N_("post-copy failed"),
);

VIR_ENUM_DECL(virshDomainBlockedReason);
VIR_ENUM_IMPL(virshDomainBlockedReason,
              VIR_DOMAIN_BLOCKED_LAST,
              N_("unknown"),
);

VIR_ENUM_DECL(virshDomainPausedReason);
VIR_ENUM_IMPL(virshDomainPausedReason,
              VIR_DOMAIN_PAUSED_LAST,
              N_("unknown"),
              N_("user"),
              N_("migrating"),
              N_("saving"),
              N_("dumping"),
              N_("I/O error"),
              N_("watchdog"),
              N_("from snapshot"),
              N_("shutting down"),
              N_("creating snapshot"),
              N_("crashed"),
              N_("starting up"),
              N_("post-copy"),
              N_("post-copy failed"),
);

VIR_ENUM_DECL(virshDomainShutdownReason);
VIR_ENUM_IMPL(virshDomainShutdownReason,
              VIR_DOMAIN_SHUTDOWN_LAST,
              N_("unknown"),
              N_("user"),
);

VIR_ENUM_DECL(virshDomainShutoffReason);
VIR_ENUM_IMPL(virshDomainShutoffReason,
              VIR_DOMAIN_SHUTOFF_LAST,
              N_("unknown"),
              N_("shutdown"),
              N_("destroyed"),
              N_("crashed"),
              N_("migrated"),
              N_("saved"),
              N_("failed"),
              N_("from snapshot"),
              N_("daemon"),
);

VIR_ENUM_DECL(virshDomainCrashedReason);
VIR_ENUM_IMPL(virshDomainCrashedReason,
              VIR_DOMAIN_CRASHED_LAST,
              N_("unknown"),
              N_("panicked"),
);

VIR_ENUM_DECL(virshDomainPMSuspendedReason);
VIR_ENUM_IMPL(virshDomainPMSuspendedReason,
              VIR_DOMAIN_PMSUSPENDED_LAST,
              N_("unknown"),
);

static const char *
virshDomainStateReasonToString(int state, int reason)
{
    const char *str = NULL;
    switch ((virDomainState) state) {
    case VIR_DOMAIN_NOSTATE:
        str = virshDomainNostateReasonTypeToString(reason);
        break;
    case VIR_DOMAIN_RUNNING:
        str = virshDomainRunningReasonTypeToString(reason);
        break;
    case VIR_DOMAIN_BLOCKED:
        str = virshDomainBlockedReasonTypeToString(reason);
        break;
    case VIR_DOMAIN_PAUSED:
        str = virshDomainPausedReasonTypeToString(reason);
        break;
    case VIR_DOMAIN_SHUTDOWN:
        str = virshDomainShutdownReasonTypeToString(reason);
        break;
    case VIR_DOMAIN_SHUTOFF:
        str = virshDomainShutoffReasonTypeToString(reason);
        break;
    case VIR_DOMAIN_CRASHED:
        str = virshDomainCrashedReasonTypeToString(reason);
        break;
    case VIR_DOMAIN_PMSUSPENDED:
        str = virshDomainPMSuspendedReasonTypeToString(reason);
        break;
    case VIR_DOMAIN_LAST:
        ;
    }

    return str ? _(str) : _("unknown");
}

/*
 * "dommemstat" command
 */
static const vshCmdInfo info_dommemstat[] = {
    {.name = "help",
     .data = N_("get memory statistics for a domain")
    },
    {.name = "desc",
     .data = N_("Get memory statistics for a running domain.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_dommemstat[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE),
    {.name = "period",
     .type = VSH_OT_INT,
     .flags = VSH_OFLAG_REQ_OPT,
     .help = N_("period in seconds to set collection")
    },
    VIRSH_COMMON_OPT_CONFIG(N_("affect next boot")),
    VIRSH_COMMON_OPT_LIVE(N_("affect running domain")),
    VIRSH_COMMON_OPT_CURRENT(N_("affect current domain")),
    {.name = NULL}
};

static bool
cmdDomMemStat(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshDomain) dom = NULL;
    const char *name;
    virDomainMemoryStatStruct stats[VIR_DOMAIN_MEMORY_STAT_NR];
    unsigned int nr_stats;
    size_t i;
    int rv = 0;
    int period = -1;
    bool config = vshCommandOptBool(cmd, "config");
    bool live = vshCommandOptBool(cmd, "live");
    bool current = vshCommandOptBool(cmd, "current");
    unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT;

    VSH_EXCLUSIVE_OPTIONS_VAR(current, live);
    VSH_EXCLUSIVE_OPTIONS_VAR(current, config);
    if (config)
        flags |= VIR_DOMAIN_AFFECT_CONFIG;
    if (live)
        flags |= VIR_DOMAIN_AFFECT_LIVE;

    if (!(dom = virshCommandOptDomain(ctl, cmd, &name)))
        return false;

    /* If none of the options were specified and we're active
     * then be sure to allow active modification */
    if (!current && !live && !config && virDomainIsActive(dom) == 1)
        flags |= VIR_DOMAIN_AFFECT_LIVE;

    /* Providing a period will adjust the balloon driver collection period.
     * This is not really an unsigned long, but it
     */
    if ((rv = vshCommandOptInt(ctl, cmd, "period", &period)) < 0)
        return false;
    if (rv > 0) {
        if (period < 0) {
            vshError(ctl, _("Invalid collection period value '%d'"), period);
            return false;
        }

        if (virDomainSetMemoryStatsPeriod(dom, period, flags) < 0) {
            vshError(ctl, "%s",
                     _("Unable to change balloon collection period."));
            return false;
        }
        return true;
    }

    nr_stats = virDomainMemoryStats(dom, stats, VIR_DOMAIN_MEMORY_STAT_NR, 0);
    if (nr_stats == -1) {
        vshError(ctl, _("Failed to get memory statistics for domain %s"), name);
        return false;
    }

    for (i = 0; i < nr_stats; i++) {
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_SWAP_IN)
            vshPrint(ctl, "swap_in %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_SWAP_OUT)
            vshPrint(ctl, "swap_out %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_MAJOR_FAULT)
            vshPrint(ctl, "major_fault %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_MINOR_FAULT)
            vshPrint(ctl, "minor_fault %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_UNUSED)
            vshPrint(ctl, "unused %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_AVAILABLE)
            vshPrint(ctl, "available %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_USABLE)
            vshPrint(ctl, "usable %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_ACTUAL_BALLOON)
            vshPrint(ctl, "actual %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_RSS)
            vshPrint(ctl, "rss %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_LAST_UPDATE)
            vshPrint(ctl, "last_update %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_DISK_CACHES)
            vshPrint(ctl, "disk_caches %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_HUGETLB_PGALLOC)
            vshPrint(ctl, "hugetlb_pgalloc %llu\n", stats[i].val);
        if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_HUGETLB_PGFAIL)
            vshPrint(ctl, "hugetlb_pgfail %llu\n", stats[i].val);
    }

    return true;
}

/*
 * "domblkinfo" command
 */
static const vshCmdInfo info_domblkinfo[] = {
    {.name = "help",
     .data = N_("domain block device size information")
    },
    {.name = "desc",
     .data = N_("Get block device size info for a domain.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_domblkinfo[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
    {.name = "device",
     .type = VSH_OT_STRING,
     .completer = virshDomainDiskTargetCompleter,
     .help = N_("block device")
    },
    {.name = "human",
     .type = VSH_OT_BOOL,
     .help = N_("Human readable output")
    },
    {.name = "all",
     .type = VSH_OT_BOOL,
     .help = N_("display all block devices info")
    },
    {.name = NULL}
};

static bool
cmdDomblkinfoGet(const virDomainBlockInfo *info,
                 char **cap,
                 char **alloc,
                 char **phy,
                 bool human)
{
    if (info->capacity == 0 && info->allocation == 0 && info->physical == 0) {
        *cap = g_strdup("-");
        *alloc = g_strdup("-");
        *phy = g_strdup("-");
    } else if (!human) {
        *cap = g_strdup_printf("%llu", info->capacity);
        *alloc = g_strdup_printf("%llu", info->allocation);
        *phy = g_strdup_printf("%llu", info->physical);
    } else {
        double val_cap, val_alloc, val_phy;
        const char *unit_cap, *unit_alloc, *unit_phy;

        val_cap = vshPrettyCapacity(info->capacity, &unit_cap);
        val_alloc = vshPrettyCapacity(info->allocation, &unit_alloc);
        val_phy = vshPrettyCapacity(info->physical, &unit_phy);

        *cap = g_strdup_printf("%.3lf %s", val_cap, unit_cap);
        *alloc = g_strdup_printf("%.3lf %s", val_alloc, unit_alloc);
        *phy = g_strdup_printf("%.3lf %s", val_phy, unit_phy);
    }

    return true;
}


static bool
cmdDomblkinfo(vshControl *ctl, const vshCmd *cmd)
{
    virDomainBlockInfo info;
    g_autoptr(virshDomain) dom = NULL;
    bool human = false;
    bool all = false;
    const char *device = NULL;
    g_autoptr(xmlDoc) xmldoc = NULL;
    g_autoptr(xmlXPathContext) ctxt = NULL;
    int ndisks;
    size_t i;
    g_autofree xmlNodePtr *disks = NULL;
    g_autoptr(vshTable) table = NULL;

    VSH_EXCLUSIVE_OPTIONS("all", "device");

    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
        return false;

    all = vshCommandOptBool(cmd, "all");
    if (!all && vshCommandOptStringQuiet(ctl, cmd, "device", &device) <= 0) {
        vshError(ctl, "command 'domblkinfo' requires <device> option");
        return false;
    }

    human = vshCommandOptBool(cmd, "human");

    if (all) {
        bool active = virDomainIsActive(dom) == 1;
        int rc;

        if (virshDomainGetXML(ctl, cmd, 0, &xmldoc, &ctxt) < 0)
            return false;

        ndisks = virXPathNodeSet("./devices/disk", ctxt, &disks);
        if (ndisks < 0)
            return false;

        /* title */
        table = vshTableNew(_("Target"), _("Capacity"), _("Allocation"), _("Physical"), NULL);
        if (!table)
            return false;

        for (i = 0; i < ndisks; i++) {
            g_autofree char *target = NULL;
            g_autofree char *protocol = NULL;
            g_autofree char *cap = NULL;
            g_autofree char *alloc = NULL;
            g_autofree char *phy = NULL;

            ctxt->node = disks[i];
            protocol = virXPathString("string(./source/@protocol)", ctxt);
            target = virXPathString("string(./target/@dev)", ctxt);

            if (virXPathBoolean("boolean(./source)", ctxt) == 1) {

                rc = virDomainGetBlockInfo(dom, target, &info, 0);

                if (rc < 0) {
                    /* If protocol is present that's an indication of a
                     * networked storage device which cannot provide statistics,
                     * so generate 0 based data and get the next disk. */
                    if (protocol && !active &&
                        virGetLastErrorCode() == VIR_ERR_INTERNAL_ERROR &&
                        virGetLastErrorDomain() == VIR_FROM_STORAGE) {
                        memset(&info, 0, sizeof(info));
                        vshResetLibvirtError();
                    } else {
                        return false;
                    }
                }
            } else {
                /* if we don't call virDomainGetBlockInfo() who clears 'info'
                 * we have to do it manually */
                memset(&info, 0, sizeof(info));
            }

            if (!cmdDomblkinfoGet(&info, &cap, &alloc, &phy, human))
                return false;
            if (vshTableRowAppend(table, target, cap, alloc, phy, NULL) < 0)
                return false;
        }

        vshTablePrintToStdout(table, ctl);

    } else {
        g_autofree char *cap = NULL;
        g_autofree char *alloc = NULL;
        g_autofree char *phy = NULL;

        if (virDomainGetBlockInfo(dom, device, &info, 0) < 0)
            return false;

        if (!cmdDomblkinfoGet(&info, &cap, &alloc, &phy, human))
            return false;
        vshPrint(ctl, "%-15s %s\n", _("Capacity:"), cap);
        vshPrint(ctl, "%-15s %s\n", _("Allocation:"), alloc);
        vshPrint(ctl, "%-15s %s\n", _("Physical:"), phy);
    }

    return true;
}

/*
 * "domblklist" command
 */
static const vshCmdInfo info_domblklist[] = {
    {.name = "help",
     .data = N_("list all domain blocks")
    },
    {.name = "desc",
     .data = N_("Get the summary of block devices for a domain.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_domblklist[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
    {.name = "inactive",
     .type = VSH_OT_BOOL,
     .help = N_("get inactive rather than running configuration")
    },
    {.name = "details",
     .type = VSH_OT_BOOL,
     .help = N_("additionally display the type and device value")
    },
    {.name = NULL}
};

static bool
cmdDomblklist(vshControl *ctl, const vshCmd *cmd)
{
    unsigned int flags = 0;
    g_autoptr(xmlDoc) xmldoc = NULL;
    g_autoptr(xmlXPathContext) ctxt = NULL;
    int ndisks;
    g_autofree xmlNodePtr *disks = NULL;
    size_t i;
    bool details = false;
    g_autoptr(vshTable) table = NULL;

    if (vshCommandOptBool(cmd, "inactive"))
        flags |= VIR_DOMAIN_XML_INACTIVE;

    details = vshCommandOptBool(cmd, "details");

    if (virshDomainGetXML(ctl, cmd, flags, &xmldoc, &ctxt) < 0)
        return false;

    ndisks = virXPathNodeSet("./devices/disk", ctxt, &disks);
    if (ndisks < 0)
        return false;

    if (details)
        table = vshTableNew(_("Type"), _("Device"), _("Target"), _("Source"), NULL);
    else
        table = vshTableNew(_("Target"), _("Source"), NULL);

    if (!table)
        return false;

    for (i = 0; i < ndisks; i++) {
        g_autofree char *type = NULL;
        g_autofree char *device = NULL;
        g_autofree char *target = NULL;
        g_autofree char *source = NULL;

        ctxt->node = disks[i];

        type = virXPathString("string(./@type)", ctxt);
        if (details) {
            device = virXPathString("string(./@device)", ctxt);
            if (!type || !device) {
                vshPrint(ctl, "unable to query block list details");
                return false;
            }
        }

        target = virXPathString("string(./target/@dev)", ctxt);
        if (!target) {
            vshError(ctl, "unable to query block list");
            return false;
        }

        if (STREQ_NULLABLE(type, "nvme")) {
            g_autofree char *namespace = NULL;
            virPCIDeviceAddress addr = { 0 };
            xmlNodePtr addrNode = NULL;

            if (!(namespace = virXPathString("string(./source/@namespace)", ctxt)) ||
                !(addrNode = virXPathNode("./source/address", ctxt)) ||
                virPCIDeviceAddressParseXML(addrNode, &addr) < 0) {
                vshError(ctl, "Unable to query NVMe disk address");
                return false;
            }

            source = g_strdup_printf("nvme://%04x:%02x:%02x.%d/%s",
                                     addr.domain, addr.bus, addr.slot,
                                     addr.function, namespace);
        } else {
            source = virXPathString("string(./source/@file"
                                    "|./source/@dev"
                                    "|./source/@dir"
                                    "|./source/@name"
                                    "|./source/@volume"
                                    "|./source/@path)", ctxt);
        }

        if (details) {
            if (vshTableRowAppend(table, type, device, target,
                                  NULLSTR_MINUS(source), NULL) < 0)
                return false;
        } else {
            if (vshTableRowAppend(table, target,
                                  NULLSTR_MINUS(source), NULL) < 0)
                return false;
        }
    }

    vshTablePrintToStdout(table, ctl);

    return true;
}

/*
 * "domiflist" command
 */
static const vshCmdInfo info_domiflist[] = {
    {"help", N_("list all domain virtual interfaces")},
    {"desc", N_("Get the summary of virtual interfaces for a domain.")},
    {NULL, NULL}
};

static const vshCmdOptDef opts_domiflist[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
    {.name = "inactive",
     .type = VSH_OT_BOOL,
     .help = N_("get inactive rather than running configuration")
    },
    {.name = NULL}
};

static bool
cmdDomiflist(vshControl *ctl, const vshCmd *cmd)
{
    unsigned int flags = 0;
    g_autoptr(xmlDoc) xmldoc = NULL;
    g_autoptr(xmlXPathContext) ctxt = NULL;
    int ninterfaces;
    g_autofree xmlNodePtr *interfaces = NULL;
    size_t i;
    g_autoptr(vshTable) table = NULL;

    if (vshCommandOptBool(cmd, "inactive"))
        flags |= VIR_DOMAIN_XML_INACTIVE;

    if (virshDomainGetXML(ctl, cmd, flags, &xmldoc, &ctxt) < 0)
        return false;

    ninterfaces = virXPathNodeSet("./devices/interface", ctxt, &interfaces);
    if (ninterfaces < 0)
        return false;

    table = vshTableNew(_("Interface"), _("Type"),
                        _("Source"), _("Model"), _("MAC"), NULL);
    if (!table)
        return false;

    for (i = 0; i < ninterfaces; i++) {
        g_autofree char *type = NULL;
        g_autofree char *source = NULL;
        g_autofree char *target = NULL;
        g_autofree char *model = NULL;
        g_autofree char *mac = NULL;

        ctxt->node = interfaces[i];
        type = virXPathString("string(./@type)", ctxt);

        source = virXPathString("string(./source/@bridge"
                                "|./source/@dev"
                                "|./source/@network"
                                "|./source/@name"
                                "|./source/@path)", ctxt);

        target = virXPathString("string(./target/@dev)", ctxt);
        model = virXPathString("string(./model/@type)", ctxt);
        mac = virXPathString("string(./mac/@address)", ctxt);

        if (vshTableRowAppend(table,
                              target ? target : "-",
                              type,
                              source ? source : "-",
                              model ? model : "-",
                              mac ? mac : "-",
                              NULL) < 0)
            return false;
    }

    vshTablePrintToStdout(table, ctl);

    return true;
}

/*
 * "domif-getlink" command
 */
static const vshCmdInfo info_domif_getlink[] = {
    {.name = "help",
     .data = N_("get link state of a virtual interface")
    },
    {.name = "desc",
     .data = N_("Get link state of a domain's virtual interface.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_domif_getlink[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
    {.name = "interface",
     .type = VSH_OT_DATA,
     .flags = VSH_OFLAG_REQ,
     .completer = virshDomainInterfaceCompleter,
     .help = N_("interface device (MAC Address)")
    },
    {.name = "persistent",
     .type = VSH_OT_ALIAS,
     .help = "config"
    },
    VIRSH_COMMON_OPT_CONFIG(N_("Get persistent interface state")),
    {.name = NULL}
};

static bool
cmdDomIfGetLink(vshControl *ctl, const vshCmd *cmd)
{
    const char *iface = NULL;
    g_autofree char *state = NULL;
    g_autofree char *xpath = NULL;
    virMacAddr macaddr;
    char macstr[VIR_MAC_STRING_BUFLEN] = "";
    g_autoptr(xmlDoc) xml = NULL;
    g_autoptr(xmlXPathContext) ctxt = NULL;
    g_autofree xmlNodePtr *interfaces = NULL;
    int ninterfaces;
    unsigned int flags = 0;

    if (vshCommandOptStringReq(ctl, cmd, "interface", &iface) < 0)
        return false;

    if (vshCommandOptBool(cmd, "config"))
        flags = VIR_DOMAIN_XML_INACTIVE;

    if (virshDomainGetXML(ctl, cmd, flags, &xml, &ctxt) < 0)
        return false;

    /* normalize the mac addr */
    if (virMacAddrParse(iface, &macaddr) == 0)
        virMacAddrFormat(&macaddr, macstr);

    xpath = g_strdup_printf("/domain/devices/interface[(mac/@address = '%s') or "
                            "                          (target/@dev = '%s')]", macstr,
                            iface);

    if ((ninterfaces = virXPathNodeSet(xpath, ctxt, &interfaces)) < 0) {
        vshError(ctl, _("Failed to extract interface information"));
        return false;
    }

    if (ninterfaces < 1) {
        if (macstr[0])
            vshError(ctl, _("Interface (mac: %s) not found."), macstr);
        else
            vshError(ctl, _("Interface (dev: %s) not found."), iface);

        return false;
    } else if (ninterfaces > 1) {
        vshError(ctl, _("multiple matching interfaces found"));
        return false;
    }

    ctxt->node = interfaces[0];

    if ((state = virXPathString("string(./link/@state)", ctxt)))
        vshPrint(ctl, "%s %s", iface, state);
    else
        vshPrint(ctl, "%s up", iface);

    return true;
}

/*
 * "domcontrol" command
 */
static const vshCmdInfo info_domcontrol[] = {
    {.name = "help",
     .data = N_("domain control interface state")
    },
    {.name = "desc",
     .data = N_("Returns state of a control interface to the domain.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_domcontrol[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE),
    {.name = NULL}
};

static bool
cmdDomControl(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshDomain) dom = NULL;
    virDomainControlInfo info;

    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
        return false;

    if (virDomainGetControlInfo(dom, &info, 0) < 0)
        return false;

    if (info.state != VIR_DOMAIN_CONTROL_OK &&
        info.state != VIR_DOMAIN_CONTROL_ERROR) {
        vshPrint(ctl, "%s (%0.3fs)\n",
                 virshDomainControlStateToString(info.state),
                 info.stateTime / 1000.0);
    } else if (info.state == VIR_DOMAIN_CONTROL_ERROR && info.details > 0) {
        vshPrint(ctl, "%s: %s\n",
                 virshDomainControlStateToString(info.state),
                 virshDomainControlErrorReasonToString(info.details));
    } else {
        vshPrint(ctl, "%s\n",
                 virshDomainControlStateToString(info.state));
    }

    return true;
}

/*
 * "domblkstat" command
 */
static const vshCmdInfo info_domblkstat[] = {
    {.name = "help",
     .data = N_("get device block stats for a domain")
    },
    {.name = "desc",
     .data = N_("Get device block stats for a running domain. See man page or "
                "use --human for explanation of fields")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_domblkstat[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE),
    {.name = "device",
     .type = VSH_OT_STRING,
     .flags = VSH_OFLAG_EMPTY_OK,
     .completer = virshDomainDiskTargetCompleter,
     .help = N_("block device")
    },
    {.name = "human",
     .type = VSH_OT_BOOL,
     .help = N_("print a more human readable output")
    },
    {.name = NULL}
};

struct _domblkstat_sequence {
    const char *field;  /* field name */
    const char *legacy; /* legacy name from previous releases */
    const char *human;  /* human-friendly explanation */
};

/* sequence of values for output to honor legacy format from previous
 * versions */
static const struct _domblkstat_sequence domblkstat_output[] = {
    { VIR_DOMAIN_BLOCK_STATS_READ_REQ,          "rd_req",
      N_("number of read operations:") }, /* 0 */
    { VIR_DOMAIN_BLOCK_STATS_READ_BYTES,        "rd_bytes",
      N_("number of bytes read:") }, /* 1 */
    { VIR_DOMAIN_BLOCK_STATS_WRITE_REQ,         "wr_req",
      N_("number of write operations:") }, /* 2 */
    { VIR_DOMAIN_BLOCK_STATS_WRITE_BYTES,       "wr_bytes",
      N_("number of bytes written:") }, /* 3 */
    { VIR_DOMAIN_BLOCK_STATS_ERRS,              "errs",
      N_("error count:") }, /* 4 */
    { VIR_DOMAIN_BLOCK_STATS_FLUSH_REQ,         NULL,
      N_("number of flush operations:") }, /* 5 */
    { VIR_DOMAIN_BLOCK_STATS_READ_TOTAL_TIMES,  NULL,
      N_("total duration of reads (ns):") }, /* 6 */
    { VIR_DOMAIN_BLOCK_STATS_WRITE_TOTAL_TIMES, NULL,
      N_("total duration of writes (ns):") }, /* 7 */
    { VIR_DOMAIN_BLOCK_STATS_FLUSH_TOTAL_TIMES, NULL,
      N_("total duration of flushes (ns):") }, /* 8 */
    { NULL, NULL, NULL }
};

#define DOMBLKSTAT_LEGACY_PRINT(ID, VALUE) \
    if (VALUE >= 0) \
        vshPrint(ctl, "%s %-*s %lld\n", device, \
                 human ? 31 : 0, \
                 human ? _(domblkstat_output[ID].human) \
                 : domblkstat_output[ID].legacy, \
                 VALUE);

static bool
cmdDomblkstat(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshDomain) dom = NULL;
    const char *name = NULL, *device = NULL;
    virDomainBlockStatsStruct stats;
    g_autofree virTypedParameterPtr params = NULL;
    virTypedParameterPtr par = NULL;
    const char *field = NULL;
    int rc, nparams = 0;
    size_t i;
    bool human = vshCommandOptBool(cmd, "human"); /* human readable output */

    if (!(dom = virshCommandOptDomain(ctl, cmd, &name)))
        return false;

    /* device argument is optional now. if it's missing, supply empty
       string to denote 'all devices'. A NULL device arg would violate
       API contract.
     */
    if (vshCommandOptStringReq(ctl, cmd, "device", &device) < 0)
        return false;

    if (!device)
        device = "";

    rc = virDomainBlockStatsFlags(dom, device, NULL, &nparams, 0);

    /* It might fail when virDomainBlockStatsFlags is not
     * supported on older libvirt, fallback to use virDomainBlockStats
     * then.
     */
    if (rc < 0) {
        /* try older API if newer is not supported */
        if (last_error->code != VIR_ERR_NO_SUPPORT)
            return false;

        vshResetLibvirtError();

        if (virDomainBlockStats(dom, device, &stats,
                                sizeof(stats)) == -1) {
            vshError(ctl, _("Failed to get block stats %s %s"),
                     name, device);
            return false;
        }

        /* human friendly output */
        if (human) {
            vshPrint(ctl, N_("Device: %s\n"), device);
            device = "";
        }

        DOMBLKSTAT_LEGACY_PRINT(0, stats.rd_req);
        DOMBLKSTAT_LEGACY_PRINT(1, stats.rd_bytes);
        DOMBLKSTAT_LEGACY_PRINT(2, stats.wr_req);
        DOMBLKSTAT_LEGACY_PRINT(3, stats.wr_bytes);
        DOMBLKSTAT_LEGACY_PRINT(4, stats.errs);
    } else {
        params = g_new0(virTypedParameter, nparams);
        if (virDomainBlockStatsFlags(dom, device, params, &nparams, 0) < 0) {
            vshError(ctl, _("Failed to get block stats for domain '%s' device '%s'"), name, device);
            return false;
        }

        /* set for prettier output */
        if (human) {
            vshPrint(ctl, N_("Device: %s\n"), device);
            device = "";
        }

        /* at first print all known values in desired order */
        for (i = 0; domblkstat_output[i].field != NULL; i++) {
            g_autofree char *value = NULL;

            if (!(par = virTypedParamsGet(params, nparams,
                                          domblkstat_output[i].field)))
                continue;

            value = vshGetTypedParamValue(ctl, par);

            /* to print other not supported fields, mark the already printed */
            par->field[0] = '\0'; /* set the name to empty string */

            /* translate into human readable or legacy spelling */
            field = NULL;
            if (human)
                field = _(domblkstat_output[i].human);
            else
                field = domblkstat_output[i].legacy;

            /* use the provided spelling if no translation is available */
            if (!field)
                field = domblkstat_output[i].field;

            vshPrint(ctl, "%s %-*s %s\n", device,
                     human ? 31 : 0, field, value);
        }

        /* go through the fields again, for remaining fields */
        for (i = 0; i < nparams; i++) {
            g_autofree char *value = NULL;

            if (!*params[i].field)
                continue;

            value = vshGetTypedParamValue(ctl, params+i);
            vshPrint(ctl, "%s %s %s\n", device, params[i].field, value);
        }
    }

    return true;
}
#undef DOMBLKSTAT_LEGACY_PRINT

/*
 * "domifstat" command
 */
static const vshCmdInfo info_domifstat[] = {
    {.name = "help",
     .data = N_("get network interface stats for a domain")
    },
    {.name = "desc",
     .data = N_("Get network interface stats for a running domain.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_domifstat[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE),
    {.name = "interface",
     .type = VSH_OT_DATA,
     .flags = VSH_OFLAG_REQ,
     .completer = virshDomainInterfaceCompleter,
     .help = N_("interface device specified by name or MAC Address")
    },
    {.name = NULL}
};

static bool
cmdDomIfstat(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshDomain) dom = NULL;
    const char *name = NULL, *device = NULL;
    virDomainInterfaceStatsStruct stats;

    if (!(dom = virshCommandOptDomain(ctl, cmd, &name)))
        return false;

    if (vshCommandOptStringReq(ctl, cmd, "interface", &device) < 0)
        return false;

    if (virDomainInterfaceStats(dom, device, &stats, sizeof(stats)) == -1) {
        vshError(ctl, _("Failed to get interface stats %s %s"), name, device);
        return false;
    }

    if (stats.rx_bytes >= 0)
        vshPrint(ctl, "%s rx_bytes %lld\n", device, stats.rx_bytes);

    if (stats.rx_packets >= 0)
        vshPrint(ctl, "%s rx_packets %lld\n", device, stats.rx_packets);

    if (stats.rx_errs >= 0)
        vshPrint(ctl, "%s rx_errs %lld\n", device, stats.rx_errs);

    if (stats.rx_drop >= 0)
        vshPrint(ctl, "%s rx_drop %lld\n", device, stats.rx_drop);

    if (stats.tx_bytes >= 0)
        vshPrint(ctl, "%s tx_bytes %lld\n", device, stats.tx_bytes);

    if (stats.tx_packets >= 0)
        vshPrint(ctl, "%s tx_packets %lld\n", device, stats.tx_packets);

    if (stats.tx_errs >= 0)
        vshPrint(ctl, "%s tx_errs %lld\n", device, stats.tx_errs);

    if (stats.tx_drop >= 0)
        vshPrint(ctl, "%s tx_drop %lld\n", device, stats.tx_drop);

    return true;
}

/*
 * "domblkerror" command
 */
static const vshCmdInfo info_domblkerror[] = {
    {.name = "help",
     .data = N_("Show errors on block devices")
    },
    {.name = "desc",
     .data = N_("Show block device errors")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_domblkerror[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE),
    {.name = NULL}
};

static bool
cmdDomBlkError(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshDomain) dom = NULL;
    virDomainDiskErrorPtr disks = NULL;
    unsigned int ndisks = 0;
    size_t i;
    int count;
    bool ret = false;

    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
        return false;

    if ((count = virDomainGetDiskErrors(dom, NULL, 0, 0)) < 0)
        goto cleanup;

    if (count > 0) {
        disks = g_new0(virDomainDiskError, count);
        ndisks = count;

        if ((count = virDomainGetDiskErrors(dom, disks, ndisks, 0)) == -1)
            goto cleanup;
    }

    if (count == 0) {
        vshPrint(ctl, _("No errors found\n"));
    } else {
        for (i = 0; i < count; i++) {
            vshPrint(ctl, "%s: %s\n",
                     disks[i].disk,
                     virshDomainIOErrorToString(disks[i].error));
        }
    }

    ret = true;

 cleanup:
    for (i = 0; i < ndisks; i++)
        VIR_FREE(disks[i].disk);
    VIR_FREE(disks);
    return ret;
}

/*
 * "dominfo" command
 */
static const vshCmdInfo info_dominfo[] = {
    {.name = "help",
     .data = N_("domain information")
    },
    {.name = "desc",
     .data = N_("Returns basic information about the domain.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_dominfo[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
    {.name = NULL}
};

static bool
cmdDominfo(vshControl *ctl, const vshCmd *cmd)
{
    virDomainInfo info;
    g_autoptr(virshDomain) dom = NULL;
    virSecurityModel secmodel;
    virSecurityLabelPtr seclabel;
    int persistent = 0;
    bool ret = true;
    int autostart;
    unsigned int id;
    char uuid[VIR_UUID_STRING_BUFLEN];
    g_autofree char *ostype = NULL;
    int has_managed_save = 0;
    virshControl *priv = ctl->privData;
    g_auto(GStrv) messages = NULL;

    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
        return false;

    id = virDomainGetID(dom);
    if (id == ((unsigned int)-1))
        vshPrint(ctl, "%-15s %s\n", _("Id:"), "-");
    else
        vshPrint(ctl, "%-15s %d\n", _("Id:"), id);
    vshPrint(ctl, "%-15s %s\n", _("Name:"), virDomainGetName(dom));

    if (virDomainGetUUIDString(dom, &uuid[0]) == 0)
        vshPrint(ctl, "%-15s %s\n", _("UUID:"), uuid);

    if ((ostype = virDomainGetOSType(dom)))
        vshPrint(ctl, "%-15s %s\n", _("OS Type:"), ostype);

    if (virDomainGetInfo(dom, &info) == 0) {
        vshPrint(ctl, "%-15s %s\n", _("State:"),
                 virshDomainStateToString(info.state));

        vshPrint(ctl, "%-15s %d\n", _("CPU(s):"), info.nrVirtCpu);

        if (info.cpuTime != 0) {
            double cpuUsed = info.cpuTime;

            cpuUsed /= 1000000000.0;

            vshPrint(ctl, "%-15s %.1lfs\n", _("CPU time:"), cpuUsed);
        }

        if (info.maxMem != UINT_MAX)
            vshPrint(ctl, "%-15s %lu KiB\n", _("Max memory:"),
                 info.maxMem);
        else
            vshPrint(ctl, "%-15s %s\n", _("Max memory:"),
                 _("no limit"));

        vshPrint(ctl, "%-15s %lu KiB\n", _("Used memory:"),
                 info.memory);

    } else {
        ret = false;
    }

    /* Check and display whether the domain is persistent or not */
    persistent = virDomainIsPersistent(dom);
    vshDebug(ctl, VSH_ERR_DEBUG, "Domain persistent flag value: %d\n",
             persistent);
    if (persistent < 0)
        vshPrint(ctl, "%-15s %s\n", _("Persistent:"), _("unknown"));
    else
        vshPrint(ctl, "%-15s %s\n", _("Persistent:"), persistent ? _("yes") : _("no"));

    /* Check and display whether the domain autostarts or not */
    if (!virDomainGetAutostart(dom, &autostart)) {
        vshPrint(ctl, "%-15s %s\n", _("Autostart:"),
                 autostart ? _("enable") : _("disable"));
    }

    has_managed_save = virDomainHasManagedSaveImage(dom, 0);
    if (has_managed_save < 0)
        vshPrint(ctl, "%-15s %s\n", _("Managed save:"), _("unknown"));
    else
        vshPrint(ctl, "%-15s %s\n", _("Managed save:"),
                 has_managed_save ? _("yes") : _("no"));

    /* Security model and label information */
    memset(&secmodel, 0, sizeof(secmodel));
    if (virNodeGetSecurityModel(priv->conn, &secmodel) == -1) {
        if (last_error->code != VIR_ERR_NO_SUPPORT) {
            return false;
        } else {
            vshResetLibvirtError();
        }
    } else {
        /* Only print something if a security model is active */
        if (secmodel.model[0] != '\0') {
            vshPrint(ctl, "%-15s %s\n", _("Security model:"), secmodel.model);
            vshPrint(ctl, "%-15s %s\n", _("Security DOI:"), secmodel.doi);

            /* Security labels are only valid for active domains */
            seclabel = g_new0(virSecurityLabel, 1);

            if (virDomainGetSecurityLabel(dom, seclabel) == -1) {
                VIR_FREE(seclabel);
                return false;
            } else {
                if (seclabel->label[0] != '\0')
                    vshPrint(ctl, "%-15s %s (%s)\n", _("Security label:"),
                             seclabel->label, seclabel->enforcing ? "enforcing" : "permissive");
            }

            VIR_FREE(seclabel);
        }
    }

    if (virDomainGetMessages(dom, &messages, 0) > 0) {
        size_t i;
        for (i = 0; messages[i] != NULL; i++) {
            vshPrint(ctl, "%-15s %s\n",
                     i == 0 ? _("Messages:") : "", messages[i]);
        }
    }

    return ret;
}

/*
 * "domstate" command
 */
static const vshCmdInfo info_domstate[] = {
    {.name = "help",
     .data = N_("domain state")
    },
    {.name = "desc",
     .data = N_("Returns state about a domain.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_domstate[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
    {.name = "reason",
     .type = VSH_OT_BOOL,
     .help = N_("also print reason for the state")
    },
    {.name = NULL}
};

static bool
cmdDomstate(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshDomain) dom = NULL;
    bool showReason = vshCommandOptBool(cmd, "reason");
    int state, reason;

    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
        return false;

    if ((state = virshDomainState(ctl, dom, &reason)) < 0)
        return false;

    if (showReason) {
        vshPrint(ctl, "%s (%s)\n",
                 virshDomainStateToString(state),
                 virshDomainStateReasonToString(state, reason));
    } else {
        vshPrint(ctl, "%s\n",
                 virshDomainStateToString(state));
    }

    return true;
}

/*
 * "domtime" command
 */
static const vshCmdInfo info_domtime[] = {
    {.name = "help",
     .data = N_("domain time")
    },
    {.name = "desc",
     .data = N_("Gets or sets the domain's system time")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_domtime[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE),
    {.name = "now",
     .type = VSH_OT_BOOL,
     .help = N_("set to the time of the host running virsh")
    },
    {.name = "pretty",
     .type = VSH_OT_BOOL,
     .help = N_("print domain's time in human readable form")
    },
    {.name = "sync",
     .type = VSH_OT_BOOL,
     .help = N_("instead of setting given time, synchronize from domain's RTC"),
    },
    {.name = "time",
     .type = VSH_OT_INT,
     .help = N_("time to set")
    },
    {.name = NULL}
};

static bool
cmdDomTime(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshDomain) dom = NULL;
    bool now = vshCommandOptBool(cmd, "now");
    bool pretty = vshCommandOptBool(cmd, "pretty");
    bool rtcSync = vshCommandOptBool(cmd, "sync");
    long long seconds = 0;
    unsigned int nseconds = 0;
    unsigned int flags = 0;
    bool doSet = false;
    int rv;

    VSH_EXCLUSIVE_OPTIONS("time", "now");
    VSH_EXCLUSIVE_OPTIONS("time", "sync");
    VSH_EXCLUSIVE_OPTIONS("now", "sync");

    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
        return false;

    rv = vshCommandOptLongLong(ctl, cmd, "time", &seconds);

    if (rv < 0) {
        /* invalid integer format */
        return false;
    } else if (rv > 0) {
        /* valid integer to set */
        doSet = true;
    }

    if (doSet || now || rtcSync) {
        if (now && ((seconds = time(NULL)) == (time_t) -1)) {
            vshError(ctl, _("Unable to get current time"));
            return false;
        }

        if (rtcSync)
            flags |= VIR_DOMAIN_TIME_SYNC;

        if (virDomainSetTime(dom, seconds, nseconds, flags) < 0)
            return false;

    } else {
        if (virDomainGetTime(dom, &seconds, &nseconds, flags) < 0)
            return false;

        if (pretty) {
            g_autoptr(GDateTime) then = NULL;
            g_autofree char *thenstr = NULL;

            then = g_date_time_new_from_unix_utc(seconds);
            thenstr = g_date_time_format(then, "%Y-%m-%d %H:%M:%S");

            vshPrint(ctl, _("Time: %s"), thenstr);
        } else {
            vshPrint(ctl, _("Time: %lld"), seconds);
        }
    }

    return true;
}

/*
 * "list" command
 */
static const vshCmdInfo info_list[] = {
    {.name = "help",
     .data = N_("list domains")
    },
    {.name = "desc",
     .data = N_("Returns list of domains.")
    },
    {.name = NULL}
};

/* compare domains, pack NULLed ones at the end */
static int
virshDomainSorter(const void *a, const void *b)
{
    virDomainPtr *da = (virDomainPtr *) a;
    virDomainPtr *db = (virDomainPtr *) b;
    unsigned int ida;
    unsigned int idb;
    unsigned int inactive = (unsigned int) -1;

    if (*da && !*db)
        return -1;

    if (!*da)
        return *db != NULL;

    ida = virDomainGetID(*da);
    idb = virDomainGetID(*db);

    if (ida == inactive && idb == inactive)
        return vshStrcasecmp(virDomainGetName(*da), virDomainGetName(*db));

    if (ida != inactive && idb != inactive) {
        if (ida > idb)
            return 1;
        else if (ida < idb)
            return -1;
    }

    if (ida != inactive)
        return -1;
    else
        return 1;
}

struct virshDomainList {
    virDomainPtr *domains;
    size_t ndomains;
};

static void
virshDomainListFree(struct virshDomainList *domlist)
{
    size_t i;

    if (domlist && domlist->domains) {
        for (i = 0; i < domlist->ndomains; i++)
            virshDomainFree(domlist->domains[i]);
        g_free(domlist->domains);
    }
    g_free(domlist);
}

static struct virshDomainList *
virshDomainListCollect(vshControl *ctl, unsigned int flags)
{
    struct virshDomainList *list = g_new0(struct virshDomainList, 1);
    size_t i;
    int ret;
    int *ids = NULL;
    int nids = 0;
    char **names = NULL;
    int nnames = 0;
    virDomainPtr dom;
    bool success = false;
    size_t deleted = 0;
    int persistent;
    int autostart;
    int state;
    int nsnap;
    int nchk;
    int mansave;
    virshControl *priv = ctl->privData;

    /* try the list with flags support (0.9.13 and later) */
    if ((ret = virConnectListAllDomains(priv->conn, &list->domains,
                                        flags)) >= 0) {
        list->ndomains = ret;
        goto finished;
    }

    /* check if the command is actually supported */
    if (last_error && last_error->code == VIR_ERR_NO_SUPPORT) {
        vshResetLibvirtError();
        goto fallback;
    }

    if (last_error && last_error->code ==  VIR_ERR_INVALID_ARG) {
        /* try the new API again but mask non-guaranteed flags */
        unsigned int newflags = flags & (VIR_CONNECT_LIST_DOMAINS_ACTIVE |
                                         VIR_CONNECT_LIST_DOMAINS_INACTIVE);

        vshResetLibvirtError();
        if ((ret = virConnectListAllDomains(priv->conn, &list->domains,
                                            newflags)) >= 0) {
            list->ndomains = ret;
            goto filter;
        }
    }

    /* there was an error during the first or second call */
    vshError(ctl, "%s", _("Failed to list domains"));
    goto cleanup;


 fallback:
    /* fall back to old method (0.9.12 and older) */
    vshResetLibvirtError();

    /* list active domains, if necessary */
    if (!VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_ACTIVE) ||
        VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_ACTIVE)) {
        if ((nids = virConnectNumOfDomains(priv->conn)) < 0) {
            vshError(ctl, "%s", _("Failed to list active domains"));
            goto cleanup;
        }

        if (nids) {
            ids = g_new0(int, nids);

            if ((nids = virConnectListDomains(priv->conn, ids, nids)) < 0) {
                vshError(ctl, "%s", _("Failed to list active domains"));
                goto cleanup;
            }
        }
    }

    if (!VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_ACTIVE) ||
        VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_INACTIVE)) {
        if ((nnames = virConnectNumOfDefinedDomains(priv->conn)) < 0) {
            vshError(ctl, "%s", _("Failed to list inactive domains"));
            goto cleanup;
        }

        if (nnames) {
            names = g_new0(char *, nnames);

            if ((nnames = virConnectListDefinedDomains(priv->conn, names,
                                                      nnames)) < 0) {
                vshError(ctl, "%s", _("Failed to list inactive domains"));
                goto cleanup;
            }
        }
    }

    list->domains = g_new0(virDomainPtr, nids + nnames);
    list->ndomains = 0;

    /* get active domains */
    for (i = 0; i < nids; i++) {
        if (!(dom = virDomainLookupByID(priv->conn, ids[i])))
            continue;
        list->domains[list->ndomains++] = dom;
    }

    /* get inactive domains */
    for (i = 0; i < nnames; i++) {
        if (!(dom = virDomainLookupByName(priv->conn, names[i])))
            continue;
        list->domains[list->ndomains++] = dom;
    }

    /* truncate domains that weren't found */
    deleted = (nids + nnames) - list->ndomains;

 filter:
    /* filter list the list if the list was acquired by fallback means */
    for (i = 0; i < list->ndomains; i++) {
        dom = list->domains[i];

        /* persistence filter */
        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_PERSISTENT)) {
            if ((persistent = virDomainIsPersistent(dom)) < 0) {
                vshError(ctl, "%s", _("Failed to get domain persistence info"));
                goto cleanup;
            }

            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_PERSISTENT) && persistent) ||
                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_TRANSIENT) && !persistent)))
                goto remove_entry;
        }

        /* domain state filter */
        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_STATE)) {
            if (virDomainGetState(dom, &state, NULL, 0) < 0) {
                vshError(ctl, "%s", _("Failed to get domain state"));
                goto cleanup;
            }

            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_RUNNING) &&
                   state == VIR_DOMAIN_RUNNING) ||
                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_PAUSED) &&
                   state == VIR_DOMAIN_PAUSED) ||
                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_SHUTOFF) &&
                   state == VIR_DOMAIN_SHUTOFF) ||
                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_OTHER) &&
                   (state != VIR_DOMAIN_RUNNING &&
                    state != VIR_DOMAIN_PAUSED &&
                    state != VIR_DOMAIN_SHUTOFF))))
                goto remove_entry;
        }

        /* autostart filter */
        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_AUTOSTART)) {
            if (virDomainGetAutostart(dom, &autostart) < 0) {
                vshError(ctl, "%s", _("Failed to get domain autostart state"));
                goto cleanup;
            }

            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_AUTOSTART) && autostart) ||
                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_AUTOSTART) && !autostart)))
                goto remove_entry;
        }

        /* managed save filter */
        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_MANAGEDSAVE)) {
            if ((mansave = virDomainHasManagedSaveImage(dom, 0)) < 0) {
                vshError(ctl, "%s",
                         _("Failed to check for managed save image"));
                goto cleanup;
            }

            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_MANAGEDSAVE) && mansave) ||
                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_MANAGEDSAVE) && !mansave)))
                goto remove_entry;
        }

        /* snapshot filter */
        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_SNAPSHOT)) {
            if ((nsnap = virDomainSnapshotNum(dom, 0)) < 0) {
                vshError(ctl, "%s", _("Failed to get snapshot count"));
                goto cleanup;
            }
            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT) && nsnap > 0) ||
                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_SNAPSHOT) && nsnap == 0)))
                goto remove_entry;
        }

        /* checkpoint filter */
        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_CHECKPOINT)) {
            if ((nchk = virDomainListAllCheckpoints(dom, NULL, 0)) < 0) {
                vshError(ctl, "%s", _("Failed to get checkpoint count"));
                goto cleanup;
            }
            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT) && nchk > 0) ||
                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_CHECKPOINT) && nchk == 0)))
                goto remove_entry;
        }

        /* the domain matched all filters, it may stay */
        continue;

 remove_entry:
        /* the domain has to be removed as it failed one of the filters */
        g_clear_pointer(&list->domains[i], virshDomainFree);
        deleted++;
    }

 finished:
    /* sort the list */
    if (list->domains && list->ndomains)
        qsort(list->domains, list->ndomains, sizeof(*list->domains),
              virshDomainSorter);

    /* truncate the list if filter simulation deleted entries */
    if (deleted)
        VIR_SHRINK_N(list->domains, list->ndomains, deleted);

    success = true;

 cleanup:
    for (i = 0; nnames != -1 && i < nnames; i++)
        VIR_FREE(names[i]);

    if (!success) {
        g_clear_pointer(&list, virshDomainListFree);
    }

    VIR_FREE(names);
    VIR_FREE(ids);
    return list;
}

static const vshCmdOptDef opts_list[] = {
    {.name = "inactive",
     .type = VSH_OT_BOOL,
     .help = N_("list inactive domains")
    },
    {.name = "all",
     .type = VSH_OT_BOOL,
     .help = N_("list inactive & active domains")
    },
    {.name = "transient",
     .type = VSH_OT_BOOL,
     .help = N_("list transient domains")
    },
    {.name = "persistent",
     .type = VSH_OT_BOOL,
     .help = N_("list persistent domains")
    },
    {.name = "with-snapshot",
     .type = VSH_OT_BOOL,
     .help = N_("list domains with existing snapshot")
    },
    {.name = "without-snapshot",
     .type = VSH_OT_BOOL,
     .help = N_("list domains without a snapshot")
    },
    {.name = "with-checkpoint",
     .type = VSH_OT_BOOL,
     .help = N_("list domains with existing checkpoint")
    },
    {.name = "without-checkpoint",
     .type = VSH_OT_BOOL,
     .help = N_("list domains without a checkpoint")
    },
    {.name = "state-running",
     .type = VSH_OT_BOOL,
     .help = N_("list domains in running state")
    },
    {.name = "state-paused",
     .type = VSH_OT_BOOL,
     .help = N_("list domains in paused state")
    },
    {.name = "state-shutoff",
     .type = VSH_OT_BOOL,
     .help = N_("list domains in shutoff state")
    },
    {.name = "state-other",
     .type = VSH_OT_BOOL,
     .help = N_("list domains in other states")
    },
    {.name = "autostart",
     .type = VSH_OT_BOOL,
     .help = N_("list domains with autostart enabled")
    },
    {.name = "no-autostart",
     .type = VSH_OT_BOOL,
     .help = N_("list domains with autostart disabled")
    },
    {.name = "with-managed-save",
     .type = VSH_OT_BOOL,
     .help = N_("list domains with managed save state")
    },
    {.name = "without-managed-save",
     .type = VSH_OT_BOOL,
     .help = N_("list domains without managed save")
    },
    {.name = "uuid",
     .type = VSH_OT_BOOL,
     .help = N_("list uuid's only")
    },
    {.name = "name",
     .type = VSH_OT_BOOL,
     .help = N_("list domain names only")
    },
    {.name = "id",
     .type = VSH_OT_BOOL,
     .help = N_("list domain IDs only")
    },
    {.name = "table",
     .type = VSH_OT_BOOL,
     .help = N_("list table (default)")
    },
    {.name = "managed-save",
     .type = VSH_OT_BOOL,
     .help = N_("mark inactive domains with managed save state")
    },
    {.name = "title",
     .type = VSH_OT_BOOL,
     .help = N_("show domain title")
    },
    {.name = NULL}
};

#define FILTER(NAME, FLAG) \
    if (vshCommandOptBool(cmd, NAME)) \
        flags |= (FLAG)
static bool
cmdList(vshControl *ctl, const vshCmd *cmd)
{
    bool managed = vshCommandOptBool(cmd, "managed-save");
    bool optTitle = vshCommandOptBool(cmd, "title");
    bool optTable = vshCommandOptBool(cmd, "table");
    bool optUUID = vshCommandOptBool(cmd, "uuid");
    bool optName = vshCommandOptBool(cmd, "name");
    bool optID = vshCommandOptBool(cmd, "id");
    size_t i;
    char uuid[VIR_UUID_STRING_BUFLEN];
    int state;
    bool ret = false;
    struct virshDomainList *list = NULL;
    virDomainPtr dom;
    char id_buf[VIR_INT64_STR_BUFLEN];
    unsigned int id;
    unsigned int flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE;
    g_autoptr(vshTable) table = NULL;

    /* construct filter flags */
    if (vshCommandOptBool(cmd, "inactive") ||
        vshCommandOptBool(cmd, "state-shutoff"))
        flags = VIR_CONNECT_LIST_DOMAINS_INACTIVE;

    if (vshCommandOptBool(cmd, "all"))
        flags = VIR_CONNECT_LIST_DOMAINS_INACTIVE |
                VIR_CONNECT_LIST_DOMAINS_ACTIVE;

    FILTER("persistent", VIR_CONNECT_LIST_DOMAINS_PERSISTENT);
    FILTER("transient",  VIR_CONNECT_LIST_DOMAINS_TRANSIENT);

    FILTER("with-managed-save",    VIR_CONNECT_LIST_DOMAINS_MANAGEDSAVE);
    FILTER("without-managed-save", VIR_CONNECT_LIST_DOMAINS_NO_MANAGEDSAVE);

    FILTER("autostart",    VIR_CONNECT_LIST_DOMAINS_AUTOSTART);
    FILTER("no-autostart", VIR_CONNECT_LIST_DOMAINS_NO_AUTOSTART);

    FILTER("with-snapshot",    VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT);
    FILTER("without-snapshot", VIR_CONNECT_LIST_DOMAINS_NO_SNAPSHOT);

    FILTER("with-checkpoint",    VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT);
    FILTER("without-checkpoint", VIR_CONNECT_LIST_DOMAINS_NO_CHECKPOINT);

    FILTER("state-running", VIR_CONNECT_LIST_DOMAINS_RUNNING);
    FILTER("state-paused",  VIR_CONNECT_LIST_DOMAINS_PAUSED);
    FILTER("state-shutoff", VIR_CONNECT_LIST_DOMAINS_SHUTOFF);
    FILTER("state-other",   VIR_CONNECT_LIST_DOMAINS_OTHER);

    VSH_EXCLUSIVE_OPTIONS("table", "name");
    VSH_EXCLUSIVE_OPTIONS("table", "uuid");
    VSH_EXCLUSIVE_OPTIONS("table", "id");

    if (!optUUID && !optName && !optID)
        optTable = true;

    if (!(list = virshDomainListCollect(ctl, flags)))
        goto cleanup;

    /* print table header in legacy mode */
    if (optTable) {
        if (optTitle)
            table = vshTableNew(_("Id"), _("Name"), _("State"), _("Title"), NULL);
        else
            table = vshTableNew(_("Id"), _("Name"), _("State"), NULL);

        if (!table)
            goto cleanup;
    }

    for (i = 0; i < list->ndomains; i++) {
        const char *sep = "";

        dom = list->domains[i];
        id = virDomainGetID(dom);
        if (id != (unsigned int) -1)
            g_snprintf(id_buf, sizeof(id_buf), "%d", id);
        else
            ignore_value(virStrcpyStatic(id_buf, "-"));

        if (optTable) {
            state = virshDomainState(ctl, dom, NULL);

            /* Domain could've been removed in the meantime */
            if (state < 0)
                continue;

            if (managed && state == VIR_DOMAIN_SHUTOFF &&
                virDomainHasManagedSaveImage(dom, 0) > 0)
                state = -2;

            if (optTitle) {
                g_autofree char *title = NULL;

                if (!(title = virshGetDomainDescription(ctl, dom, true, 0)))
                    goto cleanup;
                if (vshTableRowAppend(table, id_buf,
                                      virDomainGetName(dom),
                                      state == -2 ? _("saved")
                                      : virshDomainStateToString(state),
                                      title, NULL) < 0)
                    goto cleanup;
            } else {
                if (vshTableRowAppend(table, id_buf,
                                      virDomainGetName(dom),
                                      state == -2 ? _("saved")
                                      : virshDomainStateToString(state),
                                      NULL) < 0)
                    goto cleanup;
            }

        } else {
            if (optUUID) {
                if (virDomainGetUUIDString(dom, uuid) < 0) {
                    vshError(ctl, "%s", _("Failed to get domain's UUID"));
                    goto cleanup;
                }
                vshPrint(ctl, "%s", uuid);
                sep = " ";
            }
            if (optID) {
                /* If we are asked to print IDs only then do that
                 * only for live domains. */
                if (id == (unsigned int) -1 && !optUUID && !optName)
                    continue;
                vshPrint(ctl, "%s%s", sep, id_buf);
                sep = " ";
            }
            if (optName) {
                vshPrint(ctl, "%s%s", sep, virDomainGetName(dom));
                sep = " ";
            }
            vshPrint(ctl, "\n");
        }
    }

    if (optTable)
        vshTablePrintToStdout(table, ctl);

    ret = true;
 cleanup:
    virshDomainListFree(list);
    return ret;
}
#undef FILTER

/*
 * "domstats" command
 */
static const vshCmdInfo info_domstats[] = {
    {.name = "help",
     .data = N_("get statistics about one or multiple domains")
    },
    {.name = "desc",
     .data = N_("Gets statistics about one or more (or all) domains")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_domstats[] = {
    {.name = "state",
     .type = VSH_OT_BOOL,
     .help = N_("report domain state"),
    },
    {.name = "cpu-total",
     .type = VSH_OT_BOOL,
     .help = N_("report domain physical cpu usage"),
    },
    {.name = "balloon",
     .type = VSH_OT_BOOL,
     .help = N_("report domain balloon statistics"),
    },
    {.name = "vcpu",
     .type = VSH_OT_BOOL,
     .help = N_("report domain virtual cpu information"),
    },
    {.name = "interface",
     .type = VSH_OT_BOOL,
     .help = N_("report domain network interface information"),
    },
    {.name = "block",
     .type = VSH_OT_BOOL,
     .help = N_("report domain block device statistics"),
    },
    {.name = "perf",
     .type = VSH_OT_BOOL,
     .help = N_("report domain perf event statistics"),
    },
    {.name = "iothread",
     .type = VSH_OT_BOOL,
     .help = N_("report domain IOThread information"),
    },
    {.name = "memory",
     .type = VSH_OT_BOOL,
     .help = N_("report domain memory usage"),
    },
    {.name = "dirtyrate",
     .type = VSH_OT_BOOL,
     .help = N_("report domain dirty rate information"),
    },
    {.name = "list-active",
     .type = VSH_OT_BOOL,
     .help = N_("list only active domains"),
    },
    {.name = "list-inactive",
     .type = VSH_OT_BOOL,
     .help = N_("list only inactive domains"),
    },
    {.name = "list-persistent",
     .type = VSH_OT_BOOL,
     .help = N_("list only persistent domains"),
    },
    {.name = "list-transient",
     .type = VSH_OT_BOOL,
     .help = N_("list only transient domains"),
    },
    {.name = "list-running",
     .type = VSH_OT_BOOL,
     .help = N_("list only running domains"),
    },
    {.name = "list-paused",
     .type = VSH_OT_BOOL,
     .help = N_("list only paused domains"),
    },
    {.name = "list-shutoff",
     .type = VSH_OT_BOOL,
     .help = N_("list only shutoff domains"),
    },
    {.name = "list-other",
     .type = VSH_OT_BOOL,
     .help = N_("list only domains in other states"),
    },
    {.name = "raw",
     .type = VSH_OT_BOOL,
     .help = N_("do not pretty-print the fields"),
    },
    {.name = "enforce",
     .type = VSH_OT_BOOL,
     .help = N_("enforce requested stats parameters"),
    },
    {.name = "backing",
     .type = VSH_OT_BOOL,
     .help = N_("add backing chain information to block stats"),
    },
    {.name = "nowait",
     .type = VSH_OT_BOOL,
     .help = N_("report only stats that are accessible instantly"),
    },
    VIRSH_COMMON_OPT_DOMAIN_OT_ARGV(N_("list of domains to get stats for"), 0),
    {.name = NULL}
};


static bool
virshDomainStatsPrintRecord(vshControl *ctl G_GNUC_UNUSED,
                            virDomainStatsRecordPtr record,
                            bool raw G_GNUC_UNUSED)
{
    size_t i;

    vshPrint(ctl, "Domain: '%s'\n", virDomainGetName(record->dom));

    /* XXX: Implement pretty-printing */

    for (i = 0; i < record->nparams; i++) {
        g_autofree char *param = NULL;

        if (!(param = vshGetTypedParamValue(ctl, record->params + i)))
            return false;

        vshPrint(ctl, "  %s=%s\n", record->params[i].field, param);
    }

    return true;
}

static bool
cmdDomstats(vshControl *ctl, const vshCmd *cmd)
{
    unsigned int stats = 0;
    virDomainPtr *domlist = NULL;
    virDomainPtr dom;
    size_t ndoms = 0;
    virDomainStatsRecordPtr *records = NULL;
    virDomainStatsRecordPtr *next;
    bool raw = vshCommandOptBool(cmd, "raw");
    int flags = 0;
    const vshCmdOpt *opt = NULL;
    bool ret = false;
    virshControl *priv = ctl->privData;

    if (vshCommandOptBool(cmd, "state"))
        stats |= VIR_DOMAIN_STATS_STATE;

    if (vshCommandOptBool(cmd, "cpu-total"))
        stats |= VIR_DOMAIN_STATS_CPU_TOTAL;

    if (vshCommandOptBool(cmd, "balloon"))
        stats |= VIR_DOMAIN_STATS_BALLOON;

    if (vshCommandOptBool(cmd, "vcpu"))
        stats |= VIR_DOMAIN_STATS_VCPU;

    if (vshCommandOptBool(cmd, "interface"))
        stats |= VIR_DOMAIN_STATS_INTERFACE;

    if (vshCommandOptBool(cmd, "block"))
        stats |= VIR_DOMAIN_STATS_BLOCK;

    if (vshCommandOptBool(cmd, "perf"))
        stats |= VIR_DOMAIN_STATS_PERF;

    if (vshCommandOptBool(cmd, "iothread"))
        stats |= VIR_DOMAIN_STATS_IOTHREAD;

    if (vshCommandOptBool(cmd, "memory"))
        stats |= VIR_DOMAIN_STATS_MEMORY;

    if (vshCommandOptBool(cmd, "dirtyrate"))
        stats |= VIR_DOMAIN_STATS_DIRTYRATE;

    if (vshCommandOptBool(cmd, "list-active"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_ACTIVE;

    if (vshCommandOptBool(cmd, "list-inactive"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_INACTIVE;

    if (vshCommandOptBool(cmd, "list-persistent"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_PERSISTENT;

    if (vshCommandOptBool(cmd, "list-transient"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_TRANSIENT;

    if (vshCommandOptBool(cmd, "list-running"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_RUNNING;

    if (vshCommandOptBool(cmd, "list-paused"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_PAUSED;

    if (vshCommandOptBool(cmd, "list-shutoff"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_SHUTOFF;

    if (vshCommandOptBool(cmd, "list-other"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_OTHER;

    if (vshCommandOptBool(cmd, "enforce"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_ENFORCE_STATS;

    if (vshCommandOptBool(cmd, "backing"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING;

    if (vshCommandOptBool(cmd, "nowait"))
        flags |= VIR_CONNECT_GET_ALL_DOMAINS_STATS_NOWAIT;

    if (vshCommandOptBool(cmd, "domain")) {
        domlist = g_new0(virDomainPtr, 1);
        ndoms = 1;

        while ((opt = vshCommandOptArgv(ctl, cmd, opt))) {
            if (!(dom = virshLookupDomainBy(ctl, opt->data,
                                            VIRSH_BYID |
                                            VIRSH_BYUUID | VIRSH_BYNAME)))
                goto cleanup;

            if (VIR_INSERT_ELEMENT(domlist, ndoms - 1, ndoms, dom) < 0)
                goto cleanup;
        }

        if (virDomainListGetStats(domlist,
                                  stats,
                                  &records,
                                  flags) < 0)
            goto cleanup;
    } else {
       if ((virConnectGetAllDomainStats(priv->conn,
                                        stats,
                                        &records,
                                        flags)) < 0)
           goto cleanup;
    }

    next = records;
    while (*next) {
        if (!virshDomainStatsPrintRecord(ctl, *next, raw))
            goto cleanup;

        if (*(++next))
            vshPrint(ctl, "\n");
    }

    ret = true;
 cleanup:
    virDomainStatsRecordListFree(records);
    virObjectListFree(domlist);

    return ret;
}

/* "domifaddr" command
 */
static const vshCmdInfo info_domifaddr[] = {
    {"help", N_("Get network interfaces' addresses for a running domain")},
    {"desc", N_("Get network interfaces' addresses for a running domain")},
    {NULL, NULL}
};

static const vshCmdOptDef opts_domifaddr[] = {
    VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE),
    {.name = "interface",
     .type = VSH_OT_STRING,
     .flags = VSH_OFLAG_NONE,
     .completer = virshDomainInterfaceCompleter,
     .help = N_("network interface name")},
    {.name = "full",
     .type = VSH_OT_BOOL,
     .flags = VSH_OFLAG_NONE,
     .help = N_("always display names and MACs of interfaces")},
    {.name = "source",
     .type = VSH_OT_STRING,
     .flags = VSH_OFLAG_NONE,
     .completer = virshDomainInterfaceAddrSourceCompleter,
     .help = N_("address source: 'lease', 'agent', or 'arp'")},
    {.name = NULL}
};

VIR_ENUM_IMPL(virshDomainInterfaceAddressesSource,
              VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LAST,
              "lease",
              "agent",
              "arp");

static bool
cmdDomIfAddr(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshDomain) dom = NULL;
    const char *ifacestr = NULL;
    virDomainInterfacePtr *ifaces = NULL;
    size_t i, j;
    int ifaces_count = 0;
    bool ret = false;
    bool full = vshCommandOptBool(cmd, "full");
    const char *sourcestr = NULL;
    int source = VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE;

    if (vshCommandOptStringReq(ctl, cmd, "interface", &ifacestr) < 0)
        return false;
    if (vshCommandOptStringReq(ctl, cmd, "source", &sourcestr) < 0)
        return false;

    if (sourcestr &&
        (source = virshDomainInterfaceAddressesSourceTypeFromString(sourcestr)) < 0) {
        vshError(ctl, _("Unknown data source '%s'"), sourcestr);
        return false;
    }

    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
        return false;

    if ((ifaces_count = virDomainInterfaceAddresses(dom, &ifaces, source, 0)) < 0) {
        vshError(ctl, _("Failed to query for interfaces addresses"));
        goto cleanup;
    }

    vshPrintExtra(ctl, " %-10s %-20s %-8s     %s\n%s%s\n", _("Name"),
                  _("MAC address"), _("Protocol"), _("Address"),
                  _("-------------------------------------------------"),
                  _("------------------------------"));

    for (i = 0; i < ifaces_count; i++) {
        virDomainInterfacePtr iface = ifaces[i];
        const char *type = NULL;

        if (ifacestr && STRNEQ(ifacestr, iface->name))
            continue;

        /* When the interface has no IP address */
        if (!iface->naddrs) {
            vshPrint(ctl, " %-10s %-17s    %-12s %s\n",
                     iface->name,
                     iface->hwaddr ? iface->hwaddr : "N/A", "N/A", "N/A");
            continue;
        }

        for (j = 0; j < iface->naddrs; j++) {
            g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
            g_autofree char *ip_addr_str = NULL;

            switch (iface->addrs[j].type) {
            case VIR_IP_ADDR_TYPE_IPV4:
                type = "ipv4";
                break;
            case VIR_IP_ADDR_TYPE_IPV6:
                type = "ipv6";
                break;
            }

            virBufferAsprintf(&buf, "%-12s %s/%d",
                              type, iface->addrs[j].addr,
                              iface->addrs[j].prefix);

            ip_addr_str = virBufferContentAndReset(&buf);

            if (!ip_addr_str)
                ip_addr_str = g_strdup("");

            /* Don't repeat interface name */
            if (full || !j)
                vshPrint(ctl, " %-10s %-17s    %s\n",
                         iface->name,
                         NULLSTR_EMPTY(iface->hwaddr), ip_addr_str);
            else
                vshPrint(ctl, " %-10s %-17s    %s\n",
                         "-", "-", ip_addr_str);
        }
    }

    ret = true;

 cleanup:
    if (ifaces && ifaces_count > 0) {
        for (i = 0; i < ifaces_count; i++)
            virDomainInterfaceFree(ifaces[i]);
    }
    VIR_FREE(ifaces);

    return ret;
}

const vshCmdDef domMonitoringCmds[] = {
    {.name = "domblkerror",
     .handler = cmdDomBlkError,
     .opts = opts_domblkerror,
     .info = info_domblkerror,
     .flags = 0
    },
    {.name = "domblkinfo",
     .handler = cmdDomblkinfo,
     .opts = opts_domblkinfo,
     .info = info_domblkinfo,
     .flags = 0
    },
    {.name = "domblklist",
     .handler = cmdDomblklist,
     .opts = opts_domblklist,
     .info = info_domblklist,
     .flags = 0
    },
    {.name = "domblkstat",
     .handler = cmdDomblkstat,
     .opts = opts_domblkstat,
     .info = info_domblkstat,
     .flags = 0
    },
    {.name = "domcontrol",
     .handler = cmdDomControl,
     .opts = opts_domcontrol,
     .info = info_domcontrol,
     .flags = 0
    },
    {.name = "domif-getlink",
     .handler = cmdDomIfGetLink,
     .opts = opts_domif_getlink,
     .info = info_domif_getlink,
     .flags = 0
    },
    {.name = "domifaddr",
     .handler = cmdDomIfAddr,
     .opts = opts_domifaddr,
     .info = info_domifaddr,
     .flags = 0
    },
    {.name = "domiflist",
     .handler = cmdDomiflist,
     .opts = opts_domiflist,
     .info = info_domiflist,
     .flags = 0
    },
    {.name = "domifstat",
     .handler = cmdDomIfstat,
     .opts = opts_domifstat,
     .info = info_domifstat,
     .flags = 0
    },
    {.name = "dominfo",
     .handler = cmdDominfo,
     .opts = opts_dominfo,
     .info = info_dominfo,
     .flags = 0
    },
    {.name = "dommemstat",
     .handler = cmdDomMemStat,
     .opts = opts_dommemstat,
     .info = info_dommemstat,
     .flags = 0
    },
    {.name = "domstate",
     .handler = cmdDomstate,
     .opts = opts_domstate,
     .info = info_domstate,
     .flags = 0
    },
    {.name = "domstats",
     .handler = cmdDomstats,
     .opts = opts_domstats,
     .info = info_domstats,
     .flags = 0
    },
    {.name = "domtime",
     .handler = cmdDomTime,
     .opts = opts_domtime,
     .info = info_domtime,
     .flags = 0
    },
    {.name = "list",
     .handler = cmdList,
     .opts = opts_list,
     .info = info_list,
     .flags = 0
    },
    {.name = NULL}
};
