/*
* virsh-util.c: helpers for virsh
*
* 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-util.h"
#include "virfile.h"
#include "virstring.h"
#include "virxml.h"
static virDomainPtr
virshLookupDomainInternal(vshControl *ctl,
const char *cmdname,
const char *name,
unsigned int flags)
{
virDomainPtr dom = NULL;
int id;
virshControl *priv = ctl->privData;
virCheckFlags(VIRSH_BYID | VIRSH_BYUUID | VIRSH_BYNAME, NULL);
/* try it by ID */
if (flags & VIRSH_BYID) {
if (virStrToLong_i(name, NULL, 10, &id) == 0 && id >= 0) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: looks like ID\n",
cmdname);
dom = virDomainLookupByID(priv->conn, id);
}
}
/* try it by UUID */
if (!dom && (flags & VIRSH_BYUUID) &&
strlen(name) == VIR_UUID_STRING_BUFLEN-1) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: trying as domain UUID\n",
cmdname);
dom = virDomainLookupByUUIDString(priv->conn, name);
}
/* try it by NAME */
if (!dom && (flags & VIRSH_BYNAME)) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: trying as domain NAME\n",
cmdname);
dom = virDomainLookupByName(priv->conn, name);
}
vshResetLibvirtError();
if (!dom)
vshError(ctl, _("failed to get domain '%1$s'"), name);
return dom;
}
virDomainPtr
virshLookupDomainBy(vshControl *ctl,
const char *name,
unsigned int flags)
{
return virshLookupDomainInternal(ctl, "unknown", name, flags);
}
virDomainPtr
virshCommandOptDomainBy(vshControl *ctl,
const vshCmd *cmd,
const char **name,
unsigned int flags)
{
const char *n = NULL;
const char *optname = "domain";
if (vshCommandOptString(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;
return virshLookupDomainInternal(ctl, cmd->def->name, n, flags);
}
virDomainPtr
virshCommandOptDomain(vshControl *ctl,
const vshCmd *cmd,
const char **name)
{
return virshCommandOptDomainBy(ctl, cmd, name,
VIRSH_BYID | VIRSH_BYUUID | VIRSH_BYNAME);
}
int
virshDomainState(vshControl *ctl,
virDomainPtr dom,
int *reason)
{
virDomainInfo info;
virshControl *priv = ctl->privData;
if (reason)
*reason = -1;
if (!priv->useGetInfo) {
int state;
if (virDomainGetState(dom, &state, reason, 0) < 0) {
if (virGetLastErrorCode() == VIR_ERR_NO_SUPPORT)
priv->useGetInfo = true;
else
return -1;
} else {
return state;
}
}
/* fall back to virDomainGetInfo if virDomainGetState is not supported */
if (virDomainGetInfo(dom, &info) < 0)
return -1;
return info.state;
}
int
virshStreamSink(virStreamPtr st G_GNUC_UNUSED,
const char *bytes,
size_t nbytes,
void *opaque)
{
virshStreamCallbackData *cbData = opaque;
return safewrite(cbData->fd, bytes, nbytes);
}
int
virshStreamSource(virStreamPtr st G_GNUC_UNUSED,
char *bytes,
size_t nbytes,
void *opaque)
{
virshStreamCallbackData *cbData = opaque;
int fd = cbData->fd;
return saferead(fd, bytes, nbytes);
}
int
virshStreamSourceSkip(virStreamPtr st G_GNUC_UNUSED,
long long offset,
void *opaque)
{
virshStreamCallbackData *cbData = opaque;
int fd = cbData->fd;
if (lseek(fd, offset, SEEK_CUR) == (off_t) -1)
return -1;
return 0;
}
int
virshStreamSkip(virStreamPtr st G_GNUC_UNUSED,
long long offset,
void *opaque)
{
virshStreamCallbackData *cbData = opaque;
off_t cur;
if (cbData->isBlock) {
g_autofree char * buf = NULL;
const size_t buflen = 1 * 1024 * 1024; /* 1MiB */
/* While for files it's enough to lseek() and ftruncate() to create
* a hole which would emulate zeroes on read(), for block devices
* we have to write zeroes to read() zeroes. And we have to write
* @got bytes of zeroes. Do that in smaller chunks though.*/
buf = g_new0(char, buflen);
while (offset) {
size_t count = MIN(offset, buflen);
ssize_t r;
if ((r = safewrite(cbData->fd, buf, count)) < 0)
return -1;
offset -= r;
}
} else {
if ((cur = lseek(cbData->fd, offset, SEEK_CUR)) == (off_t) -1)
return -1;
if (ftruncate(cbData->fd, cur) < 0)
return -1;
}
return 0;
}
int
virshStreamInData(virStreamPtr st G_GNUC_UNUSED,
int *inData,
long long *offset,
void *opaque)
{
virshStreamCallbackData *cbData = opaque;
vshControl *ctl = cbData->ctl;
int fd = cbData->fd;
if (cbData->isBlock) {
/* Block devices are always in data section by definition. The
* @sectionLen is slightly more tricky. While we could try and get
* how much bytes is there left until EOF, we can pretend there is
* always X bytes left and let the saferead() below hit EOF (which
* is then handled gracefully anyway). Worst case scenario, this
* branch is called more than once.
* X was chosen to be 1MiB but it has ho special meaning. */
*inData = 1;
*offset = 1 * 1024 * 1024;
} else {
if (virFileInData(fd, inData, offset) < 0) {
vshError(ctl, "%s", _("Unable to get current position in stream"));
return -1;
}
}
return 0;
}
void
virshDomainFree(virDomainPtr dom)
{
if (!dom)
return;
vshSaveLibvirtHelperError();
virDomainFree(dom); /* sc_prohibit_obj_free_apis_in_virsh */
}
void
virshDomainCheckpointFree(virDomainCheckpointPtr chk)
{
if (!chk)
return;
vshSaveLibvirtHelperError();
virDomainCheckpointFree(chk); /* sc_prohibit_obj_free_apis_in_virsh */
}
void
virshDomainSnapshotFree(virDomainSnapshotPtr snap)
{
if (!snap)
return;
vshSaveLibvirtHelperError();
virDomainSnapshotFree(snap); /* sc_prohibit_obj_free_apis_in_virsh */
}
void
virshInterfaceFree(virInterfacePtr iface)
{
if (!iface)
return;
vshSaveLibvirtHelperError();
virInterfaceFree(iface); /* sc_prohibit_obj_free_apis_in_virsh */
}
void
virshNetworkFree(virNetworkPtr network)
{
if (!network)
return;
vshSaveLibvirtHelperError();
virNetworkFree(network); /* sc_prohibit_obj_free_apis_in_virsh */
}
void
virshNodeDeviceFree(virNodeDevicePtr device)
{
if (!device)
return;
vshSaveLibvirtHelperError();
virNodeDeviceFree(device); /* sc_prohibit_obj_free_apis_in_virsh */
}
void
virshNWFilterFree(virNWFilterPtr nwfilter)
{
if (!nwfilter)
return;
vshSaveLibvirtHelperError();
virNWFilterFree(nwfilter); /* sc_prohibit_obj_free_apis_in_virsh */
}
void
virshSecretFree(virSecretPtr secret)
{
if (!secret)
return;
vshSaveLibvirtHelperError();
virSecretFree(secret); /* sc_prohibit_obj_free_apis_in_virsh */
}
void
virshStoragePoolFree(virStoragePoolPtr pool)
{
if (!pool)
return;
vshSaveLibvirtHelperError();
virStoragePoolFree(pool); /* sc_prohibit_obj_free_apis_in_virsh */
}
void
virshStorageVolFree(virStorageVolPtr vol)
{
if (!vol)
return;
vshSaveLibvirtHelperError();
virStorageVolFree(vol); /* sc_prohibit_obj_free_apis_in_virsh */
}
void
virshStreamFree(virStreamPtr stream)
{
if (!stream)
return;
vshSaveLibvirtHelperError();
virStreamFree(stream); /* sc_prohibit_obj_free_apis_in_virsh */
}
int
virshDomainGetXMLFromDom(vshControl *ctl,
virDomainPtr dom,
unsigned int flags,
xmlDocPtr *xml,
xmlXPathContextPtr *ctxt)
{
g_autofree char *desc = NULL;
if (!(desc = virDomainGetXMLDesc(dom, flags))) {
vshError(ctl, _("Failed to get domain description xml"));
return -1;
}
*xml = virXMLParseStringCtxt(desc, _("(domain_definition)"), ctxt);
if (!(*xml)) {
vshError(ctl, _("Failed to parse domain description xml"));
return -1;
}
return 0;
}
int
virshNetworkGetXMLFromNet(vshControl *ctl,
virNetworkPtr net,
unsigned int flags,
xmlDocPtr *xml,
xmlXPathContextPtr *ctxt)
{
g_autofree char *desc = NULL;
if (!(desc = virNetworkGetXMLDesc(net, flags))) {
vshError(ctl, _("Failed to get network description xml"));
return -1;
}
*xml = virXMLParseStringCtxt(desc, _("(network_definition)"), ctxt);
if (!(*xml)) {
vshError(ctl, _("Failed to parse network description xml"));
return -1;
}
return 0;
}
int
virshDomainGetXML(vshControl *ctl,
const vshCmd *cmd,
unsigned int flags,
xmlDocPtr *xml,
xmlXPathContextPtr *ctxt)
{
virDomainPtr dom;
int ret;
if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
return -1;
ret = virshDomainGetXMLFromDom(ctl, dom, flags, xml, ctxt);
virshDomainFree(dom);
return ret;
}
VIR_ENUM_IMPL(virshDomainBlockJob,
VIR_DOMAIN_BLOCK_JOB_TYPE_LAST,
N_("Unknown job"),
N_("Block Pull"),
N_("Block Copy"),
N_("Block Commit"),
N_("Active Block Commit"),
N_("Backup"),
);
const char *
virshDomainBlockJobToString(int type)
{
const char *str = virshDomainBlockJobTypeToString(type);
return str ? _(str) : _("Unknown job");
}
bool
virshDumpXML(vshControl *ctl,
const char *xml,
const char *url,
const char *xpath,
bool wrap)
{
g_autoptr(xmlDoc) doc = NULL;
g_autoptr(xmlXPathContext) ctxt = NULL;
g_autofree xmlNodePtr *nodes = NULL;
int nnodes = 0;
size_t i;
if (xpath == NULL) {
vshPrint(ctl, "%s", xml);
return true;
}
doc = virXMLParseStringCtxtWithIndent(xml, url, &ctxt);
if (!doc)
return false;
if ((nnodes = virXPathNodeSet(xpath, ctxt, &nodes)) < 0) {
return false;
}
if (wrap) {
g_autoptr(xmlDoc) newdoc = xmlNewDoc((xmlChar *)"1.0");
xmlNodePtr newroot = xmlNewNode(NULL, (xmlChar *)"nodes");
g_autofree char *xmlbit = NULL;
xmlDocSetRootElement(newdoc, newroot);
for (i = 0; i < nnodes; i++) {
g_autoptr(xmlNode) copy = xmlDocCopyNode(nodes[i], newdoc, 1);
if (!xmlAddChild(newroot, copy))
return false;
copy = NULL;
}
xmlbit = virXMLNodeToString(doc, newroot);
vshPrint(ctl, "%s\n", xmlbit);
} else {
for (i = 0; i < nnodes; i++) {
g_autofree char *xmlbit = virXMLNodeToString(doc, nodes[i]);
vshPrint(ctl, "%s\n", xmlbit);
}
}
return true;
}