ACC SHELL
/**
* File:
* include/installation/scr_switch_debugger.ycp
*
* Module:
* System installation
*
* Summary:
* Debugs SCR switch failure
*
* Authors:
* Lukas Ocilka <locilka@suse.cz>
*
*/
{
textdomain "installation";
/*
* ATTENTION: This functionality is called when SCR switch fails.
* It means that there is (probably) no other SCR running
* and we have to create one first.
*/
import "FileUtils";
import "Popup";
import "Label";
import "Icon";
import "Installation";
import "HTML";
import "String";
// test result (Checking for xyz... Passed)
string result_ok = _("Passed");
// test result (Checking for xyz... Failed)
string result_failed = ("Failed");
/* ********************************************************************* */
// --> Configuration
// path of of the failed chroot (SCROpen)
// assigned in main function RunSCRSwitchDebugger()
string chroot_path = nil;
// SCR of the inst-sys
string new_SCR_path = "/";
// chroot binary
string test_chroot_binary = "/usr/bin/chroot";
// binary for 'any' command exists
string test_binary_exists = "/bin/ls";
// any command for the chroot command
string test_do_chroot = "/bin/ls -1 /";
// y2base path
string test_y2base = "/usr/lib/YaST2/bin/y2base";
// get all installed rpm packages
string test_rpm = "rpm -qa";
// all needed rpm packages
list <string> needed_rpm_packages = [
"yast2", "yast2-installation", "yast2-core", "yast2-bootloader", "yast2-packager"
];
// is the package %1 installed?
string test_one_rpm = "rpm -q %1";
// what requires the %1 package?
string test_requires = "rpm -q --requires %1";
// what provides the %1 object (can contain "()"s)
string test_whatprovides = "rpm -q --whatprovides '%1'";
// where logs are stored
string yast_logs = "/var/log/YaST2/";
// YaST log file
string yast_logfile = "y2log";
// <-- Configuration
/* ********************************************************************* */
// --> Helper Functions
// UI dialog
term SCRSwitchDialog () {
return `VBox (
`Left (`HBox (
`HSquash (`MarginBox (0.5, 0.2, Icon::Error())),
// heading
`Heading (_("Switching to the Installed System Failed"))
)),
`VSpacing (0.5),
// informative text
`MarginBox (
1, 1, `VBox (
`Left (`Label (sformat (
// TRANSLATORS: an error message
// %1 - logfile, possibly with errors
// %2 - link to our bugzilla
// %3 - directory where YaST logs are stored
// %4 - link to the Yast Bug Reporting HOWTO Web page
_("Switching to the installed system has failed
More information can be found near the end of the '%1' file.
This is worth reporting a bug at %2.
Please, attach also all YaST logs stored in the '%3' directory.
See %4 for more information about YaST logs."),
"/var/log/YaST2/y2log",
"http://bugzilla.novell.com/",
"/var/log/YaST2/",
// link to the Yast Bug Reporting HOWTO
// for translators: use the localized page for your language if it exists,
// check the combo box "In other laguages" on top of the page
_("http://en.opensuse.org/Bugs/YaST")
)))
)
),
`MarginBox (
1, 1, `VBox (
`MinWidth (70,
// used for progress
`LogView (
`id (`log_view),
// log-view label
_("&Checking the Installed System..."),
18, 500
)
),
`ReplacePoint(`id(`dialog_rp), `Empty())
)
)
);
}
// reports a progress with reslt
void ReportTest (string test_description, boolean test_result) {
// report it to the log
y2milestone("%1 %2", test_description, test_result);
// report it to the UI
UI::ChangeWidget (`id(`log_view), `LastLine,
sformat ("%1 %2\n", test_description, (test_result ? result_ok : result_failed))
);
if (test_result) {
// passed
return;
}
y2error("-- I.C. Winner --");
UI::ChangeWidget (
`id(`log_view), `LastLine,
"\n" +
sformat (
// TRANSLATORS: an error message
// %1 - link to our bugzilla
// %2 - directory where YaST logs are stored
_("This is worth reporting a bug at %1.
Please, attach also all YaST logs stored in the '%2' directory."),
"http://bugzilla.novell.com/",
"/var/log/YaST2/"
) +
"\n"
);
}
// report just some progress
void ReportProgress (string progress_s) {
progress_s = sformat ("=== %1 ===", progress_s);
y2milestone("%1", progress_s);
UI::ChangeWidget (`id(`log_view), `LastLine, "\n" + progress_s + "\n");
}
// report just a line, no modifications
void ReportLine (string line) {
y2milestone("%1", line);
UI::ChangeWidget (`id(`log_view), `LastLine, line + "\n");
}
// <-- Helper Functions
/* ********************************************************************* */
// --> Tests
// checks whether the chroot binary exists
boolean RunSCRSwitchTest_ChrootBinary () {
boolean test_result = FileUtils::Exists (test_chroot_binary);
ReportTest (
// Test progress
sformat (_("Checking for %1 binary..."), test_chroot_binary),
test_result
);
return test_result;
}
// checks whether the new SCR path exists
boolean RunSCRSwitchTest_ChrootPath () {
boolean test_result = FileUtils::IsDirectory (chroot_path);
ReportTest (
// Test progress
sformat (_("Checking for chroot directory %1..."), chroot_path),
test_result
);
return test_result;
}
boolean RunSCRSwitchTest_ListFilesInChroot () {
map ret = (map) WFM::Execute (.local.bash_output, sformat ("ls -1 '%1'", chroot_path));
ReportTest (
// Test progress
sformat (_("Checking for chroot directory content (%1)..."), ret["stdout"]:""),
true
);
return true;
}
// checks whether the new SCR path exists
boolean RunSCRSwitchTest_BinaryExists () {
string exec_file = sformat ("%1%2", chroot_path, test_binary_exists);
boolean test_result = FileUtils::Exists (exec_file);
ReportTest (
// Test progress
sformat (_("Checking for binary %1..."), exec_file),
test_result
);
return test_result;
}
// tries to chroot
boolean RunSCRSwitchTest_DoChroot () {
map <string, any> test = (map <string, any>) SCR::Execute (.target.bash_output,
sformat ("%1 %2 %3",
test_chroot_binary, // chroot command
chroot_path, // where to chroot
test_do_chroot // what to execute
)
);
boolean test_result = ((integer) test["exit"]:42 == 0);
ReportTest (
// Test progress
_("Trying to chroot..."),
test_result
);
y2milestone("Debug: exit>%1<\nstdout>\n%2<\nstderr>%3<",
test["exit"]:0, test["stdout"]:"", test["stderr"]:""
);
return test_result;
}
// checks whether the y2base binary exists
boolean RunSCRSwitchTest_Y2BASE () {
string y2basefile = sformat("%1%2", chroot_path, test_y2base);
boolean test_result = FileUtils::Exists (y2basefile);
ReportTest (
// Test progress
sformat (_("Checking for %1 in %2..."), test_y2base, chroot_path),
test_result
);
return test_result;
}
boolean RunSCRSwitchTest_FreeSpace () {
ReportProgress (_("Checking free space"));
// Local command
map parts_cmd = (map) WFM::Execute (.local.bash_output,
"mount " +
"| grep -v '^\\(/proc\\) on' " +
"| sed 's/\\/.* on \\(.*\\) type .*/\\1/'"
);
map <string, integer> partitions = $[];
if (parts_cmd["exit"]:-1 != 0) {
y2error ("Cannot find out current partitions");
// even if it is an error, we should check more
return true;
} else {
// Spash at the end or not
string inst_dir = Installation::destdir + (regexpmatch (Installation::destdir, "/$") ? "" : "/");
integer inst_dir_length = size (inst_dir);
y2milestone ("InstDir: '%1'", inst_dir);
foreach (string one_partition, splitstring (parts_cmd["stdout"]:"", "\n"), {
// begin of the one_partition matches the inst_dir
if (substring (one_partition, 0, inst_dir_length) == inst_dir) {
// chrooted to the Installation::destdir
partitions[substring (one_partition, inst_dir_length - 1)] =
(integer) SCR::Read (.system.freespace, one_partition);
}
});
}
y2milestone ("Partitions: %1", partitions);
boolean test_result = true;
foreach (string partition, integer free_space, partitions, {
boolean this_test = true;
if (free_space <= 0) {
test_result = false;
this_test = false;
}
ReportTest (
sformat (
// test result, %1 is replaced with the directory, e.g., /var
// %2 is replaced with the free space in that partition, e.g., 2.8 GB
_("Checking for free space in the %1 directory: %2"),
partition,
// linked to the text above (sometimes replaces the '%2')
(free_space < 0 ? _("Unable to find out"):String::FormatSize (free_space))
),
this_test
);
});
return test_result;
}
// tries to get all installed packages from new SCR
boolean RunSCRSwitchTest_DoRPMCheck () {
map <string, any> test = (map <string, any>) SCR::Execute (.target.bash_output,
sformat ("%1 %2 %3",
test_chroot_binary, // chroot command
chroot_path, // where to chroot
test_rpm // what to execute
)
);
boolean test_result = ((integer) test["exit"]:42 == 0);
ReportTest (
// Test progress
_("Checking for installed RPM packages..."),
test_result
);
y2milestone("Debug: exit>%1<\nstdout>\n%2<\nstderr>%3<",
test["exit"]:0, test["stdout"]:"", test["stderr"]:""
);
return test_result;
}
// checks whether the RPM is installed in SCR
boolean RunSCRSwitchTest_CheckWhetherInstalled (string package_name) {
boolean test_result = nil;
boolean ret = true;
string one_rpm_installed = nil;
one_rpm_installed = sformat ("%1 %2 %3",
test_chroot_binary,
chroot_path,
sformat (test_one_rpm, package_name)
);
map <string, any> test = (map <string, any>) SCR::Execute (.target.bash_output, one_rpm_installed);
test_result = (test["exit"]:-1 == 0);
if (test_result != true) ret = false;
ReportTest (
// Test progress
sformat(_("Checking whether RPM package %1 is installed..."), package_name),
test_result
);
y2milestone("Debug: %1", test);
return ret;
}
// check which packages are required by needed packages
boolean RunSCRSwitchTest_DoNeededRPMsRequire (string package_name) {
boolean test_result = nil;
boolean ret = true;
map <string, any> required_packages = (map <string, any>) SCR::Execute (.target.bash_output,
sformat ("%1 %2 %3",
test_chroot_binary,
chroot_path,
sformat (test_requires, package_name)
)
);
test_result = ((integer) required_packages["exit"]:42 == 0);
if (! test_result) ret = false;
ReportTest (
// Test progress
sformat(_("Checking what requires RPM package %1..."), package_name),
test_result
);
// we have required objects
if (test_result) {
string required_packages_s = required_packages["stdout"]:"";
list <string> already_checked = [];
// check all required objects (sorted and only once)
foreach (string one_require, toset (splitstring (required_packages_s, "\n")), {
if (one_require == nil || one_require == "") continue;
// already checked
if (contains (already_checked, one_require)) continue;
// do not check again
already_checked = add (already_checked, one_require);
if (regexpmatch (one_require, "[ \t]")) {
one_require = regexpsub (one_require, "^([^ \t]*)[ \t]", "\\1");
}
map <string, any> what_provides = (map <string, any>) SCR::Execute (.target.bash_output,
sformat ("%1 %2 %3",
test_chroot_binary,
chroot_path,
sformat (test_whatprovides, one_require)
)
);
test_result = ((integer) what_provides["exit"]:42 == 0);
if (! test_result) {
// do not check whether required objects are installed
// if we don't have which they are
break;
ret = false;
}
string what_provides_s = what_provides["stdout"]:"";
boolean at_least_one = false;
// checks whether objects that provides something are installed
foreach (string one_provides, toset (splitstring (what_provides_s, "\n")), {
if (one_provides == "") continue;
if (RunSCRSwitchTest_CheckWhetherInstalled (one_provides)) {
at_least_one = true;
break;
} else {
ret = false;
}
});
// none of what_provides is installed
// or nothing provides the requierd object
if (! at_least_one) ret = false;
});
}
return ret;
}
// checks a package, whether it is installed
// if it is installed, whether is has installed requires
boolean RunSCRSwitchTest_DoNeededRPMsCheck () {
boolean ret = true;
// check whether all needed packages are installed
foreach (string package_name, sort (needed_rpm_packages), {
// Test progress
ReportProgress (sformat (_("Running complex check on package %1..."), package_name));
// is the package installed?
if (! RunSCRSwitchTest_CheckWhetherInstalled (package_name)) {
ret = false;
break;
}
// if it is installed, check whetheris has all dependencies
else if (! RunSCRSwitchTest_DoNeededRPMsRequire (package_name)) {
ret = false;
break;
}
});
return ret;
}
list <string> YaST_log_lines = [];
void PrintLinesFromTo (integer from_line, integer to_line) {
// start with the first line or further
if (from_line < 0) from_line = 0;
// print all lines
y2milestone ("Logging from: %1 to: %2", from_line, to_line);
while (from_line <= to_line) {
ReportLine (YaST_log_lines[from_line]:"");
from_line = from_line + 1;
}
}
// checks the YaST log on the installed system
boolean RunSCRSwitchTest_SCRChrootYaSTLog () {
boolean ret = true;
string logfile = sformat ("%1/%2/%3", Installation::destdir, yast_logs, yast_logfile);
y2milestone ("Checking file %1", logfile);
ReportProgress (sformat (_("Checking YaST log file %1..."), logfile));
string YaST_log = (string) WFM::Read (.local.string, logfile);
integer current_line = -1;
// cannot open YaST log
if (YaST_log == nil) {
ret = false;
ReportTest (_("Opening file..."), ret);
// checking YaST log
} else {
YaST_log_lines = splitstring (YaST_log, "\n");
foreach (string one_line, YaST_log_lines, {
current_line = current_line + 1;
// SCR has died, printing the last 15 lines
if (regexpmatch (one_line, " Finished YaST.* component ")) {
ReportLine (_("SCR process has died, printing the last log lines..."));
integer start_line = current_line - 15;
PrintLinesFromTo (start_line, current_line);
ret = false;
break;
// YaST got killed
} else if (regexpmatch (one_line, " got signal ")) {
// Print just the last line
ReportLine (_("YaST process got killed"));
PrintLinesFromTo (current_line, current_line);
ret = false;
break;
}
});
YaST_log_lines = [];
ReportTest (_("Checking YaST log..."), ret);
}
return ret;
}
// main test
boolean RunSCRSwitchTests () {
// Test progress
ReportProgress (_("System Checking"));
if (! RunSCRSwitchTest_ChrootBinary()) return false;
if (! RunSCRSwitchTest_ChrootPath()) return false;
if (! RunSCRSwitchTest_ListFilesInChroot()) return false;
if (! RunSCRSwitchTest_BinaryExists()) return false;
if (! RunSCRSwitchTest_DoChroot()) return false;
if (! RunSCRSwitchTest_Y2BASE()) return false;
if (! RunSCRSwitchTest_FreeSpace()) return false;
if (! RunSCRSwitchTest_DoRPMCheck()) return false;
// checking all mandatory packages
if (! RunSCRSwitchTest_DoNeededRPMsCheck()) return false;
if (! RunSCRSwitchTest_SCRChrootYaSTLog()) return false;
// Add new checks here...
return true;
}
// <-- Tests
/* ********************************************************************* */
// --> Special Functions
/**
* Copy YaST logs from the just installed system to inst-sys
*/
void CopyY2logsFromSCRToInstSys () {
// where SCR logs are stored now
string scr_logs_directory = sformat ("%1%2", chroot_path, yast_logs);
// where to copy them
string copy_to_directory = sformat ("%1InstalledSystemLogs", yast_logs);
string command = sformat (
"cp -avr '%1' '%2'",
scr_logs_directory, copy_to_directory
);
y2milestone ("Copying YaST logs from the system to inst-sys: %1 -> %2", command,
WFM::Execute (.local.bash_output, command)
);
}
integer FindSCRPID () {
map cmd = (map) WFM::Execute (
.local.bash_output,
"LC_ALL=C /bin/ps a | grep 'scr stdio' | grep -v 'grep'"
);
if (cmd["exit"]:-1 != 0) {
y2error ("Cannot find scr process");
return -1;
}
list <string> outlines = filter (string one_outline, splitstring (cmd["stdout"]:"", "\n"), {
return one_outline != "";
});
string outline = outlines[size (outlines) - 1]:"";
if (! regexpmatch (outline, "^[[:digit:]]+")) {
y2error ("No PID in %1", outline);
return -1;
}
outline = regexpsub (outline, "^([[:digit:]]+)[[:space:]].*", "\\1");
integer ret = tointeger (outline);
y2milestone ("SCR PID: %1", ret);
return ret;
}
// This is potentially insecure
void SwitchY2Debug (integer PID) {
if (PID == nil) {
y2error ("PID cannot be: %1", PID);
return;
}
string cmd = sformat ("kill -s USR1 %1", PID);
y2milestone ("Adjusting Y2DEBUG >%1<: %2", cmd, WFM::Execute (.local.bash_output, cmd));
}
// <-- Special Functions
/* ********************************************************************* */
/**
* Function debugs why the SCR switch failed and reports
* it to user.
*
* @param string failed_chroot_chroot
*/
void RunSCRSwitchDebugger (string failed_chroot_path) {
if (failed_chroot_path == nil) {
y2error("Chroot path not defined!");
// popup error
Popup::Error(_("Unknown chroot path. The debugger cannot continue."));
return;
}
// will be used for all chroot calls later
chroot_path = failed_chroot_path;
// if any SCR exists
integer old_SCR = WFM::SCRGetDefault ();
integer new_SCR = WFM::SCROpen ("chroot=" + new_SCR_path + ":scr", false);
if (new_SCR < 0) {
y2error("Cannot conenct to SCR %1", new_SCR_path);
Popup::Error(_("Connecting to the inst-sys failed, debugger cannot continue."));
return;
}
// Set the new SCR as a defalt one
WFM::SCRSetDefault (new_SCR);
// Copy all YaST log files from SCR to Inst-Sys
// before SCR test
CopyY2logsFromSCRToInstSys();
y2milestone("* ---------- Debugger Start ---------- *");
UI::OpenDialog (SCRSwitchDialog());
RunSCRSwitchTests();
UI::ReplaceWidget (`id (`dialog_rp), `PushButton (`id(`ok), Label::OKButton()));
any ret = nil;
while (ret != `ok) {
ret = UI::UserInput();
}
UI::CloseDialog ();
y2milestone("* ---------- Debugger Finish ---------- *");
// Close the SCR created for testing
WFM::SCRClose (new_SCR);
// Set the previous one as the default
WFM::SCRSetDefault (old_SCR);
// Copy all YaST log files from SCR to Inst-Sys
// after SCR tests
CopyY2logsFromSCRToInstSys();
}
}
ACC SHELL 2018