#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# «ubuntu-bootstrap» - Ubiquity plugin for Installing Ubuntu Recovery
#                      modified from dell-recovery
#
# Copyright (C) 2010-2011, Dell Inc.
#
# Author:
#  - Mario Limonciello <Mario_Limonciello@Dell.com>
#
# This 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 application; if not, write to the Free Software Foundation, Inc., 51
# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
################################################################################
import debconf
import subprocess
import os
import re
import shutil
import sys
import syslog
import hashlib
import gi
gi.require_version('UDisks', '2.0')
from threading import Thread
from apt.cache import Cache
import dbus
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)

from ubiquity.plugin import InstallPlugin, Plugin, PluginUI
from ubiquity import misc
from ubiquity import i18n

import ubunturecovery.recovery_common as magic
from ubunturecovery.recovery_threading import ProgressBySize
from ubunturecovery import disksmgr
from ubunturecovery import grub
from ubunturecovery import metaclass

#Translation support
from gettext import gettext as _
from gettext import bindtextdomain, textdomain

NAME = 'ubuntu-bootstrap'
BEFORE = 'language'
WEIGHT = 12
OEM = False

CDROM_MOUNT = '/cdrom'
ISO_MOUNT = '/isodevice'

TYPE_NTFS = '07'
TYPE_NTFS_RE = '27'
TYPE_VFAT = '0b'
TYPE_VFAT_LBA = '0c'

ENABLE_LANGUAGE_PAGE = 'ubuntu-recovery/enable_language_page'
# Save the below flag so that the phase1's setting affect phase2
TPL_DI_LOCALE = 'debian-installer/locale'

#Continually Reused ubiquity templates
RECOVERY_TYPE_QUESTION =  'ubuntu-recovery/recovery_type'
ENTIRE_DISK_ONLY = 'ubuntu-recovery/entire_disk_only'
AUTO_POWER_OPTION = 'ubuntu-recovery/auto_power_option'
KEEP_HOME_PARTITION = 'ubuntu-recovery/keep_home_partition'
CREATE_HOME_PARTITION = 'ubuntu-recovery/create_home_partition'
DUAL_BOOT_QUESTION = 'ubuntu-recovery/dual_boot'
DUAL_BOOT_LAYOUT_QUESTION = 'ubuntu-recovery/dual_boot_layout'
OS_PARTITION_QUESTION = 'ubuntu-recovery/os_partition'
SWAP_PARTITION_QUESTION = 'ubuntu-recovery/swap_partition'
ACTIVE_PARTITION_QUESTION = 'ubuntu-recovery/active_partition'
FAIL_PARTITION_QUESTION = 'ubuntu-recovery/fail_partition'
DISK_LAYOUT_QUESTION = 'ubuntu-recovery/disk_layout'
SWAP_QUESTION = 'ubuntu-recovery/swap'
RP_FILESYSTEM_QUESTION = 'ubuntu-recovery/recovery_partition_filesystem'
RP_OFFSET_QUESTION = 'ubuntu-recovery/recovery_partition_offset'
DRIVER_INSTALL_QUESTION = 'ubuntu-recovery/disable-driver-install'
USER_INTERFACE_QUESTION = 'ubuntu-oobe/user-interface'
RP_GRUBCFGS = {'recovery_partition.cfg': 'grub.cfg',
               'common.cfg' : 'common.cfg'}
UEFI_HELPER = '/usr/share/ubuntu/scripts/uefi_helper.sh'
SECURE_BOOT_VAR = '/sys/firmware/efi/vars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c/data'
UEFI_SIZE_QUESTION = 'ubuntu-recovery/uefi_partition_size'
RP_SIZE_QUESTION = 'ubuntu-recovery/recovery_partition_size'

#######################
# Noninteractive Page #
#######################

class PageNoninteractive(PluginUI):
    """Non-Interactive frontend for the ubuntu-bootstrap ubiquity plugin"""
    def __init__(self, controller, *args, **kwargs):
        self.controller = controller
        PluginUI.__init__(self, controller, *args, **kwargs)

    def get_type(self):
        '''For the noninteractive frontend, get_type always returns an empty str
            This is because the noninteractive frontend always runs in "factory"
            mode, which expects such a str""'''
        return ""

    def set_type(self, value, stage):
        """Empty skeleton function for the non-interactive UI"""
        pass

    def show_dialog(self, which, data = None):
        """Empty skeleton function for the non-interactive UI"""
        pass

    def get_selected_device(self):
        """Empty skeleton function for the non-interactive UI"""
        pass

    def populate_devices(self, devices):
        """Empty skeleton function for the non-interactive UI"""
        pass

    def set_advanced(self, item, value):
        """Empty skeleton function for the non-interactive UI"""
        pass

    def get_advanced(self, item):
        """Empty skeleton function for the non-interactive UI"""
        return ''

############
# GTK Page #
############

class PageGtk(PluginUI):
    """GTK frontend for the ubuntu-bootstrap ubiquity plugin"""
    #OK, so we're not "really" a language page
    #We are just cheating a little bit to make sure our widgets are translated
    plugin_is_language = True
    plugin_title = 'ubiquity/text/ubuntu_recovery_title'

    def __init__(self, controller, *args, **kwargs):
        self.plugin_widgets = None

        oem = 'UBIQUITY_OEM_USER_CONFIG' in os.environ

        self.efi = False
        self.genuine = magic.check_vendor()

        if not oem:
            gi.require_version('Gtk', '3.0')
            from gi.repository import Gtk
            builder = Gtk.Builder()
            builder.add_from_file('/usr/share/ubiquity/gtk/stepUbuntuBootstrap.ui')
            builder.connect_signals(self)
            self.controller = controller
            self.controller.add_builder(builder)
            self.plugin_widgets = builder.get_object('stepUbuntuBootstrap')
            self.automated_recovery = builder.get_object('automated_recovery')
            self.automated_recovery_box = builder.get_object('automated_recovery_box')
            self.automated_combobox = builder.get_object('hard_drive_combobox')
            self.interactive_recovery = builder.get_object('interactive_recovery')
            self.interactive_recovery_box = builder.get_object('interactive_recovery_box')
            self.hdd_recovery = builder.get_object('hdd_recovery')
            self.hdd_recovery_box = builder.get_object('hdd_recovery_box')
            self.hidden_radio = builder.get_object('hidden_radio')
            self.reboot_dialog = builder.get_object('reboot_dialog')
            self.title = builder.get_object('ubuntu_recovery_title')
            self.advanced_options = builder.get_object('ubuntu_recovery_advanced_options')
            self.dual_dialog = builder.get_object('dual_dialog')
            self.info_box = builder.get_object('info_box')
            self.info_spinner = Gtk.Spinner()
            builder.get_object('info_spinner_box').add(self.info_spinner)
            self.err_dialog = builder.get_object('err_dialog')
            self.remove_media_dialog = builder.get_object('remove_media_dialog')

            self.hdd_delete_user_data = builder.get_object('hdd_delete_user_data')
            self.hdd_warning_label = builder.get_object('hdd_warning_label')

            #advanced page widgets
            self.icon = builder.get_object('ubuntu_image')
            self.advanced_page = builder.get_object('advanced_window')
            self.advanced_table = builder.get_object('advanced_table')
            self.version_detail = builder.get_object('version_detail')
            self.mount_detail = builder.get_object('mountpoint_detail')
            self.memory_detail = builder.get_object('memory_detail')
            self.proprietary_combobox = builder.get_object('disable_proprietary_driver_combobox')
            self.dual_combobox = builder.get_object('dual_combobox')
            self.dual_layout_combobox = builder.get_object('dual_layout_combobox')
            self.active_partition_combobox = builder.get_object('active_partition_combobox')
            self.rp_filesystem_combobox = builder.get_object('recovery_partition_filesystem_checkbox')
            self.disk_layout_combobox = builder.get_object('disk_layout_combobox')
            self.swap_combobox = builder.get_object('swap_behavior_combobox')
            self.ui_combobox = builder.get_object('default_ui_combobox')

            #populate dynamic comboboxes
            self._populate_dynamic_comoboxes()

            if not (self.genuine and 'UBIQUITY_AUTOMATIC' in os.environ):
                builder.get_object('error_box').show()

            #set the flag of enable_language_page as default
            self.enable_language_page = False

            PluginUI.__init__(self, controller, *args, **kwargs)

    def plugin_get_current_page(self):
        """Called when ubiquity tries to realize this page.
           * Disable the progress bar
           * Check whether we are on genuine hardware
        """
        #are we real?
        if not (self.genuine and 'UBIQUITY_AUTOMATIC' in os.environ):
            self.advanced_table.set_sensitive(False)
            self.interactive_recovery_box.hide()
            self.automated_recovery_box.hide()
            self.automated_recovery.set_sensitive(False)
            self.interactive_recovery.set_sensitive(False)
            self.controller.allow_go_forward(False)
        self.toggle_progress()

        return self.plugin_widgets

    def toggle_progress(self):
        """Toggles the progress bar for RP build"""
        if 'UBIQUITY_AUTOMATIC' in os.environ and \
                            hasattr(self.controller, 'toggle_progress_section'):
            self.controller.toggle_progress_section()

    def get_type(self):
        """Returns the type of recovery to do from GUI"""
        if self.automated_recovery.get_active():
            return "automatic"
        elif self.interactive_recovery.get_active():
            return "interactive"
        elif self.hdd_recovery.get_active():
            return "hdd"
        else:
            return ""

    def get_selected_device(self):
        """Returns the selected device from the GUI"""
        device = size = ''
        model = self.automated_combobox.get_model()
        iterator = self.automated_combobox.get_active_iter()
        if iterator is not None:
            device = model.get_value(iterator, 0)
            size = model.get_value(iterator, 1)
        return (device, size)

    def set_type(self, value, stage):
        """Sets the type of recovery to do in GUI"""
        if not self.genuine:
            return
        self.hidden_radio.set_active(True)

        if value == "automatic":
            self.automated_recovery.set_active(True)
        elif value == "interactive":
            self.interactive_recovery.set_active(True)
        elif value == "factory":
            if stage == 2: 
                self.plugin_widgets.hide()
        else:
            self.controller.allow_go_forward(False)
            if value == "hdd":
                self.advanced_table.set_sensitive(False)
                self.hdd_recovery_box.show()
                self.interactive_recovery_box.hide()
                self.automated_recovery_box.hide()
                self.interactive_recovery.set_sensitive(False)
                self.automated_recovery.set_sensitive(False)

    def set_language_page_flag(self, value):
        self.enable_language_page = value

    def toggle_type(self, widget):
        """Allows the user to go forward after they've made a selection'"""
        self.controller.allow_go_forward(True)
        # When ENABLE_LANGUAGE_PAGE, we allow to go backward.
        if self.enable_language_page:
            self.controller.allow_go_backward(True)
        self.automated_combobox.set_sensitive(self.automated_recovery.get_active())

    def show_dialog(self, which, data = None):
        """Shows a dialog"""
        if which == "info":
            self.controller._wizard.quit.set_label(
                         self.controller.get_string('ubiquity/imported/cancel'))
            self.controller.allow_go_forward(False)
            self.automated_recovery_box.hide()
            self.interactive_recovery_box.hide()
            self.info_box.show_all()
            self.info_spinner.start()
            self.toggle_progress()
        elif which == "forward":
            self.automated_recovery_box.hide()
            self.interactive_recovery_box.hide()
            self.toggle_progress()
        else:
            self.info_spinner.stop()
            if which == "exception":
                self.err_dialog.format_secondary_text(str(data))
                self.err_dialog.run()
                self.err_dialog.hide()
                return

            self.controller.toggle_top_level()
            if which == "reboot":
                self.reboot_dialog.show_all()
                self.reboot_dialog.run()

            elif which == DUAL_BOOT_QUESTION:
                self.dual_dialog.show_all()
                self.dual_dialog.run()

            elif which == "remove-media":
                self.remove_media_dialog.show_all()
                self.remove_media_dialog.run()

    def populate_devices(self, devices):
        """Feeds a selection of devices into the GUI
           devices should be an array of 3 column arrays
        """
        #populate the devices
        liststore = self.automated_combobox.get_model()
        for device in devices:
            liststore.append(device)

        #default to the first item active (it should be sorted anyway)
        self.automated_combobox.set_active(0)

    ##                      ##
    ## Advanced GUI options ##
    ##                      ##
    def toggle_advanced(self, widget, data = None):
        """Shows the advanced page"""
        self.plugin_widgets.set_sensitive(False)
        self.advanced_page.run()
        self.advanced_page.hide()
        self.plugin_widgets.set_sensitive(True)

    def _populate_dynamic_comoboxes(self):
        """Fills up comboboxes with dynamic items based on the squashfs"""
        liststore = self.ui_combobox.get_model()
        uies = magic.find_supported_ui()
        for item in uies:
            liststore.append([item, uies[item]])

    def _map_combobox(self, item):
        """Maps a combobox to a question"""
        combobox = None
        if item == USER_INTERFACE_QUESTION:
            combobox = self.ui_combobox
        elif item == DRIVER_INSTALL_QUESTION:
            combobox = self.proprietary_combobox
        elif item == ACTIVE_PARTITION_QUESTION:
            combobox = self.active_partition_combobox
        elif item == RP_FILESYSTEM_QUESTION:
            combobox = self.rp_filesystem_combobox
        elif item == DISK_LAYOUT_QUESTION:
            combobox = self.disk_layout_combobox
        elif item == SWAP_QUESTION:
            combobox = self.swap_combobox
        elif item == DUAL_BOOT_QUESTION:
            combobox = self.dual_combobox
        elif item == DUAL_BOOT_LAYOUT_QUESTION:
            combobox = self.dual_layout_combobox
        return combobox

    def set_advanced(self, item, value):
        """Populates the options that should be on the advanced page"""

        if item == 'efi' and value:
            self.efi = True
            self.disk_layout_combobox.set_sensitive(False)
            self.active_partition_combobox.set_sensitive(False)
            self.dual_combobox.set_sensitive(False)
        elif item == "mem" and value:
            self.memory_detail.set_markup("Total Memory: %f GB" % value)
        elif item == "version":
            self.version_detail.set_markup("Version: %s" % value)
        elif item == "mount":
            self.mount_detail.set_markup("Mounted From: %s" % value)
        else:
            if type(value) is bool:
                if value:
                    value = 'true'
                else:
                    value = 'false'
            combobox = self._map_combobox(item)
            if combobox:
                iterator = find_item_iterator(combobox, value)
                if iterator is not None:
                    combobox.set_active_iter(iterator)
                else:
                    syslog.syslog("DEBUG: setting %s to %s failed" % \
                                                                  (item, value))
                    combobox.set_active(0)

            #dual boot mode. ui changes for this
            if item == DUAL_BOOT_QUESTION and self.genuine:
                value = misc.create_bool(value)
                self.dual_layout_combobox.set_sensitive(value)
                if value:
                    self.interactive_recovery_box.hide()
                else:
                    self.interactive_recovery_box.show()
                self.interactive_recovery.set_sensitive(not value)

    def get_advanced(self, item):
        """Returns the value in an advanced key"""
        combobox = self._map_combobox(item)
        if combobox:
            model = combobox.get_model()
            iterator = combobox.get_active_iter()
            return model.get_value(iterator, 0)
        else:
            return ""

    def advanced_callback(self, widget, data = None):
        """Callback when an advanced widget is toggled"""
        if widget == self.proprietary_combobox:
            #nothing changes if we change proprietary drivers currently
            pass
        elif widget == self.active_partition_combobox:
            #nothing changes if we change active partition currently
            pass
        elif widget == self.rp_filesystem_combobox:
            #nothing changes if we change RP filesystem currently
            pass
        elif widget == self.swap_combobox:
            #nothing change if we change swap currently
            pass
        else:
            model = widget.get_model()
            iterator = widget.get_active_iter()
            if iterator is not None:
                answer = model.get_value(iterator, 0)

            if widget == self.disk_layout_combobox:
                if answer == "gpt":
                    find_n_set_iterator(self.active_partition_combobox, self.efi)
                    self.active_partition_combobox.set_sensitive(False)
                else:
                    self.active_partition_combobox.set_sensitive(True)
            elif widget == self.dual_combobox:
                answer = misc.create_bool(answer)
                if not self.efi:
                    #set the type back to msdos
                    find_n_set_iterator(self.disk_layout_combobox, "msdos")
                    self.disk_layout_combobox.set_sensitive(not answer)
                #hide in the UI - this is a little special because it hides
                #some basic settings too
                self.set_advanced(DUAL_BOOT_QUESTION, answer)

################
# Debconf Page #
################

class Page(Plugin):
    """Debconf driven page for the ubuntu-bootstrap ubiquity plugin"""
    def __init__(self, frontend, db=None, ui=None):
        #setup locales
        bindtextdomain(magic.DOMAIN, magic.LOCALEDIR)
        textdomain(magic.DOMAIN)

        #selected_device
        self.device = None
        self.device_size = 0

        self.disk_size = None
        #conf
        self.preseed_config = ''
        self.rp_filesystem = None
        self.rp_size = None
        self.uefi_size = None
        self.fail_partition = None
        self.disk_layout = None
        self.swap_part = None
        self.swap = None
        self.dual = None
        self.uuid = None
        self.os_part = None
        self.rp_part = None
        self.rp_offset = '0'
        self.grub_part = None
        self.entire_disk_only = False
        self.auto_power_option = None
        self.stage = 1
        self.keep_home_partition = False
        self.create_home_partition = False
        # handler
        self.rp_builder = None
        self.platinfo = magic.PlatInfo()
        self.partition = Partition()
        self.disksmgr = disksmgr.Udisks()
        self.grubinstaller = grub.GrubInstaller('/mnt',
                                                '/usr/share/ubuntu/grub',
                                                self.platinfo.isefi)
        Plugin.__init__(self, frontend, db, ui)

    def prepare(self, unfiltered=False):
        """Prepare the Debconf portion of the plugin and gather all data"""
        #version
        with misc.raised_privileges():
            version = magic.get_pkgversion()
        self.log("version %s" % version)

        #mountpoint
        mount = ''
        mount = find_boot_device()
        self.log("mounted from %s" % mount)

        #recovery type
        rec_type = None
        try:
            rec_type = self.db.get(RECOVERY_TYPE_QUESTION)
        except debconf.DebconfError as err:
            self.log(str(err))
            rec_type = 'dynamic'
            self.db.register('debian-installer/dummy', RECOVERY_TYPE_QUESTION)
            self.db.set(RECOVERY_TYPE_QUESTION, rec_type)

        rec_hotkey_label = self.db.get('ubuntu-recovery/recovery_hotkey/partition_label')
        #If we were preseeded to dynamic, look for an RP
        rec_part = self.disksmgr.find_factory_rp_stats(rec_hotkey_label)
        if rec_part and "slave" in rec_part:
            self.stage = 2
        if rec_type == 'dynamic':
            if self.stage == 2 and rec_part["slave"] in mount:
                self.log("Detected RP at %s, setting to factory boot" % mount)
                rec_type = 'factory'
            else:
                self.log("No (matching) RP found.  Assuming media based boot")
                rec_type = 'dvd'
        elif rec_type == 'hdd':
            self.log("Boot from recovery partition to do on-disk recovery.")
            if self.platinfo.isefi and misc.create_bool(self.db.get("ubuntu-recovery/recover_uefi_boot_entry")):
                uefi_boot_entry_fifo = misc.create_bool(self.db.get("ubuntu-recovery/uefi_boot_entry_fifo"))
                # The parent device is the one which we want to change.
                uefi_device = rec_part["slave"]
                self.log("uefi_device is %s" % uefi_device)
                order = ('recovery', 'ubuntu')
                menu_is_changed = False
                if uefi_boot_entry_fifo:
                    order = ('ubuntu', 'recovery')
                for entry in order:
                    if entry == 'recovery':
                        # Check and recover 'recovery' EFI boot entry
                        if misc.create_bool(self.db.get("ubuntu-recovery/add_uefi_boot_entry")):
                            label = self.db.get("ubuntu-recovery/uefi_boot_entry_label")
                            loader = '\\EFI\\BOOT\\BOOTX64.EFI'
                            if not misc.execute_root(UEFI_HELPER, 'check', loader, label):
                                misc.execute_root(UEFI_HELPER, 'add', uefi_device, self.partition.efi, loader, label)
                                menu_is_changed = True
                    elif entry == 'ubuntu':
                        # Check and recover 'ubuntu' EFI boot entry
                        label = 'ubuntu'
                        loader = '\\EFI\\ubuntu\\grubx64.efi'
                        with misc.raised_privileges():
                            if os.path.exists(SECURE_BOOT_VAR) and repr(open(SECURE_BOOT_VAR).read()) == "'\\x01'":
                                loader = '\\EFI\\ubuntu\\shimx64.efi'
                        if not misc.execute_root(UEFI_HELPER, 'check', loader, label):
                            # Clean all boot entries to make 'ubuntu' as the first boot entry.
                            if uefi_boot_entry_fifo:
                                misc.execute_root(UEFI_HELPER, 'clean')
                            misc.execute_root(UEFI_HELPER, 'add', uefi_device, self.partition.efi, loader, label)
                            menu_is_changed = True
                # Reboot only when UEFI boot option menu is changed.
                if menu_is_changed and misc.create_bool(self.db.get("ubuntu-recovery/recover_uefi_boot_entry_reboot")):
                    machine_shutdown(self, sys._getframe().f_code.co_name, 'reboot')
        #If we're doing an unattended install/re-install on a system with a previous recovery 
        #partition layout, default to stage 1 so we can reinstall smoothly. 
        else:
            self.stage = 1

        # get the flag of enable_language_page
        # And we don't need set it in phase2
        if self.stage == 1 or rec_type == 'dvd':
            try:
                self.enable_language_page = misc.create_bool(self.db.get(ENABLE_LANGUAGE_PAGE))
            except debconf.DebconfError as err:
                self.log(str(err))
                self.enable_language_page = False
            self.ui.set_language_page_flag(self.enable_language_page)
        else:
            self.enable_language_page = False

        #Media boots should be interrupted at first screen in --automatic mode
        if rec_type == 'factory':
            self.db.fset(RECOVERY_TYPE_QUESTION, 'seen', 'true')
        else:
            self.db.set(RECOVERY_TYPE_QUESTION, '')
            self.db.fset(RECOVERY_TYPE_QUESTION, 'seen', 'false')

        #In case we preseeded the partitions we need installed to
        try:
            self.os_part = self.db.get(OS_PARTITION_QUESTION)
            if not self.os_part:
                self.os_part = self.partition.os
        except debconf.DebconfError as err:
            self.log(str(err))
            self.os_part = self.partition.os
            self.preseed(OS_PARTITION_QUESTION, self.os_part)

        try:
            self.swap_part = self.db.get(SWAP_PARTITION_QUESTION)
            if not self.swap_part:
                self.swap_part = self.partition.swap
        except debconf.DebconfError as err:
            self.log(str(err))
            self.swap_part = self.partition.swap
            self.preseed(SWAP_PARTITION_QUESTION, self.swap_part)

        #Support cases where the recovery partition isn't a linux partition
        try:
            self.rp_filesystem = self.db.get(RP_FILESYSTEM_QUESTION)
        except debconf.DebconfError as err:
            self.log(str(err))
            self.rp_filesystem = TYPE_VFAT_LBA
            self.preseed(RP_FILESYSTEM_QUESTION, self.rp_filesystem)

        #Determine the size for the recovery partition.
        try:
            self.rp_size = self.db.get(RP_SIZE_QUESTION)
        except debconf.DebconfError as err:
            self.log(str(err))
            self.rp_size = '0';
            self.preseed(RP_SIZE_QUESTION, self.rp_size)

        #Support cases where the recovery partition isn't a linux partition
        try:
            self.uefi_size = self.db.get(UEFI_SIZE_QUESTION)
        except debconf.DebconfError as err:
            self.log(str(err))
            self.uefi_size = '50';
            self.preseed(UEFI_SIZE_QUESTION, self.uefi_size)

        #Determine the offset for the recovery partition.
        try:
            self.rp_offset = self.db.get(RP_OFFSET_QUESTION)
        except debconf.DebconfError as err:
            self.log(str(err))
            self.rp_offset = '0'
            self.preseed(RP_OFFSET_QUESTION, self.rp_offset)

        #Check if entire_disk_only is enabled
        try:
            self.entire_disk_only = misc.create_bool(self.db.get(ENTIRE_DISK_ONLY))
        except debconf.DebconfError as err:
            self.log(str(err))
            self.entire_disk_only = False

        #Check if auto_power_option is enabled
        try:
            self.auto_power_option = self.db.get(AUTO_POWER_OPTION)
        except debconf.DebconfError as err:
            self.log(str(err))
            self.auto_power_option = None

        #Check if keep_home_partition is enabled
        try:
            self.keep_home_partition = misc.create_bool(self.db.get(KEEP_HOME_PARTITION))
        except debconf.DebconfError as err:
            self.log(str(err))
            self.keep_home_partition = False

        try:
            self.create_home_partition = misc.create_bool(self.db.get(CREATE_HOME_PARTITION))
        except debconf.DebconfError as err:
            self.log(str(err))
            self.create_home_partition = False

        #Check if we are set in dual-boot mode
        try:
            self.dual = misc.create_bool(self.db.get(DUAL_BOOT_QUESTION))
        except debconf.DebconfError as err:
            self.log(str(err))
            self.dual = False

        try:
            self.dual_layout = self.db.get(DUAL_BOOT_LAYOUT_QUESTION)
        except debconf.DebconfError as err:
            self.log(str(err))
            self.dual_layout = 'primary'

        #If we are successful for an MBR install, this is where we boot to
        try:
            pass_partition = self.db.get(ACTIVE_PARTITION_QUESTION)
            if not pass_partition:
                pass_partition = self.os_part
                self.preseed(ACTIVE_PARTITION_QUESTION, pass_partition)
        except debconf.DebconfError as err:
            self.log(str(err))
            pass_partition = self.os_part
            self.preseed(ACTIVE_PARTITION_QUESTION, pass_partition)

        #In case an MBR install fails, this is where we boot to
        try:
            self.fail_partition = self.db.get(FAIL_PARTITION_QUESTION)
            if not self.fail_partition:
                self.fail_partition = self.partition.recovery
        except debconf.DebconfError as err:
            self.log(str(err))
            self.fail_partition = self.partition.recovery
            self.preseed(FAIL_PARTITION_QUESTION, self.fail_partition)

        #The requested disk layout type
        #This is generally for debug purposes, but will be overridden if we
        #determine that we are actually going to be doing an EFI install
        try:
            self.disk_layout = self.db.get(DISK_LAYOUT_QUESTION)
        except debconf.DebconfError as err:
            self.log(str(err))
            self.log("Exception: %s. fallback disk layout to msdos" % str(err))
            self.disk_layout = 'msdos'
            self.preseed(DISK_LAYOUT_QUESTION, self.disk_layout)

        #Behavior of the swap partition
        try:
            self.swap = self.db.get(SWAP_QUESTION)
            if self.swap != "dynamic":
                self.swap = misc.create_bool(self.swap)
        except debconf.DebconfError as err:
            self.log(str(err))
            self.swap = 'dynamic'

        #Proprietary driver installation preventions
        try:
            proprietary = self.db.get(DRIVER_INSTALL_QUESTION)
        except debconf.DebconfError as err:
            self.log(str(err))
            proprietary = ''

        #If we detect that we are booted into uEFI mode, then we only want
        #to do a GPT install.  Actually a MBR install would work in most
        #cases, but we can't make assumptions about 16-bit anymore (and
        #preparing a UP because of it)
        if os.path.isdir('/proc/efi') or os.path.isdir('/sys/firmware/efi'):
            self.log("EFI system, set disk layout to GPT")
            self.efi = True
            self.disk_layout = 'gpt'
            self.preseed(DISK_LAYOUT_QUESTION, self.disk_layout)
        
        if pass_partition == 'dynamic':
            #Force EFI Parition or bios_grub partition active
            if self.disk_layout == 'gpt':
                pass_partition = self.partition.efi
            #Force (new) OS partition to be active.
            else:
                pass_partition = self.os_part
            self.preseed(ACTIVE_PARTITION_QUESTION, pass_partition)

           
        #default UI
        try:
            user_interface = self.db.get(USER_INTERFACE_QUESTION)
        except debconf.DebconfError as err:
            self.log(str(err))
            user_interface = 'dynamic'
            self.preseed(USER_INTERFACE_QUESTION, user_interface)

        #Default in EFI case, but also possible in MBR case
        if self.disk_layout == 'gpt':
            #Force EFI partition or bios_grub partition active
            self.preseed(ACTIVE_PARTITION_QUESTION, self.partition.efi)

        #Fill in UI data
        #Don't use loop to instead this block.(LP: #1441469)
       
        self.ui.set_advanced("mount", mount)
        self.ui.set_advanced("version", version)
        self.ui.set_advanced(DUAL_BOOT_LAYOUT_QUESTION, self.dual_layout)
        self.ui.set_advanced(DUAL_BOOT_QUESTION, self.dual)
        self.ui.set_advanced(ACTIVE_PARTITION_QUESTION, pass_partition)
        self.ui.set_advanced(DISK_LAYOUT_QUESTION, self.disk_layout)
        self.ui.set_advanced(SWAP_QUESTION, self.swap)
        self.ui.set_advanced(DRIVER_INSTALL_QUESTION, proprietary)
        self.ui.set_advanced(USER_INTERFACE_QUESTION, user_interface)
        self.ui.set_advanced(RP_FILESYSTEM_QUESTION, self.rp_filesystem)
        self.ui.set_advanced(RP_OFFSET_QUESTION, self.rp_offset)
        self.ui.set_advanced("mem", self.platinfo.memsize)
        self.ui.set_advanced("efi", self.platinfo.isefi)

        self.ui.set_type(rec_type, self.stage)

        interactive_recovery = misc.create_bool(self.db.get("ubuntu-recovery/interactive_recovery"))
        if self.entire_disk_only or not interactive_recovery:
            self.log("entire_disk_only is enabled or ubuntu-recovery/interactive_recovery is false, hide ui.interactive_recovery")
            self.ui.interactive_recovery.hide()
            self.ui.interactive_recovery_box.hide()


        if rec_type == 'hdd':
            if self.keep_home_partition:
                self.ui.hdd_delete_user_data.show()
                self.ui.hdd_warning_label.hide()
            else:
                self.ui.hdd_delete_user_data.hide()
                self.ui.hdd_warning_label.show()

        #Make sure some locale was set so we can guarantee automatic mode
        try:
            language = self.db.get('debian-installer/locale')
        except debconf.DebconfError:
            language = ''
        if not language:
            language = 'en_US.UTF-8'
            self.preseed('debian-installer/locale', language)
            self.ui.controller.translate(language)

        #Clarify which device we're operating on initially in the UI
        try:
            self.fixup_recovery_devices()
            if (rec_type == 'factory' and self.stage == 2) or rec_type == 'hdd':
                self.fixup_factory_devices(rec_part)
        except Exception as err:
            self.handle_exception(err)
            self.cancel_handler()

        #Manually translate some strings those don't belong to GtkLabel. (LP: #1089789)
        title = self.ui.title.get_text()
        self.ui.reboot_dialog.set_title(title)
        self.ui.dual_dialog.set_title(title)
        self.ui.controller._wizard.live_installer.set_title(title)
        label = self.ui.advanced_options.get_text()
        self.ui.icon.set_tooltip_markup(label)

        return (['/usr/share/ubiquity/ubuntu-bootstrap'], [RECOVERY_TYPE_QUESTION])

    def ok_handler(self):
        """Copy answers from debconf questions"""
        #basic questions
        rec_type = self.ui.get_type()
        self.log("recovery type set to '%s'" % rec_type)
        self.preseed(RECOVERY_TYPE_QUESTION, rec_type)
        # Only on the automatic mode we could select device from combobox.
        if rec_type == 'automatic':
            (device, size) = self.ui.get_selected_device()
            if device:
                self.device = device
            if size:
                self.device_size = size

        self.log("Selected device is '%s' and size %s." % (self.device, self.device_size))

        # For mmc or nvme device, the partition number is p1, p2 ... after /dev/mmcblk0, need to modify it
        self.partition.set_device_partition(self.device)

        #advanced questions
        for question in [DUAL_BOOT_QUESTION,
                         DUAL_BOOT_LAYOUT_QUESTION,
                         ACTIVE_PARTITION_QUESTION,
                         DISK_LAYOUT_QUESTION,
                         SWAP_QUESTION,
                         DRIVER_INSTALL_QUESTION,
                         USER_INTERFACE_QUESTION,
                         RP_FILESYSTEM_QUESTION]:
            answer = self.ui.get_advanced(question)
            if answer:
                self.log("advanced option %s set to %s" % (question, answer))
                self.preseed_config += question + "=" + answer + " "
                if question == RP_FILESYSTEM_QUESTION:
                    self.rp_filesystem = answer
                elif question == DISK_LAYOUT_QUESTION:
                    self.disk_layout = answer
                elif question == DUAL_BOOT_QUESTION:
                    answer = misc.create_bool(answer)
                    self.dual = answer
                elif question == DUAL_BOOT_LAYOUT_QUESTION:
                    self.dual_layout = answer
            if type(answer) is bool:
                self.preseed_bool(question, answer)
            else:
                self.preseed(question, answer)

        # We will get the locale from language page, we need to save it for stage2
        setting_value = self.db.get(TPL_DI_LOCALE)
        if setting_value:
            self.log("Setting option %s set to %s" % (TPL_DI_LOCALE, setting_value))
            self.preseed_config += TPL_DI_LOCALE + "=" + setting_value + " "

        # Remove 'ubuntu' UEFI boot entry to avoid that the system does not boot if power is cut off during a recovery.
        if self.platinfo.isefi:
            misc.execute_root(UEFI_HELPER, 'remove', 'ubuntu')
        # Set boot flag at recovery partition to avoid that the system does not boot if power is cut off during a recovery.
        elif self.disk_layout == 'msdos':
            misc.execute_root('sfdisk', self.device, '-A', self.fail_partition)

        # Unset 'rectype' to make it automatically doing again when power is cut off during a recovery.
        # Set 'ondiskrec' to avoid red grub menu of on-disk recovery. (LP: #1054250)
        if rec_type is None or rec_type == '':
            if os.path.exists(ISO_MOUNT):
                rp = ISO_MOUNT
            else:
                rp = CDROM_MOUNT
            misc.execute_root('mount', '-o', 'remount,rw', rp)
            misc.execute_root('grub-editenv', rp + '/boot/grub/grubenv', 'unset', 'rectype')
            misc.execute_root('grub-editenv', rp + '/boot/grub/grubenv', 'set', 'ondiskrec=1')
            misc.execute_root('mount', '-o', 'remount,ro', rp)

        return Plugin.ok_handler(self)

    def report_progress(self, info, percent):
        """Reports to the frontend an update about th progress"""
        self.frontend.debconf_progress_info(info)
        self.frontend.debconf_progress_set(percent)

    def cleanup(self):
        """Do all the real processing for this plugin.
           * This has to be done here because ok_handler won't run in a fully
             automated load, and we need to run all steps in all scenarios
           * Run is the wrong time too because it runs before the user can
             answer potential questions
        """
        # collect information RPbuilder need
        rpconf = magic.Values(
            os_part=self.os_part,
            rp_filesystem=self.rp_filesystem,
            rp_size=int(self.rp_size) if self.rp_size.isdigit() else 0,
            uefi_size=int(self.uefi_size) if self.uefi_size.isdigit() else 0,
            rp_offset=self.rp_offset,
            device=self.device,
            device_size=self.device_size,
            dual=self.dual,
            dual_layout=self.dual_layout,
            rec_type = self.db.get(RECOVERY_TYPE_QUESTION),
            rec_hotkey_trigger = self.db.get('ubuntu-recovery/recovery_hotkey/trigger'),
            rec_hotkey_filesystemtype = self.db.get('ubuntu-recovery/recovery_hotkey/filesystem_type'),
            rec_hotkey_label = self.db.get('ubuntu-recovery/recovery_hotkey/partition_label'),
            add_uefi_boot_entry = misc.create_bool(self.db.get("ubuntu-recovery/add_uefi_boot_entry")),
            uefi_boot_entry_fifo = misc.create_bool(self.db.get("ubuntu-recovery/uefi_boot_entry_fifo")),
            uefi_boot_entry_label = self.db.get("ubuntu-recovery/uefi_boot_entry_label"))

        try:
            # User recovery - need to copy RP
            if rpconf.rec_type == "automatic" or \
               (rpconf.rec_type == "factory" and self.stage == 1):
                
                if not (rpconf.rec_type == "factory" and self.stage == 1):
                    self.ui.show_dialog("info")

                # Check and call hook for RD/QA Internal use
                try:
                    pre_cmd = self.db.get('ubuntu-recovery/pre_phase1_command')
                    if pre_cmd:
                        misc.execute_root('sh', '-c', pre_cmd)
                except debconf.DebconfError:
                    pass

                self.disksmgr.disable_swap()

                # Find and delete the home partition (if it exists)
                self.remove_home_partition()

                #init progress bar and size thread
                self.frontend.debconf_progress_start(0, 100, "")
                size_thread = ProgressBySize(_("Copying Files"),
                                               "/mnt",
                                               "0")
                size_thread.progress = self.report_progress
                #init builder
                self.rp_builder = RPbuilder(rpconf,
                                            self.preseed_config,
                                            self.grubinstaller,
                                            size_thread)
                self.rp_builder.exit = self.exit_ui_loops
                self.rp_builder.status = self.report_progress
                self.rp_builder.start()
                self.enter_ui_loop()
                self.rp_builder.join()
                if self.rp_builder.exception:
                    self.handle_exception(self.rp_builder.exception)

                # Check and call hook for RD/QA Internal use
                try:
                    post_cmd = self.db.get('ubuntu-recovery/post_phase1_command')
                    if post_cmd:
                        misc.execute_root('sh', '-c', post_cmd)
                except debconf.DebconfError:
                    pass
  
                if misc.create_bool(self.db.get("ubuntu-recovery/prompt_remove_media")) and \
                   misc.is_removable(find_boot_device()):
                    with misc.raised_privileges():
                        self.ui.show_dialog("remove-media")
                        open('/proc/sysrq-trigger', 'w').write('b')

                machine_shutdown(self, sys._getframe().f_code.co_name, self.auto_power_option)
            # User recovery - resizing drives
            elif rpconf.rec_type == "interactive":
                self.ui.show_dialog("forward")
                self.unset_drive_preseeds()
            # Factory install, and booting from RP
            else:
                # yuning: XXX: we can not use rpconf.rec_type to determine
                #   this since it's cleared before reach here, however check
                #   check cmdline in such way is too dirty, to be improved.
                if 'ubuntu-recovery/recovery_type=hdd' in open('/proc/cmdline', 'r').read().split():
                    self.ui.toggle_progress()

                self.sleep_network()
                self.disksmgr.disable_swap()
                self.clean_recipe()
                if os.path.exists(ISO_MOUNT):
                    rp = ISO_MOUNT
                else:
                    rp = CDROM_MOUNT
                # Set 'recordfail' flag when there is no 'ondiskrec' flag. (LP: #1054243) (LP: #1054250)
                # This flag will be unset after it successfully finishes.
                if os.path.exists(rp + '/boot/grub/grubenv') and not misc.execute_root('grep', 'ondiskrec=1', rp + '/boot/grub/grubenv'):
                    misc.execute_root('mount', '-o', 'remount,rw', rp)
                    misc.execute_root('grub-editenv', rp + '/boot/grub/grubenv', 'set', 'recordfail=1')
                    misc.execute_root('mount', '-o', 'remount,ro', rp)
                if self.ui.hdd_delete_user_data.get_active() or \
                   not self.ui.hdd_recovery.get_active() or \
                   not self.keep_home_partition:
                    self.remove_home_partition()
                if self.create_home_partition:
                    recipe_file = self.db.get('partman-auto/expert_recipe_file')
                    if recipe_file:
                        self.create_alt_disk_recipe_file()
                    if self.ui.hdd_recovery.get_active() and \
                       not self.ui.hdd_delete_user_data.get_active() and \
                       self.keep_home_partition:
                        if recipe_file:
                            self.db.set('partman-auto/expert_recipe_file', recipe_file + '-alt')
                        else:
                            self.clean_recipe_home()
                self.remove_extra_partitions()
                self.install_grub()
                if self.platinfo.isefi and rpconf.uefi_boot_entry_fifo and rpconf.add_uefi_boot_entry:
                    # Clean all boot entries to make 'ubuntu' as the first boot entry.
                    misc.execute_root(UEFI_HELPER, 'clean')
                    # Check and recover 'ubuntu' EFI boot entry
                    label = 'ubuntu'
                    loader = '\\EFI\\ubuntu\\grubx64.efi'
                    with misc.raised_privileges():
                        if os.path.exists(SECURE_BOOT_VAR) and repr(open(SECURE_BOOT_VAR).read()) == "'\\x01'":
                            loader = '\\EFI\\ubuntu\\shimx64.efi'
                    misc.execute_root(UEFI_HELPER, 'add', rpconf.device, self.partition.efi, loader, label)
                    # Check and recover 'recovery' EFI boot entry
                    label = rpconf.uefi_boot_entry_label
                    loader = '\\EFI\\BOOT\\BOOTX64.EFI'
                    misc.execute_root(UEFI_HELPER, 'add', rpconf.device, self.partition.efi, loader, label)
        except Exception as err:
            #For interactive types of installs show an error then reboot
            #Otherwise, just reboot the system
            if rpconf.rec_type == "automatic" or rpconf.rec_type == "interactive" or \
               ('UBIQUITY_DEBUG' in os.environ and 'UBIQUITY_ONLY' in os.environ):
                self.handle_exception(err)
            self.cancel_handler()

        if i18n.get_translations():
            reget = False
        else:
            reget = True
        #translate languages
        self.ui.controller.translate(just_me=False, not_me=True, reget=reget)
        Plugin.cleanup(self)
        # When ENABLE_LANGUAGE_PAGE, and click the 'Back', we need stop debconf, or it'll be locked.
        if self.enable_language_page:
            self.frontend.stop_debconf()
            self.ui.controller.translate(just_me=False, not_me=True, reget=reget)

    def install_grub(self):
        """Installs grub on the recovery partition"""

        # In a lot of scenarios it will already be there.
        # Don't install if:
        # * We're dual boot
        # * We're GPT (or EFI)
        # * We're on an NTFS filesystem
        # * Factory grub exists (ntldr)
        # * Grubenv exists (grubenv)
        if self.dual or self.disk_layout == 'gpt' or \
                self.rp_filesystem == TYPE_NTFS or \
                self.rp_filesystem == TYPE_NTFS_RE or \
                os.path.exists(os.path.join(CDROM_MOUNT, 'ntldr')) or \
                os.path.exists(os.path.join(CDROM_MOUNT, 'boot', 'grub', 'grubenv')):
            return

        #test for bug 700910.  if around, then don't try to install grub.
        try:
            test700910 = magic.fetch_output(['grub-probe', '--target=device', self.device + self.partition.recovery]).strip()
        except Exception as e:
            self.log("Exception: %s." % str(e))
            test700910 = 'aufs'
        if test700910 == 'aufs':
            self.log("Bug 700910 detected.  Aborting GRUB installation.")
            return

        self.log("Installing GRUB to %s" % self.device + self.partition.recovery)

        #Mount R/W
        if os.path.exists(ISO_MOUNT):
            target = ISO_MOUNT
        else:
            target = CDROM_MOUNT
        cd_mount   = misc.execute_root('mount', '-o', 'remount,rw', target)
        if cd_mount is False:
            raise RuntimeError("CD Mount failed")

        #Check for a grub.cfg to start - make as necessary
        with misc.raised_privileges():
            self.grubinstaller.install_rp_grubcfg(RP_GRUBCFGS,
                                      {'uuid':self.uuid,
                                       'rp_number':self.partition.recovery},
                                        self.dual)

        #Do the actual grub installation
        self.grubinstaller.install_grub(target, self.device + self.partition.recovery)
        uncd_mount = misc.execute_root('mount', '-o', 'remount,ro', target)
        if uncd_mount is False:
            syslog.syslog("Uncd mount failed.  This may be normal depending on the filesystem.")

    def sleep_network(self):
        """Requests the network be disabled for the duration of install to
           prevent conflicts"""
        try:
            bus = dbus.SystemBus()
            backend_iface = dbus.Interface(bus.get_object(magic.DBUS_BUS_NAME, '/RecoveryMedia'), magic.DBUS_INTERFACE_NAME)
            backend_iface.force_network(False)
            backend_iface.request_exit()
        except Exception as e:
            self.log("Exception: %s." % str(e))

    def clean_recipe(self):
        """Cleans up the recipe to remove swap if we have a small drive"""

        #don't mess with dual boot recipes
        if self.dual:
            return

        #If we are in dynamic (ubuntu-recovery/swap=dynamic) and small drive
        #   or we explicitly disabled (ubuntu-recovery/swap=false)
        if not self.swap or (self.swap == "dynamic" and \
                                       (self.platinfo.memsize > 32 or self.disk_size <= 128)):
            self.log("Performing swap recipe fixup (%s, hdd: %i, mem: %f)" % \
                                        (self.swap, self.disk_size, self.platinfo.memsize))
            try:
                #look for an external recipe and make a new recipe file from it.
                recipe = ''
                recipe_file = self.db.get('partman-auto/expert_recipe_file')
                if recipe_file:
                    new_recipe_file = recipe_file + '-new'
                    if os.path.exists(new_recipe_file):
                        self.db.set('partman-auto/expert_recipe_file', new_recipe_file)
                    else:
                        with open(recipe_file, 'r') as rfd:
                            lines = rfd.readlines()
                            for line in lines:
                                recipe += line
                        rfd.close()
                        
                        new_recipe = ''
                        swap_pattern = re.compile('.*linux-swap.*')
                        for stanza in recipe.split('.'):
                            if not swap_pattern.search(stanza) and stanza.strip():
                                new_recipe += stanza
                                new_recipe += '.'
                        if os.path.exists(ISO_MOUNT): 
                            rp = ISO_MOUNT
                        else:
                            rp = CDROM_MOUNT
                        misc.execute_root('mount', '-o', 'remount,rw', rp)
                        with misc.raised_privileges():
                            with open(new_recipe_file, 'w') as wfd:
                                for line in new_recipe:
                                    wfd.write(line)
                                wfd.write('\n')
                            wfd.close()
                        self.db.set('partman-auto/expert_recipe_file', new_recipe_file)
                else:
                    recipe = self.db.get('partman-auto/expert_recipe')
                    swap_pattern = re.compile('.*linux-swap.*')
                    (name, stanzas) = recipe.split('::')
                    new_recipe = name + '::'
                    for stanza in stanzas.split('.'):
                        if not swap_pattern.match(stanza) and stanza.strip():
                            new_recipe += stanza
                            new_recipe += '.'
                    self.db.set('partman-auto/expert_recipe', new_recipe)
            except debconf.DebconfError as err:
                self.log(str(err))

    def clean_recipe_home(self):
        """Cleans up the expert-recipe to remove the home partition stanza if
           the home partition is to be kept"""

        self.log("Performing home partition fixup")

        try:
            new_recipe = ''
            recipe = self.db.get('partman-auto/expert_recipe')
            extra_part = re.compile('.*-1.*')
            for stanza in recipe.split('.'):
                if not extra_part.search(stanza) and stanza.strip():
                    new_recipe += stanza
                    new_recipe += '.'
            self.db.set('partman-auto/expert_recipe', new_recipe)
        except debconf.DebconfError as err:
            self.log(str(err))

    def create_alt_disk_recipe_file(self):
        """Makes an alternate disk-recipe to use in the case a user wants to
           keep their /home partition"""

        self.log("Making an alternate expert_recipe_file for preserving the home partition")

        try:
            recipe = ''
            recipe_file = self.db.get('partman-auto/expert_recipe_file')
            alt_recipe_file = recipe_file + '-alt'
            if os.path.exists(alt_recipe_file):
                return
            with open(recipe_file, 'r') as rfd:
                lines = rfd.readlines()
                for line in lines:
                    recipe += line
            rfd.close()

            new_recipe = ''
            extra_part = re.compile('.*-1.*')
            for stanza in recipe.split('.'):
                if not extra_part.search(stanza) and stanza.strip():
                    new_recipe += stanza
                    new_recipe += '.'

            if os.path.exists(ISO_MOUNT):
                rp = ISO_MOUNT
            else:
                rp = CDROM_MOUNT
            misc.execute_root('mount', '-o', 'remount,rw', rp)
            with misc.raised_privileges():
                with open(alt_recipe_file, 'w') as wfd:
                    for line in new_recipe:
                        wfd.write(line)
                    wfd.write('\n')
                wfd.close()
            misc.execute_root('mount', '-o', 'remount,ro', rp)
        except debconf.DebconfError as err:
            self.log(str(err))

    def remove_extra_partitions(self):
        """Removes partitions we are installing on for the process to start"""
        if self.disk_layout == 'msdos':
            #First set the new partition active
            active = misc.execute_root('sfdisk', self.device, '-A', self.fail_partition)
            if active is False:
                self.log("Failed to set partition %s active on %s" % \
                                             (self.fail_partition, self.device))
        #check for small disks.
        #on small disks or big mem, don't look for extended or delete swap.
        if not self.swap or (self.swap == "dynamic" and \
                                       (self.platinfo.memsize > 32 or self.disk_size <= 128)):
            self.swap_part = ''
            total_partitions = 0
        else:
            #check for extended partitions
            with misc.raised_privileges():
                total_partitions = len(magic.fetch_output(['partx', self.device]).split('\n'))-1
        #remove extras
        for number in (self.os_part, self.swap_part):
            if number.isdigit():
                remove = misc.execute_root('parted', '-s', self.device, 'rm', number)
                if remove is False:
                    self.log("Error removing partition number: %s on %s (this may be normal)'" % (number, self.device))
                refresh = misc.execute_root('partx', '-d', '--nr', number, self.device)
                if refresh is False:
                    self.log("Error updating partition %s for kernel device %s (this may be normal)'" % (number, self.device))
        #if there were extended, cleanup
        if total_partitions > 4:
            refresh = misc.execute_root('partx', '-d', '--nr', '5-' + str(total_partitions), self.device)
            if refresh is False:
                self.log("Error removing extended partitions 5-%s for kernel device %s (this may be normal)'" % (total_partitions, self.device))

    def remove_home_partition(self):
        """NULLs out the filesystem label and removes the home partition"""
        with misc.raised_privileges():
            command1 = subprocess.Popen(['parted', self.device, 'print'], stdout=subprocess.PIPE)
            output1 = command1.communicate()[0].decode('utf-8').split('\n')
            for line in output1:
                processed_line = line.split()
                if len(processed_line) >= 1 and processed_line[0].isdigit():
                    part = self.device + processed_line[0] if not self.partition.prefix_p else self.device + 'p' + processed_line[0]
                    command2 = subprocess.Popen(['e2label', part], stdout=subprocess.PIPE)
                    output2 = command2.communicate()[0].decode('utf-8').split('\n')
                    if output2[0] == 'User_Data':
                        misc.execute_root('e2label', part, 'NULL')
                        remove = misc.execute_root('parted', '-s', self.device, 'rm', processed_line[0])
                        if remove is False:
                            self.log("Error removing home partition!")
                        refresh = misc.execute_root('partx', '-d', '--nr', processed_line[0], self.device)
                        if refresh is False:
                            self.log("Error updating home partition!")

    def unset_drive_preseeds(self):
        """Unsets any preseeds that are related to setting a drive"""
        for key in [ 'partman-auto/init_automatically_partition',
                     'partman-auto/disk',
                     'partman-auto/expert_recipe',
                     'partman-auto/expert_recipe_file',
                     'partman-basicfilesystems/no_swap',
                     'grub-installer/only_debian',
                     'grub-installer/with_other_os',
                     'grub-installer/bootdev',
                     'grub-installer/make_active',
                     'oem-config/early_command',
                     'oem-config/late_command',
                     'ubuntu-recovery/active_partition',
                     'ubuntu-recovery/fail_partition',
                     'ubiquity/poweroff',
                     'ubiquity/reboot' ]:
            self.db.fset(key, 'seen', 'false')
            self.db.reset(key, '')
        self.db.set('ubiquity/partman-skip-unmount', 'false')
        self.db.set('partman/filter_mounted', 'true')

    def fixup_recovery_devices(self):
        """Discovers the first hard disk to install to"""
        self.log("Multiple disk candidates were found: %s" % self.disksmgr.disks)

        #Always choose the first candidate to start
        self.device = self.disksmgr.disks[0][0]
        self.log("Initially selected candidate disk: %s" % self.device)

        #populate UI
        self.ui.populate_devices(self.disksmgr.disks)

    def fixup_factory_devices(self, rec_part):
        """Find the factory recovery partition, and re-adjust preseeds to use that data"""
        #Ignore any EDD settings - we want to just plop on the same drive with
        #the right FS label (which will be valid right now)
        #Don't you dare put a USB stick in the system with that label right now!

        self.device = rec_part["slave"]

        if os.path.exists(ISO_MOUNT):
            location = ISO_MOUNT
        else:
            location = CDROM_MOUNT
        #early = '/usr/share/ubuntu/scripts/oem_config.sh early %s %s' % (rec_part['device'], location)
        early = self.db.get('oem-config/early_command') + ' %s %s' %(rec_part['device'], location)
        self.db.set('oem-config/early_command', early)
        self.db.set('partman-auto/disk', self.device)

        if self.disk_layout == 'msdos':
            self.db.set('grub-installer/bootdev', self.device + self.os_part)
        elif self.disk_layout == 'gpt':
            self.db.set('grub-installer/bootdev', self.device)

        if rec_part["fs"] == "ntfs":
            self.rp_filesystem = TYPE_NTFS_RE
        elif rec_part["fs"] == "vfat":
            self.rp_filesystem = TYPE_VFAT_LBA
        else:
            raise RuntimeError("Unknown filesystem on recovery partition: %s" % rec_part["fs"])

        if self.dual_layout == 'logical':
            expert_question = 'partman-auto/expert_recipe'
            self.db.set(expert_question,
                    self.db.get(expert_question).replace('primary', 'logical'))
            self.db.set('ubiquity/install_bootloader', 'false')

        self.disk_size = rec_part["size_gb"]
        self.uuid = rec_part["uuid"]

        self.log("Detected device we are operating on is %s" % self.device)
        self.log("Detected a %s filesystem on the %s recovery partition" % (rec_part["fs"], rec_part["label"]))

    def cancel_handler(self):
        """Called when we don't want to perform recovery'"""
        # When enalbe langauge we just call cancel_handler, don't shutdown.
        if self.enable_language_page:
            Plugin.cancel_handler(self)
        else:
            machine_shutdown(self, sys._getframe().f_code.co_name, 'reboot')

    def handle_exception(self, err):
        """Handle all exceptions thrown by any part of the application"""
        self.log(str(err))
        self.ui.show_dialog("exception", err)

    def log(self, error):
        """Outputs a debugging string to /var/log/installer/debug"""
        self.debug("%s: %s" % (NAME, error))

############################
# RP Builder Worker Thread #
############################

class RPbuilder(Thread):
    """The recovery partition builder worker thread"""
    def __init__(self, conf, preseed_config, grubinstaller, sizing_thread):
        self.conf = conf
        self.platinfo = magic.PlatInfo()
        self.partition = Partition()
        self.preseed_config = preseed_config
        self.grubinstaller = grubinstaller
        self.grubinstaller.conf = conf
        self.file_size_thread = sizing_thread
        self.exception = None
        Thread.__init__(self)

        # For mmc or nvme device, the partition number is p1, p2 ... after /dev/mmcblk0, need to modify it
        self.partition.set_device_partition(self.conf.device)

    def build_rp(self, cushion=1500):
        """Copies content to the recovery partition using a parted wrapper.

           This might be better implemented in python-parted or parted_server/partman,
           but those would require extra dependencies, and are generally more complex
           than necessary for what needs to be accomplished here."""
        black_pattern = re.compile('^casper-rw$')

        #Things we know ahead of time will cause us to error out
        if self.platinfo.disk_layout == 'gpt':
            if self.conf.dual:
                raise RuntimeError("Dual boot is not yet supported when configuring the disk as GPT.")
        elif self.platinfo.disk_layout == 'msdos':
            pass
        else:
            raise RuntimeError("Unsupported disk layout: %s" % self.platinfo.disk_layout)

        #Check if we are booted from same device as target
        mounted_device = find_boot_device()
        if self.conf.device in mounted_device:
            raise RuntimeError("Attempting to install to the same device as booted from.\nYou will need to clear the contents of the recovery partition\nmanually to proceed.")

        #Adjust recovery partition type to something parted will recognize
        if self.conf.rp_filesystem == TYPE_NTFS or \
           self.conf.rp_filesystem == TYPE_NTFS_RE:
            self.conf.rp_filesystem = 'ntfs'
        elif self.conf.rp_filesystem == TYPE_VFAT or \
             self.conf.rp_filesystem == TYPE_VFAT_LBA:
            self.conf.rp_filesystem = 'fat32'
        else:
            raise RuntimeError("Unsupported recovery partition filesystem: %s" % self.conf.rp_filesystem)

        #Default partition numbers
        rp_part   = self.partition.recovery
        grub_part = self.partition.recovery

        #Calculate RP size
        rp_size = magic.black_tree("size", black_pattern, CDROM_MOUNT)
        #in mbytes
        rp_size_mb = (rp_size / 1000000) + cushion

        if self.conf.rp_size > rp_size_mb:
            rp_size_mb = self.conf.rp_size

        # Erase MBR and GPT
        command = ('dd', 'if=/dev/zero', 'of=%s' % self.conf.device, 'bs=1M', 'count=1')
        if misc.execute_root(*command) is False:
            raise RuntimeError("Error erasing MBR and GPT on %s" % self.conf.device)

        # Build new partition table
        command = ('parted', '-s', self.conf.device, 'mklabel', self.platinfo.disk_layout)
        if misc.execute_root(*command) is False:
            raise RuntimeError("Error creating new partition table %s on %s" % (self.platinfo.disk_layout, self.conf.device))

        self.status(_("Creating Partitions"), 1)
        if self.platinfo.disk_layout == 'msdos':
            #Create an MBR
            path = find_syslinux_file('mbr.bin')
            if not path:
                raise RuntimeError("Missing both DRMK and syslinux MBR")
            with open(path, 'rb') as mbr:
                with misc.raised_privileges():
                    with open(self.conf.device, 'wb') as out:
                        out.write(mbr.read(440))

            #Build RP
            command = ('parted', '-a', 'minimal', '-s', self.conf.device, 'mkpart', 'primary', self.conf.rp_filesystem, self.conf.rp_offset, str(rp_size_mb))
            if misc.execute_root(*command) is False:
                raise RuntimeError("Error creating new %s mb recovery partition on %s" % (rp_size_mb, self.conf.device))

            #Set RP active (bootable)
            command = ('parted', '-s', self.conf.device, 'set', rp_part, 'boot', 'on')
            if misc.execute_root(*command) is False:
                raise RuntimeError("Error setting recovery partition active %s" % (self.conf.device))

            #Dual boot creates more partitions
            if self.conf.dual:
                my_os_part = 5120 #mb
                other_os_part_end = (int(self.conf.device_size) / 1000000) - my_os_part

                commands = [('parted', '-a', 'minimal', '-s', self.conf.device, 'mkpart', 'primary', 'ntfs', str(rp_size_mb), str(other_os_part_end)),
                            ('mkfs.ntfs' , '-f', '-L', 'OS', self.conf.device + '3')]
                if self.conf.dual_layout == 'primary':
                    commands.append(('parted', '-a', 'minimal', '-s', self.conf.device, 'mkpart', 'primary', 'fat32', str(other_os_part_end), str(other_os_part_end + my_os_part)))
                    commands.append(('mkfs.msdos', '-n', 'ubuntu'  , self.conf.device + '4'))
                    #Grub needs to be on the 4th partition to kick off the ubuntu install
                    grub_part = '4'
                else:
                    grub_part = '1'
                for command in commands:
                    if misc.execute_root(*command) is False:
                        raise RuntimeError("Error building dual boot partitions")

        #GPT Layout
        elif self.platinfo.disk_layout == 'gpt':
            if self.platinfo.isefi:
                grub_size = self.conf.uefi_size if self.conf.uefi_size > 50 else 50
                commands = [('parted', '-s', self.conf.device, '-a', 'minimal', 'mkpart', 'primary', 'fat32', self.conf.rp_offset, str(grub_size)),
                            ('parted', '-s', self.conf.device, 'name', '1', "'EFI'"),
                            ('udevadm', 'settle'),
                            ('mkfs.msdos', '-n', 'EFI System', self.conf.device + self.partition.efi),
                            ('parted', '-s', self.conf.device, 'set', '1', 'boot', 'on')]
            else:
                grub_size = 1.5
                commands = [('parted', '-a', 'minimal', '-s', self.conf.device, 'mkpart', 'biosboot', self.conf.rp_offset, str(grub_size)),
                            ('udevadm', 'settle'),
                            ('parted', '-s', self.conf.device, 'set', '1', 'bios_grub', 'on')]
            for command in commands:
                if misc.execute_root(*command) is False:
                    if self.platinfo.isefi:
                        raise RuntimeError("Error creating new %s mb EFI boot partition on %s" % (grub_size, self.conf.device + self.partition.efi))
                    else:
                        raise RuntimeError("Error creating new %s mb grub partition on %s" % (grub_size, self.conf.device + self.partition.efi))

            # Install firmware into UEFI System Partition
            grub_part = self.partition.efi

            #Build RP
            command = ('parted', '-a', 'minimal', '-s', self.conf.device, 'mkpart', "'Recovery Partition'", self.conf.rp_filesystem, str(grub_size), str(rp_size_mb + grub_size))
            if misc.execute_root(*command) is False:
                raise RuntimeError("Error creating new %s mb recovery partition on %s" % (rp_size_mb, self.conf.device))

        # Wait for the parted event queue to finish. (LP: #1583015)
        command = ('udevadm', 'settle')
        if misc.execute_root(*command) is False:
            raise RuntimeError("Error waiting for partition event queue complete.")

        #Build RP filesystem
        self.status(_("Formatting Partitions"), 2)
        if self.conf.rp_filesystem == 'fat32':
            command = ('mkfs.msdos', '-n', self.conf.rec_hotkey_label, self.conf.device + rp_part)
        elif self.conf.rp_filesystem == 'ntfs':
            command = ('mkfs.ntfs', '-f', '-L', self.conf.rec_hotkey_label, self.conf.device + rp_part)
        if misc.execute_root(*command) is False:
            raise RuntimeError("Error creating %s filesystem on %s%s" % (self.conf.rp_filesystem, self.conf.device, rp_part))

        # Wait for the filesystem event queue to finish. (LP: #972980)
        command = ('udevadm', 'settle')
        if misc.execute_root(*command) is False:
            raise RuntimeError("Error waiting for the event queue to finish.")

        #Mount RP
        if misc.execute_root('mount', self.conf.device + rp_part, '/mnt') is False:
            raise RuntimeError("Error mounting %s%s" % (self.conf.device, rp_part))

        #Update status and start the file size thread
        self.file_size_thread.reset_write(rp_size)
        self.file_size_thread.set_scale_factor(85)
        self.file_size_thread.set_starting_value(2)
        self.file_size_thread.start()

        #Copy RP Files
        with misc.raised_privileges():
            subprocess.call(['mkdir', '-p', '/mnt/boot/grub'])
            magic.black_tree("copy", black_pattern, CDROM_MOUNT, '/mnt')

        self.file_size_thread.join()

        #If dual boot, mount the proper /boot partition first
        if self.conf.dual:
            if misc.execute_root('mount', self.conf.device + grub_part, '/mnt') is False:
                raise RuntimeError("Error mounting %s%s" % (self.conf.device, grub_part))

        #find uuid of drive
        with misc.raised_privileges():
            uuid = magic.get_part_uuid(self.conf.device + rp_part)

        #read in any old seed
        seed = magic.Seed(os.path.join('/mnt', 'preseed', 'ubuntu-recovery.cfg'))

        #process the new options
        for item in self.preseed_config.split():
            if '=' in item:
                key, value = item.split('=')
                seed.set(key, value)

        #write out a ubuntu-recovery.seed configuration file
        with misc.raised_privileges():
            if not os.path.isdir(os.path.join('/mnt', 'preseed')):
                os.makedirs(os.path.join('/mnt', 'preseed'))
            seed.save()
        #Check for a grub.cfg - replace as necessary
            self.grubinstaller.install_rp_grubcfg(RP_GRUBCFGS,
                                              {'uuid':uuid,
                                               'rp_number':rp_part},
                                              self.conf.dual,
                                              True)

        #Install grub
        self.status(_("Installing GRUB"), 88)
        self.grubinstaller.install_rp_grub(self.conf.device + rp_part, self.conf.device + grub_part)

        with misc.raised_privileges():
            os.rename('/mnt/efi/boot/bootx64.efi', '/mnt/efi/boot/shimx64.efi')

        #Create UEFI boot entry
        with misc.raised_privileges():
            if self.platinfo.isefi and self.conf.add_uefi_boot_entry:
                self.status(_("Creating UEFI boot entry"), 89)
                if self.conf.uefi_boot_entry_fifo:
                    misc.execute_root(UEFI_HELPER, 'clean')
                misc.execute_root(UEFI_HELPER, 'add', self.conf.device, grub_part, '\\EFI\\BOOT\\shimx64.efi', self.conf.uefi_boot_entry_label)

        #dual boot needs primary #4 unmounted
        if self.conf.dual:
            misc.execute_root('umount', '/mnt')
            self.status(_("Building G2LDR"), 90)
            self.grubinstaller.build_g2ldr()

        #Build new UUID
        if int(self.platinfo.memsize) >= 1: #GB
            if os.path.isdir(ISO_MOUNT):
                syslog.syslog("Skipping UUID generation - booted from ISO image.")
            else:
                self.status(_("Regenerating UUID / initramfs"), 90)
                with misc.raised_privileges():
                    magic.create_new_uuid(os.path.join(CDROM_MOUNT, 'casper'),
                            os.path.join(CDROM_MOUNT, '.disk'),
                            os.path.join('/mnt', 'casper'),
                            os.path.join('/mnt', '.disk'),
                            include_bootstrap=True)
        else:
            #The new UUID just fixes the installed-twice-on-same-system scenario
            #most users won't need that anyway so it's just nice to have
            syslog.syslog("Skipping casper UUID build due to low memory")
        misc.execute_root('umount', '/mnt')

        # set EFI system partition type id
        if self.platinfo.isefi:
            with misc.raised_privileges():
                data = 't\n%s\n%s\n\nw\ny\n' % (self.partition.efi_offset, 'ef00')
                magic.fetch_output(['gdisk', self.conf.device], data)

        # set filesystem type id
        if self.conf.rec_hotkey_filesystemtype:
            with misc.raised_privileges():
                if self.platinfo.isefi:
                    data = 't\n%s\n%s%s\n\nw\ny\n' % (self.partition.recovery_offset, self.conf.rec_hotkey_filesystemtype, '00')
                    magic.fetch_output(['gdisk', self.conf.device], data)
                else:
                    if rp_part is '1':
                        data = 't\n%s\n\nw\n' % (self.conf.rec_hotkey_filesystemtype)
                    else:
                        data = 't\n%s\n%s\n\nw\n' % (rp_part, self.conf.rec_hotkey_filesystemtype)
                    magic.fetch_output(['fdisk', self.conf.device], data)

    def exit(self):
        """Function to request the builder thread to close"""
        pass

    def status(self, info, percent):
        """Stub function for passing data back up"""
        pass

    def run(self):
        """Start the RP builder thread"""
        try:
            self.build_rp()
        except Exception as err:
            self.exception = err
        self.exit()

####################
# Helper Functions #
####################

def find_boot_device():
    """Finds the device we're booted from'"""
    with open('/proc/mounts', 'r') as mounts:
        for line in mounts.readlines():
            if ISO_MOUNT in line:
                mounted_device = line.split()[0]
                break
            if CDROM_MOUNT in line:
                found = line.split()[0]
                if not 'loop' in found:
                    mounted_device = line.split()[0]
                    break
    return mounted_device

def find_syslinux_file(filename, path='/usr/lib/syslinux'):
    """Find syslinux files on disk"""
    for root, dirs, files in os.walk(path):
        if filename in files:
            return os.path.join(root, filename)
    return None

def machine_shutdown(objpath, function_name, power_command):
    """Shutdown or reboot the machine"""
    if power_command == 'poweroff':
        command = 'poweroff'
    elif power_command == 'reboot':
        command = 'reboot'
    else:
        command = 'reboot'
    result = misc.execute_root(command)
    if result is False:
        raise RuntimeError("Reboot failed from %s (%s)" % (function_name, str(objpath)))

def find_item_iterator(combobox, value, column = 0):
    """Searches a combobox for a value and returns the iterator that matches"""
    model = combobox.get_model()
    iterator = model.get_iter_first()
    while iterator is not None:
        if value == model.get_value(iterator, column):
            break
        iterator = model.iter_next(iterator)
    return iterator

def find_n_set_iterator(combobox, value, column = 0):
    """Searches a combobox for a value, and sets the iterator to that value if
       it's found"""
    iterator = find_item_iterator(combobox, value, column)
    if iterator is not None:
        combobox.set_active_iter(iterator)

##################
# Helper Classes #
##################

class Partition(object, metaclass=metaclass.Singleton):
    def __init__(self):
        self.platinfo = magic.PlatInfo()
        self.prefix_p = False

        if self.platinfo.isefi:
            self.disklabel = 'gpt'
            self.efi = '1'
            self.recovery = '2'
            self.os = '3'
            self.swap = '4'

            self.efi_offset = '1'
            self.recovery_offset = '2'
            self.os_offset = '3'
            self.swap_offset = '4'
        else:
            self.disklabel = 'msdos'
            self.recovery = '1'
            self.os = '2'
            self.swap = '3'

            self.recovery_offset = '1'
            self.os_offset = '2'
            self.swap_offset = '3'

    def set_device_partition(self, what_device):
        if 'mmcblk' in what_device or 'nvme' in what_device or 'md' in what_device:
            self.prefix_p = True
            self.efi = 'p1'
            self.recovery = 'p2'
            self.os = 'p3'
            self.swap = 'p4'

###########################################
# Commands Processed During Install class #
###########################################

class Install(InstallPlugin):
    """The install time ubuntu-bootstrap ubiquity plugin"""

    def __init__(self, frontend, db=None, ui=None):
        self.progress = None
        self.target = None
        self.platinfo = magic.PlatInfo()
        self.partition = Partition()
        InstallPlugin.__init__(self, frontend, db, ui)

    def find_unconditional_debs(self):
        '''Finds any debs from debs/main that we want unconditionally installed
           (but ONLY the latest version on the media)'''
        from apt_inst import DebFile
        from apt_pkg import TagSection

        def parse(fname):
            """ read a deb """
            control = DebFile(fname).control.extractdata("control")
            sections = TagSection(control)
            if "Modaliases" in sections:
                modaliases = sections["Modaliases"]
            else:
                modaliases = ''
            return (sections["Architecture"], sections["Package"], modaliases)

        #process debs/main
        to_install = []
        my_arch = magic.fetch_output(['dpkg', '--print-architecture']).strip()
        for top in [ISO_MOUNT, CDROM_MOUNT]:
            repo = os.path.join(top, 'debs', 'main')
            if os.path.isdir(repo):
                for fname in os.listdir(repo):
                    if '.deb' in fname:
                        arch, package, modaliases = parse(os.path.join(repo, fname))
                        if not modaliases and (arch == "all" or arch == my_arch):
                            to_install.append(package)

        #These aren't in all images, but desirable if available
        to_install.append('dkms')
        to_install.append('adobe-flashplugin')

        return to_install

    def remove_ricoh_mmc(self):
        '''Removes the ricoh_mmc kernel module which is known to cause problems
           with MDIAGS'''
        lsmod = magic.fetch_output('lsmod').split('\n')
        for line in lsmod:
            if line.startswith('ricoh_mmc'):
                misc.execute('rmmod', line.split()[0])

    def propagate_kernel_parameters(self):
        '''Copies in kernel command line parameters that were needed during
           installation'''
        extra = magic.find_extra_kernel_options()
        new = ''
        for item in extra.split():
            if not 'debian-installer/'                in item and \
               not 'console-setup/'                   in item and \
               not 'locale='                          in item and \
               not 'BOOT_IMAGE='                      in item and \
               not 'iso-scan/'                        in item and \
               not 'ubiquity'                         in item:
                new += '%s ' % item
        extra = new.strip()

        grubf = os.path.join(self.target, 'etc/default/grub')
        if extra and os.path.exists(grubf):
            #read/write new grub
            with open(grubf, 'r') as rfd:
                default_grub = rfd.readlines()
            with open(grubf, 'w') as wfd:
                for line in default_grub:
                    if 'GRUB_CMDLINE_LINUX_DEFAULT' in line:
                        line = line.replace('GRUB_CMDLINE_LINUX_DEFAULT="', \
                                      'GRUB_CMDLINE_LINUX_DEFAULT="%s ' % extra)
                    wfd.write(line)
            from ubiquity import install_misc
            install_misc.chrex(self.target, 'update-grub')

    def remove_unwanted_drivers(self):
        '''Removes drivers that were preseeded to not used for postinstall'''
        drivers = ''

        try:
            drivers = self.progress.get(DRIVER_INSTALL_QUESTION).split(',')
        except debconf.DebconfError:
            pass

        if len(drivers) > 0:
            for driver in drivers:
                if driver:
                    with open (os.path.join(self.target, '/usr/share/jockey/modaliases/', driver), 'w') as wfd:
                        wfd.write('reset %s\n' % driver)

    def mark_upgrades(self):
        '''Mark packages that can upgrade to upgrade during install'''
        cache = Cache()
        to_install = []
        for key in list(cache.keys()):
            if cache[key].is_upgradable:
                to_install.append(key)
        del cache
        return to_install


    def g2ldr(self):
        '''Builds a grub2 based loader to allow booting a logical partition'''
        #Mount the disk
        if os.path.exists(ISO_MOUNT):
            mount = ISO_MOUNT
        else:
            mount = CDROM_MOUNT
            misc.execute_root('mount', '-o', 'remount,rw', CDROM_MOUNT)

        grub.create_g2ldr(self.target, mount, self.target)

        #Don't re-run installation
        if os.path.exists(os.path.join(mount, 'grub', 'grub.cfg')):
            os.unlink(os.path.join(mount, 'grub', 'grub.cfg'))

    def wake_network(self):
        """Wakes the network back up"""
        bus = dbus.SystemBus()
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        try:
            backend_iface = dbus.Interface(bus.get_object(magic.DBUS_BUS_NAME, '/RecoveryMedia'), magic.DBUS_INTERFACE_NAME)
            backend_iface.force_network(True)
            backend_iface.request_exit()
        except Exception:
            pass


    def install(self, target, progress, *args, **kwargs):
        '''This is highly dependent upon being called AFTER configure_apt
        in install.  If that is ever converted into a plugin, we'll
        have some major problems!'''
        genuine = magic.check_vendor()
        if not genuine:
            raise RuntimeError("This recovery media requires Ubuntu Hardware.")

        self.target = target
        self.progress = progress

        rec_hotkey_label = self.db.get('ubuntu-recovery/recovery_hotkey/partition_label')
        rec_part = magic.find_partition(rec_hotkey_label)

        from ubiquity import install_misc
        to_install = []
        to_remove  = []

        #The last thing to do is set an active partition
        #This happens at the end of success command
        active = ''
        try:
            active = progress.get(ACTIVE_PARTITION_QUESTION)
        except debconf.DebconfError:
            pass
        try:
            layout = progress.get(DISK_LAYOUT_QUESTION)
        except debconf.DebconfError as e:
            syslog.syslog("Exception: %s. fallback disk layout to msdos" % str(e))
            layout = 'msdos'

        if active.isdigit():
            disk = progress.get('partman-auto/disk')
            with open('/dev/shm/set_bootable', 'w') as wfd:
                #If we have an MBR,
                if layout == 'msdos':
                    #we use the active partition bit in it
                    wfd.write('sfdisk %s -A %s\n' % (disk, active))

                    #in factory process if we backed up an MBR, that would have already
                    #been restored.
                    if not os.path.exists(os.path.join(CDROM_MOUNT, 'factory', 'mbr.bin')):
                        #test the md5 of the MBR to match DRMK or syslinux
                        #if they don't match, rewrite MBR
                        with misc.raised_privileges():
                            with open(disk, 'rb') as rfd:
                                disk_mbr = rfd.read(440)
                        path = None
                        for p in ['/usr/share/ubuntu/up', '/usr/lib/syslinux']:
                            path = find_syslinux_file('mbr.bin', p)
                        with open(path, 'rb') as rfd:
                            file_mbr = rfd.read(440)
                        if hashlib.md5(file_mbr).hexdigest() != hashlib.md5(disk_mbr).hexdigest():
                            if path is None or not os.path.exists(path):
                                raise RuntimeError("Missing DRMK and syslinux MBR")
                            wfd.write('dd if=%s of=%s bs=440 count=1\n' % (path, disk))
                #If we have GPT, we need to go down other paths
                elif layout == 'gpt':
                    #If we're booted in EFI mode, then the OS has already set
                    #the correct Bootnum active
                    if self.platinfo.isefi:
                        pass
                    #If we're not booted to EFI mode, but using GPT,
                    else:
                        #See https://bugs.launchpad.net/ubuntu/+source/partman-partitioning/+bug/592813
                        #for why we need to have this workaround in the first place
                        result = misc.execute_root('parted', '-s', disk, 'set', active, 'bios_grub', 'on')
                        if result is False:
                            raise RuntimeError("Error working around bug 592813.")

                        wfd.write('grub-install --no-floppy %s\n' % disk)
            os.chmod('/dev/shm/set_bootable', 0o755)

        #if we are loop mounted, make sure the chroot knows it too
        if os.path.isdir(ISO_MOUNT):
            os.makedirs(os.path.join(self.target, ISO_MOUNT.lstrip('/')))
            misc.execute_root('mount', '--bind', ISO_MOUNT, os.path.join(self.target, ISO_MOUNT.lstrip('/')))

        #Fixup pool to only accept stuff on /cdrom or /isodevice
        # - This is reverted during SUCCESS_SCRIPT
        # - Might be in livefs already, but we always copy in in case there was an udpate
        #pool_cmd = '/usr/share/ubuntu/scripts/pool.sh'
        #shutil.copy(pool_cmd, os.path.join(self.target, 'tmp', os.path.basename(pool_cmd)))
        #install_misc.chrex(self.target, os.path.join('/tmp', os.path.basename(pool_cmd)))

        #Stuff that is installed on all configs without fish scripts
        to_install += self.find_unconditional_debs()

        #Query Dual boot or not
        try:
            dual = misc.create_bool(progress.get(DUAL_BOOT_QUESTION))
        except debconf.DebconfError:
            dual = False

        if dual:
            #build grub2 loader for logical partitions when necessary
            try:
                layout = progress.get(DUAL_BOOT_LAYOUT_QUESTION)
                if layout == 'logical':
                    self.g2ldr()
            except debconf.DebconfError:
                raise RuntimeError("Error determining dual boot layout.")

        #install ubuntu-recovery in non dual mode only if there is an RP
        elif rec_part:
            to_install.append('ubuntu-recovery')

            #block os-prober in grub-installer
            os.rename('/usr/bin/os-prober', '/usr/bin/os-prober.real')

            #don't allow OS prober to probe other drives in single OS install
            with open(os.path.join(self.target, 'etc/default/grub'), 'r') as rfd:
                default_grub = rfd.readlines()
            with open(os.path.join(self.target, 'etc/default/grub'), 'w') as wfd:
                found = False
                for line in default_grub:
                    if line.startswith("GRUB_DISABLE_OS_PROBER="):
                        line = "GRUB_DISABLE_OS_PROBER=true\n"
                        found = True
                    wfd.write(line)
                if not found:
                    wfd.write("GRUB_DISABLE_OS_PROBER=true\n")


        to_install += self.mark_upgrades()

        self.remove_unwanted_drivers()

        self.remove_ricoh_mmc()

        self.propagate_kernel_parameters()

        self.wake_network()

        install_misc.record_installed(to_install)
        install_misc.record_removed(to_remove)

        return InstallPlugin.install(self, target, progress, *args, **kwargs)
