diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | tools/Makefile.am | 26 | ||||
-rw-r--r-- | tools/virt-admin.c | 546 | ||||
-rw-r--r-- | tools/virt-admin.h | 46 |
5 files changed, 618 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore index 35edd113cd..0d12c5c156 100644 --- a/.gitignore +++ b/.gitignore @@ -183,6 +183,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/po/POTFILES.in b/po/POTFILES.in index 4dc147600d..669322858a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -276,6 +276,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 d5638d9b20..62073f9053 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,28 @@ 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) \ + $(STATIC_BINARIES) \ + $(PIE_LDFLAGS) \ + $(NULL) +virt_admin_LDADD = \ + ../src/libvirt-admin.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 0000000000..f36d2eddce --- /dev/null +++ b/tools/virt-admin.c @@ -0,0 +1,546 @@ +/* + * 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/>. + * Authors: + * Erik Skultety <eskultet@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 "configmake.h" +#include "internal.h" +#include "viralloc.h" +#include "virerror.h" +#include "virfile.h" +#include "virstring.h" +#include "virthread.h" + +/* Gnulib doesn't guarantee SA_SIGINFO support. */ +#ifndef SA_SIGINFO +# define SA_SIGINFO 0 +#endif + +#define VIRT_ADMIN_PROMPT "virt-admin # " + +static char *progname; + +static const vshCmdGrp cmdGroups[]; +static const vshClientHooks hooks; + +static int +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 -1; + } else { + if (priv->wantReconnect) + vshPrint(ctl, "%s\n", _("Reconnected to the admin server")); + else + vshPrint(ctl, "%s\n", _("Connected to the admin server")); + } + + return 0; +} + +static int +vshAdmDisconnect(vshControl *ctl) +{ + int ret = 0; + vshAdmControlPtr priv = ctl->privData; + + if (!priv->conn) + return ret; + + ret = virAdmConnectClose(priv->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")); + priv->conn = NULL; + return ret; +} + +/* + * vshAdmReconnect: + * + * Reconnect to a daemon's admin server + * + */ +static void +vshAdmReconnect(vshControl *ctl) +{ + vshAdmControlPtr priv = ctl->privData; + if (priv->conn) + priv->wantReconnect = true; + + vshAdmDisconnect(ctl); + vshAdmConnect(ctl, 0); + + priv->wantReconnect = 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_("connect to daemon's admin server") + }, + {.name = "desc", + .data = N_("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; + + if (vshCommandOptStringReq(ctl, cmd, "name", &name) < 0) + return false; + + VIR_FREE(ctl->connname); + ctl->connname = vshStrdup(ctl, name); + + vshAdmReconnect(ctl); + + return !!priv->conn; +} + +static void * +vshAdmConnectionHandler(vshControl *ctl) +{ + vshAdmControlPtr priv = ctl->privData; + + if (!priv->conn) + 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->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 void +vshAdmDeinit(vshControl *ctl) +{ + vshAdmControlPtr priv = ctl->privData; + + vshDeinit(ctl); + VIR_FREE(ctl->connname); + + if (priv->conn) + vshAdmDisconnect(ctl); + + 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); + + ctl->eventLoopStarted = false; + } + + virMutexDestroy(&ctl->lock); +} + +/* + * 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:")); +#ifdef WITH_LIBVIRTD + vshPrint(ctl, " Daemon"); +#endif +#ifdef ENABLE_DEBUG + vshPrint(ctl, " Debug"); +#endif +#if WITH_READLINE + vshPrint(ctl, " Readline"); +#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:hl: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 itself", "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->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; + } + + 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 0000000000..62228ccab1 --- /dev/null +++ b/tools/virt-admin.h @@ -0,0 +1,46 @@ +/* + * 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@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 + */ + +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 */ |