/**
 * File:	modules/NetworkModules.ycp
 * Package:	Network configuration
 * Summary:	Hardware config manipulation (/etc/sysconfig/hardware/hwcfg-*)
 * Authors:	Michal Svec <msvec@suse.cz>
 *
 * $Id: NetworkModules.ycp 21023 2005-01-27 16:26:03Z mvidner $
 *
 * The new sysconfig naming is interface (eg. eth0) vs. device
 * (eg. NE2000 card), but historically yast has called them device
 * vs. module.
 *
 * This module is a big mess because originally it was intended to
 * have similar structure to NetworkDevices, with config files
 * categorized in two levels [type, id], but now there is just one
 * level. Lots of code is just copied and unused.
 */

{

module "NetworkModules";
textdomain "network";

import "Arch";
import "Map";
import "NetworkDevices";
import "String";

/**
 * Current module identifier
 * @example eth0, eth1:blah, lo, ...
 */
/* global */ string Name = "";

/**
 * Alias means Driver here (think of modules.conf)
 */
global string Alias = "";
global string Options = "";

/* S/390 stuff */
global string SCRIPTUP = "";
global string SCRIPTUP_ccw = "";
global string SCRIPTUP_ccwgroup = "";
global string SCRIPTDOWN = "";
global string CCW_CHAN_IDS = "";
global string CCW_CHAN_MODE = "";
global string CCW_CHAN_NUM = "";
global string QETH_OPTIONS = "";
global string QETH_IPA_TAKEOVER = "";

/**
 * Current module information
 * @example $["BOOTPROTO":"dhcp", "STARTMODE":"auto"]
 */
global map Current = $[];

/**
 * Modules information
 * @see ReadModules
 */
map<string,map> Modules = $[];

/**
 * Modules information
 * @see ReadModules
 */
map<string,map> OriginalModules = $[];

/**
 * Deleted modules
 */
list<string> Deleted = [];

/**
 * True if modules are already read
 */
boolean initialized = false;

symbol operation = nil;

global list<string> GetFreeNames(string prefix, integer num);
global string GetFreeName(string prefix);

/**
 * Read modules from files
 * @return true if sucess
 */
global define boolean Read() {

    if(initialized == true) return true;

    Modules = $[];

    /* preparation */
    list<string> allfiles = SCR::Dir(.sysconfig.hardware.section);
    if(allfiles == nil) allfiles = [];
    list<string> modules = filter(string file, allfiles, {
	return !regexpmatch(file, "[~]");
    });
    y2debug("modules=%1", modules);

    /*
    modules = filter(string d, modules, {
	return regexpmatch(d, "[a-z][a-z-]*[0-9]*");
    });
    y2debug("modules=%1", modules);
    */

    /* Read modules */
    maplist(string d, modules, {

	if(haskey(Modules, d)) {
	    y2error("module already present: %1", d);
	    return;
	}

	string pth = ".sysconfig.hardware.value.\"" + d + "\"";
	y2debug("pth=%1", pth);
	list<string> values = SCR::Dir(topath(pth));
	y2debug("values=%1", values);

	map config = $[];
	maplist(string val, values, {
	    string item = (string) SCR::Read(topath(pth + "." + val));
	    if(item == nil) return;
	    config[val] = item;
	    y2debug("config=%1", config);
	});

	Modules[d] = config;
    });
    y2debug("Modules=%1", Modules);

    OriginalModules = Modules;
    initialized = true;
    return true;
}

/**
 */
define map Filter(map<string,map> modules, string devregex) {
    if(modules == nil || devregex == nil || devregex == "")
	return modules;

    /* FIXME: broken filtering */
    return modules;

    string regex = "^(" + NetworkDevices::CardRegex[devregex]:devregex + ")[^a-zA-Z].*$";
    y2debug("regex=%1", regex);
    modules = filter(string file, map devmap, modules, {
	return regexpmatch(file, regex) == true;
    });
    y2debug("modules=%1", modules);
    return modules;
}

/**
 */
define map FilterNOT(map<string,map> modules, string devregex) {
    if(modules == nil || devregex == nil || devregex == "")
	return $[];

    /* FIXME: broken filtering */
    return $[];

    string regex = "^(" + NetworkDevices::CardRegex[devregex]:devregex + ")[^a-zA-Z].*$";
    y2debug("regex=%1", regex);
    modules = filter(string file, map devmap, modules, {
	return regexpmatch(file, regex) != true;
    });
    y2debug("modules=%1", modules);
    return modules;
}

/**
 * One-way difference of two maps. Take away the identical items.
 * @param left  left map
 * @param right right map
 * @return the keys of left which are not in right or whose values are
 * not equal in the two maps.
 */
define list MapMinus (map left, map right) {
    list ret = [];
    foreach (any lkey, any lval, left, {
	if (!haskey (right, lkey) || lval != right[lkey]:nil)
	{
	    ret = add (ret, lkey);
	}
    });
    return ret;
}

/**
 * Write modules to files
 * And also do hwdown before and hwup after, on changed ones (#44794)
 * @param devregex regular expression for the module type
 * @return true if success
 * @example NetworkModule::Write("eth|tr");
 */
global define boolean Write(string devregex) {

    y2milestone("Writing configuration");
    y2debug("Modules=%1", Modules);
    y2debug("Deleted=%1", Deleted);

    map Mods = Filter(Modules, devregex);
    map OriginalMods = Filter(OriginalModules, devregex);
    y2milestone("OriginalMods=%1", OriginalMods);
    y2milestone("Mods=%1", Mods);

    /* Check for changes */
    if(Mods == OriginalMods) {
	y2milestone("No changes to %1 modules -> nothing to write", devregex);
	return true;
    }

    /* modules which have to be brought down
       because they have been deleted or changed */
    list todown = MapMinus (OriginalMods, Mods);
    foreach (string down, (list<string>)todown, {
	y2milestone ("hwdown %1", down);
	SCR::Execute (.target.bash, "/sbin/hwdown " + down);
    });

    /* remove deleted modules */
    y2milestone("Deleted=%1", Deleted);
    foreach(string d, Deleted, {
	// if(!haskey(OriginalMods, d)) return;
	/* delete config file */
	path p = topath(".sysconfig.hardware.section.\"" + d + "\"");
	y2debug("deleting: %1", p);
	SCR::Write(p, nil);
    });

    /* Modules with chmod=0600 */
    list chmod = [];

    /* write all modules */
    maplist(string d, map devmap, (map<string,map>) Mods, {

	/* write sysconfig */
	string p = ".sysconfig.hardware.value.\"" + d + "\".";

	/* write all keys to config */
	maplist(string k, (list<string>) Map::Keys(devmap), {
	    SCR::Write(topath(p + k), devmap[k]:"");
	});
    });

    /* Finish him */
    SCR::Write(.sysconfig.hardware, nil);

    /* modules which have to be brought up
       because they have been added or changed */
    list toup = MapMinus (Mods, OriginalMods);
    foreach (string up, (list<string>)toup, {
	y2milestone ("hwup %1", up);
	SCR::Execute (.target.bash, "/sbin/hwup " + up);
    });

    return true;
}

global boolean AddM() {

    Alias = "";
    Options = "";

    SCRIPTUP = "";
    SCRIPTUP_ccw = "";
    SCRIPTUP_ccwgroup = "";
    SCRIPTDOWN = "";
    CCW_CHAN_IDS = "";
    CCW_CHAN_MODE = "";
    CCW_CHAN_NUM = "";
    QETH_OPTIONS = "";
    QETH_IPA_TAKEOVER = "";

    return true;
}

global boolean SelectM(string name) {
    if(name == nil || name == "" || !haskey(Modules, name)) {
	y2error("Wrong module: %1", name);
	return false;
    }

    Name = name;

    Alias = Modules[name, "MODULE"]:"";
    Options = Modules[name, "MODULE_OPTIONS"]:"";

    SCRIPTUP = Modules[name, "SCRIPTUP"]:"";
    SCRIPTUP_ccw = Modules[name, "SCRIPTUP_ccw"]:"";
    SCRIPTUP_ccwgroup = Modules[name, "SCRIPTUP_ccwgroup"]:"";
    SCRIPTDOWN = Modules[name, "SCRIPTDOWN"]:"";
    CCW_CHAN_IDS = Modules[name, "CCW_CHAN_IDS"]:"";
    CCW_CHAN_MODE = Modules[name, "CCW_CHAN_MODE"]:"";
    CCW_CHAN_NUM = Modules[name, "CCW_CHAN_NUM"]:"";
    QETH_OPTIONS = Modules[name, "QETH_OPTIONS"]:"";
    QETH_IPA_TAKEOVER = Modules[name, "QETH_IPA_TAKEOVER"]:"";

    y2milestone("Modules=%1", Modules);
    return true;
}

/**
 * Commit data to Modules[name]
 * @param name hwcfg name
 */
global boolean ChangeM(string name) {
    if(name == nil || name == "" /* || !haskey(Modules, name) */) {
	y2error("Wrong module: %1", name);
	return false;
    }

    if(!haskey(Modules, name)) Modules[name] = $[];

    Modules[name, "MODULE"] = Alias;
    Modules[name, "MODULE_OPTIONS"] = Options;
    Modules[name, "STARTMODE"] = "auto";

    if(Arch::s390 ()) {
	Modules[name, "SCRIPTUP"] = SCRIPTUP;
	Modules[name, "SCRIPTUP_ccw"] = SCRIPTUP_ccw;
	Modules[name, "SCRIPTUP_ccwgroup"] = SCRIPTUP_ccwgroup;
	Modules[name, "SCRIPTDOWN"] = SCRIPTDOWN;
	Modules[name, "CCW_CHAN_IDS"] = CCW_CHAN_IDS;
	Modules[name, "CCW_CHAN_MODE"] = CCW_CHAN_MODE;
	Modules[name, "CCW_CHAN_NUM"] = CCW_CHAN_NUM;
	if (QETH_OPTIONS != nil && QETH_OPTIONS != "")
	    Modules[name, "QETH_OPTIONS"] = QETH_OPTIONS;
	if (QETH_IPA_TAKEOVER != nil && QETH_IPA_TAKEOVER != "")
	    Modules[name, "QETH_IPA_TAKEOVER"] = QETH_IPA_TAKEOVER;
    }

    y2milestone("Modules=%1", Modules);
    return true;
}

global boolean DeleteM(string name) {
    if(name == nil || name == "" || !haskey(Modules, name)) {
	y2error("Wrong module: %1", name);
	return false;
    }
    Deleted = add(Deleted, name);
    Modules = remove(Modules, name);
    y2milestone("Deleted=%1", Deleted);
    y2milestone("Modules=%1", Modules);
    return true;
}

global string GetFreeName(string prefix) {
    return GetFreeNames(prefix, 1)[0]:"";
}

global list<string> GetFreeNames(string prefix, integer num) {
    y2debug("Modules=%1", Modules);

    list<string> mods = (list<string>) Map::Keys(Modules);
    y2debug("mods=%1", mods);

    integer i = 0;
    integer count = 0;
    list<string> ret = [];

    /* Remaining numbered modules */
    while(count < num) {
	string ii = sformat("%1%2", prefix, i);
	if(!contains(mods, ii)) {
	    ret = add(ret, ii);
	    count = count + 1;
	}
	i = i + 1;
    }

    y2debug("Free modules=%1", ret);
    return ret;
}

global define list ListM(string devregex) {

    list ret = [];
//    if(devregex == "" || devregex == nil) {
	maplist(string n, map d, Modules, {
	    ret[size(ret)] = n;
	});
/*    }
    else {
	string regex = "^(" + NetworkDevices::CardRegex[devregex]:devregex + ")[0-9]*$";
	maplist(string t, map d, Modules, {
	    if(regexpmatch(t, regex)) {
		maplist(string n, (list<string>) Map::Keys(d), {
		    ret[size(ret)] = NetworkDevices::device_name(t,n);
		});
	    }
	});
    }
*/

    y2debug("ret=%1", ret);
    return ret;
}

/**
 * Import data
 * @param settings settings to be imported
 * @return true on success
 */
global define boolean Import(string devregex, map<string,map> modules) {
    map Mods = FilterNOT(Modules, devregex);
    y2debug("Mods=%1", Mods);
    Modules = (map<string,map>) union(Mods, modules);
    OriginalModules = nil;
    return true;
}

/**
 * Export data
 * @return dumped settings (later acceptable by Import())
 */
global define map<string,map> Export(string devregex) {
    map Mods = Filter(Modules, devregex);
    y2debug("Mods=%1", Mods);
    return (map<string,map>) Mods;
}

/**
 * Were the modules changed?
 * @return true if modified
 */
global define boolean Modified(string devregex) {
    map Mods = Filter(Modules, devregex);
    map OriginalMods = Filter(OriginalModules, devregex);
    y2debug("OriginalMods=%1", OriginalMods);
    y2debug("Mods=%1", Mods);
    return Mods == OriginalMods;
}

global define list GetFreeModules(string type, integer num) {
    y2debug("Modules=%1", Modules);
    y2debug("type,num=%1,%2", type, num);
    y2debug("Modules[%1]=%2", type, Modules[type]:$[]);

    list curdevs = Map::Keys(Modules[type]:$[]);
    y2debug("curdevs=%1", curdevs);

    integer i = 0;
    integer count = 0;
    list ret = [];

    /* Hotpluggable modules */
    if(NetworkDevices::IsHotplug(type) && !contains(curdevs, "")) {
	y2debug("Added simple hotplug module");
	count = count + 1;
	ret = add(ret, "");
    }

    /* Remaining numbered modules */
    while(count < num) {
	string ii = sformat("%1", i);
	if(!contains(curdevs, ii)) {
	    ret = add(ret, ii);
	    count = count + 1;
	}
	i = i + 1;
    }

    y2debug("Free modules=%1", ret);
    return ret;
}

/**
 * Get modules of the given type
 * @param type modules type ("" for all)
 * @return list of found modules
 */
global define list List(string devregex) {

    list ret = [];
    if(devregex == "" || devregex == nil) {
	maplist(string t, map d, Modules, {
	    maplist(string n, (list<string>) Map::Keys(d), {
		ret[size(ret)] = NetworkDevices::device_name(t,n);
	    });
	});
    }
    else {
	string regex = "^(" + NetworkDevices::CardRegex[devregex]:devregex + ")[0-9]*$";
	maplist(string t, map d, Modules, {
	    if(regexpmatch(t, regex)) {
		maplist(string n, (list<string>) Map::Keys(d), {
		    ret[size(ret)] = NetworkDevices::device_name(t,n);
		});
	    }
	});
	/*
	map d = Modules[type]:$[];
	maplist(string n, Map::Keys(d), {ret[size(ret)] = NetworkDevices::device_name(type,n);});
	*/
    }

    y2debug("ret=%1", ret);
    return ret;
}

/**
 * Call HwUp for hardware that is not handled by hotplug (#44626)
 */
global boolean HwUp () {
    boolean ok = true;
    foreach (string hwcfg_name, any v, Modules, {
	if (regexpmatch (hwcfg_name, "^static"))
	{
	    ok = SCR::Execute (.target.bash, "hwup "+ hwcfg_name) == 0 && ok;
	}
    });
    return ok;
}

/**
 * #46803: forbid "/" (filename), maybe also "-" (separator) "_" (escape)
 */
global string ValidCharsHwcfg () {
    return String::ValidCharsFilename ();
}


/* EOF */
}
