/**
 *  File:
 *    Backup.ycp
 *
 *  Module:
 *    Backup module
 *
 *  Authors:
 *    Ladislav Slezak <lslezak@suse.cz>
 *
 *  $Id: Backup.ycp 22195 2005-03-03 11:21:39Z locilka $
 *
 *  Main file for backup module
 *
 */


{

module	"Backup";

import "Progress";
import "Report";
import "Nfs";
import "Popup";

include "backup/functions.ycp";
include "hwinfo/classnames.ycp";

textdomain "backup";

// maximum cron file index
integer max_cron_index = 0;

global string script_store_ext2_area = "/sbin/e2image";
global string script_get_partition_table = "/sbin/fdisk -l";
global string script_get_files = "/usr/lib/YaST2/bin/backup_search.pl";
global string script_create_archive = "/usr/lib/YaST2/bin/backup_archive.pl";

// day names, key is integer used in crontab
global map<integer,string> daynames = $[
    1 : _("Monday"),
    2 : _("Tuesday"),
    3 : _("Wednesday"),
    4 : _("Thursday"),
    5 : _("Friday"),
    6 : _("Saturday"),
    7 : _("Sunday")
];

global map<integer,string> ordinal_numbers = $[
     1 :  _("1st"),
     2 :  _("2nd"),
     3 :  _("3rd"),
     4 :  _("4th"),
     5 :  _("5th"),
     6 :  _("6th"),
     7 :  _("7th"),
     8 :  _("8th"),
     9 :  _("9th"),
    10 : _("10th"),
    11 : _("11th"),
    12 : _("12th"),
    13 : _("13th"),
    14 : _("14th"),
    15 : _("15th"),
    16 : _("16th"),
    17 : _("17th"),
    18 : _("18th"),
    19 : _("19th"),
    20 : _("20th"),
    21 : _("21st"),
    22 : _("22nd"),
    23 : _("23rd"),
    24 : _("24th"),
    25 : _("25th"),
    26 : _("26th"),
    27 : _("27th"),
    28 : _("28th"),
    29 : _("29th"),
    30 : _("30th"),
    31 : _("31st")
];

// global settings

global map<string, map> backup_profiles = $[] ;			// map of all available profiles

// global defaults
global string default_archive_name = "";		// archive file name
global string default_description = "";			// user comment
global symbol default_archive_type = `tgz;		// archive type

global boolean default_multi_volume = false;
global symbol default_volume_size = `fd144;
global string default_user_volume_size = "";
global symbol default_user_volume_unit = nil;

global boolean default_search = true;			// search files which do not belong to any package
global boolean default_system = false;			// backup system areas
global boolean default_display = false;			// display files before creating archive
global boolean default_do_md5_test = true;
global boolean default_perms = true;			// store RPM file if owner/permissions were changed

global list<string> default_default_dir = [ "/media", "/tmp", "/var/lock", "/var/run", "/var/tmp", "/var/cache", "/sys", "/windows" ];		// default excluded directoried from search
global list<string> default_dir_list = default_default_dir;			// selected directoried to exclude
global list<string> default_regexp_list = [];

// iso9660 is used on CDROM, ntfs read-only
global list<string> default_fs_exclude = [ "iso9660", "ntfs" ];		// selected filesystems to exclude from search
global list<string> default_detected_fs = nil;			// detected filesystems

global list< map<string,any> > default_detected_ext2 = nil;		// detected mounted ext2 filesystems
global list<term> default_ext2_backup = [];			// selected ext2 filesystems to backup

global boolean default_backup_pt = true;		// backup partition table

global boolean default_backup_all_ext2 = false;		// backup all mounted ext2 partitions
global boolean default_backup_none_ext2 = true;		// backup none ext2 partitions
global boolean default_backup_selected_ext2 = false;	// backup selected ext2 partitions

global string default_tmp_dir = "/tmp";

//global list default_all_entered_dirs = [];

global map<string,map> default_backup_files = $[ ];		// all found files to backup
global map<string,map> default_selected_files = nil;	// selected files to backup
global list<string> default_unselected_files = [ ];	// files, which user explicitly unselected
//global list default_selected_directories = [];	// default directories to backup

//global boolean default_LVMsnapshot = true;
//global boolean default_testonly = false;
global boolean default_autoprofile = true;
//global boolean default_systembackup = true;
global symbol default_target_type = `file;
//global string default_target_device = nil;
//global map default_target_devices_options = $[];
global string default_temporary_dir = "/var/lib/YaST2/backup/tmp";

global string default_nfsserver = "";
global string default_nfsexport = "";

global boolean default_mail_summary = true;

// global variables initialized to default values:
global string archive_name = default_archive_name;		// archive file name
global string description = default_description;		// user comment
global symbol archive_type = default_archive_type;		// archive type

global boolean profile_is_new_one = false;	// newly created archive

global boolean multi_volume = default_multi_volume;
global symbol volume_size = default_volume_size;
global string user_volume_size = default_user_volume_size;
global symbol user_volume_unit = default_user_volume_unit;

global integer user_vol_size = 0;
global string temporary_dir = default_temporary_dir;
global boolean mail_summary = default_mail_summary;

global boolean search = default_search;			// search files which do not belong to any package
global boolean system = default_system;			// backup system areas
global boolean display = default_display;		// display files before creating archive
global boolean do_md5_test = default_do_md5_test;
global boolean perms = default_perms;

global symbol target_type = default_target_type;
//global string target_device = default_target_device;
//global map target_devices_options = default_target_devices_options;

global list<string> default_dir = default_default_dir;		// default excluded directoried from search
global list<string> dir_list = default_dir_list;		// selected directoried to exclude

global list<string> regexp_list = default_regexp_list;

global list<string> fs_exclude = default_fs_exclude;		// selected filesystems to exclude from search
global list<string> detected_fs = default_detected_fs;		// detected filesystems

global list< map<string,any> > detected_ext2 = default_detected_ext2;	// detected mounted ext2 filesystems
global list<term> ext2_backup = default_ext2_backup;		// selected ext2 filesystems to backup

global boolean backup_pt = default_backup_pt;		// backup partition table

global boolean backup_all_ext2 = default_backup_all_ext2;		// backup all mounted ext2 partitions
global boolean backup_none_ext2 = default_backup_none_ext2;		// backup none ext2 partitions
global boolean backup_selected_ext2 = default_backup_selected_ext2;	// backup selected ext2 partitions

global string tmp_dir = default_tmp_dir;

global boolean cron_mode = false;
global string cron_profile = "";

//global boolean LVMsnapshot = default_LVMsnapshot;
//global boolean testonly = default_testonly;
global boolean autoprofile = default_autoprofile;
//global boolean systembackup = default_systembackup;

global string nfsserver = default_nfsserver;
global string nfsexport = default_nfsexport;
global string nfsmount = nil;	// NFS mount point, remember for unmounting

global map<string,map> backup_files = (map<string,map>)eval( default_backup_files );		// all found files to backup
global map<string,map> selected_files = (map<string,map>)eval( default_selected_files );	// selected files to backup
global list<string> unselected_files = default_unselected_files;	// files, which user explicitly unselected

//global list selected_directories = default_selected_directories;
//global list all_entered_dirs = default_all_entered_dirs;

global boolean no_interactive = false; 	// whether the user should setup configuration manually
global string selected_profile = nil;   // name of the selected profile, nil for no selected profile (default settings)

// default volume size if it wasn't detected
global integer undetected_volume_size = 1024*1024*1024;

global list<string> installable_packages = [];
global list<string> complete_backup = [];

// list of files to be deleted finishing the backup editation
global list<string> remove_cron_files = [];

// result of removing old archives
global map remove_result = $[];

// cached detected mount points
map detected_mpoints = nil;
// end of global settings

// media description - capacity is maximum file size which fits
// to formatted medium using widely used file system (FAT on floppies)

global list< map<string,any> > cd_media_descriptions = [
    $[
	"label" : _("CD-R/RW 650 MB (74 min.)"),
	"symbol" : `cd650,
	"capacity" : 649*1024*1024	// exact size is 650.4 MB - remaining space is for ISO fs
    ],
    $[
	"label" : _("CD-R/RW 700 MB (80 min.)"),
	"symbol" : `cd700,
	"capacity" : 702*1024*1024	// exact size is 703.1 MB - remaining space is for ISO fs
    ]
];

global list< map<string,any> > floppy_media_descriptions = [
    $[
	"label" : _("Floppy 1.44 MB"),
	"symbol" : `fd144,
	"capacity" : 1423*1024		// 1457664B is exact size for FAT fs
    ],
    $[
	"label" : _("Floppy 1.2 MB"),
	"symbol" : `fd12,
	"capacity" : 1185*1024 // 1213952B is exact size for FAT fs
    ]
];

global list< map<string,any> > zip_media_descriptions = [
    $[
	"label" : _("ZIP 100 MB"),
	"symbol" : `zip100,
	"capacity" : 95*1024*1024	// exact size is 96MiB (64 heads, 32 sectors, 96 cylinders, 512B sector)
    ]
/*    $[
	"label" : _("ZIP 250 MB"),
	"symbol" : `zip250,
	"capacity" : ?????
    ],*/
];

global list< map<string,any> > misc_descriptions = [
/*    $[
	"label" : _("Default Volume Size"),
	"symbol" : `default_size,
	"capacity" : 1024*1024*1024
    ]*/
];

global list< map<string,any> > media_descriptions = (list< map<string,any> >) merge(merge(merge(cd_media_descriptions, floppy_media_descriptions), zip_media_descriptions), misc_descriptions);

global list< map<string,any> > units_description = [
    $[
	"label" : _("bytes"),
	"capacity" : 1,
	"symbol"   : `B
    ],
    $[
	// 10^3 bytes
	"label" : _("kB"),
	"capacity" : 1000,
	"symbol"   : `kB
    ],
    $[
	// 2^10 bytes
	"label" : _("kiB"),
	"capacity" : 1024,
	"symbol"   : `kiB
    ],
    $[
	// 10^6 bytes
	"label" : _("MB"),
	"capacity" : 1000000,
	"symbol"   : `MB
    ],
    $[
	// 2^20 bytes
	"label" : _("MiB"),
	"capacity" : 1024*1024,
	"symbol"   : `MiB
    ],
];


/**
 * Return capacity of required medium
 * @param media Medium descriptions
 * @param m Identification of required medium
 * @return integer Size of medium in bytes
 */

global define integer GetCapacity(list<map<string,any> > media, symbol m) ``{
    integer result = nil;

    if (media != nil)
    {
	foreach(map<string,any> val, media,
	    ``{
		if (((symbol)(val["symbol"]:nil)) == m)
		{
		    result = (integer)(val["capacity"]:nil);
		}
	    }
	);
    }

    return result;
}


/**
 * Return backup_search.pl script parameters according to state of variables
 * @return string String with command line parameters
 */

global define string get_search_script_parameters() ``{
    string script_options = " --start-dir / --output-progress";       // required parameter for YaST2 frontend

    if (search)
    {
	script_options = script_options + " --search";
    }

	y2milestone("Directories to exclude: %1", dir_list);

	if (size(dir_list) > 0)
	{
	    foreach(string d, dir_list,
		``{
		    if (d != nil)
		    {
			script_options = script_options + " --exclude-dir " + d;
		    }
		}
	    );
	}

	y2milestone("Files to exclude: %1", regexp_list);

	if (size(regexp_list) > 0)
	{
	    foreach(string r, regexp_list,
		``{
		    if (r != nil)
		    {
			// TODO: single quotes in tmp_d need to handled properly
			script_options = script_options + " --exclude-files '" + r + "'";
		    }
		}
	    );
	}

	y2milestone("Filesystems to exclude: %1", fs_exclude);

	if (size(fs_exclude) > 0)
	{
	    foreach(string i, fs_exclude, ``{script_options = script_options + " --exclude-fs " + i;});
	}

    // save list of installable packages and pass it to the search script
    if (size(installable_packages) > 0)
    {
	string content = mergestring(installable_packages, "\n");
	string listfile = (string)SCR::Read(.target.tmpdir) + "/packagelist";

	SCR::Write(.target.string, listfile, content);

	script_options = script_options + " --inst-src-packages " + listfile;
    }

    if (!do_md5_test)
    {
	script_options = script_options + " --no-md5";
    }

    // add widget file option
    script_options = script_options + " --widget-file " + ((string)SCR::Read(.target.tmpdir)) + "/items.ycp";

    // add package verification option
    script_options = script_options + " --pkg-verification";

    y2milestone("Search script options: %1", script_options);

    return script_options;
}

/**
 * Pre-backup function - mount NFS share if required
 * @return boolean true on success
 */
global define boolean PrepareBackup() ``{
    if (target_type == `nfs && nfsmount == nil)
    {
	nfsmount = Nfs::Mount(nfsserver, nfsexport, nil, "");
	return nfsmount != nil;
    }

    return true;
}

/**
 * Post-backup function - unmount mounted NFS share
 * @return boolean true on success
 */
global define boolean PostBackup() ``{
    if (target_type == `nfs && nfsmount != nil)
    {
	boolean ret = Nfs::Unmount(nfsmount);
	nfsmount = nil;
	return ret;
    }

    return true;
}

/**
 * Return backup_search.pl script parameters according to state of variables
 * @param file_list Where is list of files to backup stored
 * @param file_comment Where is comment stored
 * @return string String with command line parameters
 */

global define string get_archive_script_parameters(string file_list, string file_comment) ``{
    string archive_options = " --verbose --files-info " + file_list + " --comment-file " + file_comment;

    if (size(complete_backup) > 0)
    {
	// store list of completely backed up files into a file
	string complete_string = mergestring(complete_backup, "\n");
	string tmpdir = (string)SCR::Read(.target.tmpdir);

	SCR::Write(.target.string, tmpdir + "/complete_backup", complete_string);
	archive_options = archive_options + " --complete-backup " + tmpdir + "/complete_backup";
    }
    else
    {
	y2debug("complete_backup is empty");
    }

    y2debug("nfsmount: %1, archive_name: %2", nfsmount, archive_name);

    archive_options = archive_options + " --archive-name " + ((target_type == `file) ? archive_name :
	sformat("%1/%2", nfsmount, archive_name));

    if (system)
    {
        // add partition tabel option
	if (backup_pt)
	{
	    archive_options = archive_options + " --store-ptable";
	}

	list<string> tmp_selected_pt = [];
	foreach(term sel_tmp_pt, ext2_backup,	// get device names from `item(`id(XYZ), "XYZ")
	    ``{
		string tmp = (string)(sel_tmp_pt[1]:nil);

		if (tmp != nil)
		{
		    tmp_selected_pt = add(tmp_selected_pt, tmp);
		}
	    }
	);

	list<string> detected_ext2_strings = [];

	foreach(map<string, any> info, detected_ext2,
	    ``{
		string part = (string)(info["partition"]:nil);

		if (part != nil)
		{
		    detected_ext2_strings = add(detected_ext2_strings, part);
		}
	    }
	);

	list<string> partitions = (backup_all_ext2) ? detected_ext2_strings : (backup_none_ext2 ? [] : tmp_selected_pt);

	y2milestone("Backup Ext2 partitions: %1", partitions);

	foreach(string spt, partitions, ``{archive_options = archive_options + " --store-ext2 " + spt;});
    }


    map typemap = $[
	`tgz : "tgz",
	`tbz : "tbz2",
	`tar : "tar",
	`stgz : "stgz",
	`stbz : "stbz2",
	`star : "star",
	`txt : "txt"
    ];

    archive_options = archive_options + " --archive-type " + typemap[archive_type]:"tgz";


    if (multi_volume)
    {
	if (volume_size == `user_defined)
	{
	    // compute volume size (in kiB)
	    integer vol_size = tointeger( ((tofloat(user_volume_size)) * (tofloat(GetCapacity(units_description, user_volume_unit)))) / 1024.0 );

	    y2debug("Volume size is %1 kiB", vol_size);

	    if (vol_size > 0)
	    {
		archive_options = archive_options + " --multi-volume " + sformat("%1", vol_size);
	    }
	    else
	    {
		y2warning("Bad volume size: %1", user_volume_size);
	    }
	}
	else
	{
	    archive_options = archive_options + " --multi-volume " + tointeger(tofloat(GetCapacity(media_descriptions, volume_size)) / 1024.0);
	}
    }

    if (size(tmp_dir) > 0)
    {
	archive_options = archive_options + " --tmp-dir " + tmp_dir;
    }

    y2milestone("Archive script options: %1", archive_options);

    return archive_options;
}


/**
 * Exclude file systems without device
 */

global define void ExcludeNodevFS() ``{
    map<string,string> filesystems = (map<string,string>)SCR::Read(.proc.filesystems);

    if (filesystems == nil)
    {
	return;
    }

    foreach(string k, string v, filesystems, ``{
	    if (v == "nodev")
	    {
		fs_exclude = add(fs_exclude, k);
	    }
	}
    );

    fs_exclude = toset(fs_exclude);

    y2milestone("Detected nodev filesystems: %1", fs_exclude);
}


/**
 * Write autoinstallation profile to file autoinst.xml to the same directory as archive
 * @param volumes list of created archives (it is written to the XML profile as restoration source)
 * @return map map $[ "result" : boolean (true on success), "profile" : string (profile file name) ]
 */

global define map WriteProfile(list<string> volumes) ``{
    string archive = ((target_type == `nfs && nfsmount != nil) ? nfsmount + "/" : "") + archive_name;
    integer pos = findlastof(archive, "/");
    string dir = "";
    string file = archive;

    if (pos != nil && pos > 0)
    {
	dir = substring(archive, 0, pos) + "/";
	file = substring(archive, pos + 1);
    }

    string directory = dir;

    y2debug("dir: %1, file: %2", dir, file);

    string prefix = "file://";

    // change prefix according to volume size or archive destination
    // check if file is written to NFS file system
    list<map> fs = (list<map>)SCR::Read(.proc.mounts);

    fs = filter(map info, fs, ``{return ((string)(info["vfstype"]:"")) == "nfs";});

    foreach(map info, fs, ``{
	    string mountpoint = info["file"]:"";
	    string spec = info["spec"]:"";

	    string server = substring(spec, 0, findfirstof(spec, ":"));
	    string remdir = substring(spec, findfirstof(spec, ":") + 1);

	    if (mountpoint != "" && spec != "")
	    {
		if (substring(archive, 0, size(mountpoint)) == mountpoint)
		{
		    y2milestone("NFS server: %1, directory: %2", server, remdir);

		    prefix = "nfs://";
		    dir = server + ":" + remdir + "/";
		}
	    }
	}
    );

    // set prefix according to volume size
    if (prefix == "" && multi_volume == true)
    {
	if (volume_size == `fd144 || volume_size == `fd12)
	{
	    prefix = "fd://";
	    dir = "/";
	}
	else if (volume_size == `cd700 || volume_size == `cd650)
	{
	    prefix = "cd://";
	    dir = "/";
	}
    }

    y2debug("backup write profile: prefix=%1, dir=%2", prefix, dir);

    list volumestrings = [];

    if (size(volumes) > 0)
    {
	foreach(string volfile, volumes, ``{
		string f = volfile;
		integer pos = findlastof(volfile, "/");
		if (pos != nil && pos > 0)
		{
		    f = substring(volfile, pos + 1);
		}

		volumestrings = add(volumestrings, prefix + dir + f);
	    }
	);
    }
    else
    {
	volumestrings = [ prefix + dir + file ];
    }

    map restore = $[ "archives" : volumestrings ];

    // add default selection - select all packages to restore
    map packages_sel = $[];

    foreach(string pkg, map info, selected_files, ``{
	    // get package base name
	    if (pkg != "")
	    {
		pkg = regexpsub(pkg, "(.*)-.*-.*", "\\1");
	    }
	    else
	    {
		pkg = "_NoPackage_";
	    }

	    packages_sel[pkg] = $["sel_type" : "X"];
	}
    );

    if (directory == "")
    {
	directory = "/";
    }

    // store profile to this file
    string profilefile = directory + GetBaseName(archive_name) + ".xml";

    y2debug("Profile location: %1", profilefile);

    // create and save autoinstallation profile
    boolean res = CloneSystem(profilefile, ["lan"], "restore", restore);
    y2milestone("Clone result: %1", res);

    if (target_type == `nfs)
    {
	pos = findlastof(archive_name, "/");
	string nm = (pos != nil && pos > 0) ? substring(archive_name, 0, pos) : "";

	y2debug("pos: %1, nm: %2", pos, nm);

	// update XML location if it was stored on NFS
	profilefile = nfsserver + ":" + nfsexport + "/" + nm + (size(nm) > 0 ? "/" : "") + GetBaseName(archive_name) + ".xml";
	y2debug("Updated profile location: %1", profilefile);
    }

    return $[ "result" : res, "profile" : profilefile ];
}


/**
 * Parse cron file
 * @param filename File to parse
 * @return map parsed values: $["auto":boolean, "day":integer, "hour":integer,
 *         "minute":integer, "weekday":integer, "every":symbol] or empty map if
 *         parse error occured
 */

global define map ReadCronSetting(string filename) ``{
    map ret = $[];

    if (filename == nil || filename == "")
    {
	return ret;
    }

    string filecontent = (string)SCR::Read(.target.string, filename);
    list<string> lines = splitstring(filecontent, "\n");

    // filter out comments
    lines = filter(string l, lines, ``(!regexpmatch(l, "^[ \t]*#")));

    string line = lines[0]:"";

    if (line == nil || line == "")
    {
	return ret;
    }

    string regex = "^([^ \t]*)[ \t]*([^ \t]*)[ \t]([^ \t]*)[ \t]([^ \t]*)[ \t]([^ \t]*)[ \t]*[^ \t]*[ \t]*/usr/lib/YaST2/bin/backup_cron[ \t]*\"*[ \t]*profile[ \t]*=[ \t]*([^\"]*)\"*";
    symbol every = `none;
    map cronsettings = $[];
    string profilename = "";

    // is cron setting supported (ranges, lists and steps are NOT supported)
    boolean unknown_settings = false;
    boolean bad_settings = false;

    if (regexpmatch(line, regex))
    {
	string minute_str = regexpsub(line, regex, "\\1");
	string hour_str = regexpsub(line, regex, "\\2");

	y2milestone("minute_str: %1, hour_str: %2", minute_str, hour_str);

	if (!regexpmatch(minute_str, "^[0-9]*$") || !regexpmatch(hour_str, "^[0-9]*$"))
	{
	    unknown_settings = true;
	}

	y2milestone("unknown_settings: %1", unknown_settings);
	integer minute = tointeger(minute_str);
	integer hour = tointeger(hour_str);

	if (hour > 23 || hour < 0 || minute > 59 || minute < 0)
	{
	    bad_settings = true;
	}

	string day = regexpsub(line, regex, "\\3");
	string month = regexpsub(line, regex, "\\4");
	string weekday = regexpsub(line, regex, "\\5");

	y2milestone("line: %1", line);
	y2milestone("day: %1", day);

	integer intday = 1;
	integer intweekday = 0;

	profilename = regexpsub(line, regex, "\\6");
	y2milestone("profilename: %1", profilename);

	if (month != "*")
	{
	    // error
	    unknown_settings = true;
	}

	if (day == "*" && weekday == "*")
	{
	    // start every day
	    every = `day;
	}
	else if (day == "*")
	{
	    every = `week;

	    if (!regexpmatch(weekday, "^[0-9]*$"))
	    {
		unknown_settings = true;
	    }

	    intweekday = tointeger(weekday);

	    if (intweekday > 7 || intweekday < 0)
	    {
		bad_settings = true;
	    }
	}
	else if (weekday == "*")
	{
	    every = `month;

	    if (!regexpmatch(day, "^[0-9]*$"))
	    {
		unknown_settings = true;
	    }

	    intday = tointeger(day);

	    if (intday > 31 || intday < 1)
	    {
		bad_settings = true;
	    }
	}
	else
	{
	    unknown_settings = true;
	}

	cronsettings = $["auto":true, "day":intday, "hour":hour, "minute":minute, "weekday":intweekday, "every":every];
	y2milestone("cronsettings: %1", cronsettings);
    }
    else
    {
	unknown_settings = true;
    }

    if (unknown_settings == true)
    {
	Report::Warning(sformat(_("Cron settings for profile '%1'
in file '%2'
are not fully supported.
"), profilename, filename));
    }

    if (bad_settings == true)
    {
	Report::Error(sformat(_("Some time values for profile '%1'\nin file '%2'\nare out of range."), profilename, filename));
    }

    return (every != `none) ? $["profilename":profilename, "cronsettings": cronsettings] : $[];
}


/**
 * Parse all /etc/cron.d/yast2-backup-* files and update profiles
 */

global define void ReadCronSettings() ``{
    string crondir = "/etc/cron.d";
    list<string> files = (list<string>)SCR::Read(.target.dir, crondir);

    // reset cron setings
    foreach(string name, map opts, backup_profiles, ``{

	    map tmp = (map)eval(opts);
	    map cr = (map)eval(opts[`cron_settings]:$[]);

	    cr["cronfile"] = "";
	    cr["cron_changed"] = false;

	    tmp[`cron_settings] = (map)eval(cr);

	    backup_profiles[name] = (map)eval(tmp);
	}
    );

    if (files != nil && size(files) > 0)
    {
	// parse all /etc/cron.d/yast2-backup-* files
	foreach(string file, files, ``{
		if (regexpmatch(file, "^yast2-backup-[0-9]*$") == true)
		{
		    integer cron_index = tointeger(regexpsub(file, "yast2-backup-([0-9]*)", "\\1"));

		    y2milestone("cron_index: %1", cron_index);
		    if (cron_index > max_cron_index)
		    {
			max_cron_index = cron_index;
		    }

		    // parse cron file
		    map cron = ReadCronSetting(crondir + "/" + file);
		    y2milestone("parsed cron config: %1", cron);

		    if (cron != $[] && cron != nil)
		    {
			string profilename = cron["profilename"]:"";
			map cronsettings = (map)eval(cron["cronsettings"]:$[]);

			// update profile
			if (profilename != "" && cronsettings != $[])
			{
			    map profile = (map)eval(backup_profiles[profilename]:$[]);

			    cronsettings["cronfile"] = crondir + "/" + file;

			    // merge maps - include old backup settings from read profile
			    cronsettings = union((map)eval(profile[`cron_settings]:$[]), cronsettings);

			    profile[`cron_settings] = (map)eval(cronsettings);
			    backup_profiles[profilename] = (map)eval(profile);
			}
		    }
		}
	    }
	);
    }

    y2milestone("max_cron_index: %1", max_cron_index);
}


/**
 * Read backup profiles from file, do not set any global settings, just
 * @see backup_profiles. The profiles are stored in hardcoded place (/var/adm/YaST/backup/profiles).
 * @return boolean true if there are some profiles available
 */

global define boolean ReadBackupProfiles() ``{
    backup_profiles = (map<string, map>) SCR::Read( .target.ycp, "/var/adm/YaST/backup/profiles" );

    // if the list is empty or the file does not exists, set empty map
    if( backup_profiles == nil ) backup_profiles = $[];

    foreach(string profname, map opts, backup_profiles, ``{
	    y2debug("Read profile %1: %2", profname, opts);
	    if (opts[`cron_settings, "auto"]:false == true)
	    {
		y2debug("Deactivating profile %1", profname);
		opts[`cron_settings, "auto"] = false;
		backup_profiles[profname] = eval(opts);
	    }
	}
    );

    // add cron settings
    ReadCronSettings();

    return ( backup_profiles != $[] ) ;
}

/**
 * Create cron file content for selected profile.
 * @param profilename Name of the profile
 * @return string Cron content or empty string if profile has
 *         disabled automatic start
 */

global define string CreateCronSetting(string profilename) ``{
    map input = backup_profiles[profilename, `cron_settings]:$[];
    string ret = "";

    // return empty string if cron setting was not changed
    if (input == nil || input == $[] || input["cron_changed"]:false == false)
    {
	return ret;
    }

    if (input["auto"]:false == true)
    {
	integer hour = input["hour"]:0;
	integer minute = input["minute"]:0;
	integer day = input["day"]:1;
	integer weekday = input["weekday"]:0;
	symbol every = input["every"]:`unknown;

	if (every == `day)
	{
	    ret = sformat("%1 %2 * * *  root  /usr/lib/YaST2/bin/backup_cron \"profile=%3\"\n", minute, hour, profilename);
	}
	else if (every == `week)
	{
	    ret = sformat("%1 %2 * * %3  root  /usr/lib/YaST2/bin/backup_cron \"profile=%4\"\n", minute, hour, weekday, profilename);
	}
	else if (every == `month)
	{
	    ret = sformat("%1 %2 %3 * *  root  /usr/lib/YaST2/bin/backup_cron \"profile=%4\"\n", minute, hour, day, profilename);
	}

	// add comment to the first line
	ret = "# Please do not edit this file manually, use YaST2 backup module instead\n" + ret;
    }

    return ret;
}


/**
 * Write cron settings from profiles to /etc/cron.d/yast2-backup-* files
 */

global define void WriteCronSettings() ``{
    y2milestone("backup_profiles: %1", backup_profiles);

    // write cron files
    foreach(string name, map opts, backup_profiles, ``{
	    // cron file content
	    string setting = CreateCronSetting(name);
	    string cron_file = opts[`cron_settings, "cronfile"]:"";

	    y2milestone("name: %1", name);
	    y2milestone("setting: %1", setting);
	    y2milestone("cron_settings: %1", opts[`cron_settings]:$[]);

	    if (setting != "" && setting != nil)
	    {
		// is already cron file existing?
		if (size(cron_file) == 0)
		{
		    // no, create new file
		    max_cron_index = max_cron_index + 1;
		    cron_file = sformat("/etc/cron.d/yast2-backup-%1", max_cron_index);

		    // remember new cron file name
		    backup_profiles[name, `cron_settings, "cronfile"] = cron_file;
		}

		SCR::Write(.target.string, cron_file, setting);
		y2milestone("Created file: %1", cron_file);
	    }
	    else if (size(cron_file) > 0 && opts[`cron_settings, "auto"]:false == false)
	    {
		// remove existing cron file
		SCR::Execute(.target.bash, "/bin/rm -f " + cron_file);
		y2milestone("removed old cron file: %1", cron_file);
	    }

	    // mark saved value as unchanged
	    map prof = (map)eval(backup_profiles[name]:$[]);
	    map cron_s = (map)eval(prof[`cron_settings]:$[]);

	    cron_s["cron_changed"] = false;
	    prof[`cron_settings] = (map)eval(cron_s);
	    backup_profiles[name] = (map)eval(prof);
	}
    );
}


/**
 * Write the backup profiles to a file - hardcoded /var/adm/YaST/backup/profiles.
 * @return boolean true if the write operation was successful.
 */

global define boolean WriteBackupProfiles() ``{
    // update cron setting
    WriteCronSettings();

    if( !SCR::Write (.target.ycp, "/var/adm/YaST/backup/profiles", backup_profiles ) )
    {
	y2error( "Unable to write profiles into a file" );
	Popup::Error( _("Could not store profiles to disk.
The profile changes will be lost.") );
	return false;
    }

    foreach (string filename, remove_cron_files, {
	if (filename != "") {
	    y2milestone("Removing file: '%1'", filename);
	    if ( ! (boolean) SCR::Execute (.target.remove, filename) ) {
		y2warning("Cannot remove cron file '%1'", filename);
	    }
	}
    });

    return true;
}


/**
 * Take the current profile information and store it into a given profile.
 * If the profile already exists, it will be overwritten.
 * @param profile_name name of a profile to be stored into
 */

global define void StoreSettingsToBackupProfile( string profile_name ) ``{

    map new_profile = $[
	`archive_name 		: archive_name,
	`description  		: description,
	`archive_type 		: archive_type,
	`multi_volume 		: multi_volume,
	`volume_size  		: volume_size,
	`user_volume_size 	: user_volume_size,
	`user_volume_unit 	: user_volume_unit,
	`search 		: search,
	`system 		: system,
	`display 		: display,
	`do_md5_test 		: do_md5_test,
	`default_dir		: default_dir,
	`dir_list		: dir_list,
	`fs_exclude		: fs_exclude,
	`regexp_list		: regexp_list,
	`detected_fs		: detected_fs,
	`detected_ext2		: detected_ext2,
	`ext2_backup		: ext2_backup,
	`backup_pt		: backup_pt,
	`backup_all_ext2	: backup_all_ext2,
	`backup_none_ext2	: backup_none_ext2,
	`backup_selected_ext2	: backup_selected_ext2,
	`unselected_files 	: unselected_files,
//	`all_entered_dirs	: all_entered_dirs,
//	`selected_directories	: selected_directories,
//	`LVMsnapshot		: LVMsnapshot,
//	`testonly		: testonly,
	`autoprofile		: autoprofile,
//	`systembackup		: systembackup,
	`perms			: perms,
	`nfsserver		: nfsserver,
	`nfsexport		: nfsexport,
	`mail_summary		: mail_summary,
	`tmp_dir		: tmp_dir,
	`target_type		: target_type
//	`target_device		: target_device,
//	`target_devices_options	: target_devices_options
    ] ;

    // add the new profile
    backup_profiles[profile_name] = new_profile;
}

/**
 * Restore the global settings from a given backup profile.
 * @param profile_name name of a profile to be used
 * @return If the name of the profile cannot be found, return false, otherwise return true.
 */
global define boolean RestoreSettingsFromBackupProfile( string profile_name ) ``{

    // return false, is there is no such profile
    if( !haskey( backup_profiles, profile_name ) ) return false;

    // get the profile data
    map profile = (map) (backup_profiles[profile_name]:nil);

    // editing archive instead of adding new one
    profile_is_new_one = false;

    // setup global settings according to profile
    // TODO: check, if all settings are valid
    archive_name =	profile[ `archive_name ]:	default_archive_name;
    description =	profile[ `description ]:	default_description;
    archive_type =	profile[ `archive_type ]:	default_archive_type;
    multi_volume =	profile[ `multi_volume ]:	default_multi_volume;
    volume_size =	profile[ `volume_size ]:	default_volume_size;
    user_volume_size =  profile[ `user_volume_size ]:	default_user_volume_size;
    user_volume_unit =	profile[ `user_volume_unit ]:	default_user_volume_unit;
    search =		profile[ `search ]:		default_search;
    system =		profile[ `system ]:		default_system;
    display =		profile[ `display ]:		default_display;
    do_md5_test =	profile[ `do_md5_test ]:	default_do_md5_test;
    default_dir =	profile[ `default_dir ]:	default_default_dir;

//    dir_list =		profile[ `dir_list ]:		default_dir_list;

    any read_dir_list = profile[ `dir_list ]:		default_dir_list;

    // convert list of items to list of strings
	if (is(read_dir_list, list<string>))
	{
	    dir_list = (list<string>) read_dir_list;
	}
	else if (is(read_dir_list, list<term>))
	{
	    // convert dir list from the old format
	    list<string> new_dir_list = [];

	    foreach(term i, (list<term>)read_dir_list,
		``{
		    term tmp_id = (term)i[0]:nil;

		    if (tmp_id != nil)
		    {
			string tmp_d = (string)tmp_id[0]:nil;

			if (tmp_d != nil)
			{
			    new_dir_list = add(new_dir_list, tmp_d);
			}
		    }
		}
	    );

	    dir_list = new_dir_list;
	}
	else
	{
	    y2warning("Excluded directories - unsupported data type, value is %1", read_dir_list);
	}

    fs_exclude =	profile[ `fs_exclude ]:		default_fs_exclude;
    detected_fs =	profile[ `detected_fs ]:	default_detected_fs;
    detected_ext2 =	profile[ `detected_ext2 ]:	default_detected_ext2;
    ext2_backup =	profile[ `ext2_backup ]:	default_ext2_backup;
    backup_pt =		profile[ `backup_pt ]:		default_backup_pt;
    backup_all_ext2 =	profile[ `backup_all_ext2 ]:	default_backup_all_ext2;
    backup_none_ext2 =	profile[ `backup_none_ext2 ]:	default_backup_none_ext2;
    backup_selected_ext2 =	profile[ `backup_selected_ext2 ]:	default_backup_selected_ext2;
    unselected_files =	profile[ `unselected_files ]:	default_unselected_files;
//    all_entered_dirs =	profile[ `all_entered_dirs ]:	default_all_entered_dirs;
//    selected_directories = profile[ `selected_directories ]:	default_selected_directories;
//    LVMsnapshot =	profile[ `LVMsnapshot ]:	default_LVMsnapshot;
//    testonly =		profile[ `testonly ]:		default_testonly;
    autoprofile =	profile[ `autoprofile ]:	default_autoprofile;
//    systembackup =	profile[ `systembackup ]:	default_systembackup;
    perms =		profile[ `perms ]:		default_perms;
    nfsserver =		profile[ `nfsserver ]:		default_nfsserver;
    nfsexport =		profile[ `nfsexport ]:		default_nfsexport;
    target_type =	profile[ `target_type ]:	default_target_type;
//    target_device =	profile[ `target_device ]:	default_target_device;
//    target_devices_options = profile[ `target_devices_options ]:	default_target_devices_options;
    mail_summary =	profile[ `mail_summary ]:	default_mail_summary;
    tmp_dir =		profile[ `tmp_dir ]:		default_tmp_dir;
    regexp_list =	profile[ `regexp_list ]:	default_regexp_list;

    selected_files = default_selected_files;
    backup_files = default_backup_files;

    selected_profile = profile_name;

    return true;
}

/**
 * Restore the default global settings.
 */
global define void RestoreDefaultSettings() ``{

    // setup global settings according to defaults
    archive_name = default_archive_name;
    description = default_description;
    archive_type = default_archive_type;
    multi_volume = default_multi_volume;
    volume_size = default_volume_size;
    user_volume_size = default_user_volume_size;
    user_volume_unit = default_user_volume_unit;
    search = default_search;
    system = default_system;
    display = default_display;
    do_md5_test = default_do_md5_test;
    default_dir = default_default_dir;
    dir_list = default_dir_list;
    fs_exclude = default_fs_exclude;
    detected_fs = default_detected_fs;
    detected_ext2 = default_detected_ext2;
    ext2_backup = default_ext2_backup;
    backup_pt = default_backup_pt;
    backup_all_ext2 = default_backup_all_ext2;
    backup_none_ext2 = default_backup_none_ext2;
    backup_selected_ext2 = default_backup_selected_ext2;
    unselected_files = default_unselected_files;
//    all_entered_dirs =	eval( default_all_entered_dirs );
//    selected_directories = eval( default_selected_directories );
//    LVMsnapshot = default_LVMsnapshot;
//    testonly = default_testonly;
    autoprofile = default_autoprofile;
//    systembackup = default_systembackup;
    perms = default_perms;
    nfsserver = default_nfsserver;
    nfsexport = default_nfsexport;
    target_type = default_target_type;
//    target_devices_options = eval(default_target_devices_options);
    mail_summary = default_mail_summary;
    tmp_dir = default_tmp_dir;
    regexp_list = default_regexp_list;

    selected_files = (map<string,map>)eval(default_selected_files);
    backup_files = (map<string,map>)eval(default_backup_files);

    selected_profile = nil;
}

/**
 * Get a sorted list of profile names currently available.
 * @return the list of strings (possibly empty).
 */
global define list<string> BackupProfileNames() ``{
    list<string> result = (list<string>)maplist(string key, any value, backup_profiles, ``(key) );
    if( result == nil ) return [];
    else return sort( result );
}

/**
 * Create description of automatic backup.
 * @param profilename Name of the profile
 * @return string description string or empty string if profile has
 *         disabled automatic start
 */

global define string CreateCronDescription(string profilename) ``{
    map input = backup_profiles[profilename, `cron_settings]:$[];
    string ret = "";

    if (input == nil || input == $[])
    {
	return ret;
    }

    if (input["auto"]:false == true)
    {
	integer hour = input["hour"]:0;
	integer minute = input["minute"]:0;
	integer day = input["day"]:1;
	integer weekday = input["weekday"]:0;
	symbol every = input["every"]:`unknown;

	// hour/minutes time format - set according your local used format
	// usually used conversion specificators:
	// %H - hour (0..23), %I - hour (0..12)
	// %M - minute (0..59), %p - `AM' or `PM'
	// (see man date for more details)
	string timeformat = _("%I:%M %p");

	string bashcommand = sformat("/bin/date --date '%1:%2' '+%3'", hour, minute, timeformat);
	// convert hour and minutes to localized time string - use date utility
	map result = (map)SCR::Execute(.target.bash_output, bashcommand);
	string ltime  = mergestring(splitstring((string)(result["stdout"]:""), "\n"), "");

	if (ltime == "")
	{
	    // table item - specified time is invalid
	    ret = _("-- Invalid time --");
	}
	else if (every == `day)
	{
	    // table item - start backup every day (%1 is time)
	    ret = sformat(_("Backup Daily (at %1)"), ltime);
	}
	else if (every == `week)
	{
	    // table item - start backup every week (%1 is day name, %2 is time)
	    ret = sformat(_("Backup Weekly (%1 at %2)"), daynames[weekday]:"?", ltime);
	}
	else if (every == `month)
	{
	    // table item - start backup once a month (%1 is day (ordinal number, e.g. 5th), %2 is time)
	    ret = sformat(_("Backup Monthly (%1 day at %2)"), ordinal_numbers[day]:"?", ltime);
	}
    }

    return ret;
}

/**
 * Helper function to extract the list of currently available profiles
 * @return list List of item used in the table widget
 */
global define list<term> BackupProfileDescriptions() ``{
    list result = maplist( string key, map value, backup_profiles,
        ``{
	    // description can be multiline - merge lines
	    string descr = mergestring(splitstring(value[`description]:default_description, "\n"), " ");
	    map displayinfo = UI::GetDisplayInfo();
	    // limit size of description shown in the table
	    // maximum length half of width in ncurses UI
	    integer maxsize = (displayinfo["TextMode"]:false) ? (displayinfo["Width"]:80 / 2) : 40;

	    if (size(descr) > maxsize)
	    {
		// use only the beginning of the description, add dots
		descr = substring(descr, 0, maxsize) + "...";
	    }

	    return `item( `id( key ), key, descr, CreateCronDescription(key) );
	}
    );

    if (result == nil) return [];
    else return result;
}

/**
 * Remove given profile.
 * @param profile_name name of a profile to be removed
 * @param boolean remove_cronfile defines whether also the cron settings (stored in file) should be removed
 * @return If the name of the profile cannot be found, return false, otherwise return true.
 */
global define boolean RemoveBackupProfile( string profile_name, boolean remove_cronfile ) ``{
    // return false, is there is no such profile
    if( !haskey( backup_profiles, profile_name ) ) return false;

    // If there is some cronfile assigned to the profile, remove it too
    if (remove_cronfile && backup_profiles[profile_name,`cron_settings,"cronfile"]:nil != nil) {
	string filename = backup_profiles[profile_name,`cron_settings,"cronfile"]:"";

	y2milestone("File '%1' has been marked to be removed", filename);
	remove_cron_files = add(remove_cron_files, filename);
    }
    backup_profiles = (map<string, map>) remove( backup_profiles, profile_name );

    return true;
}



/**
 * Try to detect all removable devices present in the system
 * @param only_writable return only writable devices (e.g. exclude CD-ROMs)
 * @return map Removable devices info
 */

global define map RemovableDevices(boolean only_writable) ``{
    map ret = $[];

    // detect SCSI, IDE and floppy devices
    list<map> devs = (list<map>)merge(merge((list<map>)SCR::Read(.probe.scsi), (list<map>)SCR::Read(.probe.ide)), (list<map>)SCR::Read(.probe.floppy));

    if (size(devs) > 0)
    {
	foreach(map dev, devs,
	    ``{
		if (dev["class_id"]:nil == 262 && dev["sub_class_id"]:0 != 0)	// Mass storage device, but not a disk
		{
		    string dev_name = dev["dev_name"]:"";
		    string model = dev["model"]:"";
		    string bus = dev["bus"]:"";
		    integer sub_class_id = dev["sub_class_id"]:128;		// default is "Storage device"
		    symbol type_symbol = `unknown;

		    // use non-rewinding tape device
		    if (size(dev_name) > 0 && sub_class_id == 1)		// check if device is tape
		    {
			list<string> parts = splitstring(dev_name, "/");

			// add 'n' to the device name if it is missing
			// e.g. /dev/st0 (rewinding) -> /dev/nst0 (non-rewinding)
			if (!regexpmatch(parts[size(parts) - 1]:"", "^n"))
			{
			    parts[size(parts) - 1] = "n" + parts[size(parts) - 1]:"";

			    dev_name = mergestring(parts, "/");

			    dev["dev_name"] = dev_name;
			}

			type_symbol = `tape;
		    }

		    // type of device (cdrom, disk, tape...) was not detected
		    string type = ClassNames[262, sub_class_id]:_("Unknown device type");

		    // remove read only devices if it was requested
		    // remove CD/DVD-ROM devices, other devices are considered as writable,
		    // it doesn't check if inserted medium is writable!

		    if (sub_class_id == 2 && only_writable)
		    {
			// CD-ROM sub class, only writable devices are requested
			// if CD device is not CD-R/RW or DVD-R/RW/RAM it is read only
			if (!(dev["cdr"]:false || dev["cdrw"]:false
			    || dev["dvdram"]:false || dev["dvdr"]:false))
			{
			    dev_name = "";
			}

			type_symbol = `cd;
		    }

		    // predefined media sizes for device - initialize to all types
		    list media = media_descriptions;
		    symbol preselected = nil;
		    integer user_size = 0;

		    if (dev["dvd"]:false)
		    {
			type = "DVD-ROM";
			type_symbol = `dvd;

			if (only_writable)
			{
			    dev_name = "";
			}
		    }
		    else if (dev["cdr"]:false || dev["cdrw"]:false)
		    {
			// CD-R or CD-RW writer device
			type = _("CD Writer");
			type_symbol = (dev["cdr"]:false) ? `cdr : `cdrw;
			media = cd_media_descriptions;
			preselected = `cd700;
		    }
		    else if (dev["dvdr"]:false)
		    {
			// DVD-R, DVD+R... writer device
			type = _("DVD Writer");
			type_symbol = `dvdr;
		    }
		    else if (dev["dvdram"]:false)
		    {
			type = "DVD-RAM";
			type_symbol = `dvdram;
		    }
		    else if (dev["zip"]:false && dev["sub_class_id"]:0 == 3)
		    {
			type = "ZIP";
			type_symbol = `zip;
			media = zip_media_descriptions;

			// get medium size
			map geometry = dev["resource", "disk_log_geo"]:$[];
			integer sz = geometry["cylinders"]:0 * geometry["heads"]:0 * geometry["sectors"]:0;
			integer sect_sz = (dev["size", "unit"]:"" == "sectors") ? (dev["size", "y"]:512) : 0;
			integer raw_size = sz * sect_sz;

			// preselect medium size
			if (raw_size == 96*64*32*512)
			{
			    // this is ZIP-100
			    preselected = `zip100;
			}
			else if (raw_size > 0)
			{
			    // unknown medium, use raw size minus 1MB for file system
			    preselected = `user;
			    user_size = raw_size - 1024*1024;
			}

		    }
		    // floppy
		    else if (dev["sub_class_id"]:0 == 3)
		    {
			type_symbol = `floppy;
			media = floppy_media_descriptions;
			list<map> sizes = dev["resource", "size"]:[];
			integer sect_sz = 0;

			foreach(map m, sizes,
			    ``{
				string unit = m["unit"]:"";

				if (unit == "sectors")
				{
				    sect_sz = m["x"]:0 * m["y"]:512;
				}
			    }
			);

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

			if (sect_sz > 0)
			{
			    if (sect_sz == 2880*512)
			    {
				// 1.44 floppy
				preselected = `fd144;
			    }
/*			    else if (sect_sz == 1186*512)
			    {
				// 1.2 floppy
				preselected = `fd12;
			    }*/
			}
		    }

		    // volume size was'nt detected, use default value
		    if (preselected == nil)
		    {
			preselected = `user;
			user_size = undetected_volume_size;
		    }

		    if (size(dev_name) > 0)
		    {
			ret = add(ret, dev_name, $[ "model" : model, "type" : type, "bus" : bus, "media" : media, "preselected" : preselected, "user_size" : user_size, "type_symbol" : type_symbol ]);
		    }
		}
	    }
	);
    }

    return ret;
}

/**
 * Read all packages available on the installation sources
 */
global define void ReadInstallablePackages() ``{
    installable_packages = GetInstallPackages();
    y2debug("installable_packages: %1", installable_packages);
}

/**
 * Returns detected mount points
 * @return map detected mount points
 */
global define map DetectedMountPoints() ``{
    // return cached value if available
    if (detected_mpoints == nil)
    {
	detected_mpoints = DetectMountpoints();
    }

    return detected_mpoints;
}

/**
 * Returns local archive name (required if NFS target is used)
 * @return string local archive name
 */
global define string GetLocalArchiveName() ``{
    string ret = archive_name;

    if (target_type == `nfs && nfsmount != nil)
    {
	ret = nfsmount + "/" + archive_name;
    }

    return ret;
}

/**
 * Returns string generated from selected files list. String is in format which
 * is accepted by backup_archive.pl script. Used global variables:
 * selected_files, backup_files.
 * @return map map with keys "filelist" (string - list of files), "sel_files"
 *	(integer - number of selected files), "sel_packages" (integer: number
 *	of selected packages).
 * @see <a href="../backup_specification.html">Backup module specification</a>
 */

global define map MapFilesToString() ``{
    string result = "";
    integer num_files = 0;
    integer num_pack = 0;

    if (Backup::selected_files == nil || Backup::backup_files == nil)
    {
	return $[];
    }

    foreach(string pkg, map info, Backup::selected_files,
        ``{
            if (pkg != "")
            {
		string tmp = "Package: " + pkg + "\nInstalled: "
		    + Backup::backup_files[pkg, "install_prefixes"]:"(none)" + "\n";

		list<string> chfiles = info["changed_files"]:[];
		result = result + tmp + mergestring(chfiles, "\n") + "\n";

		num_files = num_files + size(chfiles);
		num_pack = num_pack + 1;
            }
        }
    );

    list<string> nopkg = (list<string>) (Backup::selected_files["", "changed_files"]:[]);

    if (size(nopkg) > 0)
    {
	result = result + "Nopackage:\n" + mergestring(nopkg, "\n") + "\n";

	num_files = num_files + size(nopkg);
	num_pack = num_pack + 1;
    }

    return $[ "filelist" : result, "sel_files" : num_files, "sel_packages" : num_pack ];
}

/**
 * Remove and/or rename old existing single archives
 * @param name Archive name
 * @param max Maximum count of existing archives
 * @return map result
 */
global define map RemoveOldSingleArchives(string name, integer max) ``{
    list removed = [];
    map renamed = $[];

    if (name == "" || name == nil)
    {
	return $[];
    }

    // check whether archive already exists
    integer sz = (integer)SCR::Read(.target.size, name);

    if (sz < 0)
    {
	// file doesn't exist, success
	y2milestone("Archive doesn't exist");
	return $[];
    }

    // check wheter older archives exist
    list<string> parts = splitstring(name, "/");
    string fname = parts[size(parts) - 1]:"";
    string dir = mergestring(remove(parts, size(parts) - 1), "/");

    if (size(fname) == 0)
    {
	return $[];
    }

    string command = "/bin/ls -1 -t " + dir + "/*-" + fname + " 2> /dev/null";
    map result = (map)SCR::Execute(.target.bash_output, command);
    list<string> files = splitstring(result["stdout"]:"", "\n");

    list mv_dates = [];

    // filter files with date - use regexp
    files = filter(string file, files, ``(regexpmatch(file, "^" + dir + "/[0-9]{14}-" + fname + "$")));

    y2milestone("Old archives: %1", files);

    if (size(files) >= max && max >= 0)
    {
	// remove the old archives
	while(size(files) >= max)
	{
	    string oldarchive = files[size(files) - 1]:"__DUMMY__";

	    // remove old archive
	    command = "/bin/rm -f " + oldarchive;
	    y2milestone("Removing old archive: %1", oldarchive);
	    result = (map)SCR::Execute(.target.bash_output, command);

	    string removedoldarchive = oldarchive;

	    // update NFS archive name
	    if (Backup::target_type == `nfs)
	    {
		removedoldarchive = Backup::nfsserver + ":" + Backup::nfsexport + substring(oldarchive, size(Backup::nfsmount));
	    }

	    removed = add(removed, removedoldarchive);

	    // remove old XML profile
	    string oldXML = dir + "/" + GetBaseName(oldarchive) + ".xml";
	    command = "/bin/rm -f " + oldXML;
	    result = (map)SCR::Execute(.target.bash_output, command);

	    // update NFS archive name
	    if (Backup::target_type == `nfs)
	    {
		oldXML = Backup::nfsserver + ":" + Backup::nfsexport + substring(oldXML, size(Backup::nfsmount));
	    }

	    removed = add(removed, oldXML);

	    files = remove(files, size(files) - 1);
	}
    }

    map stat = (map)SCR::Read(.target.stat, name);
    integer ctime = stat["ctime"]:0;
    string ctime_str = SecondsToDateString(ctime);

    // rename existing archive
    command = "/bin/mv -f " + name + " " + dir + "/" + ctime_str + "-" + fname;
    result = (map)SCR::Execute(.target.bash_output, command);

    string old_name = name;
    string new_name = dir + "/" + ctime_str + "-" + fname;

    // update NFS archive name
    if (Backup::target_type == `nfs)
    {
	old_name = Backup::nfsserver + ":" + Backup::nfsexport + substring(name, size(Backup::nfsmount));
	new_name = Backup::nfsserver + ":" + Backup::nfsexport + substring(new_name, size(Backup::nfsmount));
	y2debug("NFS archive, old_name: %1, new_name: %2", old_name, new_name);
    }


//    renamed[name] = dir + "/" + ctime_str + "-" + fname;
    renamed[old_name] = new_name;

    // rename autoinstallation profile
    string oldXML = dir + "/" + GetBaseName(name) + ".xml";
    string newXML = dir + "/" + ctime_str + "-" + GetBaseName(fname) + ".xml";

    command = "/bin/mv -f " + oldXML + " " + newXML;
    result = (map)SCR::Execute(.target.bash_output, command);

    // update NFS archive name
    if (Backup::target_type == `nfs)
    {
	oldXML = Backup::nfsserver + ":" + Backup::nfsexport + substring(oldXML, size(Backup::nfsmount));
	newXML = Backup::nfsserver + ":" + Backup::nfsexport + substring(newXML, size(Backup::nfsmount));
	y2debug("NFS archive, oldXML: %1, newXML: %2", oldXML, newXML);
    }

    renamed[oldXML] = newXML;

    return $[ "removed" : removed, "renamed" : renamed ];
}


/**
 * Remove and/or rename old existing multivolume archives
 * @param name Archive name
 * @param max Maximum count of existing archives
 * @return map result
 */
global define map RemoveOldMultiArchives(string name, integer max) ``{
    list removed = [];
    map renamed = $[];

    if (name == "" || name == nil)
    {
	return $[];
    }

    // check wheter older archives exist
    list<string> parts = splitstring(name, "/");
    string fname = parts[size(parts) - 1]:"";
    string dir = mergestring(remove(parts, size(parts) - 1), "/");

    if (size(fname) == 0)
    {
	return $[];
    }

    // check whether first archive already exists
    integer sz = (integer)SCR::Read(.target.size, dir + "/" + "01_" + fname);

    if (sz < 0)
    {
	// file doesn't exist, success
	y2milestone("First multivolume archive doesn't exist");
	return $[];
    }
    else
    {
	y2milestone("First multivolume archive already exists");
    }

    string command = "/bin/ls -1 -t " + dir + "/*-*_" + fname + " 2> /dev/null";
    map result = (map)SCR::Execute(.target.bash_output, command);
    list<string> files = splitstring(result["stdout"]:"", "\n");

    list mv_dates = [];

    // filter files with date - use regexp
    list<string> multi = [];
    foreach(string file, files, ``{
	    if (regexpmatch(file, "^" + dir + "/[0-9]{14}-[0-9][0-9]+_" + fname + "$"))
	    {
		multi = add(multi, file);

		string date = regexpsub(file,  "^" + dir + "/([0-9]{14})-[0-9][0-9]+_" + fname + "$", "\\1");

		if (!contains(mv_dates, date))
		{
		    mv_dates = add(mv_dates, date);
		}
	    }
	}
    );
    files = multi;

    y2milestone("Old archives: %1", files);
    y2milestone("Old archive dates: %1", mv_dates);

    if (size(mv_dates) >= max && max >= 0)
    {
	// remove the old archives
	while(size(mv_dates) >= max)
	{
	    string oldarchivedate = mv_dates[size(mv_dates) - 1]:"__DUMMY__";

	    y2milestone("removing archives with date %1", oldarchivedate);

	    foreach(string fn, files,
		``{
		    if (regexpmatch(fn, "^" + dir + "/" + oldarchivedate + "-[0-9]+_" + fname + "$"))
		    {
			// remove old archive
			command = "/bin/rm -f " + fn;
			y2milestone("Removing old volume: %1", fn);

			// update NFS archive name
			if (Backup::target_type == `nfs)
			{
			    fn = Backup::nfsserver + ":" + Backup::nfsexport + substring(fn, size(Backup::nfsmount));
			}

			removed = add(removed, fn);
			SCR::Execute(.target.bash_output, command);
		    }
		}
	    );

	    // remove old XML profile
	    string oldXML = dir + "/" + oldarchivedate + "-" + GetBaseName(fname) + ".xml";
	    command = "/bin/rm -f " + oldXML;

	    // update NFS archive name
	    if (Backup::target_type == `nfs)
	    {
		oldXML = Backup::nfsserver + ":" + Backup::nfsexport + substring(oldXML, size(Backup::nfsmount));
	    }

	    removed = add(removed, oldXML);
	    result = (map)SCR::Execute(.target.bash_output, command);

	    mv_dates = remove(mv_dates, size(mv_dates) - 1);
	}
    }

    // get creation time of the first part of the archive
    map stat = (map)SCR::Read(.target.stat, dir + "/" + "01_" + fname);
    integer ctime = stat["ctime"]:0;
    string ctime_str = SecondsToDateString(ctime);

    command = "/bin/ls -1 -t " + dir + "/*_" + fname + " 2> /dev/null";
    result = (map)SCR::Execute(.target.bash_output, command);
    files = splitstring(result["stdout"]:"", "\n");
    files = filter(string file, files, ``(regexpmatch(file, "^" + dir + "/[0-9]+_" + fname + "$")));
    y2milestone("Existing volumes: %1", files);

    foreach(string volume, files, ``{
	list<string> vol_parts = splitstring(volume, "/");
	string vol_fname = vol_parts[size(vol_parts) - 1]:"";
	string vol_dir = mergestring(remove(vol_parts, size(vol_parts) - 1), "/");

        // rename existing archive
	string from =  vol_dir + "/" + vol_fname;
	string to = vol_dir + "/" + ctime_str + "-" + vol_fname;
        command = "/bin/mv -f " + from + " " + to;
	result = (map)SCR::Execute(.target.bash_output, command);

	// update NFS archive name
	if (Backup::target_type == `nfs)
	{
	    from = Backup::nfsserver + ":" + Backup::nfsexport + substring(from, size(Backup::nfsmount));
	    to = Backup::nfsserver + ":" + Backup::nfsexport + substring(to, size(Backup::nfsmount));
	    y2debug("NFS archive, from: %1, to: %2", from, to);
	}

	renamed[from] = to;

	y2milestone("renamed volume %1", volume);
	}
    );

    // rename autoinstallation profile
    string oldXML = dir + "/" + GetBaseName(name) + ".xml";
    string newXML = dir + "/" + ctime_str + "-" + GetBaseName(fname) + ".xml";

    command = "/bin/mv -f " + oldXML + " " + newXML;
    result = (map)SCR::Execute(.target.bash_output, command);

    // update NFS archive name
    if (Backup::target_type == `nfs)
    {
	oldXML = Backup::nfsserver + ":" + Backup::nfsexport + substring(oldXML, size(Backup::nfsmount));
	newXML = Backup::nfsserver + ":" + Backup::nfsexport + substring(newXML, size(Backup::nfsmount));
	y2debug("NFS archive, oldXML: %1, newXML: %2", oldXML, newXML);
    }

    renamed[oldXML] = newXML;

    return $[ "removed" : removed, "renamed" : renamed ];
}

/**
 * Remove and/or rename old existing archives
 * @param name Archive name
 * @param max Maximum count of existing archives
 * @param multivolume Is archive archive multivolume?
 * @return map result
 */
global define map RemoveOldArchives(string name, integer max, boolean multivolume) ``{
    return (multivolume == true) ? RemoveOldMultiArchives(name, max)
	: RemoveOldSingleArchives(name, max);
}


}
