[libvirt] virsh bash completion file

Hi, I've been trying out a bash autocompletion file by Geoff Low (slight hack by me, don't blame him for my hack), and it's working pretty nicely. I'm not sure where to put it in the git tree, but it seems like it'd be nice to have upstream? thanks, -serge ==================================================== #!/bin/bash ############################################################################# # # virsh bash completion # Author: Geoff Low (glow@cmedresearch.com) # ############################################################################ VIRSH=$(which virsh) function get_main_option_list { # assume options are specified as [--option1 | --option2 | ...] OPTIONS=$(${VIRSH} help | grep '|' | sed -e 's/.*\[\(.*\)\]/\1/g;s/|//g') } function get_option_list { # get the options for a choice local option=$1; # assume options are specified as [--option1 | --option2 | ...] OPTIONS=$(${VIRSH} help ${option} 2> /dev/null | grep '|' | sed -e 's/.*\[\(.*\)\]/\1/g;s/|//g') } function _virsh_complete() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" cnxn="" i=0 while [[ $i -lt $COMP_CWORD ]]; do if [ ${COMP_WORDS[i]} = "-c" ]; then tgt=`echo ${COMP_LINE} | sed -e 's/^.* -c \([^ \t]*\).*$/\1/'` cnxn="-c $tgt" break fi let i++ done if [ ! -z $tgt ]; then if [ $COMP_CWORD -eq 5 ]; then prev="${COMP_WORDS[0]}" fi fi _all_domains=$(${VIRSH} ${cnxn} --q list --all 2> /dev/null | grep -E "(running|shut off)$" | awk '{print $2,$3}') all_domains=$(echo "$_all_domains" | cut -d " " -f 1) all_networks=$(${VIRSH} --q net-list --all 2> /dev/null | grep 'active' | cut -d " " -f 1) live_domains=$(echo "$_all_domains" | grep -E "running$" | awk '{print $1}') shutoff_domains=$(echo "$_all_domains" | grep -E "shut$" | awk '{print $1}') case "${prev}" in virsh | --help) get_main_option_list ALL_OPTS=$(${VIRSH} -q help 2> /dev/null | sed '1,2d' | awk '{print $1}') COMPREPLY=( $(compgen -W "$OPTIONS $ALL_OPTS" -- ${cur}) ) ;; list) get_option_list ${prev} COMPREPLY=( $(compgen -W "$OPTIONS" -- ${cur}) ) ;; autostart | dominfo | domuuid | domid | dominfo | domname | dumpxml | setmem ) get_option_list ${prev} COMPREPLY=( $(compgen -W "$OPTIONS $all_domains" -- ${cur}) ) ;; start | undefine) get_option_list ${prev} COMPREPLY=( $(compgen -W "$OPTIONS $shutoff_domains" -- ${cur}) ) ;; create | define) get_option_list ${prev} xml_files=$(ls *.xml) COMPREPLY=( $(compgen -W "$OPTIONS $xml_files" -- ${cur}) ) ;; shutdown | domstate | console | destroy | reboot | save | suspend | resume | vcpuinfo | vncdisplay) get_option_list ${prev} COMPREPLY=( $(compgen -W "$OPTIONS $live_domains" -- ${cur}) ) ;; connect) get_option_list ${prev} URI_TEMPLATES="xen:// qemu:// test:// xen+ssh:// test+tcp:// qemu+unix:// qemu+tls:// qemu+ssh://" COMPREPLY=( $(compgen -W "$OPTIONS $URI_TEMPLATES" -- ${cur}) ) ;; *) COMPREPLY=( $(compgen -f -- ${cur}) ) return 0 ;; esac } complete -F _virsh_complete virsh

On 10/05/2011 02:17 PM, Serge E. Hallyn wrote:
Hi,
I've been trying out a bash autocompletion file by Geoff Low (slight hack by me, don't blame him for my hack), and it's working pretty nicely. I'm not sure where to put it in the git tree, but it seems like it'd be nice to have upstream?
What I'd rather have upstream is: virsh completion args... which outputs one string per line of valid completions given the context of args. Then we could leverage a single completion code both from bash and from the virsh interactive shell; also, you'd only have to write the bash completion routines once (figure out how to make it call into 'virsh completion'), rather than chasing a moving target (update the bash completion every time new virsh commands and flags are added).
VIRSH=$(which virsh)
Not quite right. Rather, this should be an add-on to the bash-completion project, at which point you are writing a dropin for /etc/bash_completion.d/virsh, and where you start life with: # helper functions... have virsh && _virsh() { ... main completion } && complete -F _virsh virsh
function get_main_option_list
Don't use this obsolete syntax. Stick to the POSIX function syntax (look at existing files in /etc/bash_completion.d for pointers, or see my above example of _virsh(){}).
{ # assume options are specified as [--option1 | --option2 | ...] OPTIONS=$(${VIRSH} help | grep '|' | sed -e 's/.*\[\(.*\)\]/\1/g;s/|//g')
To some extent, bash_completion already offers some helper functions that can already do this work for most applications, without having to reinvent the wheel. Furthermore, this doesn't work - it matches nothing: $ virsh help | grep '|' $ While I'd love to see better bash completion support, I think that we should be going about it by fixing virsh to make it easier to query what completions make sense, so I'm not going to spend much time further reviewing this. Of course, others are free to use this in the interim, while we still lack decent virsh support. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Quoting Eric Blake (eblake@redhat.com):
While I'd love to see better bash completion support, I think that we should be going about it by fixing virsh to make it easier to query what completions make sense, so I'm not going to spend much time further reviewing this. Of course, others are free to use this
Thanks, Eric, makes sense. -serge

On Wed, Oct 05, 2011 at 02:33:44PM -0600, Eric Blake wrote:
On 10/05/2011 02:17 PM, Serge E. Hallyn wrote:
Hi,
I've been trying out a bash autocompletion file by Geoff Low (slight hack by me, don't blame him for my hack), and it's working pretty nicely. I'm not sure where to put it in the git tree, but it seems like it'd be nice to have upstream?
What I'd rather have upstream is:
virsh completion args...
which outputs one string per line of valid completions given the context of args. Then we could leverage a single completion code both from bash and from the virsh interactive shell; also, you'd only have to write the bash completion routines once (figure out how to make it call into 'virsh completion'), rather than chasing a moving target (update the bash completion every time new virsh commands and flags are added).
The patch I wrote tried todo something similar to that, adding a bunch of hidden '_complete-XXX' commands. https://www.redhat.com/archives/libvir-list/2008-July/msg00175.html it was still a little bit too manual for my liking though. It'd be nice if we could do a completion command that is properly metadata driven from the command arg type data Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Wed, Oct 05, 2011 at 03:17:47PM -0500, Serge E. Hallyn wrote:
Hi,
I've been trying out a bash autocompletion file by Geoff Low (slight hack by me, don't blame him for my hack), and it's working pretty nicely. I'm not sure where to put it in the git tree, but it seems like it'd be nice to have upstream?
David Lutterkort previously suggested this https://www.redhat.com/archives/libvir-list/2007-October/msg00231.html And I didn't ever notice that and so wrote one myself https://www.redhat.com/archives/libvir-list/2008-July/msg00175.html https://www.redhat.com/archives/libvir-list/2008-July/msg00177.html Neither ever got committed. There were some things I didn't much like about mine, but I can't really remember now. Third time lucky perhaps :-) Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Quoting Daniel P. Berrange (berrange@redhat.com):
On Wed, Oct 05, 2011 at 03:17:47PM -0500, Serge E. Hallyn wrote:
Hi,
I've been trying out a bash autocompletion file by Geoff Low (slight hack by me, don't blame him for my hack), and it's working pretty nicely. I'm not sure where to put it in the git tree, but it seems like it'd be nice to have upstream?
David Lutterkort previously suggested this
https://www.redhat.com/archives/libvir-list/2007-October/msg00231.html
And I didn't ever notice that and so wrote one myself
https://www.redhat.com/archives/libvir-list/2008-July/msg00175.html https://www.redhat.com/archives/libvir-list/2008-July/msg00177.html
Neither ever got committed. There were some things I didn't much like about mine, but I can't really remember now.
Third time lucky perhaps :-)
So the things you didn't much like must not have been *too* bad? :) Are you going to make some changes and resubmit, or would you like me to test (and port as needed) the above patches first? thanks, -serge

On Thu, Oct 06, 2011 at 08:30:55AM -0500, Serge E. Hallyn wrote:
Quoting Daniel P. Berrange (berrange@redhat.com):
On Wed, Oct 05, 2011 at 03:17:47PM -0500, Serge E. Hallyn wrote:
Hi,
I've been trying out a bash autocompletion file by Geoff Low (slight hack by me, don't blame him for my hack), and it's working pretty nicely. I'm not sure where to put it in the git tree, but it seems like it'd be nice to have upstream?
David Lutterkort previously suggested this
https://www.redhat.com/archives/libvir-list/2007-October/msg00231.html
And I didn't ever notice that and so wrote one myself
https://www.redhat.com/archives/libvir-list/2008-July/msg00175.html https://www.redhat.com/archives/libvir-list/2008-July/msg00177.html
Neither ever got committed. There were some things I didn't much like about mine, but I can't really remember now.
Third time lucky perhaps :-)
So the things you didn't much like must not have been *too* bad? :) Are you going to make some changes and resubmit, or would you like me to test (and port as needed) the above patches first?
I don't have time right now to update my own patches. You, or anyone else, are free to take them forward as desired... Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Quoting Daniel P. Berrange (berrange@redhat.com):
On Thu, Oct 06, 2011 at 08:30:55AM -0500, Serge E. Hallyn wrote:
Quoting Daniel P. Berrange (berrange@redhat.com):
On Wed, Oct 05, 2011 at 03:17:47PM -0500, Serge E. Hallyn wrote:
Hi,
I've been trying out a bash autocompletion file by Geoff Low (slight hack by me, don't blame him for my hack), and it's working pretty nicely. I'm not sure where to put it in the git tree, but it seems like it'd be nice to have upstream?
David Lutterkort previously suggested this
https://www.redhat.com/archives/libvir-list/2007-October/msg00231.html
And I didn't ever notice that and so wrote one myself
https://www.redhat.com/archives/libvir-list/2008-July/msg00175.html https://www.redhat.com/archives/libvir-list/2008-July/msg00177.html
Neither ever got committed. There were some things I didn't much like about mine, but I can't really remember now.
Third time lucky perhaps :-)
So the things you didn't much like must not have been *too* bad? :) Are you going to make some changes and resubmit, or would you like me to test (and port as needed) the above patches first?
I don't have time right now to update my own patches. You, or anyone else, are free to take them forward as desired...
Well I've got the patch ported so that it mostly works - autocompletes for commands and vm names at least. (ugly patch against 0.9.8 attached just for conversation) But I can't figure out how one should do uri completion. Any hints on where to get such a list now? thanks, -serge Index: libvirt-0.9.8/include/libvirt/libvirt.h =================================================================== --- libvirt-0.9.8.orig/include/libvirt/libvirt.h 2011-12-08 06:58:49.000000000 +0000 +++ libvirt-0.9.8/include/libvirt/libvirt.h 2012-02-08 03:52:16.008232120 +0000 @@ -1065,6 +1065,7 @@ unsigned int flags); int virConnectRef (virConnectPtr conn); int virConnectClose (virConnectPtr conn); +char ** virConnectURIs (int *nuris); const char * virConnectGetType (virConnectPtr conn); int virConnectGetVersion (virConnectPtr conn, unsigned long *hvVer); Index: libvirt-0.9.8/include/libvirt/libvirt.h.in =================================================================== --- libvirt-0.9.8.orig/include/libvirt/libvirt.h.in 2011-12-06 08:38:51.000000000 +0000 +++ libvirt-0.9.8/include/libvirt/libvirt.h.in 2012-02-08 03:52:35.132225305 +0000 @@ -1065,6 +1065,7 @@ unsigned int flags); int virConnectRef (virConnectPtr conn); int virConnectClose (virConnectPtr conn); +char ** virConnectURIs (int *nuris); const char * virConnectGetType (virConnectPtr conn); int virConnectGetVersion (virConnectPtr conn, unsigned long *hvVer); Index: libvirt-0.9.8/libvirt.spec.in =================================================================== --- libvirt-0.9.8.orig/libvirt.spec.in 2011-12-08 06:54:48.000000000 +0000 +++ libvirt-0.9.8/libvirt.spec.in 2012-02-08 03:54:03.300230906 +0000 @@ -1050,6 +1050,7 @@ %{_unitdir}/libvirtd.service %endif %doc daemon/libvirtd.upstart +%{_sysconfdir}/bash_completion.d/virsh %config(noreplace) %{_sysconfdir}/sysconfig/libvirtd %config(noreplace) %{_sysconfdir}/libvirt/libvirtd.conf %if 0%{?fedora} >= 14 || 0%{?rhel} >= 6 Index: libvirt-0.9.8/src/Makefile.am =================================================================== --- libvirt-0.9.8.orig/src/Makefile.am 2012-02-08 03:46:38.000000000 +0000 +++ libvirt-0.9.8/src/Makefile.am 2012-02-08 20:51:04.340232096 +0000 @@ -22,7 +22,9 @@ $(COVERAGE_CFLAGS) AM_LDFLAGS = $(COVERAGE_LDFLAGS) -EXTRA_DIST = $(conf_DATA) util/keymaps.csv +bashdir = $(sysconfdir)/bash_completion.d + +EXTRA_DIST = $(conf_DATA) util/keymaps.csv virsh.bash BUILT_SOURCES = CLEANFILES = @@ -1584,6 +1586,8 @@ $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml && \ rm $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml.t; } endif + $(MKDIR_P) "$(DESTDIR)$(bashdir)" + $(INSTALL_DATA) "$(srcdir)/virsh.bash" "$(DESTDIR)$(bashdir)/virsh" uninstall-local:: rmdir "$(DESTDIR)$(localstatedir)/cache/libvirt" ||: @@ -1621,6 +1625,7 @@ rmdir "$(DESTDIR)$(localstatedir)/run/libvirt/network" ||: endif rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt" ||: + rm -f "$(DESTDIR)$(bashdir)/virsh" ||: CLEANFILES += *.gcov .libs/*.gcda .libs/*.gcno *.gcno *.gcda *.i *.s DISTCLEANFILES += $(GENERATED_SYM_FILES) Index: libvirt-0.9.8/src/libvirt.c =================================================================== --- libvirt-0.9.8.orig/src/libvirt.c 2011-12-08 02:26:59.000000000 +0000 +++ libvirt-0.9.8/src/libvirt.c 2012-02-09 04:13:30.168233577 +0000 @@ -1317,6 +1317,77 @@ } /** + * virConnectURIs: + * @nuris: pointer to an integer in which to store the number of URIS + * + * This functions returns the list of available connection + * URIs. The caller is responsible for freeing the returned + * strings, and the array they're stored in. + * + * Returns the URIs in case of success or NULL case of error. + */ +char ** +virConnectURIs(int *nuris) +{ + char **ret = NULL; + int i; + int n = 0; + + if (!initialized) + if (virInitialize() < 0) + return NULL; + + for (i = 0; i < virDriverTabCount; i++) { + virConnectPtr conn; + int res; + + printf("URI %d: 1, name %s\n", i, virDriverTab[i]->name); + conn = virGetConnect(); + if (!conn) + goto no_memory; + printf("URI %d: 2\n", i); + res = virDriverTab[i]->open (conn, NULL, 0); + printf("URI %d: 3\n", i); + if (res != VIR_DRV_OPEN_SUCCESS) + goto next; + printf("URI %d: 4\n", i); + if (!conn->uri) + goto next; + printf("URI %d: 5\n", i); + if (VIR_REALLOC_N(ret, n+1) < 0) { + printf("URI %d: 6\n", i); + virConnectClose(conn); + goto no_memory; + } + printf("URI %d: 7, conn->uri is\n", i, conn->uri); + if ((ret[n++] = strdup(conn->uri)) == NULL) { + printf("URI %d: 8\n", i); + virConnectClose(conn); + goto no_memory; + } + printf("URI %d: 9\n", i); +next: + printf("URI %d: 10\n", i); + virConnectClose(conn); + } + + *nuris = n; + printf("URI %d: 11\n", *nuris); + for (i=0; i<n; i++) + printf("uri %d: %s\n", i, ret[i]); + return ret; + +no_memory: + printf("URI %d: 12\n", *nuris); + for (i = 0 ; i < n ; i++) + VIR_FREE(ret[i]); + VIR_FREE(ret); + *nuris = 0; + virLibConnError(VIR_ERR_NO_MEMORY, __FUNCTION__); + return NULL; +} + +/** * virConnectOpen: * @name: URI of the hypervisor * Index: libvirt-0.9.8/src/libvirt_public.syms =================================================================== --- libvirt-0.9.8.orig/src/libvirt_public.syms 2011-12-02 03:59:50.000000000 +0000 +++ libvirt-0.9.8/src/libvirt_public.syms 2012-02-08 04:03:52.360265205 +0000 @@ -506,6 +506,7 @@ virDomainGetBlockIoTune; virDomainSetBlockIoTune; virNodeSuspendForDuration; + virConnectURIs; } LIBVIRT_0.9.7; # .... define new API here using predicted next version number .... Index: libvirt-0.9.8/src/virsh.bash =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ libvirt-0.9.8/src/virsh.bash 2012-02-08 22:56:26.536232538 +0000 @@ -0,0 +1,83 @@ +_virsh() +{ + local cur prev cmd cmd_index arg arg_index i results skipconn + local virsh="$1" + local canonical=0 + + COMPREPLY=() + cur="$2" + prev="$3" + + if [[ $COMP_CWORD > 1 && ${COMP_WORDS[COMP_CWORD-1]} = '--connect' ]]; then + results="$($virsh _complete-uri)" || nets="" + fi + + skipconn=0 + if [ -z "$results" ]; then + # searching for the command name + for ((i=1; $i<=$COMP_CWORD; i++)); do + if [[ ${COMP_WORDS[i]} != -* ]]; then + if [[ ${COMP_WORDS[i-1]} = '--connect' ]]; then + echo "${COMP_WORDS[i]}" | grep '///' >/dev/null 2>&1 + test "$?" = 1 && skipconn=1 + test "$skipconn" = 0 && virsh="$virsh ${COMP_WORDS[i-1]} ${COMP_WORDS[i]}" + else + if [[ $i < $COMP_CWORD ]]; then + cmd="${COMP_WORDS[i]}" + cmd_index=$i + arg_index=`expr $COMP_CWORD - $cmd_index` + break + fi + fi + fi + done + + if [[ "$cur" == -* ]]; then + # Generate args - global or command specific + results="$($virsh _complete-command options "$cmd")" + else + if [ -z "$cmd" ]; then + # No command set, so generate list of all commands + results="$($virsh _complete-command commands)" || commands="" + else + # Command set, to generate command specific args + n=0 + for i in "$($virsh _complete-command arguments "$cmd")" + do + n=`expr $n + 1` + if [ $n = $arg_index ]; then + arg=$i + break + fi + done + + case $arg in + file) + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -o "default" -- "$cur")) + ;; + + domain) + test "$skipconn" = 0 && results="$($virsh _complete-domain)" || doms="" + ;; + + network) + test "$skipconn" = 0 && results="$($virsh _complete-network)" || nets="" + ;; + + pool) + test "$skipconn" = 0 && results="$($virsh _complete-pool)" || nets="" + ;; + + uri) + results="$($virsh _complete-uri)" || nets="" + ;; + esac + fi + fi + fi + if [ ! -z "$results" ]; then + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$results' -- "$cur")) + fi +} + +complete -F _virsh virsh Index: libvirt-0.9.8/tools/virsh.c =================================================================== --- libvirt-0.9.8.orig/tools/virsh.c 2012-02-08 03:46:38.000000000 +0000 +++ libvirt-0.9.8/tools/virsh.c 2012-02-08 22:31:14.928233016 +0000 @@ -623,6 +623,414 @@ */ /* + * "_compcmds" command + */ +static vshCmdInfo info_compcommand[] = { + {"syntax", "_complete-command [commands|arguments|options] [<command>]"}, + {"help", gettext_noop("print shell completion data")}, + {"desc", gettext_noop("Prints data useful for shell autocompletion of commands.")}, + + {NULL, NULL} +}; + +static vshCmdOptDef opts_compcommand[] = { + {"type", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("type of data")}, + {"cmd", VSH_OT_DATA, 0, gettext_noop("command name")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdCompCommand(vshControl * ctl, const vshCmd * cmd) +{ + const char *type; + const char *name; + + if (vshCommandOptString(cmd, "type", &type) <= 0) + type = "commands"; + if (vshCommandOptString(cmd, "cmd", &name) <= 0) + name = NULL; + + if (STREQ(type, "commands")) { + const vshCmdGrp *grp; + const vshCmdDef *def; + for (grp = cmdGroups; grp->name; grp++) { + for (def = grp->commands; def->name; def++) + if (def->name[0] != '_') + vshPrint(ctl, "%s\n", def->name); + } + } else if (STREQ(type, "options")) { + if (name) { + const vshCmdDef *def = vshCmddefSearch(name); + if (!def) { + vshError(ctl, _("command '%s' doesn't exist"), name); + return false; + } else if (def->opts) { + const vshCmdOptDef *opt; + for (opt = def->opts; opt->name; opt++) + if (opt->type == VSH_OT_BOOL || + opt->type == VSH_OT_INT || + opt->type == VSH_OT_STRING) + vshPrint(ctl, "--%s\n", opt->name); + } + } else { + vshPrint(ctl, + "-c\n--connect\n" + "-r\n--readonly\n" + "-d\n--debug\n" + "-h\n--help\n" + "-q\n--quiet\n" + "-t\n--timing\n" + "-l\n--log\n" + "-v\n--version\n"); + } + } else if (STREQ(type, "arguments")) { + if (!name) { + vshError(ctl, "%s", _("no command specified")); + return false; + } else { + const vshCmdDef *def = vshCmddefSearch(name); + if (!def) { + vshError(ctl, _("command '%s' doesn't exist"), name); + return false; + } else if (def->opts) { + const vshCmdOptDef *opt; + for (opt = def->opts; opt->name; opt++) + if (opt->type == VSH_OT_DATA) + vshPrint(ctl, "%s\n", opt->name); + } + } + } + return true; +} + +/* + * "_compuris" command + */ +static vshCmdInfo info_compuri[] = { + {"syntax", "_complete-uri"}, + {"help", gettext_noop("print shell completion data for URIs")}, + {"desc", gettext_noop("Prints data useful for shell autocompletion of URIs.")}, + + {NULL, NULL} +}; + +static vshCmdOptDef opts_compuri[] = { + {NULL, 0, 0, NULL} +}; + +static bool +cmdCompUri(vshControl * ctl, const vshCmd * cmd ATTRIBUTE_UNUSED) +{ + char **uris = NULL; + int nuris, i; + + uris = virConnectURIs(&nuris); + if (!uris) { + vshError(ctl, "%s", _("Failed to list connection URIs")); + return false; + } + for (i = 0 ; i < nuris ; i++) { + vshPrint(ctl, "%s\n", uris[i]); + free(uris[i]); + } + + free(uris); + return true; +} +/* + * "_compdomains" command + */ +static vshCmdInfo info_compdomain[] = { + {"syntax", "_complete-domain"}, + {"help", gettext_noop("print shell completion data for domains")}, + {"desc", gettext_noop("Prints data useful for shell autocompletion of domains.")}, + + {NULL, NULL} +}; + +static vshCmdOptDef opts_compdomain[] = { + {NULL, 0, 0, NULL} +}; + +static bool +cmdCompDomain(vshControl * ctl, const vshCmd * cmd ATTRIBUTE_UNUSED) +{ + int *ids = NULL; + char **names = NULL; + int maxname = 0, maxid = 0, i; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + maxid = virConnectNumOfDomains(ctl->conn); + if (maxid < 0) { + vshError(ctl, "%s", _("Failed to list active domains")); + return false; + } + if (maxid) { + ids = vshMalloc(ctl, sizeof(int) * maxid); + + if ((maxid = virConnectListDomains(ctl->conn, &ids[0], maxid)) < 0) { + vshError(ctl, "%s", _("Failed to list active domains")); + free(ids); + return false; + } + + qsort(&ids[0], maxid, sizeof(int), idsorter); + } + maxname = virConnectNumOfDefinedDomains(ctl->conn); + if (maxname < 0) { + vshError(ctl, "%s", _("Failed to list inactive domains")); + free(ids); + return false; + } + if (maxname) { + names = vshMalloc(ctl, sizeof(char *) * maxname); + + if ((maxname = virConnectListDefinedDomains(ctl->conn, names, maxname)) < 0) { + vshError(ctl, "%s", _("Failed to list inactive domains")); + free(ids); + free(names); + return false; + } + + qsort(&names[0], maxname, sizeof(char*), namesorter); + } + + for (i = 0; i < maxid; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virDomainPtr dom = virDomainLookupByID(ctl->conn, ids[i]); + /* this kind of work with domains is not atomic operation */ + if (!dom) + continue; + virDomainGetUUIDString(dom, uuid); + vshPrint(ctl, "%d\n%s\n%s\n", + virDomainGetID(dom), + virDomainGetName(dom), + uuid); + virDomainFree(dom); + } + for (i = 0; i < maxname; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virDomainPtr dom = virDomainLookupByName(ctl->conn, names[i]); + + /* this kind of work with domains is not atomic operation */ + if (!dom) { + free(names[i]); + continue; + } + virDomainGetUUIDString(dom, uuid); + vshPrint(ctl, "%s\n%s\n", + virDomainGetName(dom), + uuid); + virDomainFree(dom); + free(names[i]); + } + free(ids); + free(names); + return true; +} + +/* + * "_compnetworks" command + */ +static vshCmdInfo info_compnetwork[] = { + {"syntax", "_complete-network"}, + {"help", gettext_noop("print shell completion data for networks")}, + {"desc", gettext_noop("Prints data useful for shell autocompletion of networks.")}, + + {NULL, NULL} +}; + +static vshCmdOptDef opts_compnetwork[] = { + {NULL, 0, 0, NULL} +}; + +static bool +cmdCompNetwork(vshControl * ctl, const vshCmd * cmd ATTRIBUTE_UNUSED) +{ + char **activeNames = NULL, **inactiveNames = NULL; + int maxactive = 0, maxinactive = 0, i; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + maxactive = virConnectNumOfNetworks(ctl->conn); + if (maxactive < 0) { + vshError(ctl, "%s", _("Failed to list active networks")); + return false; + } + if (maxactive) { + activeNames = vshMalloc(ctl, sizeof(char *) * maxactive); + + if ((maxactive = virConnectListNetworks(ctl->conn, activeNames, + maxactive)) < 0) { + vshError(ctl, "%s", _("Failed to list active networks")); + free(activeNames); + return false; + } + + qsort(&activeNames[0], maxactive, sizeof(char *), namesorter); + } + maxinactive = virConnectNumOfDefinedNetworks(ctl->conn); + if (maxinactive < 0) { + vshError(ctl, "%s", _("Failed to list inactive networks")); + free(activeNames); + return false; + } + if (maxinactive) { + inactiveNames = vshMalloc(ctl, sizeof(char *) * maxinactive); + + if ((maxinactive = virConnectListDefinedNetworks(ctl->conn, inactiveNames, maxinactive)) < 0) { + vshError(ctl, "%s", _("Failed to list inactive networks")); + free(activeNames); + free(inactiveNames); + return false; + } + + qsort(&inactiveNames[0], maxinactive, sizeof(char*), namesorter); + } + + for (i = 0; i < maxactive; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virNetworkPtr network = virNetworkLookupByName(ctl->conn, activeNames[i]); + + /* this kind of work with networks is not atomic operation */ + if (!network) { + free(activeNames[i]); + continue; + } + virNetworkGetUUIDString(network, uuid); + vshPrint(ctl, "%s\n%s\n", + virNetworkGetName(network), + uuid); + virNetworkFree(network); + free(activeNames[i]); + } + for (i = 0; i < maxinactive; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virNetworkPtr network = virNetworkLookupByName(ctl->conn, inactiveNames[i]); + + /* this kind of work with networks is not atomic operation */ + if (!network) { + free(inactiveNames[i]); + continue; + } + virNetworkGetUUIDString(network, uuid); + vshPrint(ctl, "%s\n%s\n", + virNetworkGetName(network), + uuid); + + virNetworkFree(network); + free(inactiveNames[i]); + } + free(activeNames); + free(inactiveNames); + + return true; +} + + +/* + * "_comppools" command + */ +static vshCmdInfo info_comppool[] = { + {"syntax", "_complete-pool"}, + {"help", gettext_noop("print shell completion data for pools")}, + {"desc", gettext_noop("Prints data useful for shell autocompletion of pools.")}, + + {NULL, NULL} +}; + +static vshCmdOptDef opts_comppool[] = { + {NULL, 0, 0, NULL} +}; + +static bool +cmdCompPool(vshControl * ctl, const vshCmd * cmd ATTRIBUTE_UNUSED) +{ + char **activeNames = NULL, **inactiveNames = NULL; + int maxactive = 0, maxinactive = 0, i; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + maxactive = virConnectNumOfStoragePools(ctl->conn); + if (maxactive < 0) { + vshError(ctl, "%s", _("Failed to list active pools")); + return false; + } + if (maxactive) { + activeNames = vshMalloc(ctl, sizeof(char *) * maxactive); + + if ((maxactive = virConnectListStoragePools(ctl->conn, activeNames, + maxactive)) < 0) { + vshError(ctl, "%s", _("Failed to list active pools")); + free(activeNames); + return false; + } + + qsort(&activeNames[0], maxactive, sizeof(char *), namesorter); + } + maxinactive = virConnectNumOfDefinedStoragePools(ctl->conn); + if (maxinactive < 0) { + vshError(ctl, "%s", _("Failed to list inactive pools")); + free(activeNames); + return false; + } + if (maxinactive) { + inactiveNames = vshMalloc(ctl, sizeof(char *) * maxinactive); + + if ((maxinactive = virConnectListDefinedStoragePools(ctl->conn, inactiveNames, maxinactive)) < 0) { + vshError(ctl, "%s", _("Failed to list inactive pools")); + free(activeNames); + free(inactiveNames); + return false; + } + + qsort(&inactiveNames[0], maxinactive, sizeof(char*), namesorter); + } + + for (i = 0; i < maxactive; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virStoragePoolPtr pool = virStoragePoolLookupByName(ctl->conn, activeNames[i]); + + /* this kind of work with pools is not atomic operation */ + if (!pool) { + free(activeNames[i]); + continue; + } + virStoragePoolGetUUIDString(pool, uuid); + vshPrint(ctl, "%s\n%s\n", + virStoragePoolGetName(pool), + uuid); + virStoragePoolFree(pool); + free(activeNames[i]); + } + for (i = 0; i < maxinactive; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virStoragePoolPtr pool = virStoragePoolLookupByName(ctl->conn, inactiveNames[i]); + + /* this kind of work with pools is not atomic operation */ + if (!pool) { + free(inactiveNames[i]); + continue; + } + virStoragePoolGetUUIDString(pool, uuid); + vshPrint(ctl, "%s\n%s\n", + virStoragePoolGetName(pool), + uuid); + + virStoragePoolFree(pool); + free(inactiveNames[i]); + } + free(activeNames); + free(inactiveNames); + + return true; +} + +/* * "help" command */ static const vshCmdInfo info_help[] = { @@ -654,7 +1062,8 @@ grp->keyword); for (def = grp->commands; def->name; def++) - vshPrint(ctl, " %-30s %s\n", def->name, + if (def->name[0] != '_') + vshPrint(ctl, " %-30s %s\n", def->name, _(vshCmddefGetInfo(def, "help"))); vshPrint(ctl, "\n"); @@ -15335,6 +15744,11 @@ }; static const vshCmdDef virshCmds[] = { + {"_complete-command", cmdCompCommand, opts_compcommand, info_compcommand, VSH_CMD_FLAG_NOCONNECT}, + {"_complete-uri", cmdCompUri, opts_compuri, info_compuri, VSH_CMD_FLAG_NOCONNECT}, + {"_complete-domain", cmdCompDomain, opts_compdomain, info_compdomain, VSH_CMD_FLAG_NOCONNECT}, + {"_complete-network", cmdCompNetwork, opts_compnetwork, info_compnetwork, VSH_CMD_FLAG_NOCONNECT}, + {"_complete-pool", cmdCompPool, opts_comppool, info_comppool, VSH_CMD_FLAG_NOCONNECT}, {"cd", cmdCd, opts_cd, info_cd, VSH_CMD_FLAG_NOCONNECT}, {"echo", cmdEcho, opts_echo, info_echo, VSH_CMD_FLAG_NOCONNECT}, {"exit", cmdQuit, NULL, info_quit, VSH_CMD_FLAG_NOCONNECT}, @@ -16606,6 +17020,7 @@ vshCommandArgvParse(vshControl *ctl, int nargs, char **argv) { vshCommandParser parser; + bool ret; if (nargs <= 0) return false; @@ -16613,7 +17028,20 @@ parser.arg_pos = argv; parser.arg_end = argv + nargs; parser.getNextArg = vshCommandArgvGetArg; - return vshCommandParse(ctl, &parser); + ret = vshCommandParse(ctl, &parser); + + /* Special case 'help' to avoid virConnectOpen */ + if (ctl->cmd && + ctl->cmd->def && + ctl->cmd->def->name && + (STREQ(ctl->cmd->def->name, "help") || + STREQ(ctl->cmd->def->name, "_complete-command") || + STREQ(ctl->cmd->def->name, "_complete-uri"))) { + ret = vshCommandRun(ctl, ctl->cmd); + exit(ret ? EXIT_SUCCESS : EXIT_FAILURE); + } + + return ret; } /* ---------------------- Index: libvirt-0.9.8/python/generator.py =================================================================== --- libvirt-0.9.8.orig/python/generator.py 2011-12-02 03:59:49.000000000 +0000 +++ libvirt-0.9.8/python/generator.py 2012-02-08 20:01:59.268229464 +0000 @@ -224,6 +224,7 @@ qemu_functions_failed = [] functions_skipped = [ "virConnectListDomains", + "virConnectURIs", ] qemu_functions_skipped = [] @@ -340,6 +341,7 @@ 'virConnectGetVersion', 'virConnectGetLibVersion', 'virConnectListDomainsID', + 'virConnectURIs', 'virConnectListDefinedDomains', 'virConnectListNetworks', 'virConnectListDefinedNetworks', @@ -428,6 +430,7 @@ # functions than those already listed skip_function = ( 'virConnectListDomains', # Python API is called virConectListDomainsID for unknown reasons + 'virConnectURIs', 'virConnSetErrorFunc', # Not used in Python API XXX is this a bug ? 'virResetError', # Not used in Python API XXX is this a bug ? 'virGetVersion', # Python C code is manually written Index: libvirt-0.9.8/src/Makefile.in =================================================================== --- libvirt-0.9.8.orig/src/Makefile.in 2012-02-08 03:46:38.000000000 +0000 +++ libvirt-0.9.8/src/Makefile.in 2012-02-08 20:51:29.580231787 +0000 @@ -2548,6 +2548,8 @@ AM_LDFLAGS = $(COVERAGE_LDFLAGS) +bashdir = $(sysconfdir)/bash_completion.d + # Add all conditional sources just in case... EXTRA_DIST = $(conf_DATA) util/keymaps.csv $(srcdir)/util/virkeymaps.h \ $(srcdir)/util/keymaps.csv $(srcdir)/util/virkeycode-mapgen.py \ @@ -2586,7 +2588,7 @@ rpc/gensystemtap.pl rpc/virnetprotocol.x \ rpc/virkeepaliveprotocol.x $(am__append_137) $(am__append_139) \ $(STORAGE_HELPER_DISK_SOURCES) $(LXC_CONTROLLER_SOURCES) \ - $(SECURITY_DRIVER_APPARMOR_HELPER_SOURCES) + $(SECURITY_DRIVER_APPARMOR_HELPER_SOURCES) virsh.bash BUILT_SOURCES = $(ESX_DRIVER_GENERATED) $(HYPERV_DRIVER_GENERATED) \ $(GENERATED_SYM_FILES) $(am__append_128) $(am__append_133) \ $(VIR_NET_RPC_GENERATED) @@ -7016,6 +7018,8 @@ @WITH_NETWORK_TRUE@ cp $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml.t \ @WITH_NETWORK_TRUE@ $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml && \ @WITH_NETWORK_TRUE@ rm $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml.t; } + $(MKDIR_P) "$(DESTDIR)$(bashdir)" + $(INSTALL_DATA) "$(srcdir)/virsh.bash" "$(DESTDIR)$(bashdir)/virsh" uninstall-local:: rmdir "$(DESTDIR)$(localstatedir)/cache/libvirt" ||: @@ -7041,6 +7045,7 @@ @WITH_NETWORK_TRUE@ rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt/network" ||: @WITH_NETWORK_TRUE@ rmdir "$(DESTDIR)$(localstatedir)/run/libvirt/network" ||: rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt" ||: + rm -f "$(DESTDIR)$(bashdir)/virsh" ||: # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded.

Ok, here is a patch which works for me. (I do see that I'll need to change some of the files edited to deal with the pre-autoconf tree, but I wanted to get it out there) I considered adding a name field to the virDriver entries, but the WITH_REMOTE combinations would get trick, and the cheap way I did it here has its own advantages. If this looks generally ok, and if noone else gets to it first, the next time I have some time I'll send a patch against the uptodate git tree. thanks, -serge Index: libvirt-0.9.8/include/libvirt/libvirt.h =================================================================== --- libvirt-0.9.8.orig/include/libvirt/libvirt.h 2011-12-08 06:58:49.000000000 +0000 +++ libvirt-0.9.8/include/libvirt/libvirt.h 2012-02-14 16:07:54.145332561 +0000 @@ -1065,6 +1065,7 @@ unsigned int flags); int virConnectRef (virConnectPtr conn); int virConnectClose (virConnectPtr conn); +char ** virConnectURIs (int *nuris); const char * virConnectGetType (virConnectPtr conn); int virConnectGetVersion (virConnectPtr conn, unsigned long *hvVer); Index: libvirt-0.9.8/include/libvirt/libvirt.h.in =================================================================== --- libvirt-0.9.8.orig/include/libvirt/libvirt.h.in 2011-12-06 08:38:51.000000000 +0000 +++ libvirt-0.9.8/include/libvirt/libvirt.h.in 2012-02-14 16:07:54.145332561 +0000 @@ -1065,6 +1065,7 @@ unsigned int flags); int virConnectRef (virConnectPtr conn); int virConnectClose (virConnectPtr conn); +char ** virConnectURIs (int *nuris); const char * virConnectGetType (virConnectPtr conn); int virConnectGetVersion (virConnectPtr conn, unsigned long *hvVer); Index: libvirt-0.9.8/libvirt.spec.in =================================================================== --- libvirt-0.9.8.orig/libvirt.spec.in 2011-12-08 06:54:48.000000000 +0000 +++ libvirt-0.9.8/libvirt.spec.in 2012-02-14 16:07:54.145332561 +0000 @@ -1050,6 +1050,7 @@ %{_unitdir}/libvirtd.service %endif %doc daemon/libvirtd.upstart +%{_sysconfdir}/bash_completion.d/virsh %config(noreplace) %{_sysconfdir}/sysconfig/libvirtd %config(noreplace) %{_sysconfdir}/libvirt/libvirtd.conf %if 0%{?fedora} >= 14 || 0%{?rhel} >= 6 Index: libvirt-0.9.8/src/Makefile.am =================================================================== --- libvirt-0.9.8.orig/src/Makefile.am 2012-02-14 16:04:38.000000000 +0000 +++ libvirt-0.9.8/src/Makefile.am 2012-02-14 16:07:54.145332561 +0000 @@ -22,7 +22,9 @@ $(COVERAGE_CFLAGS) AM_LDFLAGS = $(COVERAGE_LDFLAGS) -EXTRA_DIST = $(conf_DATA) util/keymaps.csv +bashdir = $(sysconfdir)/bash_completion.d + +EXTRA_DIST = $(conf_DATA) util/keymaps.csv virsh.bash BUILT_SOURCES = CLEANFILES = @@ -1584,6 +1586,8 @@ $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml && \ rm $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml.t; } endif + $(MKDIR_P) "$(DESTDIR)$(bashdir)" + $(INSTALL_DATA) "$(srcdir)/virsh.bash" "$(DESTDIR)$(bashdir)/virsh" uninstall-local:: rmdir "$(DESTDIR)$(localstatedir)/cache/libvirt" ||: @@ -1621,6 +1625,7 @@ rmdir "$(DESTDIR)$(localstatedir)/run/libvirt/network" ||: endif rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt" ||: + rm -f "$(DESTDIR)$(bashdir)/virsh" ||: CLEANFILES += *.gcov .libs/*.gcda .libs/*.gcno *.gcno *.gcda *.i *.s DISTCLEANFILES += $(GENERATED_SYM_FILES) Index: libvirt-0.9.8/src/libvirt.c =================================================================== --- libvirt-0.9.8.orig/src/libvirt.c 2011-12-08 02:26:59.000000000 +0000 +++ libvirt-0.9.8/src/libvirt.c 2012-02-14 16:51:20.997376629 +0000 @@ -1316,6 +1316,43 @@ return NULL; } +char *v_uris[] = { +#ifdef WITH_XEN + "xen://", +#ifdef WITH_REMOTE + "xen+ssh://", +#endif +#endif +#ifdef WITH_LXC + "lxc://", +#endif +#ifdef WITH_QEMU + "qemu://", +#ifdef WITH_REMOTE + "qemu+unix://", + "qemu+tls://", + "qemu+ssh://", +#endif +#endif +}; + +/** + * virConnectURIs: + * @nuris: pointer to an integer in which to store the number of URIS + * + * This functions returns the list of available connection + * URIs. The caller is responsible for freeing the returned + * strings, and the array they're stored in. + * + * Returns the URIs in case of success or NULL case of error. + */ +char ** +virConnectURIs(int *nuris) +{ + *nuris = ARRAY_CARDINALITY(v_uris); + return v_uris; +} + /** * virConnectOpen: * @name: URI of the hypervisor Index: libvirt-0.9.8/src/libvirt_public.syms =================================================================== --- libvirt-0.9.8.orig/src/libvirt_public.syms 2011-12-02 03:59:50.000000000 +0000 +++ libvirt-0.9.8/src/libvirt_public.syms 2012-02-14 16:07:54.149332488 +0000 @@ -506,6 +506,7 @@ virDomainGetBlockIoTune; virDomainSetBlockIoTune; virNodeSuspendForDuration; + virConnectURIs; } LIBVIRT_0.9.7; # .... define new API here using predicted next version number .... Index: libvirt-0.9.8/src/virsh.bash =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ libvirt-0.9.8/src/virsh.bash 2012-02-14 16:07:54.149332488 +0000 @@ -0,0 +1,89 @@ +_virsh() +{ + local cur prev cmd cmd_index arg arg_index i results skipconn + local virsh="$1" + local canonical=0 + + COMP_WORDBREAKS=${COMP_WORDBREAKS//:} + + COMPREPLY=() + cur="$2" + prev="$3" + + if [ $COMP_CWORD > 1 ]; then + c="${COMP_WORDS[COMP_CWORD-1]}" + if [ $c = '-c' -o $c = '--connect' ]; then + results="$($virsh _complete-uri)" || nets="" + fi + fi + + skipconn=0 + if [ -z "$results" ]; then + # searching for the command name + for ((i=1; $i<=$COMP_CWORD; i++)); do + if [[ ${COMP_WORDS[i]} != -* ]]; then + c="${COMP_WORDS[i-1]}" + if [[ $c == '-c' || $c == '--connect' ]]; then + test "$?" = 1 && skipconn=1 + test "$skipconn" -eq 0 && virsh="$virsh ${COMP_WORDS[i-1]} ${COMP_WORDS[i]}" + cmd="" + else + if [[ $i < $COMP_CWORD ]]; then + cmd="${COMP_WORDS[i]}" + cmd_index=$i + arg_index=`expr $COMP_CWORD - $cmd_index` + break + fi + fi + fi + done + + if [[ "$cur" == -* ]]; then + # Generate args - global or command specific + results="$($virsh _complete-command options "$cmd")" + else + if [ -z "$cmd" ]; then + # No command set, so generate list of all commands + results="$($virsh _complete-command commands)" || commands="" + else + # Command set, to generate command specific args + n=0 + for i in "$($virsh _complete-command arguments "$cmd")" + do + n=`expr $n + 1` + if [ $n = $arg_index ]; then + arg=$i + break + fi + done + + case $arg in + file) + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -o "default" -- "$cur")) + ;; + + domain) + test "$skipconn" = 0 && results="$($virsh _complete-domain)" || doms="" + ;; + + network) + test "$skipconn" = 0 && results="$($virsh _complete-network)" || nets="" + ;; + + pool) + test "$skipconn" = 0 && results="$($virsh _complete-pool)" || nets="" + ;; + + uri) + results="$($virsh _complete-uri)" || nets="" + ;; + esac + fi + fi + fi + if [ ! -z "$results" ]; then + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$results' -- "$cur")) + fi +} + +complete -F _virsh virsh Index: libvirt-0.9.8/tools/virsh.c =================================================================== --- libvirt-0.9.8.orig/tools/virsh.c 2012-02-14 16:04:38.000000000 +0000 +++ libvirt-0.9.8/tools/virsh.c 2012-02-14 16:07:54.153332478 +0000 @@ -623,6 +623,411 @@ */ /* + * "_compcmds" command + */ +static vshCmdInfo info_compcommand[] = { + {"syntax", "_complete-command [commands|arguments|options] [<command>]"}, + {"help", gettext_noop("print shell completion data")}, + {"desc", gettext_noop("Prints data useful for shell autocompletion of commands.")}, + + {NULL, NULL} +}; + +static vshCmdOptDef opts_compcommand[] = { + {"type", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("type of data")}, + {"cmd", VSH_OT_DATA, 0, gettext_noop("command name")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdCompCommand(vshControl * ctl, const vshCmd * cmd) +{ + const char *type; + const char *name; + + if (vshCommandOptString(cmd, "type", &type) <= 0) + type = "commands"; + if (vshCommandOptString(cmd, "cmd", &name) <= 0) + name = NULL; + + if (STREQ(type, "commands")) { + const vshCmdGrp *grp; + const vshCmdDef *def; + for (grp = cmdGroups; grp->name; grp++) { + for (def = grp->commands; def->name; def++) + if (def->name[0] != '_') + vshPrint(ctl, "%s\n", def->name); + } + } else if (STREQ(type, "options")) { + if (name) { + const vshCmdDef *def = vshCmddefSearch(name); + if (!def) { + vshError(ctl, _("command '%s' doesn't exist"), name); + return false; + } else if (def->opts) { + const vshCmdOptDef *opt; + for (opt = def->opts; opt->name; opt++) + if (opt->type == VSH_OT_BOOL || + opt->type == VSH_OT_INT || + opt->type == VSH_OT_STRING) + vshPrint(ctl, "--%s\n", opt->name); + } + } else { + vshPrint(ctl, + "-c\n--connect\n" + "-r\n--readonly\n" + "-d\n--debug\n" + "-h\n--help\n" + "-q\n--quiet\n" + "-t\n--timing\n" + "-l\n--log\n" + "-v\n--version\n"); + } + } else if (STREQ(type, "arguments")) { + if (!name) { + vshError(ctl, "%s", _("no command specified")); + return false; + } else { + const vshCmdDef *def = vshCmddefSearch(name); + if (!def) { + vshError(ctl, _("command '%s' doesn't exist"), name); + return false; + } else if (def->opts) { + const vshCmdOptDef *opt; + for (opt = def->opts; opt->name; opt++) + if (opt->type == VSH_OT_DATA) + vshPrint(ctl, "%s\n", opt->name); + } + } + } + return true; +} + +/* + * "_compuris" command + */ +static vshCmdInfo info_compuri[] = { + {"syntax", "_complete-uri"}, + {"help", gettext_noop("print shell completion data for URIs")}, + {"desc", gettext_noop("Prints data useful for shell autocompletion of URIs.")}, + + {NULL, NULL} +}; + +static vshCmdOptDef opts_compuri[] = { + {NULL, 0, 0, NULL} +}; + +static bool +cmdCompUri(vshControl * ctl, const vshCmd * cmd ATTRIBUTE_UNUSED) +{ + char **uris = NULL; + int nuris, i; + + uris = virConnectURIs(&nuris); + if (!uris) { + vshError(ctl, "%s", _("Failed to list connection URIs")); + return false; + } + for (i = 0 ; i < nuris ; i++) + vshPrint(ctl, "%s\n", uris[i]); + + return true; +} +/* + * "_compdomains" command + */ +static vshCmdInfo info_compdomain[] = { + {"syntax", "_complete-domain"}, + {"help", gettext_noop("print shell completion data for domains")}, + {"desc", gettext_noop("Prints data useful for shell autocompletion of domains.")}, + + {NULL, NULL} +}; + +static vshCmdOptDef opts_compdomain[] = { + {NULL, 0, 0, NULL} +}; + +static bool +cmdCompDomain(vshControl * ctl, const vshCmd * cmd ATTRIBUTE_UNUSED) +{ + int *ids = NULL; + char **names = NULL; + int maxname = 0, maxid = 0, i; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + maxid = virConnectNumOfDomains(ctl->conn); + if (maxid < 0) { + vshError(ctl, "%s", _("Failed to list active domains")); + return false; + } + if (maxid) { + ids = vshMalloc(ctl, sizeof(int) * maxid); + + if ((maxid = virConnectListDomains(ctl->conn, &ids[0], maxid)) < 0) { + vshError(ctl, "%s", _("Failed to list active domains")); + free(ids); + return false; + } + + qsort(&ids[0], maxid, sizeof(int), idsorter); + } + maxname = virConnectNumOfDefinedDomains(ctl->conn); + if (maxname < 0) { + vshError(ctl, "%s", _("Failed to list inactive domains")); + free(ids); + return false; + } + if (maxname) { + names = vshMalloc(ctl, sizeof(char *) * maxname); + + if ((maxname = virConnectListDefinedDomains(ctl->conn, names, maxname)) < 0) { + vshError(ctl, "%s", _("Failed to list inactive domains")); + free(ids); + free(names); + return false; + } + + qsort(&names[0], maxname, sizeof(char*), namesorter); + } + + for (i = 0; i < maxid; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virDomainPtr dom = virDomainLookupByID(ctl->conn, ids[i]); + /* this kind of work with domains is not atomic operation */ + if (!dom) + continue; + virDomainGetUUIDString(dom, uuid); + vshPrint(ctl, "%d\n%s\n%s\n", + virDomainGetID(dom), + virDomainGetName(dom), + uuid); + virDomainFree(dom); + } + for (i = 0; i < maxname; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virDomainPtr dom = virDomainLookupByName(ctl->conn, names[i]); + + /* this kind of work with domains is not atomic operation */ + if (!dom) { + free(names[i]); + continue; + } + virDomainGetUUIDString(dom, uuid); + vshPrint(ctl, "%s\n%s\n", + virDomainGetName(dom), + uuid); + virDomainFree(dom); + free(names[i]); + } + free(ids); + free(names); + return true; +} + +/* + * "_compnetworks" command + */ +static vshCmdInfo info_compnetwork[] = { + {"syntax", "_complete-network"}, + {"help", gettext_noop("print shell completion data for networks")}, + {"desc", gettext_noop("Prints data useful for shell autocompletion of networks.")}, + + {NULL, NULL} +}; + +static vshCmdOptDef opts_compnetwork[] = { + {NULL, 0, 0, NULL} +}; + +static bool +cmdCompNetwork(vshControl * ctl, const vshCmd * cmd ATTRIBUTE_UNUSED) +{ + char **activeNames = NULL, **inactiveNames = NULL; + int maxactive = 0, maxinactive = 0, i; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + maxactive = virConnectNumOfNetworks(ctl->conn); + if (maxactive < 0) { + vshError(ctl, "%s", _("Failed to list active networks")); + return false; + } + if (maxactive) { + activeNames = vshMalloc(ctl, sizeof(char *) * maxactive); + + if ((maxactive = virConnectListNetworks(ctl->conn, activeNames, + maxactive)) < 0) { + vshError(ctl, "%s", _("Failed to list active networks")); + free(activeNames); + return false; + } + + qsort(&activeNames[0], maxactive, sizeof(char *), namesorter); + } + maxinactive = virConnectNumOfDefinedNetworks(ctl->conn); + if (maxinactive < 0) { + vshError(ctl, "%s", _("Failed to list inactive networks")); + free(activeNames); + return false; + } + if (maxinactive) { + inactiveNames = vshMalloc(ctl, sizeof(char *) * maxinactive); + + if ((maxinactive = virConnectListDefinedNetworks(ctl->conn, inactiveNames, maxinactive)) < 0) { + vshError(ctl, "%s", _("Failed to list inactive networks")); + free(activeNames); + free(inactiveNames); + return false; + } + + qsort(&inactiveNames[0], maxinactive, sizeof(char*), namesorter); + } + + for (i = 0; i < maxactive; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virNetworkPtr network = virNetworkLookupByName(ctl->conn, activeNames[i]); + + /* this kind of work with networks is not atomic operation */ + if (!network) { + free(activeNames[i]); + continue; + } + virNetworkGetUUIDString(network, uuid); + vshPrint(ctl, "%s\n%s\n", + virNetworkGetName(network), + uuid); + virNetworkFree(network); + free(activeNames[i]); + } + for (i = 0; i < maxinactive; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virNetworkPtr network = virNetworkLookupByName(ctl->conn, inactiveNames[i]); + + /* this kind of work with networks is not atomic operation */ + if (!network) { + free(inactiveNames[i]); + continue; + } + virNetworkGetUUIDString(network, uuid); + vshPrint(ctl, "%s\n%s\n", + virNetworkGetName(network), + uuid); + + virNetworkFree(network); + free(inactiveNames[i]); + } + free(activeNames); + free(inactiveNames); + + return true; +} + + +/* + * "_comppools" command + */ +static vshCmdInfo info_comppool[] = { + {"syntax", "_complete-pool"}, + {"help", gettext_noop("print shell completion data for pools")}, + {"desc", gettext_noop("Prints data useful for shell autocompletion of pools.")}, + + {NULL, NULL} +}; + +static vshCmdOptDef opts_comppool[] = { + {NULL, 0, 0, NULL} +}; + +static bool +cmdCompPool(vshControl * ctl, const vshCmd * cmd ATTRIBUTE_UNUSED) +{ + char **activeNames = NULL, **inactiveNames = NULL; + int maxactive = 0, maxinactive = 0, i; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + maxactive = virConnectNumOfStoragePools(ctl->conn); + if (maxactive < 0) { + vshError(ctl, "%s", _("Failed to list active pools")); + return false; + } + if (maxactive) { + activeNames = vshMalloc(ctl, sizeof(char *) * maxactive); + + if ((maxactive = virConnectListStoragePools(ctl->conn, activeNames, + maxactive)) < 0) { + vshError(ctl, "%s", _("Failed to list active pools")); + free(activeNames); + return false; + } + + qsort(&activeNames[0], maxactive, sizeof(char *), namesorter); + } + maxinactive = virConnectNumOfDefinedStoragePools(ctl->conn); + if (maxinactive < 0) { + vshError(ctl, "%s", _("Failed to list inactive pools")); + free(activeNames); + return false; + } + if (maxinactive) { + inactiveNames = vshMalloc(ctl, sizeof(char *) * maxinactive); + + if ((maxinactive = virConnectListDefinedStoragePools(ctl->conn, inactiveNames, maxinactive)) < 0) { + vshError(ctl, "%s", _("Failed to list inactive pools")); + free(activeNames); + free(inactiveNames); + return false; + } + + qsort(&inactiveNames[0], maxinactive, sizeof(char*), namesorter); + } + + for (i = 0; i < maxactive; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virStoragePoolPtr pool = virStoragePoolLookupByName(ctl->conn, activeNames[i]); + + /* this kind of work with pools is not atomic operation */ + if (!pool) { + free(activeNames[i]); + continue; + } + virStoragePoolGetUUIDString(pool, uuid); + vshPrint(ctl, "%s\n%s\n", + virStoragePoolGetName(pool), + uuid); + virStoragePoolFree(pool); + free(activeNames[i]); + } + for (i = 0; i < maxinactive; i++) { + char uuid[VIR_UUID_STRING_BUFLEN]; + virStoragePoolPtr pool = virStoragePoolLookupByName(ctl->conn, inactiveNames[i]); + + /* this kind of work with pools is not atomic operation */ + if (!pool) { + free(inactiveNames[i]); + continue; + } + virStoragePoolGetUUIDString(pool, uuid); + vshPrint(ctl, "%s\n%s\n", + virStoragePoolGetName(pool), + uuid); + + virStoragePoolFree(pool); + free(inactiveNames[i]); + } + free(activeNames); + free(inactiveNames); + + return true; +} + +/* * "help" command */ static const vshCmdInfo info_help[] = { @@ -654,7 +1059,8 @@ grp->keyword); for (def = grp->commands; def->name; def++) - vshPrint(ctl, " %-30s %s\n", def->name, + if (def->name[0] != '_') + vshPrint(ctl, " %-30s %s\n", def->name, _(vshCmddefGetInfo(def, "help"))); vshPrint(ctl, "\n"); @@ -15335,6 +15741,11 @@ }; static const vshCmdDef virshCmds[] = { + {"_complete-command", cmdCompCommand, opts_compcommand, info_compcommand, VSH_CMD_FLAG_NOCONNECT}, + {"_complete-uri", cmdCompUri, opts_compuri, info_compuri, VSH_CMD_FLAG_NOCONNECT}, + {"_complete-domain", cmdCompDomain, opts_compdomain, info_compdomain, VSH_CMD_FLAG_NOCONNECT}, + {"_complete-network", cmdCompNetwork, opts_compnetwork, info_compnetwork, VSH_CMD_FLAG_NOCONNECT}, + {"_complete-pool", cmdCompPool, opts_comppool, info_comppool, VSH_CMD_FLAG_NOCONNECT}, {"cd", cmdCd, opts_cd, info_cd, VSH_CMD_FLAG_NOCONNECT}, {"echo", cmdEcho, opts_echo, info_echo, VSH_CMD_FLAG_NOCONNECT}, {"exit", cmdQuit, NULL, info_quit, VSH_CMD_FLAG_NOCONNECT}, @@ -16606,6 +17017,7 @@ vshCommandArgvParse(vshControl *ctl, int nargs, char **argv) { vshCommandParser parser; + bool ret; if (nargs <= 0) return false; @@ -16613,7 +17025,20 @@ parser.arg_pos = argv; parser.arg_end = argv + nargs; parser.getNextArg = vshCommandArgvGetArg; - return vshCommandParse(ctl, &parser); + ret = vshCommandParse(ctl, &parser); + + /* Special case 'help' to avoid virConnectOpen */ + if (ctl->cmd && + ctl->cmd->def && + ctl->cmd->def->name && + (STREQ(ctl->cmd->def->name, "help") || + STREQ(ctl->cmd->def->name, "_complete-command") || + STREQ(ctl->cmd->def->name, "_complete-uri"))) { + ret = vshCommandRun(ctl, ctl->cmd); + exit(ret ? EXIT_SUCCESS : EXIT_FAILURE); + } + + return ret; } /* ---------------------- Index: libvirt-0.9.8/python/generator.py =================================================================== --- libvirt-0.9.8.orig/python/generator.py 2011-12-02 03:59:49.000000000 +0000 +++ libvirt-0.9.8/python/generator.py 2012-02-14 16:07:54.153332478 +0000 @@ -224,6 +224,7 @@ qemu_functions_failed = [] functions_skipped = [ "virConnectListDomains", + "virConnectURIs", ] qemu_functions_skipped = [] @@ -340,6 +341,7 @@ 'virConnectGetVersion', 'virConnectGetLibVersion', 'virConnectListDomainsID', + 'virConnectURIs', 'virConnectListDefinedDomains', 'virConnectListNetworks', 'virConnectListDefinedNetworks', @@ -428,6 +430,7 @@ # functions than those already listed skip_function = ( 'virConnectListDomains', # Python API is called virConectListDomainsID for unknown reasons + 'virConnectURIs', 'virConnSetErrorFunc', # Not used in Python API XXX is this a bug ? 'virResetError', # Not used in Python API XXX is this a bug ? 'virGetVersion', # Python C code is manually written Index: libvirt-0.9.8/src/Makefile.in =================================================================== --- libvirt-0.9.8.orig/src/Makefile.in 2012-02-14 16:04:38.000000000 +0000 +++ libvirt-0.9.8/src/Makefile.in 2012-02-14 16:07:54.153332478 +0000 @@ -2548,6 +2548,8 @@ AM_LDFLAGS = $(COVERAGE_LDFLAGS) +bashdir = $(sysconfdir)/bash_completion.d + # Add all conditional sources just in case... EXTRA_DIST = $(conf_DATA) util/keymaps.csv $(srcdir)/util/virkeymaps.h \ $(srcdir)/util/keymaps.csv $(srcdir)/util/virkeycode-mapgen.py \ @@ -2586,7 +2588,7 @@ rpc/gensystemtap.pl rpc/virnetprotocol.x \ rpc/virkeepaliveprotocol.x $(am__append_137) $(am__append_139) \ $(STORAGE_HELPER_DISK_SOURCES) $(LXC_CONTROLLER_SOURCES) \ - $(SECURITY_DRIVER_APPARMOR_HELPER_SOURCES) + $(SECURITY_DRIVER_APPARMOR_HELPER_SOURCES) virsh.bash BUILT_SOURCES = $(ESX_DRIVER_GENERATED) $(HYPERV_DRIVER_GENERATED) \ $(GENERATED_SYM_FILES) $(am__append_128) $(am__append_133) \ $(VIR_NET_RPC_GENERATED) @@ -7016,6 +7018,8 @@ @WITH_NETWORK_TRUE@ cp $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml.t \ @WITH_NETWORK_TRUE@ $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml && \ @WITH_NETWORK_TRUE@ rm $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml.t; } + $(MKDIR_P) "$(DESTDIR)$(bashdir)" + $(INSTALL_DATA) "$(srcdir)/virsh.bash" "$(DESTDIR)$(bashdir)/virsh" uninstall-local:: rmdir "$(DESTDIR)$(localstatedir)/cache/libvirt" ||: @@ -7041,6 +7045,7 @@ @WITH_NETWORK_TRUE@ rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt/network" ||: @WITH_NETWORK_TRUE@ rmdir "$(DESTDIR)$(localstatedir)/run/libvirt/network" ||: rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt" ||: + rm -f "$(DESTDIR)$(bashdir)/virsh" ||: # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded.
participants (4)
-
Daniel P. Berrange
-
Eric Blake
-
Serge E. Hallyn
-
Serge Hallyn