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