/* * virsh-nodedev.c: Commands in node device group * * Copyright (C) 2005, 2007-2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include "virsh-completer-nodedev.h" #include "virsh-nodedev.h" #include "virsh-util.h" #include "internal.h" #include "viralloc.h" #include "virfile.h" #include "virtime.h" #include "conf/node_device_conf.h" #include "virenum.h" #include "virutil.h" /* * "nodedev-create" command */ static const vshCmdInfo info_node_device_create = { .help = N_("create a device defined " "by an XML file on the node"), .desc = N_("Create a device on the node. Note that this " "command creates devices on the physical host " "that can then be assigned to a virtual machine."), }; static const vshCmdOptDef opts_node_device_create[] = { VIRSH_COMMON_OPT_FILE(N_("file containing an XML description " "of the device")), {.name = "validate", .type = VSH_OT_BOOL, .help = N_("validate the XML against the schema") }, {.name = NULL} }; static bool cmdNodeDeviceCreate(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshNodeDevice) dev = NULL; const char *from = NULL; g_autofree char *buffer = NULL; virshControl *priv = ctl->privData; unsigned int flags = 0; if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0) return false; if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) return false; if (vshCommandOptBool(cmd, "validate")) flags |= VIR_NODE_DEVICE_CREATE_XML_VALIDATE; if (!(dev = virNodeDeviceCreateXML(priv->conn, buffer, flags))) { vshError(ctl, _("Failed to create node device from %1$s"), from); return false; } vshPrintExtra(ctl, _("Node device %1$s created from %2$s\n"), virNodeDeviceGetName(dev), from); return true; } /* * "nodedev-destroy" command */ static const vshCmdInfo info_node_device_destroy = { .help = N_("destroy (stop) a device on the node"), .desc = N_("Destroy a device on the node. Note that this " "command destroys devices on the physical host"), }; static const vshCmdOptDef opts_node_device_destroy[] = { {.name = "name", .type = VSH_OT_ALIAS, .help = "device" }, {.name = "device", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("device name or wwn pair in 'wwnn,wwpn' format"), .completer = virshNodeDeviceNameCompleter, }, {.name = NULL} }; static virNodeDevice* vshFindNodeDevice(vshControl *ctl, const char *value) { virNodeDevicePtr dev = NULL; g_auto(GStrv) arr = NULL; int narr; virshControl *priv = ctl->privData; if (strchr(value, ',')) { narr = vshStringToArray(value, &arr); if (narr != 2) { vshError(ctl, _("Malformed device value '%1$s'"), value); return NULL; } if (!virValidateWWN(arr[0]) || !virValidateWWN(arr[1])) return NULL; dev = virNodeDeviceLookupSCSIHostByWWN(priv->conn, arr[0], arr[1], 0); } else { dev = virNodeDeviceLookupByName(priv->conn, value); } if (!dev) { vshError(ctl, "%s '%s'", _("Could not find matching device"), value); return NULL; } return dev; } static bool cmdNodeDeviceDestroy(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshNodeDevice) dev = NULL; const char *device_value = NULL; if (vshCommandOptStringReq(ctl, cmd, "device", &device_value) < 0) return false; dev = vshFindNodeDevice(ctl, device_value); if (!dev) return false; if (virNodeDeviceDestroy(dev) == 0) { vshPrintExtra(ctl, _("Destroyed node device '%1$s'\n"), device_value); } else { vshError(ctl, _("Failed to destroy node device '%1$s'"), device_value); return false; } return true; } struct virshNodeList { char **names; char **parents; }; static const char * virshNodeListLookup(int devid, bool parent, void *opaque) { struct virshNodeList *arrays = opaque; if (parent) return arrays->parents[devid]; return arrays->names[devid]; } static int virshNodeDeviceSorter(const void *a, const void *b, void *opaque G_GNUC_UNUSED) { virNodeDevicePtr *na = (virNodeDevicePtr *) a; virNodeDevicePtr *nb = (virNodeDevicePtr *) b; if (*na && !*nb) return -1; if (!*na) return *nb != NULL; return vshStrcasecmp(virNodeDeviceGetName(*na), virNodeDeviceGetName(*nb)); } struct virshNodeDeviceList { virNodeDevicePtr *devices; size_t ndevices; }; static void virshNodeDeviceListFree(struct virshNodeDeviceList *list) { size_t i; if (list && list->devices) { for (i = 0; i < list->ndevices; i++) { virshNodeDeviceFree(list->devices[i]); } g_free(list->devices); } g_free(list); } static struct virshNodeDeviceList * virshNodeDeviceListCollect(vshControl *ctl, char **capnames, int ncapnames, unsigned int flags) { struct virshNodeDeviceList *list = g_new0(struct virshNodeDeviceList, 1); size_t i; int ret; virNodeDevicePtr device; bool success = false; size_t deleted = 0; int ndevices = 0; char **names = NULL; virshControl *priv = ctl->privData; /* try the list with flags support (0.10.2 and later) */ if ((ret = virConnectListAllNodeDevices(priv->conn, &list->devices, flags)) >= 0) { list->ndevices = ret; goto finished; } /* check if the command is actually supported */ if (last_error && last_error->code == VIR_ERR_NO_SUPPORT) goto fallback; /* there was an error during the call */ vshError(ctl, "%s", _("Failed to list node devices")); goto cleanup; fallback: /* fall back to old method (0.10.1 and older) */ vshResetLibvirtError(); ndevices = virNodeNumOfDevices(priv->conn, NULL, 0); if (ndevices < 0) { vshError(ctl, "%s", _("Failed to count node devices")); goto cleanup; } if (ndevices == 0) return list; names = g_new0(char *, ndevices); ndevices = virNodeListDevices(priv->conn, NULL, names, ndevices, 0); if (ndevices < 0) { vshError(ctl, "%s", _("Failed to list node devices")); goto cleanup; } list->devices = g_new0(virNodeDevicePtr, ndevices); list->ndevices = 0; /* get the node devices */ for (i = 0; i < ndevices; i++) { if (!(device = virNodeDeviceLookupByName(priv->conn, names[i]))) continue; list->devices[list->ndevices++] = device; } /* truncate domains that weren't found */ deleted = ndevices - list->ndevices; if (!capnames) goto finished; /* filter the list if the list was acquired by fallback means */ for (i = 0; i < list->ndevices; i++) { g_autofree char **caps = NULL; int ncaps = 0; bool match = false; size_t j, k; device = list->devices[i]; if ((ncaps = virNodeDeviceNumOfCaps(device)) < 0) { vshError(ctl, "%s", _("Failed to get capability numbers of the device")); goto cleanup; } caps = g_new0(char *, ncaps); if ((ncaps = virNodeDeviceListCaps(device, caps, ncaps)) < 0) { vshError(ctl, "%s", _("Failed to get capability names of the device")); goto cleanup; } /* Check if the device's capability matches with provided * capabilities. */ for (j = 0; j < ncaps; j++) { for (k = 0; k < ncapnames; k++) { if (STREQ(caps[j], capnames[k])) { match = true; break; } } } if (!match) goto remove_entry; /* the device matched all filters, it may stay */ continue; remove_entry: /* the device has to be removed as it failed one of the filters */ g_clear_pointer(&list->devices[i], virshNodeDeviceFree); deleted++; } finished: /* sort the list */ if (list->devices && list->ndevices) { g_qsort_with_data(list->devices, list->ndevices, sizeof(*list->devices), virshNodeDeviceSorter, NULL); } /* truncate the list if filter simulation deleted entries */ if (deleted) VIR_SHRINK_N(list->devices, list->ndevices, deleted); success = true; cleanup: for (i = 0; ndevices != -1 && i < ndevices; i++) VIR_FREE(names[i]); VIR_FREE(names); if (!success) { g_clear_pointer(&list, virshNodeDeviceListFree); } return list; } /* * "nodedev-list" command */ static const vshCmdInfo info_node_list_devices = { .help = N_("enumerate devices on this host"), .desc = "", }; static const vshCmdOptDef opts_node_list_devices[] = { {.name = "tree", .type = VSH_OT_BOOL, .help = N_("list devices in a tree") }, {.name = "cap", .type = VSH_OT_STRING, .completer = virshNodeDeviceCapabilityNameCompleter, .help = N_("capability names, separated by comma") }, {.name = "inactive", .type = VSH_OT_BOOL, .help = N_("list inactive devices") }, {.name = "all", .type = VSH_OT_BOOL, .help = N_("list inactive & active devices") }, {.name = "persistent", .type = VSH_OT_BOOL, .help = N_("list persistent devices") }, {.name = "transient", .type = VSH_OT_BOOL, .help = N_("list transient devices") }, {.name = NULL} }; static bool cmdNodeListDevices(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED) { const char *cap_str = NULL; size_t i; bool tree = vshCommandOptBool(cmd, "tree"); bool ret = true; unsigned int flags = 0; g_auto(GStrv) caps = NULL; int ncaps = 0; struct virshNodeDeviceList *list = NULL; int cap_type = -1; bool inactive = vshCommandOptBool(cmd, "inactive"); bool all = vshCommandOptBool(cmd, "all"); bool persistent = vshCommandOptBool(cmd, "persistent"); bool transient = vshCommandOptBool(cmd, "transient"); ignore_value(vshCommandOptStringQuiet(ctl, cmd, "cap", &cap_str)); if (cap_str) { if ((ncaps = vshStringToArray(cap_str, &caps)) < 0) return false; } if (all && inactive) { vshError(ctl, "%s", _("Option --all is incompatible with --inactive")); return false; } if (transient && (persistent || inactive)) { vshError(ctl, "%s", _("Option --transient is incompatible with --persistent and --inactive")); return false; } if (tree && (cap_str || inactive || persistent || transient)) { vshError(ctl, "%s", _("Option --tree is incompatible with --cap, --inactive, --persistent and --transient")); return false; } for (i = 0; i < ncaps; i++) { if ((cap_type = virNodeDevCapTypeFromString(caps[i])) < 0) { vshError(ctl, "%s", _("Invalid capability type")); ret = false; goto cleanup; } switch ((virNodeDevCapType) cap_type) { case VIR_NODE_DEV_CAP_SYSTEM: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_SYSTEM; break; case VIR_NODE_DEV_CAP_PCI_DEV: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_PCI_DEV; break; case VIR_NODE_DEV_CAP_USB_DEV: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_USB_DEV; break; case VIR_NODE_DEV_CAP_USB_INTERFACE: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_USB_INTERFACE; break; case VIR_NODE_DEV_CAP_NET: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_NET; break; case VIR_NODE_DEV_CAP_SCSI_HOST: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_SCSI_HOST; break; case VIR_NODE_DEV_CAP_SCSI_TARGET: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_SCSI_TARGET; break; case VIR_NODE_DEV_CAP_SCSI: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_SCSI; break; case VIR_NODE_DEV_CAP_STORAGE: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_STORAGE; break; case VIR_NODE_DEV_CAP_FC_HOST: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST; break; case VIR_NODE_DEV_CAP_VPORTS: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_VPORTS; break; case VIR_NODE_DEV_CAP_SCSI_GENERIC: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_SCSI_GENERIC; break; case VIR_NODE_DEV_CAP_DRM: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_DRM; break; case VIR_NODE_DEV_CAP_MDEV_TYPES: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_MDEV_TYPES; break; case VIR_NODE_DEV_CAP_MDEV: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_MDEV; break; case VIR_NODE_DEV_CAP_VPD: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_VPD; break; case VIR_NODE_DEV_CAP_CCW_DEV: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_CCW_DEV; break; case VIR_NODE_DEV_CAP_CSS_DEV: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_CSS_DEV; break; case VIR_NODE_DEV_CAP_VDPA: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_VDPA; break; case VIR_NODE_DEV_CAP_AP_CARD: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_CARD; break; case VIR_NODE_DEV_CAP_AP_QUEUE: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_QUEUE; break; case VIR_NODE_DEV_CAP_AP_MATRIX: flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_MATRIX; break; case VIR_NODE_DEV_CAP_LAST: break; } } if (inactive || all) flags |= VIR_CONNECT_LIST_NODE_DEVICES_INACTIVE; if (!inactive) flags |= VIR_CONNECT_LIST_NODE_DEVICES_ACTIVE; if (persistent) flags |= VIR_CONNECT_LIST_NODE_DEVICES_PERSISTENT; if (transient) flags |= VIR_CONNECT_LIST_NODE_DEVICES_TRANSIENT; if (!(list = virshNodeDeviceListCollect(ctl, caps, ncaps, flags))) { ret = false; goto cleanup; } if (tree) { char **parents = g_new0(char *, list->ndevices); char **names = g_new0(char *, list->ndevices); struct virshNodeList arrays = { names, parents }; for (i = 0; i < list->ndevices; i++) names[i] = g_strdup(virNodeDeviceGetName(list->devices[i])); for (i = 0; i < list->ndevices; i++) { virNodeDevicePtr dev = list->devices[i]; if (STRNEQ(names[i], "computer")) { parents[i] = g_strdup(virNodeDeviceGetParent(dev)); } else { parents[i] = NULL; } } for (i = 0; i < list->ndevices; i++) { if (parents[i] == NULL && vshTreePrint(ctl, virshNodeListLookup, &arrays, list->ndevices, i) < 0) ret = false; } for (i = 0; i < list->ndevices; i++) VIR_FREE(parents[i]); VIR_FREE(parents); for (i = 0; i < list->ndevices; i++) VIR_FREE(names[i]); VIR_FREE(names); } else { for (i = 0; i < list->ndevices; i++) vshPrint(ctl, "%s\n", virNodeDeviceGetName(list->devices[i])); } cleanup: virshNodeDeviceListFree(list); return ret; } /* * "nodedev-dumpxml" command */ static const vshCmdInfo info_node_device_dumpxml = { .help = N_("node device details in XML"), .desc = N_("Output the node device details as an XML dump to stdout."), }; static const vshCmdOptDef opts_node_device_dumpxml[] = { {.name = "device", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("device name or wwn pair in 'wwnn,wwpn' format"), .completer = virshNodeDeviceNameCompleter, }, {.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 cmdNodeDeviceDumpXML(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshNodeDevice) device = NULL; g_autofree char *xml = NULL; const char *device_value = NULL; unsigned int flags = 0; bool wrap = vshCommandOptBool(cmd, "wrap"); const char *xpath = NULL; if (vshCommandOptStringReq(ctl, cmd, "device", &device_value) < 0) return false; if (vshCommandOptStringQuiet(ctl, cmd, "xpath", &xpath) < 0) return false; device = vshFindNodeDevice(ctl, device_value); if (!device) return false; if (vshCommandOptBool(cmd, "inactive")) flags |= VIR_NODE_DEVICE_XML_INACTIVE; if (!(xml = virNodeDeviceGetXMLDesc(device, flags))) return false; return virshDumpXML(ctl, xml, "node-device", xpath, wrap); } /* * "nodedev-detach" command */ static const vshCmdInfo info_node_device_detach = { .help = N_("detach node device from its device driver"), .desc = N_("Detach node device from its device driver before assigning to a domain."), }; static const vshCmdOptDef opts_node_device_detach[] = { {.name = "device", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("device key"), .completer = virshNodeDeviceNameCompleter, }, {.name = "driver", .type = VSH_OT_STRING, .completer = virshNodeDevicePCIBackendCompleter, .help = N_("pci device assignment backend driver (e.g. 'vfio' or 'xen')") }, {.name = NULL} }; static bool cmdNodeDeviceDetach(vshControl *ctl, const vshCmd *cmd) { const char *name = NULL; const char *driverName = NULL; g_autoptr(virshNodeDevice) device = NULL; bool ret = true; virshControl *priv = ctl->privData; if (vshCommandOptStringReq(ctl, cmd, "device", &name) < 0) return false; ignore_value(vshCommandOptStringQuiet(ctl, cmd, "driver", &driverName)); if (!(device = virNodeDeviceLookupByName(priv->conn, name))) { vshError(ctl, _("Could not find matching device '%1$s'"), name); return false; } if (driverName) { /* we must use the newer API that accepts a driverName */ if (virNodeDeviceDetachFlags(device, driverName, 0) < 0) ret = false; } else { /* Yes, our (old) public API is misspelled. At least virsh * can accept either spelling. */ if (virNodeDeviceDettach(device) < 0) ret = false; } if (ret) vshPrintExtra(ctl, _("Device %1$s detached\n"), name); else vshError(ctl, _("Failed to detach device %1$s"), name); return ret; } /* * "nodedev-reattach" command */ static const vshCmdInfo info_node_device_reattach = { .help = N_("reattach node device to its device driver"), .desc = N_("Reattach node device to its device driver once released by the domain."), }; static const vshCmdOptDef opts_node_device_reattach[] = { {.name = "device", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("device key"), .completer = virshNodeDeviceNameCompleter, }, {.name = NULL} }; static bool cmdNodeDeviceReAttach(vshControl *ctl, const vshCmd *cmd) { const char *name = NULL; g_autoptr(virshNodeDevice) device = NULL; bool ret = true; virshControl *priv = ctl->privData; if (vshCommandOptStringReq(ctl, cmd, "device", &name) < 0) return false; if (!(device = virNodeDeviceLookupByName(priv->conn, name))) { vshError(ctl, _("Could not find matching device '%1$s'"), name); return false; } if (virNodeDeviceReAttach(device) == 0) { vshPrintExtra(ctl, _("Device %1$s re-attached\n"), name); } else { vshError(ctl, _("Failed to re-attach device %1$s"), name); ret = false; } return ret; } /* * "nodedev-reset" command */ static const vshCmdInfo info_node_device_reset = { .help = N_("reset node device"), .desc = N_("Reset node device before or after assigning to a domain."), }; static const vshCmdOptDef opts_node_device_reset[] = { {.name = "device", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("device key"), .completer = virshNodeDeviceNameCompleter, }, {.name = NULL} }; static bool cmdNodeDeviceReset(vshControl *ctl, const vshCmd *cmd) { const char *name = NULL; g_autoptr(virshNodeDevice) device = NULL; bool ret = true; virshControl *priv = ctl->privData; if (vshCommandOptStringReq(ctl, cmd, "device", &name) < 0) return false; if (!(device = virNodeDeviceLookupByName(priv->conn, name))) { vshError(ctl, _("Could not find matching device '%1$s'"), name); return false; } if (virNodeDeviceReset(device) == 0) { vshPrintExtra(ctl, _("Device %1$s reset\n"), name); } else { vshError(ctl, _("Failed to reset device %1$s"), name); ret = false; } return ret; } /* * "nodedev-event" command */ VIR_ENUM_DECL(virshNodeDeviceEvent); VIR_ENUM_IMPL(virshNodeDeviceEvent, VIR_NODE_DEVICE_EVENT_LAST, N_("Created"), N_("Deleted"), N_("Defined"), N_("Undefined")); static const char * virshNodeDeviceEventToString(int event) { const char *str = virshNodeDeviceEventTypeToString(event); return str ? _(str) : _("unknown"); } struct virshNodeDeviceEventData { vshControl *ctl; bool loop; bool timestamp; int count; virshNodeDeviceEventCallback *cb; }; typedef struct virshNodeDeviceEventData virshNodeDeviceEventData; static void vshEventLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED, virNodeDevicePtr dev, int event, int detail G_GNUC_UNUSED, void *opaque) { virshNodeDeviceEventData *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 node device %2$s: %3$s\n"), timestamp, virNodeDeviceGetName(dev), virshNodeDeviceEventToString(event)); } else { vshPrint(data->ctl, _("event 'lifecycle' for node device %1$s: %2$s\n"), virNodeDeviceGetName(dev), virshNodeDeviceEventToString(event)); } data->count++; if (!data->loop) vshEventDone(data->ctl); } static void vshEventGenericPrint(virConnectPtr conn G_GNUC_UNUSED, virNodeDevicePtr dev, void *opaque) { virshNodeDeviceEventData *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 '%2$s' for node device %3$s\n"), timestamp, data->cb->name, virNodeDeviceGetName(dev)); } else { vshPrint(data->ctl, _("event '%1$s' for node device %2$s\n"), data->cb->name, virNodeDeviceGetName(dev)); } data->count++; if (!data->loop) vshEventDone(data->ctl); } virshNodeDeviceEventCallback virshNodeDeviceEventCallbacks[] = { { "lifecycle", VIR_NODE_DEVICE_EVENT_CALLBACK(vshEventLifecyclePrint), }, { "update", vshEventGenericPrint, } }; G_STATIC_ASSERT(VIR_NODE_DEVICE_EVENT_ID_LAST == G_N_ELEMENTS(virshNodeDeviceEventCallbacks)); static const vshCmdInfo info_node_device_event = { .help = N_("Node Device Events"), .desc = N_("List event types, or wait for node device events to occur"), }; static const vshCmdOptDef opts_node_device_event[] = { {.name = "device", .type = VSH_OT_STRING, .help = N_("filter by node device name"), .completer = virshNodeDeviceNameCompleter, }, {.name = "event", .type = VSH_OT_STRING, .completer = virshNodeDeviceEventNameCompleter, .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 cmdNodeDeviceEvent(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshNodeDevice) dev = NULL; bool ret = false; int eventId = -1; int timeout = 0; virshNodeDeviceEventData data; const char *eventName = NULL; const char *device_value = NULL; int event; virshControl *priv = ctl->privData; if (vshCommandOptBool(cmd, "list")) { size_t i; for (i = 0; i < VIR_NODE_DEVICE_EVENT_ID_LAST; i++) vshPrint(ctl, "%s\n", virshNodeDeviceEventCallbacks[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_NODE_DEVICE_EVENT_ID_LAST; event++) if (STREQ(eventName, virshNodeDeviceEventCallbacks[event].name)) break; if (event == VIR_NODE_DEVICE_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 = &virshNodeDeviceEventCallbacks[event]; if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0) return false; if (vshCommandOptStringReq(ctl, cmd, "device", &device_value) < 0) return false; if (device_value) { if (!(dev = virNodeDeviceLookupByName(priv->conn, device_value))) { vshError(ctl, "%s '%s'", _("Could not find matching device"), device_value); goto cleanup; } } if (vshEventStart(ctl, timeout) < 0) goto cleanup; if ((eventId = virConnectNodeDeviceEventRegisterAny(priv->conn, dev, 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 && virConnectNodeDeviceEventDeregisterAny(priv->conn, eventId) < 0) ret = false; return ret; } /* * "nodedev-undefine" command */ static const vshCmdInfo info_node_device_undefine = { .help = N_("Undefine an inactive node device"), .desc = N_("Undefines the configuration for an inactive node device"), }; static const vshCmdOptDef opts_node_device_undefine[] = { {.name = "device", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("device name or wwn pair in 'wwnn,wwpn' format"), .completer = virshNodeDeviceNameCompleter, }, {.name = NULL} }; static bool cmdNodeDeviceUndefine(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED) { g_autoptr(virshNodeDevice) dev = NULL; const char *device_value = NULL; if (vshCommandOptStringReq(ctl, cmd, "device", &device_value) < 0) return false; dev = vshFindNodeDevice(ctl, device_value); if (!dev) return false; if (virNodeDeviceUndefine(dev, 0) < 0) { vshError(ctl, _("Failed to undefine node device '%1$s'"), device_value); return false; } vshPrintExtra(ctl, _("Undefined node device '%1$s'\n"), device_value); return true; } /* * "nodedev-define" command */ static const vshCmdInfo info_node_device_define = { .help = N_("Define or modify a device by an xml file on a node"), .desc = N_("Defines or modifies a persistent device on the node that " "can be assigned to a domain. The device must be started " "before it can be assigned to a domain."), }; static const vshCmdOptDef opts_node_device_define[] = { VIRSH_COMMON_OPT_FILE(N_("file containing an XML description " "of the device")), {.name = "validate", .type = VSH_OT_BOOL, .help = N_("validate the XML against the schema") }, {.name = NULL} }; static bool cmdNodeDeviceDefine(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED) { g_autoptr(virshNodeDevice) dev = NULL; const char *from = NULL; g_autofree char *buffer = NULL; virshControl *priv = ctl->privData; unsigned int flags = 0; if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0) return false; if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) return false; if (vshCommandOptBool(cmd, "validate")) flags |= VIR_NODE_DEVICE_DEFINE_XML_VALIDATE; if (!(dev = virNodeDeviceDefineXML(priv->conn, buffer, flags))) { vshError(ctl, _("Failed to define node device from '%1$s'"), from); return false; } vshPrintExtra(ctl, _("Node device '%1$s' defined from '%2$s'\n"), virNodeDeviceGetName(dev), from); return true; } /* * "nodedev-start" command */ static const vshCmdInfo info_node_device_start = { .help = N_("Start an inactive node device"), .desc = N_("Starts an inactive node device that was previously defined"), }; static const vshCmdOptDef opts_node_device_start[] = { {.name = "device", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("device name"), .completer = virshNodeDeviceNameCompleter, }, {.name = NULL} }; static bool cmdNodeDeviceStart(vshControl *ctl, const vshCmd *cmd) { const char *name = NULL; g_autoptr(virshNodeDevice) device = NULL; bool ret = true; virshControl *priv = ctl->privData; if (vshCommandOptStringReq(ctl, cmd, "device", &name) < 0) return false; if (!(device = virNodeDeviceLookupByName(priv->conn, name))) { vshError(ctl, _("Could not find matching device '%1$s'"), name); return false; } if (virNodeDeviceCreate(device, 0) == 0) { vshPrintExtra(ctl, _("Device %1$s started\n"), name); } else { vshError(ctl, _("Failed to start device %1$s"), name); ret = false; } return ret; } /* * "nodedev-autostart" command */ static const vshCmdInfo info_node_device_autostart = { .help = N_("autostart a defined node device"), .desc = N_("Configure a node device to be automatically started at boot."), }; static const vshCmdOptDef opts_node_device_autostart[] = { {.name = "device", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("device name or wwn pair in 'wwnn,wwpn' format"), .completer = virshNodeDeviceNameCompleter, }, {.name = "disable", .type = VSH_OT_BOOL, .help = N_("disable autostarting") }, {.name = NULL} }; static bool cmdNodeDeviceAutostart(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshNodeDevice) dev = NULL; const char *name = NULL; int autostart; if (vshCommandOptStringReq(ctl, cmd, "device", &name) < 0) return false; dev = vshFindNodeDevice(ctl, name); if (!dev) return false; autostart = !vshCommandOptBool(cmd, "disable"); if (virNodeDeviceSetAutostart(dev, autostart) < 0) { if (autostart) vshError(ctl, _("failed to mark device %1$s as autostarted"), name); else vshError(ctl, _("failed to unmark device %1$s as autostarted"), name); return false; } if (autostart) vshPrintExtra(ctl, _("Device %1$s marked as autostarted\n"), name); else vshPrintExtra(ctl, _("Device %1$s unmarked as autostarted\n"), name); return true; } /* * "nodedev-info" command */ static const vshCmdInfo info_node_device_info = { .help = N_("node device information"), .desc = N_("Returns basic information about the node device"), }; static const vshCmdOptDef opts_node_device_info[] = { {.name = "device", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("device name or wwn pair in 'wwnn,wwpn' format"), .completer = virshNodeDeviceNameCompleter, }, {.name = NULL} }; static bool cmdNodeDeviceInfo(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshNodeDevice) device = NULL; const char *device_value = NULL; int autostart; const char *parent = NULL; if (vshCommandOptStringReq(ctl, cmd, "device", &device_value) < 0) return false; device = vshFindNodeDevice(ctl, device_value); if (!device) return false; parent = virNodeDeviceGetParent(device); vshPrint(ctl, "%-15s %s\n", _("Name:"), virNodeDeviceGetName(device)); vshPrint(ctl, "%-15s %s\n", _("Parent:"), parent ? parent : ""); vshPrint(ctl, "%-15s %s\n", _("Active:"), virNodeDeviceIsActive(device) ? _("yes") : _("no")); vshPrint(ctl, "%-15s %s\n", _("Persistent:"), virNodeDeviceIsPersistent(device) ? _("yes") : _("no")); if (virNodeDeviceGetAutostart(device, &autostart) < 0) vshPrint(ctl, "%-15s %s\n", _("Autostart:"), _("no autostart")); else vshPrint(ctl, "%-15s %s\n", _("Autostart:"), autostart ? _("yes") : _("no")); return true; } /* * "nodedev-update" command */ static const vshCmdInfo info_node_device_update = { .help = N_("Update a active and/or inactive node device"), .desc = N_("Updates the configuration of a node device"), }; static const vshCmdOptDef opts_node_device_update[] = { {.name = "device", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("device name or wwn pair in 'wwnn,wwpn' format"), .completer = virshNodeDeviceNameCompleter, }, VIRSH_COMMON_OPT_FILE(N_("file containing an XML description " "of the device")), VIRSH_COMMON_OPT_CONFIG(N_("affect next node device startup")), VIRSH_COMMON_OPT_LIVE(N_("affect running node device")), VIRSH_COMMON_OPT_CURRENT(N_("affect current state of node device")), {.name = NULL} }; static bool cmdNodeDeviceUpdate(vshControl *ctl, const vshCmd *cmd) { bool ret = false; g_autoptr(virshNodeDevice) device = NULL; const char *device_value = NULL; const char *from = NULL; g_autofree char *xml = NULL; bool config = vshCommandOptBool(cmd, "config"); bool live = vshCommandOptBool(cmd, "live"); unsigned int flags = VIR_NODE_DEVICE_UPDATE_AFFECT_CURRENT; VSH_EXCLUSIVE_OPTIONS("current", "live"); VSH_EXCLUSIVE_OPTIONS("current", "config"); if (vshCommandOptStringReq(ctl, cmd, "device", &device_value) < 0) return false; device = vshFindNodeDevice(ctl, device_value); if (!device) return false; if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0) goto cleanup; if (virFileReadAll(from, VSH_MAX_XML_FILE, &xml) < 0) goto cleanup; if (config) flags |= VIR_NODE_DEVICE_UPDATE_AFFECT_CONFIG; if (live) flags |= VIR_NODE_DEVICE_UPDATE_AFFECT_LIVE; if (virNodeDeviceUpdate(device, xml, flags) < 0) { vshError(ctl, _("Failed to update node device %1$s from '%2$s'"), virNodeDeviceGetName(device), from); goto cleanup; } if (config) { if (live) { vshPrintExtra(ctl, _("Updated node device %1$s persistent config and live state"), virNodeDeviceGetName(device)); } else { vshPrintExtra(ctl, _("Updated node device %1$s persistent config"), virNodeDeviceGetName(device)); } } else if (live) { vshPrintExtra(ctl, _("Updated node device %1$s live state"), virNodeDeviceGetName(device)); } else if (virNodeDeviceIsActive(device)) { vshPrintExtra(ctl, _("Updated node device %1$s live state"), virNodeDeviceGetName(device)); } else { vshPrintExtra(ctl, _("Updated node device %1$s persistent config"), virNodeDeviceGetName(device)); } ret = true; cleanup: vshReportError(ctl); return ret; } const vshCmdDef nodedevCmds[] = { {.name = "nodedev-create", .handler = cmdNodeDeviceCreate, .opts = opts_node_device_create, .info = &info_node_device_create, .flags = 0 }, {.name = "nodedev-destroy", .handler = cmdNodeDeviceDestroy, .opts = opts_node_device_destroy, .info = &info_node_device_destroy, .flags = 0 }, {.name = "nodedev-detach", .handler = cmdNodeDeviceDetach, .opts = opts_node_device_detach, .info = &info_node_device_detach, .flags = 0 }, {.name = "nodedev-dettach", .alias = "nodedev-detach" }, {.name = "nodedev-dumpxml", .handler = cmdNodeDeviceDumpXML, .opts = opts_node_device_dumpxml, .info = &info_node_device_dumpxml, .flags = 0 }, {.name = "nodedev-list", .handler = cmdNodeListDevices, .opts = opts_node_list_devices, .info = &info_node_list_devices, .flags = 0 }, {.name = "nodedev-reattach", .handler = cmdNodeDeviceReAttach, .opts = opts_node_device_reattach, .info = &info_node_device_reattach, .flags = 0 }, {.name = "nodedev-reset", .handler = cmdNodeDeviceReset, .opts = opts_node_device_reset, .info = &info_node_device_reset, .flags = 0 }, {.name = "nodedev-event", .handler = cmdNodeDeviceEvent, .opts = opts_node_device_event, .info = &info_node_device_event, .flags = 0 }, {.name = "nodedev-define", .handler = cmdNodeDeviceDefine, .opts = opts_node_device_define, .info = &info_node_device_define, .flags = 0 }, {.name = "nodedev-undefine", .handler = cmdNodeDeviceUndefine, .opts = opts_node_device_undefine, .info = &info_node_device_undefine, .flags = 0 }, {.name = "nodedev-start", .handler = cmdNodeDeviceStart, .opts = opts_node_device_start, .info = &info_node_device_start, .flags = 0 }, {.name = "nodedev-autostart", .handler = cmdNodeDeviceAutostart, .opts = opts_node_device_autostart, .info = &info_node_device_autostart, .flags = 0 }, {.name = "nodedev-info", .handler = cmdNodeDeviceInfo, .opts = opts_node_device_info, .info = &info_node_device_info, .flags = 0 }, {.name = "nodedev-update", .handler = cmdNodeDeviceUpdate, .opts = opts_node_device_update, .info = &info_node_device_update, .flags = 0 }, {.name = NULL} };