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

#include "internal.h"
#include "viralloc.h"
#include "virfile.h"
#include "virtime.h"
#include "conf/network_conf.h"
#include "vsh-table.h"
#include "virenum.h"

#define VIRSH_COMMON_OPT_NETWORK(_helpstr, cflags) \
    {.name = "network", \
     .type = VSH_OT_DATA, \
     .flags = VSH_OFLAG_REQ, \
     .help = _helpstr, \
     .completer = virshNetworkNameCompleter, \
     .completer_flags = cflags, \
    }

#define VIRSH_COMMON_OPT_NETWORK_FULL(cflags) \
    VIRSH_COMMON_OPT_NETWORK(N_("network name or uuid"), cflags)

#define VIRSH_COMMON_OPT_NETWORK_OT_STRING(_helpstr, cflags) \
    {.name = "network", \
     .type = VSH_OT_STRING, \
     .help = _helpstr, \
     .completer = virshNetworkNameCompleter, \
     .completer_flags = cflags, \
    }

#define VIRSH_COMMON_OPT_NETWORK_OT_STRING_FULL(cflags) \
    VIRSH_COMMON_OPT_NETWORK_OT_STRING(N_("network name or uuid"), cflags)

#define VIRSH_COMMON_OPT_NETWORK_PORT(cflags) \
    {.name = "port", \
     .type = VSH_OT_DATA, \
     .flags = VSH_OFLAG_REQ, \
     .help = N_("port UUID"), \
     .completer = virshNetworkPortUUIDCompleter, \
     .completer_flags = cflags, \
    }


virNetworkPtr
virshCommandOptNetworkBy(vshControl *ctl, const vshCmd *cmd,
                         const char **name, unsigned int flags)
{
    virNetworkPtr network = NULL;
    const char *n = NULL;
    const char *optname = "network";
    virshControl *priv = ctl->privData;

    virCheckFlags(VIRSH_BYUUID | VIRSH_BYNAME, NULL);

    if (vshCommandOptStringReq(ctl, cmd, optname, &n) < 0)
        return NULL;

    vshDebug(ctl, VSH_ERR_INFO, "%s: found option <%s>: %s\n",
             cmd->def->name, optname, n);

    if (name)
        *name = n;

    /* try it by UUID */
    if ((flags & VIRSH_BYUUID) && strlen(n) == VIR_UUID_STRING_BUFLEN-1) {
        vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as network UUID\n",
                 cmd->def->name, optname);
        network = virNetworkLookupByUUIDString(priv->conn, n);
    }
    /* try it by NAME */
    if (!network && (flags & VIRSH_BYNAME)) {
        vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as network NAME\n",
                 cmd->def->name, optname);
        network = virNetworkLookupByName(priv->conn, n);
    }

    if (!network)
        vshError(ctl, _("failed to get network '%s'"), n);

    return network;
}


virNetworkPortPtr
virshCommandOptNetworkPort(vshControl *ctl, const vshCmd *cmd,
                           virNetworkPtr net,
                           const char **name)
{
    virNetworkPortPtr port = NULL;
    const char *n = NULL;
    const char *optname = "port";

    if (vshCommandOptStringReq(ctl, cmd, optname, &n) < 0)
        return NULL;

    vshDebug(ctl, VSH_ERR_INFO, "%s: found option <%s>: %s\n",
             cmd->def->name, optname, n);

    if (name)
        *name = n;

    vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as network UUID\n",
             cmd->def->name, optname);
    port = virNetworkPortLookupByUUIDString(net, n);

    if (!port)
        vshError(ctl, _("failed to get network port '%s'"), n);

    return port;
}

/*
 * "net-autostart" command
 */
static const vshCmdInfo info_network_autostart[] = {
    {.name = "help",
     .data = N_("autostart a network")
    },
    {.name = "desc",
     .data = N_("Configure a network to be automatically started at boot.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_autostart[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(VIR_CONNECT_LIST_NETWORKS_PERSISTENT),
    {.name = "disable",
     .type = VSH_OT_BOOL,
     .help = N_("disable autostarting")
    },
    {.name = NULL}
};

static bool
cmdNetworkAutostart(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    const char *name;
    int autostart;

    if (!(network = virshCommandOptNetwork(ctl, cmd, &name)))
        return false;

    autostart = !vshCommandOptBool(cmd, "disable");

    if (virNetworkSetAutostart(network, autostart) < 0) {
        if (autostart)
            vshError(ctl, _("failed to mark network %s as autostarted"), name);
        else
            vshError(ctl, _("failed to unmark network %s as autostarted"), name);
        return false;
    }

    if (autostart)
        vshPrintExtra(ctl, _("Network %s marked as autostarted\n"), name);
    else
        vshPrintExtra(ctl, _("Network %s unmarked as autostarted\n"), name);

    return true;
}

/*
 * "net-create" command
 */
static const vshCmdInfo info_network_create[] = {
    {.name = "help",
     .data = N_("create a network from an XML file")
    },
    {.name = "desc",
     .data = N_("Create a network.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_create[] = {
    VIRSH_COMMON_OPT_FILE(N_("file containing an XML network description")),
    {.name = "validate",
     .type = VSH_OT_BOOL,
     .help = N_("validate the XML against the schema")
    },
    {.name = NULL}
};

static bool
cmdNetworkCreate(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    const char *from = NULL;
    g_autofree char *buffer = NULL;
    unsigned int flags = 0;
    virshControl *priv = ctl->privData;

    if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0)
        return false;

    if (vshCommandOptBool(cmd, "validate"))
        flags |= VIR_NETWORK_CREATE_VALIDATE;

    if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0)
        return false;

    if (flags)
        network = virNetworkCreateXMLFlags(priv->conn, buffer, flags);
    else
        network = virNetworkCreateXML(priv->conn, buffer);

    if (!network) {
        vshError(ctl, _("Failed to create network from %s"), from);
        return false;
    }

    vshPrintExtra(ctl, _("Network %s created from %s\n"),
                  virNetworkGetName(network), from);
    return true;
}

/*
 * "net-define" command
 */
static const vshCmdInfo info_network_define[] = {
    {.name = "help",
     .data = N_("define an inactive persistent virtual network or modify "
                "an existing persistent one from an XML file")
    },
    {.name = "desc",
     .data = N_("Define or modify a persistent virtual network.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_define[] = {
    VIRSH_COMMON_OPT_FILE(N_("file containing an XML network description")),
    {.name = "validate",
     .type = VSH_OT_BOOL,
     .help = N_("validate the XML against the schema")
    },
    {.name = NULL}
};

static bool
cmdNetworkDefine(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    const char *from = NULL;
    g_autofree char *buffer = NULL;
    unsigned int flags = 0;
    virshControl *priv = ctl->privData;

    if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0)
        return false;

    if (vshCommandOptBool(cmd, "validate"))
        flags |= VIR_NETWORK_DEFINE_VALIDATE;

    if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0)
        return false;

    if (flags)
        network = virNetworkDefineXMLFlags(priv->conn, buffer, flags);
    else
        network = virNetworkDefineXML(priv->conn, buffer);

    if (!network) {
        vshError(ctl, _("Failed to define network from %s"), from);
        return false;
    }

    vshPrintExtra(ctl, _("Network %s defined from %s\n"),
                  virNetworkGetName(network), from);
    return true;
}

/*
 * "net-destroy" command
 */
static const vshCmdInfo info_network_destroy[] = {
    {.name = "help",
     .data = N_("destroy (stop) a network")
    },
    {.name = "desc",
     .data = N_("Forcefully stop a given network.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_destroy[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(VIR_CONNECT_LIST_NETWORKS_ACTIVE),
    {.name = NULL}
};

static bool
cmdNetworkDestroy(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    bool ret = true;
    const char *name;

    if (!(network = virshCommandOptNetwork(ctl, cmd, &name)))
        return false;

    if (virNetworkDestroy(network) == 0) {
        vshPrintExtra(ctl, _("Network %s destroyed\n"), name);
    } else {
        vshError(ctl, _("Failed to destroy network %s"), name);
        ret = false;
    }

    return ret;
}

/*
 * "net-dumpxml" command
 */
static const vshCmdInfo info_network_dumpxml[] = {
    {.name = "help",
     .data = N_("network information in XML")
    },
    {.name = "desc",
     .data = N_("Output the network information as an XML dump to stdout.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_dumpxml[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(0),
    {.name = "inactive",
     .type = VSH_OT_BOOL,
     .help = N_("show inactive defined XML")
    },
    {.name = "xpath",
     .type = VSH_OT_STRING,
     .completer = virshCompleteEmpty,
     .help = N_("xpath expression to filter the XML document")
    },
    {.name = "wrap",
     .type = VSH_OT_BOOL,
     .help = N_("wrap xpath results in an common root element"),
    },
    {.name = NULL}
};

static bool
cmdNetworkDumpXML(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    g_autofree char *xml = NULL;
    unsigned int flags = 0;
    bool wrap = vshCommandOptBool(cmd, "wrap");
    const char *xpath = NULL;

    if (!(network = virshCommandOptNetwork(ctl, cmd, NULL)))
        return false;

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

    if (vshCommandOptStringQuiet(ctl, cmd, "xpath", &xpath) < 0)
        return false;

    if (!(xml = virNetworkGetXMLDesc(network, flags)))
        return false;

    return virshDumpXML(ctl, xml, "network", xpath, wrap);
}

/*
 * "net-info" command
 */
static const vshCmdInfo info_network_info[] = {
    {.name = "help",
     .data = N_("network information")
    },
    {.name = "desc",
     .data = N_("Returns basic information about the network")
    },
    {.name = NULL}
};

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

static bool
cmdNetworkInfo(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    char uuid[VIR_UUID_STRING_BUFLEN];
    int autostart;
    int persistent = -1;
    int active = -1;
    char *bridge = NULL;

    if (!(network = virshCommandOptNetwork(ctl, cmd, NULL)))
        return false;

    vshPrint(ctl, "%-15s %s\n", _("Name:"), virNetworkGetName(network));

    if (virNetworkGetUUIDString(network, uuid) == 0)
        vshPrint(ctl, "%-15s %s\n", _("UUID:"), uuid);

    active = virNetworkIsActive(network);
    if (active >= 0)
        vshPrint(ctl, "%-15s %s\n", _("Active:"), active? _("yes") : _("no"));

    persistent = virNetworkIsPersistent(network);
    if (persistent < 0)
        vshPrint(ctl, "%-15s %s\n", _("Persistent:"), _("unknown"));
    else
        vshPrint(ctl, "%-15s %s\n", _("Persistent:"), persistent ? _("yes") : _("no"));

    if (virNetworkGetAutostart(network, &autostart) < 0)
        vshPrint(ctl, "%-15s %s\n", _("Autostart:"), _("no autostart"));
    else
        vshPrint(ctl, "%-15s %s\n", _("Autostart:"), autostart ? _("yes") : _("no"));

    bridge = virNetworkGetBridgeName(network);
    if (bridge)
        vshPrint(ctl, "%-15s %s\n", _("Bridge:"), bridge);

    VIR_FREE(bridge);
    return true;
}

static int
virshNetworkSorter(const void *a, const void *b)
{
    virNetworkPtr *na = (virNetworkPtr *) a;
    virNetworkPtr *nb = (virNetworkPtr *) b;

    if (*na && !*nb)
        return -1;

    if (!*na)
        return *nb != NULL;

    return vshStrcasecmp(virNetworkGetName(*na),
                      virNetworkGetName(*nb));
}

struct virshNetworkList {
    virNetworkPtr *nets;
    size_t nnets;
};

static void
virshNetworkListFree(struct virshNetworkList *list)
{
    size_t i;

    if (list && list->nets) {
        for (i = 0; i < list->nnets; i++) {
            virshNetworkFree(list->nets[i]);
        }
        g_free(list->nets);
    }
    g_free(list);
}

static struct virshNetworkList *
virshNetworkListCollect(vshControl *ctl,
                        unsigned int flags)
{
    struct virshNetworkList *list = g_new0(struct virshNetworkList, 1);
    size_t i;
    int ret;
    char **names = NULL;
    virNetworkPtr net;
    bool success = false;
    size_t deleted = 0;
    int persistent;
    int autostart;
    int nActiveNets = 0;
    int nInactiveNets = 0;
    int nAllNets = 0;
    virshControl *priv = ctl->privData;

    /* try the list with flags support (0.10.2 and later) */
    if ((ret = virConnectListAllNetworks(priv->conn,
                                         &list->nets,
                                         flags)) >= 0) {
        list->nnets = ret;
        goto finished;
    }

    /* check if the command is actually supported */
    if (last_error && last_error->code == VIR_ERR_NO_SUPPORT)
        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_NETWORKS_ACTIVE |
                                         VIR_CONNECT_LIST_NETWORKS_INACTIVE);

        vshResetLibvirtError();
        if ((ret = virConnectListAllNetworks(priv->conn, &list->nets,
                                             newflags)) >= 0) {
            list->nnets = ret;
            goto filter;
        }
    }

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


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

    /* Get the number of active networks */
    if (!VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_ACTIVE) ||
        VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_ACTIVE)) {
        if ((nActiveNets = virConnectNumOfNetworks(priv->conn)) < 0) {
            vshError(ctl, "%s", _("Failed to get the number of active networks"));
            goto cleanup;
        }
    }

    /* Get the number of inactive networks */
    if (!VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_ACTIVE) ||
        VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_INACTIVE)) {
        if ((nInactiveNets = virConnectNumOfDefinedNetworks(priv->conn)) < 0) {
            vshError(ctl, "%s", _("Failed to get the number of inactive networks"));
            goto cleanup;
        }
    }

    nAllNets = nActiveNets + nInactiveNets;

    if (nAllNets == 0)
         return list;

    names = g_new0(char *, nAllNets);

    /* Retrieve a list of active network names */
    if (!VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_ACTIVE) ||
        VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_ACTIVE)) {
        if (virConnectListNetworks(priv->conn,
                                   names, nActiveNets) < 0) {
            vshError(ctl, "%s", _("Failed to list active networks"));
            goto cleanup;
        }
    }

    /* Add the inactive networks to the end of the name list */
    if (!VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_ACTIVE) ||
        VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_ACTIVE)) {
        if (virConnectListDefinedNetworks(priv->conn,
                                          &names[nActiveNets],
                                          nInactiveNets) < 0) {
            vshError(ctl, "%s", _("Failed to list inactive networks"));
            goto cleanup;
        }
    }

    list->nets = g_new0(virNetworkPtr, nAllNets);
    list->nnets = 0;

    /* get active networks */
    for (i = 0; i < nActiveNets; i++) {
        if (!(net = virNetworkLookupByName(priv->conn, names[i])))
            continue;
        list->nets[list->nnets++] = net;
    }

    /* get inactive networks */
    for (i = 0; i < nInactiveNets; i++) {
        if (!(net = virNetworkLookupByName(priv->conn, names[i])))
            continue;
        list->nets[list->nnets++] = net;
    }

    /* truncate networks that weren't found */
    deleted = nAllNets - list->nnets;

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

        /* persistence filter */
        if (VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_PERSISTENT)) {
            if ((persistent = virNetworkIsPersistent(net)) < 0) {
                vshError(ctl, "%s", _("Failed to get network persistence info"));
                goto cleanup;
            }

            if (!((VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_PERSISTENT) && persistent) ||
                  (VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_TRANSIENT) && !persistent)))
                goto remove_entry;
        }

        /* autostart filter */
        if (VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_AUTOSTART)) {
            if (virNetworkGetAutostart(net, &autostart) < 0) {
                vshError(ctl, "%s", _("Failed to get network autostart state"));
                goto cleanup;
            }

            if (!((VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_AUTOSTART) && autostart) ||
                  (VSH_MATCH(VIR_CONNECT_LIST_NETWORKS_NO_AUTOSTART) && !autostart)))
                goto remove_entry;
        }
        /* the pool matched all filters, it may stay */
        continue;

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

 finished:
    /* sort the list */
    if (list->nets && list->nnets)
        qsort(list->nets, list->nnets,
              sizeof(*list->nets), virshNetworkSorter);

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

    success = true;

 cleanup:
    for (i = 0; i < nAllNets; i++)
        VIR_FREE(names[i]);
    VIR_FREE(names);

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

    return list;
}

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

static const vshCmdOptDef opts_network_list[] = {
    {.name = "inactive",
     .type = VSH_OT_BOOL,
     .help = N_("list inactive networks")
    },
    {.name = "all",
     .type = VSH_OT_BOOL,
     .help = N_("list inactive & active networks")
    },
    {.name = "persistent",
     .type = VSH_OT_BOOL,
     .help = N_("list persistent networks")
    },
    {.name = "transient",
     .type = VSH_OT_BOOL,
     .help = N_("list transient networks")
    },
    {.name = "autostart",
     .type = VSH_OT_BOOL,
     .help = N_("list networks with autostart enabled")
    },
    {.name = "no-autostart",
     .type = VSH_OT_BOOL,
     .help = N_("list networks with autostart disabled")
    },
    {.name = "uuid",
     .type = VSH_OT_BOOL,
     .help = N_("list uuid's only")
    },
    {.name = "name",
     .type = VSH_OT_BOOL,
     .help = N_("list network names only")
    },
    {.name = "table",
     .type = VSH_OT_BOOL,
     .help = N_("list table (default)")
    },
    {.name = NULL}
};

#define FILTER(NAME, FLAG) \
    if (vshCommandOptBool(cmd, NAME)) \
        flags |= (FLAG)
static bool
cmdNetworkList(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED)
{
    struct virshNetworkList *list = NULL;
    size_t i;
    bool ret = false;
    bool optName = vshCommandOptBool(cmd, "name");
    bool optTable = vshCommandOptBool(cmd, "table");
    bool optUUID = vshCommandOptBool(cmd, "uuid");
    char uuid[VIR_UUID_STRING_BUFLEN];
    unsigned int flags = VIR_CONNECT_LIST_NETWORKS_ACTIVE;
    g_autoptr(vshTable) table = NULL;

    if (vshCommandOptBool(cmd, "inactive"))
        flags = VIR_CONNECT_LIST_NETWORKS_INACTIVE;

    if (vshCommandOptBool(cmd, "all"))
        flags = VIR_CONNECT_LIST_NETWORKS_ACTIVE |
                VIR_CONNECT_LIST_NETWORKS_INACTIVE;

    FILTER("persistent", VIR_CONNECT_LIST_NETWORKS_PERSISTENT);
    FILTER("transient", VIR_CONNECT_LIST_NETWORKS_TRANSIENT);

    FILTER("autostart", VIR_CONNECT_LIST_NETWORKS_AUTOSTART);
    FILTER("no-autostart", VIR_CONNECT_LIST_NETWORKS_NO_AUTOSTART);

    if (optTable + optName + optUUID > 1) {
        vshError(ctl, "%s",
                 _("Only one argument from --table, --name and --uuid "
                   "may be specified."));
        return false;
    }

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

    if (!(list = virshNetworkListCollect(ctl, flags)))
        return false;

    if (optTable) {
        table = vshTableNew(_("Name"), _("State"), _("Autostart"),
                            _("Persistent"), NULL);
        if (!table)
            goto cleanup;
    }

    for (i = 0; i < list->nnets; i++) {
        virNetworkPtr network = list->nets[i];
        const char *autostartStr;
        int is_autostart = 0;

        if (optTable) {
            if (virNetworkGetAutostart(network, &is_autostart) < 0)
                autostartStr = _("no autostart");
            else
                autostartStr = is_autostart ? _("yes") : _("no");

            if (vshTableRowAppend(table,
                                  virNetworkGetName(network),
                                  virNetworkIsActive(network) ?
                                  _("active") : _("inactive"),
                                  autostartStr,
                                  virNetworkIsPersistent(network) ?
                                  _("yes") : _("no"),
                                  NULL) < 0)
                goto cleanup;
        } else if (optUUID) {
            if (virNetworkGetUUIDString(network, uuid) < 0) {
                vshError(ctl, "%s", _("Failed to get network's UUID"));
                goto cleanup;
            }
            vshPrint(ctl, "%s\n", uuid);
        } else if (optName) {
            vshPrint(ctl, "%s\n", virNetworkGetName(network));
        }
    }

    if (optTable)
        vshTablePrintToStdout(table, ctl);

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

/*
 * "net-name" command
 */
static const vshCmdInfo info_network_name[] = {
    {.name = "help",
     .data = N_("convert a network UUID to network name")
    },
    {.name = "desc",
     .data = ""
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_name[] = {
    {.name = "network",
     .type = VSH_OT_DATA,
     .flags = VSH_OFLAG_REQ,
     .completer = virshNetworkUUIDCompleter,
     .help = N_("network uuid")
    },
    {.name = NULL}
};

static bool
cmdNetworkName(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;

    if (!(network = virshCommandOptNetworkBy(ctl, cmd, NULL,
                                             VIRSH_BYUUID)))
        return false;

    vshPrint(ctl, "%s\n", virNetworkGetName(network));
    return true;
}

/*
 * "net-start" command
 */
static const vshCmdInfo info_network_start[] = {
    {.name = "help",
     .data = N_("start a (previously defined) inactive network")
    },
    {.name = "desc",
     .data = N_("Start a network.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_start[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(VIR_CONNECT_LIST_NETWORKS_INACTIVE),
    {.name = NULL}
};

static bool
cmdNetworkStart(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    bool ret = true;
    const char *name = NULL;

    if (!(network = virshCommandOptNetwork(ctl, cmd, &name)))
         return false;

    if (virNetworkCreate(network) == 0) {
        vshPrintExtra(ctl, _("Network %s started\n"), name);
    } else {
        vshError(ctl, _("Failed to start network %s"), name);
        ret = false;
    }
    return ret;
}

/*
 * "net-undefine" command
 */
static const vshCmdInfo info_network_undefine[] = {
    {.name = "help",
     .data = N_("undefine a persistent network")
    },
    {.name = "desc",
     .data = N_("Undefine the configuration for a persistent network.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_undefine[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(VIR_CONNECT_LIST_NETWORKS_PERSISTENT),
    {.name = NULL}
};

static bool
cmdNetworkUndefine(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    bool ret = true;
    const char *name;

    if (!(network = virshCommandOptNetwork(ctl, cmd, &name)))
        return false;

    if (virNetworkUndefine(network) == 0) {
        vshPrintExtra(ctl, _("Network %s has been undefined\n"), name);
    } else {
        vshError(ctl, _("Failed to undefine network %s"), name);
        ret = false;
    }

    return ret;
}

/*
 * "net-update" command
 */
static const vshCmdInfo info_network_update[] = {
    {.name = "help",
     .data = N_("update parts of an existing network's configuration")
    },
    {.name = "desc",
     .data = ""
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_update[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(0),
    {.name = "command",
     .type = VSH_OT_DATA,
     .flags = VSH_OFLAG_REQ,
     .completer = virshNetworkUpdateCommandCompleter,
     .help = N_("type of update (add-first, add-last (add), delete, or modify)")
    },
    {.name = "section",
     .type = VSH_OT_DATA,
     .flags = VSH_OFLAG_REQ,
     .completer = virshNetworkUpdateSectionCompleter,
     .help = N_("which section of network configuration to update")
    },
    {.name = "xml",
     .type = VSH_OT_DATA,
     .flags = VSH_OFLAG_REQ,
     .completer = virshCompletePathLocalExisting,
     .help = N_("name of file containing xml (or, if it starts with '<', the complete "
                "xml element itself) to add/modify, or to be matched for search")
    },
    {.name = "parent-index",
     .type = VSH_OT_INT,
     .help = N_("which parent object to search through")
    },
    VIRSH_COMMON_OPT_CONFIG(N_("affect next network startup")),
    VIRSH_COMMON_OPT_LIVE(N_("affect running network")),
    VIRSH_COMMON_OPT_CURRENT(N_("affect current state of network")),
    {.name = NULL}
};

VIR_ENUM_IMPL(virshNetworkUpdateCommand,
              VIR_NETWORK_UPDATE_COMMAND_LAST,
              "none", "modify", "delete", "add-last", "add-first");

VIR_ENUM_IMPL(virshNetworkSection,
              VIR_NETWORK_SECTION_LAST,
              "none", "bridge", "domain", "ip", "ip-dhcp-host",
              "ip-dhcp-range", "forward", "forward-interface",
              "forward-pf", "portgroup", "dns-host", "dns-txt",
              "dns-srv");

static bool
cmdNetworkUpdate(vshControl *ctl, const vshCmd *cmd)
{
    bool ret = false;
    g_autoptr(virshNetwork) network = NULL;
    const char *commandStr = NULL;
    const char *sectionStr = NULL;
    int command, section, parentIndex = -1;
    const char *xml = NULL;
    g_autofree char *xmlFromFile = NULL;
    bool config = vshCommandOptBool(cmd, "config");
    bool live = vshCommandOptBool(cmd, "live");
    unsigned int flags = VIR_NETWORK_UPDATE_AFFECT_CURRENT;

    VSH_EXCLUSIVE_OPTIONS("current", "live");
    VSH_EXCLUSIVE_OPTIONS("current", "config");

    if (!(network = virshCommandOptNetwork(ctl, cmd, NULL)))
        return false;

    if (vshCommandOptStringReq(ctl, cmd, "command", &commandStr) < 0)
        goto cleanup;

    if (STREQ(commandStr, "add")) {
        /* "add" is a synonym for "add-last" */
        command = VIR_NETWORK_UPDATE_COMMAND_ADD_LAST;
    } else {
        command = virshNetworkUpdateCommandTypeFromString(commandStr);
        if (command <= 0) {
            vshError(ctl, _("unrecognized command name '%s'"), commandStr);
            goto cleanup;
        }
    }

    if (vshCommandOptStringReq(ctl, cmd, "section", &sectionStr) < 0)
        goto cleanup;

    section = virshNetworkSectionTypeFromString(sectionStr);
    if (section <= 0) {
        vshError(ctl, _("unrecognized section name '%s'"), sectionStr);
        goto cleanup;
    }

    if (vshCommandOptInt(ctl, cmd, "parent-index", &parentIndex) < 0)
        goto cleanup;

    /* The goal is to have a full xml element in the "xml"
     * string. This is provided in the --xml option, either directly
     * (detected by the first character being "<"), or indirectly by
     * supplying a filename (first character isn't "<") that contains
     * the desired xml.
     */

    if (vshCommandOptStringReq(ctl, cmd, "xml", &xml) < 0)
        goto cleanup;

    if (*xml != '<') {
        /* contents of xmldata is actually the name of a file that
         * contains the xml.
         */
        if (virFileReadAll(xml, VSH_MAX_XML_FILE, &xmlFromFile) < 0)
            goto cleanup;
        /* NB: the original xml is just a const char * that points
         * to a string owned by the vshCmd object, and will be freed
         * by vshCommandFree, so it's safe to lose its pointer here.
         */
        xml = xmlFromFile;
    }

    if (config)
        flags |= VIR_NETWORK_UPDATE_AFFECT_CONFIG;
    if (live)
        flags |= VIR_NETWORK_UPDATE_AFFECT_LIVE;

    if (virNetworkUpdate(network, command,
                         section, parentIndex, xml, flags) < 0) {
        vshError(ctl, _("Failed to update network %s"),
                 virNetworkGetName(network));
        goto cleanup;
    }

    if (config) {
        if (live)
            vshPrintExtra(ctl, _("Updated network %s persistent config and "
                                 "live state"),
                          virNetworkGetName(network));
        else
            vshPrintExtra(ctl, _("Updated network %s persistent config"),
                          virNetworkGetName(network));
    } else if (live) {
        vshPrintExtra(ctl, _("Updated network %s live state"),
                      virNetworkGetName(network));
    } else if (virNetworkIsActive(network)) {
        vshPrintExtra(ctl, _("Updated network %s live state"),
                      virNetworkGetName(network));
    } else {
        vshPrintExtra(ctl, _("Updated network %s persistent config"),
                      virNetworkGetName(network));
    }

    ret = true;
 cleanup:
    vshReportError(ctl);
    return ret;
}

/*
 * "net-uuid" command
 */
static const vshCmdInfo info_network_uuid[] = {
    {.name = "help",
     .data = N_("convert a network name to network UUID")
    },
    {.name = "desc",
     .data = ""
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_uuid[] = {
    VIRSH_COMMON_OPT_NETWORK(N_("network name"), 0),
    {.name = NULL}
};

static bool
cmdNetworkUuid(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    char uuid[VIR_UUID_STRING_BUFLEN];

    if (!(network = virshCommandOptNetworkBy(ctl, cmd, NULL,
                                             VIRSH_BYNAME)))
        return false;

    if (virNetworkGetUUIDString(network, uuid) != -1)
        vshPrint(ctl, "%s\n", uuid);
    else
        vshError(ctl, "%s", _("failed to get network UUID"));

    return true;
}

/*
 * "net-edit" command
 */
static const vshCmdInfo info_network_edit[] = {
    {.name = "help",
     .data = N_("edit XML configuration for a network")
    },
    {.name = "desc",
     .data = N_("Edit the XML configuration for a network.")
    },
    {.name = NULL}
};

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

static char *virshNetworkGetXMLDesc(virNetworkPtr network)
{
    unsigned int flags = VIR_NETWORK_XML_INACTIVE;
    char *doc = virNetworkGetXMLDesc(network, flags);

    if (!doc && last_error->code == VIR_ERR_INVALID_ARG) {
        /* The server side libvirt doesn't support
         * VIR_NETWORK_XML_INACTIVE, so retry without it.
         */
        vshResetLibvirtError();
        flags &= ~VIR_NETWORK_XML_INACTIVE;
        doc = virNetworkGetXMLDesc(network, flags);
    }
    return doc;
}

static bool
cmdNetworkEdit(vshControl *ctl, const vshCmd *cmd)
{
    bool ret = false;
    g_autoptr(virshNetwork) network = NULL;
    g_autoptr(virshNetwork) network_edited = NULL;
    virshControl *priv = ctl->privData;

    network = virshCommandOptNetwork(ctl, cmd, NULL);
    if (network == NULL)
        goto cleanup;

#define EDIT_GET_XML virshNetworkGetXMLDesc(network)
#define EDIT_NOT_CHANGED \
    do { \
        vshPrintExtra(ctl, _("Network %s XML configuration not changed.\n"), \
                      virNetworkGetName(network)); \
        ret = true; \
        goto edit_cleanup; \
    } while (0)
#define EDIT_DEFINE \
    (network_edited = virNetworkDefineXML(priv->conn, doc_edited))
#include "virsh-edit.c"

    vshPrintExtra(ctl, _("Network %s XML configuration edited.\n"),
                  virNetworkGetName(network_edited));

    ret = true;

 cleanup:
    return ret;
}


/*
 * "net-event" command
 */
VIR_ENUM_DECL(virshNetworkEvent);
VIR_ENUM_IMPL(virshNetworkEvent,
              VIR_NETWORK_EVENT_LAST,
              N_("Defined"),
              N_("Undefined"),
              N_("Started"),
              N_("Stopped"));

static const char *
virshNetworkEventToString(int event)
{
    const char *str = virshNetworkEventTypeToString(event);
    return str ? _(str) : _("unknown");
}

struct virshNetEventData {
    vshControl *ctl;
    bool loop;
    bool timestamp;
    int count;
    virshNetworkEventCallback *cb;
};
typedef struct virshNetEventData virshNetEventData;

VIR_ENUM_DECL(virshNetworkEventId);
VIR_ENUM_IMPL(virshNetworkEventId,
              VIR_NETWORK_EVENT_ID_LAST,
              "lifecycle");

static void
vshEventLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED,
                       virNetworkPtr net,
                       int event,
                       int detail G_GNUC_UNUSED,
                       void *opaque)
{
    virshNetEventData *data = opaque;

    if (!data->loop && data->count)
        return;

    if (data->timestamp) {
        char timestamp[VIR_TIME_STRING_BUFLEN];

        if (virTimeStringNowRaw(timestamp) < 0)
            timestamp[0] = '\0';

        vshPrint(data->ctl, _("%s: event 'lifecycle' for network %s: %s\n"),
                 timestamp,
                 virNetworkGetName(net), virshNetworkEventToString(event));
    } else {
        vshPrint(data->ctl, _("event 'lifecycle' for network %s: %s\n"),
                 virNetworkGetName(net), virshNetworkEventToString(event));
    }

    data->count++;
    if (!data->loop)
        vshEventDone(data->ctl);
}

virshNetworkEventCallback virshNetworkEventCallbacks[] = {
    { "lifecycle",
      VIR_NETWORK_EVENT_CALLBACK(vshEventLifecyclePrint), },
};
G_STATIC_ASSERT(VIR_NETWORK_EVENT_ID_LAST == G_N_ELEMENTS(virshNetworkEventCallbacks));

static const vshCmdInfo info_network_event[] = {
    {.name = "help",
     .data = N_("Network Events")
    },
    {.name = "desc",
     .data = N_("List event types, or wait for network events to occur")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_event[] = {
    VIRSH_COMMON_OPT_NETWORK_OT_STRING(N_("filter by network name or uuid"), 0),
    {.name = "event",
     .type = VSH_OT_STRING,
     .completer = virshNetworkEventNameCompleter,
     .help = N_("which event type to wait for")
    },
    {.name = "loop",
     .type = VSH_OT_BOOL,
     .help = N_("loop until timeout or interrupt, rather than one-shot")
    },
    {.name = "timeout",
     .type = VSH_OT_INT,
     .help = N_("timeout seconds")
    },
    {.name = "list",
     .type = VSH_OT_BOOL,
     .help = N_("list valid event types")
    },
    {.name = "timestamp",
     .type = VSH_OT_BOOL,
     .help = N_("show timestamp for each printed event")
    },
    {.name = NULL}
};

static bool
cmdNetworkEvent(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) net = NULL;
    bool ret = false;
    int eventId = -1;
    int timeout = 0;
    virshNetEventData data;
    const char *eventName = NULL;
    int event;
    virshControl *priv = ctl->privData;

    if (vshCommandOptBool(cmd, "list")) {
        size_t i;

        for (i = 0; i < VIR_NETWORK_EVENT_ID_LAST; i++)
            vshPrint(ctl, "%s\n", virshNetworkEventCallbacks[i].name);
        return true;
    }

    if (vshCommandOptStringReq(ctl, cmd, "event", &eventName) < 0)
        return false;
    if (!eventName) {
        vshError(ctl, "%s", _("either --list or --event <type> is required"));
        return false;
    }
    for (event = 0; event < VIR_NETWORK_EVENT_ID_LAST; event++)
        if (STREQ(eventName, virshNetworkEventCallbacks[event].name))
            break;
    if (event == VIR_NETWORK_EVENT_ID_LAST) {
        vshError(ctl, _("unknown event type %s"), eventName);
        return false;
    }

    data.ctl = ctl;
    data.loop = vshCommandOptBool(cmd, "loop");
    data.timestamp = vshCommandOptBool(cmd, "timestamp");
    data.count = 0;
    data.cb = &virshNetworkEventCallbacks[event];
    if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0)
        return false;

    if (vshCommandOptBool(cmd, "network"))
        net = virshCommandOptNetwork(ctl, cmd, NULL);
    if (vshEventStart(ctl, timeout) < 0)
        goto cleanup;

    if ((eventId = virConnectNetworkEventRegisterAny(priv->conn, net, event,
                                                     data.cb->cb,
                                                     &data, NULL)) < 0)
        goto cleanup;
    switch (vshEventWait(ctl)) {
    case VSH_EVENT_INTERRUPT:
        vshPrint(ctl, "%s", _("event loop interrupted\n"));
        break;
    case VSH_EVENT_TIMEOUT:
        vshPrint(ctl, "%s", _("event loop timed out\n"));
        break;
    case VSH_EVENT_DONE:
        break;
    default:
        goto cleanup;
    }
    vshPrint(ctl, _("events received: %d\n"), data.count);
    if (data.count)
        ret = true;

 cleanup:
    vshEventCleanup(ctl);
    if (eventId >= 0 &&
        virConnectNetworkEventDeregisterAny(priv->conn, eventId) < 0)
        ret = false;
    return ret;
}


/*
 * "net-dhcp-leases" command
 */
static const vshCmdInfo info_network_dhcp_leases[] = {
    {.name = "help",
     .data = N_("print lease info for a given network")
    },
    {.name = "desc",
     .data = N_("Print lease info for a given network")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_dhcp_leases[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(VIR_CONNECT_LIST_NETWORKS_ACTIVE),
    {.name = "mac",
     .type = VSH_OT_STRING,
     .flags = VSH_OFLAG_NONE,
     .help = N_("MAC address"),
     .completer = virshNetworkDhcpMacCompleter,
    },
    {.name = NULL}
};

static int
virshNetworkDHCPLeaseSorter(const void *a, const void *b)
{
    virNetworkDHCPLeasePtr *lease1 = (virNetworkDHCPLeasePtr *) a;
    virNetworkDHCPLeasePtr *lease2 = (virNetworkDHCPLeasePtr *) b;

    if (*lease1 && !*lease2)
        return -1;

    if (!*lease1)
        return *lease2 != NULL;

    return vshStrcasecmp((*lease1)->mac, (*lease2)->mac);
}

static bool
cmdNetworkDHCPLeases(vshControl *ctl, const vshCmd *cmd)
{
    const char *name = NULL;
    const char *mac = NULL;
    virNetworkDHCPLeasePtr *leases = NULL;
    int nleases = 0;
    bool ret = false;
    size_t i;
    unsigned int flags = 0;
    g_autoptr(virshNetwork) network = NULL;
    g_autoptr(vshTable) table = NULL;

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

    if (!(network = virshCommandOptNetwork(ctl, cmd, &name)))
        return false;

    if ((nleases = virNetworkGetDHCPLeases(network, mac, &leases, flags)) < 0) {
        vshError(ctl, _("Failed to get leases info for %s"), name);
        goto cleanup;
    }

    /* Sort the list according to MAC Address/IAID */
    qsort(leases, nleases, sizeof(*leases), virshNetworkDHCPLeaseSorter);

    table = vshTableNew(_("Expiry Time"), _("MAC address"), _("Protocol"),
                        _("IP address"), _("Hostname"), _("Client ID or DUID"),
                        NULL);
    if (!table)
        goto cleanup;

    for (i = 0; i < nleases; i++) {
        const char *typestr = NULL;
        g_autofree char *cidr_format = NULL;
        virNetworkDHCPLeasePtr lease = leases[i];
        g_autoptr(GDateTime) then = g_date_time_new_from_unix_local(lease->expirytime);
        g_autofree char *thenstr = NULL;

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

        if (lease->type == VIR_IP_ADDR_TYPE_IPV4)
            typestr = "ipv4";
        else if (lease->type == VIR_IP_ADDR_TYPE_IPV6)
            typestr = "ipv6";

        cidr_format = g_strdup_printf("%s/%d", lease->ipaddr, lease->prefix);

        if (vshTableRowAppend(table,
                              thenstr,
                              NULLSTR_MINUS(lease->mac),
                              NULLSTR_MINUS(typestr),
                              NULLSTR_MINUS(cidr_format),
                              NULLSTR_MINUS(lease->hostname),
                              NULLSTR_MINUS(lease->clientid),
                              NULL) < 0)
            goto cleanup;
    }

    vshTablePrintToStdout(table, ctl);

    ret = true;

 cleanup:
    if (leases) {
        for (i = 0; i < nleases; i++)
            virNetworkDHCPLeaseFree(leases[i]);
        VIR_FREE(leases);
    }
    return ret;
}

/*
 * "net-port-create" command
 */
static const vshCmdInfo info_network_port_create[] = {
    {.name = "help",
     .data = N_("create a network port from an XML file")
    },
    {.name = "desc",
     .data = N_("Create a network port.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_port_create[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(VIR_CONNECT_LIST_NETWORKS_ACTIVE),
    VIRSH_COMMON_OPT_FILE(N_("file containing an XML network port description")),
    {.name = "validate",
     .type = VSH_OT_BOOL,
     .help = N_("validate the XML against the schema")
    },
    {.name = NULL}
};

static bool
cmdNetworkPortCreate(vshControl *ctl, const vshCmd *cmd)
{
    virNetworkPortPtr port = NULL;
    const char *from = NULL;
    bool ret = false;
    char *buffer = NULL;
    g_autoptr(virshNetwork) network = NULL;
    unsigned int flags = 0;

    network = virshCommandOptNetwork(ctl, cmd, NULL);
    if (network == NULL)
        goto cleanup;

    if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0)
        goto cleanup;

    if (vshCommandOptBool(cmd, "validate"))
        flags |= VIR_NETWORK_PORT_CREATE_VALIDATE;

    if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) {
        vshSaveLibvirtError();
        goto cleanup;
    }

    port = virNetworkPortCreateXML(network, buffer, flags);

    if (port != NULL) {
        char uuidstr[VIR_UUID_STRING_BUFLEN];
        virNetworkPortGetUUIDString(port, uuidstr);
        vshPrintExtra(ctl, _("Network port %s created from %s\n"),
                      uuidstr, from);
    } else {
        vshError(ctl, _("Failed to create network from %s"), from);
        goto cleanup;
    }

    ret = true;
 cleanup:
    VIR_FREE(buffer);
    if (port)
        virNetworkPortFree(port);
    return ret;
}

/*
 * "net-port-dumpxml" command
 */
static const vshCmdInfo info_network_port_dumpxml[] = {
    {.name = "help",
     .data = N_("network port information in XML")
    },
    {.name = "desc",
     .data = N_("Output the network port information as an XML dump to stdout.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_port_dumpxml[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(VIR_CONNECT_LIST_NETWORKS_ACTIVE),
    VIRSH_COMMON_OPT_NETWORK_PORT(0),
    {.name = "xpath",
     .type = VSH_OT_STRING,
     .completer = virshCompleteEmpty,
     .help = N_("xpath expression to filter the XML document")
    },
    {.name = "wrap",
     .type = VSH_OT_BOOL,
     .help = N_("wrap xpath results in an common root element"),
    },
    {.name = NULL}
};

static bool
cmdNetworkPortDumpXML(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    virNetworkPortPtr port = NULL;
    bool ret = true;
    g_autofree char *xml = NULL;
    unsigned int flags = 0;
    bool wrap = vshCommandOptBool(cmd, "wrap");
    const char *xpath = NULL;

    if (!(network = virshCommandOptNetwork(ctl, cmd, NULL)))
        goto cleanup;

    if (!(port = virshCommandOptNetworkPort(ctl, cmd, network, NULL)))
        goto cleanup;

    if (vshCommandOptStringQuiet(ctl, cmd, "xpath", &xpath) < 0)
        return false;

    if (!(xml = virNetworkPortGetXMLDesc(port, flags)))
        goto cleanup;

    ret = virshDumpXML(ctl, xml, "network-port", xpath, wrap);

 cleanup:
    if (port)
        virNetworkPortFree(port);
    return ret;
}


/*
 * "net-port-delete" command
 */
static const vshCmdInfo info_network_port_delete[] = {
    {.name = "help",
     .data = N_("delete the specified network port")
    },
    {.name = "desc",
     .data = N_("Delete the specified network port.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_port_delete[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(VIR_CONNECT_LIST_NETWORKS_ACTIVE),
    VIRSH_COMMON_OPT_NETWORK_PORT(0),
    {.name = NULL}
};

static bool
cmdNetworkPortDelete(vshControl *ctl, const vshCmd *cmd)
{
    g_autoptr(virshNetwork) network = NULL;
    virNetworkPortPtr port = NULL;
    bool ret = true;
    char uuidstr[VIR_UUID_STRING_BUFLEN];

    if (!(network = virshCommandOptNetwork(ctl, cmd, NULL)))
        goto cleanup;

    if (!(port = virshCommandOptNetworkPort(ctl, cmd, network, NULL)))
        goto cleanup;

    if (virNetworkPortGetUUIDString(port, uuidstr) < 0)
        goto cleanup;

    if (virNetworkPortDelete(port, 0) < 0) {
        vshError(ctl, _("Failed to delete network port %s"), uuidstr);
        goto cleanup;
    } else {
        vshPrintExtra(ctl, _("Network port %s deleted\n"), uuidstr);
    }

    ret = true;
 cleanup:
    if (port)
        virNetworkPortFree(port);
    return ret;
}


static int
virshNetworkPortSorter(const void *a, const void *b)
{
    virNetworkPortPtr *na = (virNetworkPortPtr *) a;
    virNetworkPortPtr *nb = (virNetworkPortPtr *) b;
    unsigned char uuida[VIR_UUID_BUFLEN];
    unsigned char uuidb[VIR_UUID_BUFLEN];

    if (*na && !*nb)
        return -1;

    if (!*na)
        return *nb != NULL;

    if (virNetworkPortGetUUID(*na, uuida) < 0 ||
        virNetworkPortGetUUID(*nb, uuidb) < 0)
        return -1;

    return memcmp(uuida, uuidb, VIR_UUID_BUFLEN);
}

struct virshNetworkPortList {
    virNetworkPortPtr *ports;
    size_t nports;
};

static void
virshNetworkPortListFree(struct virshNetworkPortList *list)
{
    size_t i;

    if (list && list->ports) {
        for (i = 0; i < list->nports; i++) {
            if (list->ports[i])
                virNetworkPortFree(list->ports[i]);
        }
        g_free(list->ports);
    }
    g_free(list);
}

static struct virshNetworkPortList *
virshNetworkPortListCollect(vshControl *ctl,
                            const vshCmd *cmd,
                            unsigned int flags)
{
    struct virshNetworkPortList *list = g_new0(struct virshNetworkPortList, 1);
    int ret;
    g_autoptr(virshNetwork) network = NULL;
    bool success = false;

    if (!(network = virshCommandOptNetwork(ctl, cmd, NULL)))
        goto cleanup;

    if ((ret = virNetworkListAllPorts(network,
                                      &list->ports,
                                      flags)) < 0)
        goto cleanup;

    list->nports = ret;

    /* sort the list */
    if (list->ports && list->nports)
        qsort(list->ports, list->nports,
              sizeof(*list->ports), virshNetworkPortSorter);

    success = true;

 cleanup:
    if (!success) {
        g_clear_pointer(&list, virshNetworkPortListFree);
    }

    return list;
}

/*
 * "net-list" command
 */
static const vshCmdInfo info_network_port_list[] = {
    {.name = "help",
     .data = N_("list network ports")
    },
    {.name = "desc",
     .data = N_("Returns list of network ports.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_network_port_list[] = {
    VIRSH_COMMON_OPT_NETWORK_FULL(VIR_CONNECT_LIST_NETWORKS_ACTIVE),
    {.name = "uuid",
     .type = VSH_OT_BOOL,
     .help = N_("list uuid's only")
    },
    {.name = "table",
     .type = VSH_OT_BOOL,
     .help = N_("list table (default)")
    },
    {.name = NULL}
};

static bool
cmdNetworkPortList(vshControl *ctl, const vshCmd *cmd)
{
    struct virshNetworkPortList *list = NULL;
    size_t i;
    bool ret = false;
    bool optTable = vshCommandOptBool(cmd, "table");
    bool optUUID = vshCommandOptBool(cmd, "uuid");
    char uuid[VIR_UUID_STRING_BUFLEN];
    unsigned int flags = 0;
    g_autoptr(vshTable) table = NULL;

    if (optTable + optUUID > 1) {
        vshError(ctl, "%s",
                 _("Only one argument from --table and --uuid "
                   "may be specified."));
        return false;
    }

    if (!optUUID)
        optTable = true;

    if (!(list = virshNetworkPortListCollect(ctl, cmd, flags)))
        return false;

    if (optTable) {
        table = vshTableNew(_("UUID"), NULL);
        if (!table)
            goto cleanup;
    }

    for (i = 0; i < list->nports; i++) {
        virNetworkPortPtr port = list->ports[i];

        if (virNetworkPortGetUUIDString(port, uuid) < 0) {
            vshError(ctl, "%s", _("Failed to get network's UUID"));
            goto cleanup;
        }
        if (optTable) {
            if (vshTableRowAppend(table, uuid, NULL) < 0)
                goto cleanup;
        } else if (optUUID) {
            vshPrint(ctl, "%s\n", uuid);
        }
    }

    if (optTable)
        vshTablePrintToStdout(table, ctl);

    ret = true;
 cleanup:
    virshNetworkPortListFree(list);
    return ret;
}


const vshCmdDef networkCmds[] = {
    {.name = "net-autostart",
     .handler = cmdNetworkAutostart,
     .opts = opts_network_autostart,
     .info = info_network_autostart,
     .flags = 0
    },
    {.name = "net-create",
     .handler = cmdNetworkCreate,
     .opts = opts_network_create,
     .info = info_network_create,
     .flags = 0
    },
    {.name = "net-define",
     .handler = cmdNetworkDefine,
     .opts = opts_network_define,
     .info = info_network_define,
     .flags = 0
    },
    {.name = "net-destroy",
     .handler = cmdNetworkDestroy,
     .opts = opts_network_destroy,
     .info = info_network_destroy,
     .flags = 0
    },
    {.name = "net-dhcp-leases",
     .handler = cmdNetworkDHCPLeases,
     .opts = opts_network_dhcp_leases,
     .info = info_network_dhcp_leases,
     .flags = 0,
    },
    {.name = "net-dumpxml",
     .handler = cmdNetworkDumpXML,
     .opts = opts_network_dumpxml,
     .info = info_network_dumpxml,
     .flags = 0
    },
    {.name = "net-edit",
     .handler = cmdNetworkEdit,
     .opts = opts_network_edit,
     .info = info_network_edit,
     .flags = 0
    },
    {.name = "net-event",
     .handler = cmdNetworkEvent,
     .opts = opts_network_event,
     .info = info_network_event,
     .flags = 0
    },
    {.name = "net-info",
     .handler = cmdNetworkInfo,
     .opts = opts_network_info,
     .info = info_network_info,
     .flags = 0
    },
    {.name = "net-list",
     .handler = cmdNetworkList,
     .opts = opts_network_list,
     .info = info_network_list,
     .flags = 0
    },
    {.name = "net-name",
     .handler = cmdNetworkName,
     .opts = opts_network_name,
     .info = info_network_name,
     .flags = 0
    },
    {.name = "net-start",
     .handler = cmdNetworkStart,
     .opts = opts_network_start,
     .info = info_network_start,
     .flags = 0
    },
    {.name = "net-undefine",
     .handler = cmdNetworkUndefine,
     .opts = opts_network_undefine,
     .info = info_network_undefine,
     .flags = 0
    },
    {.name = "net-update",
     .handler = cmdNetworkUpdate,
     .opts = opts_network_update,
     .info = info_network_update,
     .flags = 0
    },
    {.name = "net-uuid",
     .handler = cmdNetworkUuid,
     .opts = opts_network_uuid,
     .info = info_network_uuid,
     .flags = 0
    },
    {.name = "net-port-list",
     .handler = cmdNetworkPortList,
     .opts = opts_network_port_list,
     .info = info_network_port_list,
     .flags = 0
    },
    {.name = "net-port-create",
     .handler = cmdNetworkPortCreate,
     .opts = opts_network_port_create,
     .info = info_network_port_create,
     .flags = 0
    },
    {.name = "net-port-dumpxml",
     .handler = cmdNetworkPortDumpXML,
     .opts = opts_network_port_dumpxml,
     .info = info_network_port_dumpxml,
     .flags = 0
    },
    {.name = "net-port-delete",
     .handler = cmdNetworkPortDelete,
     .opts = opts_network_port_delete,
     .info = info_network_port_delete,
     .flags = 0
    },
    {.name = NULL}
};
