86a14b53fb
When stdout is redirected to some file or command two things will happen: - qvm-run will not automatically color the output as stdout is not a TTY - even when coloring is forced, it will not work, as the control sequence (on stdout) will be redirected anyway Fix this by handling stdout and stderr independently and output color switching sequence to each of them. Fixes QubesOS/qubes-issues#2190
223 lines
9.2 KiB
Python
Executable File
223 lines
9.2 KiB
Python
Executable File
#!/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:
|
|
sys.stdout.write("\033[0;{}m".format(options.color_output))
|
|
if options.passio and options.color_stderr is not None:
|
|
sys.stderr.write("\033[0;{}m".format(options.color_stderr))
|
|
|
|
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.passio and options.color_stderr is not None:
|
|
sys.stderr.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")
|
|
if options.passio and options.color_stderr is not None:
|
|
sys.stderr.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("--no-color-stderr", action="store_false",
|
|
dest="color_stderr", default=None,
|
|
help="Disable marking VM stderr 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)")
|
|
|
|
parser.add_option("--color-stderr", action="store", type="int",
|
|
dest="color_stderr",
|
|
help="Force marking VM stderr with given ANSI style ("
|
|
"use 31 for red)")
|
|
|
|
(options, args) = parser.parse_args ()
|
|
|
|
if (options.passio and not options.localcmd) and options.run_on_all_running:
|
|
parser.error ("Options --all and --pass-io cannot be used together")
|
|
|
|
if options.localcmd and not options.passio:
|
|
print >> sys.stderr, "WARNING: option --localcmd have no effect " \
|
|
"without --pass-io"
|
|
|
|
if options.passio:
|
|
options.verbose = False
|
|
|
|
if options.color_output is None:
|
|
if os.isatty(sys.stdout.fileno()) and not options.localcmd:
|
|
options.color_output = 31
|
|
elif options.color_output is False:
|
|
options.color_output = None
|
|
|
|
if options.color_stderr is None:
|
|
if os.isatty(sys.stderr.fileno()) and not options.localcmd:
|
|
options.color_stderr = 31
|
|
elif options.color_stderr is False:
|
|
options.color_stderr = 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)
|
|
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()
|