# -*- coding: utf-8 -*- # # livecd.py # # Copyright (C) 2010 Fabio Erculiani # Copyright (C) 2011 wiktor w brodlo # Copyright (C) 2011 Gentoo Foundation # # 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 . # # System imports import os import statvfs import subprocess import commands import stat import time import shutil import gettext _ = lambda x: gettext.ldgettext("anaconda", x) # Anaconda imports import storage import flags from constants import productPath as PRODUCT_PATH, productName as PRODUCT_NAME, \ DISPATCH_BACK import backend import isys import iutil import logging from anaconda_log import PROGRAM_LOG_FILE import gentoo.utils from gentoo import Portage log = logging.getLogger("anaconda") class LiveCDCopyBackend(backend.AnacondaBackend): def __init__(self, anaconda): backend.AnacondaBackend.__init__(self, anaconda) flags.livecdInstall = True self.supportsUpgrades = True self.supportsPackageSelection = False self._root = anaconda.rootPath self.osimg = anaconda.methodstr[8:] # On Gentoo we're using a stage3 as an installation source # FIXME: Replace this with checking for stage3 #if not os.path.ismount(self.osimg): # anaconda.intf.messageWindow(_("Unable to find image"), # _("The given location [%s] isn't a valid %s " # "live CD to use as an installation source.") # %(self.osimg, PRODUCT_NAME), type = "custom", # custom_icon="error", # custom_buttons=[_("Exit installer")]) # raise SystemExit(1) # _getLiveSize() needs to be adjusted but more measurements are needed def _getLiveSize(self): #st = os.statvfs(PRODUCT_PATH) compressed_byte_size = 150000000 #st.f_blocks * st.f_bsize return compressed_byte_size * 3 # 3 times is enough def _getLiveSizeMB(self): return self._getLiveSize() / 1048576 def postAction(self, anaconda): try: anaconda.storage.umountFilesystems(swapoff = False) os.rmdir(self._root) except Exception, e: log.error("Unable to unmount filesystems: %s" % e) def checkSupportedUpgrade(self, anaconda): if anaconda.dir == DISPATCH_BACK: return def doPreInstall(self, anaconda): self._progress = gentoo.utils.GentooProgress(anaconda) self._progress.start() self._gentoo_install = gentoo.utils.GentooInstall(anaconda) # We use anaconda.upgrade as bootloader recovery step self._bootloader_recovery = anaconda.upgrade self._install_grub = not self.anaconda.dispatch.stepInSkipList("instbootloader") def doInstall(self, anaconda): # Disable internal Anaconda bootloader setup, doesn't support GRUB2 anaconda.dispatch.skipStep("instbootloader", skip = 1) if self._bootloader_recovery: log.info("Preparing to recover Gentoo") self._progress.set_label(_("Recovering Gentoo.")) self._progress.set_fraction(0.0) return else: log.info("Preparing to install Gentoo") self._progress.set_label(_("Installing Gentoo onto hard drive.")) self._progress.set_fraction(0.0) # Actually install self._gentoo_install.live_install() # Now copy the portage tree self._gentoo_install.copy_portage() self._gentoo_install.set_profile() self._gentoo_install.write_makeconf() self._gentoo_install.install_setup_tools() self._gentoo_install.install_kernel() action = _("Configuring Gentoo") self._progress.set_label(action) self._progress.set_fraction(0.0) self._gentoo_install.setup_users() self._gentoo_install.setup_language() # before ldconfig, thx self._progress.set_fraction(0.1) # if simple networking is enabled, disable NetworkManager if self.anaconda.instClass.simplenet: self._gentoo_install.setup_manual_networking() else: self._gentoo_install.setup_networkmanager_networking() self._gentoo_install.setup_keyboard() self._progress.set_fraction(0.2) self._gentoo_install.setup_sudo() self._gentoo_install.setup_audio() self._progress.set_fraction(0.3) self._gentoo_install.setup_xorg() #self._gentoo_install.remove_proprietary_drivers() # TODO: This needs to be tried and fixed properly on Gentoo once installing X works #try: # self._gentoo_install.setup_nvidia_legacy() #except Exception as e: # # caused by Entropy bug <0.99.47.2, remove in future # log.error("Unable to install legacy nvidia drivers: %s" % e) self._gentoo_install.configure_services() self._progress.set_fraction(0.4) self._gentoo_install.copy_udev() self._gentoo_install.env_update() self._progress.set_fraction(0.5) self._gentoo_install.spawn_chroot("locale-gen", silent = True) self._gentoo_install.spawn_chroot("ldconfig") self._progress.set_fraction(0.6) # Fix a possible /tmp problem self._gentoo_install.spawn("chmod a+w "+self._root+"/tmp") var_tmp = self._root + "/var/tmp" if not os.path.isdir(var_tmp): # wtf! os.makedirs(var_tmp) var_tmp_keep = os.path.join(var_tmp, ".keep") if not os.path.isfile(var_tmp_keep): with open(var_tmp_keep, "w") as wt: wt.flush() self._progress.set_fraction(0.7) self._gentoo_install.install_systools() action = _("Gentoo configuration complete") self._progress.set_label(action) self._progress.set_fraction(1.0) def doPostInstall(self, anaconda): #self._gentoo_install.setup_portage_mirrors() #self._gentoo_install.language_packs_install() self._gentoo_install.emit_install_done() storage.writeEscrowPackets(anaconda) self._gentoo_install.destroy() #const_kill_threads() #entropy leftover anaconda.intf.setInstallProgressClass(None) def writeConfiguration(self): """ System configuration is written in anaconda.write(). Add extra config files setup here. """ # Write critical configuration not automatically written self.anaconda.storage.fsset.write() log.info("Do we need to run GRUB2 setup? => %s" % (self._install_grub,)) if self._install_grub: # HACK: since Anaconda doesn't support grub2 yet # Grub configuration is disabled # and this code overrides it encrypted, root_crypted, swap_crypted = self._setup_grub2() if encrypted: if swap_crypted: swap_name, swap_dev = swap_crypted old_swap_name = swap_dev._name swap_dev._name = swap_name if root_crypted: root_name, root_dev = root_crypted old_root_name = root_dev._name if root_name == "root": # comes from /dev/mapper/root root_dev._name = root_name # HACK: since swap device path value is potentially changed # it is required to rewrite the fstab (circular dependency, sigh) self.anaconda.storage.fsset.write() if swap_crypted: swap_dev._name = old_swap_name if root_crypted: root_dev._name = old_root_name self._copy_logs() def _copy_logs(self): # copy log files into chroot isys.sync() config_files = ["/tmp/anaconda.log", "/tmp/lvmout", "/tmp/resize.out", "/tmp/program.log", "/tmp/storage.log", "/tmp/emerge-fail.log"] install_dir = self._root + "/var/log/installer" if not os.path.isdir(install_dir): os.makedirs(install_dir) for config_file in config_files: if not os.path.isfile(config_file): continue dest_path = os.path.join(install_dir, os.path.basename(config_file)) shutil.copy2(config_file, dest_path) def _get_bootloader_args(self): cmdline = open("/proc/cmdline", "r").read().strip().split() # keymaps genkernel vs system map keymaps_map = { 'azerty': 'azerty', 'be-latin1': 'be', 'bg_bds-utf8': 'bg', 'br-abnt2': 'br-a', 'by': 'by', 'cf': 'cf', 'croat': 'croat', 'cz-lat2': 'cz', 'de': 'de', 'dk': 'dk', 'es': 'es', 'et': 'et', 'fi': 'fi', 'fr-latin9': 'fr', 'gr': 'gr', 'hu': 'hu', 'is-latin1': 'is', 'it': 'it', 'jp106': 'jp', 'mk': 'mk', 'nl': 'nl', 'no': 'no', 'pl2': 'pl', 'pt-latin1': 'pt', 'ro': 'ro', 'ru': 'ru', 'sk-qwerty': 'sk-y', 'slovene': 'slovene', 'trq': 'trq', 'ua-utf': 'ua', 'uk': 'uk', 'us': 'us', } console_kbd, xxx, yyy, zzz = self._gentoo_install.get_keyboard_layout() gk_kbd = keymaps_map.get(console_kbd) # look for kernel arguments we know should be preserved and add them ourargs = ["speakup_synth=", "apic", "noapic", "apm=", "ide=", "noht", "acpi=", "video=", "vga=", "init=", "splash=", "console=", "pci=routeirq", "irqpoll", "nohdparm", "pci=", "floppy.floppy=", "all-generic-ide", "gentoo=", "res=", "hsync=", "refresh=", "noddc", "xdriver=", "onlyvesa", "nvidia=", "dodmraid", "dmraid", "gentoomce", "quiet", "scandelay=", "doslowusb", "docrypt", "dokeymap", "keymap=", "radeon.modeset=", "modeset=", "nomodeset"] # Setup genkernel (init) keyboard layout if gk_kbd is not None: if "dokeymap" not in cmdline: cmdline.append("dokeymap") cmdline.append("keymap=%s" % (gk_kbd,)) # setup USB parameters, if installing on USB root_is_removable = getattr(self.anaconda.storage.rootDevice, "removable", False) if root_is_removable: cmdline.append("scandelay=10") previous_vga = None final_cmdline = [] for arg in cmdline: for check in ourargs: if arg.startswith(check): final_cmdline.append(arg) if arg.startswith("vga="): if previous_vga in final_cmdline: final_cmdline.remove(previous_vga) previous_vga = arg fsset = self.anaconda.storage.fsset swap_devices = fsset.swapDevices # subclass root_device = self.anaconda.storage.rootDevice # device.format.mountpoint, device.format.type, device.format.mountable, # device.format.options, device.path, device.fstabSpec root_crypted = False swap_crypted = False delayed_crypt_swap = None if swap_devices: log.info("Found swap devices: %s" % (swap_devices,)) swap_dev = swap_devices[0] swap_crypto_dev = None for name in fsset.cryptTab.mappings.keys(): swap_crypto_dev = fsset.cryptTab[name]['device'] if swap_dev == swap_crypto_dev or swap_dev.dependsOn( swap_crypto_dev): swap_crypted = True break if swap_crypted: # genkernel hardcoded bullshit, cannot change /dev/mapper/swap # change inside swap_dev, fstabSpec should return /dev/mapper/swap swap_crypted = ("swap", swap_dev) final_cmdline.append("resume=swap:%s" % (swap_dev.path,)) final_cmdline.append("real_resume=%s" % (swap_dev.path,)) # NOTE: cannot use swap_crypto_dev.fstabSpec because # genkernel doesn't support UUID= on crypto delayed_crypt_swap = swap_crypto_dev.path else: final_cmdline.append("resume=swap:%s" % (swap_dev.fstabSpec,)) final_cmdline.append("real_resume=%s" % (swap_dev.fstabSpec,)) # setup LVM lvscan_out = commands.getoutput("LANG=C LC_ALL=C lvscan").split("\n")[0].strip() if not lvscan_out.startswith("No volume groups found"): final_cmdline.append("dolvm") crypto_dev = None for name in fsset.cryptTab.mappings.keys(): crypto_dev = fsset.cryptTab[name]['device'] if root_device == crypto_dev or root_device.dependsOn(crypto_dev): root_crypted = True break def is_parent_a_simple_device(root_device): if not hasattr(root_device, 'parents'): return False for parent in root_device.parents: if not isinstance(parent, storage.devices.PartitionDevice): return False return True def is_parent_a_md_device(root_device): if not hasattr(root_device, 'parents'): return False for parent in root_device.parents: if not isinstance(parent, storage.devices.MDRaidArrayDevice): return False return True def translate_real_root(root_device, crypted): if crypted and is_parent_a_md_device(root_device): return "/dev/mapper/root" if crypted and is_parent_a_simple_device(root_device): return "/dev/mapper/root" if isinstance(root_device, storage.devices.MDRaidArrayDevice): return root_device.path return root_device.fstabSpec crypt_root = None if root_crypted: log.info("Root crypted? %s, %s, crypto_dev: %s" % (root_crypted, root_device.path, crypto_dev.path)) # NOTE: cannot use crypto_dev.fstabSpec because # genkernel doesn't support UUID= on crypto translated_real_root = translate_real_root(root_device, True) root_crypted = (os.path.basename(translated_real_root), root_device) final_cmdline.append("root=%s crypt_root=%s" % ( translated_real_root, crypto_dev.path,)) # due to genkernel initramfs stupidity, when crypt_root = crypt_swap # do not add crypt_swap. if delayed_crypt_swap == crypto_dev.path: delayed_crypt_swap = None else: log.info("Root crypted? Nope!") final_cmdline.append("root=%s" % ( translate_real_root(root_device, False),)) # always add docrypt, loads kernel mods required by cryptsetup devices if "docrypt" not in final_cmdline: final_cmdline.append("docrypt") if delayed_crypt_swap: final_cmdline.append("crypt_swap=%s" % (delayed_crypt_swap,)) log.info("Generated boot cmdline: %s" % (final_cmdline,)) return final_cmdline, root_crypted, swap_crypted def _setup_grub2(self): cmdline_args, root_crypted, swap_crypted = self._get_bootloader_args() log.info("_setup_grub2, cmdline_args: %s | " "root_crypted: %s | swap_crypted: %s" % (cmdline_args, root_crypted, swap_crypted,)) # "sda" grub_target = self.anaconda.bootloader.getDevice() try: # boot_device = self.anaconda.storage.mountpoints["/boot"] except KeyError: boot_device = self.anaconda.storage.mountpoints["/"] cmdline_str = ' '.join(cmdline_args) # if root_device or swap encrypted, replace splash=silent if root_crypted or swap_crypted: cmdline_str = cmdline_str.replace('splash=silent', 'splash=verbose') log.info("_setup_grub2, grub_target: %s | " "boot_device: %s | cmdline_str: %s" % (grub_target, boot_device, cmdline_str,)) self._write_grub2(cmdline_str, grub_target) # disable Anaconda bootloader code self.anaconda.bootloader.defaultDevice = -1 return root_crypted or swap_crypted, root_crypted, swap_crypted def _write_grub2(self, cmdline, grub_target): default_file_noroot = "/etc/default/grub" grub_cfg_noroot = "/boot/grub/grub.cfg" log.info("%s: %s => %s\n" % ("_write_grub2", "begin", locals())) # setup grub variables # this file must exist # drop vga= from cmdline #cmdline = ' '.join([x for x in cmdline.split() if \ # not x.startswith("vga=")]) grub_gentoo_file = self._root + "/etc/default/gentoo-grub" grub_gentoo_dir = os.path.dirname(grub_gentoo_file) if not os.path.isdir(grub_gentoo_dir): os.makedirs(grub_gentoo_dir) with open(grub_gentoo_file, "w") as f_w: f_w.write("# this file has been added by the Anaconda Installer\n") f_w.write("# containing default installer bootloader arguments.\n") f_w.write("# DO NOT EDIT NOR REMOVE THIS FILE DIRECTLY !!!\n") f_w.write('GRUB_CMDLINE_LINUX="${GRUB_CMDLINE_LINUX} %s"\n' % ( cmdline,)) f_w.flush() if self.anaconda.bootloader.password and self.anaconda.bootloader.pure: # still no proper support, so implement what can be implemented # XXX: unencrypted password support pass_file = self._root + "/etc/grub.d/00_password" f_w = open(pass_file, "w") f_w.write("""\ set superuser="root" password root """+str(self.anaconda.bootloader.pure)+""" """) f_w.flush() f_w.close() # remove device.map if found dev_map = self._root + "/boot/grub/device.map" if os.path.isfile(dev_map): os.remove(dev_map) # this must be done before, otherwise gfx mode is not enabled iutil.execWithRedirect('/sbin/grub2-install', ["/dev/" + grub_target, "--recheck", "--force"], stdout = PROGRAM_LOG_FILE, stderr = PROGRAM_LOG_FILE, root = self._root ) iutil.execWithRedirect('/sbin/grub-mkconfig', ["--output=%s" % (grub_cfg_noroot,)], stdout = PROGRAM_LOG_FILE, stderr = PROGRAM_LOG_FILE, root = self._root ) log.info("%s: %s => %s\n" % ("_write_grub2", "end", locals())) def kernelVersionList(self, rootPath = "/"): """ This won't be used, because our Anaconda codebase is using grub2 """ return [] def doBackendSetup(self, anaconda): ossize = self._getLiveSizeMB() slash = anaconda.storage.rootDevice if slash.size < ossize: rc = anaconda.intf.messageWindow(_("Warning"), _("The root filesystem you created is " "not large enough for this live " "image (%.2f MB required). But I coult be mistaken.") % ossize, type = "custom", custom_icon = "error", custom_buttons=[_("_Back"), _("_Exit installer")]) # package/group selection doesn't apply for this backend def groupExists(self, group): pass def selectGroup(self, group, *args): pass def deselectGroup(self, group, *args): pass def selectPackage(self, pkg, *args): pass def deselectPackage(self, pkg, *args): pass def packageExists(self, pkg): return True def getDefaultGroups(self, anaconda): return [] def writePackagesKS(self, f, anaconda): pass