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
2024-10-10 17:39:41 +03:00
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
2024-10-09 21:37:10 +03:00
if val == " no " : # pragma: no cover
2024-10-10 17:39:41 +03:00
log . debug ( " %s = %s , defaulting to --no-fork " , key , val )
return False
2024-10-09 21:37:10 +03:00
if val : # pragma: no cover
2024-10-10 17:39:41 +03:00
log . warning ( " Unknown %s = %s , expected ' yes ' or ' no ' " , key , val )
virt-manager: Switch to `--no-fork` by default
The reason we fork by default, is to force ssh to invoke
ssh-askpass when a password is required, rather than prompt on
a terminal no one is looking at. There's a more thorough
explanation here:
https://github.com/virt-manager/virt-manager/issues/731
With SSH_ASKPASS_REQUIRE=force, we now have a way to force ssh
to use askpass in the above scenario, when ssh and libvirt are new
enough.
The default forking behavior has caused maintenance pain in the
past, and is currently causing issues on macos:
https://github.com/virt-manager/virt-manager/issues/620
Let's flip the default to `--no-fork`. The VIRT_MANAGER_DEFAULT_FORK
env variable is there as an escape hatch incase I really miscalculated.
I don't expect many people are depending on use of askpass either
way, or if they are, they are launching virt-manager from their
desktop and not a terminal, which already gives us the correct
behavior AFAICT>
My suspicion is barely anyone will notice, which is why
I'm ok with changing this now, despite the libvirt support being
brand new.
If this doesn't raise any issues, then we can eventually drop
the forking behavior all together.
Signed-off-by: Cole Robinson <crobinso@redhat.com>
2024-10-10 17:52:20 +03:00
# Default is `--no-fork`
return False
2024-10-10 17:28:45 +03:00
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 ( ) ) )