#
# text.py - text mode frontend to anaconda
#
# 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 .
#
# Author(s): Erik Troan
# Matt Wilson
#
from snack import *
import sys
import os
import isys
import iutil
import time
import signal
import parted
import product
import string
from language import expandLangs
from flags import flags
from constants_text import *
from constants import *
from network import hasActiveNetDev
from installinterfacebase import InstallInterfaceBase
import imputil
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)
import logging
log = logging.getLogger("anaconda")
stepToClasses = {
"language" : ("language_text", "LanguageWindow"),
"keyboard" : ("keyboard_text", "KeyboardWindow"),
"welcome" : ("welcome_text", "WelcomeWindow"),
"parttype" : ("partition_text", "PartitionTypeWindow"),
"addswap" : ("upgrade_text", "UpgradeSwapWindow"),
"upgrademigratefs" : ("upgrade_text", "UpgradeMigrateFSWindow"),
"zfcpconfig": ("zfcp_text", ("ZFCPWindow")),
"findinstall" : ("upgrade_text", ("UpgradeExamineWindow")),
"upgbootloader": ("upgrade_bootloader_text", "UpgradeBootloaderWindow"),
"network" : ("network_text", ("HostnameWindow")),
"timezone" : ("timezone_text", "TimezoneWindow"),
"accounts" : ("userauth_text", "RootPasswordWindow"),
"useraccounts" : ("userauth_text", "UserPasswordWindow"),
"tasksel": ("task_text", "TaskWindow"),
"install" : ("progress_text", "setupForInstall"),
"complete" : ("complete_text", "FinishedWindow"),
}
if iutil.isS390():
stepToClasses["bootloader"] = ("zipl_text", ( "ZiplWindow"))
class InstallWindow:
def __call__ (self, screen):
raise RuntimeError, "Unimplemented screen"
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 == string.lower(_("Cancel")):
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 LuksPassphraseWindow:
def __init__(self, screen, passphrase = "", preexist = False):
self.screen = screen
self.passphrase = passphrase
self.minLength = 8
self.preexist = preexist
self.txt = _("Choose a passphrase for the encrypted devices. You "
"will be prompted for this passphrase during system boot.")
self.rc = None
def run(self):
toplevel = GridForm(self.screen, _("Passphrase for encrypted device"),
1, 5)
txt = TextboxReflowed(65, self.txt)
toplevel.add(txt, 0, 0)
passphraseentry = Entry(60, password = 1)
toplevel.add(passphraseentry, 0, 1, (0,0,0,1))
confirmentry = Entry(60, password = 1)
toplevel.add(confirmentry, 0, 2, (0,0,0,1))
if self.preexist:
globalcheckbox = Checkbox(_("Also add this passphrase to all existing encrypted devices"), isOn = True)
toplevel.add(globalcheckbox, 0, 3)
buttons = ButtonBar(self.screen, [TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON])
toplevel.add(buttons, 0, 4, growx=1)
passphraseentry.set(self.passphrase)
confirmentry.set(self.passphrase)
while True:
rc = toplevel.run()
res = buttons.buttonPressed(rc)
passphrase = None
if res == TEXT_OK_CHECK or rc == "F12":
passphrase = passphraseentry.value()
confirm = confirmentry.value()
if passphrase != confirm:
ButtonChoiceWindow(self.screen,
_("Error with passphrase"),
_("The passphrases you entered were "
"different. Please try again."),
buttons=[TEXT_OK_BUTTON])
passphraseentry.set("")
confirmentry.set("")
continue
if len(passphrase) < self.minLength:
ButtonChoiceWindow(self.screen,
_("Error with passphrase"),
P_("The passphrase must be at least "
"%d character long.",
"The passphrase must be at least "
"%d characters long.",
self.minLength)
% (self.minLength,),
buttons=[TEXT_OK_BUTTON])
passphraseentry.set("")
confirmentry.set("")
continue
else:
passphrase = self.passphrase
passphraseentry.set(self.passphrase)
confirmentry.set(self.passphrase)
retrofit = False
if self.preexist:
retrofit = globalcheckbox.selected()
self.rc = passphrase
return (self.rc, retrofit)
def pop(self):
self.screen.popWindow()
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, 4)
txt = TextboxReflowed(65, self.txt)
toplevel.add(txt, 0, 0)
passphraseentry = Entry(60, password = 1)
toplevel.add(passphraseentry, 0, 1, (0,0,0,1))
globalcheckbox = Checkbox(_("This is a global passphrase"))
toplevel.add(globalcheckbox, 0, 2)
buttons = ButtonBar(self.screen, [TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON])
toplevel.add(buttons, 0, 3, growx=1)
rc = toplevel.run()
res = buttons.buttonPressed(rc)
passphrase = None
isglobal = False
if res == TEXT_OK_CHECK:
passphrase = passphraseentry.value().strip()
isglobal = globalcheckbox.selected()
self.rc = (passphrase, isglobal)
return self.rc
def pop(self):
self.screen.popWindow()
class InstallInterface(InstallInterfaceBase):
def progressWindow(self, title, text, total, updpct = 0.05, pulse = False):
return ProgressWindow(self.screen, title, text, total, updpct, pulse)
def setInstallProgressClass(self, c):
self.instProgress = c
def exitWindow(self, title, text):
return self.messageWindow(title, text, type="custom",
custom_buttons=[_("Exit installer")])
def messageWindow(self, title, text, type="ok", default = None,
custom_icon=None, custom_buttons=[]):
if type == "ok":
ButtonChoiceWindow(self.screen, title, text,
buttons=[TEXT_OK_BUTTON])
elif type == "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 type == "custom":
tmpbut = []
for but in custom_buttons:
tmpbut.append(string.replace(but,"_",""))
rc = ButtonChoiceWindow(self.screen, title, text, width=60,
buttons=tmpbut)
idx = 0
for b in tmpbut:
if string.lower(b) == rc:
return idx
idx = idx + 1
return 0
else:
return OkCancelWindow(self.screen, title, text)
def detailedMessageWindow(self, title, text, longText=None, type="ok",
default=None, custom_icon=None,
custom_buttons=[]):
t = TextboxReflowed(60, text, maxHeight=8)
lt = Textbox(60, 6, longText, scroll=1, wrap=1)
g = GridFormHelp(self.screen, title, help, 1, 3)
g.add(t, 0, 0)
g.add(lt, 0, 1, padding = (0, 1, 0, 1))
if type == "ok":
bb = ButtonBar(self.screen, [TEXT_OK_BUTTON])
g.add(bb, 0, 2, growx = 1)
return bb.buttonPressed(g.runOnce(None, None))
elif type == "yesno":
if default and default == "no":
buttons = [TEXT_NO_BUTTON, TEXT_YES_BUTTON]
else:
buttons = [TEXT_YES_BUTTON, TEXT_NO_BUTTON]
bb = ButtonBar(self.screen, buttons)
g.add(bb, 0, 2, growx = 1)
rc = bb.buttonPressed(g.runOnce(None, None))
if rc == "yes":
return 1
else:
return 0
elif type == "custom":
buttons = []
idx = 0
for button in custom_buttons:
buttons.append(string.replace(button, "_", ""))
bb = ButtonBar(self.screen, buttons)
g.add(bb, 0, 2, growx = 1)
rc = bb.buttonPressed(g.runOnce(None, None))
for b in buttons:
if string.lower(b) == rc:
return idx
idx += 1
return 0
else:
return self.messageWindow(title, text, type, default, custom_icon,
custom_buttons)
def createRepoWindow(self):
self.messageWindow(_("Error"),
_("Repository editing is not available in text mode."))
def editRepoWindow(self, repoObj):
self.messageWindow(_("Error"),
_("Repository editing is not available in text mode."))
def entryWindow(self, title, text, prompt, entrylength = None):
(res, value) = EntryWindow(self.screen, title, text, [prompt])
if res == "cancel":
return None
r = value[0]
r.strip()
return r
def getLuksPassphrase(self, passphrase = "", preexist = False):
w = LuksPassphraseWindow(self.screen, passphrase = passphrase,
preexist = preexist)
rc = w.run()
w.pop()
return rc
def passphraseEntryWindow(self, device):
w = PassphraseEntryWindow(self.screen, device)
(passphrase, isglobal) = w.run()
w.pop()
return (passphrase, isglobal)
def enableNetwork(self):
if len(self.anaconda.network.netdevices) == 0:
return False
from netconfig_text import NetworkConfiguratorText
w = NetworkConfiguratorText(self.screen, self.anaconda)
ret = w.run()
return ret != INSTALL_BACK
def kickstartErrorWindow(self, text):
s = _("The following error was found while parsing the "
"kickstart configuration file:\n\n%s") %(text,)
self.messageWindow(_("Error Parsing Kickstart Config"),
s,
type = "custom",
custom_buttons = [("_Reboot")],
custom_icon="error")
def mainExceptionWindow(self, shortText, longTextFile):
from meh.ui.text import MainExceptionWindow
log.critical(shortText)
exnWin = MainExceptionWindow(shortText, longTextFile, screen=self.screen)
return exnWin
def saveExceptionWindow(self, accountManager, signature, *args, **kwargs):
from meh.ui.text import SaveExceptionWindow
win = SaveExceptionWindow (accountManager, signature, screen=self.screen,
*args, **kwargs)
win.run()
def waitWindow(self, title, text):
return WaitWindow(self.screen, title, text)
def beep(self):
# no-op. could call newtBell() if it was bound
pass
def drawFrame(self):
self.screen.drawRootText (0, 0, self.screen.width * " ")
if productArch:
self.screen.drawRootText (0, 0, _("Welcome to %(productName)s for %(productArch)s") % {'productName': productName, 'productArch': productArch})
else:
self.screen.drawRootText (0, 0, _("Welcome to %s") % productName)
self.screen.pushHelpLine(_(" / between elements | selects | next screen"))
def setScreen(self, screen):
self.screen = screen
def shutdown(self):
self.screen.finish()
self.screen = None
def suspend(self):
self.screen.suspend()
def resume(self):
self.screen.resume()
def __init__(self):
InstallInterfaceBase.__init__(self)
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGTSTP, signal.SIG_IGN)
self.screen = SnackScreen()
self.instProgress = None
self._initLabelAnswers = {}
self._inconsistentLVMAnswers = {}
def __del__(self):
if self.screen:
self.screen.finish()
def isRealConsole(self):
"""Returns True if this is a _real_ console that can do things, False
for non-real consoles such as serial, i/p virtual consoles or xen."""
if flags.serial or flags.virtpconsole:
return False
if isys.isPseudoTTY(0):
return False
if isys.isVioConsole():
return False
return True
def resetInitializeDiskQuestion(self):
self._initLabelAnswers = {}
def questionInitializeDisk(self, path, description, size, details=""):
retVal = False # The less destructive default
if not path:
return retVal
# we are caching answers so that we don't
# ask in each storage.reset() again
if path in self._initLabelAnswers:
log.info("UI not asking about disk initialization, "
"using cached answer: %s" % self._initLabelAnswers[path])
return self._initLabelAnswers[path]
elif "all" in self._initLabelAnswers:
log.info("UI not asking about disk initialization, "
"using cached answer: %s" % self._initLabelAnswers["all"])
return self._initLabelAnswers["all"]
rc = self.messageWindow(_("Warning"),
_("Error processing drive:\n\n"
"%(path)s\n%(size)-0.fMB\n%(description)s\n\n"
"This device may need to be reinitialized.\n\n"
"REINITIALIZING WILL CAUSE ALL DATA TO BE LOST!\n\n"
"This action may also be applied to all other disks "
"needing reinitialization.%(details)s")
% {'path': path, 'size': size,
'description': description, 'details': details},
type="custom",
custom_buttons = [ _("_Ignore"),
_("Ignore _all"),
_("_Re-initialize"),
_("Re-ini_tialize all") ],
custom_icon="question")
if rc == 0:
retVal = False
elif rc == 1:
path = "all"
retVal = False
elif rc == 2:
retVal = True
elif rc == 3:
path = "all"
retVal = True
self._initLabelAnswers[path] = retVal
return retVal
def resetReinitInconsistentLVMQuestion(self):
self._inconsistentLVMAnswers = {}
def questionReinitInconsistentLVM(self, pv_names=None, lv_name=None, vg_name=None):
retVal = False # The less destructive default
allSet = frozenset(["all"])
if not pv_names or (lv_name is None and vg_name is None):
return retVal
# We are caching answers so that we don't ask for ignoring
# in each storage.reset() again (note that reinitialization is
# done right after confirmation in dialog, not as a planned
# action).
key = frozenset(pv_names)
if key in self._inconsistentLVMAnswers:
log.info("UI not asking about disk initialization, "
"using cached answer: %s" % self._inconsistentLVMAnswers[key])
return self._inconsistentLVMAnswers[key]
elif allSet in self._inconsistentLVMAnswers:
log.info("UI not asking about disk initialization, "
"using cached answer: %s" % self._inconsistentLVMAnswers[allSet])
return self._inconsistentLVMAnswers[allSet]
if vg_name is not None:
message = "Volume Group %s" % vg_name
elif lv_name is not None:
message = "Logical Volume %s" % lv_name
na = {'msg': message, 'pvs': ", ".join(pv_names)}
rc = self.messageWindow(_("Warning"),
_("Error processing LVM.\n"
"There is inconsistent LVM data on %(msg)s. You can "
"reinitialize all related PVs (%(pvs)s) which will erase "
"the LVM metadata, or ignore which will preserve the "
"contents. This action may also be applied to all other "
"PVs with inconsistent metadata.") % na,
type="custom",
custom_buttons = [ _("_Ignore"),
_("Ignore _all"),
_("_Re-initialize"),
_("Re-ini_tialize all") ],
custom_icon="question")
if rc == 0:
retVal = False
elif rc == 1:
key = allSet
retVal = False
elif rc == 2:
retVal = True
elif rc == 3:
key = allSet
retval = True
self._inconsistentLVMAnswers[key] = retVal
return retVal
def run(self, anaconda):
self.anaconda = anaconda
instLang = anaconda.instLanguage
if instLang.getFontFile(instLang.instLang) == "none":
if not anaconda.ksdata:
ButtonChoiceWindow(self.screen, "Language Unavailable",
"%s display is unavailable in text mode. "
"The installation will continue in "
"English." % (instLang.instLang,),
buttons=[TEXT_OK_BUTTON])
if not self.isRealConsole():
self.screen.suspendCallback(spawnShell, self.screen)
# drop into the python debugger on ctrl-z if we're running in test mode
if flags.debug:
self.screen.suspendCallback(debugSelf, self.screen)
# draw the frame after setting up the fallback
self.drawFrame()
lastrc = INSTALL_OK
(step, instance) = anaconda.dispatch.currentStep()
while step:
(file, classNames) = stepToClasses[step]
if type(classNames) != type(()):
classNames = (classNames,)
if lastrc == INSTALL_OK:
step = 0
else:
step = len(classNames) - 1
while step >= 0 and step < len(classNames):
# reget the args. they could change (especially direction)
(foo, args) = anaconda.dispatch.currentStep()
nextWindow = None
while 1:
try:
found = imputil.imp.find_module(file)
loaded = imputil.imp.load_module(classNames[step],
found[0], found[1],
found[2])
nextWindow = loaded.__dict__[classNames[step]]
break
except ImportError, e:
rc = ButtonChoiceWindow(self.screen, _("Error!"),
_("An error occurred when attempting "
"to load an installer interface "
"component.\n\nclassName = %s")
% (classNames[step],),
buttons=[_("Exit"), _("Retry")])
if rc == string.lower(_("Exit")):
sys.exit(0)
win = nextWindow()
#log.info("TUI running step %s (class %s, file %s)" %
#(step, file, classNames))
rc = win(self.screen, instance)
if rc == INSTALL_NOOP:
rc = lastrc
if rc == INSTALL_BACK:
step = step - 1
anaconda.dispatch.dir = DISPATCH_BACK
elif rc == INSTALL_OK:
step = step + 1
anaconda.dispatch.dir = DISPATCH_FORWARD
lastrc = rc
if step == -1:
if not anaconda.dispatch.canGoBack():
ButtonChoiceWindow(self.screen, _("Cancelled"),
_("I can't go to the previous step "
"from here. You will have to try "
"again."),
buttons=[_("OK")])
anaconda.dispatch.gotoPrev()
else:
anaconda.dispatch.gotoNext()
(step, args) = anaconda.dispatch.currentStep()
self.screen.finish()
def setSteps(self, anaconda):
anaconda.dispatch.skipStep("filtertype", permanent=1)
anaconda.dispatch.skipStep("filter", permanent=1)
anaconda.dispatch.skipStep("cleardiskssel", permanent=1)
anaconda.dispatch.skipStep("group-selection", permanent=1)
def killSelf(screen):
screen.finish()
os._exit(0)
def debugSelf(screen):
screen.suspend()
import pdb
try:
pdb.set_trace()
except:
sys.exit(-1)
screen.resume()
def spawnShell(screen):
screen.suspend()
print("\n\nType to return to the install program.\n")
if os.path.exists("/bin/sh"):
iutil.execConsole()
else:
print("Unable to find /bin/sh to execute! Not starting shell")
time.sleep(5)
screen.resume()