From f1a0b1af3947a71269763e37eaba7809f59376b7 Mon Sep 17 00:00:00 2001 From: Wojtek Porczyk Date: Mon, 28 Dec 2015 18:14:37 +0100 Subject: [PATCH] qubes/tools: add qvm-run, qvm-{,un}pause Also change convention of calling main(): now command returns its numeric value instead of bool. Also fixed QSB#13 fixes QubesOS/qubes-issues#1226 --- doc/manpages/qvm-pause.rst | 34 +++++ doc/manpages/qvm-run.rst | 109 ++++++++------ doc/manpages/qvm-unpause.rst | 34 +++++ qubes/tests/int/tools/qubes_create.py | 4 +- qubes/tools/__init__.py | 45 +++++- qubes/tools/qubes_create.py | 4 +- qubes/tools/qubes_prefs.py | 14 +- qubes/tools/qvm_create.py | 4 +- qubes/tools/qvm_kill.py | 4 +- qubes/tools/qvm_ls.py | 4 +- qubes/tools/qvm_pause.py | 50 +++++++ qubes/tools/qvm_prefs.py | 14 +- qubes/tools/qvm_run.py | 133 +++++++++++++++++ qubes/tools/qvm_start.py | 4 +- qubes/tools/qvm_unpause.py | 49 +++++++ qubes/vm/qubesvm.py | 20 +-- qvm-tools/qvm-run | 199 -------------------------- rpm_spec/core-dom0.spec | 3 + 18 files changed, 437 insertions(+), 291 deletions(-) create mode 100644 doc/manpages/qvm-pause.rst create mode 100644 doc/manpages/qvm-unpause.rst create mode 100644 qubes/tools/qvm_pause.py create mode 100644 qubes/tools/qvm_run.py create mode 100644 qubes/tools/qvm_unpause.py delete mode 100755 qvm-tools/qvm-run diff --git a/doc/manpages/qvm-pause.rst b/doc/manpages/qvm-pause.rst new file mode 100644 index 00000000..15368cf6 --- /dev/null +++ b/doc/manpages/qvm-pause.rst @@ -0,0 +1,34 @@ +.. program:: qvm-pause + +:program:`qvm-pause` -- pause a domain +====================================== + +Synopsis +-------- + +:command:`qvm-pause` [-h] [--verbose] [--quiet] *VMNAME* + +Options +------- + +.. option:: --help, -h + + Show the help message and exit. + +.. option:: --verbose, -v + + Increase verbosity. + +.. option:: --quiet, -q + + Decrease verbosity. + +Authors +------- + +| Joanna Rutkowska +| Rafal Wojtczuk +| Marek Marczykowski +| Wojtek Porczyk + +.. vim: ts=3 sw=3 et tw=80 diff --git a/doc/manpages/qvm-run.rst b/doc/manpages/qvm-run.rst index 958c8cd5..94c21c32 100644 --- a/doc/manpages/qvm-run.rst +++ b/doc/manpages/qvm-run.rst @@ -1,80 +1,97 @@ .. program:: qvm-run -===================================================== -:program:`qvm-run` -- Run a command on a specified VM +:program:`qvm-run` -- Run a command in a specified VM ===================================================== Synopsis -======== -:command:`qvm-run` [*options*] [<*vm-name*>] [<*cmd*>] +-------- + +:command:`qvm-run` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--user *USER*] [--autostart] [--pass-io] [--localcmd *COMMAND*] [--gui] [--no-gui] [--colour-output *COLOR*] [--no-color-output] [--filter-escape-chars] [--no-filter-escape-chars] [*VMNAME*] *COMMAND* Options -======= +------- .. option:: --help, -h - Show this help message and exit + Show the help message and exit. + +.. option:: --verbose, -v + + Increase verbosity. .. option:: --quiet, -q - Be quiet - -.. option:: --auto, -a - - Auto start the VM if not running - -.. option:: --user=USER, -u USER - - Run command in a VM as a specified user - -.. option:: --tray - - Use tray notifications instead of stdout + Decrease verbosity. .. option:: --all - Run command on all currently running VMs (or all paused, in case of :option:`--unpause`) + Run the command on all qubes. You can use :option:`--exclude` to limit the + qubes set. Command is never run on the dom0. -.. option:: --exclude=EXCLUDE_LIST +.. option:: --exclude - When :option:`--all` is used: exclude this VM name (might be repeated) + Exclude the qube from :option:`--all`. -.. option:: --wait +.. option:: --user=USER, -u USER - Wait for the VM(s) to shutdown + Run command in a qube as *USER*. -.. option:: --shutdown +.. option:: --auto, --autostart, -a - Do 'xl shutdown' for the VM(s) (can be combined with :option:`--all` and - :option:`--wait`) - - .. deprecated:: R2 - Use :manpage:`qvm-shutdown(1)` instead. - -.. option:: --pause - - Do 'xl pause' for the VM(s) (can be combined with :option:`--all` and - :option:`--wait`) - -.. option:: --unpause - - Do 'xl unpause' for the VM(s) (can be combined with :option:`--all` and - :option:`--wait`) + Start the qube if it is not running. .. option:: --pass-io, -p - Pass stdin/stdout/stderr from remote program + Pass standard input and output to and from the remote program. -.. option:: --localcmd=LOCALCMD +.. option:: --localcmd=COMMAND - With :option:`--pass-io`, pass stdin/stdout/stderr to the given program + With :option:`--pass-io`, pass standard input and output to and from the + given program. -.. option:: --force +.. option:: --gui - Force operation, even if may damage other VMs (eg. shutdown of NetVM) + Run the command with GUI forwarding enabled, which is the default. This + switch can be used to counter :option:`--no-gui`. + +.. option:: --no-gui, --nogui + + Run the command without GUI forwarding enabled. Can be switched back with + :option:`--gui`. + +.. option:: --colour-output=COLOUR, --color-output=COLOR + + Mark the qube output with given ANSI colour (ie. "31" for red). The exact + apping of numbers to colours and styles depends of the particular terminal + emulator. + + Colouring can be disabled with :option:`--no-colour-output`. + +.. option:: --no-colour-output, --no-color-output + + Disable colouring the stdio. + +.. option:: --filter-escape-chars + + Filter terminal escape sequences (default if output is terminal). + + Terminal control characters are a security issue, which in worst case amount + to arbitrary command execution. In the simplest case this requires two often + found codes: terminal title setting (which puts arbitrary string in the + window title) and title repo reporting (which puts that string on the shell's + standard input. + +.. option:: --no-filter-escape-chars + + Do not filter terminal escape sequences. This is DANGEROUS when output is + a terminal emulator. See :option:`--filter-escape-chars` for explanation. Authors -======= +------- + | Joanna Rutkowska | Rafal Wojtczuk | Marek Marczykowski +| Wojtek Porczyk + +.. vim: ts=3 sw=3 et tw=80 diff --git a/doc/manpages/qvm-unpause.rst b/doc/manpages/qvm-unpause.rst new file mode 100644 index 00000000..24c644ce --- /dev/null +++ b/doc/manpages/qvm-unpause.rst @@ -0,0 +1,34 @@ +.. program:: qvm-unpause + +:program:`qvm-unpause` -- unpause a domain +========================================== + +Synopsis +-------- + +:command:`qvm-unpause` [-h] [--verbose] [--quiet] *VMNAME* + +Options +------- + +.. option:: --help, -h + + Show the help message and exit. + +.. option:: --verbose, -v + + Increase verbosity. + +.. option:: --quiet, -q + + Decrease verbosity. + +Authors +------- + +| Joanna Rutkowska +| Rafal Wojtczuk +| Marek Marczykowski +| Wojtek Porczyk + +.. vim: ts=3 sw=3 et tw=80 diff --git a/qubes/tests/int/tools/qubes_create.py b/qubes/tests/int/tools/qubes_create.py index 18d45b70..5d57b53a 100644 --- a/qubes/tests/int/tools/qubes_create.py +++ b/qubes/tests/int/tools/qubes_create.py @@ -31,11 +31,11 @@ import qubes.tests @qubes.tests.skipUnlessDom0 class TC_00_qubes_create(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase): def test_000_basic(self): - self.assertTrue(qubes.tools.qubes_create.main(( + self.assertEqual(0, qubes.tools.qubes_create.main(( '--qubesxml', qubes.tests.XMLPATH))) def test_001_property(self): - self.assertTrue(qubes.tools.qubes_create.main(( + self.assertEqual(0, qubes.tools.qubes_create.main(( '--qubesxml', qubes.tests.XMLPATH, '--property', 'default_kernel=testkernel'))) diff --git a/qubes/tools/__init__.py b/qubes/tools/__init__.py index 66257840..597a3f4d 100644 --- a/qubes/tools/__init__.py +++ b/qubes/tools/__init__.py @@ -32,6 +32,9 @@ import os import qubes.log +#: constant returned when some action should be performed on all qubes +VM_ALL = object() + class PropertyAction(argparse.Action): '''Action for argument parser that stores a property.''' @@ -148,6 +151,7 @@ class QubesArgumentParser(argparse.ArgumentParser): want_app_no_instance=False, want_force_root=False, want_vm=False, + want_vm_all=False, **kwargs): super(QubesArgumentParser, self).__init__(**kwargs) @@ -156,6 +160,7 @@ class QubesArgumentParser(argparse.ArgumentParser): self._want_app_no_instance = want_app_no_instance self._want_force_root = want_force_root self._want_vm = want_vm + self._want_vm_all = want_vm_all if self._want_app: self.add_argument('--qubesxml', metavar='FILE', @@ -176,8 +181,21 @@ class QubesArgumentParser(argparse.ArgumentParser): help='force to run as root') if self._want_vm: - self.add_argument('vm', metavar='VMNAME', - action='store', + if self._want_vm_all: + vmchoice = self.add_mutually_exclusive_group() + vmchoice.add_argument('--all', + action='store_const', const=VM_ALL, dest='vm', + help='perform the action on all qubes') + vmchoice.add_argument('--exclude', + action='append', default=[], + help='exclude the qube from --all') + nargs = '?' + else: + vmchoice = self + nargs = None + + vmchoice.add_argument('vm', metavar='VMNAME', + action='store', nargs=nargs, help='name of the domain') self.set_defaults(verbose=1, quiet=0) @@ -191,10 +209,25 @@ class QubesArgumentParser(argparse.ArgumentParser): namespace.app = qubes.Qubes(namespace.app) if self._want_vm: - try: - namespace.vm = namespace.app.domains[namespace.vm] - except KeyError: - self.error('no such domain: {!r}'.format(namespace.vm)) + if self._want_vm_all: + if namespace.vm is VM_ALL: + namespace.vm = [vm for vm in namespace.app.domains + if vm.qid != 0 and vm.name not in namespace.exclude] + else: + if namespace.exclude: + self.error('--exclude can only be used with --all') + try: + namespace.vm = \ + (namespace.app.domains[namespace.vm],) + except KeyError: + self.error( + 'no such domain: {!r}'.format(namespace.vm)) + + else: + try: + namespace.vm = namespace.app.domains[namespace.vm] + except KeyError: + self.error('no such domain: {!r}'.format(namespace.vm)) if self._want_force_root: self.dont_run_as_root(namespace) diff --git a/qubes/tools/qubes_create.py b/qubes/tools/qubes_create.py index 791454a7..360e6a03 100644 --- a/qubes/tools/qubes_create.py +++ b/qubes/tools/qubes_create.py @@ -49,8 +49,8 @@ def main(args=None): args = parser.parse_args(args) qubes.Qubes.create_empty_store(args.app, **args.properties) - return True + return 0 if __name__ == '__main__': - sys.exit(not main()) + sys.exit(main()) diff --git a/qubes/tools/qubes_prefs.py b/qubes/tools/qubes_prefs.py index 1fc3d363..649e050f 100644 --- a/qubes/tools/qubes_prefs.py +++ b/qubes/tools/qubes_prefs.py @@ -58,8 +58,8 @@ parser.add_argument('--unset', '--default', '--delete', '-D', ' instead') -def main(): - args = parser.parse_args() +def main(args=None): + args = parser.parse_args(args) if args.property is None: properties = args.app.property_list() @@ -80,24 +80,24 @@ def main(): print('{name:{width}s} - {value!r}'.format( name=prop.__name__, width=width, value=value)) - return True + return 0 if args.value is not None: setattr(args.app, args.property, args.value) args.app.save() - return True + return 0 if args.delete: delattr(args.app, args.property) args.app.save() - return True + return 0 print(str(getattr(args.app, args.property))) - return True + return 0 if __name__ == '__main__': - sys.exit(not main()) + sys.exit(main()) diff --git a/qubes/tools/qvm_create.py b/qubes/tools/qvm_create.py index 2b0b39f2..269fcf8d 100644 --- a/qubes/tools/qvm_create.py +++ b/qubes/tools/qvm_create.py @@ -147,8 +147,8 @@ def main(args=None): args.app.save() - return True + return 0 if __name__ == '__main__': - sys.exit(not main()) + sys.exit(main()) diff --git a/qubes/tools/qvm_kill.py b/qubes/tools/qvm_kill.py index 89ce4f51..ce23985b 100644 --- a/qubes/tools/qvm_kill.py +++ b/qubes/tools/qvm_kill.py @@ -48,8 +48,8 @@ def main(args=None): except (IOError, OSError, qubes.exc.QubesException) as e: parser.error_runtime(str(e)) - return True + return 0 if __name__ == '__main__': - sys.exit(not main()) + sys.exit(main()) diff --git a/qubes/tools/qvm_ls.py b/qubes/tools/qvm_ls.py index b8a4d55f..891b1578 100644 --- a/qubes/tools/qvm_ls.py +++ b/qubes/tools/qvm_ls.py @@ -606,8 +606,8 @@ def main(args=None): table = Table(args.app, columns) table.write_table(sys.stdout) - return True + return 0 if __name__ == '__main__': - sys.exit(not main()) + sys.exit(main()) diff --git a/qubes/tools/qvm_pause.py b/qubes/tools/qvm_pause.py new file mode 100644 index 00000000..77932e37 --- /dev/null +++ b/qubes/tools/qvm_pause.py @@ -0,0 +1,50 @@ +#!/usr/bin/python2 -O +# vim: fileencoding=utf-8 + +# +# The Qubes OS Project, https://www.qubes-os.org/ +# +# Copyright (C) 2010-2015 Joanna Rutkowska +# Copyright (C) 2015 Wojtek Porczyk +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +'''qvm-pause - Pause a domain''' + +import sys +import qubes + + +parser = qubes.tools.QubesArgumentParser( + want_vm=True, + description='pause a domain') + + +def main(args=None): + '''Main routine of :program:`qvm-pause`. + + :param list args: Optional arguments to override those delivered from \ + command line. + ''' + + args = parser.parse_args(args) + args.vm.pause() + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/qubes/tools/qvm_prefs.py b/qubes/tools/qvm_prefs.py index be9d297e..92b649bd 100644 --- a/qubes/tools/qvm_prefs.py +++ b/qubes/tools/qvm_prefs.py @@ -61,8 +61,8 @@ parser.add_argument('--unset', '--default', '--delete', '-D', ' instead') -def main(): - args = parser.parse_args() +def main(args=None): + args = parser.parse_args(args) if args.property is None: properties = args.vm.property_list() @@ -83,24 +83,24 @@ def main(): print('{name:{width}s} - {value!r}'.format( name=prop.__name__, width=width, value=value)) - return True + return 0 if args.value is not None: setattr(args.vm, args.property, args.value) args.app.save() - return True + return 0 if args.delete: delattr(args.vm, args.property) args.app.save() - return True + return 0 print(str(getattr(args.vm, args.property))) - return True + return 0 if __name__ == '__main__': - sys.exit(not main()) + sys.exit(main()) diff --git a/qubes/tools/qvm_run.py b/qubes/tools/qvm_run.py new file mode 100644 index 00000000..6cb39036 --- /dev/null +++ b/qubes/tools/qvm_run.py @@ -0,0 +1,133 @@ +#!/usr/bin/python2 +# -*- encoding: utf8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010-2015 Joanna Rutkowska +# Copyright (C) 2015 Wojtek Porczyk +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +from __future__ import print_function + +import os +import sys + +import qubes +import qubes.exc +import qubes.tools + + +parser = qubes.tools.QubesArgumentParser( + want_vm=True, + want_vm_all=True) + +parser.add_argument('--user', '-u', metavar='USER', + help='run command in a qube as USER') + +parser.add_argument('--autostart', '--auto', '-a', + action='store_true', default=False, + help='start the qube if it is not running') + +parser.add_argument('--pass-io', '-p', + action='store_true', dest='passio', default=False, + help='pass stdio from remote program') + +parser.add_argument('--localcmd', metavar='COMMAND', + help='with --pass-io, pass stdio to the given program') + +parser.add_argument('--gui', + action='store_true', default=True, + help='run the command with GUI (default on)') + +parser.add_argument('--no-gui', '--nogui', + action='store_false', dest='gui', + help='run the command without GUI') + +parser.add_argument('--colour-output', '--color-output', metavar='COLOUR', + action='store', dest='color_output', default=None, + help='mark the qube output with given ANSI colour (ie. "31" for red)') + +parser.add_argument('--no-colour-output', '--no-color-output', + action='store_false', dest='color_output', + help='disable colouring the stdio') + +parser.add_argument('--filter-escape-chars', + action='store_true', dest='filter_esc', + default=os.isatty(sys.stdout.fileno()), + help='filter terminal escape sequences (default if output is terminal)') + +parser.add_argument('--no-filter-escape-chars', + action='store_false', dest='filter_esc', + help='do not filter terminal escape sequences; DANGEROUS when output is a' + ' terminal emulator') + +parser.add_argument('cmd', metavar='COMMAND', + help='command to run') + +# +# parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) +# parser.add_option ("--tray", action="store_true", dest="tray", default=False, +# help="Use tray notifications instead of stdout" ) +# parser.add_option ("--pause", action="store_true", dest="pause", default=False, +# help="Do 'xl pause' for the VM(s) (can be combined this with --all)") +# parser.add_option ("--unpause", action="store_true", dest="unpause", default=False, +# help="Do 'xl unpause' for the VM(s) (can be combined this with --all)") +# parser.add_option ("--nogui", action="store_false", dest="gui", default=True, +# help="Run command without gui") +## + +def main(args=None): + args = parser.parse_args(args) + if args.color_output is None and args.filter_esc: + args.color_output = '31' + + if args.vm is qubes.tools.VM_ALL and args.passio: + parser.error('--all and --passio are mutually exclusive') + if args.color_output and not args.filter_esc: + parser.error('--color-output must be used with --filter-escape-chars') + + retcode = 0 + for vm in args.vm: + if args.color_output: + sys.stdout.write('\033[0;{}m'.format(args.color_output)) + sys.stdout.flush() + try: + retcode = max(retcode, vm.run(args.cmd, + user=args.user, + autostart=args.autostart, + passio=args.passio, + localcmd=args.localcmd, + gui=args.gui, + filter_esc=args.filter_esc)) + + except qubes.exc.QubesException as e: + if args.color_output: + sys.stdout.write('\033[0m') + sys.stdout.flush() + vm.log.error(str(e)) + return -1 + + finally: + if args.color_output: + sys.stdout.write('\033[0m') + sys.stdout.flush() + + return retcode + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/qubes/tools/qvm_start.py b/qubes/tools/qvm_start.py index 37248709..9cfb64f0 100644 --- a/qubes/tools/qvm_start.py +++ b/qubes/tools/qvm_start.py @@ -134,8 +134,8 @@ def main(args=None): except qubes.exc.QubesException as e: parser.error_runtime('Qubes error: {!r}'.format(e)) - return True + return 0 if __name__ == '__main__': - sys.exit(not main()) + sys.exit(main()) diff --git a/qubes/tools/qvm_unpause.py b/qubes/tools/qvm_unpause.py new file mode 100644 index 00000000..03df896a --- /dev/null +++ b/qubes/tools/qvm_unpause.py @@ -0,0 +1,49 @@ +#!/usr/bin/python2 -O +# vim: fileencoding=utf-8 + +# +# The Qubes OS Project, https://www.qubes-os.org/ +# +# Copyright (C) 2010-2015 Joanna Rutkowska +# Copyright (C) 2015 Wojtek Porczyk +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +'''qvm-unpause - Unause a domain''' + +import sys +import qubes + + +parser = qubes.tools.QubesArgumentParser( + want_vm=True, + description='unpause a domain') + +def main(args=None): + '''Main routine of :program:`qvm-unpause`. + + :param list args: Optional arguments to override those delivered from \ + command line. + ''' + + args = parser.parse_args(args) + args.vm.unpause() + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index d57c0930..a25828b0 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -932,21 +932,13 @@ class QubesVM(qubes.vm.BaseVM): if os.isatty(sys.stderr.fileno()): args += ['-T'] - # TODO: QSB#13 - if passio: - if os.name == 'nt': - # wait for qrexec-client to exit, otherwise client is not - # properly attached to console if qvm-run is executed from - # cmd.exe - ret = subprocess.call(args) - exit(ret) - os.execv(qubes.config.system_path['qrexec_client_path'], args) - exit(1) - call_kwargs = {} - if ignore_stderr: - null = open("/dev/null", "w") + if ignore_stderr or not passio: + null = open("/dev/null", "rw") call_kwargs['stderr'] = null + if not passio: + call_kwargs['stdin'] = null + call_kwargs['stdout'] = null if passio_popen: popen_kwargs = {'stdout': subprocess.PIPE} @@ -959,7 +951,7 @@ class QubesVM(qubes.vm.BaseVM): if null: null.close() return p - if not wait: + if not wait and not passio: args += ["-e"] retcode = subprocess.call(args, **call_kwargs) if null: diff --git a/qvm-tools/qvm-run b/qvm-tools/qvm-run deleted file mode 100755 index f404a6c2..00000000 --- a/qvm-tools/qvm-run +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/python2 -# -*- encoding: utf8 -*- -# -# The Qubes OS Project, http://www.qubes-os.org -# -# Copyright (C) 2010 Joanna Rutkowska -# Copyright (C) 2010 Rafal Wojtczuk -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# - -from qubes.qubes import QubesVmCollection -from qubes.qubes import QubesException -from qubes.notify import notify_error_qubes_manager -from qubes.notify import tray_notify,tray_notify_error,tray_notify_init -from optparse import OptionParser -import sys -import os -import os.path - -def vm_run_cmd(vm, cmd, options): - if options.pause: - if options.verbose: - print >> sys.stderr, "Pausing VM: '{0}'...".format(vm.name) - vm.pause() - return 0 - - if options.unpause: - if options.verbose: - print >> sys.stderr, "UnPausing VM: '{0}'...".format(vm.name) - vm.unpause() - return 0 - - if options.verbose: - print >> sys.stderr, "Running command on VM: '{0}'...".format(vm.name) - if options.passio and options.color_output is not None: - print "\033[0;%dm" % options.color_output, - - try: - def tray_notify_generic(level, str): - if level == "info": - tray_notify(str, label=vm.label) - elif level == "error": - tray_notify_error(str) - - return vm.run(cmd, autostart = options.auto, - verbose = options.verbose, - user = options.user, - notify_function = tray_notify_generic if options.tray else None, - passio = options.passio, localcmd = options.localcmd, - gui = options.gui, filter_esc = options.filter_esc) - except QubesException as err: - if options.passio and options.color_output is not None: - sys.stdout.write("\033[0m") - if options.tray: - tray_notify_error(str(err)) - notify_error_qubes_manager(vm.name, str(err)) - print >> sys.stderr, "ERROR(%s): %s" % (str(vm.name), str(err)) - return 1 - finally: - if options.passio and options.color_output is not None: - sys.stdout.write("\033[0m") - -def main(): - usage = "usage: %prog [options] [] []" - parser = OptionParser (usage) - parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) - parser.add_option ("-a", "--auto", action="store_true", dest="auto", default=False, - help="Auto start the VM if not running") - parser.add_option ("-u", "--user", action="store", dest="user", default=None, - help="Run command in a VM as a specified user") - parser.add_option ("--tray", action="store_true", dest="tray", default=False, - help="Use tray notifications instead of stdout" ) - - parser.add_option ("--all", action="store_true", dest="run_on_all_running", default=False, - help="Run command on all currently running VMs (or all paused, in case of --unpause)") - - parser.add_option ("--exclude", action="append", dest="exclude_list", - help="When --all is used: exclude this VM name (may be " - "repeated)") - - parser.add_option ("--pause", action="store_true", dest="pause", default=False, - help="Do 'xl pause' for the VM(s) (can be combined this with --all)") - - parser.add_option ("--unpause", action="store_true", dest="unpause", default=False, - help="Do 'xl unpause' for the VM(s) (can be combined this with --all)") - - parser.add_option ("-p", "--pass-io", action="store_true", dest="passio", default=False, - help="Pass stdin/stdout/stderr from remote program (implies -q)") - - parser.add_option ("--localcmd", action="store", dest="localcmd", default=None, - help="With --pass-io, pass stdin/stdout/stderr to the given program") - - parser.add_option ("--nogui", action="store_false", dest="gui", default=True, - help="Run command without gui") - - parser.add_option ("--filter-escape-chars", action="store_true", - dest="filter_esc", - default=os.isatty(sys.stdout.fileno()), - help="Filter terminal escape sequences (default if " - "output is terminal)") - parser.add_option("--no-filter-escape-chars", action="store_false", - dest="filter_esc", - help="Do not filter terminal escape sequences - " - "overrides --filter-escape-chars, DANGEROUS when " - "output is terminal") - - parser.add_option("--no-color-output", action="store_false", - dest="color_output", default=None, - help="Disable marking VM output with red color") - - parser.add_option("--color-output", action="store", type="int", - dest="color_output", - help="Force marking VM output with given ANSI style (" - "use 31 for red)") - - (options, args) = parser.parse_args () - - if options.passio and options.run_on_all_running: - parser.error ("Options --all and --pass-io cannot be used together") - - if options.passio: - options.verbose = False - - if options.color_output is None: - if os.isatty(sys.stdout.fileno()): - options.color_output = 31 - elif options.color_output is False: - options.color_output = None - - if (options.pause or options.unpause): - takes_cmd_argument = False - else: - takes_cmd_argument = True - - if options.run_on_all_running: - if len(args) < 1 and takes_cmd_argument: - parser.error ("You must provide a command to execute on all the VMs.") - if len(args) > 1 or ((not takes_cmd_argument) and len(args) > 0): - parser.error ("To many arguments...") - cmdstr = args[0] if takes_cmd_argument else None - else: - if len (args) < 1 and not takes_cmd_argument: - parser.error ("You must specify the VM name to pause/unpause.") - if len (args) < 2 and takes_cmd_argument: - parser.error ("You must specify the VM name and the command to execute in the VM.") - if len (args) > 2 or ((not takes_cmd_argument) and len(args) > 1): - parser.error ("To many arguments...") - vmname = args[0] - cmdstr = args[1] if takes_cmd_argument else None - - if options.tray: - tray_notify_init() - - qvm_collection = QubesVmCollection() - qvm_collection.lock_db_for_reading() - qvm_collection.load() - qvm_collection.unlock_db() - - vms_list = [] - if options.run_on_all_running: - all_vms = [vm for vm in qvm_collection.values()] - for vm in all_vms: - if options.exclude_list is not None and vm.name in options.exclude_list: - continue - if vm.qid == 0: - continue - if (options.unpause and vm.is_paused()) or (not options.unpause and vm.is_running()): - vms_list.append (vm) - # disable options incompatible with --all - options.passio = False - else: - vm = qvm_collection.get_vm_by_name(vmname) - if vm is None: - print >> sys.stderr, "A VM with the name '{0}' does not exist in the system!".format(vmname) - exit(1) - vms_list.append(vm) - - retcode = 0 - for vm in vms_list: - r = vm_run_cmd(vm, cmdstr, options) - retcode = max(r, retcode) - - exit(retcode) - -main() diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index a37530d0..d4b612c4 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -236,8 +236,11 @@ fi %{python_sitelib}/qubes/tools/qvm_create.py* %{python_sitelib}/qubes/tools/qvm_kill.py* %{python_sitelib}/qubes/tools/qvm_ls.py* +%{python_sitelib}/qubes/tools/qvm_pause.py* %{python_sitelib}/qubes/tools/qvm_prefs.py* +%{python_sitelib}/qubes/tools/qvm_run.py* %{python_sitelib}/qubes/tools/qvm_start.py* +%{python_sitelib}/qubes/tools/qvm_unpause.py* %dir %{python_sitelib}/qubes/ext %{python_sitelib}/qubes/ext/__init__.py*