ACC SHELL

Path : /usr/share/YaST2/include/bootloader/grub/
File Upload :
Current File : //usr/share/YaST2/include/bootloader/grub/misc.ycp

/**
 * File:
 *      include/bootloader/grub/misc.ycp
 *
 * Module:
 *      Bootloader installation and configuration
 *
 * Summary:
 *      Miscelaneous functions for configuring and installing GRUB bootloader
 *
 * Authors:
 *      Jiri Srain <jsrain@suse.cz>
 *      Olaf Dabrunz <od@suse.de>
 *
 * $Id: misc.ycp 61449 2010-03-23 09:56:57Z juhliarik $
 *
 */

{
    textdomain "bootloader";
    import "Storage";
    import "StorageDevices";
    import "Mode";
    import "BootCommon";
    import "PackageSystem";
    import "Map";


// --------------------------------------------------------------
// --------------------------------------------------------------
// updateMBR and related stuff, plus InstallingToFloppy() (taken from
// routines/misc.ycp)

/**
 * Check if installation to floppy is performed
 * @return true if installing bootloader to floppy
 */
global boolean grub_InstallingToFloppy () {
    boolean ret = false;
    // there is no boot_floppy flag, the installation to floppy devices needs
    // to be specified in the boot_custom flag
    // (FIXME: which is freely editable, as soon as the generic 'selectdevice'
    // widget allows this; also need perl-Bootloader to put the floppy device
    // in the list of the boot_custom widget)
    if (BootCommon::globals["boot_custom"]:nil == nil)
	ret = false;
    else if (BootCommon::globals["boot_custom"]:nil ==
	     StorageDevices::FloppyDevice())
	ret = true;
    else if (contains (BootStorage::getFloppyDevices (),
		       BootCommon::globals["boot_custom"]:nil))
	ret = true;
    y2milestone ("Installing to floppy: %1", ret);
    return ret;
}

    /**
      * Given a device name to which we install the bootloader (loader_device),
      * get the name of the partition which should be activated.
      * Also return the device file name of the disk device that corresponds to
      * loader_device (i.e. where the corresponding MBR can be found).
      * @param loader_device string the device to install bootloader to
      * @return a map $[ "dev" : string, "mbr": string, "num": any]
      *  containing device (eg. "/dev/hda4"), disk (eg. "/dev/hda") and
      *  partition number (eg. 4)
      */
//      * @param boot_partition string the partition holding /boot subtree
//    map<string,any> getPartitionToActivate (string boot_partition,
//	string loader_device)
    map<string,any> grub_getPartitionToActivate (string loader_device)
    {
	map p_dev = Storage::GetDiskPartition (loader_device);
	integer num = BootCommon::myToInteger( p_dev["nr"]:nil );
	string mbr_dev = p_dev["disk"]:"";

	// If loader_device is /dev/md* (which means bootloader is installed to
	// /dev/md*), return the info map for the first device in BIOS ID order
	// that underlies the soft-RAID and that has a BIOS ID (devices without
	// BIOS ID are excluded).
	// If no device is found in this way, return the info map for the
	// soft-RAID device ("/dev/md", "/dev/md[0-9]*").
	// FIXME: use ::storage to detect md devices, not by name!
	// FIXME: return info for ALL underlying soft-RAID devices here, so
	// that all MBRs can be backed-up and all partitions that need to be
	// activated can be activated. This requires a map<map<...>> return
	// value, and code on the caller side that evaluates this.
	if (substring (loader_device, 0, 7) == "/dev/md")
	{
	    map<string,integer> md = BootCommon::Md2Partitions (loader_device);
	    integer min = 256; // max. is 255; 256 means "no bios_id found"
	    string device = "";
	    foreach (string d, integer id, md, {
		if (id < min)
		{
		    min = id;
		    device = d;
		}
	    });
	    if (device != "")
	    {
		map p_dev = Storage::GetDiskPartition (device);
		num = BootCommon::myToInteger( p_dev["nr"]:nil );
		mbr_dev = p_dev["disk"]:"";
	    }
	}
	// If loader_device is a disk device ("/dev/sda"), that means that we
	// install the bootloader to the MBR. In this case, activate /boot
	// partition.
	// (partial fix of #20637)
	// FIXME: necessity and purpose are unclear: if we install the
	// bootloader to the MBR, then why do we need to activate the /boot
	// partition? Stage1 of a GRUB has the first block of the stage2
	// hard-coded inside.
	// This code was added because a /boot partition on a /dev/cciss device
	// was not activated in bug #20637. Anyway, it probably never worked,
	// since the bootloader was not installed to the MBR in that bug (and
	// thus this code is not triggered).
	// The real problem may have been that Storage::GetDiskPartition() did
	// not know how to parse /dev/cciss/c0d0p1, so that the default case at
	// the beginning of this function did not set up correct values. These
	// days, Storage::GetDiskPartition() looks OK with /dev/cciss.
	// Deactivated this code, so that "/boot" does not get activated
	// unecessarily when GRUB stage1 is installed to the MBR anyway (this
	// would unecessarily have broken drive C: detection on older MS
	// operating systems).
//	else if (num == 0)
//	{
//	    p_dev = Storage::GetDiskPartition (boot_partition);
//	    num = BootCommon::myToInteger( p_dev["nr"]:nil );
//	    mbr_dev = p_dev["disk"]:"";
//
//	    if (size (BootCommon::Md2Partitions (boot_partition)) > 1)
//	    {
//		foreach (string k, integer v, BootCommon::Md2Partitions (boot_partition),{
//		    if (search (k, loader_device) == 0)
//		    {
//			p_dev = Storage::GetDiskPartition (k);
//			num = BootCommon::myToInteger( p_dev["nr"]:nil );
//			mbr_dev = p_dev["disk"]:"";
//		    }
//		});
//	    }
//	}

	// (bnc # 337742) - Unable to boot the openSUSE (32 and 64 bits) after installation
	// if loader_device is disk device activate BootStorage::BootPartitionDevice
	if (num == 0)
	{
	    y2milestone ("loader_device is disk device");
	    p_dev = Storage::GetDiskPartition (BootStorage::BootPartitionDevice);
	    num = BootCommon::myToInteger( p_dev["nr"]:nil );
        }

	if (num > 4)
	{
	    y2milestone ("Bootloader partition type is logical");
	    map tm = Storage::GetTargetMap ();
	    list<map> partitions = tm[mbr_dev, "partitions"]:[];
	    foreach (map p, partitions, ``{
		if (p["type"]:nil == `extended)
		{
		    num = p["nr"]:num;
		    y2milestone ("Using extended partition %1 instead",num);
		}
	    });
	}

	map<string,any> ret = $[
	    "num" : num,
	    "mbr" : mbr_dev,
	    "dev" : Storage::GetDeviceName (mbr_dev, num),
	];
	
	y2milestone("Partition for activating: %1", ret);
	return ret;
    }

    /**
     * Get a list of partitions to activate if user wants to activate
     * boot partition
     * @return a list of partitions to activate
     */
    list<map<string, any> > grub_getPartitionsToActivate () {
	map<string,integer> md = $[];
	list<list<string> > underlying_devs = [];
	list<string> devs = [];

	list<string> boot_devices = [];
	
	// bnc#494630 - add also boot partitions from soft-raids 
	string boot_device = BootCommon::getBootPartition();
	if (substring (boot_device, 0, 7) == "/dev/md")
	{
		boot_devices = add(boot_devices, boot_device);
		foreach (string dev, BootCommon::GetBootloaderDevices(), {
			boot_devices = add(boot_devices, dev);
		});
	} else {

		boot_devices = BootCommon::GetBootloaderDevices();
	}

	// get a list of all bootloader devices or their underlying soft-RAID
	// devices, if necessary
	underlying_devs = maplist (string dev, boot_devices, {
	    md = BootCommon::Md2Partitions(dev);
	    if ( size(md) > 0 ) {
		devs = maplist (string k, integer v, md, ``(k));
		return devs;
	    }
	    return [dev];
	});
	list<string> bootloader_base_devices = flatten(underlying_devs);

	if (size (bootloader_base_devices) == 0)
	{
	    bootloader_base_devices = BootCommon::GetBootloaderDevices();
	}
	list<map<string,any> > ret = maplist (string partition, bootloader_base_devices, {
	    return grub_getPartitionToActivate (partition);
	});
	return toset (ret);
    }

    /**
     * Get the list of MBR disks that should be rewritten by generic code
     * if user wants to do so
     * @return a list of device names to be rewritten
     */
    list<string> grub_getMbrsToRewrite () {
	list<string> ret = [BootCommon::mbrDisk];
	map<string,integer> md = $[];
	list<list<string> > underlying_devs = [];
	list<string> devs = [];
        list<string> boot_devices = [];

        // bnc#494630 - add also boot partitions from soft-raids
        string boot_device = BootCommon::getBootPartition();
        if (substring (boot_device, 0, 7) == "/dev/md")
        {
                boot_devices = add(boot_devices, boot_device);
                foreach (string dev, BootCommon::GetBootloaderDevices(), {
                        boot_devices = add(boot_devices, dev);
                });
        } else {

                boot_devices = BootCommon::GetBootloaderDevices();
        }

	// get a list of all bootloader devices or their underlying soft-RAID
	// devices, if necessary
	underlying_devs = maplist (string dev, boot_devices, {
	    md = BootCommon::Md2Partitions(dev);
	    if ( size(md) > 0 ) {
		devs = maplist (string k, integer v, md, ``(k));
		return devs;
	    }
	    return [dev];
	});
	list<string> bootloader_base_devices = flatten(underlying_devs);

	// find the MBRs on the same disks as the devices underlying the boot
	// devices; if for any of the "underlying" or "base" devices no device
	// for acessing the MBR can be determined, include mbrDisk in the list
	list<string> mbrs = maplist (string dev, bootloader_base_devices, {
	    dev = (grub_getPartitionToActivate (dev))["mbr"]:BootCommon::mbrDisk;
	    return dev;
	});
	// FIXME: the exact semantics of this check is unclear; but it seems OK
	// to keep this as a sanity check and a check for an empty list;
	// mbrDisk _should_ be included in mbrs; the exact cases for this need
	// to be found and documented though
	if (contains (mbrs, BootCommon::mbrDisk))
	{
	    ret = (list<string>)merge (ret, mbrs);
	}
	return toset (ret);
    }

    /**
      * Get last change time of file
      * @param filename string name of file
      * @return string last change date as YYYY-MM-DD-HH-MM-SS
      */
     string grub_getFileChangeDate (string filename) {
	map stat = (map) SCR::Read (.target.stat, filename);
	integer ctime = stat["ctime"]:0;
	string command = sformat (
	  "date --date='1970-01-01 00:00:00 %1 seconds' +\"%%Y-%%m-%%d-%%H-%%M-%%S\"",
	  ctime);
	map out = (map) SCR::Execute (.target.bash_output, command);
	string c_time = out["stdout"]:"";
	y2debug ("File %1: last change %2", filename, c_time);
	return c_time;
    }

    /**
      * Save current MBR to /boot/backup_mbr
      * Also save to /var/lib/YaST2/backup_boot_sectors/%device, if some
      * existing, rename it
      * @param device string name of device
      */
    void grub_saveMBR (string device) {
	string device_file = mergestring (splitstring (device, "/"), "_");
	string device_file_path = "/var/lib/YaST2/backup_boot_sectors/"
	    + device_file;
	string device_file_path_to_logs = "/var/log/YaST2/"
	    + device_file;
	SCR::Execute (.target.bash,
	    "test -d /var/lib/YaST2/backup_boot_sectors || mkdir /var/lib/YaST2/backup_boot_sectors");
	if (SCR::Read (.target.size, device_file_path) > 0)
	{
	    list<string> contents = (list<string>) SCR::Read (.target.dir, "/var/lib/YaST2/backup_boot_sectors");
	    contents = filter (string c, contents, ``(regexpmatch (c, sformat (
		"%1-.*-.*-.*-.*-.*-.*", device_file))));
	    contents = sort (contents);
	    integer index = 0;
	    integer siz = size (contents);
	    while (index + 10 < siz)
	    {
		SCR::Execute (.target.remove,
		    sformat ("/var/lib/YaST2/backup_boot_sectors/%1", contents[index]:""));
		index = index + 1;
	    }
	    string change_date = grub_getFileChangeDate (device_file_path);
	    SCR::Execute (.target.bash, sformat (
		"/bin/mv %1 %1-%2",
		device_file_path, change_date));
	}
	SCR::Execute (.target.bash, sformat (
	    "/bin/dd if=%1 of=%2 bs=512 count=1 2>&1",
	    device, device_file_path));
	// save MBR to yast2 log directory
	SCR::Execute (.target.bash, sformat (
	    "/bin/dd if=%1 of=%2 bs=512 count=1 2>&1",
	    device, device_file_path_to_logs));
	if (device == BootCommon::mbrDisk)
	{
	    SCR::Execute (.target.bash, sformat (
		"/bin/dd if=%1 of=%2 bs=512 count=1 2>&1",
		device, "/boot/backup_mbr"));

   	    // save thinkpad MBR 
	    if (BootCommon::ThinkPadMBR(device))
	    {
	       string device_file_path_thinkpad = device_file_path + "thinkpadMBR";
	       y2milestone("Backup thinkpad MBR");
	       SCR::Execute(.target.bash, sformat (
	       "cp %1 %2 2>&1", device_file_path, device_file_path_thinkpad));
	    }

	}
    }

    /**
      * Update contents of MBR (active partition and booting code)
      * FIXME move tis function to lilolike.ycp
      * @return boolean true on success
      */
    global define boolean grub_updateMBR () ``{
	// FIXME: do the real thing in perl_Bootloader
	boolean activate = ( BootCommon::globals["activate"]:"false" == "true" );
	boolean generic_mbr = ( BootCommon::globals["generic_mbr"]:"false" == "true" );

	y2milestone ("Updating disk system area, activate partition: %1, " +
		     "install generic boot code in MBR: %2", activate, generic_mbr);

	// After a proposal is done, Bootloader::Propose() always sets
	// backup_mbr to true. The default is false. No other parts of the code
	// currently change this flag.
	if (BootCommon::backup_mbr)
	{
	    y2milestone ("Doing MBR backup: MBR Disk: %1, loader devices: %2",
		BootCommon::mbrDisk, BootCommon::GetBootloaderDevices());
	    list<string> disks_to_rewrite = (list<string>)toset (merge (
		grub_getMbrsToRewrite (),
		merge([BootCommon::mbrDisk], BootCommon::GetBootloaderDevices())));
	    y2milestone ("Creating backup of boot sectors of %1",
		disks_to_rewrite);
	    foreach (string d, disks_to_rewrite, {
		grub_saveMBR (d);
	    });
	}
	boolean ret = true;
	// if the bootloader stage 1 is not installed in the MBR, but
	// ConfigureLocation() asked us to replace some problematic existing
	// MBR, then overwrite the boot code (only, not the partition list!) in
	// the MBR with generic (currently DOS?) bootloader stage1 code
	if (generic_mbr && ! contains(BootCommon::GetBootloaderDevices(), BootCommon::mbrDisk))
	{
	    if (! Stage::initial ())
	    {
		PackageSystem::Install ("master-boot-code");
	    }
	    y2milestone("Updating code in MBR: MBR Disk: %1, loader devices: %2",
		BootCommon::mbrDisk, BootCommon::GetBootloaderDevices());
	    list<string> disks_to_rewrite = grub_getMbrsToRewrite ();
	    foreach (string d, disks_to_rewrite, {
		y2milestone ("Copying generic MBR code to %1", d);
                // added fix 446 -> 440 for Vista booting problem bnc #396444
		string command = sformat (
		    "/bin/dd bs=440 count=1 if=%1 of=%2",
		    "/usr/lib/boot/master-boot-code",
		    d);
		y2milestone ("Running command %1", command);
		map out = (map)SCR::Execute (.target.bash_output, command);
		integer exit = out["exit"]:0;
		y2milestone ("Command output: %1", out);
		ret = ret && (0 == exit);
	    });
	}

	if (activate)
	{
	  foreach (map m_activate, grub_getPartitionsToActivate (), {
	    any num = m_activate["num"]:0;
	    string mbr_dev = m_activate["mbr"]:"";
	    if (num != 0 && mbr_dev != "")
	    {
		// if primary partition
		if ((! is (num, integer)) || num <= 4)
		{
		    y2milestone ("Activating partition %1 on %2", num, mbr_dev);
		    // FIXME: this is the most rotten code since molded sliced bread
		    // move to bootloader/Core/GRUB.pm or similar
		    // TESTME: make sure that parted does not destroy BSD
		    // slices (#suse24740): cf. section 5.1 of "info parted":
		    //   Parted only supports the BSD disk label system.
		    //   Parted is unlikely to support the partition slice
		    //   system in the future because the semantics are rather
		    //   strange, and don't work like "normal" partition tables
		    //   do.
		    // FIXED: investigate proper handling of the activate flag
		    // (kernel ioctls in parted etc.) and fix parted
		    string command = sformat
			("/usr/sbin/parted -s %1 set %2 boot on", mbr_dev, num);
		    y2milestone ("Running command %1", command);
		    map out = (map)SCR::Execute (.target.bash_output, command);
		    integer exit = out["exit"]:0;
		    y2milestone ("Command output: %1", out);
		    ret = ret && (0 == exit);
		}
	    }
	    else
	    {
		y2error ("Cannot activate %1", m_activate);
	    }
	  });
	}
	return ret;
    }


// --------------------------------------------------------------
// --------------------------------------------------------------
// LocationProposal() and related stuff (taken from routines/lilolike.ycp)


/**
 * SetBootloaderDevice()
 * Set "boot_*" flags in the globals map according to the boot device selected
 * with parameter selected_location. Only a single boot device can be selected
 * with this function. The function cannot be used to set a custom boot device.
 * It will always be deleted.
 *
 * FIXME: `mbr_md is probably unneeded; AFA we can see, this decision is
 * automatic anyway and perl-Bootloader should be able to make it without help
 * from the user or the proposal.
 *
 * @param selected_location symbol one of `boot `root `mbr `extended `mbr_md `none
 */
define void SetBootloaderDevice(symbol selected_location) {
    // first, default to all off:
    foreach (string flag, ["boot_boot", "boot_root", "boot_mbr", "boot_extended"], {
	BootCommon::globals[flag] = "false";
    });
    // need to remove the boot_custom key to switch this value off
    if (haskey (BootCommon::globals, "boot_custom")) {
	BootCommon::globals = remove (BootCommon::globals, "boot_custom");
    }

    if ( selected_location == `root ) {
	BootCommon::globals["boot_root"] = "true";
    } else if ( selected_location == `boot ) {
	BootCommon::globals["boot_boot"] = "true";
    } else if ( selected_location == `mbr ) {
	BootCommon::globals["boot_mbr"] = "true";
    } else if ( selected_location == `extended ) {
	BootCommon::globals["boot_extended"] = "true";
    }
}

/**
 * function check all partitions and it tries to find /boot partition
 * if it is MD Raid and soft-riad return correct device for analyse MBR
 * @param list<map> list of partitions
 * @return string device for analyse MBR
 */
define string soft_MDraid_boot_disk(list<map> partitions)
{
   string result = "";
   string boot_device = "";
   if ((BootStorage::BootPartitionDevice != nil) && (BootStorage::BootPartitionDevice != ""))
       boot_device = BootStorage::BootPartitionDevice;
   else
       boot_device = BootStorage::RootPartitionDevice;

   foreach(map p, partitions, {
	if (p["device"]:"" == boot_device)
	{
	   if ((p["type"]:nil == `sw_raid) && (tolower(p["fstype"]:"") == "md raid"))
	   {
	      string device_1 = p["devices",0]:"";
	      y2debug("device_1: %1", device_1);
	      map dp = Storage::GetDiskPartition (device_1);
	      y2debug("dp: %1", dp);
	      result = dp["disk"]:"";
	   }
	}
   });
   y2milestone("Device for analyse MBR from soft-raid (MD-Raid only): %1", result);
   return result;
}



/**
 * grub_ConfigureLocation()
 * Where to install the bootloader.
 * Returns the type of device where to install: one of `boot `root `mbr `extended `mbr_md
 * Also sets the boot_* keys in the internal global variable globals accordingly.
 *
 * @return string type of location proposed to bootloader
 */
define symbol grub_ConfigureLocation() {
    // NOTE: selected_location is a temporary local variable now; the global
    // variable is not used for grub anymore
    symbol selected_location = `mbr;             // default to mbr

    boolean vista_mbr = false;
    // check whether the /boot partition
    //  - is primary:				is_logical  -> false
    //  - is on the first disk (with the MBR):  boot_partition_is_on_mbr_disk -> true

    map<string,any> tm = Storage::GetTargetMap ();
    map dp = Storage::GetDiskPartition (BootStorage::BootPartitionDevice);
    string boot_partition_disk = dp["disk"]:"";
    boolean boot_partition_is_on_mbr_disk =
	(boot_partition_disk == BootCommon::mbrDisk);

    map dm = tm[boot_partition_disk]:$[];
    list<map> partitions_on_boot_partition_disk = dm["partitions"]:[];
    boolean is_logical = false;
    string extended = nil;

    // determine the underlying devices for the "/boot" partition (either the
    // BootPartitionDevice, or the devices from which the soft-RAID device for
    // "/boot" is built)
    list<string> underlying_boot_partition_devices = [ BootStorage::BootPartitionDevice ];
    map<string,integer> md_info = BootCommon::Md2Partitions (BootStorage::BootPartitionDevice);
    if (md_info != nil && size (md_info) > 0)
    {
	boot_partition_is_on_mbr_disk = false;
	underlying_boot_partition_devices = maplist (string dev, integer bios_id, md_info, {
	    map pdp = Storage::GetDiskPartition (dev);
	    string p_disk = pdp["disk"]:"";
	    if (p_disk == BootCommon::mbrDisk)
		boot_partition_is_on_mbr_disk = true;
	    return dev;
	});
    }
    y2milestone ("Boot partition devices: %1", underlying_boot_partition_devices);

    foreach (map p, partitions_on_boot_partition_disk, {
	if (p["type"]:nil == `extended)
	{
	    extended = (string)p["device"]:nil;
	}
	else if (contains (underlying_boot_partition_devices, p["device"]:"")
	    && p["type"]:nil == `logical)
	{
	    // If any of the underlying_boot_partition_devices can be found on
	    // the boot_partition_disk AND is a logical partition, set
	    // is_logical to true.
	    // For soft-RAID this will not match anyway ("/dev/[hs]da*" does not
	    // match "/dev/md*").
	    is_logical = true;
	}
    });
    y2milestone ("/boot is on 1st disk: %1", boot_partition_is_on_mbr_disk);
    y2milestone ("/boot is in logical partition: %1", is_logical);
    y2milestone ("The extended partition: %1", extended);

    // keep_mbr, if the MBR contains special code that needs to be kept,
    //           like Thinkpad boot code (and ATM only Thinkpad boot code
    //           is recognized)
    boolean keep_mbr = BootCommon::KeepMBR (BootCommon::mbrDisk);

    // if is primary, store bootloader there

    integer exit = 0;
    // there was check if boot device is on logical partition
    // IMO it is good idea check MBR also in this case
    // see bug #279837 comment #53
    if (boot_partition_is_on_mbr_disk)
    {
	selected_location = (BootStorage::BootPartitionDevice != BootStorage::RootPartitionDevice) ? `boot : `root;
	BootCommon::globals["activate"] = "true";
	BootCommon::activate_changed = true;

        // check if there is raid and if it soft-raid select correct device for analyse MBR
        // bnc #398356
	if (size (underlying_boot_partition_devices) > 1)
	    boot_partition_disk = soft_MDraid_boot_disk(partitions_on_boot_partition_disk);
	if (boot_partition_disk == "")
	    boot_partition_disk = dp["disk"]:"";
	// bnc #483797 cannot read 512 bytes from...
	string out = "";
	if (boot_partition_disk != "")
	{
	    out = BootCommon::examineMBR(boot_partition_disk);
	} else {
	    y2error("Boot partition disk not found");
	}	
	BootCommon::globals["generic_mbr"] = ((out != "vista") && (! keep_mbr)) ? "true" : "false";
	if (out == "vista")
	{
		y2milestone("Vista MBR...");
		vista_mbr = true;
	}

	
    }
    else if (size (underlying_boot_partition_devices) > 1)
    {
// FIXME: `mbr_md is probably unneeded; AFA we can see, this decision is
// automatic anyway and perl-Bootloader should be able to make it without help
// from the user or the proposal.
// In one or two places yast2-bootloader needs to find out all underlying MBR
// devices, if we install stage 1 to a soft-RAID. These places need to find out
// themselves if we have MBRs on a soft-RAID or not.
	// selected_location = `mbr_md;
	selected_location = `mbr;
    }

    if (keep_mbr)
    {
	if (is_logical && extended != nil)
	    selected_location = `extended;
	else
	    selected_location = (BootStorage::BootPartitionDevice != BootStorage::RootPartitionDevice) ? `boot : `root;
    }

    SetBootloaderDevice(selected_location);
    if (! contains (BootStorage::getPartitionList (`boot, "grub"), (BootCommon::GetBootloaderDevices())[0]:nil))
    {
	selected_location = `mbr;             // default to mbr
	SetBootloaderDevice(selected_location);
    }

    y2milestone ("grub_ConfigureLocation (%1 on %2)",
	selected_location, BootCommon::GetBootloaderDevices());

    // set active flag, if needed
    if (selected_location == `mbr && size (underlying_boot_partition_devices) <= 1)
    {
	// We are installing into MBR:
	// If there is an active partition, then we do not need to activate
	// one (otherwise we do).
	// Reason: if we use our own MBR code, we do not rely on the activate
	// flag in the partition table to boot Linux. Thus, the activated
	// partition can remain activated, which causes less problems with
	// other installed OSes like Windows (older versions assign the C:
	// drive letter to the activated partition).
	BootCommon::globals["activate"] =
	       (size (Storage::GetBootPartition (BootCommon::mbrDisk)) == 0) ? "true" : "false";

    }
    else
    {
	// if not installing to MBR, always activate (so the generic MBR will
	// boot Linux)

	// kokso: fix the problem with proposing installation generic boot code to "/" or "/boot"
	// kokso: if boot device is on logical partition
        if (is_logical && extended != nil && (BootCommon::globals["generic_mbr"]:"" == "true" ||
	    vista_mbr))
	    selected_location = `extended;
	BootCommon::globals["activate"] = "true";
	SetBootloaderDevice(selected_location);
    }

    return selected_location;
}

/**
  * Find extended partition device (if it exists) on the same device where the
  * BootPartitionDevice is located
  *
  * BootPartitionDevice must be set
  *
  * @return string device name of extended partition, or nil if none found
  */
string grub_GetExtendedPartitionDev () {
    string ret = nil;

    map<string,any> tm = Storage::GetTargetMap ();

    string device = "";
    if (BootStorage::BootPartitionDevice != "")
	device = BootStorage::BootPartitionDevice;
    else 
        device = BootStorage::RootPartitionDevice;

    map dp = Storage::GetDiskPartition (device);
    string disk = dp["disk"]:"";
    map dm = tm[disk]:$[];
    list<map> partitions = dm["partitions"]:[];
    foreach (map p, partitions, {
	if (p["type"]:nil == `extended)
	{
	    ret = (string)p["device"]:nil;
	}
    });

    return ret;
}

/**
  * Detect "/boot", "/" (root), extended partition device and MBR disk device
  *
  * If no bootloader device has been set up yet (globals["boot_*"]), or the
  * first (FIXME(!)) device is not available as a boot partition, also call
  * grub_ConfigureLocation to configure globals["boot_*"] and set the
  * globals["activate"] and globals["generic_mbr"] flags if needed
  * all these settings are stored in internal variables
  */
define void grub_DetectDisks () {
    // #151501: AutoYaST also needs to know the activate flag and the
    // "boot_*" settings (formerly the loader_device); jsrain also said
    // that skipping setting these variables is probably a bug:
    // commenting out the skip code, but this may need to be changed and made dependent
    // on a "clone" flag (i.e. make the choice to provide minimal (i.e. let
    // YaST do partial proposals on the target system) or maximal (i.e.
    // stay as closely as possible to this system) info in the AutoYaST XML
    // file)
    // if (Mode::config ())
    //    return;
    map mp = Storage::GetMountPoints();

    list mountdata_boot = mp["/boot"]:(mp["/"]:[]);
    list mountdata_root = mp["/"]:[];

    y2milestone( "mountPoints %1", mp );
    y2milestone( "mountdata_boot %1", mountdata_boot );

    BootStorage::RootPartitionDevice = mp["/", 0]:"";

    if (BootStorage::RootPartitionDevice == "")
    {
	y2error ("No mountpoint for / !!");
    }

    // if /boot changed, re-configure location
    BootStorage::BootPartitionDevice
	= mountdata_boot[0]:BootStorage::RootPartitionDevice;

    // get extended partition device (if exists)
    BootStorage::ExtendedPartitionDevice = grub_GetExtendedPartitionDev();

    if (BootCommon::mbrDisk == "" || BootCommon::mbrDisk == nil)
    {
	// mbr detection.
	BootCommon::mbrDisk = BootCommon::FindMBRDisk();
    }

    // if no bootloader devices have been set up, or any of the set up
    // bootloader devices have become unavailable, then re-propose the
    // bootloader location.
    list<string> all_boot_partitions = BootStorage::getPartitionList(`boot, "grub");
    list<string> bldevs = BootCommon::GetBootloaderDevices();
    boolean need_location_reconfigure = false;

    if ( bldevs == nil || bldevs == ["/dev/null"] )
	need_location_reconfigure = true;
    else {
	foreach (string dev, bldevs, {
	    if (!contains(all_boot_partitions, dev))
		need_location_reconfigure = true;
	});
    }

    if ( need_location_reconfigure )
	grub_ConfigureLocation ();
}

/**
 * Check whether any disk settings for the disks we currently use were changed
 * since last checking
 * @return map map containing boolean "changed" and string "reason"
 */
define map<string, any> grub_DisksChanged () {
    map<string, any> ret = $[
	"changed" : false,
	"reason"  : ""
    ];

    if (Mode::config ())
	return ret;

    map mp = Storage::GetMountPoints();
    string actual_root = mp["/", 0]:"";
    string actual_boot = mp["/boot", 0]:actual_root;
    string actual_extended = grub_GetExtendedPartitionDev();

    if ( BootCommon::globals["boot_boot"]:"false" == "true" &&
	 actual_boot != BootStorage::BootPartitionDevice )
	ret = $[
	    "changed" :	true,
	    "reason"  :	(string) ret["reason"]:"" +
			"Selected bootloader location \"/boot\" is not on " +
			BootStorage::BootPartitionDevice +
			" any more.\n"
	];

    if ( BootCommon::globals["boot_root"]:"false" == "true" &&
	 actual_root != BootStorage::RootPartitionDevice )
	ret = $[
	    "changed" :	true,
	    "reason"  :	(string) ret["reason"]:"" +
			"Selected bootloader location \"/\" is not on " +
			BootStorage::RootPartitionDevice +
			" any more.\n"
	];

    if ( BootCommon::globals["boot_mbr"]:"false" == "true" ) {
	string actual_mbr = BootCommon::FindMBRDisk();

	if ( actual_mbr != BootCommon::mbrDisk )
	    ret = $[
		"changed" : true,
		"reason"  : (string) ret["reason"]:"" +
			    "Selected bootloader location MBR is not on " +
			    BootCommon::mbrDisk +
			    " any more.\n"
	    ];
    }

    if ( BootCommon::globals["boot_extended"]:"false" == "true" &&
	 actual_extended != BootStorage::ExtendedPartitionDevice )
	ret = $[
	    "changed" : true,
	    "reason"  : (string) ret["reason"]:"" +
			"Selected bootloader location \"extended partition\" is not on " +
			BootStorage::ExtendedPartitionDevice +
			" any more.\n"
	];


    if ( BootCommon::globals["boot_custom"]:nil != nil  && BootCommon::globals["boot_custom"]:nil != "") {
	list all_boot_partitions = BootStorage::getPartitionList(`boot, "grub");

	if ( ! contains(all_boot_partitions, BootCommon::globals["boot_custom"]:nil) )
	    ret = $[
		"changed" : true,
		"reason"  : (string) ret["reason"]:"" +
			    "Selected custom bootloader partition " +
			    BootCommon::globals["boot_custom"]:nil +
			    " is not available any more.\n"
	    ];
    }

    if ( ret["changed"]:false )
	y2milestone ("Location should be set again");

    return ret;
}

/**
 * Propose the boot loader location for grub
 *  - if no proposal has been made, collects the devices for "/", "/boot", MBR
 *    and makes a new proposal
 *  - if no device mapping exists, creates a device mapping
 *  - if the devices that were somehow (proposal, user interface) selected for
 *    bootloader installation do not match the current partitioning any more
 *    (e.g. "/boot" partition was selected but is not available anymore (and
 *    "/" did not move there), "/" was selected but has moved, etc.), then also
 *    re-collect the devices for "/", "/boot", MBR and make a new proposal
 */
define void grub_LocationProposal () {
    y2milestone("globals: %1", BootCommon::globals);
    y2milestone("Mode::autoinst: %1", Mode::autoinst());
    y2milestone("haskey( BootCommon::globals, boot_boot ): %1", haskey( BootCommon::globals, "boot_boot" ));
    string md_mbr = "";
    if (! BootCommon::was_proposed ||
	  // During autoinstall, the autoyast profile must contain a bootloader
	  // device specification (we currently really only support system
	  // *cloning* with autoyast...). But as a convenience, and because
	  // this kind of magic is available for empty globals and sections, we
	  // propose a bootloader location if none was specified.
	  // Note that "no bootloader device" can be specified by explicitly
	  // setting this up, e.g. by setting one or all boot_* flags to
	  // "false".
	  // FIXME: add to LILO, ELILO; POWERLILO already should have this
	  // (check again)
	  ( Mode::autoinst() &&
	    ! haskey( BootCommon::globals, "boot_boot" ) &&
	    ! haskey( BootCommon::globals, "boot_root" ) &&
	    ! haskey( BootCommon::globals, "boot_mbr" ) &&
	    ! haskey( BootCommon::globals, "boot_extended" ) &&
//	    ! haskey( BootCommon::globals, "boot_mbr_md" ) &&
	    ! haskey( BootCommon::globals, "boot_custom" )
	  ))
    {
	grub_DetectDisks ();
	BootCommon::del_parts = BootStorage::getPartitionList (`deleted, "grub");
	// check whether edd is loaded; if not: load it
	string lsmod_command = "lsmod | grep edd";
	y2milestone ("Running command %1", lsmod_command);
	map lsmod_out = (map)SCR::Execute (.target.bash_output, lsmod_command);
	y2milestone ("Command output: %1", lsmod_out);
	boolean edd_loaded = lsmod_out["exit"]:0 == 0;
	if (! edd_loaded)
	{
	    string command = "/sbin/modprobe edd";
	    y2milestone ("Loading EDD module, running %1", command);
	    map out = (map)SCR::Execute (.target.bash_output, command);
	    y2milestone ("Command output: %1", out);
	}
	md_mbr = BootStorage::addMDSettingsToGlobals();
	if (md_mbr != "")
	    BootCommon::globals["boot_md_mbr"] = md_mbr;
    }
    y2milestone("(2) globals: %1", BootCommon::globals);

    // refresh device map
    if ((BootStorage::device_mapping == nil)
	|| (size (BootStorage::device_mapping) == 0)
	|| ((BootCommon::cached_settings_base_data_change_time != Storage::GetTargetChangeTime()) &&
	   // bnc#585824 - Bootloader doesn't use defined device map from autoyast
	   (!(Mode::autoinst() && (BootCommon::cached_settings_base_data_change_time == nil)))))
    {
	BootStorage::ProposeDeviceMap();
	md_mbr = BootStorage::addMDSettingsToGlobals();
	if (md_mbr != "")
	    BootCommon::globals["boot_md_mbr"] = md_mbr;
	BootCommon::InitializeLibrary (true, "grub");
    }

    if ( ! Mode::autoinst () ) {
	map<string, any> changed = grub_DisksChanged ();
	if ( changed["changed"]:false ) {
	    if (BootCommon::askLocationResetPopup (changed["reason"]:"Disk configuration changed.\n")) {
		SetBootloaderDevice(`none);
		y2milestone ("Reconfiguring locations");
		grub_DetectDisks ();
	    }
	}
    }
}

// --------------------------------------------------------------
// --------------------------------------------------------------
// other stuff


/** FATE #301994 - Correct device mapping in case windows is installed on the second HD
 * Check if chainloader section with windows is on the first disk
 * 
 * @param map<string,any> section from  BootCommon::sections
 * @return boolean true if it is necessary remap section
 */
define boolean isWidnowsOnBootDisk(map<string,any> section)
{
	// check if it is windows chainloader
	if ((search(tolower(section["name"]:""), "windows") != nil)
	   || (search(tolower(section["original_name"]:""), "windows") != nil))
	{
		map p_dev = Storage::GetDiskPartition (section["chainloader"]:"");

		string disk_dev = p_dev["disk"]:"";
		if (disk_dev == "")
		{
			y2error("trying find disk for windows chainloader failed");
			return false;
		}
		// find grub id in device map for chainloader device
		string grub_id = BootStorage::device_mapping[disk_dev]:"";
		y2milestone("Disk from windows chainloader: %1 grub id from device map: %2", disk_dev, grub_id);

		// check if disk is the first in order...
		if (grub_id != "hd0")
			return true;

	}
	return false;
}

/** FATE #301994 - Correct device mapping in case windows is installed on the second HD
 * Remap and make active windows chainloader section
 * if it is not on the boot (the first) disk
 * @param list of sections
 * @return list of sections
 */

define list<map<string,any> > checkWindowsSection(list<map<string,any> > sections)
{

	// list of idexes from sections where is chainloader
	// and where is necessary add remapping and makeactive
	list < integer > list_index =[];
	// counter
	integer index = -1;
	// check all sections...
	foreach(map<string,any> section, sections,
	{
		index = index +1;
		if (haskey(section, "chainloader"))
		{
			y2debug("chainloader section: %1", section);
			// add only indexes for update
			if (isWidnowsOnBootDisk(section))
				list_index = add(list_index, index);
		};
	});

	if (size(list_index) > 0)
	{
		foreach (integer idx, list_index,
		{
			sections[idx, "remap"] = "true";
			sections[idx, "makeactive"] = "true"; 
			y2milestone("Added remap and makeactive for section: %1", sections[idx]:$[]);
		});
	}

	y2debug("Checking sections for windows chainloader: %1", sections);
	return sections;
}


/**
 * FATE #303548 - Grub: limit device.map to devices detected by BIOS Int 13
 * The function reduces records (devices) in device.map
 * Grub doesn't support more than 8 devices in device.map
 * @return boolean true if device map was reduced
 */
global boolean ReduceDeviceMapTo8() 
{
    boolean result = false;

    if (size(BootStorage::device_mapping)>8)
    {
	result = true;
	list <string> bios_order = (list <string> ) Map::Values(BootStorage::device_mapping);
	//delete all grub devices with order more than 9
	bios_order = filter(string key, bios_order, {return (size(key) < 4);});
	bios_order = lsort(bios_order);
	y2debug("ordered values (grub devices): %1", bios_order);
	map<string,string> inverse_device_map = $[];
	map<string,string> new_device_map = $[];
	y2milestone("Device map before reducing: %1", BootStorage::device_mapping);
	foreach(string key, string value, BootStorage::device_mapping,
	{
	    inverse_device_map[value]=key;
	});

	y2debug("inverse_device_map: %1", inverse_device_map);
	integer index = 0;

	foreach(string key, bios_order,
	{
	    string device_name = inverse_device_map[key]:"";

	    if (index < 8)
	    {
		y2debug("adding device: %1 with key: %2 and index is: %3", device_name, key, index);
		new_device_map[device_name]=key;
		index = index + 1;
	    } else {
		break;
	    }
	});	
	BootStorage::device_mapping = new_device_map;
	y2milestone("Device map after reducing: %1", BootStorage::device_mapping);
    } else {
	y2milestone("Device map includes less than 9 devices. It is not reduced. device_map: %1", BootStorage::device_mapping);
    }
    return result;
}


/**
 * FATE #303548 - Grub: limit device.map to devices detected by BIOS Int 13
 * The function check if boot device is in device.map
 * Grub doesn't support more than 8 devices in device.map
 * @param string boot device
 * @param string boot device with name by mountby
 * @return boolean true if there is missing boot device
 */
define boolean checkBootDeviceInDeviceMap(string boot_dev, string boot_dev_mountby) 
{
    boolean result = false;

    if (size(BootStorage::device_mapping)>8)
    {
	result = false;
	list <string> bios_order = (list <string> ) Map::Values(BootStorage::device_mapping);
	//delete all grub devices with order more than 9
	bios_order = filter(string key, bios_order, {return (size(key) < 4);});
	bios_order = lsort(bios_order);
	y2debug("ordered values (grub devices): %1", bios_order);
	map<string,string> inverse_device_map = $[];
	foreach(string key, string value, BootStorage::device_mapping,
	{
	    inverse_device_map[value]=key;
	});

	y2debug("inverse_device_map: %1", inverse_device_map);
	integer index = 0;
	boolean boot_device_added = false;
	foreach(string key, bios_order,
	{
	    string device_name = inverse_device_map[key]:"";

	    if (index < 8)
	    {
		if ((device_name == boot_dev) || (device_name == boot_dev_mountby))
		    boot_device_added = true;
		index = index + 1;
	    } else {
		if (boot_device_added)
		{
		    y2milestone("Device map includes boot disk");
		    break;
		} else {
		    y2error("Device map doesn't include boot disk");
		    result = true;
		    break;
		}
	    }
	});	
    } else {
	y2milestone("Device map includes less than 9 devices.");
    }
    return result;
}




} //last "}"

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

ACC SHELL 2018