ACC SHELL

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

/**
 * File:	modules/NetworkInterfaces.ycp
 * Package:	Network configuration
 * Summary:	Interface manipulation (/etc/sysconfig/network/ifcfg-*)
 * Authors:	Michal Svec <msvec@suse.cz>
 *
 * $Id: NetworkInterfaces.ycp 43062 2007-12-13 16:12:26Z mzugec $
 *
 * The new sysconfig naming is interface (eg. eth0) vs. device
 * (eg. NE2000 card), but historically yast has called them device
 * vs. module.
 */

{

module "NetworkInterfaces";
textdomain "base";

import "Arch";
import "Map";
import "Mode";
import "Netmask";
import "String";

/**
 * False suppresses tones of logs 'NetworkInterfaces.ycp:ABC Check(eth,id-00:aa:bb:cc:dd:ee,)'
 */
global boolean report_every_check = true;

/**
 * Current device identifier
 * @example eth0, eth1:blah, lo, ...
 * Add, Edit and Delete copy the requested device info (via Select)
 * to Name and Current,
 * Commit puts it back
 */
global string Name = "";

// value is not just string, can be a map for aliases 
typedef map<string, any> ifcfg_t;
typedef map<string, map<string, ifcfg_t> > devices_t;

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

/**
 * Interface information:
 * Devices[string type, string id] is a map with the contents of
 * ifcfg-<i>type</i>-<i>id</i>. Separating type from id is useful because
 * the type determines the fields of the interface file.
 * Multiple addresses for an interface are nested maps
 * [type, id, "_aliases", aid]
 * @see Read
 */
devices_t Devices = $[];

/**
 * Devices information
 * @see Read
 */
devices_t OriginalDevices = $[];

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

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

/**
 * Which operation is pending?
 */
/* global */ symbol operation = nil;
// FIXME: used in lan/address.ycp (#17346) -> "global"

/**
 * Predefined network card regular expressions
 */
global map<string,string> CardRegex = $[
    "netcard"	: "arc|ath|bnep|ci|ctc|dummy|bond|escon|eth|fddi|ficon|hsi|qeth|lcs|iucv|myri|tr|usb|wlan|xp|vlan|br|tun|tap|ib",
    "modem"	: "ppp|modem",
    "isdn"	: "isdn|ippp",
    "dsl"	: "dsl",
    /* other: irlan|lo|plip|... */
];

// define string HotplugRegex(list<string> devs);

/**
 * Supported hotplug types
 */
list<string> HotplugTypes = [ "pcmcia", "usb"/*, "pci" */];

/**
 * Create a list of hot-pluggable device names for the given devices
 */
define string HotplugRegex(list<string> devs) {
    string ret = "";
    foreach(string dev, devs, {
	foreach(string hot, HotplugTypes, {
	    ret = ret + "|" + dev + "-" + hot + "|" + dev + "-" + hot + "-";
	});
    });
    return ret;
}

/**
 * Predefined network device regular expressions
 */
global map<string,string> DeviceRegex = $[
    /* device types */
    "netcard"	: CardRegex["netcard"]:"" + HotplugRegex(["ath", "eth", "tr", "wlan"]) + "|usb-usb|usb-usb-",
    "modem"	: CardRegex["modem"]:"",
    "isdn"	: CardRegex["isdn"]:"" + HotplugRegex(["isdn", "ippp"]),
    "dsl"	: CardRegex["dsl"]:"",
    /* device groups */
    "dialup"	: CardRegex["modem"]:"" + "|" + CardRegex["dsl"]:"" + "|" + CardRegex["isdn"]:"",
];

/**
 * Types in order from fastest to slowest.
 * @see FastestRegexps
 */
map<integer,string> FastestTypes = $[
    1 : "dsl",
    2 : "isdn",
    3 : "modem",
    4 : "netcard"
];

/**
 * @see Push
 */
map stack = $[];

// -------------------- components of configuration names --------------------

/**
 * A single character used to separate alias id
 */
string alias_separator = "#";

/**
 * ifcfg name = type + id + alias_id
 * If id is numeric, it is not separated from type, otherwise separated by "-"
 * Id may be empty
 * Alias_id, if nonempty, is separated by alias_separator
 */
string ifcfg_name_regex =
    "^" +
    // ip6: #48696
    "(ip6tnl|mip6mnha|[" + String::CAlpha() + "]+)" + "-?" +
    "([^" + alias_separator + "]*)" + alias_separator + "?" +
    "(.*)" +
    "$";

string ifcfg_part (string ifcfg, string part) {
    if (regexpmatch (ifcfg, ifcfg_name_regex) != true)
    {
	return "";
    }
    string ret = regexpsub (ifcfg, ifcfg_name_regex, "\\" + part);
    return (ret == nil)? "": ret;
}

/**
 * Return a device type
 * @param dev device
 * @return device type
 * @example device_type("eth1") -> "eth"
 * @example device_type("eth-pcmcia-0") -> "eth-pcmcia"
 */
global string device_type (string dev) {
    return ifcfg_part (dev, "1");
}

global string GetType(string dev) {
 string type = ifcfg_part (dev, "1");
 foreach(string dev_type,map confs, Devices, {
  if (haskey(confs, dev)){
   string int_type = confs[dev, "INTERFACETYPE"]:"";
   if (size(confs[dev, "TUNNEL"]:"")>0) int_type = confs[dev, "TUNNEL"]:"";
   if(size(int_type)>0) type = int_type;
  }
 });
 return type;
}

/**
 * Return device type in human readable form :-)
 * @param dev device
 * @return device type 
 * @example GetDeviceType(eth-bus-pci-0000:01:07.0) -> "Network Card"
 * @example GetDeviceType(modem0) -> "Modem"
 */
global string GetDeviceType(string dev) {
    if (regexpmatch(dev,"^" + DeviceRegex["netcard"]:"")) {
	return(_("Network Card"));
    }
    else if (regexpmatch(dev,"^" + DeviceRegex["modem"]:"")) {
	return(_("Modem"));
    }
    else if (regexpmatch(dev,"^" + DeviceRegex["isdn"]:"")) {
	return(_("ISDN"));
    }
    else if (regexpmatch(dev,"^" + DeviceRegex["dsl"]:"")) {
	return(_("DSL"));
    }
    else return(_("Unknown"));
}

/**
 * Return a device number
 * @param dev device
 * @return device number
 * @example device_num("eth1") -> "1"
 * @example device_num("lo") -> ""
 */
global string device_num (string dev) {
    return ifcfg_part (dev, "2");
}

/**
 * Return a device alias number
 * @param dev device
 * @return alias number
 * @example alias_num("eth1#2") -> "2"
 * @example alias_num("eth1#blah") -> "blah"
 */
global string alias_num (string dev) {
    return ifcfg_part (dev, "3");
}

/**
 * Create a device name from its type and number
 * @param typ device type
 * @param num device number
 * @return device name
 * @example device_name("eth", "1") -> "eth1"
 * @example device_name("lo", "") -> "lo"
 */
global string device_name (string typ, string num) {
    if(typ == nil || typ == "") {
	y2error("wrong type: %1", typ);
	return nil;
    }
    if(num == nil /* || num < 0 */) {
	y2error("wrong number: %1", num);
	return nil;
    }
    /* FIXME: devname
    if(IsHotplug(typ) && num != "") return sformat("%1-%2", typ, num);
    return sformat("%1%2", typ, num);
    */
    if(regexpmatch(num, "^[0-9]*$"))
	return sformat("%1%2", typ, num);
    return sformat("%1-%2", typ, num);
}

/**
 * Create a alias name from its type and numbers
 * @param typ device type
 * @param num device number
 * @param anum alias number
 * @return alias name
 * @example alias_name("eth", "1", "2") -> "eth1#2"
 */
global string alias_name (string typ, string num, string anum) {
    if(typ == nil || typ == "") {
	y2error("wrong type: %1", typ);
	return nil;
    }
    if(num == nil /* || num < 0 */) {
	y2error("wrong number: %1", num);
	return nil;
    }
    if(anum == nil || anum == "") {
	y2error("wrong alias number: %1", anum);
	return nil;
    }
    return sformat("%1#%2", device_name(typ, num), anum);
}

/**
 * Test hotplugability of a device
 * @param type device type
 * @return true if hotpluggable
 */
global boolean IsHotplug (string type) {
    if(type == "" || type == nil) return false;
    if(regexpmatch(type, "(pcmcia|usb|pci)$")) return true;
    return false;
}

/**
 * Return matching inteface for this hardware ID (uses getcfg-interface)
 * @param dev unique device string
 * return interface name
 * @example MatchInterface("eth-id-00:01:DE:AD:BE:EF") -> "eth0"
 */
/*
global string MatchInterface(string dev) {
    string cmd = "getcfg-interface " + dev;
    map dn =(map) SCR::Execute(.target.bash_output, cmd);
    string devname = deletechars(dn["stdout"]:"", "\n");

    return devname;
}
*/
/**
 * Test whether device is connected (Link:up)
 * The info is taken from sysfs
 * @param dev unique device string
 * @return true if connected
 */
global boolean IsConnected(string dev) {
    if (!Mode::testsuite()) {
//        string iface = MatchInterface(dev);
        string cmd = "cat /sys/class/net/" + dev + "/carrier";

        map ret = (map) SCR::Execute(.target.bash_output, cmd);
	y2milestone("Sysfs returned %1", ret); 

        return ( deletechars( ret["stdout"]:"", "\n") == "1" ? true : false );
    }
    else
        //Assume all devices are connected in testsuite mode
	return true;

}

/**
 * Return real type of the device (incl. PCMCIA, USB, ...)
 * @param type basic device type
 * @param hotplug hot plug type
 * @return real type
 * @example RealType("eth", "usb") -> "eth-usb"
 */
global string RealType (string type, string hotplug) {

    y2debug("type=%1", type);
    if(type == "" || type == nil) {
	y2error("Wrong type: %1", type);
	return "eth";
    }

    if(hotplug == "" || hotplug == nil)
	return type;

    string realtype = type + "-" + hotplug;
    y2debug("realtype=%1", realtype);
    return realtype;
}

// ---------------------------------------------------------------------------

/**
 * STARTMODE: onboot, on and boot are aliases for auto
 */
global map<string, any> CanonicalizeStartmode (map<string, any> ifcfg) {
    map <string, string> canonicalize_startmode = $[
	"on": "auto",
	"boot": "auto",
	"onboot": "auto",
	];
    string startmode = ifcfg["STARTMODE"]:"";
    ifcfg["STARTMODE"] = canonicalize_startmode[startmode]:startmode;
    return ifcfg;
}

/**
 * Canonicalize netmask data (#46885)
 * Sysconfig allows:
 * IPADDR=10.0.0.1/8
 * IPADDR=10.0.0.1 PREFIXLEN=8
 * IPADDR=10.0.0.1 NETMASK=255.0.0.0
 * (IPADDR overrides PREFIXLEN, NETMASK used only if prefix length unspecified)
 * If prefix length and NETMASK are unspecified, 32 is implied.
 * Canonicalize it to
 * IPADDR=10.0.0.1 PREFIXLEN= NETMASK=255.0.0.0
 * @param ifcfg a map containing IPADDR and possibly NETMASK, PREFIXLEN
 * and possibly other fields
 * @return the map with IPADDR, NETMASK adjusted; PREFIXLEN ""
 * others unchanged. If IPADDR is empty, return the original.
 */
global map<string, any> CanonicalizeIP (map<string, any> ifcfg) {
    if (ifcfg == nil)
    {
	return nil;
    }

    list<string> ip_and_prefix = splitstring (ifcfg["IPADDR"]:"", "/");
    string ipaddr = ip_and_prefix[0]:"";
    if (ipaddr == "")		// DHCP or inconsistent
    {
	return ifcfg;
    }
    string prefixlen = ip_and_prefix[1]:"";
    if (prefixlen == "")
    {
	prefixlen = ifcfg["PREFIXLEN"]:"";
    }
    if (prefixlen == "")
    {
	prefixlen = tostring (Netmask::ToBits (ifcfg["NETMASK"]:""));
    }

    // Now we have ipaddr and prefixlen
    // Let's compute the rest
    string netmask = Netmask::FromBits (tointeger (prefixlen));
    map<string, any> ret = ifcfg;
    ret["IPADDR"] = ipaddr;
    ret["PREFIXLEN"] = prefixlen;
    ret["NETMASK"] = netmask;
    return ret;
}

list<string> SensitiveFields = [
    "WIRELESS_WPA_PASSWORD",
    "WIRELESS_WPA_PSK",
    // the unnumbered one should be empty but just in case
    "WIRELESS_KEY",
    "WIRELESS_KEY_0",
    "WIRELESS_KEY_1",
    "WIRELESS_KEY_2",
    "WIRELESS_KEY_3",
    ];

/**
 * Conceal secret information, such as WEP keys, so that the output
 * can be passed to y2log and bugzilla.
 * @param ifcfg one ifcfg
 * @return ifcfg with secret fields masked out
 */
global map ConcealSecrets1 (map<string, any> ifcfg) {
    if (ifcfg == nil)
    {
	return nil;
    }
    map out = mapmap (string k, any v, ifcfg, {
	if (contains (SensitiveFields, k) && v != "")
	{
	    v = "CONCEALED";
	}
	return $[k: v];
    });
    return out;
}

/**
 * Conceal secret information, such as WEP keys, so that the output
 * can be passed to y2log and bugzilla. (#65741)
 * @param devs a two-level map of ifcfgs like Devices
 * @return ifcfgs with secret fields masked out
 */
global map ConcealSecrets (map devs) {
    if (devs == nil)
    {
	return nil;
    }
    map out = mapmap (string t, map<string, map<string, any> > tdevs,
		      (map<string, map<string, map<string, any> > >) devs, {
	map tout = mapmap (string id, map<string, any> ifcfg, tdevs, {
	    return $[id: ConcealSecrets1 (ifcfg)];
	});
	return $[t: tout];
    });
    return out;
}

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

    // initialized = true; // FIXME
    if(initialized == true) return true;

    Devices = $[];

    /* Variables which could be suffixed and thus duplicated */
    list Locals = [ "IPADDR", "REMOTE_IPADDR", "NETMASK", "PREFIXLEN",
	    "BROADCAST", "SCOPE", "LABEL", "IP_OPTIONS" ];

    /* preparation */
    list<string> allfiles = SCR::Dir(.network.section);
    if(allfiles == nil) allfiles = [];
    list<string> devices = filter(string file, allfiles, {
	return !regexpmatch(file, "[~]");
    });
    y2debug("devices=%1", devices);
    /* FIXME: devname
    devices = filter(string d, devices, {
	return regexpmatch(d, "[a-z][a-z-]*[0-9]*");
    });
    y2debug("devices=%1", devices);
    */

    /* Read devices */
    maplist(string d, devices, {
	string devtype = GetType(d);

//	string devnum = "";
	// if(regexpmatch(d, "[a-z][a-z-]*[0-9]+"))
//	    devnum = sformat("%1", device_num(d));
//	y2debug("devnum=%1", devnum);

	map<string, ifcfg_t> dev = Devices[devtype]:$[];
	string pth = ".network.value.\"" + d + "\"";
	y2debug("pth=%1", pth);
	list<string> values = SCR::Dir(topath(pth));
	y2debug("values=%1", values);

	map<string, any> config = $[];
	maplist(string val, values, {
	    string item = (string) SCR::Read(topath(pth + "." + val));
	    y2debug("item=%1", item);
	    if(item == nil) return;
	    /* No underscore '_' -> global */
	    /* Also temporarily standard globals */
	    if(find(val, "_") < 0 || contains(Locals, val)) {
		config[val] = item;
		return;
	    }
	    /* Try to strip _suffix */
	    string v = substring(val, 0, findlastof(val, "_"));
	    string s = substring(val, findlastof(val, "_"));
	    if(size(s) > 1) s = substring(s, 1);
	    y2milestone("%1:%2:%3", val, v, s);
	    /* Global */
	    if(!contains(Locals, v))
		config[val] = item;
	    /* Local */
	    else {
		map _aliases = config["_aliases"]:$[];
		map suf = _aliases[s]:$[];
		suf[v] = item;
		_aliases[s] = suf;
		config["_aliases"] = _aliases;
	    }
	});
	y2milestone("config=%1", ConcealSecrets1 (config));

	// canonicalize, #46885
	map <string, map> caliases = mapmap (string a, map<string, any> c, (map<string,map<string, any> >)config["_aliases"]:$[], {
	    return $[a: CanonicalizeIP (c)];
	});
	if (caliases != $[])	// unconditionally?
	{
	    config["_aliases"] = caliases;
	}
	config = CanonicalizeIP (config);
	config = CanonicalizeStartmode (config);

        if (size(config["INTERFACETYPE"]:"")>0) {
		 y2milestone("option INTERFACETYPE changes devtype from %1 to %2", devtype, config["INTERFACETYPE"]:"");
		 devtype=config["INTERFACETYPE"]:"";
		 dev = Devices[devtype]:$[];
		}
/*
	if(haskey(dev, devnum)) {
	    y2error("device already present: %1", devnum);
	    return;
	}
*/
	dev[d] = config;
	Devices[devtype] = dev;
    });
    y2debug("Devices=%1", Devices);

    OriginalDevices = Devices;
    initialized = true;
    return true;
}

/**
 * re-read all settings again from system
 * for creating new proposal from scratch (#170558)
 */
global boolean CleanCacheRead(){
 initialized = false;
 return Read();
}


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

    string regex = "^(" + DeviceRegex[devregex]:devregex + ")[0-9]*$";
    y2debug("regex=%1", regex);
    devices = filter(string file, map devmap, devices, {
	return regexpmatch(file, regex) == true;
    });
    y2debug("devices=%1", devices);
    return devices;
}

/**
 * Used in BuildSummary, BuildOverview
 */
global map<string,map> FilterDevices (string devregex) {
    return Filter (Devices, devregex);
}

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

    string regex = "^(" + DeviceRegex[devregex]:devregex + ")[0-9]*$";
    y2debug("regex=%1", regex);
    devices = filter(string file, map devmap, devices, {
	return regexpmatch(file, regex) != true;
    });
    y2debug("devices=%1", devices);
    return devices;
}

/**
 * For the NAME field, filter out characters that will case problems
 * for the shell or the ini agent. (#72164)
 * It should be done in more places but this field is most susceptible.
 * @param s a string
 * @return s with some characters removed, esp. the single quote
 */
string ShellSafe (string s) {
    s = filterchars (s, String::CGraph () + " ");
    return deletechars (s, "'");
}

/**
 * SCR::Write (p, ShellSafe (s)) and if s had to be changed,
 * log the _path_ (not the value, for privacy).
 * @see ShellSafe
 * @param p SCR path
 * @param s value
 * @return success
 */
boolean ShellSafeWrite (path p, string s) {
    string safe_s = ShellSafe (s);
    if (safe_s != s)
    {
	y2milestone ("Changed: %1", p);
    }
    return SCR::Write (p, safe_s);
}

/**
 * Write devices to files
 * @param devregex regular expression for the device type
 * @return true if success
 * @example NetworkDevice::Write("eth|tr");
 */
global define boolean Write(string devregex) {
    y2milestone("Writing configuration");
    y2debug("Devices=%1", Devices);
    y2debug("Deleted=%1", Deleted);

    map Devs = Filter(Devices, devregex);
    map OriginalDevs = Filter(OriginalDevices, devregex);
    y2milestone("OriginalDevs=%1", ConcealSecrets (OriginalDevs));
    y2milestone("Devs=%1", ConcealSecrets (Devs));

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

    /* remove deleted devices */
    y2milestone("Deleted=%1", Deleted);
    foreach(string d, Deleted, {
	// if(!haskey(OriginalDevs, d)) return;
	string anum = alias_num (d);
	if (anum == "")
	{
	    /* delete config file */
	    path p = add (.network.section, d);
	    y2debug("deleting: %1", p);
	    SCR::Write(p, nil);
	}
	else
	{
	    string typ = device_type (d);
	    string num = device_num (d);
	    string dev = device_name (typ, num);
	    path base = add (.network.value, dev);
	    // look in OriginalDevs because we need to catch all variables
	    // of the alias
	    foreach (string key, any dummy, OriginalDevs[typ, dev, "_aliases", anum]:$[], {
		path p = add (base, key + "_" + anum);
		y2debug ("deleting: %1", p);
		SCR::Write (p, nil);
	    });
	}
    });

    /* Devices with chmod=0600 */
    list<string> chmod = [];

    /* write all devices */
    maplist(string typ, map<string,map<string,any> > devsmap, (map<string, map<string, map<string, any> > >) Devs, {
	maplist(string config, map<string,any> devmap, devsmap, {
	   if (devmap == OriginalDevs[typ,config]:$[])
		return;
	    /* write sysconfig */
	    string p = ".network.value.\"" + config + "\".";


	if (size(devmap["IPADDR"]:"")>0 && size(devmap["NETMASK"]:"")>0){
	 devmap["IPADDR"] = sformat("%1/%2", devmap["IPADDR"]:"", Netmask::ToBits(devmap["NETMASK"]:""));
	 devmap=remove(devmap, "NETMASK");
	 //TODO : delete NETMASK from config file
	} else {
	 if (size(devmap["IPADDR"]:"")>0 && size(devmap["PREFIXLEN"]:"")>0){
	  devmap["IPADDR"] = sformat("%1/%2", devmap["IPADDR"]:"", devmap["PREFIXLEN"]:"");
	  devmap=remove(devmap, "PREFIXLEN");
	 //TODO : delete PREFIXLEN from config file
	 }
	}


	    /* write all keys to config */
	    maplist(string k, (list<string>) Map::Keys(devmap), {
		/* Write aliases */
		if(k == "_aliases") {
		    maplist(string anum, map<string,string> amap, devmap[k]:$[], {
			// Normally defaulting the label would be done
			// when creating the map, not here when
			// writing, but we create it in 2 ways so it's
			// better here. Actually it does not work because
			// the edit dialog nukes LABEL :-(
//			boolean seen_label = false;
		if (size(amap["IPADDR"]:"")>0 && size(amap["NETMASK"]:"")>0){
		 amap["IPADDR"] = sformat("%1/%2", amap["IPADDR"]:"", Netmask::ToBits(amap["NETMASK"]:""));
		 amap=remove(amap, "NETMASK");
		 //TODO : delete NETMASK from config file
		} else {
		 if (size(amap["IPADDR"]:"")>0 && size(amap["PREFIXLEN"]:"")>0){
		  amap["IPADDR"] = sformat("%1/%2", amap["IPADDR"]:"", amap["PREFIXLEN"]:"");
		  amap=remove(amap, "PREFIXLEN");
		 //TODO : delete PREFIXLEN from config file
		 }
		}

			maplist(string ak, string av, amap, {
			    string akk = ak + "_" + anum;
			    ShellSafeWrite (topath (p + akk), av);
//			    seen_label = seen_label || ak == "LABEL";
			});
/*
			if (!seen_label)
			{
			    ShellSafeWrite (topath (p + ("LABEL_" + anum)), anum);
			}
*/
		    });
		}
		/* Write regular keys */
		else
		    ShellSafeWrite (topath (p + k), devmap[k]:"");
	    });

	    /* update libhd unique number * /
	    // FIXME: move it somewhere else: hardware
	    string unq = devmap["UNIQUE"]:"";
	    if(unq != "") SCR::Write(.probe.status.configured, unq, `yes);
	    */

	    /* 0600 if contains encryption key (#24842) */
	    boolean has_key = find (string k, SensitiveFields,
				    ``( devmap[k]:"" != "" )) != nil;
	    string file = "/etc/sysconfig/network/ifcfg-" + config;
	    y2debug("Permission change: %1, %2", has_key, file);
	    if(has_key) {
		y2debug("CHANGED");
		chmod = add(chmod, file);
	    }
	});
    });

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

    /* CHMOD */
    y2debug("chmod=%1", chmod);
    maplist(string file, chmod, {
	y2debug("changing: %1", file);
	SCR::Execute(.target.bash, "/bin/chmod 0600 " + file);
    });

    // Deleted = [];
    // OriginalDevices = Devices;
    // Cannot do it because we have written only part of Devices.
    // This module should be rewritten to objects.
    return true;
}

/**
 * Import data
 * @param settings settings to be imported
 * @return true on success
 */
global define boolean Import(string devregex, map<string,map> devices) {
    map Devs = FilterNOT(Devices, devregex);
    y2debug("Devs=%1", Devs);

    devices = mapmap(string typ, map devsmap, devices, {
	return $[typ: mapmap(string num, map<string, any> config, (map<string,map<string, any> >) devsmap, {
	    config = CanonicalizeIP (config);
	    config = CanonicalizeStartmode (config);
	    return $[num: config];
	})];
    });

    Devices = (devices_t) union(Devs, devices);
    OriginalDevices = nil;
    return true;
}

/**
 * Return supported network device types (for type netcard)
 * for this hardware
 */
global list<string> GetDeviceTypes(){
 list<string> dev_types = ["eth", "tr", "vlan", "br", "tun", "tap", "ib", "bond"];
 if(Arch::s390 ()) dev_types = (list<string>) merge(dev_types, [ "hsi", "ctc", "escon", "ficon", "iucv", "qeth", "lcs"]);
	else dev_types = (list<string>) merge(dev_types, [ "arc", "bnep", "dummy", "fddi", "myri", "usb", "wlan" ]);
 if(Arch::ia64 ()) dev_types = add(dev_types, "xp");

 foreach(string device,dev_types, {
 if (!contains(splitstring(DeviceRegex["netcard"]:"", "|"), device)) y2error("%1 is not contained in DeviceRegex[\"netcard\"]", device);
 });
 return dev_types;
}

/**
 * Return textual device type
 * @param type device type
 * @param type description type 
 * @return textual form of device type
 * @example GetDevTypeDescription("eth", false) -> "Ethernet"
 * @example GetDevTypeDescription("eth", true)  -> "Ethernet Network Card"
 */
global string GetDevTypeDescription(string type, boolean longdescr){
    if(issubstring(type, "#"))
	/* Device type label */
	// This is what used to be Virtual Interface (eth0:1).
	// In our data model, additional addresses for an interface
	// are represented as its sub-interfaces.
	// And also we frequently confuse "device" and "interface"
	// :-(
	return _("Additional Address");

    map device_types = $[
	/* Device type label */
	"arc"	: [ _("ARCnet"),  _("ARCnet Network Card") ],
	/* Device type label */
	"atm"	: [ _("ATM"), _("Asynchronous Transfer Mode (ATM)") ],
	/* Device type label */
	"bnep"	: [ _("Bluetooth"), _("Bluetooth Connection") ],
	/* Device type label */
	"bond"	: [ _("Bond"), _("Bond Network") ],
	/* Device type label */
	"ci"	: [ _("CLAW"), _("Common Link Access for Workstation (CLAW)") ],
	/* Device type label */
	"contr"	: [ _("ISDN"), _("ISDN Card") ],
	/* Device type label */
	"ctc"	: [ _("CTC"), _("Channel to Channel Interface (CTC)") ],
	/* Device type label */
	"dsl"	: [ _("DSL"), _("DSL Connection") ],
	/* Device type label */
	"dummy"	: [ _("Dummy"), _("Dummy Network Device") ],
	/* Device type label */
	"escon"	: [ _("ESCON"), _("Enterprise System Connector (ESCON)") ],
	/* Device type label */
	"eth"	: [ _("Ethernet"), _("Ethernet Network Card") ],
	/* Device type label */
	"fddi"	: [ _("FDDI"), _("FDDI Network Card") ],
	/* Device type label */
	"ficon"	: [ _("FICON"), _("Fiberchannel System Connector (FICON)") ],
	/* Device type label */
	"hippi"	: [ _("HIPPI"), _("HIgh Performance Parallel Interface (HIPPI)") ],
	/* Device type label */
	"hsi"	: [ _("Hipersockets"), _("Hipersockets Interface (HSI)") ],
	/* Device type label */
	"ippp"	: [ _("ISDN"), _("ISDN Connection") ],
	/* Device type label */
	"irlan"	: [ _("IrDA"), _("Infrared Network Device") ],
	/* Device type label */
	"irda"	: [ _("IrDA"), _("Infrared Device") ],
	/* Device type label */
	"isdn"	: [ _("ISDN"), _("ISDN Connection") ],
	/* Device type label */
	"iucv"	: [ _("IUCV"), _("Inter User Communication Vehicle (IUCV)") ],
	/* Device type label */
	"lcs"	: [ _("OSA LCS"), _("OSA LCS Network Card") ],
	/* Device type label */
	"lo"	: [ _("Loopback"), _("Loopback Device") ],
	/* Device type label */
	"modem"	: [ _("Modem"), _("Modem") ],
	/* Device type label */
	"myri"	: [ _("Myrinet"), _("Myrinet Network Card") ],
	/* Device type label */
	"net"	: [ _("ISDN"), _("ISDN Connection") ],
	/* Device type label */
	"plip"	: [ _("Parallel Line"), _("Parallel Line Connection") ],
	/* Device type label */
	"ppp"	: [ _("Modem"), _("Modem") ],
	/* Device type label */
	"qeth"	: [ _("QETH"), _("OSA-Express or QDIO Device (QETH)") ],
	/* Device type label */
	"sit"	: [ _("IPv6-in-IPv4"), _("IPv6-in-IPv4 Encapsulation Device") ],
	/* Device type label */
	"slip"	: [ _("Serial Line"), _("Serial Line Connection") ],
	/* Device type label */
	"tr"	: [ _("Token Ring"), _("Token Ring Network Card") ],
	/* Device type label */
	"usb"	: [ _("USB"), _("USB Network Device") ],
	/* Device type label */
	"vmnet"	: [ _("VMWare"), _("VMWare Network Device") ],
	/* Device type label */
	"wlan"	: [ _("Wireless"), _("Wireless Network Card") ],
	/* Device type label */
	"xp"	: [ _("XPNET"), _("XP Network") ],
	/* Device type label */
	"vlan"	: [ _("VLAN"), _("Virtual LAN") ],
	/* Device type label */
	"br"	: [ _("Bridge"), _("Network Bridge") ],
	/* Device type label */
	"tun"	: [ _("TUN"), _("Network TUNnel") ],
	/* Device type label */
	"tap"	: [ _("TAP"), _("Network TAP") ],
	/* Device type label */
	"ib"	: [ _("InfiniBand"), _("InfiniBand Device") ],
    ];

    if(haskey(device_types, type)) return device_types[type, (longdescr==true) ? 1 : 0]:"";

    string type1 = String::FirstChunk (type, "-");
    if(haskey(device_types, type1))
	return device_types[type1, (longdescr==true) ? 1 : 0]:"";

    y2error("Unknown type: %1", type);
    return type;
}

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

/**
 * Were the devices changed?
 * @return true if modified
 */
global define boolean Modified(string devregex) {
    map Devs = Filter(Devices, devregex);
    map OriginalDevs = Filter(OriginalDevices, devregex);
    y2debug("OriginalDevs=%1", OriginalDevs);
    y2debug("Devs=%1", Devs);
    return Devs == OriginalDevs;
}

global define list<string> GetFreeDevices(string type, integer num) {
    y2debug("Devices=%1", Devices);
    y2debug("type,num=%1,%2", type, num);
    y2debug("Devices[%1]=%2", type, Devices[type]:$[]);

     list curdevs = [];
     foreach(string dev, (list<string>)Map::Keys(Devices[type]:$[]), {
      if(issubstring(dev, type)) dev=device_num(dev);
      curdevs=add(curdevs, dev);
     });

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

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

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

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

/**
 * Compute free devices
 * @param type device type
 * @param num how many free devices return
 * @return num of free devices
 * @example GetFreeDevices("eth", 2) -&gt; [ 1, 2 ]
 */
global define list GetFreeDevicesOld(string type, integer num) {
    y2debug("Devices=%1", Devices);
    y2debug("type,num=%1,%2", type, num);
    y2debug("Devices[%1]=%2", type, Devices[type]:$[]);

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

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

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

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

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

/**
 * Return free device
 * @param type device type
 * @return free device
 * @example GetFreeDevice("eth") -&gt; "1"
 */
global define string GetFreeDevice(string type) {
    y2debug("type=%1", type);
    list <string> freedevs = GetFreeDevices(type, 1);
    string ret = (string) freedevs[0]:nil;
    if(ret == nil) y2error("Free device location error: %1", ret);
    y2debug("Free device=%1", ret);
    return ret;
}

/**
 * Check presence of the device (alias)
 * @param dev device identifier
 * @return true if device is present
 */
global define boolean Check(string dev) {

    y2debug("Check(%1)", dev);
    string typ = GetType(dev);
//    string num = device_num(dev);
//    string anum = alias_num(dev);
    if (report_every_check) y2milestone("Check(%1)", dev);
    if(!haskey(Devices, typ)) return false;

    map devsmap = Devices[typ]:$[];
    if(!haskey(devsmap, dev)) return false;

    /* FIXME NI: not needed?
    Name = dev;
    Current = (map) eval(devsmap[num]:$[]);
    */

/*
    if(anum != "") {
	map devmap = devsmap[num]:$[];
	map amap = devmap["_aliases"]:$[];
	if(!haskey(amap, anum))
	    return false;
	// FIXME NI: not needed?
//	Current = (map) eval(amap[anum]:$[]);
//	alias = anum;
    }
*/
    y2debug("Check passed");
    return true;
}

/**
 * Select the given device
 * @param device to select ("" for new device, default values)
 * @return true if success
 */
global define boolean Select(string name) {

    Name = "";
    Current = $[];

    y2debug("name=%1", name);
    if(name != "" && !Check(name)) {
	y2error("No such device: %1", name);
	return false;
    }

    Name = name;
    // FIXME NI: Current = Devices[device_type(Name), device_num(Name)]:$[];
    // may be fixed already. or not: #39236
    string t = GetType(Name);
    Current = Devices[t, Name]:$[];
    string a = alias_num(Name);
    if(a != nil && a != "") Current = Current["_aliases", a]:$[];

    if(Current == $[]) {
	/* Default device map */
	Current = $[
	    /* FIXME: remaining items */
	];
    }

    y2debug("Name=%1", Name);
    y2debug("Current=%1", Current);

    return true;
}

/**
 * Add a new device
 * @return true if success
 */
global define boolean Add() {
    operation = nil;
    if(Select("") != true) return false;
    operation = `add;
    return true;
}

/**
 * Edit the given device
 * @param dev device to edit
 * @return true if success
 */
global define boolean Edit(string name) {
    operation = nil;
    if(Select(name) != true) return false;
    operation = `edit;
    return true;
}

/**
 * Delete the given device
 * @param dev device to delete
 * @return true if success
 */
global define boolean Delete(string name) {
    operation = nil;
    if(Select(name) != true) return false;
    operation = `delete;
    return true;
}

/**
 * Update Devices map
 * @param dev device identifier
 * @param newdev new device map
 * @param check if check if device already exists
 * @return true if success
 */
define boolean Change2(string name, ifcfg_t newdev, boolean check) {
    y2debug("Change(%1,%2,%3)", name, newdev, check);
    y2debug("Devices=%1", Devices);
    if(Check(name) && check) {
	y2error("Device already present: %1", name);
	return false;
    }

    string t = GetType(name);
    if (name==Name) {
     string int_type = Current["INTERFACETYPE"]:"";
     if(size(int_type)>0) t = int_type;
    }
    string a = alias_num(name);
    y2debug("ChangeDevice(%1)", name);

    map<string, ifcfg_t> devsmap = Devices[t]:$[];
    ifcfg_t devmap = devsmap[name]:$[];
    map amap = devmap["_aliases"]:$[];

    if(a != "") {
	amap[a] = newdev;
	devmap["_aliases"] = amap;
    }
    else
	devmap = newdev;

    devsmap[name] = devmap;
    Devices[t] = devsmap;

    y2debug("Devices=%1", Devices);
    return true;
}

global boolean Delete2(string name) {
    if(!Check(name)) {
	y2error("Device not found: %1", name);
	return false;
    }

    string t = GetType(name);
//    string d = device_num(name);
    string a = alias_num(name);
    map<string, ifcfg_t> devsmap = Devices[t]:$[];

    if(a != "") {
	map amap = devsmap[name, "_aliases"]:$[];
	amap = remove(amap, a);
	devsmap[name, "_aliases"] = amap;
    }
    else
	devsmap = remove(devsmap, name);

    Devices[t] = devsmap;

    // Originally this avoided errors in the log when deleting an
    // interface that was not present at Read (had no ifcfg file).
    // #115448: OriginalDevices is not updated after Write so
    // returning to the network proposal and deleting a card would not work.
    if (true ||
       haskey(OriginalDevices, t) && haskey(OriginalDevices[t]:$[], name)) {
	y2milestone("Deleting file: %1", name);
	Deleted[size(Deleted)] = name;
    }
    else {
	y2milestone("Not deleting file: %1", name);
	y2debug("OriginalDevices=%1", OriginalDevices);
	y2debug("a=%1", a);
    }
    return true;
}

/**
 * Add the alias to the list of deleted items.
 * Called when exiting from the aliases-of-device dialog.
 * #48191
 */
global boolean DeleteAlias (string device, string aid) {
    string alias = sformat ("%1#%2", device, aid);
    y2milestone("Deleting alias: %1", alias);
    Deleted[size(Deleted)] = alias;
    return true;
}

global define boolean Commit() {
    y2debug("Name=%1", Name);
    y2debug("Current=%1", Current);
    y2debug("Devices=%1", Devices);
    y2debug("Deleted=%1", Deleted);
    y2debug("operation=%1", operation);

    if(operation == `add || operation == `edit) {
	Change2(Name, Current, operation == `add);
    }
    else if(operation == `delete) {
	Delete2(Name);
    }
    else {
	y2error("Unknown operation: %1 (%2)", operation, Name);
	return false;
    }

    y2debug("Devices=%1", Devices);
    y2debug("Deleted=%1", Deleted);

    Name = "";
    Current = $[];
    operation = nil;

    return true;
}

global define string GetValue(string name, string key) {
    if(!Select(name)) return nil;
    return Current[key]:"";
}

global define boolean SetValue(string name, string key, string value) {
    if(!Edit(name)) return nil;
    if(key == nil || key == "" || value == nil) return false;
    Current[key] = value;
    return Commit();
}

/**
 * get IP addres + additional IP addresses
 * @param identifier for network interface
 * @return list of IP addresses of selected interface
 */

global list<string> GetIP(string device){
 Select(device);
 list<string> ips = [ GetValue(device, "IPADDR") ];
 foreach(string key, map<string, any> value, (map<string, map<string, any> >)Current["_aliases"]:$[], {
  ips = add(ips, value["IPADDR"]:"");
 });
 return ips;
}


/**
 * Locate devices of the given type and value
 * @param key device key
 * @param val device value
 * @return list of devices with key=val
 */
global define list<string> Locate(string key, string val) {
    list<string> ret = [];
    maplist(string typ, map devsmap, Devices, {
	maplist(string device, map devmap, (map<string,map>) devsmap, {
	    if(devmap[key]:"" == val) ret = add(ret, device);
	});
    });

    return ret;
}

/**
 * Locate devices of the given type and value
 * @param key device key
 * @param val device value
 * @return list of devices with key!=val
 */
global define list<string> LocateNOT(string key, string val) {
    list<string> ret = [];
    maplist(string typ, map devsmap, Devices, {
	maplist(string device, map devmap, (map<string,map>) devsmap, {
	    if(devmap[key]:"" != val) ret = add(ret, device);
	});
    });

    return ret;
}

/**
 * Check if any device is using the specified provider
 * @param provider provider identification
 * @return true if there is any
 */
global define boolean LocateProvider(string provider) {
    list devs = Locate("PROVIDER", provider);
    return size(devs) > 0;
}

/**
 * Update /dev/modem symlink
 * @return true if success
 */
global define boolean UpdateModemSymlink() {
    boolean ret = false;
    if(contains(Map::Keys(Devices), "modem")) {
	list ml = Map::Keys(Devices["modem"]:$[]);
	string ms = ml[0]:"0";
	// map mm = Devices["modem"]:$[][ms]:$[];
	map mm = Devices["modem", ms]:$[];
	string mdev = mm["MODEM_DEVICE"]:"";
	if(mdev != "" && mdev != "/dev/modem") {
	    string curlink = nil;
	    map m = (map) SCR::Read(.target.lstat, "/dev/modem");
	    if(m["islink"]:false == true)
		curlink = (string) SCR::Read(.target.symlink, "/dev/modem");
	    if(curlink != mdev) {
		SCR::Execute(.target.symlink, mdev, "/dev/modem");
		ret = true;
	    }
	}
    }
    return ret;
}

/**
 * Clean the hotplug devices compatibility symlink,
 * usually ifcfg-eth-pcmcia -> ifcfg-eth-pcmcia-0.
 * @return true if success
 */
global define boolean CleanHotplugSymlink() {

    list<string> types = [ "eth-pcmcia", "eth-usb", "tr-pcmcia", "tr-usb" ];
    maplist(string t, types, {
	string link = "/etc/sysconfig/network/ifcfg-" + t;
	y2debug("link=%1", link);
	map lstat = (map) SCR::Read(.target.lstat, link);
	if(lstat["islink"]:false == true) {
	    string file = (string) SCR::Read(.target.symlink, link);
	    file = "/etc/sysconfig/network/" + file;
	    y2debug("file=%1", file);
	    if(SCR::Read(.target.size, file) > -1) {
		y2milestone("Cleaning hotplug symlink");
		y2milestone("Devices[%1]=%2", t, Devices[t]:$[]);
		Devices[t] = remove(Devices[t]:$[], "");
		y2milestone("Devices[%1]=%2", t, Devices[t]:$[]);
	    }
	}
    });

    y2debug("Devices=%1", Devices);
    return true;
}

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

    list<string> ret = [];
    if(devregex == "" || devregex == nil) {
	maplist(string t, map d, Devices, {
	    maplist(string device, (list<string>) Map::Keys(d), {
		ret[size(ret)] = device;
	    });
	});
    }
    else {
	// it's a regex for type, not the whole name
	string regex = "^(" + DeviceRegex[devregex]:devregex + ")$";
	maplist(string t, map d, Devices, {
	    if(regexpmatch(t, regex)) {
		maplist(string device, (list<string>) Map::Keys(d), {
		    ret[size(ret)] = device;
		});
	    }
	});
    }
    ret = filter(string row, ret, {
		if (row!=nil) return true;
		y2error("Filtering out : %1", row);
		return false;
	});
    y2milestone("List( %1 ) = %2", devregex, ret);
    return ret;
}

/**
 * Find the fastest available device
 */
global define string Fastest() {

    string ret = "";
    list<string> devices = List("");

    /* Find the fastest device */
    foreach(integer num, string type, FastestTypes, {
	foreach(string dev, devices, {
	    if(ret == "" && regexpmatch(dev, "^" + DeviceRegex[type]:"" + "[0-9]*$")
	      && IsConnected(dev)) 
		ret = dev;
	});
    });

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

global define string FastestType(string name) {
    string ret = "";
    maplist(integer num, string type, FastestTypes, {
	string regex = DeviceRegex[type]:"";
	if (ret == "" && regexpmatch(name, "^" + regex + "[0-9]*$"))
	ret = type;
    });
    /*
    maplist(string typ, string regex, DeviceRegex, {
	if (ret == "" && regexpmatch(name, "^" + regex + "[0-9]*$"))
	ret = typ;
    });
    */
    return ret;
}

/**
 * Check if the given device has any virtual alias.
 * @param dev device to be checked
 * @return true if there are some aliases
 */
global define boolean HasAliases(string name) {

    if(!Check(name)) {
	y2error("Device not found: %1", name);
	return false;
    }

    string t = device_type(name);
    string d = device_num(name);
    string a = alias_num(name);

    return a == "" && Devices[t, d, "_aliases"]:$[] != $[];
}

/**
 * DSL needs to save its config while the underlying network card is
 * being configured.
 */
global define void Push() {
    if(stack != $[]) y2error("Stack not empty: %1", stack);
    stack["Name"] = Name;
    stack["Current"] = Current;
    stack["operation"] = operation;
    y2milestone("PUSH: %1", stack);
}

global define void Pop() {
    y2milestone("POP: %1", stack);
    Name = stack["Name"]:"";
    Current = stack["Current"]:$[];
    operation = (symbol) stack["operation"]:nil;
    stack = $[];
}

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

/**
 * list of all devices except given one by parameter dev
 * also loopback is ommited
 */

global list<string> ListDevicesExcept(string dev){
 list<string> devices = filter(string s, LocateNOT("DEVICE", dev), {return s!="lo";});
 return devices;
}

/* EOF */
}

ACC SHELL 2018