Includes bcl's suggestions.
I couldn't really see any Requires to remove, but someone else clue me in since I'm sure I might be missing something.
New rescue mode doesn't require these; nothing else uses them.
Related: rhbz#965985 --- po/POTFILES.in | 2 - pyanaconda/installinterfacebase.py | 32 ---------- pyanaconda/text.py | 128 ------------------------------------- 3 files changed, 162 deletions(-) delete mode 100644 pyanaconda/installinterfacebase.py delete mode 100644 pyanaconda/text.py
diff --git a/po/POTFILES.in b/po/POTFILES.in index 7b5d1ac..3d226fb 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -11,7 +11,6 @@ pyanaconda/exception.py pyanaconda/image.py pyanaconda/install.py pyanaconda/installclass.py -pyanaconda/installinterfacebase.py pyanaconda/iutil.py pyanaconda/kickstart.py pyanaconda/network.py @@ -19,7 +18,6 @@ pyanaconda/product.py pyanaconda/regexes.py pyanaconda/rescue.py pyanaconda/storage_utils.py -pyanaconda/text.py pyanaconda/users.py pyanaconda/vnc.py
diff --git a/pyanaconda/installinterfacebase.py b/pyanaconda/installinterfacebase.py deleted file mode 100644 index c657236..0000000 --- a/pyanaconda/installinterfacebase.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# installinterfacebase.py: a baseclass for anaconda interface classes -# -# Copyright (C) 2010 Red Hat, Inc. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# -# Author(s): Hans de Goede hdegoede@redhat.com - -import logging -log = logging.getLogger("anaconda") - -class InstallInterfaceBase(object): - def messageWindow(self, title, text, ty="ok", default = None, - custom_buttons=None, custom_icon=None): - raise NotImplementedError - - def detailedMessageWindow(self, title, text, longText=None, ty="ok", - default=None, custom_icon=None, - custom_buttons=None, expanded=False): - raise NotImplementedError diff --git a/pyanaconda/text.py b/pyanaconda/text.py deleted file mode 100644 index a049c30..0000000 --- a/pyanaconda/text.py +++ /dev/null @@ -1,128 +0,0 @@ -# -# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Red Hat, Inc. -# All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# -# Author(s): Erik Troan ewt@redhat.com -# Matt Wilson msw@redhat.com -# - -from snack import ButtonBar, ButtonChoiceWindow, Entry, GridForm, Scale, TextboxReflowed -from pyanaconda.constants_text import TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON, TEXT_OK_CHECK -from pyanaconda.i18n import _ - -class WaitWindow: - def pop(self): - self.screen.popWindow() - self.screen.refresh() - - def refresh(self): - pass - - def __init__(self, screen, title, text): - self.screen = screen - width = 40 - if (len(text) < width): - width = len(text) - - t = TextboxReflowed(width, text) - - g = GridForm(self.screen, title, 1, 1) - g.add(t, 0, 0) - g.draw() - self.screen.refresh() - -class OkCancelWindow: - def getrc(self): - return self.rc - - def __init__(self, screen, title, text): - rc = ButtonChoiceWindow(screen, title, text, buttons=[TEXT_OK_BUTTON, _("Cancel")]) - if rc == _("Cancel").lower(): - self.rc = 1 - else: - self.rc = 0 - -class ProgressWindow: - def pop(self): - self.screen.popWindow() - self.screen.refresh() - del self.scale - self.scale = None - - def pulse(self): - pass - - def set(self, amount): - self.scale.set(int(float(amount) * self.multiplier)) - self.screen.refresh() - - def refresh(self): - pass - - def __init__(self, screen, title, text, total, updpct = 0.05, pulse = False): - self.multiplier = 1 - if total == 1.0: - self.multiplier = 100 - self.screen = screen - width = 55 - if (len(text) > width): - width = len(text) - - t = TextboxReflowed(width, text) - - g = GridForm(self.screen, title, 1, 2) - g.add(t, 0, 0, (0, 0, 0, 1), anchorLeft=1) - - self.scale = Scale(int(width), int(float(total) * self.multiplier)) - if not pulse: - g.add(self.scale, 0, 1) - - g.draw() - self.screen.refresh() - -class PassphraseEntryWindow: - def __init__(self, screen, device): - self.screen = screen - self.txt = _("Device %s is encrypted. In order to " - "access the device's contents during " - "installation you must enter the device's " - "passphrase below.") % (device,) - self.rc = None - - def run(self): - toplevel = GridForm(self.screen, _("Passphrase"), 1, 3) - - txt = TextboxReflowed(65, self.txt) - toplevel.add(txt, 0, 0) - - passphraseentry = Entry(60, password = 1) - toplevel.add(passphraseentry, 0, 1, (0,0,0,1)) - - buttons = ButtonBar(self.screen, [TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON]) - toplevel.add(buttons, 0, 2, growx=1) - - rc = toplevel.run() - res = buttons.buttonPressed(rc) - - passphrase = None - if res == TEXT_OK_CHECK or rc == "F12": - passphrase = passphraseentry.value().strip() - - self.rc = passphrase - return self.rc - - def pop(self): - self.screen.popWindow()
Without ncurses, we don't need to keep these around anymore for the new text mode.
Related: rhbz#965985 --- pyanaconda/constants_text.py | 32 -------------------------------- 1 file changed, 32 deletions(-)
diff --git a/pyanaconda/constants_text.py b/pyanaconda/constants_text.py index 750a984..d31478a 100644 --- a/pyanaconda/constants_text.py +++ b/pyanaconda/constants_text.py @@ -21,38 +21,6 @@ from pyanaconda.constants import * from pyanaconda.i18n import _, N_
-class Translator: - """A simple class to facilitate on-the-fly translation for newt buttons""" - def __init__(self, button, check): - self.button = button - self.check = check - - def __getitem__(self, which): - if which == 0: - return _(self.button) - elif which == 1: - return self.check - raise IndexError - - def __len__(self): - return 2 - -TEXT_OK_STR = N_("OK") -TEXT_OK_CHECK = "ok" -TEXT_OK_BUTTON = Translator(TEXT_OK_STR, TEXT_OK_CHECK) - -TEXT_CANCEL_STR = N_("Cancel") -TEXT_CANCEL_CHECK = "cancel" -TEXT_CANCEL_BUTTON = Translator(TEXT_CANCEL_STR, TEXT_CANCEL_CHECK) - -TEXT_YES_STR = N_("Yes") -TEXT_YES_CHECK = "yes" -TEXT_YES_BUTTON = Translator(TEXT_YES_STR, TEXT_YES_CHECK) - -TEXT_NO_STR = N_("No") -TEXT_NO_CHECK = "no" -TEXT_NO_BUTTON = Translator(TEXT_NO_STR, TEXT_NO_CHECK) - # Make the return calls from the UIScreen input() function more clear INPUT_PROCESSED = None INPUT_DISCARDED = False
This enables the installer to use the new rescue mode, and adds the new rescue mode file, which now uses the current TUI back-end.
There are a couple of small hacks in here, which were unfortunately necessary; otherwise it would mean basically rewriting the text mode code for input handling, which is not prudent at this moment.
Resolves: rhbz#965985 --- anaconda | 41 +-- pyanaconda/exception.py | 2 +- pyanaconda/rescue.py | 669 ++++++++++++++++++++--------------------- pyanaconda/ui/tui/tuiobject.py | 47 +++ 4 files changed, 388 insertions(+), 371 deletions(-)
diff --git a/anaconda b/anaconda index 360d5ad..4b70591 100755 --- a/anaconda +++ b/anaconda @@ -1304,10 +1304,6 @@ if __name__ == "__main__": if anaconda.displayMode != 'g' and not anaconda.ksdata.xconfig.startX: anaconda.ksdata.skipx.skipx = True
- if flags.rescue_mode: - from pyanaconda import rescue - anaconda.intf = rescue.RescueInterface() - # Set flag to prompt for missing ks data if anaconda.displayMode == 'c': flags.ksprompt = False @@ -1328,27 +1324,11 @@ if __name__ == "__main__": if image_count: anaconda.storage.setupDiskImages()
- # only install interactive exception handler in interactive modes - if ksdata.displaymode.displayMode != DISPLAY_MODE_CMDLINE or flags.debug: - from pyanaconda import exception - anaconda.mehConfig = exception.initExceptionHandling(anaconda) - - # add our own additional signal handlers - signal.signal(signal.SIGUSR1, lambda signum, frame: - exception.test_exception_handling()) - signal.signal(signal.SIGUSR2, lambda signum, frame: anaconda.dumpState()) - atexit.register(exitHandler, ksdata.reboot, anaconda.storage) - from blivet import storageInitialize from pyanaconda.packaging import payloadMgr from pyanaconda.network import networkInitialize, wait_for_connecting_NM_thread from pyanaconda.timezone import time_initialize
- if flags.rescue_mode: - rescue.doRescue(anaconda.intf, anaconda.rescue_mount, ksdata) - else: - cleanPStore() - networkInitialize(ksdata) if not flags.dirInstall: threadMgr.add(AnacondaThread(name=constants.THREAD_STORAGE, target=storageInitialize, @@ -1358,6 +1338,27 @@ if __name__ == "__main__":
threadMgr.add(AnacondaThread(name=constants.THREAD_WAIT_FOR_CONNECTING_NM, target=wait_for_connecting_NM_thread, args=(ksdata,)))
+ if flags.rescue_mode: + from pyanaconda.ui.tui.simpleline import App + from pyanaconda.rescue import RescueMode + app = App("Rescue Mode") + spoke = RescueMode(app, anaconda.ksdata, anaconda.storage) + app.schedule_screen(spoke) + app.run() + else: + cleanPStore() + + # only install interactive exception handler in interactive modes + if ksdata.displaymode.displayMode != DISPLAY_MODE_CMDLINE or flags.debug: + from pyanaconda import exception + anaconda.mehConfig = exception.initExceptionHandling(anaconda) + + # add our own additional signal handlers + signal.signal(signal.SIGUSR1, lambda signum, frame: + exception.test_exception_handling()) + signal.signal(signal.SIGUSR2, lambda signum, frame: anaconda.dumpState()) + atexit.register(exitHandler, ksdata.reboot, anaconda.storage) + # Fallback to default for interactive or for a kickstart with no installation method. fallback = not (flags.automatedInstall and ksdata.method.method) payloadMgr.restartThread(anaconda.storage, ksdata, anaconda.payload, anaconda.instClass, diff --git a/pyanaconda/exception.py b/pyanaconda/exception.py index b18c6cc..07ff9fc 100644 --- a/pyanaconda/exception.py +++ b/pyanaconda/exception.py @@ -252,7 +252,7 @@ def initExceptionHandling(anaconda): "_bootloader.password", "payload._groups", "payload._yum"], - localSkipList=[ "passphrase", "password", "_oldweak", "_password" ], + localSkipList=[ "passphrase", "password", "_oldweak", "_password" "try_passphrase" ], fileList=fileList)
conf.register_callback("lsblk_output", lsblk_callback, attchmnt_only=True) diff --git a/pyanaconda/rescue.py b/pyanaconda/rescue.py index 63bc0c1..96fa21d 100644 --- a/pyanaconda/rescue.py +++ b/pyanaconda/rescue.py @@ -1,7 +1,7 @@ # # rescue.py - anaconda rescue mode setup # -# Copyright (C) 2001, 2002, 2003, 2004 Red Hat, Inc. All rights reserved. +# Copyright (C) 2015 Red Hat, Inc. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,113 +16,35 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/. # -# Author(s): Mike Fulbright msf@redhat.com -# Jeremy Katz katzj@redhat.com +# Author(s): Samantha N. Bueno sbueno@redhat.com # -import sys -import os -from pyanaconda import iutil -import shutil -import time -import re -import subprocess - -from snack import ButtonChoiceWindow, ListboxChoiceWindow,SnackScreen +from blivet import mountExistingSystem, findExistingInstallations +from blivet.errors import StorageError, DirtyFSError +from blivet.devices import LUKSDevice
+from pyanaconda import iutil from pyanaconda.constants import ANACONDA_CLEANUP -from pyanaconda.constants_text import TEXT_OK_BUTTON, TEXT_NO_BUTTON, TEXT_YES_BUTTON -from pyanaconda.text import WaitWindow, OkCancelWindow, ProgressWindow, PassphraseEntryWindow +from pyanaconda.constants_text import INPUT_PROCESSED from pyanaconda.flags import flags -from pyanaconda.installinterfacebase import InstallInterfaceBase -from pyanaconda.i18n import _ +from pyanaconda.i18n import _, N_ from pyanaconda.kickstart import runPostScripts - -from blivet import mountExistingSystem -from blivet.errors import StorageError, DirtyFSError -from blivet.devices import LUKSDevice +from pyanaconda.ui.tui.simpleline import TextWidget, ColumnWidget, CheckboxWidget +from pyanaconda.ui.tui.spokes import NormalTUISpoke +from pyanaconda.ui.tui.tuiobject import YesNoDialog, PasswordDialog
from pykickstart.constants import KS_REBOOT, KS_SHUTDOWN
-import meh.ui.text +import os +import shutil +import time
import logging log = logging.getLogger("anaconda")
-class RescueInterface(InstallInterfaceBase): - def waitWindow(self, title, text): - return WaitWindow(self.screen, title, text) - - def progressWindow(self, title, text, total, updpct = 0.05, pulse = False): - return ProgressWindow(self.screen, title, text, total, updpct, pulse) - - def detailedMessageWindow(self, title, text, longText=None, ty="ok", - default=None, custom_icon=None, - custom_buttons=None, expanded=False): - return self.messageWindow(title, text, ty, default, custom_icon, - custom_buttons) - - def messageWindow(self, title, text, ty = "ok", default = None, - custom_icon=None, custom_buttons=None): - if custom_buttons is None: - custom_buttons = [] - - if ty == "ok": - ButtonChoiceWindow(self.screen, title, text, buttons=[TEXT_OK_BUTTON]) - elif ty == "yesno": - if default and default == "no": - btnlist = [TEXT_NO_BUTTON, TEXT_YES_BUTTON] - else: - btnlist = [TEXT_YES_BUTTON, TEXT_NO_BUTTON] - rc = ButtonChoiceWindow(self.screen, title, text, buttons=btnlist) - if rc == "yes": - return 1 - else: - return 0 - elif ty == "custom": - tmpbut = [] - for but in custom_buttons: - tmpbut.append(but.replace("_","")) - - rc = ButtonChoiceWindow(self.screen, title, text, width=60, buttons=tmpbut) - - idx = 0 - for b in tmpbut: - if b.lower() == rc: - return idx - idx += 1 - return 0 - else: - return OkCancelWindow(self.screen, title, text) - - def passphraseEntryWindow(self, device): - w = PassphraseEntryWindow(self.screen, device) - passphrase = w.run() - w.pop() - return passphrase - - @property - def meh_interface(self): - return self._meh_interface - - @property - def tty_num(self): - return 1 - - def shutdown (self): - self.screen.finish() - - def suspend(self): - pass +__all__ = ["RescueMode", "RootSpoke", "RescueMountSpoke"]
- def resume(self): - pass - - def __init__(self): - InstallInterfaceBase.__init__(self) - self.screen = SnackScreen() - self._meh_interface = meh.ui.text.TextIntf() - -def makeFStab(instPath = ""): +def makeFStab(instPath=""): + """Make the fs tab.""" if os.access("/proc/mounts", os.R_OK): f = open("/proc/mounts", "r") buf = f.read() @@ -138,8 +60,30 @@ def makeFStab(instPath = ""): except IOError as e: log.info("failed to write /etc/fstab: %s", e)
-# make sure they have a resolv.conf in the chroot +def run_shell(): + """Launch a shell.""" + if flags.imageInstall: + print(_("Run %s to unmount the system when you are finished.") + % ANACONDA_CLEANUP) + else: + print(_("When finished, please exit from the shell and your " + "system will reboot.")) + + proc = None + if proc is None or proc.returncode != 0: + if os.path.exists("/bin/bash"): + iutil.execConsole() + else: + print(_("Unable to find /bin/bash to execute! Not starting shell.")) + time.sleep(5) + + if not flags.imageInstall: + iutil.execWithRedirect("systemctl", ["--no-wall", "reboot"]) + else: + return None + def makeResolvConf(instPath): + """Make the resolv.conf file in the chroot.""" if flags.imageInstall: return
@@ -171,62 +115,125 @@ def makeResolvConf(instPath): f.write(buf) f.close()
-def runShell(screen = None, msg=""): - if screen: - screen.suspend() +class RescueMode(NormalTUISpoke): + title = N_("Rescue")
- print - if msg: - print(msg) + # If it acts like a spoke and looks like a spoke, is it a spoke? Not + # always. This is independent of any hub(s), so pass in some fake data + def __init__(self, app, data, storage=None, payload=None, instclass=None): + NormalTUISpoke.__init__(self, app, data, storage, payload, instclass) + if flags.automatedInstall: + self._ro = data.rescue.romount + else: + self._ro = False
- if flags.imageInstall: - print(_("Run %s to unmount the system when you are finished.") - % ANACONDA_CLEANUP) - else: - print(_("When finished please exit from the shell and your " - "system will reboot.")) - print + self._root = None + self._choices = (_("Continue"), _("Read-only mount"), _("Skip to shell"), ("Quit (Reboot)"))
- proc = None + def initialize(self): + NormalTUISpoke.initialize(self)
- if os.path.exists("/usr/bin/firstaidkit-qs"): - proc = subprocess.Popen(["/usr/bin/firstaidkit-qs"]) - proc.wait() + for f in ["services", "protocols", "group", "man.config", + "nsswitch.conf", "selinux", "mke2fs.conf"]: + try: + os.symlink('/mnt/runtime/etc/' + f, '/etc/' + f) + except OSError: + pass + + def prompt(self, args=None): + """ Override the default TUI prompt.""" + return _("Please make a selection from the above: ") + + def refresh(self, args=None): + NormalTUISpoke.refresh(self, args) + + self._window += [TextWidget(_("The rescue environment will now attempt " + "to find your Linux installation and mount it under " + "the directory : %s. You can then make any changes " + "required to your system. Choose '1' to proceed with " + "this step.\nYou can choose to mount your file" + "systems read-only instead of read-write by choosing " + "'2'.\nIf for some reason this process does not work " + "choose '3' to skip directly to a shell.\n\n") % (iutil.getSysroot())), ""] + + for idx, choice in enumerate(self._choices): + number = TextWidget("%2d)" % (idx + 1)) + c = ColumnWidget([(3, [number]), (None, [TextWidget(choice)])], 1) + self._window += [c, ""] + + return True + + def input(self, args, key): + """Override any input so we can launch rescue mode.""" + try: + keyid = int(key) - 1 + except ValueError: + pass
- if proc is None or proc.returncode!=0: - if os.path.exists("/bin/bash"): - iutil.execConsole() + if keyid == 3: + # quit/reboot + d = YesNoDialog(self.app, _(self.app.quit_message)) + self.app.switch_screen_modal(d) + if d.answer: + iutil.execWithRedirect("systemctl", ["--no-wall", "reboot"]) + elif keyid == 2: + # skip to/run shell + run_shell() + elif (keyid == 1 or keyid == 0): + # user chose 0 (continue/rw-mount) or 1 (ro-mount) + # decrypt any luks devices + self._unlock_devices() + + # this sleep may look pointless, but it seems necessary, in + # order for some task to complete; otherwise no existing + # installations are discovered. IOW, this is a hack. + time.sleep(2) + # attempt to find previous installations + roots = findExistingInstallations(self.storage.devicetree) + if len(roots) == 1: + self._root = roots[0] + elif len(roots) > 1: + # have to prompt user for which root to mount + rootspoke = RootSpoke(self.app, self.data, self.storage, + self.payload, self.instclass, roots) + self.app.switch_screen_modal(rootspoke) + self._root = rootspoke.root + + # if only one root detected, or user has chosen which root + # to mount, go ahead and do that + newspoke = RescueMountSpoke(self.app, self.data, + self.storage, self.payload, self.instclass, keyid, self._root) + self.app.switch_screen_modal(newspoke) + self.close() else: - print(_("Unable to find /bin/sh to execute! Not starting shell")) - time.sleep(5) - - if screen: - screen.finish() - -def _exception_handler_wrapper(orig_except_handler, screen, *args): - """ - Helper function that wraps the exception handler with snack shutdown. + # user entered some invalid number choice + return key +
- :param orig_except_handler: original exception handler that should be run - after the wrapping changes are done - :type orig_except_handler: exception handler as can be set as sys.excepthook - :param screen: snack screen that should be shut down before further actions - :type screen: snack screen + return INPUT_PROCESSED
- """ + def apply(self): + """Move along home.""" + pass
- screen.finish() - return orig_except_handler(*args) + def _unlock_devices(self): + """ + Loop through devices and attempt to unlock any which are detected as + LUKS devices. + """ + try_passphrase = None + for device in self.storage.devices: + if device.format.type != "luks": + continue
-def _unlock_devices(intf, storage): - try_passphrase = None - for device in storage.devices: - if device.format.type == "luks": skip = False unlocked = False while not (skip or unlocked): if try_passphrase is None: - passphrase = intf.passphraseEntryWindow(device.name) + p = PasswordDialog(self.app, device.name) + self.app.switch_screen_modal(p) + if p.answer: + passphrase = p.answer.strip() else: passphrase = try_passphrase
@@ -241,8 +248,8 @@ def _unlock_devices(intf, storage): luks_dev = LUKSDevice(device.format.mapName, parents=[device], exists=True) - storage.devicetree._addDevice(luks_dev) - storage.devicetree.populate() + self.storage.devicetree._addDevice(luks_dev) + self.storage.devicetree.populate() unlocked = True # try to use the same passhprase for other devices try_passphrase = passphrase @@ -251,239 +258,201 @@ def _unlock_devices(intf, storage): device.teardown(recursive=True) device.format.passphrase = None try_passphrase = None + return True
-def doRescue(intf, rescue_mount, ksdata): - import blivet - - # XXX: hook the exception handler wrapper that turns off snack first - orig_hook = sys.excepthook - sys.excepthook = lambda ty, val, tb: _exception_handler_wrapper(orig_hook, - intf.screen, - ty, val, tb) +class RootSpoke(NormalTUISpoke): + title = N_("Root Selection")
- for f in [ "services", "protocols", "group", "joe", "man.config", - "nsswitch.conf", "selinux", "mke2fs.conf" ]: - try: - os.symlink('/mnt/runtime/etc/' + f, '/etc/' + f) - except OSError: - pass + def __init__(self, app, data, storage, payload, instclass, roots): + NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)
- # Early shell access with no disk access attempts - if not rescue_mount: - # the %post should be responsible for mounting all needed file systems - # NOTE: 1st script must be bash or simple python as nothing else might be available in the rescue image - if flags.automatedInstall and ksdata.scripts: - runPostScripts(ksdata.scripts) - else: - runShell() + self._root = None + self._roots = roots + # default to selecting the first root in the list + self._selection = 0
- sys.exit(0) + @property + def indirect(self): + return True
- if flags.automatedInstall: - readOnly = ksdata.rescue.romount - else: - # prompt to see if we should try and find root filesystem and mount - # everything in /etc/fstab on that root - while True: - rc = ButtonChoiceWindow(intf.screen, _("Rescue"), - _("The rescue environment will now attempt to find your " - "Linux installation and mount it under the directory " - "%s. You can then make any changes required to your " - "system. If you want to proceed with this step choose " - "'Continue'. You can also choose to mount your file systems " - "read-only instead of read-write by choosing 'Read-Only'. " - "\n\n" - "If for some reason this process fails you can choose 'Skip' " - "and this step will be skipped and you will go directly to a " - "command shell.\n\n") % (iutil.getSysroot(),), - [_("Continue"), _("Read-Only"), _("Skip")] ) - - if rc == _("Skip").lower(): - runShell(intf.screen) - sys.exit(0) - else: - readOnly = rc == _("Read-Only").lower() + def refresh(self, args=None): + NormalTUISpoke.refresh(self, args)
- break + message = _("The following installations were discovered on your system.\n") + self._window += [TextWidget(message), ""]
- sto = blivet.Blivet(ksdata=ksdata) - blivet.storageInitialize(sto, ksdata, []) - _unlock_devices(intf, sto) - roots = blivet.findExistingInstallations(sto.devicetree) + for i, root in enumerate(self._roots): + box = CheckboxWidget(title="%i) %s on %s" % (i + 1, _(root.name), root.device.path), + completed=(self._selection == i)) + self._window += [box, ""]
- if not roots: - root = None - elif len(roots) == 1: - root = roots[0] - else: - height = min (len (roots), 12) - if height == 12: - scroll = 1 - else: - scroll = 0 - - lst = [] - for root in roots: - lst.append("%s" % root.name) - - (button, choice) = \ - ListboxChoiceWindow(intf.screen, _("System to Rescue"), - _("Which device holds the root partition " - "of your installation?"), lst, - [ _("OK"), _("Exit") ], width = 30, - scroll = scroll, height = height, - help = "multipleroot") - - if button == _("Exit").lower(): - root = None - else: - root = roots[choice] + return True
- rootmounted = False + def prompt(self, args=None): + """ Override the default TUI prompt.""" + return _("Please make your selection from the above list.\nPress 'c' " + "to continue after you have made your selection. ")
- if root: + def input(self, args, key): + """Move along home.""" try: - if not flags.imageInstall: - msg = _("The system will reboot automatically when you exit " - "from the shell.") + keyid = int(key) - 1 + except ValueError: + if key.lower() == "c": + self.apply() + self.close() + return INPUT_PROCESSED else: - msg = _("Run %s to unmount the system " - "when you are finished.") % ANACONDA_CLEANUP + return key + + if 0 <= keyid < len(self._roots): + self._selection = keyid + return INPUT_PROCESSED + + def apply(self): + """Apply our selection.""" + self._root = self._roots[self._selection] + + @property + def root(self): + """The selected root fs to mount.""" + return self._root + +class RescueMountSpoke(NormalTUISpoke): + # 1 = continue/rw-mount, 2 = ro-mount + title = N_("Rescue Mount") + + def __init__(self, app, data, storage, payload, instclass, selection, root): + NormalTUISpoke.__init__(self, app, data, storage, payload, instclass) + + self.readOnly = selection + # root to mount + self._root = root
+ def refresh(self, args=None): + NormalTUISpoke.refresh(self, args) + + if self._root: try: - mountExistingSystem(sto.fsset, root.device, - allowDirty = True, - readOnly = readOnly) - except DirtyFSError: - if flags.automatedInstall: - log.error("System had dirty file systems which you chose not to mount") - else: - ButtonChoiceWindow(intf.screen, _("Rescue"), - _("Your system had dirty file systems which you chose not " - "to mount. Press return to get a shell from which " - "you can fsck and mount your partitions. %s") % msg, - [_("OK")], width = 50) - rootmounted = False - else: - if flags.automatedInstall: - log.info("System has been mounted under: %s", iutil.getSysroot()) - else: - ButtonChoiceWindow(intf.screen, _("Rescue"), - _("Your system has been mounted under %(rootPath)s.\n\n" - "Press <return> to get a shell. If you would like to " - "make your system the root environment, run the command:\n\n" - "\tchroot %(rootPath)s\n\n%(msg)s") % - {'rootPath': iutil.getSysroot(), - 'msg': msg}, - [_("OK")] ) - rootmounted = True - - # now turn on swap - if not readOnly: - try: - sto.turnOnSwap() - except StorageError: - log.error("Error enabling swap") - - # and selinux too - if flags.selinux: - # we have to catch the possible exception - # because we support read-only mounting - try: - fd = open("%s/.autorelabel" % iutil.getSysroot(), "w+") - fd.close() - except IOError: - log.warning("cannot touch /.autorelabel") - - # set a library path to use mounted fs - libdirs = os.environ.get("LD_LIBRARY_PATH", "").split(":") - mounted = map(lambda dir: "/mnt/sysimage%s" % dir, libdirs) - os.environ["LD_LIBRARY_PATH"] = ":".join(libdirs + mounted) - - # find groff data dir - gversion = None try: - glst = os.listdir("/mnt/sysimage/usr/share/groff") - except OSError: - pass + mountExistingSystem(self.storage.fsset, self._root.device, + allowDirty=True, readOnly=self.readOnly) + except DirtyFSError: + rootmounted = False + if flags.automatedInstall: + log.error("System had dirty file systems which you chose not to mount") + else: + text = TextWidget(_("Your system had dirty file systems which you chose " + "not to mount. From a shell, you can fsck and mount " + "your partitions manually.\n")) + self._window.append(text) + return True else: - # find a directory which is a numeral, its where - # data files are - for gdir in glst: - if re.match(r'\d[.\d]+\d$', gdir): - gversion = gdir - break - - if gversion is not None: - gpath = "/mnt/sysimage/usr/share/groff/"+gversion - os.environ["GROFF_FONT_PATH"] = gpath + '/font' - os.environ["GROFF_TMAC_PATH"] = "%s:/mnt/sysimage/usr/share/groff/site-tmac" % (gpath + '/tmac',) - - # do we have bash? - try: - if os.access("/usr/bin/bash", os.R_OK): - os.symlink ("/usr/bin/bash", "/bin/bash") - except OSError: - pass - except (ValueError, LookupError, SyntaxError, NameError): - raise - except Exception as e: - log.error("doRescue caught exception: %s", e) - if flags.automatedInstall: - log.error("An error occurred trying to mount some or all of your system") + if flags.automatedInstall: + log.info("System has been mounted under: %s", iutil.getSysroot()) + else: + text = TextWidget(_("Your system has been mounted under %s.\n\nIf " + "you would like to make your system the root " + "environment, run the command:\n\n\tchroot %s\n" + % (iutil.getSysroot(), iutil.getSysroot()))) + self._window.append(text) + rootmounted = True + + # now turn on swap + if not flags.imageInstall or not self.readOnly: + try: + self.storage.turnOnSwap() + except StorageError: + log.error("Error enabling swap.") + + # turn on selinux also + if flags.selinux: + # we have to catch the possible exception, because we + # support read-only mounting + try: + fd = open("%s/.autorelabel" % iutil.getSysroot(), "w+") + fd.close() + except IOError: + log.warning("Cannot touch %s/.autorelabel", iutil.getSysroot()) + + # set a libpath to use mounted fs + libdirs = os.environ.get("LD_LIBRARY_PATH", "").split(":") + mounted = map(lambda dir: "/mnt/sysimage%s" % dir, libdirs) + os.environ["LD_LIBRARY_PATH"] = ":".join(libdirs + mounted) + + # do we have bash? + try: + if os.access("/usr/bin/bash", os.R_OK): + os.symlink("/usr/bin/bash", "/bin/bash") + except OSError: + pass + except (ValueError, LookupError, SyntaxError, NameError): + pass + except Exception as e: + if flags.automatedInstall: + msg = _("Run %s to unmount the system when you are finished.\n") % ANACONDA_CLEANUP + + text = TextWidget(_("An error occurred trying to mount some or all of " + "your system. Some of it may be mounted under %s\n\n") + iutil.getSysroot() + msg) + self._window.append(text) + return True + else: + if flags.automatedInstall and self.data.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: + log.info("No Linux partitions found.") + text = TextWidget(_("You don't have any Linux partitions. Rebooting.\n")) + self._window.append(text) + # should probably wait a few seconds to show the message + time.sleep(5) + iutil.execWithRedirect("systemctl", ["--no-wall", "reboot"]) else: if not flags.imageInstall: - msg = _("The system will reboot automatically when you " - "exit from the shell.") + msg = _("The system will reboot automatically when you exit" + " from the shell.\n") else: - msg = _("Run %s to unmount the system " - "when you are finished.") % ANACONDA_CLEANUP - - ButtonChoiceWindow(intf.screen, _("Rescue"), - _("An error occurred trying to mount some or all of your " - "system. Some of it may be mounted under %s.\n\n" - "Press <return> to get a shell.") % iutil.getSysroot() + msg, - [_("OK")] ) - else: - if flags.automatedInstall and ksdata.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: - log.info("No Linux partitions found") - intf.screen.finish() - print(_("You don't have any Linux partitions. Rebooting.\n")) - sys.exit(0) - else: - if not flags.imageInstall: - msg = _(" The system will reboot automatically when you exit " - "from the shell.") - else: - msg = "" - ButtonChoiceWindow(intf.screen, _("Rescue Mode"), - _("You don't have any Linux partitions. Press " - "return to get a shell.%s") % msg, - [ _("OK") ], width = 50) + msg = "" + text = TextWidget(_("You don't have any Linux partitions. %s\n") % msg) + self._window.append(text) + return True
- msgStr = "" + if rootmounted and not self.readOnly: + self.storage.makeMtab() + try: + makeResolvConf(iutil.getSysroot()) + except(OSError, IOError) as e: + log.error("Error making resolv.conf: %s", e) + text = TextWidget(_("Your system is mounted under the %s directory.") % iutil.getSysroot()) + self._window.append(text)
- if rootmounted and not readOnly: - sto.makeMtab() - try: - makeResolvConf(iutil.getSysroot()) - except (OSError, IOError) as e: - log.error("error making a resolv.conf: %s", e) - msgStr = _("Your system is mounted under the %s directory.") % (iutil.getSysroot(),) - ButtonChoiceWindow(intf.screen, _("Rescue"), msgStr, [_("OK")] ) + # create /etc/fstab in ramdisk so it's easier to work with RO mounted fs + makeFStab()
- # we do not need ncurses anymore, shut them down - intf.shutdown() + # run %post if we've mounted everything + if rootmounted and not self.readOnly and flags.automatedInstall: + runPostScripts(self.data.scripts)
- #create /etc/fstab in ramdisk, so it is easier to work with RO mounted filesystems - makeFStab() + return True
- # run %post if we've mounted everything - if rootmounted and not readOnly and flags.automatedInstall: - runPostScripts(ksdata.scripts) + def apply(self): + if flags.automatedInstall and self.data.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: + iutil.execWithRedirect("systemctl", ["--no-wall", "reboot"])
- # start shell if reboot wasn't requested - if not flags.automatedInstall or not ksdata.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: - runShell(msg=msgStr) + if not flags.automatedInstall or not self.data.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: + run_shell() + + def prompt(self, args=None): + """ Override the default TUI prompt.""" + return _("Please press <return> to get a shell. ") + + def input(self, args, key): + """Move along home.""" + run_shell() + + if not flags.imageInstall: + iutil.execWithRedirect("systemctl", ["--no-wall", "reboot"]) + + return INPUT_PROCESSED + + @property + def indirect(self): + return True
- sys.exit(0) diff --git a/pyanaconda/ui/tui/tuiobject.py b/pyanaconda/ui/tui/tuiobject.py index 0a5243f..73e7283 100644 --- a/pyanaconda/ui/tui/tuiobject.py +++ b/pyanaconda/ui/tui/tuiobject.py @@ -55,6 +55,53 @@ class ErrorDialog(tui.UIScreen): self.close() return INPUT_PROCESSED
+class PasswordDialog(tui.UIScreen): + """Dialog screen for password input.""" + + title = N_("Password") + + def __init__(self, app, device): + """ + :param app: the running application reference + :type app: instance of App class + """ + + tui.UIScreen.__init__(self, app) + self._device = device + self._message = "You must enter your LUKS passphrase to decrypt device %s" % device + self._password = None + + def refresh(self, args=None): + tui.UIScreen.refresh(self, args) + text = tui.TextWidget(self._message) + self._window.append(tui.CenterWidget(text)) + self._window.append(u"") + return True + + def prompt(self, args=None): + self._password = self.app.raw_input(_("Passphrase: "), hidden=True) + if not self._password: + return None + else: + # this may seem innocuous, but it's really a giant hack; we should + # not be calling close() from prompt(), but the input handling code + # in the TUI is such that without this very simple workaround, we + # would be forever pelting users with a prompt to enter their pw + self.close() + + @property + def answer(self): + """The response can be None (no response) or the password entered.""" + return self._password + + def input(self, args, key): + if key: + self._password = key + self.close() + return True + else: + return False + class YesNoDialog(tui.UIScreen): """Dialog screen for Yes - No questions."""
On Thu, 2015-06-18 at 13:44 -0400, Samantha N. Bueno wrote:
This enables the installer to use the new rescue mode, and adds the new rescue mode file, which now uses the current TUI back-end.
There are a couple of small hacks in here, which were unfortunately necessary; otherwise it would mean basically rewriting the text mode code for input handling, which is not prudent at this moment.
Resolves: rhbz#965985
anaconda | 41 +-- pyanaconda/exception.py | 2 +- pyanaconda/rescue.py | 669 ++++++++++++++++++++--------------------- pyanaconda/ui/tui/tuiobject.py | 47 +++ 4 files changed, 388 insertions(+), 371 deletions(-)
diff --git a/anaconda b/anaconda index 360d5ad..4b70591 100755 --- a/anaconda +++ b/anaconda @@ -1304,10 +1304,6 @@ if __name__ == "__main__": if anaconda.displayMode != 'g' and not anaconda.ksdata.xconfig.startX: anaconda.ksdata.skipx.skipx = True
- if flags.rescue_mode:
from pyanaconda import rescueanaconda.intf = rescue.RescueInterface()- # Set flag to prompt for missing ks data if anaconda.displayMode == 'c': flags.ksprompt = False
@@ -1328,27 +1324,11 @@ if __name__ == "__main__": if image_count: anaconda.storage.setupDiskImages()
# only install interactive exception handler in interactive modes
if ksdata.displaymode.displayMode != DISPLAY_MODE_CMDLINE or flags.debug:
from pyanaconda import exceptionanaconda.mehConfig = exception.initExceptionHandling(anaconda)# add our own additional signal handlers
signal.signal(signal.SIGUSR1, lambda signum, frame:
exception.test_exception_handling())signal.signal(signal.SIGUSR2, lambda signum, frame: anaconda.dumpState())
atexit.register(exitHandler, ksdata.reboot, anaconda.storage)
from blivet import storageInitialize from pyanaconda.packaging import payloadMgr from pyanaconda.network import networkInitialize, wait_for_connecting_NM_thread from pyanaconda.timezone import time_initialize
if flags.rescue_mode:
rescue.doRescue(anaconda.intf, anaconda.rescue_mount, ksdata)else:
cleanPStore()networkInitialize(ksdata) if not flags.dirInstall: threadMgr.add(AnacondaThread(name=constants.THREAD_STORAGE, target=storageInitialize,
@@ -1358,6 +1338,27 @@ if __name__ == "__main__":
threadMgr.add(AnacondaThread(name=constants.THREAD_WAIT_FOR_CONNECTING_NM, target=wait_for_connecting_NM_thread, args=(ksdata,)))
- if flags.rescue_mode:
from pyanaconda.ui.tui.simpleline import Appfrom pyanaconda.rescue import RescueModeapp = App("Rescue Mode")spoke = RescueMode(app, anaconda.ksdata, anaconda.storage)app.schedule_screen(spoke)app.run()- else:
cleanPStore()- # only install interactive exception handler in interactive modes
- if ksdata.displaymode.displayMode != DISPLAY_MODE_CMDLINE or flags.debug:
from pyanaconda import exceptionanaconda.mehConfig = exception.initExceptionHandling(anaconda)
Why is the exception handler initialization moved after the rescue mode session? Does our exception handling not work with the new rescue mode? If so, we should fix that and enable the exception handler for rescue too (in a follow-up patch in the future).
- # add our own additional signal handlers
- signal.signal(signal.SIGUSR1, lambda signum, frame:
exception.test_exception_handling())- signal.signal(signal.SIGUSR2, lambda signum, frame: anaconda.dumpState())
- atexit.register(exitHandler, ksdata.reboot, anaconda.storage)
- # Fallback to default for interactive or for a kickstart with no installation method. fallback = not (flags.automatedInstall and ksdata.method.method) payloadMgr.restartThread(anaconda.storage, ksdata, anaconda.payload, anaconda.instClass,
diff --git a/pyanaconda/exception.py b/pyanaconda/exception.py index b18c6cc..07ff9fc 100644 --- a/pyanaconda/exception.py +++ b/pyanaconda/exception.py @@ -252,7 +252,7 @@ def initExceptionHandling(anaconda): "_bootloader.password", "payload._groups", "payload._yum"],
localSkipList=[ "passphrase", "password", "_oldweak", "_password" ],
localSkipList=[ "passphrase", "password", "_oldweak", "_password" "try_passphrase" ], fileList=fileList)conf.register_callback("lsblk_output", lsblk_callback, attchmnt_only=True)
diff --git a/pyanaconda/rescue.py b/pyanaconda/rescue.py index 63bc0c1..96fa21d 100644 --- a/pyanaconda/rescue.py +++ b/pyanaconda/rescue.py @@ -1,7 +1,7 @@ # # rescue.py - anaconda rescue mode setup # -# Copyright (C) 2001, 2002, 2003, 2004 Red Hat, Inc. All rights reserved. +# Copyright (C) 2015 Red Hat, Inc. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,113 +16,35 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/. # -# Author(s): Mike Fulbright msf@redhat.com -# Jeremy Katz katzj@redhat.com +# Author(s): Samantha N. Bueno sbueno@redhat.com # -import sys -import os -from pyanaconda import iutil -import shutil -import time -import re -import subprocess
-from snack import ButtonChoiceWindow, ListboxChoiceWindow,SnackScreen +from blivet import mountExistingSystem, findExistingInstallations +from blivet.errors import StorageError, DirtyFSError +from blivet.devices import LUKSDevice
+from pyanaconda import iutil from pyanaconda.constants import ANACONDA_CLEANUP -from pyanaconda.constants_text import TEXT_OK_BUTTON, TEXT_NO_BUTTON, TEXT_YES_BUTTON -from pyanaconda.text import WaitWindow, OkCancelWindow, ProgressWindow, PassphraseEntryWindow +from pyanaconda.constants_text import INPUT_PROCESSED from pyanaconda.flags import flags -from pyanaconda.installinterfacebase import InstallInterfaceBase -from pyanaconda.i18n import _ +from pyanaconda.i18n import _, N_ from pyanaconda.kickstart import runPostScripts
-from blivet import mountExistingSystem -from blivet.errors import StorageError, DirtyFSError -from blivet.devices import LUKSDevice +from pyanaconda.ui.tui.simpleline import TextWidget, ColumnWidget, CheckboxWidget +from pyanaconda.ui.tui.spokes import NormalTUISpoke +from pyanaconda.ui.tui.tuiobject import YesNoDialog, PasswordDialog
from pykickstart.constants import KS_REBOOT, KS_SHUTDOWN
-import meh.ui.text +import os +import shutil +import time
import logging log = logging.getLogger("anaconda")
-class RescueInterface(InstallInterfaceBase):
- def waitWindow(self, title, text):
return WaitWindow(self.screen, title, text)- def progressWindow(self, title, text, total, updpct = 0.05, pulse = False):
return ProgressWindow(self.screen, title, text, total, updpct, pulse)- def detailedMessageWindow(self, title, text, longText=None, ty="ok",
default=None, custom_icon=None,custom_buttons=None, expanded=False):return self.messageWindow(title, text, ty, default, custom_icon,custom_buttons)- def messageWindow(self, title, text, ty = "ok", default = None,
custom_icon=None, custom_buttons=None):if custom_buttons is None:custom_buttons = []if ty == "ok":ButtonChoiceWindow(self.screen, title, text, buttons=[TEXT_OK_BUTTON])elif ty == "yesno":if default and default == "no":btnlist = [TEXT_NO_BUTTON, TEXT_YES_BUTTON]else:btnlist = [TEXT_YES_BUTTON, TEXT_NO_BUTTON]rc = ButtonChoiceWindow(self.screen, title, text, buttons=btnlist)if rc == "yes":return 1else:return 0elif ty == "custom":tmpbut = []for but in custom_buttons:tmpbut.append(but.replace("_",""))rc = ButtonChoiceWindow(self.screen, title, text, width=60, buttons=tmpbut)idx = 0for b in tmpbut:if b.lower() == rc:return idxidx += 1return 0else:return OkCancelWindow(self.screen, title, text)- def passphraseEntryWindow(self, device):
w = PassphraseEntryWindow(self.screen, device)passphrase = w.run()w.pop()return passphrase- @property
- def meh_interface(self):
return self._meh_interface- @property
- def tty_num(self):
return 1- def shutdown (self):
self.screen.finish()- def suspend(self):
pass+__all__ = ["RescueMode", "RootSpoke", "RescueMountSpoke"]
- def resume(self):
pass- def __init__(self):
InstallInterfaceBase.__init__(self)self.screen = SnackScreen()self._meh_interface = meh.ui.text.TextIntf()-def makeFStab(instPath = ""): +def makeFStab(instPath=""):
- """Make the fs tab.""" if os.access("/proc/mounts", os.R_OK): f = open("/proc/mounts", "r") buf = f.read()
@@ -138,8 +60,30 @@ def makeFStab(instPath = ""): except IOError as e: log.info("failed to write /etc/fstab: %s", e)
-# make sure they have a resolv.conf in the chroot +def run_shell():
- """Launch a shell."""
- if flags.imageInstall:
print(_("Run %s to unmount the system when you are finished.")% ANACONDA_CLEANUP)- else:
print(_("When finished, please exit from the shell and your ""system will reboot."))- proc = None
- if proc is None or proc.returncode != 0:
if os.path.exists("/bin/bash"):iutil.execConsole()else:print(_("Unable to find /bin/bash to execute! Not starting shell."))time.sleep(5)- if not flags.imageInstall:
iutil.execWithRedirect("systemctl", ["--no-wall", "reboot"])- else:
return Nonedef makeResolvConf(instPath):
- """Make the resolv.conf file in the chroot.""" if flags.imageInstall: return
@@ -171,62 +115,125 @@ def makeResolvConf(instPath): f.write(buf) f.close()
-def runShell(screen = None, msg=""):
- if screen:
screen.suspend()+class RescueMode(NormalTUISpoke):
- title = N_("Rescue")
- if msg:
print(msg)
- # If it acts like a spoke and looks like a spoke, is it a spoke? Not
- # always. This is independent of any hub(s), so pass in some fake data
- def __init__(self, app, data, storage=None, payload=None, instclass=None):
NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)if flags.automatedInstall:self._ro = data.rescue.romountelse:self._ro = False
- if flags.imageInstall:
print(_("Run %s to unmount the system when you are finished.")% ANACONDA_CLEANUP)- else:
print(_("When finished please exit from the shell and your ""system will reboot."))
self._root = Noneself._choices = (_("Continue"), _("Read-only mount"), _("Skip to shell"), ("Quit (Reboot)"))
- proc = None
- def initialize(self):
NormalTUISpoke.initialize(self)
- if os.path.exists("/usr/bin/firstaidkit-qs"):
proc = subprocess.Popen(["/usr/bin/firstaidkit-qs"])proc.wait()
for f in ["services", "protocols", "group", "man.config","nsswitch.conf", "selinux", "mke2fs.conf"]:try:os.symlink('/mnt/runtime/etc/' + f, '/etc/' + f)except OSError:pass- def prompt(self, args=None):
""" Override the default TUI prompt."""return _("Please make a selection from the above: ")- def refresh(self, args=None):
NormalTUISpoke.refresh(self, args)self._window += [TextWidget(_("The rescue environment will now attempt ""to find your Linux installation and mount it under ""the directory : %s. You can then make any changes ""required to your system. Choose '1' to proceed with ""this step.\nYou can choose to mount your file""systems read-only instead of read-write by choosing ""'2'.\nIf for some reason this process does not work ""choose '3' to skip directly to a shell.\n\n") % (iutil.getSysroot())), ""]for idx, choice in enumerate(self._choices):number = TextWidget("%2d)" % (idx + 1))c = ColumnWidget([(3, [number]), (None, [TextWidget(choice)])], 1)self._window += [c, ""]return True- def input(self, args, key):
"""Override any input so we can launch rescue mode."""try:keyid = int(key) - 1except ValueError:pass
- if proc is None or proc.returncode!=0:
if os.path.exists("/bin/bash"):iutil.execConsole()
if keyid == 3:# quit/rebootd = YesNoDialog(self.app, _(self.app.quit_message))self.app.switch_screen_modal(d)if d.answer:iutil.execWithRedirect("systemctl", ["--no-wall", "reboot"])
Shouldn't this reboot be guarded with 'if flags.imageInstall:' too?
On Fri, Jun 19, 2015 at 01:24:50PM +0200, Vratislav Podzimek wrote:
On Thu, 2015-06-18 at 13:44 -0400, Samantha N. Bueno wrote:
This enables the installer to use the new rescue mode, and adds the new rescue mode file, which now uses the current TUI back-end.
There are a couple of small hacks in here, which were unfortunately necessary; otherwise it would mean basically rewriting the text mode code for input handling, which is not prudent at this moment.
Resolves: rhbz#965985
anaconda | 41 +-- pyanaconda/exception.py | 2 +- pyanaconda/rescue.py | 669 ++++++++++++++++++++--------------------- pyanaconda/ui/tui/tuiobject.py | 47 +++ 4 files changed, 388 insertions(+), 371 deletions(-)
diff --git a/anaconda b/anaconda index 360d5ad..4b70591 100755 --- a/anaconda +++ b/anaconda @@ -1358,6 +1338,27 @@ if __name__ == "__main__":
threadMgr.add(AnacondaThread(name=constants.THREAD_WAIT_FOR_CONNECTING_NM, target=wait_for_connecting_NM_thread, args=(ksdata,)))
- if flags.rescue_mode:
from pyanaconda.ui.tui.simpleline import Appfrom pyanaconda.rescue import RescueModeapp = App("Rescue Mode")spoke = RescueMode(app, anaconda.ksdata, anaconda.storage)app.schedule_screen(spoke)app.run()- else:
cleanPStore()- # only install interactive exception handler in interactive modes
- if ksdata.displaymode.displayMode != DISPLAY_MODE_CMDLINE or flags.debug:
from pyanaconda import exceptionanaconda.mehConfig = exception.initExceptionHandling(anaconda)Why is the exception handler initialization moved after the rescue mode session? Does our exception handling not work with the new rescue mode? If so, we should fix that and enable the exception handler for rescue too (in a follow-up patch in the future).
Mostly because that was just oversight on my part. However, moving the exception handler initialization to before the rescue mode session seems to produce an error that I just can't seem to debug. I'll take your advice and fix it in some follow-up patch since I really need/want to get this set committed.
Thanks for that fourth patch you added as well!
Samantha
- # add our own additional signal handlers
- signal.signal(signal.SIGUSR1, lambda signum, frame:
exception.test_exception_handling())- signal.signal(signal.SIGUSR2, lambda signum, frame: anaconda.dumpState())
- atexit.register(exitHandler, ksdata.reboot, anaconda.storage)
- # Fallback to default for interactive or for a kickstart with no installation method. fallback = not (flags.automatedInstall and ksdata.method.method) payloadMgr.restartThread(anaconda.storage, ksdata, anaconda.payload, anaconda.instClass,
diff --git a/pyanaconda/exception.py b/pyanaconda/exception.py index b18c6cc..07ff9fc 100644 --- a/pyanaconda/exception.py +++ b/pyanaconda/exception.py @@ -252,7 +252,7 @@ def initExceptionHandling(anaconda): "_bootloader.password", "payload._groups", "payload._yum"],
localSkipList=[ "passphrase", "password", "_oldweak", "_password" ],
localSkipList=[ "passphrase", "password", "_oldweak", "_password" "try_passphrase" ], fileList=fileList)conf.register_callback("lsblk_output", lsblk_callback, attchmnt_only=True)
diff --git a/pyanaconda/rescue.py b/pyanaconda/rescue.py index 63bc0c1..96fa21d 100644 --- a/pyanaconda/rescue.py +++ b/pyanaconda/rescue.py @@ -1,7 +1,7 @@ # # rescue.py - anaconda rescue mode setup # -# Copyright (C) 2001, 2002, 2003, 2004 Red Hat, Inc. All rights reserved. +# Copyright (C) 2015 Red Hat, Inc. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,113 +16,35 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/. # -# Author(s): Mike Fulbright msf@redhat.com -# Jeremy Katz katzj@redhat.com +# Author(s): Samantha N. Bueno sbueno@redhat.com # -import sys -import os -from pyanaconda import iutil -import shutil -import time -import re -import subprocess
-from snack import ButtonChoiceWindow, ListboxChoiceWindow,SnackScreen +from blivet import mountExistingSystem, findExistingInstallations +from blivet.errors import StorageError, DirtyFSError +from blivet.devices import LUKSDevice
+from pyanaconda import iutil from pyanaconda.constants import ANACONDA_CLEANUP -from pyanaconda.constants_text import TEXT_OK_BUTTON, TEXT_NO_BUTTON, TEXT_YES_BUTTON -from pyanaconda.text import WaitWindow, OkCancelWindow, ProgressWindow, PassphraseEntryWindow +from pyanaconda.constants_text import INPUT_PROCESSED from pyanaconda.flags import flags -from pyanaconda.installinterfacebase import InstallInterfaceBase -from pyanaconda.i18n import _ +from pyanaconda.i18n import _, N_ from pyanaconda.kickstart import runPostScripts
-from blivet import mountExistingSystem -from blivet.errors import StorageError, DirtyFSError -from blivet.devices import LUKSDevice +from pyanaconda.ui.tui.simpleline import TextWidget, ColumnWidget, CheckboxWidget +from pyanaconda.ui.tui.spokes import NormalTUISpoke +from pyanaconda.ui.tui.tuiobject import YesNoDialog, PasswordDialog
from pykickstart.constants import KS_REBOOT, KS_SHUTDOWN
-import meh.ui.text +import os +import shutil +import time
import logging log = logging.getLogger("anaconda")
-class RescueInterface(InstallInterfaceBase):
- def waitWindow(self, title, text):
return WaitWindow(self.screen, title, text)- def progressWindow(self, title, text, total, updpct = 0.05, pulse = False):
return ProgressWindow(self.screen, title, text, total, updpct, pulse)- def detailedMessageWindow(self, title, text, longText=None, ty="ok",
default=None, custom_icon=None,custom_buttons=None, expanded=False):return self.messageWindow(title, text, ty, default, custom_icon,custom_buttons)- def messageWindow(self, title, text, ty = "ok", default = None,
custom_icon=None, custom_buttons=None):if custom_buttons is None:custom_buttons = []if ty == "ok":ButtonChoiceWindow(self.screen, title, text, buttons=[TEXT_OK_BUTTON])elif ty == "yesno":if default and default == "no":btnlist = [TEXT_NO_BUTTON, TEXT_YES_BUTTON]else:btnlist = [TEXT_YES_BUTTON, TEXT_NO_BUTTON]rc = ButtonChoiceWindow(self.screen, title, text, buttons=btnlist)if rc == "yes":return 1else:return 0elif ty == "custom":tmpbut = []for but in custom_buttons:tmpbut.append(but.replace("_",""))rc = ButtonChoiceWindow(self.screen, title, text, width=60, buttons=tmpbut)idx = 0for b in tmpbut:if b.lower() == rc:return idxidx += 1return 0else:return OkCancelWindow(self.screen, title, text)- def passphraseEntryWindow(self, device):
w = PassphraseEntryWindow(self.screen, device)passphrase = w.run()w.pop()return passphrase- @property
- def meh_interface(self):
return self._meh_interface- @property
- def tty_num(self):
return 1- def shutdown (self):
self.screen.finish()- def suspend(self):
pass+__all__ = ["RescueMode", "RootSpoke", "RescueMountSpoke"]
- def resume(self):
pass- def __init__(self):
InstallInterfaceBase.__init__(self)self.screen = SnackScreen()self._meh_interface = meh.ui.text.TextIntf()-def makeFStab(instPath = ""): +def makeFStab(instPath=""):
- """Make the fs tab.""" if os.access("/proc/mounts", os.R_OK): f = open("/proc/mounts", "r") buf = f.read()
@@ -138,8 +60,30 @@ def makeFStab(instPath = ""): except IOError as e: log.info("failed to write /etc/fstab: %s", e)
-# make sure they have a resolv.conf in the chroot +def run_shell():
- """Launch a shell."""
- if flags.imageInstall:
print(_("Run %s to unmount the system when you are finished.")% ANACONDA_CLEANUP)- else:
print(_("When finished, please exit from the shell and your ""system will reboot."))- proc = None
- if proc is None or proc.returncode != 0:
if os.path.exists("/bin/bash"):iutil.execConsole()else:print(_("Unable to find /bin/bash to execute! Not starting shell."))time.sleep(5)- if not flags.imageInstall:
iutil.execWithRedirect("systemctl", ["--no-wall", "reboot"])- else:
return Nonedef makeResolvConf(instPath):
- """Make the resolv.conf file in the chroot.""" if flags.imageInstall: return
@@ -171,62 +115,125 @@ def makeResolvConf(instPath): f.write(buf) f.close()
-def runShell(screen = None, msg=""):
- if screen:
screen.suspend()+class RescueMode(NormalTUISpoke):
- title = N_("Rescue")
- if msg:
print(msg)
- # If it acts like a spoke and looks like a spoke, is it a spoke? Not
- # always. This is independent of any hub(s), so pass in some fake data
- def __init__(self, app, data, storage=None, payload=None, instclass=None):
NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)if flags.automatedInstall:self._ro = data.rescue.romountelse:self._ro = False
- if flags.imageInstall:
print(_("Run %s to unmount the system when you are finished.")% ANACONDA_CLEANUP)- else:
print(_("When finished please exit from the shell and your ""system will reboot."))
self._root = Noneself._choices = (_("Continue"), _("Read-only mount"), _("Skip to shell"), ("Quit (Reboot)"))
- proc = None
- def initialize(self):
NormalTUISpoke.initialize(self)
- if os.path.exists("/usr/bin/firstaidkit-qs"):
proc = subprocess.Popen(["/usr/bin/firstaidkit-qs"])proc.wait()
for f in ["services", "protocols", "group", "man.config","nsswitch.conf", "selinux", "mke2fs.conf"]:try:os.symlink('/mnt/runtime/etc/' + f, '/etc/' + f)except OSError:pass- def prompt(self, args=None):
""" Override the default TUI prompt."""return _("Please make a selection from the above: ")- def refresh(self, args=None):
NormalTUISpoke.refresh(self, args)self._window += [TextWidget(_("The rescue environment will now attempt ""to find your Linux installation and mount it under ""the directory : %s. You can then make any changes ""required to your system. Choose '1' to proceed with ""this step.\nYou can choose to mount your file""systems read-only instead of read-write by choosing ""'2'.\nIf for some reason this process does not work ""choose '3' to skip directly to a shell.\n\n") % (iutil.getSysroot())), ""]for idx, choice in enumerate(self._choices):number = TextWidget("%2d)" % (idx + 1))c = ColumnWidget([(3, [number]), (None, [TextWidget(choice)])], 1)self._window += [c, ""]return True- def input(self, args, key):
"""Override any input so we can launch rescue mode."""try:keyid = int(key) - 1except ValueError:pass
- if proc is None or proc.returncode!=0:
if os.path.exists("/bin/bash"):iutil.execConsole()
if keyid == 3:# quit/rebootd = YesNoDialog(self.app, _(self.app.quit_message))self.app.switch_screen_modal(d)if d.answer:iutil.execWithRedirect("systemctl", ["--no-wall", "reboot"])Shouldn't this reboot be guarded with 'if flags.imageInstall:' too?
-- Vratislav Podzimek
Anaconda Rider | Red Hat, Inc. | Brno - Czech Republic
anaconda-patches mailing list anaconda-patches@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/anaconda-patches
With the new rescue mode it's not really worth it to keep using snack/newt to let users know they cannot continue because of not enough RAM being availaible and requiring. We can use simple prints and get rid of the python-newt dependency.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- anaconda | 10 ++++------ anaconda.spec.in | 3 --- 2 files changed, 4 insertions(+), 9 deletions(-)
diff --git a/anaconda b/anaconda index 6c3fb56..2dca2a5 100755 --- a/anaconda +++ b/anaconda @@ -504,18 +504,16 @@ def check_memory(anaconda, options, display_mode=None): "needed_ram": needed_ram, "total_ram": total_ram} if needed_ram > total_ram: - from snack import SnackScreen, ButtonChoiceWindow if options.liveinst: # pylint: disable=logging-not-lazy stdoutLog.warning(reason % reason_args) gtk_warning(livecd_title, reason % reason_args) else: reason += reboot_extra - screen = SnackScreen() - ButtonChoiceWindow(screen, _('Fatal Error'), - reason % reason_args, - buttons = (_("OK"),)) - screen.finish() + print(reason % reason_args) + print(_("The installation cannot continue and the system will be rebooted")) + print(_("Press ENTER to continue")) + input()
iutil.ipmi_report(constants.IPMI_ABORTED) sys.exit(1) diff --git a/anaconda.spec.in b/anaconda.spec.in index 8c7a2ac..d2aa410 100644 --- a/anaconda.spec.in +++ b/anaconda.spec.in @@ -134,9 +134,6 @@ Requires: kexec-tools
Requires: python3-coverage
-# Used by rescue.py and the low RAM message in /sbin/anaconda -Requires: newt-python3 - # required because of the rescue mode and VNC question Requires: anaconda-tui = %{version}-%{release}
With the new rescue mode it's not really worth it to keep using snack/newt to let users know they cannot continue because of not enough RAM being availaible and requiring. We can use simple prints and get rid of the python-newt dependency.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com
I can't ACK this patch hard enough.
- Chris
On Thu, 2015-06-18 at 13:44 -0400, Samantha N. Bueno wrote:
Includes bcl's suggestions.
I couldn't really see any Requires to remove, but someone else clue me in since I'm sure I might be missing something.
Looks good to me other than the one question for PATCH 3/3. And I think we could add what I proposed as PATCH 4/4.
anaconda-patches@lists.fedorahosted.org