ACC SHELL

Path : /usr/share/YaST2/include/installation/
File Upload :
Current File : //usr/share/YaST2/include/installation/scr_switch_debugger.ycp

/**
 * 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