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@xxxxxxxxxx> + * Karel Zak <kzak@xxxxxxxxxx> + * Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#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@xxxxxxxxxx> + * Karel Zak <kzak@xxxxxxxxxx> + * Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list