ACC SHELL

Path : /usr/share/YaST2/modules/
File Upload :
Current File : //usr/share/YaST2/modules/ImageInstallation.ycp

/**
 * File:
 *	ImageInstallation.ycp
 *
 * Module:
 *	ImageInstallation
 *
 * Summary:
 *	Support functions for installation via images
 *
 * Authors:
 *	Jiri Srain <jsrain@suse.cz>
 *	Lukas Ocilka <locilka@suse.cz>
 *
 */
{

module "ImageInstallation";

import "Installation";
import "XML";
import "Progress";
import "Report";
import "String";
import "Arch";
import "PackageCallbacks";
import "Popup";
import "SlideShow";
import "ProductControl";
import "ProductFeatures";
import "Packages";
import "PackagesUI";

textdomain "installation";

/**
 * Repository holding all images
 */
integer _repo = nil;

/**
 * Set the repository to get images from
 * @param repo integer the repository identification
 */
global void SetRepo (integer repo) {
    _repo = repo;
    y2milestone ("New images repo: %1", _repo);
}

/**
 * Adjusts the repository for images
 */
void InitRepo () {
    if (_repo != nil)
	return;

    SetRepo (Packages::theSources[0]:0);
}

/**
 * Description of all available images
 */
map<string,map<string,any> > _images = $[];

/**
 * Order of images
 */
list<string> _image_order = [];

/**
 * Image with software management metadata
 */
string _metadata_image = "";

/**
 * Template for the path for an image on the media
 */
string _image_path = "/images";

/**
 * List of already mounted images
 */
list<string> _mounted_images = [];

/**
 * @struct $[
 *   "image_filaname" : $[
 *     // size of an unpacked image in bytes
 *     "size" : integer,
 *     // number of files and directories in an image
 *     "files" : integer,
 *   ]
 * ]
 */
map <string, map <string, integer> > images_details = $[];

/**
 * Image currently being deployed
 */
map <string, any> _current_image = $[];

/**
 * display progress messages every NUMBERth record
 */
integer _checkpoint = 400;

/**
 * NUMBER of bytes per record, multiple of 512
 */
integer _record_size = 10240;

global list <string> last_patterns_selected = [];

global boolean changed_by_user = false;

/**
 * Defines whether some installation images are available
 */
global boolean image_installation_available = nil;

boolean debug_mode = nil;

boolean ThisIsADebugMode () {
    if (debug_mode == nil) {
	debug_mode = (ProductFeatures::GetBooleanFeature("globals", "debug_deploying") == true);
	y2milestone ("ImageInstallation debug mode: %1", debug_mode);
    }

    return debug_mode;
}

/**
 * Name of image containing software management metadata (if exists)
 * @return string image with sw mgmt metadata
 */
global string SwMgmtImage () {
    return _metadata_image;
}

/**
 * Order of images to be deployed
 * @return a list of images definint the order
 */
global list<string> ImageOrder () {
    return _image_order;
}

/**
 * Returns list of currently selected images.
 *
 * @return map <string,map <string,any> > images
 * @see AddImage
 *
 * @struct $[
 *   "image_id":$[
 *     "file":filename,
 *     "type":type
 *   ], ...
 * ]
 */
map <string,map <string,any> > GetCurrentImages () {
    return _images;
}

/**
 * Add information about new image
 * @param name string the name/id of the image
 * @param file string the file name of the image
 * @param type string the type of the image, one of "tar" and "fs"
 */
void AddImage (string name, string file, string type) {
    _images[file] = $[
	"file" : file,
	"type" : type,
	"name" : name,
    ];
}

/**
 * Removes the downloaded image. If the file is writable, releases
 * all sources because only libzypp knows which files are copies
 * and which are just symlinks to sources (e.g., nfs://, smb://).
 */
void RemoveTemporaryImage (string image) {
    map out = (map) SCR::Execute (
	.target.bash_output,
	sformat ("test -w '%1' && echo -n writable", String::Quote (image))
    );

    // Command has either failed or file is writable (non-empty stdout)
    if (out["exit"]:-1 != 0 || out["stdout"]:"" != "") {
	y2milestone ("Releasing sources to remove temporary files");
	Pkg::SourceReleaseAll();
    }
}

void (integer) tar_image_progress = nil;

global void SetDeployTarImageProgress (void (integer) tip) {
    tar_image_progress = tip;
    y2milestone ("New tar_image_progress: %1", tar_image_progress);
}

boolean (integer, integer, integer) download_image_progress = nil;

global void SetDownloadTarImageProgress (boolean (integer, integer, integer) tip) {
    download_image_progress = tip;
    y2milestone ("New download_image_progress: %1", download_image_progress);
}

void (string, string) start_download_handler = nil;

// BNC #449792
global void SetStartDownloadImageProgress (void (string, string) sdi) {
    start_download_handler = sdi;
    y2milestone ("New start_download_handler: %1", start_download_handler);
}

void (string, integer) generic_set_progress = nil;

global void SetOverallDeployingProgress (void (string, integer) odp) {
    generic_set_progress = odp;
    y2milestone ("New generic_set_progress: %1", generic_set_progress);
}

/**
 * Deploy an image of the filesystem type
 * @param id string the id of the image
 * @param target string the directory to deploy the image to
 * @return boolean true on success
 */
boolean DeployTarImage (string id, string target) {
    InitRepo();

    string file = _images[id, "file"]:"";
    y2milestone ("Untarring image %1 (%2) to %3", id, file, target);
    file = sformat ("%1/%2", _image_path, file);
    // BNC #409927
    // Checking files for signatures
    string image = Pkg::SourceProvideDigestedFile (_repo, 1, file, false);

    if (image == nil)
    {
	y2error ("File %1 not found on media", file);
	return false;
    }

    // reset, adjust labels, etc.
    if (tar_image_progress != nil)
	tar_image_progress (0);

    y2milestone ("Creating target directory");
    string cmd = sformat ("test -d %1 || mkdir -p %1", target);
    map out = (map)SCR::Execute (.target.bash_output, cmd);
    y2milestone ("Executing %1 returned %2", cmd, out);

    y2milestone ("Untarring the image");

    // lzma
    if (regexpmatch (image, "\.lzma$")) {
	cmd = sformat (
	    "lzmadec < '%1' | tar --numeric-owner --totals --checkpoint=%3 --record-size=%4 -C '%2' -xf -",
	    String::Quote (image), String::Quote (target), _checkpoint, _record_size
	);
    // xzdec
    // BNC #476079
    } else if (regexpmatch (image, "\.xz$")) {
	cmd = sformat (
	    "xzdec < '%1' | tar --numeric-owner --totals --checkpoint=%3 --record-size=%4 -C '%2' -xf -",
	    String::Quote (image), String::Quote (target), _checkpoint, _record_size
	);
    // bzip2, gzip
    } else {
	cmd = sformat (
	    "tar --numeric-owner --checkpoint=%3 --record-size=%4 --totals -C '%2' -xf '%1'",
	    String::Quote (image), String::Quote (target), _checkpoint, _record_size
	);
    }
    y2milestone ("Calling: %1", cmd);

    integer pid = (integer) SCR::Execute(.process.start_shell, cmd);

    string newline = "";

    string read_checkpoint_str = "^tar: Read checkpoint ([0123456789]+)$";

    // Otherwise it will never make 100%
    integer better_feeling_constant = _checkpoint;

    any ret = nil;
    boolean aborted = false;

    while (SCR::Read (.process.running, pid) == true) {
	newline = (string) SCR::Read (.process.read_line_stderr, pid);

	if (newline != nil) {
	    if (! regexpmatch (newline, read_checkpoint_str)) {
		y2milestone ("Deploying image: %1", newline);
		continue;
	    }

	    newline = regexpsub (newline, read_checkpoint_str, "\\1");

	    if (newline == nil || newline == "")
		continue;

	    if (tar_image_progress != nil)
		tar_image_progress (tointeger (newline) + better_feeling_constant);
	} else {
	    ret = UI::PollInput();
	    if (ret == `abort || ret == `cancel) {
		if (Popup::ConfirmAbort (`unusable)) {
		    y2warning ("Aborted!");
		    aborted = true;
		    break;
		}
	    } else {
		SlideShow::HandleInput( ret );
		sleep (200);
	    }
	}
    }

    // BNC #456337
    // Checking the exit code (0 = OK, nil = still running, 'else' = error)
    integer exitcode = (integer) SCR::Read (.process.status, pid);

    if (exitcode != nil && exitcode != 0) {
	y2milestone (
	    "Deploying has failed, exit code was: %1, stderr: %2",
	    exitcode, SCR::Read (.process.read_stderr, pid)
	);
	aborted = true;
    }

    y2milestone ("Finished");

    if (aborted)
	return nil;

    // adjust labels etc.
    if (tar_image_progress != nil)
	tar_image_progress (100);

    RemoveTemporaryImage (image);

    return out["exit"]:-1 == 0;
}

/**
 * Deploy an image of the filesystem type
 * @param id string the id of the image
 * @param target string the directory to deploy the image to
 * @return boolean true on success
 */
boolean DeployFsImage (string id, string target) {
    InitRepo();

    string file = _images[id, "file"]:"";
    y2milestone ("Deploying FS image %1 (%2) on %3", id, file, target);
    file = sformat ("%1/%2", _image_path, file);
    // BNC #409927
    // Checking files for signatures
    string image = Pkg::SourceProvideDigestedFile (_repo, 1, file, false);

    if (image == nil)
    {
	y2error ("File %1 not found on media", file);
	return false;
    }

    y2milestone ("Creating temporary directory");
    string tmpdir = (string)SCR::Read (.target.tmpdir) + sformat ("/images/%1", id);
    string cmd = sformat ("test -d %1 || mkdir -p %1", tmpdir);
    map out = (map)SCR::Execute (.target.bash_output, cmd);
    y2milestone ("Executing %1 returned %2", cmd, out);

    y2milestone ("Mounting the image");
    cmd = sformat ("mount -o loop %1 %2", image, tmpdir);
    out = (map)SCR::Execute (.target.bash_output, cmd);
    y2milestone ("Executing %1 returned %2", cmd, out);

    y2milestone ("Creating target directory");
    cmd = sformat ("test -d %1 || mkdir -p %1", target);
    out = (map)SCR::Execute (.target.bash_output, cmd);
    y2milestone ("Executing %1 returned %2", cmd, out);

    y2milestone ("Copying contents of the image");
    cmd = sformat ("cp -a %1/* %2", tmpdir, target);
    out = (map)SCR::Execute (.target.bash_output, cmd);
    y2milestone ("Executing %1 returned %2", cmd, out);

    y2milestone ("Unmounting image from temporary directory");
    cmd = sformat ("umount -d -f -l %1", tmpdir);
    out = (map)SCR::Execute (.target.bash_output, cmd);
    y2milestone ("Executing %1 returned %2", cmd, out);

    RemoveTemporaryImage (image);

    return out["exit"]:-1 == 0;
    // FIXME error checking
}

/**
 * Mount an image of the filesystem type
 * Does not integrate to the system, mounts on target
 * @param id string the id of the image
 * @param target string the directory to deploy the image to
 * @return boolean true on success
 */
boolean MountFsImage (string id, string target) {
    InitRepo();

    string file = _images[id, "file"]:"";
    y2milestone ("Mounting image %1 (%2) on %3", id, file, target);
    file = sformat ("%1/%2", _image_path, file);
    // BNC #409927
    // Checking files for signatures
    string image = Pkg::SourceProvideDigestedFile (_repo, 1, file, false);

    if (image == nil)
    {
	y2error ("File %1 not found on media", file);
	return false;
    }
    string cmd = sformat ("test -d %1 || mkdir -p %1", target);
    map out = (map)SCR::Execute (.target.bash_output, cmd);
    y2milestone ("Executing %1 returned %2", cmd, out);
    cmd = sformat ("mount -o loop %1 %2", image, target);
    out = (map)SCR::Execute (.target.bash_output, cmd);
    y2milestone ("Executing %1 returned %2", cmd, out);
    return out["exit"]:-1 == 0;
    // FIXME error checking
    // FIXME unmounting
}

integer _current_image_from_imageset = -1;

global integer TotalSize()
{
    integer sum = 0;
    
    y2milestone( "Computing total images size from [%1], data %2", _image_order, images_details );
    foreach( string image, _image_order, {
	// 128 MB as a fallback size
	// otherwise progress would not move at all
	sum = sum + images_details[image,"size"]:134217728;
    });
    
    y2milestone( "Total images size: %1", sum );
    return sum;
}

void SetCurrentImageDetails (map <string,any> img) {
    _current_image_from_imageset = _current_image_from_imageset + 1;

    if (size (images_details) == 0) {
	y2warning ("Images details are empty");
    }

    _current_image = $[
	"file" : img["file"]:"",
	"name" : img["name"]:"",
	"size" : images_details[img["file"]:"","size"]:0,
	"files" : images_details[img["file"]:"","files"]:0,
	// 100% progress
	"max_progress" : tointeger (images_details[img["file"]:"","size"]:0 / _record_size),
	"image_nr" : _current_image_from_imageset,
    ];
}

global map <string, any> GetCurrentImageDetails () {
    return _current_image;
}

/**
 * Deploy an image (internal implementation)
 * @param id string the id of the image
 * @param target string the directory to deploy the image to
 * @param temporary boolean true to only mount if possible (no copy)
 * @return boolean true on success
 */
boolean _DeployImage (string id, string target, boolean temporary) {
    map<string,any> img = _images[id]:$[];
    if (img == $[])
    {
	y2error ("Image %1 does not exist", id);
    }

    string type = img["type"]:"";

    SetCurrentImageDetails (img);

    if (type == "fs")
	return temporary
	    ? MountFsImage (id, target)
	    : DeployFsImage (id, target);
    else if (type == "tar")
	return DeployTarImage (id, target);

    y2error ("Unknown type of image: %1", type);
    return false;
}

/**
 * Deploy an image
 * @param id string the id of the image
 * @param target string the directory to deploy the image to
 * @return boolean true on success
 */
global boolean DeployImage (string id, string target) {
    y2milestone ("Deploying image %1 to %2", id, target);
    return _DeployImage (id, target, false);
}

/**
 * Deploy an image temporarily (just mount if possible)
 * @param id string the id of the image
 * @param target string the directory to deploy the image to,
 * @return boolean true on success
 */
global boolean DeployImageTemporarily (string id, string target) {
    y2milestone ("Temporarily delploying image %1 to %2", id, target);
    return _DeployImage (id, target, true);
}

/**
 * UnDeploy an image temporarily (if possible, only for the FS images)
 * @param id string the id of the image
 * @param target string the directory to deploy the image to,
 * @return boolean true on success
 */
global boolean CleanTemporaryImage (string id, string target) {
    y2milestone ("UnDelploying temporary image %1 from %2", id, target);
    if (_images[id, "type"]:"" == "fs")
    {
	string cmd = sformat ("umount %1", target);
	map out = (map)SCR::Execute (.target.bash_output, cmd);
	y2milestone ("Executing %1 returned %2", cmd, out);
	return out["exit"]:-1 == 0;
    }
    y2milestone ("Cannot undeploy image of type %1", _images[id, "type"]:"");
    return true;
}

/**
 * Loads non-mandatory details for every single selected image.
 */
global boolean FillUpImagesDetails () {
    InitRepo();

    // bnc #439104
    if (_repo == nil) {
	y2warning ("No images-repository defined");
	return true;
    }

    // ppc (covers also ppc64), i386, x86_64 ...
    string filename = nil;

    list <string> possible_files = [
	sformat ("%1/details-%2.xml", _image_path, Arch::arch_short()),
	sformat ("%1/details.xml", _image_path),
    ];

    foreach (string try_file, possible_files, {
	// BNC #409927
	// Checking files for signatures
	filename = Pkg::SourceProvideDigestedFile (_repo, 1, try_file, true);

	if (filename != nil && filename != "") {
	    y2milestone ("Using details file: %1 (%2)", filename, try_file);
	    break;
	}
    });

    if (filename == nil) {
	y2milestone ("No image installation details found");
	return false;
    }

    map <string,any> read_details = XML::XMLToYCPFile (filename);
    if (read_details == nil) {
	y2error ("Cannot parse imagesets details");
	return false;
    }

    if (! haskey (read_details, "details")) {
	y2warning ("No images details in details.xml");
	return false;
    }

    images_details = $[];

    foreach (map image_detail, read_details["details"]:[], {
	string file = image_detail["file"]:"";

	if (file == nil || file == "") return;

	integer files = tointeger (image_detail["files"]:"0");
	integer isize = tointeger (image_detail["size"]:"0");

	images_details[file] = $[
	    "files" : files,
	    "size" : isize,
	];
    });

    // FIXME: y2debug
    y2milestone ("Details: %1", images_details);
    return true;
}

/**
 * Deploy all images
 * @param images a list of images to deploy
 * @param target string directory where to deploy the images
 * @param progress a function to report overal progress
 */
global boolean DeployImages (list<string> images, string target, void(integer,integer) progress) {
    // unregister callbacks
    PackageCallbacks::RegisterEmptyProgressCallbacks();

    // downloads details*.xml file
    FillUpImagesDetails();

    // register own callback for downloading
    if (download_image_progress != nil)
	Pkg::CallbackProgressDownload (download_image_progress);

    // register own callback for start downloading
    if (start_download_handler != nil)
	Pkg::CallbackStartDownload (start_download_handler);

    integer num = -1;
    _current_image_from_imageset = -1;
    boolean aborted = nil;

    foreach (string img, images, {
	num = num + 1;
	if (progress != nil)
	    progress (num, 0);

	if (DeployImage (img, target) == nil) {
	    aborted = true;
	    y2milestone ("Aborting...");
	    break;
	}

	if (progress != nil)
	    progress (num, 100);
    });

    if (aborted == true) {
	return nil;
    }

    // unregister downloading progress
    if (download_image_progress != nil)
	Pkg::CallbackProgressDownload (nil);

    // reregister callbacks
    PackageCallbacks::RestorePreviousProgressCallbacks();

    return true;
// TODO error checking
}

/**
 * Returns the intersection of both patterns supported by the imageset
 * and patterns going to be installed.
 */
integer CountMatchingPatterns (list <string> imageset_patterns, list <string> installed_patterns) {
    integer ret = 0;

    foreach (string one_installed_pattern, installed_patterns, {
	if (contains (imageset_patterns, one_installed_pattern))
	    ret = ret + 1;
    });

    return ret;
}

boolean EnoughPatternsMatching (integer matching_patterns, integer patterns_in_imagesets) {
    if (matching_patterns == nil || matching_patterns < 0)
	return false;

    if (patterns_in_imagesets == nil || patterns_in_imagesets < 0)
	return false;

    // it's actually matching_patterns = patterns_in_imagesets
    return (matching_patterns >= patterns_in_imagesets);
}

/**
 * Find a set of images which suites selected patterns
 * @param patterns a list of patterns which are selected
 * @return boolean true on success or when media does not contain any images
 */
global boolean FindImageSet (list<string> patterns) {
    InitRepo();

    // reset all data
    _images = $[];
    _image_order = [];
    _metadata_image = "";

    // checking whether images are supported
    // BNC #409927
    // Checking files for signatures
    string filename = Pkg::SourceProvideDigestedFile (_repo, 1, sformat ("%1/images.xml", _image_path), false);

    if (filename == nil)
    {
	image_installation_available = false;
	Installation::image_installation = false;
	Installation::image_only = false;
	y2milestone ("Image list for installation not found");
	return true;
    }

    map<string,any> image_descr = XML::XMLToYCPFile (filename);
    if (image_descr == nil)
    {
	image_installation_available = false;
	Installation::image_installation = false;
	Installation::image_only = false;
	Report::Error (_("Failed to read information about installation images"));
	return false;
    }

    // images are supported
    // bnc #492745: Do not offer images if there are none
    image_installation_available = true;

    list<map<string,any> > image_sets = image_descr["image_sets"]:[];
    y2debug ("Image set descriptions: %1", image_sets);
    map<string,any> result = $[];

    // more patterns could match at once
    // as we can't merge the meta image, only one can be selected
    map <string, map <string, any> > possible_patterns = $[];
    map <string, integer> matching_patterns = $[];
    map <string, integer> patterns_in_imagesets = $[];

    // ppc (covers also ppc64), i386, x86_64 ...
    string arch_short = Arch::arch_short();
    y2milestone ("Current architecture is: %1", arch_short);

    // filter out imagesets for another architecture
    image_sets = filter (map<string,any> image, image_sets, {
	list <string> imageset_archs = splitstring (image["archs"]:"", " ,");

	// no architecture defined == noarch
	if (size (imageset_archs) == 0) {
	    return true;
	// does architecture match?
	} else {
	    if (contains (imageset_archs, arch_short)) {
		return true;
	    } else {
		// For debugging purpose
		y2milestone ("Filtered-out, Patterns: %1, Archs: %2", image["patterns"]:"", image["archs"]:"");
		return false;
	    }
	}
    });

    // trying to find all matching patterns
    foreach (map<string,any> image, image_sets, {
	string pattern = image["patterns"]:"";

	list <string> imageset_patterns = splitstring (pattern, ", ");
	patterns_in_imagesets[pattern] = size (imageset_patterns);

	// no image-pattern defined, matches all patterns
	if (size (imageset_patterns) == 0) {
	    possible_patterns[pattern] = image;
	// image-patterns matches to patterns got as parameter
	} else {
	    matching_patterns[pattern] = CountMatchingPatterns (imageset_patterns, patterns);

	    if (matching_patterns[pattern]:0 > 0) {
		possible_patterns[pattern] = image;
	    } else {
		// For debugging purpose
		y2milestone ("Filtered-out, Patterns: %1, Matching: %2",
		    image["patterns"]:"", matching_patterns[pattern]:-1);
	    }
	}
    });

    y2debug ("Matching patterns: %1, sizes: %2", possible_patterns, matching_patterns);

    // selecting the best imageset
    string last_pattern = "";

    if (size (possible_patterns) > 0) {
	integer last_number_of_matching_patterns = -1;
	last_pattern = "";

	foreach (string pattern, map <string, any> image, possible_patterns, {
	    if (
		// imageset matches more patterns than the currently best-one
		matching_patterns[pattern]:0 > last_number_of_matching_patterns
		&&
		// enough patterns matches the selected imageset
		EnoughPatternsMatching (matching_patterns[pattern]:0, patterns_in_imagesets[pattern]:0)
	    ) {
		last_number_of_matching_patterns = matching_patterns[pattern]:0;
		result = image;
		last_pattern = pattern;
	    }
	});
    }

    y2milestone ("Result: %1/%2", last_pattern, result);

    // No matching pattern
    if (result == $[]) {
	Installation::image_installation = false;
	Installation::image_only= false;
	y2milestone ("No image for installation found");
	return true;
    }

    // We've selected one
    Installation::image_installation = true;

    if (haskey (result, "pkg_image")) {
	_metadata_image = result["pkg_image"]:"";
    } else {
	Installation::image_only= true;
    }

    // Adding images one by one into the pool
    foreach (map<string,string> img, result["images"]:[], {
	// image must have unique <file>...</file> defined
	if (img["file"]:"" == "") {
	    y2error ("No file defined for %1", img);
	    return;
	}

	_image_order = add (_image_order, img["file"]:"");
	AddImage (img["name"]:"", img["file"]:"", img["type"]:"");
    });

    y2milestone ("Image-only installation: %1", Installation::image_only);
    y2milestone ("Images: %1", _images);
    y2milestone ("Image installation order: %1", _image_order);

    if (! Installation::image_only)
	y2milestone ("Image with software management metadata: %1", _metadata_image);

    return true;
}

/**
 * Returns map with description which images will be used
 *
 * @return map with description
 *
 * @struct $[
 *   "deploying_enabled" : boolean,
 *   "images" : returned by GetCurrentImages()
 * ]
 *
 * @see GetCurrentImages()
 */
global map ImagesToUse () {
    map ret = $[];

    if (Installation::image_installation == true) {
	ret = $[
	    "deploying_enabled" : true,
	    "images" : GetCurrentImages(),
	];
    } else {
	ret["deploying_enabled"] = false;
    }

    return ret;
}

/**
 * Copy a subtree, limit to a single filesystem
 * @param from string source directory
 * @param to string target directory
 * @return boolean true on success
 */
global boolean FileSystemCopy (string from, string to,
    integer progress_start,
    integer progress_finish)
{
    string cmd = sformat ("du -x -B 1048576 -s %1", from);
    y2milestone ("Executing %1", cmd);
    map out = (map)SCR::Execute (.target.bash_output, cmd);
    y2milestone ("Output: %1", out);
    string total_str = out["stdout"]:"";
    integer total_mb = tointeger (total_str);
    if (total_mb == 0)
	total_mb = 1024*1024*1024; // should be big enough

    string tmp_pipe1 = (string)SCR::Read (.target.tmpdir) + "/system_clone_fifo_1";
    string tmp_pipe2 = (string)SCR::Read (.target.tmpdir) + "/system_clone_fifo_2";
    // FIXME this does not copy pipes in filesystem (usually not an issue)
    cmd = sformat (
	"mkfifo %3 ;
	 mkfifo %4 ;
	 tar -C %1 --hard-dereference --numeric-owner -cSf %3 --one-file-system . &
	 dd bs=1048576 if=%3 of=%4 >&2 &
	 jobs -l >&2;
	 tar -C %2 --numeric-owner -xSf %4",
	from, to, tmp_pipe1, tmp_pipe2);
    y2milestone ("Executing %1", cmd);
    integer process = (integer)SCR::Execute(.process.start_shell, cmd, $[]);
    string pid = "";

    while((boolean)SCR::Read(.process.running, process))
    {
	string done = nil;
	string line = (string)SCR::Read (.process.read_line_stderr, process);
	while (line != nil)
	{
	    if (pid == "")
	    {
		if (! regexpmatch (line, sformat ("dd bs=1048576 if=%1 of=%2", tmp_pipe1, tmp_pipe2)))
		    pid = "";
		else
		{
		    pid = regexpsub (line, "([0-9]+) [^ 0-9]+ +dd", "\\1");
		    y2milestone ("DD's pid: %1", pid);
		    // sleep in order not to kill -USR1 to dd too early, otherwise it finishes
		    sleep (5000);
		}
	    }
	    else if (regexpmatch (line, "^[0-9]+ "))
		done = regexpsub (line, "^([0-9]+) ", "\\1");
	    y2debug ("Done: %1", done);
	    line = (string)SCR::Read (.process.read_line_stderr, process);
	}
	if (pid != "")
	{
	    cmd = sformat ("/bin/kill -USR1 %1", pid);
	    y2debug ("Executing %1", cmd);
	    SCR::Execute (.target.bash, cmd);
	}
	sleep (300);
	if (done != nil)
	{
	    integer progress = progress_start + (progress_finish - progress_start) * tointeger (done) / total_mb / 1024 / 1024;
	    y2debug ("Setting progress to %1", progress);
	    SlideShow::StageProgress( progress, nil);
	    SlideShow::SubProgress( (progress_finish - progress_start) * tointeger (done) / total_mb / 1024 / 1024, nil);
	}
    }

    integer copy_result = (integer)SCR::Read (.process.status, process);
    y2milestone ("Result: %1", copy_result);
    SCR::Execute (.target.remove, tmp_pipe1);
    SCR::Execute (.target.remove, tmp_pipe2);
    cmd = sformat ("chown --reference=%1 %2; chmod --reference=%1 %2",
	from, to);
    y2milestone ("Executing %1", cmd);
    out = (map)SCR::Execute (.target.bash_output, cmd);
    y2milestone ("Result: %1", out);
    return out["exit"]:-1 == 0 && copy_result == 0;
}

    // --> Storing and restoring states

    /**
     * List of all handled types.
     */
    // list <symbol> all_supported_types = [`product, `pattern, `language, `package, `patch];
    // Zypp currently counts [ `product, `pattern, `language ]
    list <symbol> all_supported_types = [`package, `patch];

    /**
     * Map that stores all the requested states of all handled/supported types.
     */
    map <symbol, map <string, list <map> > > objects_state = $[];

    map progress_layout = $[ 
	"storing_user_prefs" : $[
	    "steps_start_at" :  0,
	    "steps_reserved" :  6,
	],
	"deploying_images" : $[
	    "steps_start_at" :  6,
	    "steps_reserved" : 84,
	    
	],
	"restoring_user_prefs" : $[
	    "steps_start_at" : 90,
	    "steps_reserved" : 10,
	],
    ];

    global integer GetProgressLayoutDetails (string id, string details) {
	return progress_layout[id, details]:0;
    }

    global string GetProgressLayoutLabel (string id) {
	return progress_layout[id, "label"]:_("Deploying...");
    }

    global void AdjustProgressLayout (string id, integer steps_total, string label) {
	if (! haskey (progress_layout, id)) {
	    y2error ("Unknown key: %1", id);
	    return;
	}

	progress_layout[id, "label"] = label;
	progress_layout[id, "steps_total"] = steps_total;
    }

    /**
     * Function stores all new/requested states of all handled/supported types.
     *
     * @see all_supported_types
     * @see objects_state
     */
    global void StoreAllChanges () {
	integer nr_steps = 4 * size (all_supported_types);
	string id = "storing_user_prefs";

	AdjustProgressLayout (id, nr_steps, _("Storing user preferences..."));

	if (generic_set_progress != nil)
	    generic_set_progress (id, 0);

        // Query for changed state of all knwon types
        // 'changed' means that they were 'installed' and 'not locked' before
        foreach (symbol one_type, all_supported_types, {
            // list of $[ "name":string, "version":string, "arch":string, "source":integer, "status":symbol, "locked":boolean ]
            // status is `installed, `removed, `selected or `available, source is source ID or -1 if the resolvable is installed in the target
            // if status is `available and locked is true then the object is set to taboo
            // if status is `installed and locked is true then the object locked
            list <map <string, any> > resolvable_properties = Pkg::ResolvableProperties ("", one_type, "");

	    // FIXME: Store only those keys we need (arch, name, version?)

            objects_state[one_type] = $[];

            list <map <string, any> > remove_resolvables = filter (map <string, any> one_object, resolvable_properties, {
                return (one_object["status"]:`unknown == `removed);
            });
            objects_state[one_type, "remove"] = remove_resolvables;

	    if (generic_set_progress != nil)
		generic_set_progress (id, nil);

            list <map <string, any> > install_resolvables = filter (map <string, any> one_object, resolvable_properties, {
                return (one_object["status"]:`unknown == `selected);
            });
            objects_state[one_type, "install"] = install_resolvables;

	    if (generic_set_progress != nil)
		generic_set_progress (id, nil);

            list <map <string, any> > taboo_resolvables = filter (map <string, any> one_object, resolvable_properties, {
                return (one_object["status"]:`unknown == `available && one_object["locked"]:false == true);
            });
            objects_state[one_type, "taboo"] = taboo_resolvables;

	    if (generic_set_progress != nil)
		generic_set_progress (id, nil);

            list <map <string, any> > lock_resolvables = filter (map <string, any> one_object, resolvable_properties, {
                return (one_object["status"]:`unknown == `installed && one_object["locked"]:false == true);
            });
            objects_state[one_type, "lock"] = lock_resolvables;

	    if (generic_set_progress != nil)
		generic_set_progress (id, nil);
        });

	if (ThisIsADebugMode()) {
	    // map <symbol, map <string, list <map> > > objects_state = $[];
	    foreach (symbol object_type, map <string, list <map> > objects_status, objects_state, {
		foreach (string one_status, list <map> list_of_objects, objects_status, {
		    y2milestone ("Object type: %1, New status: %2, List of objects: %3", object_type, one_status, list_of_objects);
		});
	    });
	}
    }

    /**
     * @return boolean whether the package should be additionally installed
     */
    boolean ProceedWithSelected (map <string, any> & one_object, symbol & one_type) {
	// This package has been selected to be installed

	string arch = one_object["arch"]:"";
	// Query for all packages of the same version
	list <map <string, any> > resolvable_properties = Pkg::ResolvableProperties (
	    one_object["name"]:"-x-", one_type, one_object["version"]:"-x-"
	);

	if (ThisIsADebugMode())
	    y2milestone ("Looking for %1 returned %2", one_object, resolvable_properties);

	// Leave only already installed (and matching the same architecture)
	resolvable_properties = filter (map <string, any> one_resolvable, resolvable_properties, {
	    return (one_resolvable["status"]:`unknown == `installed && one_resolvable["arch"]:"" == arch);
	});

	if (ThisIsADebugMode())
	    y2milestone ("Resolvables installed: %1", resolvable_properties);

	boolean ret = nil;

	// There are some installed (matching the same arch, version, and name)
	if (size (resolvable_properties) > 0) {
	    y2milestone ("Resolvable type: %1, name: %2 already installed", one_type, one_object["name"]:"-x-");
	    // Let's keep the installed version
	    Pkg::ResolvableNeutral (one_object["name"]:"-x-", one_type, true);
	    // is already installed
	    ret = false;
	// They are not installed
	} else {
	    y2milestone ("Installing type: %1, details: %2,%3,%4",
		one_type, one_object["name"]:"", one_object["arch"]:"", one_object["version"]:"");
	    // Confirm we want to install them (they might have been added as dependencies)
	    Pkg::ResolvableInstallArchVersion (one_object["name"]:"", one_type, one_object["arch"]:"", one_object["version"]:"");
	    // should be installed
	    ret = true;
	}

	return ret;
    }

    /**
     * Restores packages statuses from 'objects_state': Selects packages for removal, installation, upgrade.
     *
     * @return boolean if successful
     */
    global boolean RestoreAllChanges () {
	integer nr_steps = 4 * size (all_supported_types);
	string id = "restoring_user_prefs";

	AdjustProgressLayout (id, nr_steps, _("Restoring user preferences..."));

	if (generic_set_progress != nil)
	    generic_set_progress (id, 0);

        foreach (symbol one_type, all_supported_types, {
            list <map <string, any> > resolvable_properties = Pkg::ResolvableProperties ("", one_type, "");

	    // All packages selected for installation
	    // both `to-install and `to-upgrade (already) installed
            list <map <string, any> > to_install = filter (map <string, any> one_resolvable, resolvable_properties, {
                return (one_resolvable["status"]:`unknown == `selected);
            });

	    if (generic_set_progress != nil)
		generic_set_progress (id, nil);

	    // List of all packages selected for installation (just names)
	    list <string> selected_for_installation_pkgnames = maplist (map one_resolvable, objects_state[one_type, "install"]:[], {
		return one_resolvable["name"]:"";
	    });

	    // All packages selected to be installed
	    // [ $[ "arch" : ... , "name" : ... , "version" : ... ], ... ]
	    list <map <string, string> > selected_for_installation = maplist (map one_resolvable, objects_state[one_type, "install"]:[], {
		return $[ "arch":one_resolvable["arch"]:"", "name":one_resolvable["name"]:"", "version":one_resolvable["version"]:"" ];
	    });

	    if (generic_set_progress != nil)
		generic_set_progress (id, nil);

	    // Delete all packages that are installed but should not be
	    map <string, string> one_already_installed_resolvable = $[];
            foreach (map <string, any> one_resolvable, resolvable_properties, {
        	// We are interested in the already installed resolvables only
                if (one_resolvable["status"]:`unknown != `installed && one_resolvable["status"]:`unknown != `selected) {
		    return;
		}

                one_already_installed_resolvable =  $[
		    "arch":one_resolvable["arch"]:"", "name":one_resolvable["name"]:"", "version":one_resolvable["version"]:""
            	];

		// Already installed resolvable but not in list of resolvables to be installed
		if (! contains (selected_for_installation, one_already_installed_resolvable)) {
		    // BNC #489448: Do not remove package which is installed in different version and/or arch
		    // It will be upgraded later
		    if (contains (selected_for_installation_pkgnames, one_resolvable["name"]:"-x-")) {
			y2milestone ("Not Removing type: %1, name: %2 version: %3",
			    one_type, one_resolvable["name"]:"-x-", one_resolvable["version"]:"-x-");
		    // Package is installed or selected but should not be, remove it
		    } else {
			y2milestone ("Removing type: %1, name: %2 version: %3",
			    one_type, one_resolvable["name"]:"-x-", one_resolvable["version"]:"-x-");
			Pkg::ResolvableRemove (one_resolvable["name"]:"-x-", one_type);
		    }
		}
            });

	    if (generic_set_progress != nil)
		generic_set_progress (id, nil);

	    // Install all packages that aren't yet
	    foreach (map <string, any> one_to_install, to_install, {
		ProceedWithSelected (one_to_install, one_type);
	    });

	    if (generic_set_progress != nil)
		generic_set_progress (id, nil);
        });

	// Free the memory
	objects_state = $[];

	// Return 'true' if YaST can solve deps. automatically
	if (Pkg::PkgSolve (true) == true) {
	    y2milestone ("Dependencies solved atomatically");
	    return true;
	}

	// Error message
	Report::Error (_("Installation was unable to solve package dependecies automatically.
Package manager will be opened for you to solve them manually."));

	boolean ret = false;

	// BNC #Trying to solve deps. manually
	while (true) {
	    y2warning ("Cannot solve dependecies automatically, opening Packages UI");
	    symbol diaret = PackagesUI::RunPackageSelector ($["enable_repo_mgr":false, "mode":`summaryMode]);
	    y2milestone ("RunPackageSelector returned %1", diaret);

	    // User didn't solve the deps manually
	    if (diaret == `cancel) {
		ret = false;
		if (Popup::ConfirmAbort (`unusable)) {
		    y2warning ("User abort...");
		    break;
		}
		// Aborting not confirmed, next round
		continue;
	    // Solved! (somehow)
	    } else {
		ret = true;
		break;
	    }
	}

	y2milestone ("Dependencies solved: %1", ret);
	return ret;
    }

    // <-- Storing and restoring states

    global void FreeInternalVariables () {
	last_patterns_selected = [];
	_images = $[];
	_image_order = [];
	images_details = $[];
	_mounted_images = [];
    }
}

ACC SHELL 2018