ACC SHELL

Path : /usr/share/YaST2/modules/
File Upload :
Current File : //usr/share/YaST2/modules/BootGRUB.ycp

/**
 * File:
 *      modules/BootGRUB.ycp
 *
 * Module:
 *      Bootloader installation and configuration
 *
 * Summary:
 *      Module containing specific functions for GRUB configuration
 *      and installation
 *
 * Authors:
 *      Jiri Srain <jsrain@suse.cz>
 *      Joachim Plack <jplack@suse.de>
 *      Olaf Dabrunz <od@suse.de>
 *      Philipp Thomas <pth@suse.de>
 *
 * $Id: BootGRUB.ycp 58279 2009-08-04 16:01:51Z juhliarik $
 *
 */

{
module "BootGRUB";

textdomain "bootloader";

import "Arch";
import "BootCommon";
import "BootStorage";
import "Kernel";
import "Mode";
import "Stage";
import "Storage";
import "StorageDevices";
import "Pkg";
import "HTML";

// private variables

/**
  * Shall proposal merge menus?
  */
global symbol merge_level = `main;

/**
 * The variable indicate if client bootloader_preupdate
 * successful update device map
 * If success true else false 
 */

global boolean update_device_map_done = false;

// variables for temporary data

/**
  * Disks order for ordering widget purproses
  */
global list<string> disks_order = nil;

// includes

include "bootloader/grub/misc.ycp";
include "bootloader/routines/popups.ycp";
include "bootloader/grub/dialogs.ycp";


// end of mandatory functions
//----------------------------------------------------------------------------


// wrapper function to adjust to new grub name sceme
map<string,any> CreateLinuxSection (string title) {
    return  BootCommon::CreateLinuxSection (title);
}


/**
 * Check for additional kernels which could go to the proposed settings
 * @return a list of kernels to propose
 */
global list<map<string,string> > CheckAdditionalKernels () {
    list<string> files = (list<string>)SCR::Read (.target.dir, "/boot");
    string binary = Kernel::GetBinary ();
    list<string> kernels = filter (string k, files, {
	return substring (k, 0, size (binary)) == binary;
    });
    kernels = filter (string k, kernels, {
	return k != ""
	    && k != binary
	    && regexpmatch (k, sformat ("^%1-.+$", binary));
    });
    if (contains (kernels, binary))
    {
	string defaultv = (string)SCR::Read (.target.symlink,
	    sformat ("/boot/%1", binary));
	defaultv = ""; // FIXME remove this line
	kernels = filter (string k, kernels, {
	    return k != defaultv;
	});
    }
    list<map<string,string> > ret = maplist (string k, kernels, {
	string version = regexpsub (k, sformat ("^%1-(.+)$", binary), "\\1");
	map<string,string> info = $[
	    "version" : "Kernel-" + version,
	    "image" : sformat ("/boot/%1", k)
	];
	if (contains (files, sformat ("initrd-%1", version)))
	{
	    info["initrd"] = sformat ("/boot/initrd-%1", version);
	}
	return info;
    });

    y2milestone ("Additional sections to propose: %1", ret);
    return ret;
}


/**
  * Propose sections to bootloader menu
  * modifies internal structures
  */
  //FIXME really needs refactor, it is really huge function
global define void CreateSections () {
    y2debug ("Creating GRUB sections from scratch");
    list<map<string,any> > out = [];

    out = add (out, CreateLinuxSection ("linux"));
    if (BootCommon::XenPresent ())
        out = add (out, CreateLinuxSection ("xen"));

    list<string> others_ignore = [];

    string linux_fallback_text = "Linux other";
    integer fallback_num = 1;

    // get a list of bootable (= "aa55" at the end of block 0) primary
    // partitions classified by partition type:
    //
    // "/dev/sda3 Linux other 1"
    // "/dev/sda4 Linux other 2"
    // "/dev/sda2 dos 1"
    // "/dev/sda3 windows 1"
    // "/dev/sda3 OS/2 Boot Manager 1"
    // "/dev/sda1 Vendor diagnostic"
    // ...
    list<string> others = (list<string>)Storage::GetForeignPrimary();
    y2milestone ("Other primaries: %1", others);

    // get list of all Linux Partitions on all real disks
    list<map> other_l = (list<map>)Storage::GetOtherLinuxPartitions();
    y2milestone ("Other linux parts: %1", other_l);

    list<string> destroyed_partitions
        = BootStorage::getPartitionList (`destroyed, "grub");

    string tmpdir = (string)SCR::Read (.target.tmpdir) + "/bldetect/";

    // load additional modules for filesystems used by other linux partitions,
    // if needed
    if (merge_level != `none && other_l != nil && size (other_l) > 0
	&& 0 == SCR::Execute (.target.bash,
	   sformat ("test -d %1 || /bin/mkdir %1", tmpdir))
	)
    {
        y2milestone ("Detecting other Linux parts");
	list filesystems = maplist (map p, other_l,
				    ``(p["used_fs"]:(any)""));
	filesystems = toset (filter (any f, filesystems, ``(f != "")));
	filesystems = filter (any f, filesystems, ``(f != `ext2));
	y2debug ("Have to modprobe %1", filesystems);
	foreach (any f, filesystems, {
	    map fsmods = $[
		`ext2 : "",
		`ext3 : "ext3",
		`reiser : "reiserfs",
		`xfs : "xfs",
		`jfs : "jfs"
	    ];
	    string modname = fsmods[f]:"";
	    y2debug ("Module name is %1", modname);
	    if (modname != "")
	    {
		integer r = (integer)SCR::Execute (.target.bash,
		    sformat ("/sbin/modprobe %1", modname));
		y2debug ("result of loading %1 is %2", modname, r);
	    }
	});

	foreach (map o, other_l, {

	    // Here is the general logic:
	    //
	    // not mountable                                                        =>  no entry
	    //     mountable        bootable        has_menu_lst        name_found  =>  chainloader ("openSUSE 10.2 (/dev/sda2)")
	    //     mountable        bootable                        not name_found  =>  chainloader ("Linux other 1 (/dev/sda2)")
	    //     mountable    not bootable        has_menu_lst        name_found  =>  configfile  ("openSUSE 10.2 (/dev/sda2)")
	    //     mountable    not bootable        has_menu_lst    not name_found  =>  configfile  ("Linux other 1 (/dev/sda2)")
	    //     mountable    not bootable    not has_menu_lst                    =>  no entry

	    boolean bootable = false;
	    boolean has_menu_lst = false;
	    string menu_lst = "";
	    boolean name_found = false;
	    string new_sect_name_prefix = "";

	    BootCommon::InitializeLibrary (false, "grub");
	    string dev = o["device"]:"";
	    if (dev != "" && 0 == SCR::Execute (.target.bash,
		    sformat ("/bin/mount %1 %2", dev, tmpdir))
		)
	    {
		// mountable: true

	        y2milestone ("Mounted %1", dev);
		list<string> filenames = [];
		foreach (string fn, [
// not needed since there is a symlink in /boot directory
// named boot pointing to the /boot directory
// this caused bug #23346 - the file was found twice
//			tmpdir + "grub/menu.lst",
			tmpdir + "boot/grub/menu.lst"
		    ], {
			if (-1 != (integer)SCR::Read (.target.size, fn))
			    filenames = add (filenames, fn);
		});
		y2milestone ("Found files %1", filenames);

		// bootable: ?
		bootable = BootCommon::IsPartitionBootable(dev);

		// has_menu_lst: ?
		// name_found: ?
		//
		// look for a readable menu.lst and try to extract a section
		// name from a qualifying section
		foreach (string f, filenames, {
		    if (name_found)
			return;

		    y2debug ("Checking file %1", f);
		    string fc = (string)SCR::Read (.target.string, f);
		    if (fc == nil)
			return;

		    has_menu_lst = true;
		    menu_lst = substring(f, size(tmpdir));
		    if ( substring(menu_lst, 0, 1) != "/" )
			menu_lst = "/" + menu_lst;

		    string dm = (string)SCR::Read (.target.string,
			regexpsub (f, "(.*)menu.lst$",
				   "\\1device.map"));
		    y2debug ("Device map file name: %1",
			     regexpsub (f, "(.*)menu.lst$",
					"\\1device.map"));
		    y2debug ("Device map contents: %1", dm);

		    // pass the menu.lst and device.map to the parser
		    map<string,string> files = $[
						 "/boot/grub/menu.lst" : fc,
						 ];
		    if (dm == nil)
		        return;

		    files["/boot/grub/device.map"] = dm;

		    BootCommon::InitializeLibrary (false, "grub");
		    BootCommon::SetFilesContents (files);
		    list<map<string,any> > sects
		        = BootCommon::GetSections ();
		    y2debug ("Found sections %1", sects);

		    // bnc #448010 grub doesn't add another installed Linux in installation
		    map<string,string> globs = BootCommon::GetGlobal ();
		    string default_sec_name = globs["default"]:"";

		    // look only for "default" == "initial" entries, not all entries
		    sects = filter (map<string,any> s, sects, {
			return ((s["initial"]:nil != nil) || (s["name"]:nil == default_sec_name));
		    });

		    // now find the name of the first non-broken "initial" section and get its name
		    foreach (map<string,any> s, sects, ``{
			if (name_found)
			    return;

			boolean _use = true;
			// this is a heuristic: if the mounted partition or the
			// root partition referenced in the section is "new,
			// deleted or formatted", then do not use the section
			list<string> devs = [dev];
			string _d = s["root"]:"";
			if (_d != nil && _d != "")
			    devs = add (devs, _d);
			devs = (list<string>)filter (string d, devs,
				``(d != ""
				   && d != nil
				   && d != "/dev/null" && d != "false"));
			devs = toset (devs);
			devs = maplist (string d, devs, {
			    return BootCommon::UpdateDevice (d);
			});
			foreach (string _d, devs, {
			    if (contains (destroyed_partitions, _d))
			        _use = false;
			});

			if (_use)
			{
			    // no need to translate here...
			    new_sect_name_prefix = s["name"]:"";
			    name_found = true;
			}
		    });
		});

		// now evaluate the collected information:
		//
		//     mountable        bootable        has_menu_lst        name_found  =>  chainloader ("openSUSE 10.2 (/dev/sda2)")
		//     mountable        bootable                        not name_found  =>  chainloader ("Linux other 1 (/dev/sda2)")
		//     mountable    not bootable        has_menu_lst        name_found  =>  configfile  ("openSUSE 10.2 (/dev/sda2)")
		//     mountable    not bootable        has_menu_lst    not name_found  =>  configfile  ("Linux other 1 (/dev/sda2)")
		//     mountable    not bootable    not has_menu_lst                    =>  no entry	FIXME: should this be  handled with a chainloader entry anyway, in case it becomes bootable later?

		if (bootable || has_menu_lst)
		{
		    // set up the new section entry
		    map<string,any> new_sect = $[
			    "root" : BootStorage::Dev2MountByDev(dev),
			    "__changed" : true,
			    "__auto" : true,
			    "__devs" : [dev]
		    ];

		    if (bootable)
		    {
			//     mountable        bootable                                        =>  chainloader (label: to be decided)
			new_sect["noverifyroot"] = "true";
			new_sect["chainloader"] = BootStorage::Dev2MountByDev(dev);
			new_sect["blockoffset"] = "1";
			new_sect["type"] = "other";
		    }
		    else
		    {
			//     mountable    not bootable                                        =>  configfile  (label: to be decided)
			//new_sect["configfile"] = sformat("(%1)%2", dev, menu_lst);
			new_sect["configfile"] = menu_lst;
			new_sect["type"] = "menu";
		    }

		    if (name_found)
		    {
			//     mountable                                            name_found  =>              ("openSUSE 10.2 (/dev/sda2)")
			new_sect["name"] = sformat(" %1 (%2)", new_sect_name_prefix, dev);
		    }
		    else
		    {
			//     mountable                                        not name_found  =>              ("Linux other 1 (/dev/sda2)")
			new_sect["name"] = sformat("%1 %2 (%3)", linux_fallback_text, fallback_num, dev);
			fallback_num = fallback_num+1;
		    }

		    new_sect["original_name"] = new_sect["name"]:"";

		    out = add (out, new_sect);
		    // ignore this partition when going through the list of
		    // "other", non-linux partitions in the next loop below
		    others_ignore = add (others_ignore, dev);
		}


		SCR::Execute (.target.bash, sformat (
			"/bin/umount %1", dev));
	    }
	});
	SCR::Execute (.target.bash, sformat ("/bin/rmdir %1", tmpdir));
    }



    // Go through a list of "bootable" (aa55) primary partitions that may be
    // "foreign", check that it is not one of our current boot partitions and
    // if this is not a special Thinkpad or "Vendor diagnostics" partition, add
    // a chainloader entry for it.
    if (others != nil && (size (others) > 0)) {
	foreach (string o, others, {
	    list parts = splitstring (o, " ");
	    while (parts[0]:" " == "")
	      parts = remove (parts, 0);
	    string dev = parts[0]:"";
	    y2milestone ("Checking other partition %1", dev);
	    if (! contains (others_ignore, dev)) {
	        parts = remove (parts, 0);
		string label = mergestring ((list<string>)parts, " ");

		// don't add rewritten location (#19990)
		// do not check for a bootable partition boot record: partition
		// may become bootable later
		if (dev != "" && label != ""
		    && !contains(BootCommon::GetBootloaderDevices(), dev)
		    && (
			BootCommon::AddFirmwareToBootloader (
							     BootCommon::mbrDisk)
			|| (label != "Vendor diagnostics"			    // <- should probably be '&&' => not Thinkpad MBR AND not "Vendor diagnostics" partition type
			    && label != "Vendor diagnostic")
			)
		    )
		{
		    map<string,any> m = $[
			"name" : BootCommon::translateSectionTitle (label),
			"type" : "other",
			"original_name" : label,
			"chainloader" : BootStorage::Dev2MountByDev(dev),
			"__changed" : true,
			"__auto" : true,
			"__devs" : [dev],
		    ];
		    out = add (out, m);
		}
	    }
	});
    }


    if (grub_InstallingToFloppy ())
    {
        out = add (out, $[
		"name" : BootCommon::translateSectionTitle("hard disk"),
		"original_name" : "hard_disk",
		"type" : "other",
		"chainloader" : BootStorage::Dev2MountByDev(BootCommon::mbrDisk),
		"__changed" : true,
		"__auto" : true,
		"__devs" : [],
        ]);
    }
    else if (StorageDevices::FloppyPresent())
    {
        out = add (out, $[
		"name" : BootCommon::translateSectionTitle("floppy"),
		"original_name" : "floppy",
		"type" : "other",
		"chainloader" : "/dev/fd0",
		"__changed" : true,
		"__auto" : true,
		"__devs" : [],
	    ]);
    }
    out = add (out, CreateLinuxSection ("failsafe"));
    out = add (out, CreateLinuxSection ("memtest86"));

    if (Mode::normal ())
    {
        foreach (map<string,string> additional,
		 CheckAdditionalKernels (),
	{
	    string type = additional["version"]:"";
	    type = sformat ("%1", type);
	    map<string,any> s = CreateLinuxSection (type);
	    s["image"] = additional["image"]:"";
	    if (haskey (additional, "initrd"))
	        s["initrd"] = additional["initrd"]:"";
	    s["original_name"] = "linux";
	    out = add (out, s);
	});
    }
    out = filter (map<string,any> s, out, {return s != $[] && s != nil;});
    BootCommon::sections = out;
}

/**
 * Propose global options of bootloader
 */
global map<string,string> StandardGlobals () {
    return $[
	"activate": "true",
	"default" : BootCommon::sections[0, "name"]:"",
	"timeout" : "8",
	"gfxmenu" : "/boot/message",
    ];
}


// general functions

/**
 * Read settings from disk
 * @param reread boolean true to force reread settings from system
 * @param avoid_reading_device_map do not read new device map from file, use
 * internal data
 * @return boolean true on success
 */
global boolean Read (boolean reread, boolean avoid_reading_device_map) {
    BootCommon::InitializeLibrary (reread, "grub");
    if (reread) {
	BootCommon::ReadFiles (avoid_reading_device_map);
    }
    grub_DetectDisks ();
    boolean ret = BootCommon::Read (false, avoid_reading_device_map);
    // refresh device map if not read
    if (BootStorage::device_mapping == nil
	|| size (BootStorage::device_mapping) == 0)
    {
	BootStorage::ProposeDeviceMap ();
    }
    // FATE#305403: Bootloader beep configuration
    // read status of acoustic signals
    if (Mode::normal())
    {
	GfxMenu::ReadStatusAcousticSignal();
	string md_value = BootStorage::addMDSettingsToGlobals();
	string pB_md_value = BootCommon::globals["boot_md_mbr"]:"";
	if (md_value != pB_md_value)
	{
	    if (pB_md_value != "")
	    {
		list <string> disks = splitstring(pB_md_value,",");
		disks = filter(string v, disks, {return (v != "");});
		if (size(disks) == 2)
		{
		    BootCommon::enable_md_array_redundancy = true;
		    md_value = "";
		}
		y2milestone("disks from md array (perl Bootloader): %1", disks);
	    }
	    if (md_value != "")
	    {
		BootCommon::enable_md_array_redundancy = false;
		BootCommon::globals["boot_md_mbr"] = md_value;
		y2milestone("Add md array to globals: %1", BootCommon::globals);
	    }
	}
    }

    return ret;
}


/**
  * Reset bootloader settings
  * @param init boolean true to repropose also device map
  */
global define void Reset (boolean init) {
    if (Mode::autoinst ())
        return;
    BootCommon::Reset (init);
}

/**
 * Propose bootloader settings
 */
global define void Propose () {
	y2debug ("Started propose: Glob: %1, Sec: %2",
	    BootCommon::globals, BootCommon::sections);

	// if NOT was_proposed (i.e. blPropose() has not been called yet), then
	// - set up BootPartitionDevice, RootPartitionDevice
	// - if empty, set up mbrDisk
	// - if loader_device is empty or the device is not a boot device, go
	//   to grub_ConfigureLocation() and
	//	- propose
	//	    - select one loader device in the boot_* keys of the globals map
	//	    - activate (when needed, but try to be nice to Windows)
	//	- do not touch these, except when /boot partition is selected_location:
	//	    - activate_changed (set to true)
	//	    - repl_mbr (set to true when we need to update existing GRUB, lilo MBRs,
	//			or when it looks like there is no code in the MBR at all,
	//			but NOT if this is a "Generic" (DOS) MBR, some unknown code
	//			or a Thinkpad MBR)
	//
	// always propose:
	//  - device_mapping (from "bios_id"s delivered by Storage, then let
	//                    devices with unknown "bios_id"s drop into the
	//                    gaps of this mapping or append at the end)
	//
	// if '/' and '/boot' were changed and selected_location is set and not
	// "custom", ask user with popup whether it is OK to change the
	// location and change it (with grub_DetectDisks() and grub_ConfigureLocation()
	// (see above))
	grub_LocationProposal ();

	// Note that the Propose() function is called every time before
	// Summary() is called.

	if (BootCommon::sections == nil || size (BootCommon::sections) == 0)
	{
	    CreateSections ();
	    BootCommon::kernelCmdLine = Kernel::GetCmdLine ();
	}
	else
	{
	    if (Mode::autoinst ())
	    {
		// TODO whatever will be needed
		y2debug ("nothing to do in AI mode if sections exist");
	    }
	    else
		BootCommon::FixSections (BootGRUB::CreateSections);
	}
	if (BootCommon::globals == nil || size (BootCommon::globals) == 0)
	{
	    BootCommon::globals = StandardGlobals();
	}
	else
	{
            if (Mode::autoinst ())
            {
                // TODO whatever will be needed
		y2debug ("nothing to do in AI mode if globals are defined");
            }
	    // decided to merge in default values for missing keys, EVEN in AI mode (!)
	    // this is primarily done to allow for the LocationProposal() to
	    // run and set keys in globals before we check them for existing
	    // keys here; but rather than checking for an empty globals map
	    // before LocationProposal() and using the result here, we figured
	    // that augmenting the globals is not such a bad idea even for the
	    // AI case...
	    y2milestone ("merging defaults to missing keys in globals");
	    // Merge default globals where not yet set
	    BootCommon::globals = (map<string, string>) union(
		StandardGlobals(),
		BootCommon::globals
	    );
	    // this currently does nothing more than fixing the "default" key,
	    // if that points to a section that does not exist anymore
	    BootCommon::FixGlobals ();
	}
	
	// check if windows is on second disk and add remap if it is necessary
        // FATE #301994: Correct device mapping in case windows is installed on the second HD
        BootCommon::sections = checkWindowsSection(BootCommon::sections);

	if (Mode::installation())
		BootCommon::UpdateProposalFromClient();

	BootCommon::isTrustedGrub ();
	y2milestone ("Proposed sections: %1", BootCommon::sections);
	y2milestone ("Proposed globals: %1", BootCommon::globals);
    }

/**
 * Save all bootloader configuration files to the cache of the PlugLib
 * PlugLib must be initialized properly !!!
 * @param clean boolean true if settings should be cleaned up (checking their
 *  correctness, supposing all files are on the disk
 * @param init boolean true to init the library
 * @param flush boolean true to flush settings to the disk
 * @return boolean true if success
 */
global boolean Save (boolean clean, boolean init, boolean flush) 
{
    // reduce device map to 8 devices
    // FATE #303548 - Grub: limit device.map to devices detected by BIOS Int 13
    ReduceDeviceMapTo8();

    // now really save the settings
    boolean ret = BootCommon::Save (clean, init, flush);
    //importMetaData();

    return ret;
}

/** FATE#303643 Enable one-click changes in bootloader proposal
 *
 *
 */
string urlLocationSummary()
{
    y2milestone("Prepare url summary for GRUB");
    string ret = "";
    string line = "";
    list<string> locations = [];
    line = "<ul>\n<li>";
    if (BootCommon::globals["boot_mbr"]:"" == "true")
	line = line +
	    _("Boot from MBR is enabled (<a href=\"disable_boot_mbr\">disable</a>") + ")</li>\n";
    else
	line = line +
	    _("Boot from MBR is disabled (<a href=\"enable_boot_mbr\">enable</a>") + ")</li>\n";
    locations = add(locations, line);
    line ="<li>";

    boolean set_boot_boot = false;
    if ((BootCommon::globals["boot_boot"]:"" == "true") && 
	(BootStorage::BootPartitionDevice != BootStorage::RootPartitionDevice))
    {
	set_boot_boot = true;
	line = line +
	    _("Boot from /boot partition is enabled (<a href=\"disable_boot_boot\">disable</a>") + ")</li></ul>";
    }
    else if ((BootCommon::globals["boot_boot"]:"" == "false") && 
	(BootStorage::BootPartitionDevice != BootStorage::RootPartitionDevice))
    {
	set_boot_boot = true;
	line = line +
	    _("Boot from /boot partition is disabled (<a href=\"enable_boot_boot\">enable</a>") + ")</li></ul>";
    }
    if (line != "<li>")
    {	
	locations = add(locations, line);
	line ="<li>";
    }
    if ((BootCommon::globals["boot_root"]:"" == "true") && (!set_boot_boot))
	line = line +
	    _("Boot from \"/\" partition is enabled (<a href=\"disable_boot_root\">disable</a>") + ")</li></ul>";
    else if ((BootCommon::globals["boot_root"]:"" == "false") && (!set_boot_boot))
	line = line +
	    _("Boot from \"/\" partition is disabled (<a href=\"enable_boot_root\">enable</a>") + ")</li></ul>";
    if (!set_boot_boot)
	locations = add(locations, line);

    if (size(locations) > 0) {
           // FIXME: should we translate all devices to names and add MBR suffixes?
           ret = sformat (_("Change Location: %1"), mergestring (locations, " "));
    }

    return ret;
}

/**
 * Display bootloader summary
 * @return a list of summary lines
 */
global define list<string> Summary () {
    list<string> ret = [];
    string lt = BootCommon::getLoaderType (false);
    string ln = BootCommon::getLoaderName (lt, `summary);

    if (lt == "none") {
        ret = [ HTML::Colorize (ln, "red") ];
	}

    // summary text, %1 is bootloader name (eg. LILO)
    ret = add (ret, sformat (_("Boot Loader Type: %1"), ln));

    // summary text, location is location description (eg. /dev/hda)
    list<string> locations = [];
    string line = "";
    

	if (BootCommon::globals["boot_boot"]:"" == "true")
            locations = add(locations, BootStorage::BootPartitionDevice + _(" (\"/boot\")"));
	if (BootCommon::globals["boot_extended"]:"" == "true")
            locations = add(locations, BootStorage::ExtendedPartitionDevice + _(" (extended)"));
    	if (BootCommon::globals["boot_root"]:"" == "true")
            locations = add(locations, BootStorage::RootPartitionDevice + _(" (\"/\")"));
    	if (BootCommon::globals["boot_mbr"]:"" == "true")
            locations = add(locations, BootCommon::mbrDisk + _(" (MBR)"));
    	if (haskey (BootCommon::globals, "boot_custom"))
            locations = add(locations, BootCommon::globals["boot_custom"]:"");
	if (size(locations) > 0) {
           // FIXME: should we translate all devices to names and add MBR suffixes?
           ret = add (ret, sformat (_("Status Location: %1"), mergestring (locations, ", ")));
        }
    // it is necessary different summary for autoyast and installation
    // other mode than autoyast on running system
    if (!Mode::config())
    {
	//ret = add(ret, _("Change Location:"));
	ret = add(ret, urlLocationSummary());
    }

    // summary text. %1 is list of bootloader sections
    list<string> sects = [];
    foreach (map<string,any> s, BootCommon::sections, {
        string title = s["name"]:"";
	// section name "suffix" for default section
	string def = title == BootCommon::globals["default"]:"" ? _(" (default)") : "";
	sects = add (sects, String::EscapeTags (sformat ("+ %1%2", title, def)));
    });

    ret = add (ret, sformat (_("Sections:<br>%1"),
	       mergestring (sects, "<br>")));

    if (size(locations) == 0) {
        // summary text
        ret = add (ret, _("Do not install boot loader; just create configuration files"));
    }

    string order_sum = BootCommon::DiskOrderSummary ();
    if (order_sum != nil)
	ret = add (ret, order_sum);
    return ret;
}


/**
  * Update read settings to new version of configuration files
  */
global define void Update () {
    // update device map would be done in bootloader_preupdate
    // run update device only if it was not called or if update device
    // failed in bootloader_preupdate
    if (!update_device_map_done)
        BootCommon::UpdateDeviceMap ();

    // During update, for libata device name migration ("/dev/hda1" ->
    // "/dev/sda1") and somesuch, we need to re-read and parse the rest of the
    // configuration file contents after internally updating the device map in
    // perl-Bootloader. This way, the device names are consistent with the
    // partitioning information we have set up in perl-Bootloader with
    // SetDiskInfo(), and device names in other config files can be translated
    // to Unix device names (#328448, this hits sections that are not
    // (re-)created by yast-Bootloader or later by perl-Bootloader anyway).
    BootCommon::SetDeviceMap (BootStorage::device_mapping);
    Read (true, true);

    BootCommon::UpdateSections ();
    BootCommon::UpdateGlobals ();
}


/**
  * Write bootloader settings to disk
  * @return boolean true on success
  */
global define boolean Write () ``{

    boolean ret = BootCommon::UpdateBootloader ();
    if (BootCommon::location_changed || BootCommon::InstallingToFloppy ()) {
	// bnc #461613 - Unable to boot after making changes to boot loader
        // bnc #357290 - module rewrites grub generic code when leaving with no changes, which may corrupt grub
	grub_updateMBR ();
        if (BootCommon::InstallingToFloppy ()) {
	    if (! saveToFLoppyPopup ()) {
	        y2error ("Preparing floppy disk failed.");
		ret = false;
	    }
        }
	
        boolean grub_ret = BootCommon::InitializeBootloader ();
	if (grub_ret == nil)
	    grub_ret = false;

        y2milestone ("GRUB return value: %1", grub_ret);
        if (BootCommon::InstallingToFloppy ()) {
	    BootCommon::updateTimeoutPopupForFloppy
	      (BootCommon::getLoaderName ("grub", `summary));
	}
        ret = ret && grub_ret;
        ret = ret && BootCommon::PostUpdateMBR ();
    }
    return ret;
}


global map<string,symbol()> Dialogs () {
    return $[
	"installation"	: i386InstallDetailsDialog,
	"loader"	: i386LoaderDetailsDialog,
    ];
}

/**
  * Boot passed section once on next reboot.
  * @param section string section to boot
  * @return boolean true on success
  */
global define boolean FlagOnetimeBoot (string section)
{
    map result = (map)SCR::Execute (.target.bash_output, sformat (
				    "/usr/sbin/grubonce \"%1\"", BootCommon::Section2Index(section)));
    y2milestone ("grubonce returned %1", result);
    return (result["exit"]:-1 == 0);
}

list <string> grub_section_types()
{
	return ["image", "xen", "menu", "other"];
}


/**
  * Return map of provided functions
  * @return a map of functions (eg. $["write"::Write])
  */
global map<string, any> GetFunctions () {
    return $[
        "read"			: Read,
        "reset"			: Reset,
        "propose"		: Propose,
        "save"			: Save,
        "summary"		: Summary,
        "update"		: Update,
        "write"			: Write,
	"widgets"		: grubWidgets,
        "dialogs"		: Dialogs,
        "section_types"		: grub_section_types,
	"flagonetimeboot"	: FlagOnetimeBoot,
    ];
}

/**
  * Initializer of GRUB bootloader
  */
global define void Initializer () {
    y2milestone ("Called GRUB initializer");
    BootCommon::current_bootloader_attribs = $[
        "alias_keys" : [],
        "propose" : true,
        "read" : true,
        "scratch" : true,
        "additional_entries" : [`item (`id (`propose_deep),
    	// menubutton item, keep as short as possible
    	_("Propose and &Merge with Existing GRUB Menus"))],
        "restore_mbr" : true,
        "key_only_once" : false,
        "bootloader_on_disk" : true,
    ];

    BootCommon::InitializeLibrary (false, "grub");
}

/**
  * Constructor
  */
global define void BootGRUB () {
    BootCommon::bootloader_attribs["grub"] = $[
	"required_packages" : ["grub"],
	"loader_name" : "GRUB",
	"initializer" : BootGRUB::Initializer,
    ];
}


/** bnc#494630 GRUB configuration in installation workflow fail with more than 8 disks on software raid
 * Return all disks for checking in device map
 *
 * @param string boot disk
 * @return list<string> disk devices
 */

list <string> ReturnAllDisks(string boot_disk)
{
    list <string> ret = [];
    map tm = Storage::GetTargetMap ();
    map b_disk = tm[boot_disk]:$[];
    if (b_disk["type"]:nil == `CT_MD)
    {
       string boot_partition = BootCommon::getBootPartition();
       list b_disk_partitions = b_disk["partitions"]:[];
       foreach (map p, (list<map>)b_disk_partitions,
       {
           if (p["device"]:"" == boot_partition)
           {
               if ((size(p["devices"]:[]) > 0) && (p["type"]:nil == `sw_raid))
               {
                   foreach(string dev, p["devices"]:[],
                   {
                       map p_dev = Storage::GetDiskPartition (dev);
                       string disk_dev = p_dev["disk"]:"";
                       if (disk_dev != "")
                           ret = add(ret, disk_dev);
                       else
                           y2error("Real disk was not found for device: %1", dev);
                   });
               } else {
                   if (p["type"]:nil == `sw_raid)
                       y2error("soft raid partition: %1 doesn't include any devices: %2",
                               boot_partition, p["devices"]:[]);
                   else
                       y2error("Disk is not soft-raid %1", b_disk);
                   ret = add(ret, boot_disk);

               }
           }
       });
    } else {
       y2milestone("Boot disk is not on MD-RAID");
       ret = add(ret, boot_disk);
    }
    y2milestone("Devices for checking if they are in device map: %1", ret);
    return ret;
}

/** bnc#494630 GRUB configuration in installation workflow fail with more than 8 disks on software raid
 * Function check if boot disk is in device map
 *
 * @return boolean true if boot device is not in device map
 */

global boolean CheckDeviceMap()
{
    // FATE #303548 - Grub: limit device.map to devices detected by BIOS
    boolean ret = false;
    string boot_disk = BootCommon::getBootDisk();
    list <string> disks = ReturnAllDisks(boot_disk);
    if (size(disks) > 0)
    {
       foreach(string disk, disks,
       {
           ret = ret || checkBootDeviceInDeviceMap(disk, BootStorage::Dev2MountByDev(disk));
       });

    }
    return ret;
}

} // EOF

/*
 * Local variables:
 *     mode: ycp
 *     mode: font-lock
 *     mode: auto-fill
 *     indent-level: 4
 *     fill-column: 78
 * End:
 */

ACC SHELL 2018