class Anaconda(object): def __init__(self): from pyanaconda import desktop self._bootloader = None self.desktop = desktop.Desktop() self.dir = None self._display_mode = None self._interactive_mode = True self.gui_startup_failed = False self.id = None self._instClass = None self._intf = None self.isHeadless = False self.ksdata = None self.methodstr = None self.opts = None self._payload = None self.proxy = None self.decorated = False self.stage2 = None self._storage = None self.updateSrc = None self.mehConfig = None # *sigh* we still need to be able to write this out self.xdriver = None # Data for inhibiting the screensaver self.dbus_session_connection = None self.dbus_inhibit_id = None # This is used to synchronize Gtk.main calls between the graphical # interface and error dialogs. Whoever gets to their initialization code # first will lock gui_initializing self.gui_initialized = threading.Lock() # Create class for launching our dbus session self._dbus_launcher = DBusLauncher() @property def bootloader(self): if not self._bootloader: self._bootloader = get_bootloader() return self._bootloader @property def instClass(self): if not self._instClass: from pyanaconda.installclass import factory # Get install class by name. if self.ksdata.anaconda.installclass.seen: name = self.ksdata.anaconda.installclass.name self._instClass = factory.get_install_class_by_name(name) # Or just find the best one. else: self._instClass = factory.get_best_install_class() return self._instClass def _getInterface(self): return self._intf def _setInterface(self, v): # "lambda cannot contain assignment" self._intf = v def _delInterface(self): del self._intf intf = property(_getInterface, _setInterface, _delInterface) @property def payload(self): # Try to find the payload class. First try the install # class. If it doesn't give us one, fall back to the default. if not self._payload: klass = self.instClass.getBackend() if not klass: from pyanaconda.flags import flags if self.ksdata.ostreesetup.seen: from pyanaconda.payload.rpmostreepayload import RPMOSTreePayload klass = RPMOSTreePayload elif flags.livecdInstall: from pyanaconda.payload.livepayload import LiveImagePayload klass = LiveImagePayload elif self.ksdata.method.method == "liveimg": from pyanaconda.payload.livepayload import LiveImageKSPayload klass = LiveImageKSPayload else: from pyanaconda.payload.dnfpayload import DNFPayload klass = DNFPayload self._payload = klass(self.ksdata) return self._payload @property def protected(self): specs = [] if os.path.exists("/run/initramfs/livedev") and \ stat.S_ISBLK(os.stat("/run/initramfs/livedev")[stat.ST_MODE]): specs.append(os.readlink("/run/initramfs/livedev")) # methodstr and stage2 become strings in ways that pylint can't figure out # pylint: disable=unsubscriptable-object if self.methodstr and self.methodstr.startswith("hd:"): specs.append(self.methodstr[3:].split(":", 3)[0]) if self.stage2 and self.stage2.startswith("hd:"): specs.append(self.stage2[3:].split(":", 3)[0]) # zRAM swap devices need to be protected for zram_dev in glob("/dev/zram*"): specs.append(zram_dev) return specs @property def storage(self): if not self._storage: from pyanaconda.storage.osinstall import InstallerStorage import blivet.arch self._storage = InstallerStorage(ksdata=self.ksdata) self._set_default_fstype(self._storage) if blivet.arch.is_s390(): self._load_plugin_s390() return self._storage @property def display_mode(self): return self._display_mode @display_mode.setter def display_mode(self, new_mode): if isinstance(new_mode, DisplayModes): if self._display_mode: old_mode = self._display_mode log.debug("changing display mode from %s to %s", old_mode.value, new_mode.value) else: log.debug("setting display mode to %s", new_mode.value) self._display_mode = new_mode else: # unknown mode name - ignore & log an error log.error("tried to set an unknown display mode name: %s", new_mode.value) @property def interactive_mode(self): return self._interactive_mode @interactive_mode.setter def interactive_mode(self, value): if self._interactive_mode != value: self._interactive_mode = value if value: log.debug("working in interative mode") else: log.debug("working in noninteractive mode") @property def gui_mode(self): """Report if Anaconda should run with the GUI.""" return self._display_mode == DisplayModes.GUI @property def tui_mode(self): """Report if Anaconda should run with the TUI.""" return self._display_mode == DisplayModes.TUI def log_display_mode(self): if not self.display_mode: log.error("Display mode is not set!") return log.info("Display mode is set to '%s %s'.", constants.INTERACTIVE_MODE_NAME[self.interactive_mode], constants.DISPLAY_MODE_NAME[self.display_mode]) def _set_default_fstype(self, storage): fstype = None boot_fstype = None # Get the default fstype from a kickstart file. if self.ksdata.autopart.autopart and self.ksdata.autopart.fstype: fstype = self.ksdata.autopart.fstype boot_fstype = self.ksdata.autopart.fstype # Or from an install class. elif self.instClass.defaultFS: fstype = self.instClass.defaultFS boot_fstype = None # Set the default fstype. if fstype: storage.set_default_fstype(fstype) # Set the default boot fstype. if boot_fstype: storage.set_default_boot_fstype(boot_fstype) def _load_plugin_s390(self): # Make sure s390 plugin is loaded. import gi gi.require_version("BlockDev", "2.0") from gi.repository import BlockDev as blockdev # Is the plugin loaded? We are done then. if "s390" in blockdev.get_available_plugin_names(): return # Otherwise, load the plugin. plugin = blockdev.PluginSpec() plugin.name = blockdev.Plugin.S390 plugin.so_name = None blockdev.reinit([plugin], reload=False) def dumpState(self): from meh import ExceptionInfo from meh.dump import ReverseExceptionDump from inspect import stack as _stack from traceback import format_stack # Skip the frames for dumpState and the signal handler. stack = _stack()[2:] stack.reverse() exn = ReverseExceptionDump(ExceptionInfo(None, None, stack), self.mehConfig) # gather up info on the running threads threads = "\nThreads\n-------\n" # Every call to sys._current_frames() returns a new dict, so it is not # modified when threads are created or destroyed. Iterating over it is # thread safe. for thread_id, frame in sys._current_frames().items(): threads += "\nThread %s\n" % (thread_id, ) threads += "".join(format_stack(frame)) # dump to a unique file (fd, filename) = mkstemp(prefix="anaconda-tb-", dir="/tmp") dump_text = exn.traceback_and_object_dump(self) dump_text += threads dump_text_bytes = dump_text.encode("utf-8") os.write(fd, dump_text_bytes) os.close(fd) # append to a given file with open("/tmp/anaconda-tb-all.log", "a+") as f: f.write("--- traceback: %s ---\n" % filename) f.write(dump_text + "\n") def initInterface(self, addon_paths=None): if self._intf: raise RuntimeError( "Second attempt to initialize the InstallInterface") if self.gui_mode: from pyanaconda.ui.gui import GraphicalUserInterface # Run the GUI in non-fullscreen mode, so live installs can still # use the window manager self._intf = GraphicalUserInterface(self.storage, self.payload, self.instClass, gui_lock=self.gui_initialized, fullscreen=False, decorated=self.decorated) # needs to be refreshed now we know if gui or tui will take place addon_paths = addons.collect_addon_paths(constants.ADDON_PATHS, ui_subdir="gui") elif self.tui_mode: # TUI and noninteractive TUI are the same in this regard from pyanaconda.ui.tui import TextUserInterface self._intf = TextUserInterface(self.storage, self.payload, self.instClass) # needs to be refreshed now we know if gui or tui will take place addon_paths = addons.collect_addon_paths(constants.ADDON_PATHS, ui_subdir="tui") elif not self.display_mode: raise RuntimeError("Display mode not set.") else: # this should generally never happen, as display_mode now won't let # and invalid value to be set, but let's leave it here just in case # something ultra crazy happens raise RuntimeError("Unsupported display mode: %s" % self.display_mode) if addon_paths: self._intf.update_paths(addon_paths) def postConfigureInstallClass(self): """Do an install class late configuration. This will enable to configure payload. """ self.instClass.configurePayload(self.payload) def writeXdriver(self, root=None): # this should go away at some point, but until it does, we # need to keep it around. if self.xdriver is None: return if root is None: root = util.getSysroot() if not os.path.isdir("%s/etc/X11" % (root, )): os.makedirs("%s/etc/X11" % (root, ), mode=0o755) f = open("%s/etc/X11/xorg.conf" % (root, ), 'w') f.write( 'Section "Device"\n\tIdentifier "Videocard0"\n\tDriver "%s"\nEndSection\n' % self.xdriver) f.close() def run_boss_with_dbus(self): """Ensure suitable DBus is running. If not, start a new session.""" self._dbus_launcher.start_dbus_session() self._dbus_launcher.write_bus_address() run_boss() def cleanup_dbus_session(self): """Stop our DBus services and our DBus session if it is our private DBus session. Our DBus is started when no session DBus is available. """ stop_boss() self._dbus_launcher.stop()
class InitialSetup(object): def __init__(self, gui_mode): """Initialize the Initial Setup internals""" log.debug("initializing Initial Setup") # True if running in graphical mode, False otherwise (text mode) self.gui_mode = gui_mode # kickstart data self.data = None # parse any command line arguments args = self._parse_arguments() # initialize logging initial_setup_log.init(stdout_log=not args.no_stdout_log) global logging_initialized logging_initialized = True log.info("Initial Setup %s" % __version__) # check if we are running as root if os.geteuid() != 0: log.critical("Initial Setup needs to be run as root") raise InitialSetupError if self.gui_mode: log.debug("running in GUI mode") else: log.debug("running in TUI mode") self._external_reconfig = False # check if the reconfig mode should be enabled # by checking if at least one of the reconfig # files exist for reconfig_file in RECONFIG_FILES: if os.path.exists(reconfig_file): self.external_reconfig = True log.debug("reconfig trigger file found: %s", reconfig_file) if self.external_reconfig: log.debug("running in externally triggered reconfig mode") if self.gui_mode: # We need this so we can tell GI to look for overrides objects # also in anaconda source directories import gi.overrides for p in os.environ.get("ANACONDA_WIDGETS_OVERRIDES", "").split(":"): gi.overrides.__path__.insert(0, p) log.debug("GI overrides imported") from pyanaconda.addons import collect_addon_paths addon_paths = ["/usr/share/initial-setup/modules", "/usr/share/anaconda/addons"] # append ADDON_PATHS dirs at the end sys.path.extend(addon_paths) self._addon_module_paths = collect_addon_paths(addon_paths, self.gui_mode_id) log.info("found %d addon modules:", len(self._addon_module_paths)) for addon_path in self._addon_module_paths: log.debug(addon_path) # Too bad anaconda does not have modularized logging log.debug("initializing the Anaconda log") from pyanaconda import anaconda_logging anaconda_logging.init() # init threading before Gtk can do anything and before we start using threads # initThreading initializes the threadMgr instance, import it afterwards log.debug("initializing threading") from pyanaconda.threading import initThreading initThreading() # initialize network logging (needed by the Network spoke that may be shown) log.debug("initializing network logging") from pyanaconda.network import setup_ifcfg_log setup_ifcfg_log() # create class for launching our dbus session self._dbus_launcher = DBusLauncher() @property def external_reconfig(self): """External reconfig status. Reports if external (eq. not triggered by kickstart) has been enabled. :returns: True if external reconfig mode has been enabled, else False. :rtype: bool """ return self._external_reconfig @external_reconfig.setter def external_reconfig(self, value): self._external_reconfig = value @property def gui_mode_id(self): """String id of the current GUI mode :returns: "gui" if gui_mode is True, "tui" otherwise :rtype: str """ if self.gui_mode: return "gui" else: return "tui" def _parse_arguments(self): """Parse command line arguments""" # create an argparse instance parser = argparse.ArgumentParser(prog="Initial Setup", description="Initial Setup is can run during the first start of a newly installed" "system to configure it according to the needs of the user.") parser.add_argument("--no-stdout-log", action="store_true", default=False, help="don't log to stdout") parser.add_argument('--version', action='version', version=__version__) # parse arguments and return the result return parser.parse_args() def _load_kickstart(self): """Load the kickstart""" from pyanaconda import kickstart # Construct a commandMap with only the supported Anaconda's commands commandMap = dict((k, kickstart.commandMap[k]) for k in SUPPORTED_KICKSTART_COMMANDS) # Prepare new data object self.data = kickstart.AnacondaKSHandler(self._addon_module_paths["ks"], commandUpdates=commandMap) kickstart_path = INPUT_KICKSTART_PATH if os.path.exists(OUTPUT_KICKSTART_PATH): log.info("using kickstart from previous run for input") kickstart_path = OUTPUT_KICKSTART_PATH log.info("parsing input kickstart %s", kickstart_path) try: # Read the installed kickstart parser = kickstart.AnacondaKSParser(self.data) parser.readKickstart(kickstart_path) log.info("kickstart parsing done") except pykickstart.errors.KickstartError as kserr: log.critical("kickstart parsing failed: %s", kserr) log.critical("Initial Setup startup failed due to invalid kickstart file") raise InitialSetupError # if we got this far the kickstart should be valid, so send it to Boss as well boss = BOSS.get_proxy() boss.SplitKickstart(kickstart_path) errors = boss.DistributeKickstart() if errors: message = "\n\n".join("{error_message}".format_map(e) for e in errors) raise InitialSetupError(message) if self.external_reconfig: # set the reconfig flag in kickstart so that # relevant spokes show up services_proxy = SERVICES.get_proxy() services_proxy.SetSetupOnBoot(SETUP_ON_BOOT_RECONFIG) def _setup_locale(self): log.debug("setting up locale") localization_proxy = LOCALIZATION.get_proxy() # Normalize the locale environment variables if localization_proxy.Kickstarted: locale_arg = localization_proxy.Language else: locale_arg = None setup_locale_environment(locale_arg, prefer_environment=True) setup_locale(os.environ['LANG'], text_mode=not self.gui_mode) def _apply(self): # Do not execute sections that were part of the original # anaconda kickstart file (== have .seen flag set) log.info("applying changes") sections = [self.data.keyboard, self.data.lang, self.data.timezone] # data.selinux # data.firewall localization_proxy = LOCALIZATION.get_proxy() self.data.keyboard.seen = localization_proxy.KeyboardKickstarted self.data.lang.seen = localization_proxy.LanguageKickstarted timezone_proxy = TIMEZONE.get_proxy() self.data.timezone.seen = timezone_proxy.Kickstarted log.info("executing kickstart") for section in sections: section_msg = "%s on line %d" % (repr(section), section.lineno) if section.seen: log.debug("skipping %s", section_msg) continue log.debug("executing %s", section_msg) section.execute(None, self.data, None) # Prepare the user database tools u = Users() sections = [self.data.group, self.data.user, self.data.rootpw] user_proxy = USER.get_proxy() self.data.rootpw.seen = user_proxy.IsRootpwKickstarted for section in sections: section_msg = "%s on line %d" % (repr(section), section.lineno) if section.seen: log.debug("skipping %s", section_msg) continue log.debug("executing %s", section_msg) section.execute(None, self.data, None, u) # Configure all addons log.info("executing addons") self.data.addons.execute(None, self.data, None, u, None) if self.external_reconfig: # prevent the reconfig flag from being written out # to kickstart if neither /etc/reconfigSys or /.unconfigured # are present services_proxy = SERVICES.get_proxy() services_proxy.SetSetupOnBoot(SETUP_ON_BOOT_DEFAULT) # Write the kickstart data to file log.info("writing the Initial Setup kickstart file %s", OUTPUT_KICKSTART_PATH) with open(OUTPUT_KICKSTART_PATH, "w") as f: f.write(str(self.data)) log.info("finished writing the Initial Setup kickstart file") # Remove the reconfig files, if any - otherwise the reconfig mode # would start again next time the Initial Setup service is enabled. if self.external_reconfig: for reconfig_file in RECONFIG_FILES: if os.path.exists(reconfig_file): log.debug("removing reconfig trigger file: %s" % reconfig_file) os.remove(reconfig_file) # and we are done with applying changes log.info("all changes have been applied") def run(self): """Run Initial setup :param bool gui_mode: if GUI should be used (TUI is the default) :returns: True if the IS run was successful, False if it failed :rtype: bool """ # start Boss & our DBUS session self.run_boss_with_dbus() # also register boss shutdown & DBUS session cleanup via exit handler atexit.register(self.cleanup_dbus_session) # Make sure that all DBus modules are ready. if not startup_utils.wait_for_modules(timeout=30): log.error("Anaconda DBus modules failed to start on time.") return True self._load_kickstart() self._setup_locale() # initialize the screen access manager before launching the UI screen_access.initSAM() if self.gui_mode: try: # Try to import IS gui specifics log.debug("trying to import GUI") import initial_setup.gui except ImportError: log.exception("GUI import failed, falling back to TUI") self.gui_mode = False if self.gui_mode: # gui already imported (see above) # Add addons to search paths initial_setup.gui.InitialSetupGraphicalUserInterface.update_paths(self._addon_module_paths) # Initialize the UI log.debug("initializing GUI") ui = initial_setup.gui.InitialSetupGraphicalUserInterface(None, None, PostInstallClass()) else: # Import IS gui specifics import initial_setup.tui # Add addons to search paths initial_setup.tui.InitialSetupTextUserInterface.update_paths(self._addon_module_paths) # Initialize the UI log.debug("initializing TUI") ui = initial_setup.tui.InitialSetupTextUserInterface(None, None, None) # Pass the data object to user interface log.debug("setting up the UI") ui.setup(self.data) # Start the application log.info("starting the UI") ret = ui.run() # TUI returns False if the app was ended prematurely # all other cases return True or None if ret is False: log.warning("ended prematurely in TUI") return True # apply changes self._apply() # in the TUI mode shutdown the multi TTY handler if not self.gui_mode: # TODO: wait for this to finish or make it blockng ? ui.multi_tty_handler.shutdown() # and we are done return True def run_boss_with_dbus(self): """Ensure suitable DBus is running. If not, start a new session.""" self._dbus_launcher.start_dbus_session() self._dbus_launcher.write_bus_address() run_boss(kickstart_modules=SUPPORTED_KICKSTART_MODULES) def cleanup_dbus_session(self): """Stop our DBus services and our DBus session if it is our private DBus session. Our DBus is started when no session DBus is available. """ stop_boss() self._dbus_launcher.stop()