ACC SHELL

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

/**
 * File:
 *      modules/BootStorage.ycp
 *
 * Module:
 *      Bootloader installation and configuration
 *
 * Summary:
 *      Module includes specific functions for handling storage data.
 *      The idea is handling all storage data necessary for bootloader
 *      in one module.
 *
 * Authors:
 *      Jozef Uhliarik <juhliarik@suse.cz>
 *
 *
 *
 *
 */

{

module "BootStorage";

textdomain "bootloader";

import "Storage";
import "StorageDevices";
import "Arch";
import "Mode";


/**
 * Saved change time from target map - only for MapAllPartitions()
 */
global integer disk_change_time_InitBootloader = nil;

/**
 * Saved change time from target map - only for MapAllPartitions()
 */

global integer disk_change_time_MapAllPartitions = nil;

/**
 * Saved change time from target map - only for checkCallingDiskInfo()
 */

global integer disk_change_time_checkCallingDiskInfo = nil;

/**
 * bnc #468922 - problem with longtime running the parsing a huge number of disks
 * map<string,map> the map of all partitions with info about it -> 
 * necessary for Dev2MountByDev() in routines/misc.ycp 
 */
global map<string,map> all_partitions = $[];

/**
 * bnc #468922 - problem with longtime running the parsing a huge number of disks
 * map<string,map> target map try to minimalize calling Storage::GetTargetMap()
 *
 */ 
map<string,map> target_map = $[];


/**
 * mapping all devices udev-name to kernel name
 * importnat for init fucntion of perl-Bootloader
 */
global map<string, string> all_devices = $[];


/**
 * Storage locked
 */
boolean storage_initialized = false;


/**
 * device mapping between real devices and multipath
 */
global map<string,string> multipath_mapping = $[];


// FIXME: it is ugly hack because y2-storage doesn't known
// to indicate that it finish (create partitions) proposed partitioning of disk in installation
/** bnc#594482 - grub config not using uuid
 * Indicate if storage already finish partitioning of disk 
 * if some partition includes any keyword "create"
 * the value is the first found partition with flag "create" e.g. /dev/sda2
 * empty string means all partitions are created
 */
string proposed_partition = "";

/** bnc#594482 - grub config not using uuid
 * 0 - if all devices in variable all_devices are created.
 * 1 - if partition with flag "create" was found in MapDevices()
 * 2 - if proposed_partition was created or flag "create" was deleted
 * by y2-storage. the value is set in CheckProposedPartition ()
 */
integer all_devices_created = 0;

/** bnc#594482 - grub config not using uuid
 * 0 - if all devices in variable all_partitions and all_disks are created
 * 1 - if partition with flag "create" was found in MapDevices()
 * 2 - if proposed_partition was created or flag "create" was deleted
 * by y2-storage. the value is set in CheckProposedPartition ()
 */
integer all_partitions_created = 0;

/**
 * mountpoints for perl-Bootloader
 */

global map<string,any> mountpoints = $[];


/**
 * list of all partitions for perl-Bootloader
 */

global list<list> partinfo = [];

/**
 * information about MD arrays for perl-Bootloader
 */
global map<string,list<string> > md_info = $[];


/**
 * Flag indicates that bios_id_missing in disk 
 * true if missing false if at least one disk has bios_id
 */
global boolean bois_id_missing = true;

/**
 * device mapping between Linux and firmware
 */
global map<string,string> device_mapping = $[];



/**
  * string sepresenting device name of /boot partition
  * same as RootPartitionDevice if no separate /boot partition
  */
global string BootPartitionDevice = "";


/**
  * string representing device name of / partition
  */
global string RootPartitionDevice = "";

/**
  * string representing device name of extended partition
  */
global string ExtendedPartitionDevice = "";

/**
  * list of installed floppy devices
  */
global list<string> floppy_devices = nil;


/** FATE#305008: Failover boot configurations for md arrays with redundancy
 * list <string> includes physical disks used for md raid
 */

global list <string> md_physical_disks = [];


/** FATE #302219 - Use and choose persistent device names for disk devices
 * Function prepare maps with mapping disks and partitions by uuid, id, path
 * and label.
 *
 */
void MapDevices()
{
	string dev_by_something = "";
	map<string,map> devices = (map<string,map>)Storage::GetTargetMap();
	foreach (string k, map v, devices, 
	{
		// map disk by uuid
		if ((v["uuid"]:nil != "")  && (v["uuid"]:nil != nil))
		{
			dev_by_something = "/dev/disk/by-uuid/"+v["uuid"]:"";
			all_devices[dev_by_something]= k;	
		}
		// map disk by path
		if ((v["path"]:nil != "")  && (v["path"]:nil != nil))
		{
			dev_by_something = "/dev/disk/by-path/"+v["path"]:"";
			all_devices[dev_by_something]= k;
		}
		// map disk by id
		if ((v["udev_id"]:nil != nil) && (v["udev_id", 0]:nil != ""))
		{
			dev_by_something = "/dev/disk/by-id/"+v["udev_id", 0]:"";
			all_devices[dev_by_something]= k;
			// bnc #534905 - yast2 bootloader 2.18.15-1.1 damages /etc/grub.conf
			if (size(v["udev_id"]:[]) == 2 )
			{
				dev_by_something = "/dev/disk/by-id/"+v["udev_id", 1]:"";
				all_devices[dev_by_something]= k;
			}
		}
		// map partitions from disk...

		foreach (map p, (list<map>)(v["partitions"]:[]), 
		{
			// bnc#594482 - grub config not using uuid
			// if there is "not created" partition and flag for "it" is not set
			if ((p["create"]:nil == true) && (Mode::installation()))
			{
			    if (proposed_partition == "")
				proposed_partition = p["device"]:"";
			    all_devices_created = 1;
			}
			// map partition by uuid
			if ((p["uuid"]:nil != "")  && (p["uuid"]:nil != nil))
			{
				dev_by_something = "/dev/disk/by-uuid/"+p["uuid"]:"";
				all_devices[dev_by_something]= p["device"]:"";
			}

			// map partition by path
			if ((p["path"]:nil != "")  && (p["path"]:nil != nil))
			{
				dev_by_something = "/dev/disk/by-path/"+p["path"]:"";
				all_devices[dev_by_something]= p["device"]:"";	
			}

			// map partition by label
			if ((p["label"]:nil != "")  && (p["label"]:nil != nil))
			{
				dev_by_something = "/dev/disk/by-label/"+p["label"]:"";
				all_devices[dev_by_something]= p["device"]:"";
			}

			// map disk by id
			if ((p["udev_id"]:nil != nil) && (p["udev_id", 0]:nil != ""))
			{
				dev_by_something = "/dev/disk/by-id/"+p["udev_id", 0]:"";
				all_devices[dev_by_something]= p["device"]:"";
				// bnc #534905 - yast2 bootloader 2.18.15-1.1 damages /etc/grub.conf
				if (size(p["udev_id"]:[]) == 2 )
				{
					dev_by_something = "/dev/disk/by-id/"+p["udev_id", 1]:"";
					all_devices[dev_by_something]= p["device"]:"";
				}
			}

		}); // end of foreach (map p, (list<map>)(v["partitions"]:[]),
	}); // end of foreach (string k, map v, devices,
	if (Mode::installation() && all_devices_created == 2)
	{
	    all_devices_created = 0;
	    y2milestone("set status for all_devices to \"created\"");
	}
	y2debug("device name mapping to kernel names: %1", all_devices);

}



/** FATE #302219 - Use and choose persistent device names for disk devices
 * Converts a "/dev/disk/by-" device name to the corresponding kernel
 * device name, if a mapping for this name can be found in the map from
 * yast2-storage. If the given device name is not a "/dev/disk/by-" device
 * name, it is left unchanged. Also, if the information about the device
 * name cannot be found in the target map from yast2-storage, the device
 * name is left unchanged.
 *
 * @param dev string device name
 * @return string kernel device name
 */

global define string MountByDev2Dev(string dev) 
{
	y2milestone ("MountByDev2Dev: %1", dev);

	if (!regexpmatch(dev, "^/dev/disk/by-"))
	    return dev;
	string ret = dev;

	// check if it is device name by id
	if (haskey(all_devices, dev))
		ret = all_devices[dev]:"";

	y2milestone("Device %1 was converted to: %2", dev, ret);
	return ret;
}

/** bnc#594482 - grub config not using uuid
 * Function check if proposed_partition still includes
 * flag "create"
 *
 * @param map<string,map> tm
 * @return true if partition is still not created
 */

boolean CheckProposedPartition (map<string,map> tm)
{

    boolean ret = true;
    if (!Mode::installation())
    {
	y2debug("Skip CheckProposedPartition() -> it is not running installation");
	return false;
    }
    if (size(tm) == 0 || proposed_partition == "")
    {
	y2debug("proposed_partition is empty: %1", proposed_partition);
	return false;
    }
    map dp = Storage::GetDiskPartition (proposed_partition);
    string disk = dp["disk"]:"";
    list <map> partitions = (list<map>)tm[disk,"partitions"]:[];
    foreach (map p, partitions, 
    {
        if (p["device"]:"" == proposed_partition)
	{
	   if (p["create"]:nil != true)
	   {
	      proposed_partition = "";
	      y2milestone("proposed_partition is already created: %1", p);
	      if (all_devices_created == 1)
		  all_devices_created = 2;
	      if (all_partitions_created == 1)
		  all_partitions_created = 2;
	      ret = false;
	   } else {
	      y2milestone("proposed_partition: %1 is NOT created", proposed_partition);
	   }
	   break;
	}
    });
    return ret;
}

/** bnc#594482 - grub config not using uuid
 * Check if it is necessary rebuild all_devices
 *
 * @return true -> rebuild all_devices
 */

boolean RebuildMapDevices()
{
    boolean ret = false;
    boolean ret_CheckProposedPartition = CheckProposedPartition((map<string,map>)Storage::GetTargetMap());

    if (!ret_CheckProposedPartition && all_devices_created == 2)
	return true;

    return ret;
}

/**
 * Init and fullfil internal data for perl-Bootloader
 *
 * @return true if init reset/fullfil data or false and used cached data
 */

global boolean InitMapDevices ()
{
	boolean ret = false;
	if (disk_change_time_InitBootloader != Storage::GetTargetChangeTime() || RebuildMapDevices())
	{
		y2milestone("Init internal data from storage");
		MapDevices();
		disk_change_time_InitBootloader = Storage::GetTargetChangeTime();
		ret = true;
	}
	
	return ret;
}

/** bnc#594482 - grub config not using uuid
 * Check if it is necessary rebuild all_partitions and all_disks
 *
 * @return true -> rebuild all_partitions and all_disks
 */

boolean RebuilMapAllPartitions()
{
    boolean ret = false;
    boolean ret_CheckProposedPartition = CheckProposedPartition((map<string,map>)Storage::GetTargetMap());

    if (!ret_CheckProposedPartition && all_partitions_created == 2)
	return true;

    return ret;
}

/**
 * bnc #468922 - problem with longtime running the parsing a huge number of disks
 * Function initialize all_partitions only if storage change 
 * partitioning of disk
 * true if init all_partitions
 */

boolean MapAllPartitions()
{
    boolean ret = false;
    if ((disk_change_time_MapAllPartitions != Storage::GetTargetChangeTime())
	|| (size(all_partitions) < 1) || (size(target_map) < 1)
	|| RebuilMapAllPartitions())
    {
	// save last change time from storage for MapAllPartitions()
	disk_change_time_MapAllPartitions = Storage::GetTargetChangeTime();

	all_partitions = $[];
	target_map = $[];
	// get target map
	target_map = (map<string,map>)Storage::GetTargetMap();
	// map all partitions
	foreach (string k, map v, target_map,
	{
		foreach (map p, (list<map>)(v["partitions"]:[]), 
		{
			// bnc#594482 - grub config not using uuid
			// if there is "not created" partition and flag for "it" is not set
			if ((p["create"]:nil == true) && (Mode::installation()))
			{
			    if (proposed_partition == "")
				proposed_partition = p["device"]:"";
			    all_partitions_created = 1;
			}
			all_partitions[p["device"]:""] = p;
		});
	});
	ret = true;
    }
    if (Mode::installation() && all_partitions_created == 2)
    {
	all_partitions_created = 0;
	y2milestone("set status for all_partitions to \"created\"");
    }

    return ret;
}

/** FATE #302219 - Use and choose persistent device names for disk devices
 * Converts a device name to the corresponding device name it should be
 * mounted by, according to the "mountby" setting for the device from
 * yast2-storage. As a safeguard against problems, if the "mountby" device
 * name does not exist in the information from yast2-storage, it will
 * fallback to the "kernel name" ("/dev/sdXY").
 *
 * @param dev string device name
 * @return string device name according to "mountby"
 */
global define string Dev2MountByDev(string dev)
{
	string tmp_dev = MountByDev2Dev(dev);

	y2milestone ("Dev2MountByDev: %1 as kernel device name: %2", dev, tmp_dev);
	// add all_partitions to partitions
	if (MapAllPartitions())
	    y2milestone("Init all_partitions was done");

	map<string,map> partitions = all_partitions;
	map<string,map> devices = target_map;

	// (`id,`uuid,`path,`device,`label)
	symbol by_mount = nil;

	// bnc#458018 accept different mount-by for partition
	// created by user
	if (!Arch::ppc())
	{
		by_mount = Storage::GetDefaultMountBy();
		if (haskey(partitions, tmp_dev))
		{
			symbol partition_mount_by = (symbol) partitions[tmp_dev, "mountby"]:nil;
			if ((partition_mount_by != nil))
				by_mount = partition_mount_by;
		}
		
	} else {
		by_mount = `id;
	}	

	y2milestone("Mount-by: %1", by_mount);
	string ret = tmp_dev;
	switch(by_mount) 
	{
		// convert to udev_id
		case (`id):
			// partitions
			if ((partitions[tmp_dev, "udev_id"]:nil != nil) && (partitions[tmp_dev, "udev_id", 0]:nil != ""))
			{
				ret = sformat ("/dev/disk/by-id/%1", partitions[tmp_dev, "udev_id", 0]:"");
	   			y2milestone ("Device name: %1 is converted to udev id: %2", tmp_dev, ret);
	   			return ret;
			}
			// disks
			if ((devices[tmp_dev, "udev_id"]:nil != nil) && (devices[tmp_dev, "udev_id", 0]:nil != ""))
			{
	   			ret = sformat ("/dev/disk/by-id/%1", devices[tmp_dev, "udev_id", 0]:"");
	   			y2milestone ("Device name: %1 is converted to udev id: %2", tmp_dev, ret);
	   			return ret;
			}
		break;

		// convert to uuid
		case (`uuid):
			// partitions
			if ((partitions[tmp_dev, "uuid"]:nil != nil) && (partitions[tmp_dev, "uuid"]:nil != ""))
			{
	   			ret = sformat ("/dev/disk/by-uuid/%1", partitions[tmp_dev, "uuid"]:"");
	   			y2milestone ("Device name: %1 is converted to uuid: %2", tmp_dev, ret);
	   			return ret;
			}
			// disks
			if ((devices[tmp_dev, "uuid"]:nil != nil) && (devices[tmp_dev, "uuid"]:nil != ""))
			{
	   			ret = sformat ("/dev/disk/by-uuid/%1", devices[tmp_dev, "uuid"]:"");
	   			y2milestone ("Device name: %1 is converted to uuid: %2", tmp_dev, ret);
	   			return ret;
			}
		break;

		// convert to udev_path
		case (`path):
			// partitions
			if ((partitions[tmp_dev, "udev_path"]:nil != nil) && (partitions[tmp_dev, "udev_path"]:nil != ""))
			{
	   			ret = sformat ("/dev/disk/by-path/%1", partitions[tmp_dev, "udev_path"]:"");
	   			y2milestone ("Device name: %1 is converted to udev path: %2", tmp_dev, ret);
	   			return ret;
			}
			// disks
			if ((devices[tmp_dev, "udev_path"]:nil != nil) && (devices[tmp_dev, "udev_path"]:nil != ""))
			{
	   			ret = sformat ("/dev/disk/by-path/%1", devices[tmp_dev, "udev_path"]:"");
	   			y2milestone ("Device name: %1 is converted to udev path: %2", tmp_dev, ret);
	   			return ret;
			}
		break;

		// convert to label
		case (`label):
			// partitions
			if ((partitions[tmp_dev, "label"]:nil != nil) && (partitions[tmp_dev, "label"]:nil != ""))
			{
	   			ret = sformat ("/dev/disk/by-label/%1", partitions[tmp_dev, "label"]:"");
	   			y2milestone ("Device name: %1 is converted to label: %2", tmp_dev, ret);
	   			return ret;
			}
			// disks
			y2milestone ("Disk doesn't support labels - name: %1 is converted to label: %2", tmp_dev, ret);
			return ret;			
		break;

		// convert to device or uknown type
		default:
			y2warning ("Convert %1 to `device or unknwn type, result: %2", tmp_dev, ret);
			return ret;
		break;
	}

	return ret;
}


/** bnc #447591, 438243, 448110 multipath wrong device map
 * Function maps real devices to multipath e.g.
 * "/dev/sda/" : "/dev/mapper/SATA_ST3120813AS_3LS0CD7M"
 *
 * @return map <string, string> mapping real disk to multipath
 */

// FIXME grub only

map <string, string> mapRealDevicesToMultipath()
{
	map <string, string> ret = $[];
	map<string, map> tm = (map<string,map>)Storage::GetTargetMap ();
	integer num_of_real_disk = 0;
	foreach (string disk, map disk_info, tm,
	{
		if (disk_info["type"]:nil == `CT_DMMULTIPATH)
		{
			list <string> devices = (list <string>) disk_info["devices"]:[];
			if (size(devices) > 0)
			{
				foreach(string d, devices,
				{
					ret[d]=disk;
				});

			}
		}

		if (disk_info["type"]:nil == `CT_DISK)
			num_of_real_disk = num_of_real_disk +1;
		if (disk_info["bios_id"]:nil != nil)
			bois_id_missing = false;
	});
	if (num_of_real_disk == 1)
		bois_id_missing = false;
	return ret;
}




/**
 * Check if function was called or storage change
 * partitionig of disk. It is usefull fo using cached data
 * about disk. Data is send to perl-Bootloader and it includes
 * info about partitions, multi path and md-raid
 *
 * @return false if it is posible use cached data
 */

boolean checkCallingDiskInfo ()
{
    boolean ret = false;

    // fix for problem with unintialized storage library in AutoYaST mode
    // bnc #464090
    if (Mode::config() && (!storage_initialized))
    {
	storage_initialized = true;
	y2milestone("Init storage library in yast2-bootloader");
	Storage::InitLibstorage(true);
    }
    if ((disk_change_time_checkCallingDiskInfo != Storage::GetTargetChangeTime())
	|| (size(partinfo) < 1))
    {
	// save last change time from storage
	disk_change_time_checkCallingDiskInfo = Storage::GetTargetChangeTime();
	y2milestone("disk was changed by storage or partinfo is empty: %1", size(partinfo));
	y2milestone("generate partinfo, md_info, mountpoints and multipath_mapping");
	ret = true;
    } else {
	ret = false;
	y2milestone("Skip genarating partinfo, md_info, mountpoints and multipath_mapping");
    }

    return ret;
}

/**
 * Function init data for perl-Bootloader about disk
 * It means fullfil md_info, multipath_mapping, partinfo
 * and mountpoints
 */

global void InitDiskInfo()
{

    if (checkCallingDiskInfo())
    {
	// delete variables for perl-Bootloader
        md_info = $[];
	multipath_mapping = $[];
	partinfo = [];
	mountpoints = $[];

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

    	multipath_mapping = mapRealDevicesToMultipath();
    	mountpoints = mapmap (string k, list v,
	(map<string,list>)Storage::GetMountPoints (),
    	{
             // detect all raid1 md devices and mark them in md_info
             any device = v[0]:nil;
	     if (v[3]:"" == "raid1")
	         md_info[(string)device]=[];
	     return $[ k : device ];
    	});
    	mountpoints = filter (string k, any v, mountpoints, {
	     string tmpdir = (string)SCR::Read (.target.tmpdir);
	     integer tmp_sz = size (tmpdir);
	     return is (v, string) && substring (k, 0, tmp_sz) != tmpdir;
    	});

    	y2milestone ("Detected mountpoints: %1", mountpoints);

    	list<list<list> > pi = maplist (string disk, map info, tm,
    	{
	    if (info["type"]:`CT_UNKNOWN==`CT_LVM)
	        return [];
	    if (info["type"]:`CT_UNKNOWN==`CT_EVMS)
	        return [];
	    list partitions = info["partitions"]:[];
	    list<list> parts = maplist (map p, (list<map>)partitions, {
	        string raid = "";
	        if( p["used_by_type"]:`UB_NONE==`UB_MD )
		   raid = p["used_by_device"]:"";
	        string device = p["device"]:"";
	        // We only pass along RAID1 devices as all other causes
	        // severe breakage in the bootloader stack
	        if (raid != "") {
		    if (haskey (md_info, raid)) {
		        list<string> members = md_info[raid]:[];
		        members = add (members, device);
		        md_info[raid] = members;
		    }
	        }
	        any nr = p["nr"]:nil;
	        if (nr == nil)
		    nr = 0;
	        string nr_str = sformat ("%1", nr);


	        return [ device, disk, nr_str, tostring(p["fsid"]:0),
		    p["fstype"]:"unknown", tostring(p["type"]:nil),
		    tostring(p["region", 0]:0), tostring(p["region", 1]:0)
	        ];
	    });
	    return parts;
    	});
    	partinfo = flatten (pi);
    	partinfo = filter (list p, partinfo, {return p != nil && p != [];});
    	list<list> partinfo_mountby = [];
    	// adding moundby (by-id) via user preference
    	foreach(list partition, partinfo, {
	    list tmp =[];
	    string mount_by = BootStorage::Dev2MountByDev(tostring(partition[0]:""));
	    if (mount_by != tostring(partition[0]:""))
	    {
	        tmp = add(partition, mount_by);
	    } else {
	        tmp = partition;
	    }
	    partinfo_mountby = add(partinfo_mountby, tmp);
    	});
    	// y2milestone("added mountby: %1", partinfo_mountby);

    	partinfo = partinfo_mountby;

    }

}


/*** helper functions for ProposeDeviceMap: ***/
/**
 * Returns true if any device from list devices is in device_mapping
 * marked as hd0.
 */
boolean isHd0(list<string> devices)
{
    boolean ret = false;

    foreach (string value, devices,
        {
            if (device_mapping[value]:"" == "hd0"){
                ret = true;
            }
        });

    return ret;
}

/**
 * Returns first key from mapping associated with value.
 * Example:
 *      map = $[ "a" : "1",
 *               "b" : "2",
 *               "c" : "3",
 *               "d" : "2"];
 *      getDeviceFromMapping("1", map) -> "a"
 *      getDeviceFromMapping("2", map) -> "b"
 */
string getKey(string value, map<string, string> mapping)
{
    string ret = "";

    foreach (string key, string val, mapping,
        {
            if (value == val){
                ret = key;
                return;
            }
        });

    return ret;
}

/**
 * This function changes order of devices in device_mapping.
 * All devices listed in bad_devices are maped to "hdN" are moved to the end
 * (with changed number N). And second step is putting device with boot partition
 * on top (i.e. device_mapping[dev_with_boot] = "hd0").
 *
 * Example:
 *      device_mapping = $[ "/dev/sda" : "hd0",
 *                          "/dev/sdb" : "hd1",
 *                          "/dev/sdc" : "hd2",
 *                          "/dev/sdd" : "hd3",
 *                          "/dev/sde" : "hd4" ];
 *      bad_devices = [ "/dev/sda", "/dev/sdc" ];
 *
 *      changeOrderInDeviceMapping(bad_devices);
 *      // after call, device_mapping is:
 *      device_mapping -> $[ "/dev/sda" : "hd3",
 *                           "/dev/sdb" : "hd0",
 *                           "/dev/sdc" : "hd4",
 *                           "/dev/sdd" : "hd1",
 *                           "/dev/sde" : "hd2" ];
 */
void changeOrderInDeviceMapping(list<string> bad_devices)
{
    integer cur_id = 0;
    list keys = [];
    string value = "";
    string tmp = "";
    string tmp2 = "";

    // get keys from device_mapping, it's not possible to use foreach over keys and values
    // of device_mapping directly, because during the loop device_mapping is changing.
    foreach (string key, string value, device_mapping, { keys = add(keys, key); });

    // put bad_devices at bottom
    foreach (string key, (list<string>)keys,
        {
                value = device_mapping[key]:"";

                // if device is mapped on hdX and this device is _not_ in bad_devices
                if (substring(value, 0, 2) == "hd"
                    && !contains(bad_devices, key)){
                        // get device name of mapped on "hd"+cur_id
                        tmp = getKey("hd" + tostring(cur_id), device_mapping);

                        // swap tmp and key devices (swap their mapping)
                        device_mapping[tmp] = value;
                        device_mapping[key] = "hd" + tostring(cur_id);

                        cur_id = cur_id + 1;
                }
        });

}

/**
 * Check if MD raid is build on disks not on paritions
 * @param devices - list of devices from MD raid
 * @param tm - unfiltered target map
 * @return - true if MD RAID is build on disks (not on partitions) 
 */

boolean checkMDRaidDevices(list<string> devices, map<string,map> tm)
{
    boolean ret = true;
    foreach (string key, devices, {
	if (key != "" && ret)
	{
	    if (tm[key]:nil != nil)
		ret = true;
	    else
		ret = false;
	}
    });
    return ret;
}

/**
 * Function check if disk is in list of devices
 * @param string disk
 * @param list<string> list of devices
 * @return true if success
 */

boolean isDiskInList (string disk, list<string> devices)
{
    boolean ret = false;
    foreach(string dev, devices,
    {
	if (dev == disk)
	{
	   ret = true;
	   break;
	}
    });
    return ret;
}

/**
 * Check if disk is in MDRaid it means completed disk is used in RAID
 * @param disk (/dev/sda)
 * @param tm - target map
 * @return - true if disk (not only part of disk) is in MDRAID  
 */
boolean isDiskInMDRaid (string disk, map<string,map> tm) 
{
    boolean ret = false;
    foreach( string dev, map d_info, tm,
    {
	if (d_info["type"]:nil == `CT_MDPART)
	    ret = isDiskInList(disk, d_info["devices"]:[]);
	if (ret)
	    break;
    });
    return ret;
}



/*** helper functions END ***/

/**
 * Generate device map proposal, store it in internal variables.
 *
 * FATE #302075:
 *   When user is installing from USB media or any non IDE disk or bios simply
 *   set any non IDE disk as first and user is not installing on this removable
 *   (non IDE) disk, the order of disks proposed by bios must be changed because
 *   of future remove of USB disk.
 *   This function must find right place for bootloader (which is most probably
 *   boot sector of boot partition (where /boot dir is located)) and change the
 *   order of disks in device map.
 *   This method is only heuristic because order of disks after remove of usb
 *   disk can't be determined by any method.
 *
 *   Algorithm for solving problem with usb disk propsed by bios as hd0:
 *      if usbDiskDevice == hd0 && BootDevice != usbDiskDevice:
 *          change order of disks in device_mappings to have BootDevice as hd0
 */
// FIXME: remove that function from here, as it is grub only
// NOTE: there is a local copy in routines/grub/misc.ycp now
global void ProposeDeviceMap () {
    list<string> usb_disks = []; // contains those usb removable disks

    device_mapping = $[];
    multipath_mapping = $[];

    map<string,map> targetMap = $[];
    if (Mode::config ())
	y2milestone ("Skipping device map proposing in Config mode");
    else
	targetMap = (map<string,map>)Storage::GetTargetMap();

    // filter out non-disk devices
    targetMap = filter (string k, map v, targetMap, {
	 return v["type"]:`CT_UNKNOWN==`CT_DMRAID || v["type"]:`CT_UNKNOWN==`CT_DISK
	     || v["type"]:`CT_UNKNOWN==`CT_DMMULTIPATH || 
	      (v["type"]:`CT_UNKNOWN==`CT_MDPART && checkMDRaidDevices(v["devices"]:[], targetMap));
    });

    // filter out members of BIOS RAIDs and multipath devices
    targetMap = filter (string k, map v, targetMap, {
	 return v["used_by_type"]:nil != `UB_DMRAID && v["used_by_type"]:nil != `UB_DMMULTIPATH&& 
		((v["used_by_type"]:nil == `UB_MDPART) ? (!isDiskInMDRaid(k,targetMap)):true);
    });

    y2milestone ("Target map: %1", targetMap);

    // add devices with known bios_id
    // collect BIOS IDs which are used
    map ids = $[];
    foreach (string target_dev, map target, targetMap, {
       string bios_id = target["bios_id"]:"";
       if (bios_id != "")
       {
          integer index = tointeger (bios_id) - tointeger ("0x80");
          string grub_dev = sformat ("hd%1", index);
	  // FATE #303548 - doesn't add disk with same bios_id with different name (multipath machine)
	  if (! ids[index]:false)
          {
	      device_mapping[target_dev] = grub_dev;
              ids[index] = true;
	  }
       }
    });
    // and guess other devices
    // don't use already used BIOS IDs
    foreach (string target_dev, map target, targetMap, {
       string bios_id = target["bios_id"]:"";
       if (bios_id == "")
       {
          integer index = 0;
           while (ids[index]:false)
              index = index + 1;
           string grub_dev = sformat ("hd%1", index);
	   device_mapping[target_dev] = grub_dev;
           ids[index] = true;
       }
    });

    // Fill usb_disks list with usb removable devices.
    //
    // It's not easy to determine how to identify removable usb devices. Now
    // it tests if driver of device is usb-storage. If you find better
    // algorithm how to find removable usb devices, put it here into foreach
    // to apply this algorithm on all devices.
    foreach (string target_dev, map target, targetMap,
    {
        string driver = target["driver"]:"";
        if (driver == "usb-storage"){
            usb_disks = add(usb_disks, target_dev);
        }
    });
    y2milestone("Found usb discs: %1", usb_disks);
    
    // change order in device_mapping if usb disk is hd0
    // (FATE #302075)
    if (isHd0(usb_disks) && BootPartitionDevice != getKey("hd0", device_mapping)){
	   y2milestone ("Detected device mapping: %1", device_mapping);
           y2milestone("Changing order in device mapping needed...");
           changeOrderInDeviceMapping(usb_disks);
    }

    if (size(device_mapping) == 1)
       bois_id_missing = false;
    if (StorageDevices::FloppyPresent())
       device_mapping[StorageDevices::FloppyDevice()] = "fd0";

    y2milestone ("Detected device mapping: %1", device_mapping);

    multipath_mapping = mapRealDevicesToMultipath();

    y2milestone ("Detected multipath mapping: %1", multipath_mapping);

}

/**
 * Get the order of disks according to BIOS mapping
 * @return a list of all disks in the order BIOS sees them
 */
global list<string> DisksOrder () {
    if (device_mapping == nil || size (device_mapping) == 0)
    {
	ProposeDeviceMap ();
    }
    map<string,string> devmap_rev = mapmap (string k, string v, device_mapping,
    {
	return $[ v : k ];
    });
    devmap_rev = filter (string k, string v, devmap_rev, {
	return substring (k, 0, 2) == "hd";
    });
    list<string> order = maplist (string k, string v, devmap_rev, {
	return v;
    });
    return order;
}


/**
 * Function remap device map to device name (/dev/sda) 
 * or to label (ufo_disk)
 * @param map<string,string> device map
 * @return map<string,string> new device map
 */

global map<string,string> remapDeviceMap(map<string,string> device_map)
{	
	symbol by_mount = nil;
	if (Arch::ppc())
	    by_mount = `id;
        else
	    by_mount = Storage::GetDefaultMountBy();

	//by_mount = `id;
	if (by_mount == `label)
		return device_map;

	map<string,string> ret = $[];
	// convert device names in device map to the device names by device or label
	ret = mapmap (string k , string v, device_mapping, 
	{
		return $[BootStorage::MountByDev2Dev(k) : v];
	});

	return ret;
}

/**
 * Get the list of installed floppy drives
 * @return a list of floppy devices
 */
global list<string> getFloppyDevices () 
{
	if (floppy_devices == nil)
        {
            list<map> floppies = (list<map>) SCR::Read (.probe.floppy);
            floppies = filter (map f, floppies,
                ``(f["model"]:"Floppy Disk" == "Floppy Disk"));
            floppy_devices = maplist (map f, floppies,
		``(f["dev_name"]:""));
            floppy_devices = filter (string f, floppy_devices, ``(f != ""));
        }
	return floppy_devices;
}

/**
 * Returns list of partitions with "mount by" hints. Goes through the list
 * of partitions passed as a parameter and creates a list of partitions with
 * hints according to the current partitioning requested from
 * yast2-storage. To be used in a combobox or menu.
 *
 * @param parts_to_get list<string> partitions to list
 * @return a list of strings containing a partition name and a hint (if applicable)
 */
global define list<string> getHintedPartitionList(list<string> parts_to_get) 
{
	y2milestone ("getHintedPartitionList: %1", parts_to_get);
	map<string,map> devices = (map<string,map>)Storage::GetTargetMap();

	// make a map: "/dev/hda1" -> info_map_for_this_partition
	map<string,map> partitions = $[];
	foreach (string k, map v, devices, ``{
	    foreach (map p, (list<map>)(v["partitions"]:[]), ``{
		partitions[p["device"]:""] = p;
	    });
	});
	y2milestone ("getHintedPartitionList: partitions %1", partitions);

	symbol mountby = `device;
	list<string> ret = maplist (string dev, parts_to_get, ``{
	    mountby = (symbol) partitions[dev, "mountby"]:nil;
	    if ( mountby == `uuid )
	    {
		return sformat ("%1 (mount by UUID: %2)", dev,
				partitions[dev, "uuid"]:nil != nil ?
				partitions[dev, "uuid"]:"" :
				"<UUID to be created later during format>");
	    } else if ( mountby == `label )
	    {
		return sformat ("%1 (mount by LABEL: %2)", dev, partitions[dev, "label"]:"");
	    } else if ( mountby == `id )
	    {
		return sformat ("%1 (mount by ID: %2)", dev, partitions[dev, "udev_id", 0]:"");
	    } else if ( mountby == `path )
	    {
		return sformat ("%1 (mount by PATH: %2)", dev, partitions[dev, "udev_path"]:"");
	    } else if ( mountby == nil || mountby == `device )
	    {
		return dev;
	    }
	});

	y2milestone ("getHintedPartitionList: ret %1", ret);
	return ret;
}

/**
 * Returns list of partitions. Requests current partitioning from
 * yast2-storage and creates list of partition for combobox, menu or other
 * purpose.
 * @param type symbol
 *   `boot - for bootloader installation
 *   `root - for kernel root
 *   `boot_other - for bootable partitions of other systems
 *   `all - all partitions
 *   `parts_old - all partitions, except those what will be created
 *      during isntallation
 *   `deleted - all partitions deleted in current proposal
 *   `kept - all partitions that won't be deleted, new created or formatted
 *   `destroyed - all partition which are new, deleted or formatted
 * @return a list of strings
 */
global define list<string> getPartitionList(symbol type, string bl) 
{
	y2milestone ("getPartitionList: %1", type);
	map<string,map> devices = (map<string,map>)Storage::GetTargetMap();
	list<map> partitions = [];
	foreach (string k, map v, devices, ``{
	    if ((type == `boot) && (bl == "grub"))
	    {
		// check if device is in device map
		if (haskey(device_mapping, k) || (haskey(device_mapping, Dev2MountByDev(k))))
		    partitions = (list<map>)merge (partitions, (list<map>)(v["partitions"]:[]));
	    } else {
	        partitions = (list<map>)merge (partitions, (list<map>)(v["partitions"]:[]));
	    }
	});
	list<string> floppies = getFloppyDevices ();

	devices = filter (string k, map v, devices,
	                  ``(v["type"]:`CT_UNKNOWN != `CT_LVM));

	if (type == `boot || type == `boot_other)
	{
	    devices = filter (string k, map v, devices, {
			if ((v["type"]:`CT_UNKNOWN == `CT_DISK) ||
			    (v["type"]:`CT_UNKNOWN == `CT_DMRAID))
				return true;
			else
				return false;
		      });
	}
	list<string> all_disks = (list<string>) maplist (string k, map v, devices, 
	{
	    return k;
	});

	

	if (type == `deleted)
	    return maplist (map x, filter (map p, partitions, ``(p["delete"]:false)),
		``{return x["device"]:"";
	    });
	else if (type == `destroyed)
	{
	    return maplist (map x, filter (map p, partitions, {
		    return p["delete"]:false || p["format"]:false
			|| p["create"]:false;
		}),
	    {
		return x["device"]:"";
	    });
	}
        partitions = filter (map p, partitions, ``(! p["delete"]:false));
	// filter out disk which are not in device map
	if ((bl == "grub") && (type == `boot))
	    all_disks = (list<string>) filter(string k, all_disks, {
		if (haskey(device_mapping, k) || (haskey(device_mapping, Dev2MountByDev(k))))
		    return true;
		else
		    return false;
	    });
	list<string> ret = all_disks;
	if (type == `boot_other || type == `root || type == `parts_old
	    || type == `kept)
            ret = [];

	if (type == `boot)
        {
	    partitions = filter (map p, partitions,
                ``(p["type"]:`primary == `primary
		    || p["type"]:`primary == `extended
		    || p["type"]:`primary == `logical
		    || p["type"]:`primary == `sw_raid)
	    );
	    // FIXME this checking is performed on 3 places, one function should
	    // be developed for it
	    partitions = filter (map p, partitions, {
		symbol fs = (symbol)(p["used_fs"]:p["detected_fs"]:nil);
		if (fs == `xfs)
		    return false;
		return true;
	    });
        }
        else if (type == `root)
        {
            partitions = filter (map p, partitions,
                    ``(p["type"]:`primary != `extended));
        }
	else if (type == `parts_old)
	{
	    partitions = filter (map p, partitions, ``(! p["create"]:false));
	}
	else if (type == `kept)
	{
	    partitions = filter (map p, partitions, {
		return ! (p["create"]:false || p["format"]:false);
	    });
	}
        if (type != `all && type != `parts_old && type != `kept)
        {
            partitions = filter (map p, partitions,
                ``(p["fstype"]:"" != "Linux swap"));
        }
	if (type == `boot)
	{
	    partitions = filter (map p, partitions,
                ``(p["fstype"]:"" == "Linux native"
                    || p["fstype"]:"" == "Extended"
		    || p["fstype"]:"" == "Linux RAID"
		    || tolower(p["fstype"]:"") == "md raid"
		    || p["fstype"]:"" == "DM RAID"));
	}
        list<string>partition_names
	    = maplist (map p, partitions, ``((string)(p["device"]:"")));
	partition_names = filter (string p, partition_names, ``(p != ""));
        partition_names = (list<string>)merge (partition_names, floppies);
        ret = (list<string>)union (ret, partition_names);
	ret = (list<string>)toset (ret);
	return (list<string>)ret;
}

/** FATE#305008: Failover boot configurations for md arrays with redundancy
 * Check if devices has same partition number and if they are from different disks
 *
 * @param list <string> list of devices 
 * @return boolean true on success
 */
boolean checkDifferentDisks (list <string> devices)
{
    boolean ret = false;
    list <string> disks = [];
    string no_partition = "";
    foreach(string dev, devices,
    {
	map p_dev = Storage::GetDiskPartition (dev);
	if (!contains(disks, p_dev["disk"]:""))
	    disks=add(disks,p_dev["disk"]:"");
	else
	{
	    y2milestone("Same disk for md array -> disable synchronize md arrays");
	    break;
	}
	// add disk from partition to md_physical_disks
	if (!contains(md_physical_disks, p_dev["disk"]:""))
	    md_physical_disks = add(md_physical_disks,p_dev["disk"]:"");

	string no_p = p_dev["nr"]:"";
	if (no_p == "")
	{
	    y2error("Wrong number of partition: %1 from Storage::GetDiskPartition: %2", dev, p_dev);
	    break;
	}
	if (no_partition == "")
	    no_partition = no_p;
	else
	    if (no_partition == no_p)
		ret = true;
	    else
		y2milestone("Different number of partitions -> disable synchronize md arrays");

    });

    y2milestone("checkDifferentDisks for devices: %1 return: %2",devices, ret);

    return ret;
}

/** FATE#305008: Failover boot configurations for md arrays with redundancy
 * Check if device are build from 2 partitions with same number but from different disks
 *
 * @param map<string,map> tm taregte map from storage
 * @param string device (md device)
 * @return true if device is from 2 partisions with same number and different disks
 */
boolean checkMDDevices(map<string,map> tm, string device)
{
    boolean ret = false;
    map <string, any> tm_dm =  (map<string, any>) tm["/dev/md"]:$[];

    md_physical_disks = [];
    // find partitions in target map
    foreach(map p, tm_dm["partitions"]:[], 
    {
	if (p["device"]:"" == device)
	{
	    if (p["raid_type"]:"" == "raid1")
	    {
		list <string> p_devices = p["devices"]:[];
		if (size(p_devices) == 2)
		    ret = checkDifferentDisks(p_devices);
		else
		    y2milestone("Device: %1 doesn't contain 2 partitions: %2", device, p_devices);

	    } else {
		y2milestone ("Device: %1 is not on raid1: %2",device, p["raid_type"]:"");
	    }
	}
    });

    if ((size(md_physical_disks) != 2) || (contains(md_physical_disks, "")))
	y2milestone("device: %1 is based on md_physical_disks: %2 is not valid for enable redundancy",
 		    device, md_physical_disks);

    if (ret)
	y2milestone("device: %1 is based on md_physical_disks: %2 is valid for enable redundancy",
		    device, md_physical_disks);

    return ret;
}


/** FATE#305008: Failover boot configurations for md arrays with redundancy
 * Function check partitions and set redundancy available if 
 * partitioning of disk allows it.
 * It means if md array is based on 2 partitions with same number but 2 different disks
 * E.g. /dev/md0 is from /dev/sda1 and /dev/sb1 and /dev/md0 is "/"
 * There is possible only boot from MBR (GRUB not generic boot code)
 *
 * @return boolean true on success
 */

global boolean checkMDSettings ()
{
    boolean ret = false;
    map<string,map> tm = (map<string,map>)Storage::GetTargetMap();

    if (!haskey(tm, "/dev/md"))
    {
	y2milestone("Doesn't include md raid");
	return ret;
    }
    list<string> boot_devices = [];
    if ((BootPartitionDevice != "") && (BootPartitionDevice != nil))
	boot_devices = add(boot_devices, BootPartitionDevice);
    if ((BootPartitionDevice != RootPartitionDevice) && (RootPartitionDevice != "")
	&& (BootPartitionDevice != nil))
	boot_devices = add(boot_devices, RootPartitionDevice);
    if ((ExtendedPartitionDevice != "")&& (ExtendedPartitionDevice != nil))
	boot_devices = add(boot_devices, ExtendedPartitionDevice);

    y2milestone("Devices for analyse of redundacy md array: %1", boot_devices);
    foreach(string dev, boot_devices,
    {
	ret = checkMDDevices(tm, dev);
	if (!ret)
	{
	    y2milestone("Skip enable redundancy of md arrays");
	    break;
	}
    });

    return ret;
}

/** FATE#305008: Failover boot configurations for md arrays with redundancy
 * Function prapare disks for synchronizing of md array
 * 
 * @return string includes disks separatet by ","
 */

global string addMDSettingsToGlobals()
{
    string ret = "";

    if (checkMDSettings())
	ret = mergestring(md_physical_disks,",");
    return ret;
}


}

ACC SHELL 2018