qvm-run 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. #!/usr/bin/python2
  2. # -*- encoding: utf8 -*-
  3. #
  4. # The Qubes OS Project, http://www.qubes-os.org
  5. #
  6. # Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
  7. # Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
  8. #
  9. # This program is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License
  11. # as published by the Free Software Foundation; either version 2
  12. # of the License, or (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. #
  23. #
  24. from qubes.qubes import QubesVmCollection
  25. from qubes.qubes import QubesException
  26. from qubes.guihelpers import notify_error_qubes_manager
  27. from optparse import OptionParser
  28. import subprocess
  29. import socket
  30. import errno
  31. import dbus
  32. import time
  33. import sys
  34. import os
  35. import os.path
  36. notify_object = None
  37. # how long (in sec) to wait for VMs to shutdown
  38. # before killing them (when used with --wait option)
  39. from qubes.qubes import defaults
  40. def tray_notify(str, label, timeout = 3000):
  41. if notify_object:
  42. notify_object.Notify("Qubes", 0, label.icon, "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications")
  43. def tray_notify_error(str, timeout = 3000):
  44. if notify_object:
  45. notify_object.Notify("Qubes", 0, "dialog-error", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications")
  46. def vm_run_cmd(vm, cmd, options):
  47. if options.shutdown:
  48. if options.verbose:
  49. print >> sys.stderr, "Shutting down VM: '{0}'...".format(vm.name)
  50. try:
  51. vm.shutdown(force=options.force)
  52. except (QubesException) as err:
  53. # "There are other VMs connected to this VM:"
  54. print >> sys.stderr, "ERROR: {0}".format(err)
  55. if str(err).startswith("There are other VMs connected"):
  56. print >> sys.stderr, "Shutdown them first or use --force switch"
  57. exit(1)
  58. return 0
  59. if options.pause:
  60. if options.verbose:
  61. print >> sys.stderr, "Pausing VM: '{0}'...".format(vm.name)
  62. vm.pause()
  63. return 0
  64. if options.unpause:
  65. if options.verbose:
  66. print >> sys.stderr, "UnPausing VM: '{0}'...".format(vm.name)
  67. vm.unpause()
  68. return 0
  69. if options.verbose:
  70. print >> sys.stderr, "Running command on VM: '{0}'...".format(vm.name)
  71. if options.passio and options.color_output:
  72. print "\033[0;31m",
  73. try:
  74. def tray_notify_generic(level, str):
  75. if level == "info":
  76. tray_notify(str, label=vm.label)
  77. elif level == "error":
  78. tray_notify_error(str)
  79. return vm.run(cmd, autostart = options.auto,
  80. verbose = options.verbose,
  81. user = options.user,
  82. notify_function = tray_notify_generic if options.tray else None,
  83. wait = options.passio, localcmd = options.localcmd,
  84. gui = options.gui, filter_esc = options.filter_esc)
  85. except QubesException as err:
  86. if options.passio and options.color_output:
  87. print "\033[0m",
  88. if options.tray:
  89. tray_notify_error(str(err))
  90. notify_error_qubes_manager(vm.name, str(err))
  91. print >> sys.stderr, "ERROR(%s): %s" % (str(vm.name), str(err))
  92. return 1
  93. finally:
  94. if options.passio and options.color_output:
  95. print "\033[0m",
  96. def main():
  97. usage = "usage: %prog [options] [<vm-name>] [<cmd>]"
  98. parser = OptionParser (usage)
  99. parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True)
  100. parser.add_option ("-a", "--auto", action="store_true", dest="auto", default=False,
  101. help="Auto start the VM if not running")
  102. parser.add_option ("-u", "--user", action="store", dest="user", default=None,
  103. help="Run command in a VM as a specified user")
  104. parser.add_option ("--tray", action="store_true", dest="tray", default=False,
  105. help="Use tray notifications instead of stdout" )
  106. parser.add_option ("--all", action="store_true", dest="run_on_all_running", default=False,
  107. help="Run command on all currently running VMs (or all paused, in case of --unpause)")
  108. parser.add_option ("--exclude", action="append", dest="exclude_list",
  109. help="When --all is used: exclude this VM name (may be "
  110. "repeated)")
  111. parser.add_option ("--wait", action="store_true", dest="wait_for_shutdown", default=False,
  112. help="Wait for the VM(s) to shutdown")
  113. parser.add_option ("--shutdown", action="store_true", dest="shutdown", default=False,
  114. help="(deprecated) Do 'xl shutdown' for the VM(s) (can be combined this with --all and --wait)")
  115. parser.add_option ("--pause", action="store_true", dest="pause", default=False,
  116. help="Do 'xl pause' for the VM(s) (can be combined this with --all and --wait)")
  117. parser.add_option ("--unpause", action="store_true", dest="unpause", default=False,
  118. help="Do 'xl unpause' for the VM(s) (can be combined this with --all and --wait)")
  119. parser.add_option ("-p", "--pass-io", action="store_true", dest="passio", default=False,
  120. help="Pass stdin/stdout/stderr from remote program (implies -q)")
  121. parser.add_option ("--localcmd", action="store", dest="localcmd", default=None,
  122. help="With --pass-io, pass stdin/stdout/stderr to the given program")
  123. parser.add_option ("--force", action="store_true", dest="force", default=False,
  124. help="Force operation, even if may damage other VMs (eg shutdown of NetVM)")
  125. parser.add_option ("--nogui", action="store_false", dest="gui", default=True,
  126. help="Run command without gui")
  127. parser.add_option ("--filter-escape-chars", action="store_true",
  128. dest="filter_esc",
  129. default=os.isatty(sys.stdout.fileno()),
  130. help="Filter terminal escape sequences (default if "
  131. "output is terminal)")
  132. parser.add_option("--no-filter-escape-chars", action="store_false",
  133. dest="filter_esc",
  134. help="Do not filter terminal escape sequences - "
  135. "overrides --filter-escape-chars, DANGEROUS when "
  136. "output is terminal")
  137. parser.add_option("--no-color-output", action="store_false",
  138. dest="color_output", default=None,
  139. help="Disable marking VM output with red color")
  140. (options, args) = parser.parse_args ()
  141. if options.passio and options.run_on_all_running:
  142. parser.error ("Options --all and --pass-io cannot be used together")
  143. if options.passio:
  144. options.verbose = False
  145. if options.color_output is None:
  146. options.color_output = os.isatty(sys.stdout.fileno())
  147. if options.shutdown:
  148. print >>sys.stderr, "WARNING: --shutdown is deprecated. Use qvm-shutdown instead."
  149. if (options.shutdown or options.pause or options.unpause):
  150. takes_cmd_argument = False
  151. else:
  152. takes_cmd_argument = True
  153. if options.run_on_all_running:
  154. if len(args) < 1 and takes_cmd_argument:
  155. parser.error ("You must provide a command to execute on all the VMs.")
  156. if len(args) > 1 or ((not takes_cmd_argument) and len(args) > 0):
  157. parser.error ("To many arguments...")
  158. cmdstr = args[0] if takes_cmd_argument else None
  159. else:
  160. if len (args) < 1 and not takes_cmd_argument:
  161. parser.error ("You must specify the VM name to shutdown/pause/unpause.")
  162. if len (args) < 2 and takes_cmd_argument:
  163. parser.error ("You must specify the VM name and the command to execute in the VM.")
  164. if len (args) > 2 or ((not takes_cmd_argument) and len(args) > 1):
  165. parser.error ("To many arguments...")
  166. vmname = args[0]
  167. cmdstr = args[1] if takes_cmd_argument else None
  168. if options.tray:
  169. global notify_object
  170. try:
  171. notify_object = dbus.SessionBus().get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
  172. except dbus.DBusException as ex:
  173. print >>sys.stderr, "WARNING: failed connect to tray notification service: %s" % str(ex)
  174. qvm_collection = QubesVmCollection()
  175. qvm_collection.lock_db_for_reading()
  176. qvm_collection.load()
  177. qvm_collection.unlock_db()
  178. vms_list = []
  179. if options.run_on_all_running:
  180. all_vms = [vm for vm in qvm_collection.values()]
  181. for vm in all_vms:
  182. if options.exclude_list is not None and vm.name in options.exclude_list:
  183. continue
  184. if vm.qid == 0:
  185. continue
  186. if (options.unpause and vm.is_paused()) or (not options.unpause and vm.is_running()):
  187. vms_list.append (vm)
  188. # disable options incompatible with --all
  189. options.passio = False
  190. else:
  191. vm = qvm_collection.get_vm_by_name(vmname)
  192. if vm is None:
  193. print >> sys.stderr, "A VM with the name '{0}' does not exist in the system!".format(vmname)
  194. exit(1)
  195. vms_list.append(vm)
  196. retcode = 0
  197. for vm in vms_list:
  198. r = vm_run_cmd(vm, cmdstr, options)
  199. retcode = max(r, retcode)
  200. if options.wait_for_shutdown:
  201. if options.verbose:
  202. print >> sys.stderr, "Waiting for the VM(s) to shutdown..."
  203. shutdown_counter = 0
  204. while len (vms_list):
  205. if options.verbose:
  206. print >> sys.stderr, "Waiting for VMs: ", [vm.name for vm in vms_list]
  207. for vm in vms_list:
  208. if not vm.is_running():
  209. vms_list.remove (vm)
  210. if shutdown_counter > defaults["shutdown_counter_max"]:
  211. # kill the VM
  212. if options.verbose:
  213. print >> sys.stderr, "Killing the (apparently hanging) VM '{0}'...".format(vm.name)
  214. vm.force_shutdown()
  215. #vms_list.remove(vm)
  216. shutdown_counter += 1
  217. time.sleep (1)
  218. exit (0) # there is no point in executing the other daemons in the case of --wait
  219. exit(retcode)
  220. main()