2883 lines
102 KiB
Python
2883 lines
102 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
|
|
@author: Fabio Erculiani <lxnay@sabayon.org>
|
|
@contact: lxnay@sabayon.org
|
|
@copyright: Fabio Erculiani
|
|
@license: GPL-2
|
|
|
|
B{Entropy Package Manager Client Package Interface}.
|
|
|
|
"""
|
|
import sys
|
|
import os
|
|
import errno
|
|
import stat
|
|
import shutil
|
|
import tempfile
|
|
import time
|
|
|
|
from entropy.const import etpConst, etpSys, const_setup_perms, \
|
|
const_isunicode, const_convert_to_unicode
|
|
from entropy.exceptions import PermissionDenied, SPMError
|
|
from entropy.i18n import _
|
|
from entropy.output import TextInterface, brown, blue, bold, darkgreen, \
|
|
darkblue, red, purple, darkred
|
|
from entropy.misc import TimeScheduled
|
|
from entropy.db import EntropyRepository
|
|
from entropy.client.interfaces.client import Client
|
|
from entropy.cache import EntropyCacher
|
|
from entropy.core.settings.base import SystemSettings
|
|
from entropy.security import System as SystemSecurity, \
|
|
Repository as RepositorySecurity
|
|
|
|
import entropy.tools
|
|
|
|
class Package:
|
|
|
|
def __init__(self, EquoInstance):
|
|
|
|
if not isinstance(EquoInstance, Client):
|
|
mytxt = "A valid Client instance or subclass is needed"
|
|
raise AttributeError(mytxt)
|
|
self.Entropy = EquoInstance
|
|
|
|
self._system_settings = SystemSettings()
|
|
self.Cacher = EntropyCacher()
|
|
self.pkgmeta = {}
|
|
self.__prepared = False
|
|
self.matched_atom = ()
|
|
self.valid_actions = ("source", "fetch", "multi_fetch", "remove",
|
|
"remove_conflict", "install", "config"
|
|
)
|
|
self.action = None
|
|
self.fetch_abort_function = None
|
|
self.xterm_title = ''
|
|
|
|
def kill(self):
|
|
self.pkgmeta.clear()
|
|
|
|
self.matched_atom = ()
|
|
self.valid_actions = ()
|
|
self.action = None
|
|
self.__prepared = False
|
|
self.fetch_abort_function = None
|
|
|
|
def error_on_prepared(self):
|
|
if self.__prepared:
|
|
mytxt = _("Already prepared")
|
|
raise PermissionDenied("PermissionDenied: %s" % (mytxt,))
|
|
|
|
def error_on_not_prepared(self):
|
|
if not self.__prepared:
|
|
mytxt = _("Not yet prepared")
|
|
raise PermissionDenied("PermissionDenied: %s" % (mytxt,))
|
|
|
|
def check_action_validity(self, action):
|
|
if action not in self.valid_actions:
|
|
raise AttributeError("Action must be in %s" % (self.valid_actions,))
|
|
|
|
@staticmethod
|
|
def get_standard_fetch_disk_path(download):
|
|
"""
|
|
Return standard path where package is going to be downloaded.
|
|
"download" argument passed must come from
|
|
EntropyRepository.retrieveDownloadURL()
|
|
"""
|
|
return os.path.join(etpConst['entropyworkdir'], download)
|
|
|
|
def __get_fetch_disk_path(self, download):
|
|
"""
|
|
Return proper Entropy package store path
|
|
"""
|
|
if 'fetch_path' in self.pkgmeta:
|
|
# only supported by fetch action, multifetch also unsupported
|
|
pkg_disk_path = os.path.join(self.pkgmeta['fetch_path'],
|
|
os.path.basename(download))
|
|
else:
|
|
pkg_disk_path = Package.get_standard_fetch_disk_path(download)
|
|
return pkg_disk_path
|
|
|
|
def __check_pkg_path_download(self, download, checksum = None):
|
|
# is the file available
|
|
pkg_path = self.__get_fetch_disk_path(download)
|
|
if os.path.isfile(pkg_path):
|
|
|
|
if checksum is None:
|
|
return 0
|
|
# check digest
|
|
md5res = entropy.tools.compare_md5(pkg_path, checksum)
|
|
if md5res:
|
|
return 0
|
|
return -2
|
|
|
|
return -1
|
|
|
|
def _match_checksum(self, repository, checksum, download, signatures):
|
|
|
|
self.error_on_not_prepared()
|
|
|
|
sys_settings = self._system_settings
|
|
sys_set_plg_id = \
|
|
etpConst['system_settings_plugins_ids']['client_plugin']
|
|
enabled_hashes = sys_settings[sys_set_plg_id]['misc']['packagehashes']
|
|
|
|
pkg_disk_path = self.__get_fetch_disk_path(download)
|
|
pkg_disk_path_mtime = pkg_disk_path + ".mtime"
|
|
|
|
def do_mtime_validation():
|
|
if not (os.path.isfile(pkg_disk_path_mtime) and \
|
|
os.access(pkg_disk_path_mtime, os.R_OK)):
|
|
return 1
|
|
if not (os.path.isfile(pkg_disk_path) and \
|
|
os.access(pkg_disk_path, os.R_OK)):
|
|
return 2
|
|
|
|
with open(pkg_disk_path_mtime, "r") as mt_f:
|
|
stored_mtime = mt_f.read().strip()
|
|
|
|
# get pkg mtime
|
|
cur_mtime = str(os.path.getmtime(pkg_disk_path))
|
|
if cur_mtime == stored_mtime:
|
|
return 0
|
|
return 1
|
|
|
|
def do_store_mtime():
|
|
if not (os.path.isfile(pkg_disk_path) and \
|
|
os.access(pkg_disk_path, os.R_OK)):
|
|
return
|
|
with open(pkg_disk_path_mtime, "w") as mt_f:
|
|
cur_mtime = str(os.path.getmtime(pkg_disk_path))
|
|
mt_f.write(cur_mtime)
|
|
mt_f.flush()
|
|
|
|
def do_compare_gpg(pkg_path, hash_val):
|
|
|
|
try:
|
|
repo_sec = self.Entropy.RepositorySecurity()
|
|
except RepositorySecurity.GPGServiceNotAvailable:
|
|
return None
|
|
|
|
# check if we have repository pubkey
|
|
try:
|
|
if not repo_sec.is_pubkey_available(repository):
|
|
return None
|
|
except repo_sec.KeyExpired:
|
|
# key is expired
|
|
return None
|
|
|
|
# write gpg signature to disk for verification
|
|
tmp_fd, tmp_path = tempfile.mkstemp()
|
|
os.close(tmp_fd)
|
|
with open(tmp_path, "wb") as tmp_f:
|
|
tmp_f.write(hash_val)
|
|
tmp_f.flush()
|
|
|
|
try:
|
|
# actually verify
|
|
valid, err_msg = repo_sec.verify_file(repository, pkg_path,
|
|
tmp_path)
|
|
finally:
|
|
os.remove(tmp_path)
|
|
|
|
if valid:
|
|
return True
|
|
|
|
if err_msg:
|
|
self.Entropy.output(
|
|
"%s: %s, %s" % (
|
|
darkred(_("Package signature verification error for")),
|
|
purple("GPG"),
|
|
err_msg,
|
|
),
|
|
importance = 0,
|
|
type = "error",
|
|
header = darkred(" ## ")
|
|
)
|
|
return False
|
|
|
|
signature_vry_map = {
|
|
'sha1': entropy.tools.compare_sha1,
|
|
'sha256': entropy.tools.compare_sha256,
|
|
'sha512': entropy.tools.compare_sha512,
|
|
'gpg': do_compare_gpg,
|
|
}
|
|
|
|
def do_signatures_validation(signatures):
|
|
# check signatures, if available
|
|
if isinstance(signatures, dict):
|
|
for hash_type in sorted(signatures):
|
|
hash_val = signatures[hash_type]
|
|
# XXX workaround bug on unreleased
|
|
# entropy versions
|
|
if hash_val in signatures:
|
|
continue
|
|
if hash_val is None:
|
|
continue
|
|
if hash_type not in enabled_hashes:
|
|
self.Entropy.output(
|
|
"%s %s" % (
|
|
purple(hash_type.upper()),
|
|
darkgreen(_("disabled")),
|
|
),
|
|
importance = 0,
|
|
type = "info",
|
|
header = " : "
|
|
)
|
|
continue
|
|
|
|
cmp_func = signature_vry_map.get(hash_type)
|
|
if cmp_func is None:
|
|
continue
|
|
|
|
self.Entropy.output(
|
|
"%s: %s" % (blue(_("Checking package signature")),
|
|
purple(hash_type.upper()),),
|
|
importance = 0,
|
|
type = "info",
|
|
header = red(" ## "),
|
|
back = True
|
|
)
|
|
valid = cmp_func(pkg_disk_path, hash_val)
|
|
if valid is None:
|
|
self.Entropy.output(
|
|
"%s '%s' %s" % (
|
|
darkred(_("Package signature verification")),
|
|
purple(hash_type.upper()),
|
|
darkred(_("temporarily unavailable")),
|
|
),
|
|
importance = 0,
|
|
type = "warning",
|
|
header = darkred(" ## ")
|
|
)
|
|
continue
|
|
if not valid:
|
|
self.Entropy.output(
|
|
"%s: %s %s" % (
|
|
darkred(_("Package signature")),
|
|
purple(hash_type.upper()),
|
|
darkred(_("does not match the recorded one")),
|
|
),
|
|
importance = 0,
|
|
type = "error",
|
|
header = darkred(" ## ")
|
|
)
|
|
return 1
|
|
self.Entropy.output(
|
|
"%s %s" % (
|
|
purple(hash_type.upper()),
|
|
darkgreen(_("matches")),
|
|
),
|
|
importance = 0,
|
|
type = "info",
|
|
header = " : "
|
|
)
|
|
return 0
|
|
|
|
dlcount = 0
|
|
match = False
|
|
|
|
while dlcount <= 5:
|
|
|
|
self.Entropy.output(
|
|
blue(_("Checking package checksum...")),
|
|
importance = 0,
|
|
type = "info",
|
|
header = red(" ## "),
|
|
back = True
|
|
)
|
|
|
|
dlcheck = self.__check_pkg_path_download(download,
|
|
checksum = checksum)
|
|
if dlcheck == 0:
|
|
basef = os.path.basename(download)
|
|
self.Entropy.output(
|
|
"%s: %s" % (
|
|
blue(_("Package checksum matches")),
|
|
darkgreen(basef),
|
|
),
|
|
importance = 0,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
# check if package has been already checked
|
|
dlcheck = do_mtime_validation()
|
|
if dlcheck != 0:
|
|
dlcheck = do_signatures_validation(signatures)
|
|
|
|
if dlcheck == 0:
|
|
do_store_mtime()
|
|
self.pkgmeta['verified'] = True
|
|
match = True
|
|
break # file downloaded successfully
|
|
|
|
if dlcheck != 0:
|
|
dlcount += 1
|
|
mytxt = _("Checksum does not match. Download attempt #%s") % (
|
|
dlcount,
|
|
)
|
|
self.Entropy.output(
|
|
darkred(mytxt),
|
|
importance = 0,
|
|
type = "warning",
|
|
header = darkred(" ## ")
|
|
)
|
|
fetch = self.Entropy.fetch_file_on_mirrors(
|
|
repository,
|
|
download,
|
|
pkg_disk_path,
|
|
checksum,
|
|
fetch_abort_function = self.fetch_abort_function,
|
|
)
|
|
if fetch != 0:
|
|
self.Entropy.output(
|
|
blue(_("Cannot properly fetch package! Quitting.")),
|
|
importance = 0,
|
|
type = "error",
|
|
header = darkred(" ## ")
|
|
)
|
|
return fetch
|
|
|
|
# package is fetched, let's loop one more time
|
|
# to make sure to run all the checksum checks
|
|
continue
|
|
|
|
if not match:
|
|
mytxt = _("Cannot fetch package or checksum does not match")
|
|
mytxt2 = _("Try to download latest repositories")
|
|
for txt in (mytxt, mytxt2,):
|
|
self.Entropy.output(
|
|
"%s." % (blue(txt),),
|
|
importance = 0,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
return 1
|
|
|
|
return 0
|
|
|
|
def multi_match_checksum(self):
|
|
rc = 0
|
|
for repository, download, digest, signatures in \
|
|
self.pkgmeta['multi_checksum_list']:
|
|
|
|
rc = self._match_checksum(repository, digest, download, signatures)
|
|
if rc != 0:
|
|
break
|
|
|
|
return rc
|
|
|
|
def __unpack_package(self):
|
|
|
|
if not self.pkgmeta['merge_from']:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Unpacking package: %s" % (self.pkgmeta['atom'],)
|
|
)
|
|
else:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Merging package: %s" % (self.pkgmeta['atom'],)
|
|
)
|
|
|
|
unpack_dir = self.pkgmeta['unpackdir']
|
|
unpack_dir_raw = self.pkgmeta['unpackdir'].encode('raw_unicode_escape')
|
|
|
|
if os.path.isdir(unpack_dir):
|
|
shutil.rmtree(unpack_dir_raw)
|
|
elif os.path.isfile(unpack_dir):
|
|
os.remove(unpack_dir_raw)
|
|
os.makedirs(self.pkgmeta['imagedir'])
|
|
|
|
if not os.path.isfile(self.pkgmeta['pkgpath']) and \
|
|
not self.pkgmeta['merge_from']:
|
|
|
|
if os.path.isdir(self.pkgmeta['pkgpath']):
|
|
shutil.rmtree(self.pkgmeta['pkgpath'])
|
|
if os.path.islink(self.pkgmeta['pkgpath']):
|
|
os.remove(self.pkgmeta['pkgpath'])
|
|
self.pkgmeta['verified'] = False
|
|
rc = self.fetch_step()
|
|
if rc != 0:
|
|
return rc
|
|
|
|
if not self.pkgmeta['merge_from']:
|
|
|
|
# extract entropy database from package file
|
|
# in order to avoid having to read content data
|
|
# from the repository database, which, in future
|
|
# is allowed to not provide such info.
|
|
pkg_dbdir = os.path.dirname(self.pkgmeta['pkgdbpath'])
|
|
if not os.path.isdir(pkg_dbdir):
|
|
os.makedirs(pkg_dbdir, 0o755)
|
|
|
|
# extract edb
|
|
entropy.tools.dump_entropy_metadata(self.pkgmeta['pkgpath'],
|
|
self.pkgmeta['pkgdbpath'])
|
|
|
|
unpack_tries = 3
|
|
while True:
|
|
unpack_tries -= 1
|
|
try:
|
|
rc = entropy.tools.spawn_function(
|
|
entropy.tools.uncompress_tarball,
|
|
self.pkgmeta['pkgpath'],
|
|
extract_path = self.pkgmeta['imagedir'],
|
|
catch_empty = True
|
|
)
|
|
except EOFError:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]", etpConst['logging']['normal_loglevel_id'],
|
|
"EOFError on " + self.pkgmeta['pkgpath']
|
|
)
|
|
rc = 1
|
|
except:
|
|
# this will make devs to actually catch the
|
|
# right exception and prepare a fix
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Raising Unicode/Pickling Error for " + \
|
|
self.pkgmeta['pkgpath']
|
|
)
|
|
rc = entropy.tools.uncompress_tarball(
|
|
self.pkgmeta['pkgpath'],
|
|
extract_path = self.pkgmeta['imagedir'],
|
|
catch_empty = True
|
|
)
|
|
if rc == 0:
|
|
break
|
|
|
|
if unpack_tries <= 0:
|
|
return rc
|
|
# otherwise, try to download it again
|
|
self.pkgmeta['verified'] = False
|
|
f_rc = self.fetch_step()
|
|
if f_rc != 0:
|
|
return f_rc
|
|
|
|
else:
|
|
|
|
pid = os.fork()
|
|
if pid > 0:
|
|
os.waitpid(pid, 0)
|
|
else:
|
|
self.__fill_image_dir(self.pkgmeta['merge_from'],
|
|
self.pkgmeta['imagedir'])
|
|
os._exit(0)
|
|
|
|
spm_class = self.Entropy.Spm_class()
|
|
# call Spm unpack hook
|
|
return spm_class.entropy_install_unpack_hook(self.Entropy, self.pkgmeta)
|
|
|
|
|
|
def __configure_package(self):
|
|
|
|
try:
|
|
Spm = self.Entropy.Spm()
|
|
except Exception as err:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Source Package Manager not available: %s | %s" % (
|
|
type(Exception), err,
|
|
)
|
|
)
|
|
return 1
|
|
|
|
self.Entropy.output(
|
|
"SPM: %s" % (brown(_("configuration phase")),),
|
|
importance = 0,
|
|
header = red(" ## ")
|
|
)
|
|
return Spm.execute_package_phase(self.pkgmeta, "configure")
|
|
|
|
|
|
def __remove_package(self):
|
|
|
|
self.Entropy.clear_cache()
|
|
|
|
self.Entropy.clientLog.log("[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Removing package: %s" % (self.pkgmeta['removeatom'],))
|
|
|
|
mytxt = "%s: %s" % (
|
|
blue(_("Removing from Entropy")),
|
|
red(self.pkgmeta['removeatom']),
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
automerge_metadata = \
|
|
self.Entropy.installed_repository().retrieveAutomergefiles(
|
|
self.pkgmeta['removeidpackage'], get_dict = True
|
|
)
|
|
self.remove_installed_package(self.pkgmeta['removeidpackage'])
|
|
# commit changes, to avoid users pressing CTRL+C and still having
|
|
# all the db entries in, so we need to commit at every iteration
|
|
self.Entropy.installed_repository().commitChanges()
|
|
|
|
# if another package with the same atom is installed in
|
|
# Entropy db, do not call SPM at all because it would cause
|
|
# to get that package removed from there resulting in matching
|
|
# inconsistencies.
|
|
# -- of course, we need to drop versiontag before being able to look
|
|
# for other pkgs with same atom but different tag (which is an
|
|
# entropy-only metadatum)
|
|
test_atom = entropy.tools.remove_tag(self.pkgmeta['removeatom'])
|
|
others_installed = self.Entropy.installed_repository().getIdpackages(test_atom)
|
|
|
|
# It's obvious that clientdb cannot have more than one idpackage
|
|
# featuring the same "atom" value, but still, let's be fault-tolerant.
|
|
spm_rc = 0
|
|
|
|
if not others_installed:
|
|
spm_rc = self.spm_remove_package()
|
|
|
|
for other_installed in others_installed:
|
|
# we have one installed, we need to update SPM uid
|
|
spm_rc = self.spm_update_package_uid(other_installed,
|
|
self.pkgmeta['removeatom'])
|
|
if spm_rc != 0:
|
|
break # ohi ohi ohi
|
|
|
|
if spm_rc != 0:
|
|
return spm_rc
|
|
|
|
self.remove_content_from_system(self.pkgmeta['removeidpackage'],
|
|
automerge_metadata)
|
|
|
|
return 0
|
|
|
|
def remove_installed_package(self, idpackage):
|
|
"""
|
|
Remove installed package from Entropy installed packages repository.
|
|
|
|
@param idpackage: Entropy Repository package identifier
|
|
@type idpackage: int
|
|
"""
|
|
self.Entropy.installed_repository().removePackage(idpackage, do_commit = False,
|
|
do_cleanup = False)
|
|
|
|
def remove_content_from_system(self, idpackage, automerge_metadata = None):
|
|
"""
|
|
Remove installed package content (files/directories) from live system.
|
|
|
|
@param idpackage: Entropy Repository package identifier
|
|
@type idpackage: int
|
|
@keyword automerge_metadata: Entropy "automerge metadata"
|
|
@type automerge_metadata: dict
|
|
"""
|
|
if automerge_metadata is None:
|
|
automerge_metadata = {}
|
|
|
|
sys_root = etpConst['systemroot']
|
|
# load CONFIG_PROTECT and CONFIG_PROTECT_MASK
|
|
sys_settings = self._system_settings
|
|
protect = self.Entropy.get_installed_package_config_protect(idpackage)
|
|
mask = self.Entropy.get_installed_package_config_protect(idpackage,
|
|
mask = True)
|
|
|
|
sys_set_plg_id = \
|
|
etpConst['system_settings_plugins_ids']['client_plugin']
|
|
col_protect = sys_settings[sys_set_plg_id]['misc']['collisionprotect']
|
|
|
|
# remove files from system
|
|
directories = set()
|
|
directories_cache = set()
|
|
not_removed_due_to_collisions = set()
|
|
colliding_path_messages = set()
|
|
|
|
remove_content = sorted(self.pkgmeta['removecontent'], reverse = True)
|
|
for item in remove_content:
|
|
|
|
if not item:
|
|
continue # empty element??
|
|
|
|
sys_root_item = sys_root + item
|
|
|
|
# collision check
|
|
if col_protect > 0:
|
|
|
|
if self.Entropy.installed_repository().isFileAvailable(item) \
|
|
and os.path.isfile(sys_root_item):
|
|
|
|
# in this way we filter out directories
|
|
colliding_path_messages.add(sys_root_item)
|
|
not_removed_due_to_collisions.add(item)
|
|
continue
|
|
|
|
protected = False
|
|
in_mask = False
|
|
|
|
if (not self.pkgmeta['removeconfig']) and \
|
|
(not self.pkgmeta['diffremoval']):
|
|
|
|
protected_item_test = sys_root_item
|
|
if const_isunicode(protected_item_test):
|
|
protected_item_test = protected_item_test.encode('utf-8')
|
|
|
|
in_mask, protected, x, do_continue = \
|
|
self._handle_config_protect(
|
|
protect, mask, None, protected_item_test,
|
|
do_allocation_check = False, do_quiet = True
|
|
)
|
|
|
|
if do_continue:
|
|
protected = True
|
|
|
|
# when files have not been modified by the user
|
|
# and they are inside a config protect directory
|
|
# we could even remove them directly
|
|
if in_mask:
|
|
|
|
oldprot_md5 = automerge_metadata.get(item)
|
|
if oldprot_md5 and os.path.exists(protected_item_test) and \
|
|
os.access(protected_item_test, os.R_OK):
|
|
|
|
in_system_md5 = entropy.tools.md5sum(
|
|
protected_item_test)
|
|
|
|
if oldprot_md5 == in_system_md5:
|
|
prot_msg = _("Removing config file, never modified")
|
|
mytxt = "%s: %s" % (
|
|
darkgreen(prot_msg),
|
|
blue(item),
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
protected = False
|
|
do_continue = False
|
|
|
|
# Is file or directory a protected item?
|
|
if protected:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['verbose_loglevel_id'],
|
|
"[remove] Protecting config file: %s" % (sys_root_item,)
|
|
)
|
|
mytxt = "[%s] %s: %s" % (
|
|
red(_("remove")),
|
|
brown(_("Protecting config file")),
|
|
sys_root_item,
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "warning",
|
|
header = red(" ## ")
|
|
)
|
|
continue
|
|
|
|
|
|
try:
|
|
os.lstat(sys_root_item)
|
|
|
|
except OSError:
|
|
continue # skip file, does not exist
|
|
|
|
except UnicodeEncodeError:
|
|
msg = _("This package contains a badly encoded file !!!")
|
|
mytxt = brown(msg)
|
|
self.Entropy.output(
|
|
red("QA: ")+mytxt,
|
|
importance = 1,
|
|
type = "warning",
|
|
header = darkred(" ## ")
|
|
)
|
|
continue # file has a really bad encoding
|
|
|
|
if os.path.isdir(sys_root_item) and \
|
|
os.path.islink(sys_root_item):
|
|
# S_ISDIR returns False for directory symlinks,
|
|
# so using os.path.isdir valid directory symlink
|
|
if sys_root_item not in directories_cache:
|
|
directories.add((sys_root_item, "link"))
|
|
directories_cache.add(sys_root_item)
|
|
continue
|
|
|
|
if os.path.isdir(sys_root_item):
|
|
# plain directory
|
|
if sys_root_item not in directories_cache:
|
|
directories.add((sys_root_item, "dir"))
|
|
directories_cache.add(sys_root_item)
|
|
continue
|
|
|
|
# files, symlinks or not
|
|
# just a file or symlink or broken
|
|
# directory symlink (remove now)
|
|
|
|
try:
|
|
os.remove(sys_root_item)
|
|
except OSError as err:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"[remove] Unable to remove %s, error: %s" % (
|
|
sys_root_item, err,)
|
|
)
|
|
continue
|
|
|
|
# add its parent directory
|
|
dirobj = os.path.dirname(sys_root_item)
|
|
if dirobj not in directories_cache:
|
|
if os.path.isdir(dirobj) and os.path.islink(dirobj):
|
|
directories.add((dirobj, "link"))
|
|
elif os.path.isdir(dirobj):
|
|
directories.add((dirobj, "dir"))
|
|
|
|
directories_cache.add(dirobj)
|
|
|
|
|
|
if colliding_path_messages:
|
|
self.Entropy.output(
|
|
"%s:" % (_("Collision found during removal of"),),
|
|
importance = 1,
|
|
type = "warning",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
for path in sorted(colliding_path_messages):
|
|
self.Entropy.output(
|
|
purple(path),
|
|
importance = 0,
|
|
type = "warning",
|
|
header = red(" ## ")
|
|
)
|
|
self.Entropy.clientLog.log("[Package]", etpConst['logging']['normal_loglevel_id'],
|
|
"Collision found during removal of %s - cannot overwrite" % (
|
|
path,)
|
|
)
|
|
|
|
# removing files not removed from removecontent.
|
|
# it happened that boot services not removed due to
|
|
# collisions got removed from their belonging runlevels
|
|
# by postremove step.
|
|
# since this is a set, it is a mapped type, so every
|
|
# other instance around will feature this update
|
|
self.pkgmeta['removecontent'] -= not_removed_due_to_collisions
|
|
|
|
# now handle directories
|
|
directories = sorted(directories, reverse = True)
|
|
while True:
|
|
taint = False
|
|
for directory, dirtype in directories:
|
|
mydir = "%s%s" % (sys_root, directory,)
|
|
if dirtype == "link":
|
|
try:
|
|
mylist = os.listdir(mydir)
|
|
if not mylist:
|
|
try:
|
|
os.remove(mydir)
|
|
taint = True
|
|
except OSError:
|
|
pass
|
|
except OSError:
|
|
pass
|
|
elif dirtype == "dir":
|
|
try:
|
|
mylist = os.listdir(mydir)
|
|
if not mylist:
|
|
try:
|
|
os.rmdir(mydir)
|
|
taint = True
|
|
except OSError:
|
|
pass
|
|
except OSError:
|
|
pass
|
|
|
|
if not taint:
|
|
break
|
|
|
|
del directories_cache
|
|
del directories
|
|
|
|
def _cleanup_package(self, unpack_dir):
|
|
# remove unpack dir
|
|
shutil.rmtree(unpack_dir, True)
|
|
try:
|
|
os.rmdir(unpack_dir)
|
|
except OSError:
|
|
pass
|
|
return 0
|
|
|
|
def __install_package(self):
|
|
|
|
# clear on-disk cache
|
|
self.Entropy.clear_cache()
|
|
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Installing package: %s" % (self.pkgmeta['atom'],)
|
|
)
|
|
|
|
already_protected_config_files = {}
|
|
if self.pkgmeta['removeidpackage'] != -1:
|
|
already_protected_config_files = \
|
|
self.Entropy.installed_repository().retrieveAutomergefiles(
|
|
self.pkgmeta['removeidpackage'], get_dict = True
|
|
)
|
|
|
|
# copy files over - install
|
|
# use fork? (in this case all the changed structures
|
|
# need to be pushed back)
|
|
rc = self.move_image_to_system(already_protected_config_files)
|
|
if rc != 0:
|
|
return rc
|
|
|
|
# inject into database
|
|
mytxt = "%s: %s" % (
|
|
blue(_("Updating database")),
|
|
red(self.pkgmeta['atom']),
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
idpackage = self.add_installed_package()
|
|
|
|
# remove old files and spm stuff
|
|
if self.pkgmeta['removeidpackage'] != -1:
|
|
|
|
# doing a diff removal
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Remove old package: %s" % (self.pkgmeta['removeatom'],)
|
|
)
|
|
|
|
self.Entropy.output(
|
|
blue(_("Cleaning previously installed information...")),
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
spm_rc = self.spm_remove_package()
|
|
if spm_rc != 0:
|
|
return spm_rc
|
|
|
|
self.remove_content_from_system(self.pkgmeta['removeidpackage'],
|
|
automerge_metadata = already_protected_config_files)
|
|
|
|
return self.spm_install_package(idpackage)
|
|
|
|
def spm_install_package(self, idpackage):
|
|
"""
|
|
Call Source Package Manager interface and tell it to register our
|
|
newly installed package.
|
|
|
|
@param idpackage: Entropy repository package identifier
|
|
@type idpackage: int
|
|
@return: execution status
|
|
@rtype: int
|
|
"""
|
|
try:
|
|
Spm = self.Entropy.Spm()
|
|
except Exception as err:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Source Package Manager not available: %s | %s" % (
|
|
type(Exception), err,
|
|
)
|
|
)
|
|
return -1
|
|
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Installing new SPM entry: %s" % (self.pkgmeta['atom'],)
|
|
)
|
|
|
|
spm_uid = Spm.add_installed_package(self.pkgmeta)
|
|
if spm_uid != -1:
|
|
self.Entropy.installed_repository().insertSpmUid(idpackage, spm_uid)
|
|
|
|
return 0
|
|
|
|
def spm_update_package_uid(self, idpackage, entropy_atom):
|
|
"""
|
|
Update Source Package Manager <-> Entropy package identifiers coupling.
|
|
Entropy can handle multiple packages in the same scope from a SPM POV
|
|
(see the "package tag" feature to provide linux kernel module packages
|
|
for different kernel versions). This method just reassigns a new SPM
|
|
unique package identifier to Entropy.
|
|
|
|
@param idpackage: Entropy package identifier bound to given entropy_atom
|
|
@type idpackage: int
|
|
@param entropy_atom: Entropy package atom, must be converted to a valid
|
|
SPM package atom.
|
|
@type entropy_atom: string
|
|
@return: execution status
|
|
@rtype: int
|
|
"""
|
|
try:
|
|
Spm = self.Entropy.Spm()
|
|
except Exception as err:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Source Package Manager not available: %s | %s" % (
|
|
type(Exception), err,
|
|
)
|
|
)
|
|
return -1
|
|
|
|
spm_package = Spm.convert_from_entropy_package_name(entropy_atom)
|
|
try:
|
|
spm_uid = Spm.assign_uid_to_installed_package(spm_package)
|
|
except (SPMError, KeyError,):
|
|
# installed package not available, we must ignore it
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Spm uid not available for Spm package: %s (pkg not avail?)" % (
|
|
spm_package,
|
|
)
|
|
)
|
|
return 0
|
|
|
|
if spm_uid != -1:
|
|
self.Entropy.installed_repository().insertSpmUid(idpackage, spm_uid)
|
|
|
|
return 0
|
|
|
|
|
|
def spm_remove_package(self):
|
|
"""
|
|
Call Source Package Manager interface and tell it to remove our
|
|
just removed package.
|
|
|
|
@return: execution status
|
|
@rtype: int
|
|
"""
|
|
try:
|
|
Spm = self.Entropy.Spm()
|
|
except Exception as err:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Source Package Manager not available: %s | %s" % (
|
|
type(Exception), err,
|
|
)
|
|
)
|
|
return -1
|
|
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Removing from SPM: %s" % (self.pkgmeta['removeatom'],)
|
|
)
|
|
|
|
return Spm.remove_installed_package(self.pkgmeta)
|
|
|
|
|
|
def add_installed_package(self):
|
|
"""
|
|
For internal use only.
|
|
Copy package from repository to installed packages one.
|
|
"""
|
|
|
|
# fetch info
|
|
smart_pkg = self.pkgmeta['smartpackage']
|
|
dbconn = self.Entropy.open_repository(self.pkgmeta['repository'])
|
|
|
|
if smart_pkg or self.pkgmeta['merge_from']:
|
|
|
|
data = dbconn.getPackageData(self.pkgmeta['idpackage'],
|
|
content_insert_formatted = True, get_changelog = False)
|
|
|
|
if self.pkgmeta['removeidpackage'] != -1:
|
|
self.pkgmeta['removecontent'].update(
|
|
self.Entropy.installed_repository().contentDiff(
|
|
self.pkgmeta['removeidpackage'],
|
|
dbconn,
|
|
self.pkgmeta['idpackage']
|
|
)
|
|
)
|
|
|
|
else:
|
|
|
|
# normal repositories
|
|
data = dbconn.getPackageData(self.pkgmeta['idpackage'],
|
|
get_content = False, get_changelog = False)
|
|
|
|
# indexing_override = False : no need to index tables
|
|
# xcache = False : no need to use on-disk cache
|
|
# skipChecks = False : creating missing tables is unwanted,
|
|
# and also no foreign keys update
|
|
# readOnly = True: no need to open in write mode
|
|
pkg_dbconn = self.Entropy.open_generic_database(
|
|
self.pkgmeta['pkgdbpath'], skipChecks = True,
|
|
indexing_override = False, readOnly = True,
|
|
xcache = False)
|
|
|
|
# it is safe to consider that package dbs coming from repos
|
|
# contain only one entry
|
|
pkg_idpackage = sorted(pkg_dbconn.listAllIdpackages())[0]
|
|
content = pkg_dbconn.retrieveContent(
|
|
pkg_idpackage, extended = True,
|
|
formatted = True, insert_formatted = True
|
|
)
|
|
real_idpk = self.pkgmeta['idpackage']
|
|
content = [(real_idpk, x, y,) for orig_idpk, x, y in content]
|
|
data['content'] = content
|
|
|
|
if self.pkgmeta['removeidpackage'] != -1:
|
|
self.pkgmeta['removecontent'].update(
|
|
self.Entropy.installed_repository().contentDiff(
|
|
self.pkgmeta['removeidpackage'],
|
|
pkg_dbconn,
|
|
pkg_idpackage
|
|
)
|
|
)
|
|
|
|
pkg_dbconn.closeDB()
|
|
|
|
# this is needed to make postinstall trigger to work properly
|
|
trigger_content = set((x[1] for x in data['content']))
|
|
self.pkgmeta['triggers']['install']['content'] = trigger_content
|
|
|
|
# open client db
|
|
# always set data['injected'] to False
|
|
# installed packages database SHOULD never have more
|
|
# than one package for scope (key+slot)
|
|
data['injected'] = False
|
|
# spm counter will be set in self._install_package_into_spm_database()
|
|
data['counter'] = -1
|
|
# branch must be always set properly, it could happen it's not
|
|
# when installing packages through their .tbz2s
|
|
data['branch'] = self._system_settings['repositories']['branch']
|
|
# there is no need to store needed paths into db
|
|
if "needed_paths" in data:
|
|
del data['needed_paths']
|
|
# there is no need to store changelog data into db
|
|
if "changelog" in data:
|
|
del data['changelog']
|
|
|
|
idpackage, rev, x = self.Entropy.installed_repository().handlePackage(
|
|
data, forcedRevision = data['revision'], formattedContent = True)
|
|
|
|
# update datecreation
|
|
ctime = time.time()
|
|
self.Entropy.installed_repository().setCreationDate(idpackage, str(ctime))
|
|
|
|
# TODO: remove this in future, drop changelog table
|
|
self.Entropy.installed_repository().dropChangelog()
|
|
|
|
# add idpk to the installedtable
|
|
self.Entropy.installed_repository().dropInstalledPackageFromStore(idpackage)
|
|
self.Entropy.installed_repository().storeInstalledPackage(idpackage,
|
|
self.pkgmeta['repository'], self.pkgmeta['install_source'])
|
|
|
|
automerge_data = self.pkgmeta.get('configprotect_data')
|
|
if automerge_data:
|
|
self.Entropy.installed_repository().insertAutomergefiles(idpackage,
|
|
automerge_data)
|
|
|
|
# clear depends table, this will make clientdb dependstable to be
|
|
# regenerated during the next request (retrieveReverseDependencies)
|
|
self.Entropy.installed_repository().taintReverseDependenciesMetadata()
|
|
return idpackage
|
|
|
|
def __fill_image_dir(self, mergeFrom, image_dir):
|
|
|
|
dbconn = self.Entropy.open_repository(self.pkgmeta['repository'])
|
|
# this is triggered by merge_from pkgmeta metadata
|
|
# even if repositories are allowed to not have content
|
|
# metadata, in this particular case, it is mandatory
|
|
package_content = dbconn.retrieveContent(
|
|
self.pkgmeta['idpackage'], extended = True, formatted = True)
|
|
contents = sorted(package_content)
|
|
|
|
# collect files
|
|
for path in contents:
|
|
# convert back to filesystem str
|
|
encoded_path = path
|
|
path = os.path.join(mergeFrom, encoded_path[1:])
|
|
topath = os.path.join(image_dir, encoded_path[1:])
|
|
path = path.encode('raw_unicode_escape')
|
|
topath = topath.encode('raw_unicode_escape')
|
|
|
|
try:
|
|
exist = os.lstat(path)
|
|
except OSError:
|
|
continue # skip file
|
|
ftype = package_content[encoded_path]
|
|
|
|
if 'dir' == ftype and \
|
|
not stat.S_ISDIR(exist.st_mode) and \
|
|
os.path.isdir(path):
|
|
# workaround for directory symlink issues
|
|
path = os.path.realpath(path)
|
|
|
|
copystat = False
|
|
# if our directory is a symlink instead, then copy the symlink
|
|
if os.path.islink(path):
|
|
tolink = os.readlink(path)
|
|
if os.path.islink(topath):
|
|
os.remove(topath)
|
|
os.symlink(tolink, topath)
|
|
elif os.path.isdir(path):
|
|
if not os.path.isdir(topath):
|
|
os.makedirs(topath)
|
|
copystat = True
|
|
elif os.path.isfile(path):
|
|
if os.path.isfile(topath):
|
|
os.remove(topath) # should never happen
|
|
shutil.copy2(path, topath)
|
|
copystat = True
|
|
|
|
if copystat:
|
|
user = os.stat(path)[stat.ST_UID]
|
|
group = os.stat(path)[stat.ST_GID]
|
|
os.chown(topath, user, group)
|
|
shutil.copystat(path, topath)
|
|
|
|
|
|
def move_image_to_system(self, already_protected_config_files):
|
|
|
|
# load CONFIG_PROTECT and its mask
|
|
protect = self.Entropy.get_package_match_config_protect(
|
|
self.matched_atom)
|
|
mask = self.Entropy.get_package_match_config_protect(
|
|
self.matched_atom, mask = True)
|
|
|
|
# support for unit testing settings
|
|
sys_root = self.pkgmeta.get('unittest_root', '') + \
|
|
etpConst['systemroot']
|
|
sys_set_plg_id = \
|
|
etpConst['system_settings_plugins_ids']['client_plugin']
|
|
misc_data = self._system_settings[sys_set_plg_id]['misc']
|
|
col_protect = misc_data['collisionprotect']
|
|
items_installed = set()
|
|
|
|
# setup image_dir properly
|
|
image_dir = self.pkgmeta['imagedir'][:]
|
|
if sys.hexversion < 0x3000000:
|
|
image_dir = image_dir.encode('utf-8')
|
|
movefile = entropy.tools.movefile
|
|
|
|
def workout_subdir(currentdir, subdir):
|
|
|
|
imagepath_dir = os.path.join(currentdir, subdir)
|
|
rootdir = sys_root + imagepath_dir[len(image_dir):]
|
|
|
|
# handle broken symlinks
|
|
if os.path.islink(rootdir) and not os.path.exists(rootdir):
|
|
# broken symlink
|
|
os.remove(rootdir)
|
|
|
|
# if our directory is a file on the live system
|
|
elif os.path.isfile(rootdir): # really weird...!
|
|
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"WARNING!!! %s is a file when it should be " \
|
|
"a directory !! Removing in 20 seconds..." % (rootdir,)
|
|
)
|
|
mytxt = darkred(_("%s is a file when should be a " \
|
|
"directory !! Removing in 20 seconds...") % (rootdir,))
|
|
|
|
self.Entropy.output(
|
|
red("QA: ")+mytxt,
|
|
importance = 1,
|
|
type = "warning",
|
|
header = red(" !!! ")
|
|
)
|
|
os.remove(rootdir)
|
|
|
|
# if our directory is a symlink instead, then copy the symlink
|
|
if os.path.islink(imagepath_dir):
|
|
|
|
# if our live system features a directory instead of
|
|
# a symlink, we should consider removing the directory
|
|
if not os.path.islink(rootdir) and os.path.isdir(rootdir):
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"WARNING!!! %s is a directory when it should be " \
|
|
"a symlink !! Removing in 20 seconds..." % (
|
|
rootdir,)
|
|
)
|
|
mytxt = "%s: %s" % (
|
|
_("directory expected, symlink found"),
|
|
rootdir,
|
|
)
|
|
mytxt2 = _("Removing in 20 seconds !!")
|
|
for txt in (mytxt, mytxt2,):
|
|
self.Entropy.output(
|
|
darkred("QA: ") + darkred(txt),
|
|
importance = 1,
|
|
type = "warning",
|
|
header = red(" !!! ")
|
|
)
|
|
|
|
# fucking kill it in any case!
|
|
# rootdir must die! die die die die!
|
|
# /me brings chainsaw
|
|
try:
|
|
shutil.rmtree(rootdir, True)
|
|
except (shutil.Error, OSError,) as err:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"WARNING!!! Failed to rm %s " \
|
|
"directory ! [workout_subdir/1]: %s" % (
|
|
rootdir, err,
|
|
)
|
|
)
|
|
|
|
tolink = os.readlink(imagepath_dir)
|
|
live_tolink = None
|
|
if os.path.islink(rootdir):
|
|
live_tolink = os.readlink(rootdir)
|
|
|
|
if tolink != live_tolink:
|
|
if os.path.lexists(rootdir):
|
|
# at this point, it must be a file
|
|
os.remove(rootdir)
|
|
os.symlink(tolink, rootdir)
|
|
|
|
elif not os.path.isdir(rootdir) and not \
|
|
os.access(rootdir, os.R_OK):
|
|
# directory not found, we need to create it
|
|
|
|
try:
|
|
# really force a simple mkdir first of all
|
|
os.mkdir(rootdir)
|
|
except OSError:
|
|
os.makedirs(rootdir)
|
|
|
|
|
|
if not os.path.islink(rootdir) and os.access(rootdir, os.W_OK):
|
|
|
|
# symlink doesn't need permissions, also
|
|
# until os.walk ends they might be broken
|
|
# XXX also, added os.access() check because
|
|
# there might be directories/files unwritable
|
|
# what to do otherwise?
|
|
user = os.stat(imagepath_dir)[stat.ST_UID]
|
|
group = os.stat(imagepath_dir)[stat.ST_GID]
|
|
os.chown(rootdir, user, group)
|
|
shutil.copystat(imagepath_dir, rootdir)
|
|
|
|
item_dir, item_base = os.path.split(rootdir)
|
|
item_dir = os.path.realpath(item_dir)
|
|
item_inst = os.path.join(item_dir, item_base)
|
|
items_installed.add(item_inst)
|
|
|
|
|
|
def workout_file(currentdir, item):
|
|
|
|
fromfile = os.path.join(currentdir, item)
|
|
tofile = sys_root + fromfile[len(image_dir):]
|
|
|
|
if col_protect > 1:
|
|
todbfile = fromfile[len(image_dir):]
|
|
myrc = self._handle_install_collision_protect(tofile,
|
|
todbfile)
|
|
if not myrc:
|
|
return 0
|
|
|
|
prot_old_tofile = tofile[len(sys_root):]
|
|
pre_tofile = tofile[:]
|
|
in_mask, protected, tofile, do_return = \
|
|
self._handle_config_protect(protect, mask, fromfile, tofile)
|
|
|
|
# collect new config automerge data
|
|
if in_mask and os.path.exists(fromfile):
|
|
try:
|
|
prot_md5 = entropy.tools.md5sum(fromfile)
|
|
self.pkgmeta['configprotect_data'].append(
|
|
(prot_old_tofile, prot_md5,))
|
|
except (IOError,) as err:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"WARNING!!! Failed to get md5 of %s " \
|
|
"file ! [workout_file/1]: %s" % (
|
|
fromfile, err,
|
|
)
|
|
)
|
|
|
|
# check if it's really necessary to protect file
|
|
if protected:
|
|
|
|
# second task
|
|
oldprot_md5 = already_protected_config_files.get(
|
|
prot_old_tofile)
|
|
|
|
if oldprot_md5 and os.path.exists(pre_tofile) and \
|
|
os.access(pre_tofile, os.R_OK):
|
|
|
|
try:
|
|
in_system_md5 = entropy.tools.md5sum(pre_tofile)
|
|
except (IOError,):
|
|
# which is a clearly invalid value
|
|
in_system_md5 = "0000"
|
|
|
|
if oldprot_md5 == in_system_md5:
|
|
# we can merge it, files, even if
|
|
# contains changes have not been modified
|
|
# by the user
|
|
msg = _("Automerging config file, never modified")
|
|
mytxt = "%s: %s" % (
|
|
darkgreen(msg),
|
|
blue(pre_tofile),
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
protected = False
|
|
do_return = False
|
|
tofile = pre_tofile
|
|
|
|
if do_return:
|
|
return 0
|
|
|
|
if os.path.realpath(fromfile) == os.path.realpath(tofile) and \
|
|
os.path.islink(tofile):
|
|
# there is a serious issue here, better removing tofile,
|
|
# happened to someone.
|
|
|
|
try:
|
|
# try to cope...
|
|
os.remove(tofile)
|
|
except (OSError, IOError,) as err:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"WARNING!!! Failed to cope to oddity of %s " \
|
|
"file ! [workout_file/2]: %s" % (
|
|
tofile, err,
|
|
)
|
|
)
|
|
|
|
# if our file is a dir on the live system
|
|
if os.path.isdir(tofile) and not os.path.islink(tofile):
|
|
|
|
# really weird...!
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"WARNING!!! %s is a directory when it should " \
|
|
"be a file !! Removing in 20 seconds..." % (tofile,)
|
|
)
|
|
|
|
mytxt = "%s: %s" % (
|
|
_("file expected, directory found"),
|
|
const_convert_to_unicode(tofile),
|
|
)
|
|
mytxt2 = _("Removing in 20 seconds !!")
|
|
for txt in (mytxt, mytxt2,):
|
|
self.Entropy.output(
|
|
darkred("QA: ") + darkred(txt),
|
|
importance = 1,
|
|
type = "warning",
|
|
header = red(" !!! ")
|
|
)
|
|
|
|
try:
|
|
shutil.rmtree(tofile, True)
|
|
except (shutil.Error, IOError,) as err:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"WARNING!!! Failed to cope to oddity of %s " \
|
|
"file ! [workout_file/3]: %s" % (
|
|
tofile, err,
|
|
)
|
|
)
|
|
|
|
# moving file using the raw format
|
|
try:
|
|
done = movefile(fromfile, tofile,
|
|
src_basedir = image_dir)
|
|
except (IOError,) as err:
|
|
# try to move forward, sometimes packages might be
|
|
# fucked up and contain broken things
|
|
if err.errno not in (errno.ENOENT, errno.EACCES,):
|
|
raise
|
|
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"WARNING!!! Error during file move" \
|
|
" to system: %s => %s | IGNORED: %s" % (
|
|
const_convert_to_unicode(fromfile),
|
|
const_convert_to_unicode(tofile),
|
|
err,
|
|
)
|
|
)
|
|
done = True
|
|
|
|
if not done:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"WARNING!!! Error during file move" \
|
|
" to system: %s => %s" % (fromfile, tofile,)
|
|
)
|
|
mytxt = "%s: %s => %s, %s" % (
|
|
_("File move error"),
|
|
const_convert_to_unicode(fromfile),
|
|
const_convert_to_unicode(tofile),
|
|
_("please report"),
|
|
)
|
|
self.Entropy.output(
|
|
red("QA: ")+darkred(mytxt),
|
|
importance = 1,
|
|
type = "warning",
|
|
header = red(" !!! ")
|
|
)
|
|
return 4
|
|
|
|
item_dir = os.path.realpath(os.path.dirname(tofile))
|
|
item_inst = os.path.join(item_dir, os.path.basename(tofile))
|
|
items_installed.add(item_inst)
|
|
|
|
if protected:
|
|
# add to disk cache
|
|
self.Entropy.FileUpdates.add_to_cache(tofile, quiet = True)
|
|
|
|
return 0
|
|
|
|
# merge data into system
|
|
for currentdir, subdirs, files in os.walk(image_dir):
|
|
|
|
# create subdirs
|
|
for subdir in subdirs:
|
|
workout_subdir(currentdir, subdir)
|
|
|
|
for item in files:
|
|
move_st = workout_file(currentdir, item)
|
|
if move_st != 0:
|
|
return move_st
|
|
|
|
# this is useful to avoid the removal of installed
|
|
# files by __remove_package just because
|
|
# there's a difference in the directory path, perhaps,
|
|
# which is not handled correctly by
|
|
# EntropyRepository.contentDiff for obvious reasons
|
|
# (think about stuff in /usr/lib and /usr/lib64,
|
|
# where the latter is just a symlink to the former)
|
|
if self.pkgmeta.get('removecontent'):
|
|
my_remove_content = set()
|
|
for mypath in self.pkgmeta['removecontent']:
|
|
|
|
if not mypath:
|
|
continue # empty?
|
|
|
|
item_dir = os.path.dirname("%s%s" % (sys_root, mypath,))
|
|
item = os.path.join(os.path.realpath(item_dir),
|
|
os.path.basename(mypath))
|
|
|
|
if item in items_installed:
|
|
my_remove_content.add(item)
|
|
|
|
self.pkgmeta['removecontent'] -= my_remove_content
|
|
|
|
return 0
|
|
|
|
def __allocate_masked_file(self, maskfile, fromfile):
|
|
|
|
# check if file and tofile are equal
|
|
if os.path.isfile(maskfile) and os.path.isfile(fromfile):
|
|
old = entropy.tools.md5sum(fromfile)
|
|
new = entropy.tools.md5sum(maskfile)
|
|
if old == new:
|
|
return maskfile, False
|
|
|
|
counter = -1
|
|
newfile = ""
|
|
previousfile = ""
|
|
|
|
while True:
|
|
counter += 1
|
|
txtcounter = str(counter)
|
|
oldtxtcounter = str(counter-1)
|
|
txtcounter_len = 4-len(txtcounter)
|
|
cnt = 0
|
|
while cnt < txtcounter_len:
|
|
txtcounter = "0"+txtcounter
|
|
oldtxtcounter = "0"+oldtxtcounter
|
|
cnt += 1
|
|
newfile = os.path.join(os.path.dirname(maskfile),
|
|
"._cfg" + txtcounter + "_" + os.path.basename(maskfile))
|
|
if counter > 0:
|
|
previousfile = os.path.join(os.path.dirname(maskfile),
|
|
"._cfg" + oldtxtcounter + "_" + os.path.basename(maskfile))
|
|
else:
|
|
previousfile = os.path.join(os.path.dirname(maskfile),
|
|
"._cfg0000_" + os.path.basename(maskfile))
|
|
if not os.path.exists(newfile):
|
|
break
|
|
|
|
if not newfile:
|
|
newfile = os.path.join(os.path.dirname(maskfile),
|
|
"._cfg0000_" + os.path.basename(maskfile))
|
|
else:
|
|
|
|
if os.path.exists(previousfile):
|
|
|
|
# compare fromfile with previousfile
|
|
new = entropy.tools.md5sum(fromfile)
|
|
old = entropy.tools.md5sum(previousfile)
|
|
if new == old:
|
|
return previousfile, False
|
|
|
|
# compare old and new, if they match,
|
|
# suggest previousfile directly
|
|
new = entropy.tools.md5sum(maskfile)
|
|
old = entropy.tools.md5sum(previousfile)
|
|
if new == old:
|
|
return previousfile, False
|
|
|
|
return newfile, True
|
|
|
|
def _handle_config_protect(self, protect, mask, fromfile, tofile,
|
|
do_allocation_check = True, do_quiet = False):
|
|
"""
|
|
Handle configuration file protection. This method contains the logic
|
|
for determining if a file should be protected from overwrite.
|
|
"""
|
|
|
|
protected = False
|
|
tofile_before_protect = tofile
|
|
do_continue = False
|
|
in_mask = False
|
|
encoded_protect = [x.encode('raw_unicode_escape') for x in protect]
|
|
|
|
if tofile in encoded_protect:
|
|
protected = True
|
|
in_mask = True
|
|
|
|
elif os.path.dirname(tofile) in encoded_protect:
|
|
protected = True
|
|
in_mask = True
|
|
|
|
else:
|
|
tofile_testdir = os.path.dirname(tofile)
|
|
old_tofile_testdir = None
|
|
while tofile_testdir != old_tofile_testdir:
|
|
if tofile_testdir in encoded_protect:
|
|
protected = True
|
|
in_mask = True
|
|
break
|
|
old_tofile_testdir = tofile_testdir
|
|
tofile_testdir = os.path.dirname(tofile_testdir)
|
|
|
|
if protected: # check if perhaps, file is masked, so unprotected
|
|
newmask = [x.encode('raw_unicode_escape') for x in mask]
|
|
|
|
if tofile in newmask:
|
|
protected = False
|
|
in_mask = False
|
|
|
|
elif os.path.dirname(tofile) in newmask:
|
|
protected = False
|
|
in_mask = False
|
|
|
|
else:
|
|
tofile_testdir = os.path.dirname(tofile)
|
|
old_tofile_testdir = None
|
|
while tofile_testdir != old_tofile_testdir:
|
|
if tofile_testdir in newmask:
|
|
protected = False
|
|
in_mask = False
|
|
break
|
|
old_tofile_testdir = tofile_testdir
|
|
tofile_testdir = os.path.dirname(tofile_testdir)
|
|
|
|
if not os.path.lexists(tofile):
|
|
protected = False # file doesn't exist
|
|
|
|
# check if it's a text file
|
|
if protected and os.path.isfile(tofile) and os.access(tofile, os.R_OK):
|
|
protected = entropy.tools.istextfile(tofile)
|
|
in_mask = protected
|
|
else:
|
|
protected = False # it's not a file
|
|
|
|
if not protected:
|
|
return in_mask, protected, tofile, do_continue
|
|
|
|
## ##
|
|
# file is protected #
|
|
##__________________##
|
|
|
|
sys_set_plg_id = \
|
|
etpConst['system_settings_plugins_ids']['client_plugin']
|
|
client_settings = self._system_settings[sys_set_plg_id]
|
|
misc_settings = client_settings['misc']
|
|
|
|
# check if protection is disabled for this element
|
|
if tofile in misc_settings['configprotectskip']:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Skipping config file installation/removal, " \
|
|
"as stated in client.conf: %s" % (tofile,)
|
|
)
|
|
if not do_quiet:
|
|
mytxt = "%s: %s" % (
|
|
_("Skipping file installation/removal"),
|
|
tofile,
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "warning",
|
|
header = darkred(" ## ")
|
|
)
|
|
do_continue = True
|
|
return in_mask, protected, tofile, do_continue
|
|
|
|
## ##
|
|
# file is protected (2) #
|
|
##______________________##
|
|
|
|
prot_status = True
|
|
if do_allocation_check:
|
|
tofile, prot_status = self.__allocate_masked_file(tofile, fromfile)
|
|
|
|
if not prot_status:
|
|
# a protected file with the same content
|
|
# is already in place, so not going to protect
|
|
# the same file twice
|
|
protected = False
|
|
return in_mask, protected, tofile, do_continue
|
|
|
|
## ##
|
|
# file is protected (3) #
|
|
##______________________##
|
|
|
|
oldtofile = tofile
|
|
if oldtofile.find("._cfg") != -1:
|
|
oldtofile = os.path.join(os.path.dirname(oldtofile),
|
|
os.path.basename(oldtofile)[10:])
|
|
|
|
if not do_quiet:
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"Protecting config file: %s" % (oldtofile,)
|
|
)
|
|
mytxt = red("%s: %s") % (_("Protecting config file"), oldtofile,)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "warning",
|
|
header = darkred(" ## ")
|
|
)
|
|
|
|
return in_mask, protected, tofile, do_continue
|
|
|
|
|
|
def _handle_install_collision_protect(self, tofile, todbfile):
|
|
|
|
avail = self.Entropy.installed_repository().isFileAvailable(todbfile,
|
|
get_id = True)
|
|
|
|
if (self.pkgmeta['removeidpackage'] not in avail) and avail:
|
|
mytxt = darkred(_("Collision found during install for"))
|
|
mytxt += " %s - %s" % (
|
|
blue(tofile),
|
|
darkred(_("cannot overwrite")),
|
|
)
|
|
self.Entropy.output(
|
|
red("QA: ")+mytxt,
|
|
importance = 1,
|
|
type = "warning",
|
|
header = darkred(" ## ")
|
|
)
|
|
self.Entropy.clientLog.log(
|
|
"[Package]",
|
|
etpConst['logging']['normal_loglevel_id'],
|
|
"WARNING!!! Collision found during install " \
|
|
"for %s - cannot overwrite" % (tofile,)
|
|
)
|
|
return False
|
|
|
|
return True
|
|
|
|
def sources_fetch_step(self):
|
|
self.error_on_not_prepared()
|
|
|
|
down_data = self.pkgmeta['download']
|
|
down_keys = list(down_data.keys())
|
|
d_cache = set()
|
|
rc = 0
|
|
key_cache = [os.path.basename(x) for x in down_keys]
|
|
|
|
for key in sorted(down_keys):
|
|
|
|
key_name = os.path.basename(key)
|
|
if key_name in d_cache: continue
|
|
# first fine wins
|
|
|
|
for url in down_data[key]:
|
|
|
|
file_name = os.path.basename(url)
|
|
if self.pkgmeta.get('fetch_path'):
|
|
dest_file = os.path.join(self.pkgmeta['fetch_path'],
|
|
file_name)
|
|
else:
|
|
dest_file = os.path.join(self.pkgmeta['unpackdir'],
|
|
file_name)
|
|
|
|
rc = self._fetch_source(url, dest_file)
|
|
if rc == 0:
|
|
d_cache.add(key_name)
|
|
break
|
|
|
|
key_cache.remove(key_name)
|
|
if rc != 0 and key_name not in key_cache:
|
|
break
|
|
|
|
rc = 0
|
|
|
|
return rc
|
|
|
|
def _fetch_source(self, url, dest_file):
|
|
rc = 1
|
|
try:
|
|
|
|
mytxt = "%s: %s" % (blue(_("Downloading")), brown(url),)
|
|
# now fetch the new one
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
rc, data_transfer, resumed = self.Entropy._fetch_file(
|
|
url,
|
|
dest_file,
|
|
None,
|
|
None,
|
|
False,
|
|
fetch_file_abort_function = self.fetch_abort_function
|
|
)
|
|
if rc == 0:
|
|
mytxt = blue("%s: ") % (_("Successfully downloaded from"),)
|
|
mytxt += red(entropy.tools.spliturl(url)[1])
|
|
human_bytes = entropy.tools.bytes_into_human(data_transfer)
|
|
mytxt += " %s %s/%s" % (_("at"), human_bytes, _("second"),)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
self.Entropy.output(
|
|
"%s: %s" % (blue(_("Local path")), brown(dest_file),),
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" # ")
|
|
)
|
|
else:
|
|
error_message = blue("%s: %s") % (
|
|
_("Error downloading from"),
|
|
red(entropy.tools.spliturl(url)[1]),
|
|
)
|
|
# something bad happened
|
|
if rc == -1:
|
|
error_message += " - %s." % (
|
|
_("file not available on this mirror"),
|
|
)
|
|
elif rc == -3:
|
|
error_message += " - not found."
|
|
elif rc == -100:
|
|
error_message += " - %s." % (_("discarded download"),)
|
|
else:
|
|
error_message += " - %s: %s" % (_("unknown reason"), rc,)
|
|
self.Entropy.output(
|
|
error_message,
|
|
importance = 1,
|
|
type = "warning",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
return rc
|
|
|
|
def fetch_step(self):
|
|
self.error_on_not_prepared()
|
|
|
|
mytxt = "%s: %s" % (blue(_("Downloading archive")),
|
|
red(os.path.basename(self.pkgmeta['download'])),)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
pkg_disk_path = self.__get_fetch_disk_path(self.pkgmeta['download'])
|
|
|
|
rc = 0
|
|
if not self.pkgmeta['verified']:
|
|
|
|
rc = self.Entropy.fetch_file_on_mirrors(
|
|
self.pkgmeta['repository'],
|
|
self.pkgmeta['download'],
|
|
pkg_disk_path,
|
|
self.pkgmeta['checksum'],
|
|
fetch_abort_function = self.fetch_abort_function
|
|
)
|
|
|
|
if rc == 0:
|
|
return 0
|
|
|
|
mytxt = "%s. %s: %s" % (
|
|
red(_("Package cannot be fetched. Try to update repositories")),
|
|
blue(_("Error")),
|
|
rc,
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "error",
|
|
header = darkred(" ## ")
|
|
)
|
|
|
|
return rc
|
|
|
|
def multi_fetch_step(self):
|
|
self.error_on_not_prepared()
|
|
|
|
m_fetch_len = len(self.pkgmeta['multi_fetch_list'])
|
|
mytxt = "%s: %s %s" % (
|
|
blue(_("Downloading")),
|
|
darkred(str(m_fetch_len)),
|
|
_("archives"),
|
|
)
|
|
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
rc, err_list = self.Entropy.fetch_files_on_mirrors(
|
|
self.pkgmeta['multi_fetch_list'],
|
|
self.pkgmeta['checksum'],
|
|
fetch_abort_function = self.fetch_abort_function
|
|
)
|
|
|
|
if rc == 0:
|
|
return 0
|
|
|
|
mytxt = _("Some packages cannot be fetched")
|
|
mytxt2 = _("Try to update your repositories and retry")
|
|
mytxt3 = "%s: %s" % (brown(_("Error")), bold(str(rc)),)
|
|
for txt in (mytxt, mytxt2,):
|
|
self.Entropy.output(
|
|
"%s." % (darkred(txt),),
|
|
importance = 0,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
self.Entropy.output(
|
|
mytxt3,
|
|
importance = 0,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
for repo, fname, cksum, signatures in err_list:
|
|
self.Entropy.output(
|
|
"[%s|%s] %s" % (blue(repo), darkgreen(cksum), darkred(fname),),
|
|
importance = 1,
|
|
type = "error",
|
|
header = darkred(" # ")
|
|
)
|
|
|
|
return rc
|
|
|
|
def fetch_not_available_step(self):
|
|
self.Entropy.output(
|
|
blue(_("Package cannot be downloaded, unknown error.")),
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
return 0
|
|
|
|
def vanished_step(self):
|
|
self.Entropy.output(
|
|
blue(_("Installed package in queue vanished, skipping.")),
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
return 0
|
|
|
|
def checksum_step(self):
|
|
self.error_on_not_prepared()
|
|
return self._match_checksum(self.pkgmeta['repository'],
|
|
self.pkgmeta['checksum'], self.pkgmeta['download'],
|
|
self.pkgmeta['signatures'])
|
|
|
|
def multi_checksum_step(self):
|
|
self.error_on_not_prepared()
|
|
return self.multi_match_checksum()
|
|
|
|
def unpack_step(self):
|
|
self.error_on_not_prepared()
|
|
|
|
if not self.pkgmeta['merge_from']:
|
|
mytxt = "%s: %s" % (
|
|
blue(_("Unpacking package")),
|
|
red(os.path.basename(self.pkgmeta['download'])),
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
else:
|
|
mytxt = "%s: %s" % (
|
|
blue(_("Merging package")),
|
|
red(os.path.basename(self.pkgmeta['atom'])),
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
rc = self.__unpack_package()
|
|
if rc != 0:
|
|
if rc == 512:
|
|
errormsg = "%s. %s. %s: 512" % (
|
|
red(_("You are running out of disk space")),
|
|
red(_("I bet, you're probably Michele")),
|
|
blue(_("Error")),
|
|
)
|
|
else:
|
|
msg = _("An error occured while trying to unpack the package")
|
|
errormsg = "%s. %s. %s: %s" % (
|
|
red(msg),
|
|
red(_("Check if your system is healthy")),
|
|
blue(_("Error")),
|
|
rc,
|
|
)
|
|
self.Entropy.output(
|
|
errormsg,
|
|
importance = 1,
|
|
type = "error",
|
|
header = red(" ## ")
|
|
)
|
|
return rc
|
|
|
|
def install_step(self):
|
|
self.error_on_not_prepared()
|
|
mytxt = "%s: %s" % (
|
|
blue(_("Installing package")),
|
|
red(self.pkgmeta['atom']),
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
if self.pkgmeta.get('description'):
|
|
mytxt = "[%s]" % (purple(self.pkgmeta.get('description')),)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
rc = self.__install_package()
|
|
if rc != 0:
|
|
mytxt = "%s. %s. %s: %s" % (
|
|
red(_("An error occured while trying to install the package")),
|
|
red(_("Check if your system is healthy")),
|
|
blue(_("Error")),
|
|
rc,
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "error",
|
|
header = red(" ## ")
|
|
)
|
|
return rc
|
|
|
|
def remove_step(self):
|
|
self.error_on_not_prepared()
|
|
mytxt = "%s: %s" % (
|
|
blue(_("Removing data")),
|
|
red(self.pkgmeta['removeatom']),
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
rc = self.__remove_package()
|
|
if rc != 0:
|
|
mytxt = "%s. %s. %s: %s" % (
|
|
red(_("An error occured while trying to remove the package")),
|
|
red(_("Check if you have enough disk space on your hard disk")),
|
|
blue(_("Error")),
|
|
rc,
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "error",
|
|
header = red(" ## ")
|
|
)
|
|
return rc
|
|
|
|
def cleanup_step(self):
|
|
self.error_on_not_prepared()
|
|
mytxt = "%s: %s" % (
|
|
blue(_("Cleaning")),
|
|
red(self.pkgmeta['atom']),
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
self._cleanup_package(self.pkgmeta['unpackdir'])
|
|
# we don't care if cleanupPackage fails since it's not critical
|
|
return 0
|
|
|
|
def logmessages_step(self):
|
|
for msg in self.pkgmeta['messages']:
|
|
self.Entropy.clientLog.write(">>> "+msg)
|
|
return 0
|
|
|
|
def postinstall_step(self):
|
|
self.error_on_not_prepared()
|
|
pkgdata = self.pkgmeta['triggers'].get('install')
|
|
if pkgdata:
|
|
trigger = self.Entropy.Triggers('postinstall', pkgdata, self.action)
|
|
do = trigger.prepare()
|
|
if do:
|
|
trigger.run()
|
|
trigger.kill()
|
|
del pkgdata
|
|
return 0
|
|
|
|
def preinstall_step(self):
|
|
self.error_on_not_prepared()
|
|
pkgdata = self.pkgmeta['triggers'].get('install')
|
|
if pkgdata:
|
|
|
|
trigger = self.Entropy.Triggers('preinstall', pkgdata, self.action)
|
|
do = trigger.prepare()
|
|
if self.pkgmeta.get("diffremoval") and do:
|
|
# diffremoval is true only when the
|
|
# removal is triggered by a package install
|
|
remdata = self.pkgmeta['triggers'].get('remove')
|
|
if remdata:
|
|
r_trigger = self.Entropy.Triggers('preremove', remdata,
|
|
self.action)
|
|
r_trigger.prepare()
|
|
r_trigger.triggers = [x for x in trigger.triggers if x \
|
|
not in r_trigger.triggers]
|
|
r_trigger.kill()
|
|
del remdata
|
|
if do:
|
|
trigger.run()
|
|
trigger.kill()
|
|
|
|
del pkgdata
|
|
return 0
|
|
|
|
def preremove_step(self):
|
|
self.error_on_not_prepared()
|
|
remdata = self.pkgmeta['triggers'].get('remove')
|
|
if remdata:
|
|
trigger = self.Entropy.Triggers('preremove', remdata, self.action)
|
|
do = trigger.prepare()
|
|
if do:
|
|
trigger.run()
|
|
trigger.kill()
|
|
del remdata
|
|
return 0
|
|
|
|
def postremove_step(self):
|
|
self.error_on_not_prepared()
|
|
remdata = self.pkgmeta['triggers'].get('remove')
|
|
if remdata:
|
|
|
|
trigger = self.Entropy.Triggers('postremove', remdata, self.action)
|
|
do = trigger.prepare()
|
|
if self.pkgmeta['diffremoval'] and \
|
|
(self.pkgmeta.get("atom") is not None) and do:
|
|
# diffremoval is true only when the remove
|
|
# action is triggered by pkgs install
|
|
pkgdata = self.pkgmeta['triggers'].get('install')
|
|
if pkgdata:
|
|
i_trigger = self.Entropy.Triggers('postinstall', pkgdata,
|
|
self.action)
|
|
i_trigger.prepare()
|
|
i_trigger.triggers = [x for x in trigger.triggers if x \
|
|
not in i_trigger.triggers]
|
|
i_trigger.kill()
|
|
del pkgdata
|
|
if do:
|
|
trigger.run()
|
|
trigger.kill()
|
|
|
|
del remdata
|
|
return 0
|
|
|
|
def removeconflict_step(self):
|
|
self.error_on_not_prepared()
|
|
|
|
for idpackage in self.pkgmeta['conflicts']:
|
|
if not self.Entropy.installed_repository().isIdpackageAvailable(idpackage):
|
|
continue
|
|
|
|
pkg = self.Entropy.Package()
|
|
pkg.prepare((idpackage,), "remove_conflict",
|
|
self.pkgmeta['remove_metaopts'])
|
|
|
|
rc = pkg.run(xterm_header = self.xterm_title)
|
|
pkg.kill()
|
|
if rc != 0:
|
|
return rc
|
|
|
|
return 0
|
|
|
|
def config_step(self):
|
|
self.error_on_not_prepared()
|
|
|
|
mytxt = "%s: %s" % (
|
|
blue(_("Configuring package")),
|
|
red(self.pkgmeta['atom']),
|
|
)
|
|
self.Entropy.output(
|
|
mytxt,
|
|
importance = 1,
|
|
type = "info",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
conf_rc = self.__configure_package()
|
|
if conf_rc == 1:
|
|
mytxt = _("An error occured while trying to configure the package")
|
|
mytxt2 = "%s. %s: %s" % (
|
|
red(_("Make sure that your system is healthy")),
|
|
blue(_("Error")),
|
|
conf_rc,
|
|
)
|
|
self.Entropy.output(
|
|
darkred(mytxt),
|
|
importance = 1,
|
|
type = "error",
|
|
header = red(" ## ")
|
|
)
|
|
self.Entropy.output(
|
|
mytxt2,
|
|
importance = 1,
|
|
type = "error",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
elif conf_rc == 2:
|
|
mytxt = _("An error occured while trying to configure the package")
|
|
mytxt2 = "%s. %s: %s" % (
|
|
red(_("It seems that Source Package Manager entry is missing")),
|
|
blue(_("Error")),
|
|
conf_rc,
|
|
)
|
|
self.Entropy.output(
|
|
darkred(mytxt),
|
|
importance = 1,
|
|
type = "error",
|
|
header = red(" ## ")
|
|
)
|
|
self.Entropy.output(
|
|
mytxt2,
|
|
importance = 1,
|
|
type = "error",
|
|
header = red(" ## ")
|
|
)
|
|
|
|
return conf_rc
|
|
|
|
def run_stepper(self, xterm_header):
|
|
if xterm_header is None:
|
|
xterm_header = ""
|
|
|
|
if 'remove_installed_vanished' in self.pkgmeta:
|
|
self.xterm_title += ' %s' % (_("Installed package vanished"),)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
rc = self.vanished_step()
|
|
return rc
|
|
|
|
if 'fetch_not_available' in self.pkgmeta:
|
|
self.xterm_title += ' %s' % (_("Fetch not available"),)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
rc = self.fetch_not_available_step()
|
|
return rc
|
|
|
|
def do_fetch():
|
|
self.xterm_title += ' %s: %s' % (
|
|
_("Fetching"),
|
|
os.path.basename(self.pkgmeta['download']),
|
|
)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.fetch_step()
|
|
|
|
def do_multi_fetch():
|
|
self.xterm_title += ' %s: %s %s' % (_("Multi Fetching"),
|
|
len(self.pkgmeta['multi_fetch_list']), _("packages"),)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.multi_fetch_step()
|
|
|
|
def do_sources_fetch():
|
|
self.xterm_title += ' %s: %s' % (
|
|
_("Fetching sources"),
|
|
os.path.basename(self.pkgmeta['atom']),)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.sources_fetch_step()
|
|
|
|
def do_checksum():
|
|
self.xterm_title += ' %s: %s' % (_("Verifying"),
|
|
os.path.basename(self.pkgmeta['download']),)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.checksum_step()
|
|
|
|
def do_multi_checksum():
|
|
self.xterm_title += ' %s: %s %s' % (_("Multi Verification"),
|
|
len(self.pkgmeta['multi_checksum_list']), _("packages"),)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.multi_checksum_step()
|
|
|
|
def do_unpack():
|
|
if not self.pkgmeta['merge_from']:
|
|
mytxt = _("Unpacking")
|
|
self.xterm_title += ' %s: %s' % (
|
|
mytxt,
|
|
os.path.basename(self.pkgmeta['download']),
|
|
)
|
|
else:
|
|
mytxt = _("Merging")
|
|
self.xterm_title += ' %s: %s' % (
|
|
mytxt,
|
|
os.path.basename(self.pkgmeta['atom']),
|
|
)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.unpack_step()
|
|
|
|
def do_remove_conflicts():
|
|
return self.removeconflict_step()
|
|
|
|
def do_install():
|
|
self.xterm_title += ' %s: %s' % (
|
|
_("Installing"),
|
|
self.pkgmeta['atom'],
|
|
)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.install_step()
|
|
|
|
def do_remove():
|
|
self.xterm_title += ' %s: %s' % (
|
|
_("Removing"),
|
|
self.pkgmeta['removeatom'],
|
|
)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.remove_step()
|
|
|
|
def do_logmessages():
|
|
return self.logmessages_step()
|
|
|
|
def do_cleanup():
|
|
self.xterm_title += ' %s: %s' % (
|
|
_("Cleaning"),
|
|
self.pkgmeta['atom'],
|
|
)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.cleanup_step()
|
|
|
|
def do_postinstall():
|
|
self.xterm_title += ' %s: %s' % (
|
|
_("Postinstall"),
|
|
self.pkgmeta['atom'],
|
|
)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.postinstall_step()
|
|
|
|
def do_preinstall():
|
|
self.xterm_title += ' %s: %s' % (
|
|
_("Preinstall"),
|
|
self.pkgmeta['atom'],
|
|
)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.preinstall_step()
|
|
|
|
def do_preremove():
|
|
self.xterm_title += ' %s: %s' % (
|
|
_("Preremove"),
|
|
self.pkgmeta['removeatom'],
|
|
)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.preremove_step()
|
|
|
|
def do_postremove():
|
|
self.xterm_title += ' %s: %s' % (
|
|
_("Postremove"),
|
|
self.pkgmeta['removeatom'],
|
|
)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.postremove_step()
|
|
|
|
def do_config():
|
|
self.xterm_title += ' %s: %s' % (
|
|
_("Configuring"),
|
|
self.pkgmeta['atom'],
|
|
)
|
|
self.Entropy.set_title(self.xterm_title)
|
|
return self.config_step()
|
|
|
|
steps_data = {
|
|
"fetch": do_fetch,
|
|
"multi_fetch": do_multi_fetch,
|
|
"multi_checksum": do_multi_checksum,
|
|
"sources_fetch": do_sources_fetch,
|
|
"checksum": do_checksum,
|
|
"unpack": do_unpack,
|
|
"remove_conflicts": do_remove_conflicts,
|
|
"install": do_install,
|
|
"remove": do_remove,
|
|
"logmessages": do_logmessages,
|
|
"cleanup": do_cleanup,
|
|
"postinstall": do_postinstall,
|
|
"preinstall": do_preinstall,
|
|
"postremove": do_postremove,
|
|
"preremove": do_preremove,
|
|
"config": do_config,
|
|
}
|
|
|
|
rc = 0
|
|
for step in self.pkgmeta['steps']:
|
|
self.xterm_title = xterm_header
|
|
rc = steps_data.get(step)()
|
|
if rc != 0:
|
|
break
|
|
return rc
|
|
|
|
|
|
def run(self, xterm_header = None):
|
|
self.error_on_not_prepared()
|
|
|
|
gave_up = self.Entropy.lock_check(self.Entropy.resources_check_lock)
|
|
if gave_up:
|
|
return 20
|
|
|
|
locked = self.Entropy.application_lock_check()
|
|
if locked:
|
|
return 21
|
|
|
|
# lock
|
|
acquired = self.Entropy.resources_create_lock()
|
|
if not acquired:
|
|
self.Entropy.output(
|
|
blue(_("Cannot acquire Entropy resources lock.")),
|
|
importance = 2,
|
|
type = "error",
|
|
header = darkred(" ## ")
|
|
)
|
|
return 4 # app locked during lock acquire
|
|
try:
|
|
rc = self.run_stepper(xterm_header)
|
|
finally:
|
|
self.Entropy.resources_remove_lock()
|
|
|
|
if rc != 0:
|
|
self.Entropy.output(
|
|
blue(_("An error occured. Action aborted.")),
|
|
importance = 2,
|
|
type = "error",
|
|
header = darkred(" ## ")
|
|
)
|
|
return rc
|
|
|
|
def prepare(self, matched_atom, action, metaopts = None):
|
|
self.error_on_prepared()
|
|
|
|
self.check_action_validity(action)
|
|
|
|
self.action = action
|
|
self.matched_atom = matched_atom
|
|
|
|
if metaopts is None:
|
|
metaopts = {}
|
|
self.metaopts = metaopts
|
|
|
|
# generate metadata dictionary
|
|
self.generate_metadata()
|
|
|
|
def generate_metadata(self):
|
|
self.error_on_prepared()
|
|
|
|
self.check_action_validity(self.action)
|
|
|
|
if self.action == "fetch":
|
|
self.__generate_fetch_metadata()
|
|
elif self.action == "multi_fetch":
|
|
self.__generate_multi_fetch_metadata()
|
|
elif self.action in ("remove", "remove_conflict"):
|
|
self.__generate_remove_metadata()
|
|
elif self.action == "install":
|
|
self.__generate_install_metadata()
|
|
elif self.action == "source":
|
|
self.__generate_fetch_metadata(sources = True)
|
|
elif self.action == "config":
|
|
self.__generate_config_metadata()
|
|
|
|
self.__prepared = True
|
|
|
|
def __generate_remove_metadata(self):
|
|
|
|
self.pkgmeta.clear()
|
|
idpackage = self.matched_atom[0]
|
|
|
|
if not self.Entropy.installed_repository().isIdpackageAvailable(idpackage):
|
|
self.pkgmeta['remove_installed_vanished'] = True
|
|
return 0
|
|
|
|
self.pkgmeta['idpackage'] = idpackage
|
|
self.pkgmeta['removeidpackage'] = idpackage
|
|
self.pkgmeta['configprotect_data'] = []
|
|
self.pkgmeta['triggers'] = {}
|
|
self.pkgmeta['removeatom'] = \
|
|
self.Entropy.installed_repository().retrieveAtom(idpackage)
|
|
self.pkgmeta['slot'] = \
|
|
self.Entropy.installed_repository().retrieveSlot(idpackage)
|
|
self.pkgmeta['versiontag'] = \
|
|
self.Entropy.installed_repository().retrieveTag(idpackage)
|
|
self.pkgmeta['diffremoval'] = False
|
|
|
|
remove_config = False
|
|
if 'removeconfig' in self.metaopts:
|
|
remove_config = self.metaopts.get('removeconfig')
|
|
self.pkgmeta['removeconfig'] = remove_config
|
|
|
|
self.pkgmeta['removecontent'] = \
|
|
self.Entropy.installed_repository().retrieveContent(idpackage)
|
|
self.pkgmeta['triggers']['remove'] = \
|
|
self.Entropy.installed_repository().getTriggerInfo(idpackage)
|
|
self.pkgmeta['triggers']['remove']['removecontent'] = \
|
|
self.pkgmeta['removecontent']
|
|
self.pkgmeta['triggers']['remove']['accept_license'] = \
|
|
self.Entropy.installed_repository().retrieveLicensedataKeys(idpackage)
|
|
|
|
self.pkgmeta['steps'] = [
|
|
"preremove", "remove", "postremove"
|
|
]
|
|
|
|
return 0
|
|
|
|
def __generate_config_metadata(self):
|
|
self.pkgmeta.clear()
|
|
idpackage = self.matched_atom[0]
|
|
|
|
self.pkgmeta['atom'] = \
|
|
self.Entropy.installed_repository().retrieveAtom(idpackage)
|
|
key, slot = self.Entropy.installed_repository().retrieveKeySlot(idpackage)
|
|
self.pkgmeta['key'], self.pkgmeta['slot'] = key, slot
|
|
self.pkgmeta['version'] = \
|
|
self.Entropy.installed_repository().retrieveVersion(idpackage)
|
|
self.pkgmeta['category'] = \
|
|
self.Entropy.installed_repository().retrieveCategory(idpackage)
|
|
self.pkgmeta['name'] = \
|
|
self.Entropy.installed_repository().retrieveName(idpackage)
|
|
self.pkgmeta['accept_license'] = \
|
|
self.Entropy.installed_repository().retrieveLicensedataKeys(idpackage)
|
|
self.pkgmeta['steps'] = []
|
|
self.pkgmeta['steps'].append("config")
|
|
|
|
return 0
|
|
|
|
def __generate_install_metadata(self):
|
|
self.pkgmeta.clear()
|
|
|
|
idpackage, repository = self.matched_atom
|
|
self.pkgmeta['idpackage'] = idpackage
|
|
self.pkgmeta['repository'] = repository
|
|
|
|
# fetch abort function
|
|
if 'fetch_abort_function' in self.metaopts:
|
|
self.fetch_abort_function = \
|
|
self.metaopts.pop('fetch_abort_function')
|
|
|
|
install_source = etpConst['install_sources']['unknown']
|
|
meta_inst_source = self.metaopts.get('install_source', install_source)
|
|
if meta_inst_source in list(etpConst['install_sources'].values()):
|
|
install_source = meta_inst_source
|
|
self.pkgmeta['install_source'] = install_source
|
|
|
|
self.pkgmeta['configprotect_data'] = []
|
|
dbconn = self.Entropy.open_repository(repository)
|
|
self.pkgmeta['triggers'] = {}
|
|
self.pkgmeta['atom'] = dbconn.retrieveAtom(idpackage)
|
|
self.pkgmeta['slot'] = dbconn.retrieveSlot(idpackage)
|
|
|
|
ver, tag, rev = dbconn.getVersioningData(idpackage)
|
|
self.pkgmeta['version'] = ver
|
|
self.pkgmeta['versiontag'] = tag
|
|
self.pkgmeta['revision'] = rev
|
|
|
|
self.pkgmeta['category'] = dbconn.retrieveCategory(idpackage)
|
|
self.pkgmeta['download'] = dbconn.retrieveDownloadURL(idpackage)
|
|
self.pkgmeta['name'] = dbconn.retrieveName(idpackage)
|
|
self.pkgmeta['messages'] = dbconn.retrieveMessages(idpackage)
|
|
self.pkgmeta['checksum'] = dbconn.retrieveDigest(idpackage)
|
|
sha1, sha256, sha512, gpg = dbconn.retrieveSignatures(idpackage)
|
|
signatures = {
|
|
'sha1': sha1,
|
|
'sha256': sha256,
|
|
'sha512': sha512,
|
|
'gpg': gpg,
|
|
}
|
|
self.pkgmeta['signatures'] = signatures
|
|
self.pkgmeta['accept_license'] = \
|
|
dbconn.retrieveLicensedataKeys(idpackage)
|
|
self.pkgmeta['conflicts'] = \
|
|
self.Entropy.get_match_conflicts(self.matched_atom)
|
|
|
|
description = dbconn.retrieveDescription(idpackage)
|
|
if description:
|
|
if len(description) > 74:
|
|
description = description[:74].strip()
|
|
description += "..."
|
|
self.pkgmeta['description'] = description
|
|
|
|
# fill action queue
|
|
self.pkgmeta['removeidpackage'] = -1
|
|
removeConfig = False
|
|
if 'removeconfig' in self.metaopts:
|
|
removeConfig = self.metaopts.get('removeconfig')
|
|
|
|
self.pkgmeta['remove_metaopts'] = {
|
|
'removeconfig': True,
|
|
}
|
|
if 'remove_metaopts' in self.metaopts:
|
|
self.pkgmeta['remove_metaopts'] = \
|
|
self.metaopts.get('remove_metaopts')
|
|
|
|
self.pkgmeta['merge_from'] = None
|
|
mf = self.metaopts.get('merge_from')
|
|
if mf != None:
|
|
self.pkgmeta['merge_from'] = const_convert_to_unicode(mf)
|
|
self.pkgmeta['removeconfig'] = removeConfig
|
|
|
|
pkgkey = entropy.tools.dep_getkey(self.pkgmeta['atom'])
|
|
inst_idpackage, inst_rc = self.Entropy.installed_repository().atomMatch(pkgkey,
|
|
matchSlot = self.pkgmeta['slot'])
|
|
|
|
# filled later...
|
|
self.pkgmeta['removecontent'] = set()
|
|
self.pkgmeta['removeidpackage'] = inst_idpackage
|
|
|
|
if self.pkgmeta['removeidpackage'] != -1:
|
|
avail = self.Entropy.installed_repository().isIdpackageAvailable(
|
|
self.pkgmeta['removeidpackage'])
|
|
if avail:
|
|
inst_atom = self.Entropy.installed_repository().retrieveAtom(
|
|
self.pkgmeta['removeidpackage'])
|
|
self.pkgmeta['removeatom'] = inst_atom
|
|
else:
|
|
self.pkgmeta['removeidpackage'] = -1
|
|
|
|
# smartpackage ?
|
|
self.pkgmeta['smartpackage'] = False
|
|
# set unpack dir and image dir
|
|
if self.pkgmeta['repository'].endswith(etpConst['packagesext']):
|
|
|
|
try:
|
|
compiled_arch = dbconn.getSetting("arch")
|
|
arch_fine = compiled_arch == etpConst['currentarch']
|
|
except KeyError:
|
|
arch_fine = True # sorry, old db, cannot check
|
|
|
|
if not arch_fine:
|
|
self.pkgmeta.clear()
|
|
self.__prepared = False
|
|
return -1
|
|
|
|
repo_data = self._system_settings['repositories']
|
|
repo_meta = repo_data['available'][self.pkgmeta['repository']]
|
|
self.pkgmeta['smartpackage'] = repo_meta['smartpackage']
|
|
self.pkgmeta['pkgpath'] = repo_meta['pkgpath']
|
|
|
|
else:
|
|
self.pkgmeta['pkgpath'] = self.__get_fetch_disk_path(
|
|
self.pkgmeta['download'])
|
|
|
|
self.pkgmeta['unpackdir'] = etpConst['entropyunpackdir'] + \
|
|
os.path.sep + self.pkgmeta['download']
|
|
|
|
self.pkgmeta['imagedir'] = etpConst['entropyunpackdir'] + \
|
|
os.path.sep + self.pkgmeta['download'] + os.path.sep + \
|
|
etpConst['entropyimagerelativepath']
|
|
|
|
self.pkgmeta['pkgdbpath'] = os.path.join(self.pkgmeta['unpackdir'],
|
|
"edb/pkg.db")
|
|
|
|
# compare both versions and if they match, disable removeidpackage
|
|
if self.pkgmeta['removeidpackage'] != -1:
|
|
|
|
# differential remove list
|
|
self.pkgmeta['diffremoval'] = True
|
|
self.pkgmeta['removeatom'] = \
|
|
self.Entropy.installed_repository().retrieveAtom(
|
|
self.pkgmeta['removeidpackage'])
|
|
|
|
self.pkgmeta['triggers']['remove'] = \
|
|
self.Entropy.installed_repository().getTriggerInfo(
|
|
self.pkgmeta['removeidpackage']
|
|
)
|
|
self.pkgmeta['triggers']['remove']['removecontent'] = \
|
|
self.pkgmeta['removecontent'] # pass reference, not copy! nevva!
|
|
self.pkgmeta['triggers']['remove']['accept_license'] = \
|
|
self.Entropy.installed_repository().retrieveLicensedataKeys(
|
|
self.pkgmeta['removeidpackage'])
|
|
|
|
# set steps
|
|
self.pkgmeta['steps'] = []
|
|
if self.pkgmeta['conflicts']:
|
|
self.pkgmeta['steps'].append("remove_conflicts")
|
|
# install
|
|
self.pkgmeta['steps'].append("unpack")
|
|
# preinstall placed before preremove in order
|
|
# to respect Spm order
|
|
self.pkgmeta['steps'].append("preinstall")
|
|
if (self.pkgmeta['removeidpackage'] != -1):
|
|
self.pkgmeta['steps'].append("preremove")
|
|
self.pkgmeta['steps'].append("install")
|
|
if (self.pkgmeta['removeidpackage'] != -1):
|
|
self.pkgmeta['steps'].append("postremove")
|
|
self.pkgmeta['steps'].append("postinstall")
|
|
self.pkgmeta['steps'].append("logmessages")
|
|
self.pkgmeta['steps'].append("cleanup")
|
|
|
|
self.pkgmeta['triggers']['install'] = dbconn.getTriggerInfo(idpackage)
|
|
self.pkgmeta['triggers']['install']['accept_license'] = \
|
|
self.pkgmeta['accept_license']
|
|
self.pkgmeta['triggers']['install']['unpackdir'] = \
|
|
self.pkgmeta['unpackdir']
|
|
self.pkgmeta['triggers']['install']['imagedir'] = \
|
|
self.pkgmeta['imagedir']
|
|
|
|
spm_class = self.Entropy.Spm_class()
|
|
# call Spm setup hook
|
|
return spm_class.entropy_install_setup_hook(self.Entropy, self.pkgmeta)
|
|
|
|
def __generate_fetch_metadata(self, sources = False):
|
|
self.pkgmeta.clear()
|
|
|
|
idpackage, repository = self.matched_atom
|
|
dochecksum = True
|
|
|
|
# fetch abort function
|
|
if 'fetch_abort_function' in self.metaopts:
|
|
self.fetch_abort_function = \
|
|
self.metaopts.pop('fetch_abort_function')
|
|
|
|
if 'dochecksum' in self.metaopts:
|
|
dochecksum = self.metaopts.get('dochecksum')
|
|
|
|
# NOTE: if you want to implement download-to-dir feature in your
|
|
# client, you've found what you were looking for.
|
|
# fetch_path is the path where data should be downloaded
|
|
# it overrides default path
|
|
if 'fetch_path' in self.metaopts:
|
|
fetch_path = self.metaopts.get('fetch_path')
|
|
if entropy.tools.is_valid_path(fetch_path):
|
|
self.pkgmeta['fetch_path'] = fetch_path
|
|
|
|
self.pkgmeta['repository'] = repository
|
|
self.pkgmeta['idpackage'] = idpackage
|
|
dbconn = self.Entropy.open_repository(repository)
|
|
self.pkgmeta['atom'] = dbconn.retrieveAtom(idpackage)
|
|
if sources:
|
|
self.pkgmeta['download'] = dbconn.retrieveSources(idpackage,
|
|
extended = True)
|
|
else:
|
|
self.pkgmeta['checksum'] = dbconn.retrieveDigest(idpackage)
|
|
sha1, sha256, sha512, gpg = dbconn.retrieveSignatures(idpackage)
|
|
signatures = {
|
|
'sha1': sha1,
|
|
'sha256': sha256,
|
|
'sha512': sha512,
|
|
'gpg': gpg,
|
|
}
|
|
self.pkgmeta['signatures'] = signatures
|
|
self.pkgmeta['download'] = dbconn.retrieveDownloadURL(idpackage)
|
|
|
|
if not self.pkgmeta['download']:
|
|
self.pkgmeta['fetch_not_available'] = True
|
|
return 0
|
|
|
|
self.pkgmeta['verified'] = False
|
|
self.pkgmeta['steps'] = []
|
|
if not repository.endswith(etpConst['packagesext']) and not sources:
|
|
dl_check = self.__check_pkg_path_download(self.pkgmeta['download'],
|
|
None)
|
|
|
|
if dl_check < 0:
|
|
self.pkgmeta['steps'].append("fetch")
|
|
if dochecksum:
|
|
self.pkgmeta['steps'].append("checksum")
|
|
|
|
elif sources:
|
|
self.pkgmeta['steps'].append("sources_fetch")
|
|
|
|
if sources:
|
|
# create sources destination directory
|
|
unpack_dir = os.path.join(etpConst['entropyunpackdir'],
|
|
"sources", self.pkgmeta['atom'])
|
|
self.pkgmeta['unpackdir'] = unpack_dir
|
|
|
|
if not self.pkgmeta.get('fetch_path'):
|
|
if os.path.lexists(unpack_dir):
|
|
if os.path.isfile(unpack_dir):
|
|
os.remove(unpack_dir)
|
|
elif os.path.isdir(unpack_dir):
|
|
shutil.rmtree(unpack_dir, True)
|
|
if not os.path.lexists(unpack_dir):
|
|
os.makedirs(unpack_dir, 0o775)
|
|
const_setup_perms(unpack_dir, etpConst['entropygid'])
|
|
return 0
|
|
|
|
# downloading binary package
|
|
# if file exists, first checksum then fetch
|
|
down_path = self.__get_fetch_disk_path(self.pkgmeta['download'])
|
|
self.pkgmeta['pkgpath'] = down_path # provide for convenience
|
|
# and uniformity with "install" action
|
|
if os.access(down_path, os.R_OK) and os.path.isfile(down_path):
|
|
|
|
# check size first
|
|
repo_size = dbconn.retrieveSize(idpackage)
|
|
f = open(down_path, "r")
|
|
f.seek(0, os.SEEK_END)
|
|
disk_size = f.tell()
|
|
f.close()
|
|
if repo_size == disk_size:
|
|
self.pkgmeta['steps'].reverse()
|
|
|
|
return 0
|
|
|
|
def __generate_multi_fetch_metadata(self):
|
|
self.pkgmeta.clear()
|
|
|
|
if not isinstance(self.matched_atom, list):
|
|
raise AttributeError(
|
|
"matched_atom must be a list of tuples, not %s" % (
|
|
type(self.matched_atom,)
|
|
)
|
|
)
|
|
|
|
dochecksum = True
|
|
|
|
# meta options
|
|
if 'fetch_abort_function' in self.metaopts:
|
|
self.fetch_abort_function = \
|
|
self.metaopts.pop('fetch_abort_function')
|
|
|
|
if 'dochecksum' in self.metaopts:
|
|
dochecksum = self.metaopts.get('dochecksum')
|
|
self.pkgmeta['checksum'] = dochecksum
|
|
|
|
matches = self.matched_atom
|
|
self.pkgmeta['matches'] = matches
|
|
self.pkgmeta['atoms'] = []
|
|
self.pkgmeta['repository_atoms'] = {}
|
|
temp_fetch_list = []
|
|
temp_checksum_list = []
|
|
temp_already_downloaded_count = 0
|
|
for idpackage, repository in matches:
|
|
|
|
if repository.endswith(etpConst['packagesext']):
|
|
continue
|
|
|
|
dbconn = self.Entropy.open_repository(repository)
|
|
myatom = dbconn.retrieveAtom(idpackage)
|
|
|
|
# general purpose metadata
|
|
self.pkgmeta['atoms'].append(myatom)
|
|
if repository not in self.pkgmeta['repository_atoms']:
|
|
self.pkgmeta['repository_atoms'][repository] = set()
|
|
self.pkgmeta['repository_atoms'][repository].add(myatom)
|
|
|
|
download = dbconn.retrieveDownloadURL(idpackage)
|
|
digest = dbconn.retrieveDigest(idpackage)
|
|
|
|
sha1, sha256, sha512, gpg = dbconn.retrieveSignatures(idpackage)
|
|
signatures = {
|
|
'sha1': sha1,
|
|
'sha256': sha256,
|
|
'sha512': sha512,
|
|
'gpg': gpg,
|
|
}
|
|
|
|
repo_size = dbconn.retrieveSize(idpackage)
|
|
if self.__check_pkg_path_download(download, None) < 0:
|
|
obj = (repository, download, digest, signatures,)
|
|
temp_fetch_list.append(obj)
|
|
continue
|
|
|
|
elif dochecksum:
|
|
obj = (repository, download, digest, signatures,)
|
|
temp_checksum_list.append(obj)
|
|
|
|
down_path = self.__get_fetch_disk_path(download)
|
|
if os.path.isfile(down_path):
|
|
with open(down_path, "r") as f:
|
|
f.seek(0, os.SEEK_END)
|
|
disk_size = f.tell()
|
|
if repo_size == disk_size:
|
|
temp_already_downloaded_count += 1
|
|
|
|
self.pkgmeta['steps'] = []
|
|
self.pkgmeta['multi_fetch_list'] = temp_fetch_list
|
|
self.pkgmeta['multi_checksum_list'] = temp_checksum_list
|
|
if self.pkgmeta['multi_fetch_list']:
|
|
self.pkgmeta['steps'].append("multi_fetch")
|
|
if self.pkgmeta['multi_checksum_list']:
|
|
self.pkgmeta['steps'].append("multi_checksum")
|
|
if temp_already_downloaded_count == len(temp_checksum_list):
|
|
self.pkgmeta['steps'].reverse()
|
|
|
|
return 0
|