/**
 * File:	include/inetd/routines.ycp
 * Package:	Configuration of inetd
 * Summary:	Miscelanous functions for configuration of inetd.
 * Authors:	Petr Hadraba <phadraba@suse.cz>
 *		Martin Lazar <mlazar@suse.cz>
 *
 * $Id: routines.ycp 21007 2005-01-27 09:52:59Z mlazar $
 */

{

textdomain "inetd";

import "Inetd";
import "Progress";
import "Service";
import "Popup";
import "Package";
import "UsersCache";
import "String";

define term ServiceToTableItem (map service, integer ni_index);
define boolean ServicesMatch (map<string, any> a, map<string, any> b);
define boolean IsInstalled (string rpm);

/**
 * Check for pending Abort press
 * @return true if pending abort
 */
define boolean PollAbort() ``{
    return UI::PollInput() == `abort;
}

/**
 * If modified, ask for confirmation
 * @return true if abort is confirmed
 */
define boolean ReallyAbort() ``{
    return !Inetd::Modified() || Popup::ReallyAbort(true);
}

/**
 * Progress::NextStage and Progress::Title combined into one function
 * @param title progressbar title
 */
define void ProgressNextStage(string title) ``{
    Progress::NextStage();
    Progress::Title(title);
}

/**
 * Used for cpmparisons whether the servers match:
 * If server is /usr/sbin/tcpd, consider server_args instead.
 * Then take the firse word (strips arguments or the parenthesized pkg name).
 * Then take the last slash-delimited component.
 * For sparse matching: nil is returned if server is nil
 * (or if server args is nil AND is needed)
 * @param server "server" field of a service
 * @param server_args "server_args" field of a service
 * @return basename of the real server
 */
define string GetServerBasename (string server, string server_args) ``{
    string result = server;
    // discard tcpd
    if (result == "/usr/sbin/tcpd")
    {
	result = server_args;
    }
    // check nil
    if (result != nil)
    {
	// program only
	result = String::FirstChunk (result, " \t");
	// basename
	list<string> comp = splitstring (result, "/");
	result = comp[size (comp) - 1]:"";
    }
    return result;
}

/**
 * Considers the maps as structs and tests
 * some of their fields for equality (conjunctively, short circuit).
 * @param a one struct
 * @param b other struct
 * @param fields list of keys
 * @return Do the maps have all the named fields equal?
 */
define boolean struct_match (map a, map b, list fields) ``{
    // short circuit: use the _find_ builtin to get the mismatching key
    any mismatch = find (any key, fields,
			 ``( a[key]:nil != b[key]:nil ));
    // mismatch is nil => they match
    return mismatch == nil;
}

/**
 * Considers the maps as structs and tests
 * some of their fields for equality (conjunctively, short circuit).
 * If a key is missing in either of the maps, it is considered as matching.
 * <p> Used when merging autoyast items, to match only those fields that
 * are specified in the profile. There, only one map is sparse.
 * (the profile map)
 * @param a one struct
 * @param b other struct
 * @param fields list of keys
 * @return Do the maps have all named fields that are in both of them equal?
 * @example match: $["a": 1, "b": 2, "c": 3], $["b": 2, "d": 4]
 */
define boolean struct_match_sparse (map a, map b, list fields) ``{
    // short circuit: use the _find_ builtin to get the mismatching key
    any mismatch = find (any key, fields,
			 ``(
			     ! ( a[key]:nil == b[key]:nil ||
				 !haskey (a, key) ||
				 !haskey (b, key))
			     ));
    // mismatch is nil => they match
    return mismatch == nil;
}

/**
 * Determine, if service package is installed.
 * This function requires full configuration
 * (like Inetd::netd_conf) and standalone map with service.
 * Linear complexity (in the working config)
 * @param netd_conf Full configuration
 * @param s a @ref service
 * @return match found
 */
define boolean isServiceMatchPresent (list<map <string, any> > netd_conf, map<string, any> s) ``{
    map match = find (map<string, any> i, netd_conf, ``(
			  ServicesMatch (i, s)
			  ));
    return match != nil;
}

/**
 * This function merges real xinetd configuration (read by agent) and
 * available services packages generated for SuSE distribution
 * This function is automaticaly calld by CreateTableData() if Inetd::configured_service
 * is `xinetd.

 * Adds those from default_conf that do not have a matching
 * service in the working config.

 * @param netd_conf not used in this function, but used for
 * isServiceMatchPresent call.
 * @param table_data Table data structure used as source data
 * @return list New table data
 */
define list<term> AddNotInstalled (list<map <string, any> > netd_conf, list<term> table_data) ``{
    //y2milestone("in function mergeXinetdConfs()");

    list<map <string, any> > defaults = Inetd::default_conf;

    integer index = 0;
    foreach(map<string, any> line, defaults, ``{
	if(! isServiceMatchPresent (netd_conf, line)) {
	    index = index + 1;
	    term entry = ServiceToTableItem (line, index);
	    table_data = add(table_data, entry);
	}
    });
    return table_data;
}

/**
 * @param service @ref service
 * @param ni_index if nil, use iid as id, otherwise "NI"+ni_index
 * @return main table item
 */
define term ServiceToTableItem (map service, integer ni_index) ``{
    y2debug ("* %1", service["service"]:"");
    string  status_text     = "";
    string  wait_text       = "";

    // determine service is enabled (enabled text)
    string changed_text = (service["changed"]:false) ? "X" : "";
    if (service["enabled"]:true) {
	/* Translators: Service status: On = running, --- = stopped */
	status_text = _("On");
    }
    else {
	/* Translators: This string you can leave unchanged */
	status_text = _("---");
    }

    // HACK:
    // our data structures kinda suck, so we have no quick way of determining
    // what package a service belongs to.
    // But IsInstalled returns true for "" so we don't get to install an
    // unknown package.
    if (! IsInstalled (service["package"]:""))
    {
	/* Translators: This is used for status "Not Installed".
	   Please, make the
	   translation as short as possible. */
	status_text = _("NI");
    }

    // determine wait mode (convert to string)
    if (service["wait"]:true) {
	wait_text = "yes";
    }
    else {
	wait_text = "no";
    }
    string sname = service["service"]:"";
    string rpc_ver = service["rpc_version"]:"";
    if (rpc_ver != "") {
	sname = sname + "/" + rpc_ver;
    }
    string user_group = service["group"]:"";
    if (user_group == "") {
	user_group = service["user"]:"";
    }
    else {
	// TODO switch to ":" as separator
	user_group = service["user"]:"" + "." + user_group;
    }
    // create line for table structure
    term entry = `item(
	`id (
	    ni_index == nil ?
	    (service["iid"]:"0") :
	    ("NI" + ni_index)
	    ),
	changed_text,
	status_text,
	sname,
	service["socket_type"]:"",
	service["protocol"]:"",
	wait_text,
	user_group,
	service["server"]:"",
	service["server_args"]:"");
    return entry;
}

/**
 * Converts configuration format from agent to table data structures
 * @param netd_conf netd_conf handles whole configuration of configured service
 * @return returnes table data
 */
define list<term> CreateTableData(list<map <string, any> > netd_conf) ``{
    list<term>    table_input     = [];

    foreach(map service, netd_conf, ``{
	// service must be marked as nondeleted ("deleted" == false or "deleted" not exists)
	if (! service["deleted"]:false) {
	    term entry = ServiceToTableItem (service, nil);
	    // add line to table structure
	    table_input = add(table_input, entry);
	};
    });

    // now can do it for both superservers
    // ... not yet
    // because in the system data, we do not have the "package" field

    table_input = AddNotInstalled (netd_conf, table_input);

    // table_input = find out which are installed
    // but that would require a better data model.
    // let's try doing with the current one.
    return table_input;
}

/**
 * Read user names from passwd.
 * It does not get the NIS entries.
 * "+" is filtered out.
 * @return list users
 */
define list<string> CreateLocalUsersList() ``{
    list<string> users = (list<string>) merge (
	UsersCache::GetUsernames("local"),
	UsersCache::GetUsernames("system")
    );
    users = sort(users);
    y2debug("users: %1", users);

    users = add(users, _("--default--"));
    return users;
}

/**
 * Read group names from  group
 * It does not get the NIS entries.
 * "+" is filtered out.
 * @return list groups
 */
define list<string> CreateLocalGroupsList() ``{
    list<string> groups = (list<string>) merge (
	UsersCache::GetGroupnames("local"),
	UsersCache::GetGroupnames("system")
    );
    groups = sort(groups);
    y2debug("groups: %1", groups);

    groups              = add(groups, _("--default--"));

    return groups;
}

/**
 * Find any service to be enabled
 * If no found, return `no
 * @param ready_conf ready_conf handles whole service configuration (Inetd::netd_conf)
 * @return returnes if found `yes, otherwise `no
 */
define symbol IsAnyServiceEnabled(list<map <string, any> > ready_conf) ``{
    symbol ret = `no;

    foreach(map<string, any> line, ready_conf, ``{
	if(line["deleted"]:false == false) {
	    if(line["enabled"]:false != false) {
		ret = `yes;
	    }
	}
    });
    return ret;
}

/**
 * Do the services match?
 * Considered fields:
 * "script", "service", "protocol", "server" (or "server_args", if tcpd)
 * The matching is sparse: a missing field in either map
 * means the field is condidered as matching.
 * @param a one map
 * @param b other map
 * @return match?
 */
define boolean ServicesMatch (map<string, any> a, map<string, any> b) ``{
    if (struct_match_sparse (a, b, ["script", "service", "protocol"]))
    {
	// Compare whether the server matches
	// Watch out for tcpd, use basenames
	// because the 8.2 UI produced server="in.ftpd (ftpd)"
	string a_serverbn = GetServerBasename ((string) (a["server"]:nil),
					       (string) (a["server_args"]:nil));
	string b_serverbn = GetServerBasename ((string) (b["server"]:nil),
					       (string) (b["server_args"]:nil));
	if (a_serverbn == nil ||
	    b_serverbn == nil ||
	    a_serverbn == b_serverbn)
	{
	    return true;
	}
    }
    return false;
}

/**
 * Get contents of environment variable.
 * @param environment name of requested variable
 * @return environment variable contents
 */
define string getenv(string environment) ``{
    string command = sformat("echo $%1", environment);
    map result     = (map) SCR::Execute(.target.bash_output, command, []);
    if(result["stdout"]:"" == "") {
	return nil;
    } else {
	return (string) (result["stdout"]:"");
    }
}

/**
 * Cache for @ref IsInstalled.
 */
map is_installed_cache = $[];

/**
 * Clears cache for @ref IsInstalled .
 * This is too wasteful. Later, when it works, optimize for single packages.
 */
define void IsInstalledClearCache () ``{
     is_installed_cache = $[];
}

/**
 * Queries RPM or autoyast list whether a package is installed.
 * Results are cached in @ref is_installed_cache
 * @param rpm eg. "finger-server", "" has special significance:
 * package unknown, so it cannot be installed
 * @return installed?
 */
define boolean IsInstalled (string rpm) ``{
    boolean result = rpm == ""? true: is_installed_cache[rpm]:nil;
    if (result == nil)
    {
	result = Package::Installed (rpm);
	is_installed_cache = add (is_installed_cache, rpm, result);
    }
    y2debug ("%1: %2", rpm, result);
    return result;
}

/* EOF */
}
