/**
 * 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>
 *
 * $Id: misc.ycp 22040 2005-03-01 09:36:57Z jsrain $
 *
 */

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

define boolean allowEmbed15 () {
    // allow only for /boot or /root device selected
    if (! (BootCommon::loader_device == BootCommon::BootPartitionDevice
	|| BootCommon::loader_device == BootCommon::RootPartitionDevice
	|| BootCommon::loader_device == BootCommon::mbrDisk
    ))
    {
	return false;
    }
    // check filesystem on /boot for Reiserfs and JFS
    map mp = Storage::GetMountPoints ();
    list bp_info
	= mp["/boot"]:mp["/"]:[];
    list<map> partitions
	= Storage::GetTargetMap ()[bp_info[2]:"", "partitions"]:[];
    boolean ret = false;
    foreach (map p, partitions, {
	if (p["device"]:"" == BootCommon::BootPartitionDevice)
	{
	    symbol fs = (symbol)p["used_fs"]:nil;
	    if (fs == `reiser || fs == `jfs)
		ret = true;
	}
    });
    return ret;
}

    /**
      * Encrypt the password before storing it in variables
      * @param passwd string unencrypted password
      * @return string password in the form to write to GRUB's menu.lst
      */
    global define string updatePasswdBeforeSave (string passwd) ``{
	passwd = sformat ("--md5 %1", cryptmd5 (passwd));
	return passwd;
    }

    /**
      * Read GRUB device maping from /boot/grub/device.map file
      * @return list of lists like
      * [["(hd0)", "/dev/hda"], ["(fd0)", "/dev/fd0"]]
      */
    global define list<list<string> > readDeviceMap () ``{
	list<list<string> > devmap = maplist (string k, string v,
	    (map<string,string>)BootCommon::io_structure["device_map"]:$[],
	{
	    return [ v, k ];
	});
	y2milestone ("Read device map: %1", devmap);
	return devmap;
    }

    /**
      * Save GRUB device map to file readable for GRUB
      * @param devmap list device map the same way as read with readDeviceMap
      * @return boolean true on success
      */
    global define boolean saveDeviceMap (list<list<string> > devmap) ``{
	map<string,string> dm = listmap (list<string> e, devmap,
	{
	    return $[ e[1]:"" : e[0]:"" ];
	});
	BootCommon::io_structure["device_map"] = dm;
    }

    /**
      * Generate device map proposal
      * @return list generated device map
      */
    global define list<list<string> > proposeDeviceMap () ``{
	map ids = $[];
	list<list<string> > mapping = [];
        map<string,map> targetMap = $[];
	if (Mode::config ())
	{
	    y2milestone ("Skipping device map proposing in Config mode");
/*	    import "AutoinstStorage";
	    targetMap = listmap (map d, AutoinstStorage::drives, ``(
		[d["device"]:"", d]
	    ));
	    targetMap = filter (any k, any v, targetMap, ``(k != ""));
	    y2debug ("Autoinst device map: %1", targetMap);
*/	}
	else
	{
	    targetMap = (map<string,map>)Storage::GetTargetMap();
	}
	targetMap = filter (string k, map v, targetMap, ``(
	    ! v["is_lvm_vg"]:false && ! issubstring (k, "/dev/md")
	    && ! issubstring (k, "/dev/evms")
	));
	y2milestone ("Target map: %1", targetMap);
	// add devices with know bios_id
        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);
                mapping = add (mapping, [grub_dev, target_dev]);
		ids[index] = true;
            }
        });
	// and guess other devices
	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);
                mapping = add (mapping, [grub_dev, target_dev]);
		ids[index] = true;
            }
        });
	if (StorageDevices::FloppyPresent)
	{
	    mapping = add (mapping, ["(fd0)", StorageDevices::FloppyDevice]);
	}
	y2milestone ("Detected device mapping: %1", mapping);
	return mapping;
    }

/**
 * Update the device map according to changed device names
 * @param devmap list original device map
 * @return list updated device map
 */
global void updateDeviceMap () {
    device_map = maplist (list<string> entry, device_map, {
	y2milestone ("Updating device in devmap entry %1:%2",
	    entry[0]:"", entry[1]:"");
	entry[1] = BootCommon::UpdateDevice (entry[1]:"");
	return entry;
    });
    y2milestone ("Updated device map: %1", device_map);
}

    /**
     * translate UNIX device /dev/hda to grub device (hd0)
     * @param o_dev string UNIX device
     * @return string grub device
     */
    global define string unixDev2grubDev (string o_dev) ``{
	if (o_dev != nil && o_dev != "" && regexpmatch (o_dev, "^\\(.*\\)$"))
	    return o_dev;
	// md can't be translated as they are, use one of partitions
	if (regexpmatch (o_dev, "\/dev\/md[0-9]+"))
	{
	    o_dev = BootCommon::Md2Partition (o_dev);
	}
        integer i = 0;
        map p_dev = Storage::GetDiskPartition (o_dev);
        any num = p_dev["nr"]:nil;
        o_dev = p_dev["disk"]:"";
        if (findlastof (o_dev, "/") == nil)
            o_dev = o_dev + "/dev/";
	list<list<string> > dm_lines = (list<list<string> >)filter (
	    list l,
	    device_map,
	    ``(l[1]:"" == o_dev));
	list<string> dm_line = dm_lines[0]:[];
	string dev = dm_line[0]:"";
	if (dev == "")
	    dev = "(null)";
        else if (num != 0 && num != "" && num != nil)
        {
            integer int_num = (integer)num - 1;
            string number = sformat ("%1", int_num);
            dev = substring (dev, 0, size (dev) - 1) + "," + number + ")";
        }
        return dev;
    }

    /**
      * translate grub device (hd0) to UNIX device /dev/hda
      * @param o_dev string grub device
      * @return string UNIX device
      */
    global define string grubDev2unixDev (string o_dev) ``{
	if (o_dev != nil && o_dev != "" && ! regexpmatch (o_dev, "^\\(.*\\)$"))
	    return o_dev;
	string number = "";
	if (o_dev == nil)
	    return o_dev;
	if (findfirstof (o_dev, ",") != nil)
	{
	    number = substring (o_dev, (findfirstof (o_dev, ",") + 1));
	    number = substring (number, 0, (size (number) - 1));
	    o_dev = substring (o_dev, 0, findfirstof (o_dev, ",")) + ")";
	}
	list<list<string> > dm_lines = (list<list<string> >)filter (
	    list l,
	    device_map,
	    ``(l[0]:"" == o_dev));
	list<string> dm_line = dm_lines[0]:[];
	string dev = dm_line[1]:"";
	if (dev == "")
	    dev = "/dev/null";
	else if (number != "")
	{
	    integer num = tointeger (number);
	    num = num + 1;
	    dev = Storage::GetDeviceName (dev, num);
	}
	dev = BootCommon::Partition2Md (dev);
	return dev;
    }

    /**
      * translate filename path (eg. (hd0,0)/kernel) to list of device
      *  and relative path
      * @param devpth string fileststem path (eg. (hd0,0)/vmlinuz)
      * @return list containing device and relative path,
      *  eg. ["(hd0,0)", "/vmlinuz"]
      */
    global define list<string> splitDevPath (string devpth) ``{
	integer brake = nil;
	if (devpth != nil && issubstring (devpth, ")"))
	    brake = findfirstof (devpth, ")") + 1;
	list<string> ret = [];
	if (brake != nil)
	{
	    ret[0] = substring (devpth, 0, brake);
	    ret[1] = substring (devpth, brake);
	}
	else
	{
	    ret[0] = nil;
	    ret[1] = devpth;
	}
        return ret;
    }

    /**
      * translate list of device and relative path
      *  to filename path (eg. (hd0,0)/kernel)
      * @param devpth list of two strings, first for device name, second for
      *  relative path (eg. ["(hd0,0)", "/vmlinuz"])
      * @return string fileststem path (eg. (hd0,0)/vmlinuz)
      */
    global define string mergeDevPath (list devpth) ``{
        if (substring (devpth[1]:"", 0, 1) != "/")
            devpth[1] = "/" + devpth[1]:"";
	if (devpth[0]:"" == nil || devpth[1]:"" == nil)
	    return nil;
	string ret = devpth[0]:"" + devpth[1]:"";
        return ret;
    }

    /**
      * translate UNIX filename to grub path
      * @param filename string UNIX-style filename (eg. "/boot/vmlinuz")
      * @return string GRUB-like filename (eg. "(hd0,0)/vmlinuz")
      */
    global define string file2grub (string filename) ``{
	if (Mode::config ())
	    return filename;
	if (regexpmatch (filename, "^\\(.+\\)\\/.+$"))
	    return filename;
	list split = BootCommon::splitPath (filename);
	split[0] = unixDev2grubDev (split[0]:"");
	return mergeDevPath (split);
    }

    /**
      * translate grub path to UNIX filename
      * @param grubpath string GRUB-like filename (eg. "(hd0,0)/vmlinux")
      * @return string UNIX-style filename (eg. "/boot/vmlinuz")
      */
    global define string grub2file (string grubpath) ``{
	if (regexpmatch (grubpath, "^\\/.+$"))
	    return grubpath;
	list<string> split = splitDevPath (grubpath);
	split[0] = grubDev2unixDev (split[0]:"");
	return BootCommon::mergePath (split);
    }

    /**
      * parse /etc/grub file from other file
      * @param file string contents of grub.conf file
      * @return map of settings
      */
    global define map<string,string> parseGrubConf (string file) ``{
	if (file == "" || file == nil)
	    return $[];
	list<string> lines = splitstring (file, "\n");
	lines = filter (string l, lines, ``(l != ""));
	map<string,string> settings = $[];
	list<string> devices = [];
	string root = "";
	foreach (string l, lines, ``{
	    list<string> entries = splitstring (l, " ");
	    entries = filter (string e, entries, ``(e != ""));
	    if (entries[0]:"" == "root")
	        root = entries[1]:"";
	    else if (entries[0]:"" == "install")
	    {
	        integer shift = 0;
		integer shift1 = 1 + shift;
	        string tmp = entries[shift1]:"";
	        while (substring (tmp, 0, 2) == "--")
	        {
		    string prefix = settings["prefix"]:"";
		    prefix = prefix + " " + tmp;
	            settings = add (settings, "prefix", prefix);
	            shift = shift + 1;
		    shift1 = 1 + shift;
	            tmp = entries[shift1]:"";
	        }
	        settings = add (settings, "stage1", tmp);
		integer shift2 = 2 + shift;
	        tmp = entries[shift2]:"";
	        if ("d" == tmp)
	        {
	            settings = add (settings, "discswitch", "d");
	            shift = shift + 1;
		    integer shift2 = 2 + shift;
	            tmp = entries[shift2]:"";
	        }
		devices = add (devices, tmp);
	        settings = add (settings, "device", grubDev2unixDev (tmp));
		integer shift3 = 3 + shift;
	        tmp = entries[shift3]:"";
	        settings = add (settings, "stage2", tmp);
		integer shift4 = 4 + shift;
	        tmp = entries[shift4]:"";
	        settings = add (settings, "addr", tmp);
		integer shift5 = 5 + shift;
	        tmp = entries[shift5]:"";
	        if (substring (tmp, 0, 1) == "/")
	            tmp = sformat ("%1%2", root, tmp);
	        settings = add (settings, "menu", tmp);
		settings["command"] = "install";
	    }
	    else if (entries[0]:"" == "setup")
	    {
		integer shift = 0;
		string tmp = entries[shift + 1]:"";
		while (substring (tmp, 0, 2) == "--")
		{
		    string prefix = settings["prefix"]:"";
		    prefix = prefix + " " + tmp;
	            settings = add (settings, "prefix", prefix);
	            shift = shift + 1;
	            tmp = entries[shift + 1]:"";
		}
		devices = add (devices, tmp);
	        settings["device"] = grubDev2unixDev (tmp);
		root = entries[shift + 2]:"";
		settings["command"] = "setup";
	    }
	    settings = add (settings, "root", root);
	});

	// MD devices handling
	if (size (devices) > 1)
	{
	    list<string> boot_devices = sort (maplist (string d, devices, {
		return grubDev2unixDev (d);
	    }));
	    string use_device = "";
	    // first check all MD arrays
	    map<string,map> tm = Storage::GetTargetMap ();
	    list<string> md = maplist (map m, tm["/dev/md", "partitions"]:[], {
		return m["device"]:"";
	    });
	    if (Mode::test ())
		md = ["/dev/md0"];
	    find (string md_disk, md, {
		list<string> md_members = sort (maplist (
		    string s,
		    integer id,
		    BootCommon::Md2Partitions (md_disk),
		{
		    return s;
		}));
		if (Mode::test ())
		    md_members = ["/dev/hda1", "/dev/hdb1"];
		if (md_members == boot_devices)
		{
		    use_device = md_disk;
		    return true;
		}
		return false;
	    });
	    // check MBRs of all disks holding /boot partition
	    if (use_device == "")
	    {
		map<string,integer> boot_md
		    = BootCommon::Md2Partitions (
			BootCommon::BootPartitionDevice);
		if (Mode::test ())
		    boot_md = $["/dev/hda1" : 128, "/dev/hdb1" : 129];
		list<string> md_disks = maplist (string d, integer b, boot_md,
		{
		    map p_dev = Storage::GetDiskPartition (d);
		    return p_dev["disk"]:"";
		});
		md_disks = sort (md_disks);
		if (boot_devices == md_disks)
		{
		    use_device = "mbr_md";
		}
	    }
	    if (use_device != "")
		settings["device"] = use_device;
	}
	return settings;
    }

    /**
      * parse /etc/grub file
      * @return map of settings
      */
    global define map<string,string> readGrubConf () ``{
	return parseGrubConf ((string)
	    SCR::Read (.target.string, "/etc/grub.conf"));
    }



    /**
      * get /etc/grub.conf file from settings as string
      * @param settings map of settings
      * @return string file contents
      */
    global define string getGrubConf (map<string,string> settings) ``{
	string root = sformat ("root %1\n", settings["root"]:"");
	string dev = settings["device"]:"";
	list<string> devices = [];
	if (dev == "mbr_md")
	{
	    dev = BootCommon::BootPartitionDevice;
	    map<string,integer> md =  BootCommon::Md2Partitions (dev);
	    if (Mode::test ())
		md = $["/dev/hda1" : 128, "/dev/hdb1" : 129];
	    devices = maplist (string d, integer b, md, {
		map p_dev = Storage::GetDiskPartition (d);
		return p_dev["disk"]:"";
	    });
	}
	else if (regexpmatch (dev, "^\\/dev\\/md.*$"))
	{
	    map<string,integer> md =  BootCommon::Md2Partitions (dev);
	    if (Mode::test ())
		md = $["/dev/hda1" : 128, "/dev/hdb1" : 129];
	    devices = maplist (string d, integer b, md, { return d;});
	}
	else
	{
	    devices = [dev];
	}
	devices = maplist (string d, devices, {
	    return unixDev2grubDev (d);
	});
	devices = filter (string d, devices, {
	    return d != "" && d != nil && d != "(null)" && d != "/dev/null";
	});
	devices = toset (devices);
	list<string> lines = maplist (string d, devices, {
	    string line = sformat (settings["discswitch"]:"" == "d"
		? "install %6 %1 d %2 %3 %4 %5"
		: "install %6 %1 %2 %3 %4 %5",
		    settings["stage1"]:"",
		    d,
		    settings["stage2"]:"",
		    settings["addr"]:"",
		    settings["menu"]:"",
		    settings["prefix"]:"");
	    return line;
	});
	string res = sformat ("%1%2\nquit\n",
	    root,
	    mergestring (lines, "\n"));
	return res;
    }

    /**
      * save /etc/grub.conf
      * @param settings map settings
      * @return boolean true if success
      */
    global define boolean saveGrubConf (map<string,string> settings) ``{
	if (settings == $[])
	    return true;
	return SCR::Write (.target.string, "/etc/grub.conf",
	    getGrubConf (settings));
    }

    /**
      * Propose /etc/grub.con
      * @return map grub-conf file
      */
    global define map<string,string> proposeGrubConf () ``{
	list<string> stage1 = splitDevPath (file2grub ("/boot/grub/stage1"));
	list<string> stage2 = splitDevPath (file2grub ("/boot/grub/stage2"));
	y2milestone ("Proposed grub.conf: Stage1: %1, Stage2: %2",
	    stage1, stage2);

	string s1_part = BootCommon::loader_device;
	if (s1_part == "mbr_md")
	{
	    s1_part = BootCommon::BootPartitionDevice;
	}
	s1_part = BootCommon::Md2Partition (s1_part);
	map dev_info = Storage::GetDiskPartition (s1_part);
	string s1_dev = dev_info["disk"]:"";
	string s2_part = grubDev2unixDev (stage2[0]:"");
	s2_part = BootCommon::Md2Partition (s2_part);
	dev_info = Storage::GetDiskPartition (s2_part);
	string s2_dev = dev_info["disk"]:"";

	string discswitch = "";
	if (s1_dev != s2_dev)
	{
	    discswitch = "d";
	}

	return $[
	    "discswitch" : discswitch,
	    "root" : stage1[0]:"",
	    "stage1" : stage1[1]:"",
	    "device" : BootCommon::loader_device,
	    "stage2" : stage2[1]:"",
	    "addr" : "0x8000",
	    "menu" : file2grub ("/boot/grub/menu.lst"),
	    "prefix" : "--stage2=/boot/grub/stage2",
	];
    }

    /**
      * Remaps values of map src according to mapping
      * @param src map source map
      * @param mapping map mapping description
      * @return list modified map
      */
    global define list<map> remapDisks (list<map> src, map<string,string> mapping) ``{
	foreach (string k, string v, mapping, ``{
	   mapping = add (mapping, substring (k, 0, size (k) - 1) + ",",
				substring (v, 0, size (v) - 1) + ",");

	});
	map<string,string> m1 = $[];
	map<string,string> m2 = $[];
	integer counter = 0;
	foreach (string k, string v, mapping, ``{
	    string scounter = sformat ("\\*\\*\\*\n%1\n*\\*\\*\\", counter);
	    m1[k] = scounter;
	    m2[scounter] = v;
	    counter = counter + 1;
	});

	list<map> ret = maplist (map v, src, ``{
	    if (v["key"]:"" != "__intern__" && is (v["value"]:(any)"",  string))
	    {
		foreach (string s, string d, m1, ``{
		    if (is (v["value"]:nil, string) && v["value"]:nil != nil)
			v["value"]
			    = BootCommon::replaceAll (v["value"]:"", s, d);
		});
		foreach (string s, string d, m2, ``{
		    if (is (v["value"]:nil, string) && v["value"]:nil != nil)
			v["value"]
			    = BootCommon::replaceAll (v["value"]:"", s, d);
		});
	    }
	    return v;
	});
	return ret;
    }




}
