[libvirt] [PATCH 01/13] tools: Introduce new client generic module vsh

Erik Skultety eskultet at redhat.com
Mon Jun 29 15:37:35 UTC 2015


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 at redhat.com>
+ * Karel Zak <kzak at redhat.com>
+ * Daniel P. Berrange <berrange at 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 at redhat.com>
+ * Karel Zak <kzak at redhat.com>
+ * Daniel P. Berrange <berrange at 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




More information about the libvir-list mailing list