/*
* 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_STRING,
.positional = true,
.required = true,
.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_STRING,
.positional = true,
.required = true,
.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_STRING,
.positional = true,
.required = true,
.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_STRING,
.positional = true,
.required = true,
.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_STRING,
.positional = true,
.required = true,
.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_STRING,
.positional = true,
.required = true,
.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_STRING,
.positional = true,
.required = true,
.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_STRING,
.positional = true,
.required = true,
.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_STRING,
.positional = true,
.required = true,
.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_STRING,
.positional = true,
.required = true,
.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}
};