/* * 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 * . */ #include #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 '%1$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 '%1$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 %1$s as autostarted"), name); else vshError(ctl, _("failed to unmark network %1$s as autostarted"), name); return false; } if (autostart) vshPrintExtra(ctl, _("Network %1$s marked as autostarted\n"), name); else vshPrintExtra(ctl, _("Network %1$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 %1$s"), from); return false; } vshPrintExtra(ctl, _("Network %1$s created from %2$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 %1$s"), from); return false; } vshPrintExtra(ctl, _("Network %1$s defined from %2$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 %1$s destroyed\n"), name); } else { vshError(ctl, _("Failed to destroy network %1$s"), name); ret = false; } return ret; } /* * "net-desc" command */ static const vshCmdInfo info_network_desc[] = { {.name = "help", .data = N_("show or set network's description or title") }, {.name = "desc", .data = N_("Allows setting or modifying the description or title of a network.") }, {.name = NULL} }; static const vshCmdOptDef opts_network_desc[] = { VIRSH_COMMON_OPT_NETWORK_FULL(0), VIRSH_COMMON_OPT_LIVE(N_("modify/get running state")), VIRSH_COMMON_OPT_CONFIG(N_("modify/get persistent configuration")), VIRSH_COMMON_OPT_CURRENT(N_("modify/get current state configuration")), {.name = "title", .type = VSH_OT_BOOL, .help = N_("modify/get the title instead of description") }, {.name = "edit", .type = VSH_OT_BOOL, .help = N_("open an editor to modify the description") }, {.name = "new-desc", .type = VSH_OT_ARGV, .help = N_("message") }, {.name = NULL} }; /* extract description or title from network xml */ static char * virshGetNetworkDescription(vshControl *ctl, virNetworkPtr net, bool title, unsigned int flags, unsigned int queryflags) { char *desc = NULL; g_autoptr(xmlDoc) doc = NULL; g_autoptr(xmlXPathContext) ctxt = NULL; int type; if (title) type = VIR_NETWORK_METADATA_TITLE; else type = VIR_NETWORK_METADATA_DESCRIPTION; if ((desc = virNetworkGetMetadata(net, type, NULL, flags))) { return desc; } else { int errCode = virGetLastErrorCode(); if (errCode == VIR_ERR_NO_NETWORK_METADATA) { desc = g_strdup(""); vshResetLibvirtError(); return desc; } if (errCode != VIR_ERR_NO_SUPPORT) return desc; } /* fall back to xml */ if (virshNetworkGetXMLFromNet(ctl, net, queryflags, &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; } static bool cmdNetworkDesc(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshNetwork) net = NULL; bool config = vshCommandOptBool(cmd, "config"); bool live = vshCommandOptBool(cmd, "live"); bool current = vshCommandOptBool(cmd, "current"); bool title = vshCommandOptBool(cmd, "title"); bool edit = vshCommandOptBool(cmd, "edit"); int type; g_autofree char *descArg = NULL; const vshCmdOpt *opt = NULL; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; unsigned int flags = VIR_NETWORK_UPDATE_AFFECT_CURRENT; unsigned int queryflags = 0; VSH_EXCLUSIVE_OPTIONS_VAR(current, live); VSH_EXCLUSIVE_OPTIONS_VAR(current, config); if (config) { flags |= VIR_NETWORK_UPDATE_AFFECT_CONFIG; queryflags |= VIR_NETWORK_XML_INACTIVE; } if (live) flags |= VIR_NETWORK_UPDATE_AFFECT_LIVE; if (!(net = virshCommandOptNetwork(ctl, cmd, NULL))) return false; if (title) type = VIR_NETWORK_METADATA_TITLE; else type = VIR_NETWORK_METADATA_DESCRIPTION; while ((opt = vshCommandOptArgv(ctl, cmd, opt))) virBufferAsprintf(&buf, "%s ", opt->data); virBufferTrim(&buf, " "); descArg = virBufferContentAndReset(&buf); if (edit || descArg) { g_autofree char *descNet = NULL; g_autofree char *descNew = NULL; if (!(descNet = virshGetNetworkDescription(ctl, net, title, flags, queryflags))) return false; if (!descArg) descArg = g_strdup(descNet); if (edit) { g_autoptr(vshTempFile) tmp = NULL; g_autofree char *desc_edited = NULL; char *tmpstr; /* Create and open the temporary file. */ if (!(tmp = vshEditWriteToTempFile(ctl, descArg))) return false; /* Start the editor. */ if (vshEditFile(ctl, tmp) == -1) return false; /* Read back the edited file. */ if (!(desc_edited = vshEditReadBackFile(ctl, tmp))) return false; /* strip a possible newline at the end of file; some * editors enforce a newline, this makes editing the title * more convenient */ if (title && (tmpstr = strrchr(desc_edited, '\n')) && *(tmpstr+1) == '\0') *tmpstr = '\0'; /* Compare original XML with edited. Has it changed at all? */ if (STREQ(descNet, desc_edited)) { if (title) vshPrintExtra(ctl, "%s", _("Network title not changed\n")); else vshPrintExtra(ctl, "%s", _("Network description not changed\n")); return true; } descNew = g_steal_pointer(&desc_edited); } else { descNew = g_steal_pointer(&descArg); } if (virNetworkSetMetadata(net, type, descNew, NULL, NULL, flags) < 0) { if (title) vshError(ctl, "%s", _("Failed to set new network title")); else vshError(ctl, "%s", _("Failed to set new network description")); return false; } if (title) vshPrintExtra(ctl, "%s", _("Network title updated successfully")); else vshPrintExtra(ctl, "%s", _("Network description updated successfully")); } else { g_autofree char *desc = virshGetNetworkDescription(ctl, net, title, flags, queryflags); if (!desc) return false; if (strlen(desc) > 0) { vshPrint(ctl, "%s", desc); } else { if (title) vshPrintExtra(ctl, _("No title for network: %1$s"), virNetworkGetName(net)); else vshPrintExtra(ctl, _("No description for network: %1$s"), virNetworkGetName(net)); } } return true; } /* * "net-metadata" command */ static const vshCmdInfo info_network_metadata[] = { {.name = "help", .data = N_("show or set network's custom XML metadata") }, {.name = "desc", .data = N_("Shows or modifies the XML metadata of a network.") }, {.name = NULL} }; static const vshCmdOptDef opts_network_metadata[] = { VIRSH_COMMON_OPT_NETWORK_FULL(0), {.name = "uri", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("URI of the namespace") }, VIRSH_COMMON_OPT_LIVE(N_("modify/get running state")), VIRSH_COMMON_OPT_CONFIG(N_("modify/get persistent configuration")), VIRSH_COMMON_OPT_CURRENT(N_("modify/get current state configuration")), {.name = "edit", .type = VSH_OT_BOOL, .help = N_("use an editor to change the metadata") }, {.name = "key", .type = VSH_OT_STRING, .help = N_("key to be used as a namespace identifier"), }, {.name = "set", .type = VSH_OT_STRING, .completer = virshCompleteEmpty, .help = N_("new metadata to set"), }, {.name = "remove", .type = VSH_OT_BOOL, .help = N_("remove the metadata corresponding to an uri") }, {.name = NULL} }; /* helper to add new metadata using the --edit option */ static char * virshNetworkGetEditMetadata(vshControl *ctl G_GNUC_UNUSED, virNetworkPtr net, const char *uri, unsigned int flags) { char *ret; if (!(ret = virNetworkGetMetadata(net, VIR_NETWORK_METADATA_ELEMENT, uri, flags))) { vshResetLibvirtError(); ret = g_strdup("\n"); } return ret; } static bool cmdNetworkMetadata(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshNetwork) net = NULL; bool config = vshCommandOptBool(cmd, "config"); bool live = vshCommandOptBool(cmd, "live"); bool current = vshCommandOptBool(cmd, "current"); bool edit = vshCommandOptBool(cmd, "edit"); bool rem = vshCommandOptBool(cmd, "remove"); const char *set = NULL; const char *uri = NULL; const char *key = NULL; unsigned int flags = VIR_NETWORK_UPDATE_AFFECT_CURRENT; bool ret = false; VSH_EXCLUSIVE_OPTIONS_VAR(current, live); VSH_EXCLUSIVE_OPTIONS_VAR(current, config); VSH_EXCLUSIVE_OPTIONS("edit", "set"); VSH_EXCLUSIVE_OPTIONS("remove", "set"); VSH_EXCLUSIVE_OPTIONS("remove", "edit"); if (config) flags |= VIR_NETWORK_UPDATE_AFFECT_CONFIG; if (live) flags |= VIR_NETWORK_UPDATE_AFFECT_LIVE; if (!(net = virshCommandOptNetwork(ctl, cmd, NULL))) return false; if (vshCommandOptStringReq(ctl, cmd, "uri", &uri) < 0 || vshCommandOptStringReq(ctl, cmd, "key", &key) < 0 || vshCommandOptStringReq(ctl, cmd, "set", &set) < 0) return false; if ((set || edit) && !key) { vshError(ctl, "%s", _("namespace key is required when modifying metadata")); return false; } if (set || rem) { if (virNetworkSetMetadata(net, VIR_NETWORK_METADATA_ELEMENT, set, key, uri, flags)) return false; if (rem) vshPrintExtra(ctl, "%s\n", _("Metadata removed")); else vshPrintExtra(ctl, "%s\n", _("Metadata modified")); } else if (edit) { #define EDIT_GET_XML \ virshNetworkGetEditMetadata(ctl, net, uri, flags) #define EDIT_NOT_CHANGED \ do { \ vshPrintExtra(ctl, "%s", _("Metadata not changed")); \ ret = true; \ goto edit_cleanup; \ } while (0) #define EDIT_DEFINE \ (virNetworkSetMetadata(net, VIR_NETWORK_METADATA_ELEMENT, doc_edited, \ key, uri, flags) == 0) #include "virsh-edit.c" vshPrintExtra(ctl, "%s\n", _("Metadata modified")); } else { g_autofree char *data = NULL; /* get */ if (!(data = virNetworkGetMetadata(net, VIR_NETWORK_METADATA_ELEMENT, uri, flags))) return false; vshPrint(ctl, "%s\n", data); } ret = true; cleanup: 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, .flags = VSH_OFLAG_REQ_OPT, .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; g_autofree 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); return true; } static int virshNetworkSorter(const void *a, const void *b, void *opaque G_GNUC_UNUSED) { 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) { g_qsort_with_data(list->nets, list->nnets, sizeof(*list->nets), virshNetworkSorter, NULL); } /* 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 = "title", .type = VSH_OT_BOOL, .help = N_("show network title") }, {.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 optTitle = vshCommandOptBool(cmd, "title"); 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) { if (optTitle) table = vshTableNew(_("Name"), _("State"), _("Autostart"), _("Persistent"), _("Title"), NULL); else 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 (optTitle) { g_autofree char *title = NULL; if (!(title = virshGetNetworkDescription(ctl, network, true, 0, 0))) goto cleanup; if (vshTableRowAppend(table, virNetworkGetName(network), virNetworkIsActive(network) ? _("active") : _("inactive"), autostartStr, virNetworkIsPersistent(network) ? _("yes") : _("no"), title, NULL) < 0) goto cleanup; } else { 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 %1$s started\n"), name); } else { vshError(ctl, _("Failed to start network %1$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 %1$s has been undefined\n"), name); } else { vshError(ctl, _("Failed to undefine network %1$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 '%1$s'"), commandStr); goto cleanup; } } if (vshCommandOptStringReq(ctl, cmd, "section", §ionStr) < 0) goto cleanup; section = virshNetworkSectionTypeFromString(sectionStr); if (section <= 0) { vshError(ctl, _("unrecognized section name '%1$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 %1$s"), virNetworkGetName(network)); goto cleanup; } if (config) { if (live) vshPrintExtra(ctl, _("Updated network %1$s persistent config and live state"), virNetworkGetName(network)); else vshPrintExtra(ctl, _("Updated network %1$s persistent config"), virNetworkGetName(network)); } else if (live) { vshPrintExtra(ctl, _("Updated network %1$s live state"), virNetworkGetName(network)); } else if (virNetworkIsActive(network)) { vshPrintExtra(ctl, _("Updated network %1$s live state"), virNetworkGetName(network)); } else { vshPrintExtra(ctl, _("Updated network %1$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 %1$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 %1$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", "metadata-change"); 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, _("%1$s: event 'lifecycle' for network %2$s: %3$s\n"), timestamp, virNetworkGetName(net), virshNetworkEventToString(event)); } else { vshPrint(data->ctl, _("event 'lifecycle' for network %1$s: %2$s\n"), virNetworkGetName(net), virshNetworkEventToString(event)); } data->count++; if (!data->loop) vshEventDone(data->ctl); } VIR_ENUM_DECL(virshNetworkEventMetadataChangeType); VIR_ENUM_IMPL(virshNetworkEventMetadataChangeType, VIR_NETWORK_METADATA_LAST, N_("description"), N_("title"), N_("element")); #define UNKNOWNSTR(str) (str ? str : N_("unsupported value")) static void vshEventMetadataChangePrint(virConnectPtr conn G_GNUC_UNUSED, virNetworkPtr net, int type, const char *nsuri, 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, _("%1$s: event 'metadata-change' for network %2$s: type %3$s, uri %4$s\n"), timestamp, virNetworkGetName(net), UNKNOWNSTR(virshNetworkEventMetadataChangeTypeTypeToString(type)), NULLSTR(nsuri)); } else { vshPrint(data->ctl, _("event 'metadata-change' for network %1$s: type %2$s, uri %3$s\n"), virNetworkGetName(net), UNKNOWNSTR(virshNetworkEventMetadataChangeTypeTypeToString(type)), NULLSTR(nsuri)); } data->count++; if (!data->loop) vshEventDone(data->ctl); } virshNetworkEventCallback virshNetworkEventCallbacks[] = { { "lifecycle", VIR_NETWORK_EVENT_CALLBACK(vshEventLifecyclePrint), }, { "metadata-change", VIR_NETWORK_EVENT_CALLBACK(vshEventMetadataChangePrint), }, }; 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 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 %1$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: %1$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, void *opaque G_GNUC_UNUSED) { 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 %1$s"), name); goto cleanup; } /* Sort the list according to MAC Address/IAID */ g_qsort_with_data(leases, nleases, sizeof(*leases), virshNetworkDHCPLeaseSorter, NULL); 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; g_autofree 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 %1$s created from %2$s\n"), uuidstr, from); } else { vshError(ctl, _("Failed to create network from %1$s"), from); goto cleanup; } ret = true; cleanup: 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, .flags = VSH_OFLAG_REQ_OPT, .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 %1$s"), uuidstr); goto cleanup; } else { vshPrintExtra(ctl, _("Network port %1$s deleted\n"), uuidstr); } ret = true; cleanup: if (port) virNetworkPortFree(port); return ret; } static int virshNetworkPortSorter(const void *a, const void *b, void *opaque G_GNUC_UNUSED) { 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) { g_qsort_with_data(list->ports, list->nports, sizeof(*list->ports), virshNetworkPortSorter, NULL); } 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-desc", .handler = cmdNetworkDesc, .opts = opts_network_desc, .info = info_network_desc, .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-metadata", .handler = cmdNetworkMetadata, .opts = opts_network_metadata, .info = info_network_metadata, .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} };