This patch introduces virt-admin client which is based on virsh client,
but had to reimplement several methods to meet virt-admin specific needs
or remove unnecessary virsh specific logic.
---
.gitignore | 1 +
daemon/libvirtd.c | 1 -
po/POTFILES.in | 1 +
tools/Makefile.am | 28 ++-
tools/virt-admin.c | 581 +++++++++++++++++++++++++++++++++++++++++++++++++++++
tools/virt-admin.h | 47 +++++
6 files changed, 656 insertions(+), 3 deletions(-)
create mode 100644 tools/virt-admin.c
create mode 100644 tools/virt-admin.h
diff --git a/.gitignore b/.gitignore
index 2d52a8f..a776947 100644
--- a/.gitignore
+++ b/.gitignore
@@ -176,6 +176,7 @@
/tools/virt-login-shell
/tools/virsh
/tools/virsh-*-edit.c
+/tools/virt-admin
/tools/virt-*-validate
/tools/virt-sanlock-cleanup
/tools/wireshark/src/plugin.c
diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c
index 250094b..26ccf59 100644
--- a/daemon/libvirtd.c
+++ b/daemon/libvirtd.c
@@ -522,7 +522,6 @@ daemonSetupNetworking(virNetServerPtr srv,
virNetServerAddService(srv, svcRO, NULL) < 0)
goto cleanup;
- /* Temporarily disabled */
if (sock_path_adm && false) {
VIR_DEBUG("Registering unix socket %s", sock_path_adm);
if (!(svcAdm = virNetServerServiceNewUNIX(sock_path_adm,
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0cc5b99..d0840f4 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -270,6 +270,7 @@ tools/virsh-pool.c
tools/virsh-secret.c
tools/virsh-snapshot.c
tools/virsh-volume.c
+tools/virt-admin.c
tools/virt-host-validate-common.c
tools/virt-host-validate-lxc.c
tools/virt-host-validate-qemu.c
diff --git a/tools/Makefile.am b/tools/Makefile.am
index d5638d9..e68fe84 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,4 +1,4 @@
-## Copyright (C) 2005-2014 Red Hat, Inc.
+## Copyright (C) 2005-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
@@ -63,7 +63,7 @@ confdir = $(sysconfdir)/libvirt
conf_DATA =
bin_SCRIPTS = virt-xml-validate virt-pki-validate
-bin_PROGRAMS = virsh virt-host-validate
+bin_PROGRAMS = virsh virt-host-validate virt-admin
libexec_SCRIPTS = libvirt-guests.sh
if WITH_SANLOCK
@@ -230,6 +230,30 @@ virsh_CFLAGS = \
$(PIE_CFLAGS) \
$(COVERAGE_CFLAGS) \
$(LIBXML_CFLAGS)
+
+virt_admin_SOURCES = \
+ virt-admin.c virt-admin.h \
+ $(NULL)
+
+virt_admin_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+virt_admin_LDADD = \
+ $(STATIC_BINARIES) \
+ $(PIE_LDFLAGS) \
+ ../src/libvirt.la \
+ ../src/libvirt-admin.la \
+ ../gnulib/lib/libgnu.la \
+ libvirt_shell.la \
+ $(LIBXML_LIBS) \
+ $(NULL)
+virt_admin_CFLAGS = \
+ $(WARN_CFLAGS) \
+ $(PIE_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(LIBXML_CFLAGS) \
+ $(READLINE_CFLAGS)
BUILT_SOURCES =
if WITH_WIN_ICON
diff --git a/tools/virt-admin.c b/tools/virt-admin.c
new file mode 100644
index 0000000..cc33b7b
--- /dev/null
+++ b/tools/virt-admin.c
@@ -0,0 +1,581 @@
+/*
+ * virt-admin.c: a shell to exercise the libvirt admin API
+ *
+ * Copyright (C) 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/>.
+ *
+ * Erik Skultety <eskultet(a)redhat.com>
+ */
+
+#include <config.h>
+#include "virt-admin.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <locale.h>
+
+#if WITH_READLINE
+# include <readline/readline.h>
+# include <readline/history.h>
+#endif
+
+#include "internal.h"
+#include "virerror.h"
+#include "viralloc.h"
+#include "virfile.h"
+#include "configmake.h"
+#include "virthread.h"
+#include "virstring.h"
+
+/* Gnulib doesn't guarantee SA_SIGINFO support. */
+#ifndef SA_SIGINFO
+# define SA_SIGINFO 0
+#endif
+
+#define VIRT_ADMIN_PROMPT "virt-admin # "
+
+#define vshAdmDisconnect(ctl, conn) vshAdmDisconnectInternal(ctl, &conn);
+
+static char *progname;
+
+static const vshCmdGrp cmdGroups[];
+static const vshClientHooks hooks;
+
+/*
+ * Detection of disconnections and automatic reconnection support
+ */
+static bool disconnected; /* we may have been disconnected */
+
+static virAdmConnectPtr
+vshAdmConnect(vshControl *ctl, unsigned int flags)
+{
+ vshAdmControlPtr priv = ctl->privData;
+
+ priv->conn = virAdmConnectOpen(ctl->connname, flags);
+
+ if (!priv->conn) {
+ if (priv->wantReconnect)
+ vshError(ctl, "%s", _("Failed to reconnect to the admin
server"));
+ else
+ vshError(ctl, "%s", _("Failed to connect to the admin
server"));
+ return NULL;
+ } else {
+ if (priv->wantReconnect)
+ vshPrint(ctl, "%s\n", _("Reconnected to the admin
server"));
+ else
+ vshPrint(ctl, "%s\n", _("Connected to the admin
server"));
+ }
+
+ return priv->conn;
+}
+
+static int
+vshAdmDisconnectInternal(vshControl *ctl, virAdmConnectPtr *conn)
+{
+ int ret = 0;
+
+ if (!*conn)
+ return ret;
+
+ ret = virAdmConnectClose(*conn);
+ if (ret < 0)
+ vshError(ctl, "%s", _("Failed to disconnect from the admin
server"));
+ else if (ret > 0)
+ vshError(ctl, "%s", _("One or more references were leaked after
"
+ "disconnect from the hypervisor"));
+ *conn = NULL;
+ return ret;
+}
+
+/*
+ * vshAdmReconnect:
+ *
+ * Reconnect to a daemon's admin server
+ *
+ */
+static void
+vshAdmReconnect(vshControl *ctl)
+{
+ vshAdmControlPtr priv = ctl->privData;
+ if (priv->conn || disconnected)
+ priv->wantReconnect = true;
+
+ vshAdmDisconnect(ctl, priv->conn);
+ priv->conn = vshAdmConnect(ctl, 0);
+
+ priv->wantReconnect = false;
+ disconnected = false;
+}
+
+/* ---------------
+ * Command Connect
+ * ---------------
+ */
+
+static const vshCmdOptDef opts_connect[] = {
+ {.name = "name",
+ .type = VSH_OT_STRING,
+ .flags = VSH_OFLAG_EMPTY_OK,
+ .help = N_("daemon's admin server connection URI")
+ },
+ {.name = NULL}
+};
+
+static const vshCmdInfo info_connect[] = {
+ {.name = "help",
+ .data = N_("(re)connect to daemon's admin server")
+ },
+ {.name = "desc",
+ .data = N_("(Re)connect to a daemon's administrating server.")
+ },
+ {.name = NULL}
+};
+
+static bool
+cmdConnect(vshControl *ctl, const vshCmd *cmd)
+{
+ const char *name = NULL;
+ vshAdmControlPtr priv = ctl->privData;
+
+ VIR_FREE(ctl->connname);
+ if (vshCommandOptStringReq(ctl, cmd, "name", &name) < 0)
+ return false;
+
+ ctl->connname = vshStrdup(ctl, name);
+
+ vshAdmReconnect(ctl);
+ if (!priv->conn)
+ return false;
+
+ return true;
+}
+
+static void *
+vshAdmConnectionHandler(vshControl *ctl)
+{
+ vshAdmControlPtr priv = ctl->privData;
+
+ if (!priv->conn || disconnected)
+ vshAdmReconnect(ctl);
+
+ if (!priv->conn) {
+ vshError(ctl, "%s", _("no valid connection"));
+ return NULL;
+ }
+
+ return priv->conn;
+}
+
+/*
+ * Initialize connection.
+ */
+static bool
+vshAdmInit(vshControl *ctl)
+{
+ vshAdmControlPtr priv = ctl->privData;
+
+ /* Since we have the commandline arguments parsed, we need to
+ * reload our initial settings to make debugging and readline
+ * work properly */
+ vshInitReload(ctl);
+
+ if (priv->conn)
+ return false;
+
+ /* set up the library error handler */
+ virSetErrorFunc(NULL, vshErrorHandler);
+
+ 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->connname) {
+ vshAdmReconnect(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 (!priv->conn) {
+ vshReportError(ctl);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void
+vshAdmDeinitTimer(int timer ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED)
+{
+ /* nothing to be done here */
+}
+
+/*
+ * Deinitialize virt-admin
+ */
+static bool
+vshAdmDeinit(vshControl *ctl)
+{
+ vshAdmControlPtr priv = ctl->privData;
+
+ vshDeinit(ctl);
+ VIR_FREE(ctl->connname);
+
+ if (priv->conn)
+ vshAdmDisconnect(ctl, priv->conn);
+
+ virResetLastError();
+
+ if (ctl->eventLoopStarted) {
+ int timer;
+
+ virMutexLock(&ctl->lock);
+ ctl->quit = true;
+ /* HACK: Add a dummy timeout to break event loop */
+ timer = virEventAddTimeout(0, vshAdmDeinitTimer, 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
+vshAdmUsage(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 daemon admin connection
URI\n"
+ " -d | --debug=NUM debug level [0-4]\n"
+ " -h | --help this help\n"
+ " -l | --log=FILE output logging to file\n"
+ " -q | --quiet quiet mode\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
+vshAdmShowVersion(vshControl *ctl ATTRIBUTE_UNUSED)
+{
+ /* FIXME - list a copyright blurb, as in GNU programs? */
+ vshPrint(ctl, _("Virt-admin 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, "\n");
+ vshPrint(ctl, "%s", _(" Miscellaneous:"));
+#ifdef WITH_LIBVIRTD
+ vshPrint(ctl, " Daemon");
+#endif
+#ifdef WITH_SECDRIVER_APPARMOR
+ vshPrint(ctl, " AppArmor");
+#endif
+#ifdef WITH_SECDRIVER_SELINUX
+ vshPrint(ctl, " SELinux");
+#endif
+#ifdef ENABLE_DEBUG
+ vshPrint(ctl, " Debug");
+#endif
+#if WITH_READLINE
+ vshPrint(ctl, " Readline");
+#endif
+#ifdef WITH_DRIVER_MODULES
+ vshPrint(ctl, " Modular");
+#endif
+ vshPrint(ctl, "\n");
+}
+
+static bool
+vshAdmParseArgv(vshControl *ctl, int argc, char **argv)
+{
+ int arg, debug;
+ size_t i;
+ int longindex = -1;
+ struct option opt[] = {
+ {"connect", required_argument, NULL, 'c'},
+ {"debug", required_argument, NULL, 'd'},
+ {"help", no_argument, NULL, 'h'},
+ {"log", required_argument, NULL, 'l'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"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 virt-admin options. */
+ while ((arg = getopt_long(argc, argv, "+:c:d:e:h:l:qvV", opt,
&longindex)) != -1) {
+ switch (arg) {
+ case 'c':
+ VIR_FREE(ctl->connname);
+ ctl->connname = 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 'h':
+ vshAdmUsage();
+ exit(EXIT_SUCCESS);
+ break;
+ case 'l':
+ vshCloseLogFile(ctl);
+ ctl->logfile = vshStrdup(ctl, optarg);
+ vshOpenLogFile(ctl);
+ break;
+ case 'q':
+ ctl->quiet = true;
+ break;
+ case 'v':
+ if (STRNEQ_NULLABLE(optarg, "long")) {
+ puts(VERSION);
+ exit(EXIT_SUCCESS);
+ }
+ /* fall through */
+ case 'V':
+ vshAdmShowVersion(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) {
+ ctl->imode = true;
+ } else {
+ /* 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 vshAdmCmds[] = {
+ VSH_CMD_CD,
+ VSH_CMD_ECHO,
+ VSH_CMD_EXIT,
+ VSH_CMD_HELP,
+ VSH_CMD_PWD,
+ VSH_CMD_QUIT,
+ {.name = "connect",
+ .handler = cmdConnect,
+ .opts = opts_connect,
+ .info = info_connect,
+ .flags = VSH_CMD_FLAG_NOCONNECT
+ },
+ {.name = NULL}
+};
+
+static const vshCmdGrp cmdGroups[] = {
+ {VIRT_ADMIN_CMD_GRP_SHELL, "virt-admin", vshAdmCmds},
+ {NULL, NULL, NULL}
+};
+
+static const vshClientHooks hooks = {
+ .connHandler = vshAdmConnectionHandler
+};
+
+int
+main(int argc, char **argv)
+{
+ vshControl _ctl, *ctl = &_ctl;
+ vshAdmControl virtAdminCtl;
+ const char *defaultConn;
+ bool ret = true;
+
+ memset(ctl, 0, sizeof(vshControl));
+ memset(&virtAdminCtl, 0, sizeof(vshAdmControl));
+ ctl->name = "virt-admin"; /* hardcoded name of the binary */
+ ctl->log_fd = -1; /* Initialize log file descriptor */
+ ctl->debug = VSH_DEBUG_DEFAULT;
+ ctl->hooks = &hooks;
+
+ ctl->eventPipe[0] = -1;
+ ctl->eventPipe[1] = -1;
+ ctl->eventTimerId = -1;
+ ctl->privData = &virtAdminCtl;
+
+ if (!(progname = strrchr(argv[0], '/')))
+ progname = argv[0];
+ else
+ progname++;
+ ctl->progname = progname;
+
+ 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 ((defaultConn = virGetEnvBlockSUID("LIBVIRT_DEFAULT_ADMIN_URI")))
+ ctl->connname = vshStrdup(ctl, defaultConn);
+
+ if (!vshInit(ctl, cmdGroups, NULL))
+ exit(EXIT_FAILURE);
+
+ if (!vshAdmParseArgv(ctl, argc, argv) ||
+ !vshAdmInit(ctl)) {
+ vshAdmDeinit(ctl);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!ctl->imode) {
+ ret = vshCommandRun(ctl, ctl->cmd);
+ } else {
+ /* interactive mode */
+ if (!ctl->quiet) {
+ vshPrint(ctl,
+ _("Welcome to %s, the administrating virtualization "
+ "interactive terminal.\n\n"),
+ progname);
+ vshPrint(ctl, "%s",
+ _("Type: 'help' for help with commands\n"
+ " 'quit' to quit\n\n"));
+ }
+
+ do {
+ ctl->cmdstr = vshReadline(ctl, VIRT_ADMIN_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 */
+ }
+
+ vshAdmDeinit(ctl);
+ exit(ret ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/tools/virt-admin.h b/tools/virt-admin.h
new file mode 100644
index 0000000..f7a8931
--- /dev/null
+++ b/tools/virt-admin.h
@@ -0,0 +1,47 @@
+/*
+ * virt-admin.h: a shell to exercise the libvirt admin API
+ *
+ * Copyright (C) 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/>.
+ *
+ * Erik Skultety <eskultet(a)redhat.com>
+ */
+
+#ifndef VIRT_ADMIN_H
+# define VIRT_ADMIN_H
+
+# include "internal.h"
+# include "vsh.h"
+
+# define VIR_FROM_THIS VIR_FROM_NONE
+
+/*
+ * Command group types
+ */
+# define VIRT_ADMIN_CMD_GRP_SHELL "Virt-admin itself"
+
+typedef struct _vshAdmControl vshAdmControl;
+typedef vshAdmControl *vshAdmControlPtr;
+
+/*
+ * adminControl
+ */
+struct _vshAdmControl {
+ virAdmConnectPtr conn; /* connection to a daemon's admin server */
+ bool wantReconnect;
+};
+
+#endif /* VIRT_ADMIN_H */
--
2.4.3