/**
 * File:
 *      modules/BootELILO.ycp
 *
 * Module:
 *      Bootloader installation and configuration
 *
 * Summary:
 *      Module containing specific functions for ELILO configuration
 *      and installation
 *
 * Authors:
 *      Jiri Srain <jsrain@suse.cz>
 *      Andreas Schwab <schwab@suse.de>
 *
 * $Id: BootELILO.ycp 21296 2005-02-07 13:32:13Z jsrain $
 *
 */

{

    // FIXME paths will be probably changed because of bugzilla 21644

    module "BootELILO";

    textdomain "bootloader";

    import "BootCommon";
    import "Kernel";
    import "Mode";
    import "Stage";
    import "Storage";

// private variables

    /**
      * Name of EFI entry when read settings
      */
    global string old_efi_entry = nil;

    /**
      * Should be original EFI entry removed?
      */
    global boolean remove_old_efi = true;

    /**
      * elilo.conf path
      */
    global string elilo_conf_filename = "/boot/efi/SuSE/elilo.conf";

    /**
      * True if EFI entry should be recreated
      */
    global boolean create_efi_entry = true;

    /**
      * True if the EFI layout was changed
      */
    global boolean efi_layout_changed = false;


    include "bootloader/elilo/misc.ycp";
    include "bootloader/elilo/widgets.ycp";
    include "bootloader/routines/routines.ycp";


// misc. functions

    /**
      * Create strings representing cfg. files for by-hand editation
      * @return map filename -> contents
      */
    global define map<string,string> GetFiles() ``{
	return $[]; // FIXME remove
// TODO rewrite using the library
//        BootCommon::saveCfgFileToAgent ();
        return $[
            elilo_conf_filename : (string)SCR::Read (.elilo.tostring),
        ];
    }

    /**
      * Store strings representing cfg. files to internal structures
      * @param files map filename -> contents
      */
    global define void SetFiles (map<string,string> files) ``{
	return; // FIXME remove
        if (SCR::Read (.elilo.tostring)
	    != files[elilo_conf_filename]:"")
	{
            BootCommon::files_edited = true;
	}
        SCR::Write (.elilo.fromstring, files[elilo_conf_filename]:"");
// TODO rewrite using the library
//        BootCommon::loadCfgFileFromAgent ();
    }

    /**
      * Create linux section
      * @param title strign section title (linux, failsafe, memtest)
      * @return section description structure
      */
    global define list<map> createLinuxSection (string title) ``{
	// not using .shipped images any more
        string initrd = "initrd";
        string image = "vmlinuz";
        string append = title == "failsafe"
            ? "ide=nodma nohalt noresume selinux=0 barrier=off "
		+ BootCommon::GetAdditionalFailsafeParams ()
            : "";
	list <string> split = BootCommon::splitPath (initrd);
	initrd = split[1]:initrd;
	split = BootCommon::splitPath (image);
	image = split[1]:image;
        list<map> ret = [
            $[ "key" : "label", "value" : BootCommon::translateSectionTitle (title)],
            $[ "key" : "image", "value" : image],
            $[ "key" : "initrd", "value" : initrd],
            $[ "key" : "root", "value" : BootCommon::dev2majminIfNonStandard (
		BootCommon::RootPartitionDevice)],
            $[ "key" : "__intern__", "value" : $[
                "auto" : true,
                "changed" : false,
                "devs" : [BootCommon::RootPartitionDevice],
		"type" : title,
            ]],
        ];
	if (append != "")
	    ret = add (ret, $[ "key" : "append", "value" : append]);
        if (Kernel::GetVgaType () != "" && title != "failsafe")
            ret = add (ret, $["key" : "vga", "value" : Kernel::GetVgaType ()]);
        return ret;
    }



    /**
      * Propose sections to bootloader menu
      * modifies internal sreuctures
      */
    global define void createSections () ``{
	BootCommon::sections = [ createLinuxSection ("linux"),
	    createLinuxSection ("failsafe") ];
    }

    /**
      * Fix sections of bootloader menu during proposal regeneration
      * modifies internal sreuctures
      */
    global define void fixSections () ``{
	BootCommon::liloFixSections (BootELILO::createSections);
	return;
    }

    /**
      * Propose global options of bootloader
      * modifies internal structures
      */
    global define void createGlobals () ``{
	string append = sformat ("%1 %2 %3 splash=silent",
	    "selinux=0",
	    BootCommon::GetAdditionalKernelParams (),
	    Kernel::GetCmdLine ());
	// on SIG Altix change kernel default hash tables sizes
	if (SCR::Read (.target.stat, "/proc/sgi_sn") != $[])
	{
	    append = append + " thash_entries=2097152";
	}
	BootCommon::globals = [
	    $[ "key" : "prompt", "value" : true ],
	    $[ "key" : "timeout", "value" : 80 ],
	    $[ "key" : "read-only", "value" : true ],
	    $[ "key" : "relocatable", "value" : true ],
	    $[ "key" : "default", "value" : BootCommon::translateSectionTitle ("linux") ],
	    $[ "key" : "append", "value" : append ],
	];
    }

    /**
      * Update sections of bootloader menu
      * modifies internal sreuctures
      * @param replace boolean true if old sectinos shall be replaced
      */
    global define void updateSections (boolean replace) ``{
	BootCommon::liloUpdateSections (replace, createLinuxSection);
	BootCommon::sections = maplist (list<map> s, BootCommon::sections, ``{
	    s = maplist (map m, s, ``{
		if (m["key"]:"" == "initrd")
		{
		    if (substring (m["value"]:"", 0, 7) == "/initrd")
			m["value"] = "initrd";

		}
		if (m["key"]:"" == "image")
		{
		    if (substring (m["value"]:"", 0, 8) == "/vmlinuz")
			m["value"] = "vmlinuz";

		}
		return m;
	    });
	    return s;
	});
// TODO maybe change the paths? Probably not needed, to be decised
    }

    /**
      * Update global options of bootloader
      * modifies internal sreuctures
      */
    global define void updateGlobals () ``{
	BootCommon::globals = maplist (map e, BootCommon::globals, ``{
	    if (e["key"]:"" == "timeout")
		e["value"] = 80;
	    else if (e["key"]:"" == "append")
	    {
		string option = e["value"]:"";
		foreach (string o, BootCommon::ListAdditionalKernelParams (), {
		    option = BootCommon::setKernelParamToLine
			(option, o, "false");
		});
		option = option + " "
		    + BootCommon::GetAdditionalKernelParams ();
		if (BootCommon::getKernelParamFromLine
		    (option, "splash") == "false")
		{
		    option = BootCommon::setKernelParamToLine
			(option, "splash", "silent");
		}
	    }
	    // and on SGI Altix, change default kernel hash table sizes
	    if (e["key"]:"" == "append"
		&& SCR::Read (.target.stat, "/proc/sgi_sn") != $[])
	    {
		e["value"] = BootCommon::setKernelParamToLine (
		    e["value"]:"",
		    "thash_entries",
		    "2097152");
	    }
	    else if (e["key"]:"" == "boot")
		e["value"] = BootCommon::UpdateDevice (e["value"]:"");
	    return e;
	});
    }


// general functions


    /**
      * Export bootloader settings to a map
      * @return bootloader settings
      */
    global define map Export () ``{
	map ret = BootCommon::Export ();
	ret["old_efi_entry"] = old_efi_entry;
        ret["remove_old_efi"] = remove_old_efi;
	ret["elilo_conf_filename"] = elilo_conf_filename;
	ret["create_efi_entry"] = create_efi_entry;
        return ret;
    }

    /**
      * Import settings from a map
      * @param settings map of bootloader settings
      */
    global define boolean Import (map<string, any> settings) ``{
	BootCommon::Import (settings);
	old_efi_entry = (string) (settings["old_efi_entry"]:nil);
	remove_old_efi = settings["remove_old_efi"]:true;
	elilo_conf_filename = getEliloConfFilename ();
	create_efi_entry
	    = settings["create_efi_entry"]:(settings["location"]:"" != "");
	return true;
    }


    /**
      * Propose bootloader settings
      */
    global define void Propose () ``{
	import "Product";
	if (! BootCommon::was_proposed)
	{
	    create_efi_entry = true;
	    BootCommon::location
		= sformat ("%1 %2", Product::name, Product::version);
	}
	if (! Stage::initial ())
	    create_efi_entry = true;
	if (Mode::update ())
	    create_efi_entry = false;
	elilo_conf_filename = getEliloConfFilename ();
	BootCommon::DetectDisks ();
	BootCommon::del_parts = BootCommon::getPartitionList (`deleted);

	if (BootCommon::sections == nil || size (BootCommon::sections) == 0)
	{
	    createSections ();
	    BootCommon::kernelCmdLine = Kernel::GetCmdLine ();
	}
	else
	{
	    if (Mode::autoinst ())
	    {
		y2debug ("Nothing to do for propose in AI mode");
	    }
	    else
		fixSections ();
	}
	if (BootCommon::globals == nil || size (BootCommon::globals) == 0)
	{
	    createGlobals ();
	}

	if (BootCommon::location == "" || BootCommon::location == nil)
	{
	    BootCommon::location
		= sformat ("%1 %2", Product::name, Product::version);
	}
	y2milestone ("EFI entry name: %1", BootCommon::location);
        y2milestone ("Proposed sections: %1", BootCommon::sections);
        y2milestone ("Proposed globals: %1", BootCommon::globals);
    }

    /**
      * Read settings from disk
      * @return boolean true on success
      */
    global define boolean Read () ``{
	import "Product";
	boolean efi_entry_found = false;
	elilo_conf_filename = getEliloConfFilename ();
	// copy old elilo.conf from /boot/<something> to /etc in case of upgrade
	// (if /etc/elilo.conf doesn't exist)
	if (SCR::Read (.target.size, "/etc/elilo.conf") <= 0
	    && SCR::Read (.target.size, elilo_conf_filename) > 0)
	{
	    SCR::Execute (.target.bash, sformat (
		"/bin/cp %1 /etc/elilo.conf", elilo_conf_filename));
	}
	SCR::Execute (.target.bash, "/bin/touch /etc/elilo.conf");
	BootCommon::DetectDisks ();
	boolean ret = BootCommon::Read ();
	BootCommon::location
	    = (string)SCR::Read (.sysconfig.bootloader.LOADER_LOCATION);
	// check for meaningless EFI entry name in sysconfig
	if ("mbr" == BootCommon::location || "" == BootCommon::location
	    || nil == BootCommon::location)
	{
	    string efi_path = BootCommon::replaceAll (
		getEliloConfSubpath (), "/", "\\");
	    map efi_status = (map)SCR::Execute (.target.bash_output, sformat (
		"/usr/sbin/efibootmgr |grep \"%1\"", efi_path));
	    if (efi_status["exit"]:0 != 0)
	    {
		BootCommon::location
		    = sformat ("%1 %2", Product::name, Product::version);
	    }
	    else
	    {
		string output = efi_status["stdout"]:"";
		list lines = splitstring (output, "\n");
		output = lines[0]:"";
		if (regexpmatch (output, "Boot.*\* (.*)  HD"))
		{
		    BootCommon::location
			= regexpsub (output, "Boot.*\* (.*)  HD", "\\1");
		    efi_entry_found = true;
		}
		else
		{
		    BootCommon::location
			= sformat ("%1 %2", Product::name, Product::version);
		}
	    }
	}
	else
	{
	    efi_entry_found = 0 == SCR::Execute (.target.bash, sformat (
                "/usr/sbin/efibootmgr |grep \"%1\"", BootCommon::location));
	}
	create_efi_entry = false;
	old_efi_entry = efi_entry_found ? BootCommon::location : (string)nil;
	old_efi_entry = "XXXXX";
	return ret;
    }

    /**
      * Reset bootloader settings
      */
    global define void Reset (boolean init) ``{
	if (Mode::autoinst ())
	    return;
	remove_old_efi = true;
	create_efi_entry = true;
	BootCommon::Reset (init);
    }

    /**
      * Save all bootloader configuration files
      * @return boolean true if success
      */
    global define boolean Save () ``{
	string prefix = "";//getEfiMountPoint ();
	string relative_prefix = "/boot/";//getEliloConfDir () + "/";
	SCR::Execute (.target.bash,
	    sformat ("test -d %1 || /bin/mkdir -p %1", getEliloConfDir ()));
        SCR::Execute (.target.bash, sformat (
	    "test -f %1 && /bin/cp %1 %1.old", elilo_conf_filename));
        SCR::Execute (.target.bash, sformat ("/usr/bin/touch %1",
	    elilo_conf_filename));

	BootCommon::liloRemoveUnexistentSections (prefix, relative_prefix);
        BootCommon::sections = maplist (list<map> s, BootCommon::sections, ``{
            string initrd = (string)lookup_value (s, "initrd", "");
            string title = (string)lookup_value (s, "label", "");
	    map intern = (map)lookup_value (s, "__intern__", $[]);
	    string type = intern["type"]:"";

            if ((type == "linux" || type == "failsafe")
                && BootCommon::write_settings["insert_initrd"]:false)
            {
		initrd = "initrd";
                s = modifySection (s, "initrd", initrd);
            }
            if (initrd != "")
            {
		string check_prefix = "";
		if (substring (initrd, 0, 1) == "/")
		    check_prefix = prefix;
		else
		    check_prefix = relative_prefix;
                if (-1 == SCR::Read (.target.size, check_prefix + initrd))
                    s = modifySection (s, "initrd", nil);
            }
            return s;
        });
	BootCommon::globals = modifySection (BootCommon::globals,
	    "append",
	    BootCommon::updateKernelParams (
		(string)lookup_value (BootCommon::globals, "append", "")
	    )
	);
        boolean ret = BootCommon::Save ();

        return ret;
    }

    /**
      * Display bootloader summary
      * @return a list of summary lines
      */
    global define list<string> Summary () ``{
	// summary text, %1 is bootloader name (eg. LILO)
	list<string> result = [ sformat (_("Boot loader type: %1"),
	    BootCommon::getLoaderName (BootCommon::getLoaderType (false))) ];
	if (BootCommon::location == "" || BootCommon::location == nil
	    || ! create_efi_entry)
	{
	    result
		// summary text
		= add (result, _("Do Not Create EFI Boot Manager Entry"));
	}
	else
	{
	    result = add (result, sformat (
		// summary text, %1 is label of the entry of EFI boot manager
		_("Create EFI Boot Manager Entry %1"),
                BootCommon::location));
	}
        list<string> sects = [];
        foreach (list<map> s, BootCommon::sections, ``{
            string title = (string)
		lookup_value (s, BootCommon::getSectionTitle (), "");
                // section name "suffix" for default section
            string def = title == BootCommon::getDefaultSection ()
		? _(" (default)")
		: "";
            sects = add (sects, sformat ("%1%2", title, def));
        });
        // summary text. %1 is list of bootloader sections
        result = add (result, sformat (_("Sections: %1"),
            mergestring (sects, ", ")));
        return result;
    }

    /**
      * Update read settings to new version of configuration files
      */
    global define void Update () ``{
	updateSections (true);
	updateGlobals ();
    }

    /**
      * Write bootloader settings to disk
      * @return boolean true on success
      */
    global define boolean Write () ``{
	SCR::Execute (.target.bash, "/sbin/elilo");
	if (! create_efi_entry)
	    return true;
	boolean ret = true;
	if (BootCommon::location_changed)
	{
	    // remove existing end old menu entry
	    list<string> efi_entries_to_remove = toset ([
		BootCommon::location,
		remove_old_efi ? old_efi_entry : ""
	    ]);
	    efi_entries_to_remove = filter (string e, efi_entries_to_remove, ``(
		e != nil && e != ""));

	    foreach (string efi_label, efi_entries_to_remove, ``{
		string command = sformat (
"BOOTENTRY=\"`/usr/sbin/efibootmgr |grep \"%1\" |cut -f1 -d\* |cut -c5-`\"
if [ \"$BOOTENTRY\" != \"\" ]; then
  /usr/sbin/efibootmgr -b $BOOTENTRY -B -q
fi", efi_label);
		y2milestone ("Running command %1", command);
		ret = 0 == SCR::Execute (.target.bash, command);
	    });

	    map mp = Storage::GetMountPoints();
	    string boot_dev = mp[getEfiMountPoint (), 0]:"/boot/efi";
	    map splited = Storage::GetDiskPartition (boot_dev);
	    any boot_part = splited["nr"]:0;
	    any boot_disk = splited["disk"]:"";
	    y2milestone ("Disk: %1, Part: %2", boot_disk, boot_part);

	    if (BootCommon::location != "" && BootCommon::location != nil)
	    {
		string bl_command = sformat (
		    "/usr/sbin/efibootmgr -c -L \"%1\" -d %2 -l '\\efi\\SuSE\\elilo.efi' -p %3 -v -w >> /var/log/YaST2/y2logefi 2>&1",
		    BootCommon::location, boot_disk, boot_part);
		string bl_logfile = "/var/log/YaST2/y2logefi";
		boolean command_ret
		    = BootCommon::installBootLoader (bl_command, bl_logfile);
		ret = ret && command_ret;
	    }
	}
	return ret;
    }

    /**
      * Get map of bootloader options
      * @return map of options
      */
    global define map GetOptions () ``{
	map ret = BootCommon::GetOptions ();
	ret[`global] = filter (any o, ret[`global]:[], ``(
	    o != "lba" && o != "lba32" && o != "linear"));
	ret[`global] = merge (ret[`global]:[], [ "noedd30", "chooser",
	    "relocatable"]);
	return ret;
    }

    /**
      * Return map of provided functions
      * @return map map of functions (eg. $["write":``(BootLILO::Write ())])
      */
    global define map GetFunctions () ``{
	return $[
	    "export"	: BootELILO::Export,
	    "import"	: BootELILO::Import,
	    "read"	: BootELILO::Read,
	    "reset"	: BootELILO::Reset,
	    "propose"	: BootELILO::Propose,
	    "save"	: BootELILO::Save,
	    "summary"	: BootELILO::Summary,
	    "update"	: BootELILO::Update,
	    "write"	: BootELILO::Write,
	    "getfiles"	: BootELILO::GetFiles,
	    "setfiles"	: BootELILO::SetFiles,
	];
    }

    /**
      * Initializer of LILO bootloader
      */
    global define void Initializer () ``{
	y2milestone ("Called ELILO initializer");
	BootCommon::current_bootloader_attribs = $[
            "section_title" : "label",
            "kernel_key" : "image",
            "required_packages" : ["elilo"],
            "loader_name" : "ELILO",
            "default_key" : "default",
            "default_type" : `label,
            "timeout_multiplier" : 10,
            "alias_keys" : ["alias"],
	    "convert_function" : BootCommon::liloConvertSection,
            "kernel_params" : $[
                "vga" : "vga",
                "root" : "root",
                `other : "append",
            ],
            "propose" : true,
            "read" : true,
            "scratch" : true,
            "restore_mbr" : false,
            "key_only_once" : true,
	    "bootloader_on_disk" : false,
	    "option_list" : GetOptions (),
	];
	BootCommon::cwm_options = (map<string, map<string,any> >)union (
	    BootCommon::GetOptionMaps (),
	    BootELILO::GetOptionMaps ());
	BootCommon::cwm_widgets = (map<string, map<string,any> >)union (
	    BootCommon::GetWidgetMaps (),
	    BootELILO::GetWidgetMaps ());
    }

    /**
      * Constructor
      */
    global define void BootELILO () ``{
        BootCommon::bootloader_attribs["elilo"] = $[
            "required_packages" : ["elilo"],
            "loader_name" : "ELILO",
	    "initializer" : BootELILO::Initializer,
        ];
    }

}
