home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Chip 2007 January, February, March & April
/
Chip-Cover-CD-2007-02.iso
/
boot
/
i386
/
root
/
usr
/
share
/
YaST2
/
modules
/
RunlevelEd.ycp
< prev
next >
Wrap
Text File
|
2006-11-29
|
27KB
|
942 lines
/**
* 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 33299 2006-10-10 09:39:23Z locilka $
*/
{
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_runlevel = "";
/**
* 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_runlevel = (string) SCR::Read (.init.scripts.default_runlevel);
default_orig = default_runlevel;
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_runlevel);
}
/**
* 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_runlevel != default_orig)
SCR::Write (.init.scripts.default_runlevel, default_runlevel);
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_runlevel != 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_runlevel != "")
{
ret["default"] = default_runlevel;
}
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_runlevel = (string) SCR::Read (.init.scripts.default_runlevel);
default_orig = default_runlevel;
// 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_runlevel = 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_runlevel));
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_runlevel));
// 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_runlevel;
}
global integer SetDefaultRunlevel(string rl) {
default_runlevel = 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;
}
/**
* Returns items for default runlevel combo box.
* (Excludes 0, 1, 6, S and B)
* @param mode if `auto, adds Unchanged. if `proposal, only 2, 3 and 5
* @return list List of items. Default is selected.
*/
global list getDefaultPicker (symbol mode) ``{
list items = [];
list<string> rls = RunlevelEd::runlevels;
if (mode == `auto)
{
rls = prepend (rls, "");
}
else if (mode == `proposal)
{
// We could read the list from SCR (#37071) but
// inittab in the inst-sys is pre-lsb so we have to override it
rls = ["2", "3", "5"];
}
foreach (string i, rls, ``{
// which ones to avoid: #36110
if ("0" != i && "1" != i && "6" != i && "S" != i && "B" != i)
{
items[size(items)] = `item (`id (i),
RunlevelEd::getRunlevelDescr (i),
i == RunlevelEd::default_runlevel);
}
});
return items;
}
}