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
/
ProductControl.ycp
< prev
next >
Wrap
Text File
|
2006-11-29
|
28KB
|
1,142 lines
/**
* File: modules/ProductControl.ycp
* Package: installation
* Summary: Product Control routines
* Authors: Anas Nashif <nashif@suse.de>
*
* $Id: ProductControl.ycp 33822 2006-10-30 08:53:52Z locilka $
*/
{
textdomain "base";
module "ProductControl";
import "XML";
import "ProductFeatures";
import "Mode";
import "Arch";
import "Stage";
import "Directory";
import "Label";
import "Wizard";
import "Report";
import "Hooks";
import "Popup";
// The complete parsed control file
global map productControl = $[];
// all workflows
global list<map> workflows = [];
// all proposals
global list<map> proposals = [];
// inst_finish steps
global list<map<string,any> > inst_finish = [];
// modules to be offered to clone configuration at the end of installation
global list<string> clone_modules = [];
// texts which are product specific
map<string,map<string, string> > texts = $[];
// Location of a custom control file
global string custom_control_file = "";
// Control file in service packs
global string y2update_control_file = "/y2update/control.xml";
// The custom control file location, usually copied from
// the root of the CD to the installation directory by linuxrc
global string default_control_file = "/control.xml";
// The file above get saved into the installed system for later
// processing
global string saved_control_file = Directory::etcdir + "/control.xml";
// The packaged file which contains all default worklfows
global string packaged_control_file = "/usr/share/YaST2/control/control.xml";
// The control file we are using for this session.
global string current_control_file = "";
// Current Wizard Step
global string CurrentWizardStep = "";
// Last recently used stage_mode for RetranslateWizardSteps
global list<map> last_stage_mode = [];
// List of module to disable in the current run
global list<string> DisabledModules = [];
// List of proposals to disable in the current run
global list<string> DisabledProposals = [];
// Log files for hooks
global list<string> logfiles = [];
global integer first_step = nil;
global integer restarting_step = nil;
string _client_prefix = "inst_";
list stack = [];
string first_id = "";
integer current_step = 0;
global integer CurrentStep () {
return current_step;
}
/**
* Set Client Prefix
*/
global define void setClientPrefix(string prefix) {
_client_prefix = prefix;
return;
}
/**
* Check if a module is disabled
* @param map module map
* @return boolean
*/
global define boolean checkDisabled (map mod )
{
if (contains(DisabledModules, mod["name"]:""))
{
return true;
}
return false;
}
global boolean checkHeading(map mod) {
return haskey (mod, "heading");
}
/**
* Read XML Control File
* @param string control file
* @return boolean
*/
global define boolean ReadControlFile( string controlfile)
{
productControl = XML::XMLToYCPFile(controlfile);
if (productControl == nil)
return false;
workflows = productControl["workflows"]:[];
proposals = productControl["proposals"]:[];
inst_finish = productControl["inst_finish_stages"]:[];
clone_modules = productControl["clone_modules"]:[];
foreach (string section,
["software", "globals", "network", "partitioning"],
{
if (haskey (productControl, section))
{
ProductFeatures::SetSection (section,
(map<string,any>)(productControl[section]:$[]));
}
});
// FIXME would be nice if it could be done generic way
if (size( productControl["partitioning", "partitions"]:[] ) > 0)
{
map partitioning = productControl["partitioning"]:$[];
ProductFeatures::SetBooleanFeature ("partitioning",
"flexible_partitioning", true);
ProductFeatures::SetFeature ("partitioning", "FlexiblePartitioning",
partitioning);
}
string textdom = productControl["textdomain"]:"control";
texts = mapmap (string key, map<string,string> text,
productControl["texts"]:$[],
{
string label = text["label"]:"";
return $[ key : $[
"label" : label,
"textdomain" : textdom,
] ];
});
return true;
}
boolean Check(string allowed, string current)
{
// create allowed list
list<string> allowedlist = filter(string s,
splitstring(deletechars(allowed, " "), ","), ``(s!=""));
y2debug("allowedlist: %1", allowedlist );
y2debug("current: %1", current );
if (size(allowedlist) == 0 )
{
return true;
}
else if (contains(allowedlist, current ))
{
return true;
}
else
{
return false;
}
}
/**
* Check if valid architecture
* @param map module data
* @param map default data
* @return boolean true if arch match
*/
global define boolean checkArch(map mod , map def)
{
y2milestone("mod: %1", mod);
string archs = mod["archs"]:"";
if (archs == "")
{
archs=def["archs"]:"all";
}
if (archs == "all") {
return true;
}
y2milestone("short arch desc: %1", Arch::arch_short () );
y2milestone("supported archs: %1", archs );
if (issubstring(archs,Arch::arch_short ()))
{
return true;
}
return false;
};
string getClientName(string name ) {
if (Mode::test())
{
return "inst_test_workflow";
}
// All client start with "inst_".
string client = "";
if ( custom_control_file != "")
{
return name;
}
else
{
if (issubstring(name, _client_prefix)) {
return name;
} else {
client = _client_prefix + name;
return client;
}
}
}
/**
* Return term to be used to run module with CallFunction
* @param map module data
* @param map default data
* @return term module data with params
*/
global define term getClientTerm (map step, map def, any former_result)
{
string client = getClientName(step["name"]:"dummy");
term result = toterm(client);
map<string,any> arguments = $[];
foreach(string button, ["enable_back", "enable_next"], ``{
string default_setting = def[button]:"yes";
arguments[button] = step[button]:default_setting == "yes";
});
if (haskey(step,"proposal"))
{
arguments["proposal"] = step["proposal"]:"";
}
map<string, any> other_args = step["arguments"]:$[];
if (size(other_args) > 0 )
arguments = (map<string, any>)union(arguments, other_args );
if (is(former_result,symbol) && former_result == `back)
arguments["going_back"] = true;
if (Mode::test())
{
arguments["step_name"] = step["name"]:"";
arguments["step_id"] = step["id"]:"";
}
result = add(result, arguments);
return result;
}
/**
* Get workflow defaults
* @param string stage
* @param string mode
* @return map defaults
*/
global define map getModeDefaults(string stage, string mode)
{
list<map> tmp = filter(map wf, workflows, ``(
Check(wf["stage"]:"", stage) && Check(wf["mode"]:"", mode )
));
map workflow = tmp[0]:$[];
return workflow["defaults"]:$[];
}
/**
* Prepare Workflow Scripts
* @param m Workflow module map
* @return void
*/
void PrepareScripts(map m)
{
string tmp_dir = (string)WFM::Read(.local.tmpdir, []);
if (haskey(m,"prescript"))
{
string interpreter = m["prescript", "interpreter"]:"shell";
string source = m["prescript", "source"]:"";
string type = (interpreter == "shell") ? "sh" : "pl";
string f = sformat("%1/%2_pre.%3", tmp_dir, m["name"]:"", type);
WFM::Write(.local.string,f , source);
logfiles = add(logfiles, sformat("%1.log", sformat("%1_pre.%2",
m["name"]:"", type)));
}
if (haskey(m,"postscript"))
{
string interpreter = m["postscript", "interpreter"]:"shell";
string source = m["postscript", "source"]:"";
string type = (interpreter == "shell") ? "sh" : "pl";
string f = sformat("%1/%2_post.%3", tmp_dir, m["name"]:"", type);
WFM::Write(.local.string, f, source);
logfiles = add(logfiles, sformat("%1.log", sformat("%1_post.%2",
m["name"]:"", type)));
}
return;
}
/**
* Get list of required files for the workflow.
* @return list<string> Required files list.
*/
global list<string> RequiredFiles (string stage, string mode)
{
// Files needed during installation.
list<string> needed_client_files = [];
list<map> tmp = filter(map wf, workflows, ``(
Check(wf["stage"]:"", stage) && Check(wf["mode"]:"", mode )
));
map workflow = tmp[0]:$[];
list<map> modules = workflow["modules"]:[];
integer id = 1;
modules = filter(map m, modules, ``(m["enabled"]:true));
foreach(map m, modules,
{
string client = "";
if (Stage::firstboot ())
{
client = m["name"]:"dummy";
}
else
{
if (issubstring(m["name"]:"dummy", "inst_"))
{
client = m["name"]:"dummy";
}
else
{
client = "inst_" + m["name"]:"dummy";
}
}
client = Directory::clientdir + "/" + client + ".ycp";
needed_client_files = add(needed_client_files, client);
});
needed_client_files=toset(needed_client_files);
return needed_client_files;
}
/**
* Get Workflow
* @param stage Stage
* @param mode Mdoe
* @return map Workflow map
*/
global define map getCompleteWorkflow(string stage, string mode)
{
list<map> tmp = filter(map wf, workflows, ``(
Check(wf["stage"]:"", stage) && Check(wf["mode"]:"", mode )
));
map workflow = tmp[0]:$[];
return workflow;
}
/**
* Get modules of current Workflow
* @param string stage
* @param string mode
* @param boolean all enabled and disabled or enabled only
* @return list<map> modules
*/
global define list<map> getModules(string stage, string mode, symbol which)
{
y2debug("workflows: %1", workflows );
list<map> tmp = filter(map wf, workflows, ``(
Check(wf["stage"]:"", stage) && Check(wf["mode"]:"", mode )
));
map workflow = tmp[0]:$[];
y2debug("Workflow: %1", workflow );
list<map> modules = workflow["modules"]:[];
y2debug("modules: %1", modules );
integer id = 1;
if (which == `enabled)
{
modules = filter (map m, modules, {
return m["enabled"]:true && ! checkDisabled (m);
});
}
modules = maplist(map m, modules, ``{
m["id"] = sformat("%1_%2", stage, id);
PrepareScripts(m);
id = id + 1;
return (m);
});
y2debug("Log files: %1", logfiles);
return modules;
}
/**
* Get Workflow Label
* @param string stage
* @param string mode
* @return string
*/
global string getWorkflowLabel(string stage, string mode, string wz_td)
{
list<map> tmp = filter(map wf, workflows, ``(
Check(wf["stage"]:"", stage) && Check(wf["mode"]:"", mode )
));
map workflow = tmp[0]:$[];
string label = workflow["label"]:"";
if (label == "")
return "";
if (haskey (workflow, "textdomain"))
return dgettext (workflow["textdomain"]:"", label);
else
return dgettext (wz_td, label);
}
/**
* Add Wizard Steps
* @param list<map> stagemode A List of maps containing info about complete
* installation workflow.
* @return void
*/
global define void AddWizardSteps(list<map> stagemode)
{
last_stage_mode = stagemode;
// UI::WizardCommand() can safely be called even if the respective UI
// doesn't support the wizard widget, but for optimization it makes sense
// to do expensive operations only when it is available.
// if ( ! UI::HasSpecialWidget(`Wizard ) )
// return;
string wizard_textdomain = (string) productControl["textdomain"]:"control";
y2debug( "Using textdomain '%1' for wizard steps", wizard_textdomain );
string first_id = "";
// UI::WizardCommand(`SetVerboseCommands( true ) );
foreach (map sm , stagemode, ``{
y2debug( "Adding wizard steps for %1", sm );
string slabel = getWorkflowLabel(sm["stage"]:"", sm["mode"]:"", wizard_textdomain);
if ( slabel != "" )
{
UI::WizardCommand (`AddStepHeading (slabel));
}
string last_label = "";
string last_domain = "";
foreach(map m, getModules(sm["stage"]:"", sm["mode"]:"", `enabled),
``{
y2debug("Adding wizard step: %1", m );
if (haskey (m, "heading") && m["label"]:"" != "")
{
UI::WizardCommand (`AddStepHeading (
haskey (m, "textdomain")
? dgettext (m["textdomain"]:"", m["label"]:"")
: dgettext (wizard_textdomain, m["label"]:"")));
}
else if (m["label"]:"" != "")
{
if (first_id=="")
{
first_id = m["id"]:"";
}
UI::WizardCommand(`AddStep (
haskey (m, "textdomain")
? dgettext (m["textdomain"]:"", m["label"]:"")
: dgettext (wizard_textdomain, m["label"]:""),
m["id"]:"" ));
last_label = m["label"]:"";
last_domain = m["textdomain"]:"";
} else {
if (first_id=="")
{
first_id = m["id"]:"";
}
if (last_label != "")
{
if (last_domain != "")
UI::WizardCommand(`AddStep( dgettext(
last_domain,
last_label ),
m["id"]:"" )
);
else
UI::WizardCommand(`AddStep( dgettext(
wizard_textdomain,
last_label ),
m["id"]:"" )
);
}
}
});
});
UI::WizardCommand(`SetCurrentStep( first_id ) );
return;
}
/**
* Update Steps
*/
global define void UpdateWizardSteps(list<map> stagemode)
{
last_stage_mode = stagemode;
UI::WizardCommand(`DeleteSteps());
AddWizardSteps( stagemode );
UI::WizardCommand(`UpdateSteps());
UI::WizardCommand(`SetCurrentStep( CurrentWizardStep ) );
return;
}
/**
* Retranslate Wizard Steps
*/
global define void RetranslateWizardSteps()
{
if ( size( last_stage_mode ) > 0 )
{
y2debug( "Retranslating wizard steps" );
UpdateWizardSteps( last_stage_mode );
}
}
define list<map> getMatchingProposal(
string stage,
string mode,
string proptype)
{
y2milestone("Stage: %1 Mode: %2, Type: %3", stage, mode, proptype);
// First we search for proposals for current stage if there are
// any.
list<map> props = filter(map p, proposals, ``(Check(p["stage"]:"", stage)));
y2debug("1. proposals: %1", props);
// Then we check for mode: installation or update
props = filter(map p, props, ``(
Check(p["mode"]:"", mode)));
y2debug("2. proposals: %1", props);
// Now we check for architecture
y2debug("Architecture: %1, Proposals: %2", Arch::architecture (), props );
list<map> arch_proposals = filter(map p, props, ``(
p["name"]:"" == proptype &&
issubstring(p["archs"]:"dummy", Arch::arch_short () )));
y2debug("3. arch proposals: %1", arch_proposals );
props = filter(map p, props, ``(
p["archs"]:"" == "" || p["archs"]:"" == "all"
)
);
y2debug("4. other proposals: %1", props );
// If architecture specific proposals are available, we continue with those
// and check for proposal type, else we continue with pre arch proposal
// list
if (size(arch_proposals) > 0 )
{
props = filter(map p, arch_proposals, ``(
p["name"]:"" == proptype ));
y2debug ("5. arch proposals: %1", props);
}
else
{
props = filter(map p, props, ``(
p["name"]:"" == proptype ));
y2debug ("5. other proposals: %1", props);
}
if (size(props)> 1 )
{
y2error("Something Wrong happened, more than one proposal after filter:
%1", props);
}
// old style proposal
y2milestone ("Proposal modules: %1",props[0, "proposal_modules"]:nil );
return props;
}
/**
* Get modules of current Workflow
* @param string stage
* @param string mode
* @return list<string> modules
*/
global define list < list > getProposals(string stage, string mode, string proptype) {
list<map> props = getMatchingProposal(stage, mode, proptype);
list< list > final_proposals = maplist(any p, props[0, "proposal_modules"]:[], ``{
string proposal_name = "";
integer order_value = 50;
if ( is (p, string) )
{
proposal_name = (string)p;
}
else
{
map<string,string> pm = (map<string,string>)p;
proposal_name = pm["name"]:"";
string proposal_order = pm["presentation_order"]:"50";
order_value = tointeger (proposal_order);
if (order_value == nil)
{
y2error ("Unable to use '%1' as proposal order, using %2 instead"
, proposal_order, 50);
order_value = 50;
}
}
// All proposal file names end with _proposal
if (!issubstring(proposal_name, "_proposal"))
return( [proposal_name + "_proposal", order_value ] );
else
return( [proposal_name, order_value] );
});
y2debug("final proposals: %1", final_proposals );
return final_proposals;
}
/**
* Get Proposal list that can not be changed by the user.
* @return list<string> list of locked proposals
*/
global list<string> getLockedProposals (string stage,string mode,string proptype) {
list<map> props = getMatchingProposal(stage, mode, proptype);
list<string> locked_proposals = maplist(string p, props[0, "locked_modules"]:[], ``{
if (!issubstring(p, "_proposal"))
return(p + "_proposal");
else
return(p);
});
return locked_proposals;
}
/**
* Return text domain
*/
global string getProposalTextDomain() {
string current_proposal_textdomain = (string)
productControl["textdomain"]:"control";
y2debug( "Using textdomain '%1' for proposals", current_proposal_textdomain);
return current_proposal_textdomain;
}
/**
* Return proposal Label
*/
global map getProposalProperties( string stage, string mode, string proptype
)
{
list<map> proposals = getMatchingProposal(stage, mode, proptype);
map proposal = proposals[0]:$[];
if (haskey (proposal, "proposal_tabs"))
{
string text_domain = productControl["textdomain"]:"control";
proposal["proposal_tabs"] = maplist (map tab,
proposal["proposal_tabs"]:[],
{
string domain = tab["textdomain"]:text_domain;
tab["label"] = dgettext (domain, tab["label"]:"");
return tab;
});
}
return proposal;
}
global string GetTranslatedText (string key) {
map<string,string> text = texts[key]:$[];
string label = text["label"]:"";
if (label == "")
{
y2error ("The requested label %1 does not exist", key);
return "";
}
string domain = text["textdomain"]:"";
if (domain == "")
{
y2error ("The text domain for label %1 not set", key);
return label;
}
return dgettext (domain, label);
}
/**
* Initialize Product Control
* @return boolean True on success
*/
global define boolean Init()
{
boolean ret = false;
current_control_file = "";
list<string> order =
[
y2update_control_file, // /y2update/control.xml
default_control_file, // /control.xml
saved_control_file, // /etc/YaST2/control.xml
packaged_control_file // /usr/share/YaST2/control/control.xml
];
if ( custom_control_file != "")
{
order=prepend(order, custom_control_file);
}
y2milestone("Candidates: %1", order );
foreach(string cf, order,
{
if ((integer)SCR::Read( .target.size, cf )>0
&& current_control_file == "")
{
current_control_file = cf;
}
});
if (current_control_file == "")
{
y2error("Control file not found");
return false;
}
y2milestone("Reading control file: %1", current_control_file );
ReadControlFile( current_control_file );
return (current_control_file != "");
}
/**
* Re-translate static part of wizard dialog and other predefined messages
* after language change
*/
void retranslateWizardDialog()
{
y2milestone( "Retranslating messages 1" );
// Make sure the labels for default function keys are retranslated, too.
// Using Label::DefaultFunctionKeyMap() from Label module.
UI::SetFunctionKeys( Label::DefaultFunctionKeyMap() );
// Activate language changes on static part of wizard dialog
ProductControl::RetranslateWizardSteps();
Wizard::RetranslateButtons();
Wizard::SetFocusToNextButton();
return;
}
void addToStack(string name) {
stack=add(stack, name);
return;
}
global boolean wasRun(string name)
{
return contains(stack, name);
}
global symbol RunFrom (integer from, boolean allow_back)
{
any former_result = `next;
symbol final_result = nil;
current_step = from; // current module
Wizard::SetFocusToNextButton();
y2debug("Starting Workflow with \"%1\" \"%2\"", Stage::stage (), Mode::mode ());
list<map> modules =
getModules( Stage::stage (), Mode::mode (), `enabled);
map defaults = getModeDefaults(Stage::stage (), Mode::mode ());
y2debug("modules: %1", modules);
if (size(modules) == 0 )
{
y2error("No workflow found: %1", modules );
// error report
Report::Error(_("No workflow defined for this installation mode."));
return `abort;
}
integer minimum_step = allow_back ? 0 : from;
while ((current_step >= 0) && (current_step < size(modules)))
{
map step = modules[current_step]:$[];
string step_name = step["name"]:"";
boolean run_in_update_mode = step["update"]:true; // default is true
boolean retranslate = step["retranslate"]:false;
string step_id = step["id"]:"";
if (current_step <= minimum_step)
{
step["enable_back"] = "no";
}
boolean do_continue = false;
if (!checkArch(step, defaults))
{
do_continue = true;
}
if (checkDisabled(step))
{
do_continue = true;
}
if (checkHeading(step))
{
do_continue = true;
}
if (!run_in_update_mode && Mode::update ())
{
do_continue = true;
}
if ( do_continue )
{
if (former_result == `next)
{
if (current_step <= minimum_step && ! allow_back)
minimum_step = minimum_step + 1;
current_step = current_step + 1;
}
else
current_step = current_step - 1;
}
if ( do_continue ) continue;
term argterm = getClientTerm( step, defaults, former_result);
y2debug("Running module: %1 (%2)", argterm, current_step);
symbol module_name = symbolof( argterm );
y2milestone( "Calling %1", argterm );
if (!wasRun(step_name)) {
Hooks::Checkpoint (sformat("%1", module_name), true);
Hooks::Run (step_name, true);
}
list args = [];
integer i = 0;
while (i < size(argterm))
{
any def = nil;
args[i] = argterm[i]:def;
i = i + 1;
}
UI::WizardCommand(`SetCurrentStep( step_id ) );
CurrentWizardStep = step_id;
// Register what step we are going to run
if (!Stage::initial())
{
if (!SCR::Write (.target.string, "/var/lib/YaST2/step", step_id))
y2error("Error writing step identifier");
}
symbol result = (symbol) WFM::CallFunction (getClientName(step_name), args);
y2milestone ("Calling %1 returned %2", argterm, result);
// Remove file if step was run and returned (without a crash);
if (current_step < size(modules) - 1 && !Stage::initial())
{
if (!(boolean)SCR::Execute(.target.remove, "/var/lib/YaST2/step"))
y2error("Error removing step identifier");
}
// Dont call hook scripts after installation is done. (#36831)
if (current_step < size(modules) - 1 && !wasRun(step_name))
Hooks::Run (step_name, false );
else
y2milestone("Not running hooks at the end of the installation");
// This should be safe (#36831)
Hooks::Checkpoint ( step_name, false); // exit hook
addToStack(step_name);
if ( retranslate )
{
y2milestone("retranslate");
retranslateWizardDialog();
retranslate = false;
}
// If the module return nil, something basic went wrong.
// We show a stub dialog instead.
if (result == nil)
{
/**
* If workflow module is marked as optional, skip if it returns nil,
* For example, if it is not installed.
*/
if (step["optional"]:false)
{
y2milestone("Skipping optional %1", symbolof(argterm) );
current_step = current_step + 1;
continue;
}
any r = nil;
r = Popup::ModuleError(sformat("The module %1 does not work.", symbolof(argterm)));
if (r == `next)
current_step = current_step + 1;
else if (r == `back)
current_step = current_step - 1;
else if (r != `again)
{
UI::CloseDialog();
return nil;
}
continue;
}
if (result == `next)
{
current_step = current_step + 1;
}
else if (result == `back)
{
current_step = current_step - 1;
}
else if (result == `cancel)
{
break;
}
else if (result == `abort)
{
// FATE #300422
// handling when user aborts the second stage installation
if (Stage::cont ()) {
final_result = result;
}
break;
}
else if (result == `finish)
{
break;
}
else if (result == `again)
{
continue; // Show same dialog again
}
else if (result == `restart_yast)
{
final_result = result;
break;
}
else if (result == `restart_same_step)
{
final_result = result;
break;
}
else if (result == `reboot)
{
final_result = result;
break;
}
else if (result == `auto)
{
if (former_result != nil)
{
if (former_result == `next)
{
// if the first client just returns `auto , the back button
// of the next client must be disabled
if (current_step <= minimum_step && ! allow_back)
minimum_step = minimum_step + 1;
current_step = current_step + 1;
}
else if (former_result == `back)
current_step = current_step - 1;
}
continue;
}
former_result = result;
}
if (former_result == `abort)
{
final_result = `abort;
}
y2milestone ("Former result: %1, Final result: %2", former_result, final_result);
if (final_result != nil) {
y2milestone ("Final result already set.");
} else if (current_step <= -1) {
final_result = `back;
} else {
final_result = `next;
}
y2milestone ("Current step: %1, Returning: %2", current_step, final_result);
return final_result;
}
/**
* Run Workflow
*
*/
global symbol Run () {
symbol ret = RunFrom (0, false);
y2milestone ("Run() returning %1", ret);
return ret;
}
// Functions to access restart information
/**
* List steps which were skipped since last restart of YaST
* @return a list of maps describing the steps
*/
global list<map> SkippedSteps () {
list<map> modules = getModules( Stage::stage (), Mode::mode (), `enabled);
if (first_step == nil)
return nil;
if (first_step >= size (modules))
return nil;
integer index = 0;
list<map> ret = [];
while (index < first_step)
{
ret = add (ret, modules[index]:$[]);
index = index + 1;
}
return ret;
}
/**
* Return step which restarted YaST (or rebooted the system)
* @return a map describing the step
*/
global map RestartingStep () {
if (restarting_step == nil)
return nil;
list<map> modules = getModules( Stage::stage (), Mode::mode (), `enabled);
return modules[restarting_step]:$[];
}
/**
* ProductControl Constructor
* @return void
*/
global define void ProductControl()
{
if (!Init())
{
y2error("control file missing");
}
return;
}
// EOF
}