1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
|
# Copyright (C) 2006, 2014 Red Hat, Inc.
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import argparse
import os
import signal
import sys
import traceback
import gi
gi.require_version("Gdk", "3.0")
gi.require_version("Gtk", "3.0")
gi.require_version("LibvirtGLib", "1.0")
from gi.repository import LibvirtGLib
from virtinst import BuildConfig
from virtinst import cli
from virtinst import log
from .lib.testmock import CLITestOptionsClass
# pygobject commit that I believe universally changed bool arguments
# to handle passed None. Without this, None usage keeps slipping into
# the code, so add the requirement.
# https://github.com/GNOME/pygobject/commit/6c69fc7b582ec1fd3faef4de3fade9a0cb7f8c05
_PYGOBJECT_VERSION = "3.31.3"
try:
gi.check_version(_PYGOBJECT_VERSION)
except (ValueError, AttributeError): # pragma: no cover
print("pygobject3 %s or later is required." % _PYGOBJECT_VERSION)
sys.exit(1)
def _show_startup_error(msg, details):
log.debug("Error starting virt-manager: %s\n%s", msg, details, exc_info=True)
from .error import vmmErrorDialog
err = vmmErrorDialog.get_instance()
title = _("Error starting Virtual Machine Manager")
errmsg = _("Error starting Virtual Machine Manager: %(error)s") % {"error": msg}
err.show_err(errmsg, details=details, title=title, modal=True, debug=False)
def _import_gtk(leftovers):
# The never ending fork+gsettings problems now require us to
# import Gtk _after_ the fork. This creates a funny race, since we
# need to parse the command line arguments to know if we need to
# fork, but need to import Gtk before cli processing so it can
# handle --g-fatal-args. We strip out our flags first and pass the
# left overs to gtk
origargv = sys.argv
try:
sys.argv = origargv[:1] + leftovers[:]
from gi.repository import Gtk
leftovers = sys.argv[1:]
if Gtk.check_version(3, 22, 0): # pragma: no cover
print("gtk3 3.22.0 or later is required.")
sys.exit(1)
# This will error if Gtk wasn't correctly initialized
Gtk.init()
globals()["Gtk"] = Gtk
# This ensures we can init gsettings correctly
from . import config
ignore = config
finally:
sys.argv = origargv
return leftovers
def _setup_gsettings_path(schemadir):
"""
If running from the virt-manager.git srcdir, compile our gsettings
schema and use it directly
"""
import subprocess
import shutil
exe = shutil.which("glib-compile-schemas")
if not exe: # pragma: no cover
raise RuntimeError("You must install glib-compile-schemas to run virt-manager from git.")
subprocess.check_call([exe, "--strict", schemadir])
def drop_tty():
# We fork and setsid so that we drop the controlling
# tty. This prevents libvirt's SSH tunnels from prompting
# for user input if SSH keys/agent aren't configured.
if os.fork() != 0:
# pylint: disable=protected-access
os._exit(0) # pragma: no cover
os.setsid()
def drop_stdio():
# This is part of the fork process described in drop_tty()
for fd in range(0, 2):
try:
os.close(fd)
except OSError: # pragma: no cover
pass
os.open(os.devnull, os.O_RDWR)
os.dup2(0, 1)
os.dup2(0, 2)
def do_we_fork(options):
if options.debug or options.no_fork:
return False
if options.fork:
return True
key = "VIRT_MANAGER_DEFAULT_FORK"
val = os.environ.get(key, None)
if val == "yes":
log.debug("%s=%s, defaulting to --fork", key, val)
return True
if val == "no": # pragma: no cover
log.debug("%s=%s, defaulting to --no-fork", key, val)
return False
if val: # pragma: no cover
log.warning("Unknown %s=%s, expected 'yes' or 'no'", key, val)
# Default is `--no-fork`
return False
def parse_commandline():
epilog = "Also accepts standard GTK arguments like --g-fatal-warnings"
parser = argparse.ArgumentParser(usage="virt-manager [options]", epilog=epilog)
parser.add_argument("--version", action="version", version=BuildConfig.version)
parser.set_defaults(domain=None)
# Trace every libvirt API call to debug output
parser.add_argument("--trace-libvirt", choices=["all", "mainloop"], help=argparse.SUPPRESS)
# comma separated string of options to tweak app behavior,
# for manual and automated testing config
parser.add_argument("--test-options", action="append", default=[], help=argparse.SUPPRESS)
parser.add_argument(
"-c", "--connect", dest="uri", help="Connect to hypervisor at URI", metavar="URI"
)
parser.add_argument(
"--debug",
action="store_true",
help="Print debug output to stdout (implies --no-fork)",
default=False,
)
parser.add_argument(
"--no-fork", action="store_true", help="Don't fork into background on startup"
)
parser.add_argument(
"--fork",
action="store_true",
help="Force fork into background on startup (this is the default)",
)
parser.add_argument("--show-domain-creator", action="store_true", help="Show 'New VM' wizard")
parser.add_argument(
"--show-domain-editor", metavar="NAME|ID|UUID", help="Show domain details window"
)
parser.add_argument(
"--show-domain-performance", metavar="NAME|ID|UUID", help="Show domain performance window"
)
parser.add_argument(
"--show-domain-console", metavar="NAME|ID|UUID", help="Show domain graphical console window"
)
parser.add_argument(
"--show-domain-delete", metavar="NAME|ID|UUID", help="Show domain delete window"
)
parser.add_argument(
"--show-host-summary", action="store_true", help="Show connection details window"
)
parser.add_argument(
"--show-systray", action="store_true", help="Launch virt-manager only in system tray"
)
return parser.parse_known_args()
def main():
(options, leftovers) = parse_commandline()
cli.setupLogging("virt-manager", options.debug, False, False)
log.debug("virt-manager version: %s", BuildConfig.version)
log.debug("virtManager import: %s", os.path.dirname(__file__))
if BuildConfig.running_from_srcdir:
_setup_gsettings_path(BuildConfig.gsettings_dir)
if options.trace_libvirt:
log.debug("Libvirt tracing requested")
from .lib import module_trace
import libvirt
module_trace.wrap_module(
libvirt, mainloop=(options.trace_libvirt == "mainloop"), regex=None
)
CLITestOptions = CLITestOptionsClass(options.test_options)
# With F27 gnome+wayland we need to set these before GTK import
os.environ["GSETTINGS_SCHEMA_DIR"] = BuildConfig.gsettings_dir
# Force SSH to use askpass if a password is required,
# rather than possibly prompting on a terminal the user isn't looking at.
os.environ.setdefault("SSH_ASKPASS_REQUIRE", "force")
log.debug("Using SSH_ASKPASS_REQUIRE=%s", os.environ["SSH_ASKPASS_REQUIRE"])
# Now we've got basic environment up & running we can fork
do_fork = do_we_fork(options)
if do_fork:
drop_tty()
leftovers = _import_gtk(leftovers)
Gtk = globals()["Gtk"]
# Do this after the Gtk import so the user has a chance of seeing any error
if do_fork:
drop_stdio()
if leftovers:
raise RuntimeError("Unhandled command line options '%s'" % leftovers)
log.debug(
"PyGObject version: %d.%d.%d", gi.version_info[0], gi.version_info[1], gi.version_info[2]
)
log.debug(
"GTK version: %d.%d.%d",
Gtk.get_major_version(),
Gtk.get_minor_version(),
Gtk.get_micro_version(),
)
# Prime the vmmConfig cache
from . import config
config.vmmConfig.get_instance(BuildConfig, CLITestOptions)
# Add our icon dir to icon theme
icon_theme = Gtk.IconTheme.get_default()
icon_theme.prepend_search_path(BuildConfig.icon_dir)
from .engine import vmmEngine
Gtk.Window.set_default_icon_name("virt-manager")
show_window = None
domain = None
if options.show_domain_creator:
show_window = vmmEngine.CLI_SHOW_DOMAIN_CREATOR
elif options.show_host_summary:
show_window = vmmEngine.CLI_SHOW_HOST_SUMMARY
elif options.show_domain_editor:
show_window = vmmEngine.CLI_SHOW_DOMAIN_EDITOR
domain = options.show_domain_editor
elif options.show_domain_performance:
show_window = vmmEngine.CLI_SHOW_DOMAIN_PERFORMANCE
domain = options.show_domain_performance
elif options.show_domain_console:
show_window = vmmEngine.CLI_SHOW_DOMAIN_CONSOLE
domain = options.show_domain_console
elif options.show_domain_delete:
show_window = vmmEngine.CLI_SHOW_DOMAIN_DELETE
domain = options.show_domain_delete
elif options.show_systray:
show_window = vmmEngine.CLI_SHOW_SYSTEM_TRAY
if (
show_window and show_window != vmmEngine.CLI_SHOW_SYSTEM_TRAY and options.uri is None
): # pragma: no cover
raise RuntimeError("can't use --show-* options without --connect (except --show-systray)")
skip_autostart = False
if show_window and show_window != vmmEngine.CLI_SHOW_SYSTEM_TRAY:
skip_autostart = True
# Hook libvirt events into glib main loop
LibvirtGLib.init(None)
LibvirtGLib.event_register()
engine = vmmEngine.get_instance()
# Actually exit when we receive ctrl-c
from gi.repository import GLib
def _sigint_handler(user_data):
ignore = user_data
log.debug("Received KeyboardInterrupt. Exiting application.")
engine.exit_app()
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, _sigint_handler, None)
engine.start(options.uri, show_window, domain, skip_autostart)
def runcli():
try:
main()
except KeyboardInterrupt: # pragma: no cover
log.debug("Received KeyboardInterrupt. Exiting application.")
except Exception as run_e:
if "Gtk" not in globals():
raise # pragma: no cover
_show_startup_error(str(run_e), "".join(traceback.format_exc()))
|