In order to share as much virsh' logic as possible with upcomming
virt-admin client we need to split virsh logic into virsh specific and
client generic features. This patch only introduces these file that are
identical to the virsh.{c,h}
---
cfg.mk | 2 +-
po/POTFILES.in | 3 +-
tools/vsh.c | 3800 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tools/vsh.h | 527 ++++++++
4 files changed, 4330 insertions(+), 2 deletions(-)
create mode 100644 tools/vsh.c
create mode 100644 tools/vsh.h
diff --git a/cfg.mk b/cfg.mk
index 0d1a03c..f26191f 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -1086,7 +1086,7 @@ $(srcdir)/src/admin/admin_client.h:
$(srcdir)/src/admin/admin_protocol.x
$(MAKE) -C src admin/admin_client.h
# List all syntax-check exemptions:
-exclude_file_name_regexp--sc_avoid_strcase = ^tools/virsh\.h$$
+exclude_file_name_regexp--sc_avoid_strcase = ^tools/(virsh|vsh)\.h$$
_src1=libvirt-stream|fdstream|qemu/qemu_monitor|util/(vircommand|virfile)|xen/xend_internal|rpc/virnetsocket|lxc/lxc_controller|locking/lock_daemon
_test1=shunloadtest|virnettlscontexttest|virnettlssessiontest|vircgroupmock
diff --git a/po/POTFILES.in b/po/POTFILES.in
index a75f5ae..e0d1014 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -258,7 +258,6 @@ src/xenconfig/xen_xm.c
tests/virpolkittest.c
tools/libvirt-guests.sh.in
tools/virsh.c
-tools/virsh.h
tools/virsh-console.c
tools/virsh-domain-monitor.c
tools/virsh-domain.c
@@ -277,3 +276,5 @@ tools/virt-host-validate-lxc.c
tools/virt-host-validate-qemu.c
tools/virt-host-validate.c
tools/virt-login-shell.c
+tools/vsh.c
+tools/vsh.h
diff --git a/tools/vsh.c b/tools/vsh.c
new file mode 100644
index 0000000..609c8f3
--- /dev/null
+++ b/tools/vsh.c
@@ -0,0 +1,3800 @@
+/*
+ * vsh.c: common data to be used by clients to exercise the libvirt API
+ *
+ * Copyright (C) 2005, 2007-2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <
http://www.gnu.org/licenses/>.
+ *
+ * Daniel Veillard <veillard(a)redhat.com>
+ * Karel Zak <kzak(a)redhat.com>
+ * Daniel P. Berrange <berrange(a)redhat.com>
+ */
+
+#include <config.h>
+#include "vsh.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/time.h>
+#include "c-ctype.h"
+#include <fcntl.h>
+#include <locale.h>
+#include <time.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+#include <strings.h>
+#include <signal.h>
+
+#if WITH_READLINE
+# include <readline/readline.h>
+# include <readline/history.h>
+#endif
+
+#include "internal.h"
+#include "virerror.h"
+#include "virbuffer.h"
+#include "viralloc.h"
+#include <libvirt/libvirt-qemu.h>
+#include <libvirt/libvirt-lxc.h>
+#include "virfile.h"
+#include "configmake.h"
+#include "virthread.h"
+#include "vircommand.h"
+#include "conf/domain_conf.h"
+#include "virtypedparam.h"
+#include "virstring.h"
+
+#include "virsh-console.h"
+#include "virsh-domain.h"
+#include "virsh-domain-monitor.h"
+#include "virsh-host.h"
+#include "virsh-interface.h"
+#include "virsh-network.h"
+#include "virsh-nodedev.h"
+#include "virsh-nwfilter.h"
+#include "virsh-pool.h"
+#include "virsh-secret.h"
+#include "virsh-snapshot.h"
+#include "virsh-volume.h"
+
+/* Gnulib doesn't guarantee SA_SIGINFO support. */
+#ifndef SA_SIGINFO
+# define SA_SIGINFO 0
+#endif
+
+static char *progname;
+
+static const vshCmdGrp cmdGroups[];
+
+/* Bypass header poison */
+#undef strdup
+
+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);
+}
+
+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);
+}
+
+char *
+_vshStrdup(vshControl *ctl, const char *s, const char *filename, int line)
+{
+ char *x;
+
+ if (VIR_STRDUP(x, s) >= 0)
+ 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. */
+#define strdup use_vshStrdup_instead_of_strdup
+
+int
+vshNameSorter(const void *a, const void *b)
+{
+ const char **sa = (const char**)a;
+ const char **sb = (const char**)b;
+
+ return vshStrcasecmp(*sa, *sb);
+}
+
+double
+vshPrettyCapacity(unsigned long long val, const char **unit)
+{
+ double limit = 1024;
+
+ if (val < limit) {
+ *unit = "B";
+ return val;
+ }
+ limit *= 1024;
+ if (val < limit) {
+ *unit = "KiB";
+ return val / (limit / 1024);
+ }
+ limit *= 1024;
+ if (val < limit) {
+ *unit = "MiB";
+ return val / (limit / 1024);
+ }
+ limit *= 1024;
+ if (val < limit) {
+ *unit = "GiB";
+ return val / (limit / 1024);
+ }
+ limit *= 1024;
+ if (val < limit) {
+ *unit = "TiB";
+ return val / (limit / 1024);
+ }
+ limit *= 1024;
+ if (val < limit) {
+ *unit = "PiB";
+ return val / (limit / 1024);
+ }
+ limit *= 1024;
+ *unit = "EiB";
+ return val / (limit / 1024);
+}
+
+/*
+ * Convert the strings separated by ',' into array. The returned
+ * array is a NULL terminated string list. The caller has to free
+ * the array using virStringFreeList or a similar method.
+ *
+ * Returns the length of the filled array on success, or -1
+ * on error.
+ */
+int
+vshStringToArray(const char *str,
+ char ***array)
+{
+ char *str_copied = vshStrdup(NULL, str);
+ char *str_tok = NULL;
+ char *tmp;
+ unsigned int nstr_tokens = 0;
+ char **arr = NULL;
+ size_t len = strlen(str_copied);
+
+ /* tokenize the string from user and save its parts into an array */
+ nstr_tokens = 1;
+
+ /* count the delimiters, recognizing ,, as an escape for a
+ * literal comma */
+ str_tok = str_copied;
+ while ((str_tok = strchr(str_tok, ','))) {
+ if (str_tok[1] == ',')
+ str_tok++;
+ else
+ nstr_tokens++;
+ str_tok++;
+ }
+
+ /* reserve the NULL element at the end */
+ if (VIR_ALLOC_N(arr, nstr_tokens + 1) < 0) {
+ VIR_FREE(str_copied);
+ return -1;
+ }
+
+ /* tokenize the input string, while treating ,, as a literal comma */
+ nstr_tokens = 0;
+ tmp = str_tok = str_copied;
+ while ((tmp = strchr(tmp, ','))) {
+ if (tmp[1] == ',') {
+ memmove(&tmp[1], &tmp[2], len - (tmp - str_copied) - 2 + 1);
+ len--;
+ tmp++;
+ continue;
+ }
+ *tmp++ = '\0';
+ arr[nstr_tokens++] = vshStrdup(NULL, str_tok);
+ str_tok = tmp;
+ }
+ arr[nstr_tokens++] = vshStrdup(NULL, str_tok);
+
+ *array = arr;
+ VIR_FREE(str_copied);
+ return nstr_tokens;
+}
+
+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 (virGetEnvAllowSUID("VIRSH_DEBUG") != NULL)
+ virDefaultErrorFunc(error);
+}
+
+/* Store a libvirt error that is from a helper API that doesn't raise errors
+ * so it doesn't get overwritten */
+void
+vshSaveLibvirtError(void)
+{
+ virFreeError(last_error);
+ last_error = virSaveLastError();
+}
+
+/*
+ * Reset libvirt error on graceful fallback paths
+ */
+void
+vshResetLibvirtError(void)
+{
+ virFreeError(last_error);
+ last_error = NULL;
+}
+
+/*
+ * 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.
+ */
+void
+vshReportError(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:
+ vshResetLibvirtError();
+}
+
+/*
+ * Detection of disconnections and automatic reconnection support
+ */
+static int disconnected; /* we may have been disconnected */
+
+/*
+ * vshCatchDisconnect:
+ *
+ * We get here when the connection was closed. We can't do much in the
+ * handler, just save the fact it was raised.
+ */
+static void
+vshCatchDisconnect(virConnectPtr conn ATTRIBUTE_UNUSED,
+ int reason,
+ void *opaque ATTRIBUTE_UNUSED)
+{
+ if (reason != VIR_CONNECT_CLOSE_REASON_CLIENT)
+ disconnected++;
+}
+
+/* Main Function which should be used for connecting.
+ * This function properly handles keepalive settings. */
+virConnectPtr
+vshConnect(vshControl *ctl, const char *uri, bool readonly)
+{
+ virConnectPtr c = NULL;
+ int interval = 5; /* Default */
+ int count = 6; /* Default */
+ bool keepalive_forced = false;
+
+ if (ctl->keepalive_interval >= 0) {
+ interval = ctl->keepalive_interval;
+ keepalive_forced = true;
+ }
+ if (ctl->keepalive_count >= 0) {
+ count = ctl->keepalive_count;
+ keepalive_forced = true;
+ }
+
+ c = virConnectOpenAuth(uri, virConnectAuthPtrDefault,
+ readonly ? VIR_CONNECT_RO : 0);
+ if (!c)
+ return NULL;
+
+ if (interval > 0 &&
+ virConnectSetKeepAlive(c, interval, count) != 0) {
+ if (keepalive_forced) {
+ vshError(ctl, "%s",
+ _("Cannot setup keepalive on connection "
+ "as requested, disconnecting"));
+ virConnectClose(c);
+ return NULL;
+ }
+ vshDebug(ctl, VSH_ERR_INFO, "%s",
+ _("Failed to setup keepalive on connection\n"));
+ }
+
+ return c;
+}
+
+/*
+ * vshReconnect:
+ *
+ * Reconnect after a disconnect from libvirtd
+ *
+ */
+static void
+vshReconnect(vshControl *ctl)
+{
+ bool connected = false;
+
+ if (ctl->conn) {
+ int ret;
+
+ connected = true;
+
+ virConnectUnregisterCloseCallback(ctl->conn, vshCatchDisconnect);
+ ret = virConnectClose(ctl->conn);
+ if (ret < 0)
+ vshError(ctl, "%s", _("Failed to disconnect from the
hypervisor"));
+ else if (ret > 0)
+ vshError(ctl, "%s", _("One or more references were leaked
after "
+ "disconnect from the hypervisor"));
+ }
+
+ ctl->conn = vshConnect(ctl, ctl->name, ctl->readonly);
+
+ if (!ctl->conn) {
+ if (disconnected)
+ vshError(ctl, "%s", _("Failed to reconnect to the
hypervisor"));
+ else
+ vshError(ctl, "%s", _("failed to connect to the
hypervisor"));
+ } else {
+ if (virConnectRegisterCloseCallback(ctl->conn, vshCatchDisconnect,
+ NULL, NULL) < 0)
+ vshError(ctl, "%s", _("Unable to register disconnect
callback"));
+ if (connected)
+ vshError(ctl, "%s", _("Reconnected to the hypervisor"));
+ }
+ disconnected = 0;
+ ctl->useGetInfo = false;
+ ctl->useSnapshotOld = false;
+ ctl->blockJobNoBytes = false;
+}
+
+
+/*
+ * "connect" command
+ */
+static const vshCmdInfo info_connect[] = {
+ {.name = "help",
+ .data = N_("(re)connect to hypervisor")
+ },
+ {.name = "desc",
+ .data = N_("Connect to local hypervisor. This is built-in "
+ "command after shell start up.")
+ },
+ {.name = NULL}
+};
+
+static const vshCmdOptDef opts_connect[] = {
+ {.name = "name",
+ .type = VSH_OT_STRING,
+ .flags = VSH_OFLAG_EMPTY_OK,
+ .help = N_("hypervisor connection URI")
+ },
+ {.name = "readonly",
+ .type = VSH_OT_BOOL,
+ .help = N_("read-only connection")
+ },
+ {.name = NULL}
+};
+
+static bool
+cmdConnect(vshControl *ctl, const vshCmd *cmd)
+{
+ bool ro = vshCommandOptBool(cmd, "readonly");
+ const char *name = NULL;
+
+ if (ctl->conn) {
+ int ret;
+
+ virConnectUnregisterCloseCallback(ctl->conn, vshCatchDisconnect);
+ ret = virConnectClose(ctl->conn);
+ if (ret < 0)
+ vshError(ctl, "%s", _("Failed to disconnect from the
hypervisor"));
+ else if (ret > 0)
+ vshError(ctl, "%s", _("One or more references were leaked
after "
+ "disconnect from the hypervisor"));
+ ctl->conn = NULL;
+ }
+
+ VIR_FREE(ctl->name);
+ if (vshCommandOptStringReq(ctl, cmd, "name", &name) < 0)
+ return false;
+
+ ctl->name = vshStrdup(ctl, name);
+
+ ctl->useGetInfo = false;
+ ctl->useSnapshotOld = false;
+ ctl->blockJobNoBytes = false;
+ ctl->readonly = ro;
+
+ ctl->conn = vshConnect(ctl, ctl->name, ctl->readonly);
+
+ if (!ctl->conn) {
+ vshError(ctl, "%s", _("Failed to connect to the
hypervisor"));
+ return false;
+ }
+
+ if (virConnectRegisterCloseCallback(ctl->conn, vshCatchDisconnect,
+ NULL, NULL) < 0)
+ vshError(ctl, "%s", _("Unable to register disconnect
callback"));
+
+ return true;
+}
+
+
+#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
+ * 'n' if he doesn't want to
+ * 'i' if he wants to try defining it again while ignoring validation
+ * 'f' if he forcibly wants to
+ * -1 on error
+ * 0 otherwise
+ */
+int
+vshAskReedit(vshControl *ctl, const char *msg, bool relax_avail)
+{
+ int c = -1;
+
+ if (!isatty(STDIN_FILENO))
+ return -1;
+
+ vshReportError(ctl);
+
+ if (vshTTYMakeRaw(ctl, false) < 0)
+ return -1;
+
+ while (true) {
+ vshPrint(ctl, "\r%s %s %s: ", msg, _("Try again?"),
+ relax_avail ? "[y,n,i,f,?]" : "[y,n,f,?]");
+ c = c_tolower(getchar());
+
+ if (c == '?') {
+ vshPrintRaw(ctl,
+ "",
+ _("y - yes, start editor again"),
+ _("n - no, throw away my changes"),
+ NULL);
+
+ if (relax_avail) {
+ vshPrintRaw(ctl,
+ _("i - turn off validation and try to redefine
again"),
+ NULL);
+ }
+
+ vshPrintRaw(ctl,
+ _("f - force, try to redefine again"),
+ _("? - print this help"),
+ NULL);
+ continue;
+ } else if (c == 'y' || c == 'n' || c == 'f' ||
+ (relax_avail && c == 'i')) {
+ break;
+ }
+ }
+
+ vshTTYRestore(ctl);
+
+ vshPrint(ctl, "\r\n");
+ return c;
+}
+#else /* WIN32 */
+int
+vshAskReedit(vshControl *ctl,
+ const char *msg ATTRIBUTE_UNUSED,
+ bool relax_avail ATTRIBUTE_UNUSED)
+{
+ vshDebug(ctl, VSH_ERR_WARNING, "%s", _("This function is not "
+ "supported on WIN32 platform"));
+ return 0;
+}
+#endif /* WIN32 */
+
+int vshStreamSink(virStreamPtr st ATTRIBUTE_UNUSED,
+ const char *bytes, size_t nbytes, void *opaque)
+{
+ int *fd = opaque;
+
+ return safewrite(*fd, bytes, nbytes);
+}
+
+/* ---------------
+ * Commands
+ * ---------------
+ */
+
+/*
+ * "help" command
+ */
+static const vshCmdInfo info_help[] = {
+ {.name = "help",
+ .data = N_("print help")
+ },
+ {.name = "desc",
+ .data = N_("Prints global help, command specific help, or help for a\n"
+ " group of related commands")
+ },
+ {.name = NULL}
+};
+
+static const vshCmdOptDef opts_help[] = {
+ {.name = "command",
+ .type = VSH_OT_STRING,
+ .help = N_("Prints global help, command specific help, or help for a group of
related commands")
+ },
+ {.name = NULL}
+};
+
+static bool
+cmdHelp(vshControl *ctl, const vshCmd *cmd)
+ {
+ const char *name = NULL;
+
+ if (vshCommandOptString(ctl, 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;
+ }
+}
+
+/* Tree listing helpers. */
+
+static int
+vshTreePrintInternal(vshControl *ctl,
+ vshTreeLookup lookup,
+ void *opaque,
+ int num_devices,
+ int devid,
+ int lastdev,
+ bool root,
+ virBufferPtr indent)
+{
+ size_t 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, " ");
+ if (virBufferError(indent))
+ goto cleanup;
+ 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;
+}
+
+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;
+}
+
+/* Common code for the edit / net-edit / pool-edit functions which follow. */
+char *
+vshEditWriteToTempFile(vshControl *ctl, const char *doc)
+{
+ char *ret;
+ const char *tmpdir;
+ int fd;
+ char ebuf[1024];
+
+ tmpdir = virGetEnvBlockSUID("TMPDIR");
+ if (!tmpdir) tmpdir = "/tmp";
+ if (virAsprintf(&ret, "%s/virshXXXXXX.xml", tmpdir) < 0) {
+ vshError(ctl, "%s", _("out of memory"));
+ return NULL;
+ }
+ fd = mkostemps(ret, 4, O_CLOEXEC);
+ if (fd == -1) {
+ vshError(ctl, _("mkostemps: failed to create temporary file: %s"),
+ virStrerror(errno, ebuf, sizeof(ebuf)));
+ VIR_FREE(ret);
+ return NULL;
+ }
+
+ if (safewrite(fd, doc, strlen(doc)) == -1) {
+ vshError(ctl, _("write: %s: failed to write to temporary file: %s"),
+ ret, virStrerror(errno, ebuf, sizeof(ebuf)));
+ 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, virStrerror(errno, ebuf, sizeof(ebuf)));
+ 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-/_.:@"
+
+int
+vshEditFile(vshControl *ctl, const char *filename)
+{
+ const char *editor;
+ virCommandPtr cmd;
+ int ret = -1;
+ int outfd = STDOUT_FILENO;
+ int errfd = STDERR_FILENO;
+
+ editor = virGetEnvBlockSUID("VISUAL");
+ if (!editor)
+ editor = virGetEnvBlockSUID("EDITOR");
+ if (!editor)
+ editor = DEFAULT_EDITOR;
+
+ /* 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) {
+ vshReportError(ctl);
+ goto cleanup;
+ }
+ ret = 0;
+
+ cleanup:
+ virCommandFree(cmd);
+ return ret;
+}
+
+char *
+vshEditReadBackFile(vshControl *ctl, const char *filename)
+{
+ char *ret;
+ char ebuf[1024];
+
+ if (virFileReadAll(filename, VSH_MAX_XML_FILE, &ret) == -1) {
+ vshError(ctl,
+ _("%s: failed to read temporary file: %s"),
+ filename, virStrerror(errno, ebuf, sizeof(ebuf)));
+ return NULL;
+ }
+ return ret;
+}
+
+
+/*
+ * "cd" command
+ */
+static const vshCmdInfo info_cd[] = {
+ {.name = "help",
+ .data = N_("change the current directory")
+ },
+ {.name = "desc",
+ .data = N_("Change the current directory.")
+ },
+ {.name = NULL}
+};
+
+static const vshCmdOptDef opts_cd[] = {
+ {.name = "dir",
+ .type = VSH_OT_STRING,
+ .help = N_("directory to switch to (default: home or else root)")
+ },
+ {.name = NULL}
+};
+
+static bool
+cmdCd(vshControl *ctl, const vshCmd *cmd)
+{
+ const char *dir = NULL;
+ char *dir_malloced = NULL;
+ bool ret = true;
+ char ebuf[1024];
+
+ if (!ctl->imode) {
+ vshError(ctl, "%s", _("cd: command valid only in interactive
mode"));
+ return false;
+ }
+
+ if (vshCommandOptString(ctl, cmd, "dir", &dir) <= 0)
+ dir = dir_malloced = virGetUserDirectory();
+ if (!dir)
+ dir = "/";
+
+ if (chdir(dir) == -1) {
+ vshError(ctl, _("cd: %s: %s"),
+ virStrerror(errno, ebuf, sizeof(ebuf)), dir);
+ ret = false;
+ }
+
+ VIR_FREE(dir_malloced);
+ return ret;
+}
+
+/*
+ * "pwd" command
+ */
+static const vshCmdInfo info_pwd[] = {
+ {.name = "help",
+ .data = N_("print the current directory")
+ },
+ {.name = "desc",
+ .data = N_("Print the current directory.")
+ },
+ {.name = NULL}
+};
+
+static bool
+cmdPwd(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
+{
+ char *cwd;
+ bool ret = true;
+ char ebuf[1024];
+
+ cwd = getcwd(NULL, 0);
+ if (!cwd) {
+ vshError(ctl, _("pwd: cannot get current directory: %s"),
+ virStrerror(errno, ebuf, sizeof(ebuf)));
+ ret = false;
+ } else {
+ vshPrint(ctl, _("%s\n"), cwd);
+ VIR_FREE(cwd);
+ }
+
+ return ret;
+}
+
+/*
+ * "echo" command
+ */
+static const vshCmdInfo info_echo[] = {
+ {.name = "help",
+ .data = N_("echo arguments")
+ },
+ {.name = "desc",
+ .data = N_("Echo back arguments, possibly with quoting.")
+ },
+ {.name = NULL}
+};
+
+static const vshCmdOptDef opts_echo[] = {
+ {.name = "shell",
+ .type = VSH_OT_BOOL,
+ .help = N_("escape for shell use")
+ },
+ {.name = "xml",
+ .type = VSH_OT_BOOL,
+ .help = N_("escape for XML use")
+ },
+ {.name = "str",
+ .type = VSH_OT_ALIAS,
+ .help = "string"
+ },
+ {.name = "hi",
+ .type = VSH_OT_ALIAS,
+ .help = "string=hello"
+ },
+ {.name = "string",
+ .type = VSH_OT_ARGV,
+ .help = N_("arguments to echo")
+ },
+ {.name = 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(ctl, cmd, opt))) {
+ char *str;
+ virBuffer xmlbuf = VIR_BUFFER_INITIALIZER;
+
+ arg = opt->data;
+
+ if (count)
+ virBufferAddChar(&buf, ' ');
+
+ if (xml) {
+ virBufferEscapeString(&xmlbuf, "%s", arg);
+ if (virBufferError(&xmlbuf)) {
+ 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;
+}
+
+/*
+ * "quit" command
+ */
+static const vshCmdInfo info_quit[] = {
+ {.name = "help",
+ .data = N_("quit this interactive terminal")
+ },
+ {.name = "desc",
+ .data = ""
+ },
+ {.name = NULL}
+};
+
+static bool
+cmdQuit(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
+{
+ ctl->imode = false;
+ return true;
+}
+
+/* ---------------
+ * Utils for work with command definition
+ * ---------------
+ */
+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)
+{
+ size_t 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) {
+ optional = true;
+ if (opt->flags & VSH_OFLAG_REQ)
+ return -1; /* bool options can't be mandatory */
+ continue;
+ }
+ if (opt->type == VSH_OT_ALIAS) {
+ size_t j;
+ char *name = (char *)opt->help; /* cast away const */
+ char *p;
+
+ if (opt->flags || !opt->help)
+ return -1; /* alias options are tracked by the original name */
+ if ((p = strchr(name, '=')) &&
+ VIR_STRNDUP(name, name, p - name) < 0)
+ return -1;
+ for (j = i + 1; cmd->opts[j].name; j++) {
+ if (STREQ(name, cmd->opts[j].name) &&
+ cmd->opts[j].type != VSH_OT_ALIAS)
+ break;
+ }
+ if (name != opt->help) {
+ VIR_FREE(name);
+ /* If alias comes with value, replacement must not be bool */
+ if (cmd->opts[j].type == VSH_OT_BOOL)
+ return -1;
+ }
+ 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;
+ else
+ optional = true;
+ continue;
+ }
+
+ *opts_need_arg |= 1 << i;
+ if (opt->flags & VSH_OFLAG_REQ) {
+ if (optional && opt->type != VSH_OT_ARGV)
+ 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 vshCmdOptDef helpopt = {
+ .name = "help",
+ .type = VSH_OT_BOOL,
+ .help = N_("print help for this function")
+};
+static const vshCmdOptDef *
+vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name,
+ uint32_t *opts_seen, int *opt_index, char **optstr)
+{
+ size_t i;
+ const vshCmdOptDef *ret = NULL;
+ char *alias = NULL;
+
+ if (STREQ(name, helpopt.name))
+ return &helpopt;
+
+ 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) {
+ char *value;
+
+ /* Two types of replacements:
+ opt->help = "string": straight replacement of name
+ opt->help = "string=value": treat boolean flag as
+ alias of option and its default value */
+ sa_assert(!alias);
+ if (VIR_STRDUP(alias, opt->help) < 0)
+ goto cleanup;
+ name = alias;
+ if ((value = strchr(name, '='))) {
+ *value = '\0';
+ if (*optstr) {
+ vshError(ctl, _("invalid '=' after option
--%s"),
+ opt->name);
+ goto cleanup;
+ }
+ if (VIR_STRDUP(*optstr, value + 1) < 0)
+ goto cleanup;
+ }
+ continue;
+ }
+ if ((*opts_seen & (1 << i)) && opt->type != VSH_OT_ARGV)
{
+ vshError(ctl, _("option --%s already seen"), name);
+ goto cleanup;
+ }
+ *opts_seen |= 1 << i;
+ *opt_index = i;
+ ret = opt;
+ goto cleanup;
+ }
+ }
+
+ if (STRNEQ(cmd->name, "help")) {
+ vshError(ctl, _("command '%s' doesn't support option
--%s"),
+ cmd->name, name);
+ }
+ cleanup:
+ VIR_FREE(alias);
+ return ret;
+}
+
+static const vshCmdOptDef *
+vshCmddefGetData(const vshCmdDef *cmd, uint32_t *opts_need_arg,
+ uint32_t *opts_seen)
+{
+ size_t 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;
+ size_t 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;
+}
+
+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;
+}
+
+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;
+}
+
+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++) {
+ if (cmd->flags & VSH_CMD_FLAG_ALIAS)
+ continue;
+ vshPrint(ctl, " %-30s %s\n", cmd->name,
+ _(vshCmddefGetInfo(cmd, "help")));
+ }
+ }
+
+ return true;
+}
+
+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;
+ }
+ 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 && def->opts->name) {
+ 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 */
+ if (opt->flags & VSH_OFLAG_REQ) {
+ vshError(ctl,
+ _("internal error: bad options in command:
'%s'"),
+ def->name);
+ return false;
+ }
+ snprintf(buf, sizeof(buf), _("--%s <string>"),
opt->name);
+ break;
+ case VSH_OT_DATA:
+ /* OT_DATA should always be VSH_OFLAG_REQ */
+ if (!(opt->flags & VSH_OFLAG_REQ)) {
+ vshError(ctl,
+ _("internal error: bad options in command:
'%s'"),
+ def->name);
+ return false;
+ }
+ 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;
+ }
+
+ 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
+ * @needData: true if option must be non-boolean
+ *
+ * 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 assert if NAME is not
+ * valid (which indicates a programming error). No error messages are
+ * issued if a value is returned.
+ */
+static int
+vshCommandOpt(const vshCmd *cmd, const char *name, vshCmdOpt **opt,
+ bool needData)
+{
+ vshCmdOpt *candidate = cmd->opts;
+ const vshCmdOptDef *valid = cmd->def->opts;
+ int ret = 0;
+
+ /* See if option is valid and/or required. */
+ *opt = NULL;
+ while (valid) {
+ assert(valid->name);
+ if (STREQ(name, valid->name))
+ break;
+ valid++;
+ }
+ assert(!needData || valid->type != VSH_OT_BOOL);
+ if (valid->flags & VSH_OFLAG_REQ)
+ ret = -1;
+
+ /* See if option is present on command line. */
+ while (candidate) {
+ if (STREQ(candidate->def->name, name)) {
+ *opt = candidate;
+ ret = 1;
+ break;
+ }
+ candidate = candidate->next;
+ }
+ return ret;
+}
+
+/**
+ * vshCommandOptInt:
+ * @ctl virsh control structure
+ * @cmd command reference
+ * @name option name
+ * @value result
+ *
+ * Convert option to int.
+ * On error, a message is displayed.
+ *
+ * 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)
+ */
+int
+vshCommandOptInt(vshControl *ctl, const vshCmd *cmd,
+ const char *name, int *value)
+{
+ vshCmdOpt *arg;
+ int ret;
+
+ if ((ret = vshCommandOpt(cmd, name, &arg, true)) <= 0)
+ return ret;
+
+ if ((ret = virStrToLong_i(arg->data, NULL, 10, value)) < 0)
+ vshError(ctl,
+ _("Numeric value '%s' for <%s> option is malformed or
out of range"),
+ arg->data, name);
+ else
+ ret = 1;
+
+ return ret;
+}
+
+static int
+vshCommandOptUIntInternal(vshControl *ctl,
+ const vshCmd *cmd,
+ const char *name,
+ unsigned int *value,
+ bool wrap)
+{
+ vshCmdOpt *arg;
+ int ret;
+
+ if ((ret = vshCommandOpt(cmd, name, &arg, true)) <= 0)
+ return ret;
+
+ if (wrap)
+ ret = virStrToLong_ui(arg->data, NULL, 10, value);
+ else
+ ret = virStrToLong_uip(arg->data, NULL, 10, value);
+ if (ret < 0)
+ vshError(ctl,
+ _("Numeric value '%s' for <%s> option is malformed or
out of range"),
+ arg->data, name);
+ else
+ ret = 1;
+
+ return ret;
+}
+
+/**
+ * vshCommandOptUInt:
+ * @ctl virsh control structure
+ * @cmd command reference
+ * @name option name
+ * @value result
+ *
+ * Convert option to unsigned int, reject negative numbers
+ * See vshCommandOptInt()
+ */
+int
+vshCommandOptUInt(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned int *value)
+{
+ return vshCommandOptUIntInternal(ctl, cmd, name, value, false);
+}
+
+/**
+ * vshCommandOptUIntWrap:
+ * @ctl virsh control structure
+ * @cmd command reference
+ * @name option name
+ * @value result
+ *
+ * Convert option to unsigned int, wraps negative numbers to positive
+ * See vshCommandOptInt()
+ */
+int
+vshCommandOptUIntWrap(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned int *value)
+{
+ return vshCommandOptUIntInternal(ctl, cmd, name, value, true);
+}
+
+static int
+vshCommandOptULInternal(vshControl *ctl,
+ const vshCmd *cmd,
+ const char *name,
+ unsigned long *value,
+ bool wrap)
+{
+ vshCmdOpt *arg;
+ int ret;
+
+ if ((ret = vshCommandOpt(cmd, name, &arg, true)) <= 0)
+ return ret;
+
+ if (wrap)
+ ret = virStrToLong_ul(arg->data, NULL, 10, value);
+ else
+ ret = virStrToLong_ulp(arg->data, NULL, 10, value);
+ if (ret < 0)
+ vshError(ctl,
+ _("Numeric value '%s' for <%s> option is malformed or
out of range"),
+ arg->data, name);
+ else
+ ret = 1;
+
+ return ret;
+}
+
+/*
+ * vshCommandOptUL:
+ * @ctl virsh control structure
+ * @cmd command reference
+ * @name option name
+ * @value result
+ *
+ * Convert option to unsigned long
+ * See vshCommandOptInt()
+ */
+int
+vshCommandOptUL(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned long *value)
+{
+ return vshCommandOptULInternal(ctl, cmd, name, value, false);
+}
+
+/**
+ * vshCommandOptULWrap:
+ * @ctl virsh control structure
+ * @cmd command reference
+ * @name option name
+ * @value result
+ *
+ * Convert option to unsigned long, wraps negative numbers to positive
+ * See vshCommandOptInt()
+ */
+int
+vshCommandOptULWrap(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned long *value)
+{
+ return vshCommandOptULInternal(ctl, cmd, name, value, true);
+}
+
+/**
+ * vshCommandOptString:
+ * @ctl virsh control structure
+ * @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)
+ */
+int
+vshCommandOptString(vshControl *ctl ATTRIBUTE_UNUSED, const vshCmd *cmd,
+ const char *name, const char **value)
+{
+ vshCmdOpt *arg;
+ int ret;
+
+ if ((ret = vshCommandOpt(cmd, name, &arg, true)) <= 0)
+ return ret;
+
+ if (!*arg->data && !(arg->def->flags & VSH_OFLAG_EMPTY_OK))
+ return -1;
+ *value = arg->data;
+ return 1;
+}
+
+/**
+ * vshCommandOptStringReq:
+ * @ctl virsh control structure
+ * @cmd command structure
+ * @name option name
+ * @value result (updated to NULL or the option argument)
+ *
+ * Gets a option argument as string.
+ *
+ * Returns 0 on success or when the option is not present and not
+ * required, *value is set to the option argument. On error -1 is
+ * returned and error message printed.
+ */
+int
+vshCommandOptStringReq(vshControl *ctl,
+ const vshCmd *cmd,
+ const char *name,
+ const char **value)
+{
+ vshCmdOpt *arg;
+ int ret;
+ const char *error = NULL;
+
+ /* clear out the value */
+ *value = NULL;
+
+ ret = vshCommandOpt(cmd, name, &arg, true);
+ /* option is not required and not present */
+ if (ret == 0)
+ return 0;
+ /* this should not be propagated here, just to be sure */
+ if (ret == -1)
+ error = N_("Mandatory option not present");
+ else if (!*arg->data && !(arg->def->flags &
VSH_OFLAG_EMPTY_OK))
+ error = N_("Option argument is empty");
+
+ if (error) {
+ vshError(ctl, _("Failed to get option '%s': %s"), name,
_(error));
+ return -1;
+ }
+
+ *value = arg->data;
+ return 0;
+}
+
+/**
+ * vshCommandOptLongLong:
+ * @ctl virsh control structure
+ * @cmd command reference
+ * @name option name
+ * @value result
+ *
+ * Returns option as long long
+ * See vshCommandOptInt()
+ */
+int
+vshCommandOptLongLong(vshControl *ctl, const vshCmd *cmd,
+ const char *name, long long *value)
+{
+ vshCmdOpt *arg;
+ int ret;
+
+ if ((ret = vshCommandOpt(cmd, name, &arg, true)) <= 0)
+ return ret;
+
+ if ((ret = virStrToLong_ll(arg->data, NULL, 10, value)) < 0)
+ vshError(ctl,
+ _("Numeric value '%s' for <%s> option is malformed or
out of range"),
+ arg->data, name);
+ else
+ ret = 1;
+
+ return ret;
+}
+
+static int
+vshCommandOptULongLongInternal(vshControl *ctl,
+ const vshCmd *cmd,
+ const char *name,
+ unsigned long long *value,
+ bool wrap)
+{
+ vshCmdOpt *arg;
+ int ret;
+
+ if ((ret = vshCommandOpt(cmd, name, &arg, true)) <= 0)
+ return ret;
+
+ if (wrap)
+ ret = virStrToLong_ull(arg->data, NULL, 10, value);
+ else
+ ret = virStrToLong_ullp(arg->data, NULL, 10, value);
+ if (ret < 0)
+ vshError(ctl,
+ _("Numeric value '%s' for <%s> option is malformed or
out of range"),
+ arg->data, name);
+ else
+ ret = 1;
+
+ return ret;
+}
+
+/**
+ * vshCommandOptULongLong:
+ * @ctl virsh control structure
+ * @cmd command reference
+ * @name option name
+ * @value result
+ *
+ * Returns option as long long, rejects negative numbers
+ * See vshCommandOptInt()
+ */
+int
+vshCommandOptULongLong(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned long long *value)
+{
+ return vshCommandOptULongLongInternal(ctl, cmd, name, value, false);
+}
+
+/**
+ * vshCommandOptULongLongWrap:
+ * @ctl virsh control structure
+ * @cmd command reference
+ * @name option name
+ * @value result
+ *
+ * Returns option as long long, wraps negative numbers to positive
+ * See vshCommandOptInt()
+ */
+int
+vshCommandOptULongLongWrap(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned long long *value)
+{
+ return vshCommandOptULongLongInternal(ctl, cmd, name, value, true);
+}
+
+/**
+ * vshCommandOptScaledInt:
+ * @ctl virsh control structure
+ * @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()
+ */
+int
+vshCommandOptScaledInt(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned long long *value,
+ int scale, unsigned long long max)
+{
+ vshCmdOpt *arg;
+ char *end;
+ int ret;
+
+ if ((ret = vshCommandOpt(cmd, name, &arg, true)) <= 0)
+ return ret;
+ if (virStrToLong_ullp(arg->data, &end, 10, value) < 0 ||
+ virScaleInteger(value, end, scale, max) < 0)
+ {
+ vshError(ctl,
+ _("Numeric value '%s' for <%s> option is malformed or
out of range"),
+ arg->data, name);
+ ret = -1;
+ } else {
+ ret = 1;
+ }
+
+ return ret;
+}
+
+
+/**
+ * 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.
+ */
+bool
+vshCommandOptBool(const vshCmd *cmd, const char *name)
+{
+ vshCmdOpt *dummy;
+
+ return vshCommandOpt(cmd, name, &dummy, false) == 1;
+}
+
+/**
+ * vshCommandOptArgv:
+ * @ctl virsh control structure
+ * @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.
+ */
+const vshCmdOpt *
+vshCommandOptArgv(vshControl *ctl ATTRIBUTE_UNUSED, 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;
+}
+
+/*
+ * vshCommandOptTimeoutToMs:
+ * @ctl virsh control structure
+ * @cmd command reference
+ * @timeout result
+ *
+ * Parse an optional --timeout parameter in seconds, but store the
+ * value of the timeout in milliseconds.
+ * See vshCommandOptInt()
+ */
+int
+vshCommandOptTimeoutToMs(vshControl *ctl, const vshCmd *cmd, int *timeout)
+{
+ int ret;
+ unsigned int utimeout;
+
+ if ((ret = vshCommandOptUInt(ctl, cmd, "timeout", &utimeout)) <= 0)
+ return ret;
+
+ /* Ensure that the timeout is not zero and that we can convert
+ * it from seconds to milliseconds without overflowing. */
+ if (utimeout == 0 || utimeout > INT_MAX / 1000) {
+ vshError(ctl,
+ _("Numeric value '%u' for <%s> option is malformed or
out of range"),
+ utimeout,
+ "timeout");
+ ret = -1;
+ } else {
+ *timeout = ((int) utimeout) * 1000;
+ }
+
+ return ret;
+}
+
+static bool
+vshConnectionUsability(vshControl *ctl, virConnectPtr conn)
+{
+ if (!conn ||
+ virConnectIsAlive(conn) == 0) {
+ vshError(ctl, "%s", _("no valid connection"));
+ return false;
+ }
+
+ /* The connection is considered dead only if
+ * virConnectIsAlive() successfuly says so.
+ */
+ vshResetLibvirtError();
+
+ return true;
+}
+
+/*
+ * 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);
+
+ if ((cmd->def->flags & VSH_CMD_FLAG_NOCONNECT) ||
+ vshConnectionUsability(ctl, ctl->conn)) {
+ ret = cmd->def->handler(ctl, cmd);
+ } else {
+ /* connection is not usable, return error */
+ ret = false;
+ }
+
+ 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)
+ vshReportError(ctl);
+
+ if (STREQ(cmd->def->name, "quit") ||
+ STREQ(cmd->def->name, "exit")) /* hack ... */
+ return ret;
+
+ if (enable_timing) {
+ double diff_ms = (((after.tv_sec - before.tv_sec) * 1000.0) +
+ ((after.tv_usec - before.tv_usec) / 1000.0));
+
+ vshPrint(ctl, _("\n(Time: %.3f ms)\n\n"), diff_ms);
+ } 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 vshCommandParser;
+struct _vshCommandParser {
+ vshCommandToken(*getNextArg)(vshControl *, vshCommandParser *,
+ char **);
+ /* vshCommandStringGetArg() */
+ char *pos;
+ /* vshCommandArgvGetArg() */
+ char **arg_pos;
+ char **arg_end;
+};
+
+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 = 0;
+
+ if (optstr) {
+ *optstr = '\0'; /* convert the '=' to '\0'
*/
+ optstr = vshStrdup(ctl, optstr + 1);
+ }
+ /* Special case 'help' to ignore all spurious options */
+ if (!(opt = vshCmddefGetOption(ctl, cmd, tkdata + 2,
+ &opts_seen, &opt_index,
+ &optstr))) {
+ VIR_FREE(optstr);
+ if (STREQ(cmd->name, "help"))
+ continue;
+ 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:
+ /* Special case 'help' to ignore spurious data */
+ if (!(opt = vshCmddefGetData(cmd, &opts_need_arg,
+ &opts_seen)) &&
+ STRNEQ(cmd->name, "help")) {
+ 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));
+ vshCmdOpt *tmpopt = first;
+
+ /* if we encountered --help, replace parsed command with
+ * 'help <cmdname>' */
+ for (tmpopt = first; tmpopt; tmpopt = tmpopt->next) {
+ if (STRNEQ(tmpopt->def->name, "help"))
+ continue;
+
+ vshCommandOptFree(first);
+ first = vshMalloc(ctl, sizeof(vshCmdOpt));
+ first->def = &(opts_help[0]);
+ first->data = vshStrdup(ctl, cmd->name);
+ first->next = NULL;
+
+ cmd = vshCmddefSearch("help");
+ opts_required = 0;
+ opts_seen = 0;
+ break;
+ }
+
+ 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
+ * ---------------
+ */
+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;
+}
+
+/* Return a non-NULL string representation of a typed parameter; exit
+ * if we are out of memory. */
+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:
+ str = vshStrdup(ctl, 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;
+}
+
+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);
+}
+
+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);
+}
+
+
+bool
+vshTTYIsInterruptCharacter(vshControl *ctl ATTRIBUTE_UNUSED,
+ const char chr ATTRIBUTE_UNUSED)
+{
+#ifndef WIN32
+ if (ctl->istty &&
+ ctl->termattr.c_cc[VINTR] == chr)
+ return true;
+#endif
+
+ return false;
+}
+
+
+bool
+vshTTYAvailable(vshControl *ctl)
+{
+ return ctl->istty;
+}
+
+
+int
+vshTTYDisableInterrupt(vshControl *ctl ATTRIBUTE_UNUSED)
+{
+#ifndef WIN32
+ struct termios termset = ctl->termattr;
+
+ if (!ctl->istty)
+ return -1;
+
+ /* check if we need to set the terminal */
+ if (termset.c_cc[VINTR] == _POSIX_VDISABLE)
+ return 0;
+
+ termset.c_cc[VINTR] = _POSIX_VDISABLE;
+ termset.c_lflag &= ~ICANON;
+
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &termset) < 0)
+ return -1;
+#endif
+
+ return 0;
+}
+
+
+int
+vshTTYRestore(vshControl *ctl ATTRIBUTE_UNUSED)
+{
+#ifndef WIN32
+ if (!ctl->istty)
+ return 0;
+
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &ctl->termattr) < 0)
+ return -1;
+#endif
+
+ return 0;
+}
+
+
+#if !defined(WIN32) && !defined(HAVE_CFMAKERAW)
+/* provide fallback in case cfmakeraw isn't available */
+static void
+cfmakeraw(struct termios *attr)
+{
+ attr->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
+ | INLCR | IGNCR | ICRNL | IXON);
+ attr->c_oflag &= ~OPOST;
+ attr->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+ attr->c_cflag &= ~(CSIZE | PARENB);
+ attr->c_cflag |= CS8;
+}
+#endif /* !WIN32 && !HAVE_CFMAKERAW */
+
+
+int
+vshTTYMakeRaw(vshControl *ctl ATTRIBUTE_UNUSED,
+ bool report_errors ATTRIBUTE_UNUSED)
+{
+#ifndef WIN32
+ struct termios rawattr = ctl->termattr;
+ char ebuf[1024];
+
+ if (!ctl->istty) {
+ if (report_errors) {
+ vshError(ctl, "%s",
+ _("unable to make terminal raw: console isn't a
tty"));
+ }
+
+ return -1;
+ }
+
+ cfmakeraw(&rawattr);
+
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &rawattr) < 0) {
+ if (report_errors)
+ vshError(ctl, _("unable to set tty attributes: %s"),
+ virStrerror(errno, ebuf, sizeof(ebuf)));
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+
+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)
+ vshReportError(ctl);
+ }
+}
+
+
+/*
+ * Helpers for waiting for a libvirt event.
+ */
+
+/* We want to use SIGINT to cancel a wait; but as signal handlers
+ * don't have an opaque argument, we have to use static storage. */
+static int vshEventFd = -1;
+static struct sigaction vshEventOldAction;
+
+
+/* Signal handler installed in vshEventStart, removed in vshEventCleanup. */
+static void
+vshEventInt(int sig ATTRIBUTE_UNUSED,
+ siginfo_t *siginfo ATTRIBUTE_UNUSED,
+ void *context ATTRIBUTE_UNUSED)
+{
+ char reason = VSH_EVENT_INTERRUPT;
+ if (vshEventFd >= 0)
+ ignore_value(safewrite(vshEventFd, &reason, 1));
+}
+
+
+/* Event loop handler used to limit length of waiting for any other event. */
+static void
+vshEventTimeout(int timer ATTRIBUTE_UNUSED,
+ void *opaque)
+{
+ vshControl *ctl = opaque;
+ char reason = VSH_EVENT_TIMEOUT;
+
+ if (ctl->eventPipe[1] >= 0)
+ ignore_value(safewrite(ctl->eventPipe[1], &reason, 1));
+}
+
+
+/**
+ * vshEventStart:
+ * @ctl virsh command struct
+ * @timeout_ms max wait time in milliseconds, or 0 for indefinite
+ *
+ * Set up a wait for a libvirt event. The wait can be canceled by
+ * SIGINT or by calling vshEventDone() in your event handler. If
+ * @timeout_ms is positive, the wait will also end if the timeout
+ * expires. Call vshEventWait() to block the main thread (the event
+ * handler runs in the event loop thread). When done (including if
+ * there was an error registering for an event), use vshEventCleanup()
+ * to quit waiting. Returns 0 on success, -1 on failure. */
+int
+vshEventStart(vshControl *ctl, int timeout_ms)
+{
+ struct sigaction action;
+
+ assert(ctl->eventPipe[0] == -1 && ctl->eventPipe[1] == -1 &&
+ vshEventFd == -1 && ctl->eventTimerId >= 0);
+ if (pipe2(ctl->eventPipe, O_CLOEXEC) < 0) {
+ char ebuf[1024];
+
+ vshError(ctl, _("failed to create pipe: %s"),
+ virStrerror(errno, ebuf, sizeof(ebuf)));
+ return -1;
+ }
+ vshEventFd = ctl->eventPipe[1];
+
+ action.sa_sigaction = vshEventInt;
+ action.sa_flags = SA_SIGINFO;
+ sigemptyset(&action.sa_mask);
+ sigaction(SIGINT, &action, &vshEventOldAction);
+
+ if (timeout_ms)
+ virEventUpdateTimeout(ctl->eventTimerId, timeout_ms);
+
+ return 0;
+}
+
+
+/**
+ * vshEventDone:
+ * @ctl virsh command struct
+ *
+ * Call this from an event callback to let the main thread quit
+ * blocking on further events.
+ */
+void
+vshEventDone(vshControl *ctl)
+{
+ char reason = VSH_EVENT_DONE;
+
+ if (ctl->eventPipe[1] >= 0)
+ ignore_value(safewrite(ctl->eventPipe[1], &reason, 1));
+}
+
+
+/**
+ * vshEventWait:
+ * @ctl virsh command struct
+ *
+ * Call this in the main thread after calling vshEventStart() then
+ * registering for one or more events. This call will block until
+ * SIGINT, the timeout registered at the start, or until one of your
+ * event handlers calls vshEventDone(). Returns an enum VSH_EVENT_*
+ * stating how the wait concluded, or -1 on error.
+ */
+int
+vshEventWait(vshControl *ctl)
+{
+ char buf;
+ int rv;
+
+ assert(ctl->eventPipe[0] >= 0);
+ while ((rv = read(ctl->eventPipe[0], &buf, 1)) < 0 && errno ==
EINTR);
+ if (rv != 1) {
+ char ebuf[1024];
+
+ if (!rv)
+ errno = EPIPE;
+ vshError(ctl, _("failed to determine loop exit status: %s"),
+ virStrerror(errno, ebuf, sizeof(ebuf)));
+ return -1;
+ }
+ return buf;
+}
+
+
+/**
+ * vshEventCleanup:
+ * @ctl virsh command struct
+ *
+ * Call at the end of any function that has used vshEventStart(), to
+ * tear down any remaining SIGINT or timeout handlers.
+ */
+void
+vshEventCleanup(vshControl *ctl)
+{
+ if (vshEventFd >= 0) {
+ sigaction(SIGINT, &vshEventOldAction, NULL);
+ vshEventFd = -1;
+ }
+ VIR_FORCE_CLOSE(ctl->eventPipe[0]);
+ VIR_FORCE_CLOSE(ctl->eventPipe[1]);
+ virEventUpdateTimeout(ctl->eventTimerId, -1);
+}
+
+
+/*
+ * Initialize debug settings.
+ */
+static void
+vshInitDebug(vshControl *ctl)
+{
+ const char *debugEnv;
+
+ if (ctl->debug == VSH_DEBUG_DEFAULT) {
+ /* log level not set from commandline, check env variable */
+ debugEnv = virGetEnvAllowSUID("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 = virGetEnvBlockSUID("VIRSH_LOG_FILE");
+ if (debugEnv && *debugEnv) {
+ ctl->logfile = vshStrdup(ctl, debugEnv);
+ vshOpenLogFile(ctl);
+ }
+ }
+}
+
+/*
+ * Initialize connection.
+ */
+static bool
+vshInit(vshControl *ctl)
+{
+ /* Since we have the commandline arguments parsed, we need to
+ * re-initialize all the debugging to make it work properly */
+ vshInitDebug(ctl);
+
+ if (ctl->conn)
+ return false;
+
+ /* set up the library error handler */
+ virSetErrorFunc(NULL, virshErrorHandler);
+
+ if (virEventRegisterDefaultImpl() < 0)
+ return false;
+
+ if (virThreadCreate(&ctl->eventLoop, true, vshEventLoop, ctl) < 0)
+ return false;
+ ctl->eventLoopStarted = true;
+
+ if ((ctl->eventTimerId = virEventAddTimeout(-1, vshEventTimeout, ctl,
+ NULL)) < 0)
+ return false;
+
+ if (ctl->name) {
+ vshReconnect(ctl);
+ /* 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) {
+ vshReportError(ctl);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#define LOGFILE_FLAGS (O_WRONLY | O_APPEND | O_CREAT | O_SYNC)
+
+/**
+ * vshOpenLogFile:
+ *
+ * Open log file.
+ */
+void
+vshOpenLogFile(vshControl *ctl)
+{
+ if (ctl->logfile == NULL)
+ return;
+
+ 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.
+ */
+void
+vshOutputLogFile(vshControl *ctl, int log_level, const char *msg_format,
+ va_list ap)
+{
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+ char *str = NULL;
+ size_t len;
+ const char *lvl = "";
+ time_t stTime;
+ struct tm stTm;
+
+ if (ctl->log_fd == -1)
+ return;
+
+ /**
+ * create log format
+ *
+ * [YYYY.MM.DD HH:MM:SS SIGNATURE PID] LOG_LEVEL message
+ */
+ time(&stTime);
+ localtime_r(&stTime, &stTm);
+ 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;
+
+ VIR_FREE(str);
+ return;
+
+ error:
+ vshCloseLogFile(ctl);
+ vshError(ctl, "%s", _("failed to write the log file"));
+ virBufferFreeAndReset(&buf);
+ VIR_FREE(str);
+}
+
+/**
+ * vshCloseLogFile:
+ *
+ * Close log file.
+ */
+void
+vshCloseLogFile(vshControl *ctl)
+{
+ char ebuf[1024];
+
+ /* log file close */
+ if (VIR_CLOSE(ctl->log_fd) < 0) {
+ vshError(ctl, _("%s: failed to write log file: %s"),
+ ctl->logfile ? ctl->logfile : "?",
+ virStrerror(errno, ebuf, sizeof(ebuf)));
+ }
+
+ if (ctl->logfile) {
+ VIR_FREE(ctl->logfile);
+ ctl->logfile = NULL;
+ }
+}
+
+#if WITH_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;
+ 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;
+}
+
+# define VIRSH_HISTSIZE_MAX 500000
+
+static int
+vshReadlineInit(vshControl *ctl)
+{
+ char *userdir = NULL;
+ int max_history = 500;
+ const char *histsize_str;
+
+ /* Allow conditional parsing of the ~/.inputrc file.
+ * Work around ancient readline 4.1 (hello Mac OS X),
+ * which declared it as 'char *' instead of 'const char *'.
+ */
+ rl_readline_name = (char *) "virsh";
+
+ /* Tell the completer that we want a crack first. */
+ rl_attempted_completion_function = vshReadlineCompletion;
+
+ /* Limit the total size of the history buffer */
+ if ((histsize_str = virGetEnvBlockSUID("VIRSH_HISTSIZE"))) {
+ if (virStrToLong_i(histsize_str, NULL, 10, &max_history) < 0) {
+ vshError(ctl, "%s", _("Bad $VIRSH_HISTSIZE value."));
+ VIR_FREE(userdir);
+ return -1;
+ } else if (max_history > VIRSH_HISTSIZE_MAX || max_history < 0) {
+ vshError(ctl, _("$VIRSH_HISTSIZE value should be between 0 and
%d"),
+ VIRSH_HISTSIZE_MAX);
+ VIR_FREE(userdir);
+ return -1;
+ }
+ }
+ stifle_history(max_history);
+
+ /* 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 /* !WITH_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 /* !WITH_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;
+ virConnectUnregisterCloseCallback(ctl->conn, vshCatchDisconnect);
+ ret = virConnectClose(ctl->conn);
+ if (ret < 0)
+ vshError(ctl, "%s", _("Failed to disconnect from the
hypervisor"));
+ else if (ret > 0)
+ vshError(ctl, "%s", _("One or more references were leaked
after "
+ "disconnect from the hypervisor"));
+ }
+ 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);
+
+ if (ctl->eventTimerId != -1)
+ virEventRemoveTimeout(ctl->eventTimerId);
+
+ 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"
+ " -d | --debug=NUM debug level [0-4]\n"
+ " -e | --escape <char> set escape sequence for
console\n"
+ " -h | --help this help\n"
+ " -k | --keepalive-interval=NUM\n"
+ " keepalive interval in seconds, 0
for disable\n"
+ " -K | --keepalive-count=NUM\n"
+ " number of possible missed
keepalive messages\n"
+ " -l | --log=FILE output logging to file\n"
+ " -q | --quiet quiet mode\n"
+ " -r | --readonly connect readonly\n"
+ " -t | --timing print timing information\n"
+ " -v short version\n"
+ " -V long version\n"
+ " --version[=TYPE] version, TYPE is short or long
(default short)\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_BHYVE
+ vshPrint(ctl, " Bhyve");
+#endif
+#ifdef WITH_TEST
+ vshPrint(ctl, " Test");
+#endif
+ vshPrint(ctl, "\n");
+
+ vshPrint(ctl, "%s", _(" Networking:"));
+#ifdef WITH_REMOTE
+ vshPrint(ctl, " Remote");
+#endif
+#ifdef WITH_NETWORK
+ vshPrint(ctl, " Network");
+#endif
+#ifdef WITH_BRIDGE
+ vshPrint(ctl, " Bridging");
+#endif
+#if defined(WITH_INTERFACE)
+ vshPrint(ctl, " Interface");
+# if defined(WITH_NETCF)
+ vshPrint(ctl, " netcf");
+# elif defined(WITH_UDEV)
+ vshPrint(ctl, " udev");
+# endif
+#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
+#ifdef WITH_STORAGE_GLUSTER
+ vshPrint(ctl, " Gluster");
+#endif
+ vshPrint(ctl, "\n");
+
+ vshPrint(ctl, "%s", _(" Miscellaneous:"));
+#ifdef WITH_LIBVIRTD
+ vshPrint(ctl, " Daemon");
+#endif
+#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
+#if WITH_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, debug, keepalive;
+ size_t i;
+ int longindex = -1;
+ struct option opt[] = {
+ {"connect", required_argument, NULL, 'c'},
+ {"debug", required_argument, NULL, 'd'},
+ {"escape", required_argument, NULL, 'e'},
+ {"help", no_argument, NULL, 'h'},
+ {"keepalive-interval", required_argument, NULL, 'k'},
+ {"keepalive-count", required_argument, NULL, 'K'},
+ {"log", required_argument, NULL, 'l'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"readonly", no_argument, NULL, 'r'},
+ {"timing", no_argument, NULL, 't'},
+ {"version", optional_argument, NULL, 'v'},
+ {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, "+:c:d:e:hk:K:l:qrtvV", opt,
&longindex)) != -1) {
+ switch (arg) {
+ case 'c':
+ VIR_FREE(ctl->name);
+ ctl->name = vshStrdup(ctl, optarg);
+ break;
+ case 'd':
+ if (virStrToLong_i(optarg, NULL, 10, &debug) < 0) {
+ vshError(ctl, _("option %s takes a numeric argument"),
+ longindex == -1 ? "-d" : "--debug");
+ exit(EXIT_FAILURE);
+ }
+ if (debug < VSH_ERR_DEBUG || debug > VSH_ERR_ERROR)
+ vshError(ctl, _("ignoring debug level %d out of range
[%d-%d]"),
+ debug, VSH_ERR_DEBUG, VSH_ERR_ERROR);
+ else
+ ctl->debug = debug;
+ 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;
+ case 'h':
+ vshUsage();
+ exit(EXIT_SUCCESS);
+ break;
+ case 'k':
+ if (virStrToLong_i(optarg, NULL, 0, &keepalive) < 0) {
+ vshError(ctl,
+ _("Invalid value for option %s"),
+ longindex == -1 ? "-k" :
"--keepalive-interval");
+ exit(EXIT_FAILURE);
+ }
+
+ if (keepalive < 0) {
+ vshError(ctl,
+ _("option %s requires a positive integer argument"),
+ longindex == -1 ? "-k" :
"--keepalive-interval");
+ exit(EXIT_FAILURE);
+ }
+ ctl->keepalive_interval = keepalive;
+ break;
+ case 'K':
+ if (virStrToLong_i(optarg, NULL, 0, &keepalive) < 0) {
+ vshError(ctl,
+ _("Invalid value for option %s"),
+ longindex == -1 ? "-K" :
"--keepalive-count");
+ exit(EXIT_FAILURE);
+ }
+
+ if (keepalive < 0) {
+ vshError(ctl,
+ _("option %s requires a positive integer argument"),
+ longindex == -1 ? "-K" :
"--keepalive-count");
+ exit(EXIT_FAILURE);
+ }
+ ctl->keepalive_count = keepalive;
+ break;
+ case 'l':
+ vshCloseLogFile(ctl);
+ ctl->logfile = vshStrdup(ctl, optarg);
+ vshOpenLogFile(ctl);
+ break;
+ case 'q':
+ ctl->quiet = true;
+ break;
+ case 't':
+ ctl->timing = true;
+ break;
+ case 'r':
+ ctl->readonly = true;
+ break;
+ case 'v':
+ if (STRNEQ_NULLABLE(optarg, "long")) {
+ puts(VERSION);
+ exit(EXIT_SUCCESS);
+ }
+ /* fall through */
+ case 'V':
+ vshShowVersion(ctl);
+ exit(EXIT_SUCCESS);
+ case ':':
+ for (i = 0; opt[i].name != NULL; i++) {
+ if (opt[i].val == optopt)
+ break;
+ }
+ if (opt[i].name)
+ vshError(ctl, _("option '-%c'/'--%s' requires an
argument"),
+ optopt, opt[i].name);
+ else
+ vshError(ctl, _("option '-%c' requires an argument"),
optopt);
+ exit(EXIT_FAILURE);
+ case '?':
+ if (optopt)
+ vshError(ctl, _("unsupported option '-%c'. See
--help."), optopt);
+ else
+ vshError(ctl, _("unsupported option '%s'. See
--help."), argv[optind - 1]);
+ exit(EXIT_FAILURE);
+ default:
+ vshError(ctl, _("unknown option"));
+ exit(EXIT_FAILURE);
+ }
+ longindex = -1;
+ }
+
+ 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;
+}
+
+static const vshCmdDef virshCmds[] = {
+ {.name = "cd",
+ .handler = cmdCd,
+ .opts = opts_cd,
+ .info = info_cd,
+ .flags = VSH_CMD_FLAG_NOCONNECT
+ },
+ {.name = "connect",
+ .handler = cmdConnect,
+ .opts = opts_connect,
+ .info = info_connect,
+ .flags = VSH_CMD_FLAG_NOCONNECT
+ },
+ {.name = "echo",
+ .handler = cmdEcho,
+ .opts = opts_echo,
+ .info = info_echo,
+ .flags = VSH_CMD_FLAG_NOCONNECT
+ },
+ {.name = "exit",
+ .handler = cmdQuit,
+ .opts = NULL,
+ .info = info_quit,
+ .flags = VSH_CMD_FLAG_NOCONNECT
+ },
+ {.name = "help",
+ .handler = cmdHelp,
+ .opts = opts_help,
+ .info = info_help,
+ .flags = VSH_CMD_FLAG_NOCONNECT
+ },
+ {.name = "pwd",
+ .handler = cmdPwd,
+ .opts = NULL,
+ .info = info_pwd,
+ .flags = VSH_CMD_FLAG_NOCONNECT
+ },
+ {.name = "quit",
+ .handler = cmdQuit,
+ .opts = NULL,
+ .info = info_quit,
+ .flags = VSH_CMD_FLAG_NOCONNECT
+ },
+ {.name = NULL}
+};
+
+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}
+};
+
+int
+main(int argc, char **argv)
+{
+ vshControl _ctl, *ctl = &_ctl;
+ const 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 = "^]"; /* Same default as telnet */
+
+ /* In order to distinguish default from setting to 0 */
+ ctl->keepalive_interval = -1;
+ ctl->keepalive_count = -1;
+
+ ctl->eventPipe[0] = -1;
+ ctl->eventPipe[1] = -1;
+ ctl->eventTimerId = -1;
+
+ 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 (isatty(STDIN_FILENO)) {
+ ctl->istty = true;
+
+#ifndef WIN32
+ if (tcgetattr(STDIN_FILENO, &ctl->termattr) < 0)
+ ctl->istty = false;
+#endif
+ }
+
+ 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;
+ }
+
+ virFileActivateDirOverride(argv[0]);
+
+ if (!(progname = strrchr(argv[0], '/')))
+ progname = argv[0];
+ else
+ progname++;
+
+ if ((defaultConn = virGetEnvBlockSUID("VIRSH_DEFAULT_CONNECT_URI")))
+ ctl->name = vshStrdup(ctl, defaultConn);
+
+ vshInitDebug(ctl);
+
+ if (!vshParseArgv(ctl, argc, argv) ||
+ !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 WITH_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);
+}
diff --git a/tools/vsh.h b/tools/vsh.h
new file mode 100644
index 0000000..345eb99
--- /dev/null
+++ b/tools/vsh.h
@@ -0,0 +1,527 @@
+/*
+ * vsh.h: common data to be used by clients to exercise the libvirt API
+ *
+ * Copyright (C) 2005, 2007-2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <
http://www.gnu.org/licenses/>.
+ *
+ * Daniel Veillard <veillard(a)redhat.com>
+ * Karel Zak <kzak(a)redhat.com>
+ * Daniel P. Berrange <berrange(a)redhat.com>
+ */
+
+#ifndef VSH_H
+# define VSH_H
+
+# include <stdio.h>
+# include <stdlib.h>
+# include <string.h>
+# include <stdarg.h>
+# include <unistd.h>
+# include <sys/stat.h>
+# include <termios.h>
+
+# include "internal.h"
+# include "virerror.h"
+# include "virthread.h"
+
+# define VSH_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 VSH_MATCH(FLAG) (flags & (FLAG))
+
+/**
+ * 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 */
+};
+
+/* forward declarations */
+typedef struct _vshCmd vshCmd;
+typedef struct _vshCmdDef vshCmdDef;
+typedef struct _vshCmdGrp vshCmdGrp;
+typedef struct _vshCmdInfo vshCmdInfo;
+typedef struct _vshCmdOpt vshCmdOpt;
+typedef struct _vshCmdOptDef vshCmdOptDef;
+typedef struct _vshControl vshControl;
+typedef struct _vshCtrlData vshCtrlData;
+
+typedef char **(*vshCompleter)(unsigned int flags);
+
+/*
+ * vshCmdInfo -- name/value pair for information about command
+ *
+ * Commands should have at least the following names:
+ * "help" - short description of command
+ * "desc" - description of command, or empty string
+ */
+struct _vshCmdInfo {
+ const char *name; /* name of information, or NULL for list end */
+ const char *data; /* non-NULL information */
+};
+
+/*
+ * vshCmdOptDef - command option definition
+ */
+struct _vshCmdOptDef {
+ 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 */
+ vshCompleter completer; /* option completer */
+ unsigned int completer_flags; /* option completer flags */
+};
+
+/*
+ * vshCmdOpt - command options
+ *
+ * After parsing a command, all arguments to the command have been
+ * collected into a list of these objects.
+ */
+struct _vshCmdOpt {
+ const vshCmdOptDef *def; /* non-NULL pointer to option definition */
+ char *data; /* allocated data, or NULL for bool option */
+ vshCmdOpt *next;
+};
+
+/*
+ * 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
+ */
+struct _vshCmdDef {
+ 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 */
+};
+
+/*
+ * vshCmd - parsed command
+ */
+struct _vshCmd {
+ const vshCmdDef *def; /* command definition */
+ vshCmdOpt *opts; /* list of command arguments */
+ vshCmd *next; /* next command */
+};
+
+/*
+ * vshControl
+ */
+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 */
+ bool blockJobNoBytes; /* true if _BANDWIDTH_BYTE blockjob flags
+ are missing */
+ virThread eventLoop;
+ virMutex lock;
+ bool eventLoopStarted;
+ bool quit;
+ int eventPipe[2]; /* Write-to-self pipe to end waiting for an
+ * event to occur */
+ int eventTimerId; /* id of event loop timeout registration */
+
+ const char *escapeChar; /* String representation of
+ console escape character */
+
+ int keepalive_interval; /* Client keepalive interval */
+ int keepalive_count; /* Client keepalive count */
+
+# ifndef WIN32
+ struct termios termattr; /* settings of the tty terminal */
+# endif
+ bool istty; /* is the terminal a tty */
+};
+
+struct _vshCmdGrp {
+ const char *name; /* name of group, or NULL for list end */
+ const char *keyword; /* help keyword */
+ const vshCmdDef *commands;
+};
+
+void vshError(vshControl *ctl, const char *format, ...)
+ ATTRIBUTE_FMT_PRINTF(2, 3);
+void vshOpenLogFile(vshControl *ctl);
+void vshOutputLogFile(vshControl *ctl, int log_level, const char *format,
+ va_list ap)
+ ATTRIBUTE_FMT_PRINTF(3, 0);
+void vshCloseLogFile(vshControl *ctl);
+
+virConnectPtr vshConnect(vshControl *ctl, const char *uri, bool readonly);
+
+const char *vshCmddefGetInfo(const vshCmdDef *cmd, const char *info);
+const vshCmdDef *vshCmddefSearch(const char *cmdname);
+bool vshCmddefHelp(vshControl *ctl, const char *name);
+const vshCmdGrp *vshCmdGrpSearch(const char *grpname);
+bool vshCmdGrpHelp(vshControl *ctl, const char *name);
+
+int vshCommandOptInt(vshControl *ctl, const vshCmd *cmd,
+ const char *name, int *value)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+int vshCommandOptUInt(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned int *value)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+int vshCommandOptUIntWrap(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned int *value)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+int vshCommandOptUL(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned long *value)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+int vshCommandOptULWrap(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned long *value)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+int vshCommandOptString(vshControl *ctl, const vshCmd *cmd,
+ const char *name, const char **value)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+int vshCommandOptStringReq(vshControl *ctl, const vshCmd *cmd,
+ const char *name, const char **value)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+int vshCommandOptLongLong(vshControl *ctl, const vshCmd *cmd,
+ const char *name, long long *value)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+int vshCommandOptULongLong(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned long long *value)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+int vshCommandOptULongLongWrap(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned long long *value)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+int vshCommandOptScaledInt(vshControl *ctl, const vshCmd *cmd,
+ const char *name, unsigned long long *value,
+ int scale, unsigned long long max)
+ ATTRIBUTE_NONNULL(4) ATTRIBUTE_RETURN_CHECK;
+bool vshCommandOptBool(const vshCmd *cmd, const char *name);
+const vshCmdOpt *vshCommandOptArgv(vshControl *ctl, const vshCmd *cmd,
+ const vshCmdOpt *opt);
+int vshCommandOptTimeoutToMs(vshControl *ctl, const vshCmd *cmd, int *timeout);
+
+/* Filter flags for various vshCommandOpt*By() functions */
+typedef enum {
+ VSH_BYID = (1 << 1),
+ VSH_BYUUID = (1 << 2),
+ VSH_BYNAME = (1 << 3),
+ VSH_BYMAC = (1 << 4),
+} vshLookupByFlags;
+
+/* 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);
+int vshTreePrint(vshControl *ctl, vshTreeLookup lookup, void *opaque,
+ int num_devices, int devid);
+
+void vshPrintExtra(vshControl *ctl, const char *format, ...)
+ ATTRIBUTE_FMT_PRINTF(2, 3);
+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__)
+
+/* User visible sort, so we want locale-specific case comparison. */
+# define vshStrcasecmp(S1, S2) strcasecmp(S1, S2)
+int vshNameSorter(const void *a, const void *b);
+
+int vshDomainState(vshControl *ctl, virDomainPtr dom, int *reason);
+virTypedParameterPtr vshFindTypedParamByName(const char *name,
+ virTypedParameterPtr list,
+ int count);
+char *vshGetTypedParamValue(vshControl *ctl, virTypedParameterPtr item)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
+char *vshEditWriteToTempFile(vshControl *ctl, const char *doc);
+int vshEditFile(vshControl *ctl, const char *filename);
+char *vshEditReadBackFile(vshControl *ctl, const char *filename);
+int vshAskReedit(vshControl *ctl, const char *msg, bool relax_avail);
+int vshStreamSink(virStreamPtr st, const char *bytes, size_t nbytes,
+ void *opaque);
+double vshPrettyCapacity(unsigned long long val, const char **unit);
+int vshStringToArray(const char *str, char ***array);
+
+/* Typedefs, function prototypes for job progress reporting.
+ * There are used by some long lingering commands like
+ * migrate, dump, save, managedsave.
+ */
+struct _vshCtrlData {
+ vshControl *ctl;
+ const vshCmd *cmd;
+ int writefd;
+ virConnectPtr dconn;
+};
+
+/* error handling */
+extern virErrorPtr last_error;
+void vshReportError(vshControl *ctl);
+void vshResetLibvirtError(void);
+void vshSaveLibvirtError(void);
+
+/* terminal modifications */
+bool vshTTYIsInterruptCharacter(vshControl *ctl, const char chr);
+int vshTTYDisableInterrupt(vshControl *ctl);
+int vshTTYRestore(vshControl *ctl);
+int vshTTYMakeRaw(vshControl *ctl, bool report_errors);
+bool vshTTYAvailable(vshControl *ctl);
+
+/* waiting for events */
+enum {
+ VSH_EVENT_INTERRUPT,
+ VSH_EVENT_TIMEOUT,
+ VSH_EVENT_DONE,
+};
+int vshEventStart(vshControl *ctl, int timeout_ms);
+void vshEventDone(vshControl *ctl);
+int vshEventWait(vshControl *ctl);
+void vshEventCleanup(vshControl *ctl);
+
+/* allocation wrappers */
+void *_vshMalloc(vshControl *ctl, size_t sz, const char *filename, int line);
+# define vshMalloc(_ctl, _sz) _vshMalloc(_ctl, _sz, __FILE__, __LINE__)
+
+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__)
+
+char *_vshStrdup(vshControl *ctl, const char *s, const char *filename,
+ int line);
+# define vshStrdup(_ctl, _s) _vshStrdup(_ctl, _s, __FILE__, __LINE__)
+
+/* 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
+
+/* Macros to help dealing with mutually exclusive options. */
+
+/* VSH_EXCLUSIVE_OPTIONS_EXPR:
+ *
+ * @NAME1: String containing the name of the option.
+ * @EXPR1: Expression to validate the variable (boolean variable)
+ * @NAME2: String containing the name of the option.
+ * @EXPR2: Expression to validate the variable (boolean variable)
+ *
+ * Reject mutually exclusive command options in virsh. Use the
+ * provided expression to check the variables.
+ *
+ * This helper does an early return and therefore it has to be called
+ * before anything that would require cleanup.
+ */
+# define VSH_EXCLUSIVE_OPTIONS_EXPR(NAME1, EXPR1, NAME2, EXPR2) \
+ if ((EXPR1) && (EXPR2)) { \
+ vshError(ctl, _("Options --%s and --%s are mutually exclusive"), \
+ NAME1, NAME2); \
+ return false; \
+ }
+
+/* VSH_EXCLUSIVE_OPTIONS:
+ *
+ * @NAME1: String containing the name of the option.
+ * @NAME2: String containing the name of the option.
+ *
+ * Reject mutually exclusive command options in virsh. Use the
+ * vshCommandOptBool call to request them.
+ *
+ * This helper does an early return and therefore it has to be called
+ * before anything that would require cleanup.
+ */
+# define VSH_EXCLUSIVE_OPTIONS(NAME1, NAME2) \
+ VSH_EXCLUSIVE_OPTIONS_EXPR(NAME1, vshCommandOptBool(cmd, NAME1), \
+ NAME2, vshCommandOptBool(cmd, NAME2))
+
+/* VSH_EXCLUSIVE_OPTIONS_VAR:
+ *
+ * @VARNAME1: Boolean variable containing the value of the option of same name
+ * @VARNAME2: Boolean variable containing the value of the option of same name
+ *
+ * Reject mutually exclusive command options in virsh. Check in variables that
+ * contain the value and have same name as the option.
+ *
+ * This helper does an early return and therefore it has to be called
+ * before anything that would require cleanup.
+ */
+# define VSH_EXCLUSIVE_OPTIONS_VAR(VARNAME1, VARNAME2) \
+ VSH_EXCLUSIVE_OPTIONS_EXPR(#VARNAME1, VARNAME1, #VARNAME2, VARNAME2)
+
+/* Macros to help dealing with required options. */
+
+/* VSH_REQUIRE_OPTION_EXPR:
+ *
+ * @NAME1: String containing the name of the option.
+ * @EXPR1: Expression to validate the variable (boolean variable).
+ * @NAME2: String containing the name of required option.
+ * @EXPR2: Expression to validate the variable (boolean variable).
+ *
+ * Check if required command options in virsh was set. Use the
+ * provided expression to check the variables.
+ *
+ * This helper does an early return and therefore it has to be called
+ * before anything that would require cleanup.
+ */
+# define VSH_REQUIRE_OPTION_EXPR(NAME1, EXPR1, NAME2, EXPR2) \
+ do { \
+ if ((EXPR1) && !(EXPR2)) { \
+ vshError(ctl, _("Option --%s is required by option --%s"), \
+ NAME2, NAME1); \
+ return false; \
+ } \
+ } while (0)
+
+/* VSH_REQUIRE_OPTION:
+ *
+ * @NAME1: String containing the name of the option.
+ * @NAME2: String containing the name of required option.
+ *
+ * Check if required command options in virsh was set. Use the
+ * vshCommandOptBool call to request them.
+ *
+ * This helper does an early return and therefore it has to be called
+ * before anything that would require cleanup.
+ */
+# define VSH_REQUIRE_OPTION(NAME1, NAME2) \
+ VSH_REQUIRE_OPTION_EXPR(NAME1, vshCommandOptBool(cmd, NAME1), \
+ NAME2, vshCommandOptBool(cmd, NAME2))
+
+/* VSH_REQUIRE_OPTION_VAR:
+ *
+ * @VARNAME1: Boolean variable containing the value of the option of same name.
+ * @VARNAME2: Boolean variable containing the value of required option of
+ * same name.
+ *
+ * Check if required command options in virsh was set. Check in variables
+ * that contain the value and have same name as the option.
+ *
+ * This helper does an early return and therefore it has to be called
+ * before anything that would require cleanup.
+ */
+# define VSH_REQUIRE_OPTION_VAR(VARNAME1, VARNAME2) \
+ VSH_REQUIRE_OPTION_EXPR(#VARNAME1, VARNAME1, #VARNAME2, VARNAME2)
+
+#endif /* VSH_H */
--
1.9.3