diff options
Diffstat (limited to 'sabayon/livecd.py')
-rw-r--r-- | sabayon/livecd.py | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/sabayon/livecd.py b/sabayon/livecd.py new file mode 100644 index 0000000..1526e08 --- /dev/null +++ b/sabayon/livecd.py @@ -0,0 +1,550 @@ +# -*- coding: utf-8 -*- +# +# livecd.py +# +# Copyright (C) 2010 Fabio Erculiani +# +# 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/>. +# + +# 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 sabayon.utils +from sabayon import Entropy + +# Entropy imports +from entropy.const import etpConst, const_kill_threads +from entropy.misc import TimeScheduled, ParallelTask +from entropy.cache import EntropyCacher +import entropy.tools + +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:] + 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) + + def _getLiveSize(self): + st = os.statvfs(PRODUCT_PATH) + compressed_byte_size = 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 = sabayon.utils.SabayonProgress(anaconda) + self._progress.start() + self._entropy = Entropy() + self._entropy.connect_progress(self._progress) + self._sabayon_install = sabayon.utils.SabayonInstall(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 Sabayon") + self._progress.set_label(_("Recovering Sabayon.")) + self._progress.set_fraction(0.0) + return + else: + log.info("Preparing to install Sabayon") + + self._progress.set_label(_("Installing Sabayon onto hard drive.")) + self._progress.set_fraction(0.0) + + # Actually install + self._sabayon_install.live_install() + self._sabayon_install.setup_users() + self._sabayon_install.setup_language() # before ldconfig, thx + # if simple networking is enabled, disable NetworkManager + if self.anaconda.instClass.simplenet: + self._sabayon_install.setup_manual_networking() + else: + self._sabayon_install.setup_networkmanager_networking() + self._sabayon_install.setup_keyboard() + + action = _("Configuring Sabayon") + self._progress.set_label(action) + self._progress.set_fraction(0.9) + + self._sabayon_install.setup_sudo() + self._sabayon_install.setup_audio() + self._sabayon_install.setup_xorg() + self._sabayon_install.remove_proprietary_drivers() + try: + self._sabayon_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._progress.set_fraction(0.95) + self._sabayon_install.configure_services() + self._sabayon_install.copy_udev() + self._sabayon_install.env_update() + self._sabayon_install.spawn_chroot("locale-gen", silent = True) + self._sabayon_install.spawn_chroot("ldconfig") + # Fix a possible /tmp problem + self._sabayon_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() + + action = _("Sabayon configuration complete") + self._progress.set_label(action) + self._progress.set_fraction(1.0) + + def doPostInstall(self, anaconda): + + self._sabayon_install.setup_entropy_mirrors() + self._sabayon_install.language_packs_install() + self._sabayon_install.emit_install_done() + + storage.writeEscrowPackets(anaconda) + + self._sabayon_install.destroy() + if hasattr(self._entropy, "shutdown"): + self._entropy.shutdown() + else: + self._entropy.destroy() + EntropyCacher().stop() + + const_kill_threads() + 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"] + 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): + + # 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._sabayon_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", + "sabayonmce", "quiet", "scandelay=", "doslowusb", "docrypt", + "dokeymap", "keymap=", "radeon.modeset=", "modeset=", "nomodeset"] + + # Sabayon MCE install -> MCE support + # use reference, yeah + cmdline = self._sabayon_install.cmdline + if Entropy.is_sabayon_mce() and ("sabayonmce" not in cmdline): + cmdline.append("sabayonmce") + + # 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 + # <storage.devices.Device> 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" <string> + grub_target = self.anaconda.bootloader.getDevice() + try: + # <storage.device.PartitionDevice> + 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=")]) + + # Since Sabayon 5.4, we also write to /etc/default/sabayon-grub + grub_sabayon_file = self._root + "/etc/default/sabayon-grub" + grub_sabayon_dir = os.path.dirname(grub_sabayon_file) + if not os.path.isdir(grub_sabayon_dir): + os.makedirs(grub_sabayon_dir) + with open(grub_sabayon_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 |