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
This commit is contained in:
Wojtek Porczyk 2015-12-28 18:14:37 +01:00
parent 7b30361fa6
commit f1a0b1af39
18 changed files with 437 additions and 291 deletions

View File

@ -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 <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>
| Wojtek Porczyk <woju at invisiblethingslab dot com>
.. vim: ts=3 sw=3 et tw=80

View File

@ -1,80 +1,97 @@
.. program:: qvm-run .. program:: qvm-run
===================================================== :program:`qvm-run` -- Run a command in a specified VM
:program:`qvm-run` -- Run a command on a specified VM
===================================================== =====================================================
Synopsis 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 Options
======= -------
.. option:: --help, -h .. option:: --help, -h
Show this help message and exit Show the help message and exit.
.. option:: --verbose, -v
Increase verbosity.
.. option:: --quiet, -q .. option:: --quiet, -q
Be quiet Decrease verbosity.
.. 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
.. option:: --all .. 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 Start the qube if it is not running.
: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`)
.. option:: --pass-io, -p .. 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 Authors
======= -------
| Joanna Rutkowska <joanna at invisiblethingslab dot com> | Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com> | Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com> | Marek Marczykowski <marmarek at invisiblethingslab dot com>
| Wojtek Porczyk <woju at invisiblethingslab dot com>
.. vim: ts=3 sw=3 et tw=80

View File

@ -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 <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>
| Wojtek Porczyk <woju at invisiblethingslab dot com>
.. vim: ts=3 sw=3 et tw=80

View File

@ -31,11 +31,11 @@ import qubes.tests
@qubes.tests.skipUnlessDom0 @qubes.tests.skipUnlessDom0
class TC_00_qubes_create(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase): class TC_00_qubes_create(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
def test_000_basic(self): def test_000_basic(self):
self.assertTrue(qubes.tools.qubes_create.main(( self.assertEqual(0, qubes.tools.qubes_create.main((
'--qubesxml', qubes.tests.XMLPATH))) '--qubesxml', qubes.tests.XMLPATH)))
def test_001_property(self): def test_001_property(self):
self.assertTrue(qubes.tools.qubes_create.main(( self.assertEqual(0, qubes.tools.qubes_create.main((
'--qubesxml', qubes.tests.XMLPATH, '--qubesxml', qubes.tests.XMLPATH,
'--property', 'default_kernel=testkernel'))) '--property', 'default_kernel=testkernel')))

View File

@ -32,6 +32,9 @@ import os
import qubes.log import qubes.log
#: constant returned when some action should be performed on all qubes
VM_ALL = object()
class PropertyAction(argparse.Action): class PropertyAction(argparse.Action):
'''Action for argument parser that stores a property.''' '''Action for argument parser that stores a property.'''
@ -148,6 +151,7 @@ class QubesArgumentParser(argparse.ArgumentParser):
want_app_no_instance=False, want_app_no_instance=False,
want_force_root=False, want_force_root=False,
want_vm=False, want_vm=False,
want_vm_all=False,
**kwargs): **kwargs):
super(QubesArgumentParser, self).__init__(**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_app_no_instance = want_app_no_instance
self._want_force_root = want_force_root self._want_force_root = want_force_root
self._want_vm = want_vm self._want_vm = want_vm
self._want_vm_all = want_vm_all
if self._want_app: if self._want_app:
self.add_argument('--qubesxml', metavar='FILE', self.add_argument('--qubesxml', metavar='FILE',
@ -176,8 +181,21 @@ class QubesArgumentParser(argparse.ArgumentParser):
help='force to run as root') help='force to run as root')
if self._want_vm: if self._want_vm:
self.add_argument('vm', metavar='VMNAME', if self._want_vm_all:
action='store', 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') help='name of the domain')
self.set_defaults(verbose=1, quiet=0) self.set_defaults(verbose=1, quiet=0)
@ -191,6 +209,21 @@ class QubesArgumentParser(argparse.ArgumentParser):
namespace.app = qubes.Qubes(namespace.app) namespace.app = qubes.Qubes(namespace.app)
if self._want_vm: if self._want_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: try:
namespace.vm = namespace.app.domains[namespace.vm] namespace.vm = namespace.app.domains[namespace.vm]
except KeyError: except KeyError:

View File

@ -49,8 +49,8 @@ def main(args=None):
args = parser.parse_args(args) args = parser.parse_args(args)
qubes.Qubes.create_empty_store(args.app, **args.properties) qubes.Qubes.create_empty_store(args.app, **args.properties)
return True return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(not main()) sys.exit(main())

View File

@ -58,8 +58,8 @@ parser.add_argument('--unset', '--default', '--delete', '-D',
' instead') ' instead')
def main(): def main(args=None):
args = parser.parse_args() args = parser.parse_args(args)
if args.property is None: if args.property is None:
properties = args.app.property_list() properties = args.app.property_list()
@ -80,24 +80,24 @@ def main():
print('{name:{width}s} - {value!r}'.format( print('{name:{width}s} - {value!r}'.format(
name=prop.__name__, width=width, value=value)) name=prop.__name__, width=width, value=value))
return True return 0
if args.value is not None: if args.value is not None:
setattr(args.app, args.property, args.value) setattr(args.app, args.property, args.value)
args.app.save() args.app.save()
return True return 0
if args.delete: if args.delete:
delattr(args.app, args.property) delattr(args.app, args.property)
args.app.save() args.app.save()
return True return 0
print(str(getattr(args.app, args.property))) print(str(getattr(args.app, args.property)))
return True return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(not main()) sys.exit(main())

View File

@ -147,8 +147,8 @@ def main(args=None):
args.app.save() args.app.save()
return True return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(not main()) sys.exit(main())

View File

@ -48,8 +48,8 @@ def main(args=None):
except (IOError, OSError, qubes.exc.QubesException) as e: except (IOError, OSError, qubes.exc.QubesException) as e:
parser.error_runtime(str(e)) parser.error_runtime(str(e))
return True return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(not main()) sys.exit(main())

View File

@ -606,8 +606,8 @@ def main(args=None):
table = Table(args.app, columns) table = Table(args.app, columns)
table.write_table(sys.stdout) table.write_table(sys.stdout)
return True return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(not main()) sys.exit(main())

50
qubes/tools/qvm_pause.py Normal file
View File

@ -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 <joanna@invisiblethingslab.com>
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
#
# 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())

View File

@ -61,8 +61,8 @@ parser.add_argument('--unset', '--default', '--delete', '-D',
' instead') ' instead')
def main(): def main(args=None):
args = parser.parse_args() args = parser.parse_args(args)
if args.property is None: if args.property is None:
properties = args.vm.property_list() properties = args.vm.property_list()
@ -83,24 +83,24 @@ def main():
print('{name:{width}s} - {value!r}'.format( print('{name:{width}s} - {value!r}'.format(
name=prop.__name__, width=width, value=value)) name=prop.__name__, width=width, value=value))
return True return 0
if args.value is not None: if args.value is not None:
setattr(args.vm, args.property, args.value) setattr(args.vm, args.property, args.value)
args.app.save() args.app.save()
return True return 0
if args.delete: if args.delete:
delattr(args.vm, args.property) delattr(args.vm, args.property)
args.app.save() args.app.save()
return True return 0
print(str(getattr(args.vm, args.property))) print(str(getattr(args.vm, args.property)))
return True return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(not main()) sys.exit(main())

133
qubes/tools/qvm_run.py Normal file
View File

@ -0,0 +1,133 @@
#!/usr/bin/python2
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010-2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
#
# 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())

View File

@ -134,8 +134,8 @@ def main(args=None):
except qubes.exc.QubesException as e: except qubes.exc.QubesException as e:
parser.error_runtime('Qubes error: {!r}'.format(e)) parser.error_runtime('Qubes error: {!r}'.format(e))
return True return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(not main()) sys.exit(main())

View File

@ -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 <joanna@invisiblethingslab.com>
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
#
# 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())

View File

@ -932,21 +932,13 @@ class QubesVM(qubes.vm.BaseVM):
if os.isatty(sys.stderr.fileno()): if os.isatty(sys.stderr.fileno()):
args += ['-T'] 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 = {} call_kwargs = {}
if ignore_stderr: if ignore_stderr or not passio:
null = open("/dev/null", "w") null = open("/dev/null", "rw")
call_kwargs['stderr'] = null call_kwargs['stderr'] = null
if not passio:
call_kwargs['stdin'] = null
call_kwargs['stdout'] = null
if passio_popen: if passio_popen:
popen_kwargs = {'stdout': subprocess.PIPE} popen_kwargs = {'stdout': subprocess.PIPE}
@ -959,7 +951,7 @@ class QubesVM(qubes.vm.BaseVM):
if null: if null:
null.close() null.close()
return p return p
if not wait: if not wait and not passio:
args += ["-e"] args += ["-e"]
retcode = subprocess.call(args, **call_kwargs) retcode = subprocess.call(args, **call_kwargs)
if null: if null:

View File

@ -1,199 +0,0 @@
#!/usr/bin/python2
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
#
# 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] [<vm-name>] [<cmd>]"
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()

View File

@ -236,8 +236,11 @@ fi
%{python_sitelib}/qubes/tools/qvm_create.py* %{python_sitelib}/qubes/tools/qvm_create.py*
%{python_sitelib}/qubes/tools/qvm_kill.py* %{python_sitelib}/qubes/tools/qvm_kill.py*
%{python_sitelib}/qubes/tools/qvm_ls.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_prefs.py*
%{python_sitelib}/qubes/tools/qvm_run.py*
%{python_sitelib}/qubes/tools/qvm_start.py* %{python_sitelib}/qubes/tools/qvm_start.py*
%{python_sitelib}/qubes/tools/qvm_unpause.py*
%dir %{python_sitelib}/qubes/ext %dir %{python_sitelib}/qubes/ext
%{python_sitelib}/qubes/ext/__init__.py* %{python_sitelib}/qubes/ext/__init__.py*