2020-01-27 01:12:09 +03:00
# 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
2021-05-22 20:26:32 +03:00
gi . require_version ( " Gdk " , " 3.0 " )
gi . require_version ( " Gtk " , " 3.0 " )
2020-01-27 01:12:09 +03:00
gi . require_version ( ' LibvirtGLib ' , ' 1.0 ' )
from gi . repository import LibvirtGLib
from virtinst import BuildConfig
from virtinst import cli
from virtinst import log
2020-08-24 23:03:00 +03:00
from . lib . testmock import CLITestOptionsClass
2021-02-08 20:59:29 +03:00
# 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 "
2020-01-27 01:12:09 +03:00
try :
2021-02-08 20:59:29 +03:00
gi . check_version ( _PYGOBJECT_VERSION )
2020-08-28 18:48:00 +03:00
except ( ValueError , AttributeError ) : # pragma: no cover
2021-02-08 20:59:29 +03:00
print ( " pygobject3 %s or later is required. " % _PYGOBJECT_VERSION )
2020-01-27 01:12:09 +03:00
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 " )
2020-09-17 09:44:05 +03:00
errmsg = ( _ ( " Error starting Virtual Machine Manager: %(error)s " ) %
{ " error " : msg } )
err . show_err ( errmsg ,
2020-01-27 01:12:09 +03:00
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 : ]
2020-08-28 18:48:00 +03:00
if Gtk . check_version ( 3 , 22 , 0 ) : # pragma: no cover
2020-01-27 01:12:09 +03:00
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 :
2020-08-28 18:48:00 +03:00
# pylint: disable=protected-access
os . _exit ( 0 ) # pragma: no cover
2020-01-27 01:12:09 +03:00
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 )
2020-08-28 18:48:00 +03:00
except OSError : # pragma: no cover
2020-01-27 01:12:09 +03:00
pass
os . open ( os . devnull , os . O_RDWR )
os . dup2 ( 0 , 1 )
os . dup2 ( 0 , 2 )
2024-10-10 17:28:45 +03:00
def do_we_fork ( options ) :
if options . debug or options . no_fork :
return False
if options . fork :
return True
# Default is `--fork`
return True
2020-01-27 01:12:09 +03:00
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
2020-08-24 23:03:00 +03:00
parser . add_argument ( " --test-options " , action = ' append ' ,
default = [ ] , help = argparse . SUPPRESS )
2020-01-27 01:12:09 +03:00
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 " )
2024-10-10 17:28:45 +03:00
parser . add_argument ( " --fork " , action = " store_true " ,
help = " Force fork into background on startup (this is the default) " )
2020-01-27 01:12:09 +03:00
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 " )
2021-11-04 03:45:04 +03:00
parser . add_argument ( " --show-systray " , action = " store_true " ,
2024-03-03 19:54:37 +03:00
help = " Launch virt-manager only in system tray " )
2020-01-27 01:12:09 +03:00
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 )
2020-08-29 20:07:19 +03:00
CLITestOptions = CLITestOptionsClass ( options . test_options )
2020-08-23 21:58:01 +03:00
2020-01-27 01:12:09 +03:00
# With F27 gnome+wayland we need to set these before GTK import
os . environ [ " GSETTINGS_SCHEMA_DIR " ] = BuildConfig . gsettings_dir
2024-09-24 16:41:07 +03:00
# 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 " ] )
2020-01-27 01:12:09 +03:00
# Now we've got basic environment up & running we can fork
2024-10-10 17:28:45 +03:00
do_fork = do_we_fork ( options )
if do_fork :
2020-01-27 01:12:09 +03:00
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
2024-10-10 17:28:45 +03:00
if do_fork :
2020-01-27 01:12:09 +03:00
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
2021-11-04 03:45:04 +03:00
elif options . show_systray :
show_window = vmmEngine . CLI_SHOW_SYSTEM_TRAY
2020-01-27 01:12:09 +03:00
2021-11-04 03:45:04 +03:00
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) " )
2020-01-27 01:12:09 +03:00
skip_autostart = False
2021-11-04 03:45:04 +03:00
if show_window and show_window != vmmEngine . CLI_SHOW_SYSTEM_TRAY :
2020-01-27 01:12:09 +03:00
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 ( )
2020-08-28 18:48:00 +03:00
except KeyboardInterrupt : # pragma: no cover
2020-01-27 01:12:09 +03:00
log . debug ( " Received KeyboardInterrupt. Exiting application. " )
except Exception as run_e :
if " Gtk " not in globals ( ) :
2020-08-28 18:48:00 +03:00
raise # pragma: no cover
2020-01-27 01:12:09 +03:00
_show_startup_error ( str ( run_e ) , " " . join ( traceback . format_exc ( ) ) )