/**
 * File:	modules/Sysconfig.ycp
 * Package:	Configuration of sysconfig
 * Summary:	Data for configuration of sysconfig, input and output functions.
 * Authors:	Ladislav Slezak <lslezak@suse.cz>
 *
 * $Id: Sysconfig.ycp,v 1.53 2004/11/24 13:09:48 lslezak Exp $
 *
 * Representation of the configuration of sysconfig.
 * Input and output routines.
 */

{

module "Sysconfig";
textdomain "sysconfig";

import "Progress";
import "Report";
import "Summary";
import "Directory";
import "Label";
import "IP";
import "String";
import "Service";
import "Mode";


global list configfiles = ["/etc/sysconfig/*", "/etc/sysconfig/network/ifcfg-*",
    "/etc/sysconfig/network/wireless", "/etc/sysconfig/network/dhcp",
    "/etc/sysconfig/network/config", Directory::ydatadir + "/descriptions",
    "/etc/sysconfig/powersave/*", "/etc/sysconfig/uml/*"
    ];

global string powertweakconfig = "/etc/powertweak/tweaks";

// Additional files from Import
list custom_files = [];

// all read variables
map variables = $[];

// modified variables
map<string, string> modified_variables = $[];

// comment for non-variable nodes
map node_comments = $[];

// location for each variable
map<string,string> variable_locations = $[];

global map parse_param = $[
    "separator" : ",",
    "unique" : true,
    "remove_whitespace" : true
];

global boolean write_only = false;

global list<list> tree_content = [];

global boolean powertweak_mode = false;

// map of actions to start when variable is modified
map<string, any> actions = $[];

global boolean ConfirmActions = false;

boolean config_modified = false;

/**
 * Data was modified?
 * @return true if modified
 */
global define boolean Modified() ``{
    return (size(modified_variables) > 0) || config_modified;
};

global define void SetModified() {
    config_modified = true;
}

/**
 * Get variable name from variable identification or empty string if input is invalid
 * @param id Variable identification
 * @return string Variable name
 * @example get_name_from_id("var$file") -> "var"
 */
global define string get_name_from_id(string id) ``{

    if (id == nil)
    {
	return "";
    }

    integer pos = findfirstof(id, "$");

    if (pos != nil && pos >= 0)
    {
	return substring(id, 0, findfirstof(id, "$"));
    }
    else
    {
	return "";
    }
}


/**
 * Get file name where is variable located from variable identification
 * @param id Variable identification
 * @return string File name
 */
global define string get_file_from_id(string id) ``{
    if (id == nil)
    {
	return "";
    }

    integer pos = findfirstof(id, "$");

    if (pos != nil && pos >= 0)
    {
	return substring(id, pos + 1);
    }
    else
    {
	return "";
    }
}

/**
 * Get comment without metadata
 * @param input Input string
 * @return string Comment used as variable description
 */
global define string get_only_comment(string input) ``{
    if (input == nil || input == "")
    {
	return "";
    }

    list<string> lines = splitstring(input, "\n");

    string ret = "";

    foreach(string line, lines, ``{
	    string com_line = regexpsub(line, "^#([^#].*)", "\\1");

	    if (com_line == nil)
	    {
		// add empty lines
		if (regexpmatch(line, "^#[ \t]*$") == true)
		{
		    ret = ret + "\n";
		}
	    }
	    else
	    {
		ret = ret + com_line + "\n";
	    }
	}
    );

    return ret;
}

/**
 * Search in syscnfig files for value
 * @param params search parameters
 * @param show_progress if true progress bar will be displayed
 * @return list<string> List of found variables (IDs)
 */
global define list<string> search(map params, boolean show_progress) ``{
    list<string> found = [];

    // get all configuration files
    list<string> files = (list<string>) SCR::Dir(.syseditor.section);

    if (show_progress == true)
    {
	// Translation: Progress bar label
	UI::OpenDialog(`ProgressBar(`id(`progress), _("Searching..."), size(files), 0));
    }

    boolean search_varname = params["varname"]:true;
    boolean search_description = params["description"]:false;
    boolean search_value = params["value"]:false;
    boolean case_insensitive = params["insensitive"]:false;
    string search_string = params["search"]:"";

    if (search_string == "")
    {
        UI::CloseDialog();
	return found;
    }

    if (case_insensitive == true)
    {
	search_string = tolower(search_string);
    }

    integer index = 0;

    foreach(string file, files, ``{
	    // get all variables in file
	    path var_path = add(.syseditor.value, file);
	    list<string> variables = (list<string>)SCR::Dir(var_path);

	    y2debug("Searching in file %1", file);

	    foreach(string var, variables,
		``{
		    boolean already_found = false;

		    y2debug("Searching in variable %1", var);

		    if (search_varname == true)
		    {
			string var2 = var;
			if (case_insensitive)
			{
			    var2 = tolower(var);
			}

			if (issubstring(var2, search_string))
			{
			    found = add(found, var + "$" + file);
			    already_found = true;
			}
		    }

		    // search in variable value if it is requested and previous check was unsuccessful
		    if (search_value == true && already_found == false)
		    {
			string read_value = (string) SCR::Read(add(add(.syseditor.value, file), var));

			if (case_insensitive)
			{
			    read_value = tolower(read_value);
			}

			if (issubstring(read_value, search_string))
			{
			    found = add(found, var + "$" + file);
			    already_found = true;
			}
		    }

		    if (search_description == true && already_found == false)
		    {
			// read comment without metadata
			string read_comment = get_only_comment((string) SCR::Read(add(add(.syseditor.value_comment, file), var)));

			if (case_insensitive)
			{
			    read_comment = tolower(read_comment);
			}

			if (issubstring(read_comment, search_string))
			{
			    found = add(found, var + "$" + file);
			}
		    }
		}
	    );

	    if (show_progress == true)
	    {
		index = index + 1;
		UI::ChangeWidget(`id(`progress), `Value, index);
	    }

	}
    );

    if (show_progress == true)
    {
	UI::CloseDialog();
    }

    y2debug("Found: %1", found);

    return found;
}

/**
 * Remove white spaces at beginning or at the end of string
 * @param input Input string
 * @return string String without white spaces
 */
global define string remove_whitespaces(string input) ``{
    if (input == nil)
    {
	return nil;
    }

    string removed_whitespaces = regexpsub(input, "^[ \t]*(([^ \t]*[ \t]*[^ \t]+)*)[ \t]*$", "\\1");

    return (removed_whitespaces != nil) ? removed_whitespaces : input;
}

/**
 * Get metadata lines from input string
 * @param input Input string
 * @return list<string> Metadata lines in list
 */
global define list<string> get_metadata(string input) ``{
    if (input == nil || input == "")
    {
	return [];
    }

    list<string> lines = splitstring(input, "\n");
    return (filter(string line, lines, ``(regexpmatch(line, "^##.*"))));
}

/**
 * Parse metadata from comment
 * @param comment Input comment
 * @return map parsed metadata
 */
global define map<string, string> parse_metadata(string comment) ``{
    map<string, string> ret = $[];

    // get metadata part of comment
    list<string> metalines = get_metadata(comment);
    list<string> joined_multilines = [];
    string multiline = "";

    y2debug("metadata: %1", metalines);

    // join multi line metadata lines
    foreach(string metaline, metalines, ``{
	    if (substring(metaline, size(metaline) - 1, 1) != "\\")
	    {
		if (multiline != "")
		{
		    // this not first multiline so remove comment mark
		    string without_comment = regexpsub(metaline, "^##(.*)", "\\1");

		    if (without_comment != nil)
		    {
			metaline = without_comment;
		    }
		}
		joined_multilines = add(joined_multilines, multiline + metaline);
		multiline = "";
	    }
	    else
	    {
		string part = substring(metaline, 0, size(metaline) - 1);

		if (multiline != "")
		{
		    // this not first multiline so remove comment mark
		    string without_comment = regexpsub(part, "^##(.*)", "\\1");

		    if (without_comment != nil)
		    {
			part = without_comment;
		    }
		}

		// add line to the previous lines
		multiline = multiline + part;
	    }
	}
    );

    y2debug("metadata after multiline joining: %1", joined_multilines);

    // parse each metadata line
    foreach(string metaline, joined_multilines, ``{

	    /* Ignore lines with ### -- general comments */
	    if (regexpmatch(metaline, "^###"))
	    {
		return;
	    }

	    string meta = regexpsub(metaline, "^##[ \t]*(.*)", "\\1");

	    // split sting to the tag and value part
	    integer colon_pos = findfirstof(meta, ":");
	    string tag = "";
	    string val = "";

	    if (colon_pos == nil)
	    {
		// colon is missing
		tag = meta;
	    }
	    else
	    {
		tag = substring(meta, 0, colon_pos);

		if (size(meta) > colon_pos + 1)
		{
		    val = substring(meta, colon_pos + 1);
		}
	    }

	    // remove whitespaces from parts
	    tag = remove_whitespaces(tag);
	    val = remove_whitespaces(val);

	    y2milestone("tag: %1 val: '%2'", tag, val);

	    // add tag and value to map if they are present in comment
	    if (tag != "")
	    {
		ret = (map<string, string>) add(ret, tag, val);
	    }
	    else
	    {
		// ignore separator lines
		if (!regexpmatch(metaline, "^#*$"))
		{
		    y2warning("Unknown metadata line: %1", metaline);
		}
	    }
	}
    );

    return ret;
}


/**
 * Get variable location in tree widget from variable identification
 * @param id Variable identification
 * @return string Variable location
 */
global define string get_location_from_id(string id) ``{
    return variable_locations[id]:"";
}

/**
 * Get description of selected variable
 * @param varid Variable identification
 * @return map Description map
 */
global define map<string, any> get_description(string varid) ``{
    string varname = get_name_from_id(varid);
    string fname = get_file_from_id(varid);

    path comment_path = add(add(.syseditor.value_comment, fname), varname);
    path value_path = add(add(.syseditor.value, fname), varname);
    string comment = (string)SCR::Read(comment_path);
    list<string> all_variables = (list<string>)SCR::Dir(add(.syseditor.value, fname));
    string used_comment = varname;

    // no comment present
    if (comment != nil && size(comment) == 0 && !regexpmatch(fname, "^/etc/sysconfig/network/ifcfg-.*"))
    {
	y2warning("Comment for variable %1 is missing", varid);

	list<string> reversed = [];

	integer i = 0;
	boolean found = false;

	while (i < size(all_variables) && found == false)
	{
	    string v = all_variables[i]:"";

	    if (v == varname)
	    {
		found = true;
	    }
	    else
	    {
		reversed = prepend(reversed, v);
	    }

	    i = i + 1;
	}

	if (found == true)
	{
	    i = 0;
	    comment = "";
	    string v = "";

	    y2debug("reversed: %1", reversed);
	    while (i < size(reversed) && comment == "")
	    {
		v = reversed[i]:"";
		used_comment = v;
		comment = (string) SCR::Read(add(add(.syseditor.value_comment, fname), v));

		i = i + 1;
	    }

	    y2warning("Variable: %1 Using comment from variable: %2", varname, v);
	}

    }

    // remove config file header at the beginning of the file
    // header is comment from beginning of the file to the empty line
    if (used_comment == all_variables[0]:"" && comment != nil)
    {
	y2debug("Reading first variable from the file");
	// comment is read from the first variable
	// remove header if it's present
	y2debug("Whole comment: %1", comment);
	list<string> lines = splitstring(comment, "\n");
	list<string> filtered = [];

	// remove last empty string from list (caused by last new line char)
	if (lines[size(lines) - 1]:nil == "")
	{
	    lines = remove(lines, size(lines) - 1);
	}

	if (contains(lines, "") == true)
	{
	    y2milestone("Header comment detected");
	    boolean adding = false;

	    // filter out variables before empty line
	    filtered = filter(string line, lines, ``{

		    if (line == "")
		    {
			adding = true;
		    }
		    else if (adding == true)
		    {
			return true;
		    }

		    return false;
		}
	    );

	    // merge strings
	    comment = mergestring(filtered, "\n");
	}
    }

    map<string, any> meta = parse_metadata(comment);

    string template_only_comment = "";

    // for network configuration file read comments from configuration template
    if (regexpmatch(fname, "^/etc/sysconfig/network/ifcfg-.*"))
    {
	string template_comment = (string) SCR::Read(add(add(.sysconfig.network.template, "value_comment"), varname));
	map<string,string> template_meta = parse_metadata(template_comment);

	if (size(template_meta) > 0)
	{
	    // add missing metadata values from template
	    foreach(string key, string value, template_meta, ``{
		    if (!haskey(meta, key))
		    {
			meta[key] = value;
		    }
		}
	    );
	}

	template_only_comment = get_only_comment(template_comment);

	if (size(template_only_comment) > 0)
	{
	    template_only_comment = template_only_comment + "\n";
	}

	y2milestone("Comment read from template: %1", template_only_comment);
	y2milestone("Meta read from template: %1", template_meta);
    }

    string deflt = (string) (meta["Default"]:nil);

    if (deflt != nil)
    {
	list parsed = String::ParseOptions(deflt, parse_param);
	meta["Default"] = parsed[0]:"";

	y2debug("Read default value: %1", parsed[0]:"");
    }

    string new_value = modified_variables[varid]:nil;

    // check if value was changed
    if (new_value != nil)
    {
	meta["new_value"] = new_value;
    }

    meta["name"] = varname;
    meta["file"] = get_file_from_id(varid);
    meta["location"] = (varname != "") ? get_location_from_id(varid) : varid;
    meta["comment"] = (varname != "") ? template_only_comment + get_only_comment(comment) : (node_comments[varid]:"");
    meta["value"] = SCR::Read(value_path);

    // add action commands
    if (size(actions[varid]:$[]) > 0)
    {
	meta["actions"] = actions[varid]:$[];
    }

    return meta;
}

/**
 * Set new variable value
 * @param variable Variable identification
 * @param new_value New value
 * @param force If true - do not check if new value is valid
 * @param force_change Force value as changed even if it is equal to old value
 * @return symbol Result: `not_found (specified variable was not found in config file),
 *   `not_valid (new  value is not valid - doesn't match variable type definition),
 *   `ok (success)
 */
global define symbol set_value(string variable, string new_value, boolean force, boolean force_change) ``{
    map desc = get_description(variable);
    string name = get_name_from_id(variable);

    if (name == "")
    {
	return `not_found;
    }

    string modif = modified_variables[variable]:nil;
    string old = desc["value"]:"";

    // use default value (or emty string) instead of the curent value in autoyast
    if (Mode::config())
    {
	old = (haskey(desc, "Default")) ? desc["Default"]:"" : "";
    }


    string curr_val = (modif != nil) ? modif : old;

    if (force_change || new_value != curr_val)
    {
	y2milestone("variable: %1 changed from: %2 to: %3", variable, curr_val, new_value);

	if (new_value == old && !force_change)
	{
	    // variable was reset to the original value, remove it from map of modified
	    y2debug("Variable %1 was reset to the original value", variable);
	    modified_variables = (map<string, string>) remove(modified_variables, variable);
	}
	else
	{
	    boolean valid = false;

	    if (force == false)
	    {
		// check data type
		string type = desc["Type"]:"string";

		if (type == "string" || regexpmatch(type, "^string\\(.*\\)$") == true)
		{
		    // string type is valid always
		    valid = true;
		}
		else if (type == "yesno")
		{
		    valid = (new_value == "yes" || new_value == "no");
		}
		else if (type == "boolean")
		{
		    valid = (new_value == "true" || new_value == "false");
		}
		else if (type == "integer")
		{
		    valid = regexpmatch(new_value, "^-{0,1}[0-9]*$");

		}
		else if (regexpmatch(type, "^list\\(.*\\)$"))
		{
		    string listopt = regexpsub(type, "^list\\((.*)\\)$", "\\1");
		    list parsed_opts = String::ParseOptions(listopt, parse_param);

		    valid = contains(parsed_opts, new_value);
		}
		else if (regexpmatch(type, "^integer\\(-{0,1}[0-9]*:-{0,1}[0-9]*\\)$"))
		{
		    // check if input is integer
		    valid = regexpmatch(new_value, "^-{0,1}[0-9]*$");

		    if (valid == true)
		    {
			// it is integer, check range
			string min = regexpsub(type, "^integer\\((-{0,1}[0-9]*):-{0,1}[0-9]*\\)$", "\\1");
			string max = regexpsub(type, "^integer\\(-{0,1}[0-9]*:(-{0,1}[0-9]*)\\)$", "\\1");

			y2milestone("min: %1  max: %2", min, max);

			integer min_int = tointeger(min);
			integer max_int = tointeger(max);
			integer new_int = tointeger(new_value);

			y2milestone("min_int: %1  max_int: %2", min_int, max_int);

			if (max != "" && min != "")
			{
			    valid = (new_int >= min_int && new_int <= max_int);
			}
			else if (max == "")
			{
			    valid = (new_int >= min_int);
			}
			else if (min == "")
			{
			    valid = (new_int <= max_int);
			}
			else
			{
			    // empty range, valid is set to true
			    y2warning("empty integer range, assuming any integer");
			}
		    }
		}
		else if (regexpmatch(type, "^regexp\\(.*\\)$"))
		{
		    string regex = regexpsub(type, "^regexp\\((.*)\\)$", "\\1");
		    valid = regexpmatch(new_value, regex);
		}
		else if (type == "ip")
		{
		    // check IP adress using function from network/ip.ycp include
		    valid = IP::Check(new_value);
		}
		else if (type == "ip4")
		{
		    // check IP adress using function from network/ip.ycp include
		    valid = IP::Check4(new_value);
		}
		else if (type == "ip6")
		{
		    // check IP adress using function from network/ip.ycp include
		    valid = IP::Check6(new_value);
		}
		else
		{
		    y2warning("Unknown data type %1 for variable %2", type, name);
		}
	    }

	    if (valid == false && force == false)
	    {
		return `not_valid;
	    }
	    else
	    {
		modified_variables[variable] = new_value;
		return `ok;
	    }
	}
    }

    // value was not changed => OK
    return `ok;
}

/**
 * Return modification status of variable
 * @param varid Variable identification
 * @return boolean True if variable was modified
 */
global define boolean modified(string varid) ``{
    return haskey(modified_variables, varid);
}

/**
 * Get list of modified variables
 * @return list List of modified variables
 */
global define list<string> get_modified()
``{

    list<string> ret = [];

    foreach(string varid, string new_value, modified_variables,
	``{
	    ret = add(ret, varid);
	}
    );

    return ret;
}

/**
 * Get list of all variables
 * @return list List of variable identifications
 */
global define list<string> get_all()
``{
    list<string> ret = [];

    foreach(string varid, string new_value, variable_locations,
	``{
	    ret = add(ret, varid);
	}
    );

    return ret;
}

/**
 * Get map of all variables
 * @return map Map of variable names, key is variable name, value is a list of variable identifications
 */
global define map<string, list<string> > get_all_names()
``{
    map<string, list<string> > ret = $[];

    foreach(string varid, string new_value, variable_locations,
	``{
	    string name = get_name_from_id(varid);

	    if (haskey(ret, name) == true)
	    {
		ret = (map<string, list<string> >) add(ret, name, add(ret[name]:[], varid));
	    }
	    else
	    {
		ret = (map<string, list<string> >) add(ret, name, [varid]);
	    }
	}
    );

    return ret;
}

/**
 * Register .syseditor path (use INI agent in multiple file mode)
 */
global define void RegisterAgents() {
    list<string> files = (Sysconfig::powertweak_mode == true) ? [powertweakconfig] : configfiles;
    string tmpdir = (string) SCR::Read(.target.tmpdir);

    if (tmpdir == nil || tmpdir == "")
    {
	y2security("Using /tmp directory !");
	tmpdir = "/tmp";
    }

    // register configuration files in SCR using INI-agent
    string agentdef = sformat(".syseditor\n\n`ag_ini(`IniAgent( %1,\n", files) + (string)SCR::Read(.target.string, Directory::ydatadir + (powertweak_mode ? "/powertweak.agent" : "/sysedit.agent"));
    string tmp = tmpdir + "/sysconfig-agent.scr";

    SCR::Write(.target.string, tmp, agentdef);
    SCR::RegisterAgent(.syseditor, tmp);
}

/**
 * Read all sysconfig variables
 * @return true on success
 */
global define boolean Read() ``{

    // TODO: solve custom_files parameter problem (used in Import)
    // read only powerteak config or all sysconfig files
    list<string> files = (Sysconfig::powertweak_mode == true) ? [powertweakconfig] : configfiles;

    string tmpdir = (string) SCR::Read(.target.tmpdir);
    if (tmpdir == nil || tmpdir == "")
    {
	y2security("Using /tmp directory !");
	tmpdir = "/tmp";
    }

    // register .syseditor path
    RegisterAgents();

    // register agent for reading network template
    string agentdef = sformat(".sysconfig.network.template,\n\n`ag_ini(`IniAgent(\"/etc/sysconfig/network/ifcfg.template\"\n,") + (string) SCR::Read(.target.string, Directory::ydatadir + "/sysedit.agent");
    string tmp = tmpdir + "/sysconfig-template-agent.scr";

    SCR::Write(.target.string, tmp, agentdef);
    SCR::RegisterAgent(.sysconfig.network.template, tmp);


    // list of all config files
    y2milestone("Registered config files: %1", SCR::Dir(.syseditor.section));

    // create script options
    string param = "";
    foreach(string par, files, ``{param = param + "'" + par + "' ";});

    // create tree definition list and description map using external Perl script
    SCR::Execute(.target.bash, Directory::bindir + "/parse_configs.pl " + (powertweak_mode ? "--powertweak " : "") + param + "> " + tmpdir + "/treedef.ycp");

    // read list
    list parsed_output = (list) SCR::Read(.target.ycp, tmpdir + "/treedef.ycp");
    tree_content = parsed_output[0]:[];

    node_comments = parsed_output[1]:$[];

    variable_locations = parsed_output[2]:$[];

    // redefined variables (variables which are defined in more files)
    map redefined_vars = parsed_output[3]:$[];
    if (size(redefined_vars) > 0)
    {
	y2warning("Redefined variables: %1", redefined_vars);
    }

    // read map with activation commands
    actions = parsed_output[4]:$[];

    return true;
}

/**
 * Display confirmation dialog
 * @param message Confirmation message
 * @param command Command to confirm
 * @return symbol `cont - start command, `skip - skip this command, `abort - skip all remaining commands
 */
define symbol ConfirmationDialog(string message, string command) ``{
    UI::OpenDialog(
	`opt(`decorated),
	`VBox(
	    `Label(message),
	    (size(command) > 0) ? `Label(_("Command: ") + command) : `Empty(),
	    `VSpacing(0.5),
	    `HBox(
		`PushButton(`id(`cont), Label::ContinueButton()),
		// button label
		`PushButton(`id(`skip), _("S&kip")),
		`PushButton(`id(`abort), Label::AbortButton())
	    )
	)
    );

    symbol ret = nil;

    while (ret != `cont && ret != `skip && ret != `abort)
    {
	ret = (symbol) UI::UserInput();

	if (ret == `close)
	{
	    ret = `abort;
	}
    }

    UI::CloseDialog();

    return ret;
}

/**
 * Start activation command, ask user to confirm it when it is required.
 * Display specified error message when the command fails.
 * @param start_command Command to start
 * @param label Progress bar label
 * @param error Error message displayed when command failes
 * @param confirm Confirmation messge
 * @param confirmaction Display confirmation dialog
 * @return symbol `success - command was started, `failed - command failed (non-zero exit value),
 * `skip - command was skipped, `abort - command starting was aborted
 */
define symbol StartCommand(string start_command, string label, string error, string confirm, boolean confirmaction) ``{
    if (size(start_command) == 0)
    {
	return `success;
    }

    // set progress bar label
    Progress::Title(label);

    if (confirmaction == true)
    {
	// show confirmation dialog
	symbol input = ConfirmationDialog(confirm, start_command);

	if (input != `cont)
	{
	    return input;
	}
    }

    y2milestone("Starting: %1", start_command);

    map output = (map) SCR::Execute(.target.bash_output, start_command);
    integer exit = output["exit"]:0;
    y2milestone("Result: %1", exit);

    if (exit != 0)
    {
	Report::Error(error);
	return `failed;
    }

    return `success;
}


/**
 * Write all sysconfig settings
 * @return boolean true on success
 */
global define boolean Write() ``{

    // remember all actions - start each action only once
    map<string,boolean> Configmodules = $[];
    map<string,boolean> Restarted = $[];
    map<string,boolean> Reloaded = $[];
    map<string,boolean> Commands = $[];
    // will be complete SuSEconfig started?
    boolean CompleteSuSEconfig = false;

    // aborted?
    boolean abort = false;

    // SuSEconfig script name
    string SuSEconfigName = "SuSEconfig";

    // start presave commands
    foreach (string vid, string new_val, modified_variables, ``{
	    if (abort)
	    {
		return;
	    }

	    // get activation map for variable
	    string presave = (string) (actions[vid, "Pre"]:nil);

	    if (presave != nil && size(presave) > 0)
	    {
		string confirm = _("A command will be executed");
		string label = sformat(_("Starting command: %1..."), presave);
		string error = sformat(_("Command %1 failed"), presave);

		symbol precommandresult = StartCommand(presave, label, error, confirm, ConfirmActions);

		if (precommandresult == `abort)
		{
		    abort = true;
		}
		else if (precommandresult == `failed)
		{
		    // PreSaveCommand returned non zero value
		    // remove activation data for failed variable
		    // (insert empty Command: to not start complete SuSEconfig)
		    actions[vid] = $["Cmd" : ""];
		}
		else if (precommandresult != `success && precommandresult != `skip)
		{
		    y2error("Unknown return value from StartCommand(): %1", precommandresult);
		}
	    }
	}
    );

    if (abort)
    {
	return false;
    }

    foreach (string vid, string new_val, modified_variables, ``{
	// get activation map for variable
	map<string, any> activate = actions[vid]:$[];

	string restart_service = (string) (activate["Rest"]:nil);
	string reload_service = (string) (activate["Reld"]:nil);
	string config_module = (string) (activate["Cfg"]:nil);
	string bash_command = (string) (activate["Cmd"]:nil);

	if (config_module == nil && reload_service == nil && restart_service == nil && bash_command == nil && CompleteSuSEconfig == false && !write_only)
	{
	    // start complete SuSEconfig (backward compatibility)
	    CompleteSuSEconfig = true;
	}
	else
	{
	    if (restart_service != nil && size(restart_service) > 0)
	    {
		list<string> parsed = String::ParseOptions(restart_service, parse_param);
		foreach(string s, parsed,
		    ``{
			Restarted[s] = true;
		    }
		);
	    }

	    if (reload_service != nil && size(reload_service) > 0)
	    {
		list<string> parsed = String::ParseOptions(reload_service, parse_param);
		foreach(string s, parsed,
		    ``{
			Reloaded[s] = true;
		    }
		);
	    }

	    if (config_module != nil && size(config_module) > 0)
	    {
		list<string> parsed = String::ParseOptions(config_module, parse_param);
		foreach(string s, parsed,
		    ``{
			Configmodules[s] = true;
		    }
		);
	    }

	    if (bash_command != nil && size(bash_command) > 0)
	    {
		Commands[bash_command] = true;
	    }
	}
    });

    // write dialog caption
    string caption = (powertweak_mode == true) ? _("Saving Powertweak Configuration") : _("Saving sysconfig Configuration");

    // set the right number of stages
    integer steps = (powertweak_mode == false) ? size(modified_variables) + size(Restarted) + size(Reloaded) + size(Commands) + ((CompleteSuSEconfig) ? 1 : size(Configmodules)) + 1 /* flush*/ + 3 /* 3 stages */ : (size(modified_variables) + 2) /* 2 stages */;

    // We do not set help text here, because it was set outside
    Progress::New(caption, " ", steps, [
	// progress bar item
	    _("Write the new settings"),
	    _("Activate the changes")
	],
	nil,
	""
    );

    Progress::NextStage();

    boolean ret = true;

    // save each changed variable
    foreach (string vid, string new_val, modified_variables,
	``{
	    string file = get_file_from_id(vid);
	    string name = get_name_from_id(vid);
	    path value_path = add(add(.syseditor.value, file), name);

	    boolean written = SCR::Write(value_path, new_val);
	    if (written == false)
	    {
		// error popup: %1 - variable name (e.g. DISPLAYMANAGER), %2 - file name (/etc/sysconfig/displaymanager)
		Report::Error(sformat(_("Saving variable %1 to the file %2 failed."), name, file));
		ret = false;
	    }

	    // progress bar label, %1 is variable name (e.g. DISPLAYMANAGER)
	    Progress::Title(sformat(_("Saving variable %1..."), name));
	    Progress::NextStep();
	}
    );

    Progress::Title(_("Saving changes to the files..."));
    // flush changes
    SCR::Write(.syseditor, nil);
    Progress::NextStep();

    // now start required activation commands
    Progress::NextStage();

    // activate powertweak configuration
    if (powertweak_mode == true)
    {
	Progress::Title(sformat(_("Starting command: %1..."), "/usr/sbin/powertweakd --no-daemon"));

	y2milestone("Activating powertweak configuration");
	ret = (SCR::Execute(.target.bash, "/usr/sbin/powertweakd --no-daemon") == 0);

	if (ret == false)
	{
	    return ret;
	}

	// activate powertweak at startup
	ret = Service::Enable("powertweakd");
	return ret;
    }

    // start SuSEconfig
    if (CompleteSuSEconfig == true)
    {
	// action command is missing, start complete SuSEconfig (backward compatibility)
	string start_command = "/sbin/SuSEconfig";
	// message
	string confirm = _("All configuration scripts will be started.");
	// progeress bar label
	string label = _("Starting configuration scripts...");
	// Error message
	string error = _("Configuration script failed.");

	Progress::NextStep();

	if (StartCommand(start_command, label, error, confirm, ConfirmActions) == `abort)
	{
	    return false;
	}
    }
    else if (size(Configmodules) > 0)
    {
	// start only required SuSEconfig modules
	foreach(string modulename, boolean dummy, Configmodules,
	``{
	    if (abort) return;

	    // action command is missing, start complete SuSEconfig (backward compatibility)
	    string start_command = sformat("/sbin/SuSEconfig --module %1", modulename);
	    string confirm = sformat(_("Configuration module %1 will be started."), modulename);
	    // progress bar label - %1 is module name
	    string label = sformat(_("Starting configuration module %1..."), modulename);
	    // error message (config module failed) - %1 is module name
	    string error = sformat(_("Configuration module %1 failed."), modulename);

	    Progress::NextStep();

	    if (StartCommand(start_command, label, error, confirm, ConfirmActions) == `abort)
	    {
		abort = true;
	    }
	}
	);
    }

    if (abort) return false;

    if (size(Reloaded) > 0)
    {
	// restart required services
	foreach(string servicename, boolean dummy, Reloaded,
	``{
	    if (abort) return;
	    // check whether service is running
	    string check_command = sformat("/etc/init.d/%1 status", servicename);
	    integer result = (integer)SCR::Execute(.target.bash, check_command);
	    y2milestone("%1 service status: %2", servicename, result);

	    if (result == 0)
	    {
		// service is running, reload it
		string start_command = sformat("/etc/init.d/%1 reload", servicename);
		string confirm = sformat(_("Service %1 will be reloaded"), servicename);
		string label = sformat(_("Reloading service %1..."), servicename);
		string error = sformat(_("Reload of the service %1 failed"), servicename);

		Progress::NextStep();

		if (StartCommand(start_command, label, error, confirm, ConfirmActions) == `abort)
		{
		    abort = true;
		}
	    }
	}
	);
    }

    if (abort) return false;

    if (size(Restarted) > 0)
    {
	// restart required services
	foreach(string servicename, boolean dummy, Restarted,
	``{
	    // check whether service is running
	    string check_command = sformat("/etc/init.d/%1 status", servicename);
	    integer result = (integer)SCR::Execute(.target.bash, check_command);
	    y2milestone("%1 service status: %2", servicename, result);

	    Progress::NextStep();

	    if (result == 0)
	    {
		// service is running, restart it
		string start_command = sformat("/etc/init.d/%1 restart", servicename);
		string confirm = sformat(_("Service %1 will be restarted"), servicename);
		string label = sformat(_("Restarting service %1..."), servicename);
		string error = sformat(_("Restart of the service %1 failed"), servicename);

		if (StartCommand(start_command, label, error, confirm, ConfirmActions) == `abort)
		{
		    abort = true;
		}
	    }
	}
	);
    }

    if (size(Commands) > 0)
    {
	// start generic commands
	foreach(string cmd, boolean dummy, Commands,
	``{
	    y2milestone("Command: %1", cmd);
	    Progress::NextStep();

	    if (size(cmd) > 0)
	    {
		string confirm = _("A command will be executed");
		string label = sformat(_("Starting command: %1..."), cmd);
		string error = sformat(_("Command %1 failed"), cmd);

		if (StartCommand(cmd, label, error, confirm, ConfirmActions) == `abort)
		{
		    abort = true;
		}
	    }
	}
	);
    }

    if (abort) return false;

    // set 100% in progress bar
    Progress::NextStep();
    Progress::Title(_("Finished"));

    // set "finished" mark for the last stage
    Progress::NextStage();

    return ret;
}

/**
 * Set all sysconfig settings from the list
 * (For use by autoinstallation.)
 * @param settings The YCP structure to be set.
 */
global define void Set(list<map> settings) ``{
    if (settings != nil)
    {
	modified_variables = $[];
	custom_files = configfiles;

	// convert from 8.1 export format
	foreach(map setting, settings,
	    ``{
		string n = setting["sysconfig_key"]:"";
		string f = setting["sysconfig_path"]:"";
		string v = setting["sysconfig_value"]:"";

		// compatibility mode for older release with relative path
		if (findfirstof(f, "/") != 0)
		{
		    f = sformat("/etc/sysconfig/%1", f);
		}

		string key = sformat("%1$%2", n, f);

		modified_variables = (map<string, string>) add(modified_variables, key, v);

		// add configuration file if it isn't already specified
		if (!contains(custom_files, f))
		{
		    custom_files = add(custom_files, f);
		}
	    }
	);
    }
}

/**
 * Set all sysconfig settings from the list and read information from files
 * (For use by autoinstallation.)
 * @param settings The YCP structure to be imported.
 * @return boolean True on success
 */
global define boolean Import (list settings) ``{
    list<map> settings_maps = (list<map>) settings;
    // set values in the list
    Set(settings_maps);

    // register agent for user defined files, read values
    return true;
}

/**
 * Dump the sysconfig settings to a single map
 * (For use by autoinstallation.)
 * @return list Dumped settings (later acceptable by Import ())
 */
global define list Export () ``{
    // return structured map (for 8.1 compatibility)

    list ret = [];

    if (size(modified_variables) > 0)
    {
	foreach(string varid, string val, modified_variables,
	    ``{
		string n = get_name_from_id(varid);
		string f = get_file_from_id(varid);

		map m = $[
		    "sysconfig_key"   : n,
		    "sysconfig_path"  : f,
		    "sysconfig_value" : val
		];

		ret = add(ret, m);
	    }
	);
    }

    return ret;
}

/**
 * Create a textual summary
 * @return summary of the current configuration
 */
global define string Summary() ``{
    // configuration summary headline
    string summary = Summary::AddHeader("", _("Configuration Summary"));

    y2milestone("Summary: %1", modified_variables);

    if (size(modified_variables) > 0)
    {
	foreach(string varid, string newval, modified_variables,
	    ``{
		string varnam = get_name_from_id(varid);
		string filename = get_file_from_id(varid);

		summary = Summary::AddLine(summary, sformat("%1=\"%2\" (%3)", varnam, newval, filename));
	    }
	);
    }
    else
    {
	summary = Summary::AddLine(summary, Summary::NotConfigured());
    }

    return summary;
}

/* EOF */
}
