/**
 * File:
 *   RunlevelEd.ycp
 * Package:
 *   System Services (Runlevel) (formerly known as Runlevel Editor)
 * Summary:
 *   Data for configuration of services, input and output functions.
 *
 * Authors:
 *   Martin Vidner <mvidner@suse.cz>
 *   Petr Blahos <pblahos@suse.cz>
 *   Martin Lazar <mlazar@suse.cz>
 *
 * $Id: RunlevelEd.ycp 21394 2005-02-08 09:36:55Z jsuchome $
 */

{
    module "RunlevelEd";
    textdomain "runlevel";

    import "Service";
    import "Progress";
    import "Summary";
    import "Report";
    import "CommandLine";

    include "runlevel/toposort.ycp";

    /**
     * Proposal parameter: if it changes, we repropose
     */
    global boolean x11_selected = nil;

    /**
     * Like "requires" but in reverse direction.
     * Used for stopping and disabling services.
     */
    map<string, list<string> >  what_requires = $[];

    /**
     * A graph where nodes are scripts or system facilities
     * but not normal facilities (ie. provides are solved).
     */
    map<string, list<string> >  requires = $[];

    define void buildRequires ();
    define void addRequires (string service, list<string> req_facilities);
    define list reverse (list l);
    global define boolean StartContainsImplicitly (list<string> rls, string rl);
    define boolean ImplicitlySubset (list<string> rls_a, list<string> rls_b);
    define boolean subset (list a, list b);

    /**
     * @struct service
     * One service is described by such map: <pre>
      "servicename" : $[
	"defstart" : [ "2", "3", "5", ], // Default-Start comment
	"defstop"  : [ "0", "1", "6", ], // Default-Stop  comment

	// "should" dependencies (+) are filtered by addRequires
	"reqstart" : [ "$network", "portmap" ], // Required-Start comment
	"reqstop"  : [ "$network", "portmap" ], // Required-Stop  comment

	"shortdescription" : "text...",       // Description comment
	"description" : "text...",       // Description comment

	// which runlevels service is really started/stopped in
	// read from /etc/init.d/{rc?,boot}.d/* links
	//
	// Note that the boot process (init.d/boot.d) is considered
	// a "B" runlevel that is implicitly contained in the other runlevels.
	// Using
	//   list st = services ["boot.local", "start"]:[]
	//   contains (st, "3") // DON'T
	// results in false but that's probably not what you want.
	// Use
	//   StartContainsImplicitly (st, "3")
	// which tests for "3" and "B".
	"start" : [ "3", "5", ],
	"stop"  : [ "3", "5", ],

	"started" : 0, // return from rcservice status (integer)

	"dirty" : false, // was the entry changed?
      ]</pre>
     */
    //
    //


    /**
     * List of all services. Each item is a map described above.
     * @ref service
     */
    global map<string,map> services = $[];

    /**
     * List of all service names.
     * Filled by Read, used to get all services' status.
     */
    global list service_list = [];

    /**
     * Default runlevel (after boot)
     */
    global string default = "";

    /**
     * Backup of default runlevel.
     */
    string default_orig = "";

    /**
     * List of all runlevels available in the system.
     */
    global list<string> runlevels = [];

    /**
     * Current runlevel
     */
    global string current = "";

    /* Dependency solving: */
    /**
     * ONLY ONE SCRIPT provides a facility in this model.
     * In SuSE packages, the only exception are sendmail and postfix
     * both providing sendmail but they cannot be installed together
     * anyway.
     * atd has Provides: at, so
     *   what_provides["at"] == "atd";
     * Identity is not represented explicitly: ypbind has Provides: ypbind, but
     *   haskey (what_provides, "ypbind") == false;
     */
    map what_provides = $[];

    /**
     * System facility definitions
     * "should" dependencies (+) are filtered by addRequires
     * /etc/insserv.conf:
     *   system_requires["$network"] == ["network", "+pcmcia", "+hotplug"];
     */
    map<string, list<string> > system_requires = $[];

    /**
     * Read settings
     * @return success
     */
    global define boolean Read () ``{
	// progress caption
	Progress::Simple (_("Initializing system services (runlevel). Please wait..."), " ", 7, "");
	Progress::NextStep ();
	runlevels= (list<string>) SCR::Read (.init.scripts.runlevel_list);
	if (0 == size (runlevels))
	{
	    runlevels = ["0", "1", "2", "3", "5", "6", "S", ];
	}
	Progress::NextStep ();

	current = (string) SCR::Read (.init.scripts.current_runlevel);
	Progress::NextStep ();

	//..
	default = (string) SCR::Read (.init.scripts.default_runlevel);
	default_orig = default;
	Progress::NextStep ();

	system_requires = (map<string, list<string> >) SCR::Read (.init.insserv_conf);
	Progress::NextStep ();

	map details = (map) SCR::Read (.init.scripts.runlevels);
	Progress::NextStep ();
	services = (map<string,map>) SCR::Read (.init.scripts.comments);
	Progress::NextStep ();
	services = (map<string, map>) mapmap (string k, map v, services, ``{
	    foreach (string f, v["provides"]:[], ``{
		// identity implicit; only the first script provides a facility
		if (f != k && !haskey (what_provides, f))
		{
		    what_provides[f] = k;
		}
	    });

	    service_list[size(service_list)] = k;


	    // play tennis
	    map second_service = details[k]:$[];
	    v["start"] = second_service["start"]:[];
	    v["stop"] = second_service["stop"]:[];
	    return $[ k: v ];
	});
	buildRequires ();
	what_requires = ReverseGraph (requires);
	Progress::NextStep ();
	return true;
    }

    /**
     * If there's a dependency loop, dependency checking is disabled.
     */
    boolean dependencies_disabled = false;
    /**
     * Create requires from services, system_requires and what_provides.
     */
    define void buildRequires () ``{
	foreach (string service, map comments, services, ``{
	    addRequires (service, (list<string>) (comments["reqstart"]:[]));
	});
	foreach (string sys_f, list<string> req, system_requires, ``{
	    addRequires (sys_f, req);
	});
    }
    /**
     * Resolve provides, filter out "should" dependencies (+)
     * and add the requirements to "requires".
     * Missing services are not detected.
     * @param service a service
     * @param req_facilities its required facilities
     */
    define void addRequires (string service, list<string> req_facilities) ``{
	list<string> req_s = filter (string f, req_facilities,
			     ``( substring (f, 0, 1) != "+"));
	req_s = maplist (string f, req_s, ``( what_provides[f]:f ));
	requires[service] = req_s;
    }

    /**
     * Resolve which services need to be enabled/disabled
     * @param service a service
     * @param enable enabling or disabling a service?
     * @return a list of services (excluding itself) required to start
     * a service (enable) or to be stopped because they require the
     * service (disable), ordered by their dependencies. Missing
     * services are included, system facilities excluded.<br>
     * If dependencies are disabled, returns an empty list, as if
     * there were no dependencies.
     */
    global define list<string> ServiceDependencies (string service,
						    boolean enable) ``{
	if (dependencies_disabled)
	{
	    return [];
	}
	// make a dependency subgraph
	map<string, list<string> > s_req = ReachableSubgraph (enable? requires: what_requires,
				       service);
	y2debug ("DEPGRAPH %1: %2", service, s_req);
	// sort it
	list r = TopologicalSort (s_req);
	list<string> sorted = r[0]:[];
	list rest = r[1]:[];
	if (size (rest) > 0)
	{
	    // TODO: localize the loop, disable it only locally
	    // and say what scripts form it
	    Report::Error (_("A dependency loop was detected.
Further dependency checking will be disabled."));
	    dependencies_disabled = true;
	}

	// filter system facilities
	sorted = filter (string f, sorted, ``(substring (f, 0, 1) != "$"));
	// remove the original service
	sorted = remove (sorted, 0);
	// reverse it so that the required services are first
	return (list<string>) reverse (sorted);
    }

    /**
     * Argh, not a builtin
     * @param l a list
     * @return reversed list
     */
    define list reverse (list l) ``{
	if (l == nil)
	{
	    return nil;
	}
	list result = [];
	foreach (any item, l, ``{
	    result = prepend (result, item);
	});
	return result;
    }

    /**
     * Gets a list of dependent services and a target state they
     * should be in. Filters out those that are already in the target
     * state.
     * If both init_time and run_time are on, a conjunction is needed.
     * @param svcs	dependent services
     * @param rls	used for init_time
     * @param enable	on/off:
     * @param init_time enable/disable
     * @param run_time  start/stop
     */
    global define list<string> FilterAlreadyDoneServices (list<string> svcs,
							  list<string> rls,
							  boolean enable,
							  boolean init_time,
							  boolean run_time) ``{

	// one: exactly one runlevel. nil means (disable) in all runlevels
	boolean one = rls != nil && size (rls) == 1;
	if (init_time && !enable &&
	    rls != nil && !one)
	{
	    // should not happen
	    y2error ("Disabling in a nontrivial set of runlevels (%1) not implemented.", rls);
	    return nil;
	}
	string rl = rls[0]:"";

	return filter (string service, svcs, ``{
	    boolean all_ok = nil; // is the service in the target state?

	    // run_time
	    boolean run_ok = true;
	    if (run_time)
	    {
		boolean started =
		    services[service, "started"]:-1 == 0 ||
		    // boot scripts are always considered to be started,
		    // even if they return 4 :(
		    services[service, "defstart"]:[] == [ "B" ] ||
		    // and while we're at it with kludges,
		    // pretend nfs is running (ie. /usr is available)
		    // because it reports 3 when no nfs imports are defined :(
		    // TODO resolve it better!
		    service == "nfs";
		run_ok = started == enable;
	    }

	    // init_time
	    boolean init_ok = true;
	    if (init_time)
	    {
		list<string> start = services[service, "start"]:[];

		if (one)
		{
		    init_ok = enable == StartContainsImplicitly (start, rl);
		}
		else
		{
		    if (enable)
		    {
			init_ok = ImplicitlySubset (rls, start);
		    }
		    else
		    {
			// rls is nil, we only support disabling
			// in one or all runleves at once
			init_ok = start == [];
		    }
		}
	    }


	    // keep it in the list if something needs to be done
	    return !(init_ok && run_ok);
	});
    }


    /**
     * Is a service started in a runlevel, given the list of rulevels
     * it is started in?
     * This looks like a simple contains,
     * but "B" implicitly expands to all runlevels.
     * See also bug #17234.
     * @param rls runlevels the service is started in
     * @param rl  which runlevel is tested
     * @return should it be running in rl?
     */
    global define boolean StartContainsImplicitly (list<string> rls,
						   string rl) ``{
	return contains (rls, "B") || contains (rls, rl);
    }
    /**
     * Whether a set of runlevels is a subset of another set of runlevels.
     * But expands "B" to the whole set
     */
    define boolean ImplicitlySubset (list<string> rls_a,
					    list<string> rls_b) ``{
	return contains (rls_b, "B") || subset (rls_a, rls_b);
    }

    /**
     * @param a a set
     * @param b a set
     * @return a \subseteq b
     */
    define boolean subset (list a, list b) ``{
	return size (union (a, b)) <= size (b);
    }

    /**
     * Set all dirty services as clean and tries to read
     * original "start"/"stop" for them.
     */
    global define void ClearServices () ``{
	services = (map<string, map>)mapmap (string k, map v, services, ``{
	    if (v["dirty"]:false)
	    {
		v["dirty"] = false;
		map r = (map) SCR::Read (.init.scripts.runlevel, k);
		r = r[k]:$[];
		v["start"] = r["start"]:[];
		v["stop"] = r["stop"]:[];
	    }
	    return $[k: v];
	});
    }

    /**
     * Is a service disabled?
     * Checks whether the default runlevel is in the list of runlevels
     * @param service service to check
     * @return boolean true if service is disabled
     */
    global define boolean isDisabled (map service) ``{
	return !contains(service["start"]:[], default);
    }

    /**
     * Check for portmap. Portmap should be started if inetd, nfs,
     * nfsserver, nis, ... is started. This checks the dependency.
     * @return string name of the first enabled service that requires portmap
     */
    global define string CheckPortmap () ``{
	if (!isDisabled (services["portmap"]:$[]))  // if portmap is enabled, there is no problem
	    return nil;
	string req = nil;
	list in = [];
	foreach (string k, map v, services, ``{
	    if (contains (v["reqstart"]:[], "portmap") && size (v["start"]:[]) > 0)
	    {
		in = union (in, toset (v["start"]:[]));
		if (nil == req)
		    req = k;
	    }
	});
	return size (in) > 0 ? req : nil;
    }

    /**
     * Save changed services into proper runlevels. Save also changed
     * default runlevel.
     * @return success
     */
    global define boolean Write () ``{
	integer prsize = size (services);
	// progress caption
	Progress::Simple (_("Saving changes to runlevels."), " ", prsize + 1, "");

	if (default != default_orig)
	    SCR::Write (.init.scripts.default_runlevel, default);
	Progress::NextStep ();

	map<string,map> failed = (map<string, map>)filter (string k, map v, services, ``{
	    boolean fail = false;
	    if (v["dirty"]:false)
	    {
		// progress item, %1 is a service (init script) name
		Progress::Title (sformat (_("Service %1"), k));
		// save!
		list start = v["start"]:[];
		y2milestone ("Setting %1: %2", k, start);
		string verbose = sformat(_("Setting %1: %2"), k, start);
		CommandLine::PrintVerbose(verbose);
		fail = ! Service::Finetune (k, start);
	    }
	    else
		// progress item, %1 is a service (init script) name
		Progress::Title (sformat (_("Skipping service %1."), k));
	    Progress::NextStep ();
	    return fail;
	});
	Progress::NextStep ();
	string failed_s = mergestring ((list<string>) maplist (string k, any v, failed, ``(k)), ", ");
	if (size (failed_s) > 0)
	{
	    Report::Error (sformat (_("Failed services: %1."), failed_s));
	    return false;
	}
	return true;
    }

    /**
     * Were some settings changed?
     * @return true if yes
     */
    global define boolean isDirty () ``{
	if (default != default_orig)
	    return true;

	boolean dirty = false;
	foreach (string k, map v, services, ``{
	    if (dirty)
	    {
		return ;
	    }
	    if (v["dirty"]:false)
	    {
		dirty = true;
	    }
	});
	return dirty;
    }

    /**
     * Returns true if the settings were modified
     * @return settings were modified
     */
    global boolean GetModified () {
	return isDirty ();
    }

    /**
     * Function sets an internal variable indicating that any
     * settings were modified to "true".
     * Used for autoinst cloning.
     */
    global void SetModified () {
	// Make sure GetModified will return true
	default_orig = "---";
	// maybe we should also touch dirty for all services,
	// but that depends on what autoinst Clone really wants
    }

    /**
     * Export user settings.
     * @return user settings:<pre>$[
     *    "services": $[ map of dirty services ],
     *    "default":  the default runlevel, if changed,
     *]</pre>
     */
    global define map Export () ``{
        y2debug("services: %1", services);
	map<string,map> svc = (map<string, map>)filter (string k, map v, services, ``{
	    return v["dirty"]:false;
	});
	list tmp_services = maplist(string service_name, map service_data, svc, ``{
	    string  service_start = mergestring(service_data["start"]:[], " ");
	    string service_stop = mergestring(service_data["stop"]:[], " ");

	    map service_map = $[];
	    service_map["service_name"] = service_name;
	    if (size(service_start) > 0 )
	    {
		service_map["service_start"] = service_start;
	    }
	    if (size(service_stop) > 0 )
	    {
		service_map["service_stop"] = service_stop;
	    }

	    return (service_map);
	});
        map ret = $[];
        if (size(tmp_services)>0)
        {
	    ret = $[ "services" : tmp_services ];
        }
	if ( default != "")
	{
	    ret["default"] = default;
	}
	return ret;
    }
    /**
     * Import user settings
     * @param s user settings
     * @see Export
     * @return success state
     */
    global define boolean Import (map s) ``{
	runlevels = (list<string>) SCR::Read (.init.scripts.runlevel_list);
	if (0 == size (runlevels))
	{
	    runlevels = ["0", "1", "2", "3", "5", "6", "S", ];
	}

	//..
	default = (string) SCR::Read (.init.scripts.default_runlevel);
	default_orig = default;

	// and finaly process map being imported
	list<map> new = s["services"]:[];
	map<string,map> tmp_services = (map<string, map>)listmap(map service, new,  ``{
	    string name = service["service_name"]:"";
	    list stop = [];
	    list start = [];
	    if (haskey(service, "service_status"))
	    {
		if (service["service_status"]:"" == "enable")
		{
		    map info = Service::Info(name);
		    y2milestone("service info for %1: %2", name, info);
		    start = info["defstart"]:[];
		    stop = info["defstop"]:[];
		}
		else if (service["service_status"]:"" == "disable")
		{
		    start = [];
		    stop = [];
		}
	    }
	    else
	    {
		start = splitstring(service["service_start"]:"", " ");
		stop = splitstring(service["service_stop"]:"", " ");
	    }

	    map service_map = $[];
	    if (size(start) > 0 )
	    service_map["start"] = start;
	    if (size(stop) > 0 )
	    service_map["stop"] = stop;
	    return($[name:service_map]);
	});

	if (size (tmp_services) > 0)
	{
	    foreach (string k, map v, tmp_services, ``{
		if (nil == services[k]:nil)
		{
		    y2milestone ("Service %1 is not installed on target system, adding it by hand.", k);
		}
		v["dirty"] = true;
		services[k] = v;
	    });
	}
	else
		services = $[];
	// default
	if (haskey (s, "default"))
	{
	    default = s["default"]:"";
	    default_orig = "---";
	}
	return true;
    }

    /**
     * Returns textual runlevel description.
     * Descriptions are hard-coded in ycp script.
     * @param rl Runlevel to check.
     * @return string Description.
     */
    global define string getRunlevelDescr (string rl) ``{
	map<string, string> descr = $[
	    // descriptions of runlevels. there must be number: description
	    // number is runlevel name
	    // runlevel description
	    "0" : _("0: System halt"),
	    // runlevel description
	    "1" : _("1: Single user mode"),
	    // runlevel description
	    "2" : _("2: Local multiuser without remote network"),
	    // runlevel description
	    "3" : _("3: Full multiuser with network"),
	    // runlevel description
	    "4" : _("4: User defined"),
	    // runlevel description
	    "5" : _("5: Full multiuser with network and display manager"),
	    // runlevel description
	    "6" : _("6: System reboot"),
	    // runlevel description
	    // internal one: without a number
	    "" : _("Unchanged"),
	    ];
	return descr[rl]:rl;
    }

    /**
     * @return Html formatted summary for the installation proposal
     */
    global define string ProposalSummary() ``{
	string sum = "";
	// summary header
	sum = Summary::OpenList(sum);
	sum = Summary::AddListItem(sum,  getRunlevelDescr (default));
	sum = Summary::CloseList(sum);

	return sum;
    }

    /**
     * @return Html formatted configuration summary
     */
    global define string Summary()
	``{
	string sum = "";
        sum = Summary::AddHeader(sum, _("Default Runlevel"));
        sum = Summary::AddLine(sum,  getRunlevelDescr (default));
	// summary header
	sum = Summary::AddHeader(sum, _("Services"));

	if (size(services)>0) {
	    sum = Summary::OpenList(sum);
	    foreach(string k, map v, services, ``{
		if (v["dirty"]:false)
		{
		    string item = sformat (
			// summary item: %1 service name,
			// %2, %3 list of runlevels it starts/stops in
			_("<p><b>%1</b><br> Start: %2</p>"),
			k,
			mergestring(v["start"]:[], " ")
			);
		    sum = Summary::AddListItem(sum, item );
		}
	    });
	    sum = Summary::CloseList(sum);
	}
	else
	{
	    sum =  Summary::AddLine(sum, Summary::NotConfigured());
	}
	return sum;
    }

    // visualization helper

    /**
     * A buffer for @ref sprint
     */
    string sprint_buffer = "";

    /**
     * String print
     * @param s a string to add to @ref sprint_buffer
     */
    define void sprint (string s) ``{
	sprint_buffer = sprint_buffer + s;
    }

    /**
     * @return a graphviz graph of the service dependencies
     */
    global define string DotRequires () ``{
	map in_attr = $[
	    "$remote_fs": "[color=yellow, minlen=2]",
	    "$local_fs": "[color=green, minlen=2]",
	    "$network": "[color=magenta, minlen=2]",
	    "$syslog": "[color=cyan, minlen=2]",
	    ];
	sprint_buffer = "";
	sprint ("digraph services {\n");
	sprint ("\trankdir=LR;\n");
	sprint ("\t\"!missing\"[rank=max];\n");
	sprint ("\t\"$syslog\" -> \"$network\" [style=invis, minlen=10];\n");
	sprint ("\t\"$remote_fs\" -> \"$syslog\" [style=invis, minlen=5];\n");
	foreach (string n, list<string> e, requires, ``{
	    foreach (string target, e, ``{
		string attr = in_attr[target]:"";
		sprint (sformat ("\t\"%1\" -> \"%2\"%3;\n", n, target, attr));
	    });
	});
	sprint ("}\n");
	return sprint_buffer;
    }

/*** LiMaL API PART I. - Runlevels in /etc/inittab File ***/

global string GetCurrentRunlevel() {
    return current == "" ? "unknown" : current;
}
global string GetDefaultRunlevel() {
    return default;
}

global integer SetDefaultRunlevel(string rl) {
    default = rl;
    return 0;
}

global define list<string> GetAvailableRunlevels() {
    return runlevels;
}

/*** LiMaL API PART II. - Files and/or Symlinks in /etc/init.d and /etc/rc?.d ***/
global boolean ServiceAvailable(string service_name)
{
    return (boolean)SCR::Read(.init.scripts.exists, service_name);
}

global list<string> GetAvailableServices(boolean simple)
{
    list<string> s = (list<string>)maplist(string service_name, map opts, RunlevelEd::services, {return service_name;});
    if (simple) {
	s = filter(string service_name, s, {return !contains(services[service_name,"defstart"]:[], "B");});
    }
    return s;
}

global list<string> GetServiceCurrentStartRunlevels(string service_name)
{
    return services[service_name,"start"]:[];
}

global list<string> GetServiceCurrentStopRunlevels(string service_name)
{
    return services[service_name,"stop"]:[];
}

/*** LiMaL API PART III. - LSB Comments in Init Scripts ***/

//string GetServiceLSBCommentValue(string service_name, string key); // return particular LSB comment
//list_of_strings GetServiceLSBCommentKeys(string service_name); // return list of presented LSB comment keys

global list<string> GetServiceDefaultStartRunlevels(string service_name)
{
    return services[service_name,"defstart"]:[];
}

global list<string> GetServiceDefaultStopRunlevels(string service_name)
{
    return services[service_name,"defstop"]:[];
}

//boolean GetServiceDefaultEnabled(string service_name); // not in LSB?

global string GetServiceShortDescription(string service_name)
{
    return services[service_name, "shortdescription"]:"";
}

global string GetServiceDescription(string service_name)
{
    return services[service_name, "description"]:"";
}

//list_of_strings GetServiceProvides(string service_name);
//list_of_strings GetServiceRequiredStart(string service_name);
//list_of_strings GetServiceRequiredStop(string service_name);
//list_of_strings GetServiceShouldStart(string service_name);
//list_of_strings GetServiceShouldStop(string service_name);

/*** LiMaL API PART V. - Installation and Removal of init.d Files ***/

/**
 * Enable specified service, and all required services.
 * @param service	service name
 * @param rls		runlevels to enable in or nil for default runlevels
 * @return		0 = ok, 1 = service not found
 */
global integer ServiceInstall(string service, list<string> rls)
{
    if (!haskey(services, service))
	return 1; // service not found

    if (rls == nil)
	rls = GetServiceDefaultStartRunlevels(service);

    list<string> dep_s = RunlevelEd::ServiceDependencies(service, true);
    dep_s = RunlevelEd::FilterAlreadyDoneServices(dep_s, rls, true, true, false);
    if (dep_s != nil) {
	foreach(string i, dep_s, {
	    list<string> default_rls = GetServiceDefaultStartRunlevels(i);
	    if (contains(default_rls, "B")) {
		services[i,"start"] = union(["B"], services[service,"start"]:[]);
	    } else {
		services[i,"start"] = union(rls, services[service,"start"]:[]);
	    }
	    services[i,"dirty"] = true;
	});
    }
    services[service,"start"] = union(rls, services[service,"start"]:[]);
    services[service,"dirty"] = true;
    
    return 0;
}


/**
 * Disable specified service, and all dependence services.
 * @param service	service name
 * @param rls		runlevels to disable in or nil for default runlevels
 * @return		0 = ok
 */
global integer ServiceRemove(string service, list<string> rls)
{
    if (!haskey(services, service))
	return 0; // service not found (no error)

    if (rls == nil) {
	rls = GetServiceDefaultStartRunlevels(service);
    }
    list<string> dep_s = RunlevelEd::ServiceDependencies(service, false);
    foreach(string rl, (list<string>)union(rls, ["B"]), {
	list<string> dep_s1 = RunlevelEd::FilterAlreadyDoneServices(dep_s, [rl], false, true, false);
	if (dep_s1 != nil) {
	    foreach(string j, dep_s, {
		services[j,"start"] = filter(string i, services[j,"start"]:[], {return !contains(rls, i);});
		services[j,"dirty"] = true;
	    });
	}
    });
    services[service,"start"] = filter(string i, services[service,"start"]:[], {return !contains(rls, i);});
    services[service,"dirty"] = true;
    
    return 0;
}



}
