libvirt/tools/virsh.c
Sebastian Wiedenroth 29bc4fe646 Add a sheepdog backend for the storage driver
This patch brings support to manage sheepdog pools and volumes to libvirt.
It uses the "collie" command-line utility that comes with sheepdog for that.

A sheepdog pool in libvirt maps to a sheepdog cluster.
It needs a host and port to connect to, which in most cases
is just going to be the default of localhost on port 7000.

A sheepdog volume in libvirt maps to a sheepdog vdi.
To create one specify the pool, a name and the capacity.
Volumes can also be resized later.

In the volume XML the vdi name has to be put into the <target><path>.
To use the volume as a disk source for virtual machines specify
the vdi name as "name" attribute of the <source>.
The host and port information from the pool are specified inside the host tag.

  <disk type='network'>
    ...
    <source protocol="sheepdog" name="vdi_name">
      <host name="localhost" port="7000"/>
    </source>
  </disk>

To work right this patch parses the output of collie,
so it relies on the raw output option. There recently was a bug which caused
size information to be reported wrong. This is fixed upstream already and
will be in the next release.

Signed-off-by: Sebastian Wiedenroth <wiedi@frubar.net>
2012-07-18 20:08:27 +01:00

21109 lines
615 KiB
C

/*
* virsh.c: a shell to exercise the libvirt API
*
* Copyright (C) 2005, 2007-2012 Red Hat, Inc.
*
* See COPYING.LIB for the License of this software
*
* Daniel Veillard <veillard@redhat.com>
* Karel Zak <kzak@redhat.com>
* Daniel P. Berrange <berrange@redhat.com>
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include "c-ctype.h"
#include <fcntl.h>
#include <locale.h>
#include <time.h>
#include <limits.h>
#include <assert.h>
#include <sys/stat.h>
#include <inttypes.h>
#include <signal.h>
#include <poll.h>
#include <strings.h>
#include <termios.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xmlsave.h>
#ifdef HAVE_READLINE_READLINE_H
# include <readline/readline.h>
# include <readline/history.h>
#endif
#include "internal.h"
#include "virterror_internal.h"
#include "base64.h"
#include "buf.h"
#include "console.h"
#include "util.h"
#include "memory.h"
#include "xml.h"
#include "libvirt/libvirt-qemu.h"
#include "virfile.h"
#include "event_poll.h"
#include "configmake.h"
#include "threads.h"
#include "command.h"
#include "virkeycode.h"
#include "virnetdevbandwidth.h"
#include "util/bitmap.h"
#include "conf/domain_conf.h"
#include "virtypedparam.h"
#include "intprops.h"
#include "conf/virdomainlist.h"
static char *progname;
#define VIRSH_MAX_XML_FILE 10*1024*1024
#define VSH_PROMPT_RW "virsh # "
#define VSH_PROMPT_RO "virsh > "
#define VIR_FROM_THIS VIR_FROM_NONE
#define GETTIMEOFDAY(T) gettimeofday(T, NULL)
#define DIFF_MSEC(T, U) \
((((int) ((T)->tv_sec - (U)->tv_sec)) * 1000000.0 + \
((int) ((T)->tv_usec - (U)->tv_usec))) / 1000.0)
/* Default escape char Ctrl-] as per telnet */
#define CTRL_CLOSE_BRACKET "^]"
/**
* The log configuration
*/
#define MSG_BUFFER 4096
#define SIGN_NAME "virsh"
#define DIR_MODE (S_IWUSR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) /* 0755 */
#define FILE_MODE (S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH) /* 0644 */
#define LOCK_MODE (S_IWUSR | S_IRUSR) /* 0600 */
#define LVL_DEBUG "DEBUG"
#define LVL_INFO "INFO"
#define LVL_NOTICE "NOTICE"
#define LVL_WARNING "WARNING"
#define LVL_ERROR "ERROR"
/**
* vshErrorLevel:
*
* Indicates the level of a log message
*/
typedef enum {
VSH_ERR_DEBUG = 0,
VSH_ERR_INFO,
VSH_ERR_NOTICE,
VSH_ERR_WARNING,
VSH_ERR_ERROR
} vshErrorLevel;
#define VSH_DEBUG_DEFAULT VSH_ERR_ERROR
/*
* virsh command line grammar:
*
* command_line = <command>\n | <command>; <command>; ...
*
* command = <keyword> <option> [--] <data>
*
* option = <bool_option> | <int_option> | <string_option>
* data = <string>
*
* bool_option = --optionname
* int_option = --optionname <number> | --optionname=<number>
* string_option = --optionname <string> | --optionname=<string>
*
* keyword = [a-zA-Z][a-zA-Z-]*
* number = [0-9]+
* string = ('[^']*'|"([^\\"]|\\.)*"|([^ \t\n\\'"]|\\.))+
*
*/
/*
* vshCmdOptType - command option type
*/
typedef enum {
VSH_OT_BOOL, /* optional boolean option */
VSH_OT_STRING, /* optional string option */
VSH_OT_INT, /* optional or mandatory int option */
VSH_OT_DATA, /* string data (as non-option) */
VSH_OT_ARGV, /* remaining arguments */
VSH_OT_ALIAS, /* alternate spelling for a later argument */
} vshCmdOptType;
/*
* Command group types
*/
#define VSH_CMD_GRP_DOM_MANAGEMENT "Domain Management"
#define VSH_CMD_GRP_DOM_MONITORING "Domain Monitoring"
#define VSH_CMD_GRP_STORAGE_POOL "Storage Pool"
#define VSH_CMD_GRP_STORAGE_VOL "Storage Volume"
#define VSH_CMD_GRP_NETWORK "Networking"
#define VSH_CMD_GRP_NODEDEV "Node Device"
#define VSH_CMD_GRP_IFACE "Interface"
#define VSH_CMD_GRP_NWFILTER "Network Filter"
#define VSH_CMD_GRP_SECRET "Secret"
#define VSH_CMD_GRP_SNAPSHOT "Snapshot"
#define VSH_CMD_GRP_HOST_AND_HV "Host and Hypervisor"
#define VSH_CMD_GRP_VIRSH "Virsh itself"
/*
* Command Option Flags
*/
enum {
VSH_OFLAG_NONE = 0, /* without flags */
VSH_OFLAG_REQ = (1 << 0), /* option required */
VSH_OFLAG_EMPTY_OK = (1 << 1), /* empty string option allowed */
VSH_OFLAG_REQ_OPT = (1 << 2), /* --optionname required */
};
/* dummy */
typedef struct __vshControl vshControl;
typedef struct __vshCmd vshCmd;
/*
* vshCmdInfo -- name/value pair for information about command
*
* Commands should have at least the following names:
* "name" - command name
* "desc" - description of command, or empty string
*/
typedef struct {
const char *name; /* name of information, or NULL for list end */
const char *data; /* non-NULL information */
} vshCmdInfo;
/*
* vshCmdOptDef - command option definition
*/
typedef struct {
const char *name; /* the name of option, or NULL for list end */
vshCmdOptType type; /* option type */
unsigned int flags; /* flags */
const char *help; /* non-NULL help string; or for VSH_OT_ALIAS
* the name of a later public option */
} vshCmdOptDef;
/*
* vshCmdOpt - command options
*
* After parsing a command, all arguments to the command have been
* collected into a list of these objects.
*/
typedef struct vshCmdOpt {
const vshCmdOptDef *def; /* non-NULL pointer to option definition */
char *data; /* allocated data, or NULL for bool option */
struct vshCmdOpt *next;
} vshCmdOpt;
/*
* Command Usage Flags
*/
enum {
VSH_CMD_FLAG_NOCONNECT = (1 << 0), /* no prior connection needed */
VSH_CMD_FLAG_ALIAS = (1 << 1), /* command is an alias */
};
/*
* vshCmdDef - command definition
*/
typedef struct {
const char *name; /* name of command, or NULL for list end */
bool (*handler) (vshControl *, const vshCmd *); /* command handler */
const vshCmdOptDef *opts; /* definition of command options */
const vshCmdInfo *info; /* details about command */
unsigned int flags; /* bitwise OR of VSH_CMD_FLAG */
} vshCmdDef;
/*
* vshCmd - parsed command
*/
typedef struct __vshCmd {
const vshCmdDef *def; /* command definition */
vshCmdOpt *opts; /* list of command arguments */
struct __vshCmd *next; /* next command */
} __vshCmd;
/*
* vshControl
*/
typedef struct __vshControl {
char *name; /* connection name */
virConnectPtr conn; /* connection to hypervisor (MAY BE NULL) */
vshCmd *cmd; /* the current command */
char *cmdstr; /* string with command */
bool imode; /* interactive mode? */
bool quiet; /* quiet mode */
int debug; /* print debug messages? */
bool timing; /* print timing info? */
bool readonly; /* connect readonly (first time only, not
* during explicit connect command)
*/
char *logfile; /* log file name */
int log_fd; /* log file descriptor */
char *historydir; /* readline history directory name */
char *historyfile; /* readline history file name */
bool useGetInfo; /* must use virDomainGetInfo, since
virDomainGetState is not supported */
bool useSnapshotOld; /* cannot use virDomainSnapshotGetParent or
virDomainSnapshotNumChildren */
virThread eventLoop;
virMutex lock;
bool eventLoopStarted;
bool quit;
const char *escapeChar; /* String representation of
console escape character */
} __vshControl;
typedef struct vshCmdGrp {
const char *name; /* name of group, or NULL for list end */
const char *keyword; /* help keyword */
const vshCmdDef *commands;
} vshCmdGrp;
static const vshCmdGrp cmdGroups[];
static void vshError(vshControl *ctl, const char *format, ...)
ATTRIBUTE_FMT_PRINTF(2, 3);
static bool vshInit(vshControl *ctl);
static bool vshDeinit(vshControl *ctl);
static void vshUsage(void);
static void vshOpenLogFile(vshControl *ctl);
static void vshOutputLogFile(vshControl *ctl, int log_level, const char *format, va_list ap)
ATTRIBUTE_FMT_PRINTF(3, 0);
static void vshCloseLogFile(vshControl *ctl);
static bool vshParseArgv(vshControl *ctl, int argc, char **argv);
static const char *vshCmddefGetInfo(const vshCmdDef *cmd, const char *info);
static const vshCmdDef *vshCmddefSearch(const char *cmdname);
static bool vshCmddefHelp(vshControl *ctl, const char *name);
static const vshCmdGrp *vshCmdGrpSearch(const char *grpname);
static bool vshCmdGrpHelp(vshControl *ctl, const char *name);
static int vshCommandOpt(const vshCmd *cmd, const char *name, vshCmdOpt **opt)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
ATTRIBUTE_RETURN_CHECK;
static int vshCommandOptInt(const vshCmd *cmd, const char *name, int *value)
ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
static int vshCommandOptUInt(const vshCmd *cmd, const char *name,
unsigned int *value)
ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
static int vshCommandOptUL(const vshCmd *cmd, const char *name,
unsigned long *value)
ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
static int vshCommandOptString(const vshCmd *cmd, const char *name,
const char **value)
ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
static int vshCommandOptLongLong(const vshCmd *cmd, const char *name,
long long *value)
ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
static int vshCommandOptULongLong(const vshCmd *cmd, const char *name,
unsigned long long *value)
ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
static int vshCommandOptScaledInt(const vshCmd *cmd, const char *name,
unsigned long long *value, int scale,
unsigned long long max)
ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
static bool vshCommandOptBool(const vshCmd *cmd, const char *name);
static const vshCmdOpt *vshCommandOptArgv(const vshCmd *cmd,
const vshCmdOpt *opt);
static char *vshGetDomainDescription(vshControl *ctl, virDomainPtr dom,
bool title, unsigned int flags)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK;
#define VSH_BYID (1 << 1)
#define VSH_BYUUID (1 << 2)
#define VSH_BYNAME (1 << 3)
#define VSH_BYMAC (1 << 4)
static virDomainPtr vshCommandOptDomainBy(vshControl *ctl, const vshCmd *cmd,
const char **name, int flag);
/* default is lookup by Id, Name and UUID */
#define vshCommandOptDomain(_ctl, _cmd, _name) \
vshCommandOptDomainBy(_ctl, _cmd, _name, VSH_BYID|VSH_BYUUID|VSH_BYNAME)
static virNetworkPtr vshCommandOptNetworkBy(vshControl *ctl, const vshCmd *cmd,
const char **name, int flag);
/* default is lookup by Name and UUID */
#define vshCommandOptNetwork(_ctl, _cmd, _name) \
vshCommandOptNetworkBy(_ctl, _cmd, _name, \
VSH_BYUUID|VSH_BYNAME)
static virNWFilterPtr vshCommandOptNWFilterBy(vshControl *ctl, const vshCmd *cmd,
const char **name, int flag);
/* default is lookup by Name and UUID */
#define vshCommandOptNWFilter(_ctl, _cmd, _name) \
vshCommandOptNWFilterBy(_ctl, _cmd, _name, \
VSH_BYUUID|VSH_BYNAME)
static virInterfacePtr vshCommandOptInterfaceBy(vshControl *ctl, const vshCmd *cmd,
const char *optname,
const char **name, int flag);
/* default is lookup by Name and MAC */
#define vshCommandOptInterface(_ctl, _cmd, _name) \
vshCommandOptInterfaceBy(_ctl, _cmd, NULL, _name, \
VSH_BYMAC|VSH_BYNAME)
static virStoragePoolPtr vshCommandOptPoolBy(vshControl *ctl, const vshCmd *cmd,
const char *optname, const char **name, int flag);
/* default is lookup by Name and UUID */
#define vshCommandOptPool(_ctl, _cmd, _optname, _name) \
vshCommandOptPoolBy(_ctl, _cmd, _optname, _name, \
VSH_BYUUID|VSH_BYNAME)
static virStorageVolPtr vshCommandOptVolBy(vshControl *ctl, const vshCmd *cmd,
const char *optname,
const char *pooloptname,
const char **name, int flag);
/* default is lookup by Name and UUID */
#define vshCommandOptVol(_ctl, _cmd, _optname, _pooloptname, _name) \
vshCommandOptVolBy(_ctl, _cmd, _optname, _pooloptname, _name, \
VSH_BYUUID|VSH_BYNAME)
static virSecretPtr vshCommandOptSecret(vshControl *ctl, const vshCmd *cmd,
const char **name);
static void vshPrintExtra(vshControl *ctl, const char *format, ...)
ATTRIBUTE_FMT_PRINTF(2, 3);
static void vshDebug(vshControl *ctl, int level, const char *format, ...)
ATTRIBUTE_FMT_PRINTF(3, 4);
/* XXX: add batch support */
#define vshPrint(_ctl, ...) vshPrintExtra(NULL, __VA_ARGS__)
static int vshDomainState(vshControl *ctl, virDomainPtr dom, int *reason);
static const char *vshDomainStateToString(int state);
static const char *vshDomainStateReasonToString(int state, int reason);
static const char *vshDomainControlStateToString(int state);
static const char *vshDomainVcpuStateToString(int state);
static bool vshConnectionUsability(vshControl *ctl, virConnectPtr conn);
static virTypedParameterPtr vshFindTypedParamByName(const char *name,
virTypedParameterPtr list,
int count);
static char *vshGetTypedParamValue(vshControl *ctl, virTypedParameterPtr item)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
static char *editWriteToTempFile(vshControl *ctl, const char *doc);
static int editFile(vshControl *ctl, const char *filename);
static char *editReadBackFile(vshControl *ctl, const char *filename);
/* Typedefs, function prototypes for job progress reporting.
* There are used by some long lingering commands like
* migrate, dump, save, managedsave.
*/
typedef struct __vshCtrlData {
vshControl *ctl;
const vshCmd *cmd;
int writefd;
} vshCtrlData;
typedef void (*jobWatchTimeoutFunc) (vshControl *ctl, virDomainPtr dom,
void *opaque);
static bool
vshWatchJob(vshControl *ctl,
virDomainPtr dom,
bool verbose,
int pipe_fd,
int timeout,
jobWatchTimeoutFunc timeout_func,
void *opaque,
const char *label);
static void *_vshMalloc(vshControl *ctl, size_t sz, const char *filename, int line);
#define vshMalloc(_ctl, _sz) _vshMalloc(_ctl, _sz, __FILE__, __LINE__)
static void *_vshCalloc(vshControl *ctl, size_t nmemb, size_t sz, const char *filename, int line);
#define vshCalloc(_ctl, _nmemb, _sz) _vshCalloc(_ctl, _nmemb, _sz, __FILE__, __LINE__)
static char *_vshStrdup(vshControl *ctl, const char *s, const char *filename, int line);
#define vshStrdup(_ctl, _s) _vshStrdup(_ctl, _s, __FILE__, __LINE__)
static int parseRateStr(const char *rateStr, virNetDevBandwidthRatePtr rate);
static void *
_vshMalloc(vshControl *ctl, size_t size, const char *filename, int line)
{
char *x;
if (VIR_ALLOC_N(x, size) == 0)
return x;
vshError(ctl, _("%s: %d: failed to allocate %d bytes"),
filename, line, (int) size);
exit(EXIT_FAILURE);
}
static void *
_vshCalloc(vshControl *ctl, size_t nmemb, size_t size, const char *filename, int line)
{
char *x;
if (!xalloc_oversized(nmemb, size) &&
VIR_ALLOC_N(x, nmemb * size) == 0)
return x;
vshError(ctl, _("%s: %d: failed to allocate %d bytes"),
filename, line, (int) (size*nmemb));
exit(EXIT_FAILURE);
}
static char *
_vshStrdup(vshControl *ctl, const char *s, const char *filename, int line)
{
char *x;
if (s == NULL)
return NULL;
if ((x = strdup(s)))
return x;
vshError(ctl, _("%s: %d: failed to allocate %lu bytes"),
filename, line, (unsigned long)strlen(s));
exit(EXIT_FAILURE);
}
/* Poison the raw allocating identifiers in favor of our vsh variants. */
#undef malloc
#undef calloc
#undef realloc
#undef strdup
#define malloc use_vshMalloc_instead_of_malloc
#define calloc use_vshCalloc_instead_of_calloc
#define realloc use_vshRealloc_instead_of_realloc
#define strdup use_vshStrdup_instead_of_strdup
static int
vshNameSorter(const void *a, const void *b)
{
const char **sa = (const char**)a;
const char **sb = (const char**)b;
/* User visible sort, so we want locale-specific case comparison. */
return strcasecmp(*sa, *sb);
}
static double
prettyCapacity(unsigned long long val,
const char **unit) {
if (val < 1024) {
*unit = "";
return (double)val;
} else if (val < (1024.0l * 1024.0l)) {
*unit = "KiB";
return (((double)val / 1024.0l));
} else if (val < (1024.0l * 1024.0l * 1024.0l)) {
*unit = "MiB";
return (double)val / (1024.0l * 1024.0l);
} else if (val < (1024.0l * 1024.0l * 1024.0l * 1024.0l)) {
*unit = "GiB";
return (double)val / (1024.0l * 1024.0l * 1024.0l);
} else {
*unit = "TiB";
return (double)val / (1024.0l * 1024.0l * 1024.0l * 1024.0l);
}
}
static virErrorPtr last_error;
/*
* Quieten libvirt until we're done with the command.
*/
static void
virshErrorHandler(void *unused ATTRIBUTE_UNUSED, virErrorPtr error)
{
virFreeError(last_error);
last_error = virSaveLastError();
if (getenv("VIRSH_DEBUG") != NULL)
virDefaultErrorFunc(error);
}
/*
* Report an error when a command finishes. This is better than before
* (when correct operation would report errors), but it has some
* problems: we lose the smarter formatting of virDefaultErrorFunc(),
* and it can become harder to debug problems, if errors get reported
* twice during one command. This case shouldn't really happen anyway,
* and it's IMHO a bug that libvirt does that sometimes.
*/
static void
virshReportError(vshControl *ctl)
{
if (last_error == NULL) {
/* Calling directly into libvirt util functions won't trigger the
* error callback (which sets last_error), so check it ourselves.
*
* If the returned error has CODE_OK, this most likely means that
* no error was ever raised, so just ignore */
last_error = virSaveLastError();
if (!last_error || last_error->code == VIR_ERR_OK)
goto out;
}
if (last_error->code == VIR_ERR_OK) {
vshError(ctl, "%s", _("unknown error"));
goto out;
}
vshError(ctl, "%s", last_error->message);
out:
virFreeError(last_error);
last_error = NULL;
}
static volatile sig_atomic_t intCaught = 0;
static void vshCatchInt(int sig ATTRIBUTE_UNUSED,
siginfo_t *siginfo ATTRIBUTE_UNUSED,
void *context ATTRIBUTE_UNUSED)
{
intCaught = 1;
}
/*
* Detection of disconnections and automatic reconnection support
*/
static int disconnected = 0; /* we may have been disconnected */
/* Gnulib doesn't guarantee SA_SIGINFO support. */
#ifndef SA_SIGINFO
# define SA_SIGINFO 0
#endif
/*
* vshCatchDisconnect:
*
* We get here when a SIGPIPE is being raised, we can't do much in the
* handler, just save the fact it was raised
*/
static void vshCatchDisconnect(int sig, siginfo_t *siginfo,
void *context ATTRIBUTE_UNUSED) {
if (sig == SIGPIPE ||
(SA_SIGINFO && siginfo->si_signo == SIGPIPE))
disconnected++;
}
/*
* vshSetupSignals:
*
* Catch SIGPIPE signals which may arise when disconnection
* from libvirtd occurs
*/
static void
vshSetupSignals(void) {
struct sigaction sig_action;
sig_action.sa_sigaction = vshCatchDisconnect;
sig_action.sa_flags = SA_SIGINFO;
sigemptyset(&sig_action.sa_mask);
sigaction(SIGPIPE, &sig_action, NULL);
}
/*
* vshReconnect:
*
* Reconnect after a disconnect from libvirtd
*
*/
static void
vshReconnect(vshControl *ctl)
{
bool connected = false;
if (ctl->conn != NULL) {
connected = true;
virConnectClose(ctl->conn);
}
ctl->conn = virConnectOpenAuth(ctl->name,
virConnectAuthPtrDefault,
ctl->readonly ? VIR_CONNECT_RO : 0);
if (!ctl->conn)
vshError(ctl, "%s", _("Failed to reconnect to the hypervisor"));
else if (connected)
vshError(ctl, "%s", _("Reconnected to the hypervisor"));
disconnected = 0;
ctl->useGetInfo = false;
ctl->useSnapshotOld = false;
}
#ifndef WIN32
static void
vshPrintRaw(vshControl *ctl, ...)
{
va_list ap;
char *key;
va_start(ap, ctl);
while ((key = va_arg(ap, char *)) != NULL) {
vshPrint(ctl, "%s\r\n", key);
}
va_end(ap);
}
/**
* vshAskReedit:
* @msg: Question to ask user
*
* Ask user if he wants to return to previously
* edited file.
*
* Returns 'y' if he wants to
* 'f' if he forcibly wants to
* 'n' if he doesn't want to
* -1 on error
* 0 otherwise
*/
static int
vshAskReedit(vshControl *ctl, const char *msg)
{
int c = -1;
struct termios ttyattr;
if (!isatty(STDIN_FILENO))
return -1;
virshReportError(ctl);
if (vshMakeStdinRaw(&ttyattr, false) < 0)
return -1;
while (true) {
/* TRANSLATORS: For now, we aren't using LC_MESSAGES, and the user
* choices really are limited to just 'y', 'n', 'f' and '?' */
vshPrint(ctl, "\r%s %s", msg, _("Try again? [y,n,f,?]:"));
c = c_tolower(getchar());
if (c == '?') {
vshPrintRaw(ctl,
"",
_("y - yes, start editor again"),
_("n - no, throw away my changes"),
_("f - force, try to redefine again"),
_("? - print this help"),
NULL);
continue;
} else if (c == 'y' || c == 'n' || c == 'f') {
break;
}
}
tcsetattr(STDIN_FILENO, TCSAFLUSH, &ttyattr);
vshPrint(ctl, "\r\n");
return c;
}
#else /* WIN32 */
static int
vshAskReedit(vshControl *ctl, const char *msg ATTRIBUTE_UNUSED)
{
vshDebug(ctl, VSH_ERR_WARNING, "%s", _("This function is not "
"supported on WIN32 platform"));
return 0;
}
#endif /* WIN32 */
/* ---------------
* Commands
* ---------------
*/
/*
* "help" command
*/
static const vshCmdInfo info_help[] = {
{"help", N_("print help")},
{"desc", N_("Prints global help, command specific help, or help for a\n"
" group of related commands")},
{NULL, NULL}
};
static const vshCmdOptDef opts_help[] = {
{"command", VSH_OT_DATA, 0, N_("Prints global help, command specific help, or help for a group of related commands")},
{NULL, 0, 0, NULL}
};
static bool
cmdHelp(vshControl *ctl, const vshCmd *cmd)
{
const char *name = NULL;
if (vshCommandOptString(cmd, "command", &name) <= 0) {
const vshCmdGrp *grp;
const vshCmdDef *def;
vshPrint(ctl, "%s", _("Grouped commands:\n\n"));
for (grp = cmdGroups; grp->name; grp++) {
vshPrint(ctl, _(" %s (help keyword '%s'):\n"), grp->name,
grp->keyword);
for (def = grp->commands; def->name; def++) {
if (def->flags & VSH_CMD_FLAG_ALIAS)
continue;
vshPrint(ctl, " %-30s %s\n", def->name,
_(vshCmddefGetInfo(def, "help")));
}
vshPrint(ctl, "\n");
}
return true;
}
if (vshCmddefSearch(name)) {
return vshCmddefHelp(ctl, name);
} else if (vshCmdGrpSearch(name)) {
return vshCmdGrpHelp(ctl, name);
} else {
vshError(ctl, _("command or command group '%s' doesn't exist"), name);
return false;
}
}
/*
* "autostart" command
*/
static const vshCmdInfo info_autostart[] = {
{"help", N_("autostart a domain")},
{"desc",
N_("Configure a domain to be automatically started at boot.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_autostart[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"disable", VSH_OT_BOOL, 0, N_("disable autostarting")},
{NULL, 0, 0, NULL}
};
static bool
cmdAutostart(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *name;
int autostart;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
autostart = !vshCommandOptBool(cmd, "disable");
if (virDomainSetAutostart(dom, autostart) < 0) {
if (autostart)
vshError(ctl, _("Failed to mark domain %s as autostarted"), name);
else
vshError(ctl, _("Failed to unmark domain %s as autostarted"), name);
virDomainFree(dom);
return false;
}
if (autostart)
vshPrint(ctl, _("Domain %s marked as autostarted\n"), name);
else
vshPrint(ctl, _("Domain %s unmarked as autostarted\n"), name);
virDomainFree(dom);
return true;
}
/*
* "connect" command
*/
static const vshCmdInfo info_connect[] = {
{"help", N_("(re)connect to hypervisor")},
{"desc",
N_("Connect to local hypervisor. This is built-in command after shell start up.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_connect[] = {
{"name", VSH_OT_DATA, VSH_OFLAG_EMPTY_OK,
N_("hypervisor connection URI")},
{"readonly", VSH_OT_BOOL, 0, N_("read-only connection")},
{NULL, 0, 0, NULL}
};
static bool
cmdConnect(vshControl *ctl, const vshCmd *cmd)
{
bool ro = vshCommandOptBool(cmd, "readonly");
const char *name = NULL;
if (ctl->conn) {
int ret;
if ((ret = virConnectClose(ctl->conn)) != 0) {
vshError(ctl, _("Failed to disconnect from the hypervisor, %d leaked reference(s)"), ret);
return false;
}
ctl->conn = NULL;
}
VIR_FREE(ctl->name);
if (vshCommandOptString(cmd, "name", &name) < 0) {
vshError(ctl, "%s", _("Please specify valid connection URI"));
return false;
}
ctl->name = vshStrdup(ctl, name);
ctl->useGetInfo = false;
ctl->useSnapshotOld = false;
ctl->readonly = ro;
ctl->conn = virConnectOpenAuth(ctl->name, virConnectAuthPtrDefault,
ctl->readonly ? VIR_CONNECT_RO : 0);
if (!ctl->conn)
vshError(ctl, "%s", _("Failed to connect to the hypervisor"));
return !!ctl->conn;
}
#ifndef WIN32
/*
* "console" command
*/
static const vshCmdInfo info_console[] = {
{"help", N_("connect to the guest console")},
{"desc",
N_("Connect the virtual serial console for the guest")},
{NULL, NULL}
};
static const vshCmdOptDef opts_console[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"devname", VSH_OT_STRING, 0, N_("character device name")},
{"force", VSH_OT_BOOL, 0,
N_("force console connection (disconnect already connected sessions)")},
{"safe", VSH_OT_BOOL, 0,
N_("only connect if safe console handling is supported")},
{NULL, 0, 0, NULL}
};
static bool
cmdRunConsole(vshControl *ctl, virDomainPtr dom,
const char *name,
unsigned int flags)
{
bool ret = false;
int state;
if ((state = vshDomainState(ctl, dom, NULL)) < 0) {
vshError(ctl, "%s", _("Unable to get domain status"));
goto cleanup;
}
if (state == VIR_DOMAIN_SHUTOFF) {
vshError(ctl, "%s", _("The domain is not running"));
goto cleanup;
}
if (!isatty(STDIN_FILENO)) {
vshError(ctl, "%s", _("Cannot run interactive console without a controlling TTY"));
goto cleanup;
}
vshPrintExtra(ctl, _("Connected to domain %s\n"), virDomainGetName(dom));
vshPrintExtra(ctl, _("Escape character is %s\n"), ctl->escapeChar);
fflush(stdout);
if (vshRunConsole(dom, name, ctl->escapeChar, flags) == 0)
ret = true;
cleanup:
return ret;
}
static bool
cmdConsole(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = false;
bool force = vshCommandOptBool(cmd, "force");
bool safe = vshCommandOptBool(cmd, "safe");
unsigned int flags = 0;
const char *name = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptString(cmd, "devname", &name) < 0) {
vshError(ctl, "%s", _("Invalid devname"));
goto cleanup;
}
if (force)
flags |= VIR_DOMAIN_CONSOLE_FORCE;
if (safe)
flags |= VIR_DOMAIN_CONSOLE_SAFE;
ret = cmdRunConsole(ctl, dom, name, flags);
cleanup:
virDomainFree(dom);
return ret;
}
#endif /* WIN32 */
/*
* "list" command
*/
static const vshCmdInfo info_list[] = {
{"help", N_("list domains")},
{"desc", N_("Returns list of domains.")},
{NULL, NULL}
};
/* compare domains, pack NULLed ones at the end*/
static int
vshDomainSorter(const void *a, const void *b)
{
virDomainPtr *da = (virDomainPtr *) a;
virDomainPtr *db = (virDomainPtr *) b;
unsigned int ida;
unsigned int idb;
unsigned int inactive = (unsigned int) -1;
if (*da && !*db)
return -1;
if (!*da)
return *db != NULL;
ida = virDomainGetID(*da);
idb = virDomainGetID(*db);
if (ida == inactive && idb == inactive)
return strcasecmp(virDomainGetName(*da), virDomainGetName(*db));
if (ida != inactive && idb != inactive) {
if (ida > idb)
return 1;
else if (ida < idb)
return -1;
}
if (ida != inactive)
return -1;
else
return 1;
}
struct vshDomainList {
virDomainPtr *domains;
size_t ndomains;
};
typedef struct vshDomainList *vshDomainListPtr;
static void
vshDomainListFree(vshDomainListPtr domlist)
{
int i;
if (domlist && domlist->domains) {
for (i = 0; i < domlist->ndomains; i++) {
if (domlist->domains[i])
virDomainFree(domlist->domains[i]);
}
VIR_FREE(domlist->domains);
}
VIR_FREE(domlist);
}
#define MATCH(FLAG) (flags & (FLAG))
static vshDomainListPtr
vshDomainListCollect(vshControl *ctl, unsigned int flags)
{
vshDomainListPtr list = vshMalloc(ctl, sizeof(*list));
int i;
int ret;
int *ids = NULL;
int nids = 0;
char **names = NULL;
int nnames = 0;
virDomainPtr dom;
bool success = false;
size_t deleted = 0;
int persistent;
int autostart;
int state;
int nsnap;
int mansave;
/* try the list with flags support (0.9.13 and later) */
if ((ret = virConnectListAllDomains(ctl->conn, &list->domains,
flags)) >= 0) {
list->ndomains = ret;
goto finished;
}
/* check if the command is actually supported */
if (last_error && last_error->code == VIR_ERR_NO_SUPPORT) {
virFreeError(last_error);
last_error = NULL;
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_DOMAINS_ACTIVE |
VIR_CONNECT_LIST_DOMAINS_INACTIVE);
virFreeError(last_error);
last_error = NULL;
if ((ret = virConnectListAllDomains(ctl->conn, &list->domains,
newflags)) >= 0) {
list->ndomains = ret;
goto filter;
}
}
/* there was an error during the first or second call */
vshError(ctl, "%s", _("Failed to list domains"));
goto cleanup;
fallback:
/* fall back to old method (0.9.12 and older) */
virResetLastError();
/* list active domains, if necessary */
if (!MATCH(VIR_CONNECT_LIST_FILTERS_ACTIVE) ||
MATCH(VIR_CONNECT_LIST_DOMAINS_ACTIVE)) {
if ((nids = virConnectNumOfDomains(ctl->conn)) < 0) {
vshError(ctl, "%s", _("Failed to list active domains"));
goto cleanup;
}
if (nids) {
ids = vshMalloc(ctl, sizeof(int) * nids);
if ((nids = virConnectListDomains(ctl->conn, ids, nids)) < 0) {
vshError(ctl, "%s", _("Failed to list active domains"));
goto cleanup;
}
}
}
if (!MATCH(VIR_CONNECT_LIST_FILTERS_ACTIVE) ||
MATCH(VIR_CONNECT_LIST_DOMAINS_INACTIVE)) {
if ((nnames = virConnectNumOfDefinedDomains(ctl->conn)) < 0) {
vshError(ctl, "%s", _("Failed to list inactive domains"));
goto cleanup;
}
if (nnames) {
names = vshMalloc(ctl, sizeof(char *) * nnames);
if ((nnames = virConnectListDefinedDomains(ctl->conn, names,
nnames)) < 0) {
vshError(ctl, "%s", _("Failed to list inactive domains"));
goto cleanup;
}
}
}
list->domains = vshMalloc(ctl, sizeof(virDomainPtr) * (nids + nnames));
list->ndomains = 0;
/* get active domains */
for (i = 0; i < nids; i++) {
if (!(dom = virDomainLookupByID(ctl->conn, ids[i])))
continue;
list->domains[list->ndomains++] = dom;
}
/* get inactive domains */
for (i = 0; i < nnames; i++) {
if (!(dom = virDomainLookupByName(ctl->conn, names[i])))
continue;
list->domains[list->ndomains++] = dom;
}
/* truncate domains that weren't found */
deleted = (nids + nnames) - list->ndomains;
filter:
/* filter list the list if the list was acquired by fallback means */
for (i = 0; i < list->ndomains; i++) {
dom = list->domains[i];
/* persistence filter */
if (MATCH(VIR_CONNECT_LIST_FILTERS_PERSISTENT)) {
if ((persistent = virDomainIsPersistent(dom)) < 0) {
vshError(ctl, "%s", _("Failed to get domain persistence info"));
goto cleanup;
}
if (!((MATCH(VIR_CONNECT_LIST_DOMAINS_PERSISTENT) && persistent) ||
(MATCH(VIR_CONNECT_LIST_DOMAINS_TRANSIENT) && !persistent)))
goto remove_entry;
}
/* domain state filter */
if (MATCH(VIR_CONNECT_LIST_FILTERS_STATE)) {
if (virDomainGetState(dom, &state, NULL, 0) < 0) {
vshError(ctl, "%s", _("Failed to get domain state"));
goto cleanup;
}
if (!((MATCH(VIR_CONNECT_LIST_DOMAINS_RUNNING) &&
state == VIR_DOMAIN_RUNNING) ||
(MATCH(VIR_CONNECT_LIST_DOMAINS_PAUSED) &&
state == VIR_DOMAIN_PAUSED) ||
(MATCH(VIR_CONNECT_LIST_DOMAINS_SHUTOFF) &&
state == VIR_DOMAIN_SHUTOFF) ||
(MATCH(VIR_CONNECT_LIST_DOMAINS_OTHER) &&
(state != VIR_DOMAIN_RUNNING &&
state != VIR_DOMAIN_PAUSED &&
state != VIR_DOMAIN_SHUTOFF))))
goto remove_entry;
}
/* autostart filter */
if (MATCH(VIR_CONNECT_LIST_FILTERS_AUTOSTART)) {
if (virDomainGetAutostart(dom, &autostart) < 0) {
vshError(ctl, "%s", _("Failed to get domain autostart state"));
goto cleanup;
}
if (!((MATCH(VIR_CONNECT_LIST_DOMAINS_AUTOSTART) && autostart) ||
(MATCH(VIR_CONNECT_LIST_DOMAINS_NO_AUTOSTART) && !autostart)))
goto remove_entry;
}
/* managed save filter */
if (MATCH(VIR_CONNECT_LIST_FILTERS_MANAGEDSAVE)) {
if ((mansave = virDomainHasManagedSaveImage(dom, 0)) < 0) {
vshError(ctl, "%s",
_("Failed to check for managed save image"));
goto cleanup;
}
if (!((MATCH(VIR_CONNECT_LIST_DOMAINS_MANAGEDSAVE) && mansave) ||
(MATCH(VIR_CONNECT_LIST_DOMAINS_NO_MANAGEDSAVE) && !mansave)))
goto remove_entry;
}
/* snapshot filter */
if (MATCH(VIR_CONNECT_LIST_FILTERS_SNAPSHOT)) {
if ((nsnap = virDomainSnapshotNum(dom, 0)) < 0) {
vshError(ctl, "%s", _("Failed to get snapshot count"));
goto cleanup;
}
if (!((MATCH(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT) && nsnap > 0) ||
(MATCH(VIR_CONNECT_LIST_DOMAINS_NO_SNAPSHOT) && nsnap == 0)))
goto remove_entry;
}
/* the domain matched all filters, it may stay */
continue;
remove_entry:
/* the domain has to be removed as it failed one of the filters */
virDomainFree(list->domains[i]);
list->domains[i] = NULL;
deleted++;
}
finished:
/* sort the list */
if (list->domains && list->ndomains)
qsort(list->domains, list->ndomains, sizeof(*list->domains),
vshDomainSorter);
/* truncate the list if filter simulation deleted entries */
if (deleted)
VIR_SHRINK_N(list->domains, list->ndomains, deleted);
success = true;
cleanup:
for (i = 0; i < nnames; i++)
VIR_FREE(names[i]);
if (!success) {
vshDomainListFree(list);
list = NULL;
}
VIR_FREE(names);
VIR_FREE(ids);
return list;
}
#undef MATCH
static const vshCmdOptDef opts_list[] = {
{"inactive", VSH_OT_BOOL, 0, N_("list inactive domains")},
{"all", VSH_OT_BOOL, 0, N_("list inactive & active domains")},
{"transient", VSH_OT_BOOL, 0, N_("list transient domains")},
{"persistent", VSH_OT_BOOL, 0, N_("list persistent domains")},
{"with-snapshot", VSH_OT_BOOL, 0,
N_("list domains with existing snapshot")},
{"without-snapshot", VSH_OT_BOOL, 0,
N_("list domains without a snapshot")},
{"state-running", VSH_OT_BOOL, 0, N_("list domains in running state")},
{"state-paused", VSH_OT_BOOL, 0, N_("list domains in paused state")},
{"state-shutoff", VSH_OT_BOOL, 0, N_("list domains in shutoff state")},
{"state-other", VSH_OT_BOOL, 0, N_("list domains in other states")},
{"autostart", VSH_OT_BOOL, 0, N_("list domains with autostart enabled")},
{"no-autostart", VSH_OT_BOOL, 0,
N_("list domains with autostart disabled")},
{"with-managed-save", VSH_OT_BOOL, 0,
N_("list domains with managed save state")},
{"without-managed-save", VSH_OT_BOOL, 0,
N_("list domains without managed save")},
{"uuid", VSH_OT_BOOL, 0, N_("list uuid's only")},
{"name", VSH_OT_BOOL, 0, N_("list domain names only")},
{"table", VSH_OT_BOOL, 0, N_("list table (default)")},
{"managed-save", VSH_OT_BOOL, 0,
N_("mark inactive domains with managed save state")},
{"title", VSH_OT_BOOL, 0, N_("show short domain description")},
{NULL, 0, 0, NULL}
};
#define FILTER(NAME, FLAG) \
if (vshCommandOptBool(cmd, NAME)) \
flags |= (FLAG)
static bool
cmdList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
bool managed = vshCommandOptBool(cmd, "managed-save");
bool optTitle = vshCommandOptBool(cmd, "title");
bool optTable = vshCommandOptBool(cmd, "table");
bool optUUID = vshCommandOptBool(cmd, "uuid");
bool optName = vshCommandOptBool(cmd, "name");
int i;
char *title;
char uuid[VIR_UUID_STRING_BUFLEN];
int state;
bool ret = false;
vshDomainListPtr list = NULL;
virDomainPtr dom;
char id_buf[INT_BUFSIZE_BOUND(unsigned int)];
unsigned int id;
unsigned int flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE;
/* construct filter flags */
if (vshCommandOptBool(cmd, "inactive"))
flags = VIR_CONNECT_LIST_DOMAINS_INACTIVE;
if (vshCommandOptBool(cmd, "all"))
flags = VIR_CONNECT_LIST_DOMAINS_INACTIVE |
VIR_CONNECT_LIST_DOMAINS_ACTIVE;
FILTER("persistent", VIR_CONNECT_LIST_DOMAINS_PERSISTENT);
FILTER("transient", VIR_CONNECT_LIST_DOMAINS_TRANSIENT);
FILTER("with-managed-save", VIR_CONNECT_LIST_DOMAINS_MANAGEDSAVE);
FILTER("without-managed-save", VIR_CONNECT_LIST_DOMAINS_NO_MANAGEDSAVE);
FILTER("autostart", VIR_CONNECT_LIST_DOMAINS_AUTOSTART);
FILTER("no-autostart", VIR_CONNECT_LIST_DOMAINS_NO_AUTOSTART);
FILTER("with-snapshot", VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT);
FILTER("without-snapshot", VIR_CONNECT_LIST_DOMAINS_NO_SNAPSHOT);
FILTER("state-running", VIR_CONNECT_LIST_DOMAINS_RUNNING);
FILTER("state-paused", VIR_CONNECT_LIST_DOMAINS_PAUSED);
FILTER("state-shutoff", VIR_CONNECT_LIST_DOMAINS_SHUTOFF);
FILTER("state-other", VIR_CONNECT_LIST_DOMAINS_OTHER);
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 (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(list = vshDomainListCollect(ctl, flags)))
goto cleanup;
/* print table header in legacy mode */
if (optTable) {
if (optTitle)
vshPrintExtra(ctl, " %-5s %-30s %-10s %-20s\n%s\n",
_("Id"), _("Name"), _("State"), _("Title"),
"-----------------------------------------"
"-----------------------------------------");
else
vshPrintExtra(ctl, " %-5s %-30s %s\n%s\n",
_("Id"), _("Name"), _("State"),
"-----------------------------------------"
"-----------");
}
for (i = 0; i < list->ndomains; i++) {
dom = list->domains[i];
id = virDomainGetID(dom);
if (id != (unsigned int) -1)
snprintf(id_buf, sizeof(id_buf), "%d", id);
else
ignore_value(virStrcpyStatic(id_buf, "-"));
state = vshDomainState(ctl, dom, NULL);
if (optTable && managed && state == VIR_DOMAIN_SHUTOFF &&
virDomainHasManagedSaveImage(dom, 0) > 0)
state = -2;
if (optTable) {
if (optTitle) {
if (!(title = vshGetDomainDescription(ctl, dom, true, 0)))
goto cleanup;
vshPrint(ctl, " %-5s %-30s %-10s %-20s\n", id_buf,
virDomainGetName(dom),
state == -2 ? _("saved") : _(vshDomainStateToString(state)),
title);
VIR_FREE(title);
} else {
vshPrint(ctl, " %-5s %-30s %s\n", id_buf,
virDomainGetName(dom),
state == -2 ? _("saved") : _(vshDomainStateToString(state)));
}
} else if (optUUID) {
if (virDomainGetUUIDString(dom, uuid) < 0) {
vshError(ctl, "%s", _("Failed to get domain's UUID"));
goto cleanup;
}
vshPrint(ctl, "%s\n", uuid);
} else if (optName) {
vshPrint(ctl, "%s\n", virDomainGetName(dom));
}
}
ret = true;
cleanup:
vshDomainListFree(list);
return ret;
}
#undef FILTER
/*
* "desc" command for managing domain description and title
*/
static const vshCmdInfo info_desc[] = {
{"help", N_("show or set domain's description or title")},
{"desc", N_("Allows to show or modify description or title of a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_desc[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"live", VSH_OT_BOOL, 0, N_("modify/get running state")},
{"config", VSH_OT_BOOL, 0, N_("modify/get persistent configuration")},
{"current", VSH_OT_BOOL, 0, N_("modify/get current state configuration")},
{"title", VSH_OT_BOOL, 0, N_("modify/get the title instead of description")},
{"edit", VSH_OT_BOOL, 0, N_("open an editor to modify the description")},
{"new-desc", VSH_OT_ARGV, 0, N_("message")},
{NULL, 0, 0, NULL}
};
static bool
cmdDesc(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
virDomainPtr dom;
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 state;
int type;
char *desc = NULL;
char *desc_edited = NULL;
char *tmp = NULL;
char *tmpstr;
const vshCmdOpt *opt = NULL;
virBuffer buf = VIR_BUFFER_INITIALIZER;
bool pad = false;
bool ret = false;
unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT;
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if ((state = vshDomainState(ctl, dom, NULL)) < 0)
goto cleanup;
while ((opt = vshCommandOptArgv(cmd, opt))) {
if (pad)
virBufferAddChar(&buf, ' ');
pad = true;
virBufferAdd(&buf, opt->data, -1);
}
if (title)
type = VIR_DOMAIN_METADATA_TITLE;
else
type = VIR_DOMAIN_METADATA_DESCRIPTION;
if (virBufferError(&buf)) {
vshPrint(ctl, "%s", _("Failed to collect new description/title"));
goto cleanup;
}
desc = virBufferContentAndReset(&buf);
if (edit || desc) {
if (!desc) {
desc = vshGetDomainDescription(ctl, dom, title,
config?VIR_DOMAIN_XML_INACTIVE:0);
if (!desc)
goto cleanup;
}
if (edit) {
/* Create and open the temporary file. */
if (!(tmp = editWriteToTempFile(ctl, desc)))
goto cleanup;
/* Start the editor. */
if (editFile(ctl, tmp) == -1)
goto cleanup;
/* Read back the edited file. */
if (!(desc_edited = editReadBackFile(ctl, tmp)))
goto cleanup;
/* 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(desc, desc_edited)) {
vshPrint(ctl, _("Domain description not changed.\n"));
ret = true;
goto cleanup;
}
VIR_FREE(desc);
desc = desc_edited;
desc_edited = NULL;
}
if (virDomainSetMetadata(dom, type, desc, NULL, NULL, flags) < 0) {
vshError(ctl, "%s",
_("Failed to set new domain description"));
goto cleanup;
}
vshPrint(ctl, "%s", _("Domain description updated successfully"));
} else {
desc = vshGetDomainDescription(ctl, dom, title,
config?VIR_DOMAIN_XML_INACTIVE:0);
if (!desc)
goto cleanup;
if (strlen(desc) > 0)
vshPrint(ctl, "%s", desc);
else
vshPrint(ctl, _("No description for domain: %s"),
virDomainGetName(dom));
}
ret = true;
cleanup:
VIR_FREE(desc_edited);
VIR_FREE(desc);
if (tmp) {
unlink(tmp);
VIR_FREE(tmp);
}
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "domstate" command
*/
static const vshCmdInfo info_domstate[] = {
{"help", N_("domain state")},
{"desc", N_("Returns state about a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domstate[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"reason", VSH_OT_BOOL, 0, N_("also print reason for the state")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomstate(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = true;
bool showReason = vshCommandOptBool(cmd, "reason");
int state, reason;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if ((state = vshDomainState(ctl, dom, &reason)) < 0) {
ret = false;
goto cleanup;
}
if (showReason) {
vshPrint(ctl, "%s (%s)\n",
_(vshDomainStateToString(state)),
vshDomainStateReasonToString(state, reason));
} else {
vshPrint(ctl, "%s\n",
_(vshDomainStateToString(state)));
}
cleanup:
virDomainFree(dom);
return ret;
}
/*
* "domcontrol" command
*/
static const vshCmdInfo info_domcontrol[] = {
{"help", N_("domain control interface state")},
{"desc", N_("Returns state of a control interface to the domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domcontrol[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomControl(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = true;
virDomainControlInfo info;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (virDomainGetControlInfo(dom, &info, 0) < 0) {
ret = false;
goto cleanup;
}
if (info.state != VIR_DOMAIN_CONTROL_OK &&
info.state != VIR_DOMAIN_CONTROL_ERROR) {
vshPrint(ctl, "%s (%0.3fs)\n",
_(vshDomainControlStateToString(info.state)),
info.stateTime / 1000.0);
} else {
vshPrint(ctl, "%s\n",
_(vshDomainControlStateToString(info.state)));
}
cleanup:
virDomainFree(dom);
return ret;
}
/* "domblkstat" command
*/
static const vshCmdInfo info_domblkstat[] = {
{"help", N_("get device block stats for a domain")},
{"desc", N_("Get device block stats for a running domain. See man page or "
"use --human for explanation of fields")},
{NULL,NULL}
};
static const vshCmdOptDef opts_domblkstat[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"device", VSH_OT_DATA, VSH_OFLAG_REQ, N_("block device")},
{"human", VSH_OT_BOOL, 0, N_("print a more human readable output")},
{NULL, 0, 0, NULL}
};
struct _domblkstat_sequence {
const char *field; /* field name */
const char *legacy; /* legacy name from previous releases */
const char *human; /* human-friendly explanation */
};
/* sequence of values for output to honor legacy format from previous
* versions */
static const struct _domblkstat_sequence domblkstat_output[] = {
{ VIR_DOMAIN_BLOCK_STATS_READ_REQ, "rd_req",
N_("number of read operations:") }, /* 0 */
{ VIR_DOMAIN_BLOCK_STATS_READ_BYTES, "rd_bytes",
N_("number of bytes read:") }, /* 1 */
{ VIR_DOMAIN_BLOCK_STATS_WRITE_REQ, "wr_req",
N_("number of write operations:") }, /* 2 */
{ VIR_DOMAIN_BLOCK_STATS_WRITE_BYTES, "wr_bytes",
N_("number of bytes written:") }, /* 3 */
{ VIR_DOMAIN_BLOCK_STATS_ERRS, "errs",
N_("error count:") }, /* 4 */
{ VIR_DOMAIN_BLOCK_STATS_FLUSH_REQ, NULL,
N_("number of flush operations:") }, /* 5 */
{ VIR_DOMAIN_BLOCK_STATS_READ_TOTAL_TIMES, NULL,
N_("total duration of reads (ns):") }, /* 6 */
{ VIR_DOMAIN_BLOCK_STATS_WRITE_TOTAL_TIMES, NULL,
N_("total duration of writes (ns):") }, /* 7 */
{ VIR_DOMAIN_BLOCK_STATS_FLUSH_TOTAL_TIMES, NULL,
N_("total duration of flushes (ns):") }, /* 8 */
{ NULL, NULL, NULL }
};
#define DOMBLKSTAT_LEGACY_PRINT(ID, VALUE) \
if (VALUE >= 0) \
vshPrint(ctl, "%s %-*s %lld\n", device, \
human ? 31 : 0, \
human ? _(domblkstat_output[ID].human) \
: domblkstat_output[ID].legacy, \
VALUE);
static bool
cmdDomblkstat(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *name = NULL, *device = NULL;
struct _virDomainBlockStats stats;
virTypedParameterPtr params = NULL;
virTypedParameterPtr par = NULL;
char *value = NULL;
const char *field = NULL;
int rc, nparams = 0;
int i = 0;
bool ret = false;
bool human = vshCommandOptBool(cmd, "human"); /* human readable output */
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (vshCommandOptString(cmd, "device", &device) <= 0)
goto cleanup;
rc = virDomainBlockStatsFlags(dom, device, NULL, &nparams, 0);
/* It might fail when virDomainBlockStatsFlags is not
* supported on older libvirt, fallback to use virDomainBlockStats
* then.
*/
if (rc < 0) {
/* try older API if newer is not supported */
if (last_error->code != VIR_ERR_NO_SUPPORT)
goto cleanup;
virFreeError(last_error);
last_error = NULL;
if (virDomainBlockStats(dom, device, &stats,
sizeof(stats)) == -1) {
vshError(ctl, _("Failed to get block stats %s %s"),
name, device);
goto cleanup;
}
/* human friendly output */
if (human) {
vshPrint(ctl, N_("Device: %s\n"), device);
device = "";
}
DOMBLKSTAT_LEGACY_PRINT(0, stats.rd_req);
DOMBLKSTAT_LEGACY_PRINT(1, stats.rd_bytes);
DOMBLKSTAT_LEGACY_PRINT(2, stats.wr_req);
DOMBLKSTAT_LEGACY_PRINT(3, stats.wr_bytes);
DOMBLKSTAT_LEGACY_PRINT(4, stats.errs);
} else {
params = vshCalloc(ctl, nparams, sizeof(*params));
if (virDomainBlockStatsFlags(dom, device, params, &nparams, 0) < 0) {
vshError(ctl, _("Failed to get block stats %s %s"), name, device);
goto cleanup;
}
/* set for prettier output */
if (human) {
vshPrint(ctl, N_("Device: %s\n"), device);
device = "";
}
/* at first print all known values in desired order */
for (i = 0; domblkstat_output[i].field != NULL; i++) {
if (!(par = vshFindTypedParamByName(domblkstat_output[i].field,
params,
nparams)))
continue;
value = vshGetTypedParamValue(ctl, par);
/* to print other not supported fields, mark the already printed */
par->field[0] = '\0'; /* set the name to empty string */
/* translate into human readable or legacy spelling */
field = NULL;
if (human)
field = _(domblkstat_output[i].human);
else
field = domblkstat_output[i].legacy;
/* use the provided spelling if no translation is available */
if (!field)
field = domblkstat_output[i].field;
vshPrint(ctl, "%s %-*s %s\n", device,
human ? 31 : 0, field, value);
VIR_FREE(value);
}
/* go through the fields again, for remaining fields */
for (i = 0; i < nparams; i++) {
if (!*params[i].field)
continue;
value = vshGetTypedParamValue(ctl, params+i);
vshPrint(ctl, "%s %s %s\n", device, params[i].field, value);
VIR_FREE(value);
}
}
ret = true;
cleanup:
VIR_FREE(params);
virDomainFree(dom);
return ret;
}
#undef DOMBLKSTAT_LEGACY_PRINT
/* "domifstat" command
*/
static const vshCmdInfo info_domifstat[] = {
{"help", N_("get network interface stats for a domain")},
{"desc", N_("Get network interface stats for a running domain.")},
{NULL,NULL}
};
static const vshCmdOptDef opts_domifstat[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface device")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomIfstat(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *name = NULL, *device = NULL;
struct _virDomainInterfaceStats stats;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (vshCommandOptString(cmd, "interface", &device) <= 0) {
virDomainFree(dom);
return false;
}
if (virDomainInterfaceStats(dom, device, &stats, sizeof(stats)) == -1) {
vshError(ctl, _("Failed to get interface stats %s %s"), name, device);
virDomainFree(dom);
return false;
}
if (stats.rx_bytes >= 0)
vshPrint(ctl, "%s rx_bytes %lld\n", device, stats.rx_bytes);
if (stats.rx_packets >= 0)
vshPrint(ctl, "%s rx_packets %lld\n", device, stats.rx_packets);
if (stats.rx_errs >= 0)
vshPrint(ctl, "%s rx_errs %lld\n", device, stats.rx_errs);
if (stats.rx_drop >= 0)
vshPrint(ctl, "%s rx_drop %lld\n", device, stats.rx_drop);
if (stats.tx_bytes >= 0)
vshPrint(ctl, "%s tx_bytes %lld\n", device, stats.tx_bytes);
if (stats.tx_packets >= 0)
vshPrint(ctl, "%s tx_packets %lld\n", device, stats.tx_packets);
if (stats.tx_errs >= 0)
vshPrint(ctl, "%s tx_errs %lld\n", device, stats.tx_errs);
if (stats.tx_drop >= 0)
vshPrint(ctl, "%s tx_drop %lld\n", device, stats.tx_drop);
virDomainFree(dom);
return true;
}
/* "domif-setlink" command
*/
static const vshCmdInfo info_domif_setlink[] = {
{"help", N_("set link state of a virtual interface")},
{"desc", N_("Set link state of a domain's virtual interface. This command wraps usage of update-device command.")},
{NULL,NULL}
};
static const vshCmdOptDef opts_domif_setlink[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface device (MAC Address)")},
{"state", VSH_OT_DATA, VSH_OFLAG_REQ, N_("new state of the device")},
{"persistent", VSH_OT_ALIAS, 0, "config"},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomIfSetLink(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *iface;
const char *state;
const char *value;
const char *desc;
virMacAddr macaddr;
const char *element;
const char *attr;
bool config;
bool ret = false;
unsigned int flags = 0;
int i;
xmlDocPtr xml = NULL;
xmlXPathContextPtr ctxt = NULL;
xmlXPathObjectPtr obj = NULL;
xmlNodePtr cur = NULL;
xmlBufferPtr xml_buf = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptString(cmd, "interface", &iface) <= 0)
goto cleanup;
if (vshCommandOptString(cmd, "state", &state) <= 0)
goto cleanup;
config = vshCommandOptBool(cmd, "config");
if (STRNEQ(state, "up") && STRNEQ(state, "down")) {
vshError(ctl, _("invalid link state '%s'"), state);
goto cleanup;
}
/* get persistent or live description of network device */
desc = virDomainGetXMLDesc(dom, config ? VIR_DOMAIN_XML_INACTIVE : 0);
if (desc == NULL) {
vshError(ctl, _("Failed to get domain description xml"));
goto cleanup;
}
if (config)
flags = VIR_DOMAIN_AFFECT_CONFIG;
else
flags = VIR_DOMAIN_AFFECT_LIVE;
if (virDomainIsActive(dom) == 0)
flags = VIR_DOMAIN_AFFECT_CONFIG;
/* extract current network device description */
xml = virXMLParseStringCtxt(desc, _("(domain_definition)"), &ctxt);
VIR_FREE(desc);
if (!xml) {
vshError(ctl, _("Failed to parse domain description xml"));
goto cleanup;
}
obj = xmlXPathEval(BAD_CAST "/domain/devices/interface", ctxt);
if (obj == NULL || obj->type != XPATH_NODESET ||
obj->nodesetval == NULL || obj->nodesetval->nodeNr == 0) {
vshError(ctl, _("Failed to extract interface information or no interfaces found"));
goto cleanup;
}
if (virMacAddrParse(iface, &macaddr) == 0) {
element = "mac";
attr = "address";
} else {
element = "target";
attr = "dev";
}
/* find interface with matching mac addr */
for (i = 0; i < obj->nodesetval->nodeNr; i++) {
cur = obj->nodesetval->nodeTab[i]->children;
while (cur) {
if (cur->type == XML_ELEMENT_NODE &&
xmlStrEqual(cur->name, BAD_CAST element)) {
value = virXMLPropString(cur, attr);
if (STRCASEEQ(value, iface)) {
VIR_FREE(value);
goto hit;
}
VIR_FREE(value);
}
cur = cur->next;
}
}
vshError(ctl, _("interface (%s: %s) not found"), element, iface);
goto cleanup;
hit:
/* find and modify/add link state node */
/* try to find <link> element */
cur = obj->nodesetval->nodeTab[i]->children;
while (cur) {
if (cur->type == XML_ELEMENT_NODE &&
xmlStrEqual(cur->name, BAD_CAST "link")) {
/* found, just modify the property */
xmlSetProp(cur, BAD_CAST "state", BAD_CAST state);
break;
}
cur = cur->next;
}
if (!cur) {
/* element <link> not found, add one */
cur = xmlNewChild(obj->nodesetval->nodeTab[i],
NULL,
BAD_CAST "link",
NULL);
if (!cur)
goto cleanup;
if (xmlNewProp(cur, BAD_CAST "state", BAD_CAST state) == NULL)
goto cleanup;
}
xml_buf = xmlBufferCreate();
if (!xml_buf) {
vshError(ctl, _("Failed to allocate memory"));
goto cleanup;
}
if (xmlNodeDump(xml_buf, xml, obj->nodesetval->nodeTab[i], 0, 0) < 0 ) {
vshError(ctl, _("Failed to create XML"));
goto cleanup;
}
if (virDomainUpdateDeviceFlags(dom, (char *)xmlBufferContent(xml_buf), flags) < 0) {
vshError(ctl, _("Failed to update interface link state"));
goto cleanup;
} else {
vshPrint(ctl, "%s", _("Device updated successfully\n"));
ret = true;
}
cleanup:
xmlXPathFreeObject(obj);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
xmlBufferFree(xml_buf);
if (dom)
virDomainFree(dom);
return ret;
}
/* "domif-getlink" command
*/
static const vshCmdInfo info_domif_getlink[] = {
{"help", N_("get link state of a virtual interface")},
{"desc", N_("Get link state of a domain's virtual interface.")},
{NULL,NULL}
};
static const vshCmdOptDef opts_domif_getlink[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface device (MAC Address)")},
{"persistent", VSH_OT_ALIAS, 0, "config"},
{"config", VSH_OT_BOOL, 0, N_("Get persistent interface state")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomIfGetLink(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *iface = NULL;
int flags = 0;
char *state = NULL;
char *value = NULL;
virMacAddr macaddr;
const char *element;
const char *attr;
bool ret = false;
int i;
char *desc;
xmlDocPtr xml = NULL;
xmlXPathContextPtr ctxt = NULL;
xmlNodePtr cur = NULL;
xmlXPathObjectPtr obj = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptString(cmd, "interface", &iface) <= 0) {
virDomainFree(dom);
return false;
}
if (vshCommandOptBool(cmd, "config"))
flags = VIR_DOMAIN_XML_INACTIVE;
desc = virDomainGetXMLDesc(dom, flags);
if (desc == NULL) {
vshError(ctl, _("Failed to get domain description xml"));
goto cleanup;
}
xml = virXMLParseStringCtxt(desc, _("(domain_definition)"), &ctxt);
VIR_FREE(desc);
if (!xml) {
vshError(ctl, _("Failed to parse domain description xml"));
goto cleanup;
}
obj = xmlXPathEval(BAD_CAST "/domain/devices/interface", ctxt);
if (obj == NULL || obj->type != XPATH_NODESET ||
obj->nodesetval == NULL || obj->nodesetval->nodeNr == 0) {
vshError(ctl, _("Failed to extract interface information or no interfaces found"));
goto cleanup;
}
if (virMacAddrParse(iface, &macaddr) == 0) {
element = "mac";
attr = "address";
} else {
element = "target";
attr = "dev";
}
/* find interface with matching mac addr */
for (i = 0; i < obj->nodesetval->nodeNr; i++) {
cur = obj->nodesetval->nodeTab[i]->children;
while (cur) {
if (cur->type == XML_ELEMENT_NODE &&
xmlStrEqual(cur->name, BAD_CAST element)) {
value = virXMLPropString(cur, attr);
if (STRCASEEQ(value, iface)) {
VIR_FREE(value);
goto hit;
}
VIR_FREE(value);
}
cur = cur->next;
}
}
vshError(ctl, _("Interface (%s: %s) not found."), element, iface);
goto cleanup;
hit:
cur = obj->nodesetval->nodeTab[i]->children;
while (cur) {
if (cur->type == XML_ELEMENT_NODE &&
xmlStrEqual(cur->name, BAD_CAST "link")) {
state = virXMLPropString(cur, "state");
vshPrint(ctl, "%s %s", iface, state);
VIR_FREE(state);
goto cleanup;
}
cur = cur->next;
}
/* attribute not found */
vshPrint(ctl, "%s default", iface);
ret = true;
cleanup:
xmlXPathFreeObject(obj);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
if (dom)
virDomainFree(dom);
return ret;
}
/* "domiftune" command
*/
static const vshCmdInfo info_domiftune[] = {
{"help", N_("get/set parameters of a virtual interface")},
{"desc", N_("Get/set parameters of a domain's virtual interface.")},
{NULL,NULL}
};
static const vshCmdOptDef opts_domiftune[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface device (MAC Address)")},
{"inbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's incoming traffics")},
{"outbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's outgoing traffics")},
{"config", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("affect next boot")},
{"live", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("affect running domain")},
{"current", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("affect current domain")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomIftune(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *name = NULL, *device = NULL,
*inboundStr = NULL, *outboundStr = NULL;
unsigned int flags = 0;
int nparams = 0;
virTypedParameterPtr params = NULL;
bool ret = false;
bool current = vshCommandOptBool(cmd, "current");
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
virNetDevBandwidthRate inbound, outbound;
int i;
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (vshCommandOptString(cmd, "interface", &device) <= 0) {
virDomainFree(dom);
return false;
}
if (vshCommandOptString(cmd, "inbound", &inboundStr) < 0 ||
vshCommandOptString(cmd, "outbound", &outboundStr) < 0) {
vshError(ctl, "missing argument");
goto cleanup;
}
memset(&inbound, 0, sizeof(inbound));
memset(&outbound, 0, sizeof(outbound));
if (inboundStr) {
if (parseRateStr(inboundStr, &inbound) < 0) {
vshError(ctl, _("inbound format is incorrect"));
goto cleanup;
}
if (inbound.average == 0) {
vshError(ctl, _("inbound average is mandatory"));
goto cleanup;
}
nparams++; /* average */
if (inbound.peak) nparams++;
if (inbound.burst) nparams++;
}
if (outboundStr) {
if (parseRateStr(outboundStr, &outbound) < 0) {
vshError(ctl, _("outbound format is incorrect"));
goto cleanup;
}
if (outbound.average == 0) {
vshError(ctl, _("outbound average is mandatory"));
goto cleanup;
}
nparams++; /* average */
if (outbound.peak) nparams++;
if (outbound.burst) nparams++;
}
if (nparams == 0) {
/* get the number of interface parameters */
if (virDomainGetInterfaceParameters(dom, device, NULL, &nparams, flags) != 0) {
vshError(ctl, "%s",
_("Unable to get number of interface parameters"));
goto cleanup;
}
if (nparams == 0) {
/* nothing to output */
ret = true;
goto cleanup;
}
/* get all interface parameters */
params = vshCalloc(ctl, nparams, sizeof(*params));
if (!params) {
virReportOOMError();
goto cleanup;
}
if (virDomainGetInterfaceParameters(dom, device, params, &nparams, flags) != 0) {
vshError(ctl, "%s", _("Unable to get interface parameters"));
goto cleanup;
}
for (i = 0; i < nparams; i++) {
char *str = vshGetTypedParamValue(ctl, &params[i]);
vshPrint(ctl, "%-15s: %s\n", params[i].field, str);
VIR_FREE(str);
}
} else {
/* set the interface parameters */
params = vshCalloc(ctl, nparams, sizeof(*params));
if (!params) {
virReportOOMError();
goto cleanup;
}
for (i = 0; i < nparams; i++)
params[i].type = VIR_TYPED_PARAM_UINT;
i = 0;
if (inbound.average && i < nparams) {
if (!virStrcpy(params[i].field, VIR_DOMAIN_BANDWIDTH_IN_AVERAGE,
sizeof(params[i].field)))
goto cleanup;
params[i].value.ui = inbound.average;
i++;
}
if (inbound.peak && i < nparams) {
if (!virStrcpy(params[i].field, VIR_DOMAIN_BANDWIDTH_IN_PEAK,
sizeof(params[i].field)))
goto cleanup;
params[i].value.ui = inbound.peak;
i++;
}
if (inbound.burst && i < nparams) {
if (!virStrcpy(params[i].field, VIR_DOMAIN_BANDWIDTH_IN_BURST,
sizeof(params[i].field)))
goto cleanup;
params[i].value.ui = inbound.burst;
i++;
}
if (outbound.average && i < nparams) {
if (!virStrcpy(params[i].field, VIR_DOMAIN_BANDWIDTH_OUT_AVERAGE,
sizeof(params[i].field)))
goto cleanup;
params[i].value.ui = outbound.average;
i++;
}
if (outbound.peak && i < nparams) {
if (!virStrcpy(params[i].field, VIR_DOMAIN_BANDWIDTH_OUT_PEAK,
sizeof(params[i].field)))
goto cleanup;
params[i].value.ui = outbound.peak;
i++;
}
if (outbound.burst && i < nparams) {
if (!virStrcpy(params[i].field, VIR_DOMAIN_BANDWIDTH_OUT_BURST,
sizeof(params[i].field)))
goto cleanup;
params[i].value.ui = outbound.burst;
i++;
}
if (virDomainSetInterfaceParameters(dom, device, params, nparams, flags) != 0) {
vshError(ctl, "%s", _("Unable to set interface parameters"));
goto cleanup;
}
}
ret = true;
cleanup:
virTypedParameterArrayClear(params, nparams);
VIR_FREE(params);
virDomainFree(dom);
return ret;
}
/*
* "dommemstats" command
*/
static const vshCmdInfo info_dommemstat[] = {
{"help", N_("get memory statistics for a domain")},
{"desc", N_("Get memory statistics for a running domain.")},
{NULL,NULL}
};
static const vshCmdOptDef opts_dommemstat[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomMemStat(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *name;
struct _virDomainMemoryStat stats[VIR_DOMAIN_MEMORY_STAT_NR];
unsigned int nr_stats, i;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
nr_stats = virDomainMemoryStats(dom, stats, VIR_DOMAIN_MEMORY_STAT_NR, 0);
if (nr_stats == -1) {
vshError(ctl, _("Failed to get memory statistics for domain %s"), name);
virDomainFree(dom);
return false;
}
for (i = 0; i < nr_stats; i++) {
if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_SWAP_IN)
vshPrint(ctl, "swap_in %llu\n", stats[i].val);
if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_SWAP_OUT)
vshPrint(ctl, "swap_out %llu\n", stats[i].val);
if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_MAJOR_FAULT)
vshPrint(ctl, "major_fault %llu\n", stats[i].val);
if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_MINOR_FAULT)
vshPrint(ctl, "minor_fault %llu\n", stats[i].val);
if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_UNUSED)
vshPrint(ctl, "unused %llu\n", stats[i].val);
if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_AVAILABLE)
vshPrint(ctl, "available %llu\n", stats[i].val);
if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_ACTUAL_BALLOON)
vshPrint(ctl, "actual %llu\n", stats[i].val);
if (stats[i].tag == VIR_DOMAIN_MEMORY_STAT_RSS)
vshPrint(ctl, "rss %llu\n", stats[i].val);
}
virDomainFree(dom);
return true;
}
/*
* "domblkinfo" command
*/
static const vshCmdInfo info_domblkinfo[] = {
{"help", N_("domain block device size information")},
{"desc", N_("Get block device size info for a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domblkinfo[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"device", VSH_OT_DATA, VSH_OFLAG_REQ, N_("block device")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomblkinfo(vshControl *ctl, const vshCmd *cmd)
{
virDomainBlockInfo info;
virDomainPtr dom;
bool ret = true;
const char *device = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptString(cmd, "device", &device) <= 0) {
virDomainFree(dom);
return false;
}
if (virDomainGetBlockInfo(dom, device, &info, 0) < 0) {
virDomainFree(dom);
return false;
}
vshPrint(ctl, "%-15s %llu\n", _("Capacity:"), info.capacity);
vshPrint(ctl, "%-15s %llu\n", _("Allocation:"), info.allocation);
vshPrint(ctl, "%-15s %llu\n", _("Physical:"), info.physical);
virDomainFree(dom);
return ret;
}
/*
* "domblklist" command
*/
static const vshCmdInfo info_domblklist[] = {
{"help", N_("list all domain blocks")},
{"desc", N_("Get the summary of block devices for a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domblklist[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"inactive", VSH_OT_BOOL, 0,
N_("get inactive rather than running configuration")},
{"details", VSH_OT_BOOL, 0,
N_("additionally display the type and device value")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomblklist(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = false;
unsigned int flags = 0;
char *xml = NULL;
xmlDocPtr xmldoc = NULL;
xmlXPathContextPtr ctxt = NULL;
int ndisks;
xmlNodePtr *disks = NULL;
int i;
bool details = false;
if (vshCommandOptBool(cmd, "inactive"))
flags |= VIR_DOMAIN_XML_INACTIVE;
details = vshCommandOptBool(cmd, "details");
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
xml = virDomainGetXMLDesc(dom, flags);
if (!xml)
goto cleanup;
xmldoc = virXMLParseStringCtxt(xml, _("(domain_definition)"), &ctxt);
if (!xmldoc)
goto cleanup;
ndisks = virXPathNodeSet("./devices/disk", ctxt, &disks);
if (ndisks < 0)
goto cleanup;
if (details)
vshPrint(ctl, "%-10s %-10s %-10s %s\n", _("Type"),
_("Device"), _("Target"), _("Source"));
else
vshPrint(ctl, "%-10s %s\n", _("Target"), _("Source"));
vshPrint(ctl, "------------------------------------------------\n");
for (i = 0; i < ndisks; i++) {
char *type;
char *device;
char *target;
char *source;
ctxt->node = disks[i];
if (details) {
type = virXPathString("string(./@type)", ctxt);
device = virXPathString("string(./@device)", ctxt);
}
target = virXPathString("string(./target/@dev)", ctxt);
if (!target) {
vshError(ctl, "unable to query block list");
goto cleanup;
}
source = virXPathString("string(./source/@file"
"|./source/@dev"
"|./source/@dir"
"|./source/@name)", ctxt);
if (details) {
vshPrint(ctl, "%-10s %-10s %-10s %s\n", type, device,
target, source ? source : "-");
VIR_FREE(type);
VIR_FREE(device);
} else {
vshPrint(ctl, "%-10s %s\n", target, source ? source : "-");
}
VIR_FREE(target);
VIR_FREE(source);
}
ret = true;
cleanup:
VIR_FREE(disks);
virDomainFree(dom);
VIR_FREE(xml);
xmlFreeDoc(xmldoc);
xmlXPathFreeContext(ctxt);
return ret;
}
/*
* "domiflist" command
*/
static const vshCmdInfo info_domiflist[] = {
{"help", N_("list all domain virtual interfaces")},
{"desc", N_("Get the summary of virtual interfaces for a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domiflist[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"inactive", VSH_OT_BOOL, 0,
N_("get inactive rather than running configuration")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomiflist(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = false;
unsigned int flags = 0;
char *xml = NULL;
xmlDocPtr xmldoc = NULL;
xmlXPathContextPtr ctxt = NULL;
int ninterfaces;
xmlNodePtr *interfaces = NULL;
int i;
if (vshCommandOptBool(cmd, "inactive"))
flags |= VIR_DOMAIN_XML_INACTIVE;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
xml = virDomainGetXMLDesc(dom, flags);
if (!xml)
goto cleanup;
xmldoc = virXMLParseStringCtxt(xml, _("(domain_definition)"), &ctxt);
if (!xmldoc)
goto cleanup;
ninterfaces = virXPathNodeSet("./devices/interface", ctxt, &interfaces);
if (ninterfaces < 0)
goto cleanup;
vshPrint(ctl, "%-10s %-10s %-10s %-11s %s\n", _("Interface"), _("Type"),
_("Source"), _("Model"), _("MAC"));
vshPrint(ctl, "-------------------------------------------------------\n");
for (i = 0; i < ninterfaces; i++) {
char *type = NULL;
char *source = NULL;
char *target = NULL;
char *model = NULL;
char *mac = NULL;
ctxt->node = interfaces[i];
type = virXPathString("string(./@type)", ctxt);
source = virXPathString("string(./source/@bridge"
"|./source/@dev"
"|./source/@network"
"|./source/@name)", ctxt);
target = virXPathString("string(./target/@dev)", ctxt);
model = virXPathString("string(./model/@type)", ctxt);
mac = virXPathString("string(./mac/@address)", ctxt);
vshPrint(ctl, "%-10s %-10s %-10s %-11s %-10s\n",
target ? target : "-",
type,
source ? source : "-",
model ? model : "-",
mac ? mac : "-");
VIR_FREE(type);
VIR_FREE(source);
VIR_FREE(target);
VIR_FREE(model);
VIR_FREE(mac);
}
ret = true;
cleanup:
VIR_FREE(interfaces);
virDomainFree(dom);
VIR_FREE(xml);
xmlFreeDoc(xmldoc);
xmlXPathFreeContext(ctxt);
return ret;
}
/*
* "suspend" command
*/
static const vshCmdInfo info_suspend[] = {
{"help", N_("suspend a domain")},
{"desc", N_("Suspend a running domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_suspend[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdSuspend(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *name;
bool ret = true;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (virDomainSuspend(dom) == 0) {
vshPrint(ctl, _("Domain %s suspended\n"), name);
} else {
vshError(ctl, _("Failed to suspend domain %s"), name);
ret = false;
}
virDomainFree(dom);
return ret;
}
/*
* "dompmsuspend" command
*/
static const vshCmdInfo info_dom_pm_suspend[] = {
{"help", N_("suspend a domain gracefully using power management "
"functions")},
{"desc", N_("Suspends a running domain using guest OS's power management. "
"(Note: This requires a guest agent configured and running in "
"the guest OS).")},
{NULL, NULL}
};
static const vshCmdOptDef opts_dom_pm_suspend[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"duration", VSH_OT_INT, VSH_OFLAG_REQ_OPT, N_("duration in seconds")},
{"target", VSH_OT_STRING, VSH_OFLAG_REQ, N_("mem(Suspend-to-RAM), "
"disk(Suspend-to-Disk), "
"hybrid(Hybrid-Suspend)")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomPMSuspend(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *name;
bool ret = false;
const char *target = NULL;
unsigned int suspendTarget;
unsigned long long duration = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (vshCommandOptULongLong(cmd, "duration", &duration) < 0) {
vshError(ctl, _("Invalid duration argument"));
goto cleanup;
}
if (vshCommandOptString(cmd, "target", &target) < 0) {
vshError(ctl, _("Invalid target argument"));
goto cleanup;
}
if (STREQ(target, "mem"))
suspendTarget = VIR_NODE_SUSPEND_TARGET_MEM;
else if (STREQ(target, "disk"))
suspendTarget = VIR_NODE_SUSPEND_TARGET_DISK;
else if (STREQ(target, "hybrid"))
suspendTarget = VIR_NODE_SUSPEND_TARGET_HYBRID;
else {
vshError(ctl, "%s", _("Invalid target"));
goto cleanup;
}
if (virDomainPMSuspendForDuration(dom, suspendTarget, duration, 0) < 0) {
vshError(ctl, _("Domain %s could not be suspended"),
virDomainGetName(dom));
goto cleanup;
}
vshPrint(ctl, _("Domain %s successfully suspended"),
virDomainGetName(dom));
ret = true;
cleanup:
virDomainFree(dom);
return ret;
}
/*
* "dompmwakeup" command
*/
static const vshCmdInfo info_dom_pm_wakeup[] = {
{"help", N_("wakeup a domain suspended by dompmsuspend command")},
{"desc", N_("Wakeup a domain previously suspended "
"by dompmsuspend command.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_dom_pm_wakeup[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomPMWakeup(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *name;
bool ret = false;
unsigned int flags = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (virDomainPMWakeup(dom, flags) < 0) {
vshError(ctl, _("Domain %s could not be woken up"),
virDomainGetName(dom));
goto cleanup;
}
vshPrint(ctl, _("Domain %s successfully woken up"),
virDomainGetName(dom));
ret = true;
cleanup:
virDomainFree(dom);
return ret;
}
/*
* "create" command
*/
static const vshCmdInfo info_create[] = {
{"help", N_("create a domain from an XML file")},
{"desc", N_("Create a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_create[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing an XML domain description")},
#ifndef WIN32
{"console", VSH_OT_BOOL, 0, N_("attach to console after creation")},
#endif
{"paused", VSH_OT_BOOL, 0, N_("leave the guest paused after creation")},
{"autodestroy", VSH_OT_BOOL, 0, N_("automatically destroy the guest when virsh disconnects")},
{NULL, 0, 0, NULL}
};
static bool
cmdCreate(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *from = NULL;
bool ret = true;
char *buffer;
#ifndef WIN32
bool console = vshCommandOptBool(cmd, "console");
#endif
unsigned int flags = VIR_DOMAIN_NONE;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
if (vshCommandOptBool(cmd, "paused"))
flags |= VIR_DOMAIN_START_PAUSED;
if (vshCommandOptBool(cmd, "autodestroy"))
flags |= VIR_DOMAIN_START_AUTODESTROY;
dom = virDomainCreateXML(ctl->conn, buffer, flags);
VIR_FREE(buffer);
if (dom != NULL) {
vshPrint(ctl, _("Domain %s created from %s\n"),
virDomainGetName(dom), from);
#ifndef WIN32
if (console)
cmdRunConsole(ctl, dom, NULL, 0);
#endif
virDomainFree(dom);
} else {
vshError(ctl, _("Failed to create domain from %s"), from);
ret = false;
}
return ret;
}
/*
* "define" command
*/
static const vshCmdInfo info_define[] = {
{"help", N_("define (but don't start) a domain from an XML file")},
{"desc", N_("Define a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_define[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing an XML domain description")},
{NULL, 0, 0, NULL}
};
static bool
cmdDefine(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *from = NULL;
bool ret = true;
char *buffer;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
dom = virDomainDefineXML(ctl->conn, buffer);
VIR_FREE(buffer);
if (dom != NULL) {
vshPrint(ctl, _("Domain %s defined from %s\n"),
virDomainGetName(dom), from);
virDomainFree(dom);
} else {
vshError(ctl, _("Failed to define domain from %s"), from);
ret = false;
}
return ret;
}
/*
* "undefine" command
*/
static const vshCmdInfo info_undefine[] = {
{"help", N_("undefine a domain")},
{"desc",
N_("Undefine an inactive domain, or convert persistent to transient.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_undefine[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name or uuid")},
{"managed-save", VSH_OT_BOOL, 0, N_("remove domain managed state file")},
{"storage", VSH_OT_DATA, VSH_OFLAG_NONE,
N_("remove associated storage volumes (comma separated list of targets "
"or source paths) (see domblklist)")},
{"remove-all-storage", VSH_OT_BOOL, 0,
N_("remove all associated storage volumes (use with caution)")},
{"wipe-storage", VSH_OT_BOOL, VSH_OFLAG_NONE,
N_("wipe data on the removed volumes")},
{"snapshots-metadata", VSH_OT_BOOL, 0,
N_("remove all domain snapshot metadata, if inactive")},
{NULL, 0, 0, NULL}
};
static bool
cmdUndefine(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = false;
const char *name = NULL;
/* Flags to attempt. */
unsigned int flags = 0;
/* User-requested actions. */
bool managed_save = vshCommandOptBool(cmd, "managed-save");
bool snapshots_metadata = vshCommandOptBool(cmd, "snapshots-metadata");
bool wipe_storage = vshCommandOptBool(cmd, "wipe-storage");
bool remove_storage = false;
bool remove_all_storage = vshCommandOptBool(cmd, "remove-all-storage");
/* Positive if these items exist. */
int has_managed_save = 0;
int has_snapshots_metadata = 0;
int has_snapshots = 0;
/* True if undefine will not strand data, even on older servers. */
bool managed_save_safe = false;
bool snapshots_safe = false;
int rc = -1;
int running;
/* list of volumes to remove along with this domain */
const char *volumes_arg = NULL;
char *volumes = NULL;
char **volume_tokens = NULL;
char *volume_tok = NULL;
int nvolume_tokens = 0;
char *def = NULL;
char *source = NULL;
char *target = NULL;
int vol_i;
int tok_i;
xmlDocPtr doc = NULL;
xmlXPathContextPtr ctxt = NULL;
xmlNodePtr *vol_nodes = NULL;
int nvolumes = 0;
virStorageVolPtr vol = NULL;
bool vol_del_failed = false;
if (managed_save) {
flags |= VIR_DOMAIN_UNDEFINE_MANAGED_SAVE;
managed_save_safe = true;
}
if (snapshots_metadata) {
flags |= VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA;
snapshots_safe = true;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
/* check if a string that should contain list of volumes to remove is present */
if (vshCommandOptString(cmd, "storage", &volumes_arg) > 0) {
volumes = vshStrdup(ctl, volumes_arg);
if (remove_all_storage) {
vshError(ctl, _("Specified both --storage and --remove-all-storage"));
goto cleanup;
}
remove_storage = true;
}
/* Do some flag manipulation. The goal here is to disable bits
* from flags to reduce the likelihood of a server rejecting
* unknown flag bits, as well as to track conditions which are
* safe by default for the given hypervisor and server version. */
running = virDomainIsActive(dom);
if (running < 0) {
virshReportError(ctl);
goto cleanup;
}
if (!running) {
/* Undefine with snapshots only fails for inactive domains,
* and managed save only exists on inactive domains; if
* running, then we don't want to remove anything. */
has_managed_save = virDomainHasManagedSaveImage(dom, 0);
if (has_managed_save < 0) {
if (last_error->code != VIR_ERR_NO_SUPPORT) {
virshReportError(ctl);
goto cleanup;
}
virFreeError(last_error);
last_error = NULL;
has_managed_save = 0;
}
has_snapshots = virDomainSnapshotNum(dom, 0);
if (has_snapshots < 0) {
if (last_error->code != VIR_ERR_NO_SUPPORT) {
virshReportError(ctl);
goto cleanup;
}
virFreeError(last_error);
last_error = NULL;
has_snapshots = 0;
}
if (has_snapshots) {
has_snapshots_metadata
= virDomainSnapshotNum(dom, VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
if (has_snapshots_metadata < 0) {
/* The server did not know the new flag, assume that all
snapshots have metadata. */
virFreeError(last_error);
last_error = NULL;
has_snapshots_metadata = has_snapshots;
} else {
/* The server knew the new flag, all aspects of
* undefineFlags are safe. */
managed_save_safe = snapshots_safe = true;
}
}
}
if (!has_managed_save) {
flags &= ~VIR_DOMAIN_UNDEFINE_MANAGED_SAVE;
managed_save_safe = true;
}
if (has_snapshots == 0) {
snapshots_safe = true;
}
if (has_snapshots_metadata == 0) {
flags &= ~VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA;
snapshots_safe = true;
}
/* Stash domain description for later use */
if (remove_storage || remove_all_storage) {
if (running) {
vshError(ctl, _("Storage volume deletion is supported only on stopped domains"));
goto cleanup;
}
if (!(def = virDomainGetXMLDesc(dom, 0))) {
vshError(ctl, _("Could not retrieve domain XML description"));
goto cleanup;
}
}
/* Generally we want to try the new API first. However, while
* virDomainUndefineFlags was introduced at the same time as
* VIR_DOMAIN_UNDEFINE_MANAGED_SAVE in 0.9.4, the
* VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA flag was not present
* until 0.9.5; skip to piecewise emulation if we couldn't prove
* above that the new API is safe. */
if (managed_save_safe && snapshots_safe) {
rc = virDomainUndefineFlags(dom, flags);
if (rc == 0 || (last_error->code != VIR_ERR_NO_SUPPORT &&
last_error->code != VIR_ERR_INVALID_ARG))
goto out;
virFreeError(last_error);
last_error = NULL;
}
/* The new API is unsupported or unsafe; fall back to doing things
* piecewise. */
if (has_managed_save) {
if (!managed_save) {
vshError(ctl, "%s",
_("Refusing to undefine while domain managed save "
"image exists"));
goto cleanup;
}
if (virDomainManagedSaveRemove(dom, 0) < 0) {
virshReportError(ctl);
goto cleanup;
}
}
/* No way to emulate deletion of just snapshot metadata
* without support for the newer flags. Oh well. */
if (has_snapshots_metadata) {
vshError(ctl,
snapshots_metadata ?
_("Unable to remove metadata of %d snapshots") :
_("Refusing to undefine while %d snapshots exist"),
has_snapshots_metadata);
goto cleanup;
}
rc = virDomainUndefine(dom);
out:
if (rc == 0) {
vshPrint(ctl, _("Domain %s has been undefined\n"), name);
ret = true;
} else {
vshError(ctl, _("Failed to undefine domain %s"), name);
goto cleanup;
}
/* try to undefine storage volumes associated with this domain, if it's requested */
if (remove_storage || remove_all_storage) {
ret = false;
/* tokenize the string from user and save it's parts into an array */
if (volumes) {
/* count the delimiters */
volume_tok = volumes;
nvolume_tokens = 1; /* we need at least one member */
while (*volume_tok) {
if (*volume_tok == ',')
nvolume_tokens++;
volume_tok++;
}
volume_tokens = vshCalloc(ctl, nvolume_tokens, sizeof(char *));
/* tokenize the input string */
nvolume_tokens = 0;
volume_tok = volumes;
do {
volume_tokens[nvolume_tokens] = strsep(&volume_tok, ",");
nvolume_tokens++;
} while (volume_tok);
}
doc = virXMLParseStringCtxt(def, _("(domain_definition)"), &ctxt);
if (!doc)
goto cleanup;
nvolumes = virXPathNodeSet("./devices/disk", ctxt, &vol_nodes);
if (nvolumes < 0)
goto cleanup;
for (vol_i = 0; vol_i < nvolumes; vol_i++) {
ctxt->node = vol_nodes[vol_i];
VIR_FREE(target);
VIR_FREE(source);
if (vol) {
virStorageVolFree(vol);
vol = NULL;
}
/* get volume source and target paths */
if (!(target = virXPathString("string(./target/@dev)", ctxt))) {
vshError(ctl, _("Failed to enumerate devices"));
goto cleanup;
}
if (!(source = virXPathString("string("
"./source/@file|"
"./source/@dir|"
"./source/@name|"
"./source/@dev)", ctxt)) &&
virGetLastError())
goto cleanup;
/* lookup if volume was selected by user */
if (volumes) {
volume_tok = NULL;
for (tok_i = 0; tok_i < nvolume_tokens; tok_i++) {
if (volume_tokens[tok_i] &&
(STREQ_NULLABLE(volume_tokens[tok_i], target) ||
STREQ_NULLABLE(volume_tokens[tok_i], source))) {
volume_tok = volume_tokens[tok_i];
volume_tokens[tok_i] = NULL;
break;
}
}
if (!volume_tok)
continue;
}
if (!source)
continue;
if (!(vol = virStorageVolLookupByPath(ctl->conn, source))) {
vshPrint(ctl,
_("Storage volume '%s'(%s) is not managed by libvirt. "
"Remove it manually.\n"), target, source);
virResetLastError();
continue;
}
if (wipe_storage) {
vshPrint(ctl, _("Wiping volume '%s'(%s) ... "), target, source);
fflush(stdout);
if (virStorageVolWipe(vol, 0) < 0) {
vshError(ctl, _("Failed! Volume not removed."));
vol_del_failed = true;
continue;
} else {
vshPrint(ctl, _("Done.\n"));
}
}
/* delete the volume */
if (virStorageVolDelete(vol, 0) < 0) {
vshError(ctl, _("Failed to remove storage volume '%s'(%s)"),
target, source);
vol_del_failed = true;
}
vshPrint(ctl, _("Volume '%s' removed.\n"), volume_tok?volume_tok:source);
}
/* print volumes specified by user that were not found in domain definition */
if (volumes) {
for (tok_i = 0; tok_i < nvolume_tokens; tok_i++) {
if (volume_tokens[tok_i])
vshPrint(ctl, _("Volume '%s' was not found in domain's "
"definition.\n"),
volume_tokens[tok_i]);
}
}
if (!vol_del_failed)
ret = true;
}
cleanup:
VIR_FREE(source);
VIR_FREE(target);
VIR_FREE(volumes);
VIR_FREE(volume_tokens);
VIR_FREE(def);
VIR_FREE(vol_nodes);
if (vol)
virStorageVolFree(vol);
xmlFreeDoc(doc);
xmlXPathFreeContext(ctxt);
virDomainFree(dom);
return ret;
}
/*
* "start" command
*/
static const vshCmdInfo info_start[] = {
{"help", N_("start a (previously defined) inactive domain")},
{"desc", N_("Start a domain, either from the last managedsave\n"
" state, or via a fresh boot if no managedsave state\n"
" is present.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_start[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("name of the inactive domain")},
#ifndef WIN32
{"console", VSH_OT_BOOL, 0, N_("attach to console after creation")},
#endif
{"paused", VSH_OT_BOOL, 0, N_("leave the guest paused after creation")},
{"autodestroy", VSH_OT_BOOL, 0,
N_("automatically destroy the guest when virsh disconnects")},
{"bypass-cache", VSH_OT_BOOL, 0,
N_("avoid file system cache when loading")},
{"force-boot", VSH_OT_BOOL, 0,
N_("force fresh boot by discarding any managed save")},
{NULL, 0, 0, NULL}
};
static bool
cmdStart(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = false;
#ifndef WIN32
bool console = vshCommandOptBool(cmd, "console");
#endif
unsigned int flags = VIR_DOMAIN_NONE;
int rc;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomainBy(ctl, cmd, NULL,
VSH_BYNAME | VSH_BYUUID)))
return false;
if (virDomainGetID(dom) != (unsigned int)-1) {
vshError(ctl, "%s", _("Domain is already active"));
virDomainFree(dom);
return false;
}
if (vshCommandOptBool(cmd, "paused"))
flags |= VIR_DOMAIN_START_PAUSED;
if (vshCommandOptBool(cmd, "autodestroy"))
flags |= VIR_DOMAIN_START_AUTODESTROY;
if (vshCommandOptBool(cmd, "bypass-cache"))
flags |= VIR_DOMAIN_START_BYPASS_CACHE;
if (vshCommandOptBool(cmd, "force-boot"))
flags |= VIR_DOMAIN_START_FORCE_BOOT;
/* We can emulate force boot, even for older servers that reject it. */
if (flags & VIR_DOMAIN_START_FORCE_BOOT) {
if (virDomainCreateWithFlags(dom, flags) == 0)
goto started;
if (last_error->code != VIR_ERR_NO_SUPPORT &&
last_error->code != VIR_ERR_INVALID_ARG) {
virshReportError(ctl);
goto cleanup;
}
virFreeError(last_error);
last_error = NULL;
rc = virDomainHasManagedSaveImage(dom, 0);
if (rc < 0) {
/* No managed save image to remove */
virFreeError(last_error);
last_error = NULL;
} else if (rc > 0) {
if (virDomainManagedSaveRemove(dom, 0) < 0) {
virshReportError(ctl);
goto cleanup;
}
}
flags &= ~VIR_DOMAIN_START_FORCE_BOOT;
}
/* Prefer older API unless we have to pass a flag. */
if ((flags ? virDomainCreateWithFlags(dom, flags)
: virDomainCreate(dom)) < 0) {
vshError(ctl, _("Failed to start domain %s"), virDomainGetName(dom));
goto cleanup;
}
started:
vshPrint(ctl, _("Domain %s started\n"),
virDomainGetName(dom));
#ifndef WIN32
if (console && !cmdRunConsole(ctl, dom, NULL, 0))
goto cleanup;
#endif
ret = true;
cleanup:
virDomainFree(dom);
return ret;
}
/*
* "save" command
*/
static const vshCmdInfo info_save[] = {
{"help", N_("save a domain state to a file")},
{"desc", N_("Save the RAM state of a running domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_save[] = {
{"bypass-cache", VSH_OT_BOOL, 0, N_("avoid file system cache when saving")},
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("where to save the data")},
{"xml", VSH_OT_STRING, 0,
N_("filename containing updated XML for the target")},
{"running", VSH_OT_BOOL, 0, N_("set domain to be running on restore")},
{"paused", VSH_OT_BOOL, 0, N_("set domain to be paused on restore")},
{"verbose", VSH_OT_BOOL, 0, N_("display the progress of save")},
{NULL, 0, 0, NULL}
};
static void
doSave(void *opaque)
{
vshCtrlData *data = opaque;
vshControl *ctl = data->ctl;
const vshCmd *cmd = data->cmd;
char ret = '1';
virDomainPtr dom = NULL;
const char *name = NULL;
const char *to = NULL;
unsigned int flags = 0;
const char *xmlfile = NULL;
char *xml = NULL;
sigset_t sigmask, oldsigmask;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGINT);
if (pthread_sigmask(SIG_BLOCK, &sigmask, &oldsigmask) < 0)
goto out_sig;
if (!vshConnectionUsability(ctl, ctl->conn))
goto out;
if (vshCommandOptString(cmd, "file", &to) <= 0)
goto out;
if (vshCommandOptBool(cmd, "bypass-cache"))
flags |= VIR_DOMAIN_SAVE_BYPASS_CACHE;
if (vshCommandOptBool(cmd, "running"))
flags |= VIR_DOMAIN_SAVE_RUNNING;
if (vshCommandOptBool(cmd, "paused"))
flags |= VIR_DOMAIN_SAVE_PAUSED;
if (vshCommandOptString(cmd, "xml", &xmlfile) < 0) {
vshError(ctl, "%s", _("malformed xml argument"));
goto out;
}
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
goto out;
if (xmlfile &&
virFileReadAll(xmlfile, 8192, &xml) < 0)
goto out;
if (((flags || xml)
? virDomainSaveFlags(dom, to, xml, flags)
: virDomainSave(dom, to)) < 0) {
vshError(ctl, _("Failed to save domain %s to %s"), name, to);
goto out;
}
ret = '0';
out:
pthread_sigmask(SIG_SETMASK, &oldsigmask, NULL);
out_sig:
if (dom) virDomainFree(dom);
VIR_FREE(xml);
ignore_value(safewrite(data->writefd, &ret, sizeof(ret)));
}
static bool
cmdSave(vshControl *ctl, const vshCmd *cmd)
{
bool ret = false;
virDomainPtr dom = NULL;
int p[2] = {-1. -1};
virThread workerThread;
bool verbose = false;
vshCtrlData data;
const char *to = NULL;
const char *name = NULL;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (vshCommandOptString(cmd, "file", &to) <= 0)
goto cleanup;
if (vshCommandOptBool(cmd, "verbose"))
verbose = true;
if (pipe(p) < 0)
goto cleanup;
data.ctl = ctl;
data.cmd = cmd;
data.writefd = p[1];
if (virThreadCreate(&workerThread,
true,
doSave,
&data) < 0)
goto cleanup;
ret = vshWatchJob(ctl, dom, verbose, p[0], 0, NULL, NULL, _("Save"));
virThreadJoin(&workerThread);
if (ret)
vshPrint(ctl, _("\nDomain %s saved to %s\n"), name, to);
cleanup:
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "save-image-dumpxml" command
*/
static const vshCmdInfo info_save_image_dumpxml[] = {
{"help", N_("saved state domain information in XML")},
{"desc", N_("Output the domain information for a saved state file,\n"
"as an XML dump to stdout.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_save_image_dumpxml[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("saved state file to read")},
{"security-info", VSH_OT_BOOL, 0, N_("include security sensitive information in XML dump")},
{NULL, 0, 0, NULL}
};
static bool
cmdSaveImageDumpxml(vshControl *ctl, const vshCmd *cmd)
{
const char *file = NULL;
bool ret = false;
unsigned int flags = 0;
char *xml = NULL;
if (vshCommandOptBool(cmd, "security-info"))
flags |= VIR_DOMAIN_XML_SECURE;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &file) <= 0)
return false;
xml = virDomainSaveImageGetXMLDesc(ctl->conn, file, flags);
if (!xml)
goto cleanup;
vshPrint(ctl, "%s", xml);
ret = true;
cleanup:
VIR_FREE(xml);
return ret;
}
/*
* "save-image-define" command
*/
static const vshCmdInfo info_save_image_define[] = {
{"help", N_("redefine the XML for a domain's saved state file")},
{"desc", N_("Replace the domain XML associated with a saved state file")},
{NULL, NULL}
};
static const vshCmdOptDef opts_save_image_define[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("saved state file to modify")},
{"xml", VSH_OT_STRING, VSH_OFLAG_REQ,
N_("filename containing updated XML for the target")},
{"running", VSH_OT_BOOL, 0, N_("set domain to be running on restore")},
{"paused", VSH_OT_BOOL, 0, N_("set domain to be paused on restore")},
{NULL, 0, 0, NULL}
};
static bool
cmdSaveImageDefine(vshControl *ctl, const vshCmd *cmd)
{
const char *file = NULL;
bool ret = false;
const char *xmlfile = NULL;
char *xml = NULL;
unsigned int flags = 0;
if (vshCommandOptBool(cmd, "running"))
flags |= VIR_DOMAIN_SAVE_RUNNING;
if (vshCommandOptBool(cmd, "paused"))
flags |= VIR_DOMAIN_SAVE_PAUSED;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &file) <= 0)
return false;
if (vshCommandOptString(cmd, "xml", &xmlfile) <= 0) {
vshError(ctl, "%s", _("malformed or missing xml argument"));
return false;
}
if (virFileReadAll(xmlfile, 8192, &xml) < 0)
goto cleanup;
if (virDomainSaveImageDefineXML(ctl->conn, file, xml, flags) < 0) {
vshError(ctl, _("Failed to update %s"), file);
goto cleanup;
}
vshPrint(ctl, _("State file %s updated.\n"), file);
ret = true;
cleanup:
VIR_FREE(xml);
return ret;
}
/*
* "save-image-edit" command
*/
static const vshCmdInfo info_save_image_edit[] = {
{"help", N_("edit XML for a domain's saved state file")},
{"desc", N_("Edit the domain XML associated with a saved state file")},
{NULL, NULL}
};
static const vshCmdOptDef opts_save_image_edit[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("saved state file to edit")},
{"running", VSH_OT_BOOL, 0, N_("set domain to be running on restore")},
{"paused", VSH_OT_BOOL, 0, N_("set domain to be paused on restore")},
{NULL, 0, 0, NULL}
};
static bool
cmdSaveImageEdit(vshControl *ctl, const vshCmd *cmd)
{
const char *file = NULL;
bool ret = false;
unsigned int getxml_flags = VIR_DOMAIN_XML_SECURE;
unsigned int define_flags = 0;
if (vshCommandOptBool(cmd, "running"))
define_flags |= VIR_DOMAIN_SAVE_RUNNING;
if (vshCommandOptBool(cmd, "paused"))
define_flags |= VIR_DOMAIN_SAVE_PAUSED;
/* Normally, we let the API reject mutually exclusive flags.
* However, in the edit cycle, we let the user retry if the define
* step fails, but the define step will always fail on invalid
* flags, so we reject it up front to avoid looping. */
if (define_flags == (VIR_DOMAIN_SAVE_RUNNING | VIR_DOMAIN_SAVE_PAUSED)) {
vshError(ctl, "%s", _("--running and --saved are mutually exclusive"));
return false;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &file) <= 0)
return false;
#define EDIT_GET_XML \
virDomainSaveImageGetXMLDesc(ctl->conn, file, getxml_flags)
#define EDIT_NOT_CHANGED \
vshPrint(ctl, _("Saved image %s XML configuration " \
"not changed.\n"), file); \
ret = true; goto edit_cleanup;
#define EDIT_DEFINE \
virDomainSaveImageDefineXML(ctl->conn, file, doc_edited, define_flags)
#define EDIT_FREE /* */
#include "virsh-edit.c"
vshPrint(ctl, _("State file %s edited.\n"), file);
ret = true;
cleanup:
return ret;
}
/*
* "managedsave" command
*/
static const vshCmdInfo info_managedsave[] = {
{"help", N_("managed save of a domain state")},
{"desc", N_("Save and destroy a running domain, so it can be restarted from\n"
" the same state at a later time. When the virsh 'start'\n"
" command is next run for the domain, it will automatically\n"
" be started from this saved state.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_managedsave[] = {
{"bypass-cache", VSH_OT_BOOL, 0, N_("avoid file system cache when saving")},
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"running", VSH_OT_BOOL, 0, N_("set domain to be running on next start")},
{"paused", VSH_OT_BOOL, 0, N_("set domain to be paused on next start")},
{"verbose", VSH_OT_BOOL, 0, N_("display the progress of save")},
{NULL, 0, 0, NULL}
};
static void
doManagedsave(void *opaque)
{
char ret = '1';
vshCtrlData *data = opaque;
vshControl *ctl = data->ctl;
const vshCmd *cmd = data->cmd;
virDomainPtr dom = NULL;
const char *name;
unsigned int flags = 0;
sigset_t sigmask, oldsigmask;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGINT);
if (pthread_sigmask(SIG_BLOCK, &sigmask, &oldsigmask) < 0)
goto out_sig;
if (!vshConnectionUsability(ctl, ctl->conn))
goto out;
if (vshCommandOptBool(cmd, "bypass-cache"))
flags |= VIR_DOMAIN_SAVE_BYPASS_CACHE;
if (vshCommandOptBool(cmd, "running"))
flags |= VIR_DOMAIN_SAVE_RUNNING;
if (vshCommandOptBool(cmd, "paused"))
flags |= VIR_DOMAIN_SAVE_PAUSED;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
goto out;
if (virDomainManagedSave(dom, flags) < 0) {
vshError(ctl, _("Failed to save domain %s state"), name);
goto out;
}
ret = '0';
out:
pthread_sigmask(SIG_SETMASK, &oldsigmask, NULL);
out_sig:
if (dom)
virDomainFree(dom);
ignore_value(safewrite(data->writefd, &ret, sizeof(ret)));
}
static bool
cmdManagedSave(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
int p[2] = { -1, -1};
bool ret = false;
bool verbose = false;
const char *name = NULL;
vshCtrlData data;
virThread workerThread;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (vshCommandOptBool(cmd, "verbose"))
verbose = true;
if (pipe(p) < 0)
goto cleanup;
data.ctl = ctl;
data.cmd = cmd;
data.writefd = p[1];
if (virThreadCreate(&workerThread,
true,
doManagedsave,
&data) < 0)
goto cleanup;
ret = vshWatchJob(ctl, dom, verbose, p[0], 0,
NULL, NULL, _("Managedsave"));
virThreadJoin(&workerThread);
if (ret)
vshPrint(ctl, _("\nDomain %s state saved by libvirt\n"), name);
cleanup:
virDomainFree(dom);
VIR_FORCE_CLOSE(p[0]);
VIR_FORCE_CLOSE(p[1]);
return ret;
}
/*
* "managedsave-remove" command
*/
static const vshCmdInfo info_managedsaveremove[] = {
{"help", N_("Remove managed save of a domain")},
{"desc", N_("Remove an existing managed save state file from a domain")},
{NULL, NULL}
};
static const vshCmdOptDef opts_managedsaveremove[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdManagedSaveRemove(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *name;
bool ret = false;
int hassave;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
hassave = virDomainHasManagedSaveImage(dom, 0);
if (hassave < 0) {
vshError(ctl, "%s", _("Failed to check for domain managed save image"));
goto cleanup;
}
if (hassave) {
if (virDomainManagedSaveRemove(dom, 0) < 0) {
vshError(ctl, _("Failed to remove managed save image for domain %s"),
name);
goto cleanup;
}
else
vshPrint(ctl, _("Removed managedsave image for domain %s"), name);
}
else
vshPrint(ctl, _("Domain %s has no manage save image; removal skipped"),
name);
ret = true;
cleanup:
virDomainFree(dom);
return ret;
}
/*
* "schedinfo" command
*/
static const vshCmdInfo info_schedinfo[] = {
{"help", N_("show/set scheduler parameters")},
{"desc", N_("Show/Set scheduler parameters.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_schedinfo[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"set", VSH_OT_STRING, VSH_OFLAG_NONE, N_("parameter=value")},
{"weight", VSH_OT_INT, VSH_OFLAG_NONE, N_("weight for XEN_CREDIT")},
{"cap", VSH_OT_INT, VSH_OFLAG_NONE, N_("cap for XEN_CREDIT")},
{"current", VSH_OT_BOOL, 0, N_("get/set current scheduler info")},
{"config", VSH_OT_BOOL, 0, N_("get/set value to be used on next boot")},
{"live", VSH_OT_BOOL, 0, N_("get/set value from running domain")},
{NULL, 0, 0, NULL}
};
static int
cmdSchedInfoUpdate(vshControl *ctl, const vshCmd *cmd,
virTypedParameterPtr param)
{
const char *data = NULL;
/* Legacy 'weight' parameter */
if (STREQ(param->field, "weight") &&
param->type == VIR_TYPED_PARAM_UINT &&
vshCommandOptBool(cmd, "weight")) {
int val;
if (vshCommandOptInt(cmd, "weight", &val) <= 0) {
vshError(ctl, "%s", _("Invalid value of weight"));
return -1;
} else {
param->value.ui = val;
}
return 1;
}
/* Legacy 'cap' parameter */
if (STREQ(param->field, "cap") &&
param->type == VIR_TYPED_PARAM_UINT &&
vshCommandOptBool(cmd, "cap")) {
int val;
if (vshCommandOptInt(cmd, "cap", &val) <= 0) {
vshError(ctl, "%s", _("Invalid value of cap"));
return -1;
} else {
param->value.ui = val;
}
return 1;
}
if (vshCommandOptString(cmd, "set", &data) > 0) {
char *val = strchr(data, '=');
int match = 0;
if (!val) {
vshError(ctl, "%s", _("Invalid syntax for --set, expecting name=value"));
return -1;
}
*val = '\0';
match = STREQ(data, param->field);
*val = '=';
val++;
if (!match)
return 0;
switch (param->type) {
case VIR_TYPED_PARAM_INT:
if (virStrToLong_i(val, NULL, 10, &param->value.i) < 0) {
vshError(ctl, "%s",
_("Invalid value for parameter, expecting an int"));
return -1;
}
break;
case VIR_TYPED_PARAM_UINT:
if (virStrToLong_ui(val, NULL, 10, &param->value.ui) < 0) {
vshError(ctl, "%s",
_("Invalid value for parameter, expecting an unsigned int"));
return -1;
}
break;
case VIR_TYPED_PARAM_LLONG:
if (virStrToLong_ll(val, NULL, 10, &param->value.l) < 0) {
vshError(ctl, "%s",
_("Invalid value for parameter, expecting a long long"));
return -1;
}
break;
case VIR_TYPED_PARAM_ULLONG:
if (virStrToLong_ull(val, NULL, 10, &param->value.ul) < 0) {
vshError(ctl, "%s",
_("Invalid value for parameter, expecting an unsigned long long"));
return -1;
}
break;
case VIR_TYPED_PARAM_DOUBLE:
if (virStrToDouble(val, NULL, &param->value.d) < 0) {
vshError(ctl, "%s", _("Invalid value for parameter, expecting a double"));
return -1;
}
break;
case VIR_TYPED_PARAM_BOOLEAN:
param->value.b = STREQ(val, "0") ? 0 : 1;
}
return 1;
}
return 0;
}
static bool
cmdSchedinfo(vshControl *ctl, const vshCmd *cmd)
{
char *schedulertype;
virDomainPtr dom;
virTypedParameterPtr params = NULL;
int nparams = 0;
int update = 0;
int i, ret;
bool ret_val = false;
unsigned int flags = 0;
bool current = vshCommandOptBool(cmd, "current");
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
/* Print SchedulerType */
schedulertype = virDomainGetSchedulerType(dom, &nparams);
if (schedulertype != NULL) {
vshPrint(ctl, "%-15s: %s\n", _("Scheduler"),
schedulertype);
VIR_FREE(schedulertype);
} else {
vshPrint(ctl, "%-15s: %s\n", _("Scheduler"), _("Unknown"));
goto cleanup;
}
if (nparams) {
params = vshMalloc(ctl, sizeof(*params) * nparams);
memset(params, 0, sizeof(*params) * nparams);
if (flags || current) {
/* We cannot query both live and config at once, so settle
on current in that case. If we are setting, then the
two values should match when we re-query; otherwise, we
report the error later. */
ret = virDomainGetSchedulerParametersFlags(dom, params, &nparams,
((live && config) ? 0
: flags));
} else {
ret = virDomainGetSchedulerParameters(dom, params, &nparams);
}
if (ret == -1)
goto cleanup;
/* See if any params are being set */
for (i = 0; i < nparams; i++) {
ret = cmdSchedInfoUpdate(ctl, cmd, &(params[i]));
if (ret == -1)
goto cleanup;
if (ret == 1)
update = 1;
}
/* Update parameters & refresh data */
if (update) {
if (flags || current)
ret = virDomainSetSchedulerParametersFlags(dom, params,
nparams, flags);
else
ret = virDomainSetSchedulerParameters(dom, params, nparams);
if (ret == -1)
goto cleanup;
if (flags || current)
ret = virDomainGetSchedulerParametersFlags(dom, params,
&nparams,
((live && config) ? 0
: flags));
else
ret = virDomainGetSchedulerParameters(dom, params, &nparams);
if (ret == -1)
goto cleanup;
} else {
/* See if we've tried to --set var=val. If so, the fact that
we reach this point (with update == 0) means that "var" did
not match any of the settable parameters. Report the error. */
const char *var_value_pair = NULL;
if (vshCommandOptString(cmd, "set", &var_value_pair) > 0) {
vshError(ctl, _("invalid scheduler option: %s"),
var_value_pair);
goto cleanup;
}
/* When not doing --set, --live and --config do not mix. */
if (live && config) {
vshError(ctl, "%s",
_("cannot query both live and config at once"));
goto cleanup;
}
}
ret_val = true;
for (i = 0; i < nparams; i++) {
char *str = vshGetTypedParamValue(ctl, &params[i]);
vshPrint(ctl, "%-15s: %s\n", params[i].field, str);
VIR_FREE(str);
}
}
cleanup:
VIR_FREE(params);
virDomainFree(dom);
return ret_val;
}
/*
* "restore" command
*/
static const vshCmdInfo info_restore[] = {
{"help", N_("restore a domain from a saved state in a file")},
{"desc", N_("Restore a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_restore[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("the state to restore")},
{"bypass-cache", VSH_OT_BOOL, 0,
N_("avoid file system cache when restoring")},
{"xml", VSH_OT_STRING, 0,
N_("filename containing updated XML for the target")},
{"running", VSH_OT_BOOL, 0, N_("restore domain into running state")},
{"paused", VSH_OT_BOOL, 0, N_("restore domain into paused state")},
{NULL, 0, 0, NULL}
};
static bool
cmdRestore(vshControl *ctl, const vshCmd *cmd)
{
const char *from = NULL;
bool ret = false;
unsigned int flags = 0;
const char *xmlfile = NULL;
char *xml = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (vshCommandOptBool(cmd, "bypass-cache"))
flags |= VIR_DOMAIN_SAVE_BYPASS_CACHE;
if (vshCommandOptBool(cmd, "running"))
flags |= VIR_DOMAIN_SAVE_RUNNING;
if (vshCommandOptBool(cmd, "paused"))
flags |= VIR_DOMAIN_SAVE_PAUSED;
if (vshCommandOptString(cmd, "xml", &xmlfile) < 0) {
vshError(ctl, "%s", _("malformed xml argument"));
return false;
}
if (xmlfile &&
virFileReadAll(xmlfile, 8192, &xml) < 0)
goto cleanup;
if (((flags || xml)
? virDomainRestoreFlags(ctl->conn, from, xml, flags)
: virDomainRestore(ctl->conn, from)) < 0) {
vshError(ctl, _("Failed to restore domain from %s"), from);
goto cleanup;
}
vshPrint(ctl, _("Domain restored from %s\n"), from);
ret = true;
cleanup:
VIR_FREE(xml);
return ret;
}
/*
* "dump" command
*/
static const vshCmdInfo info_dump[] = {
{"help", N_("dump the core of a domain to a file for analysis")},
{"desc", N_("Core dump a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_dump[] = {
{"live", VSH_OT_BOOL, 0, N_("perform a live core dump if supported")},
{"crash", VSH_OT_BOOL, 0, N_("crash the domain after core dump")},
{"bypass-cache", VSH_OT_BOOL, 0,
N_("avoid file system cache when saving")},
{"reset", VSH_OT_BOOL, 0, N_("reset the domain after core dump")},
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("where to dump the core")},
{"verbose", VSH_OT_BOOL, 0, N_("display the progress of dump")},
{"memory-only", VSH_OT_BOOL, 0, N_("dump domain's memory only")},
{NULL, 0, 0, NULL}
};
static void
doDump(void *opaque)
{
char ret = '1';
vshCtrlData *data = opaque;
vshControl *ctl = data->ctl;
const vshCmd *cmd = data->cmd;
virDomainPtr dom = NULL;
sigset_t sigmask, oldsigmask;
const char *name = NULL;
const char *to = NULL;
unsigned int flags = 0;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGINT);
if (pthread_sigmask(SIG_BLOCK, &sigmask, &oldsigmask) < 0)
goto out_sig;
if (!vshConnectionUsability(ctl, ctl->conn))
goto out;
if (vshCommandOptString(cmd, "file", &to) <= 0)
goto out;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
goto out;
if (vshCommandOptBool(cmd, "live"))
flags |= VIR_DUMP_LIVE;
if (vshCommandOptBool(cmd, "crash"))
flags |= VIR_DUMP_CRASH;
if (vshCommandOptBool(cmd, "bypass-cache"))
flags |= VIR_DUMP_BYPASS_CACHE;
if (vshCommandOptBool(cmd, "reset"))
flags |= VIR_DUMP_RESET;
if (vshCommandOptBool(cmd, "memory-only"))
flags |= VIR_DUMP_MEMORY_ONLY;
if (virDomainCoreDump(dom, to, flags) < 0) {
vshError(ctl, _("Failed to core dump domain %s to %s"), name, to);
goto out;
}
ret = '0';
out:
pthread_sigmask(SIG_SETMASK, &oldsigmask, NULL);
out_sig:
if (dom)
virDomainFree(dom);
ignore_value(safewrite(data->writefd, &ret, sizeof(ret)));
}
static bool
cmdDump(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
int p[2] = { -1, -1};
bool ret = false;
bool verbose = false;
const char *name = NULL;
const char *to = NULL;
vshCtrlData data;
virThread workerThread;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (vshCommandOptString(cmd, "file", &to) <= 0)
return false;
if (vshCommandOptBool(cmd, "verbose"))
verbose = true;
if (pipe(p) < 0)
goto cleanup;
data.ctl = ctl;
data.cmd = cmd;
data.writefd = p[1];
if (virThreadCreate(&workerThread,
true,
doDump,
&data) < 0)
goto cleanup;
ret = vshWatchJob(ctl, dom, verbose, p[0], 0, NULL, NULL, _("Dump"));
virThreadJoin(&workerThread);
if (ret)
vshPrint(ctl, _("\nDomain %s dumped to %s\n"), name, to);
cleanup:
virDomainFree(dom);
VIR_FORCE_CLOSE(p[0]);
VIR_FORCE_CLOSE(p[1]);
return ret;
}
static const vshCmdInfo info_screenshot[] = {
{"help", N_("take a screenshot of a current domain console and store it "
"into a file")},
{"desc", N_("screenshot of a current domain console")},
{NULL, NULL}
};
static const vshCmdOptDef opts_screenshot[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"file", VSH_OT_DATA, VSH_OFLAG_NONE, N_("where to store the screenshot")},
{"screen", VSH_OT_INT, VSH_OFLAG_NONE, N_("ID of a screen to take screenshot of")},
{NULL, 0, 0, NULL}
};
static int vshStreamSink(virStreamPtr st ATTRIBUTE_UNUSED,
const char *bytes, size_t nbytes, void *opaque)
{
int *fd = opaque;
return safewrite(*fd, bytes, nbytes);
}
/**
* Generate string: '<domain name>-<timestamp>[<extension>]'
*/
static char *
vshGenFileName(vshControl *ctl, virDomainPtr dom, const char *mime)
{
char timestr[100];
struct timeval cur_time;
struct tm time_info;
const char *ext = NULL;
char *ret = NULL;
/* We should be already connected, but doesn't
* hurt to check */
if (!vshConnectionUsability(ctl, ctl->conn))
return NULL;
if (!dom) {
vshError(ctl, "%s", _("Invalid domain supplied"));
return NULL;
}
if (STREQ(mime, "image/x-portable-pixmap"))
ext = ".ppm";
else if (STREQ(mime, "image/png"))
ext = ".png";
/* add mime type here */
gettimeofday(&cur_time, NULL);
localtime_r(&cur_time.tv_sec, &time_info);
strftime(timestr, sizeof(timestr), "%Y-%m-%d-%H:%M:%S", &time_info);
if (virAsprintf(&ret, "%s-%s%s", virDomainGetName(dom),
timestr, ext ? ext : "") < 0) {
vshError(ctl, "%s", _("Out of memory"));
return NULL;
}
return ret;
}
static bool
cmdScreenshot(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *name = NULL;
char *file = NULL;
int fd = -1;
virStreamPtr st = NULL;
unsigned int screen = 0;
unsigned int flags = 0; /* currently unused */
int ret = false;
bool created = false;
bool generated = false;
char *mime = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", (const char **) &file) < 0) {
vshError(ctl, "%s", _("file must not be empty"));
return false;
}
if (vshCommandOptUInt(cmd, "screen", &screen) < 0) {
vshError(ctl, "%s", _("invalid screen ID"));
return false;
}
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
st = virStreamNew(ctl->conn, 0);
mime = virDomainScreenshot(dom, st, screen, flags);
if (!mime) {
vshError(ctl, _("could not take a screenshot of %s"), name);
goto cleanup;
}
if (!file) {
if (!(file=vshGenFileName(ctl, dom, mime)))
return false;
generated = true;
}
if ((fd = open(file, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) {
if (errno != EEXIST ||
(fd = open(file, O_WRONLY|O_TRUNC, 0666)) < 0) {
vshError(ctl, _("cannot create file %s"), file);
goto cleanup;
}
} else {
created = true;
}
if (virStreamRecvAll(st, vshStreamSink, &fd) < 0) {
vshError(ctl, _("could not receive data from domain %s"), name);
goto cleanup;
}
if (VIR_CLOSE(fd) < 0) {
vshError(ctl, _("cannot close file %s"), file);
goto cleanup;
}
if (virStreamFinish(st) < 0) {
vshError(ctl, _("cannot close stream on domain %s"), name);
goto cleanup;
}
vshPrint(ctl, _("Screenshot saved to %s, with type of %s"), file, mime);
ret = true;
cleanup:
if (!ret && created)
unlink(file);
if (generated)
VIR_FREE(file);
virDomainFree(dom);
if (st)
virStreamFree(st);
VIR_FORCE_CLOSE(fd);
VIR_FREE(mime);
return ret;
}
/*
* "resume" command
*/
static const vshCmdInfo info_resume[] = {
{"help", N_("resume a domain")},
{"desc", N_("Resume a previously suspended domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_resume[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdResume(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (virDomainResume(dom) == 0) {
vshPrint(ctl, _("Domain %s resumed\n"), name);
} else {
vshError(ctl, _("Failed to resume domain %s"), name);
ret = false;
}
virDomainFree(dom);
return ret;
}
/*
* "shutdown" command
*/
static const vshCmdInfo info_shutdown[] = {
{"help", N_("gracefully shutdown a domain")},
{"desc", N_("Run shutdown in the target domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_shutdown[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"mode", VSH_OT_STRING, VSH_OFLAG_NONE, N_("shutdown mode: acpi|agent")},
{NULL, 0, 0, NULL}
};
static bool
cmdShutdown(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = true;
const char *name;
const char *mode = NULL;
int flags = 0;
int rv;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "mode", &mode) < 0) {
vshError(ctl, "%s", _("Invalid type"));
return false;
}
if (mode) {
if (STREQ(mode, "acpi")) {
flags |= VIR_DOMAIN_SHUTDOWN_ACPI_POWER_BTN;
} else if (STREQ(mode, "agent")) {
flags |= VIR_DOMAIN_SHUTDOWN_GUEST_AGENT;
} else {
vshError(ctl, _("Unknown mode %s value, expecting 'acpi' or 'agent'"), mode);
return false;
}
}
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (flags)
rv = virDomainShutdownFlags(dom, flags);
else
rv = virDomainShutdown(dom);
if (rv == 0) {
vshPrint(ctl, _("Domain %s is being shutdown\n"), name);
} else {
vshError(ctl, _("Failed to shutdown domain %s"), name);
ret = false;
}
virDomainFree(dom);
return ret;
}
/*
* "reboot" command
*/
static const vshCmdInfo info_reboot[] = {
{"help", N_("reboot a domain")},
{"desc", N_("Run a reboot command in the target domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_reboot[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"mode", VSH_OT_STRING, VSH_OFLAG_NONE, N_("shutdown mode: acpi|agent")},
{NULL, 0, 0, NULL}
};
static bool
cmdReboot(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = true;
const char *name;
const char *mode = NULL;
int flags = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "mode", &mode) < 0) {
vshError(ctl, "%s", _("Invalid type"));
return false;
}
if (mode) {
if (STREQ(mode, "acpi")) {
flags |= VIR_DOMAIN_SHUTDOWN_ACPI_POWER_BTN;
} else if (STREQ(mode, "agent")) {
flags |= VIR_DOMAIN_SHUTDOWN_GUEST_AGENT;
} else {
vshError(ctl, _("Unknown mode %s value, expecting 'acpi' or 'agent'"), mode);
return false;
}
}
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (virDomainReboot(dom, flags) == 0) {
vshPrint(ctl, _("Domain %s is being rebooted\n"), name);
} else {
vshError(ctl, _("Failed to reboot domain %s"), name);
ret = false;
}
virDomainFree(dom);
return ret;
}
/*
* "reset" command
*/
static const vshCmdInfo info_reset[] = {
{"help", N_("reset a domain")},
{"desc", N_("Reset the target domain as if by power button")},
{NULL, NULL}
};
static const vshCmdOptDef opts_reset[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdReset(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (virDomainReset(dom, 0) == 0) {
vshPrint(ctl, _("Domain %s was reset\n"), name);
} else {
vshError(ctl, _("Failed to reset domain %s"), name);
ret = false;
}
virDomainFree(dom);
return ret;
}
/*
* "destroy" command
*/
static const vshCmdInfo info_destroy[] = {
{"help", N_("destroy (stop) a domain")},
{"desc",
N_("Forcefully stop a given domain, but leave its resources intact.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_destroy[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"graceful", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("terminate gracefully")},
{NULL, 0, 0, NULL}
};
static bool
cmdDestroy(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = true;
const char *name;
unsigned int flags = 0;
int result;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
if (vshCommandOptBool(cmd, "graceful"))
flags |= VIR_DOMAIN_DESTROY_GRACEFUL;
if (flags)
result = virDomainDestroyFlags(dom, VIR_DOMAIN_DESTROY_GRACEFUL);
else
result = virDomainDestroy(dom);
if (result == 0) {
vshPrint(ctl, _("Domain %s destroyed\n"), name);
} else {
vshError(ctl, _("Failed to destroy domain %s"), name);
ret = false;
}
virDomainFree(dom);
return ret;
}
/*
* "dominfo" command
*/
static const vshCmdInfo info_dominfo[] = {
{"help", N_("domain information")},
{"desc", N_("Returns basic information about the domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_dominfo[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdDominfo(vshControl *ctl, const vshCmd *cmd)
{
virDomainInfo info;
virDomainPtr dom;
virSecurityModel secmodel;
virSecurityLabelPtr seclabel;
int persistent = 0;
bool ret = true;
int autostart;
unsigned int id;
char *str, uuid[VIR_UUID_STRING_BUFLEN];
int has_managed_save = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
id = virDomainGetID(dom);
if (id == ((unsigned int)-1))
vshPrint(ctl, "%-15s %s\n", _("Id:"), "-");
else
vshPrint(ctl, "%-15s %d\n", _("Id:"), id);
vshPrint(ctl, "%-15s %s\n", _("Name:"), virDomainGetName(dom));
if (virDomainGetUUIDString(dom, &uuid[0])==0)
vshPrint(ctl, "%-15s %s\n", _("UUID:"), uuid);
if ((str = virDomainGetOSType(dom))) {
vshPrint(ctl, "%-15s %s\n", _("OS Type:"), str);
VIR_FREE(str);
}
if (virDomainGetInfo(dom, &info) == 0) {
vshPrint(ctl, "%-15s %s\n", _("State:"),
_(vshDomainStateToString(info.state)));
vshPrint(ctl, "%-15s %d\n", _("CPU(s):"), info.nrVirtCpu);
if (info.cpuTime != 0) {
double cpuUsed = info.cpuTime;
cpuUsed /= 1000000000.0;
vshPrint(ctl, "%-15s %.1lfs\n", _("CPU time:"), cpuUsed);
}
if (info.maxMem != UINT_MAX)
vshPrint(ctl, "%-15s %lu KiB\n", _("Max memory:"),
info.maxMem);
else
vshPrint(ctl, "%-15s %s\n", _("Max memory:"),
_("no limit"));
vshPrint(ctl, "%-15s %lu KiB\n", _("Used memory:"),
info.memory);
} else {
ret = false;
}
/* Check and display whether the domain is persistent or not */
persistent = virDomainIsPersistent(dom);
vshDebug(ctl, VSH_ERR_DEBUG, "Domain persistent flag value: %d\n",
persistent);
if (persistent < 0)
vshPrint(ctl, "%-15s %s\n", _("Persistent:"), _("unknown"));
else
vshPrint(ctl, "%-15s %s\n", _("Persistent:"), persistent ? _("yes") : _("no"));
/* Check and display whether the domain autostarts or not */
if (!virDomainGetAutostart(dom, &autostart)) {
vshPrint(ctl, "%-15s %s\n", _("Autostart:"),
autostart ? _("enable") : _("disable") );
}
has_managed_save = virDomainHasManagedSaveImage(dom, 0);
if (has_managed_save < 0)
vshPrint(ctl, "%-15s %s\n", _("Managed save:"), _("unknown"));
else
vshPrint(ctl, "%-15s %s\n", _("Managed save:"),
has_managed_save ? _("yes") : _("no"));
/* Security model and label information */
memset(&secmodel, 0, sizeof(secmodel));
if (virNodeGetSecurityModel(ctl->conn, &secmodel) == -1) {
if (last_error->code != VIR_ERR_NO_SUPPORT) {
virDomainFree(dom);
return false;
} else {
virFreeError(last_error);
last_error = NULL;
}
} else {
/* Only print something if a security model is active */
if (secmodel.model[0] != '\0') {
vshPrint(ctl, "%-15s %s\n", _("Security model:"), secmodel.model);
vshPrint(ctl, "%-15s %s\n", _("Security DOI:"), secmodel.doi);
/* Security labels are only valid for active domains */
if (VIR_ALLOC(seclabel) < 0) {
virDomainFree(dom);
return false;
}
if (virDomainGetSecurityLabel(dom, seclabel) == -1) {
virDomainFree(dom);
VIR_FREE(seclabel);
return false;
} else {
if (seclabel->label[0] != '\0')
vshPrint(ctl, "%-15s %s (%s)\n", _("Security label:"),
seclabel->label, seclabel->enforcing ? "enforcing" : "permissive");
}
VIR_FREE(seclabel);
}
}
virDomainFree(dom);
return ret;
}
/*
* "domjobinfo" command
*/
static const vshCmdInfo info_domjobinfo[] = {
{"help", N_("domain job information")},
{"desc", N_("Returns information about jobs running on a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domjobinfo[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomjobinfo(vshControl *ctl, const vshCmd *cmd)
{
virDomainJobInfo info;
virDomainPtr dom;
bool ret = true;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (virDomainGetJobInfo(dom, &info) == 0) {
const char *unit;
double val;
vshPrint(ctl, "%-17s ", _("Job type:"));
switch (info.type) {
case VIR_DOMAIN_JOB_BOUNDED:
vshPrint(ctl, "%-12s\n", _("Bounded"));
break;
case VIR_DOMAIN_JOB_UNBOUNDED:
vshPrint(ctl, "%-12s\n", _("Unbounded"));
break;
case VIR_DOMAIN_JOB_NONE:
default:
vshPrint(ctl, "%-12s\n", _("None"));
goto cleanup;
}
vshPrint(ctl, "%-17s %-12llu ms\n", _("Time elapsed:"), info.timeElapsed);
if (info.type == VIR_DOMAIN_JOB_BOUNDED)
vshPrint(ctl, "%-17s %-12llu ms\n", _("Time remaining:"), info.timeRemaining);
if (info.dataTotal || info.dataRemaining || info.dataProcessed) {
val = prettyCapacity(info.dataProcessed, &unit);
vshPrint(ctl, "%-17s %-.3lf %s\n", _("Data processed:"), val, unit);
val = prettyCapacity(info.dataRemaining, &unit);
vshPrint(ctl, "%-17s %-.3lf %s\n", _("Data remaining:"), val, unit);
val = prettyCapacity(info.dataTotal, &unit);
vshPrint(ctl, "%-17s %-.3lf %s\n", _("Data total:"), val, unit);
}
if (info.memTotal || info.memRemaining || info.memProcessed) {
val = prettyCapacity(info.memProcessed, &unit);
vshPrint(ctl, "%-17s %-.3lf %s\n", _("Memory processed:"), val, unit);
val = prettyCapacity(info.memRemaining, &unit);
vshPrint(ctl, "%-17s %-.3lf %s\n", _("Memory remaining:"), val, unit);
val = prettyCapacity(info.memTotal, &unit);
vshPrint(ctl, "%-17s %-.3lf %s\n", _("Memory total:"), val, unit);
}
if (info.fileTotal || info.fileRemaining || info.fileProcessed) {
val = prettyCapacity(info.fileProcessed, &unit);
vshPrint(ctl, "%-17s %-.3lf %s\n", _("File processed:"), val, unit);
val = prettyCapacity(info.fileRemaining, &unit);
vshPrint(ctl, "%-17s %-.3lf %s\n", _("File remaining:"), val, unit);
val = prettyCapacity(info.fileTotal, &unit);
vshPrint(ctl, "%-17s %-.3lf %s\n", _("File total:"), val, unit);
}
} else {
ret = false;
}
cleanup:
virDomainFree(dom);
return ret;
}
/*
* "domjobabort" command
*/
static const vshCmdInfo info_domjobabort[] = {
{"help", N_("abort active domain job")},
{"desc", N_("Aborts the currently running domain job")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domjobabort[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomjobabort(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = true;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (virDomainAbortJob(dom) < 0)
ret = false;
virDomainFree(dom);
return ret;
}
/*
* "freecell" command
*/
static const vshCmdInfo info_freecell[] = {
{"help", N_("NUMA free memory")},
{"desc", N_("display available free memory for the NUMA cell.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_freecell[] = {
{"cellno", VSH_OT_INT, 0, N_("NUMA cell number")},
{"all", VSH_OT_BOOL, 0, N_("show free memory for all NUMA cells")},
{NULL, 0, 0, NULL}
};
static bool
cmdFreecell(vshControl *ctl, const vshCmd *cmd)
{
bool func_ret = false;
int ret;
int cell = -1, cell_given;
unsigned long long memory;
xmlNodePtr *nodes = NULL;
unsigned long nodes_cnt;
unsigned long *nodes_id = NULL;
unsigned long long *nodes_free = NULL;
int all_given;
int i;
char *cap_xml = NULL;
xmlDocPtr xml = NULL;
xmlXPathContextPtr ctxt = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if ( (cell_given = vshCommandOptInt(cmd, "cellno", &cell)) < 0) {
vshError(ctl, "%s", _("cell number has to be a number"));
goto cleanup;
}
all_given = vshCommandOptBool(cmd, "all");
if (all_given && cell_given) {
vshError(ctl, "%s", _("--cellno and --all are mutually exclusive. "
"Please choose only one."));
goto cleanup;
}
if (all_given) {
cap_xml = virConnectGetCapabilities(ctl->conn);
if (!cap_xml) {
vshError(ctl, "%s", _("unable to get node capabilities"));
goto cleanup;
}
xml = virXMLParseStringCtxt(cap_xml, _("(capabilities)"), &ctxt);
if (!xml) {
vshError(ctl, "%s", _("unable to get node capabilities"));
goto cleanup;
}
nodes_cnt = virXPathNodeSet("/capabilities/host/topology/cells/cell",
ctxt, &nodes);
if (nodes_cnt == -1) {
vshError(ctl, "%s", _("could not get information about "
"NUMA topology"));
goto cleanup;
}
nodes_free = vshCalloc(ctl, nodes_cnt, sizeof(*nodes_free));
nodes_id = vshCalloc(ctl, nodes_cnt, sizeof(*nodes_id));
for (i = 0; i < nodes_cnt; i++) {
unsigned long id;
char *val = virXMLPropString(nodes[i], "id");
if (virStrToLong_ul(val, NULL, 10, &id)) {
vshError(ctl, "%s", _("conversion from string failed"));
VIR_FREE(val);
goto cleanup;
}
VIR_FREE(val);
nodes_id[i]=id;
ret = virNodeGetCellsFreeMemory(ctl->conn, &(nodes_free[i]), id, 1);
if (ret != 1) {
vshError(ctl, _("failed to get free memory for NUMA node "
"number: %lu"), id);
goto cleanup;
}
}
memory = 0;
for (cell = 0; cell < nodes_cnt; cell++) {
vshPrint(ctl, "%5lu: %10llu KiB\n", nodes_id[cell],
(nodes_free[cell]/1024));
memory += nodes_free[cell];
}
vshPrintExtra(ctl, "--------------------\n");
vshPrintExtra(ctl, "%5s: %10llu KiB\n", _("Total"), memory/1024);
} else {
if (!cell_given) {
memory = virNodeGetFreeMemory(ctl->conn);
if (memory == 0)
goto cleanup;
} else {
ret = virNodeGetCellsFreeMemory(ctl->conn, &memory, cell, 1);
if (ret != 1)
goto cleanup;
}
if (cell == -1)
vshPrint(ctl, "%s: %llu KiB\n", _("Total"), (memory/1024));
else
vshPrint(ctl, "%d: %llu KiB\n", cell, (memory/1024));
}
func_ret = true;
cleanup:
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
VIR_FREE(nodes);
VIR_FREE(nodes_free);
VIR_FREE(nodes_id);
VIR_FREE(cap_xml);
return func_ret;
}
/*
* "maxvcpus" command
*/
static const vshCmdInfo info_maxvcpus[] = {
{"help", N_("connection vcpu maximum")},
{"desc", N_("Show maximum number of virtual CPUs for guests on this connection.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_maxvcpus[] = {
{"type", VSH_OT_STRING, 0, N_("domain type")},
{NULL, 0, 0, NULL}
};
static bool
cmdMaxvcpus(vshControl *ctl, const vshCmd *cmd)
{
const char *type = NULL;
int vcpus;
if (vshCommandOptString(cmd, "type", &type) < 0) {
vshError(ctl, "%s", _("Invalid type"));
return false;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
vcpus = virConnectGetMaxVcpus(ctl->conn, type);
if (vcpus < 0)
return false;
vshPrint(ctl, "%d\n", vcpus);
return true;
}
/*
* "vcpucount" command
*/
static const vshCmdInfo info_vcpucount[] = {
{"help", N_("domain vcpu counts")},
{"desc", N_("Returns the number of virtual CPUs used by the domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vcpucount[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"maximum", VSH_OT_BOOL, 0, N_("get maximum cap on vcpus")},
{"active", VSH_OT_BOOL, 0, N_("get number of currently active vcpus")},
{"live", VSH_OT_BOOL, 0, N_("get value from running domain")},
{"config", VSH_OT_BOOL, 0, N_("get value to be used on next boot")},
{"current", VSH_OT_BOOL, 0,
N_("get value according to current domain state")},
{NULL, 0, 0, NULL}
};
static bool
cmdVcpucount(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = true;
bool maximum = vshCommandOptBool(cmd, "maximum");
bool active = vshCommandOptBool(cmd, "active");
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
bool current = vshCommandOptBool(cmd, "current");
bool all = maximum + active + current + config + live == 0;
int count;
/* We want one of each pair of mutually exclusive options; that
* is, use of flags requires exactly two options. We reject the
* use of more than 2 flags later on. */
if (maximum + active + current + config + live == 1) {
if (maximum || active) {
vshError(ctl,
_("when using --%s, one of --config, --live, or --current "
"must be specified"),
maximum ? "maximum" : "active");
} else {
vshError(ctl,
_("when using --%s, either --maximum or --active must be "
"specified"),
(current ? "current" : config ? "config" : "live"));
}
return false;
}
/* Backwards compatibility: prior to 0.9.4,
* VIR_DOMAIN_AFFECT_CURRENT was unsupported, and --current meant
* the opposite of --maximum. Translate the old '--current
* --live' into the new '--active --live', while treating the new
* '--maximum --current' correctly rather than rejecting it as
* '--maximum --active'. */
if (!maximum && !active && current) {
current = false;
active = true;
}
if (maximum && active) {
vshError(ctl, "%s",
_("--maximum and --active cannot both be specified"));
return false;
}
if (current + config + live > 1) {
vshError(ctl, "%s",
_("--config, --live, and --current are mutually exclusive"));
return false;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
/* In all cases, try the new API first; if it fails because we are
* talking to an older client, generally we try a fallback API
* before giving up. --current requires the new API, since we
* don't know whether the domain is running or inactive. */
if (current) {
count = virDomainGetVcpusFlags(dom,
maximum ? VIR_DOMAIN_VCPU_MAXIMUM : 0);
if (count < 0) {
virshReportError(ctl);
ret = false;
} else {
vshPrint(ctl, "%d\n", count);
}
}
if (all || (maximum && config)) {
count = virDomainGetVcpusFlags(dom, (VIR_DOMAIN_VCPU_MAXIMUM |
VIR_DOMAIN_AFFECT_CONFIG));
if (count < 0 && (last_error->code == VIR_ERR_NO_SUPPORT
|| last_error->code == VIR_ERR_INVALID_ARG)) {
char *tmp;
char *xml = virDomainGetXMLDesc(dom, VIR_DOMAIN_XML_INACTIVE);
if (xml && (tmp = strstr(xml, "<vcpu"))) {
tmp = strchr(tmp, '>');
if (!tmp || virStrToLong_i(tmp + 1, &tmp, 10, &count) < 0)
count = -1;
}
virFreeError(last_error);
last_error = NULL;
VIR_FREE(xml);
}
if (count < 0) {
virshReportError(ctl);
ret = false;
} else if (all) {
vshPrint(ctl, "%-12s %-12s %3d\n", _("maximum"), _("config"),
count);
} else {
vshPrint(ctl, "%d\n", count);
}
virFreeError(last_error);
last_error = NULL;
}
if (all || (maximum && live)) {
count = virDomainGetVcpusFlags(dom, (VIR_DOMAIN_VCPU_MAXIMUM |
VIR_DOMAIN_AFFECT_LIVE));
if (count < 0 && (last_error->code == VIR_ERR_NO_SUPPORT
|| last_error->code == VIR_ERR_INVALID_ARG)) {
count = virDomainGetMaxVcpus(dom);
}
if (count < 0) {
virshReportError(ctl);
ret = false;
} else if (all) {
vshPrint(ctl, "%-12s %-12s %3d\n", _("maximum"), _("live"),
count);
} else {
vshPrint(ctl, "%d\n", count);
}
virFreeError(last_error);
last_error = NULL;
}
if (all || (active && config)) {
count = virDomainGetVcpusFlags(dom, VIR_DOMAIN_AFFECT_CONFIG);
if (count < 0 && (last_error->code == VIR_ERR_NO_SUPPORT
|| last_error->code == VIR_ERR_INVALID_ARG)) {
char *tmp, *end;
char *xml = virDomainGetXMLDesc(dom, VIR_DOMAIN_XML_INACTIVE);
if (xml && (tmp = strstr(xml, "<vcpu"))) {
end = strchr(tmp, '>');
if (end) {
*end = '\0';
tmp = strstr(tmp, "current=");
if (!tmp)
tmp = end + 1;
else {
tmp += strlen("current=");
tmp += *tmp == '\'' || *tmp == '"';
}
}
if (!tmp || virStrToLong_i(tmp, &tmp, 10, &count) < 0)
count = -1;
}
VIR_FREE(xml);
}
if (count < 0) {
virshReportError(ctl);
ret = false;
} else if (all) {
vshPrint(ctl, "%-12s %-12s %3d\n", _("current"), _("config"),
count);
} else {
vshPrint(ctl, "%d\n", count);
}
virFreeError(last_error);
last_error = NULL;
}
if (all || (active && live)) {
count = virDomainGetVcpusFlags(dom, VIR_DOMAIN_AFFECT_LIVE);
if (count < 0 && (last_error->code == VIR_ERR_NO_SUPPORT
|| last_error->code == VIR_ERR_INVALID_ARG)) {
virDomainInfo info;
if (virDomainGetInfo(dom, &info) == 0)
count = info.nrVirtCpu;
}
if (count < 0) {
virshReportError(ctl);
ret = false;
} else if (all) {
vshPrint(ctl, "%-12s %-12s %3d\n", _("current"), _("live"),
count);
} else {
vshPrint(ctl, "%d\n", count);
}
virFreeError(last_error);
last_error = NULL;
}
virDomainFree(dom);
return ret;
}
/*
* "vcpuinfo" command
*/
static const vshCmdInfo info_vcpuinfo[] = {
{"help", N_("detailed domain vcpu information")},
{"desc", N_("Returns basic information about the domain virtual CPUs.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vcpuinfo[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdVcpuinfo(vshControl *ctl, const vshCmd *cmd)
{
virDomainInfo info;
virDomainPtr dom;
virNodeInfo nodeinfo;
virVcpuInfoPtr cpuinfo;
unsigned char *cpumaps;
int ncpus, maxcpu;
size_t cpumaplen;
bool ret = true;
int n, m;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (virNodeGetInfo(ctl->conn, &nodeinfo) != 0) {
virDomainFree(dom);
return false;
}
if (virDomainGetInfo(dom, &info) != 0) {
virDomainFree(dom);
return false;
}
cpuinfo = vshMalloc(ctl, sizeof(virVcpuInfo)*info.nrVirtCpu);
maxcpu = VIR_NODEINFO_MAXCPUS(nodeinfo);
cpumaplen = VIR_CPU_MAPLEN(maxcpu);
cpumaps = vshMalloc(ctl, info.nrVirtCpu * cpumaplen);
if ((ncpus = virDomainGetVcpus(dom,
cpuinfo, info.nrVirtCpu,
cpumaps, cpumaplen)) >= 0) {
for (n = 0 ; n < ncpus ; n++) {
vshPrint(ctl, "%-15s %d\n", _("VCPU:"), n);
vshPrint(ctl, "%-15s %d\n", _("CPU:"), cpuinfo[n].cpu);
vshPrint(ctl, "%-15s %s\n", _("State:"),
_(vshDomainVcpuStateToString(cpuinfo[n].state)));
if (cpuinfo[n].cpuTime != 0) {
double cpuUsed = cpuinfo[n].cpuTime;
cpuUsed /= 1000000000.0;
vshPrint(ctl, "%-15s %.1lfs\n", _("CPU time:"), cpuUsed);
}
vshPrint(ctl, "%-15s ", _("CPU Affinity:"));
for (m = 0; m < maxcpu; m++) {
vshPrint(ctl, "%c", VIR_CPU_USABLE(cpumaps, cpumaplen, n, m) ? 'y' : '-');
}
vshPrint(ctl, "\n");
if (n < (ncpus - 1)) {
vshPrint(ctl, "\n");
}
}
} else {
if (info.state == VIR_DOMAIN_SHUTOFF &&
(ncpus = virDomainGetVcpuPinInfo(dom, info.nrVirtCpu,
cpumaps, cpumaplen,
VIR_DOMAIN_AFFECT_CONFIG)) >= 0) {
/* fallback plan to use virDomainGetVcpuPinInfo */
for (n = 0; n < ncpus; n++) {
vshPrint(ctl, "%-15s %d\n", _("VCPU:"), n);
vshPrint(ctl, "%-15s %s\n", _("CPU:"), _("N/A"));
vshPrint(ctl, "%-15s %s\n", _("State:"), _("N/A"));
vshPrint(ctl, "%-15s %s\n", _("CPU time"), _("N/A"));
vshPrint(ctl, "%-15s ", _("CPU Affinity:"));
for (m = 0; m < maxcpu; m++) {
vshPrint(ctl, "%c",
VIR_CPU_USABLE(cpumaps, cpumaplen, n, m) ? 'y' : '-');
}
vshPrint(ctl, "\n");
if (n < (ncpus - 1)) {
vshPrint(ctl, "\n");
}
}
} else {
ret = false;
}
}
VIR_FREE(cpumaps);
VIR_FREE(cpuinfo);
virDomainFree(dom);
return ret;
}
/*
* "vcpupin" command
*/
static const vshCmdInfo info_vcpupin[] = {
{"help", N_("control or query domain vcpu affinity")},
{"desc", N_("Pin domain VCPUs to host physical CPUs.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vcpupin[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"vcpu", VSH_OT_INT, 0, N_("vcpu number")},
{"cpulist", VSH_OT_DATA, VSH_OFLAG_EMPTY_OK,
N_("host cpu number(s) to set, or omit option to query")},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"live", VSH_OT_BOOL, 0, N_("affect running domain")},
{"current", VSH_OT_BOOL, 0, N_("affect current domain")},
{NULL, 0, 0, NULL}
};
static bool
cmdVcpuPin(vshControl *ctl, const vshCmd *cmd)
{
virDomainInfo info;
virDomainPtr dom;
virNodeInfo nodeinfo;
int vcpu = -1;
const char *cpulist = NULL;
bool ret = true;
unsigned char *cpumap = NULL;
unsigned char *cpumaps = NULL;
size_t cpumaplen;
bool bit, lastbit, isInvert;
int i, cpu, lastcpu, maxcpu, ncpus;
bool unuse = false;
const char *cur;
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
bool current = vshCommandOptBool(cmd, "current");
bool query = false; /* Query mode if no cpulist */
unsigned int flags = 0;
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
/* neither option is specified */
if (!live && !config)
flags = -1;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptString(cmd, "cpulist", &cpulist) < 0) {
vshError(ctl, "%s", _("vcpupin: Missing cpulist."));
virDomainFree(dom);
return false;
}
query = !cpulist;
/* In query mode, "vcpu" is optional */
if (vshCommandOptInt(cmd, "vcpu", &vcpu) < !query) {
vshError(ctl, "%s",
_("vcpupin: Invalid or missing vCPU number."));
virDomainFree(dom);
return false;
}
if (virNodeGetInfo(ctl->conn, &nodeinfo) != 0) {
virDomainFree(dom);
return false;
}
if (virDomainGetInfo(dom, &info) != 0) {
vshError(ctl, "%s", _("vcpupin: failed to get domain information."));
virDomainFree(dom);
return false;
}
if (vcpu >= info.nrVirtCpu) {
vshError(ctl, "%s", _("vcpupin: Invalid vCPU number."));
virDomainFree(dom);
return false;
}
maxcpu = VIR_NODEINFO_MAXCPUS(nodeinfo);
cpumaplen = VIR_CPU_MAPLEN(maxcpu);
/* Query mode: show CPU affinity information then exit.*/
if (query) {
/* When query mode and neither "live", "config" nor "current"
* is specified, set VIR_DOMAIN_AFFECT_CURRENT as flags */
if (flags == -1)
flags = VIR_DOMAIN_AFFECT_CURRENT;
cpumaps = vshMalloc(ctl, info.nrVirtCpu * cpumaplen);
if ((ncpus = virDomainGetVcpuPinInfo(dom, info.nrVirtCpu,
cpumaps, cpumaplen, flags)) >= 0) {
vshPrint(ctl, "%s %s\n", _("VCPU:"), _("CPU Affinity"));
vshPrint(ctl, "----------------------------------\n");
for (i = 0; i < ncpus; i++) {
if (vcpu != -1 && i != vcpu)
continue;
bit = lastbit = isInvert = false;
lastcpu = -1;
vshPrint(ctl, "%4d: ", i);
for (cpu = 0; cpu < maxcpu; cpu++) {
bit = VIR_CPU_USABLE(cpumaps, cpumaplen, i, cpu);
isInvert = (bit ^ lastbit);
if (bit && isInvert) {
if (lastcpu == -1)
vshPrint(ctl, "%d", cpu);
else
vshPrint(ctl, ",%d", cpu);
lastcpu = cpu;
}
if (!bit && isInvert && lastcpu != cpu - 1)
vshPrint(ctl, "-%d", cpu - 1);
lastbit = bit;
}
if (bit && !isInvert) {
vshPrint(ctl, "-%d", maxcpu - 1);
}
vshPrint(ctl, "\n");
}
} else {
ret = false;
}
VIR_FREE(cpumaps);
goto cleanup;
}
/* Pin mode: pinning specified vcpu to specified physical cpus*/
cpumap = vshCalloc(ctl, cpumaplen, sizeof(cpumap));
/* Parse cpulist */
cur = cpulist;
if (*cur == 0) {
goto parse_error;
} else if (*cur == 'r') {
for (cpu = 0; cpu < maxcpu; cpu++)
VIR_USE_CPU(cpumap, cpu);
cur = "";
}
while (*cur != 0) {
/* the char '^' denotes exclusive */
if (*cur == '^') {
cur++;
unuse = true;
}
/* parse physical CPU number */
if (!c_isdigit(*cur))
goto parse_error;
cpu = virParseNumber(&cur);
if (cpu < 0) {
goto parse_error;
}
if (cpu >= maxcpu) {
vshError(ctl, _("Physical CPU %d doesn't exist."), cpu);
goto parse_error;
}
virSkipSpaces(&cur);
if (*cur == ',' || *cur == 0) {
if (unuse) {
VIR_UNUSE_CPU(cpumap, cpu);
} else {
VIR_USE_CPU(cpumap, cpu);
}
} else if (*cur == '-') {
/* the char '-' denotes range */
if (unuse) {
goto parse_error;
}
cur++;
virSkipSpaces(&cur);
/* parse the end of range */
lastcpu = virParseNumber(&cur);
if (lastcpu < cpu) {
goto parse_error;
}
if (lastcpu >= maxcpu) {
vshError(ctl, _("Physical CPU %d doesn't exist."), maxcpu);
goto parse_error;
}
for (i = cpu; i <= lastcpu; i++) {
VIR_USE_CPU(cpumap, i);
}
virSkipSpaces(&cur);
}
if (*cur == ',') {
cur++;
virSkipSpaces(&cur);
unuse = false;
} else if (*cur == 0) {
break;
} else {
goto parse_error;
}
}
if (flags == -1) {
if (virDomainPinVcpu(dom, vcpu, cpumap, cpumaplen) != 0) {
ret = false;
}
} else {
if (virDomainPinVcpuFlags(dom, vcpu, cpumap, cpumaplen, flags) != 0) {
ret = false;
}
}
cleanup:
VIR_FREE(cpumap);
virDomainFree(dom);
return ret;
parse_error:
vshError(ctl, "%s", _("cpulist: Invalid format."));
ret = false;
goto cleanup;
}
/*
* "setvcpus" command
*/
static const vshCmdInfo info_setvcpus[] = {
{"help", N_("change number of virtual CPUs")},
{"desc", N_("Change the number of virtual CPUs in the guest domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_setvcpus[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"count", VSH_OT_INT, VSH_OFLAG_REQ, N_("number of virtual CPUs")},
{"maximum", VSH_OT_BOOL, 0, N_("set maximum limit on next boot")},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"live", VSH_OT_BOOL, 0, N_("affect running domain")},
{"current", VSH_OT_BOOL, 0, N_("affect current domain")},
{NULL, 0, 0, NULL}
};
static bool
cmdSetvcpus(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
int count = 0;
bool ret = true;
bool maximum = vshCommandOptBool(cmd, "maximum");
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
bool current = vshCommandOptBool(cmd, "current");
unsigned int flags = 0;
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
/* neither option is specified */
if (!live && !config && !maximum)
flags = -1;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptInt(cmd, "count", &count) < 0 || count <= 0) {
vshError(ctl, "%s", _("Invalid number of virtual CPUs"));
goto cleanup;
}
if (flags == -1) {
if (virDomainSetVcpus(dom, count) != 0) {
ret = false;
}
} else {
/* If the --maximum flag was given, we need to ensure only the
--config flag is in effect as well */
if (maximum) {
vshDebug(ctl, VSH_ERR_DEBUG, "--maximum flag was given\n");
flags |= VIR_DOMAIN_VCPU_MAXIMUM;
/* If neither the --config nor --live flags were given, OR
if just the --live flag was given, we need to error out
warning the user that the --maximum flag can only be used
with the --config flag */
if (live || !config) {
/* Warn the user about the invalid flag combination */
vshError(ctl, _("--maximum must be used with --config only"));
ret = false;
goto cleanup;
}
}
/* Apply the virtual cpu changes */
if (virDomainSetVcpusFlags(dom, count, flags) < 0) {
ret = false;
}
}
cleanup:
virDomainFree(dom);
return ret;
}
/*
* "cpu-stats" command
*/
static const vshCmdInfo info_cpu_stats[] = {
{"help", N_("show domain cpu statistics")},
{"desc",
N_("Display per-CPU and total statistics about the domain's CPUs")},
{NULL, NULL},
};
static const vshCmdOptDef opts_cpu_stats[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"total", VSH_OT_BOOL, 0, N_("Show total statistics only")},
{"start", VSH_OT_INT, 0, N_("Show statistics from this CPU")},
{"count", VSH_OT_INT, 0, N_("Number of shown CPUs at most")},
{NULL, 0, 0, NULL},
};
static bool
cmdCPUStats(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
virTypedParameterPtr params = NULL;
int i, j, pos, max_id, cpu = -1, show_count = -1, nparams;
bool show_total = false, show_per_cpu = false;
unsigned int flags = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
show_total = vshCommandOptBool(cmd, "total");
if (vshCommandOptInt(cmd, "start", &cpu) > 0)
show_per_cpu = true;
if (vshCommandOptInt(cmd, "count", &show_count) > 0)
show_per_cpu = true;
/* default show per_cpu and total */
if (!show_total && !show_per_cpu) {
show_total = true;
show_per_cpu = true;
}
if (!show_per_cpu) /* show total stats only */
goto do_show_total;
/* check cpu, show_count, and ignore wrong argument */
if (cpu < 0)
cpu = 0;
/* get number of cpus on the node */
if ((max_id = virDomainGetCPUStats(dom, NULL, 0, 0, 0, flags)) < 0)
goto failed_stats;
if (show_count < 0 || show_count > max_id)
show_count = max_id;
/* get percpu information */
if ((nparams = virDomainGetCPUStats(dom, NULL, 0, 0, 1, flags)) < 0)
goto failed_stats;
if (!nparams) {
vshPrint(ctl, "%s", _("No per-CPU stats available"));
goto do_show_total;
}
if (VIR_ALLOC_N(params, nparams * MIN(show_count, 128)) < 0)
goto failed_params;
while (show_count) {
int ncpus = MIN(show_count, 128);
if (virDomainGetCPUStats(dom, params, nparams, cpu, ncpus, flags) < 0)
goto failed_stats;
for (i = 0; i < ncpus; i++) {
if (params[i * nparams].type == 0) /* this cpu is not in the map */
continue;
vshPrint(ctl, "CPU%d:\n", cpu + i);
for (j = 0; j < nparams; j++) {
pos = i * nparams + j;
vshPrint(ctl, "\t%-12s ", params[pos].field);
if ((STREQ(params[pos].field, VIR_DOMAIN_CPU_STATS_CPUTIME) ||
STREQ(params[pos].field, VIR_DOMAIN_CPU_STATS_VCPUTIME)) &&
params[j].type == VIR_TYPED_PARAM_ULLONG) {
vshPrint(ctl, "%9lld.%09lld seconds\n",
params[pos].value.ul / 1000000000,
params[pos].value.ul % 1000000000);
} else {
const char *s = vshGetTypedParamValue(ctl, &params[pos]);
vshPrint(ctl, _("%s\n"), s);
VIR_FREE(s);
}
}
}
cpu += ncpus;
show_count -= ncpus;
virTypedParameterArrayClear(params, nparams * ncpus);
}
VIR_FREE(params);
do_show_total:
if (!show_total)
goto cleanup;
/* get supported num of parameter for total statistics */
if ((nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, flags)) < 0)
goto failed_stats;
if (!nparams) {
vshPrint(ctl, "%s", _("No total stats available"));
goto cleanup;
}
if (VIR_ALLOC_N(params, nparams))
goto failed_params;
/* passing start_cpu == -1 gives us domain's total status */
if ((nparams = virDomainGetCPUStats(dom, params, nparams, -1, 1, flags)) < 0)
goto failed_stats;
vshPrint(ctl, _("Total:\n"));
for (i = 0; i < nparams; i++) {
vshPrint(ctl, "\t%-12s ", params[i].field);
if ((STREQ(params[i].field, VIR_DOMAIN_CPU_STATS_CPUTIME) ||
STREQ(params[i].field, VIR_DOMAIN_CPU_STATS_USERTIME) ||
STREQ(params[i].field, VIR_DOMAIN_CPU_STATS_SYSTEMTIME)) &&
params[i].type == VIR_TYPED_PARAM_ULLONG) {
vshPrint(ctl, "%9lld.%09lld seconds\n",
params[i].value.ul / 1000000000,
params[i].value.ul % 1000000000);
} else {
char *s = vshGetTypedParamValue(ctl, &params[i]);
vshPrint(ctl, "%s\n", s);
VIR_FREE(s);
}
}
virTypedParameterArrayClear(params, nparams);
VIR_FREE(params);
cleanup:
virDomainFree(dom);
return true;
failed_params:
virReportOOMError();
virDomainFree(dom);
return false;
failed_stats:
vshError(ctl, _("Failed to virDomainGetCPUStats()\n"));
VIR_FREE(params);
virDomainFree(dom);
return false;
}
/*
* "inject-nmi" command
*/
static const vshCmdInfo info_inject_nmi[] = {
{"help", N_("Inject NMI to the guest")},
{"desc", N_("Inject NMI to the guest domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_inject_nmi[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdInjectNMI(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
int ret = true;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (virDomainInjectNMI(dom, 0) < 0)
ret = false;
virDomainFree(dom);
return ret;
}
/*
* "send-key" command
*/
static const vshCmdInfo info_send_key[] = {
{"help", N_("Send keycodes to the guest")},
{"desc", N_("Send keycodes (integers or symbolic names) to the guest")},
{NULL, NULL}
};
static const vshCmdOptDef opts_send_key[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"codeset", VSH_OT_STRING, VSH_OFLAG_REQ_OPT,
N_("the codeset of keycodes, default:linux")},
{"holdtime", VSH_OT_INT, VSH_OFLAG_REQ_OPT,
N_("the time (in milliseconds) how long the keys will be held")},
{"keycode", VSH_OT_ARGV, VSH_OFLAG_REQ, N_("the key code")},
{NULL, 0, 0, NULL}
};
static int
get_integer_keycode(const char *key_name)
{
unsigned int val;
if (virStrToLong_ui(key_name, NULL, 0, &val) < 0 || val > 0xffff || !val)
return -1;
return val;
}
static bool
cmdSendKey(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
int ret = false;
const char *codeset_option;
int codeset;
int holdtime;
int count = 0;
const vshCmdOpt *opt = NULL;
int keycode;
unsigned int keycodes[VIR_DOMAIN_SEND_KEY_MAX_KEYS];
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptString(cmd, "codeset", &codeset_option) <= 0)
codeset_option = "linux";
if (vshCommandOptInt(cmd, "holdtime", &holdtime) <= 0)
holdtime = 0;
codeset = virKeycodeSetTypeFromString(codeset_option);
if ((int)codeset < 0) {
vshError(ctl, _("unknown codeset: '%s'"), codeset_option);
goto cleanup;
}
while ((opt = vshCommandOptArgv(cmd, opt))) {
if (count == VIR_DOMAIN_SEND_KEY_MAX_KEYS) {
vshError(ctl, _("too many keycodes"));
goto cleanup;
}
if ((keycode = get_integer_keycode(opt->data)) <= 0) {
if ((keycode = virKeycodeValueFromString(codeset, opt->data)) <= 0) {
vshError(ctl, _("invalid keycode: '%s'"), opt->data);
goto cleanup;
}
}
keycodes[count] = keycode;
count++;
}
if (!(virDomainSendKey(dom, codeset, holdtime, keycodes, count, 0) < 0))
ret = true;
cleanup:
virDomainFree(dom);
return ret;
}
/*
* "setmem" command
*/
static const vshCmdInfo info_setmem[] = {
{"help", N_("change memory allocation")},
{"desc", N_("Change the current memory allocation in the guest domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_setmem[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"kilobytes", VSH_OT_ALIAS, 0, "size"},
{"size", VSH_OT_INT, VSH_OFLAG_REQ,
N_("new memory size, as scaled integer (default KiB)")},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"live", VSH_OT_BOOL, 0, N_("affect running domain")},
{"current", VSH_OT_BOOL, 0, N_("affect current domain")},
{NULL, 0, 0, NULL}
};
static bool
cmdSetmem(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
unsigned long long bytes = 0;
unsigned long long max;
unsigned long kibibytes = 0;
bool ret = true;
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
bool current = vshCommandOptBool(cmd, "current");
unsigned int flags = 0;
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
/* neither option is specified */
if (!live && !config)
flags = -1;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
/* The API expects 'unsigned long' KiB, so depending on whether we
* are 32-bit or 64-bit determines the maximum we can use. */
if (sizeof(kibibytes) < sizeof(max))
max = 1024ull * ULONG_MAX;
else
max = ULONG_MAX;
if (vshCommandOptScaledInt(cmd, "size", &bytes, 1024, max) < 0) {
vshError(ctl, "%s", _("memory size has to be a number"));
virDomainFree(dom);
return false;
}
kibibytes = VIR_DIV_UP(bytes, 1024);
if (flags == -1) {
if (virDomainSetMemory(dom, kibibytes) != 0) {
ret = false;
}
} else {
if (virDomainSetMemoryFlags(dom, kibibytes, flags) < 0) {
ret = false;
}
}
virDomainFree(dom);
return ret;
}
/*
* "setmaxmem" command
*/
static const vshCmdInfo info_setmaxmem[] = {
{"help", N_("change maximum memory limit")},
{"desc", N_("Change the maximum memory allocation limit in the guest domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_setmaxmem[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"kilobytes", VSH_OT_ALIAS, 0, "size"},
{"size", VSH_OT_INT, VSH_OFLAG_REQ,
N_("new maximum memory size, as scaled integer (default KiB)")},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"live", VSH_OT_BOOL, 0, N_("affect running domain")},
{"current", VSH_OT_BOOL, 0, N_("affect current domain")},
{NULL, 0, 0, NULL}
};
static bool
cmdSetmaxmem(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
unsigned long long bytes = 0;
unsigned long long max;
unsigned long kibibytes = 0;
bool ret = true;
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
bool current = vshCommandOptBool(cmd, "current");
unsigned int flags = VIR_DOMAIN_MEM_MAXIMUM;
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
/* neither option is specified */
if (!live && !config)
flags = -1;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
/* The API expects 'unsigned long' KiB, so depending on whether we
* are 32-bit or 64-bit determines the maximum we can use. */
if (sizeof(kibibytes) < sizeof(max))
max = 1024ull * ULONG_MAX;
else
max = ULONG_MAX;
if (vshCommandOptScaledInt(cmd, "size", &bytes, 1024, max) < 0) {
vshError(ctl, "%s", _("memory size has to be a number"));
virDomainFree(dom);
return false;
}
kibibytes = VIR_DIV_UP(bytes, 1024);
if (flags == -1) {
if (virDomainSetMaxMemory(dom, kibibytes) != 0) {
vshError(ctl, "%s", _("Unable to change MaxMemorySize"));
ret = false;
}
} else {
if (virDomainSetMemoryFlags(dom, kibibytes, flags) < 0) {
vshError(ctl, "%s", _("Unable to change MaxMemorySize"));
ret = false;
}
}
virDomainFree(dom);
return ret;
}
/*
* "blkiotune" command
*/
static const vshCmdInfo info_blkiotune[] = {
{"help", N_("Get or set blkio parameters")},
{"desc", N_("Get or set the current blkio parameters for a guest"
" domain.\n"
" To get the blkio parameters use following command: \n\n"
" virsh # blkiotune <domain>")},
{NULL, NULL}
};
static const vshCmdOptDef opts_blkiotune[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"weight", VSH_OT_INT, VSH_OFLAG_NONE,
N_("IO Weight in range [100, 1000]")},
{"device-weights", VSH_OT_STRING, VSH_OFLAG_NONE,
N_("per-device IO Weights, in the form of /path/to/device,weight,...")},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"live", VSH_OT_BOOL, 0, N_("affect running domain")},
{"current", VSH_OT_BOOL, 0, N_("affect current domain")},
{NULL, 0, 0, NULL}
};
static bool
cmdBlkiotune(vshControl * ctl, const vshCmd * cmd)
{
virDomainPtr dom;
const char *device_weight = NULL;
int weight = 0;
int nparams = 0;
int rv = 0;
unsigned int i = 0;
virTypedParameterPtr params = NULL, temp = NULL;
bool ret = false;
unsigned int flags = 0;
bool current = vshCommandOptBool(cmd, "current");
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if ((rv = vshCommandOptInt(cmd, "weight", &weight)) < 0) {
vshError(ctl, "%s",
_("Unable to parse integer parameter"));
goto cleanup;
}
if (rv > 0) {
nparams++;
if (weight <= 0) {
vshError(ctl, _("Invalid value of %d for I/O weight"), weight);
goto cleanup;
}
}
rv = vshCommandOptString(cmd, "device-weights", &device_weight);
if (rv < 0) {
vshError(ctl, "%s",
_("Unable to parse string parameter"));
goto cleanup;
}
if (rv > 0) {
nparams++;
}
if (nparams == 0) {
/* get the number of blkio parameters */
if (virDomainGetBlkioParameters(dom, NULL, &nparams, flags) != 0) {
vshError(ctl, "%s",
_("Unable to get number of blkio parameters"));
goto cleanup;
}
if (nparams == 0) {
/* nothing to output */
ret = true;
goto cleanup;
}
/* now go get all the blkio parameters */
params = vshCalloc(ctl, nparams, sizeof(*params));
if (virDomainGetBlkioParameters(dom, params, &nparams, flags) != 0) {
vshError(ctl, "%s", _("Unable to get blkio parameters"));
goto cleanup;
}
for (i = 0; i < nparams; i++) {
char *str = vshGetTypedParamValue(ctl, &params[i]);
vshPrint(ctl, "%-15s: %s\n", params[i].field, str);
VIR_FREE(str);
}
} else {
/* set the blkio parameters */
params = vshCalloc(ctl, nparams, sizeof(*params));
for (i = 0; i < nparams; i++) {
temp = &params[i];
temp->type = VIR_TYPED_PARAM_UINT;
if (weight) {
temp->value.ui = weight;
if (!virStrcpy(temp->field, VIR_DOMAIN_BLKIO_WEIGHT,
sizeof(temp->field)))
goto cleanup;
weight = 0;
} else if (device_weight) {
temp->value.s = vshStrdup(ctl, device_weight);
temp->type = VIR_TYPED_PARAM_STRING;
if (!virStrcpy(temp->field, VIR_DOMAIN_BLKIO_DEVICE_WEIGHT,
sizeof(temp->field)))
goto cleanup;
device_weight = NULL;
}
}
if (virDomainSetBlkioParameters(dom, params, nparams, flags) < 0) {
vshError(ctl, "%s", _("Unable to change blkio parameters"));
goto cleanup;
}
}
ret = true;
cleanup:
virTypedParameterArrayClear(params, nparams);
VIR_FREE(params);
virDomainFree(dom);
return ret;
}
/*
* "memtune" command
*/
static const vshCmdInfo info_memtune[] = {
{"help", N_("Get or set memory parameters")},
{"desc", N_("Get or set the current memory parameters for a guest"
" domain.\n"
" To get the memory parameters use following command: \n\n"
" virsh # memtune <domain>")},
{NULL, NULL}
};
static const vshCmdOptDef opts_memtune[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"hard-limit", VSH_OT_INT, VSH_OFLAG_NONE,
N_("Max memory, as scaled integer (default KiB)")},
{"soft-limit", VSH_OT_INT, VSH_OFLAG_NONE,
N_("Memory during contention, as scaled integer (default KiB)")},
{"swap-hard-limit", VSH_OT_INT, VSH_OFLAG_NONE,
N_("Max memory plus swap, as scaled integer (default KiB)")},
{"min-guarantee", VSH_OT_INT, VSH_OFLAG_NONE,
N_("Min guaranteed memory, as scaled integer (default KiB)")},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"live", VSH_OT_BOOL, 0, N_("affect running domain")},
{"current", VSH_OT_BOOL, 0, N_("affect current domain")},
{NULL, 0, 0, NULL}
};
static int
vshMemtuneGetSize(const vshCmd *cmd, const char *name, long long *value)
{
int ret;
unsigned long long tmp;
const char *str;
char *end;
ret = vshCommandOptString(cmd, name, &str);
if (ret <= 0)
return ret;
if (virStrToLong_ll(str, &end, 10, value) < 0)
return -1;
if (*value < 0) {
*value = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
return 1;
}
tmp = *value;
if (virScaleInteger(&tmp, end, 1024, LLONG_MAX) < 0)
return -1;
*value = VIR_DIV_UP(tmp, 1024);
return 0;
}
static bool
cmdMemtune(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
long long hard_limit = 0, soft_limit = 0, swap_hard_limit = 0;
long long min_guarantee = 0;
int nparams = 0;
unsigned int i = 0;
virTypedParameterPtr params = NULL, temp = NULL;
bool ret = false;
unsigned int flags = 0;
bool current = vshCommandOptBool(cmd, "current");
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshMemtuneGetSize(cmd, "hard-limit", &hard_limit) < 0 ||
vshMemtuneGetSize(cmd, "soft-limit", &soft_limit) < 0 ||
vshMemtuneGetSize(cmd, "swap-hard-limit", &swap_hard_limit) < 0 ||
vshMemtuneGetSize(cmd, "min-guarantee", &min_guarantee) < 0) {
vshError(ctl, "%s",
_("Unable to parse integer parameter"));
goto cleanup;
}
if (hard_limit)
nparams++;
if (soft_limit)
nparams++;
if (swap_hard_limit)
nparams++;
if (min_guarantee)
nparams++;
if (nparams == 0) {
/* get the number of memory parameters */
if (virDomainGetMemoryParameters(dom, NULL, &nparams, flags) != 0) {
vshError(ctl, "%s",
_("Unable to get number of memory parameters"));
goto cleanup;
}
if (nparams == 0) {
/* nothing to output */
ret = true;
goto cleanup;
}
/* now go get all the memory parameters */
params = vshCalloc(ctl, nparams, sizeof(*params));
if (virDomainGetMemoryParameters(dom, params, &nparams, flags) != 0) {
vshError(ctl, "%s", _("Unable to get memory parameters"));
goto cleanup;
}
for (i = 0; i < nparams; i++) {
if (params[i].type == VIR_TYPED_PARAM_ULLONG &&
params[i].value.ul == VIR_DOMAIN_MEMORY_PARAM_UNLIMITED) {
vshPrint(ctl, "%-15s: %s\n", params[i].field, _("unlimited"));
} else {
char *str = vshGetTypedParamValue(ctl, &params[i]);
vshPrint(ctl, "%-15s: %s\n", params[i].field, str);
VIR_FREE(str);
}
}
ret = true;
} else {
/* set the memory parameters */
params = vshCalloc(ctl, nparams, sizeof(*params));
for (i = 0; i < nparams; i++) {
temp = &params[i];
/*
* Some magic here, this is used to fill the params structure with
* the valid arguments passed, after filling the particular
* argument we purposely make them 0, so on the next pass it goes
* to the next valid argument and so on.
*/
if (soft_limit) {
if (virTypedParameterAssign(temp,
VIR_DOMAIN_MEMORY_SOFT_LIMIT,
VIR_TYPED_PARAM_ULLONG,
soft_limit) < 0)
goto error;
soft_limit = 0;
} else if (hard_limit) {
if (virTypedParameterAssign(temp,
VIR_DOMAIN_MEMORY_HARD_LIMIT,
VIR_TYPED_PARAM_ULLONG,
hard_limit) < 0)
goto error;
hard_limit = 0;
} else if (swap_hard_limit) {
if (virTypedParameterAssign(temp,
VIR_DOMAIN_MEMORY_SWAP_HARD_LIMIT,
VIR_TYPED_PARAM_ULLONG,
swap_hard_limit) < 0)
goto error;
swap_hard_limit = 0;
} else if (min_guarantee) {
if (virTypedParameterAssign(temp,
VIR_DOMAIN_MEMORY_MIN_GUARANTEE,
VIR_TYPED_PARAM_ULLONG,
min_guarantee) < 0)
goto error;
min_guarantee = 0;
}
/* If the user has passed -1, we interpret it as unlimited */
if (temp->value.ul == -1)
temp->value.ul = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
}
if (virDomainSetMemoryParameters(dom, params, nparams, flags) != 0)
goto error;
else
ret = true;
}
cleanup:
VIR_FREE(params);
virDomainFree(dom);
return ret;
error:
vshError(ctl, "%s", _("Unable to change memory parameters"));
goto cleanup;
}
/*
* "numatune" command
*/
static const vshCmdInfo info_numatune[] = {
{"help", N_("Get or set numa parameters")},
{"desc", N_("Get or set the current numa parameters for a guest"
" domain.\n"
" To get the numa parameters use following command: \n\n"
" virsh # numatune <domain>")},
{NULL, NULL}
};
static const vshCmdOptDef opts_numatune[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"mode", VSH_OT_DATA, VSH_OFLAG_NONE,
N_("NUMA mode, one of strict, preferred and interleave")},
{"nodeset", VSH_OT_DATA, VSH_OFLAG_NONE,
N_("NUMA node selections to set")},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"live", VSH_OT_BOOL, 0, N_("affect running domain")},
{"current", VSH_OT_BOOL, 0, N_("affect current domain")},
{NULL, 0, 0, NULL}
};
static bool
cmdNumatune(vshControl * ctl, const vshCmd * cmd)
{
virDomainPtr dom;
int nparams = 0;
unsigned int i = 0;
virTypedParameterPtr params = NULL, temp = NULL;
const char *nodeset = NULL;
bool ret = false;
unsigned int flags = 0;
bool current = vshCommandOptBool(cmd, "current");
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
const char *mode = NULL;
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptString(cmd, "nodeset", &nodeset) < 0) {
vshError(ctl, "%s", _("Unable to parse nodeset."));
virDomainFree(dom);
return false;
}
if (nodeset)
nparams++;
if (vshCommandOptString(cmd, "mode", &mode) < 0) {
vshError(ctl, "%s", _("Unable to parse mode."));
virDomainFree(dom);
return false;
}
if (mode)
nparams++;
if (nparams == 0) {
/* get the number of numa parameters */
if (virDomainGetNumaParameters(dom, NULL, &nparams, flags) != 0) {
vshError(ctl, "%s",
_("Unable to get number of memory parameters"));
goto cleanup;
}
if (nparams == 0) {
/* nothing to output */
ret = true;
goto cleanup;
}
/* now go get all the numa parameters */
params = vshCalloc(ctl, nparams, sizeof(*params));
if (virDomainGetNumaParameters(dom, params, &nparams, flags) != 0) {
vshError(ctl, "%s", _("Unable to get numa parameters"));
goto cleanup;
}
for (i = 0; i < nparams; i++) {
if (params[i].type == VIR_TYPED_PARAM_INT &&
STREQ(params[i].field, VIR_DOMAIN_NUMA_MODE)) {
vshPrint(ctl, "%-15s: %s\n", params[i].field,
virDomainNumatuneMemModeTypeToString(params[i].value.i));
} else {
char *str = vshGetTypedParamValue(ctl, &params[i]);
vshPrint(ctl, "%-15s: %s\n", params[i].field, str);
VIR_FREE(str);
}
}
ret = true;
} else {
/* set the numa parameters */
params = vshCalloc(ctl, nparams, sizeof(*params));
for (i = 0; i < nparams; i++) {
temp = &params[i];
/*
* Some magic here, this is used to fill the params structure with
* the valid arguments passed, after filling the particular
* argument we purposely make them 0, so on the next pass it goes
* to the next valid argument and so on.
*/
if (mode) {
/* Accept string or integer, in case server
* understands newer integer than what strings we were
* compiled with */
if ((temp->value.i =
virDomainNumatuneMemModeTypeFromString(mode)) < 0 &&
virStrToLong_i(mode, NULL, 0, &temp->value.i) < 0) {
vshError(ctl, _("Invalid mode: %s"), mode);
goto cleanup;
}
if (!virStrcpy(temp->field, VIR_DOMAIN_NUMA_MODE,
sizeof(temp->field)))
goto cleanup;
temp->type = VIR_TYPED_PARAM_INT;
mode = NULL;
} else if (nodeset) {
temp->value.s = vshStrdup(ctl, nodeset);
temp->type = VIR_TYPED_PARAM_STRING;
if (!virStrcpy(temp->field, VIR_DOMAIN_NUMA_NODESET,
sizeof(temp->field)))
goto cleanup;
nodeset = NULL;
}
}
if (virDomainSetNumaParameters(dom, params, nparams, flags) != 0)
vshError(ctl, "%s", _("Unable to change numa parameters"));
else
ret = true;
}
cleanup:
virTypedParameterArrayClear(params, nparams);
VIR_FREE(params);
virDomainFree(dom);
return ret;
}
/*
* "nodeinfo" command
*/
static const vshCmdInfo info_nodeinfo[] = {
{"help", N_("node information")},
{"desc", N_("Returns basic information about the node.")},
{NULL, NULL}
};
static bool
cmdNodeinfo(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
virNodeInfo info;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (virNodeGetInfo(ctl->conn, &info) < 0) {
vshError(ctl, "%s", _("failed to get node information"));
return false;
}
vshPrint(ctl, "%-20s %s\n", _("CPU model:"), info.model);
vshPrint(ctl, "%-20s %d\n", _("CPU(s):"), info.cpus);
vshPrint(ctl, "%-20s %d MHz\n", _("CPU frequency:"), info.mhz);
vshPrint(ctl, "%-20s %d\n", _("CPU socket(s):"), info.sockets);
vshPrint(ctl, "%-20s %d\n", _("Core(s) per socket:"), info.cores);
vshPrint(ctl, "%-20s %d\n", _("Thread(s) per core:"), info.threads);
vshPrint(ctl, "%-20s %d\n", _("NUMA cell(s):"), info.nodes);
vshPrint(ctl, "%-20s %lu KiB\n", _("Memory size:"), info.memory);
return true;
}
/*
* "nodecpustats" command
*/
static const vshCmdInfo info_nodecpustats[] = {
{"help", N_("Prints cpu stats of the node.")},
{"desc", N_("Returns cpu stats of the node, in nanoseconds.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_node_cpustats[] = {
{"cpu", VSH_OT_INT, 0, N_("prints specified cpu statistics only.")},
{"percent", VSH_OT_BOOL, 0, N_("prints by percentage during 1 second.")},
{NULL, 0, 0, NULL}
};
static bool
cmdNodeCpuStats(vshControl *ctl, const vshCmd *cmd)
{
int i, j;
bool flag_utilization = false;
bool flag_percent = vshCommandOptBool(cmd, "percent");
int cpuNum = VIR_NODE_CPU_STATS_ALL_CPUS;
virNodeCPUStatsPtr params;
int nparams = 0;
bool ret = false;
struct cpu_stats {
unsigned long long user;
unsigned long long sys;
unsigned long long idle;
unsigned long long iowait;
unsigned long long util;
} cpu_stats[2];
double user_time, sys_time, idle_time, iowait_time, total_time;
double usage;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptInt(cmd, "cpu", &cpuNum) < 0) {
vshError(ctl, "%s", _("Invalid value of cpuNum"));
return false;
}
if (virNodeGetCPUStats(ctl->conn, cpuNum, NULL, &nparams, 0) != 0) {
vshError(ctl, "%s",
_("Unable to get number of cpu stats"));
return false;
}
if (nparams == 0) {
/* nothing to output */
return true;
}
memset(cpu_stats, 0, sizeof(cpu_stats));
params = vshCalloc(ctl, nparams, sizeof(*params));
for (i = 0; i < 2; i++) {
if (i > 0)
sleep(1);
if (virNodeGetCPUStats(ctl->conn, cpuNum, params, &nparams, 0) != 0) {
vshError(ctl, "%s", _("Unable to get node cpu stats"));
goto cleanup;
}
for (j = 0; j < nparams; j++) {
unsigned long long value = params[j].value;
if (STREQ(params[j].field, VIR_NODE_CPU_STATS_KERNEL)) {
cpu_stats[i].sys = value;
} else if (STREQ(params[j].field, VIR_NODE_CPU_STATS_USER)) {
cpu_stats[i].user = value;
} else if (STREQ(params[j].field, VIR_NODE_CPU_STATS_IDLE)) {
cpu_stats[i].idle = value;
} else if (STREQ(params[j].field, VIR_NODE_CPU_STATS_IOWAIT)) {
cpu_stats[i].iowait = value;
} else if (STREQ(params[j].field, VIR_NODE_CPU_STATS_UTILIZATION)) {
cpu_stats[i].util = value;
flag_utilization = true;
}
}
if (flag_utilization || !flag_percent)
break;
}
if (!flag_percent) {
if (!flag_utilization) {
vshPrint(ctl, "%-15s %20llu\n", _("user:"), cpu_stats[0].user);
vshPrint(ctl, "%-15s %20llu\n", _("system:"), cpu_stats[0].sys);
vshPrint(ctl, "%-15s %20llu\n", _("idle:"), cpu_stats[0].idle);
vshPrint(ctl, "%-15s %20llu\n", _("iowait:"), cpu_stats[0].iowait);
}
} else {
if (flag_utilization) {
usage = cpu_stats[0].util;
vshPrint(ctl, "%-15s %5.1lf%%\n", _("usage:"), usage);
vshPrint(ctl, "%-15s %5.1lf%%\n", _("idle:"), 100 - usage);
} else {
user_time = cpu_stats[1].user - cpu_stats[0].user;
sys_time = cpu_stats[1].sys - cpu_stats[0].sys;
idle_time = cpu_stats[1].idle - cpu_stats[0].idle;
iowait_time = cpu_stats[1].iowait - cpu_stats[0].iowait;
total_time = user_time + sys_time + idle_time + iowait_time;
usage = (user_time + sys_time) / total_time * 100;
vshPrint(ctl, "%-15s %5.1lf%%\n",
_("usage:"), usage);
vshPrint(ctl, "%-15s %5.1lf%%\n",
_("user:"), user_time / total_time * 100);
vshPrint(ctl, "%-15s %5.1lf%%\n",
_("system:"), sys_time / total_time * 100);
vshPrint(ctl, "%-15s %5.1lf%%\n",
_("idle:"), idle_time / total_time * 100);
vshPrint(ctl, "%-15s %5.1lf%%\n",
_("iowait:"), iowait_time / total_time * 100);
}
}
ret = true;
cleanup:
VIR_FREE(params);
return ret;
}
/*
* "nodememstats" command
*/
static const vshCmdInfo info_nodememstats[] = {
{"help", N_("Prints memory stats of the node.")},
{"desc", N_("Returns memory stats of the node, in kilobytes.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_node_memstats[] = {
{"cell", VSH_OT_INT, 0, N_("prints specified cell statistics only.")},
{NULL, 0, 0, NULL}
};
static bool
cmdNodeMemStats(vshControl *ctl, const vshCmd *cmd)
{
int nparams = 0;
unsigned int i = 0;
int cellNum = VIR_NODE_MEMORY_STATS_ALL_CELLS;
virNodeMemoryStatsPtr params = NULL;
bool ret = false;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptInt(cmd, "cell", &cellNum) < 0) {
vshError(ctl, "%s", _("Invalid value of cellNum"));
return false;
}
/* get the number of memory parameters */
if (virNodeGetMemoryStats(ctl->conn, cellNum, NULL, &nparams, 0) != 0) {
vshError(ctl, "%s",
_("Unable to get number of memory stats"));
goto cleanup;
}
if (nparams == 0) {
/* nothing to output */
ret = true;
goto cleanup;
}
/* now go get all the memory parameters */
params = vshCalloc(ctl, nparams, sizeof(*params));
if (virNodeGetMemoryStats(ctl->conn, cellNum, params, &nparams, 0) != 0) {
vshError(ctl, "%s", _("Unable to get memory stats"));
goto cleanup;
}
for (i = 0; i < nparams; i++)
vshPrint(ctl, "%-7s: %20llu KiB\n", params[i].field, params[i].value);
ret = true;
cleanup:
VIR_FREE(params);
return ret;
}
/*
* "nodesuspend" command
*/
static const vshCmdInfo info_nodesuspend[] = {
{"help", N_("suspend the host node for a given time duration")},
{"desc", N_("Suspend the host node for a given time duration "
"and attempt to resume thereafter.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_node_suspend[] = {
{"target", VSH_OT_DATA, VSH_OFLAG_REQ, N_("mem(Suspend-to-RAM), "
"disk(Suspend-to-Disk), hybrid(Hybrid-Suspend)")},
{"duration", VSH_OT_INT, VSH_OFLAG_REQ, N_("Suspend duration in seconds")},
{"flags", VSH_OT_INT, VSH_OFLAG_NONE, N_("Suspend flags, 0 for default")},
{NULL, 0, 0, NULL}
};
static bool
cmdNodeSuspend(vshControl *ctl, const vshCmd *cmd)
{
const char *target = NULL;
unsigned int suspendTarget;
long long duration;
unsigned int flags = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "target", &target) < 0) {
vshError(ctl, _("Invalid target argument"));
return false;
}
if (vshCommandOptLongLong(cmd, "duration", &duration) < 0) {
vshError(ctl, _("Invalid duration argument"));
return false;
}
if (vshCommandOptUInt(cmd, "flags", &flags) < 0) {
vshError(ctl, _("Invalid flags argument"));
return false;
}
if (STREQ(target, "mem"))
suspendTarget = VIR_NODE_SUSPEND_TARGET_MEM;
else if (STREQ(target, "disk"))
suspendTarget = VIR_NODE_SUSPEND_TARGET_DISK;
else if (STREQ(target, "hybrid"))
suspendTarget = VIR_NODE_SUSPEND_TARGET_HYBRID;
else {
vshError(ctl, "%s", _("Invalid target"));
return false;
}
if (duration <= 0) {
vshError(ctl, "%s", _("Invalid duration"));
return false;
}
if (virNodeSuspendForDuration(ctl->conn, suspendTarget, duration,
flags) < 0) {
vshError(ctl, "%s", _("The host was not suspended"));
return false;
}
return true;
}
/*
* "capabilities" command
*/
static const vshCmdInfo info_capabilities[] = {
{"help", N_("capabilities")},
{"desc", N_("Returns capabilities of hypervisor/driver.")},
{NULL, NULL}
};
static bool
cmdCapabilities(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
char *caps;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if ((caps = virConnectGetCapabilities(ctl->conn)) == NULL) {
vshError(ctl, "%s", _("failed to get capabilities"));
return false;
}
vshPrint(ctl, "%s\n", caps);
VIR_FREE(caps);
return true;
}
/*
* "dumpxml" command
*/
static const vshCmdInfo info_dumpxml[] = {
{"help", N_("domain information in XML")},
{"desc", N_("Output the domain information as an XML dump to stdout.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_dumpxml[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"inactive", VSH_OT_BOOL, 0, N_("show inactive defined XML")},
{"security-info", VSH_OT_BOOL, 0, N_("include security sensitive information in XML dump")},
{"update-cpu", VSH_OT_BOOL, 0, N_("update guest CPU according to host CPU")},
{NULL, 0, 0, NULL}
};
static bool
cmdDumpXML(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
bool ret = true;
char *dump;
unsigned int flags = 0;
bool inactive = vshCommandOptBool(cmd, "inactive");
bool secure = vshCommandOptBool(cmd, "security-info");
bool update = vshCommandOptBool(cmd, "update-cpu");
if (inactive)
flags |= VIR_DOMAIN_XML_INACTIVE;
if (secure)
flags |= VIR_DOMAIN_XML_SECURE;
if (update)
flags |= VIR_DOMAIN_XML_UPDATE_CPU;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
dump = virDomainGetXMLDesc(dom, flags);
if (dump != NULL) {
vshPrint(ctl, "%s", dump);
VIR_FREE(dump);
} else {
ret = false;
}
virDomainFree(dom);
return ret;
}
/*
* "domxml-from-native" command
*/
static const vshCmdInfo info_domxmlfromnative[] = {
{"help", N_("Convert native config to domain XML")},
{"desc", N_("Convert native guest configuration format to domain XML format.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domxmlfromnative[] = {
{"format", VSH_OT_DATA, VSH_OFLAG_REQ, N_("source config data format")},
{"config", VSH_OT_DATA, VSH_OFLAG_REQ, N_("config data file to import from")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomXMLFromNative(vshControl *ctl, const vshCmd *cmd)
{
bool ret = true;
const char *format = NULL;
const char *configFile = NULL;
char *configData;
char *xmlData;
unsigned int flags = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "format", &format) < 0 ||
vshCommandOptString(cmd, "config", &configFile) < 0)
return false;
if (virFileReadAll(configFile, 1024*1024, &configData) < 0)
return false;
xmlData = virConnectDomainXMLFromNative(ctl->conn, format, configData, flags);
if (xmlData != NULL) {
vshPrint(ctl, "%s", xmlData);
VIR_FREE(xmlData);
} else {
ret = false;
}
VIR_FREE(configData);
return ret;
}
/*
* "domxml-to-native" command
*/
static const vshCmdInfo info_domxmltonative[] = {
{"help", N_("Convert domain XML to native config")},
{"desc", N_("Convert domain XML config to a native guest configuration format.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domxmltonative[] = {
{"format", VSH_OT_DATA, VSH_OFLAG_REQ, N_("target config data type format")},
{"xml", VSH_OT_DATA, VSH_OFLAG_REQ, N_("xml data file to export from")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomXMLToNative(vshControl *ctl, const vshCmd *cmd)
{
bool ret = true;
const char *format = NULL;
const char *xmlFile = NULL;
char *configData;
char *xmlData;
unsigned int flags = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "format", &format) < 0
|| vshCommandOptString(cmd, "xml", &xmlFile) < 0)
return false;
if (virFileReadAll(xmlFile, 1024*1024, &xmlData) < 0)
return false;
configData = virConnectDomainXMLToNative(ctl->conn, format, xmlData, flags);
if (configData != NULL) {
vshPrint(ctl, "%s", configData);
VIR_FREE(configData);
} else {
ret = false;
}
VIR_FREE(xmlData);
return ret;
}
/*
* "domname" command
*/
static const vshCmdInfo info_domname[] = {
{"help", N_("convert a domain id or UUID to domain name")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_domname[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomname(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomainBy(ctl, cmd, NULL,
VSH_BYID|VSH_BYUUID)))
return false;
vshPrint(ctl, "%s\n", virDomainGetName(dom));
virDomainFree(dom);
return true;
}
/*
* "domid" command
*/
static const vshCmdInfo info_domid[] = {
{"help", N_("convert a domain name or UUID to domain id")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_domid[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomid(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
unsigned int id;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomainBy(ctl, cmd, NULL,
VSH_BYNAME|VSH_BYUUID)))
return false;
id = virDomainGetID(dom);
if (id == ((unsigned int)-1))
vshPrint(ctl, "%s\n", "-");
else
vshPrint(ctl, "%d\n", id);
virDomainFree(dom);
return true;
}
/*
* "domuuid" command
*/
static const vshCmdInfo info_domuuid[] = {
{"help", N_("convert a domain name or id to domain UUID")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_domuuid[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain id or name")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomuuid(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
char uuid[VIR_UUID_STRING_BUFLEN];
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomainBy(ctl, cmd, NULL,
VSH_BYNAME|VSH_BYID)))
return false;
if (virDomainGetUUIDString(dom, uuid) != -1)
vshPrint(ctl, "%s\n", uuid);
else
vshError(ctl, "%s", _("failed to get domain UUID"));
virDomainFree(dom);
return true;
}
/*
* "migrate" command
*/
static const vshCmdInfo info_migrate[] = {
{"help", N_("migrate domain to another host")},
{"desc", N_("Migrate domain to another host. Add --live for live migration.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_migrate[] = {
{"live", VSH_OT_BOOL, 0, N_("live migration")},
{"p2p", VSH_OT_BOOL, 0, N_("peer-2-peer migration")},
{"direct", VSH_OT_BOOL, 0, N_("direct migration")},
{"tunneled", VSH_OT_ALIAS, 0, "tunnelled"},
{"tunnelled", VSH_OT_BOOL, 0, N_("tunnelled migration")},
{"persistent", VSH_OT_BOOL, 0, N_("persist VM on destination")},
{"undefinesource", VSH_OT_BOOL, 0, N_("undefine VM on source")},
{"suspend", VSH_OT_BOOL, 0, N_("do not restart the domain on the destination host")},
{"copy-storage-all", VSH_OT_BOOL, 0, N_("migration with non-shared storage with full disk copy")},
{"copy-storage-inc", VSH_OT_BOOL, 0, N_("migration with non-shared storage with incremental copy (same base image shared between source and destination)")},
{"change-protection", VSH_OT_BOOL, 0,
N_("prevent any configuration changes to domain until migration ends)")},
{"unsafe", VSH_OT_BOOL, 0, N_("force migration even if it may be unsafe")},
{"verbose", VSH_OT_BOOL, 0, N_("display the progress of migration")},
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"desturi", VSH_OT_DATA, VSH_OFLAG_REQ, N_("connection URI of the destination host as seen from the client(normal migration) or source(p2p migration)")},
{"migrateuri", VSH_OT_DATA, 0, N_("migration URI, usually can be omitted")},
{"dname", VSH_OT_DATA, 0, N_("rename to new name during migration (if supported)")},
{"timeout", VSH_OT_INT, 0, N_("force guest to suspend if live migration exceeds timeout (in seconds)")},
{"xml", VSH_OT_STRING, 0, N_("filename containing updated XML for the target")},
{NULL, 0, 0, NULL}
};
static void
doMigrate(void *opaque)
{
char ret = '1';
virDomainPtr dom = NULL;
const char *desturi = NULL;
const char *migrateuri = NULL;
const char *dname = NULL;
unsigned int flags = 0;
vshCtrlData *data = opaque;
vshControl *ctl = data->ctl;
const vshCmd *cmd = data->cmd;
const char *xmlfile = NULL;
char *xml = NULL;
sigset_t sigmask, oldsigmask;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGINT);
if (pthread_sigmask(SIG_BLOCK, &sigmask, &oldsigmask) < 0)
goto out_sig;
if (!vshConnectionUsability(ctl, ctl->conn))
goto out;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
goto out;
if (vshCommandOptString(cmd, "desturi", &desturi) <= 0 ||
vshCommandOptString(cmd, "migrateuri", &migrateuri) < 0 ||
vshCommandOptString(cmd, "dname", &dname) < 0) {
vshError(ctl, "%s", _("missing argument"));
goto out;
}
if (vshCommandOptString(cmd, "xml", &xmlfile) < 0) {
vshError(ctl, "%s", _("malformed xml argument"));
goto out;
}
if (vshCommandOptBool(cmd, "live"))
flags |= VIR_MIGRATE_LIVE;
if (vshCommandOptBool(cmd, "p2p"))
flags |= VIR_MIGRATE_PEER2PEER;
if (vshCommandOptBool(cmd, "tunnelled"))
flags |= VIR_MIGRATE_TUNNELLED;
if (vshCommandOptBool(cmd, "persistent"))
flags |= VIR_MIGRATE_PERSIST_DEST;
if (vshCommandOptBool(cmd, "undefinesource"))
flags |= VIR_MIGRATE_UNDEFINE_SOURCE;
if (vshCommandOptBool(cmd, "suspend"))
flags |= VIR_MIGRATE_PAUSED;
if (vshCommandOptBool(cmd, "copy-storage-all"))
flags |= VIR_MIGRATE_NON_SHARED_DISK;
if (vshCommandOptBool(cmd, "copy-storage-inc"))
flags |= VIR_MIGRATE_NON_SHARED_INC;
if (vshCommandOptBool(cmd, "change-protection"))
flags |= VIR_MIGRATE_CHANGE_PROTECTION;
if (vshCommandOptBool(cmd, "unsafe"))
flags |= VIR_MIGRATE_UNSAFE;
if (xmlfile &&
virFileReadAll(xmlfile, 8192, &xml) < 0) {
vshError(ctl, _("file '%s' doesn't exist"), xmlfile);
goto out;
}
if ((flags & VIR_MIGRATE_PEER2PEER) ||
vshCommandOptBool(cmd, "direct")) {
/* For peer2peer migration or direct migration we only expect one URI
* a libvirt URI, or a hypervisor specific URI. */
if (migrateuri != NULL) {
vshError(ctl, "%s", _("migrate: Unexpected migrateuri for peer2peer/direct migration"));
goto out;
}
if (virDomainMigrateToURI2(dom, desturi, NULL, xml, flags, dname, 0) == 0)
ret = '0';
} else {
/* For traditional live migration, connect to the destination host directly. */
virConnectPtr dconn = NULL;
virDomainPtr ddom = NULL;
dconn = virConnectOpenAuth(desturi, virConnectAuthPtrDefault, 0);
if (!dconn) goto out;
ddom = virDomainMigrate2(dom, dconn, xml, flags, dname, migrateuri, 0);
if (ddom) {
virDomainFree(ddom);
ret = '0';
}
virConnectClose(dconn);
}
out:
pthread_sigmask(SIG_SETMASK, &oldsigmask, NULL);
out_sig:
if (dom) virDomainFree(dom);
VIR_FREE(xml);
ignore_value(safewrite(data->writefd, &ret, sizeof(ret)));
}
static void
print_job_progress(const char *label, unsigned long long remaining,
unsigned long long total)
{
int progress;
if (total == 0)
/* migration has not been started */
return;
if (remaining == 0) {
/* migration has completed */
progress = 100;
} else {
/* use float to avoid overflow */
progress = (int)(100.0 - remaining * 100.0 / total);
if (progress >= 100) {
/* migration has not completed, do not print [100 %] */
progress = 99;
}
}
/* see comments in vshError about why we must flush */
fflush(stdout);
fprintf(stderr, "\r%s: [%3d %%]", label, progress);
fflush(stderr);
}
static void
vshMigrationTimeout(vshControl *ctl,
virDomainPtr dom,
void *opaque ATTRIBUTE_UNUSED)
{
vshDebug(ctl, VSH_ERR_DEBUG, "suspending the domain, "
"since migration timed out\n");
virDomainSuspend(dom);
}
static bool
vshWatchJob(vshControl *ctl,
virDomainPtr dom,
bool verbose,
int pipe_fd,
int timeout,
jobWatchTimeoutFunc timeout_func,
void *opaque,
const char *label)
{
struct sigaction sig_action;
struct sigaction old_sig_action;
struct pollfd pollfd;
struct timeval start, curr;
virDomainJobInfo jobinfo;
int ret = -1;
char retchar;
bool functionReturn = false;
sigset_t sigmask, oldsigmask;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGINT);
intCaught = 0;
sig_action.sa_sigaction = vshCatchInt;
sig_action.sa_flags = SA_SIGINFO;
sigemptyset(&sig_action.sa_mask);
sigaction(SIGINT, &sig_action, &old_sig_action);
pollfd.fd = pipe_fd;
pollfd.events = POLLIN;
pollfd.revents = 0;
GETTIMEOFDAY(&start);
while (1) {
repoll:
ret = poll(&pollfd, 1, 500);
if (ret > 0) {
if (pollfd.revents & POLLIN &&
saferead(pipe_fd, &retchar, sizeof(retchar)) > 0 &&
retchar == '0') {
if (verbose) {
/* print [100 %] */
print_job_progress(label, 0, 1);
}
break;
}
goto cleanup;
}
if (ret < 0) {
if (errno == EINTR) {
if (intCaught) {
virDomainAbortJob(dom);
intCaught = 0;
} else {
goto repoll;
}
}
goto cleanup;
}
GETTIMEOFDAY(&curr);
if (timeout && (((int)(curr.tv_sec - start.tv_sec) * 1000 +
(int)(curr.tv_usec - start.tv_usec) / 1000) >
timeout * 1000)) {
/* suspend the domain when migration timeouts. */
vshDebug(ctl, VSH_ERR_DEBUG, "%s timeout", label);
if (timeout_func)
(timeout_func)(ctl, dom, opaque);
timeout = 0;
}
if (verbose) {
pthread_sigmask(SIG_BLOCK, &sigmask, &oldsigmask);
ret = virDomainGetJobInfo(dom, &jobinfo);
pthread_sigmask(SIG_SETMASK, &oldsigmask, NULL);
if (ret == 0)
print_job_progress(label, jobinfo.dataRemaining,
jobinfo.dataTotal);
}
}
functionReturn = true;
cleanup:
sigaction(SIGINT, &old_sig_action, NULL);
return functionReturn;
}
static bool
cmdMigrate(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
int p[2] = {-1, -1};
virThread workerThread;
bool verbose = false;
bool functionReturn = false;
int timeout = 0;
bool live_flag = false;
vshCtrlData data;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptBool(cmd, "verbose"))
verbose = true;
if (vshCommandOptBool(cmd, "live"))
live_flag = true;
if (vshCommandOptInt(cmd, "timeout", &timeout) > 0) {
if (! live_flag) {
vshError(ctl, "%s",
_("migrate: Unexpected timeout for offline migration"));
goto cleanup;
}
if (timeout < 1) {
vshError(ctl, "%s", _("migrate: Invalid timeout"));
goto cleanup;
}
/* Ensure that we can multiply by 1000 without overflowing. */
if (timeout > INT_MAX / 1000) {
vshError(ctl, "%s", _("migrate: Timeout is too big"));
goto cleanup;
}
}
if (pipe(p) < 0)
goto cleanup;
data.ctl = ctl;
data.cmd = cmd;
data.writefd = p[1];
if (virThreadCreate(&workerThread,
true,
doMigrate,
&data) < 0)
goto cleanup;
functionReturn = vshWatchJob(ctl, dom, verbose, p[0], timeout,
vshMigrationTimeout, NULL, _("Migration"));
virThreadJoin(&workerThread);
cleanup:
virDomainFree(dom);
VIR_FORCE_CLOSE(p[0]);
VIR_FORCE_CLOSE(p[1]);
return functionReturn;
}
/*
* "migrate-setmaxdowntime" command
*/
static const vshCmdInfo info_migrate_setmaxdowntime[] = {
{"help", N_("set maximum tolerable downtime")},
{"desc", N_("Set maximum tolerable downtime of a domain which is being live-migrated to another host.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_migrate_setmaxdowntime[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"downtime", VSH_OT_INT, VSH_OFLAG_REQ, N_("maximum tolerable downtime (in milliseconds) for migration")},
{NULL, 0, 0, NULL}
};
static bool
cmdMigrateSetMaxDowntime(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
long long downtime = 0;
bool ret = false;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptLongLong(cmd, "downtime", &downtime) < 0 ||
downtime < 1) {
vshError(ctl, "%s", _("migrate: Invalid downtime"));
goto done;
}
if (virDomainMigrateSetMaxDowntime(dom, downtime, 0))
goto done;
ret = true;
done:
virDomainFree(dom);
return ret;
}
/*
* "migrate-setspeed" command
*/
static const vshCmdInfo info_migrate_setspeed[] = {
{"help", N_("Set the maximum migration bandwidth")},
{"desc", N_("Set the maximum migration bandwidth (in MiB/s) for a domain "
"which is being migrated to another host.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_migrate_setspeed[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"bandwidth", VSH_OT_INT, VSH_OFLAG_REQ,
N_("migration bandwidth limit in MiB/s")},
{NULL, 0, 0, NULL}
};
static bool
cmdMigrateSetMaxSpeed(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
unsigned long bandwidth = 0;
bool ret = false;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptUL(cmd, "bandwidth", &bandwidth) < 0) {
vshError(ctl, "%s", _("migrate: Invalid bandwidth"));
goto done;
}
if (virDomainMigrateSetMaxSpeed(dom, bandwidth, 0) < 0)
goto done;
ret = true;
done:
virDomainFree(dom);
return ret;
}
/*
* "migrate-getspeed" command
*/
static const vshCmdInfo info_migrate_getspeed[] = {
{"help", N_("Get the maximum migration bandwidth")},
{"desc", N_("Get the maximum migration bandwidth (in MiB/s) for a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_migrate_getspeed[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdMigrateGetMaxSpeed(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
unsigned long bandwidth;
bool ret = false;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (virDomainMigrateGetMaxSpeed(dom, &bandwidth, 0) < 0)
goto done;
vshPrint(ctl, "%lu\n", bandwidth);
ret = true;
done:
virDomainFree(dom);
return ret;
}
typedef enum {
VSH_CMD_BLOCK_JOB_ABORT = 0,
VSH_CMD_BLOCK_JOB_INFO = 1,
VSH_CMD_BLOCK_JOB_SPEED = 2,
VSH_CMD_BLOCK_JOB_PULL = 3,
VSH_CMD_BLOCK_JOB_COPY = 4,
} vshCmdBlockJobMode;
static int
blockJobImpl(vshControl *ctl, const vshCmd *cmd,
virDomainBlockJobInfoPtr info, int mode,
virDomainPtr *pdom)
{
virDomainPtr dom = NULL;
const char *name, *path;
unsigned long bandwidth = 0;
int ret = -1;
const char *base = NULL;
unsigned int flags = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
goto cleanup;
if (vshCommandOptString(cmd, "path", &path) < 0)
goto cleanup;
if (vshCommandOptUL(cmd, "bandwidth", &bandwidth) < 0) {
vshError(ctl, "%s", _("bandwidth must be a number"));
goto cleanup;
}
switch ((vshCmdBlockJobMode) mode) {
case VSH_CMD_BLOCK_JOB_ABORT:
if (vshCommandOptBool(cmd, "async"))
flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC;
if (vshCommandOptBool(cmd, "pivot"))
flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT;
ret = virDomainBlockJobAbort(dom, path, flags);
break;
case VSH_CMD_BLOCK_JOB_INFO:
ret = virDomainGetBlockJobInfo(dom, path, info, 0);
break;
case VSH_CMD_BLOCK_JOB_SPEED:
ret = virDomainBlockJobSetSpeed(dom, path, bandwidth, 0);
break;
case VSH_CMD_BLOCK_JOB_PULL:
if (vshCommandOptString(cmd, "base", &base) < 0)
goto cleanup;
if (base)
ret = virDomainBlockRebase(dom, path, base, bandwidth, 0);
else
ret = virDomainBlockPull(dom, path, bandwidth, 0);
break;
case VSH_CMD_BLOCK_JOB_COPY:
flags |= VIR_DOMAIN_BLOCK_REBASE_COPY;
if (vshCommandOptBool(cmd, "shallow"))
flags |= VIR_DOMAIN_BLOCK_REBASE_SHALLOW;
if (vshCommandOptBool(cmd, "reuse-external"))
flags |= VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT;
if (vshCommandOptBool(cmd, "raw"))
flags |= VIR_DOMAIN_BLOCK_REBASE_COPY_RAW;
if (vshCommandOptString(cmd, "dest", &base) < 0)
goto cleanup;
ret = virDomainBlockRebase(dom, path, base, bandwidth, flags);
}
cleanup:
if (pdom && ret == 0)
*pdom = dom;
else if (dom)
virDomainFree(dom);
return ret;
}
/*
* "blockcopy" command
*/
static const vshCmdInfo info_block_copy[] = {
{"help", N_("Start a block copy operation.")},
{"desc", N_("Populate a disk from its backing image.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_block_copy[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"path", VSH_OT_DATA, VSH_OFLAG_REQ, N_("fully-qualified path of disk")},
{"dest", VSH_OT_DATA, VSH_OFLAG_REQ, N_("path of the copy to create")},
{"bandwidth", VSH_OT_DATA, VSH_OFLAG_NONE, N_("bandwidth limit in MiB/s")},
{"shallow", VSH_OT_BOOL, 0, N_("make the copy share a backing chain")},
{"reuse-external", VSH_OT_BOOL, 0, N_("reuse existing destination")},
{"raw", VSH_OT_BOOL, 0, N_("use raw destination file")},
{"wait", VSH_OT_BOOL, 0, N_("wait for job to reach mirroring phase")},
{"verbose", VSH_OT_BOOL, 0, N_("with --wait, display the progress")},
{"timeout", VSH_OT_INT, VSH_OFLAG_NONE,
N_("with --wait, abort if copy exceeds timeout (in seconds)")},
{"pivot", VSH_OT_BOOL, 0, N_("with --wait, pivot when mirroring starts")},
{"finish", VSH_OT_BOOL, 0, N_("with --wait, quit when mirroring starts")},
{"async", VSH_OT_BOOL, 0,
N_("with --wait, don't wait for cancel to finish")},
{NULL, 0, 0, NULL}
};
static bool
cmdBlockCopy(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
bool blocking = vshCommandOptBool(cmd, "wait");
bool verbose = vshCommandOptBool(cmd, "verbose");
bool pivot = vshCommandOptBool(cmd, "pivot");
bool finish = vshCommandOptBool(cmd, "finish");
int timeout = 0;
struct sigaction sig_action;
struct sigaction old_sig_action;
sigset_t sigmask;
struct timeval start;
struct timeval curr;
const char *path = NULL;
bool quit = false;
int abort_flags = 0;
if (blocking) {
if (pivot && finish) {
vshError(ctl, "%s", _("cannot mix --pivot and --finish"));
return false;
}
if (vshCommandOptInt(cmd, "timeout", &timeout) > 0) {
if (timeout < 1) {
vshError(ctl, "%s", _("migrate: Invalid timeout"));
return false;
}
/* Ensure that we can multiply by 1000 without overflowing. */
if (timeout > INT_MAX / 1000) {
vshError(ctl, "%s", _("migrate: Timeout is too big"));
return false;
}
}
if (vshCommandOptString(cmd, "path", &path) < 0)
return false;
if (vshCommandOptBool(cmd, "async"))
abort_flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGINT);
intCaught = 0;
sig_action.sa_sigaction = vshCatchInt;
sig_action.sa_flags = SA_SIGINFO;
sigemptyset(&sig_action.sa_mask);
sigaction(SIGINT, &sig_action, &old_sig_action);
GETTIMEOFDAY(&start);
} else if (verbose || vshCommandOptBool(cmd, "timeout") ||
vshCommandOptBool(cmd, "async") || pivot || finish) {
vshError(ctl, "%s", _("blocking control options require --wait"));
return false;
}
if (blockJobImpl(ctl, cmd, NULL, VSH_CMD_BLOCK_JOB_COPY, &dom) < 0)
goto cleanup;
if (!blocking) {
vshPrint(ctl, "%s", _("Block Copy started"));
ret = true;
goto cleanup;
}
while (blocking) {
virDomainBlockJobInfo info;
int result = virDomainGetBlockJobInfo(dom, path, &info, 0);
if (result <= 0) {
vshError(ctl, _("failed to query job for disk %s"), path);
goto cleanup;
}
if (verbose)
print_job_progress(_("Block Copy"), info.end - info.cur, info.end);
if (info.cur == info.end)
break;
GETTIMEOFDAY(&curr);
if (intCaught || (timeout &&
(((int)(curr.tv_sec - start.tv_sec) * 1000 +
(int)(curr.tv_usec - start.tv_usec) / 1000) >
timeout * 1000))) {
vshDebug(ctl, VSH_ERR_DEBUG,
intCaught ? "interrupted" : "timeout");
intCaught = 0;
timeout = 0;
quit = true;
if (virDomainBlockJobAbort(dom, path, abort_flags) < 0) {
vshError(ctl, _("failed to abort job for disk %s"), path);
goto cleanup;
}
if (abort_flags & VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC)
break;
} else {
usleep(500 * 1000);
}
}
if (pivot) {
abort_flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT;
if (virDomainBlockJobAbort(dom, path, abort_flags) < 0) {
vshError(ctl, _("failed to pivot job for disk %s"), path);
goto cleanup;
}
} else if (finish && virDomainBlockJobAbort(dom, path, abort_flags) < 0) {
vshError(ctl, _("failed to finish job for disk %s"), path);
goto cleanup;
}
vshPrint(ctl, "\n%s",
quit ? _("Copy aborted") :
pivot ? _("Successfully pivoted") :
finish ? _("Successfully copied") :
_("Now in mirroring phase"));
ret = true;
cleanup:
if (dom)
virDomainFree(dom);
if (blocking)
sigaction(SIGINT, &old_sig_action, NULL);
return ret;
}
/*
* "blockpull" command
*/
static const vshCmdInfo info_block_pull[] = {
{"help", N_("Populate a disk from its backing image.")},
{"desc", N_("Populate a disk from its backing image.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_block_pull[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"path", VSH_OT_DATA, VSH_OFLAG_REQ, N_("fully-qualified path of disk")},
{"bandwidth", VSH_OT_DATA, VSH_OFLAG_NONE, N_("bandwidth limit in MiB/s")},
{"base", VSH_OT_DATA, VSH_OFLAG_NONE,
N_("path of backing file in chain for a partial pull")},
{"wait", VSH_OT_BOOL, 0, N_("wait for job to finish")},
{"verbose", VSH_OT_BOOL, 0, N_("with --wait, display the progress")},
{"timeout", VSH_OT_INT, VSH_OFLAG_NONE,
N_("with --wait, abort if pull exceeds timeout (in seconds)")},
{"async", VSH_OT_BOOL, 0,
N_("with --wait, don't wait for cancel to finish")},
{NULL, 0, 0, NULL}
};
static bool
cmdBlockPull(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
bool blocking = vshCommandOptBool(cmd, "wait");
bool verbose = vshCommandOptBool(cmd, "verbose");
int timeout = 0;
struct sigaction sig_action;
struct sigaction old_sig_action;
sigset_t sigmask;
struct timeval start;
struct timeval curr;
const char *path = NULL;
bool quit = false;
int abort_flags = 0;
if (blocking) {
if (vshCommandOptInt(cmd, "timeout", &timeout) > 0) {
if (timeout < 1) {
vshError(ctl, "%s", _("invalid timeout"));
return false;
}
/* Ensure that we can multiply by 1000 without overflowing. */
if (timeout > INT_MAX / 1000) {
vshError(ctl, "%s", _("timeout is too big"));
return false;
}
}
if (vshCommandOptString(cmd, "path", &path) < 0)
return false;
if (vshCommandOptBool(cmd, "async"))
abort_flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGINT);
intCaught = 0;
sig_action.sa_sigaction = vshCatchInt;
sig_action.sa_flags = SA_SIGINFO;
sigemptyset(&sig_action.sa_mask);
sigaction(SIGINT, &sig_action, &old_sig_action);
GETTIMEOFDAY(&start);
} else if (verbose || vshCommandOptBool(cmd, "timeout") ||
vshCommandOptBool(cmd, "async")) {
vshError(ctl, "%s", _("blocking control options require --wait"));
return false;
}
if (blockJobImpl(ctl, cmd, NULL, VSH_CMD_BLOCK_JOB_PULL, &dom) < 0)
goto cleanup;
if (!blocking) {
vshPrint(ctl, "%s", _("Block Pull started"));
ret = true;
goto cleanup;
}
while (blocking) {
virDomainBlockJobInfo info;
int result = virDomainGetBlockJobInfo(dom, path, &info, 0);
if (result < 0) {
vshError(ctl, _("failed to query job for disk %s"), path);
goto cleanup;
}
if (result == 0)
break;
if (verbose)
print_job_progress(_("Block Pull"), info.end - info.cur, info.end);
GETTIMEOFDAY(&curr);
if (intCaught || (timeout &&
(((int)(curr.tv_sec - start.tv_sec) * 1000 +
(int)(curr.tv_usec - start.tv_usec) / 1000) >
timeout * 1000))) {
vshDebug(ctl, VSH_ERR_DEBUG,
intCaught ? "interrupted" : "timeout");
intCaught = 0;
timeout = 0;
quit = true;
if (virDomainBlockJobAbort(dom, path, abort_flags) < 0) {
vshError(ctl, _("failed to abort job for disk %s"), path);
goto cleanup;
}
if (abort_flags & VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC)
break;
} else {
usleep(500 * 1000);
}
}
if (verbose && !quit) {
/* printf [100 %] */
print_job_progress(_("Block Pull"), 0, 1);
}
vshPrint(ctl, "\n%s", quit ? _("Pull aborted") : _("Pull complete"));
ret = true;
cleanup:
if (dom)
virDomainFree(dom);
if (blocking)
sigaction(SIGINT, &old_sig_action, NULL);
return ret;
}
/*
* "blockjob" command
*/
static const vshCmdInfo info_block_job[] = {
{"help", N_("Manage active block operations")},
{"desc", N_("Query, adjust speed, or cancel active block operations.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_block_job[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"path", VSH_OT_DATA, VSH_OFLAG_REQ, N_("fully-qualified path of disk")},
{"abort", VSH_OT_BOOL, VSH_OFLAG_NONE,
N_("abort the active job on the specified disk")},
{"async", VSH_OT_BOOL, VSH_OFLAG_NONE,
N_("don't wait for --abort to complete")},
{"pivot", VSH_OT_BOOL, VSH_OFLAG_NONE,
N_("conclude and pivot a copy job")},
{"info", VSH_OT_BOOL, VSH_OFLAG_NONE,
N_("get active job information for the specified disk")},
{"bandwidth", VSH_OT_DATA, VSH_OFLAG_NONE,
N_("set the Bandwidth limit in MiB/s")},
{NULL, 0, 0, NULL}
};
static bool
cmdBlockJob(vshControl *ctl, const vshCmd *cmd)
{
int mode;
virDomainBlockJobInfo info;
const char *type;
int ret;
bool abortMode = (vshCommandOptBool(cmd, "abort") ||
vshCommandOptBool(cmd, "async") ||
vshCommandOptBool(cmd, "pivot"));
bool infoMode = vshCommandOptBool(cmd, "info");
bool bandwidth = vshCommandOptBool(cmd, "bandwidth");
if (abortMode + infoMode + bandwidth > 1) {
vshError(ctl, "%s",
_("conflict between --abort, --info, and --bandwidth modes"));
return false;
}
if (abortMode)
mode = VSH_CMD_BLOCK_JOB_ABORT;
else if (bandwidth)
mode = VSH_CMD_BLOCK_JOB_SPEED;
else
mode = VSH_CMD_BLOCK_JOB_INFO;
ret = blockJobImpl(ctl, cmd, &info, mode, NULL);
if (ret < 0)
return false;
if (ret == 0 || mode != VSH_CMD_BLOCK_JOB_INFO)
return true;
switch (info.type) {
case VIR_DOMAIN_BLOCK_JOB_TYPE_PULL:
type = _("Block Pull");
break;
case VIR_DOMAIN_BLOCK_JOB_TYPE_COPY:
type = _("Block Copy");
break;
default:
type = _("Unknown job");
break;
}
print_job_progress(type, info.end - info.cur, info.end);
if (info.bandwidth != 0)
vshPrint(ctl, _(" Bandwidth limit: %lu MiB/s\n"), info.bandwidth);
return true;
}
/*
* "blockresize" command
*/
static const vshCmdInfo info_block_resize[] = {
{"help", N_("Resize block device of domain.")},
{"desc", N_("Resize block device of domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_block_resize[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"path", VSH_OT_DATA, VSH_OFLAG_REQ,
N_("Fully-qualified path of block device")},
{"size", VSH_OT_INT, VSH_OFLAG_REQ,
N_("New size of the block device, as scaled integer (default KiB)")},
{NULL, 0, 0, NULL}
};
static bool
cmdBlockResize(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *path = NULL;
unsigned long long size = 0;
unsigned int flags = 0;
int ret = false;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "path", (const char **) &path) < 0) {
vshError(ctl, "%s", _("Path must not be empty"));
return false;
}
if (vshCommandOptScaledInt(cmd, "size", &size, 1024, ULLONG_MAX) < 0) {
vshError(ctl, "%s", _("Unable to parse integer"));
return false;
}
/* Prefer the older interface of KiB. */
if (size % 1024 == 0)
size /= 1024;
else
flags |= VIR_DOMAIN_BLOCK_RESIZE_BYTES;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (virDomainBlockResize(dom, path, size, flags) < 0) {
vshError(ctl, _("Failed to resize block device '%s'"), path);
} else {
vshPrint(ctl, _("Block device '%s' is resized"), path);
ret = true;
}
virDomainFree(dom);
return ret;
}
/*
* "net-autostart" command
*/
static const vshCmdInfo info_network_autostart[] = {
{"help", N_("autostart a network")},
{"desc",
N_("Configure a network to be automatically started at boot.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_autostart[] = {
{"network", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network name or uuid")},
{"disable", VSH_OT_BOOL, 0, N_("disable autostarting")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkAutostart(vshControl *ctl, const vshCmd *cmd)
{
virNetworkPtr network;
const char *name;
int autostart;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(network = vshCommandOptNetwork(ctl, cmd, &name)))
return false;
autostart = !vshCommandOptBool(cmd, "disable");
if (virNetworkSetAutostart(network, autostart) < 0) {
if (autostart)
vshError(ctl, _("failed to mark network %s as autostarted"), name);
else
vshError(ctl, _("failed to unmark network %s as autostarted"), name);
virNetworkFree(network);
return false;
}
if (autostart)
vshPrint(ctl, _("Network %s marked as autostarted\n"), name);
else
vshPrint(ctl, _("Network %s unmarked as autostarted\n"), name);
virNetworkFree(network);
return true;
}
/*
* "blkdeviotune" command
*/
static const vshCmdInfo info_blkdeviotune[] = {
{"help", N_("Set or query a block device I/O tuning parameters.")},
{"desc", N_("Set or query disk I/O parameters such as block throttling.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_blkdeviotune[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"device", VSH_OT_DATA, VSH_OFLAG_REQ, N_("block device")},
{"total_bytes_sec", VSH_OT_ALIAS, 0, "total-bytes-sec"},
{"total-bytes-sec", VSH_OT_INT, VSH_OFLAG_NONE,
N_("total throughput limit in bytes per second")},
{"read_bytes_sec", VSH_OT_ALIAS, 0, "read-bytes-sec"},
{"read-bytes-sec", VSH_OT_INT, VSH_OFLAG_NONE,
N_("read throughput limit in bytes per second")},
{"write_bytes_sec", VSH_OT_ALIAS, 0, "write-bytes-sec"},
{"write-bytes-sec", VSH_OT_INT, VSH_OFLAG_NONE,
N_("write throughput limit in bytes per second")},
{"total_iops_sec", VSH_OT_ALIAS, 0, "total-iops-sec"},
{"total-iops-sec", VSH_OT_INT, VSH_OFLAG_NONE,
N_("total I/O operations limit per second")},
{"read_iops_sec", VSH_OT_ALIAS, 0, "read-iops-sec"},
{"read-iops-sec", VSH_OT_INT, VSH_OFLAG_NONE,
N_("read I/O operations limit per second")},
{"write_iops_sec", VSH_OT_ALIAS, 0, "write-iops-sec"},
{"write-iops-sec", VSH_OT_INT, VSH_OFLAG_NONE,
N_("write I/O operations limit per second")},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"live", VSH_OT_BOOL, 0, N_("affect running domain")},
{"current", VSH_OT_BOOL, 0, N_("affect current domain")},
{NULL, 0, 0, NULL}
};
static bool
cmdBlkdeviotune(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
const char *name, *disk;
unsigned long long total_bytes_sec = 0, read_bytes_sec = 0, write_bytes_sec = 0;
unsigned long long total_iops_sec = 0, read_iops_sec = 0, write_iops_sec = 0;
int nparams = 0;
virTypedParameterPtr params = NULL;
unsigned int flags = 0, i = 0;
int rv = 0;
bool current = vshCommandOptBool(cmd, "current");
bool config = vshCommandOptBool(cmd, "config");
bool live = vshCommandOptBool(cmd, "live");
bool ret = false;
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
}
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
goto cleanup;
if (vshCommandOptString(cmd, "device", &disk) < 0)
goto cleanup;
if ((rv = vshCommandOptULongLong(cmd, "total-bytes-sec",
&total_bytes_sec)) < 0) {
vshError(ctl, "%s",
_("Unable to parse integer parameter"));
goto cleanup;
} else if (rv > 0) {
nparams++;
}
if ((rv = vshCommandOptULongLong(cmd, "read-bytes-sec",
&read_bytes_sec)) < 0) {
vshError(ctl, "%s",
_("Unable to parse integer parameter"));
goto cleanup;
} else if (rv > 0) {
nparams++;
}
if ((rv = vshCommandOptULongLong(cmd, "write-bytes-sec",
&write_bytes_sec)) < 0) {
vshError(ctl, "%s",
_("Unable to parse integer parameter"));
goto cleanup;
} else if (rv > 0) {
nparams++;
}
if ((rv = vshCommandOptULongLong(cmd, "total-iops-sec",
&total_iops_sec)) < 0) {
vshError(ctl, "%s",
_("Unable to parse integer parameter"));
goto cleanup;
} else if (rv > 0) {
nparams++;
}
if ((rv = vshCommandOptULongLong(cmd, "read-iops-sec",
&read_iops_sec)) < 0) {
vshError(ctl, "%s",
_("Unable to parse integer parameter"));
goto cleanup;
} else if (rv > 0) {
nparams++;
}
if ((rv = vshCommandOptULongLong(cmd, "write-iops-sec",
&write_iops_sec)) < 0) {
vshError(ctl, "%s",
_("Unable to parse integer parameter"));
goto cleanup;
} else if (rv > 0) {
nparams++;
}
if (nparams == 0) {
if (virDomainGetBlockIoTune(dom, NULL, NULL, &nparams, flags) != 0) {
vshError(ctl, "%s",
_("Unable to get number of block I/O throttle parameters"));
goto cleanup;
}
if (nparams == 0) {
ret = true;
goto cleanup;
}
params = vshCalloc(ctl, nparams, sizeof(*params));
if (virDomainGetBlockIoTune(dom, disk, params, &nparams, flags) != 0) {
vshError(ctl, "%s",
_("Unable to get block I/O throttle parameters"));
goto cleanup;
}
for (i = 0; i < nparams; i++) {
char *str = vshGetTypedParamValue(ctl, &params[i]);
vshPrint(ctl, "%-15s: %s\n", params[i].field, str);
VIR_FREE(str);
}
ret = true;
goto cleanup;
} else {
/* Set the block I/O throttle, match by opt since parameters can be 0 */
params = vshCalloc(ctl, nparams, sizeof(*params));
i = 0;
if (i < nparams && vshCommandOptBool(cmd, "total-bytes-sec") &&
virTypedParameterAssign(&params[i++],
VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_BYTES_SEC,
VIR_TYPED_PARAM_ULLONG,
total_bytes_sec) < 0)
goto error;
if (i < nparams && vshCommandOptBool(cmd, "read-bytes-sec") &&
virTypedParameterAssign(&params[i++],
VIR_DOMAIN_BLOCK_IOTUNE_READ_BYTES_SEC,
VIR_TYPED_PARAM_ULLONG,
read_bytes_sec) < 0)
goto error;
if (i < nparams && vshCommandOptBool(cmd, "write-bytes-sec") &&
virTypedParameterAssign(&params[i++],
VIR_DOMAIN_BLOCK_IOTUNE_WRITE_BYTES_SEC,
VIR_TYPED_PARAM_ULLONG,
write_bytes_sec) < 0)
goto error;
if (i < nparams && vshCommandOptBool(cmd, "total-iops-sec") &&
virTypedParameterAssign(&params[i++],
VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_IOPS_SEC,
VIR_TYPED_PARAM_ULLONG,
total_iops_sec) < 0)
goto error;
if (i < nparams && vshCommandOptBool(cmd, "read-iops-sec") &&
virTypedParameterAssign(&params[i++],
VIR_DOMAIN_BLOCK_IOTUNE_READ_IOPS_SEC,
VIR_TYPED_PARAM_ULLONG,
read_iops_sec) < 0)
goto error;
if (i < nparams && vshCommandOptBool(cmd, "write-iops-sec") &&
virTypedParameterAssign(&params[i++],
VIR_DOMAIN_BLOCK_IOTUNE_WRITE_IOPS_SEC,
VIR_TYPED_PARAM_ULLONG,
write_iops_sec) < 0)
goto error;
if (virDomainSetBlockIoTune(dom, disk, params, nparams, flags) < 0)
goto error;
}
ret = true;
cleanup:
VIR_FREE(params);
virDomainFree(dom);
return ret;
error:
vshError(ctl, "%s", _("Unable to change block I/O throttle"));
goto cleanup;
}
/*
* "net-create" command
*/
static const vshCmdInfo info_network_create[] = {
{"help", N_("create a network from an XML file")},
{"desc", N_("Create a network.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_create[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing an XML network description")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkCreate(vshControl *ctl, const vshCmd *cmd)
{
virNetworkPtr network;
const char *from = NULL;
bool ret = true;
char *buffer;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
network = virNetworkCreateXML(ctl->conn, buffer);
VIR_FREE(buffer);
if (network != NULL) {
vshPrint(ctl, _("Network %s created from %s\n"),
virNetworkGetName(network), from);
virNetworkFree(network);
} else {
vshError(ctl, _("Failed to create network from %s"), from);
ret = false;
}
return ret;
}
/*
* "net-define" command
*/
static const vshCmdInfo info_network_define[] = {
{"help", N_("define (but don't start) a network from an XML file")},
{"desc", N_("Define a network.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_define[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing an XML network description")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkDefine(vshControl *ctl, const vshCmd *cmd)
{
virNetworkPtr network;
const char *from = NULL;
bool ret = true;
char *buffer;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
network = virNetworkDefineXML(ctl->conn, buffer);
VIR_FREE(buffer);
if (network != NULL) {
vshPrint(ctl, _("Network %s defined from %s\n"),
virNetworkGetName(network), from);
virNetworkFree(network);
} else {
vshError(ctl, _("Failed to define network from %s"), from);
ret = false;
}
return ret;
}
/*
* "net-destroy" command
*/
static const vshCmdInfo info_network_destroy[] = {
{"help", N_("destroy (stop) a network")},
{"desc", N_("Forcefully stop a given network.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_destroy[] = {
{"network", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkDestroy(vshControl *ctl, const vshCmd *cmd)
{
virNetworkPtr network;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(network = vshCommandOptNetwork(ctl, cmd, &name)))
return false;
if (virNetworkDestroy(network) == 0) {
vshPrint(ctl, _("Network %s destroyed\n"), name);
} else {
vshError(ctl, _("Failed to destroy network %s"), name);
ret = false;
}
virNetworkFree(network);
return ret;
}
/*
* "net-dumpxml" command
*/
static const vshCmdInfo info_network_dumpxml[] = {
{"help", N_("network information in XML")},
{"desc", N_("Output the network information as an XML dump to stdout.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_dumpxml[] = {
{"network", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network name or uuid")},
{"inactive", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("network information of an inactive domain")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkDumpXML(vshControl *ctl, const vshCmd *cmd)
{
virNetworkPtr network;
bool ret = true;
char *dump;
unsigned int flags = 0;
int inactive;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(network = vshCommandOptNetwork(ctl, cmd, NULL)))
return false;
inactive = vshCommandOptBool(cmd, "inactive");
if (inactive)
flags |= VIR_NETWORK_XML_INACTIVE;
dump = virNetworkGetXMLDesc(network, flags);
if (dump != NULL) {
vshPrint(ctl, "%s", dump);
VIR_FREE(dump);
} else {
ret = false;
}
virNetworkFree(network);
return ret;
}
/*
* "net-info" command
*/
static const vshCmdInfo info_network_info[] = {
{"help", N_("network information")},
{"desc", N_("Returns basic information about the network")},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_info[] = {
{"network", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkInfo(vshControl *ctl, const vshCmd *cmd)
{
virNetworkPtr network;
char uuid[VIR_UUID_STRING_BUFLEN];
int autostart;
int persistent = -1;
int active = -1;
char *bridge = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(network = vshCommandOptNetwork(ctl, cmd, NULL)))
return false;
vshPrint(ctl, "%-15s %s\n", _("Name"), virNetworkGetName(network));
if (virNetworkGetUUIDString(network, uuid) == 0)
vshPrint(ctl, "%-15s %s\n", _("UUID"), uuid);
active = virNetworkIsActive(network);
if (active >= 0)
vshPrint(ctl, "%-15s %s\n", _("Active:"), active? _("yes") : _("no"));
persistent = virNetworkIsPersistent(network);
if (persistent < 0)
vshPrint(ctl, "%-15s %s\n", _("Persistent:"), _("unknown"));
else
vshPrint(ctl, "%-15s %s\n", _("Persistent:"), persistent ? _("yes") : _("no"));
if (virNetworkGetAutostart(network, &autostart) < 0)
vshPrint(ctl, "%-15s %s\n", _("Autostart:"), _("no autostart"));
else
vshPrint(ctl, "%-15s %s\n", _("Autostart:"), autostart ? _("yes") : _("no"));
bridge = virNetworkGetBridgeName(network);
if (bridge)
vshPrint(ctl, "%-15s %s\n", _("Bridge:"), bridge);
VIR_FREE(bridge);
virNetworkFree(network);
return true;
}
/*
* "iface-edit" command
*/
static const vshCmdInfo info_interface_edit[] = {
{"help", N_("edit XML configuration for a physical host interface")},
{"desc", N_("Edit the XML configuration for a physical host interface.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_edit[] = {
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface name or MAC address")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceEdit(vshControl *ctl, const vshCmd *cmd)
{
bool ret = false;
virInterfacePtr iface = NULL;
virInterfacePtr iface_edited = NULL;
unsigned int flags = VIR_INTERFACE_XML_INACTIVE;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
iface = vshCommandOptInterface(ctl, cmd, NULL);
if (iface == NULL)
goto cleanup;
#define EDIT_GET_XML virInterfaceGetXMLDesc(iface, flags)
#define EDIT_NOT_CHANGED \
vshPrint(ctl, _("Interface %s XML configuration not changed.\n"), \
virInterfaceGetName(iface)); \
ret = true; goto edit_cleanup;
#define EDIT_DEFINE \
(iface_edited = virInterfaceDefineXML(ctl->conn, doc_edited, 0))
#define EDIT_FREE \
if (iface_edited) \
virInterfaceFree(iface_edited);
#include "virsh-edit.c"
vshPrint(ctl, _("Interface %s XML configuration edited.\n"),
virInterfaceGetName(iface_edited));
ret = true;
cleanup:
if (iface)
virInterfaceFree(iface);
if (iface_edited)
virInterfaceFree(iface_edited);
return ret;
}
/*
* "net-list" command
*/
static const vshCmdInfo info_network_list[] = {
{"help", N_("list networks")},
{"desc", N_("Returns list of networks.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_list[] = {
{"inactive", VSH_OT_BOOL, 0, N_("list inactive networks")},
{"all", VSH_OT_BOOL, 0, N_("list inactive & active networks")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
bool inactive = vshCommandOptBool(cmd, "inactive");
bool all = vshCommandOptBool(cmd, "all");
bool active = !inactive || all;
int maxactive = 0, maxinactive = 0, i;
char **activeNames = NULL, **inactiveNames = NULL;
inactive |= all;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (active) {
maxactive = virConnectNumOfNetworks(ctl->conn);
if (maxactive < 0) {
vshError(ctl, "%s", _("Failed to list active networks"));
return false;
}
if (maxactive) {
activeNames = vshMalloc(ctl, sizeof(char *) * maxactive);
if ((maxactive = virConnectListNetworks(ctl->conn, activeNames,
maxactive)) < 0) {
vshError(ctl, "%s", _("Failed to list active networks"));
VIR_FREE(activeNames);
return false;
}
qsort(&activeNames[0], maxactive, sizeof(char *), vshNameSorter);
}
}
if (inactive) {
maxinactive = virConnectNumOfDefinedNetworks(ctl->conn);
if (maxinactive < 0) {
vshError(ctl, "%s", _("Failed to list inactive networks"));
VIR_FREE(activeNames);
return false;
}
if (maxinactive) {
inactiveNames = vshMalloc(ctl, sizeof(char *) * maxinactive);
if ((maxinactive =
virConnectListDefinedNetworks(ctl->conn, inactiveNames,
maxinactive)) < 0) {
vshError(ctl, "%s", _("Failed to list inactive networks"));
VIR_FREE(activeNames);
VIR_FREE(inactiveNames);
return false;
}
qsort(&inactiveNames[0], maxinactive, sizeof(char*), vshNameSorter);
}
}
vshPrintExtra(ctl, "%-20s %-10s %s\n", _("Name"), _("State"),
_("Autostart"));
vshPrintExtra(ctl, "-----------------------------------------\n");
for (i = 0; i < maxactive; i++) {
virNetworkPtr network =
virNetworkLookupByName(ctl->conn, activeNames[i]);
const char *autostartStr;
int autostart = 0;
/* this kind of work with networks is not atomic operation */
if (!network) {
VIR_FREE(activeNames[i]);
continue;
}
if (virNetworkGetAutostart(network, &autostart) < 0)
autostartStr = _("no autostart");
else
autostartStr = autostart ? _("yes") : _("no");
vshPrint(ctl, "%-20s %-10s %-10s\n",
virNetworkGetName(network),
_("active"),
autostartStr);
virNetworkFree(network);
VIR_FREE(activeNames[i]);
}
for (i = 0; i < maxinactive; i++) {
virNetworkPtr network = virNetworkLookupByName(ctl->conn, inactiveNames[i]);
const char *autostartStr;
int autostart = 0;
/* this kind of work with networks is not atomic operation */
if (!network) {
VIR_FREE(inactiveNames[i]);
continue;
}
if (virNetworkGetAutostart(network, &autostart) < 0)
autostartStr = _("no autostart");
else
autostartStr = autostart ? _("yes") : _("no");
vshPrint(ctl, "%-20s %-10s %-10s\n",
inactiveNames[i],
_("inactive"),
autostartStr);
virNetworkFree(network);
VIR_FREE(inactiveNames[i]);
}
VIR_FREE(activeNames);
VIR_FREE(inactiveNames);
return true;
}
/*
* "net-name" command
*/
static const vshCmdInfo info_network_name[] = {
{"help", N_("convert a network UUID to network name")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_name[] = {
{"network", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkName(vshControl *ctl, const vshCmd *cmd)
{
virNetworkPtr network;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(network = vshCommandOptNetworkBy(ctl, cmd, NULL,
VSH_BYUUID)))
return false;
vshPrint(ctl, "%s\n", virNetworkGetName(network));
virNetworkFree(network);
return true;
}
/*
* "net-start" command
*/
static const vshCmdInfo info_network_start[] = {
{"help", N_("start a (previously defined) inactive network")},
{"desc", N_("Start a network.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_start[] = {
{"network", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkStart(vshControl *ctl, const vshCmd *cmd)
{
virNetworkPtr network;
bool ret = true;
const char *name = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(network = vshCommandOptNetwork(ctl, cmd, &name)))
return false;
if (virNetworkCreate(network) == 0) {
vshPrint(ctl, _("Network %s started\n"), name);
} else {
vshError(ctl, _("Failed to start network %s"), name);
ret = false;
}
virNetworkFree(network);
return ret;
}
/*
* "net-undefine" command
*/
static const vshCmdInfo info_network_undefine[] = {
{"help", N_("undefine an inactive network")},
{"desc", N_("Undefine the configuration for an inactive network.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_undefine[] = {
{"network", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkUndefine(vshControl *ctl, const vshCmd *cmd)
{
virNetworkPtr network;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(network = vshCommandOptNetwork(ctl, cmd, &name)))
return false;
if (virNetworkUndefine(network) == 0) {
vshPrint(ctl, _("Network %s has been undefined\n"), name);
} else {
vshError(ctl, _("Failed to undefine network %s"), name);
ret = false;
}
virNetworkFree(network);
return ret;
}
/*
* "net-uuid" command
*/
static const vshCmdInfo info_network_uuid[] = {
{"help", N_("convert a network name to network UUID")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_uuid[] = {
{"network", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network name")},
{NULL, 0, 0, NULL}
};
static bool
cmdNetworkUuid(vshControl *ctl, const vshCmd *cmd)
{
virNetworkPtr network;
char uuid[VIR_UUID_STRING_BUFLEN];
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(network = vshCommandOptNetworkBy(ctl, cmd, NULL,
VSH_BYNAME)))
return false;
if (virNetworkGetUUIDString(network, uuid) != -1)
vshPrint(ctl, "%s\n", uuid);
else
vshError(ctl, "%s", _("failed to get network UUID"));
virNetworkFree(network);
return true;
}
/**************************************************************************/
/*
* "iface-list" command
*/
static const vshCmdInfo info_interface_list[] = {
{"help", N_("list physical host interfaces")},
{"desc", N_("Returns list of physical host interfaces.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_list[] = {
{"inactive", VSH_OT_BOOL, 0, N_("list inactive interfaces")},
{"all", VSH_OT_BOOL, 0, N_("list inactive & active interfaces")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
bool inactive = vshCommandOptBool(cmd, "inactive");
bool all = vshCommandOptBool(cmd, "all");
bool active = !inactive || all;
int maxactive = 0, maxinactive = 0, i;
char **activeNames = NULL, **inactiveNames = NULL;
inactive |= all;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (active) {
maxactive = virConnectNumOfInterfaces(ctl->conn);
if (maxactive < 0) {
vshError(ctl, "%s", _("Failed to list active interfaces"));
return false;
}
if (maxactive) {
activeNames = vshMalloc(ctl, sizeof(char *) * maxactive);
if ((maxactive = virConnectListInterfaces(ctl->conn, activeNames,
maxactive)) < 0) {
vshError(ctl, "%s", _("Failed to list active interfaces"));
VIR_FREE(activeNames);
return false;
}
qsort(&activeNames[0], maxactive, sizeof(char *), vshNameSorter);
}
}
if (inactive) {
maxinactive = virConnectNumOfDefinedInterfaces(ctl->conn);
if (maxinactive < 0) {
vshError(ctl, "%s", _("Failed to list inactive interfaces"));
VIR_FREE(activeNames);
return false;
}
if (maxinactive) {
inactiveNames = vshMalloc(ctl, sizeof(char *) * maxinactive);
if ((maxinactive =
virConnectListDefinedInterfaces(ctl->conn, inactiveNames,
maxinactive)) < 0) {
vshError(ctl, "%s", _("Failed to list inactive interfaces"));
VIR_FREE(activeNames);
VIR_FREE(inactiveNames);
return false;
}
qsort(&inactiveNames[0], maxinactive, sizeof(char*), vshNameSorter);
}
}
vshPrintExtra(ctl, "%-20s %-10s %s\n", _("Name"), _("State"),
_("MAC Address"));
vshPrintExtra(ctl, "--------------------------------------------\n");
for (i = 0; i < maxactive; i++) {
virInterfacePtr iface =
virInterfaceLookupByName(ctl->conn, activeNames[i]);
/* this kind of work with interfaces is not atomic */
if (!iface) {
VIR_FREE(activeNames[i]);
continue;
}
vshPrint(ctl, "%-20s %-10s %s\n",
virInterfaceGetName(iface),
_("active"),
virInterfaceGetMACString(iface));
virInterfaceFree(iface);
VIR_FREE(activeNames[i]);
}
for (i = 0; i < maxinactive; i++) {
virInterfacePtr iface =
virInterfaceLookupByName(ctl->conn, inactiveNames[i]);
/* this kind of work with interfaces is not atomic */
if (!iface) {
VIR_FREE(inactiveNames[i]);
continue;
}
vshPrint(ctl, "%-20s %-10s %s\n",
virInterfaceGetName(iface),
_("inactive"),
virInterfaceGetMACString(iface));
virInterfaceFree(iface);
VIR_FREE(inactiveNames[i]);
}
VIR_FREE(activeNames);
VIR_FREE(inactiveNames);
return true;
}
/*
* "iface-name" command
*/
static const vshCmdInfo info_interface_name[] = {
{"help", N_("convert an interface MAC address to interface name")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_name[] = {
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface mac")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceName(vshControl *ctl, const vshCmd *cmd)
{
virInterfacePtr iface;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, NULL,
VSH_BYMAC)))
return false;
vshPrint(ctl, "%s\n", virInterfaceGetName(iface));
virInterfaceFree(iface);
return true;
}
/*
* "iface-mac" command
*/
static const vshCmdInfo info_interface_mac[] = {
{"help", N_("convert an interface name to interface MAC address")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_mac[] = {
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface name")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceMAC(vshControl *ctl, const vshCmd *cmd)
{
virInterfacePtr iface;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, NULL,
VSH_BYNAME)))
return false;
vshPrint(ctl, "%s\n", virInterfaceGetMACString(iface));
virInterfaceFree(iface);
return true;
}
/*
* "iface-dumpxml" command
*/
static const vshCmdInfo info_interface_dumpxml[] = {
{"help", N_("interface information in XML")},
{"desc", N_("Output the physical host interface information as an XML dump to stdout.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_dumpxml[] = {
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface name or MAC address")},
{"inactive", VSH_OT_BOOL, 0, N_("show inactive defined XML")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceDumpXML(vshControl *ctl, const vshCmd *cmd)
{
virInterfacePtr iface;
bool ret = true;
char *dump;
unsigned int flags = 0;
bool inactive = vshCommandOptBool(cmd, "inactive");
if (inactive)
flags |= VIR_INTERFACE_XML_INACTIVE;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(iface = vshCommandOptInterface(ctl, cmd, NULL)))
return false;
dump = virInterfaceGetXMLDesc(iface, flags);
if (dump != NULL) {
vshPrint(ctl, "%s", dump);
VIR_FREE(dump);
} else {
ret = false;
}
virInterfaceFree(iface);
return ret;
}
/*
* "iface-define" command
*/
static const vshCmdInfo info_interface_define[] = {
{"help", N_("define (but don't start) a physical host interface from an XML file")},
{"desc", N_("Define a physical host interface.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_define[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing an XML interface description")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceDefine(vshControl *ctl, const vshCmd *cmd)
{
virInterfacePtr iface;
const char *from = NULL;
bool ret = true;
char *buffer;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
iface = virInterfaceDefineXML(ctl->conn, buffer, 0);
VIR_FREE(buffer);
if (iface != NULL) {
vshPrint(ctl, _("Interface %s defined from %s\n"),
virInterfaceGetName(iface), from);
virInterfaceFree(iface);
} else {
vshError(ctl, _("Failed to define interface from %s"), from);
ret = false;
}
return ret;
}
/*
* "iface-undefine" command
*/
static const vshCmdInfo info_interface_undefine[] = {
{"help", N_("undefine a physical host interface (remove it from configuration)")},
{"desc", N_("undefine an interface.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_undefine[] = {
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface name or MAC address")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceUndefine(vshControl *ctl, const vshCmd *cmd)
{
virInterfacePtr iface;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(iface = vshCommandOptInterface(ctl, cmd, &name)))
return false;
if (virInterfaceUndefine(iface) == 0) {
vshPrint(ctl, _("Interface %s undefined\n"), name);
} else {
vshError(ctl, _("Failed to undefine interface %s"), name);
ret = false;
}
virInterfaceFree(iface);
return ret;
}
/*
* "iface-start" command
*/
static const vshCmdInfo info_interface_start[] = {
{"help", N_("start a physical host interface (enable it / \"if-up\")")},
{"desc", N_("start a physical host interface.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_start[] = {
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface name or MAC address")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceStart(vshControl *ctl, const vshCmd *cmd)
{
virInterfacePtr iface;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(iface = vshCommandOptInterface(ctl, cmd, &name)))
return false;
if (virInterfaceCreate(iface, 0) == 0) {
vshPrint(ctl, _("Interface %s started\n"), name);
} else {
vshError(ctl, _("Failed to start interface %s"), name);
ret = false;
}
virInterfaceFree(iface);
return ret;
}
/*
* "iface-destroy" command
*/
static const vshCmdInfo info_interface_destroy[] = {
{"help", N_("destroy a physical host interface (disable it / \"if-down\")")},
{"desc", N_("forcefully stop a physical host interface.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_destroy[] = {
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface name or MAC address")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceDestroy(vshControl *ctl, const vshCmd *cmd)
{
virInterfacePtr iface;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(iface = vshCommandOptInterface(ctl, cmd, &name)))
return false;
if (virInterfaceDestroy(iface, 0) == 0) {
vshPrint(ctl, _("Interface %s destroyed\n"), name);
} else {
vshError(ctl, _("Failed to destroy interface %s"), name);
ret = false;
}
virInterfaceFree(iface);
return ret;
}
/*
* "iface-begin" command
*/
static const vshCmdInfo info_interface_begin[] = {
{"help", N_("create a snapshot of current interfaces settings, "
"which can be later committed (iface-commit) or "
"restored (iface-rollback)")},
{"desc", N_("Create a restore point for interfaces settings")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_begin[] = {
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceBegin(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (virInterfaceChangeBegin(ctl->conn, 0) < 0) {
vshError(ctl, "%s", _("Failed to begin network config change transaction"));
return false;
}
vshPrint(ctl, "%s", _("Network config change transaction started\n"));
return true;
}
/*
* "iface-commit" command
*/
static const vshCmdInfo info_interface_commit[] = {
{"help", N_("commit changes made since iface-begin and free restore point")},
{"desc", N_("commit changes and free restore point")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_commit[] = {
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceCommit(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (virInterfaceChangeCommit(ctl->conn, 0) < 0) {
vshError(ctl, "%s", _("Failed to commit network config change transaction"));
return false;
}
vshPrint(ctl, "%s", _("Network config change transaction committed\n"));
return true;
}
/*
* "iface-rollback" command
*/
static const vshCmdInfo info_interface_rollback[] = {
{"help", N_("rollback to previous saved configuration created via iface-begin")},
{"desc", N_("rollback to previous restore point")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_rollback[] = {
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceRollback(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (virInterfaceChangeRollback(ctl->conn, 0) < 0) {
vshError(ctl, "%s", _("Failed to rollback network config change transaction"));
return false;
}
vshPrint(ctl, "%s", _("Network config change transaction rolled back\n"));
return true;
}
/*
* "iface-bridge" command
*/
static const vshCmdInfo info_interface_bridge[] = {
{"help", N_("create a bridge device and attach an existing network device to it")},
{"desc", N_("bridge an existing network device")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_bridge[] = {
{"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("existing interface name")},
{"bridge", VSH_OT_DATA, VSH_OFLAG_REQ, N_("new bridge device name")},
{"no-stp", VSH_OT_BOOL, 0, N_("do not enable STP for this bridge")},
{"delay", VSH_OT_INT, 0,
N_("number of seconds to squelch traffic on newly connected ports")},
{"no-start", VSH_OT_BOOL, 0, N_("don't start the bridge immediately")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceBridge(vshControl *ctl, const vshCmd *cmd)
{
bool ret = false;
virInterfacePtr if_handle = NULL, br_handle = NULL;
const char *if_name, *br_name;
char *if_type = NULL, *if2_name = NULL, *delay_str = NULL;
bool stp = false, nostart = false;
unsigned int delay = 0;
char *if_xml = NULL;
xmlChar *br_xml = NULL;
int br_xml_size;
xmlDocPtr xml_doc = NULL;
xmlXPathContextPtr ctxt = NULL;
xmlNodePtr top_node, br_node, if_node, cur;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
/* Get a handle to the original device */
if (!(if_handle = vshCommandOptInterfaceBy(ctl, cmd, "interface",
&if_name, VSH_BYNAME))) {
goto cleanup;
}
/* Name for new bridge device */
if (vshCommandOptString(cmd, "bridge", &br_name) <= 0) {
vshError(ctl, "%s", _("Missing bridge device name in command"));
goto cleanup;
}
/* make sure "new" device doesn't already exist */
if ((br_handle = virInterfaceLookupByName(ctl->conn, br_name))) {
vshError(ctl, _("Network device %s already exists"), br_name);
goto cleanup;
}
/* use "no-stp" because we want "stp" to default true */
stp = !vshCommandOptBool(cmd, "no-stp");
if (vshCommandOptUInt(cmd, "delay", &delay) < 0) {
vshError(ctl, "%s", _("Unable to parse delay parameter"));
goto cleanup;
}
nostart = vshCommandOptBool(cmd, "no-start");
/* Get the original interface into an xmlDoc */
if (!(if_xml = virInterfaceGetXMLDesc(if_handle, VIR_INTERFACE_XML_INACTIVE)))
goto cleanup;
if (!(xml_doc = virXMLParseStringCtxt(if_xml,
_("(interface definition)"), &ctxt))) {
vshError(ctl, _("Failed to parse configuration of %s"), if_name);
goto cleanup;
}
top_node = ctxt->node;
/* Verify that the original device isn't already a bridge. */
if (!(if_type = virXMLPropString(top_node, "type"))) {
vshError(ctl, _("Existing device %s has no type"), if_name);
goto cleanup;
}
if (STREQ(if_type, "bridge")) {
vshError(ctl, _("Existing device %s is already a bridge"), if_name);
goto cleanup;
}
/* verify the name in the XML matches the device name */
if (!(if2_name = virXMLPropString(top_node, "name")) ||
STRNEQ(if2_name, if_name)) {
vshError(ctl, _("Interface name from config %s doesn't match given supplied name %s"),
if2_name, if_name);
goto cleanup;
}
/* Create a <bridge> node under <interface>. */
if (!(br_node = xmlNewChild(top_node, NULL, BAD_CAST "bridge", NULL))) {
vshError(ctl, "%s", _("Failed to create bridge node in xml document"));
goto cleanup;
}
/* Set stp and delay attributes in <bridge> according to the
* commandline options.
*/
if (!xmlSetProp(br_node, BAD_CAST "stp", BAD_CAST (stp ? "on" : "off"))) {
vshError(ctl, "%s", _("Failed to set stp attribute in xml document"));
goto cleanup;
}
if ((delay || stp) &&
((virAsprintf(&delay_str, "%d", delay) < 0) ||
!xmlSetProp(br_node, BAD_CAST "delay", BAD_CAST delay_str))) {
vshError(ctl, _("Failed to set bridge delay %d in xml document"), delay);
goto cleanup;
}
/* Change the type of the outer/master interface to "bridge" and the
* name to the provided bridge name.
*/
if (!xmlSetProp(top_node, BAD_CAST "type", BAD_CAST "bridge")) {
vshError(ctl, "%s", _("Failed to set bridge interface type to 'bridge' in xml document"));
goto cleanup;
}
if (!xmlSetProp(top_node, BAD_CAST "name", BAD_CAST br_name)) {
vshError(ctl, _("Failed to set master bridge interface name to '%s' in xml document"),
br_name);
goto cleanup;
}
/* Create an <interface> node under <bridge> that uses the
* original interface's type and name.
*/
if (!(if_node = xmlNewChild(br_node, NULL, BAD_CAST "interface", NULL))) {
vshError(ctl, "%s", _("Failed to create interface node under bridge node in xml document"));
goto cleanup;
}
/* set the type of the inner/slave interface to the original
* if_type, and the name to the original if_name.
*/
if (!xmlSetProp(if_node, BAD_CAST "type", BAD_CAST if_type)) {
vshError(ctl, _("Failed to set new slave interface type to '%s' in xml document"),
if_name);
goto cleanup;
}
if (!xmlSetProp(if_node, BAD_CAST "name", BAD_CAST if_name)) {
vshError(ctl, _("Failed to set new slave interface name to '%s' in xml document"),
br_name);
goto cleanup;
}
/* Cycle through all the nodes under the original <interface>,
* moving all <mac>, <bond> and <vlan> nodes down into the new
* lower level <interface>.
*/
cur = top_node->children;
while (cur) {
xmlNodePtr old = cur;
cur = cur->next;
if ((old->type == XML_ELEMENT_NODE) &&
(xmlStrEqual(old->name, BAD_CAST "mac") || /* ethernet stuff to move down */
xmlStrEqual(old->name, BAD_CAST "bond") || /* bond stuff to move down */
xmlStrEqual(old->name, BAD_CAST "vlan"))) { /* vlan stuff to move down */
xmlUnlinkNode(old);
if (!xmlAddChild(if_node, old)) {
vshError(ctl, _("Failed to move '%s' element in xml document"), old->name);
xmlFreeNode(old);
goto cleanup;
}
}
}
/* The document should now be fully converted; write it out to a string. */
xmlDocDumpMemory(xml_doc, &br_xml, &br_xml_size);
if (!br_xml || br_xml_size <= 0) {
vshError(ctl, _("Failed to format new xml document for bridge %s"), br_name);
goto cleanup;
}
/* br_xml is the new interface to define. It will automatically undefine the
* independent original interface.
*/
if (!(br_handle = virInterfaceDefineXML(ctl->conn, (char *) br_xml, 0))) {
vshError(ctl, _("Failed to define new bridge interface %s"),
br_name);
goto cleanup;
}
vshPrint(ctl, _("Created bridge %s with attached device %s\n"),
br_name, if_name);
/* start it up unless requested not to */
if (!nostart) {
if (virInterfaceCreate(br_handle, 0) < 0) {
vshError(ctl, _("Failed to start bridge interface %s"), br_name);
goto cleanup;
}
vshPrint(ctl, _("Bridge interface %s started\n"), br_name);
}
ret = true;
cleanup:
if (if_handle)
virInterfaceFree(if_handle);
if (br_handle)
virInterfaceFree(br_handle);
VIR_FREE(if_xml);
VIR_FREE(br_xml);
VIR_FREE(if_type);
VIR_FREE(if2_name);
VIR_FREE(delay_str);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml_doc);
return ret;
}
/*
* "iface-unbridge" command
*/
static const vshCmdInfo info_interface_unbridge[] = {
{"help", N_("undefine a bridge device after detaching its slave device")},
{"desc", N_("unbridge a network device")},
{NULL, NULL}
};
static const vshCmdOptDef opts_interface_unbridge[] = {
{"bridge", VSH_OT_DATA, VSH_OFLAG_REQ, N_("current bridge device name")},
{"no-start", VSH_OT_BOOL, 0,
N_("don't start the un-slaved interface immediately (not recommended)")},
{NULL, 0, 0, NULL}
};
static bool
cmdInterfaceUnbridge(vshControl *ctl, const vshCmd *cmd)
{
bool ret = false;
virInterfacePtr if_handle = NULL, br_handle = NULL;
const char *br_name;
char *if_type = NULL, *if_name = NULL;
bool nostart = false;
char *br_xml = NULL;
xmlChar *if_xml = NULL;
int if_xml_size;
xmlDocPtr xml_doc = NULL;
xmlXPathContextPtr ctxt = NULL;
xmlNodePtr top_node, br_node, if_node, cur;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
/* Get a handle to the original device */
if (!(br_handle = vshCommandOptInterfaceBy(ctl, cmd, "bridge",
&br_name, VSH_BYNAME))) {
goto cleanup;
}
nostart = vshCommandOptBool(cmd, "no-start");
/* Get the bridge xml into an xmlDoc */
if (!(br_xml = virInterfaceGetXMLDesc(br_handle, VIR_INTERFACE_XML_INACTIVE)))
goto cleanup;
if (!(xml_doc = virXMLParseStringCtxt(br_xml,
_("(bridge interface definition)"),
&ctxt))) {
vshError(ctl, _("Failed to parse configuration of %s"), br_name);
goto cleanup;
}
top_node = ctxt->node;
/* Verify that the device really is a bridge. */
if (!(if_type = virXMLPropString(top_node, "type"))) {
vshError(ctl, _("Existing device %s has no type"), br_name);
goto cleanup;
}
if (STRNEQ(if_type, "bridge")) {
vshError(ctl, _("Device %s is not a bridge"), br_name);
goto cleanup;
}
VIR_FREE(if_type);
/* verify the name in the XML matches the device name */
if (!(if_name = virXMLPropString(top_node, "name")) ||
STRNEQ(if_name, br_name)) {
vshError(ctl, _("Interface name from config %s doesn't match given supplied name %s"),
if_name, br_name);
goto cleanup;
}
VIR_FREE(if_name);
/* Find the <bridge> node under <interface>. */
if (!(br_node = virXPathNode("./bridge", ctxt))) {
vshError(ctl, "%s", _("No bridge node in xml document"));
goto cleanup;
}
if ((if_node = virXPathNode("./bridge/interface[2]", ctxt))) {
vshError(ctl, "%s", _("Multiple interfaces attached to bridge"));
goto cleanup;
}
if (!(if_node = virXPathNode("./bridge/interface", ctxt))) {
vshError(ctl, "%s", _("No interface attached to bridge"));
goto cleanup;
}
/* Change the type and name of the outer/master interface to
* the type/name of the attached slave interface.
*/
if (!(if_name = virXMLPropString(if_node, "name"))) {
vshError(ctl, _("Device attached to bridge %s has no name"), br_name);
goto cleanup;
}
if (!(if_type = virXMLPropString(if_node, "type"))) {
vshError(ctl, _("Attached device %s has no type"), if_name);
goto cleanup;
}
if (!xmlSetProp(top_node, BAD_CAST "type", BAD_CAST if_type)) {
vshError(ctl, _("Failed to set interface type to '%s' in xml document"),
if_type);
goto cleanup;
}
if (!xmlSetProp(top_node, BAD_CAST "name", BAD_CAST if_name)) {
vshError(ctl, _("Failed to set interface name to '%s' in xml document"),
if_name);
goto cleanup;
}
/* Cycle through all the nodes under the attached <interface>,
* moving all <mac>, <bond> and <vlan> nodes up into the toplevel
* <interface>.
*/
cur = if_node->children;
while (cur) {
xmlNodePtr old = cur;
cur = cur->next;
if ((old->type == XML_ELEMENT_NODE) &&
(xmlStrEqual(old->name, BAD_CAST "mac") || /* ethernet stuff to move down */
xmlStrEqual(old->name, BAD_CAST "bond") || /* bond stuff to move down */
xmlStrEqual(old->name, BAD_CAST "vlan"))) { /* vlan stuff to move down */
xmlUnlinkNode(old);
if (!xmlAddChild(top_node, old)) {
vshError(ctl, _("Failed to move '%s' element in xml document"), old->name);
xmlFreeNode(old);
goto cleanup;
}
}
}
/* The document should now be fully converted; write it out to a string. */
xmlDocDumpMemory(xml_doc, &if_xml, &if_xml_size);
if (!if_xml || if_xml_size <= 0) {
vshError(ctl, _("Failed to format new xml document for un-enslaved interface %s"),
if_name);
goto cleanup;
}
/* Destroy and Undefine the bridge device, since we otherwise
* can't safely define the unattached device.
*/
if (virInterfaceDestroy(br_handle, 0) < 0) {
vshError(ctl, _("Failed to destroy bridge interface %s"), br_name);
goto cleanup;
}
if (virInterfaceUndefine(br_handle) < 0) {
vshError(ctl, _("Failed to undefine bridge interface %s"), br_name);
goto cleanup;
}
/* if_xml is the new interface to define.
*/
if (!(if_handle = virInterfaceDefineXML(ctl->conn, (char *) if_xml, 0))) {
vshError(ctl, _("Failed to define new interface %s"), if_name);
goto cleanup;
}
vshPrint(ctl, _("Device %s un-attached from bridge %s\n"),
if_name, br_name);
/* unless requested otherwise, undefine the bridge device */
if (!nostart) {
if (virInterfaceCreate(if_handle, 0) < 0) {
vshError(ctl, _("Failed to start interface %s"), if_name);
goto cleanup;
}
vshPrint(ctl, _("Interface %s started\n"), if_name);
}
ret = true;
cleanup:
if (if_handle)
virInterfaceFree(if_handle);
if (br_handle)
virInterfaceFree(br_handle);
VIR_FREE(if_xml);
VIR_FREE(br_xml);
VIR_FREE(if_type);
VIR_FREE(if_name);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml_doc);
return ret;
}
/*
* "nwfilter-define" command
*/
static const vshCmdInfo info_nwfilter_define[] = {
{"help", N_("define or update a network filter from an XML file")},
{"desc", N_("Define a new network filter or update an existing one.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_nwfilter_define[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing an XML network filter description")},
{NULL, 0, 0, NULL}
};
static bool
cmdNWFilterDefine(vshControl *ctl, const vshCmd *cmd)
{
virNWFilterPtr nwfilter;
const char *from = NULL;
bool ret = true;
char *buffer;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
nwfilter = virNWFilterDefineXML(ctl->conn, buffer);
VIR_FREE(buffer);
if (nwfilter != NULL) {
vshPrint(ctl, _("Network filter %s defined from %s\n"),
virNWFilterGetName(nwfilter), from);
virNWFilterFree(nwfilter);
} else {
vshError(ctl, _("Failed to define network filter from %s"), from);
ret = false;
}
return ret;
}
/*
* "nwfilter-undefine" command
*/
static const vshCmdInfo info_nwfilter_undefine[] = {
{"help", N_("undefine a network filter")},
{"desc", N_("Undefine a given network filter.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_nwfilter_undefine[] = {
{"nwfilter", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network filter name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdNWFilterUndefine(vshControl *ctl, const vshCmd *cmd)
{
virNWFilterPtr nwfilter;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(nwfilter = vshCommandOptNWFilter(ctl, cmd, &name)))
return false;
if (virNWFilterUndefine(nwfilter) == 0) {
vshPrint(ctl, _("Network filter %s undefined\n"), name);
} else {
vshError(ctl, _("Failed to undefine network filter %s"), name);
ret = false;
}
virNWFilterFree(nwfilter);
return ret;
}
/*
* "nwfilter-dumpxml" command
*/
static const vshCmdInfo info_nwfilter_dumpxml[] = {
{"help", N_("network filter information in XML")},
{"desc", N_("Output the network filter information as an XML dump to stdout.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_nwfilter_dumpxml[] = {
{"nwfilter", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network filter name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdNWFilterDumpXML(vshControl *ctl, const vshCmd *cmd)
{
virNWFilterPtr nwfilter;
bool ret = true;
char *dump;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(nwfilter = vshCommandOptNWFilter(ctl, cmd, NULL)))
return false;
dump = virNWFilterGetXMLDesc(nwfilter, 0);
if (dump != NULL) {
vshPrint(ctl, "%s", dump);
VIR_FREE(dump);
} else {
ret = false;
}
virNWFilterFree(nwfilter);
return ret;
}
/*
* "nwfilter-list" command
*/
static const vshCmdInfo info_nwfilter_list[] = {
{"help", N_("list network filters")},
{"desc", N_("Returns list of network filters.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_nwfilter_list[] = {
{NULL, 0, 0, NULL}
};
static bool
cmdNWFilterList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
int numfilters, i;
char **names;
char uuid[VIR_UUID_STRING_BUFLEN];
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
numfilters = virConnectNumOfNWFilters(ctl->conn);
if (numfilters < 0) {
vshError(ctl, "%s", _("Failed to list network filters"));
return false;
}
names = vshMalloc(ctl, sizeof(char *) * numfilters);
if ((numfilters = virConnectListNWFilters(ctl->conn, names,
numfilters)) < 0) {
vshError(ctl, "%s", _("Failed to list network filters"));
VIR_FREE(names);
return false;
}
qsort(&names[0], numfilters, sizeof(char *), vshNameSorter);
vshPrintExtra(ctl, "%-36s %-20s \n", _("UUID"), _("Name"));
vshPrintExtra(ctl,
"----------------------------------------------------------------\n");
for (i = 0; i < numfilters; i++) {
virNWFilterPtr nwfilter =
virNWFilterLookupByName(ctl->conn, names[i]);
/* this kind of work with networks is not atomic operation */
if (!nwfilter) {
VIR_FREE(names[i]);
continue;
}
virNWFilterGetUUIDString(nwfilter, uuid);
vshPrint(ctl, "%-36s %-20s\n",
uuid,
virNWFilterGetName(nwfilter));
virNWFilterFree(nwfilter);
VIR_FREE(names[i]);
}
VIR_FREE(names);
return true;
}
/*
* "nwfilter-edit" command
*/
static const vshCmdInfo info_nwfilter_edit[] = {
{"help", N_("edit XML configuration for a network filter")},
{"desc", N_("Edit the XML configuration for a network filter.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_nwfilter_edit[] = {
{"nwfilter", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network filter name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdNWFilterEdit(vshControl *ctl, const vshCmd *cmd)
{
bool ret = false;
virNWFilterPtr nwfilter = NULL;
virNWFilterPtr nwfilter_edited = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
nwfilter = vshCommandOptNWFilter(ctl, cmd, NULL);
if (nwfilter == NULL)
goto cleanup;
#define EDIT_GET_XML virNWFilterGetXMLDesc(nwfilter, 0)
#define EDIT_NOT_CHANGED \
vshPrint(ctl, _("Network filter %s XML " \
"configuration not changed.\n"), \
virNWFilterGetName(nwfilter)); \
ret = true; goto edit_cleanup;
#define EDIT_DEFINE \
(nwfilter_edited = virNWFilterDefineXML(ctl->conn, doc_edited))
#define EDIT_FREE \
if (nwfilter_edited) \
virNWFilterFree(nwfilter);
#include "virsh-edit.c"
vshPrint(ctl, _("Network filter %s XML configuration edited.\n"),
virNWFilterGetName(nwfilter_edited));
ret = true;
cleanup:
if (nwfilter)
virNWFilterFree(nwfilter);
if (nwfilter_edited)
virNWFilterFree(nwfilter_edited);
return ret;
}
/**************************************************************************/
/*
* "pool-autostart" command
*/
static const vshCmdInfo info_pool_autostart[] = {
{"help", N_("autostart a pool")},
{"desc",
N_("Configure a pool to be automatically started at boot.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_autostart[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{"disable", VSH_OT_BOOL, 0, N_("disable autostarting")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolAutostart(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
const char *name;
int autostart;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", &name)))
return false;
autostart = !vshCommandOptBool(cmd, "disable");
if (virStoragePoolSetAutostart(pool, autostart) < 0) {
if (autostart)
vshError(ctl, _("failed to mark pool %s as autostarted"), name);
else
vshError(ctl, _("failed to unmark pool %s as autostarted"), name);
virStoragePoolFree(pool);
return false;
}
if (autostart)
vshPrint(ctl, _("Pool %s marked as autostarted\n"), name);
else
vshPrint(ctl, _("Pool %s unmarked as autostarted\n"), name);
virStoragePoolFree(pool);
return true;
}
/*
* "pool-create" command
*/
static const vshCmdInfo info_pool_create[] = {
{"help", N_("create a pool from an XML file")},
{"desc", N_("Create a pool.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_create[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ,
N_("file containing an XML pool description")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolCreate(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
const char *from = NULL;
bool ret = true;
char *buffer;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
pool = virStoragePoolCreateXML(ctl->conn, buffer, 0);
VIR_FREE(buffer);
if (pool != NULL) {
vshPrint(ctl, _("Pool %s created from %s\n"),
virStoragePoolGetName(pool), from);
virStoragePoolFree(pool);
} else {
vshError(ctl, _("Failed to create pool from %s"), from);
ret = false;
}
return ret;
}
/*
* "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.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_node_device_create[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ,
N_("file containing an XML description of the device")},
{NULL, 0, 0, NULL}
};
static bool
cmdNodeDeviceCreate(vshControl *ctl, const vshCmd *cmd)
{
virNodeDevicePtr dev = NULL;
const char *from = NULL;
bool ret = true;
char *buffer;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
dev = virNodeDeviceCreateXML(ctl->conn, buffer, 0);
VIR_FREE(buffer);
if (dev != NULL) {
vshPrint(ctl, _("Node device %s created from %s\n"),
virNodeDeviceGetName(dev), from);
virNodeDeviceFree(dev);
} else {
vshError(ctl, _("Failed to create node device from %s"), from);
ret = false;
}
return ret;
}
/*
* "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")},
{NULL, NULL}
};
static const vshCmdOptDef opts_node_device_destroy[] = {
{"name", VSH_OT_DATA, VSH_OFLAG_REQ,
N_("name of the device to be destroyed")},
{NULL, 0, 0, NULL}
};
static bool
cmdNodeDeviceDestroy(vshControl *ctl, const vshCmd *cmd)
{
virNodeDevicePtr dev = NULL;
bool ret = true;
const char *name = NULL;
if (!vshConnectionUsability(ctl, ctl->conn)) {
return false;
}
if (vshCommandOptString(cmd, "name", &name) <= 0)
return false;
dev = virNodeDeviceLookupByName(ctl->conn, name);
if (virNodeDeviceDestroy(dev) == 0) {
vshPrint(ctl, _("Destroyed node device '%s'\n"), name);
} else {
vshError(ctl, _("Failed to destroy node device '%s'"), name);
ret = false;
}
virNodeDeviceFree(dev);
return ret;
}
/*
* XML Building helper for pool-define-as and pool-create-as
*/
static const vshCmdOptDef opts_pool_X_as[] = {
{"name", VSH_OT_DATA, VSH_OFLAG_REQ, N_("name of the pool")},
{"print-xml", VSH_OT_BOOL, 0, N_("print XML document, but don't define/create")},
{"type", VSH_OT_DATA, VSH_OFLAG_REQ, N_("type of the pool")},
{"source-host", VSH_OT_DATA, 0, N_("source-host for underlying storage")},
{"source-path", VSH_OT_DATA, 0, N_("source path for underlying storage")},
{"source-dev", VSH_OT_DATA, 0, N_("source device for underlying storage")},
{"source-name", VSH_OT_DATA, 0, N_("source name for underlying storage")},
{"target", VSH_OT_DATA, 0, N_("target for underlying storage")},
{"source-format", VSH_OT_STRING, 0, N_("format for underlying storage")},
{NULL, 0, 0, NULL}
};
static int buildPoolXML(const vshCmd *cmd, const char **retname, char **xml) {
const char *name = NULL, *type = NULL, *srcHost = NULL, *srcPath = NULL,
*srcDev = NULL, *srcName = NULL, *srcFormat = NULL, *target = NULL;
virBuffer buf = VIR_BUFFER_INITIALIZER;
if (vshCommandOptString(cmd, "name", &name) <= 0)
goto cleanup;
if (vshCommandOptString(cmd, "type", &type) <= 0)
goto cleanup;
if (vshCommandOptString(cmd, "source-host", &srcHost) < 0 ||
vshCommandOptString(cmd, "source-path", &srcPath) < 0 ||
vshCommandOptString(cmd, "source-dev", &srcDev) < 0 ||
vshCommandOptString(cmd, "source-name", &srcName) < 0 ||
vshCommandOptString(cmd, "source-format", &srcFormat) < 0 ||
vshCommandOptString(cmd, "target", &target) < 0) {
vshError(NULL, "%s", _("missing argument"));
goto cleanup;
}
virBufferAsprintf(&buf, "<pool type='%s'>\n", type);
virBufferAsprintf(&buf, " <name>%s</name>\n", name);
if (srcHost || srcPath || srcDev || srcFormat || srcName) {
virBufferAddLit(&buf, " <source>\n");
if (srcHost)
virBufferAsprintf(&buf, " <host name='%s'/>\n", srcHost);
if (srcPath)
virBufferAsprintf(&buf, " <dir path='%s'/>\n", srcPath);
if (srcDev)
virBufferAsprintf(&buf, " <device path='%s'/>\n", srcDev);
if (srcFormat)
virBufferAsprintf(&buf, " <format type='%s'/>\n", srcFormat);
if (srcName)
virBufferAsprintf(&buf, " <name>%s</name>\n", srcName);
virBufferAddLit(&buf, " </source>\n");
}
if (target) {
virBufferAddLit(&buf, " <target>\n");
virBufferAsprintf(&buf, " <path>%s</path>\n", target);
virBufferAddLit(&buf, " </target>\n");
}
virBufferAddLit(&buf, "</pool>\n");
if (virBufferError(&buf)) {
vshPrint(ctl, "%s", _("Failed to allocate XML buffer"));
return false;
}
*xml = virBufferContentAndReset(&buf);
*retname = name;
return true;
cleanup:
virBufferFreeAndReset(&buf);
return false;
}
/*
* "pool-create-as" command
*/
static const vshCmdInfo info_pool_create_as[] = {
{"help", N_("create a pool from a set of args")},
{"desc", N_("Create a pool.")},
{NULL, NULL}
};
static bool
cmdPoolCreateAs(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
const char *name;
char *xml;
bool printXML = vshCommandOptBool(cmd, "print-xml");
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!buildPoolXML(cmd, &name, &xml))
return false;
if (printXML) {
vshPrint(ctl, "%s", xml);
VIR_FREE(xml);
} else {
pool = virStoragePoolCreateXML(ctl->conn, xml, 0);
VIR_FREE(xml);
if (pool != NULL) {
vshPrint(ctl, _("Pool %s created\n"), name);
virStoragePoolFree(pool);
} else {
vshError(ctl, _("Failed to create pool %s"), name);
return false;
}
}
return true;
}
/*
* "pool-define" command
*/
static const vshCmdInfo info_pool_define[] = {
{"help", N_("define (but don't start) a pool from an XML file")},
{"desc", N_("Define a pool.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_define[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing an XML pool description")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolDefine(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
const char *from = NULL;
bool ret = true;
char *buffer;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
pool = virStoragePoolDefineXML(ctl->conn, buffer, 0);
VIR_FREE(buffer);
if (pool != NULL) {
vshPrint(ctl, _("Pool %s defined from %s\n"),
virStoragePoolGetName(pool), from);
virStoragePoolFree(pool);
} else {
vshError(ctl, _("Failed to define pool from %s"), from);
ret = false;
}
return ret;
}
/*
* "pool-define-as" command
*/
static const vshCmdInfo info_pool_define_as[] = {
{"help", N_("define a pool from a set of args")},
{"desc", N_("Define a pool.")},
{NULL, NULL}
};
static bool
cmdPoolDefineAs(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
const char *name;
char *xml;
bool printXML = vshCommandOptBool(cmd, "print-xml");
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!buildPoolXML(cmd, &name, &xml))
return false;
if (printXML) {
vshPrint(ctl, "%s", xml);
VIR_FREE(xml);
} else {
pool = virStoragePoolDefineXML(ctl->conn, xml, 0);
VIR_FREE(xml);
if (pool != NULL) {
vshPrint(ctl, _("Pool %s defined\n"), name);
virStoragePoolFree(pool);
} else {
vshError(ctl, _("Failed to define pool %s"), name);
return false;
}
}
return true;
}
/*
* "pool-build" command
*/
static const vshCmdInfo info_pool_build[] = {
{"help", N_("build a pool")},
{"desc", N_("Build a given pool.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_build[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{"no-overwrite", VSH_OT_BOOL, 0, N_("do not overwrite an existing pool of this type")},
{"overwrite", VSH_OT_BOOL, 0, N_("overwrite any existing data")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolBuild(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
bool ret = true;
const char *name;
unsigned int flags = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", &name)))
return false;
if (vshCommandOptBool(cmd, "no-overwrite")) {
flags |= VIR_STORAGE_POOL_BUILD_NO_OVERWRITE;
}
if (vshCommandOptBool(cmd, "overwrite")) {
flags |= VIR_STORAGE_POOL_BUILD_OVERWRITE;
}
if (virStoragePoolBuild(pool, flags) == 0) {
vshPrint(ctl, _("Pool %s built\n"), name);
} else {
vshError(ctl, _("Failed to build pool %s"), name);
ret = false;
}
virStoragePoolFree(pool);
return ret;
}
/*
* "pool-destroy" command
*/
static const vshCmdInfo info_pool_destroy[] = {
{"help", N_("destroy (stop) a pool")},
{"desc",
N_("Forcefully stop a given pool. Raw data in the pool is untouched")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_destroy[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolDestroy(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", &name)))
return false;
if (virStoragePoolDestroy(pool) == 0) {
vshPrint(ctl, _("Pool %s destroyed\n"), name);
} else {
vshError(ctl, _("Failed to destroy pool %s"), name);
ret = false;
}
virStoragePoolFree(pool);
return ret;
}
/*
* "pool-delete" command
*/
static const vshCmdInfo info_pool_delete[] = {
{"help", N_("delete a pool")},
{"desc", N_("Delete a given pool.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_delete[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolDelete(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", &name)))
return false;
if (virStoragePoolDelete(pool, 0) == 0) {
vshPrint(ctl, _("Pool %s deleted\n"), name);
} else {
vshError(ctl, _("Failed to delete pool %s"), name);
ret = false;
}
virStoragePoolFree(pool);
return ret;
}
/*
* "pool-refresh" command
*/
static const vshCmdInfo info_pool_refresh[] = {
{"help", N_("refresh a pool")},
{"desc", N_("Refresh a given pool.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_refresh[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolRefresh(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", &name)))
return false;
if (virStoragePoolRefresh(pool, 0) == 0) {
vshPrint(ctl, _("Pool %s refreshed\n"), name);
} else {
vshError(ctl, _("Failed to refresh pool %s"), name);
ret = false;
}
virStoragePoolFree(pool);
return ret;
}
/*
* "pool-dumpxml" command
*/
static const vshCmdInfo info_pool_dumpxml[] = {
{"help", N_("pool information in XML")},
{"desc", N_("Output the pool information as an XML dump to stdout.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_dumpxml[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{"inactive", VSH_OT_BOOL, 0, N_("show inactive defined XML")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolDumpXML(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
bool ret = true;
bool inactive = vshCommandOptBool(cmd, "inactive");
unsigned int flags = 0;
char *dump;
if (inactive)
flags |= VIR_STORAGE_XML_INACTIVE;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", NULL)))
return false;
dump = virStoragePoolGetXMLDesc(pool, flags);
if (dump != NULL) {
vshPrint(ctl, "%s", dump);
VIR_FREE(dump);
} else {
ret = false;
}
virStoragePoolFree(pool);
return ret;
}
/*
* "pool-list" command
*/
static const vshCmdInfo info_pool_list[] = {
{"help", N_("list pools")},
{"desc", N_("Returns list of pools.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_list[] = {
{"inactive", VSH_OT_BOOL, 0, N_("list inactive pools")},
{"all", VSH_OT_BOOL, 0, N_("list inactive & active pools")},
{"details", VSH_OT_BOOL, 0, N_("display extended details for pools")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
virStoragePoolInfo info;
char **poolNames = NULL;
int i, ret;
bool functionReturn;
int numActivePools = 0, numInactivePools = 0, numAllPools = 0;
size_t stringLength = 0, nameStrLength = 0;
size_t autostartStrLength = 0, persistStrLength = 0;
size_t stateStrLength = 0, capStrLength = 0;
size_t allocStrLength = 0, availStrLength = 0;
struct poolInfoText {
char *state;
char *autostart;
char *persistent;
char *capacity;
char *allocation;
char *available;
};
struct poolInfoText *poolInfoTexts = NULL;
/* Determine the options passed by the user */
bool all = vshCommandOptBool(cmd, "all");
bool details = vshCommandOptBool(cmd, "details");
bool inactive = vshCommandOptBool(cmd, "inactive");
bool active = !inactive || all;
inactive |= all;
/* Check the connection to libvirtd daemon is still working */
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
/* Retrieve the number of active storage pools */
if (active) {
numActivePools = virConnectNumOfStoragePools(ctl->conn);
if (numActivePools < 0) {
vshError(ctl, "%s", _("Failed to list active pools"));
return false;
}
}
/* Retrieve the number of inactive storage pools */
if (inactive) {
numInactivePools = virConnectNumOfDefinedStoragePools(ctl->conn);
if (numInactivePools < 0) {
vshError(ctl, "%s", _("Failed to list inactive pools"));
return false;
}
}
/* Determine the total number of pools to list */
numAllPools = numActivePools + numInactivePools;
/* Allocate memory for arrays of storage pool names and info */
poolNames = vshCalloc(ctl, numAllPools, sizeof(*poolNames));
poolInfoTexts =
vshCalloc(ctl, numAllPools, sizeof(*poolInfoTexts));
/* Retrieve a list of active storage pool names */
if (active) {
if (virConnectListStoragePools(ctl->conn,
poolNames, numActivePools) < 0) {
vshError(ctl, "%s", _("Failed to list active pools"));
VIR_FREE(poolInfoTexts);
VIR_FREE(poolNames);
return false;
}
}
/* Add the inactive storage pools to the end of the name list */
if (inactive) {
if (virConnectListDefinedStoragePools(ctl->conn,
&poolNames[numActivePools],
numInactivePools) < 0) {
vshError(ctl, "%s", _("Failed to list inactive pools"));
VIR_FREE(poolInfoTexts);
VIR_FREE(poolNames);
return false;
}
}
/* Sort the storage pool names */
qsort(poolNames, numAllPools, sizeof(*poolNames), vshNameSorter);
/* Collect the storage pool information for display */
for (i = 0; i < numAllPools; i++) {
int autostart = 0, persistent = 0;
/* Retrieve a pool object, looking it up by name */
virStoragePoolPtr pool = virStoragePoolLookupByName(ctl->conn,
poolNames[i]);
if (!pool) {
VIR_FREE(poolNames[i]);
continue;
}
/* Retrieve the autostart status of the pool */
if (virStoragePoolGetAutostart(pool, &autostart) < 0)
poolInfoTexts[i].autostart = vshStrdup(ctl, _("no autostart"));
else
poolInfoTexts[i].autostart = vshStrdup(ctl, autostart ?
_("yes") : _("no"));
/* Retrieve the persistence status of the pool */
if (details) {
persistent = virStoragePoolIsPersistent(pool);
vshDebug(ctl, VSH_ERR_DEBUG, "Persistent flag value: %d\n",
persistent);
if (persistent < 0)
poolInfoTexts[i].persistent = vshStrdup(ctl, _("unknown"));
else
poolInfoTexts[i].persistent = vshStrdup(ctl, persistent ?
_("yes") : _("no"));
/* Keep the length of persistent string if longest so far */
stringLength = strlen(poolInfoTexts[i].persistent);
if (stringLength > persistStrLength)
persistStrLength = stringLength;
}
/* Collect further extended information about the pool */
if (virStoragePoolGetInfo(pool, &info) != 0) {
/* Something went wrong retrieving pool info, cope with it */
vshError(ctl, "%s", _("Could not retrieve pool information"));
poolInfoTexts[i].state = vshStrdup(ctl, _("unknown"));
if (details) {
poolInfoTexts[i].capacity = vshStrdup(ctl, _("unknown"));
poolInfoTexts[i].allocation = vshStrdup(ctl, _("unknown"));
poolInfoTexts[i].available = vshStrdup(ctl, _("unknown"));
}
} else {
/* Decide which state string to display */
if (details) {
/* --details option was specified, we're using detailed state
* strings */
switch (info.state) {
case VIR_STORAGE_POOL_INACTIVE:
poolInfoTexts[i].state = vshStrdup(ctl, _("inactive"));
break;
case VIR_STORAGE_POOL_BUILDING:
poolInfoTexts[i].state = vshStrdup(ctl, _("building"));
break;
case VIR_STORAGE_POOL_RUNNING:
poolInfoTexts[i].state = vshStrdup(ctl, _("running"));
break;
case VIR_STORAGE_POOL_DEGRADED:
poolInfoTexts[i].state = vshStrdup(ctl, _("degraded"));
break;
case VIR_STORAGE_POOL_INACCESSIBLE:
poolInfoTexts[i].state = vshStrdup(ctl, _("inaccessible"));
break;
}
/* Create the pool size related strings */
if (info.state == VIR_STORAGE_POOL_RUNNING ||
info.state == VIR_STORAGE_POOL_DEGRADED) {
double val;
const char *unit;
/* Create the capacity output string */
val = prettyCapacity(info.capacity, &unit);
ret = virAsprintf(&poolInfoTexts[i].capacity,
"%.2lf %s", val, unit);
if (ret < 0) {
/* An error occurred creating the string, return */
goto asprintf_failure;
}
/* Create the allocation output string */
val = prettyCapacity(info.allocation, &unit);
ret = virAsprintf(&poolInfoTexts[i].allocation,
"%.2lf %s", val, unit);
if (ret < 0) {
/* An error occurred creating the string, return */
goto asprintf_failure;
}
/* Create the available space output string */
val = prettyCapacity(info.available, &unit);
ret = virAsprintf(&poolInfoTexts[i].available,
"%.2lf %s", val, unit);
if (ret < 0) {
/* An error occurred creating the string, return */
goto asprintf_failure;
}
} else {
/* Capacity related information isn't available */
poolInfoTexts[i].capacity = vshStrdup(ctl, _("-"));
poolInfoTexts[i].allocation = vshStrdup(ctl, _("-"));
poolInfoTexts[i].available = vshStrdup(ctl, _("-"));
}
/* Keep the length of capacity string if longest so far */
stringLength = strlen(poolInfoTexts[i].capacity);
if (stringLength > capStrLength)
capStrLength = stringLength;
/* Keep the length of allocation string if longest so far */
stringLength = strlen(poolInfoTexts[i].allocation);
if (stringLength > allocStrLength)
allocStrLength = stringLength;
/* Keep the length of available string if longest so far */
stringLength = strlen(poolInfoTexts[i].available);
if (stringLength > availStrLength)
availStrLength = stringLength;
} else {
/* --details option was not specified, only active/inactive
* state strings are used */
if (info.state == VIR_STORAGE_POOL_INACTIVE)
poolInfoTexts[i].state = vshStrdup(ctl, _("inactive"));
else
poolInfoTexts[i].state = vshStrdup(ctl, _("active"));
}
}
/* Keep the length of name string if longest so far */
stringLength = strlen(poolNames[i]);
if (stringLength > nameStrLength)
nameStrLength = stringLength;
/* Keep the length of state string if longest so far */
stringLength = strlen(poolInfoTexts[i].state);
if (stringLength > stateStrLength)
stateStrLength = stringLength;
/* Keep the length of autostart string if longest so far */
stringLength = strlen(poolInfoTexts[i].autostart);
if (stringLength > autostartStrLength)
autostartStrLength = stringLength;
/* Free the pool object */
virStoragePoolFree(pool);
}
/* If the --details option wasn't selected, we output the pool
* info using the fixed string format from previous versions to
* maintain backward compatibility.
*/
/* Output basic info then return if --details option not selected */
if (!details) {
/* Output old style header */
vshPrintExtra(ctl, "%-20s %-10s %-10s\n", _("Name"), _("State"),
_("Autostart"));
vshPrintExtra(ctl, "-----------------------------------------\n");
/* Output old style pool info */
for (i = 0; i < numAllPools; i++) {
vshPrint(ctl, "%-20s %-10s %-10s\n",
poolNames[i],
poolInfoTexts[i].state,
poolInfoTexts[i].autostart);
}
/* Cleanup and return */
functionReturn = true;
goto cleanup;
}
/* We only get here if the --details option was selected. */
/* Use the length of name header string if it's longest */
stringLength = strlen(_("Name"));
if (stringLength > nameStrLength)
nameStrLength = stringLength;
/* Use the length of state header string if it's longest */
stringLength = strlen(_("State"));
if (stringLength > stateStrLength)
stateStrLength = stringLength;
/* Use the length of autostart header string if it's longest */
stringLength = strlen(_("Autostart"));
if (stringLength > autostartStrLength)
autostartStrLength = stringLength;
/* Use the length of persistent header string if it's longest */
stringLength = strlen(_("Persistent"));
if (stringLength > persistStrLength)
persistStrLength = stringLength;
/* Use the length of capacity header string if it's longest */
stringLength = strlen(_("Capacity"));
if (stringLength > capStrLength)
capStrLength = stringLength;
/* Use the length of allocation header string if it's longest */
stringLength = strlen(_("Allocation"));
if (stringLength > allocStrLength)
allocStrLength = stringLength;
/* Use the length of available header string if it's longest */
stringLength = strlen(_("Available"));
if (stringLength > availStrLength)
availStrLength = stringLength;
/* Display the string lengths for debugging. */
vshDebug(ctl, VSH_ERR_DEBUG, "Longest name string = %lu chars\n",
(unsigned long) nameStrLength);
vshDebug(ctl, VSH_ERR_DEBUG, "Longest state string = %lu chars\n",
(unsigned long) stateStrLength);
vshDebug(ctl, VSH_ERR_DEBUG, "Longest autostart string = %lu chars\n",
(unsigned long) autostartStrLength);
vshDebug(ctl, VSH_ERR_DEBUG, "Longest persistent string = %lu chars\n",
(unsigned long) persistStrLength);
vshDebug(ctl, VSH_ERR_DEBUG, "Longest capacity string = %lu chars\n",
(unsigned long) capStrLength);
vshDebug(ctl, VSH_ERR_DEBUG, "Longest allocation string = %lu chars\n",
(unsigned long) allocStrLength);
vshDebug(ctl, VSH_ERR_DEBUG, "Longest available string = %lu chars\n",
(unsigned long) availStrLength);
/* Create the output template. Each column is sized according to
* the longest string.
*/
char *outputStr;
ret = virAsprintf(&outputStr,
"%%-%lus %%-%lus %%-%lus %%-%lus %%%lus %%%lus %%%lus\n",
(unsigned long) nameStrLength,
(unsigned long) stateStrLength,
(unsigned long) autostartStrLength,
(unsigned long) persistStrLength,
(unsigned long) capStrLength,
(unsigned long) allocStrLength,
(unsigned long) availStrLength);
if (ret < 0) {
/* An error occurred creating the string, return */
goto asprintf_failure;
}
/* Display the header */
vshPrint(ctl, outputStr, _("Name"), _("State"), _("Autostart"),
_("Persistent"), _("Capacity"), _("Allocation"), _("Available"));
for (i = nameStrLength + stateStrLength + autostartStrLength
+ persistStrLength + capStrLength
+ allocStrLength + availStrLength
+ 12; i > 0; i--)
vshPrintExtra(ctl, "-");
vshPrintExtra(ctl, "\n");
/* Display the pool info rows */
for (i = 0; i < numAllPools; i++) {
vshPrint(ctl, outputStr,
poolNames[i],
poolInfoTexts[i].state,
poolInfoTexts[i].autostart,
poolInfoTexts[i].persistent,
poolInfoTexts[i].capacity,
poolInfoTexts[i].allocation,
poolInfoTexts[i].available);
}
/* Cleanup and return */
functionReturn = true;
goto cleanup;
asprintf_failure:
/* Display an appropriate error message then cleanup and return */
switch (errno) {
case ENOMEM:
/* Couldn't allocate memory */
vshError(ctl, "%s", _("Out of memory"));
break;
default:
/* Some other error */
vshError(ctl, _("virAsprintf failed (errno %d)"), errno);
}
functionReturn = false;
cleanup:
/* Safely free the memory allocated in this function */
for (i = 0; i < numAllPools; i++) {
/* Cleanup the memory for one pool info structure */
VIR_FREE(poolInfoTexts[i].state);
VIR_FREE(poolInfoTexts[i].autostart);
VIR_FREE(poolInfoTexts[i].persistent);
VIR_FREE(poolInfoTexts[i].capacity);
VIR_FREE(poolInfoTexts[i].allocation);
VIR_FREE(poolInfoTexts[i].available);
VIR_FREE(poolNames[i]);
}
/* Cleanup the memory for the initial arrays*/
VIR_FREE(poolInfoTexts);
VIR_FREE(poolNames);
/* Return the desired value */
return functionReturn;
}
/*
* "find-storage-pool-sources-as" command
*/
static const vshCmdInfo info_find_storage_pool_sources_as[] = {
{"help", N_("find potential storage pool sources")},
{"desc", N_("Returns XML <sources> document.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_find_storage_pool_sources_as[] = {
{"type", VSH_OT_DATA, VSH_OFLAG_REQ,
N_("type of storage pool sources to find")},
{"host", VSH_OT_DATA, VSH_OFLAG_NONE, N_("optional host to query")},
{"port", VSH_OT_DATA, VSH_OFLAG_NONE, N_("optional port to query")},
{"initiator", VSH_OT_DATA, VSH_OFLAG_NONE, N_("optional initiator IQN to use for query")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolDiscoverSourcesAs(vshControl * ctl, const vshCmd * cmd ATTRIBUTE_UNUSED)
{
const char *type = NULL, *host = NULL;
char *srcSpec = NULL;
char *srcList;
const char *initiator = NULL;
if (vshCommandOptString(cmd, "type", &type) <= 0 ||
vshCommandOptString(cmd, "host", &host) < 0 ||
vshCommandOptString(cmd, "initiator", &initiator) < 0) {
vshError(ctl,"%s", _("missing argument"));
return false;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (host) {
const char *port = NULL;
virBuffer buf = VIR_BUFFER_INITIALIZER;
if (vshCommandOptString(cmd, "port", &port) < 0) {
vshError(ctl, "%s", _("missing argument"));
virBufferFreeAndReset(&buf);
return false;
}
virBufferAddLit(&buf, "<source>\n");
virBufferAsprintf(&buf, " <host name='%s'", host);
if (port)
virBufferAsprintf(&buf, " port='%s'", port);
virBufferAddLit(&buf, "/>\n");
if (initiator) {
virBufferAddLit(&buf, " <initiator>\n");
virBufferAsprintf(&buf, " <iqn name='%s'/>\n", initiator);
virBufferAddLit(&buf, " </initiator>\n");
}
virBufferAddLit(&buf, "</source>\n");
if (virBufferError(&buf)) {
vshError(ctl, "%s", _("Out of memory"));
return false;
}
srcSpec = virBufferContentAndReset(&buf);
}
srcList = virConnectFindStoragePoolSources(ctl->conn, type, srcSpec, 0);
VIR_FREE(srcSpec);
if (srcList == NULL) {
vshError(ctl, _("Failed to find any %s pool sources"), type);
return false;
}
vshPrint(ctl, "%s", srcList);
VIR_FREE(srcList);
return true;
}
/*
* "find-storage-pool-sources" command
*/
static const vshCmdInfo info_find_storage_pool_sources[] = {
{"help", N_("discover potential storage pool sources")},
{"desc", N_("Returns XML <sources> document.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_find_storage_pool_sources[] = {
{"type", VSH_OT_DATA, VSH_OFLAG_REQ,
N_("type of storage pool sources to discover")},
{"srcSpec", VSH_OT_DATA, VSH_OFLAG_NONE,
N_("optional file of source xml to query for pools")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolDiscoverSources(vshControl * ctl, const vshCmd * cmd ATTRIBUTE_UNUSED)
{
const char *type = NULL, *srcSpecFile = NULL;
char *srcSpec = NULL, *srcList;
if (vshCommandOptString(cmd, "type", &type) <= 0)
return false;
if (vshCommandOptString(cmd, "srcSpec", &srcSpecFile) < 0) {
vshError(ctl, "%s", _("missing option"));
return false;
}
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (srcSpecFile && virFileReadAll(srcSpecFile, VIRSH_MAX_XML_FILE, &srcSpec) < 0)
return false;
srcList = virConnectFindStoragePoolSources(ctl->conn, type, srcSpec, 0);
VIR_FREE(srcSpec);
if (srcList == NULL) {
vshError(ctl, _("Failed to find any %s pool sources"), type);
return false;
}
vshPrint(ctl, "%s", srcList);
VIR_FREE(srcList);
return true;
}
/*
* "pool-info" command
*/
static const vshCmdInfo info_pool_info[] = {
{"help", N_("storage pool information")},
{"desc", N_("Returns basic information about the storage pool.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_info[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolInfo(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolInfo info;
virStoragePoolPtr pool;
int autostart = 0;
int persistent = 0;
bool ret = true;
char uuid[VIR_UUID_STRING_BUFLEN];
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", NULL)))
return false;
vshPrint(ctl, "%-15s %s\n", _("Name:"), virStoragePoolGetName(pool));
if (virStoragePoolGetUUIDString(pool, &uuid[0])==0)
vshPrint(ctl, "%-15s %s\n", _("UUID:"), uuid);
if (virStoragePoolGetInfo(pool, &info) == 0) {
double val;
const char *unit;
switch (info.state) {
case VIR_STORAGE_POOL_INACTIVE:
vshPrint(ctl, "%-15s %s\n", _("State:"),
_("inactive"));
break;
case VIR_STORAGE_POOL_BUILDING:
vshPrint(ctl, "%-15s %s\n", _("State:"),
_("building"));
break;
case VIR_STORAGE_POOL_RUNNING:
vshPrint(ctl, "%-15s %s\n", _("State:"),
_("running"));
break;
case VIR_STORAGE_POOL_DEGRADED:
vshPrint(ctl, "%-15s %s\n", _("State:"),
_("degraded"));
break;
case VIR_STORAGE_POOL_INACCESSIBLE:
vshPrint(ctl, "%-15s %s\n", _("State:"),
_("inaccessible"));
break;
}
/* Check and display whether the pool is persistent or not */
persistent = virStoragePoolIsPersistent(pool);
vshDebug(ctl, VSH_ERR_DEBUG, "Pool persistent flag value: %d\n",
persistent);
if (persistent < 0)
vshPrint(ctl, "%-15s %s\n", _("Persistent:"), _("unknown"));
else
vshPrint(ctl, "%-15s %s\n", _("Persistent:"), persistent ? _("yes") : _("no"));
/* Check and display whether the pool is autostarted or not */
virStoragePoolGetAutostart(pool, &autostart);
vshDebug(ctl, VSH_ERR_DEBUG, "Pool autostart flag value: %d\n",
autostart);
if (autostart < 0)
vshPrint(ctl, "%-15s %s\n", _("Autostart:"), _("no autostart"));
else
vshPrint(ctl, "%-15s %s\n", _("Autostart:"), autostart ? _("yes") : _("no"));
if (info.state == VIR_STORAGE_POOL_RUNNING ||
info.state == VIR_STORAGE_POOL_DEGRADED) {
val = prettyCapacity(info.capacity, &unit);
vshPrint(ctl, "%-15s %2.2lf %s\n", _("Capacity:"), val, unit);
val = prettyCapacity(info.allocation, &unit);
vshPrint(ctl, "%-15s %2.2lf %s\n", _("Allocation:"), val, unit);
val = prettyCapacity(info.available, &unit);
vshPrint(ctl, "%-15s %2.2lf %s\n", _("Available:"), val, unit);
}
} else {
ret = false;
}
virStoragePoolFree(pool);
return ret;
}
/*
* "pool-name" command
*/
static const vshCmdInfo info_pool_name[] = {
{"help", N_("convert a pool UUID to pool name")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_name[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolName(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPoolBy(ctl, cmd, "pool", NULL,
VSH_BYUUID)))
return false;
vshPrint(ctl, "%s\n", virStoragePoolGetName(pool));
virStoragePoolFree(pool);
return true;
}
/*
* "pool-start" command
*/
static const vshCmdInfo info_pool_start[] = {
{"help", N_("start a (previously defined) inactive pool")},
{"desc", N_("Start a pool.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_start[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("name or uuid of the inactive pool")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolStart(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
bool ret = true;
const char *name = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", &name)))
return false;
if (virStoragePoolCreate(pool, 0) == 0) {
vshPrint(ctl, _("Pool %s started\n"), name);
} else {
vshError(ctl, _("Failed to start pool %s"), name);
ret = false;
}
virStoragePoolFree(pool);
return ret;
}
/*
* "vol-create-as" command
*/
static const vshCmdInfo info_vol_create_as[] = {
{"help", N_("create a volume from a set of args")},
{"desc", N_("Create a vol.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_create_as[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name")},
{"name", VSH_OT_DATA, VSH_OFLAG_REQ, N_("name of the volume")},
{"capacity", VSH_OT_DATA, VSH_OFLAG_REQ,
N_("size of the vol, as scaled integer (default bytes)")},
{"allocation", VSH_OT_STRING, 0,
N_("initial allocation size, as scaled integer (default bytes)")},
{"format", VSH_OT_STRING, 0,
N_("file format type raw,bochs,qcow,qcow2,qed,vmdk")},
{"backing-vol", VSH_OT_STRING, 0,
N_("the backing volume if taking a snapshot")},
{"backing-vol-format", VSH_OT_STRING, 0,
N_("format of backing volume if taking a snapshot")},
{NULL, 0, 0, NULL}
};
static int
vshVolSize(const char *data, unsigned long long *val)
{
char *end;
if (virStrToLong_ull(data, &end, 10, val) < 0)
return -1;
return virScaleInteger(val, end, 1, ULLONG_MAX);
}
static bool
cmdVolCreateAs(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
virStorageVolPtr vol;
char *xml;
const char *name, *capacityStr = NULL, *allocationStr = NULL, *format = NULL;
const char *snapshotStrVol = NULL, *snapshotStrFormat = NULL;
unsigned long long capacity, allocation = 0;
virBuffer buf = VIR_BUFFER_INITIALIZER;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPoolBy(ctl, cmd, "pool", NULL,
VSH_BYNAME)))
return false;
if (vshCommandOptString(cmd, "name", &name) <= 0)
goto cleanup;
if (vshCommandOptString(cmd, "capacity", &capacityStr) <= 0)
goto cleanup;
if (vshVolSize(capacityStr, &capacity) < 0) {
vshError(ctl, _("Malformed size %s"), capacityStr);
goto cleanup;
}
if (vshCommandOptString(cmd, "allocation", &allocationStr) > 0 &&
vshVolSize(allocationStr, &allocation) < 0) {
vshError(ctl, _("Malformed size %s"), allocationStr);
goto cleanup;
}
if (vshCommandOptString(cmd, "format", &format) < 0 ||
vshCommandOptString(cmd, "backing-vol", &snapshotStrVol) < 0 ||
vshCommandOptString(cmd, "backing-vol-format",
&snapshotStrFormat) < 0) {
vshError(ctl, "%s", _("missing argument"));
goto cleanup;
}
virBufferAddLit(&buf, "<volume>\n");
virBufferAsprintf(&buf, " <name>%s</name>\n", name);
virBufferAsprintf(&buf, " <capacity>%llu</capacity>\n", capacity);
if (allocationStr)
virBufferAsprintf(&buf, " <allocation>%llu</allocation>\n", allocation);
if (format) {
virBufferAddLit(&buf, " <target>\n");
virBufferAsprintf(&buf, " <format type='%s'/>\n",format);
virBufferAddLit(&buf, " </target>\n");
}
/* Convert the snapshot parameters into backingStore XML */
if (snapshotStrVol) {
/* Lookup snapshot backing volume. Try the backing-vol
* parameter as a name */
vshDebug(ctl, VSH_ERR_DEBUG,
"%s: Look up backing store volume '%s' as name\n",
cmd->def->name, snapshotStrVol);
virStorageVolPtr snapVol = virStorageVolLookupByName(pool, snapshotStrVol);
if (snapVol)
vshDebug(ctl, VSH_ERR_DEBUG,
"%s: Backing store volume found using '%s' as name\n",
cmd->def->name, snapshotStrVol);
if (snapVol == NULL) {
/* Snapshot backing volume not found by name. Try the
* backing-vol parameter as a key */
vshDebug(ctl, VSH_ERR_DEBUG,
"%s: Look up backing store volume '%s' as key\n",
cmd->def->name, snapshotStrVol);
snapVol = virStorageVolLookupByKey(ctl->conn, snapshotStrVol);
if (snapVol)
vshDebug(ctl, VSH_ERR_DEBUG,
"%s: Backing store volume found using '%s' as key\n",
cmd->def->name, snapshotStrVol);
}
if (snapVol == NULL) {
/* Snapshot backing volume not found by key. Try the
* backing-vol parameter as a path */
vshDebug(ctl, VSH_ERR_DEBUG,
"%s: Look up backing store volume '%s' as path\n",
cmd->def->name, snapshotStrVol);
snapVol = virStorageVolLookupByPath(ctl->conn, snapshotStrVol);
if (snapVol)
vshDebug(ctl, VSH_ERR_DEBUG,
"%s: Backing store volume found using '%s' as path\n",
cmd->def->name, snapshotStrVol);
}
if (snapVol == NULL) {
vshError(ctl, _("failed to get vol '%s'"), snapshotStrVol);
goto cleanup;
}
char *snapshotStrVolPath;
if ((snapshotStrVolPath = virStorageVolGetPath(snapVol)) == NULL) {
virStorageVolFree(snapVol);
goto cleanup;
}
/* Create XML for the backing store */
virBufferAddLit(&buf, " <backingStore>\n");
virBufferAsprintf(&buf, " <path>%s</path>\n",snapshotStrVolPath);
if (snapshotStrFormat)
virBufferAsprintf(&buf, " <format type='%s'/>\n",snapshotStrFormat);
virBufferAddLit(&buf, " </backingStore>\n");
/* Cleanup snapshot allocations */
VIR_FREE(snapshotStrVolPath);
virStorageVolFree(snapVol);
}
virBufferAddLit(&buf, "</volume>\n");
if (virBufferError(&buf)) {
vshPrint(ctl, "%s", _("Failed to allocate XML buffer"));
goto cleanup;
}
xml = virBufferContentAndReset(&buf);
vol = virStorageVolCreateXML(pool, xml, 0);
VIR_FREE(xml);
virStoragePoolFree(pool);
if (vol != NULL) {
vshPrint(ctl, _("Vol %s created\n"), name);
virStorageVolFree(vol);
return true;
} else {
vshError(ctl, _("Failed to create vol %s"), name);
return false;
}
cleanup:
virBufferFreeAndReset(&buf);
virStoragePoolFree(pool);
return false;
}
/*
* "pool-undefine" command
*/
static const vshCmdInfo info_pool_undefine[] = {
{"help", N_("undefine an inactive pool")},
{"desc", N_("Undefine the configuration for an inactive pool.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_undefine[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolUndefine(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", &name)))
return false;
if (virStoragePoolUndefine(pool) == 0) {
vshPrint(ctl, _("Pool %s has been undefined\n"), name);
} else {
vshError(ctl, _("Failed to undefine pool %s"), name);
ret = false;
}
virStoragePoolFree(pool);
return ret;
}
/*
* "pool-uuid" command
*/
static const vshCmdInfo info_pool_uuid[] = {
{"help", N_("convert a pool name to pool UUID")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_uuid[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolUuid(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
char uuid[VIR_UUID_STRING_BUFLEN];
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPoolBy(ctl, cmd, "pool", NULL,
VSH_BYNAME)))
return false;
if (virStoragePoolGetUUIDString(pool, uuid) != -1)
vshPrint(ctl, "%s\n", uuid);
else
vshError(ctl, "%s", _("failed to get pool UUID"));
virStoragePoolFree(pool);
return true;
}
/*
* "vol-create" command
*/
static const vshCmdInfo info_vol_create[] = {
{"help", N_("create a vol from an XML file")},
{"desc", N_("Create a vol.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_create[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name")},
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing an XML vol description")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolCreate(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
virStorageVolPtr vol;
const char *from = NULL;
bool ret = true;
char *buffer;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(pool = vshCommandOptPoolBy(ctl, cmd, "pool", NULL,
VSH_BYNAME)))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0) {
virStoragePoolFree(pool);
return false;
}
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
virshReportError(ctl);
virStoragePoolFree(pool);
return false;
}
vol = virStorageVolCreateXML(pool, buffer, 0);
VIR_FREE(buffer);
virStoragePoolFree(pool);
if (vol != NULL) {
vshPrint(ctl, _("Vol %s created from %s\n"),
virStorageVolGetName(vol), from);
virStorageVolFree(vol);
} else {
vshError(ctl, _("Failed to create vol from %s"), from);
ret = false;
}
return ret;
}
/*
* "vol-create-from" command
*/
static const vshCmdInfo info_vol_create_from[] = {
{"help", N_("create a vol, using another volume as input")},
{"desc", N_("Create a vol from an existing volume.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_create_from[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing an XML vol description")},
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("input vol name or key")},
{"inputpool", VSH_OT_STRING, 0, N_("pool name or uuid of the input volume's pool")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolCreateFrom(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool = NULL;
virStorageVolPtr newvol = NULL, inputvol = NULL;
const char *from = NULL;
bool ret = false;
char *buffer = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", NULL)))
goto cleanup;
if (vshCommandOptString(cmd, "file", &from) <= 0) {
goto cleanup;
}
if (!(inputvol = vshCommandOptVol(ctl, cmd, "vol", "inputpool", NULL)))
goto cleanup;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
virshReportError(ctl);
goto cleanup;
}
newvol = virStorageVolCreateXMLFrom(pool, buffer, inputvol, 0);
if (newvol != NULL) {
vshPrint(ctl, _("Vol %s created from input vol %s\n"),
virStorageVolGetName(newvol), virStorageVolGetName(inputvol));
} else {
vshError(ctl, _("Failed to create vol from %s"), from);
goto cleanup;
}
ret = true;
cleanup:
VIR_FREE(buffer);
if (pool)
virStoragePoolFree(pool);
if (inputvol)
virStorageVolFree(inputvol);
if (newvol)
virStorageVolFree(newvol);
return ret;
}
static xmlChar *
makeCloneXML(const char *origxml, const char *newname)
{
xmlDocPtr doc = NULL;
xmlXPathContextPtr ctxt = NULL;
xmlXPathObjectPtr obj = NULL;
xmlChar *newxml = NULL;
int size;
doc = virXMLParseStringCtxt(origxml, _("(volume_definition)"), &ctxt);
if (!doc)
goto cleanup;
obj = xmlXPathEval(BAD_CAST "/volume/name", ctxt);
if (obj == NULL || obj->nodesetval == NULL ||
obj->nodesetval->nodeTab == NULL)
goto cleanup;
xmlNodeSetContent(obj->nodesetval->nodeTab[0], (const xmlChar *)newname);
xmlDocDumpMemory(doc, &newxml, &size);
cleanup:
xmlXPathFreeObject(obj);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(doc);
return newxml;
}
/*
* "vol-clone" command
*/
static const vshCmdInfo info_vol_clone[] = {
{"help", N_("clone a volume.")},
{"desc", N_("Clone an existing volume.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_clone[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("orig vol name or key")},
{"newname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("clone name")},
{"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolClone(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr origpool = NULL;
virStorageVolPtr origvol = NULL, newvol = NULL;
const char *name = NULL;
char *origxml = NULL;
xmlChar *newxml = NULL;
bool ret = false;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (!(origvol = vshCommandOptVol(ctl, cmd, "vol", "pool", NULL)))
goto cleanup;
origpool = virStoragePoolLookupByVolume(origvol);
if (!origpool) {
vshError(ctl, "%s", _("failed to get parent pool"));
goto cleanup;
}
if (vshCommandOptString(cmd, "newname", &name) <= 0)
goto cleanup;
origxml = virStorageVolGetXMLDesc(origvol, 0);
if (!origxml)
goto cleanup;
newxml = makeCloneXML(origxml, name);
if (!newxml) {
vshPrint(ctl, "%s", _("Failed to allocate XML buffer"));
goto cleanup;
}
newvol = virStorageVolCreateXMLFrom(origpool, (char *) newxml, origvol, 0);
if (newvol != NULL) {
vshPrint(ctl, _("Vol %s cloned from %s\n"),
virStorageVolGetName(newvol), virStorageVolGetName(origvol));
} else {
vshError(ctl, _("Failed to clone vol from %s"),
virStorageVolGetName(origvol));
goto cleanup;
}
ret = true;
cleanup:
VIR_FREE(origxml);
xmlFree(newxml);
if (origvol)
virStorageVolFree(origvol);
if (newvol)
virStorageVolFree(newvol);
if (origpool)
virStoragePoolFree(origpool);
return ret;
}
/*
* "vol-upload" command
*/
static const vshCmdInfo info_vol_upload[] = {
{"help", N_("upload a file into a volume")},
{"desc", N_("Upload a file into a volume")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_upload[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")},
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file")},
{"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
{"offset", VSH_OT_INT, 0, N_("volume offset to upload to") },
{"length", VSH_OT_INT, 0, N_("amount of data to upload") },
{NULL, 0, 0, NULL}
};
static int
cmdVolUploadSource(virStreamPtr st ATTRIBUTE_UNUSED,
char *bytes, size_t nbytes, void *opaque)
{
int *fd = opaque;
return saferead(*fd, bytes, nbytes);
}
static bool
cmdVolUpload(vshControl *ctl, const vshCmd *cmd)
{
const char *file = NULL;
virStorageVolPtr vol = NULL;
bool ret = false;
int fd = -1;
virStreamPtr st = NULL;
const char *name = NULL;
unsigned long long offset = 0, length = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (vshCommandOptULongLong(cmd, "offset", &offset) < 0) {
vshError(ctl, _("Unable to parse integer"));
return false;
}
if (vshCommandOptULongLong(cmd, "length", &length) < 0) {
vshError(ctl, _("Unable to parse integer"));
return false;
}
if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", &name))) {
return false;
}
if (vshCommandOptString(cmd, "file", &file) < 0) {
vshError(ctl, _("file must not be empty"));
goto cleanup;
}
if ((fd = open(file, O_RDONLY)) < 0) {
vshError(ctl, _("cannot read %s"), file);
goto cleanup;
}
st = virStreamNew(ctl->conn, 0);
if (virStorageVolUpload(vol, st, offset, length, 0) < 0) {
vshError(ctl, _("cannot upload to volume %s"), name);
goto cleanup;
}
if (virStreamSendAll(st, cmdVolUploadSource, &fd) < 0) {
vshError(ctl, _("cannot send data to volume %s"), name);
goto cleanup;
}
if (VIR_CLOSE(fd) < 0) {
vshError(ctl, _("cannot close file %s"), file);
virStreamAbort(st);
goto cleanup;
}
if (virStreamFinish(st) < 0) {
vshError(ctl, _("cannot close volume %s"), name);
goto cleanup;
}
ret = true;
cleanup:
if (vol)
virStorageVolFree(vol);
if (st)
virStreamFree(st);
VIR_FORCE_CLOSE(fd);
return ret;
}
/*
* "vol-download" command
*/
static const vshCmdInfo info_vol_download[] = {
{"help", N_("Download a volume to a file")},
{"desc", N_("Download a volume to a file")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_download[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")},
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file")},
{"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
{"offset", VSH_OT_INT, 0, N_("volume offset to download from") },
{"length", VSH_OT_INT, 0, N_("amount of data to download") },
{NULL, 0, 0, NULL}
};
static bool
cmdVolDownload(vshControl *ctl, const vshCmd *cmd)
{
const char *file = NULL;
virStorageVolPtr vol = NULL;
bool ret = false;
int fd = -1;
virStreamPtr st = NULL;
const char *name = NULL;
unsigned long long offset = 0, length = 0;
bool created = false;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptULongLong(cmd, "offset", &offset) < 0) {
vshError(ctl, _("Unable to parse integer"));
return false;
}
if (vshCommandOptULongLong(cmd, "length", &length) < 0) {
vshError(ctl, _("Unable to parse integer"));
return false;
}
if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", &name)))
return false;
if (vshCommandOptString(cmd, "file", &file) < 0) {
vshError(ctl, _("file must not be empty"));
goto cleanup;
}
if ((fd = open(file, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) {
if (errno != EEXIST ||
(fd = open(file, O_WRONLY|O_TRUNC, 0666)) < 0) {
vshError(ctl, _("cannot create %s"), file);
goto cleanup;
}
} else {
created = true;
}
st = virStreamNew(ctl->conn, 0);
if (virStorageVolDownload(vol, st, offset, length, 0) < 0) {
vshError(ctl, _("cannot download from volume %s"), name);
goto cleanup;
}
if (virStreamRecvAll(st, vshStreamSink, &fd) < 0) {
vshError(ctl, _("cannot receive data from volume %s"), name);
goto cleanup;
}
if (VIR_CLOSE(fd) < 0) {
vshError(ctl, _("cannot close file %s"), file);
virStreamAbort(st);
goto cleanup;
}
if (virStreamFinish(st) < 0) {
vshError(ctl, _("cannot close volume %s"), name);
goto cleanup;
}
ret = true;
cleanup:
VIR_FORCE_CLOSE(fd);
if (!ret && created)
unlink(file);
if (vol)
virStorageVolFree(vol);
if (st)
virStreamFree(st);
return ret;
}
/*
* "vol-delete" command
*/
static const vshCmdInfo info_vol_delete[] = {
{"help", N_("delete a vol")},
{"desc", N_("Delete a given vol.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_delete[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")},
{"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolDelete(vshControl *ctl, const vshCmd *cmd)
{
virStorageVolPtr vol;
bool ret = true;
const char *name;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", &name))) {
return false;
}
if (virStorageVolDelete(vol, 0) == 0) {
vshPrint(ctl, _("Vol %s deleted\n"), name);
} else {
vshError(ctl, _("Failed to delete vol %s"), name);
ret = false;
}
virStorageVolFree(vol);
return ret;
}
/*
* "vol-wipe" command
*/
static const vshCmdInfo info_vol_wipe[] = {
{"help", N_("wipe a vol")},
{"desc", N_("Ensure data previously on a volume is not accessible to future reads")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_wipe[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")},
{"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
{"algorithm", VSH_OT_STRING, 0, N_("perform selected wiping algorithm")},
{NULL, 0, 0, NULL}
};
VIR_ENUM_DECL(virStorageVolWipeAlgorithm)
VIR_ENUM_IMPL(virStorageVolWipeAlgorithm, VIR_STORAGE_VOL_WIPE_ALG_LAST,
"zero", "nnsa", "dod", "bsi", "gutmann", "schneier",
"pfitzner7", "pfitzner33", "random");
static bool
cmdVolWipe(vshControl *ctl, const vshCmd *cmd)
{
virStorageVolPtr vol;
bool ret = false;
const char *name;
const char *algorithm_str = NULL;
int algorithm = VIR_STORAGE_VOL_WIPE_ALG_ZERO;
int funcRet;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", &name))) {
return false;
}
if (vshCommandOptString(cmd, "algorithm", &algorithm_str) < 0) {
vshError(ctl, "%s", _("missing argument"));
goto out;
}
if (algorithm_str &&
(algorithm = virStorageVolWipeAlgorithmTypeFromString(algorithm_str)) < 0) {
vshError(ctl, _("Unsupported algorithm '%s'"), algorithm_str);
goto out;
}
if ((funcRet = virStorageVolWipePattern(vol, algorithm, 0)) < 0) {
if (last_error->code == VIR_ERR_NO_SUPPORT &&
algorithm == VIR_STORAGE_VOL_WIPE_ALG_ZERO)
funcRet = virStorageVolWipe(vol, 0);
}
if (funcRet < 0) {
vshError(ctl, _("Failed to wipe vol %s"), name);
goto out;
}
vshPrint(ctl, _("Vol %s wiped\n"), name);
ret = true;
out:
virStorageVolFree(vol);
return ret;
}
/*
* "vol-info" command
*/
static const vshCmdInfo info_vol_info[] = {
{"help", N_("storage vol information")},
{"desc", N_("Returns basic information about the storage vol.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_info[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")},
{"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolInfo(vshControl *ctl, const vshCmd *cmd)
{
virStorageVolInfo info;
virStorageVolPtr vol;
bool ret = true;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", NULL)))
return false;
vshPrint(ctl, "%-15s %s\n", _("Name:"), virStorageVolGetName(vol));
if (virStorageVolGetInfo(vol, &info) == 0) {
double val;
const char *unit;
switch(info.type) {
case VIR_STORAGE_VOL_FILE:
vshPrint(ctl, "%-15s %s\n", _("Type:"), _("file"));
break;
case VIR_STORAGE_VOL_BLOCK:
vshPrint(ctl, "%-15s %s\n", _("Type:"), _("block"));
break;
case VIR_STORAGE_VOL_DIR:
vshPrint(ctl, "%-15s %s\n", _("Type:"), _("dir"));
break;
case VIR_STORAGE_VOL_NETWORK:
vshPrint(ctl, "%-15s %s\n", _("Type:"), _("network"));
break;
default:
vshPrint(ctl, "%-15s %s\n", _("Type:"), _("unknown"));
}
val = prettyCapacity(info.capacity, &unit);
vshPrint(ctl, "%-15s %2.2lf %s\n", _("Capacity:"), val, unit);
val = prettyCapacity(info.allocation, &unit);
vshPrint(ctl, "%-15s %2.2lf %s\n", _("Allocation:"), val, unit);
} else {
ret = false;
}
virStorageVolFree(vol);
return ret;
}
/*
* "vol-resize" command
*/
static const vshCmdInfo info_vol_resize[] = {
{"help", N_("resize a vol")},
{"desc", N_("Resizes a storage volume.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_resize[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")},
{"capacity", VSH_OT_DATA, VSH_OFLAG_REQ,
N_("new capacity for the vol, as scaled integer (default bytes)")},
{"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
{"allocate", VSH_OT_BOOL, 0,
N_("allocate the new capacity, rather than leaving it sparse")},
{"delta", VSH_OT_BOOL, 0,
N_("use capacity as a delta to current size, rather than the new size")},
{"shrink", VSH_OT_BOOL, 0, N_("allow the resize to shrink the volume")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolResize(vshControl *ctl, const vshCmd *cmd)
{
virStorageVolPtr vol;
const char *capacityStr = NULL;
unsigned long long capacity = 0;
unsigned int flags = 0;
bool ret = false;
bool delta = false;
if (vshCommandOptBool(cmd, "allocate"))
flags |= VIR_STORAGE_VOL_RESIZE_ALLOCATE;
if (vshCommandOptBool(cmd, "delta")) {
delta = true;
flags |= VIR_STORAGE_VOL_RESIZE_DELTA;
}
if (vshCommandOptBool(cmd, "shrink"))
flags |= VIR_STORAGE_VOL_RESIZE_SHRINK;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", NULL)))
return false;
if (vshCommandOptString(cmd, "capacity", &capacityStr) <= 0)
goto cleanup;
virSkipSpaces(&capacityStr);
if (*capacityStr == '-') {
/* The API always requires a positive value; but we allow a
* negative value for convenience. */
if (delta && vshCommandOptBool(cmd, "shrink")){
capacityStr++;
} else {
vshError(ctl, "%s",
_("negative size requires --delta and --shrink"));
goto cleanup;
}
}
if (vshVolSize(capacityStr, &capacity) < 0) {
vshError(ctl, _("Malformed size %s"), capacityStr);
goto cleanup;
}
if (virStorageVolResize(vol, capacity, flags) == 0) {
vshPrint(ctl,
delta ? _("Size of volume '%s' successfully changed by %s\n")
: _("Size of volume '%s' successfully changed to %s\n"),
virStorageVolGetName(vol), capacityStr);
ret = true;
} else {
vshError(ctl,
delta ? _("Failed to change size of volume '%s' by %s\n")
: _("Failed to change size of volume '%s' to %s\n"),
virStorageVolGetName(vol), capacityStr);
ret = false;
}
cleanup:
virStorageVolFree(vol);
return ret;
}
/*
* "vol-dumpxml" command
*/
static const vshCmdInfo info_vol_dumpxml[] = {
{"help", N_("vol information in XML")},
{"desc", N_("Output the vol information as an XML dump to stdout.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_dumpxml[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")},
{"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolDumpXML(vshControl *ctl, const vshCmd *cmd)
{
virStorageVolPtr vol;
bool ret = true;
char *dump;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", NULL)))
return false;
dump = virStorageVolGetXMLDesc(vol, 0);
if (dump != NULL) {
vshPrint(ctl, "%s", dump);
VIR_FREE(dump);
} else {
ret = false;
}
virStorageVolFree(vol);
return ret;
}
/*
* "vol-list" command
*/
static const vshCmdInfo info_vol_list[] = {
{"help", N_("list vols")},
{"desc", N_("Returns list of vols by pool.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_list[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{"details", VSH_OT_BOOL, 0, N_("display extended details for volumes")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
virStorageVolInfo volumeInfo;
virStoragePoolPtr pool;
char **activeNames = NULL;
char *outputStr = NULL;
const char *unit;
double val;
bool details = vshCommandOptBool(cmd, "details");
int numVolumes = 0, i;
int ret;
bool functionReturn;
int stringLength = 0;
size_t allocStrLength = 0, capStrLength = 0;
size_t nameStrLength = 0, pathStrLength = 0;
size_t typeStrLength = 0;
struct volInfoText {
char *allocation;
char *capacity;
char *path;
char *type;
};
struct volInfoText *volInfoTexts = NULL;
/* Check the connection to libvirtd daemon is still working */
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
/* Look up the pool information given to us by the user */
if (!(pool = vshCommandOptPool(ctl, cmd, "pool", NULL)))
return false;
/* Determine the number of volumes in the pool */
numVolumes = virStoragePoolNumOfVolumes(pool);
if (numVolumes < 0) {
vshError(ctl, "%s", _("Failed to list storage volumes"));
virStoragePoolFree(pool);
return false;
}
/* Retrieve the list of volume names in the pool */
if (numVolumes > 0) {
activeNames = vshCalloc(ctl, numVolumes, sizeof(*activeNames));
if ((numVolumes = virStoragePoolListVolumes(pool, activeNames,
numVolumes)) < 0) {
vshError(ctl, "%s", _("Failed to list active vols"));
VIR_FREE(activeNames);
virStoragePoolFree(pool);
return false;
}
/* Sort the volume names */
qsort(&activeNames[0], numVolumes, sizeof(*activeNames), vshNameSorter);
/* Set aside memory for volume information pointers */
volInfoTexts = vshCalloc(ctl, numVolumes, sizeof(*volInfoTexts));
}
/* Collect the rest of the volume information for display */
for (i = 0; i < numVolumes; i++) {
/* Retrieve volume info */
virStorageVolPtr vol = virStorageVolLookupByName(pool,
activeNames[i]);
/* Retrieve the volume path */
if ((volInfoTexts[i].path = virStorageVolGetPath(vol)) == NULL) {
/* Something went wrong retrieving a volume path, cope with it */
volInfoTexts[i].path = vshStrdup(ctl, _("unknown"));
}
/* If requested, retrieve volume type and sizing information */
if (details) {
if (virStorageVolGetInfo(vol, &volumeInfo) != 0) {
/* Something went wrong retrieving volume info, cope with it */
volInfoTexts[i].allocation = vshStrdup(ctl, _("unknown"));
volInfoTexts[i].capacity = vshStrdup(ctl, _("unknown"));
volInfoTexts[i].type = vshStrdup(ctl, _("unknown"));
} else {
/* Convert the returned volume info into output strings */
/* Volume type */
switch (volumeInfo.type) {
case VIR_STORAGE_VOL_FILE:
volInfoTexts[i].type = vshStrdup(ctl, _("file"));
break;
case VIR_STORAGE_VOL_BLOCK:
volInfoTexts[i].type = vshStrdup(ctl, _("block"));
break;
case VIR_STORAGE_VOL_DIR:
volInfoTexts[i].type = vshStrdup(ctl, _("dir"));
break;
default:
volInfoTexts[i].type = vshStrdup(ctl, _("unknown"));
}
/* Create the capacity output string */
val = prettyCapacity(volumeInfo.capacity, &unit);
ret = virAsprintf(&volInfoTexts[i].capacity,
"%.2lf %s", val, unit);
if (ret < 0) {
/* An error occurred creating the string, return */
goto asprintf_failure;
}
/* Create the allocation output string */
val = prettyCapacity(volumeInfo.allocation, &unit);
ret = virAsprintf(&volInfoTexts[i].allocation,
"%.2lf %s", val, unit);
if (ret < 0) {
/* An error occurred creating the string, return */
goto asprintf_failure;
}
}
/* Remember the largest length for each output string.
* This lets us displaying header and volume information rows
* using a single, properly sized, printf style output string.
*/
/* Keep the length of name string if longest so far */
stringLength = strlen(activeNames[i]);
if (stringLength > nameStrLength)
nameStrLength = stringLength;
/* Keep the length of path string if longest so far */
stringLength = strlen(volInfoTexts[i].path);
if (stringLength > pathStrLength)
pathStrLength = stringLength;
/* Keep the length of type string if longest so far */
stringLength = strlen(volInfoTexts[i].type);
if (stringLength > typeStrLength)
typeStrLength = stringLength;
/* Keep the length of capacity string if longest so far */
stringLength = strlen(volInfoTexts[i].capacity);
if (stringLength > capStrLength)
capStrLength = stringLength;
/* Keep the length of allocation string if longest so far */
stringLength = strlen(volInfoTexts[i].allocation);
if (stringLength > allocStrLength)
allocStrLength = stringLength;
}
/* Cleanup memory allocation */
virStorageVolFree(vol);
}
/* If the --details option wasn't selected, we output the volume
* info using the fixed string format from previous versions to
* maintain backward compatibility.
*/
/* Output basic info then return if --details option not selected */
if (!details) {
/* The old output format */
vshPrintExtra(ctl, "%-20s %-40s\n", _("Name"), _("Path"));
vshPrintExtra(ctl, "-----------------------------------------\n");
for (i = 0; i < numVolumes; i++) {
vshPrint(ctl, "%-20s %-40s\n", activeNames[i],
volInfoTexts[i].path);
}
/* Cleanup and return */
functionReturn = true;
goto cleanup;
}
/* We only get here if the --details option was selected. */
/* Use the length of name header string if it's longest */
stringLength = strlen(_("Name"));
if (stringLength > nameStrLength)
nameStrLength = stringLength;
/* Use the length of path header string if it's longest */
stringLength = strlen(_("Path"));
if (stringLength > pathStrLength)
pathStrLength = stringLength;
/* Use the length of type header string if it's longest */
stringLength = strlen(_("Type"));
if (stringLength > typeStrLength)
typeStrLength = stringLength;
/* Use the length of capacity header string if it's longest */
stringLength = strlen(_("Capacity"));
if (stringLength > capStrLength)
capStrLength = stringLength;
/* Use the length of allocation header string if it's longest */
stringLength = strlen(_("Allocation"));
if (stringLength > allocStrLength)
allocStrLength = stringLength;
/* Display the string lengths for debugging */
vshDebug(ctl, VSH_ERR_DEBUG,
"Longest name string = %zu chars\n", nameStrLength);
vshDebug(ctl, VSH_ERR_DEBUG,
"Longest path string = %zu chars\n", pathStrLength);
vshDebug(ctl, VSH_ERR_DEBUG,
"Longest type string = %zu chars\n", typeStrLength);
vshDebug(ctl, VSH_ERR_DEBUG,
"Longest capacity string = %zu chars\n", capStrLength);
vshDebug(ctl, VSH_ERR_DEBUG,
"Longest allocation string = %zu chars\n", allocStrLength);
/* Create the output template */
ret = virAsprintf(&outputStr,
"%%-%lus %%-%lus %%-%lus %%%lus %%%lus\n",
(unsigned long) nameStrLength,
(unsigned long) pathStrLength,
(unsigned long) typeStrLength,
(unsigned long) capStrLength,
(unsigned long) allocStrLength);
if (ret < 0) {
/* An error occurred creating the string, return */
goto asprintf_failure;
}
/* Display the header */
vshPrint(ctl, outputStr, _("Name"), _("Path"), _("Type"),
("Capacity"), _("Allocation"));
for (i = nameStrLength + pathStrLength + typeStrLength
+ capStrLength + allocStrLength
+ 8; i > 0; i--)
vshPrintExtra(ctl, "-");
vshPrintExtra(ctl, "\n");
/* Display the volume info rows */
for (i = 0; i < numVolumes; i++) {
vshPrint(ctl, outputStr,
activeNames[i],
volInfoTexts[i].path,
volInfoTexts[i].type,
volInfoTexts[i].capacity,
volInfoTexts[i].allocation);
}
/* Cleanup and return */
functionReturn = true;
goto cleanup;
asprintf_failure:
/* Display an appropriate error message then cleanup and return */
switch (errno) {
case ENOMEM:
/* Couldn't allocate memory */
vshError(ctl, "%s", _("Out of memory"));
break;
default:
/* Some other error */
vshError(ctl, _("virAsprintf failed (errno %d)"), errno);
}
functionReturn = false;
cleanup:
/* Safely free the memory allocated in this function */
for (i = 0; i < numVolumes; i++) {
/* Cleanup the memory for one volume info structure per loop */
VIR_FREE(volInfoTexts[i].path);
VIR_FREE(volInfoTexts[i].type);
VIR_FREE(volInfoTexts[i].capacity);
VIR_FREE(volInfoTexts[i].allocation);
VIR_FREE(activeNames[i]);
}
/* Cleanup remaining memory */
VIR_FREE(outputStr);
VIR_FREE(volInfoTexts);
VIR_FREE(activeNames);
virStoragePoolFree(pool);
/* Return the desired value */
return functionReturn;
}
/*
* "vol-name" command
*/
static const vshCmdInfo info_vol_name[] = {
{"help", N_("returns the volume name for a given volume key or path")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_name[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("volume key or path")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolName(vshControl *ctl, const vshCmd *cmd)
{
virStorageVolPtr vol;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(vol = vshCommandOptVolBy(ctl, cmd, "vol", NULL, NULL,
VSH_BYUUID)))
return false;
vshPrint(ctl, "%s\n", virStorageVolGetName(vol));
virStorageVolFree(vol);
return true;
}
/*
* "vol-pool" command
*/
static const vshCmdInfo info_vol_pool[] = {
{"help", N_("returns the storage pool for a given volume key or path")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_pool[] = {
{"uuid", VSH_OT_BOOL, 0, N_("return the pool uuid rather than pool name")},
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("volume key or path")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolPool(vshControl *ctl, const vshCmd *cmd)
{
virStoragePoolPtr pool;
virStorageVolPtr vol;
char uuid[VIR_UUID_STRING_BUFLEN];
/* Check the connection to libvirtd daemon is still working */
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
/* Use the supplied string to locate the volume */
if (!(vol = vshCommandOptVolBy(ctl, cmd, "vol", NULL, NULL,
VSH_BYUUID))) {
return false;
}
/* Look up the parent storage pool for the volume */
pool = virStoragePoolLookupByVolume(vol);
if (pool == NULL) {
vshError(ctl, "%s", _("failed to get parent pool"));
virStorageVolFree(vol);
return false;
}
/* Return the requested details of the parent storage pool */
if (vshCommandOptBool(cmd, "uuid")) {
/* Retrieve and return pool UUID string */
if (virStoragePoolGetUUIDString(pool, &uuid[0]) == 0)
vshPrint(ctl, "%s\n", uuid);
} else {
/* Return the storage pool name */
vshPrint(ctl, "%s\n", virStoragePoolGetName(pool));
}
/* Cleanup */
virStorageVolFree(vol);
virStoragePoolFree(pool);
return true;
}
/*
* "vol-key" command
*/
static const vshCmdInfo info_vol_key[] = {
{"help", N_("returns the volume key for a given volume name or path")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_key[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("volume name or path")},
{"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolKey(vshControl *ctl, const vshCmd *cmd)
{
virStorageVolPtr vol;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", NULL)))
return false;
vshPrint(ctl, "%s\n", virStorageVolGetKey(vol));
virStorageVolFree(vol);
return true;
}
/*
* "vol-path" command
*/
static const vshCmdInfo info_vol_path[] = {
{"help", N_("returns the volume path for a given volume name or key")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_vol_path[] = {
{"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("volume name or key")},
{"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdVolPath(vshControl *ctl, const vshCmd *cmd)
{
virStorageVolPtr vol;
char * StorageVolPath;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", NULL))) {
return false;
}
if ((StorageVolPath = virStorageVolGetPath(vol)) == NULL) {
virStorageVolFree(vol);
return false;
}
vshPrint(ctl, "%s\n", StorageVolPath);
VIR_FREE(StorageVolPath);
virStorageVolFree(vol);
return true;
}
/*
* "secret-define" command
*/
static const vshCmdInfo info_secret_define[] = {
{"help", N_("define or modify a secret from an XML file")},
{"desc", N_("Define or modify a secret.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_secret_define[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing secret attributes in XML")},
{NULL, 0, 0, NULL}
};
static bool
cmdSecretDefine(vshControl *ctl, const vshCmd *cmd)
{
const char *from = NULL;
char *buffer;
virSecretPtr res;
char uuid[VIR_UUID_STRING_BUFLEN];
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
res = virSecretDefineXML(ctl->conn, buffer, 0);
VIR_FREE(buffer);
if (res == NULL) {
vshError(ctl, _("Failed to set attributes from %s"), from);
return false;
}
if (virSecretGetUUIDString(res, &(uuid[0])) < 0) {
vshError(ctl, "%s", _("Failed to get UUID of created secret"));
virSecretFree(res);
return false;
}
vshPrint(ctl, _("Secret %s created\n"), uuid);
virSecretFree(res);
return true;
}
/*
* "secret-dumpxml" command
*/
static const vshCmdInfo info_secret_dumpxml[] = {
{"help", N_("secret attributes in XML")},
{"desc", N_("Output attributes of a secret as an XML dump to stdout.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_secret_dumpxml[] = {
{"secret", VSH_OT_DATA, VSH_OFLAG_REQ, N_("secret UUID")},
{NULL, 0, 0, NULL}
};
static bool
cmdSecretDumpXML(vshControl *ctl, const vshCmd *cmd)
{
virSecretPtr secret;
bool ret = false;
char *xml;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
secret = vshCommandOptSecret(ctl, cmd, NULL);
if (secret == NULL)
return false;
xml = virSecretGetXMLDesc(secret, 0);
if (xml == NULL)
goto cleanup;
vshPrint(ctl, "%s", xml);
VIR_FREE(xml);
ret = true;
cleanup:
virSecretFree(secret);
return ret;
}
/*
* "secret-set-value" command
*/
static const vshCmdInfo info_secret_set_value[] = {
{"help", N_("set a secret value")},
{"desc", N_("Set a secret value.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_secret_set_value[] = {
{"secret", VSH_OT_DATA, VSH_OFLAG_REQ, N_("secret UUID")},
{"base64", VSH_OT_DATA, VSH_OFLAG_REQ, N_("base64-encoded secret value")},
{NULL, 0, 0, NULL}
};
static bool
cmdSecretSetValue(vshControl *ctl, const vshCmd *cmd)
{
virSecretPtr secret;
size_t value_size;
const char *base64 = NULL;
char *value;
int res;
bool ret = false;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
secret = vshCommandOptSecret(ctl, cmd, NULL);
if (secret == NULL)
return false;
if (vshCommandOptString(cmd, "base64", &base64) <= 0)
goto cleanup;
if (!base64_decode_alloc(base64, strlen(base64), &value, &value_size)) {
vshError(ctl, "%s", _("Invalid base64 data"));
goto cleanup;
}
if (value == NULL) {
vshError(ctl, "%s", _("Failed to allocate memory"));
return false;
}
res = virSecretSetValue(secret, (unsigned char *)value, value_size, 0);
memset(value, 0, value_size);
VIR_FREE(value);
if (res != 0) {
vshError(ctl, "%s", _("Failed to set secret value"));
goto cleanup;
}
vshPrint(ctl, "%s", _("Secret value set\n"));
ret = true;
cleanup:
virSecretFree(secret);
return ret;
}
/*
* "secret-get-value" command
*/
static const vshCmdInfo info_secret_get_value[] = {
{"help", N_("Output a secret value")},
{"desc", N_("Output a secret value to stdout.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_secret_get_value[] = {
{"secret", VSH_OT_DATA, VSH_OFLAG_REQ, N_("secret UUID")},
{NULL, 0, 0, NULL}
};
static bool
cmdSecretGetValue(vshControl *ctl, const vshCmd *cmd)
{
virSecretPtr secret;
char *base64;
unsigned char *value;
size_t value_size;
bool ret = false;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
secret = vshCommandOptSecret(ctl, cmd, NULL);
if (secret == NULL)
return false;
value = virSecretGetValue(secret, &value_size, 0);
if (value == NULL)
goto cleanup;
base64_encode_alloc((char *)value, value_size, &base64);
memset(value, 0, value_size);
VIR_FREE(value);
if (base64 == NULL) {
vshError(ctl, "%s", _("Failed to allocate memory"));
goto cleanup;
}
vshPrint(ctl, "%s", base64);
memset(base64, 0, strlen(base64));
VIR_FREE(base64);
ret = true;
cleanup:
virSecretFree(secret);
return ret;
}
/*
* "secret-undefine" command
*/
static const vshCmdInfo info_secret_undefine[] = {
{"help", N_("undefine a secret")},
{"desc", N_("Undefine a secret.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_secret_undefine[] = {
{"secret", VSH_OT_DATA, VSH_OFLAG_REQ, N_("secret UUID")},
{NULL, 0, 0, NULL}
};
static bool
cmdSecretUndefine(vshControl *ctl, const vshCmd *cmd)
{
virSecretPtr secret;
bool ret = false;
const char *uuid;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
secret = vshCommandOptSecret(ctl, cmd, &uuid);
if (secret == NULL)
return false;
if (virSecretUndefine(secret) < 0) {
vshError(ctl, _("Failed to delete secret %s"), uuid);
goto cleanup;
}
vshPrint(ctl, _("Secret %s deleted\n"), uuid);
ret = true;
cleanup:
virSecretFree(secret);
return ret;
}
/*
* "secret-list" command
*/
static const vshCmdInfo info_secret_list[] = {
{"help", N_("list secrets")},
{"desc", N_("Returns a list of secrets")},
{NULL, NULL}
};
static bool
cmdSecretList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
int maxuuids = 0, i;
char **uuids = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
maxuuids = virConnectNumOfSecrets(ctl->conn);
if (maxuuids < 0) {
vshError(ctl, "%s", _("Failed to list secrets"));
return false;
}
uuids = vshMalloc(ctl, sizeof(*uuids) * maxuuids);
maxuuids = virConnectListSecrets(ctl->conn, uuids, maxuuids);
if (maxuuids < 0) {
vshError(ctl, "%s", _("Failed to list secrets"));
VIR_FREE(uuids);
return false;
}
qsort(uuids, maxuuids, sizeof(char *), vshNameSorter);
vshPrintExtra(ctl, "%-36s %s\n", _("UUID"), _("Usage"));
vshPrintExtra(ctl, "-----------------------------------------------------------\n");
for (i = 0; i < maxuuids; i++) {
virSecretPtr sec = virSecretLookupByUUIDString(ctl->conn, uuids[i]);
const char *usageType = NULL;
if (!sec) {
VIR_FREE(uuids[i]);
continue;
}
switch (virSecretGetUsageType(sec)) {
case VIR_SECRET_USAGE_TYPE_VOLUME:
usageType = _("Volume");
break;
}
if (usageType) {
vshPrint(ctl, "%-36s %s %s\n",
uuids[i], usageType,
virSecretGetUsageID(sec));
} else {
vshPrint(ctl, "%-36s %s\n",
uuids[i], _("Unused"));
}
virSecretFree(sec);
VIR_FREE(uuids[i]);
}
VIR_FREE(uuids);
return true;
}
/*
* "version" command
*/
static const vshCmdInfo info_version[] = {
{"help", N_("show version")},
{"desc", N_("Display the system version information.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_version[] = {
{"daemon", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("report daemon version too")},
{NULL, 0, 0, NULL}
};
static bool
cmdVersion(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
unsigned long hvVersion;
const char *hvType;
unsigned long libVersion;
unsigned long includeVersion;
unsigned long apiVersion;
unsigned long daemonVersion;
int ret;
unsigned int major;
unsigned int minor;
unsigned int rel;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
hvType = virConnectGetType(ctl->conn);
if (hvType == NULL) {
vshError(ctl, "%s", _("failed to get hypervisor type"));
return false;
}
includeVersion = LIBVIR_VERSION_NUMBER;
major = includeVersion / 1000000;
includeVersion %= 1000000;
minor = includeVersion / 1000;
rel = includeVersion % 1000;
vshPrint(ctl, _("Compiled against library: libvir %d.%d.%d\n"),
major, minor, rel);
ret = virGetVersion(&libVersion, hvType, &apiVersion);
if (ret < 0) {
vshError(ctl, "%s", _("failed to get the library version"));
return false;
}
major = libVersion / 1000000;
libVersion %= 1000000;
minor = libVersion / 1000;
rel = libVersion % 1000;
vshPrint(ctl, _("Using library: libvir %d.%d.%d\n"),
major, minor, rel);
major = apiVersion / 1000000;
apiVersion %= 1000000;
minor = apiVersion / 1000;
rel = apiVersion % 1000;
vshPrint(ctl, _("Using API: %s %d.%d.%d\n"), hvType,
major, minor, rel);
ret = virConnectGetVersion(ctl->conn, &hvVersion);
if (ret < 0) {
vshError(ctl, "%s", _("failed to get the hypervisor version"));
return false;
}
if (hvVersion == 0) {
vshPrint(ctl,
_("Cannot extract running %s hypervisor version\n"), hvType);
} else {
major = hvVersion / 1000000;
hvVersion %= 1000000;
minor = hvVersion / 1000;
rel = hvVersion % 1000;
vshPrint(ctl, _("Running hypervisor: %s %d.%d.%d\n"),
hvType, major, minor, rel);
}
if (vshCommandOptBool(cmd, "daemon")) {
ret = virConnectGetLibVersion(ctl->conn, &daemonVersion);
if (ret < 0) {
vshError(ctl, "%s", _("failed to get the daemon version"));
} else {
major = daemonVersion / 1000000;
daemonVersion %= 1000000;
minor = daemonVersion / 1000;
rel = daemonVersion % 1000;
vshPrint(ctl, _("Running against daemon: %d.%d.%d\n"),
major, minor, rel);
}
}
return true;
}
/* Tree listing helpers. */
/* Given an index, return either the name of that device (non-NULL) or
* of its parent (NULL if a root). */
typedef const char * (*vshTreeLookup)(int devid, bool parent, void *opaque);
static int
vshTreePrintInternal(vshControl *ctl,
vshTreeLookup lookup,
void *opaque,
int num_devices,
int devid,
int lastdev,
bool root,
virBufferPtr indent)
{
int i;
int nextlastdev = -1;
int ret = -1;
const char *dev = (lookup)(devid, false, opaque);
if (virBufferError(indent))
goto cleanup;
/* Print this device, with indent if not at root */
vshPrint(ctl, "%s%s%s\n", virBufferCurrentContent(indent),
root ? "" : "+- ", dev);
/* Update indent to show '|' or ' ' for child devices */
if (!root) {
virBufferAddChar(indent, devid == lastdev ? ' ' : '|');
virBufferAddChar(indent, ' ');
if (virBufferError(indent))
goto cleanup;
}
/* Determine the index of the last child device */
for (i = 0 ; i < num_devices ; i++) {
const char *parent = (lookup)(i, true, opaque);
if (parent && STREQ(parent, dev))
nextlastdev = i;
}
/* If there is a child device, then print another blank line */
if (nextlastdev != -1)
vshPrint(ctl, "%s |\n", virBufferCurrentContent(indent));
/* Finally print all children */
virBufferAddLit(indent, " ");
for (i = 0 ; i < num_devices ; i++) {
const char *parent = (lookup)(i, true, opaque);
if (parent && STREQ(parent, dev) &&
vshTreePrintInternal(ctl, lookup, opaque,
num_devices, i, nextlastdev,
false, indent) < 0)
goto cleanup;
}
virBufferTrim(indent, " ", -1);
/* If there was no child device, and we're the last in
* a list of devices, then print another blank line */
if (nextlastdev == -1 && devid == lastdev)
vshPrint(ctl, "%s\n", virBufferCurrentContent(indent));
if (!root)
virBufferTrim(indent, NULL, 2);
ret = 0;
cleanup:
return ret;
}
static int
vshTreePrint(vshControl *ctl, vshTreeLookup lookup, void *opaque,
int num_devices, int devid)
{
int ret;
virBuffer indent = VIR_BUFFER_INITIALIZER;
ret = vshTreePrintInternal(ctl, lookup, opaque, num_devices,
devid, devid, true, &indent);
if (ret < 0)
vshError(ctl, "%s", _("Failed to complete tree listing"));
virBufferFreeAndReset(&indent);
return ret;
}
struct vshNodeList {
char **names;
char **parents;
};
static const char *
vshNodeListLookup(int devid, bool parent, void *opaque)
{
struct vshNodeList *arrays = opaque;
if (parent)
return arrays->parents[devid];
return arrays->names[devid];
}
/*
* "nodedev-list" command
*/
static const vshCmdInfo info_node_list_devices[] = {
{"help", N_("enumerate devices on this host")},
{"desc", ""},
{NULL, NULL}
};
static const vshCmdOptDef opts_node_list_devices[] = {
{"tree", VSH_OT_BOOL, 0, N_("list devices in a tree")},
{"cap", VSH_OT_STRING, VSH_OFLAG_NONE, N_("capability name")},
{NULL, 0, 0, NULL}
};
static bool
cmdNodeListDevices(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
const char *cap = NULL;
char **devices;
int num_devices, i;
bool tree = vshCommandOptBool(cmd, "tree");
bool ret = true;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "cap", &cap) <= 0)
cap = NULL;
num_devices = virNodeNumOfDevices(ctl->conn, cap, 0);
if (num_devices < 0) {
vshError(ctl, "%s", _("Failed to count node devices"));
return false;
} else if (num_devices == 0) {
return true;
}
devices = vshMalloc(ctl, sizeof(char *) * num_devices);
num_devices =
virNodeListDevices(ctl->conn, cap, devices, num_devices, 0);
if (num_devices < 0) {
vshError(ctl, "%s", _("Failed to list node devices"));
VIR_FREE(devices);
return false;
}
qsort(&devices[0], num_devices, sizeof(char*), vshNameSorter);
if (tree) {
char **parents = vshMalloc(ctl, sizeof(char *) * num_devices);
struct vshNodeList arrays = { devices, parents };
for (i = 0; i < num_devices; i++) {
virNodeDevicePtr dev = virNodeDeviceLookupByName(ctl->conn, devices[i]);
if (dev && STRNEQ(devices[i], "computer")) {
const char *parent = virNodeDeviceGetParent(dev);
parents[i] = parent ? vshStrdup(ctl, parent) : NULL;
} else {
parents[i] = NULL;
}
virNodeDeviceFree(dev);
}
for (i = 0 ; i < num_devices ; i++) {
if (parents[i] == NULL &&
vshTreePrint(ctl, vshNodeListLookup, &arrays, num_devices,
i) < 0)
ret = false;
}
for (i = 0 ; i < num_devices ; i++) {
VIR_FREE(devices[i]);
VIR_FREE(parents[i]);
}
VIR_FREE(parents);
} else {
for (i = 0; i < num_devices; i++) {
vshPrint(ctl, "%s\n", devices[i]);
VIR_FREE(devices[i]);
}
}
VIR_FREE(devices);
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.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_node_device_dumpxml[] = {
{"device", VSH_OT_DATA, VSH_OFLAG_REQ, N_("device key")},
{NULL, 0, 0, NULL}
};
static bool
cmdNodeDeviceDumpXML(vshControl *ctl, const vshCmd *cmd)
{
const char *name = NULL;
virNodeDevicePtr device;
char *xml;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "device", &name) <= 0)
return false;
if (!(device = virNodeDeviceLookupByName(ctl->conn, name))) {
vshError(ctl, "%s '%s'", _("Could not find matching device"), name);
return false;
}
xml = virNodeDeviceGetXMLDesc(device, 0);
if (!xml) {
virNodeDeviceFree(device);
return false;
}
vshPrint(ctl, "%s\n", xml);
VIR_FREE(xml);
virNodeDeviceFree(device);
return true;
}
/*
* "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.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_node_device_detach[] = {
{"device", VSH_OT_DATA, VSH_OFLAG_REQ, N_("device key")},
{NULL, 0, 0, NULL}
};
static bool
cmdNodeDeviceDetach(vshControl *ctl, const vshCmd *cmd)
{
const char *name = NULL;
virNodeDevicePtr device;
bool ret = true;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "device", &name) <= 0)
return false;
if (!(device = virNodeDeviceLookupByName(ctl->conn, name))) {
vshError(ctl, "%s '%s'", _("Could not find matching device"), name);
return false;
}
/* Yes, our public API is misspelled. At least virsh can accept
* either spelling. */
if (virNodeDeviceDettach(device) == 0) {
vshPrint(ctl, _("Device %s detached\n"), name);
} else {
vshError(ctl, _("Failed to detach device %s"), name);
ret = false;
}
virNodeDeviceFree(device);
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.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_node_device_reattach[] = {
{"device", VSH_OT_DATA, VSH_OFLAG_REQ, N_("device key")},
{NULL, 0, 0, NULL}
};
static bool
cmdNodeDeviceReAttach(vshControl *ctl, const vshCmd *cmd)
{
const char *name = NULL;
virNodeDevicePtr device;
bool ret = true;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "device", &name) <= 0)
return false;
if (!(device = virNodeDeviceLookupByName(ctl->conn, name))) {
vshError(ctl, "%s '%s'", _("Could not find matching device"), name);
return false;
}
if (virNodeDeviceReAttach(device) == 0) {
vshPrint(ctl, _("Device %s re-attached\n"), name);
} else {
vshError(ctl, _("Failed to re-attach device %s"), name);
ret = false;
}
virNodeDeviceFree(device);
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.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_node_device_reset[] = {
{"device", VSH_OT_DATA, VSH_OFLAG_REQ, N_("device key")},
{NULL, 0, 0, NULL}
};
static bool
cmdNodeDeviceReset(vshControl *ctl, const vshCmd *cmd)
{
const char *name = NULL;
virNodeDevicePtr device;
bool ret = true;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "device", &name) <= 0)
return false;
if (!(device = virNodeDeviceLookupByName(ctl->conn, name))) {
vshError(ctl, "%s '%s'", _("Could not find matching device"), name);
return false;
}
if (virNodeDeviceReset(device) == 0) {
vshPrint(ctl, _("Device %s reset\n"), name);
} else {
vshError(ctl, _("Failed to reset device %s"), name);
ret = false;
}
virNodeDeviceFree(device);
return ret;
}
/*
* "hostname" command
*/
static const vshCmdInfo info_hostname[] = {
{"help", N_("print the hypervisor hostname")},
{"desc", ""},
{NULL, NULL}
};
static bool
cmdHostname(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
char *hostname;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
hostname = virConnectGetHostname(ctl->conn);
if (hostname == NULL) {
vshError(ctl, "%s", _("failed to get hostname"));
return false;
}
vshPrint (ctl, "%s\n", hostname);
VIR_FREE(hostname);
return true;
}
/*
* "uri" command
*/
static const vshCmdInfo info_uri[] = {
{"help", N_("print the hypervisor canonical URI")},
{"desc", ""},
{NULL, NULL}
};
static bool
cmdURI(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
char *uri;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
uri = virConnectGetURI(ctl->conn);
if (uri == NULL) {
vshError(ctl, "%s", _("failed to get URI"));
return false;
}
vshPrint(ctl, "%s\n", uri);
VIR_FREE(uri);
return true;
}
/*
* "sysinfo" command
*/
static const vshCmdInfo info_sysinfo[] = {
{"help", N_("print the hypervisor sysinfo")},
{"desc",
N_("output an XML string for the hypervisor sysinfo, if available")},
{NULL, NULL}
};
static bool
cmdSysinfo(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
char *sysinfo;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
sysinfo = virConnectGetSysinfo(ctl->conn, 0);
if (sysinfo == NULL) {
vshError(ctl, "%s", _("failed to get sysinfo"));
return false;
}
vshPrint(ctl, "%s", sysinfo);
VIR_FREE(sysinfo);
return true;
}
/*
* "domdisplay" command
*/
static const vshCmdInfo info_domdisplay[] = {
{"help", N_("domain display connection URI")},
{"desc", N_("Output the IP address and port number for the graphical display.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domdisplay[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"include-password", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("includes the password into the connection URI if available")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomDisplay(vshControl *ctl, const vshCmd *cmd)
{
xmlDocPtr xml = NULL;
xmlXPathContextPtr ctxt = NULL;
virDomainPtr dom;
virBuffer buf = VIR_BUFFER_INITIALIZER;
bool ret = false;
char *doc;
char *xpath;
char *listen_addr;
int port, tls_port = 0;
char *passwd = NULL;
char *output = NULL;
const char *scheme[] = { "vnc", "spice", "rdp", NULL };
int iter = 0;
int tmp;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (!virDomainIsActive(dom)) {
vshError(ctl, _("Domain is not running"));
goto cleanup;
}
doc = virDomainGetXMLDesc(dom, 0);
if (!doc)
goto cleanup;
xml = virXMLParseStringCtxt(doc, _("(domain_definition)"), &ctxt);
VIR_FREE(doc);
if (!xml)
goto cleanup;
/* Attempt to grab our display info */
for (iter = 0; scheme[iter] != NULL; iter++) {
/* Create our XPATH lookup for the current display's port */
virAsprintf(&xpath, "string(/domain/devices/graphics[@type='%s']"
"/@port)", scheme[iter]);
if (!xpath) {
virReportOOMError();
goto cleanup;
}
/* Attempt to get the port number for the current graphics scheme */
tmp = virXPathInt(xpath, ctxt, &port);
VIR_FREE(xpath);
/* If there is no port number for this type, then jump to the next
* scheme
*/
if (tmp)
continue;
/* Create our XPATH lookup for the current display's address */
virAsprintf(&xpath, "string(/domain/devices/graphics[@type='%s']"
"/@listen)", scheme[iter]);
if (!xpath) {
virReportOOMError();
goto cleanup;
}
/* Attempt to get the listening addr if set for the current
* graphics scheme
*/
listen_addr = virXPathString(xpath, ctxt);
VIR_FREE(xpath);
/* Per scheme data mangling */
if (STREQ(scheme[iter], "vnc")) {
/* VNC protocol handlers take their port number as 'port' - 5900 */
port -= 5900;
} else if (STREQ(scheme[iter], "spice")) {
/* Create our XPATH lookup for the SPICE TLS Port */
virAsprintf(&xpath, "string(/domain/devices/graphics[@type='%s']"
"/@tlsPort)", scheme[iter]);
if (!xpath) {
virReportOOMError();
goto cleanup;
}
/* Attempt to get the TLS port number for SPICE */
tmp = virXPathInt(xpath, ctxt, &tls_port);
VIR_FREE(xpath);
if (tmp)
tls_port = 0;
if (vshCommandOptBool(cmd, "daemon")) {
/* Create our XPATH lookup for the SPICE password */
virAsprintf(&xpath, "string(/domain/devices/graphics"
"[@type='%s']/@passwd)", scheme[iter]);
if (!xpath) {
virReportOOMError();
goto cleanup;
}
/* Attempt to get the SPICE password */
passwd = virXPathString(xpath, ctxt);
VIR_FREE(xpath);
}
}
/* Build up the full URI, starting with the scheme */
virBufferAsprintf(&buf, "%s://", scheme[iter]);
/* Then host name or IP */
if (!listen_addr || STREQ((const char *)listen_addr, "0.0.0.0"))
virBufferAddLit(&buf, "localhost");
else
virBufferAsprintf(&buf, "%s", listen_addr);
VIR_FREE(listen_addr);
/* Add the port */
if (STREQ(scheme[iter], "spice"))
virBufferAsprintf(&buf, "?port=%d", port);
else
virBufferAsprintf(&buf, ":%d", port);
/* TLS Port */
if (tls_port)
virBufferAsprintf(&buf, "&tls-port=%d", tls_port);
/* Password */
if (passwd) {
virBufferAsprintf(&buf, "&password=%s", passwd);
VIR_FREE(passwd);
}
/* Ensure we can print our URI */
if (virBufferError(&buf)) {
vshPrint(ctl, "%s", _("Failed to create display URI"));
goto cleanup;
}
/* Print out our full URI */
output = virBufferContentAndReset(&buf);
vshPrint(ctl, "%s", output);
VIR_FREE(output);
/* We got what we came for so return successfully */
ret = true;
break;
}
cleanup:
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
virDomainFree(dom);
return ret;
}
/*
* "vncdisplay" command
*/
static const vshCmdInfo info_vncdisplay[] = {
{"help", N_("vnc display")},
{"desc", N_("Output the IP address and port number for the VNC display.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_vncdisplay[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdVNCDisplay(vshControl *ctl, const vshCmd *cmd)
{
xmlDocPtr xml = NULL;
xmlXPathContextPtr ctxt = NULL;
virDomainPtr dom;
bool ret = false;
int port = 0;
char *doc = NULL;
char *listen_addr = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
/* Check if the domain is active and don't rely on -1 for this */
if (!virDomainIsActive(dom)) {
vshError(ctl, _("Domain is not running"));
goto cleanup;
}
if (!(doc = virDomainGetXMLDesc(dom, 0)))
goto cleanup;
if (!(xml = virXMLParseStringCtxt(doc, _("(domain_definition)"), &ctxt)))
goto cleanup;
/* Get the VNC port */
if (virXPathInt("string(/domain/devices/graphics[@type='vnc']/@port)",
ctxt, &port)) {
vshError(ctl, _("Failed to get VNC port. Is this domain using VNC?"));
goto cleanup;
}
listen_addr = virXPathString("string(/domain/devices/graphics"
"[@type='vnc']/@listen)", ctxt);
if (listen_addr == NULL || STREQ(listen_addr, "0.0.0.0"))
vshPrint(ctl, ":%d\n", port-5900);
else
vshPrint(ctl, "%s:%d\n", listen_addr, port-5900);
ret = true;
cleanup:
VIR_FREE(doc);
VIR_FREE(listen_addr);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
virDomainFree(dom);
return ret;
}
/*
* "ttyconsole" command
*/
static const vshCmdInfo info_ttyconsole[] = {
{"help", N_("tty console")},
{"desc", N_("Output the device for the TTY console.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_ttyconsole[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdTTYConsole(vshControl *ctl, const vshCmd *cmd)
{
xmlDocPtr xml = NULL;
xmlXPathObjectPtr obj = NULL;
xmlXPathContextPtr ctxt = NULL;
virDomainPtr dom;
bool ret = false;
char *doc;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
doc = virDomainGetXMLDesc(dom, 0);
if (!doc)
goto cleanup;
xml = virXMLParseStringCtxt(doc, _("(domain_definition)"), &ctxt);
VIR_FREE(doc);
if (!xml)
goto cleanup;
obj = xmlXPathEval(BAD_CAST "string(/domain/devices/console/@tty)", ctxt);
if (obj == NULL || obj->type != XPATH_STRING ||
obj->stringval == NULL || obj->stringval[0] == 0) {
goto cleanup;
}
vshPrint(ctl, "%s\n", (const char *)obj->stringval);
ret = true;
cleanup:
xmlXPathFreeObject(obj);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
virDomainFree(dom);
return ret;
}
/*
* "attach-device" command
*/
static const vshCmdInfo info_attach_device[] = {
{"help", N_("attach device from an XML file")},
{"desc", N_("Attach device from an XML <file>.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_attach_device[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("XML file")},
{"persistent", VSH_OT_ALIAS, 0, "config"},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{NULL, 0, 0, NULL}
};
static bool
cmdAttachDevice(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *from = NULL;
char *buffer;
int ret;
unsigned int flags;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0) {
virDomainFree(dom);
return false;
}
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
virshReportError(ctl);
virDomainFree(dom);
return false;
}
if (vshCommandOptBool(cmd, "config")) {
flags = VIR_DOMAIN_AFFECT_CONFIG;
if (virDomainIsActive(dom) == 1)
flags |= VIR_DOMAIN_AFFECT_LIVE;
ret = virDomainAttachDeviceFlags(dom, buffer, flags);
} else {
ret = virDomainAttachDevice(dom, buffer);
}
VIR_FREE(buffer);
if (ret < 0) {
vshError(ctl, _("Failed to attach device from %s"), from);
virDomainFree(dom);
return false;
} else {
vshPrint(ctl, "%s", _("Device attached successfully\n"));
}
virDomainFree(dom);
return true;
}
/**
* Check if n1 is superset of n2, meaning n1 contains all elements and
* attributes as n2 at least. Including children.
* @n1 first node
* @n2 second node
* returns true in case n1 covers n2, false otherwise.
*/
ATTRIBUTE_UNUSED
static bool
vshNodeIsSuperset(xmlNodePtr n1, xmlNodePtr n2)
{
xmlNodePtr child1, child2;
xmlAttrPtr attr;
char *prop1, *prop2;
bool found;
bool visited;
bool ret = false;
long n1_child_size, n2_child_size, n1_iter;
virBitmapPtr bitmap;
if (!n1 && !n2)
return true;
if (!n1 || !n2)
return false;
if (!xmlStrEqual(n1->name, n2->name))
return false;
/* Iterate over n2 attributes and check if n1 contains them*/
attr = n2->properties;
while (attr) {
if (attr->type == XML_ATTRIBUTE_NODE) {
prop1 = virXMLPropString(n1, (const char *) attr->name);
prop2 = virXMLPropString(n2, (const char *) attr->name);
if (STRNEQ_NULLABLE(prop1, prop2)) {
xmlFree(prop1);
xmlFree(prop2);
return false;
}
xmlFree(prop1);
xmlFree(prop2);
}
attr = attr->next;
}
n1_child_size = virXMLChildElementCount(n1);
n2_child_size = virXMLChildElementCount(n2);
if (n1_child_size < 0 || n2_child_size < 0 ||
n1_child_size < n2_child_size)
return false;
if (n1_child_size == 0 && n2_child_size == 0)
return true;
if (!(bitmap = virBitmapAlloc(n1_child_size))) {
virReportOOMError();
return false;
}
child2 = n2->children;
while (child2) {
if (child2->type != XML_ELEMENT_NODE) {
child2 = child2->next;
continue;
}
child1 = n1->children;
n1_iter = 0;
found = false;
while (child1) {
if (child1->type != XML_ELEMENT_NODE) {
child1 = child1->next;
continue;
}
if (virBitmapGetBit(bitmap, n1_iter, &visited) < 0) {
vshError(NULL, "%s", _("Bad child elements counting."));
goto cleanup;
}
if (visited) {
child1 = child1->next;
n1_iter++;
continue;
}
if (xmlStrEqual(child1->name, child2->name)) {
found = true;
if (virBitmapSetBit(bitmap, n1_iter) < 0) {
vshError(NULL, "%s", _("Bad child elements counting."));
goto cleanup;
}
if (!vshNodeIsSuperset(child1, child2))
goto cleanup;
break;
}
child1 = child1->next;
n1_iter++;
}
if (!found)
goto cleanup;
child2 = child2->next;
}
ret = true;
cleanup:
virBitmapFree(bitmap);
return ret;
}
/**
* vshCompleteXMLFromDomain:
* @ctl vshControl for error messages printing
* @dom domain
* @oldXML device XML before
* @newXML and after completion
*
* For given domain and (probably incomplete) device XML specification try to
* find such device in domain and complete missing parts. This is however
* possible only when given device XML is sufficiently precise so it addresses
* only one device.
*
* Returns -2 when no such device exists in domain, -3 when given XML selects many
* (is too ambiguous), 0 in case of success. Otherwise returns -1. @newXML
* is touched only in case of success.
*/
ATTRIBUTE_UNUSED
static int
vshCompleteXMLFromDomain(vshControl *ctl, virDomainPtr dom, char *oldXML,
char **newXML)
{
int funcRet = -1;
char *domXML = NULL;
xmlDocPtr domDoc = NULL, devDoc = NULL;
xmlNodePtr node = NULL;
xmlXPathContextPtr domCtxt = NULL, devCtxt = NULL;
xmlNodePtr *devices = NULL;
xmlSaveCtxtPtr sctxt = NULL;
int devices_size;
char *xpath = NULL;
xmlBufferPtr buf = NULL;
int i = 0;
int indx = -1;
if (!(domXML = virDomainGetXMLDesc(dom, 0))) {
vshError(ctl, _("couldn't get XML description of domain %s"),
virDomainGetName(dom));
goto cleanup;
}
domDoc = virXMLParseStringCtxt(domXML, _("(domain_definition)"), &domCtxt);
if (!domDoc) {
vshError(ctl, _("Failed to parse domain definition xml"));
goto cleanup;
}
devDoc = virXMLParseStringCtxt(oldXML, _("(device_definition)"), &devCtxt);
if (!devDoc) {
vshError(ctl, _("Failed to parse device definition xml"));
goto cleanup;
}
node = xmlDocGetRootElement(devDoc);
buf = xmlBufferCreate();
if (!buf) {
vshError(ctl, "%s", _("out of memory"));
goto cleanup;
}
/* Get all possible devices */
virAsprintf(&xpath, "/domain/devices/%s", node->name);
if (!xpath) {
virReportOOMError();
goto cleanup;
}
devices_size = virXPathNodeSet(xpath, domCtxt, &devices);
if (devices_size < 0) {
/* error */
vshError(ctl, "%s", _("error when selecting nodes"));
goto cleanup;
} else if (devices_size == 0) {
/* no such device */
funcRet = -2;
goto cleanup;
}
/* and refine */
for (i = 0; i < devices_size; i++) {
if (vshNodeIsSuperset(devices[i], node)) {
if (indx >= 0) {
funcRet = -3; /* ambiguous */
goto cleanup;
}
indx = i;
}
}
if (indx < 0) {
funcRet = -2; /* no such device */
goto cleanup;
}
vshDebug(ctl, VSH_ERR_DEBUG, "Found device at pos %d\n", indx);
if (newXML) {
sctxt = xmlSaveToBuffer(buf, NULL, 0);
if (!sctxt) {
vshError(ctl, "%s", _("failed to create document saving context"));
goto cleanup;
}
xmlSaveTree(sctxt, devices[indx]);
xmlSaveClose(sctxt);
*newXML = (char *) xmlBufferContent(buf);
if (!*newXML) {
virReportOOMError();
goto cleanup;
}
buf->content = NULL;
}
vshDebug(ctl, VSH_ERR_DEBUG, "Old xml:\n%s\nNew xml:\n%s\n", oldXML,
newXML ? NULLSTR(*newXML) : "(null)");
funcRet = 0;
cleanup:
xmlBufferFree(buf);
VIR_FREE(devices);
xmlXPathFreeContext(devCtxt);
xmlXPathFreeContext(domCtxt);
xmlFreeDoc(devDoc);
xmlFreeDoc(domDoc);
VIR_FREE(domXML);
VIR_FREE(xpath);
return funcRet;
}
/*
* "detach-device" command
*/
static const vshCmdInfo info_detach_device[] = {
{"help", N_("detach device from an XML file")},
{"desc", N_("Detach device from an XML <file>")},
{NULL, NULL}
};
static const vshCmdOptDef opts_detach_device[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("XML file")},
{"persistent", VSH_OT_ALIAS, 0, "config"},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{NULL, 0, 0, NULL}
};
static bool
cmdDetachDevice(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
const char *from = NULL;
char *buffer = NULL;
int ret;
bool funcRet = false;
unsigned int flags;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
goto cleanup;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
virshReportError(ctl);
goto cleanup;
}
if (vshCommandOptBool(cmd, "config")) {
flags = VIR_DOMAIN_AFFECT_CONFIG;
if (virDomainIsActive(dom) == 1)
flags |= VIR_DOMAIN_AFFECT_LIVE;
ret = virDomainDetachDeviceFlags(dom, buffer, flags);
} else {
ret = virDomainDetachDevice(dom, buffer);
}
if (ret < 0) {
vshError(ctl, _("Failed to detach device from %s"), from);
goto cleanup;
}
vshPrint(ctl, "%s", _("Device detached successfully\n"));
funcRet = true;
cleanup:
VIR_FREE(buffer);
virDomainFree(dom);
return funcRet;
}
/*
* "update-device" command
*/
static const vshCmdInfo info_update_device[] = {
{"help", N_("update device from an XML file")},
{"desc", N_("Update device from an XML <file>.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_update_device[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("XML file")},
{"persistent", VSH_OT_ALIAS, 0, "config"},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"force", VSH_OT_BOOL, 0, N_("force device update")},
{NULL, 0, 0, NULL}
};
static bool
cmdUpdateDevice(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
const char *from = NULL;
char *buffer;
int ret;
unsigned int flags;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0) {
virDomainFree(dom);
return false;
}
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
virshReportError(ctl);
virDomainFree(dom);
return false;
}
if (vshCommandOptBool(cmd, "config")) {
flags = VIR_DOMAIN_AFFECT_CONFIG;
if (virDomainIsActive(dom) == 1)
flags |= VIR_DOMAIN_AFFECT_LIVE;
} else {
flags = VIR_DOMAIN_AFFECT_LIVE;
}
if (vshCommandOptBool(cmd, "force"))
flags |= VIR_DOMAIN_DEVICE_MODIFY_FORCE;
ret = virDomainUpdateDeviceFlags(dom, buffer, flags);
VIR_FREE(buffer);
if (ret < 0) {
vshError(ctl, _("Failed to update device from %s"), from);
virDomainFree(dom);
return false;
} else {
vshPrint(ctl, "%s", _("Device updated successfully\n"));
}
virDomainFree(dom);
return true;
}
/*
* "attach-interface" command
*/
static const vshCmdInfo info_attach_interface[] = {
{"help", N_("attach network interface")},
{"desc", N_("Attach new network interface.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_attach_interface[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"type", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network interface type")},
{"source", VSH_OT_DATA, VSH_OFLAG_REQ, N_("source of network interface")},
{"target", VSH_OT_DATA, 0, N_("target network name")},
{"mac", VSH_OT_DATA, 0, N_("MAC address")},
{"script", VSH_OT_DATA, 0, N_("script used to bridge network interface")},
{"model", VSH_OT_DATA, 0, N_("model type")},
{"persistent", VSH_OT_ALIAS, 0, "config"},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"inbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's incoming traffics")},
{"outbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's outgoing traffics")},
{NULL, 0, 0, NULL}
};
/* parse inbound and outbound which are in the format of
* 'average,peak,burst', in which peak and burst are optional,
* thus 'average,,burst' and 'average,peak' are also legal. */
static int parseRateStr(const char *rateStr, virNetDevBandwidthRatePtr rate)
{
const char *average = NULL;
char *peak = NULL, *burst = NULL;
average = rateStr;
if (!average)
return -1;
if (virStrToLong_ull(average, &peak, 10, &rate->average) < 0)
return -1;
/* peak will be updated to point to the end of rateStr in case
* of 'average' */
if (peak && *peak != '\0') {
burst = strchr(peak + 1, ',');
if (!(burst && (burst - peak == 1))) {
if (virStrToLong_ull(peak + 1, &burst, 10, &rate->peak) < 0)
return -1;
}
/* burst will be updated to point to the end of rateStr in case
* of 'average,peak' */
if (burst && *burst != '\0') {
if (virStrToLong_ull(burst + 1, NULL, 10, &rate->burst) < 0)
return -1;
}
}
return 0;
}
static bool
cmdAttachInterface(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
const char *mac = NULL, *target = NULL, *script = NULL,
*type = NULL, *source = NULL, *model = NULL,
*inboundStr = NULL, *outboundStr = NULL;
virNetDevBandwidthRate inbound, outbound;
int typ;
int ret;
bool functionReturn = false;
unsigned int flags;
virBuffer buf = VIR_BUFFER_INITIALIZER;
char *xml;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
goto cleanup;
if (vshCommandOptString(cmd, "type", &type) <= 0)
goto cleanup;
if (vshCommandOptString(cmd, "source", &source) < 0 ||
vshCommandOptString(cmd, "target", &target) < 0 ||
vshCommandOptString(cmd, "mac", &mac) < 0 ||
vshCommandOptString(cmd, "script", &script) < 0 ||
vshCommandOptString(cmd, "model", &model) < 0 ||
vshCommandOptString(cmd, "inbound", &inboundStr) < 0 ||
vshCommandOptString(cmd, "outbound", &outboundStr) < 0) {
vshError(ctl, "missing argument");
goto cleanup;
}
/* check interface type */
if (STREQ(type, "network")) {
typ = 1;
} else if (STREQ(type, "bridge")) {
typ = 2;
} else {
vshError(ctl, _("No support for %s in command 'attach-interface'"),
type);
goto cleanup;
}
if (inboundStr) {
memset(&inbound, 0, sizeof(inbound));
if (parseRateStr(inboundStr, &inbound) < 0) {
vshError(ctl, _("inbound format is incorrect"));
goto cleanup;
}
if (inbound.average == 0) {
vshError(ctl, _("inbound average is mandatory"));
goto cleanup;
}
}
if (outboundStr) {
memset(&outbound, 0, sizeof(outbound));
if (parseRateStr(outboundStr, &outbound) < 0) {
vshError(ctl, _("outbound format is incorrect"));
goto cleanup;
}
if (outbound.average == 0) {
vshError(ctl, _("outbound average is mandatory"));
goto cleanup;
}
}
/* Make XML of interface */
virBufferAsprintf(&buf, "<interface type='%s'>\n", type);
if (typ == 1)
virBufferAsprintf(&buf, " <source network='%s'/>\n", source);
else if (typ == 2)
virBufferAsprintf(&buf, " <source bridge='%s'/>\n", source);
if (target != NULL)
virBufferAsprintf(&buf, " <target dev='%s'/>\n", target);
if (mac != NULL)
virBufferAsprintf(&buf, " <mac address='%s'/>\n", mac);
if (script != NULL)
virBufferAsprintf(&buf, " <script path='%s'/>\n", script);
if (model != NULL)
virBufferAsprintf(&buf, " <model type='%s'/>\n", model);
if (inboundStr || outboundStr) {
virBufferAsprintf(&buf, " <bandwidth>\n");
if (inboundStr && inbound.average > 0) {
virBufferAsprintf(&buf, " <inbound average='%llu'", inbound.average);
if (inbound.peak > 0)
virBufferAsprintf(&buf, " peak='%llu'", inbound.peak);
if (inbound.burst > 0)
virBufferAsprintf(&buf, " burst='%llu'", inbound.burst);
virBufferAsprintf(&buf, "/>\n");
}
if (outboundStr && outbound.average > 0) {
virBufferAsprintf(&buf, " <outbound average='%llu'", outbound.average);
if (outbound.peak > 0)
virBufferAsprintf(&buf, " peak='%llu'", outbound.peak);
if (outbound.burst > 0)
virBufferAsprintf(&buf, " burst='%llu'", outbound.burst);
virBufferAsprintf(&buf, "/>\n");
}
virBufferAsprintf(&buf, " </bandwidth>\n");
}
virBufferAddLit(&buf, "</interface>\n");
if (virBufferError(&buf)) {
vshPrint(ctl, "%s", _("Failed to allocate XML buffer"));
goto cleanup;
}
xml = virBufferContentAndReset(&buf);
if (vshCommandOptBool(cmd, "config")) {
flags = VIR_DOMAIN_AFFECT_CONFIG;
if (virDomainIsActive(dom) == 1)
flags |= VIR_DOMAIN_AFFECT_LIVE;
ret = virDomainAttachDeviceFlags(dom, xml, flags);
} else {
ret = virDomainAttachDevice(dom, xml);
}
VIR_FREE(xml);
if (ret != 0) {
vshError(ctl, "%s", _("Failed to attach interface"));
} else {
vshPrint(ctl, "%s", _("Interface attached successfully\n"));
functionReturn = true;
}
cleanup:
if (dom)
virDomainFree(dom);
virBufferFreeAndReset(&buf);
return functionReturn;
}
/*
* "detach-interface" command
*/
static const vshCmdInfo info_detach_interface[] = {
{"help", N_("detach network interface")},
{"desc", N_("Detach network interface.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_detach_interface[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"type", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network interface type")},
{"mac", VSH_OT_STRING, 0, N_("MAC address")},
{"persistent", VSH_OT_ALIAS, 0, "config"},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{NULL, 0, 0, NULL}
};
static bool
cmdDetachInterface(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
xmlDocPtr xml = NULL;
xmlXPathObjectPtr obj=NULL;
xmlXPathContextPtr ctxt = NULL;
xmlNodePtr cur = NULL;
xmlBufferPtr xml_buf = NULL;
const char *mac =NULL, *type = NULL;
char *doc;
char buf[64];
int i = 0, diff_mac;
int ret;
int functionReturn = false;
unsigned int flags;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
goto cleanup;
if (vshCommandOptString(cmd, "type", &type) <= 0)
goto cleanup;
if (vshCommandOptString(cmd, "mac", &mac) < 0) {
vshError(ctl, "%s", _("missing option"));
goto cleanup;
}
doc = virDomainGetXMLDesc(dom, 0);
if (!doc)
goto cleanup;
xml = virXMLParseStringCtxt(doc, _("(domain_definition)"), &ctxt);
VIR_FREE(doc);
if (!xml) {
vshError(ctl, "%s", _("Failed to get interface information"));
goto cleanup;
}
snprintf(buf, sizeof(buf), "/domain/devices/interface[@type='%s']", type);
obj = xmlXPathEval(BAD_CAST buf, ctxt);
if (obj == NULL || obj->type != XPATH_NODESET ||
obj->nodesetval == NULL || obj->nodesetval->nodeNr == 0) {
vshError(ctl, _("No found interface whose type is %s"), type);
goto cleanup;
}
if (!mac && obj->nodesetval->nodeNr > 1) {
vshError(ctl, _("Domain has %d interfaces. Please specify which one "
"to detach using --mac"), obj->nodesetval->nodeNr);
goto cleanup;
}
if (!mac)
goto hit;
/* search mac */
for (; i < obj->nodesetval->nodeNr; i++) {
cur = obj->nodesetval->nodeTab[i]->children;
while (cur != NULL) {
if (cur->type == XML_ELEMENT_NODE &&
xmlStrEqual(cur->name, BAD_CAST "mac")) {
char *tmp_mac = virXMLPropString(cur, "address");
diff_mac = virMacAddrCompare(tmp_mac, mac);
VIR_FREE(tmp_mac);
if (!diff_mac) {
goto hit;
}
}
cur = cur->next;
}
}
vshError(ctl, _("No found interface whose MAC address is %s"), mac);
goto cleanup;
hit:
xml_buf = xmlBufferCreate();
if (!xml_buf) {
vshError(ctl, "%s", _("Failed to allocate memory"));
goto cleanup;
}
if (xmlNodeDump(xml_buf, xml, obj->nodesetval->nodeTab[i], 0, 0) < 0) {
vshError(ctl, "%s", _("Failed to create XML"));
goto cleanup;
}
if (vshCommandOptBool(cmd, "config")) {
flags = VIR_DOMAIN_AFFECT_CONFIG;
if (virDomainIsActive(dom) == 1)
flags |= VIR_DOMAIN_AFFECT_LIVE;
ret = virDomainDetachDeviceFlags(dom,
(char *)xmlBufferContent(xml_buf),
flags);
} else {
ret = virDomainDetachDevice(dom, (char *)xmlBufferContent(xml_buf));
}
if (ret != 0) {
vshError(ctl, "%s", _("Failed to detach interface"));
} else {
vshPrint(ctl, "%s", _("Interface detached successfully\n"));
functionReturn = true;
}
cleanup:
if (dom)
virDomainFree(dom);
xmlXPathFreeObject(obj);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
xmlBufferFree(xml_buf);
return functionReturn;
}
/*
* "attach-disk" command
*/
static const vshCmdInfo info_attach_disk[] = {
{"help", N_("attach disk device")},
{"desc", N_("Attach new disk device.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_attach_disk[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"source", VSH_OT_DATA, VSH_OFLAG_REQ | VSH_OFLAG_EMPTY_OK,
N_("source of disk device")},
{"target", VSH_OT_DATA, VSH_OFLAG_REQ, N_("target of disk device")},
{"driver", VSH_OT_STRING, 0, N_("driver of disk device")},
{"subdriver", VSH_OT_STRING, 0, N_("subdriver of disk device")},
{"cache", VSH_OT_STRING, 0, N_("cache mode of disk device")},
{"type", VSH_OT_STRING, 0, N_("target device type")},
{"mode", VSH_OT_STRING, 0, N_("mode of device reading and writing")},
{"persistent", VSH_OT_ALIAS, 0, "config"},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{"sourcetype", VSH_OT_STRING, 0, N_("type of source (block|file)")},
{"serial", VSH_OT_STRING, 0, N_("serial of disk device")},
{"shareable", VSH_OT_BOOL, 0, N_("shareable between domains")},
{"rawio", VSH_OT_BOOL, 0, N_("needs rawio capability")},
{"address", VSH_OT_STRING, 0, N_("address of disk device")},
{"multifunction", VSH_OT_BOOL, 0,
N_("use multifunction pci under specified address")},
{NULL, 0, 0, NULL}
};
enum {
DISK_ADDR_TYPE_INVALID,
DISK_ADDR_TYPE_PCI,
DISK_ADDR_TYPE_SCSI,
DISK_ADDR_TYPE_IDE,
};
struct PCIAddress {
unsigned int domain;
unsigned int bus;
unsigned int slot;
unsigned int function;
};
struct SCSIAddress {
unsigned int controller;
unsigned int bus;
unsigned int unit;
};
struct IDEAddress {
unsigned int controller;
unsigned int bus;
unsigned int unit;
};
struct DiskAddress {
int type;
union {
struct PCIAddress pci;
struct SCSIAddress scsi;
struct IDEAddress ide;
} addr;
};
static int str2PCIAddress(const char *str, struct PCIAddress *pciAddr)
{
char *domain, *bus, *slot, *function;
if (!pciAddr)
return -1;
if (!str)
return -1;
domain = (char *)str;
if (virStrToLong_ui(domain, &bus, 0, &pciAddr->domain) != 0)
return -1;
bus++;
if (virStrToLong_ui(bus, &slot, 0, &pciAddr->bus) != 0)
return -1;
slot++;
if (virStrToLong_ui(slot, &function, 0, &pciAddr->slot) != 0)
return -1;
function++;
if (virStrToLong_ui(function, NULL, 0, &pciAddr->function) != 0)
return -1;
return 0;
}
static int str2SCSIAddress(const char *str, struct SCSIAddress *scsiAddr)
{
char *controller, *bus, *unit;
if (!scsiAddr)
return -1;
if (!str)
return -1;
controller = (char *)str;
if (virStrToLong_ui(controller, &bus, 0, &scsiAddr->controller) != 0)
return -1;
bus++;
if (virStrToLong_ui(bus, &unit, 0, &scsiAddr->bus) != 0)
return -1;
unit++;
if (virStrToLong_ui(unit, NULL, 0, &scsiAddr->unit) != 0)
return -1;
return 0;
}
static int str2IDEAddress(const char *str, struct IDEAddress *ideAddr)
{
char *controller, *bus, *unit;
if (!ideAddr)
return -1;
if (!str)
return -1;
controller = (char *)str;
if (virStrToLong_ui(controller, &bus, 0, &ideAddr->controller) != 0)
return -1;
bus++;
if (virStrToLong_ui(bus, &unit, 0, &ideAddr->bus) != 0)
return -1;
unit++;
if (virStrToLong_ui(unit, NULL, 0, &ideAddr->unit) != 0)
return -1;
return 0;
}
/* pci address pci:0000.00.0x0a.0 (domain:bus:slot:function)
* ide disk address: ide:00.00.0 (controller:bus:unit)
* scsi disk address: scsi:00.00.0 (controller:bus:unit)
*/
static int str2DiskAddress(const char *str, struct DiskAddress *diskAddr)
{
char *type, *addr;
if (!diskAddr)
return -1;
if (!str)
return -1;
type = (char *)str;
addr = strchr(type, ':');
if (!addr)
return -1;
if (STREQLEN(type, "pci", addr - type)) {
diskAddr->type = DISK_ADDR_TYPE_PCI;
return str2PCIAddress(addr + 1, &diskAddr->addr.pci);
} else if (STREQLEN(type, "scsi", addr - type)) {
diskAddr->type = DISK_ADDR_TYPE_SCSI;
return str2SCSIAddress(addr + 1, &diskAddr->addr.scsi);
} else if (STREQLEN(type, "ide", addr - type)) {
diskAddr->type = DISK_ADDR_TYPE_IDE;
return str2IDEAddress(addr + 1, &diskAddr->addr.ide);
}
return -1;
}
static bool
cmdAttachDisk(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
const char *source = NULL, *target = NULL, *driver = NULL,
*subdriver = NULL, *type = NULL, *mode = NULL,
*cache = NULL, *serial = NULL, *straddr = NULL;
struct DiskAddress diskAddr;
bool isFile = false, functionReturn = false;
int ret;
unsigned int flags;
const char *stype = NULL;
virBuffer buf = VIR_BUFFER_INITIALIZER;
char *xml;
struct stat st;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
goto cleanup;
if (vshCommandOptString(cmd, "source", &source) <= 0)
goto cleanup;
/* Allow empty string as a placeholder that implies no source, for
* use in adding a cdrom drive with no disk. */
if (!*source)
source = NULL;
if (vshCommandOptString(cmd, "target", &target) <= 0)
goto cleanup;
if (vshCommandOptString(cmd, "driver", &driver) < 0 ||
vshCommandOptString(cmd, "subdriver", &subdriver) < 0 ||
vshCommandOptString(cmd, "type", &type) < 0 ||
vshCommandOptString(cmd, "mode", &mode) < 0 ||
vshCommandOptString(cmd, "cache", &cache) < 0 ||
vshCommandOptString(cmd, "serial", &serial) < 0 ||
vshCommandOptString(cmd, "address", &straddr) < 0 ||
vshCommandOptString(cmd, "sourcetype", &stype) < 0) {
vshError(ctl, "%s", _("missing option"));
goto cleanup;
}
if (!stype) {
if (driver && (STREQ(driver, "file") || STREQ(driver, "tap"))) {
isFile = true;
} else {
if (source && !stat(source, &st))
isFile = S_ISREG(st.st_mode) ? true : false;
}
} else if (STREQ(stype, "file")) {
isFile = true;
} else if (STRNEQ(stype, "block")) {
vshError(ctl, _("Unknown source type: '%s'"), stype);
goto cleanup;
}
if (mode) {
if (STRNEQ(mode, "readonly") && STRNEQ(mode, "shareable")) {
vshError(ctl, _("No support for %s in command 'attach-disk'"),
mode);
goto cleanup;
}
}
/* Make XML of disk */
virBufferAsprintf(&buf, "<disk type='%s'",
(isFile) ? "file" : "block");
if (type)
virBufferAsprintf(&buf, " device='%s'", type);
if (vshCommandOptBool(cmd, "rawio"))
virBufferAddLit(&buf, " rawio='yes'");
virBufferAddLit(&buf, ">\n");
if (driver || subdriver || cache) {
virBufferAsprintf(&buf, " <driver");
if (driver)
virBufferAsprintf(&buf, " name='%s'", driver);
if (subdriver)
virBufferAsprintf(&buf, " type='%s'", subdriver);
if (cache)
virBufferAsprintf(&buf, " cache='%s'", cache);
virBufferAddLit(&buf, "/>\n");
}
if (source)
virBufferAsprintf(&buf, " <source %s='%s'/>\n",
(isFile) ? "file" : "dev",
source);
virBufferAsprintf(&buf, " <target dev='%s'/>\n", target);
if (mode)
virBufferAsprintf(&buf, " <%s/>\n", mode);
if (serial)
virBufferAsprintf(&buf, " <serial>%s</serial>\n", serial);
if (vshCommandOptBool(cmd, "shareable"))
virBufferAsprintf(&buf, " <shareable/>\n");
if (straddr) {
if (str2DiskAddress(straddr, &diskAddr) != 0) {
vshError(ctl, _("Invalid address."));
goto cleanup;
}
if (STRPREFIX((const char *)target, "vd")) {
if (diskAddr.type == DISK_ADDR_TYPE_PCI) {
virBufferAsprintf(&buf,
" <address type='pci' domain='0x%04x'"
" bus ='0x%02x' slot='0x%02x' function='0x%0x'",
diskAddr.addr.pci.domain, diskAddr.addr.pci.bus,
diskAddr.addr.pci.slot, diskAddr.addr.pci.function);
if (vshCommandOptBool(cmd, "multifunction"))
virBufferAddLit(&buf, " multifunction='on'");
virBufferAddLit(&buf, "/>\n");
} else {
vshError(ctl, "%s", _("expecting a pci:0000.00.00.00 address."));
goto cleanup;
}
} else if (STRPREFIX((const char *)target, "sd")) {
if (diskAddr.type == DISK_ADDR_TYPE_SCSI) {
virBufferAsprintf(&buf,
" <address type='drive' controller='%d'"
" bus='%d' unit='%d' />\n",
diskAddr.addr.scsi.controller, diskAddr.addr.scsi.bus,
diskAddr.addr.scsi.unit);
} else {
vshError(ctl, "%s", _("expecting a scsi:00.00.00 address."));
goto cleanup;
}
} else if (STRPREFIX((const char *)target, "hd")) {
if (diskAddr.type == DISK_ADDR_TYPE_IDE) {
virBufferAsprintf(&buf,
" <address type='drive' controller='%d'"
" bus='%d' unit='%d' />\n",
diskAddr.addr.ide.controller, diskAddr.addr.ide.bus,
diskAddr.addr.ide.unit);
} else {
vshError(ctl, "%s", _("expecting an ide:00.00.00 address."));
goto cleanup;
}
}
}
virBufferAddLit(&buf, "</disk>\n");
if (virBufferError(&buf)) {
vshPrint(ctl, "%s", _("Failed to allocate XML buffer"));
return false;
}
xml = virBufferContentAndReset(&buf);
if (vshCommandOptBool(cmd, "config")) {
flags = VIR_DOMAIN_AFFECT_CONFIG;
if (virDomainIsActive(dom) == 1)
flags |= VIR_DOMAIN_AFFECT_LIVE;
ret = virDomainAttachDeviceFlags(dom, xml, flags);
} else {
ret = virDomainAttachDevice(dom, xml);
}
VIR_FREE(xml);
if (ret != 0) {
vshError(ctl, "%s", _("Failed to attach disk"));
} else {
vshPrint(ctl, "%s", _("Disk attached successfully\n"));
functionReturn = true;
}
cleanup:
if (dom)
virDomainFree(dom);
virBufferFreeAndReset(&buf);
return functionReturn;
}
typedef enum {
VSH_FIND_DISK_NORMAL,
VSH_FIND_DISK_CHANGEABLE,
} vshFindDiskType;
/* Helper function to find disk device in XML doc. Returns the disk
* node on success, or NULL on failure. Caller must free the result
* @path: Fully-qualified path or target of disk device.
* @type: Either VSH_FIND_DISK_NORMAL or VSH_FIND_DISK_CHANGEABLE.
*/
static xmlNodePtr
vshFindDisk(const char *doc,
const char *path,
int type)
{
xmlDocPtr xml = NULL;
xmlXPathObjectPtr obj= NULL;
xmlXPathContextPtr ctxt = NULL;
xmlNodePtr cur = NULL;
xmlNodePtr ret = NULL;
int i = 0;
xml = virXMLParseStringCtxt(doc, _("(domain_definition)"), &ctxt);
if (!xml) {
vshError(NULL, "%s", _("Failed to get disk information"));
goto cleanup;
}
obj = xmlXPathEval(BAD_CAST "/domain/devices/disk", ctxt);
if (obj == NULL ||
obj->type != XPATH_NODESET ||
obj->nodesetval == NULL ||
obj->nodesetval->nodeNr == 0) {
vshError(NULL, "%s", _("Failed to get disk information"));
goto cleanup;
}
/* search disk using @path */
for (; i < obj->nodesetval->nodeNr; i++) {
bool is_supported = true;
if (type == VSH_FIND_DISK_CHANGEABLE) {
xmlNodePtr n = obj->nodesetval->nodeTab[i];
is_supported = false;
/* Check if the disk is CDROM or floppy disk */
if (xmlStrEqual(n->name, BAD_CAST "disk")) {
char *device_value = virXMLPropString(n, "device");
if (STREQ(device_value, "cdrom") ||
STREQ(device_value, "floppy"))
is_supported = true;
VIR_FREE(device_value);
}
if (!is_supported)
continue;
}
cur = obj->nodesetval->nodeTab[i]->children;
while (cur != NULL) {
if (cur->type == XML_ELEMENT_NODE) {
char *tmp = NULL;
if (xmlStrEqual(cur->name, BAD_CAST "source")) {
if ((tmp = virXMLPropString(cur, "file")) ||
(tmp = virXMLPropString(cur, "dev")) ||
(tmp = virXMLPropString(cur, "dir")) ||
(tmp = virXMLPropString(cur, "name"))) {
}
} else if (xmlStrEqual(cur->name, BAD_CAST "target")) {
tmp = virXMLPropString(cur, "dev");
}
if (STREQ_NULLABLE(tmp, path)) {
ret = xmlCopyNode(obj->nodesetval->nodeTab[i], 1);
VIR_FREE(tmp);
goto cleanup;
}
VIR_FREE(tmp);
}
cur = cur->next;
}
}
vshError(NULL, _("No found disk whose source path or target is %s"), path);
cleanup:
xmlXPathFreeObject(obj);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
return ret;
}
typedef enum {
VSH_PREPARE_DISK_XML_NONE = 0,
VSH_PREPARE_DISK_XML_EJECT,
VSH_PREPARE_DISK_XML_INSERT,
VSH_PREPARE_DISK_XML_UPDATE,
} vshPrepareDiskXMLType;
/* Helper function to prepare disk XML. Could be used for disk
* detaching, media changing(ejecting, inserting, updating)
* for changeable disk. Returns the processed XML as string on
* success, or NULL on failure. Caller must free the result.
*/
static char *
vshPrepareDiskXML(xmlNodePtr disk_node,
const char *source,
const char *path,
int type)
{
xmlNodePtr cur = NULL;
xmlBufferPtr xml_buf = NULL;
const char *disk_type = NULL;
const char *device_type = NULL;
xmlNodePtr new_node = NULL;
char *ret = NULL;
if (!disk_node)
return NULL;
xml_buf = xmlBufferCreate();
if (!xml_buf) {
vshError(NULL, "%s", _("Failed to allocate memory"));
return NULL;
}
device_type = virXMLPropString(disk_node, "device");
if (STREQ_NULLABLE(device_type, "cdrom") ||
STREQ_NULLABLE(device_type, "floppy")) {
bool has_source = false;
disk_type = virXMLPropString(disk_node, "type");
cur = disk_node->children;
while (cur != NULL) {
if (cur->type == XML_ELEMENT_NODE &&
xmlStrEqual(cur->name, BAD_CAST "source")) {
has_source = true;
break;
}
cur = cur->next;
}
if (!has_source) {
if (type == VSH_PREPARE_DISK_XML_EJECT) {
vshError(NULL, _("The disk device '%s' doesn't have media"),
path);
goto error;
}
if (source) {
new_node = xmlNewNode(NULL, BAD_CAST "source");
xmlNewProp(new_node, (const xmlChar *)disk_type,
(const xmlChar *)source);
xmlAddChild(disk_node, new_node);
} else if (type == VSH_PREPARE_DISK_XML_INSERT) {
vshError(NULL, _("No source is specified for inserting media"));
goto error;
} else if (type == VSH_PREPARE_DISK_XML_UPDATE) {
vshError(NULL, _("No source is specified for updating media"));
goto error;
}
}
if (has_source) {
if (type == VSH_PREPARE_DISK_XML_INSERT) {
vshError(NULL, _("The disk device '%s' already has media"),
path);
goto error;
}
/* Remove the source if it tends to eject/update media. */
xmlUnlinkNode(cur);
xmlFreeNode(cur);
if (source && (type == VSH_PREPARE_DISK_XML_UPDATE)) {
new_node = xmlNewNode(NULL, BAD_CAST "source");
xmlNewProp(new_node, (const xmlChar *)disk_type,
(const xmlChar *)source);
xmlAddChild(disk_node, new_node);
}
}
}
if (xmlNodeDump(xml_buf, NULL, disk_node, 0, 0) < 0) {
vshError(NULL, "%s", _("Failed to create XML"));
goto error;
}
goto cleanup;
cleanup:
VIR_FREE(device_type);
VIR_FREE(disk_type);
if (xml_buf) {
int len = xmlBufferLength(xml_buf);
if (VIR_ALLOC_N(ret, len + 1) < 0) {
virReportOOMError();
return NULL;
}
memcpy(ret, (char *)xmlBufferContent(xml_buf), len);
ret[len] = '\0';
xmlBufferFree(xml_buf);
}
return ret;
error:
xmlBufferFree(xml_buf);
xml_buf = NULL;
goto cleanup;
}
/*
* "detach-disk" command
*/
static const vshCmdInfo info_detach_disk[] = {
{"help", N_("detach disk device")},
{"desc", N_("Detach disk device.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_detach_disk[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"target", VSH_OT_DATA, VSH_OFLAG_REQ, N_("target of disk device")},
{"persistent", VSH_OT_ALIAS, 0, "config"},
{"config", VSH_OT_BOOL, 0, N_("affect next boot")},
{NULL, 0, 0, NULL}
};
static bool
cmdDetachDisk(vshControl *ctl, const vshCmd *cmd)
{
char *disk_xml = NULL;
virDomainPtr dom = NULL;
const char *target = NULL;
char *doc = NULL;
int ret;
bool functionReturn = false;
unsigned int flags;
xmlNodePtr disk_node = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
goto cleanup;
if (vshCommandOptString(cmd, "target", &target) <= 0)
goto cleanup;
doc = virDomainGetXMLDesc(dom, 0);
if (!doc)
goto cleanup;
if (!(disk_node = vshFindDisk(doc, target, VSH_FIND_DISK_NORMAL)))
goto cleanup;
if (!(disk_xml = vshPrepareDiskXML(disk_node, NULL, NULL,
VSH_PREPARE_DISK_XML_NONE)))
goto cleanup;
if (vshCommandOptBool(cmd, "config")) {
flags = VIR_DOMAIN_AFFECT_CONFIG;
if (virDomainIsActive(dom) == 1)
flags |= VIR_DOMAIN_AFFECT_LIVE;
ret = virDomainDetachDeviceFlags(dom,
disk_xml,
flags);
} else {
ret = virDomainDetachDevice(dom, disk_xml);
}
if (ret != 0) {
vshError(ctl, "%s", _("Failed to detach disk"));
} else {
vshPrint(ctl, "%s", _("Disk detached successfully\n"));
functionReturn = true;
}
cleanup:
xmlFreeNode(disk_node);
VIR_FREE(disk_xml);
VIR_FREE(doc);
if (dom)
virDomainFree(dom);
return functionReturn;
}
/*
* "change-media" command
*/
static const vshCmdInfo info_change_media[] = {
{"help", N_("Change media of CD or floppy drive")},
{"desc", N_("Change media of CD or floppy drive.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_change_media[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"path", VSH_OT_DATA, VSH_OFLAG_REQ, N_("Fully-qualified path or "
"target of disk device")},
{"source", VSH_OT_DATA, 0, N_("source of the media")},
{"eject", VSH_OT_BOOL, 0, N_("Eject the media")},
{"insert", VSH_OT_BOOL, 0, N_("Insert the media")},
{"update", VSH_OT_BOOL, 0, N_("Update the media")},
{"current", VSH_OT_BOOL, 0, N_("can be either or both of --live and --config, "
"depends on implementation of hypervisor driver")},
{"live", VSH_OT_BOOL, 0, N_("alter live configuration of running domain")},
{"config", VSH_OT_BOOL, 0, N_("alter persistent configuration, effect observed on next boot")},
{"force", VSH_OT_BOOL, 0, N_("force media insertion")},
{NULL, 0, 0, NULL}
};
static bool
cmdChangeMedia(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
const char *source = NULL;
const char *path = NULL;
const char *doc = NULL;
xmlNodePtr disk_node = NULL;
const char *disk_xml = NULL;
int flags = 0;
bool config, live, current, force = false;
bool eject, insert, update = false;
bool ret = false;
int prepare_type = 0;
const char *action = NULL;
config = vshCommandOptBool(cmd, "config");
live = vshCommandOptBool(cmd, "live");
current = vshCommandOptBool(cmd, "current");
force = vshCommandOptBool(cmd, "force");
eject = vshCommandOptBool(cmd, "eject");
insert = vshCommandOptBool(cmd, "insert");
update = vshCommandOptBool(cmd, "update");
if (eject + insert + update > 1) {
vshError(ctl, "%s", _("--eject, --insert, and --update must be specified "
"exclusively."));
return false;
}
if (eject) {
prepare_type = VSH_PREPARE_DISK_XML_EJECT;
action = "eject";
}
if (insert) {
prepare_type = VSH_PREPARE_DISK_XML_INSERT;
action = "insert";
}
if (update || (!eject && !insert)) {
prepare_type = VSH_PREPARE_DISK_XML_UPDATE;
action = "update";
}
if (current) {
if (live || config) {
vshError(ctl, "%s", _("--current must be specified exclusively"));
return false;
}
flags = VIR_DOMAIN_AFFECT_CURRENT;
} else {
if (config)
flags |= VIR_DOMAIN_AFFECT_CONFIG;
if (live)
flags |= VIR_DOMAIN_AFFECT_LIVE;
}
if (force)
flags |= VIR_DOMAIN_DEVICE_MODIFY_FORCE;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
goto cleanup;
if (vshCommandOptString(cmd, "path", &path) <= 0)
goto cleanup;
if (vshCommandOptString(cmd, "source", &source) < 0)
goto cleanup;
if (insert && !source) {
vshError(ctl, "%s", _("No disk source specified for inserting"));
goto cleanup;
}
if (flags & VIR_DOMAIN_AFFECT_CONFIG)
doc = virDomainGetXMLDesc(dom, VIR_DOMAIN_XML_INACTIVE);
else
doc = virDomainGetXMLDesc(dom, 0);
if (!doc)
goto cleanup;
if (!(disk_node = vshFindDisk(doc, path, VSH_FIND_DISK_CHANGEABLE)))
goto cleanup;
if (!(disk_xml = vshPrepareDiskXML(disk_node, source, path, prepare_type)))
goto cleanup;
if (virDomainUpdateDeviceFlags(dom, disk_xml, flags) != 0) {
vshError(ctl, _("Failed to complete action %s on media"), action);
goto cleanup;
}
vshPrint(ctl, _("succeeded to complete action %s on media\n"), action);
ret = true;
cleanup:
VIR_FREE(doc);
xmlFreeNode(disk_node);
VIR_FREE(disk_xml);
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "cpu-compare" command
*/
static const vshCmdInfo info_cpu_compare[] = {
{"help", N_("compare host CPU with a CPU described by an XML file")},
{"desc", N_("compare CPU with host CPU")},
{NULL, NULL}
};
static const vshCmdOptDef opts_cpu_compare[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing an XML CPU description")},
{NULL, 0, 0, NULL}
};
static bool
cmdCPUCompare(vshControl *ctl, const vshCmd *cmd)
{
const char *from = NULL;
bool ret = false;
char *buffer;
int result;
const char *snippet;
xmlDocPtr xml = NULL;
xmlXPathContextPtr ctxt = NULL;
xmlBufferPtr xml_buf = NULL;
xmlNodePtr node;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
vshError(ctl, _("Failed to read file '%s' to compare"),
from);
return false;
}
/* try to extract the CPU element from as it would appear in a domain XML*/
if (!(xml = virXMLParseStringCtxt(buffer, from, &ctxt)))
goto cleanup;
if ((node = virXPathNode("/cpu|"
"/domain/cpu|"
"/capabilities/host/cpu", ctxt))) {
if (!(xml_buf = xmlBufferCreate())) {
vshError(ctl, _("Can't create XML buffer to extract CPU element."));
goto cleanup;
}
if (xmlNodeDump(xml_buf, xml, node, 0, 0) < 0) {
vshError(ctl, _("Failed to extract CPU element snippet from domain XML."));
goto cleanup;
}
snippet = (const char *) xmlBufferContent(xml_buf);
} else {
vshError(ctl, _("File '%s' does not contain a <cpu> element or is not "
"a valid domain or capabilities XML"), from);
goto cleanup;
}
result = virConnectCompareCPU(ctl->conn, snippet, 0);
switch (result) {
case VIR_CPU_COMPARE_INCOMPATIBLE:
vshPrint(ctl, _("CPU described in %s is incompatible with host CPU\n"),
from);
goto cleanup;
break;
case VIR_CPU_COMPARE_IDENTICAL:
vshPrint(ctl, _("CPU described in %s is identical to host CPU\n"),
from);
break;
case VIR_CPU_COMPARE_SUPERSET:
vshPrint(ctl, _("Host CPU is a superset of CPU described in %s\n"),
from);
break;
case VIR_CPU_COMPARE_ERROR:
default:
vshError(ctl, _("Failed to compare host CPU with %s"), from);
goto cleanup;
}
ret = true;
cleanup:
VIR_FREE(buffer);
xmlBufferFree(xml_buf);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
return ret;
}
/*
* "cpu-baseline" command
*/
static const vshCmdInfo info_cpu_baseline[] = {
{"help", N_("compute baseline CPU")},
{"desc", N_("Compute baseline CPU for a set of given CPUs.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_cpu_baseline[] = {
{"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("file containing XML CPU descriptions")},
{NULL, 0, 0, NULL}
};
static bool
cmdCPUBaseline(vshControl *ctl, const vshCmd *cmd)
{
const char *from = NULL;
bool ret = false;
char *buffer;
char *result = NULL;
const char **list = NULL;
int count = 0;
xmlDocPtr xml = NULL;
xmlNodePtr *node_list = NULL;
xmlXPathContextPtr ctxt = NULL;
xmlBufferPtr xml_buf = NULL;
virBuffer buf = VIR_BUFFER_INITIALIZER;
int i;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (vshCommandOptString(cmd, "file", &from) <= 0)
return false;
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0)
return false;
/* add a separate container around the xml */
virBufferStrcat(&buf, "<container>", buffer, "</container>", NULL);
if (virBufferError(&buf))
goto no_memory;
VIR_FREE(buffer);
buffer = virBufferContentAndReset(&buf);
if (!(xml = virXMLParseStringCtxt(buffer, from, &ctxt)))
goto cleanup;
if ((count = virXPathNodeSet("//cpu[not(ancestor::cpus)]",
ctxt, &node_list)) == -1)
goto cleanup;
if (count == 0) {
vshError(ctl, _("No host CPU specified in '%s'"), from);
goto cleanup;
}
list = vshCalloc(ctl, count, sizeof(const char *));
if (!(xml_buf = xmlBufferCreate()))
goto no_memory;
for (i = 0; i < count; i++) {
xmlBufferEmpty(xml_buf);
if (xmlNodeDump(xml_buf, xml, node_list[i], 0, 0) < 0) {
vshError(ctl, _("Failed to extract <cpu> element"));
goto cleanup;
}
list[i] = vshStrdup(ctl, (const char *)xmlBufferContent(xml_buf));
}
result = virConnectBaselineCPU(ctl->conn, list, count, 0);
if (result) {
vshPrint(ctl, "%s", result);
ret = true;
}
cleanup:
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
xmlBufferFree(xml_buf);
VIR_FREE(result);
if (list != NULL && count > 0) {
for (i = 0; i < count; i++)
VIR_FREE(list[i]);
}
VIR_FREE(list);
VIR_FREE(buffer);
return ret;
no_memory:
vshError(ctl, "%s", _("Out of memory"));
ret = false;
goto cleanup;
}
/* Common code for the edit / net-edit / pool-edit functions which follow. */
static char *
editWriteToTempFile(vshControl *ctl, const char *doc)
{
char *ret;
const char *tmpdir;
int fd;
tmpdir = getenv ("TMPDIR");
if (!tmpdir) tmpdir = "/tmp";
if (virAsprintf(&ret, "%s/virshXXXXXX.xml", tmpdir) < 0) {
vshError(ctl, "%s", _("out of memory"));
return NULL;
}
fd = mkstemps(ret, 4);
if (fd == -1) {
vshError(ctl, _("mkstemps: failed to create temporary file: %s"),
strerror(errno));
VIR_FREE(ret);
return NULL;
}
if (safewrite(fd, doc, strlen(doc)) == -1) {
vshError(ctl, _("write: %s: failed to write to temporary file: %s"),
ret, strerror(errno));
VIR_FORCE_CLOSE(fd);
unlink(ret);
VIR_FREE(ret);
return NULL;
}
if (VIR_CLOSE(fd) < 0) {
vshError(ctl, _("close: %s: failed to write or close temporary file: %s"),
ret, strerror(errno));
unlink(ret);
VIR_FREE(ret);
return NULL;
}
/* Temporary filename: caller frees. */
return ret;
}
/* Characters permitted in $EDITOR environment variable and temp filename. */
#define ACCEPTED_CHARS \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/_.:@"
static int
editFile(vshControl *ctl, const char *filename)
{
const char *editor;
virCommandPtr cmd;
int ret = -1;
int outfd = STDOUT_FILENO;
int errfd = STDERR_FILENO;
editor = getenv("VISUAL");
if (!editor)
editor = getenv("EDITOR");
if (!editor)
editor = "vi"; /* could be cruel & default to ed(1) here */
/* Check that filename doesn't contain shell meta-characters, and
* if it does, refuse to run. Follow the Unix conventions for
* EDITOR: the user can intentionally specify command options, so
* we don't protect any shell metacharacters there. Lots more
* than virsh will misbehave if EDITOR has bogus contents (which
* is why sudo scrubs it by default). Conversely, if the editor
* is safe, we can run it directly rather than wasting a shell.
*/
if (strspn(editor, ACCEPTED_CHARS) != strlen(editor)) {
if (strspn(filename, ACCEPTED_CHARS) != strlen(filename)) {
vshError(ctl,
_("%s: temporary filename contains shell meta or other "
"unacceptable characters (is $TMPDIR wrong?)"),
filename);
return -1;
}
cmd = virCommandNewArgList("sh", "-c", NULL);
virCommandAddArgFormat(cmd, "%s %s", editor, filename);
} else {
cmd = virCommandNewArgList(editor, filename, NULL);
}
virCommandSetInputFD(cmd, STDIN_FILENO);
virCommandSetOutputFD(cmd, &outfd);
virCommandSetErrorFD(cmd, &errfd);
if (virCommandRunAsync(cmd, NULL) < 0 ||
virCommandWait(cmd, NULL) < 0) {
virshReportError(ctl);
goto cleanup;
}
ret = 0;
cleanup:
virCommandFree(cmd);
return ret;
}
static char *
editReadBackFile(vshControl *ctl, const char *filename)
{
char *ret;
if (virFileReadAll(filename, VIRSH_MAX_XML_FILE, &ret) == -1) {
vshError(ctl,
_("%s: failed to read temporary file: %s"),
filename, strerror(errno));
return NULL;
}
return ret;
}
/*
* "cd" command
*/
static const vshCmdInfo info_cd[] = {
{"help", N_("change the current directory")},
{"desc", N_("Change the current directory.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_cd[] = {
{"dir", VSH_OT_DATA, 0, N_("directory to switch to (default: home or else root)")},
{NULL, 0, 0, NULL}
};
static bool
cmdCd(vshControl *ctl, const vshCmd *cmd)
{
const char *dir = NULL;
char *dir_malloced = NULL;
bool ret = true;
if (!ctl->imode) {
vshError(ctl, "%s", _("cd: command valid only in interactive mode"));
return false;
}
if (vshCommandOptString(cmd, "dir", &dir) <= 0) {
dir = dir_malloced = virGetUserDirectory();
}
if (!dir)
dir = "/";
if (chdir(dir) == -1) {
vshError(ctl, _("cd: %s: %s"), strerror(errno), dir);
ret = false;
}
VIR_FREE(dir_malloced);
return ret;
}
/*
* "pwd" command
*/
static const vshCmdInfo info_pwd[] = {
{"help", N_("print the current directory")},
{"desc", N_("Print the current directory.")},
{NULL, NULL}
};
static bool
cmdPwd(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
char *cwd;
bool ret = true;
cwd = getcwd(NULL, 0);
if (!cwd) {
vshError(ctl, _("pwd: cannot get current directory: %s"),
strerror(errno));
ret = false;
} else {
vshPrint(ctl, _("%s\n"), cwd);
VIR_FREE(cwd);
}
return ret;
}
/*
* "echo" command
*/
static const vshCmdInfo info_echo[] = {
{"help", N_("echo arguments")},
{"desc", N_("Echo back arguments, possibly with quoting.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_echo[] = {
{"shell", VSH_OT_BOOL, 0, N_("escape for shell use")},
{"xml", VSH_OT_BOOL, 0, N_("escape for XML use")},
{"str", VSH_OT_ALIAS, 0, "string"},
{"string", VSH_OT_ARGV, 0, N_("arguments to echo")},
{NULL, 0, 0, NULL}
};
/* Exists mainly for debugging virsh, but also handy for adding back
* quotes for later evaluation.
*/
static bool
cmdEcho(vshControl *ctl, const vshCmd *cmd)
{
bool shell = false;
bool xml = false;
int count = 0;
const vshCmdOpt *opt = NULL;
char *arg;
virBuffer buf = VIR_BUFFER_INITIALIZER;
if (vshCommandOptBool(cmd, "shell"))
shell = true;
if (vshCommandOptBool(cmd, "xml"))
xml = true;
while ((opt = vshCommandOptArgv(cmd, opt))) {
char *str;
virBuffer xmlbuf = VIR_BUFFER_INITIALIZER;
arg = opt->data;
if (count)
virBufferAddChar(&buf, ' ');
if (xml) {
virBufferEscapeString(&xmlbuf, "%s", arg);
if (virBufferError(&buf)) {
vshPrint(ctl, "%s", _("Failed to allocate XML buffer"));
return false;
}
str = virBufferContentAndReset(&xmlbuf);
} else {
str = vshStrdup(ctl, arg);
}
if (shell)
virBufferEscapeShell(&buf, str);
else
virBufferAdd(&buf, str, -1);
count++;
VIR_FREE(str);
}
if (virBufferError(&buf)) {
vshPrint(ctl, "%s", _("Failed to allocate XML buffer"));
return false;
}
arg = virBufferContentAndReset(&buf);
if (arg)
vshPrint(ctl, "%s", arg);
VIR_FREE(arg);
return true;
}
/*
* "edit" command
*/
static const vshCmdInfo info_edit[] = {
{"help", N_("edit XML configuration for a domain")},
{"desc", N_("Edit the XML configuration for a domain.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_edit[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdEdit(vshControl *ctl, const vshCmd *cmd)
{
bool ret = false;
virDomainPtr dom = NULL;
virDomainPtr dom_edited = NULL;
unsigned int flags = VIR_DOMAIN_XML_SECURE | VIR_DOMAIN_XML_INACTIVE;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
goto cleanup;
#define EDIT_GET_XML virDomainGetXMLDesc(dom, flags)
#define EDIT_NOT_CHANGED \
vshPrint(ctl, _("Domain %s XML configuration not changed.\n"), \
virDomainGetName(dom)); \
ret = true; goto edit_cleanup;
#define EDIT_DEFINE \
(dom_edited = virDomainDefineXML(ctl->conn, doc_edited))
#define EDIT_FREE \
if (dom_edited) \
virDomainFree(dom_edited);
#include "virsh-edit.c"
vshPrint(ctl, _("Domain %s XML configuration edited.\n"),
virDomainGetName(dom_edited));
ret = true;
cleanup:
if (dom)
virDomainFree(dom);
if (dom_edited)
virDomainFree(dom_edited);
return ret;
}
/*
* "net-edit" command
*/
static const vshCmdInfo info_network_edit[] = {
{"help", N_("edit XML configuration for a network")},
{"desc", N_("Edit the XML configuration for a network.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_network_edit[] = {
{"network", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network name or uuid")},
{NULL, 0, 0, NULL}
};
static char *vshNetworkGetXMLDesc(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.
*/
virFreeError(last_error);
last_error = NULL;
flags &= ~VIR_NETWORK_XML_INACTIVE;
doc = virNetworkGetXMLDesc(network, flags);
}
return doc;
}
static bool
cmdNetworkEdit(vshControl *ctl, const vshCmd *cmd)
{
bool ret = false;
virNetworkPtr network = NULL;
virNetworkPtr network_edited = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
network = vshCommandOptNetwork(ctl, cmd, NULL);
if (network == NULL)
goto cleanup;
#define EDIT_GET_XML vshNetworkGetXMLDesc(network)
#define EDIT_NOT_CHANGED \
vshPrint(ctl, _("Network %s XML configuration not changed.\n"), \
virNetworkGetName(network)); \
ret = true; goto edit_cleanup;
#define EDIT_DEFINE \
(network_edited = virNetworkDefineXML(ctl->conn, doc_edited))
#define EDIT_FREE \
if (network_edited) \
virNetworkFree(network_edited);
#include "virsh-edit.c"
vshPrint(ctl, _("Network %s XML configuration edited.\n"),
virNetworkGetName(network_edited));
ret = true;
cleanup:
if (network)
virNetworkFree(network);
if (network_edited)
virNetworkFree(network_edited);
return ret;
}
/*
* "pool-edit" command
*/
static const vshCmdInfo info_pool_edit[] = {
{"help", N_("edit XML configuration for a storage pool")},
{"desc", N_("Edit the XML configuration for a storage pool.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_pool_edit[] = {
{"pool", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pool name or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdPoolEdit(vshControl *ctl, const vshCmd *cmd)
{
bool ret = false;
virStoragePoolPtr pool = NULL;
virStoragePoolPtr pool_edited = NULL;
unsigned int flags = VIR_STORAGE_XML_INACTIVE;
char *tmp_desc = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
pool = vshCommandOptPool(ctl, cmd, "pool", NULL);
if (pool == NULL)
goto cleanup;
/* Some old daemons don't support _INACTIVE flag */
if (!(tmp_desc = virStoragePoolGetXMLDesc(pool, flags))) {
if (last_error->code == VIR_ERR_INVALID_ARG) {
flags &= ~VIR_STORAGE_XML_INACTIVE;
virFreeError(last_error);
last_error = NULL;
} else {
goto cleanup;
}
} else {
VIR_FREE(tmp_desc);
}
#define EDIT_GET_XML virStoragePoolGetXMLDesc(pool, flags)
#define EDIT_NOT_CHANGED \
vshPrint(ctl, _("Pool %s XML configuration not changed.\n"), \
virStoragePoolGetName(pool)); \
ret = true; goto edit_cleanup;
#define EDIT_DEFINE \
(pool_edited = virStoragePoolDefineXML(ctl->conn, doc_edited, 0))
#define EDIT_FREE \
if (pool_edited) \
virStoragePoolFree(pool_edited);
#include "virsh-edit.c"
vshPrint(ctl, _("Pool %s XML configuration edited.\n"),
virStoragePoolGetName(pool_edited));
ret = true;
cleanup:
if (pool)
virStoragePoolFree(pool);
if (pool_edited)
virStoragePoolFree(pool_edited);
return ret;
}
/*
* "quit" command
*/
static const vshCmdInfo info_quit[] = {
{"help", N_("quit this interactive terminal")},
{"desc", ""},
{NULL, NULL}
};
static bool
cmdQuit(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
ctl->imode = false;
return true;
}
/* Helper for snapshot-create and snapshot-create-as */
static bool
vshSnapshotCreate(vshControl *ctl, virDomainPtr dom, const char *buffer,
unsigned int flags, const char *from)
{
bool ret = false;
virDomainSnapshotPtr snapshot;
bool halt = false;
char *doc = NULL;
xmlDocPtr xml = NULL;
xmlXPathContextPtr ctxt = NULL;
const char *name = NULL;
snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
/* Emulate --halt on older servers. */
if (!snapshot && last_error->code == VIR_ERR_INVALID_ARG &&
(flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) {
int persistent;
virFreeError(last_error);
last_error = NULL;
persistent = virDomainIsPersistent(dom);
if (persistent < 0) {
virshReportError(ctl);
goto cleanup;
}
if (!persistent) {
vshError(ctl, "%s",
_("cannot halt after snapshot of transient domain"));
goto cleanup;
}
if (virDomainIsActive(dom) == 1)
halt = true;
flags &= ~VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
}
if (snapshot == NULL)
goto cleanup;
if (halt && virDomainDestroy(dom) < 0) {
virshReportError(ctl);
goto cleanup;
}
name = virDomainSnapshotGetName(snapshot);
if (!name) {
vshError(ctl, "%s", _("Could not get snapshot name"));
goto cleanup;
}
if (from)
vshPrint(ctl, _("Domain snapshot %s created from '%s'"), name, from);
else
vshPrint(ctl, _("Domain snapshot %s created"), name);
ret = true;
cleanup:
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
if (snapshot)
virDomainSnapshotFree(snapshot);
VIR_FREE(doc);
return ret;
}
/*
* "snapshot-create" command
*/
static const vshCmdInfo info_snapshot_create[] = {
{"help", N_("Create a snapshot from XML")},
{"desc", N_("Create a snapshot (disk and RAM) from XML")},
{NULL, NULL}
};
static const vshCmdOptDef opts_snapshot_create[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"xmlfile", VSH_OT_DATA, 0, N_("domain snapshot XML")},
{"redefine", VSH_OT_BOOL, 0, N_("redefine metadata for existing snapshot")},
{"current", VSH_OT_BOOL, 0, N_("with redefine, set current snapshot")},
{"no-metadata", VSH_OT_BOOL, 0, N_("take snapshot but create no metadata")},
{"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")},
{"disk-only", VSH_OT_BOOL, 0, N_("capture disk state but not vm state")},
{"reuse-external", VSH_OT_BOOL, 0, N_("reuse any existing external files")},
{"quiesce", VSH_OT_BOOL, 0, N_("quiesce guest's file systems")},
{"atomic", VSH_OT_BOOL, 0, N_("require atomic operation")},
{NULL, 0, 0, NULL}
};
static bool
cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
const char *from = NULL;
char *buffer = NULL;
unsigned int flags = 0;
if (vshCommandOptBool(cmd, "redefine"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
if (vshCommandOptBool(cmd, "current"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT;
if (vshCommandOptBool(cmd, "no-metadata"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA;
if (vshCommandOptBool(cmd, "halt"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
if (vshCommandOptBool(cmd, "disk-only"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
if (vshCommandOptBool(cmd, "reuse-external"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT;
if (vshCommandOptBool(cmd, "quiesce"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE;
if (vshCommandOptBool(cmd, "atomic"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
goto cleanup;
if (vshCommandOptString(cmd, "xmlfile", &from) <= 0)
buffer = vshStrdup(ctl, "<domainsnapshot/>");
else {
if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
/* we have to report the error here because during cleanup
* we'll run through virDomainFree(), which loses the
* last error
*/
virshReportError(ctl);
goto cleanup;
}
}
if (buffer == NULL) {
vshError(ctl, "%s", _("Out of memory"));
goto cleanup;
}
ret = vshSnapshotCreate(ctl, dom, buffer, flags, from);
cleanup:
VIR_FREE(buffer);
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "snapshot-create-as" command
*/
static int
vshParseSnapshotDiskspec(vshControl *ctl, virBufferPtr buf, const char *str)
{
int ret = -1;
char *name = NULL;
char *snapshot = NULL;
char *driver = NULL;
char *file = NULL;
char *spec = vshStrdup(ctl, str);
char *tmp = spec;
size_t len = strlen(str);
if (*str == ',')
goto cleanup;
name = tmp;
while ((tmp = strchr(tmp, ','))) {
if (tmp[1] == ',') {
/* Recognize ,, as an escape for a literal comma */
memmove(&tmp[1], &tmp[2], len - (tmp - spec) - 2 + 1);
len--;
tmp++;
continue;
}
/* Terminate previous string, look for next recognized one */
*tmp++ = '\0';
if (!snapshot && STRPREFIX(tmp, "snapshot="))
snapshot = tmp + strlen("snapshot=");
else if (!driver && STRPREFIX(tmp, "driver="))
driver = tmp + strlen("driver=");
else if (!file && STRPREFIX(tmp, "file="))
file = tmp + strlen("file=");
else
goto cleanup;
}
virBufferEscapeString(buf, " <disk name='%s'", name);
if (snapshot)
virBufferAsprintf(buf, " snapshot='%s'", snapshot);
if (driver || file) {
virBufferAddLit(buf, ">\n");
if (driver)
virBufferAsprintf(buf, " <driver type='%s'/>\n", driver);
if (file)
virBufferEscapeString(buf, " <source file='%s'/>\n", file);
virBufferAddLit(buf, " </disk>\n");
} else {
virBufferAddLit(buf, "/>\n");
}
ret = 0;
cleanup:
if (ret < 0)
vshError(ctl, _("unable to parse diskspec: %s"), str);
VIR_FREE(spec);
return ret;
}
static const vshCmdInfo info_snapshot_create_as[] = {
{"help", N_("Create a snapshot from a set of args")},
{"desc", N_("Create a snapshot (disk and RAM) from arguments")},
{NULL, NULL}
};
static const vshCmdOptDef opts_snapshot_create_as[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"name", VSH_OT_DATA, 0, N_("name of snapshot")},
{"description", VSH_OT_DATA, 0, N_("description of snapshot")},
{"print-xml", VSH_OT_BOOL, 0, N_("print XML document rather than create")},
{"no-metadata", VSH_OT_BOOL, 0, N_("take snapshot but create no metadata")},
{"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")},
{"disk-only", VSH_OT_BOOL, 0, N_("capture disk state but not vm state")},
{"reuse-external", VSH_OT_BOOL, 0, N_("reuse any existing external files")},
{"quiesce", VSH_OT_BOOL, 0, N_("quiesce guest's file systems")},
{"atomic", VSH_OT_BOOL, 0, N_("require atomic operation")},
{"diskspec", VSH_OT_ARGV, 0,
N_("disk attributes: disk[,snapshot=type][,driver=type][,file=name]")},
{NULL, 0, 0, NULL}
};
static bool
cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
char *buffer = NULL;
const char *name = NULL;
const char *desc = NULL;
virBuffer buf = VIR_BUFFER_INITIALIZER;
unsigned int flags = 0;
const vshCmdOpt *opt = NULL;
if (vshCommandOptBool(cmd, "no-metadata"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA;
if (vshCommandOptBool(cmd, "halt"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
if (vshCommandOptBool(cmd, "disk-only"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
if (vshCommandOptBool(cmd, "reuse-external"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT;
if (vshCommandOptBool(cmd, "quiesce"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE;
if (vshCommandOptBool(cmd, "atomic"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
goto cleanup;
if (vshCommandOptString(cmd, "name", &name) < 0 ||
vshCommandOptString(cmd, "description", &desc) < 0) {
vshError(ctl, _("argument must not be empty"));
goto cleanup;
}
virBufferAddLit(&buf, "<domainsnapshot>\n");
if (name)
virBufferEscapeString(&buf, " <name>%s</name>\n", name);
if (desc)
virBufferEscapeString(&buf, " <description>%s</description>\n", desc);
if (vshCommandOptBool(cmd, "diskspec")) {
virBufferAddLit(&buf, " <disks>\n");
while ((opt = vshCommandOptArgv(cmd, opt))) {
if (vshParseSnapshotDiskspec(ctl, &buf, opt->data) < 0) {
virBufferFreeAndReset(&buf);
goto cleanup;
}
}
virBufferAddLit(&buf, " </disks>\n");
}
virBufferAddLit(&buf, "</domainsnapshot>\n");
buffer = virBufferContentAndReset(&buf);
if (buffer == NULL) {
vshError(ctl, "%s", _("Out of memory"));
goto cleanup;
}
if (vshCommandOptBool(cmd, "print-xml")) {
vshPrint(ctl, "%s\n", buffer);
ret = true;
goto cleanup;
}
ret = vshSnapshotCreate(ctl, dom, buffer, flags, NULL);
cleanup:
VIR_FREE(buffer);
if (dom)
virDomainFree(dom);
return ret;
}
/* Helper for resolving {--current | --ARG name} into a snapshot
* belonging to DOM. If EXCLUSIVE, fail if both --current and arg are
* present. On success, populate *SNAP and *NAME, before returning 0.
* On failure, return -1 after issuing an error message. */
static int
vshLookupSnapshot(vshControl *ctl, const vshCmd *cmd,
const char *arg, bool exclusive, virDomainPtr dom,
virDomainSnapshotPtr *snap, const char **name)
{
bool current = vshCommandOptBool(cmd, "current");
const char *snapname = NULL;
if (vshCommandOptString(cmd, arg, &snapname) < 0) {
vshError(ctl, _("invalid argument for --%s"), arg);
return -1;
}
if (exclusive && current && snapname) {
vshError(ctl, _("--%s and --current are mutually exclusive"), arg);
return -1;
}
if (snapname) {
*snap = virDomainSnapshotLookupByName(dom, snapname, 0);
} else if (current) {
*snap = virDomainSnapshotCurrent(dom, 0);
} else {
vshError(ctl, _("--%s or --current is required"), arg);
return -1;
}
if (!*snap) {
virshReportError(ctl);
return -1;
}
*name = virDomainSnapshotGetName(*snap);
return 0;
}
/*
* "snapshot-edit" command
*/
static const vshCmdInfo info_snapshot_edit[] = {
{"help", N_("edit XML for a snapshot")},
{"desc", N_("Edit the domain snapshot XML for a named snapshot")},
{NULL, NULL}
};
static const vshCmdOptDef opts_snapshot_edit[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
{"current", VSH_OT_BOOL, 0, N_("also set edited snapshot as current")},
{"rename", VSH_OT_BOOL, 0, N_("allow renaming an existing snapshot")},
{"clone", VSH_OT_BOOL, 0, N_("allow cloning to new name")},
{NULL, 0, 0, NULL}
};
static bool
cmdSnapshotEdit(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
virDomainSnapshotPtr snapshot = NULL;
virDomainSnapshotPtr edited = NULL;
const char *name;
const char *edited_name;
bool ret = false;
unsigned int getxml_flags = VIR_DOMAIN_XML_SECURE;
unsigned int define_flags = VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
bool rename_okay = vshCommandOptBool(cmd, "rename");
bool clone_okay = vshCommandOptBool(cmd, "clone");
if (rename_okay && clone_okay) {
vshError(ctl, "%s",
_("--rename and --clone are mutually exclusive"));
return false;
}
if (vshCommandOptBool(cmd, "current") &&
vshCommandOptBool(cmd, "snapshotname"))
define_flags |= VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
goto cleanup;
if (vshLookupSnapshot(ctl, cmd, "snapshotname", false, dom,
&snapshot, &name) < 0)
goto cleanup;
#define EDIT_GET_XML \
virDomainSnapshotGetXMLDesc(snapshot, getxml_flags)
#define EDIT_NOT_CHANGED \
/* Depending on flags, we re-edit even if XML is unchanged. */ \
if (!(define_flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) { \
vshPrint(ctl, \
_("Snapshot %s XML configuration not changed.\n"), \
name); \
ret = true; \
goto cleanup; \
}
#define EDIT_DEFINE \
(strstr(doc, "<state>disk-snapshot</state>") ? \
define_flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY : 0), \
edited = virDomainSnapshotCreateXML(dom, doc_edited, define_flags)
#define EDIT_FREE \
if (edited) \
virDomainSnapshotFree(edited);
#include "virsh-edit.c"
edited_name = virDomainSnapshotGetName(edited);
if (STREQ(name, edited_name)) {
vshPrint(ctl, _("Snapshot %s edited.\n"), name);
} else if (clone_okay) {
vshPrint(ctl, _("Snapshot %s cloned to %s.\n"), name,
edited_name);
} else {
unsigned int delete_flags;
delete_flags = VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY;
if (virDomainSnapshotDelete(rename_okay ? snapshot : edited,
delete_flags) < 0) {
virshReportError(ctl);
vshError(ctl, _("Failed to clean up %s"),
rename_okay ? name : edited_name);
goto cleanup;
}
if (!rename_okay) {
vshError(ctl, _("Must use --rename or --clone to change %s to %s"),
name, edited_name);
goto cleanup;
}
}
ret = true;
cleanup:
if (edited)
virDomainSnapshotFree(edited);
else
vshError(ctl, _("Failed to update %s"), name);
if (snapshot)
virDomainSnapshotFree(snapshot);
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "snapshot-current" command
*/
static const vshCmdInfo info_snapshot_current[] = {
{"help", N_("Get or set the current snapshot")},
{"desc", N_("Get or set the current snapshot")},
{NULL, NULL}
};
static const vshCmdOptDef opts_snapshot_current[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"name", VSH_OT_BOOL, 0, N_("list the name, rather than the full xml")},
{"security-info", VSH_OT_BOOL, 0,
N_("include security sensitive information in XML dump")},
{"snapshotname", VSH_OT_DATA, 0,
N_("name of existing snapshot to make current")},
{NULL, 0, 0, NULL}
};
static bool
cmdSnapshotCurrent(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
int current;
virDomainSnapshotPtr snapshot = NULL;
char *xml = NULL;
const char *snapshotname = NULL;
unsigned int flags = 0;
const char *domname;
if (vshCommandOptBool(cmd, "security-info"))
flags |= VIR_DOMAIN_XML_SECURE;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
dom = vshCommandOptDomain(ctl, cmd, &domname);
if (dom == NULL)
goto cleanup;
if (vshCommandOptString(cmd, "snapshotname", &snapshotname) < 0) {
vshError(ctl, _("invalid snapshotname argument '%s'"), snapshotname);
goto cleanup;
}
if (snapshotname) {
virDomainSnapshotPtr snapshot2 = NULL;
flags = (VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE |
VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT);
if (vshCommandOptBool(cmd, "name")) {
vshError(ctl, "%s",
_("--name and snapshotname are mutually exclusive"));
goto cleanup;
}
snapshot = virDomainSnapshotLookupByName(dom, snapshotname, 0);
if (snapshot == NULL)
goto cleanup;
xml = virDomainSnapshotGetXMLDesc(snapshot, VIR_DOMAIN_XML_SECURE);
if (!xml)
goto cleanup;
/* strstr is safe here, since xml came from libvirt API and not user */
if (strstr(xml, "<state>disk-snapshot</state>"))
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
snapshot2 = virDomainSnapshotCreateXML(dom, xml, flags);
if (snapshot2 == NULL)
goto cleanup;
virDomainSnapshotFree(snapshot2);
vshPrint(ctl, _("Snapshot %s set as current"), snapshotname);
ret = true;
goto cleanup;
}
current = virDomainHasCurrentSnapshot(dom, 0);
if (current < 0) {
goto cleanup;
} else if (!current) {
vshError(ctl, _("domain '%s' has no current snapshot"), domname);
goto cleanup;
} else {
const char *name = NULL;
if (!(snapshot = virDomainSnapshotCurrent(dom, 0)))
goto cleanup;
if (vshCommandOptBool(cmd, "name")) {
name = virDomainSnapshotGetName(snapshot);
if (!name)
goto cleanup;
} else {
xml = virDomainSnapshotGetXMLDesc(snapshot, flags);
if (!xml)
goto cleanup;
}
vshPrint(ctl, "%s", name ? name : xml);
}
ret = true;
cleanup:
if (!ret)
virshReportError(ctl);
VIR_FREE(xml);
if (snapshot)
virDomainSnapshotFree(snapshot);
if (dom)
virDomainFree(dom);
return ret;
}
/* Helper function to get the name of a snapshot's parent. Caller
* must free the result. Returns 0 on success (including when it was
* proven no parent exists), and -1 on failure with error reported
* (such as no snapshot support or domain deleted in meantime). */
static int
vshGetSnapshotParent(vshControl *ctl, virDomainSnapshotPtr snapshot,
char **parent_name)
{
virDomainSnapshotPtr parent = NULL;
char *xml = NULL;
xmlDocPtr xmldoc = NULL;
xmlXPathContextPtr ctxt = NULL;
int ret = -1;
*parent_name = NULL;
/* Try new API, since it is faster. */
if (!ctl->useSnapshotOld) {
parent = virDomainSnapshotGetParent(snapshot, 0);
if (parent) {
/* API works, and virDomainSnapshotGetName will succeed */
*parent_name = vshStrdup(ctl, virDomainSnapshotGetName(parent));
ret = 0;
goto cleanup;
}
if (last_error->code == VIR_ERR_NO_DOMAIN_SNAPSHOT) {
/* API works, and we found a root with no parent */
ret = 0;
goto cleanup;
}
/* API didn't work, fall back to XML scraping. */
ctl->useSnapshotOld = true;
}
xml = virDomainSnapshotGetXMLDesc(snapshot, 0);
if (!xml)
goto cleanup;
xmldoc = virXMLParseStringCtxt(xml, _("(domain_snapshot)"), &ctxt);
if (!xmldoc)
goto cleanup;
*parent_name = virXPathString("string(/domainsnapshot/parent/name)", ctxt);
ret = 0;
cleanup:
if (ret < 0) {
virshReportError(ctl);
vshError(ctl, "%s", _("unable to determine if snapshot has parent"));
} else {
virFreeError(last_error);
last_error = NULL;
}
if (parent)
virDomainSnapshotFree(parent);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xmldoc);
VIR_FREE(xml);
return ret;
}
/*
* "snapshot-info" command
*/
static const vshCmdInfo info_snapshot_info[] = {
{"help", N_("snapshot information")},
{"desc", N_("Returns basic information about a snapshot.")},
{NULL, NULL}
};
static const vshCmdOptDef opts_snapshot_info[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
{"current", VSH_OT_BOOL, 0, N_("info on current snapshot")},
{NULL, 0, 0, NULL}
};
static bool
cmdSnapshotInfo(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
virDomainSnapshotPtr snapshot = NULL;
const char *name;
char *doc = NULL;
char *tmp;
char *parent = NULL;
bool ret = false;
int count;
unsigned int flags;
int current;
int metadata;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
return false;
if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
&snapshot, &name) < 0)
goto cleanup;
vshPrint(ctl, "%-15s %s\n", _("Name:"), name);
vshPrint(ctl, "%-15s %s\n", _("Domain:"), virDomainGetName(dom));
/* Determine if snapshot is current; this is useful enough that we
* attempt a fallback. */
current = virDomainSnapshotIsCurrent(snapshot, 0);
if (current < 0) {
virDomainSnapshotPtr other = virDomainSnapshotCurrent(dom, 0);
virResetLastError();
current = 0;
if (other) {
if (STREQ(name, virDomainSnapshotGetName(other)))
current = 1;
virDomainSnapshotFree(other);
}
}
vshPrint(ctl, "%-15s %s\n", _("Current:"),
current > 0 ? _("yes") : _("no"));
/* Get the XML configuration of the snapshot to determine the
* state of the machine at the time of the snapshot. */
doc = virDomainSnapshotGetXMLDesc(snapshot, 0);
if (!doc)
goto cleanup;
tmp = strstr(doc, "<state>");
if (!tmp) {
vshError(ctl, "%s",
_("unexpected problem reading snapshot xml"));
goto cleanup;
}
tmp += strlen("<state>");
vshPrint(ctl, "%-15s %.*s\n", _("State:"),
(int) (strchr(tmp, '<') - tmp), tmp);
if (vshGetSnapshotParent(ctl, snapshot, &parent) < 0)
goto cleanup;
vshPrint(ctl, "%-15s %s\n", _("Parent:"), parent ? parent : "-");
/* Children, Descendants. After this point, the fallback to
* compute children is too expensive, so we gracefully quit if the
* APIs don't exist. */
if (ctl->useSnapshotOld) {
ret = true;
goto cleanup;
}
flags = 0;
count = virDomainSnapshotNumChildren(snapshot, flags);
if (count < 0)
goto cleanup;
vshPrint(ctl, "%-15s %d\n", _("Children:"), count);
flags = VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
count = virDomainSnapshotNumChildren(snapshot, flags);
if (count < 0)
goto cleanup;
vshPrint(ctl, "%-15s %d\n", _("Descendants:"), count);
/* Metadata; the fallback here relies on the fact that metadata
* used to have an all-or-nothing effect on snapshot count. */
metadata = virDomainSnapshotHasMetadata(snapshot, 0);
if (metadata < 0) {
metadata = virDomainSnapshotNum(dom,
VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
virResetLastError();
}
if (metadata >= 0)
vshPrint(ctl, "%-15s %s\n", _("Metadata:"),
metadata ? _("yes") : _("no"));
ret = true;
cleanup:
VIR_FREE(doc);
VIR_FREE(parent);
if (snapshot)
virDomainSnapshotFree(snapshot);
virDomainFree(dom);
return ret;
}
/* Helpers for collecting a list of snapshots. */
struct vshSnap {
virDomainSnapshotPtr snap;
char *parent;
};
struct vshSnapshotList {
struct vshSnap *snaps;
int nsnaps;
};
typedef struct vshSnapshotList *vshSnapshotListPtr;
static void
vshSnapshotListFree(vshSnapshotListPtr snaplist)
{
int i;
if (!snaplist)
return;
if (snaplist->snaps) {
for (i = 0; i < snaplist->nsnaps; i++) {
if (snaplist->snaps[i].snap)
virDomainSnapshotFree(snaplist->snaps[i].snap);
VIR_FREE(snaplist->snaps[i].parent);
}
VIR_FREE(snaplist->snaps);
}
VIR_FREE(snaplist);
}
static int
vshSnapSorter(const void *a, const void *b)
{
const struct vshSnap *sa = a;
const struct vshSnap *sb = b;
if (sa->snap && !sb->snap)
return -1;
if (!sa->snap)
return sb->snap != NULL;
/* User visible sort, so we want locale-specific case comparison. */
return strcasecmp(virDomainSnapshotGetName(sa->snap),
virDomainSnapshotGetName(sb->snap));
}
/* Compute a list of snapshots from DOM. If FROM is provided, the
* list is limited to descendants of the given snapshot. If FLAGS is
* given, the list is filtered. If TREE is specified, then all but
* FROM or the roots will also have parent information. */
static vshSnapshotListPtr
vshSnapshotListCollect(vshControl *ctl, virDomainPtr dom,
virDomainSnapshotPtr from,
unsigned int flags, bool tree)
{
int i;
char **names = NULL;
int count = -1;
bool descendants = false;
bool roots = false;
virDomainSnapshotPtr *snaps;
vshSnapshotListPtr snaplist = vshMalloc(ctl, sizeof(*snaplist));
vshSnapshotListPtr ret = NULL;
const char *fromname = NULL;
int start_index = -1;
int deleted = 0;
/* Try the interface available in 0.9.13 and newer. */
if (!ctl->useSnapshotOld) {
if (from)
count = virDomainSnapshotListAllChildren(from, &snaps, flags);
else
count = virDomainListAllSnapshots(dom, &snaps, flags);
}
if (count >= 0) {
/* When mixing --from and --tree, we also want a copy of from
* in the list, but with no parent for that one entry. */
snaplist->snaps = vshCalloc(ctl, count + (tree && from),
sizeof(*snaplist->snaps));
snaplist->nsnaps = count;
for (i = 0; i < count; i++)
snaplist->snaps[i].snap = snaps[i];
VIR_FREE(snaps);
if (tree) {
for (i = 0; i < count; i++) {
if (vshGetSnapshotParent(ctl, snaplist->snaps[i].snap,
&snaplist->snaps[i].parent) < 0)
goto cleanup;
}
if (from) {
snaps[snaplist->nsnaps++] = from;
virDomainSnapshotRef(from);
}
}
goto success;
}
/* Assume that if we got this far, then the --no-leaves and
* --no-metadata flags were not supported. Disable groups that
* have no impact. */
/* XXX should we emulate --no-leaves? */
if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES &&
flags & VIR_DOMAIN_SNAPSHOT_LIST_LEAVES)
flags &= ~(VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES |
VIR_DOMAIN_SNAPSHOT_LIST_LEAVES);
if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA &&
flags & VIR_DOMAIN_SNAPSHOT_LIST_METADATA)
flags &= ~(VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA |
VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA) {
/* We can emulate --no-metadata if --metadata was supported,
* since it was an all-or-none attribute on old servers. */
count = virDomainSnapshotNum(dom,
VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
if (count < 0)
goto cleanup;
if (count > 0)
return snaplist;
flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA;
}
/* This uses the interfaces available in 0.8.0-0.9.6
* (virDomainSnapshotListNames, global list only) and in
* 0.9.7-0.9.12 (addition of virDomainSnapshotListChildrenNames
* for child listing, and new flags), as follows, with [*] by the
* combinations that need parent info (either for filtering
* purposes or for the resulting tree listing):
* old new
* list global as-is global as-is
* list --roots *global + filter global + flags
* list --from *global + filter child as-is
* list --from --descendants *global + filter child + flags
* list --tree *global as-is *global as-is
* list --tree --from *global + filter *child + flags
*
* Additionally, when --tree and --from are both used, from is
* added to the final list as the only element without a parent.
* Otherwise, --from does not appear in the final list.
*/
if (from) {
fromname = virDomainSnapshotGetName(from);
if (!fromname) {
vshError(ctl, "%s", _("Could not get snapshot name"));
goto cleanup;
}
descendants = (flags & VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS) || tree;
if (tree)
flags |= VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
/* Determine if we can use the new child listing API. */
if (ctl->useSnapshotOld ||
((count = virDomainSnapshotNumChildren(from, flags)) < 0 &&
last_error->code == VIR_ERR_NO_SUPPORT)) {
/* We can emulate --from. */
/* XXX can we also emulate --leaves? */
virFreeError(last_error);
last_error = NULL;
ctl->useSnapshotOld = true;
flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
goto global;
}
if (tree && count >= 0)
count++;
} else {
global:
/* Global listing (including fallback when --from failed with
* child listing). */
count = virDomainSnapshotNum(dom, flags);
/* Fall back to simulation if --roots was unsupported. */
/* XXX can we also emulate --leaves? */
if (!from && count < 0 && last_error->code == VIR_ERR_INVALID_ARG &&
(flags & VIR_DOMAIN_SNAPSHOT_LIST_ROOTS)) {
virFreeError(last_error);
last_error = NULL;
roots = true;
flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_ROOTS;
count = virDomainSnapshotNum(dom, flags);
}
}
if (count < 0) {
if (!last_error)
vshError(ctl, _("failed to collect snapshot list"));
goto cleanup;
}
if (!count)
goto success;
names = vshCalloc(ctl, sizeof(*names), count);
/* Now that we have a count, collect the list. */
if (from && !ctl->useSnapshotOld) {
if (tree) {
if (count)
count = virDomainSnapshotListChildrenNames(from, names + 1,
count - 1, flags);
if (count >= 0) {
count++;
names[0] = vshStrdup(ctl, fromname);
}
} else {
count = virDomainSnapshotListChildrenNames(from, names,
count, flags);
}
} else {
count = virDomainSnapshotListNames(dom, names, count, flags);
}
if (count < 0)
goto cleanup;
snaplist->snaps = vshCalloc(ctl, sizeof(*snaplist->snaps), count);
snaplist->nsnaps = count;
for (i = 0; i < count; i++) {
snaplist->snaps[i].snap = virDomainSnapshotLookupByName(dom,
names[i], 0);
if (!snaplist->snaps[i].snap)
goto cleanup;
}
/* Collect parents when needed. With the new API, --tree and
* --from together put from as the first element without a parent;
* with the old API we still need to do a post-process filtering
* based on all parent information. */
if (tree || (from && ctl->useSnapshotOld) || roots) {
for (i = (from && !ctl->useSnapshotOld); i < count; i++) {
if (from && ctl->useSnapshotOld && STREQ(names[i], fromname)) {
start_index = i;
if (tree)
continue;
}
if (vshGetSnapshotParent(ctl, snaplist->snaps[i].snap,
&snaplist->snaps[i].parent) < 0)
goto cleanup;
if ((from && ((tree && !snaplist->snaps[i].parent) ||
(!descendants &&
STRNEQ_NULLABLE(fromname,
snaplist->snaps[i].parent)))) ||
(roots && snaplist->snaps[i].parent)) {
virDomainSnapshotFree(snaplist->snaps[i].snap);
snaplist->snaps[i].snap = NULL;
VIR_FREE(snaplist->snaps[i].parent);
deleted++;
}
}
}
if (tree)
goto success;
if (ctl->useSnapshotOld && descendants) {
bool changed = false;
bool remaining = false;
/* Make multiple passes over the list - first pass finds
* direct children and NULLs out all roots and from, remaining
* passes NULL out any undecided entry whose parent is not
* still in list. We mark known descendants by clearing
* snaps[i].parents. Sorry, this is O(n^3) - hope your
* hierarchy isn't huge. XXX Is it worth making O(n^2 log n)
* by using qsort and bsearch? */
if (start_index < 0) {
vshError(ctl, _("snapshot %s disappeared from list"), fromname);
goto cleanup;
}
for (i = 0; i < count; i++) {
if (i == start_index || !snaplist->snaps[i].parent) {
VIR_FREE(names[i]);
virDomainSnapshotFree(snaplist->snaps[i].snap);
snaplist->snaps[i].snap = NULL;
VIR_FREE(snaplist->snaps[i].parent);
deleted++;
} else if (STREQ(snaplist->snaps[i].parent, fromname)) {
VIR_FREE(snaplist->snaps[i].parent);
changed = true;
} else {
remaining = true;
}
}
if (!changed) {
ret = vshMalloc(ctl, sizeof(*snaplist));
goto cleanup;
}
while (changed && remaining) {
changed = remaining = false;
for (i = 0; i < count; i++) {
bool found_parent = false;
int j;
if (!names[i] || !snaplist->snaps[i].parent)
continue;
for (j = 0; j < count; j++) {
if (!names[j] || i == j)
continue;
if (STREQ(snaplist->snaps[i].parent, names[j])) {
found_parent = true;
if (!snaplist->snaps[j].parent)
VIR_FREE(snaplist->snaps[i].parent);
else
remaining = true;
break;
}
}
if (!found_parent) {
changed = true;
VIR_FREE(names[i]);
virDomainSnapshotFree(snaplist->snaps[i].snap);
snaplist->snaps[i].snap = NULL;
VIR_FREE(snaplist->snaps[i].parent);
deleted++;
}
}
}
}
success:
qsort(snaplist->snaps, snaplist->nsnaps, sizeof(*snaplist->snaps),
vshSnapSorter);
snaplist->nsnaps -= deleted;
ret = snaplist;
snaplist = NULL;
cleanup:
vshSnapshotListFree(snaplist);
if (names)
for (i = 0; i < count; i++)
VIR_FREE(names[i]);
VIR_FREE(names);
return ret;
}
static const char *
vshSnapshotListLookup(int id, bool parent, void *opaque)
{
vshSnapshotListPtr snaplist = opaque;
if (parent)
return snaplist->snaps[id].parent;
return virDomainSnapshotGetName(snaplist->snaps[id].snap);
}
/*
* "snapshot-list" command
*/
static const vshCmdInfo info_snapshot_list[] = {
{"help", N_("List snapshots for a domain")},
{"desc", N_("Snapshot List")},
{NULL, NULL}
};
static const vshCmdOptDef opts_snapshot_list[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"parent", VSH_OT_BOOL, 0, N_("add a column showing parent snapshot")},
{"roots", VSH_OT_BOOL, 0, N_("list only snapshots without parents")},
{"leaves", VSH_OT_BOOL, 0, N_("list only snapshots without children")},
{"no-leaves", VSH_OT_BOOL, 0,
N_("list only snapshots that are not leaves (with children)")},
{"metadata", VSH_OT_BOOL, 0,
N_("list only snapshots that have metadata that would prevent undefine")},
{"no-metadata", VSH_OT_BOOL, 0,
N_("list only snapshots that have no metadata managed by libvirt")},
{"tree", VSH_OT_BOOL, 0, N_("list snapshots in a tree")},
{"from", VSH_OT_DATA, 0, N_("limit list to children of given snapshot")},
{"current", VSH_OT_BOOL, 0,
N_("limit list to children of current snapshot")},
{"descendants", VSH_OT_BOOL, 0, N_("with --from, list all descendants")},
{NULL, 0, 0, NULL}
};
static bool
cmdSnapshotList(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
unsigned int flags = 0;
bool show_parent = false;
int i;
xmlDocPtr xml = NULL;
xmlXPathContextPtr ctxt = NULL;
char *doc = NULL;
virDomainSnapshotPtr snapshot = NULL;
char *state = NULL;
char *parent = NULL;
long long creation_longlong;
time_t creation_time_t;
char timestr[100];
struct tm time_info;
bool tree = vshCommandOptBool(cmd, "tree");
bool leaves = vshCommandOptBool(cmd, "leaves");
bool no_leaves = vshCommandOptBool(cmd, "no-leaves");
const char *from = NULL;
virDomainSnapshotPtr start = NULL;
vshSnapshotListPtr snaplist = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
goto cleanup;
if ((vshCommandOptBool(cmd, "from") ||
vshCommandOptBool(cmd, "current")) &&
vshLookupSnapshot(ctl, cmd, "from", true, dom, &start, &from) < 0)
goto cleanup;
if (vshCommandOptBool(cmd, "parent")) {
if (vshCommandOptBool(cmd, "roots")) {
vshError(ctl, "%s",
_("--parent and --roots are mutually exclusive"));
goto cleanup;
}
if (tree) {
vshError(ctl, "%s",
_("--parent and --tree are mutually exclusive"));
goto cleanup;
}
show_parent = true;
} else if (vshCommandOptBool(cmd, "roots")) {
if (tree) {
vshError(ctl, "%s",
_("--roots and --tree are mutually exclusive"));
goto cleanup;
}
if (from) {
vshError(ctl, "%s",
_("--roots and --from are mutually exclusive"));
goto cleanup;
}
flags |= VIR_DOMAIN_SNAPSHOT_LIST_ROOTS;
}
if (leaves) {
if (tree) {
vshError(ctl, "%s",
_("--leaves and --tree are mutually exclusive"));
goto cleanup;
}
flags |= VIR_DOMAIN_SNAPSHOT_LIST_LEAVES;
}
if (no_leaves) {
if (tree) {
vshError(ctl, "%s",
_("--no-leaves and --tree are mutually exclusive"));
goto cleanup;
}
flags |= VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES;
}
if (vshCommandOptBool(cmd, "metadata")) {
flags |= VIR_DOMAIN_SNAPSHOT_LIST_METADATA;
}
if (vshCommandOptBool(cmd, "no-metadata")) {
flags |= VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA;
}
if (vshCommandOptBool(cmd, "descendants")) {
if (!from) {
vshError(ctl, "%s",
_("--descendants requires either --from or --current"));
goto cleanup;
}
flags |= VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
}
if ((snaplist = vshSnapshotListCollect(ctl, dom, start, flags,
tree)) == NULL)
goto cleanup;
if (!tree) {
if (show_parent)
vshPrintExtra(ctl, " %-20s %-25s %-15s %s",
_("Name"), _("Creation Time"), _("State"),
_("Parent"));
else
vshPrintExtra(ctl, " %-20s %-25s %s",
_("Name"), _("Creation Time"), _("State"));
vshPrintExtra(ctl, "\n"
"------------------------------------------------------------\n");
}
if (!snaplist->nsnaps) {
ret = true;
goto cleanup;
}
if (tree) {
for (i = 0; i < snaplist->nsnaps; i++) {
if (!snaplist->snaps[i].parent &&
vshTreePrint(ctl, vshSnapshotListLookup, snaplist,
snaplist->nsnaps, i) < 0)
goto cleanup;
}
ret = true;
goto cleanup;
}
for (i = 0; i < snaplist->nsnaps; i++) {
const char *name;
/* free up memory from previous iterations of the loop */
VIR_FREE(parent);
VIR_FREE(state);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
VIR_FREE(doc);
snapshot = snaplist->snaps[i].snap;
name = virDomainSnapshotGetName(snapshot);
assert(name);
doc = virDomainSnapshotGetXMLDesc(snapshot, 0);
if (!doc)
continue;
xml = virXMLParseStringCtxt(doc, _("(domain_snapshot)"), &ctxt);
if (!xml)
continue;
if (show_parent)
parent = virXPathString("string(/domainsnapshot/parent/name)",
ctxt);
state = virXPathString("string(/domainsnapshot/state)", ctxt);
if (state == NULL)
continue;
if (virXPathLongLong("string(/domainsnapshot/creationTime)", ctxt,
&creation_longlong) < 0)
continue;
creation_time_t = creation_longlong;
if (creation_time_t != creation_longlong) {
vshError(ctl, "%s", _("time_t overflow"));
continue;
}
localtime_r(&creation_time_t, &time_info);
strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S %z",
&time_info);
if (parent)
vshPrint(ctl, " %-20s %-25s %-15s %s\n",
name, timestr, state, parent);
else
vshPrint(ctl, " %-20s %-25s %s\n", name, timestr, state);
}
ret = true;
cleanup:
/* this frees up memory from the last iteration of the loop */
vshSnapshotListFree(snaplist);
VIR_FREE(parent);
VIR_FREE(state);
if (start)
virDomainSnapshotFree(start);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(xml);
VIR_FREE(doc);
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "snapshot-dumpxml" command
*/
static const vshCmdInfo info_snapshot_dumpxml[] = {
{"help", N_("Dump XML for a domain snapshot")},
{"desc", N_("Snapshot Dump XML")},
{NULL, NULL}
};
static const vshCmdOptDef opts_snapshot_dumpxml[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"snapshotname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("snapshot name")},
{"security-info", VSH_OT_BOOL, 0,
N_("include security sensitive information in XML dump")},
{NULL, 0, 0, NULL}
};
static bool
cmdSnapshotDumpXML(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
const char *name = NULL;
virDomainSnapshotPtr snapshot = NULL;
char *xml = NULL;
unsigned int flags = 0;
if (vshCommandOptBool(cmd, "security-info"))
flags |= VIR_DOMAIN_XML_SECURE;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
goto cleanup;
if (vshCommandOptString(cmd, "snapshotname", &name) <= 0)
goto cleanup;
snapshot = virDomainSnapshotLookupByName(dom, name, 0);
if (snapshot == NULL)
goto cleanup;
xml = virDomainSnapshotGetXMLDesc(snapshot, flags);
if (!xml)
goto cleanup;
vshPrint(ctl, "%s", xml);
ret = true;
cleanup:
VIR_FREE(xml);
if (snapshot)
virDomainSnapshotFree(snapshot);
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "snapshot-parent" command
*/
static const vshCmdInfo info_snapshot_parent[] = {
{"help", N_("Get the name of the parent of a snapshot")},
{"desc", N_("Extract the snapshot's parent, if any")},
{NULL, NULL}
};
static const vshCmdOptDef opts_snapshot_parent[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"snapshotname", VSH_OT_DATA, 0, N_("find parent of snapshot name")},
{"current", VSH_OT_BOOL, 0, N_("find parent of current snapshot")},
{NULL, 0, 0, NULL}
};
static bool
cmdSnapshotParent(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
const char *name = NULL;
virDomainSnapshotPtr snapshot = NULL;
char *parent = NULL;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
goto cleanup;
if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
&snapshot, &name) < 0)
goto cleanup;
if (vshGetSnapshotParent(ctl, snapshot, &parent) < 0)
goto cleanup;
if (!parent) {
vshError(ctl, _("snapshot '%s' has no parent"), name);
goto cleanup;
}
vshPrint(ctl, "%s", parent);
ret = true;
cleanup:
VIR_FREE(parent);
if (snapshot)
virDomainSnapshotFree(snapshot);
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "snapshot-revert" command
*/
static const vshCmdInfo info_snapshot_revert[] = {
{"help", N_("Revert a domain to a snapshot")},
{"desc", N_("Revert domain to snapshot")},
{NULL, NULL}
};
static const vshCmdOptDef opts_snapshot_revert[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
{"current", VSH_OT_BOOL, 0, N_("revert to current snapshot")},
{"running", VSH_OT_BOOL, 0, N_("after reverting, change state to running")},
{"paused", VSH_OT_BOOL, 0, N_("after reverting, change state to paused")},
{"force", VSH_OT_BOOL, 0, N_("try harder on risky reverts")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomainSnapshotRevert(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
const char *name = NULL;
virDomainSnapshotPtr snapshot = NULL;
unsigned int flags = 0;
bool force = false;
int result;
if (vshCommandOptBool(cmd, "running"))
flags |= VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING;
if (vshCommandOptBool(cmd, "paused"))
flags |= VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED;
/* We want virsh snapshot-revert --force to work even when talking
* to older servers that did the unsafe revert by default but
* reject the flag, so we probe without the flag, and only use it
* when the error says it will make a difference. */
if (vshCommandOptBool(cmd, "force"))
force = true;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
goto cleanup;
if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
&snapshot, &name) < 0)
goto cleanup;
result = virDomainRevertToSnapshot(snapshot, flags);
if (result < 0 && force &&
last_error->code == VIR_ERR_SNAPSHOT_REVERT_RISKY) {
flags |= VIR_DOMAIN_SNAPSHOT_REVERT_FORCE;
virFreeError(last_error);
last_error = NULL;
result = virDomainRevertToSnapshot(snapshot, flags);
}
if (result < 0)
goto cleanup;
ret = true;
cleanup:
if (snapshot)
virDomainSnapshotFree(snapshot);
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "snapshot-delete" command
*/
static const vshCmdInfo info_snapshot_delete[] = {
{"help", N_("Delete a domain snapshot")},
{"desc", N_("Snapshot Delete")},
{NULL, NULL}
};
static const vshCmdOptDef opts_snapshot_delete[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
{"current", VSH_OT_BOOL, 0, N_("delete current snapshot")},
{"children", VSH_OT_BOOL, 0, N_("delete snapshot and all children")},
{"children-only", VSH_OT_BOOL, 0, N_("delete children but not snapshot")},
{"metadata", VSH_OT_BOOL, 0,
N_("delete only libvirt metadata, leaving snapshot contents behind")},
{NULL, 0, 0, NULL}
};
static bool
cmdSnapshotDelete(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
const char *name = NULL;
virDomainSnapshotPtr snapshot = NULL;
unsigned int flags = 0;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
goto cleanup;
if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
&snapshot, &name) < 0)
goto cleanup;
if (vshCommandOptBool(cmd, "children"))
flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN;
if (vshCommandOptBool(cmd, "children-only"))
flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY;
if (vshCommandOptBool(cmd, "metadata"))
flags |= VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY;
/* XXX If we wanted, we could emulate DELETE_CHILDREN_ONLY even on
* older servers that reject the flag, by manually computing the
* list of descendants. But that's a lot of code to maintain. */
if (virDomainSnapshotDelete(snapshot, flags) == 0) {
if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)
vshPrint(ctl, _("Domain snapshot %s children deleted\n"), name);
else
vshPrint(ctl, _("Domain snapshot %s deleted\n"), name);
} else {
vshError(ctl, _("Failed to delete snapshot %s"), name);
goto cleanup;
}
ret = true;
cleanup:
if (snapshot)
virDomainSnapshotFree(snapshot);
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "domblkerror" command
*/
static const char *
vshDomainIOErrorToString(int error)
{
switch ((virDomainDiskErrorCode) error) {
case VIR_DOMAIN_DISK_ERROR_NONE:
return _("no error");
case VIR_DOMAIN_DISK_ERROR_UNSPEC:
return _("unspecified error");
case VIR_DOMAIN_DISK_ERROR_NO_SPACE:
return _("no space");
case VIR_DOMAIN_DISK_ERROR_LAST:
;
}
return _("unknown error");
}
static const vshCmdInfo info_domblkerror[] = {
{"help", N_("Show errors on block devices")},
{"desc", N_("Show block device errors")},
{NULL, NULL}
};
static const vshCmdOptDef opts_domblkerror[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id, or uuid")},
{NULL, 0, 0, NULL}
};
static bool
cmdDomBlkError(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
virDomainDiskErrorPtr disks = NULL;
unsigned int ndisks;
int i;
int count;
bool ret = false;
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return false;
if ((count = virDomainGetDiskErrors(dom, NULL, 0, 0)) < 0)
goto cleanup;
ndisks = count;
if (ndisks) {
if (VIR_ALLOC_N(disks, ndisks) < 0)
goto cleanup;
if ((count = virDomainGetDiskErrors(dom, disks, ndisks, 0)) == -1)
goto cleanup;
}
if (count == 0) {
vshPrint(ctl, _("No errors found\n"));
} else {
for (i = 0; i < count; i++) {
vshPrint(ctl, "%s: %s\n",
disks[i].disk,
vshDomainIOErrorToString(disks[i].error));
}
}
ret = true;
cleanup:
VIR_FREE(disks);
virDomainFree(dom);
return ret;
}
/*
* "qemu-monitor-command" command
*/
static const vshCmdInfo info_qemu_monitor_command[] = {
{"help", N_("QEMU Monitor Command")},
{"desc", N_("QEMU Monitor Command")},
{NULL, NULL}
};
static const vshCmdOptDef opts_qemu_monitor_command[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
{"hmp", VSH_OT_BOOL, 0, N_("command is in human monitor protocol")},
{"cmd", VSH_OT_ARGV, VSH_OFLAG_REQ, N_("command")},
{NULL, 0, 0, NULL}
};
static bool
cmdQemuMonitorCommand(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
char *monitor_cmd = NULL;
char *result = NULL;
unsigned int flags = 0;
const vshCmdOpt *opt = NULL;
virBuffer buf = VIR_BUFFER_INITIALIZER;
bool pad = false;
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
dom = vshCommandOptDomain(ctl, cmd, NULL);
if (dom == NULL)
goto cleanup;
while ((opt = vshCommandOptArgv(cmd, opt))) {
if (pad)
virBufferAddChar(&buf, ' ');
pad = true;
virBufferAdd(&buf, opt->data, -1);
}
if (virBufferError(&buf)) {
vshPrint(ctl, "%s", _("Failed to collect command"));
goto cleanup;
}
monitor_cmd = virBufferContentAndReset(&buf);
if (vshCommandOptBool(cmd, "hmp"))
flags |= VIR_DOMAIN_QEMU_MONITOR_COMMAND_HMP;
if (virDomainQemuMonitorCommand(dom, monitor_cmd, &result, flags) < 0)
goto cleanup;
printf("%s\n", result);
ret = true;
cleanup:
VIR_FREE(result);
VIR_FREE(monitor_cmd);
if (dom)
virDomainFree(dom);
return ret;
}
/*
* "qemu-attach" command
*/
static const vshCmdInfo info_qemu_attach[] = {
{"help", N_("QEMU Attach")},
{"desc", N_("QEMU Attach")},
{NULL, NULL}
};
static const vshCmdOptDef opts_qemu_attach[] = {
{"pid", VSH_OT_DATA, VSH_OFLAG_REQ, N_("pid")},
{NULL, 0, 0, NULL}
};
static bool
cmdQemuAttach(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
bool ret = false;
unsigned int flags = 0;
unsigned int pid_value; /* API uses unsigned int, not pid_t */
if (!vshConnectionUsability(ctl, ctl->conn))
goto cleanup;
if (vshCommandOptUInt(cmd, "pid", &pid_value) <= 0) {
vshError(ctl, "%s", _("missing pid value"));
goto cleanup;
}
if (!(dom = virDomainQemuAttach(ctl->conn, pid_value, flags)))
goto cleanup;
if (dom != NULL) {
vshPrint(ctl, _("Domain %s attached to pid %u\n"),
virDomainGetName(dom), pid_value);
virDomainFree(dom);
ret = true;
} else {
vshError(ctl, _("Failed to attach to pid %u"), pid_value);
}
cleanup:
return ret;
}
static const vshCmdDef domManagementCmds[] = {
{"attach-device", cmdAttachDevice, opts_attach_device,
info_attach_device, 0},
{"attach-disk", cmdAttachDisk, opts_attach_disk,
info_attach_disk, 0},
{"attach-interface", cmdAttachInterface, opts_attach_interface,
info_attach_interface, 0},
{"autostart", cmdAutostart, opts_autostart, info_autostart, 0},
{"blkdeviotune", cmdBlkdeviotune, opts_blkdeviotune, info_blkdeviotune, 0},
{"blkiotune", cmdBlkiotune, opts_blkiotune, info_blkiotune, 0},
{"blockcopy", cmdBlockCopy, opts_block_copy, info_block_copy, 0},
{"blockjob", cmdBlockJob, opts_block_job, info_block_job, 0},
{"blockpull", cmdBlockPull, opts_block_pull, info_block_pull, 0},
{"blockresize", cmdBlockResize, opts_block_resize, info_block_resize, 0},
{"change-media", cmdChangeMedia, opts_change_media, info_change_media, 0},
#ifndef WIN32
{"console", cmdConsole, opts_console, info_console, 0},
#endif
{"cpu-baseline", cmdCPUBaseline, opts_cpu_baseline, info_cpu_baseline, 0},
{"cpu-compare", cmdCPUCompare, opts_cpu_compare, info_cpu_compare, 0},
{"cpu-stats", cmdCPUStats, opts_cpu_stats, info_cpu_stats, 0},
{"create", cmdCreate, opts_create, info_create, 0},
{"define", cmdDefine, opts_define, info_define, 0},
{"desc", cmdDesc, opts_desc, info_desc, 0},
{"destroy", cmdDestroy, opts_destroy, info_destroy, 0},
{"detach-device", cmdDetachDevice, opts_detach_device,
info_detach_device, 0},
{"detach-disk", cmdDetachDisk, opts_detach_disk, info_detach_disk, 0},
{"detach-interface", cmdDetachInterface, opts_detach_interface,
info_detach_interface, 0},
{"domdisplay", cmdDomDisplay, opts_domdisplay, info_domdisplay, 0},
{"domid", cmdDomid, opts_domid, info_domid, 0},
{"domif-setlink", cmdDomIfSetLink, opts_domif_setlink, info_domif_setlink, 0},
{"domiftune", cmdDomIftune, opts_domiftune, info_domiftune, 0},
{"domjobabort", cmdDomjobabort, opts_domjobabort, info_domjobabort, 0},
{"domjobinfo", cmdDomjobinfo, opts_domjobinfo, info_domjobinfo, 0},
{"domname", cmdDomname, opts_domname, info_domname, 0},
{"dompmsuspend", cmdDomPMSuspend,
opts_dom_pm_suspend, info_dom_pm_suspend, 0},
{"dompmwakeup", cmdDomPMWakeup,
opts_dom_pm_wakeup, info_dom_pm_wakeup, 0},
{"domuuid", cmdDomuuid, opts_domuuid, info_domuuid, 0},
{"domxml-from-native", cmdDomXMLFromNative, opts_domxmlfromnative,
info_domxmlfromnative, 0},
{"domxml-to-native", cmdDomXMLToNative, opts_domxmltonative,
info_domxmltonative, 0},
{"dump", cmdDump, opts_dump, info_dump, 0},
{"dumpxml", cmdDumpXML, opts_dumpxml, info_dumpxml, 0},
{"edit", cmdEdit, opts_edit, info_edit, 0},
{"inject-nmi", cmdInjectNMI, opts_inject_nmi, info_inject_nmi, 0},
{"send-key", cmdSendKey, opts_send_key, info_send_key, 0},
{"managedsave", cmdManagedSave, opts_managedsave, info_managedsave, 0},
{"managedsave-remove", cmdManagedSaveRemove, opts_managedsaveremove,
info_managedsaveremove, 0},
{"maxvcpus", cmdMaxvcpus, opts_maxvcpus, info_maxvcpus, 0},
{"memtune", cmdMemtune, opts_memtune, info_memtune, 0},
{"migrate", cmdMigrate, opts_migrate, info_migrate, 0},
{"migrate-setmaxdowntime", cmdMigrateSetMaxDowntime,
opts_migrate_setmaxdowntime, info_migrate_setmaxdowntime, 0},
{"migrate-setspeed", cmdMigrateSetMaxSpeed,
opts_migrate_setspeed, info_migrate_setspeed, 0},
{"migrate-getspeed", cmdMigrateGetMaxSpeed,
opts_migrate_getspeed, info_migrate_getspeed, 0},
{"numatune", cmdNumatune, opts_numatune, info_numatune, 0},
{"reboot", cmdReboot, opts_reboot, info_reboot, 0},
{"reset", cmdReset, opts_reset, info_reset, 0},
{"restore", cmdRestore, opts_restore, info_restore, 0},
{"resume", cmdResume, opts_resume, info_resume, 0},
{"save", cmdSave, opts_save, info_save, 0},
{"save-image-define", cmdSaveImageDefine, opts_save_image_define,
info_save_image_define, 0},
{"save-image-dumpxml", cmdSaveImageDumpxml, opts_save_image_dumpxml,
info_save_image_dumpxml, 0},
{"save-image-edit", cmdSaveImageEdit, opts_save_image_edit,
info_save_image_edit, 0},
{"schedinfo", cmdSchedinfo, opts_schedinfo, info_schedinfo, 0},
{"screenshot", cmdScreenshot, opts_screenshot, info_screenshot, 0},
{"setmaxmem", cmdSetmaxmem, opts_setmaxmem, info_setmaxmem, 0},
{"setmem", cmdSetmem, opts_setmem, info_setmem, 0},
{"setvcpus", cmdSetvcpus, opts_setvcpus, info_setvcpus, 0},
{"shutdown", cmdShutdown, opts_shutdown, info_shutdown, 0},
{"start", cmdStart, opts_start, info_start, 0},
{"suspend", cmdSuspend, opts_suspend, info_suspend, 0},
{"ttyconsole", cmdTTYConsole, opts_ttyconsole, info_ttyconsole, 0},
{"undefine", cmdUndefine, opts_undefine, info_undefine, 0},
{"update-device", cmdUpdateDevice, opts_update_device,
info_update_device, 0},
{"vcpucount", cmdVcpucount, opts_vcpucount, info_vcpucount, 0},
{"vcpuinfo", cmdVcpuinfo, opts_vcpuinfo, info_vcpuinfo, 0},
{"vcpupin", cmdVcpuPin, opts_vcpupin, info_vcpupin, 0},
{"vncdisplay", cmdVNCDisplay, opts_vncdisplay, info_vncdisplay, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef domMonitoringCmds[] = {
{"domblkerror", cmdDomBlkError, opts_domblkerror, info_domblkerror, 0},
{"domblkinfo", cmdDomblkinfo, opts_domblkinfo, info_domblkinfo, 0},
{"domblklist", cmdDomblklist, opts_domblklist, info_domblklist, 0},
{"domblkstat", cmdDomblkstat, opts_domblkstat, info_domblkstat, 0},
{"domcontrol", cmdDomControl, opts_domcontrol, info_domcontrol, 0},
{"domif-getlink", cmdDomIfGetLink, opts_domif_getlink, info_domif_getlink, 0},
{"domiflist", cmdDomiflist, opts_domiflist, info_domiflist, 0},
{"domifstat", cmdDomIfstat, opts_domifstat, info_domifstat, 0},
{"dominfo", cmdDominfo, opts_dominfo, info_dominfo, 0},
{"dommemstat", cmdDomMemStat, opts_dommemstat, info_dommemstat, 0},
{"domstate", cmdDomstate, opts_domstate, info_domstate, 0},
{"list", cmdList, opts_list, info_list, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef storagePoolCmds[] = {
{"find-storage-pool-sources-as", cmdPoolDiscoverSourcesAs,
opts_find_storage_pool_sources_as, info_find_storage_pool_sources_as, 0},
{"find-storage-pool-sources", cmdPoolDiscoverSources,
opts_find_storage_pool_sources, info_find_storage_pool_sources, 0},
{"pool-autostart", cmdPoolAutostart, opts_pool_autostart,
info_pool_autostart, 0},
{"pool-build", cmdPoolBuild, opts_pool_build, info_pool_build, 0},
{"pool-create-as", cmdPoolCreateAs, opts_pool_X_as, info_pool_create_as, 0},
{"pool-create", cmdPoolCreate, opts_pool_create, info_pool_create, 0},
{"pool-define-as", cmdPoolDefineAs, opts_pool_X_as, info_pool_define_as, 0},
{"pool-define", cmdPoolDefine, opts_pool_define, info_pool_define, 0},
{"pool-delete", cmdPoolDelete, opts_pool_delete, info_pool_delete, 0},
{"pool-destroy", cmdPoolDestroy, opts_pool_destroy, info_pool_destroy, 0},
{"pool-dumpxml", cmdPoolDumpXML, opts_pool_dumpxml, info_pool_dumpxml, 0},
{"pool-edit", cmdPoolEdit, opts_pool_edit, info_pool_edit, 0},
{"pool-info", cmdPoolInfo, opts_pool_info, info_pool_info, 0},
{"pool-list", cmdPoolList, opts_pool_list, info_pool_list, 0},
{"pool-name", cmdPoolName, opts_pool_name, info_pool_name, 0},
{"pool-refresh", cmdPoolRefresh, opts_pool_refresh, info_pool_refresh, 0},
{"pool-start", cmdPoolStart, opts_pool_start, info_pool_start, 0},
{"pool-undefine", cmdPoolUndefine, opts_pool_undefine,
info_pool_undefine, 0},
{"pool-uuid", cmdPoolUuid, opts_pool_uuid, info_pool_uuid, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef storageVolCmds[] = {
{"vol-clone", cmdVolClone, opts_vol_clone, info_vol_clone, 0},
{"vol-create-as", cmdVolCreateAs, opts_vol_create_as,
info_vol_create_as, 0},
{"vol-create", cmdVolCreate, opts_vol_create, info_vol_create, 0},
{"vol-create-from", cmdVolCreateFrom, opts_vol_create_from,
info_vol_create_from, 0},
{"vol-delete", cmdVolDelete, opts_vol_delete, info_vol_delete, 0},
{"vol-download", cmdVolDownload, opts_vol_download, info_vol_download, 0},
{"vol-dumpxml", cmdVolDumpXML, opts_vol_dumpxml, info_vol_dumpxml, 0},
{"vol-info", cmdVolInfo, opts_vol_info, info_vol_info, 0},
{"vol-key", cmdVolKey, opts_vol_key, info_vol_key, 0},
{"vol-list", cmdVolList, opts_vol_list, info_vol_list, 0},
{"vol-name", cmdVolName, opts_vol_name, info_vol_name, 0},
{"vol-path", cmdVolPath, opts_vol_path, info_vol_path, 0},
{"vol-pool", cmdVolPool, opts_vol_pool, info_vol_pool, 0},
{"vol-resize", cmdVolResize, opts_vol_resize, info_vol_resize, 0},
{"vol-upload", cmdVolUpload, opts_vol_upload, info_vol_upload, 0},
{"vol-wipe", cmdVolWipe, opts_vol_wipe, info_vol_wipe, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef networkCmds[] = {
{"net-autostart", cmdNetworkAutostart, opts_network_autostart,
info_network_autostart, 0},
{"net-create", cmdNetworkCreate, opts_network_create,
info_network_create, 0},
{"net-define", cmdNetworkDefine, opts_network_define,
info_network_define, 0},
{"net-destroy", cmdNetworkDestroy, opts_network_destroy,
info_network_destroy, 0},
{"net-dumpxml", cmdNetworkDumpXML, opts_network_dumpxml,
info_network_dumpxml, 0},
{"net-edit", cmdNetworkEdit, opts_network_edit, info_network_edit, 0},
{"net-info", cmdNetworkInfo, opts_network_info, info_network_info, 0},
{"net-list", cmdNetworkList, opts_network_list, info_network_list, 0},
{"net-name", cmdNetworkName, opts_network_name, info_network_name, 0},
{"net-start", cmdNetworkStart, opts_network_start, info_network_start, 0},
{"net-undefine", cmdNetworkUndefine, opts_network_undefine,
info_network_undefine, 0},
{"net-uuid", cmdNetworkUuid, opts_network_uuid, info_network_uuid, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef nodedevCmds[] = {
{"nodedev-create", cmdNodeDeviceCreate, opts_node_device_create,
info_node_device_create, 0},
{"nodedev-destroy", cmdNodeDeviceDestroy, opts_node_device_destroy,
info_node_device_destroy, 0},
{"nodedev-detach", cmdNodeDeviceDetach, opts_node_device_detach,
info_node_device_detach, 0},
{"nodedev-dettach", cmdNodeDeviceDetach, opts_node_device_detach,
info_node_device_detach, VSH_CMD_FLAG_ALIAS},
{"nodedev-dumpxml", cmdNodeDeviceDumpXML, opts_node_device_dumpxml,
info_node_device_dumpxml, 0},
{"nodedev-list", cmdNodeListDevices, opts_node_list_devices,
info_node_list_devices, 0},
{"nodedev-reattach", cmdNodeDeviceReAttach, opts_node_device_reattach,
info_node_device_reattach, 0},
{"nodedev-reset", cmdNodeDeviceReset, opts_node_device_reset,
info_node_device_reset, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef ifaceCmds[] = {
{"iface-begin", cmdInterfaceBegin, opts_interface_begin,
info_interface_begin, 0},
{"iface-bridge", cmdInterfaceBridge, opts_interface_bridge,
info_interface_bridge, 0},
{"iface-commit", cmdInterfaceCommit, opts_interface_commit,
info_interface_commit, 0},
{"iface-define", cmdInterfaceDefine, opts_interface_define,
info_interface_define, 0},
{"iface-destroy", cmdInterfaceDestroy, opts_interface_destroy,
info_interface_destroy, 0},
{"iface-dumpxml", cmdInterfaceDumpXML, opts_interface_dumpxml,
info_interface_dumpxml, 0},
{"iface-edit", cmdInterfaceEdit, opts_interface_edit,
info_interface_edit, 0},
{"iface-list", cmdInterfaceList, opts_interface_list,
info_interface_list, 0},
{"iface-mac", cmdInterfaceMAC, opts_interface_mac,
info_interface_mac, 0},
{"iface-name", cmdInterfaceName, opts_interface_name,
info_interface_name, 0},
{"iface-rollback", cmdInterfaceRollback, opts_interface_rollback,
info_interface_rollback, 0},
{"iface-start", cmdInterfaceStart, opts_interface_start,
info_interface_start, 0},
{"iface-unbridge", cmdInterfaceUnbridge, opts_interface_unbridge,
info_interface_unbridge, 0},
{"iface-undefine", cmdInterfaceUndefine, opts_interface_undefine,
info_interface_undefine, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef nwfilterCmds[] = {
{"nwfilter-define", cmdNWFilterDefine, opts_nwfilter_define,
info_nwfilter_define, 0},
{"nwfilter-dumpxml", cmdNWFilterDumpXML, opts_nwfilter_dumpxml,
info_nwfilter_dumpxml, 0},
{"nwfilter-edit", cmdNWFilterEdit, opts_nwfilter_edit,
info_nwfilter_edit, 0},
{"nwfilter-list", cmdNWFilterList, opts_nwfilter_list,
info_nwfilter_list, 0},
{"nwfilter-undefine", cmdNWFilterUndefine, opts_nwfilter_undefine,
info_nwfilter_undefine, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef secretCmds[] = {
{"secret-define", cmdSecretDefine, opts_secret_define,
info_secret_define, 0},
{"secret-dumpxml", cmdSecretDumpXML, opts_secret_dumpxml,
info_secret_dumpxml, 0},
{"secret-get-value", cmdSecretGetValue, opts_secret_get_value,
info_secret_get_value, 0},
{"secret-list", cmdSecretList, NULL, info_secret_list, 0},
{"secret-set-value", cmdSecretSetValue, opts_secret_set_value,
info_secret_set_value, 0},
{"secret-undefine", cmdSecretUndefine, opts_secret_undefine,
info_secret_undefine, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef virshCmds[] = {
{"cd", cmdCd, opts_cd, info_cd, VSH_CMD_FLAG_NOCONNECT},
{"echo", cmdEcho, opts_echo, info_echo, VSH_CMD_FLAG_NOCONNECT},
{"exit", cmdQuit, NULL, info_quit, VSH_CMD_FLAG_NOCONNECT},
{"help", cmdHelp, opts_help, info_help, VSH_CMD_FLAG_NOCONNECT},
{"pwd", cmdPwd, NULL, info_pwd, VSH_CMD_FLAG_NOCONNECT},
{"quit", cmdQuit, NULL, info_quit, VSH_CMD_FLAG_NOCONNECT},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef snapshotCmds[] = {
{"snapshot-create", cmdSnapshotCreate, opts_snapshot_create,
info_snapshot_create, 0},
{"snapshot-create-as", cmdSnapshotCreateAs, opts_snapshot_create_as,
info_snapshot_create_as, 0},
{"snapshot-current", cmdSnapshotCurrent, opts_snapshot_current,
info_snapshot_current, 0},
{"snapshot-delete", cmdSnapshotDelete, opts_snapshot_delete,
info_snapshot_delete, 0},
{"snapshot-dumpxml", cmdSnapshotDumpXML, opts_snapshot_dumpxml,
info_snapshot_dumpxml, 0},
{"snapshot-edit", cmdSnapshotEdit, opts_snapshot_edit,
info_snapshot_edit, 0},
{"snapshot-info", cmdSnapshotInfo, opts_snapshot_info,
info_snapshot_info, 0},
{"snapshot-list", cmdSnapshotList, opts_snapshot_list,
info_snapshot_list, 0},
{"snapshot-parent", cmdSnapshotParent, opts_snapshot_parent,
info_snapshot_parent, 0},
{"snapshot-revert", cmdDomainSnapshotRevert, opts_snapshot_revert,
info_snapshot_revert, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdDef hostAndHypervisorCmds[] = {
{"capabilities", cmdCapabilities, NULL, info_capabilities, 0},
{"connect", cmdConnect, opts_connect, info_connect,
VSH_CMD_FLAG_NOCONNECT},
{"freecell", cmdFreecell, opts_freecell, info_freecell, 0},
{"hostname", cmdHostname, NULL, info_hostname, 0},
{"nodecpustats", cmdNodeCpuStats, opts_node_cpustats, info_nodecpustats, 0},
{"nodeinfo", cmdNodeinfo, NULL, info_nodeinfo, 0},
{"nodememstats", cmdNodeMemStats, opts_node_memstats, info_nodememstats, 0},
{"nodesuspend", cmdNodeSuspend, opts_node_suspend, info_nodesuspend, 0},
{"qemu-attach", cmdQemuAttach, opts_qemu_attach, info_qemu_attach, 0},
{"qemu-monitor-command", cmdQemuMonitorCommand, opts_qemu_monitor_command,
info_qemu_monitor_command, 0},
{"sysinfo", cmdSysinfo, NULL, info_sysinfo, 0},
{"uri", cmdURI, NULL, info_uri, 0},
{"version", cmdVersion, opts_version, info_version, 0},
{NULL, NULL, NULL, NULL, 0}
};
static const vshCmdGrp cmdGroups[] = {
{VSH_CMD_GRP_DOM_MANAGEMENT, "domain", domManagementCmds},
{VSH_CMD_GRP_DOM_MONITORING, "monitor", domMonitoringCmds},
{VSH_CMD_GRP_HOST_AND_HV, "host", hostAndHypervisorCmds},
{VSH_CMD_GRP_IFACE, "interface", ifaceCmds},
{VSH_CMD_GRP_NWFILTER, "filter", nwfilterCmds},
{VSH_CMD_GRP_NETWORK, "network", networkCmds},
{VSH_CMD_GRP_NODEDEV, "nodedev", nodedevCmds},
{VSH_CMD_GRP_SECRET, "secret", secretCmds},
{VSH_CMD_GRP_SNAPSHOT, "snapshot", snapshotCmds},
{VSH_CMD_GRP_STORAGE_POOL, "pool", storagePoolCmds},
{VSH_CMD_GRP_STORAGE_VOL, "volume", storageVolCmds},
{VSH_CMD_GRP_VIRSH, "virsh", virshCmds},
{NULL, NULL, NULL}
};
/* ---------------
* Utils for work with command definition
* ---------------
*/
static const char *
vshCmddefGetInfo(const vshCmdDef * cmd, const char *name)
{
const vshCmdInfo *info;
for (info = cmd->info; info && info->name; info++) {
if (STREQ(info->name, name))
return info->data;
}
return NULL;
}
/* Validate that the options associated with cmd can be parsed. */
static int
vshCmddefOptParse(const vshCmdDef *cmd, uint32_t *opts_need_arg,
uint32_t *opts_required)
{
int i;
bool optional = false;
*opts_need_arg = 0;
*opts_required = 0;
if (!cmd->opts)
return 0;
for (i = 0; cmd->opts[i].name; i++) {
const vshCmdOptDef *opt = &cmd->opts[i];
if (i > 31)
return -1; /* too many options */
if (opt->type == VSH_OT_BOOL) {
if (opt->flags & VSH_OFLAG_REQ)
return -1; /* bool options can't be mandatory */
continue;
}
if (opt->type == VSH_OT_ALIAS) {
int j;
if (opt->flags || !opt->help)
return -1; /* alias options are tracked by the original name */
for (j = i + 1; cmd->opts[j].name; j++) {
if (STREQ(opt->help, cmd->opts[j].name))
break;
}
if (!cmd->opts[j].name)
return -1; /* alias option must map to a later option name */
continue;
}
if (opt->flags & VSH_OFLAG_REQ_OPT) {
if (opt->flags & VSH_OFLAG_REQ)
*opts_required |= 1 << i;
continue;
}
*opts_need_arg |= 1 << i;
if (opt->flags & VSH_OFLAG_REQ) {
if (optional)
return -1; /* mandatory options must be listed first */
*opts_required |= 1 << i;
} else {
optional = true;
}
if (opt->type == VSH_OT_ARGV && cmd->opts[i + 1].name)
return -1; /* argv option must be listed last */
}
return 0;
}
static const vshCmdOptDef *
vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name,
uint32_t *opts_seen, int *opt_index)
{
int i;
for (i = 0; cmd->opts && cmd->opts[i].name; i++) {
const vshCmdOptDef *opt = &cmd->opts[i];
if (STREQ(opt->name, name)) {
if (opt->type == VSH_OT_ALIAS) {
name = opt->help;
continue;
}
if ((*opts_seen & (1 << i)) && opt->type != VSH_OT_ARGV) {
vshError(ctl, _("option --%s already seen"), name);
return NULL;
}
*opts_seen |= 1 << i;
*opt_index = i;
return opt;
}
}
vshError(ctl, _("command '%s' doesn't support option --%s"),
cmd->name, name);
return NULL;
}
static const vshCmdOptDef *
vshCmddefGetData(const vshCmdDef *cmd, uint32_t *opts_need_arg,
uint32_t *opts_seen)
{
int i;
const vshCmdOptDef *opt;
if (!*opts_need_arg)
return NULL;
/* Grab least-significant set bit */
i = ffs(*opts_need_arg) - 1;
opt = &cmd->opts[i];
if (opt->type != VSH_OT_ARGV)
*opts_need_arg &= ~(1 << i);
*opts_seen |= 1 << i;
return opt;
}
/*
* Checks for required options
*/
static int
vshCommandCheckOpts(vshControl *ctl, const vshCmd *cmd, uint32_t opts_required,
uint32_t opts_seen)
{
const vshCmdDef *def = cmd->def;
int i;
opts_required &= ~opts_seen;
if (!opts_required)
return 0;
for (i = 0; def->opts[i].name; i++) {
if (opts_required & (1 << i)) {
const vshCmdOptDef *opt = &def->opts[i];
vshError(ctl,
opt->type == VSH_OT_DATA || opt->type == VSH_OT_ARGV ?
_("command '%s' requires <%s> option") :
_("command '%s' requires --%s option"),
def->name, opt->name);
}
}
return -1;
}
static const vshCmdDef *
vshCmddefSearch(const char *cmdname)
{
const vshCmdGrp *g;
const vshCmdDef *c;
for (g = cmdGroups; g->name; g++) {
for (c = g->commands; c->name; c++) {
if (STREQ(c->name, cmdname))
return c;
}
}
return NULL;
}
static const vshCmdGrp *
vshCmdGrpSearch(const char *grpname)
{
const vshCmdGrp *g;
for (g = cmdGroups; g->name; g++) {
if (STREQ(g->name, grpname) || STREQ(g->keyword, grpname))
return g;
}
return NULL;
}
static bool
vshCmdGrpHelp(vshControl *ctl, const char *grpname)
{
const vshCmdGrp *grp = vshCmdGrpSearch(grpname);
const vshCmdDef *cmd = NULL;
if (!grp) {
vshError(ctl, _("command group '%s' doesn't exist"), grpname);
return false;
} else {
vshPrint(ctl, _(" %s (help keyword '%s'):\n"), grp->name,
grp->keyword);
for (cmd = grp->commands; cmd->name; cmd++) {
vshPrint(ctl, " %-30s %s\n", cmd->name,
_(vshCmddefGetInfo(cmd, "help")));
}
}
return true;
}
static bool
vshCmddefHelp(vshControl *ctl, const char *cmdname)
{
const vshCmdDef *def = vshCmddefSearch(cmdname);
if (!def) {
vshError(ctl, _("command '%s' doesn't exist"), cmdname);
return false;
} else {
/* Don't translate desc if it is "". */
const char *desc = vshCmddefGetInfo(def, "desc");
const char *help = _(vshCmddefGetInfo(def, "help"));
char buf[256];
uint32_t opts_need_arg;
uint32_t opts_required;
bool shortopt = false; /* true if 'arg' works instead of '--opt arg' */
if (vshCmddefOptParse(def, &opts_need_arg, &opts_required)) {
vshError(ctl, _("internal error: bad options in command: '%s'"),
def->name);
return false;
}
fputs(_(" NAME\n"), stdout);
fprintf(stdout, " %s - %s\n", def->name, help);
fputs(_("\n SYNOPSIS\n"), stdout);
fprintf(stdout, " %s", def->name);
if (def->opts) {
const vshCmdOptDef *opt;
for (opt = def->opts; opt->name; opt++) {
const char *fmt = "%s";
switch (opt->type) {
case VSH_OT_BOOL:
fmt = "[--%s]";
break;
case VSH_OT_INT:
/* xgettext:c-format */
fmt = ((opt->flags & VSH_OFLAG_REQ) ? "<%s>"
: _("[--%s <number>]"));
if (!(opt->flags & VSH_OFLAG_REQ_OPT))
shortopt = true;
break;
case VSH_OT_STRING:
/* xgettext:c-format */
fmt = _("[--%s <string>]");
if (!(opt->flags & VSH_OFLAG_REQ_OPT))
shortopt = true;
break;
case VSH_OT_DATA:
fmt = ((opt->flags & VSH_OFLAG_REQ) ? "<%s>" : "[<%s>]");
if (!(opt->flags & VSH_OFLAG_REQ_OPT))
shortopt = true;
break;
case VSH_OT_ARGV:
/* xgettext:c-format */
if (shortopt) {
fmt = (opt->flags & VSH_OFLAG_REQ)
? _("{[--%s] <string>}...")
: _("[[--%s] <string>]...");
} else {
fmt = (opt->flags & VSH_OFLAG_REQ) ? _("<%s>...")
: _("[<%s>]...");
}
break;
case VSH_OT_ALIAS:
/* aliases are intentionally undocumented */
continue;
default:
assert(0);
}
fputc(' ', stdout);
fprintf(stdout, fmt, opt->name);
}
}
fputc('\n', stdout);
if (desc[0]) {
/* Print the description only if it's not empty. */
fputs(_("\n DESCRIPTION\n"), stdout);
fprintf(stdout, " %s\n", _(desc));
}
if (def->opts) {
const vshCmdOptDef *opt;
fputs(_("\n OPTIONS\n"), stdout);
for (opt = def->opts; opt->name; opt++) {
switch (opt->type) {
case VSH_OT_BOOL:
snprintf(buf, sizeof(buf), "--%s", opt->name);
break;
case VSH_OT_INT:
snprintf(buf, sizeof(buf),
(opt->flags & VSH_OFLAG_REQ) ? _("[--%s] <number>")
: _("--%s <number>"), opt->name);
break;
case VSH_OT_STRING:
/* OT_STRING should never be VSH_OFLAG_REQ */
snprintf(buf, sizeof(buf), _("--%s <string>"), opt->name);
break;
case VSH_OT_DATA:
snprintf(buf, sizeof(buf), _("[--%s] <string>"),
opt->name);
break;
case VSH_OT_ARGV:
snprintf(buf, sizeof(buf),
shortopt ? _("[--%s] <string>") : _("<%s>"),
opt->name);
break;
case VSH_OT_ALIAS:
continue;
default:
assert(0);
}
fprintf(stdout, " %-15s %s\n", buf, _(opt->help));
}
}
fputc('\n', stdout);
}
return true;
}
/* ---------------
* Utils for work with runtime commands data
* ---------------
*/
static void
vshCommandOptFree(vshCmdOpt * arg)
{
vshCmdOpt *a = arg;
while (a) {
vshCmdOpt *tmp = a;
a = a->next;
VIR_FREE(tmp->data);
VIR_FREE(tmp);
}
}
static void
vshCommandFree(vshCmd *cmd)
{
vshCmd *c = cmd;
while (c) {
vshCmd *tmp = c;
c = c->next;
if (tmp->opts)
vshCommandOptFree(tmp->opts);
VIR_FREE(tmp);
}
}
/**
* vshCommandOpt:
* @cmd: parsed command line to search
* @name: option name to search for
* @opt: result of the search
*
* Look up an option passed to CMD by NAME. Returns 1 with *OPT set
* to the option if found, 0 with *OPT set to NULL if the name is
* valid and the option is not required, -1 with *OPT set to NULL if
* the option is required but not present, and -2 if NAME is not valid
* (-2 indicates a programming error). No error messages are issued.
*/
static int
vshCommandOpt(const vshCmd *cmd, const char *name, vshCmdOpt **opt)
{
vshCmdOpt *candidate = cmd->opts;
const vshCmdOptDef *valid = cmd->def->opts;
/* See if option is present on command line. */
while (candidate) {
if (STREQ(candidate->def->name, name)) {
*opt = candidate;
return 1;
}
candidate = candidate->next;
}
/* Option not present, see if command requires it. */
*opt = NULL;
while (valid) {
if (!valid->name)
break;
if (STREQ(name, valid->name))
return (valid->flags & VSH_OFLAG_REQ) == 0 ? 0 : -1;
valid++;
}
/* If we got here, the name is unknown. */
return -2;
}
/**
* vshCommandOptInt:
* @cmd command reference
* @name option name
* @value result
*
* Convert option to int
* Return value:
* >0 if option found and valid (@value updated)
* 0 if option not found and not required (@value untouched)
* <0 in all other cases (@value untouched)
*/
static int
vshCommandOptInt(const vshCmd *cmd, const char *name, int *value)
{
vshCmdOpt *arg;
int ret;
ret = vshCommandOpt(cmd, name, &arg);
if (ret <= 0)
return ret;
if (!arg->data) {
/* only possible on bool, but if name is bool, this is a
* programming bug */
return -2;
}
if (virStrToLong_i(arg->data, NULL, 10, value) < 0)
return -1;
return 1;
}
/**
* vshCommandOptUInt:
* @cmd command reference
* @name option name
* @value result
*
* Convert option to unsigned int
* See vshCommandOptInt()
*/
static int
vshCommandOptUInt(const vshCmd *cmd, const char *name, unsigned int *value)
{
vshCmdOpt *arg;
int ret;
ret = vshCommandOpt(cmd, name, &arg);
if (ret <= 0)
return ret;
if (!arg->data) {
/* only possible on bool, but if name is bool, this is a
* programming bug */
return -2;
}
if (virStrToLong_ui(arg->data, NULL, 10, value) < 0)
return -1;
return 1;
}
/*
* vshCommandOptUL:
* @cmd command reference
* @name option name
* @value result
*
* Convert option to unsigned long
* See vshCommandOptInt()
*/
static int
vshCommandOptUL(const vshCmd *cmd, const char *name, unsigned long *value)
{
vshCmdOpt *arg;
int ret;
ret = vshCommandOpt(cmd, name, &arg);
if (ret <= 0)
return ret;
if (!arg->data) {
/* only possible on bool, but if name is bool, this is a
* programming bug */
return -2;
}
if (virStrToLong_ul(arg->data, NULL, 10, value) < 0)
return -1;
return 1;
}
/**
* vshCommandOptString:
* @cmd command reference
* @name option name
* @value result
*
* Returns option as STRING
* Return value:
* >0 if option found and valid (@value updated)
* 0 if option not found and not required (@value untouched)
* <0 in all other cases (@value untouched)
*/
static int
vshCommandOptString(const vshCmd *cmd, const char *name, const char **value)
{
vshCmdOpt *arg;
int ret;
ret = vshCommandOpt(cmd, name, &arg);
if (ret <= 0)
return ret;
if (!arg->data) {
/* only possible on bool, but if name is bool, this is a
* programming bug */
return -2;
}
if (!*arg->data && !(arg->def->flags & VSH_OFLAG_EMPTY_OK)) {
return -1;
}
*value = arg->data;
return 1;
}
/**
* vshCommandOptLongLong:
* @cmd command reference
* @name option name
* @value result
*
* Returns option as long long
* See vshCommandOptInt()
*/
static int
vshCommandOptLongLong(const vshCmd *cmd, const char *name,
long long *value)
{
vshCmdOpt *arg;
int ret;
ret = vshCommandOpt(cmd, name, &arg);
if (ret <= 0)
return ret;
if (!arg->data) {
/* only possible on bool, but if name is bool, this is a
* programming bug */
return -2;
}
if (virStrToLong_ll(arg->data, NULL, 10, value) < 0)
return -1;
return 1;
}
/**
* vshCommandOptULongLong:
* @cmd command reference
* @name option name
* @value result
*
* Returns option as long long
* See vshCommandOptInt()
*/
static int
vshCommandOptULongLong(const vshCmd *cmd, const char *name,
unsigned long long *value)
{
vshCmdOpt *arg;
int ret;
ret = vshCommandOpt(cmd, name, &arg);
if (ret <= 0)
return ret;
if (!arg->data) {
/* only possible on bool, but if name is bool, this is a
* programming bug */
return -2;
}
if (virStrToLong_ull(arg->data, NULL, 10, value) < 0)
return -1;
return 1;
}
/**
* vshCommandOptScaledInt:
* @cmd command reference
* @name option name
* @value result
* @scale default of 1 or 1024, if no suffix is present
* @max maximum value permitted
*
* Returns option as long long, scaled according to suffix
* See vshCommandOptInt()
*/
static int
vshCommandOptScaledInt(const vshCmd *cmd, const char *name,
unsigned long long *value, int scale,
unsigned long long max)
{
const char *str;
int ret;
char *end;
ret = vshCommandOptString(cmd, name, &str);
if (ret <= 0)
return ret;
if (virStrToLong_ull(str, &end, 10, value) < 0 ||
virScaleInteger(value, end, scale, max) < 0)
return -1;
return 1;
}
/**
* vshCommandOptBool:
* @cmd command reference
* @name option name
*
* Returns true/false if the option exists. Note that this does NOT
* validate whether the option is actually boolean, or even whether
* name is legal; so that this can be used to probe whether a data
* option is present without actually using that data.
*/
static bool
vshCommandOptBool(const vshCmd *cmd, const char *name)
{
vshCmdOpt *dummy;
return vshCommandOpt(cmd, name, &dummy) == 1;
}
/**
* vshCommandOptArgv:
* @cmd command reference
* @opt starting point for the search
*
* Returns the next argv argument after OPT (or the first one if OPT
* is NULL), or NULL if no more are present.
*
* Requires that a VSH_OT_ARGV option be last in the
* list of supported options in CMD->def->opts.
*/
static const vshCmdOpt *
vshCommandOptArgv(const vshCmd *cmd, const vshCmdOpt *opt)
{
opt = opt ? opt->next : cmd->opts;
while (opt) {
if (opt->def->type == VSH_OT_ARGV) {
return opt;
}
opt = opt->next;
}
return NULL;
}
/* Determine whether CMD->opts includes an option with name OPTNAME.
If not, give a diagnostic and return false.
If so, return true. */
static bool
cmd_has_option(vshControl *ctl, const vshCmd *cmd, const char *optname)
{
/* Iterate through cmd->opts, to ensure that there is an entry
with name OPTNAME and type VSH_OT_DATA. */
bool found = false;
const vshCmdOpt *opt;
for (opt = cmd->opts; opt; opt = opt->next) {
if (STREQ(opt->def->name, optname) && opt->def->type == VSH_OT_DATA) {
found = true;
break;
}
}
if (!found)
vshError(ctl, _("internal error: virsh %s: no %s VSH_OT_DATA option"),
cmd->def->name, optname);
return found;
}
static virDomainPtr
vshCommandOptDomainBy(vshControl *ctl, const vshCmd *cmd,
const char **name, int flag)
{
virDomainPtr dom = NULL;
const char *n = NULL;
int id;
const char *optname = "domain";
if (!cmd_has_option(ctl, cmd, optname))
return NULL;
if (vshCommandOptString(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 ID */
if (flag & VSH_BYID) {
if (virStrToLong_i(n, NULL, 10, &id) == 0 && id >= 0) {
vshDebug(ctl, VSH_ERR_DEBUG,
"%s: <%s> seems like domain ID\n",
cmd->def->name, optname);
dom = virDomainLookupByID(ctl->conn, id);
}
}
/* try it by UUID */
if (dom==NULL && (flag & VSH_BYUUID) && strlen(n)==VIR_UUID_STRING_BUFLEN-1) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as domain UUID\n",
cmd->def->name, optname);
dom = virDomainLookupByUUIDString(ctl->conn, n);
}
/* try it by NAME */
if (dom==NULL && (flag & VSH_BYNAME)) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as domain NAME\n",
cmd->def->name, optname);
dom = virDomainLookupByName(ctl->conn, n);
}
if (!dom)
vshError(ctl, _("failed to get domain '%s'"), n);
return dom;
}
static virNetworkPtr
vshCommandOptNetworkBy(vshControl *ctl, const vshCmd *cmd,
const char **name, int flag)
{
virNetworkPtr network = NULL;
const char *n = NULL;
const char *optname = "network";
if (!cmd_has_option(ctl, cmd, optname))
return NULL;
if (vshCommandOptString(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 ((flag & VSH_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(ctl->conn, n);
}
/* try it by NAME */
if (network==NULL && (flag & VSH_BYNAME)) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as network NAME\n",
cmd->def->name, optname);
network = virNetworkLookupByName(ctl->conn, n);
}
if (!network)
vshError(ctl, _("failed to get network '%s'"), n);
return network;
}
static virNWFilterPtr
vshCommandOptNWFilterBy(vshControl *ctl, const vshCmd *cmd,
const char **name, int flag)
{
virNWFilterPtr nwfilter = NULL;
const char *n = NULL;
const char *optname = "nwfilter";
if (!cmd_has_option(ctl, cmd, optname))
return NULL;
if (vshCommandOptString(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 ((flag & VSH_BYUUID) && strlen(n) == VIR_UUID_STRING_BUFLEN-1) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as nwfilter UUID\n",
cmd->def->name, optname);
nwfilter = virNWFilterLookupByUUIDString(ctl->conn, n);
}
/* try it by NAME */
if (nwfilter == NULL && (flag & VSH_BYNAME)) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as nwfilter NAME\n",
cmd->def->name, optname);
nwfilter = virNWFilterLookupByName(ctl->conn, n);
}
if (!nwfilter)
vshError(ctl, _("failed to get nwfilter '%s'"), n);
return nwfilter;
}
static virInterfacePtr
vshCommandOptInterfaceBy(vshControl *ctl, const vshCmd *cmd,
const char *optname,
const char **name, int flag)
{
virInterfacePtr iface = NULL;
const char *n = NULL;
if (!optname)
optname = "interface";
if (!cmd_has_option(ctl, cmd, optname))
return NULL;
if (vshCommandOptString(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 NAME */
if (flag & VSH_BYNAME) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as interface NAME\n",
cmd->def->name, optname);
iface = virInterfaceLookupByName(ctl->conn, n);
}
/* try it by MAC */
if (iface == NULL && (flag & VSH_BYMAC)) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as interface MAC\n",
cmd->def->name, optname);
iface = virInterfaceLookupByMACString(ctl->conn, n);
}
if (!iface)
vshError(ctl, _("failed to get interface '%s'"), n);
return iface;
}
static virStoragePoolPtr
vshCommandOptPoolBy(vshControl *ctl, const vshCmd *cmd, const char *optname,
const char **name, int flag)
{
virStoragePoolPtr pool = NULL;
const char *n = NULL;
if (vshCommandOptString(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 ((flag & VSH_BYUUID) && strlen(n) == VIR_UUID_STRING_BUFLEN-1) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as pool UUID\n",
cmd->def->name, optname);
pool = virStoragePoolLookupByUUIDString(ctl->conn, n);
}
/* try it by NAME */
if (pool == NULL && (flag & VSH_BYNAME)) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as pool NAME\n",
cmd->def->name, optname);
pool = virStoragePoolLookupByName(ctl->conn, n);
}
if (!pool)
vshError(ctl, _("failed to get pool '%s'"), n);
return pool;
}
static virStorageVolPtr
vshCommandOptVolBy(vshControl *ctl, const vshCmd *cmd,
const char *optname,
const char *pooloptname,
const char **name, int flag)
{
virStorageVolPtr vol = NULL;
virStoragePoolPtr pool = NULL;
const char *n = NULL, *p = NULL;
if (vshCommandOptString(cmd, optname, &n) <= 0)
return NULL;
if (pooloptname != NULL && vshCommandOptString(cmd, pooloptname, &p) < 0) {
vshError(ctl, "%s", _("missing option"));
return NULL;
}
if (p)
pool = vshCommandOptPoolBy(ctl, cmd, pooloptname, name, flag);
vshDebug(ctl, VSH_ERR_DEBUG, "%s: found option <%s>: %s\n",
cmd->def->name, optname, n);
if (name)
*name = n;
/* try it by name */
if (pool && (flag & VSH_BYNAME)) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as vol name\n",
cmd->def->name, optname);
vol = virStorageVolLookupByName(pool, n);
}
/* try it by key */
if (vol == NULL && (flag & VSH_BYUUID)) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as vol key\n",
cmd->def->name, optname);
vol = virStorageVolLookupByKey(ctl->conn, n);
}
/* try it by path */
if (vol == NULL && (flag & VSH_BYUUID)) {
vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as vol path\n",
cmd->def->name, optname);
vol = virStorageVolLookupByPath(ctl->conn, n);
}
if (!vol) {
if (pool)
vshError(ctl, _("failed to get vol '%s'"), n);
else
vshError(ctl, _("failed to get vol '%s', specifying --%s "
"might help"), n, pooloptname);
}
if (pool)
virStoragePoolFree(pool);
return vol;
}
static virSecretPtr
vshCommandOptSecret(vshControl *ctl, const vshCmd *cmd, const char **name)
{
virSecretPtr secret = NULL;
const char *n = NULL;
const char *optname = "secret";
if (!cmd_has_option(ctl, cmd, optname))
return NULL;
if (vshCommandOptString(cmd, optname, &n) <= 0)
return NULL;
vshDebug(ctl, VSH_ERR_DEBUG,
"%s: found option <%s>: %s\n", cmd->def->name, optname, n);
if (name != NULL)
*name = n;
secret = virSecretLookupByUUIDString(ctl->conn, n);
if (secret == NULL)
vshError(ctl, _("failed to get secret '%s'"), n);
return secret;
}
/*
* Executes command(s) and returns return code from last command
*/
static bool
vshCommandRun(vshControl *ctl, const vshCmd *cmd)
{
bool ret = true;
while (cmd) {
struct timeval before, after;
bool enable_timing = ctl->timing;
if ((ctl->conn == NULL || disconnected) &&
!(cmd->def->flags & VSH_CMD_FLAG_NOCONNECT))
vshReconnect(ctl);
if (enable_timing)
GETTIMEOFDAY(&before);
ret = cmd->def->handler(ctl, cmd);
if (enable_timing)
GETTIMEOFDAY(&after);
/* try to automatically catch disconnections */
if (!ret &&
((last_error != NULL) &&
(((last_error->code == VIR_ERR_SYSTEM_ERROR) &&
(last_error->domain == VIR_FROM_REMOTE)) ||
(last_error->code == VIR_ERR_RPC) ||
(last_error->code == VIR_ERR_NO_CONNECT) ||
(last_error->code == VIR_ERR_INVALID_CONN))))
disconnected++;
if (!ret)
virshReportError(ctl);
if (!ret && disconnected != 0)
vshReconnect(ctl);
if (STREQ(cmd->def->name, "quit")) /* hack ... */
return ret;
if (enable_timing)
vshPrint(ctl, _("\n(Time: %.3f ms)\n\n"),
DIFF_MSEC(&after, &before));
else
vshPrintExtra(ctl, "\n");
cmd = cmd->next;
}
return ret;
}
/* ---------------
* Command parsing
* ---------------
*/
typedef enum {
VSH_TK_ERROR, /* Failed to parse a token */
VSH_TK_ARG, /* Arbitrary argument, might be option or empty */
VSH_TK_SUBCMD_END, /* Separation between commands */
VSH_TK_END /* No more commands */
} vshCommandToken;
typedef struct __vshCommandParser {
vshCommandToken(*getNextArg)(vshControl *, struct __vshCommandParser *,
char **);
/* vshCommandStringGetArg() */
char *pos;
/* vshCommandArgvGetArg() */
char **arg_pos;
char **arg_end;
} vshCommandParser;
static bool
vshCommandParse(vshControl *ctl, vshCommandParser *parser)
{
char *tkdata = NULL;
vshCmd *clast = NULL;
vshCmdOpt *first = NULL;
if (ctl->cmd) {
vshCommandFree(ctl->cmd);
ctl->cmd = NULL;
}
while (1) {
vshCmdOpt *last = NULL;
const vshCmdDef *cmd = NULL;
vshCommandToken tk;
bool data_only = false;
uint32_t opts_need_arg = 0;
uint32_t opts_required = 0;
uint32_t opts_seen = 0;
first = NULL;
while (1) {
const vshCmdOptDef *opt = NULL;
tkdata = NULL;
tk = parser->getNextArg(ctl, parser, &tkdata);
if (tk == VSH_TK_ERROR)
goto syntaxError;
if (tk != VSH_TK_ARG) {
VIR_FREE(tkdata);
break;
}
if (cmd == NULL) {
/* first token must be command name */
if (!(cmd = vshCmddefSearch(tkdata))) {
vshError(ctl, _("unknown command: '%s'"), tkdata);
goto syntaxError; /* ... or ignore this command only? */
}
if (vshCmddefOptParse(cmd, &opts_need_arg,
&opts_required) < 0) {
vshError(ctl,
_("internal error: bad options in command: '%s'"),
tkdata);
goto syntaxError;
}
VIR_FREE(tkdata);
} else if (data_only) {
goto get_data;
} else if (tkdata[0] == '-' && tkdata[1] == '-' &&
c_isalnum(tkdata[2])) {
char *optstr = strchr(tkdata + 2, '=');
int opt_index;
if (optstr) {
*optstr = '\0'; /* convert the '=' to '\0' */
optstr = vshStrdup(ctl, optstr + 1);
}
if (!(opt = vshCmddefGetOption(ctl, cmd, tkdata + 2,
&opts_seen, &opt_index))) {
VIR_FREE(optstr);
goto syntaxError;
}
VIR_FREE(tkdata);
if (opt->type != VSH_OT_BOOL) {
/* option data */
if (optstr)
tkdata = optstr;
else
tk = parser->getNextArg(ctl, parser, &tkdata);
if (tk == VSH_TK_ERROR)
goto syntaxError;
if (tk != VSH_TK_ARG) {
vshError(ctl,
_("expected syntax: --%s <%s>"),
opt->name,
opt->type ==
VSH_OT_INT ? _("number") : _("string"));
goto syntaxError;
}
if (opt->type != VSH_OT_ARGV)
opts_need_arg &= ~(1 << opt_index);
} else {
tkdata = NULL;
if (optstr) {
vshError(ctl, _("invalid '=' after option --%s"),
opt->name);
VIR_FREE(optstr);
goto syntaxError;
}
}
} else if (tkdata[0] == '-' && tkdata[1] == '-' &&
tkdata[2] == '\0') {
data_only = true;
continue;
} else {
get_data:
if (!(opt = vshCmddefGetData(cmd, &opts_need_arg,
&opts_seen))) {
vshError(ctl, _("unexpected data '%s'"), tkdata);
goto syntaxError;
}
}
if (opt) {
/* save option */
vshCmdOpt *arg = vshMalloc(ctl, sizeof(vshCmdOpt));
arg->def = opt;
arg->data = tkdata;
arg->next = NULL;
tkdata = NULL;
if (!first)
first = arg;
if (last)
last->next = arg;
last = arg;
vshDebug(ctl, VSH_ERR_INFO, "%s: %s(%s): %s\n",
cmd->name,
opt->name,
opt->type != VSH_OT_BOOL ? _("optdata") : _("bool"),
opt->type != VSH_OT_BOOL ? arg->data : _("(none)"));
}
}
/* command parsed -- allocate new struct for the command */
if (cmd) {
vshCmd *c = vshMalloc(ctl, sizeof(vshCmd));
c->opts = first;
c->def = cmd;
c->next = NULL;
if (vshCommandCheckOpts(ctl, c, opts_required, opts_seen) < 0) {
VIR_FREE(c);
goto syntaxError;
}
if (!ctl->cmd)
ctl->cmd = c;
if (clast)
clast->next = c;
clast = c;
}
if (tk == VSH_TK_END)
break;
}
return true;
syntaxError:
if (ctl->cmd) {
vshCommandFree(ctl->cmd);
ctl->cmd = NULL;
}
if (first)
vshCommandOptFree(first);
VIR_FREE(tkdata);
return false;
}
/* --------------------
* Command argv parsing
* --------------------
*/
static vshCommandToken ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
vshCommandArgvGetArg(vshControl *ctl, vshCommandParser *parser, char **res)
{
if (parser->arg_pos == parser->arg_end) {
*res = NULL;
return VSH_TK_END;
}
*res = vshStrdup(ctl, *parser->arg_pos);
parser->arg_pos++;
return VSH_TK_ARG;
}
static bool
vshCommandArgvParse(vshControl *ctl, int nargs, char **argv)
{
vshCommandParser parser;
if (nargs <= 0)
return false;
parser.arg_pos = argv;
parser.arg_end = argv + nargs;
parser.getNextArg = vshCommandArgvGetArg;
return vshCommandParse(ctl, &parser);
}
/* ----------------------
* Command string parsing
* ----------------------
*/
static vshCommandToken ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
vshCommandStringGetArg(vshControl *ctl, vshCommandParser *parser, char **res)
{
bool single_quote = false;
bool double_quote = false;
int sz = 0;
char *p = parser->pos;
char *q = vshStrdup(ctl, p);
*res = q;
while (*p && (*p == ' ' || *p == '\t'))
p++;
if (*p == '\0')
return VSH_TK_END;
if (*p == ';') {
parser->pos = ++p; /* = \0 or begin of next command */
return VSH_TK_SUBCMD_END;
}
while (*p) {
/* end of token is blank space or ';' */
if (!double_quote && !single_quote &&
(*p == ' ' || *p == '\t' || *p == ';'))
break;
if (!double_quote && *p == '\'') { /* single quote */
single_quote = !single_quote;
p++;
continue;
} else if (!single_quote && *p == '\\') { /* escape */
/*
* The same as the bash, a \ in "" is an escaper,
* but a \ in '' is not an escaper.
*/
p++;
if (*p == '\0') {
vshError(ctl, "%s", _("dangling \\"));
return VSH_TK_ERROR;
}
} else if (!single_quote && *p == '"') { /* double quote */
double_quote = !double_quote;
p++;
continue;
}
*q++ = *p++;
sz++;
}
if (double_quote) {
vshError(ctl, "%s", _("missing \""));
return VSH_TK_ERROR;
}
*q = '\0';
parser->pos = p;
return VSH_TK_ARG;
}
static bool
vshCommandStringParse(vshControl *ctl, char *cmdstr)
{
vshCommandParser parser;
if (cmdstr == NULL || *cmdstr == '\0')
return false;
parser.pos = cmdstr;
parser.getNextArg = vshCommandStringGetArg;
return vshCommandParse(ctl, &parser);
}
/* ---------------
* Misc utils
* ---------------
*/
static int
vshDomainState(vshControl *ctl, virDomainPtr dom, int *reason)
{
virDomainInfo info;
if (reason)
*reason = -1;
if (!ctl->useGetInfo) {
int state;
if (virDomainGetState(dom, &state, reason, 0) < 0) {
virErrorPtr err = virGetLastError();
if (err && err->code == VIR_ERR_NO_SUPPORT)
ctl->useGetInfo = true;
else
return -1;
} else {
return state;
}
}
/* fall back to virDomainGetInfo if virDomainGetState is not supported */
if (virDomainGetInfo(dom, &info) < 0)
return -1;
else
return info.state;
}
static const char *
vshDomainStateToString(int state)
{
/* Can't use virDomainStateTypeToString, because we want to mark
* strings for translation. */
switch ((virDomainState) state) {
case VIR_DOMAIN_RUNNING:
return N_("running");
case VIR_DOMAIN_BLOCKED:
return N_("idle");
case VIR_DOMAIN_PAUSED:
return N_("paused");
case VIR_DOMAIN_SHUTDOWN:
return N_("in shutdown");
case VIR_DOMAIN_SHUTOFF:
return N_("shut off");
case VIR_DOMAIN_CRASHED:
return N_("crashed");
case VIR_DOMAIN_PMSUSPENDED:
return N_("pmsuspended");
case VIR_DOMAIN_NOSTATE:
default:
;/*FALLTHROUGH*/
}
return N_("no state"); /* = dom0 state */
}
static const char *
vshDomainStateReasonToString(int state, int reason)
{
switch ((virDomainState) state) {
case VIR_DOMAIN_NOSTATE:
switch ((virDomainNostateReason) reason) {
case VIR_DOMAIN_NOSTATE_UNKNOWN:
case VIR_DOMAIN_NOSTATE_LAST:
;
}
break;
case VIR_DOMAIN_RUNNING:
switch ((virDomainRunningReason) reason) {
case VIR_DOMAIN_RUNNING_BOOTED:
return N_("booted");
case VIR_DOMAIN_RUNNING_MIGRATED:
return N_("migrated");
case VIR_DOMAIN_RUNNING_RESTORED:
return N_("restored");
case VIR_DOMAIN_RUNNING_FROM_SNAPSHOT:
return N_("from snapshot");
case VIR_DOMAIN_RUNNING_UNPAUSED:
return N_("unpaused");
case VIR_DOMAIN_RUNNING_MIGRATION_CANCELED:
return N_("migration canceled");
case VIR_DOMAIN_RUNNING_SAVE_CANCELED:
return N_("save canceled");
case VIR_DOMAIN_RUNNING_WAKEUP:
return N_("event wakeup");
case VIR_DOMAIN_RUNNING_UNKNOWN:
case VIR_DOMAIN_RUNNING_LAST:
;
}
break;
case VIR_DOMAIN_BLOCKED:
switch ((virDomainBlockedReason) reason) {
case VIR_DOMAIN_BLOCKED_UNKNOWN:
case VIR_DOMAIN_BLOCKED_LAST:
;
}
break;
case VIR_DOMAIN_PAUSED:
switch ((virDomainPausedReason) reason) {
case VIR_DOMAIN_PAUSED_USER:
return N_("user");
case VIR_DOMAIN_PAUSED_MIGRATION:
return N_("migrating");
case VIR_DOMAIN_PAUSED_SAVE:
return N_("saving");
case VIR_DOMAIN_PAUSED_DUMP:
return N_("dumping");
case VIR_DOMAIN_PAUSED_IOERROR:
return N_("I/O error");
case VIR_DOMAIN_PAUSED_WATCHDOG:
return N_("watchdog");
case VIR_DOMAIN_PAUSED_FROM_SNAPSHOT:
return N_("from snapshot");
case VIR_DOMAIN_PAUSED_SHUTTING_DOWN:
return N_("shutting down");
case VIR_DOMAIN_PAUSED_UNKNOWN:
case VIR_DOMAIN_PAUSED_LAST:
;
}
break;
case VIR_DOMAIN_SHUTDOWN:
switch ((virDomainShutdownReason) reason) {
case VIR_DOMAIN_SHUTDOWN_USER:
return N_("user");
case VIR_DOMAIN_SHUTDOWN_UNKNOWN:
case VIR_DOMAIN_SHUTDOWN_LAST:
;
}
break;
case VIR_DOMAIN_SHUTOFF:
switch ((virDomainShutoffReason) reason) {
case VIR_DOMAIN_SHUTOFF_SHUTDOWN:
return N_("shutdown");
case VIR_DOMAIN_SHUTOFF_DESTROYED:
return N_("destroyed");
case VIR_DOMAIN_SHUTOFF_CRASHED:
return N_("crashed");
case VIR_DOMAIN_SHUTOFF_MIGRATED:
return N_("migrated");
case VIR_DOMAIN_SHUTOFF_SAVED:
return N_("saved");
case VIR_DOMAIN_SHUTOFF_FAILED:
return N_("failed");
case VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT:
return N_("from snapshot");
case VIR_DOMAIN_SHUTOFF_UNKNOWN:
case VIR_DOMAIN_SHUTOFF_LAST:
;
}
break;
case VIR_DOMAIN_CRASHED:
switch ((virDomainCrashedReason) reason) {
case VIR_DOMAIN_CRASHED_UNKNOWN:
case VIR_DOMAIN_CRASHED_LAST:
;
}
break;
case VIR_DOMAIN_PMSUSPENDED:
switch ((virDomainPMSuspendedReason) reason) {
case VIR_DOMAIN_PMSUSPENDED_UNKNOWN:
case VIR_DOMAIN_PMSUSPENDED_LAST:
;
}
break;
case VIR_DOMAIN_LAST:
;
}
return N_("unknown");
}
/* extract description or title from domain xml */
static char *
vshGetDomainDescription(vshControl *ctl, virDomainPtr dom, bool title,
unsigned int flags)
{
char *desc = NULL;
char *domxml = NULL;
virErrorPtr err = NULL;
xmlDocPtr doc = NULL;
xmlXPathContextPtr ctxt = NULL;
int type;
if (title)
type = VIR_DOMAIN_METADATA_TITLE;
else
type = VIR_DOMAIN_METADATA_DESCRIPTION;
if ((desc = virDomainGetMetadata(dom, type, NULL, flags))) {
return desc;
} else {
err = virGetLastError();
if (err && err->code == VIR_ERR_NO_DOMAIN_METADATA) {
desc = vshStrdup(ctl, "");
virResetLastError();
return desc;
}
if (err && err->code != VIR_ERR_NO_SUPPORT)
return desc;
}
/* fall back to xml */
/* get domain's xml description and extract the title/description */
if (!(domxml = virDomainGetXMLDesc(dom, flags))) {
vshError(ctl, "%s", _("Failed to retrieve domain XML"));
goto cleanup;
}
doc = virXMLParseStringCtxt(domxml, _("(domain_definition)"), &ctxt);
if (!doc) {
vshError(ctl, "%s", _("Couldn't parse domain XML"));
goto cleanup;
}
if (title)
desc = virXPathString("string(./title[1])", ctxt);
else
desc = virXPathString("string(./description[1])", ctxt);
if (!desc)
desc = vshStrdup(ctl, "");
cleanup:
VIR_FREE(domxml);
xmlXPathFreeContext(ctxt);
xmlFreeDoc(doc);
return desc;
}
/* Return a non-NULL string representation of a typed parameter; exit
* if we are out of memory. */
static char *
vshGetTypedParamValue(vshControl *ctl, virTypedParameterPtr item)
{
int ret = 0;
char *str = NULL;
switch(item->type) {
case VIR_TYPED_PARAM_INT:
ret = virAsprintf(&str, "%d", item->value.i);
break;
case VIR_TYPED_PARAM_UINT:
ret = virAsprintf(&str, "%u", item->value.ui);
break;
case VIR_TYPED_PARAM_LLONG:
ret = virAsprintf(&str, "%lld", item->value.l);
break;
case VIR_TYPED_PARAM_ULLONG:
ret = virAsprintf(&str, "%llu", item->value.ul);
break;
case VIR_TYPED_PARAM_DOUBLE:
ret = virAsprintf(&str, "%f", item->value.d);
break;
case VIR_TYPED_PARAM_BOOLEAN:
ret = virAsprintf(&str, "%s", item->value.b ? _("yes") : _("no"));
break;
case VIR_TYPED_PARAM_STRING:
str = vshStrdup(ctl, item->value.s);
break;
default:
vshError(ctl, _("unimplemented parameter type %d"), item->type);
}
if (ret < 0) {
vshError(ctl, "%s", _("Out of memory"));
exit(EXIT_FAILURE);
}
return str;
}
static virTypedParameterPtr
vshFindTypedParamByName(const char *name, virTypedParameterPtr list, int count)
{
int i = count;
virTypedParameterPtr found = list;
if (!list || !name)
return NULL;
while (i-- > 0) {
if (STREQ(name, found->field))
return found;
found++; /* go to next struct in array */
}
/* not found */
return NULL;
}
static const char *
vshDomainControlStateToString(int state)
{
switch ((virDomainControlState) state) {
case VIR_DOMAIN_CONTROL_OK:
return N_("ok");
case VIR_DOMAIN_CONTROL_JOB:
return N_("background job");
case VIR_DOMAIN_CONTROL_OCCUPIED:
return N_("occupied");
case VIR_DOMAIN_CONTROL_ERROR:
return N_("error");
default:
;
}
return N_("unknown");
}
static const char *
vshDomainVcpuStateToString(int state)
{
switch (state) {
case VIR_VCPU_OFFLINE:
return N_("offline");
case VIR_VCPU_BLOCKED:
return N_("idle");
case VIR_VCPU_RUNNING:
return N_("running");
default:
;/*FALLTHROUGH*/
}
return N_("no state");
}
static bool
vshConnectionUsability(vshControl *ctl, virConnectPtr conn)
{
/* TODO: use something like virConnectionState() to
* check usability of the connection
*/
if (!conn) {
vshError(ctl, "%s", _("no valid connection"));
return false;
}
return true;
}
static void
vshDebug(vshControl *ctl, int level, const char *format, ...)
{
va_list ap;
char *str;
/* Aligning log levels to that of libvirt.
* Traces with levels >= user-specified-level
* gets logged into file
*/
if (level < ctl->debug)
return;
va_start(ap, format);
vshOutputLogFile(ctl, level, format, ap);
va_end(ap);
va_start(ap, format);
if (virVasprintf(&str, format, ap) < 0) {
/* Skip debug messages on low memory */
va_end(ap);
return;
}
va_end(ap);
fputs(str, stdout);
VIR_FREE(str);
}
static void
vshPrintExtra(vshControl *ctl, const char *format, ...)
{
va_list ap;
char *str;
if (ctl && ctl->quiet)
return;
va_start(ap, format);
if (virVasprintf(&str, format, ap) < 0) {
vshError(ctl, "%s", _("Out of memory"));
va_end(ap);
return;
}
va_end(ap);
fputs(str, stdout);
VIR_FREE(str);
}
static void
vshError(vshControl *ctl, const char *format, ...)
{
va_list ap;
char *str;
if (ctl != NULL) {
va_start(ap, format);
vshOutputLogFile(ctl, VSH_ERR_ERROR, format, ap);
va_end(ap);
}
/* Most output is to stdout, but if someone ran virsh 2>&1, then
* printing to stderr will not interleave correctly with stdout
* unless we flush between every transition between streams. */
fflush(stdout);
fputs(_("error: "), stderr);
va_start(ap, format);
/* We can't recursively call vshError on an OOM situation, so ignore
failure here. */
ignore_value(virVasprintf(&str, format, ap));
va_end(ap);
fprintf(stderr, "%s\n", NULLSTR(str));
fflush(stderr);
VIR_FREE(str);
}
static void
vshEventLoop(void *opaque)
{
vshControl *ctl = opaque;
while (1) {
bool quit;
virMutexLock(&ctl->lock);
quit = ctl->quit;
virMutexUnlock(&ctl->lock);
if (quit)
break;
if (virEventRunDefaultImpl() < 0)
virshReportError(ctl);
}
}
/*
* Initialize connection.
*/
static bool
vshInit(vshControl *ctl)
{
char *debugEnv;
if (ctl->conn)
return false;
if (ctl->debug == VSH_DEBUG_DEFAULT) {
/* log level not set from commandline, check env variable */
debugEnv = getenv("VIRSH_DEBUG");
if (debugEnv) {
int debug;
if (virStrToLong_i(debugEnv, NULL, 10, &debug) < 0 ||
debug < VSH_ERR_DEBUG || debug > VSH_ERR_ERROR) {
vshError(ctl, "%s",
_("VIRSH_DEBUG not set with a valid numeric value"));
} else {
ctl->debug = debug;
}
}
}
if (ctl->logfile == NULL) {
/* log file not set from cmdline */
debugEnv = getenv("VIRSH_LOG_FILE");
if (debugEnv && *debugEnv) {
ctl->logfile = vshStrdup(ctl, debugEnv);
}
}
vshOpenLogFile(ctl);
/* set up the library error handler */
virSetErrorFunc(NULL, virshErrorHandler);
/* set up the signals handlers to catch disconnections */
vshSetupSignals();
if (virEventRegisterDefaultImpl() < 0)
return false;
if (virThreadCreate(&ctl->eventLoop, true, vshEventLoop, ctl) < 0)
return false;
ctl->eventLoopStarted = true;
if (ctl->name) {
ctl->conn = virConnectOpenAuth(ctl->name,
virConnectAuthPtrDefault,
ctl->readonly ? VIR_CONNECT_RO : 0);
/* Connecting to a named connection must succeed, but we delay
* connecting to the default connection until we need it
* (since the first command might be 'connect' which allows a
* non-default connection, or might be 'help' which needs no
* connection).
*/
if (!ctl->conn) {
virshReportError(ctl);
vshError(ctl, "%s", _("failed to connect to the hypervisor"));
return false;
}
}
return true;
}
#define LOGFILE_FLAGS (O_WRONLY | O_APPEND | O_CREAT | O_SYNC)
/**
* vshOpenLogFile:
*
* Open log file.
*/
static void
vshOpenLogFile(vshControl *ctl)
{
struct stat st;
if (ctl->logfile == NULL)
return;
/* check log file */
if (stat(ctl->logfile, &st) == -1) {
switch (errno) {
case ENOENT:
break;
default:
vshError(ctl, "%s",
_("failed to get the log file information"));
exit(EXIT_FAILURE);
}
} else {
if (!S_ISREG(st.st_mode)) {
vshError(ctl, "%s", _("the log path is not a file"));
exit(EXIT_FAILURE);
}
}
/* log file open */
if ((ctl->log_fd = open(ctl->logfile, LOGFILE_FLAGS, FILE_MODE)) < 0) {
vshError(ctl, "%s",
_("failed to open the log file. check the log file path"));
exit(EXIT_FAILURE);
}
}
/**
* vshOutputLogFile:
*
* Outputting an error to log file.
*/
static void
vshOutputLogFile(vshControl *ctl, int log_level, const char *msg_format,
va_list ap)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
char *str;
size_t len;
const char *lvl = "";
struct timeval stTimeval;
struct tm *stTm;
if (ctl->log_fd == -1)
return;
/**
* create log format
*
* [YYYY.MM.DD HH:MM:SS SIGNATURE PID] LOG_LEVEL message
*/
gettimeofday(&stTimeval, NULL);
stTm = localtime(&stTimeval.tv_sec);
virBufferAsprintf(&buf, "[%d.%02d.%02d %02d:%02d:%02d %s %d] ",
(1900 + stTm->tm_year),
(1 + stTm->tm_mon),
stTm->tm_mday,
stTm->tm_hour,
stTm->tm_min,
stTm->tm_sec,
SIGN_NAME,
(int) getpid());
switch (log_level) {
case VSH_ERR_DEBUG:
lvl = LVL_DEBUG;
break;
case VSH_ERR_INFO:
lvl = LVL_INFO;
break;
case VSH_ERR_NOTICE:
lvl = LVL_INFO;
break;
case VSH_ERR_WARNING:
lvl = LVL_WARNING;
break;
case VSH_ERR_ERROR:
lvl = LVL_ERROR;
break;
default:
lvl = LVL_DEBUG;
break;
}
virBufferAsprintf(&buf, "%s ", lvl);
virBufferVasprintf(&buf, msg_format, ap);
virBufferAddChar(&buf, '\n');
if (virBufferError(&buf))
goto error;
str = virBufferContentAndReset(&buf);
len = strlen(str);
if (len > 1 && str[len - 2] == '\n') {
str[len - 1] = '\0';
len--;
}
/* write log */
if (safewrite(ctl->log_fd, str, len) < 0)
goto error;
return;
error:
vshCloseLogFile(ctl);
vshError(ctl, "%s", _("failed to write the log file"));
virBufferFreeAndReset(&buf);
VIR_FREE(str);
}
/**
* vshCloseLogFile:
*
* Close log file.
*/
static void
vshCloseLogFile(vshControl *ctl)
{
/* log file close */
if (VIR_CLOSE(ctl->log_fd) < 0) {
vshError(ctl, _("%s: failed to write log file: %s"),
ctl->logfile ? ctl->logfile : "?", strerror (errno));
}
if (ctl->logfile) {
VIR_FREE(ctl->logfile);
ctl->logfile = NULL;
}
}
#ifdef USE_READLINE
/* -----------------
* Readline stuff
* -----------------
*/
/*
* Generator function for command completion. STATE lets us
* know whether to start from scratch; without any state
* (i.e. STATE == 0), then we start at the top of the list.
*/
static char *
vshReadlineCommandGenerator(const char *text, int state)
{
static int grp_list_index, cmd_list_index, len;
const char *name;
const vshCmdGrp *grp;
const vshCmdDef *cmds;
if (!state) {
grp_list_index = 0;
cmd_list_index = 0;
len = strlen(text);
}
grp = cmdGroups;
/* Return the next name which partially matches from the
* command list.
*/
while (grp[grp_list_index].name) {
cmds = grp[grp_list_index].commands;
if (cmds[cmd_list_index].name) {
while ((name = cmds[cmd_list_index].name)) {
cmd_list_index++;
if (STREQLEN(name, text, len))
return vshStrdup(NULL, name);
}
} else {
cmd_list_index = 0;
grp_list_index++;
}
}
/* If no names matched, then return NULL. */
return NULL;
}
static char *
vshReadlineOptionsGenerator(const char *text, int state)
{
static int list_index, len;
static const vshCmdDef *cmd = NULL;
const char *name;
if (!state) {
/* determine command name */
char *p;
char *cmdname;
if (!(p = strchr(rl_line_buffer, ' ')))
return NULL;
cmdname = vshCalloc(NULL, (p - rl_line_buffer) + 1, 1);
memcpy(cmdname, rl_line_buffer, p - rl_line_buffer);
cmd = vshCmddefSearch(cmdname);
list_index = 0;
len = strlen(text);
VIR_FREE(cmdname);
}
if (!cmd)
return NULL;
if (!cmd->opts)
return NULL;
while ((name = cmd->opts[list_index].name)) {
const vshCmdOptDef *opt = &cmd->opts[list_index];
char *res;
list_index++;
if (opt->type == VSH_OT_DATA || opt->type == VSH_OT_ARGV)
/* ignore non --option */
continue;
if (len > 2) {
if (STRNEQLEN(name, text + 2, len - 2))
continue;
}
res = vshMalloc(NULL, strlen(name) + 3);
snprintf(res, strlen(name) + 3, "--%s", name);
return res;
}
/* If no names matched, then return NULL. */
return NULL;
}
static char **
vshReadlineCompletion(const char *text, int start,
int end ATTRIBUTE_UNUSED)
{
char **matches = (char **) NULL;
if (start == 0)
/* command name generator */
matches = rl_completion_matches(text, vshReadlineCommandGenerator);
else
/* commands options */
matches = rl_completion_matches(text, vshReadlineOptionsGenerator);
return matches;
}
static int
vshReadlineInit(vshControl *ctl)
{
char *userdir = NULL;
/* Allow conditional parsing of the ~/.inputrc file. */
rl_readline_name = "virsh";
/* Tell the completer that we want a crack first. */
rl_attempted_completion_function = vshReadlineCompletion;
/* Limit the total size of the history buffer */
stifle_history(500);
/* Prepare to read/write history from/to the $XDG_CACHE_HOME/virsh/history file */
userdir = virGetUserCacheDirectory();
if (userdir == NULL) {
vshError(ctl, "%s", _("Could not determine home directory"));
return -1;
}
if (virAsprintf(&ctl->historydir, "%s/virsh", userdir) < 0) {
vshError(ctl, "%s", _("Out of memory"));
VIR_FREE(userdir);
return -1;
}
if (virAsprintf(&ctl->historyfile, "%s/history", ctl->historydir) < 0) {
vshError(ctl, "%s", _("Out of memory"));
VIR_FREE(userdir);
return -1;
}
VIR_FREE(userdir);
read_history(ctl->historyfile);
return 0;
}
static void
vshReadlineDeinit(vshControl *ctl)
{
if (ctl->historyfile != NULL) {
if (virFileMakePathWithMode(ctl->historydir, 0755) < 0 &&
errno != EEXIST) {
char ebuf[1024];
vshError(ctl, _("Failed to create '%s': %s"),
ctl->historydir, virStrerror(errno, ebuf, sizeof(ebuf)));
} else {
write_history(ctl->historyfile);
}
}
VIR_FREE(ctl->historydir);
VIR_FREE(ctl->historyfile);
}
static char *
vshReadline(vshControl *ctl ATTRIBUTE_UNUSED, const char *prompt)
{
return readline(prompt);
}
#else /* !USE_READLINE */
static int
vshReadlineInit(vshControl *ctl ATTRIBUTE_UNUSED)
{
/* empty */
return 0;
}
static void
vshReadlineDeinit(vshControl *ctl ATTRIBUTE_UNUSED)
{
/* empty */
}
static char *
vshReadline(vshControl *ctl, const char *prompt)
{
char line[1024];
char *r;
int len;
fputs(prompt, stdout);
r = fgets(line, sizeof(line), stdin);
if (r == NULL) return NULL; /* EOF */
/* Chomp trailing \n */
len = strlen(r);
if (len > 0 && r[len-1] == '\n')
r[len-1] = '\0';
return vshStrdup(ctl, r);
}
#endif /* !USE_READLINE */
static void
vshDeinitTimer(int timer ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED)
{
/* nothing to be done here */
}
/*
* Deinitialize virsh
*/
static bool
vshDeinit(vshControl *ctl)
{
vshReadlineDeinit(ctl);
vshCloseLogFile(ctl);
VIR_FREE(ctl->name);
if (ctl->conn) {
int ret;
if ((ret = virConnectClose(ctl->conn)) != 0) {
vshError(ctl, _("Failed to disconnect from the hypervisor, %d leaked reference(s)"), ret);
}
}
virResetLastError();
if (ctl->eventLoopStarted) {
int timer;
virMutexLock(&ctl->lock);
ctl->quit = true;
/* HACK: Add a dummy timeout to break event loop */
timer = virEventAddTimeout(0, vshDeinitTimer, NULL, NULL);
virMutexUnlock(&ctl->lock);
virThreadJoin(&ctl->eventLoop);
if (timer != -1)
virEventRemoveTimeout(timer);
ctl->eventLoopStarted = false;
}
virMutexDestroy(&ctl->lock);
return true;
}
/*
* Print usage
*/
static void
vshUsage(void)
{
const vshCmdGrp *grp;
const vshCmdDef *cmd;
fprintf(stdout, _("\n%s [options]... [<command_string>]"
"\n%s [options]... <command> [args...]\n\n"
" options:\n"
" -c | --connect=URI hypervisor connection URI\n"
" -r | --readonly connect readonly\n"
" -d | --debug=NUM debug level [0-4]\n"
" -h | --help this help\n"
" -q | --quiet quiet mode\n"
" -t | --timing print timing information\n"
" -l | --log=FILE output logging to file\n"
" -v short version\n"
" -V long version\n"
" --version[=TYPE] version, TYPE is short or long (default short)\n"
" -e | --escape <char> set escape sequence for console\n\n"
" commands (non interactive mode):\n\n"), progname, progname);
for (grp = cmdGroups; grp->name; grp++) {
fprintf(stdout, _(" %s (help keyword '%s')\n"),
grp->name, grp->keyword);
for (cmd = grp->commands; cmd->name; cmd++) {
if (cmd->flags & VSH_CMD_FLAG_ALIAS)
continue;
fprintf(stdout,
" %-30s %s\n", cmd->name,
_(vshCmddefGetInfo(cmd, "help")));
}
fprintf(stdout, "\n");
}
fprintf(stdout, "%s",
_("\n (specify help <group> for details about the commands in the group)\n"));
fprintf(stdout, "%s",
_("\n (specify help <command> for details about the command)\n\n"));
return;
}
/*
* Show version and options compiled in
*/
static void
vshShowVersion(vshControl *ctl ATTRIBUTE_UNUSED)
{
/* FIXME - list a copyright blurb, as in GNU programs? */
vshPrint(ctl, _("Virsh command line tool of libvirt %s\n"), VERSION);
vshPrint(ctl, _("See web site at %s\n\n"), "http://libvirt.org/");
vshPrint(ctl, "%s", _("Compiled with support for:\n"));
vshPrint(ctl, "%s", _(" Hypervisors:"));
#ifdef WITH_QEMU
vshPrint(ctl, " QEmu/KVM");
#endif
#ifdef WITH_LXC
vshPrint(ctl, " LXC");
#endif
#ifdef WITH_UML
vshPrint(ctl, " UML");
#endif
#ifdef WITH_XEN
vshPrint(ctl, " Xen");
#endif
#ifdef WITH_LIBXL
vshPrint(ctl, " LibXL");
#endif
#ifdef WITH_OPENVZ
vshPrint(ctl, " OpenVZ");
#endif
#ifdef WITH_VMWARE
vshPrint(ctl, " VMWare");
#endif
#ifdef WITH_PHYP
vshPrint(ctl, " PHYP");
#endif
#ifdef WITH_VBOX
vshPrint(ctl, " VirtualBox");
#endif
#ifdef WITH_ESX
vshPrint(ctl, " ESX");
#endif
#ifdef WITH_HYPERV
vshPrint(ctl, " Hyper-V");
#endif
#ifdef WITH_XENAPI
vshPrint(ctl, " XenAPI");
#endif
#ifdef WITH_TEST
vshPrint(ctl, " Test");
#endif
vshPrint(ctl, "\n");
vshPrint(ctl, "%s", _(" Networking:"));
#ifdef WITH_REMOTE
vshPrint(ctl, " Remote");
#endif
#ifdef WITH_LIBVIRTD
vshPrint(ctl, " Daemon");
#endif
#ifdef WITH_NETWORK
vshPrint(ctl, " Network");
#endif
#ifdef WITH_BRIDGE
vshPrint(ctl, " Bridging");
#endif
#ifdef WITH_NETCF
vshPrint(ctl, " Interface");
#endif
#ifdef WITH_NWFILTER
vshPrint(ctl, " Nwfilter");
#endif
#ifdef WITH_VIRTUALPORT
vshPrint(ctl, " VirtualPort");
#endif
vshPrint(ctl, "\n");
vshPrint(ctl, "%s", _(" Storage:"));
#ifdef WITH_STORAGE_DIR
vshPrint(ctl, " Dir");
#endif
#ifdef WITH_STORAGE_DISK
vshPrint(ctl, " Disk");
#endif
#ifdef WITH_STORAGE_FS
vshPrint(ctl, " Filesystem");
#endif
#ifdef WITH_STORAGE_SCSI
vshPrint(ctl, " SCSI");
#endif
#ifdef WITH_STORAGE_MPATH
vshPrint(ctl, " Multipath");
#endif
#ifdef WITH_STORAGE_ISCSI
vshPrint(ctl, " iSCSI");
#endif
#ifdef WITH_STORAGE_LVM
vshPrint(ctl, " LVM");
#endif
#ifdef WITH_STORAGE_RBD
vshPrint(ctl, " RBD");
#endif
#ifdef WITH_STORAGE_SHEEPDOG
vshPrint(ctl, " Sheepdog");
#endif
vshPrint(ctl, "\n");
vshPrint(ctl, "%s", _(" Miscellaneous:"));
#ifdef WITH_NODE_DEVICES
vshPrint(ctl, " Nodedev");
#endif
#ifdef WITH_SECDRIVER_APPARMOR
vshPrint(ctl, " AppArmor");
#endif
#ifdef WITH_SECDRIVER_SELINUX
vshPrint(ctl, " SELinux");
#endif
#ifdef WITH_SECRETS
vshPrint(ctl, " Secrets");
#endif
#ifdef ENABLE_DEBUG
vshPrint(ctl, " Debug");
#endif
#ifdef WITH_DTRACE_PROBES
vshPrint(ctl, " DTrace");
#endif
#ifdef USE_READLINE
vshPrint(ctl, " Readline");
#endif
#ifdef WITH_DRIVER_MODULES
vshPrint(ctl, " Modular");
#endif
vshPrint(ctl, "\n");
}
static bool
vshAllowedEscapeChar(char c)
{
/* Allowed escape characters:
* a-z A-Z @ [ \ ] ^ _
*/
return ('a' <= c && c <= 'z') ||
('@' <= c && c <= '_');
}
/*
* argv[]: virsh [options] [command]
*
*/
static bool
vshParseArgv(vshControl *ctl, int argc, char **argv)
{
int arg, len;
struct option opt[] = {
{"debug", required_argument, NULL, 'd'},
{"help", no_argument, NULL, 'h'},
{"quiet", no_argument, NULL, 'q'},
{"timing", no_argument, NULL, 't'},
{"version", optional_argument, NULL, 'v'},
{"connect", required_argument, NULL, 'c'},
{"readonly", no_argument, NULL, 'r'},
{"log", required_argument, NULL, 'l'},
{"escape", required_argument, NULL, 'e'},
{NULL, 0, NULL, 0}
};
/* Standard (non-command) options. The leading + ensures that no
* argument reordering takes place, so that command options are
* not confused with top-level virsh options. */
while ((arg = getopt_long(argc, argv, "+d:hqtc:vVrl:e:", opt, NULL)) != -1) {
switch (arg) {
case 'd':
if (virStrToLong_i(optarg, NULL, 10, &ctl->debug) < 0) {
vshError(ctl, "%s", _("option -d takes a numeric argument"));
exit(EXIT_FAILURE);
}
break;
case 'h':
vshUsage();
exit(EXIT_SUCCESS);
break;
case 'q':
ctl->quiet = true;
break;
case 't':
ctl->timing = true;
break;
case 'c':
ctl->name = vshStrdup(ctl, optarg);
break;
case 'v':
if (STRNEQ_NULLABLE(optarg, "long")) {
puts(VERSION);
exit(EXIT_SUCCESS);
}
/* fall through */
case 'V':
vshShowVersion(ctl);
exit(EXIT_SUCCESS);
case 'r':
ctl->readonly = true;
break;
case 'l':
ctl->logfile = vshStrdup(ctl, optarg);
break;
case 'e':
len = strlen(optarg);
if ((len == 2 && *optarg == '^' &&
vshAllowedEscapeChar(optarg[1])) ||
(len == 1 && *optarg != '^')) {
ctl->escapeChar = optarg;
} else {
vshError(ctl, _("Invalid string '%s' for escape sequence"),
optarg);
exit(EXIT_FAILURE);
}
break;
default:
vshError(ctl, _("unsupported option '-%c'. See --help."), arg);
exit(EXIT_FAILURE);
}
}
if (argc > optind) {
/* parse command */
ctl->imode = false;
if (argc - optind == 1) {
vshDebug(ctl, VSH_ERR_INFO, "commands: \"%s\"\n", argv[optind]);
return vshCommandStringParse(ctl, argv[optind]);
} else {
return vshCommandArgvParse(ctl, argc - optind, argv + optind);
}
}
return true;
}
int
main(int argc, char **argv)
{
vshControl _ctl, *ctl = &_ctl;
char *defaultConn;
bool ret = true;
memset(ctl, 0, sizeof(vshControl));
ctl->imode = true; /* default is interactive mode */
ctl->log_fd = -1; /* Initialize log file descriptor */
ctl->debug = VSH_DEBUG_DEFAULT;
ctl->escapeChar = CTRL_CLOSE_BRACKET;
if (!setlocale(LC_ALL, "")) {
perror("setlocale");
/* failure to setup locale is not fatal */
}
if (!bindtextdomain(PACKAGE, LOCALEDIR)) {
perror("bindtextdomain");
return EXIT_FAILURE;
}
if (!textdomain(PACKAGE)) {
perror("textdomain");
return EXIT_FAILURE;
}
if (virMutexInit(&ctl->lock) < 0) {
vshError(ctl, "%s", _("Failed to initialize mutex"));
return EXIT_FAILURE;
}
if (virInitialize() < 0) {
vshError(ctl, "%s", _("Failed to initialize libvirt"));
return EXIT_FAILURE;
}
if (!(progname = strrchr(argv[0], '/')))
progname = argv[0];
else
progname++;
if ((defaultConn = getenv("VIRSH_DEFAULT_CONNECT_URI"))) {
ctl->name = vshStrdup(ctl, defaultConn);
}
if (!vshParseArgv(ctl, argc, argv)) {
vshDeinit(ctl);
exit(EXIT_FAILURE);
}
if (!vshInit(ctl)) {
vshDeinit(ctl);
exit(EXIT_FAILURE);
}
if (!ctl->imode) {
ret = vshCommandRun(ctl, ctl->cmd);
} else {
/* interactive mode */
if (!ctl->quiet) {
vshPrint(ctl,
_("Welcome to %s, the virtualization interactive terminal.\n\n"),
progname);
vshPrint(ctl, "%s",
_("Type: 'help' for help with commands\n"
" 'quit' to quit\n\n"));
}
if (vshReadlineInit(ctl) < 0) {
vshDeinit(ctl);
exit(EXIT_FAILURE);
}
do {
const char *prompt = ctl->readonly ? VSH_PROMPT_RO : VSH_PROMPT_RW;
ctl->cmdstr =
vshReadline(ctl, prompt);
if (ctl->cmdstr == NULL)
break; /* EOF */
if (*ctl->cmdstr) {
#if USE_READLINE
add_history(ctl->cmdstr);
#endif
if (vshCommandStringParse(ctl, ctl->cmdstr))
vshCommandRun(ctl, ctl->cmd);
}
VIR_FREE(ctl->cmdstr);
} while (ctl->imode);
if (ctl->cmdstr == NULL)
fputc('\n', stdout); /* line break after alone prompt */
}
vshDeinit(ctl);
exit(ret ? EXIT_SUCCESS : EXIT_FAILURE);
}