qvm-run 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. #!/usr/bin/python2.6
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
  6. # Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU General Public License
  10. # as published by the Free Software Foundation; either version 2
  11. # of the License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21. #
  22. #
  23. from qubes.qubes import QubesVmCollection
  24. from qubes.qubes import QubesException
  25. from optparse import OptionParser
  26. import subprocess
  27. import socket
  28. import errno
  29. import dbus
  30. import time
  31. qubes_guid_path = "/usr/bin/qubes_guid"
  32. qubes_clipd_path = "/usr/bin/qclipd"
  33. qubes_qfilexchgd_path= "/usr/bin/qfilexchgd"
  34. notify_object = None
  35. # how long (in sec) to wait for VMs to shutdown
  36. # before killing them (when used with --wait option)
  37. shutdown_counter_max = 30
  38. def tray_notify(str, label, timeout = 3000):
  39. notify_object.Notify("Qubes", 0, label.icon, "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications")
  40. def tray_notify_error(str, timeout = 3000):
  41. notify_object.Notify("Qubes", 0, "dialog-error", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications")
  42. def vm_run_cmd(vm, cmd, options):
  43. if options.shutdown:
  44. if options.verbose:
  45. print "Shutting down VM: '{0}'...".format(vm.name)
  46. subprocess.call (["/usr/sbin/xm", "shutdown", vm.name])
  47. return
  48. if options.verbose:
  49. print "Running command on VM: '{0}'...".format(vm.name)
  50. if not vm.is_running():
  51. if not options.auto:
  52. print "VM '{0}' is not running, please start it first, or use the '--auto' switch"
  53. exit (1)
  54. try:
  55. if options.verbose:
  56. print "Starting the VM '{0}'...".format(vm.name)
  57. if options.tray:
  58. tray_notify ("Starting the '{0}' VM...".format(vm.name), label=vm.label)
  59. xid = vm.start(verbose=options.verbose)
  60. except (IOError, OSError, QubesException) as err:
  61. print "ERROR: {0}".format(err)
  62. if options.tray:
  63. tray_notify_error ("Error while starting the '{0}' VM: {1}".format(vm.name, err))
  64. exit (1)
  65. except (MemoryError) as err:
  66. print "ERROR: {0}".format(err)
  67. print "Close one or more running VMs and try again."
  68. if options.tray:
  69. subprocess.call(["kdialog", "--error", "Not enough memory to start '{0}' VM! Close one or more running VMs and try again.".format(vm.name)])
  70. exit (1)
  71. if options.verbose:
  72. print "--> Starting Qubes GUId..."
  73. retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-e", cmd, "-i", vm.label.icon])
  74. if (retcode != 0) :
  75. print "ERROR: Cannot start qubes_guid!"
  76. if options.tray:
  77. tray_notify_error ("ERROR: Cannot start qubes_guid!")
  78. exit (1)
  79. else: # VM already running...
  80. guid_is_running = True
  81. xid = vm.get_xid()
  82. s = socket.socket (socket.AF_UNIX)
  83. try:
  84. s.connect ("/var/run/qubes/cmd_socket.{0}".format(xid))
  85. except (IOError, OSError) as e:
  86. if e.errno in [errno.ENOENT,errno.ECONNREFUSED]:
  87. guid_is_running = False
  88. else:
  89. print "ERROR: unix-connect: {0}".format(e)
  90. if options.tray:
  91. tray_notify_error ("ERROR: Cannot connect to GUI daemon for this VM!")
  92. exit(1)
  93. if guid_is_running:
  94. s.send (cmd)
  95. s.close()
  96. else:
  97. retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-e", cmd, "-i", vm.label.icon])
  98. if (retcode != 0) :
  99. print "ERROR: Cannot start qubes_guid!"
  100. if options.tray:
  101. tray_notify_error ("ERROR: Cannot start the GUI daemon for this VM!")
  102. exit (1)
  103. def main():
  104. usage = "usage: %prog [options] [<vm-name>] [<cmd>]"
  105. parser = OptionParser (usage)
  106. parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True)
  107. parser.add_option ("-a", "--auto", action="store_true", dest="auto", default=False,
  108. help="Auto start the VM if not running")
  109. parser.add_option ("-u", "--user", action="store", dest="user", default="user",
  110. help="Run command in a VM as a specified user")
  111. parser.add_option ("--tray", action="store_true", dest="tray", default=False,
  112. help="Use tray notifications instead of stdout" )
  113. parser.add_option ("--all", action="store_true", dest="run_on_all_running", default=False,
  114. help="Run command on all currently running VMs")
  115. parser.add_option ("--exclude", action="append", dest="exclude_list",
  116. help="When --all is used: exclude this VM name (might be repeated)")
  117. parser.add_option ("--wait", action="store_true", dest="wait_for_shutdown", default=False,
  118. help="Wait for the VM(s) to shutdown")
  119. parser.add_option ("--shutdown", action="store_true", dest="shutdown", default=False,
  120. help="Do 'xm shutdown' for the VM(s) (can be combined this with --all and --wait)")
  121. (options, args) = parser.parse_args ()
  122. if options.run_on_all_running:
  123. if len(args) < 1 and not options.shutdown:
  124. parser.error ("You must provide a command to execute on all the VMs.")
  125. if len(args) > 1 or (options.shutdown and len(args) > 0):
  126. parser.error ("To many arguments...")
  127. cmdstr = args[0] if not options.shutdown else None
  128. else:
  129. if len (args) < 1 and options.shutdown:
  130. parser.error ("You must specify the VM name to shutdown.")
  131. if len (args) < 2 and not options.shutdown:
  132. parser.error ("You must specify the VM name and the command to execute in the VM.")
  133. if len (args) > 2 or (options.shutdown and len(args) > 1):
  134. parser.error ("To many arguments...")
  135. vmname = args[0]
  136. cmdstr = args[1] if not options.shutdown else None
  137. if options.tray:
  138. global notify_object
  139. notify_object = dbus.SessionBus().get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
  140. qvm_collection = QubesVmCollection()
  141. qvm_collection.lock_db_for_reading()
  142. qvm_collection.load()
  143. qvm_collection.unlock_db()
  144. vms_list = []
  145. if options.run_on_all_running:
  146. all_vms = [vm for vm in qvm_collection.values()]
  147. for vm in all_vms:
  148. if options.exclude_list is not None and vm.name in options.exclude_list:
  149. continue
  150. if vm.qid == 0:
  151. continue
  152. if vm.is_running():
  153. vms_list.append (vm)
  154. else:
  155. vm = qvm_collection.get_vm_by_name(vmname)
  156. if vm is None:
  157. print "A VM with the name '{0}' does not exist in the system!".format(vmname)
  158. exit(1)
  159. vms_list.append(vm)
  160. if options.shutdown:
  161. cmd = None
  162. else:
  163. cmd = "{user}:{cmd}".format(user=options.user, cmd=cmdstr)
  164. for vm in vms_list:
  165. vm_run_cmd(vm, cmd, options)
  166. if options.wait_for_shutdown:
  167. if options.verbose:
  168. print "Waiting for the VM(s) to shutdown..."
  169. shutdown_counter = 0
  170. while len (vms_list):
  171. if options.verbose:
  172. print "Waiting for VMs: ", [vm.name for vm in vms_list]
  173. for vm in vms_list:
  174. if not vm.is_running():
  175. vms_list.remove (vm)
  176. if shutdown_counter > shutdown_counter_max:
  177. # kill the VM
  178. if options.verbose:
  179. print "Killing the (apparently hanging) VM '{0}'...".format(vm.name)
  180. vm.force_shutdown()
  181. #vms_list.remove(vm)
  182. shutdown_counter += 1
  183. time.sleep (1)
  184. exit (0) # there is no point in executing the other daemons in the case of --wait
  185. retcode = subprocess.call([qubes_clipd_path])
  186. if retcode != 0:
  187. print "ERROR: Cannot start qclipd!"
  188. if options.tray:
  189. tray_notify ("ERROR: Cannot start the Qubes Clipboard Notifier!")
  190. retcode = subprocess.call([qubes_qfilexchgd_path])
  191. if retcode != 0:
  192. print "ERROR: Cannot start qfilexchgd!"
  193. if options.tray:
  194. tray_notify ("ERROR: Cannot start the Qubes Inter-VM File Exchange Daemon!")
  195. main()