/**
 * File:
 *   modules/Restore.ycp
 *
 * Package:
 *   Restore module
 *
 * Summary:
 *   Data for configuration of restore, input and output functions.
 *
 * Authors:
 *   Ladislav Slezak <lslezak@suse.cz>
 *
 * $Id: Restore.ycp,v 1.55 2004/11/05 08:29:48 locilka Exp $

 * Representation of the configuration of restore.
 * Input and output routines.
 *
 */

{
    // This is "Restore" module
    module "Restore";

    import "Progress";
    import "Report";
    import "Mode";
    import "Summary";
    import "Bootloader";
    import "Service";
    import "Package";
    import "Label";
    import "Popup";
    import "URL";
    import "Message";

    textdomain "restore";

    // local file name (can be on mounted file system)
    string filename = "";

    // entered file name (e.g. nfs://server:/dir/archive.tar)
    global string inputname = "";

    global boolean completerestoration = false;

    // list of volumes in URL-like syntax
    list<string> inputvolumes = [];

    // contents of archive
    list<string> archivefiles = [];

    // installed packages at backup time
    map<string,string> installedpkgs = $[];

    // list of installed packages
    map<string,string> actualinstalledpackages = $[];

    list<string> complete_backup = [];

    // restoration archive and selection
    // "vers" : "version", "files" : ["files"], "prefix" : "prefix", "descr" : "description", "sel_type" : "X", "sel_file" : [""]
    map<string,map<string, any> > archive_info = $[];

    map<string,map> autoinst_info = $[];

    // information stored in archive
    string date = "";
    string hostname = "";
    string comment = "";

    // list of files
    list<string> volumeparts = [];

    // temporary directory
    string tempdir = "";

    // restore files to the selected directory
    global string targetDirectory = "/";

    // mount point, stored for unmounting
    string mountpoint = "";

    // nopackage identification stored in Exported map
    // (to prevent empty tag in XML profile)
    string nopackage_id = "_NoPackage_";

    /**
     * Run lilo after files are restored
     */
    global boolean runbootloader = true;

    /**
     * Run SuSEconfig after files are restored
     */
    global boolean runSuSEconfig = true;

    /**
     * Rewrite RPM db - unapack /var/lib/rpm/* files from backup if present
     */
    global boolean restoreRPMdb = nil;


    boolean config_modified = false;

    /**
     * Return modified flag
     * @return true if modified
     */
    global define boolean Modified() ``{
	return config_modified;
    };

    global define void SetModified() {
	config_modified = true;
    }

    /**
     * Selected archive has more parts
     * @return boolen True if archive have more than one part.
     */
    global define boolean IsMultiVolume() ``{
	return (size(volumeparts) > 0);
    }

    /**
     * Return date when backup archive was created. Date is stored in archive in file info/date.
     * @return string Date
     */
    global define string GetArchiveDate() ``{
	return date;
    }

    /**
     * Return name of backup archive
     * @return string Input name file name
     */
    global define string GetInputName() ``{
	return inputname;
    }

    /**
     * Return name of backup archive
     * @return string File name
     */
    global define string GetArchiveName() ``{
	return filename;
    }

    /**
     * Return user comment stored in archive. Comment is stored in file info/comment.
     * @return string Archive comment
     */
    global define string GetArchiveComment() ``{
	return comment;
    }

    /**
     * Return host name of machine on which backup archive was created. Host name is stored in archive in file info/hostname.
     * @return string Host name
     */
    global define string GetArchiveHostname() ``{
	return hostname;
    }

    /**
     * Return map with packages installed at backup time (form is $["package name" : "version"]).
     * @return map Installed packages at backup time
     */
    global define map<string,string> GetArchiveInstalledPackages() ``{
	return installedpkgs;
    }

    /**
     * Return list of files in the backup archive
     * @return list Files in the archive
     */
    global define list GetArchiveFiles() ``{
	return archivefiles;
    }


    /**
     * Read installed packages.
     * @return map Map $[ "packagename" : "version" ]
     */
    global define map<string,string> ReadActualInstalledPackages() ``{
	// do not read installed packages in autoyast config mode
	if (Mode::config () == true)
	{
	    return $[];
	}

	// init packager (TODO: later should be removed...)
	Pkg::TargetInit("/", false);

	// read info about installed packages
	list<string> info = Pkg::GetPackages(`installed, false);
	y2debug("ReadActualInstalledPackages: %1", sort(info));

	// process version info
	map<string,string> ret = $[];

	foreach(string pkg, info, ``{
		list<string> all = splitstring(pkg, " ");

		ret = add(ret, all[0]:"unknown", all[1]:"" + "-" + all[2]:"");
	    }
	);

	return ret;
    }

    /**
     * Return installed packages. Result is cached in Restore module, so only first use takes long time
     * @return map Map $[ "packagename" : "version" ]
     */
    global define map<string,string> GetActualInstalledPackages() ``{
	if (actualinstalledpackages == $[] || actualinstalledpackages == nil)
	{
	    actualinstalledpackages = ReadActualInstalledPackages();
	}

	return actualinstalledpackages;
    }


    /**
     * Return missing packages (packages which were installed at backup time, but at restore time they are not installed)
     * @return map Map $[ "packagename" : $[ "ver" : "version", "descr" : "Short description of the package"]], key description is present only if decription exists
     */
    global define map<string, map<string, string> > GetMissingPackages() ``{
	map<string,string> r = $[];

	GetActualInstalledPackages();

	// filter actual installed packages out
	r = filter(string p, string v, installedpkgs, ``{
		return (haskey(actualinstalledpackages, p) != true);
	    }
	);

	y2debug("actualinstalledpackages: %1", actualinstalledpackages);
	y2debug("GetMissingPackages r: %1", r);

	// add descriptions
	map<string, map<string,string> > ret = mapmap(string pkg, string version, r, ``{
		string descr = (Mode::test () == false) ? Pkg::PkgSummary(pkg) : "";

		if (descr == nil)
		{
		    return $[pkg: $["ver" : version]];
		}
		else
		{
		    return $[pkg: $["ver" : version, "descr" : descr ]];
		}
	    }
	);

        // ignore gpg-pubkey packages - they are not real RPMs
	ret = filter(string pkg, map<string,string> inf, ret, ``{
		return (pkg != "gpg-pubkey");
	    }
	);

	y2debug("GetMissingPackages ret: %1", ret);

	return ret;
    }

    /**
     * Return extra packages (packages which are installed at restore time, but at restore time they are installed)
     * @return map Map $[ "packagename" : $[ "ver" : "version", "descr" : "Short description of the package"]], key description is present only if decription exists
     */
    global define map<string,map<string,string> > GetExtraPackages() ``{
	map<string,string> r = $[];

	GetActualInstalledPackages();

	// filter actual installed packages out
	r = filter(string p, string v, actualinstalledpackages, ``{
		return (haskey(installedpkgs, p) != true);
	    }
	);

	// add descriptions
	map<string,map<string,string> > ret = mapmap(string pkg, string version, r, ``{
		string descr = (Mode::test () == false) ? Pkg::PkgSummary(pkg) : "";

		if (descr == nil)
		{
		    return $[pkg: $["ver" : version]];
		}
		else
		{
		    return $[pkg: $["ver" : version, "descr" : descr ]];
		}
	    }
	);

	return ret;
    }

    /**
     * Return packages which have different version at backup archive and in system
     * @return map Map $[ "packagename" : $[ "inst": "installed version", "ver" : "version at backup time, "descr" : "Short description of the package"]], key description is present only if decription exists
     */
    global define map<string, map <string, string> > GetMismatchedPackages() ``{
	map<string, map <string, string> > ret = $[];

	GetActualInstalledPackages();

	foreach(string p, string v, actualinstalledpackages, ``{
		if (haskey(installedpkgs, p) == true)
		{
		    string backupversion = (string)(installedpkgs[p]:"");

		    if (backupversion != v)
		    {
			string descr = (Mode::test () == false) ? Pkg::PkgSummary(p) : "";

			ret = (descr != nil) ? add(ret, p, $["ver": backupversion, "inst" : v, "descr" : descr]) :
			    add(ret, p, $["ver": backupversion, "inst" : v]);
		    }
		}
	    }
	);

	return ret;
    }

    /**
     * Returns selected packages (even partially).
     * @return map Map with same keys as map returned by GetArchiveInfo()
     */

    global define map<string, map> GetSelectedPackages() ``{
	map<string, map> ret = $[];

	if (archive_info != nil)
	{
	    // filter out unselected packages
	    ret = filter(string p, map info, archive_info, ``{
		    string sel_type = (string)(info["sel_type"]:" ");

		    if (sel_type == "X" || sel_type == "P")
		    {
			return true;
		    }
		    else
		    {
			return false;
		    }
		}
	    );
	}

	return ret;
    }

    /**
     * Clear cache of installed packages. Next use of GetActualInstalledPackages() function will reread installed packages.
     */
     global define void ClearInstalledPackagesCache()
     {
	 actualinstalledpackages = nil;
     }

    /**
     * Umount mounted file system.
     */

    global define void Umount() ``{
	if (mountpoint != "" && mountpoint != nil)
	{
	    y2milestone("Umount called - unmounting %1", mountpoint);
	    SCR::Execute(.target.umount, mountpoint);
	    mountpoint = "";
	}
    }

    /**
     * Access to file on NFS server
     * @param server Name or IP adress of NFS server
     * @param file File on the server
     * @return map $[ "mounted" : boolena (true on success), "mountpoint" : string (mount point) , "file" : string (file name), "server_dir" : string (directory on the server) ]
     */

    define map mountNFS(string server, string file) ``{
	integer pos = findlastof(file, "/");

	if (pos != nil)
	{
	    string dir = substring(file, 0, pos);
	    string f = substring(file, pos + 1);

	    string tmpdir = (string) SCR::Read(.target.tmpdir);
	    string mpoint = tmpdir + "/nfs";

	    if (dir == "" || dir == nil)
	    {
		dir = "/";
	    }

	    // create mount point directory
	    SCR::Execute(.target.mkdir, mpoint);

	    y2milestone("dir: %1", dir);
	    y2milestone("file: %1", f);
	    y2milestone("mpoint: %1", mpoint);

	    boolean result = (boolean) SCR::Execute(.target.mount, [server + ":" + dir, mpoint], "-t nfs -o ro");

	    return $[ "mounted" : result, "mountpoint" : mpoint , "file" : f, "server_dir" : dir ];
	}

	return $[ "mounted" : false ];
    }


    /**
     * Access to file on CD
     * @param cdindex Index of CD drive (in list SCR::Read(.probe.cdrom))
     * @return map $[ "mounted" : boolena (true on success), "mpoint" : string (mount point) ]
     */

    define map mountCD(integer cdindex) ``{
	list drives = (list) SCR::Read(.probe.cdrom);

	if (cdindex == nil || cdindex > size(drives) - 1)
	{
	    return $[ "mounted" : false ];
	}

	string cddevice = drives[cdindex, "dev_name"]:"/dev/cdrom";
	string tmpdir = (string) SCR::Read(.target.tmpdir);
	string mpoint = tmpdir + "/cd";

	// create mount point directory
	SCR::Execute(.target.mkdir, mpoint);

	boolean result = (boolean) SCR::Execute(.target.mount, [cddevice, mpoint], "-o ro" );

	return $[ "mounted" : result, "mpoint" : mpoint ];
    }


    /**
     * Access to file on floppy
     * @param fdindex Index of floppy drive (in list SCR::Read(.probe.floppy))
     * @return map $[ "mounted" : boolena (true on success), "mpoint" : string (mount point) ]
     */

    define map mountFloppy(integer fdindex) ``{
	list drives = (list) SCR::Read(.probe.floppy);

	if (fdindex == nil || fdindex > size(drives) - 1)
	{
	    return $[ "mounted" : false ];
	}

	string fddevice = drives[fdindex, "dev_name"]:"/dev/fd0";
	string tmpdir = (string) SCR::Read(.target.tmpdir);
	string mpoint = tmpdir + "/fd";

	// create mount point directory
	SCR::Execute(.target.mkdir, mpoint);

	boolean result = (boolean) SCR::Execute(.target.mount, [fddevice, mpoint], "-o ro" );

	return $[ "mounted" : result, "mpoint" : mpoint ];
    }


    /**
     * Mount device
     * @param device Device file name (e.g. /dev/cdrom, /dev/sda...)
     * @return map Map $[ "mounted" : boolean (true on success), "mpoint" : string (mount point where device was mounted) ];
     */

    define map mountDevice(string device) ``{
	if (device == nil || device == "")
	{
	    return $[ "mounted" : false ];
	}

	string tmpdir = (string) SCR::Read(.target.tmpdir);
	string mpoint = tmpdir + "/mount";

	// create mount point directory
	SCR::Execute(.target.mkdir, mpoint);

	// mount read-only
	boolean result = (boolean) SCR::Execute(.target.mount, [device, mpoint], "-o ro" );

	return $[ "mounted" : result, "mpoint" : mpoint ];
    }

    /**
     * Mount input source
     * @param input File in URl-like syntax
     * @return map Map $[ "success" : boolean (true on success), "mpoint" : string (mount point), "file" : string (file name on the local system) ];
     */

    global define map MountInput(string input) ``{

	y2milestone("MountInput(%1)", input);

	boolean success = false;
	string mpoint = "";
	string file = "";

	//parse 'input'
	string nfsprefix = "nfs://";
	string fileprefix = "file://";
	string devprefix = "dev://";
	string cdprefix = "cd";		// cd prefix can be "cd://" (equivalent to "cd0://"), "cd1://", "cd2://", ... number of CD identification
	string fdprefix = "fd";

	map parsed_url = URL::Parse(input);
	string scheme = parsed_url["scheme"]:"file";

	if (scheme == "nfs")
	{
	    string nfs_server = parsed_url["host"]:"";
	    //substring(tail, 0, pos);
	    string nfs_file = parsed_url["path"]:"";
	    //substring(tail, pos + delimiter_correction);

	    // check if portmapper is installed
	    integer result = (integer) SCR::Execute(.target.bash, "/bin/rpm -q portmap");
	    y2milestone("check result: rpm: %1", result);

	    if (result != 0)
	    {
		y2milestone("portmapper package is not installed");
		Package::DoInstall(["portmap"]);
	    }

	    // check if portmapper is running
	    integer portmapper_status = Service::Status("portmap");
	    y2milestone("portmap status: %1", portmapper_status);

	    if (portmapper_status != 0)
	    {
		// start portmapper
		boolean started = Service::Start("portmap");
		y2milestone("portmap start result: %1", started);
	    }

	    y2milestone("NFS source - server: %1  file: %2", nfs_server, nfs_file);

	    map mountresult = mountNFS(nfs_server, nfs_file);

	    if (mountresult["mounted"]:false == false)
	    {
		y2error("Cannot read source '%1' - NFS mount failed", input);
	    }
	    else
	    {
		mpoint = mountresult["mountpoint"]:"";
		file = mpoint + "/" + mountresult["file"]:"";
		success = true;

		y2milestone("mpoint: %1", mpoint);
		y2milestone("file: %1", file);
	    }
	}
	else if (scheme == "dev")
	{
	    string device = parsed_url["host"]:"";
	    string devfile = parsed_url["path"]:"";

	    y2milestone("Device source - device: %1  file: %2", device, devfile);

	    map mountresult = mountDevice(device);

	    if (mountresult["mounted"]:false == false)
	    {
		y2error("Cannot read source '%1' - device mount failed", input);
	    }
	    else
	    {
		mpoint = mountresult["mpoint"]:"";
		file = mpoint + "/" + devfile;
		success = true;
	    }
	}
	else if (scheme == "file")
	{
	    file = substring(input, size(fileprefix));
	    y2milestone("FILE source: %1", file);

	    success = true;
	}
	else if (regexpmatch(scheme, "^cd[0-9]*"))
	{
	    // get CD drive index
	    string cdindex = regexpsub(scheme, "^cd0*([0-9]*)", "\\1");
	    string cdfile = parsed_url["path"]:"";

	    if (cdindex == nil || cdindex == "")
	    {
		cdindex = "0";
	    }

	    if (cdfile == nil)
	    {
		cdfile = "";
	    }

	    y2milestone("CD source - drive: %1  file: %2", cdindex, cdfile);

	    // mount CD
	    map mountresult = mountCD(tointeger(cdindex));

	    if (mountresult["mounted"]:false == false)
	    {
		y2error("Cannot read source '%1' - mount failed", input);
	    }
	    else
	    {
		mpoint = mountresult["mpoint"]:"";
		file = mpoint + "/" + cdfile;
		success = true;
	    }
	}
	else if (regexpmatch(scheme, "^fd[0-9]*"))
	{
	    // get floppy index
	    string fdindex = regexpsub(scheme, "fd0*([0-9]*)://(.*)", "\\1");
	    string fdfile = parsed_url["path"]:"";

	    if (fdindex == nil || fdindex == "")
	    {
		fdindex = "0";
	    }

	    if (fdfile == nil)
	    {
		fdfile = "";
	    }

	    y2milestone("Floppy source - drive: %1  file: %2", fdindex, fdfile);

	    // mount floppy
	    map mountresult = mountFloppy(tointeger(fdindex));

	    if (mountresult["mounted"]:false == false)
	    {
		y2error("Cannot read source '%1' - mount failed", input);
	    }
	    else
	    {
		mpoint = mountresult["mpoint"]:"";
		file = mpoint + "/" + fdfile;
		success = true;
	    }
	}
	else
	{
	    y2error("Unknown prefix in input: %1", input);
	}

	return $[ "success" : success, "mpoint" : mpoint, "file" : file ];
    }


    /**
     * Check if volume number in archive is equal to expected volume number
     * @param filename Volume file name
     * @param num Number of volume
     * @return map Map $[ "success" : boolean (true on success), "lastvolume" : boolean (true if archive is last volume) ]
     */

    define map CheckVolume(string filename, integer num) ``{
	boolean success = false;
	boolean lastvolume = true;

	// test if archive is multi volume - use -v parameter to get file descriptions
	map detailresult = (map) SCR::Execute(.target.bash_output, "/bin/tar -v -t -f " + filename);

	list<string> stdout = splitstring(detailresult["stdout"]:"", "\n");
	string firstline = stdout[0]:"";

	y2debug("Test: First line: %1", firstline);

	// check if first line is volume label number num
	if (regexpmatch(firstline, sformat("V--------- .* YaST2 backup: Volume %1--.*--", num)) == true)
	{
	    success = true;
	}

	return $[ "success" : success, "lastvolume" : (detailresult["exit"]:-1 == 0) ];
    }


    /**
     * Copy volume to the local temporary directory
     * @param filename Source file
     * @param num Number of volume
     * @return map Map $[ "success" : boolean (true on success), "file" : string (file name in the temporary directory) ]
     */

    define map CopyVolume(string filename, integer num) ``{
	boolean success = false;
	string tmpfile = tempdir + "/" + sformat("%1", num) + ".tar";

	// copy multi volume part to temp directory
	integer exit = (integer) SCR::Execute(.target.bash, "/bin/cp " + filename + " " + tmpfile);

	if (exit == 0)
	{
	    success = true;
	}
	else
	{
	    y2error("Copy failed");
	}

	return $[ "success" : success, "file" : tmpfile ];
    }


    /**
     * Add next volume - check volume, copy volume to the temp. dir.
     * @param file File name of volume
     * @return map Map $[ "success" : boolean (true on success), "lastvolume" : boolean (true if archive is last volume) ]
     */

    define map AddVolume(string file) ``{

	integer vol = size(volumeparts) + 1;
	boolean success = false;

	// check if file is next volume
	map check = CheckVolume(file, vol);

	y2debug("CheckVolume(%1, %2): %3", file, vol, check);

	if (check["success"]:false == true)
	{

	    // copy file to temporary directory
	    map copy = CopyVolume(file, vol);
	    y2debug("CopyVolume(%1, %2): %3", file, vol, copy);

	    if (copy["success"]:false == true)
	    {
		string partname = copy["file"]:"";

		volumeparts = add (volumeparts, partname);

		success = true;
	    }
	}

	return $[ "success" : success, "lastvolume" : check["lastvolume"]:true ];
    }

    /**
     * Change restore selection of package.
     * @param pkgnames Name of package
     * @param selection New restore selection for package, map  $[ "sel_type" : "X", "sel_file" : ["files"] ]
     */
    global define void SetRestoreSelection(string pkgname, map selection) ``{
	if (pkgname == nopackage_id)
	{
	    pkgname = "";
	}

	if (haskey(archive_info, pkgname) == false)
	{
	    y2warning("Package %1 is not in archive, cannot be restored!", pkgname);
	}
	else
	{
	    string sel_type = selection["sel_type"]:" ";
	    list sel_file = [];
	    map<string, any> pkginfo = archive_info[pkgname]:$[];

	    if (pkginfo == nil)
	    {
		pkginfo = $[];
	    }

	    if (sel_type == "P")
	    {
		sel_file = selection["sel_file"]:[];
	    }
	    else if (sel_type != "X" && sel_type != " ")
	    {
		y2warning("Unknown selection type '%1' for package '%2'", sel_type, pkgname);
	    }

	    pkginfo = add(pkginfo, "sel_type", sel_type);
	    pkginfo = add(pkginfo, "sel_file", sel_file);

	    archive_info = add(archive_info, pkgname, pkginfo);
	}
    }


    /**
     * Set selection in _auto client and display properties of archive
     * @param settings Restoration selection
     */

    global define void SetSelectionProperty(map<string,map> settings) ``{
	// set selection for this archive and display archive propertyt
	foreach(string package, map info, settings, ``{
		y2debug("setting selection: package:%1, selection:%2)", package, info);

		if (package == nopackage_id)
		{
		    package = "";
		}

		SetRestoreSelection(package, info);
	    }
	);
    }



    /**
     * Read contents of archive
     * @param input File name of backup archive. File on NFS server is 'nfs://server:/dir/file.tar', local file: 'file:///dir/file.tar' (prefix is file://, directory is /dir)
     * @return boolean True if archive was succesfully read, otherwise false (file does not exist, not tar archive, broken archive, archive not created by Backup module, ...)
     */
    global define boolean Read(string input) ``{

	// umount old mount point
	Umount();

	if (tempdir == "")
	{
	    tempdir = (string) SCR::Read(.target.tmpdir);
	}

	inputname = input;

	if (Mode::test () == true)
	{
	    filename = "/tmp/dummy.tar";

	    archivefiles = [ "info/", "info/date", "info/comment", "info/files",
		"info/packages_info", "info/installed_packages", "info/hostname",
		"NOPACKAGE-20020509-0.tar.gz", "kdebase3-3.0-19-20020509-0.tar.gz", "lprng-3.8.5-49-20020509-0.tar.gz",
		"mozilla-0.9.8-54-20020509-0.tar.gz", "netcfg-2002.3.20-0-20020509-0.tar.gz" ];

	    date = "13.01.2002 14:25";
	    comment = "Some comments";
	    hostname = "linux.local";

	    installedpkgs = $["netcfg" : "2002.3.20-0", "lprng" : "3.8.5-49", "kdebase3" : "3.0-19", "gnome-applets" : "1.4.0.5-98", "mozilla" : "0.9.8-54"];
	    actualinstalledpackages = $["ggv" : "1.1.93-167", "netcfg" : "2002.3.20-0", "lprng" : "3.8.5-49", "kdebase3" : "3.0-19", "aterm" : "0.4.0"];

	    archive_info = $["" : $["descr" : "Files not owned by any package", "files":["/.qt/", "/dev/dvd", "/dev/cdrom"], "sel_type" : "X" ],
		"kdebase3" : $["descr" : "KDE base package: base system", "files":["/etc/opt/kde3/share/config/kdm/kdmrc"], "prefix" : "", "sel_type" : "X", "vers" : "3.0-19"],
		"mozilla" : $["descr" : "Open Source WWW browser", "files" : ["/opt/mozilla/chrome/installed-chrome.txt"], "prefix" : "", "sel_type" : "X", "vers" : "0.9.8-54"],
		"lprng" : $["descr" : "LPRng Print Spooler", "files" : ["/etc/init.d/lpd"], "prefix" : "", "sel_type" : "X", "vers" : "3.8.5-49"],
		"netcfg":$["descr" : "Network configuration files in /etc", "files" : ["/etc/HOSTNAME", "/etc/defaultdomain", "/etc/exports", "/etc/hosts"], "prefix":"", "sel_type":"X", "vers":"2002.3.20-0"]
		];

	    // set default selection
	    archive_info = mapmap(string p, map<string, any> i, archive_info, ``{
		    i = add(i, "sel_type", (p == "") ? "X" : ((haskey(actualinstalledpackages, p)) ? "X" : " "));

		    return $[p: i];
		}
	    );

	    return true;
	}

	// mount source
	map mresult = MountInput(input);

	y2debug("MountInput: %1", mresult);

	if (mresult["success"]:false == false)
	{
	    // error message
	    Report::Error(_("Cannot mount file system."));
	    return false;
	}

	filename = mresult["file"]:"";
	mountpoint = mresult["mpoint"]:"";

	y2milestone("filename: %1", filename);
	y2milestone("mountpoint: %1", mountpoint);

	// get archive contents
	map result = (map) SCR::Execute(.target.bash_output, "/bin/tar -t -f " + filename);

	// check tar exit value
	if (result["exit"]:-1 != 0)
	{
	    // tar failed, check whether file is first volume of multivolume archive
	    volumeparts = [];
	    map addresult = AddVolume(filename);

	    if (addresult["success"]:false == true)
	    {
		// read archive info from local copy (should be faster)
		filename = volumeparts[0]: "";
	    }
	    else
	    {
		// error message
		Report::Error(_("Cannot read archive file.
It is not a tar archive or it is broken.
"));
		return false;
	    }

	}
	else
	{
	    // if archive is not local or NFS file copy it to tempdir (even if it isn't multi volume),
	    // so removable device (e.g. CD-ROM) can be used for package installation later
	    if (substring(input, 0, size("file://")) != "file://" && substring(input, 0, size("nfs://")) != "nfs://")
	    {
		// copy archive to local file
		map copy = CopyVolume(filename, 0);

		if (copy["success"]:false == true)
		{
		    // set file name to local copy
		    filename = copy["file"]:"";

		    // umount file system
		    Umount();
		}
		else
		{
		    // error message: copy failed
		    Report::Error(_("Cannot copy archive file
to temporary directory.
"));
		    return false;
		}
	    }
	}

	// get list of files
	archivefiles = splitstring(result["stdout"]:"", "\n");
	archivefiles = filter(string f, archivefiles, ``{return f != "" && f != nil;});

	boolean compressed_packages_info = contains(archivefiles, "info/packages_info.gz");
	y2milestone("compressed_packages_info: %1", compressed_packages_info);

	if (!(contains(archivefiles, "info/files") && (contains(archivefiles, "info/packages_info") || contains(archivefiles, "info/packages_info.gz")) && contains(archivefiles, "info/installed_packages")))
	{
	    // archive doesn't contain files from backup - file is not backup archive or not first volume of multi volume archive
	    y2error("Archive does not contain 'info/files' or 'info/packages_info' or 'info/installed_packages' file!");

	    // error message
	    Report::Error(_("Archive does not contain required files.
It was probably not created by the backup module.
"));
	    return false;
	}

	y2debug("read archivefiles: %1", archivefiles);

	string infofiles = "info/comment info/hostname info/date info/installed_packages info/files info/complete_backup ";

	infofiles = infofiles + (compressed_packages_info ? "info/packages_info.gz" : "info/packages_info");

	// unpack info files
	result = (map) SCR::Execute(.target.bash_output, "/bin/tar -C " + tempdir + " -x -f " + filename + " " + infofiles + " 2> /dev/null");

	date = (string) SCR::Read(.target.string, tempdir + "/info/date");
	comment = (string) SCR::Read(.target.string, tempdir + "/info/comment");
	hostname = (string) SCR::Read(.target.string, tempdir + "/info/hostname");

	string complete_backup_str = (SCR::Read(.target.size, tempdir + "/info/complete_backup") > 0) ?
	    (string) SCR::Read(.target.string, tempdir + "/info/complete_backup")
	    : "";

	y2debug("complete_backup_str: %1", complete_backup_str);

	complete_backup = splitstring(complete_backup_str, "\n");
	y2milestone("Complete backup: %1", complete_backup);

	// read archive contents file
	string archivefs = (string) SCR::Read(.target.string, tempdir + "/info/files");

	if (archivefs != nil)
	{
	    archivefiles = splitstring(archivefs, "\n");
	    archivefiles = filter(string pk, archivefiles, ``{return pk != "" && pk != nil;});
	}

	y2debug("final archivefiles: %1", archivefiles);

	// read installed packages
	string installedpkgs_str = (string) SCR::Read(.target.string, tempdir + "/info/installed_packages");

	// convert string to list
	list<string> installedpkgs_list = splitstring(installedpkgs_str, "\n");
	installedpkgs_list = filter(string pk, installedpkgs_list, ``{return pk != "" && pk != nil;});

	// convert list to map (key - package name, value - package version)
	installedpkgs = $[];

	foreach(string fullname, installedpkgs_list, ``{
		string version = regexpsub(fullname, ".*-(.*-.*)", "\\1");
		string name = substring(fullname, 0, size(fullname) - size(version) - 1);

		installedpkgs = add(installedpkgs, name, version);
	    }
	);

	if (compressed_packages_info == true)
	{
	    y2milestone("Unpacking packages_info.gz file");
	    // uncompress compressed package info
	    SCR::Execute(.target.bash, "/usr/bin/gunzip -c " + tempdir + "/info/packages_info.gz > " + tempdir + "/info/packages_info");
	    y2milestone("File unpacked");
	}

	// covert package info file to YCP structure by perl script
	y2milestone("Converting package info");
	SCR::Execute(.target.bash, "/usr/lib/YaST2/bin/restore_parse_pkginfo.pl < " + tempdir + "/info/packages_info > " + tempdir + "/info.ycp");

	y2milestone("Reading package info");
	archive_info = (map<string,map<string, any> >) SCR::Read(.target.ycp, tempdir + "/info.ycp");
	y2milestone("Read %1 packages", size(archive_info));


	// read actual installed packages
	GetActualInstalledPackages();

	map<string, map<string, string> > mismatched = GetMismatchedPackages();

	// add package descriptions and default selection
	archive_info = mapmap(string p, map<string, any> i, archive_info, ``{
	        // decription of files not owned by any package
		string descr = (p == "") ? _("Files not owned by any package") : Pkg::PkgSummary(p);

		if (i == nil)
		{
		    i = $[];
		}

		if (descr == nil)
		{
		    descr = "";
		}

		map<string, any> t = add(i, "descr", descr);

		string sel_type = " ";

		if (Mode::config () == false)
		{
		    // set default selection to "X" (package is installed) or " " (package is not installed)
		    if (p == "")
		    {
			// set "no package" default value to "X"
			sel_type = "X";
		    }
		    else
		    {
			sel_type = (haskey(actualinstalledpackages, p) && !haskey(mismatched, p)) ? "X" : " ";
		    }
		}
		else
		{
		    // in autoinstall-config mode leave preselected value
		    sel_type = i["sel_type"]:" ";
		}

		t = add(t, "sel_type", sel_type);

		return $[p: t];
	    }
	);

	if (Mode::config () == true)
	{
	    // refresh file selection in autoinstall config mode
	    y2debug("Setting selection: %1", autoinst_info);
	    SetSelectionProperty(autoinst_info);
	}

	y2milestone("values from archive: date=%1, comment=%2, hostname=%3", date, comment, hostname);

	y2debug("installed packages at backup time: %1", installedpkgs);
	y2debug("actual installed packages: %1", GetActualInstalledPackages());

	return true;
    }


    /**
     * Set settings
     * @param settings Map with settings: start lilo, run SuSEconfig, restore RPM db
     */

    global define void Set(map settings) ``{
	y2milestone("Using settings: %1", settings);
	runbootloader = settings["runbootloader"]:true;
	runSuSEconfig = settings["runSuSEconfig"]:true;
	restoreRPMdb = settings["restoreRPMdb"]:false;
	completerestoration = settings["completerestoration"]:true;

	list<string> archiveslist = settings["archives"]:[];
	inputname = archiveslist[0]:"";
	// read archives
	if (size(archiveslist) == 0)
	{
	    // set unconfigured state
	    inputname = "";
	    archive_info = $[];
	}

	// set restore selection
	SetSelectionProperty(settings["selection"]:$[]);

	// store settings - file selection will be refreshed after archive selection
	// autoinstall config mode
	autoinst_info = settings["selection"]:$[];
	y2debug("setting autoinst_info: %1", autoinst_info);

	y2debug("archive_info: %1", archive_info);
    }

    /**
     * Get all restore settings - for use by autoinstallation
     * @param settings The YCP structure to be imported
     * @return boolean True on success
     */

    global define boolean Import(map settings) ``{
	if (settings == nil)
	{
	    return false;
	}

	Set(settings);

	return true;
    }


    /**
     * Dump the restore settings to a single map - for use by autoinstallation.
     * @return map Dumped settings (later acceptable by Import ())
     */
    global define map Export() ``{

	map selection = $[];

	if (inputname == "")
	{
	    // unconfigured
	    return $[];
	}

	foreach(string package, map info, archive_info, ``{
		string sel_type = info["sel_type"]:" ";
		list sel_file = info["sel_file"]:[];

		if (package == "")
		{
		    package = nopackage_id;
		}

		selection = add(selection, package, $[ "sel_type" : sel_type, "sel_file" : sel_file ]);
	    }
	);

	return $[
	    "archives"		: prepend(inputvolumes, inputname),
	    "runbootloader"	: runbootloader,
	    "runSuSEconfig"	: runSuSEconfig,
	    "restoreRPMdb"	: restoreRPMdb,
	    "completerestoration" : completerestoration,
	    "selection"		: selection
	];
    }

    /**
     * Return restore configuration
     * @return map Map $[ "packagename" : $["vers" : "version", "files" : ["files in the archive"], "prefix" : "installprefix", "descr" : "Short description", "sel_type" : "X", "sel_file" : ["selected files to restore"] ] ], possible values for "sel_type" key are: "X" - restore all files from archive, " " - do not restore this package, "P" - partial restore, restore only selected files at "sel_file" key. Package name "" means files not owned by any package.
     */
    global define map<string, map<string, any> > GetArchiveInfo() ``{
	return archive_info;
    }

    /**
     * Return number of packages which will be restored from archive
     * @return integer Total selected packages
     */
    global define integer TotalPackagesToRestore() ``{
	integer total = 0;

	// filter out unselected packages
	foreach(string p, map info, archive_info, ``{
		string sel_type = info["sel_type"]:" ";

		if (sel_type == "X" || sel_type == "P")
		{
		    total = total + 1;
		}
	    }
	);

	return total;
    }


    /**
     * Return number of files which will be unpacked from archive
     * @return integer Total selected files
     */
    global define integer TotalFilesToRestore() ``{
	integer total = 0;

	// filter out unselected packages and compute total restored files
	foreach(string p, map info, archive_info, ``{
		string sel_type = info["sel_type"]:" ";

		if (sel_type == "X")
		{
		    total = total + size(info["files"]:[]);
		}
		else if (sel_type == "P")
		{
		    total = total + size(info["sel_file"]:[]);
		}
	    }
	);

	return total;
    }

    /**
     * Activate boot loader configuration if requested.
     * Uses variable Restore::runbootloader
     * @return boolean true on success
     */
    global define boolean ActivateBootloader() ``{
	boolean ret = nil;

	// disable Bootloader's progress bar
	Progress::off();

	if (runbootloader == true && Mode::test () == false)
	{
	    y2milestone("activating boot loader configuration");

	    // read actual boot loader configuration
	    ret = Bootloader::Read();

	    if (ret == false)
	    {
		y2error("Boot loader read failed");
	    }
	    else
	    {
		// write configuration - force re-installation of boot loader
		Bootloader::SetWriteMode( $["save_all" : true] );
		ret = Bootloader::Write();

		if (ret == false)
		{
		    y2error("Boot loader write failed");
		}
	    }

	    if (ret == false)
	    {
		// error popup message
		Report::Error(_("Boot loader configuration failed."));
	    }

	    y2milestone("boot loader activated: %1", ret);
	}

	// re-enable progress bar
	Progress::on();

	return ret;
    }

    /**
     * Restore files from archive
     * @param abort This block is periodically evaluated, if it evaluates to true restoration will be aborted. It should be something like ``{return UI::PollInput () == `abort;} if UI exists or ``{ return false; } if there is no UI (abort will not be possible).
     * @param progress Id of progress bar or nil.
     * @param targetdir Directory to which files from archive will be upacked
     * @return map Map $[ "aborted" : boolean, "restored" : [ "restored file" ], "failed" : [ "failed file" ] ]
     */

    global define map Write(block<boolean> abort, symbol progress, string targetdir) ``{

	map<string, map> restore = archive_info;
	integer total = 0;
	integer restoredpackages = 0;
	list<string> restoredfiles = [];
	list<string> failedfiles = [];
	boolean aborted = false;

	if (size(targetdir) == 0 || substring(targetdir, 0, 1) != "/")
	{
	    // error message
	    Report::Error(sformat(_("Invalid target directory (%1)."), targetdir));
	    return $[ "aborted" : aborted, "restored" : restoredfiles, "failed" : failedfiles, "packages" : restoredpackages, "bootloader" : false ];
	}

	// create target directory if it doesn't exist
	integer out = (integer)SCR::Execute(.target.bash, "/bin/mkdir -p " + targetdir);
	if (out != 0)
	{
	    // error message
	    Report::Error( Message::UnableToCreateDirectory(targetdir) );
	}

	// filter out unselected packages and compute total restored files
	restore = filter(string p, map info, restore, ``{
		string sel_type = info["sel_type"]:" ";

		if (completerestoration) sel_type = "X";

		if (sel_type == "X")
		{
		    total = total + size(info["files"]:[]);
		    return true;
		}
		else if (sel_type == "P")
		{
		    total = total + size(info["sel_file"]:[]);
		    return true;
		}
		else
		{
		    return false;
		}
	    }
	);

	y2milestone("%1 files will be restored from archive", total);

	list<string> packages = [];
	foreach(string package, map info, restore, ``{
		packages = add(packages, package);
	    }
	);

	integer i = 0;
	symbol ret = `next;

	if (restoreRPMdb == true)
	{
	    string rpm_backup_script = "/etc/cron.daily/suse.de-backup-rpmdb";
	    // backup existing RPM DB - use aaa_base script which is started from cron
	    if (SCR::Read(.target.size, rpm_backup_script) > 0)
	    {
		y2milestone("Startting RPM backup script (%1)", rpm_backup_script);
		integer result = (integer) SCR::Execute(.target.bash, rpm_backup_script);
		y2milestone("RPM backup exit value: %1", result);
	    }
	    else
	    {
		y2warning("RPM DB backup script (%1) was not found, DB was not backed up!", rpm_backup_script);
	    }

	    string stat = archive_info["", "sel_type"]:" ";
	    y2warning("NOPACKAGE status: '%1'", stat);
	    y2warning("NOPACKAGE files: '%1'", archive_info["", "files"]:[]);

	    if (stat == " " || stat == "P")
	    {
		// RPM DB restoration is selected, but files not owned by any package are deselected
		// uncompress only RPM DB files, which are in directory /var/lib/rpm

		list<string> RPM = [];

		// search RPM DB files
		foreach(string f, archive_info["", "files"]:[], ``{
			if (regexpmatch(f, "^/var/lib/rpm/") == true)
			{
			    RPM = add(RPM, f);
			}
		    }
		);

		y2warning("found RPM DB files: %1", RPM);

		if (stat == " ")
		{
		    map in = (map)eval(archive_info[""]:$[]);

		    in["sel_type"] = "P";
		    in["sel_file"] = RPM;

		    // set new values
		    restore[""] = (map)eval(in);

		    total = total + size(RPM);
		}
		else
		{
		    // may be some files are already selected
		    // check status of each file
		    list<string> already_selected = archive_info["","sel_file"]:[];

		    foreach(string rpm_file, RPM, ``{
			   if (!contains(already_selected, rpm_file))
			    {
				already_selected = add(already_selected, rpm_file);
				total = total + 1;
			    }
			}
		    );

		    map in = (map)eval(archive_info[""]:$[]);
		    in["sel_file"] = already_selected;

		    // set new values
		    restore[""] = (map)eval(in);
		}
	    }
	}

	y2warning("packages: %1", packages);

	while (i < size(packages))
	{
	    string package = packages[i]:"nonexistingpackage";
	    map info = restore[package]:$[];
	    // progress bar label
	    string label = (package == "") ? _("Restoring files not owned by any package...") :
		// progress bar label - %1 is package name
		sformat(_("Restoring package %1..."), package);
	    string sel_type = info["sel_type"]:"";

	    if (progress != nil)
	    {
		// update name of package
		UI::ChangeWidget(`id(progress), `Label, label);
		UI::ChangeWidget(`id(progress), `Value, i);
	    }

	    if (Mode::test () == true)
	    {
		// delay
		sleep(300);
	    }
	    else
	    {
		// y2logs will be probably overwritten - backup them
		if (package == "" && targetDirectory == "/")
		{
		    integer copy = (integer) SCR::Execute(.target.bash, "cp -r /var/log/YaST2 /var/log/YaST2.before_restore");
		    y2milestone("copy y2logs to /var/log/YaST2.before_restore: ret=%1", copy);
		}

		// get subarchive name
		string name = (package == "") ? "NOPACKAGE" : (package + "-" + info["vers"]:"");
		string fileinarchive = "";

		foreach(string f, archivefiles, ``{
			if (regexpmatch(f, "^" + name + "-........-.\\.s{0,1}tar.*") == true)
			{
			    y2debug("package %1 is in archive file %2", package, f);
			    fileinarchive = f;
			}
		    }
		);

		if (fileinarchive == "")
		{
		    y2error("Can't find subarchive for package %1", package);
		}
		else
		{
		    // unpack subarchive at background
		    boolean started  = nil;

		    if (IsMultiVolume() == true)
		    {
			string param = " ";

			foreach(string f, volumeparts, ``{
				param = param + "-f " + f + " ";
			    }
			);

			string command = "export LC_ALL=C; echo q | /bin/tar -C " + tempdir + " -x -M " + param + fileinarchive + " 2> /dev/null";
			y2debug("Running command: %1", command);

			started = (boolean) SCR::Execute(.background.run, command);
		    }
		    else
		    {
			started  = (boolean) SCR::Execute(.background.run, "export LC_ALL=C; /bin/tar -C " + tempdir + " -x -f " + filename + " " + fileinarchive);
		    }

		    // abort test cycle
		    while((boolean) SCR::Read(.background.isrunning))
		    {
			sleep(100);

			aborted = eval(abort);

			if (aborted == true)
			{
			    y2warning("Restoration aborted!");
			    SCR::Execute(.background.kill, nil);	// kill subprocess
			    break;
			}
		    }

		    // break all packages cycle
		    if (aborted == true)
		    {
			break;
		    }

		    // set compression parameter
		    string compress = "";

		    // use star archiver
		    boolean star = false;

		    if (regexpmatch(fileinarchive, ".*\\.tar\\.gz$") == true)
		    {
			compress = "-z";
		    }
		    else if (regexpmatch(fileinarchive, ".*\\.tar\\.bz2$") == true)
		    {
			compress = "-j";
		    }
		    else if (
			regexpmatch(fileinarchive, ".*\\.star$") ||
			regexpmatch(fileinarchive, ".*\\.star\\.gz$") ||
			regexpmatch(fileinarchive, ".*\\.star\\.bz2$")
			== true)
		    {
			// star can autodetect used compression
			compress = "";
			star = true;
		    }

		    y2debug("compress: %1", compress);
		    y2debug("star: %1", star);
		    y2debug("fileinarchive: %1", fileinarchive);

		    string RPMdb = (restoreRPMdb) ? "" : (star ? "-not pat=var/lib/rpm" : "--exclude var/lib/rpm");

		    // files to unpack, "" means all files
		    string unpackfiles = "";

		    // select files to unpack
		    if (sel_type == "P")
		    {
			// strip leading '/'
			foreach(string f, info["sel_file"]:[], ``{
				if (size(f) > 1 && substring(f, 0, 1) == "/")
				{
				    unpackfiles = unpackfiles + " " + substring(f, 1);
				}
			    }
			);
		    }

		    // create (s)tar command
		    string tarcommand = (star == false) ? ("export LC_ALL=C; /bin/tar -C " + targetdir + " "
			    + compress + " -x -v -f " + tempdir + "/" + fileinarchive + " " + RPMdb
			    + " " + unpackfiles + " 2> " + tempdir + "/tar.stderr > " + tempdir
			    + "/tar.stdout")
			: ("export LC_ALL=C; /usr/bin/star -C " + targetdir + " "
			    + compress + " -x -v -U -f " + tempdir + "/" + fileinarchive + " " + RPMdb
			    + " " + unpackfiles + " 2> " + tempdir + "/tar.stderr > " + tempdir
			    + "/tar.stdout");
			// -U option: replace existing files

		    y2milestone("tarcommand: %1", tarcommand);

		    // check whether star is installed
                    if (SCR::Read(.target.size, "/usr/bin/star") < 0 && star == true)
                    {
			// remove short
			string cont = Label::ContinueButton();
			cont = mergestring(splitstring(cont, "&"), "");

                        // popup message text - ask to install 'star' package
			// %1 is translated 'Continue' label
                        boolean inst = Popup::ContinueCancel(sformat(_("Package 'star' is needed to extract
files from the archive.
Press '%1' to install this package.
"), cont));

                        if (inst == true)
                        {
			    // install star package

			    // initialize package manager
			    Pkg::SourceStartCache(true);
			    Pkg::TargetInit ("/", false);

			    boolean ok = true;

			    // select star package to installation
			    ok = Pkg::PkgInstall("star");

			    // solve dependencies
			    ok = ok && Pkg::PkgSolve(false);

			    // perform installation (0 means install from all media)
			    list result = Pkg::PkgCommit(0);

			    // check result
			    y2debug ("PkgCommit: %1", result);
			    inst = ok && result[1]:nil == [] && result[2]:nil == [];
                        }

                        // abort restoration if star installation failed
                        if (inst == false)
                        {
			    // error popup message
			    Report::Error(_("Package 'star' is not installed.
Press OK to exit.
"));
                            y2error("star package wasn't installed - aborting");

			    return $[ "aborted" : true, "restored" : restoredfiles, "failed" :
				failedfiles, "packages" : restoredpackages, "bootloader" : false ];
                        }
                    }

		    // start subprocess
		    started = (boolean) SCR::Execute(.background.run_output, tarcommand);

		    y2milestone("Tar command started: %1", started);

		    while(SCR::Read(.background.isrunning) == true)
		    {
			sleep(100);	// small delay

			aborted = eval(abort);

			if (aborted == true)
			{
			    y2warning("Restoration aborted!");
			    SCR::Execute(.background.kill, nil);	// kill subprocess
			    break;
			}
		    }

		    // read restored files
		    list<string> stdout = splitstring((string) SCR::Read(.target.string, tempdir + "/tar.stdout"), "\n");

		    // remove empty lines
		    if (stdout != nil)
		    {
			stdout = filter(string line, stdout, ``{return size(line) > 0;});
		    }

		    // star has more verbose output - get only file names
		    if (star && stdout != nil)
		    {
			list<string> filteredstdout = [];
			foreach(string line, stdout,
			    ``{
				string new = regexpsub(line, "x (.*) .* bytes, .* tape blocks", "\\1");

				if (new != nil)
				{
				    filteredstdout = add(filteredstdout, new);
				}
			    }
			);

			stdout = filteredstdout;
		    }

		    y2debug("stdout: %1", stdout);

		    if (stdout != nil)
		    {
			restoredfiles = (list<string>)merge (restoredfiles, stdout);
		    }

		    // read failed files
		    list<string> stderr = splitstring((string) SCR::Read(.target.string, tempdir + "/tar.stderr"), "\n");

		    // remove empty lines
		    stderr = filter(string line, stderr, ``{return size(line) > 0;});
		    integer packagefailedfiles = 0;

		    if (stderr != [])
		    {
			y2warning("stderr: %1", stderr);
		    }

		    // add file names to failedfiles
		    foreach(string line, stderr, ``{
			    y2warning("line: %1", line);

			    if (line != nil && line != "")
			    {
				string file = regexpsub(line, "s{0,1}tar: (.*)", "\\1");

				// ignore final star summary
				if (file != nil && !regexpmatch(line, "star: .* blocks \\+ .* bytes \\(total of .* bytes = .*k\\)\\.")
				    && !regexpmatch(line, "star: WARNING: .*")
				)
				{
				    failedfiles = add(failedfiles, file);
				    packagefailedfiles = packagefailedfiles + 1;

				    y2warning("Restoration of file %1 failed", file);
				}
			    }
			}
		    );

		    // package restoration failed
		    if (restoredfiles != nil && packagefailedfiles == size(restoredfiles))
		    {
			y2warning("failed package: %1", package);
		    }
		    else
		    {
			restoredpackages = restoredpackages + 1;
		    }

		    if (aborted == true)
		    {
			break;
		    }
		}
	    }

	    i = i + 1;
	}

	// sort list of restored files and remove duplicates (caused by multiple unpacking)
	restoredfiles = toset(restoredfiles);

	// remove failed files
	restoredfiles = filter(string f, restoredfiles, ``{
		return !contains(failedfiles, f);
	    }
	);

	if (runbootloader == true && progress != nil)
	{
	    // progess bar label
	    UI::ChangeWidget(`id(progress), `Label, _("Configuring boot loader..."));
	    UI::ChangeWidget(`id(progress), `Value, i);
	}

	boolean bootload = ActivateBootloader();

	return $[ "aborted" : aborted, "restored" : restoredfiles, "failed" : failedfiles, "packages" : restoredpackages, "bootloader" : bootload ];
    }

    /**
     * Read next volume of multi volume archive
     * @param input Archive name in URL-like syntax
     * @return map Map $[ "success" : boolean (true on success), "lastvolume" : boolean (true if archive is last volume) ]
     */

    global define map ReadNextVolume(string input) ``{

	// umount mounted file system
	Umount();

	boolean ret = false;
	boolean last = true;

	// mount source
	map mount = MountInput(input);

	if (mount["success"]:false == true)
	{
	    mountpoint = mount["mpoint"]:"";

	    // add mounted file
	    map addvol =  AddVolume(mount["file"]:"dummy");

	    ret = addvol["success"]:false;
	    last = addvol["lastvolume"]:true;

	    if (ret == true)
	    {
		inputvolumes = add(inputvolumes, input);
	    }
	}

	return $[ "success" : ret, "lastvolume" : last ];
    }


    /**
     * Test all volumes together
     * @return boolean True: all volumes are OK, false: an error occured
     */

    global define boolean TestAllVolumes() ``{
	boolean ret = false;

	if (size(volumeparts) > 0)
	{
	    string param = "";

	    foreach(string f, volumeparts, ``{
		    param = param + "-f " + f + " ";
		}
	    );

	    // echo 'q' to tar stdin - if error occurs tar asks for next volume, q means quit
	    integer exit = (integer) SCR::Execute(.target.bash, "echo q | /bin/tar -t -M " + param);
	    y2milestone("Test result: %1", exit);

	    ret = (exit == 0);
	}

	return ret;
    }

    /**
     * Clear all archive settings
     */

    global define void ResetArchiveSelection() ``{
	// clear selected archive
	filename = "";
	inputname = "";

	// clear content of archive
	archivefiles = [];

	// clear installed packages at backup time
	installedpkgs = $[];

	// clear archive selection
	archive_info = $[];

	// clear archive info
	date = "";
	hostname = "";
	comment = "";

	// clear list of files
	volumeparts = [];

	Umount();
    }


    /**
     * Clear all settings (archive and list of installed packages)
     */

    global define void ResetAll() ``{
	ResetArchiveSelection();

	// clear list of installed packages
	actualinstalledpackages = $[];
    }

    /**
     * Remove shortcut mark from string
     * @param scut string with shortcut mark (&)
     * @return string result
     */
    define string RemoveShortCut(string scut) ``{
	return mergestring(splitstring(scut, "&"), "");
    }

    /**
     * Convert boolean value to translated "yes" or "no" string
     * @param b input value
     * @return string translated Yes/No string
     */
    define string yesno(boolean b) ``{
	string ret = "?";

	if (b == true)
	{
	    ret = _("Yes");
	}
	if (b == false)
	{
	    ret = _("No");
	}

	return ret;
    }

    /**
     * Create restore configuration summary. Used in autoinstallation restore module configuration.
     * @return string rich text summary
     */

    global define string Summary() ``{
	if (inputname == "")
	{
	    // not configured yet
	    return Summary::NotConfigured();
	}
	else
	{
	    // Summary text header
	    string archives_info = "<P><B>" + _("Backup archive") + "<B></P>";

	    archives_info = archives_info + "<P>" + inputname;
	    foreach(string vol, inputvolumes, ``{
		    archives_info = archives_info + vol + "<BR>";
		}
	    );
	    archives_info = archives_info + "</P>";

	    // Summary text header
	    string options_info = "<P><B>" + _("Restore options") + "<B></P>";

	    options_info = options_info + "<P>" + RemoveShortCut(_("Activate &boot loader configuration after restoration")) + ":  " + yesno(runbootloader) + "<BR>"
		+ RemoveShortCut(_("Run &SuSEconfig after restoration")) + ":  " + yesno(runSuSEconfig) + "<BR>"
		+ RemoveShortCut(_("Restore RPM &database (if present in archive)")) + ":  " + yesno(restoreRPMdb) + "<BR></P>";

	    // summary text heading
	    string selection_info = "<P><B>" + _("Packages to restore") + "</B></P><P>";

	    if (completerestoration)
	    {
		// part of the summary text
		selection_info = selection_info + _("<I>Restore all files from the archive</I>");
	    }
	    else if (archive_info != nil)
	    {
		foreach(string p, map info, archive_info, ``{
			if (p == "")
			{
			    p = _("-- No package --");
			}

			if (info["sel_type"]:" " != " ")
			{
			    selection_info = selection_info + p + "<BR>";
			}
		    }
		);
	    }

	    selection_info = selection_info + "</P>";

	    return archives_info + options_info + selection_info;
	}
    }


    global define map<string, any> ProposeRPMdbRestoration() ``{

	map<string, map> extra = GetExtraPackages();
	map<string, map> missing = GetMissingPackages();
	map<string, map<string, string> > mismatched = GetMismatchedPackages();

	map<string, map> selected = GetSelectedPackages();

	// proposed DB restoration
	boolean propose = nil;

	y2debug("extra: %1, missig: %2", extra, missing);

	list selected_list = [];
	foreach(string p, map in, selected, ``{
		// store package name and vesion to the list
		selected_list = add(selected_list, p + "-" + in["vers"]:"");
	    }
	);

	y2debug("selected_list: %1", selected_list);

	// propose restoration when:
	// - missing packages contain only fully backed packages
	// - all fully backed packages will be restored
	// - there is no extra package
	if (size(extra) == 0)
	{
	    boolean all = true;
	    y2debug("size(extra): %1", size(extra));

	    foreach(string fullpkg, complete_backup, ``{

//		    integer pos = findlastof(fullpkg, "-");
//		    string basename = substring(fullpkg, pos);
//		    string version = substring(fullpkg, 0, pos);

		    if (!contains(selected_list, fullpkg))
		    {
			all = false;
		    }
		}
	    );

	    y2debug("all completely packages selected: %1", all);

	    if (all == true)
	    {
		// check missing packages - it should contain only completely backed up packages
		foreach(string pkg, map inf, missing, ``{
			string fpkg = pkg + "-" + inf["vers"]:"";

			if (!contains(complete_backup, fpkg))
			{
			    all = false;
			}
		    }
		);

		if (all == true)
		{
		    y2debug("only fully backed up packages are missing");
		    // RPM DB restoration is recommended
		    return $["proposed" : true];
		}
	    }
	}

	boolean ok = true;
	// propose no restoration if only installed packages will be restored
	foreach(string pk, map inf, selected, ``{
		string version = inf["vers"]:"";

		// selected packages are not missing

		if (ok && (missing[pk]:nil != nil || mismatched[pk]:nil != nil))
		{
		    ok = false;
		}
	    }
	);

	if (ok == true)
	{
	    return $["proposed" : false];
	}

	// there will be inconsistency between RPM DB and system,
	// some packages will be in DB but no files will be present
	// or there will be files without RPM entry or both
	// it is also possible that package versions do not match

	map<string, string> different_versions = $[];	// package in system => package in RPM in the archive
	list not_in_system = [];	// package is in DB in the archive, but not in the system and it won't be restored
	list not_in_archiveDB = [];	// not in the archive, but in the system

	foreach(string p, map<string,string> inf, mismatched, ``{
		if (selected[p]:nil != nil)
		{
//                    different_versions[p + "-" + selected["vers"]:""] = p + "-" + inf["inst"]:"";
                    different_versions[p] = p + "-" + inf["inst"]:"";
                }
	    }
	);

	foreach(string p, map inf, missing, ``{
		not_in_system = add(not_in_system, p + "-" + inf["vers"]:"");
	    }
	);

	foreach(string p, map inf, extra, ``{
		not_in_archiveDB = add(not_in_archiveDB, p + "-" + inf["vers"]:"");
	    }
	);

	// both possible operations (restoration or preserving RPM DB)
	// will cause to inconsistency in system
	return $["proposed" : nil, "mismatched" : different_versions, "missing" : missing, "extra" : extra];
    }


    global define boolean RPMrestorable() ``{
	list nopkgfiles = archive_info["", "files"]:[];
	return contains(nopkgfiles, "/var/lib/rpm/Packages");
    }

}

