c789121f84
This is core part of migration. Things not migrated yet: - DispVM (qubes_restore needs to be almost rewritten) - VM xen config files should be fixed (use "script:" prefix in block device description, perhaps generate this files on VM start) Huge, slow xend not needed any more, now it conflicts with libxl
270 lines
11 KiB
Python
Executable File
270 lines
11 KiB
Python
Executable File
#!/usr/bin/python2.6
|
|
#
|
|
# 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 optparse import OptionParser
|
|
import subprocess
|
|
import socket
|
|
import errno
|
|
import dbus
|
|
import time
|
|
import os
|
|
import os.path
|
|
|
|
qubes_guid_path = "/usr/bin/qubes_guid"
|
|
qubes_clipd_path = "/usr/bin/qclipd"
|
|
qrexec_client_path = "/usr/lib/qubes/qrexec_client"
|
|
notify_object = None
|
|
|
|
# how long (in sec) to wait for VMs to shutdown
|
|
# before killing them (when used with --wait option)
|
|
shutdown_counter_max = 30
|
|
|
|
def tray_notify(str, label, timeout = 3000):
|
|
notify_object.Notify("Qubes", 0, label.icon, "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications")
|
|
|
|
def tray_notify_error(str, timeout = 3000):
|
|
notify_object.Notify("Qubes", 0, "dialog-error", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications")
|
|
|
|
def actually_execute(domid, cmd, options):
|
|
args = [qrexec_client_path, "-d", domid, cmd]
|
|
if options.localcmd is not None:
|
|
args += [ "-l", options.localcmd]
|
|
if options.passio and not options.run_on_all_running:
|
|
os.execv(qrexec_client_path, args)
|
|
exit(1)
|
|
args += ["-e"]
|
|
subprocess.call(args)
|
|
|
|
def start_guid(vm, options):
|
|
if options.verbose:
|
|
print "--> Starting Qubes GUId..."
|
|
xid = vm.get_xid()
|
|
|
|
retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-i", vm.label.icon, "-l", str(vm.label.index)])
|
|
if (retcode != 0) :
|
|
print "ERROR: Cannot start qubes_guid!"
|
|
if options.tray:
|
|
tray_notify_error ("ERROR: Cannot start qubes_guid!")
|
|
exit (1)
|
|
|
|
if options.verbose:
|
|
print "--> Waiting for qubes-session..."
|
|
|
|
subprocess.call([qrexec_client_path, "-d", str(xid), "user:echo $$ >> /tmp/qubes-session-waiter; [ ! -f /tmp/qubes-session-env ] && exec sleep 365d"])
|
|
|
|
|
|
def vm_run_cmd(vm, cmd, options):
|
|
if options.shutdown:
|
|
if options.verbose:
|
|
print "Shutting down VM: '{0}'...".format(vm.name)
|
|
subprocess.call (["/usr/sbin/xl", "shutdown", vm.name])
|
|
return
|
|
|
|
if options.pause:
|
|
if options.verbose:
|
|
print "Pausing VM: '{0}'...".format(vm.name)
|
|
subprocess.call (["/usr/sbin/xl", "pause", vm.name])
|
|
return
|
|
|
|
if options.unpause:
|
|
if options.verbose:
|
|
print "UnPausing VM: '{0}'...".format(vm.name)
|
|
subprocess.call (["/usr/sbin/xl", "unpause", vm.name])
|
|
return
|
|
|
|
if options.verbose:
|
|
print "Running command on VM: '{0}'...".format(vm.name)
|
|
|
|
if not vm.is_running():
|
|
if not options.auto:
|
|
print "VM '{0}' is not running. Please start it first or use the '--auto' switch".format(vm.name)
|
|
exit (1)
|
|
try:
|
|
if options.verbose:
|
|
print "Starting the VM '{0}'...".format(vm.name)
|
|
if options.tray:
|
|
tray_notify ("Starting the '{0}' VM...".format(vm.name), label=vm.label)
|
|
xid = vm.start(verbose=options.verbose)
|
|
|
|
except (IOError, OSError, QubesException) as err:
|
|
print "ERROR: {0}".format(err)
|
|
if options.tray:
|
|
tray_notify_error ("Error while starting the '{0}' VM: {1}".format(vm.name, err))
|
|
exit (1)
|
|
except (MemoryError) as err:
|
|
print "ERROR: {0}".format(err)
|
|
print "Close one or more running VMs and try again."
|
|
if options.tray:
|
|
subprocess.call(["kdialog", "--error", "Not enough memory to start '{0}' VM! Close one or more running VMs and try again.".format(vm.name)])
|
|
exit (1)
|
|
|
|
if os.getenv("DISPLAY") is not None:
|
|
start_guid(vm, options)
|
|
|
|
actually_execute(str(xid), cmd, options);
|
|
|
|
else: # VM already running...
|
|
xid = vm.get_xid()
|
|
if os.getenv("DISPLAY") is not None and not os.path.isfile("/var/run/qubes/guid_running.{0}".format(xid)):
|
|
start_guid(vm, options)
|
|
actually_execute(str(xid), cmd, options);
|
|
|
|
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="user",
|
|
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 (might be repeated)")
|
|
|
|
parser.add_option ("--wait", action="store_true", dest="wait_for_shutdown", default=False,
|
|
help="Wait for the VM(s) to shutdown")
|
|
|
|
parser.add_option ("--shutdown", action="store_true", dest="shutdown", default=False,
|
|
help="Do 'xl shutdown' for the VM(s) (can be combined this with --all and --wait)")
|
|
|
|
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 and --wait)")
|
|
|
|
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 and --wait)")
|
|
|
|
parser.add_option ("--pass_io", action="store_true", dest="passio", default=False,
|
|
help="Pass stdin/stdout/stderr from remote program")
|
|
|
|
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 ("--force", action="store_true", dest="force", default=False,
|
|
help="Force operation, even if may damage other VMs (eg shutdown of NetVM)")
|
|
|
|
(options, args) = parser.parse_args ()
|
|
|
|
|
|
if (options.shutdown or 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 shutdown/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:
|
|
global notify_object
|
|
notify_object = dbus.SessionBus().get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
|
|
|
|
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 "A VM with the name '{0}' does not exist in the system!".format(vmname)
|
|
exit(1)
|
|
vms_list.append(vm)
|
|
|
|
# If stopping NetVM - stop connected VMs too
|
|
if options.shutdown and vm.is_netvm():
|
|
connected_vms = [vm for vm in qvm_collection.get_vms_connected_to(vm.qid) if vm.is_running()]
|
|
if connected_vms and not options.force:
|
|
print "ERROR: There are other VMs connected to this VM, "
|
|
print " shutdown them first or use --force option"
|
|
print "VMs list: " + str([vm.name for vm in connected_vms])
|
|
exit(1)
|
|
|
|
if takes_cmd_argument:
|
|
cmd = "{user}:{cmd}".format(user=options.user, cmd=cmdstr)
|
|
else:
|
|
cmd = None
|
|
|
|
for vm in vms_list:
|
|
vm_run_cmd(vm, cmd, options)
|
|
|
|
|
|
if options.wait_for_shutdown:
|
|
if options.verbose:
|
|
print "Waiting for the VM(s) to shutdown..."
|
|
shutdown_counter = 0
|
|
|
|
while len (vms_list):
|
|
if options.verbose:
|
|
print "Waiting for VMs: ", [vm.name for vm in vms_list]
|
|
for vm in vms_list:
|
|
if not vm.is_running():
|
|
vms_list.remove (vm)
|
|
if shutdown_counter > shutdown_counter_max:
|
|
# kill the VM
|
|
if options.verbose:
|
|
print "Killing the (apparently hanging) VM '{0}'...".format(vm.name)
|
|
vm.force_shutdown()
|
|
#vms_list.remove(vm)
|
|
|
|
shutdown_counter += 1
|
|
time.sleep (1)
|
|
exit (0) # there is no point in executing the other daemons in the case of --wait
|
|
|
|
retcode = subprocess.call([qubes_clipd_path])
|
|
if retcode != 0:
|
|
print "ERROR: Cannot start qclipd!"
|
|
if options.tray:
|
|
tray_notify ("ERROR: Cannot start the Qubes Clipboard Notifier!")
|
|
|
|
|
|
main()
|