# -*- coding: utf-8 -*-
"""

    @author: Fabio Erculiani <lxnay@sabayon.org>
    @contact: lxnay@sabayon.org
    @copyright: Fabio Erculiani
    @license: GPL-2

    B{Entropy Framework QA module}.

    This module contains various Quality Assurance routines used by Entropy.

    B{QAInterface} is the main class for QA routines used by Entropy Server
    and Entropy Client such as binary packages health check, dependency
    test, broken or missing library tes.

    B{ErrorReportInterface} is the HTTP POST based class for Entropy Client
    exceptions (errors) submission.

"""
import collections
import errno
import os
import sys
import subprocess
import stat
import codecs

from entropy.output import TextInterface
from entropy.misc import Lifo
from entropy.const import etpConst, etpSys, const_debug_write, const_mkdtemp, \
    const_mkstemp, const_debug_write, const_convert_to_rawstring, \
    const_is_python3, const_file_readable
from entropy.output import blue, darkgreen, red, darkred, bold, purple, brown, \
    teal
from entropy.exceptions import PermissionDenied, SystemDatabaseError
from entropy.i18n import _
from entropy.core import EntropyPluginStore
from entropy.core.settings.base import SystemSettings
from entropy.db.skel import EntropyRepositoryPlugin, EntropyRepositoryBase

import entropy.tools

class QAEntropyRepositoryPlugin(EntropyRepositoryPlugin):

    def __init__(self, qa_interface, metadata = None):
        """
        Entropy QA-side repository EntropyRepository Plugin class.
        This class will be instantiated and automatically added to
        EntropyRepository instances generated by Entropy QA.

        @param qa_interface: Entropy Client interface instance
        @type qa_interface: entropy.qa.QAInterface class
        @param metadata: any dict form metadata map (key => value)
        @type metadata: dict
        """
        EntropyRepositoryPlugin.__init__(self)
        self._qa = qa_interface
        if metadata is None:
            self._metadata = {}
        else:
            self._metadata = metadata

    def get_metadata(self):
        return self._metadata

    def get_id(self):
        return "__qa__"

    def add_plugin_hook(self, entropy_repository_instance):
        const_debug_write(__name__,
            "QAEntropyRepositoryPlugin: calling add_plugin_hook => %s" % (
                self,)
            )

        out_intf = self._metadata.get('output_interface')
        if out_intf is not None:
            entropy_repository_instance.output = out_intf.output
            entropy_repository_instance.ask_question = out_intf.ask_question

        return 0

class QAInterfacePlugin:
    """
    Inherit this class to create a QAInterface tests Plugin.
    You need to implement

    """
    def get_tests(self):
        """
        Return a list of callable functions that will be used by QAInterface,
        for tests execution.
        Note: the callable functions signature must be:
            bool function(package_path)

        @return: list of callable objects
        @rtype: list
        """
        raise NotImplementedError()

    def get_id(self):
        """
        Return an identifier string for this Plugin.

        @return: identifier string
        @rtype: string
        """
        raise NotImplementedError()

class QAInterface(TextInterface, EntropyPluginStore):

    """
    Entropy QA interface. This class contains all the Entropy
    QA routines used by Entropy Server and Entropy Client.

    An instance of QAInterface can be easily retrieved from
    entropy.client.interfaces.Client or entropy.server.interfaces.Server
    through an exposed QA() method.
    This is anyway a stand-alone class.

    @todo: remove non-QA methods

    """

    def __init__(self):
        """
        QAInterface constructor.
        """
        EntropyPluginStore.__init__(self)
        self._settings = SystemSettings()

    def add_plugin(self, plugin):
        """
        Add a QAInterface plugin to the testing list.

        @param plugin: QAInterfacePlugin based instance
        @type plugin: QAInterface based instance
        @raise AttributeError: if plugin is not a QAInterfacePlugin instance
        """
        if not isinstance(plugin, QAInterfacePlugin):
            raise AttributeError("Specify a QAInterfacePlugin based class")
        return EntropyPluginStore.add_plugin(self, plugin.get_id(), plugin)

    def test_reverse_dependencies_linking(self, entropy_client,
        package_matches):
        """
        Scan for broken shared objects linking.
        Note: this only works for packages actually installed on the running
        system.
        It is used by Entropy Server during packages injection into database
        to warn about potentially broken packages.

        @param entropy_client: Entropy Client instance
        @type entropy_client: entropy.client.interfaces.client.Client based
            instance object
        @param package_matches: list of entropy package matches tuples
            (package id, repo id)
        @type package_matches: list
        @return: True if any breakage is found, otherwise False
        @rtype: bool
        """

        scan_msg = blue(_("Searching for broken reverse dependencies"))
        self.output(
            "%s..." % (scan_msg,),
            importance = 1,
            level = "info",
            header = red(" @@ ")
        )

        broken = False

        count = 0
        maxcount = len(package_matches)
        for pkg_id, pkg_repo in package_matches:
            count += 1
            dbconn = entropy_client.open_repository(pkg_repo)
            atom = dbconn.retrieveAtom(pkg_id)
            scan_msg = "%s, %s:" % (
                blue(_("scanning for broken reverse dependencies")),
                darkgreen(atom),
            )
            self.output(
                "[repo:%s] %s" % (
                    darkgreen(pkg_repo),
                    scan_msg,
                ),
                importance = 1,
                level = "info",
                header = blue(" @@ "),
                back = True,
                count = (count, maxcount,)
            )
            mydepends = dbconn.retrieveReverseDependencies(pkg_id)
            if not mydepends:
                continue
            for mydepend in mydepends:
                myatom = dbconn.retrieveAtom(mydepend)
                self.output(
                    "[repo:%s] %s => %s" % (
                        darkgreen(pkg_repo),
                        darkgreen(atom),
                        darkred(myatom),
                    ),
                    importance = 0,
                    level = "info",
                    header = blue(" @@ "),
                    back = True,
                    count = (count, maxcount,)
                )
                mycontent = dbconn.retrieveContent(mydepend)
                mybreakages = self._content_test(mycontent)
                if not mybreakages:
                    continue
                broken = True
                self.output(
                    "[repo:%s] %s %s => %s" % (
                        darkgreen(pkg_repo),
                        darkgreen(atom),
                        darkred(myatom),
                        bold(_("broken libraries detected")),
                    ),
                    importance = 1,
                    level = "warning",
                    header = purple(" @@ "),
                    count = (count, maxcount,)
                )
                for mylib in mybreakages:
                    self.output(
                        "%s %s:" % (
                            darkgreen(mylib),
                            red(_("needs")),
                        ),
                        importance = 1,
                        level = "warning",
                        header = brown("   ## ")
                    )
                    for needed in mybreakages[mylib]:
                        self.output(
                            "%s" % (
                                red(needed),
                            ),
                            importance = 1,
                            level = "warning",
                            header = purple("     # ")
                        )
        return broken

    def test_missing_dependencies(self, entropy_client, package_matches,
        blacklist = None):
        """
        Scan given package matches looking for missing dependencies (checking
        ELF metadata).

        @param entropy_client: Entropy Client instance
        @type entropy_client: entropy.client.interfaces.client.Client based
            instance object
        @param package_matches: list of entropy package matches tuples
            (package id, repo id)
        @type package_matches: list
        @keyword blacklist: list of dependencies already blacklisted.
        @type blacklist: set
        @return: dict of missing dependencies to add, key is package match,
            value is a dict, with library name + ELF class as key, and
            potential missing deps as value
        @rtype: dict
        """
        if blacklist is None:
            blacklist = set()

        scan_msg = blue(_("scanning"))
        missing_map = {}
        repos = sorted(entropy_client.repositories())

        for count, (package_id, repository_id) in enumerate(package_matches, 1):
            repo = entropy_client.open_repository(repository_id)
            atom = repo.retrieveAtom(package_id)
            self.output(
                "[%s] %s: %s" % (
                            darkgreen(repository_id),
                            scan_msg,
                            darkgreen(atom),
                        ),
                importance = 1,
                level = "info",
                header = blue(" @@ "),
                back = True,
                count = (count, len(package_matches),)
            )

            missing_extended, missing = self._get_missing_libraries(
                entropy_client, (package_id, repository_id))

            if not missing:
                continue

            old_missing = missing.copy()
            missing -= blacklist
            for item in list(missing_extended.keys()):
                missing_extended[item] -= blacklist
                if not missing_extended[item]:
                    del missing_extended[item]

            if missing != old_missing:
                # print big warning to make dev aware at least
                old_missing -= missing
                if old_missing:
                    self.output(
                        "[%s] %s: %s %s:" % (
                            darkgreen(repository_id),
                            darkred("package"),
                            darkgreen(atom),
                            darkred(_("blacklisted dependencies !!!")),
                        ),
                        importance = 1,
                        level = "warning",
                        header = bold(" @@ "),
                        count = (count, len(package_matches),)
                    )
                for dep in sorted(old_missing):
                    self.output(
                            "%s" % (bold(dep),),
                            importance = 0,
                            level = "info",
                            header = blue("     # ")
                    )

            if not missing:
                continue
            if not missing_extended:
                continue

            # warn about the missing dep
            self.output(
                "[%s] %s %s:" % (
                    darkgreen(repository_id),
                    brown(atom),
                    blue(_("requires libraries"))),
                importance = 1,
                level = "warning",
                header = red(" @@ "))

            for (lib, elfclass), provider_deps in missing_extended.items():
                self.output(
                    "%s [%s:%d]" % (
                        purple(lib),
                        teal("elfclass"),
                        elfclass),
                    importance = 0,
                    level = "warning",
                    header = brown("  # "))

                if provider_deps:
                    self.output(
                        "%s:" % (
                            blue(_("library provided by")),),
                        importance = 0,
                        level = "warning",
                        header = red("   $ "))
                for dep in sorted(provider_deps):
                    self.output(
                        darkgreen(dep),
                        importance = 0,
                        level = "warning",
                        header = purple("    :: "))

            missing_map[(package_id, repository_id)] = missing_extended

        return missing_map

    def warn_missing_dependencies(self, entropy_client, package_matches):
        """
        Scan given package matches looking for missing dependencies.
        This method scans the live filesystem looking for library matches,
        so the returned information might be incorrect (packages using
        runtime LD_LIBRARY_PATH and other dlopen() magic might be reported
        as false positives).

        @param entropy_client: Entropy Client instance
        @type entropy_client: entropy.client.interfaces.client.Client based
            instance object
        @param package_matches: list of entropy package matches tuples
            (package id, repo id)
        @type package_matches: list
        @return: list (set) of broken package matches
        @rtype: set
        """
        repos = sorted(entropy_client.repositories())

        def _warn_soname(soname, elfclass):
            # try to resolve soname
            for needed_repo in repos:
                needed_dbconn = entropy_client.open_repository(needed_repo)
                pkg_ids = needed_dbconn.resolveNeeded(soname,
                    elfclass = elfclass)
                if pkg_ids:
                    pkg_atoms = sorted((
                        needed_dbconn.retrieveKeySlotAggregated(x) for x in \
                            pkg_ids))
                    pkg_atoms_string = ', '.join(pkg_atoms)
                else:
                    pkg_atoms_string = _("no packages")
                self.output(
                    "[%s:%s] %s" % (
                        brown(needed_repo),
                        teal(soname),
                        purple(pkg_atoms_string),
                    ),
                    importance = 0,
                    level = "info",
                    header = brown("     # ")
                )

        broken_matches = set()
        for count, (package_id, repo_id) in enumerate(package_matches, 1):
            dbconn = entropy_client.open_repository(repo_id)
            atom = dbconn.retrieveAtom(package_id)

            # check for untracked missing sonames (using less reliable
            # ldd check, but just warn)
            missing_sonames = self._get_unresolved_sonames(entropy_client,
                (package_id, repo_id))
            if missing_sonames:
                broken_matches.add((package_id, repo_id))

                self.output(
                    "[%s] %s: %s %s:" % (
                        darkgreen(repo_id),
                        blue("package"),
                        darkgreen(atom),
                        blue(_("is potentially missing these dependencies")),
                    ),
                    importance = 1,
                    level = "info",
                    header = red(" @@ "),
                    count = (count, len(package_matches),)
                )
            for executable, sonames in missing_sonames.items():
                elfclass = entropy.tools.read_elf_class(executable)
                self.output(
                    "%s (elf class: %s):" % (
                        brown(executable),
                        teal(str(elfclass)),
                    ),
                    importance = 0,
                    level = "info",
                    header = purple("   ## ")
                )
                for soname in sonames:
                    _warn_soname(soname, elfclass)

        return broken_matches

    def test_missing_runtime_libraries(self, entropy_client, package_matches,
        base_repository_id = None, excluded_libraries = None, silent = False):
        """
        Use collected packages ELF metadata (retrieveNeeded(),
        resolveNeeded()) to look for potentially missing
        shared libraries. This is very handy in case of library breakages
        across multiple server-side repositories.
        For example: you bump libfoo, which provides new library, the SPM forces
        you to rebuild foouser, which uses libfoo. You put both into a testing
        repository but then you only move foouser to the base repository without
        realizing the potential breakage users could run into.
        However, since there can be false positives, this routine cannot block
        you from doing this mistakes.
        Please note that the base repository is the first listed in server.conf
        and will always be considered as self-contained, meaning that all the
        dependencies and sonames must be available within the same.
        The code first tries to resolve the soname inside the same repository,
        then falls back to other ones, if any.

        @param entropy_client: Entropy Client instance
        @type entropy_client: entropy.client.interfaces.client.Client based
            instance object
        @param package_matches: list of Entropy package matches
        @type package_matches: list
        @return: list (set) of tuples of length 2 of missing sonames
            [(soname, elfclass), ...]
        @keyword base_repository_id: repository identifier supposed to be
            used as main (or base) repository
        @type base_repository_id: string
        @keyword excluded_libraries: list of libraries that should not be
            verified (perhaps, libGL.so.1 in Gentoo/Sabayon)
        @type excluded_libraries: set
        @keyword silent: no output is print to stdout
        @type silent: bool
        @rtype: set
        """
        missing_sonames = set()
        if excluded_libraries is None:
            excluded_libraries = set()
        else:
            excluded_libraries = set(excluded_libraries)

        # update content taken from brokenlinksmask.conf
        excluded_libraries.update(self._settings['broken_links_mask'])

        def _resolve_needed_content_fallback(repo, pkg_id, library):
            # at this point, perhaps the library is self contained in the
            # package itself, and in non-standard path. so, let's see if
            # it's in the content metadata before giving up.
            # Since this is expensive, try to first match it in
            # pre-hashed metadata (via resolveNeeded())
            content_iter = repo.retrieveContentIter(pkg_id)

            # NOTE: we assume that ELF class is the same as the one
            # requested, because we're not allowed to poll the
            # live system. perhaps in future we could collect
            # more metadata.
            for path, ftype in content_iter:
                if ftype == "dir":
                    continue
                b_path = os.path.basename(path)
                if library == b_path:
                    return True
            return False

        def _resolve_needed(repo, pkg_id, library, elfclass, multi_repo):

            resolved_needed = repo.resolveNeeded(library,
                elfclass = elfclass)
            if resolved_needed:
                return True

            if not resolved_needed and not multi_repo:
                # sorry, can't find it, this is the base repo already
                return _resolve_needed_content_fallback(repo, pkg_id, library)

            for repo_id in entropy_client.repositories():
                if repo_id == repo.repository_id():
                    # already searched here
                    continue
                other_repo = entropy_client.open_repository(repo_id)
                resolved_needed = other_repo.resolveNeeded(library,
                    elfclass = elfclass)
                if resolved_needed:
                    # found !
                    return True

            return _resolve_needed_content_fallback(repo, pkg_id, library)

        def _look_for_available_soname(current_repo_id, library, elfclass):
            where = frozenset()
            for repo_id in entropy_client.repositories():
                if repo_id == current_repo_id:
                    # already searched here
                    continue
                other_repo = entropy_client.open_repository(repo_id)
                resolved_needed = other_repo.resolveNeeded(library,
                    elfclass = elfclass, extended = True)
                if not resolved_needed:
                    continue
                where = [(x, repo_id, y) for x, y in resolved_needed]
                break
            return where

        count = 0
        maxcount = len(package_matches)
        for package_id, repository_id in package_matches:

            count += 1
            repo = entropy_client.open_repository(repository_id)
            atom = repo.retrieveAtom(package_id)

            if not silent:
                scan_msg = "%s, %s:" % (
                    blue(_("determining missing libraries")),
                    darkgreen(atom),
                )
                self.output(
                    "[repo:%s] %s" % (
                        darkgreen(repository_id),
                        scan_msg,
                    ),
                    importance = 1,
                    level = "info",
                    header = blue(" @@ "),
                    back = True,
                    count = (count, maxcount,)
                )

            is_base_repo = repository_id == base_repository_id

            # list of (needed, elfclass)
            needed = repo.retrieveNeeded(package_id, extended = True)
            for library, elfclass in needed:
                if library in excluded_libraries:
                    continue
                resolved = _resolve_needed(repo, package_id, library, elfclass,
                    not is_base_repo)
                if not resolved:
                    missing_sonames.add((library, elfclass))

                    if not silent:
                        self.output(
                            "[%s] %s %s: %s, class: %s" % (
                                purple(repository_id),
                                teal(atom),
                                purple(_("requires")),
                                brown(library),
                                darkgreen(str(elfclass)),
                            ),
                            importance = 1,
                            level = "warning",
                            header = " ",
                        )
                    # perhaps the lib is in some other repo?
                    resolved_needed = _look_for_available_soname(
                        repository_id, library, elfclass)
                    for pkg_id, repo_id, path in resolved_needed:
                        atom = entropy_client.open_repository(
                            repo_id).retrieveAtom(pkg_id)
                        if not silent:
                            self.output(
                                "%s: %s, %s" % (
                                    brown(_("provided by")),
                                    darkgreen(atom),
                                    path,
                                ),
                                importance = 0,
                                level = "warning",
                                header = darkred("   "),
                            )
                    if not resolved_needed and not silent:
                        self.output(
                            "%s: %s" % (
                                brown(_("provided by")),
                                darkgreen(_("no packages")),
                            ),
                            importance = 0,
                            level = "warning",
                            header = darkred("   "),
                        )

        if not silent:
            if missing_sonames:
                self.output("", level = "info", importance = 0)
            else:
                self.output(
                    darkgreen(_("no missing runtime libraries found")),
                    level = "info",
                    importance = 1
                )

        return missing_sonames

    def test_shared_objects(self, entropy_repository, broken_symbols = False,
        task_bombing_func = None, self_dir_check = True,
        dump_results_to_file = False, silent = False):

        """
        Scan system looking for broken shared object ELF library dependencies.

        @param entropy_repository: entropy.db.EntropyRepository instance
        @type entropy_repository: entropy.db.EntropyRepository instance
        @keyword broken_symbols: enable or disable broken symbols extra check.
            Symbols which are going to be checked have to be listed into:
            /etc/entropy/brokensyms.conf (regexp supported).
        @type broken_symbols: bool
        @keyword task_bombing_func: callable that will be called on every
            scan iteration to allow external routines to cleanly stop the
            execution of this function.
        @type task_bombing_func: callable
        @keyword dump_results_to_file: dump test results to files (printed)
        @type dump_results_to_file: bool
        @keyword silent: do not print anything to stdout
        @type silent: bool
        @return: tuple of length 3, composed by (1) a dict of matched packages,
            (2) a list (set) of broken ELF objects and (3) the execution status
            (int, 0 means success).
        @rtype: tuple
        """

        if not silent:
            self.output(
                blue(_("Libraries test")),
                importance = 2,
                level = "info",
                header = red(" @@ ")
            )

        syms_list_path = None
        files_list_path = None
        if dump_results_to_file:

            tmp_dir = const_mkdtemp(prefix="qa.libtest")
            syms_list_path = os.path.join(tmp_dir, "libtest_syms.txt")
            files_list_path = os.path.join(tmp_dir, "libtest_files.txt")

            dmp_data = [
                (_("Broken symbols packages list"), syms_list_path,),
                (_("Broken executables list"), files_list_path,),
            ]
            mytxt = "%s:" % (purple(_("Dumping results into these files")),)
            if not silent:
                self.output(
                    mytxt,
                    importance = 1,
                    level = "info",
                    header = blue(" @@ ")
                )
                for txt, path in dmp_data:
                    mytxt = "%s: %s" % (blue(txt), path,)
                    self.output(
                        mytxt,
                        importance = 0,
                        level = "info",
                        header = darkgreen("   ## ")
                    )

        myroot = etpConst['systemroot'] + os.path.sep
        if not etpConst['systemroot']:
            myroot = os.path.sep

        # run ldconfig first
        subprocess.call("ldconfig -r '%s' &> /dev/null" % (myroot,),
                        shell = True)

        reverse_symlink_map = self._settings['system_rev_symlinks']
        broken_syms_list = self._settings['broken_syms']
        broken_libs_mask = self._settings['broken_libs_mask']
        # make possible to pass a mask list from env
        env_broken_libs_mask = os.getenv("ETP_BROKEN_LIBS_MASK", "")
        if env_broken_libs_mask.strip():
            broken_libs_mask += env_broken_libs_mask.strip().split()

        import re

        broken_syms_list_regexp = []
        for broken_sym in broken_syms_list:
            if broken_sym.startswith("r:"):
                broken_sym = broken_sym[2:]
                if not broken_sym:
                    continue
                reg_sym = re.compile(broken_sym)
            else:
                reg_sym = re.compile(re.escape(broken_sym))
            broken_syms_list_regexp.append(reg_sym)

        broken_libs_mask_regexp = []
        broken_libs_paths_mask_regexp = []
        for broken_lib in broken_libs_mask:
            if broken_lib.startswith("r:"):
                broken_lib = broken_lib[2:]
                if not broken_lib:
                    continue
                reg_lib = re.compile(broken_lib)
            else:
                reg_lib = re.compile(re.escape(broken_lib))
            if os.path.sep in broken_lib:
                # it's a path, not a lib name
                broken_libs_paths_mask_regexp.append(reg_lib)
            else:
                broken_libs_mask_regexp.append(reg_lib)

        ldpaths = entropy.tools.collect_linker_paths()
        ldpaths += [x for x in entropy.tools.collect_paths() if \
            x not in ldpaths]

        # some crappy packages put shit here too
        ldpaths += [x for x in self._settings['extra_ldpaths'] if \
            x not in ldpaths]

        # remove duplicated dirs (due to symlinks) to speed up scanning
        for real_dir in list(reverse_symlink_map.keys()):
            syms = reverse_symlink_map[real_dir]
            for sym in syms:
                if sym in ldpaths:
                    while (real_dir in ldpaths):
                        ldpaths.remove(real_dir)
                    if not silent:
                        self.output(
                            "%s: %s, %s: %s" % (
                                brown(_("discarding directory")),
                                purple(real_dir),
                                brown(_("because it's symlinked on")),
                                purple(sym),
                            ),
                            importance = 0,
                            level = "info",
                            header = darkgreen(" @@ ")
                        )
                    break

        executables = set()
        total = len(ldpaths)
        count = 0
        sys_root_len = len(etpConst['systemroot'])

        def _are_elfs(dt):
            currentdir, subdirs, files = dt

            def _is_elf(item):
                filepath = os.path.join(currentdir, item)
                if self._is_elf_executable_or_library(filepath):
                    return filepath[sys_root_len:]

            return (x for x in map(_is_elf, files) if x is not None)

        for ldpath in sorted(ldpaths):

            if hasattr(task_bombing_func, '__call__'):
                task_bombing_func()
            count += 1
            if not silent:
                self.output(
                    blue("Tree: ")+red(etpConst['systemroot'] + ldpath),
                    importance = 0,
                    level = "info",
                    count = (count, total),
                    back = True,
                    percent = True,
                    header = "  "
                )
            try:
                ldpath = ldpath.encode('utf-8')
            except (UnicodeEncodeError,):
                ldpath = ldpath.encode(sys.getfilesystemencoding())
            mywalk_iter = os.walk(etpConst['systemroot'] + ldpath)

            for x in map(_are_elfs, mywalk_iter):
                executables.update(x)

        if not silent:
            self.output(
                blue(_("Collecting broken executables")),
                importance = 2,
                level = "info",
                header = red(" @@ ")
            )
            t = red(_("Attention")) + ": " + \
                blue(_("don't worry about libraries that "
                       "are shown here but not later."))
            self.output(
                t,
                importance = 1,
                level = "info",
                header = red(" @@ ")
            )

        enc = etpConst['conf_encoding']
        syms_list_f = None
        if syms_list_path:
            syms_list_f = codecs.open(syms_list_path, "w", encoding=enc)

        files_list_f = None
        if files_list_path:
            files_list_f = codecs.open(files_list_path, "w", encoding=enc)

        plain_brokenexecs = set()
        total = len(executables)
        count = 0
        scan_txt = blue("%s ..." % (_("Scanning libraries"),))
        for executable in executables:

            # task bombing hook
            if hasattr(task_bombing_func, '__call__'):
                task_bombing_func()

            count += 1
            if (count % 10 == 0) or (count == total) or (count == 1):
                if not silent:
                    self.output(
                        scan_txt,
                        importance = 0,
                        level = "info",
                        count = (count, total),
                        back = True,
                        percent = True,
                        header = "  "
                    )

            # filter broken paths
            # there are paths known to be broken and must be
            # excluded to avoid noisy false positives
            exec_match = False
            for reg_path in broken_libs_paths_mask_regexp:
                if reg_path.match(executable):
                    exec_match = True
                    break
            if exec_match:
                continue

            real_exec_path = etpConst['systemroot'] + executable

            myelfs = entropy.tools.read_elf_dynamic_libraries(
                real_exec_path)

            mylibs = set()
            for mylib in myelfs:
                lib_path = entropy.tools.resolve_dynamic_library(mylib,
                    executable)
                if not lib_path:
                    mylibs.add(mylib)

            # filter broken libraries
            if mylibs:

                mylib_filter = set()
                for mylib in mylibs:
                    mylib_matched = False
                    for reg_lib in broken_libs_mask_regexp:
                        if reg_lib.match(mylib):
                            mylib_matched = True
                            break

                    if mylib_matched: # filter out
                        mylib_filter.add(mylib)

                    elif self_dir_check:
                        # check inside the same directory of the failing ELF
                        # obviously, we're looking for another ELF object
                        my_real_exec_dir = os.path.dirname(real_exec_path)
                        mylib_guess = os.path.join(my_real_exec_dir, mylib)
                        try:
                            if self._is_elf_executable_or_library(mylib_guess):
                                # we have found the missing library,
                                # which wasn't in LDPATH, booooo @ package
                                # developers !! boooo!
                                mylib_filter.add(mylib)
                        except (OSError, IOError) as err:
                            if err.errno != errno.ENOENT:
                                raise

                mylibs -= mylib_filter

            broken_sym_found = set()
            if broken_symbols and not mylibs:

                read_broken_syms = entropy.tools.read_elf_broken_symbols(
                        real_exec_path)
                my_broken_syms = set()
                for read_broken_sym in read_broken_syms:
                    for reg_sym in broken_syms_list_regexp:
                        if reg_sym.match(read_broken_sym):
                            my_broken_syms.add(read_broken_sym)
                            break
                broken_sym_found.update(my_broken_syms)

            if not (mylibs or broken_sym_found):
                continue

            if mylibs:

                if files_list_f:
                    files_list_f.write(executable + "\n")

                alllibs = blue(' :: ').join(sorted(mylibs))
                if not silent:
                    self.output(
                        red(real_exec_path)+" [ "+alllibs+" ]",
                        importance = 1,
                        level = "info",
                        percent = True,
                        count = (count, total),
                        header = "  "
                    )
            elif broken_sym_found:

                allsyms = darkred(' :: ').join([brown(x) for x in \
                    broken_sym_found])
                if len(allsyms) > 50:
                    allsyms = brown(_('various broken symbols'))

                if syms_list_f and broken_sym_found:
                    syms_list_f.write("%s => %s\n" % (real_exec_path,
                        sorted(broken_sym_found),))

                if not silent:
                    self.output(
                        red(real_exec_path)+" { "+allsyms+" }",
                        importance = 1,
                        level = "info",
                        percent = True,
                        count = (count, total),
                        header = "  "
                    )

            plain_brokenexecs.add(executable)

        # close open files
        if syms_list_f:
            syms_list_f.close()
        if files_list_f:
            files_list_f.close()

        del executables
        pkgs_matched = {}

        if not etpSys['serverside']:

            # we are client side
            # this is hackish and must be fixed sooner or later
            # but for now, it works
            # Client class is singleton and is surely already
            # loaded when we get here
            from entropy.client.interfaces import Client
            client = Client()

            if not silent:
                self.output(
                    blue(_("Matching broken libraries/executables")),
                    importance = 1,
                    level = "info",
                    header = red(" @@ ")
                )
            matched = set()
            for brokenlib in plain_brokenexecs:
                # test with /usr/lib
                idpackages = entropy_repository.searchBelongs(brokenlib)
                if not idpackages:
                    # try with realpath
                    # on multilib systems this resolves to /usr/lib64
                    # which makes searchBelongs() happy
                    idpackages = entropy_repository.searchBelongs(
                        os.path.realpath(brokenlib))

                for idpackage in idpackages:

                    key, slot = entropy_repository.retrieveKeySlot(idpackage)
                    mymatch = client.atom_match(key, match_slot = slot)
                    if mymatch[0] == -1:
                        matched.add(brokenlib)
                        continue

                    obj = pkgs_matched.setdefault(brokenlib, set())

                    obj.add(mymatch)
                    matched.add(brokenlib)

            plain_brokenexecs -= matched

        return pkgs_matched, plain_brokenexecs, 0

    def _content_test(self, mycontent):
        """
        Test whether the given list of files contain files
        with broken shared object links.

        @param mycontent: list of file paths
        @type mycontent: list or set
        @return: dict containing a map between file path
        and list (set) of broken libraries (just the library name,
            the same that is contained inside ELF metadata)
        @rtype: dict
        """
        def is_contained(needed, content):
            for item in content:
                if os.path.basename(item) == needed:
                    return True
            return False

        mylibs = {}
        for myfile in mycontent:
            myfile = const_convert_to_rawstring(myfile)
            if not self._is_elf_executable_or_library(myfile):
                continue
            mylibs[myfile] = entropy.tools.read_elf_dynamic_libraries(
                myfile)

        broken_libs = {}
        for mylib in mylibs:
            for myneeded in mylibs[mylib]:
                # is this inside myself ?
                if is_contained(myneeded, mycontent):
                    continue
                found = entropy.tools.resolve_dynamic_library(myneeded,
                    mylib)
                if found:
                    continue
                if mylib not in broken_libs:
                    broken_libs[mylib] = set()
                broken_libs[mylib].add(myneeded)

        return broken_libs

    def _is_elf_executable_or_library(self, path, allow_symlink = True):
        """
        Determine whether a path is a valid ELF executable or ELF library.

        @param path: path to test
        @type path: string
        @keyword allow_symlink: True, if you accept symlinks
        @type allow_symlink: bool
        @return: True, if yes
        @rtype: bool
        """
        if not const_is_python3():
            path = const_convert_to_rawstring(path)

        try:
            st = os.stat(path)
        except (OSError, IOError):
            return False

        # is it a regular file?
        if not stat.S_ISREG(st.st_mode):
            return False

        if not allow_symlink:
            if stat.S_ISLNK(st.st_mode):
                return False

        # shared libraries must be always executable
        if not (stat.S_IMODE(st.st_mode) & stat.S_IXUSR):
            return False

        # is it a debug file? skip them.
        t_path = path
        while t_path != os.path.sep:
            if t_path in etpConst['splitdebug_dirs']:
                return False
            t_path = os.path.dirname(t_path)

        # is this really an ELF object file?
        if not entropy.tools.is_elf_file(path):
            return False

        return True

    def _get_unresolved_sonames(self, entropy_client, package_match,
        content_root = None):
        """
        This runtime dependencies function uses ldd to determine what missing
        runtime dependencies a package may expose. ldd implicitly expands the
        list (graph) of shared objects connected with an ELF using ld.so.conf
        and LD* env vars information. This is useful to warn about pontentially
        broken packages, containing untracked runtime dependencies.
        Unfortunately, this is (as opposed to _get_missing_libraries) not rocket
        science and cannot be used automatically add dependencies to
        a specific package. It's not even 100% reliable because the linking
        depends on the environment (ld.so.conf and LD*). So, just use it to
        warn developers or users.

        @param entropy_client: Entropy Client instance
        @type entropy_client: entropy.client.interfaces.client.Client based
            instance object
        @param package_match: Entropy package match: (package id, repo id).
        @type package_match: tuple
        @keyword content_root: alternative root directory containing content
        @return: dictionary composed by package ELF file path as key, and
            list (set) of missing sonames as value.
        @rtype: dict
        """
        if content_root is None:
            content_root = etpConst['systemroot']
        elif not content_root.endswith(os.path.sep):
            content_root += os.path.sep

        dependencies = self.get_deep_dependency_list(entropy_client,
            package_match, atoms = True)

        pkg_matches = set()
        for dependency in dependencies:
            match_pkg_id, match_repo_id = entropy_client.atom_match(dependency)
            if match_pkg_id == -1:
                continue
            pkg_matches.add((match_pkg_id, match_repo_id))

        provided_libs_set = set()
        package_id, repository_id = package_match
        entropy_repository = entropy_client.open_repository(repository_id)

        for lib, path, elfclass in entropy_repository.retrieveProvidedLibraries(
            package_id):
            provided_libs_set.add(path)

        for pkg_id, pkg_repo in pkg_matches:
            repo = entropy_client.open_repository(pkg_repo)
            for lib, path, elfclass in repo.retrieveProvidedLibraries(pkg_id):
                provided_libs_set.add(path)

        elf_files = collections.deque()
        for path, ftype in entropy_repository.retrieveContentIter(package_id):
            real_path = content_root + path
            if self._is_elf_executable_or_library(
                real_path, allow_symlink = False):
                elf_files.append(real_path)

        resolve_cache = {}
        unresolved_sonames = {}

        for elf_file in elf_files:
            sonames = entropy.tools.read_elf_real_dynamic_libraries(elf_file)

            for soname in sonames:
                resolved_soname_path = resolve_cache.setdefault(soname,
                    entropy.tools.resolve_dynamic_library(soname, elf_file))

                if resolved_soname_path is None:
                    # library not found on system
                    # maybe it's into our package?
                    obj = unresolved_sonames.setdefault(elf_file, set())
                    obj.add(soname)
                    continue

                if resolved_soname_path in provided_libs_set:
                    # the soname path resolves to a provided library
                    continue

                # try with the complete real path
                # (libGL.so.1 -> /usr/lib64/opengl/...)
                if os.path.realpath(resolved_soname_path) in provided_libs_set:
                    # the real soname path resolves to a provided library
                    continue

                # fixup library dir, multilib systems
                real_dir = os.path.realpath(os.path.dirname(
                    resolved_soname_path))
                multilib_resolved_soname_path = os.path.join(real_dir,
                    os.path.basename(resolved_soname_path))

                if multilib_resolved_soname_path in provided_libs_set:
                    # soname library found, phew!
                    continue

                obj = unresolved_sonames.setdefault(elf_file, set())
                obj.add(soname)

        return unresolved_sonames

    def _resolve_library(self, entropy_client, library_name, elfclass,
                         repositories):
        """
        Resolve a library name (SONAME) in the given repositories.

        @param entropy_client: Entropy Client instance
        @type entropy_client: entropy.client.interfaces.client.Client base
            class object
        @param library_name: name of the library, typically a SONAME string
        @type library_name: string
        @param elfclass: ELF class of the library to resolve
        @type elfclass: int
        @param repositories: list of repository identifiers
        @type repositories: list
        @return: a list of resolved libraries, each item of the
        list is a tuple composed by (package_id, repository_id, library_path).
        @rtype: list
        """
        results = []

        for repository_id in repositories:
            repo = entropy_client.open_repository(repository_id)
            data_solved = repo.resolveNeeded(library_name,
                elfclass = elfclass, extended = True)
            if not data_solved:
                continue

            for pkg_id, path in data_solved:
                results.append((pkg_id, repository_id, path))

        return results

    def _get_missing_libraries(self, entropy_client, package_match):
        """
        Service method able to determine whether dependencies are missing
        in the given package_match using its NEEDED ELF metadata.

        @param entropy_client: Entropy Client instance
        @type entropy_client: entropy.client.interfaces.client.Client base
            class object
        @param package_match: Entropy package match: (package id, repo id).
        @type package_match: tuple
        @return: tuple of length 2, composed by a dictionary with the
            following structure:
            {('KEY', 'SLOT': set([list of missing deps for the given key])}
            and a "plain" list (set) of missing dependencies
            set([list of missing dependencies])
        @rtype: tuple
        """

        def is_system_pkg(pkg_id, repo, system_packages):
            if (pkg_id, repo.name) in system_packages:
                return True

            reverse_deps = repo.retrieveReverseDependencies(pkg_id,
                key_slot = True)
            # with virtual packages, it can happen that system packages
            # are not directly marked as such. so, check direct inverse deps
            # and see if we find one
            for rev_pkg_key, rev_pkg_slot in reverse_deps:
                rev_pkg_id, rev_repo_id = entropy_client.atom_match(rev_pkg_key,
                    match_slot = rev_pkg_slot)
                if rev_pkg_id == -1:
                    # can't find
                    continue
                if (rev_pkg_id, rev_repo_id) in system_packages:
                    return True

            return False

        def populate_caches(pkg_id, repo, provided_libs, scope_cache):
            """
            Populate provided_libs and scope_cache structures.
            """
            provided_libs_set = repo.retrieveProvidedLibraries(pkg_id)
            for pkg_lib, pkg_libpath, pkg_elfclass in provided_libs_set:
                obj = provided_libs.setdefault((pkg_lib, pkg_elfclass), set())
                obj.add(pkg_libpath)

            key, slot = repo.retrieveKeySlot(pkg_id)
            scope_cache.add((key, slot))

        repos = list(entropy_client.repositories())
        package_id, repository_id = package_match
        # max priority to package_match repo
        repos.remove(repository_id)
        repos.insert(0, repository_id)
        dbconn = entropy_client.open_repository(repository_id)

        rdepends = {}
        rdepends_plain = set()
        neededs = dbconn.retrieveNeeded(package_id, extended = True)
        if not neededs:
            return rdepends, rdepends_plain

        provided_libs = {}
        scope_cache = set()
        dependencies = self.get_deep_dependency_list(entropy_client,
            package_match, atoms = True)

        for dependency in dependencies:
            pkg_id, repo_id = entropy_client.atom_match(dependency)
            if pkg_id == -1:
                continue
            dep_repo = entropy_client.open_repository(repo_id)
            populate_caches(pkg_id, dep_repo, provided_libs, scope_cache)

        # add myself to the provided libs metadata
        populate_caches(package_id, dbconn, provided_libs, scope_cache)

        packages_cache = set()
        package_map = {}
        package_map_reverse = {}

        # Populate the system packages cache
        system_packages = set()
        for s_repo_id in repos:
            s_repo = entropy_client.open_repository(s_repo_id)
            for s_package_id in s_repo.listAllSystemPackageIds():
                system_packages.add((s_package_id, s_repo_id))

        for needed, elfclass in neededs:
            data_solved = self._resolve_library(
                entropy_client, needed, elfclass, repos)
            data_size = len(data_solved)
            data_solved = [(pkg_id, pkg_repo, path) for \
                pkg_id, pkg_repo, path in data_solved if (pkg_id, pkg_repo)
                    not in packages_cache]
            if not data_solved or (data_size != len(data_solved)):
                continue

            # check if the package is providing its own food
            provided_paths = provided_libs.get((needed, elfclass))
            found = False
            if provided_paths:
                for pkg_id, pkg_repo, path in data_solved:
                    if path in provided_paths:
                        found = True
                        break

            if found:
                # nothing to do!
                continue

            for pkg_id, pkg_repo, path in data_solved:
                pkg_dbconn = entropy_client.open_repository(pkg_repo)
                key, slot = pkg_dbconn.retrieveKeySlot(pkg_id)
                if (key, slot) in scope_cache:
                    continue
                system_pkg = is_system_pkg(pkg_id, pkg_dbconn, system_packages)
                if system_pkg:
                    # ignore system package missing dep if this is a
                    # system package, it means that further missing
                    # deps detection will be anyway wrong.
                    # !!! this fixes improper libgcc_s.so binding with
                    # gnat-gcc on amd64.
                    # but in general, system packages are implicit deps
                    break

                map_key = (needed, elfclass)
                keyslot = "%s%s%s" % (key, etpConst['entropyslotprefix'],
                    slot,)

                obj = rdepends.setdefault(map_key, set())
                obj.add(keyslot)

                obj = package_map_reverse.setdefault(keyslot, set())
                obj.add(map_key)

                pkg_match = (pkg_id, pkg_repo)
                obj = package_map.setdefault(map_key, set())
                obj.add(pkg_match)

                rdepends_plain.add(keyslot)
                packages_cache.add(pkg_match)

        # now reduce dependencies

        r_deplist = set()
        for key, package_matches in package_map.items():
            for package_match in package_matches:
                pkg_id, pkg_repo = package_match
                pkg_dbconn = entropy_client.open_repository(pkg_repo)
                r_deplist |= pkg_dbconn.retrieveDependencies(pkg_id,
                    exclude_deptypes = \
                        [etpConst['dependency_type_ids']['bdepend_id']])

        r_keyslots = set()
        for r_dep in r_deplist:
            pkg_id, pkg_repo = entropy_client.atom_match(r_dep)
            if pkg_id == -1:
                continue
            pkg_dbconn = entropy_client.open_repository(pkg_repo)
            keyslot = pkg_dbconn.retrieveKeySlotAggregated(pkg_id)
            if keyslot in rdepends_plain:
                r_keyslots.add(keyslot)

        rdepends_plain -= r_keyslots
        for r_keyslot in r_keyslots:
            keys = [x for x in package_map_reverse.get(keyslot, set()) if \
                x in rdepends]
            for key in keys:
                rdepends[key].discard(r_keyslot)
                if not rdepends[key]:
                    del rdepends[key]

        return rdepends, rdepends_plain

    def get_deep_dependency_list(self, entropy_client, package_match,
        atoms = False, match_repo = None):
        """
        Service method which returns a complete, expanded list of dependencies.

        NOTE: this method will only return dependencies that are NOT build
            dependencies.

        @param entropy_client: Entropy Client instance
        @type entropy_client: entropy.client.interfaces.client.Client based
            instance object
        @param package_match: Entropy package match (package id, repo id)
        @type package_match: tuple
        @keyword atoms: !! return type modifier !! , make method returning
            a list of atom strings instead of list of db match tuples.
        @type atoms: bool
        @keyword match_repo: list of repositories to match, if None,
            all repositories will be used for matching.
        @param match_repo: list
        @return: list of package matches (of dependencies) or plain dependency
            string list if "atom" is True
        @rtype: set
        """
        package_id, repo = package_match
        # WARNING: in order to avoid code duplication, an undocumented feature
        # has been implemented, which is required by
        # repository-webinstall-generator
        single_repo_mode = False
        if isinstance(repo, EntropyRepositoryBase):
            dbconn = repo
            single_repo_mode = True
            if entropy_client is not None:
                # written in stone here, the day that the code has to be split
                # this will be even more clear.
                raise AttributeError("in this case, entropy_client == None!")
        else:
            dbconn = entropy_client.open_repository(repo)

        excluded_dep_types = [etpConst['dependency_type_ids']['bdepend_id']]
        mybuffer = Lifo()
        depcache = set()
        matchcache = set()
        mydeps = dbconn.retrieveDependencies(package_id,
            exclude_deptypes = excluded_dep_types)
        for mydep in mydeps:
            mybuffer.push(mydep)
        try:
            mydep = mybuffer.pop()
        except ValueError:
            mydep = None # stack empty

        result = set()
        while mydep:

            if mydep in depcache:
                try:
                    mydep = mybuffer.pop()
                except ValueError:
                    break # stack empty
                continue

            if single_repo_mode:
                pkg_id, pkg_rc = dbconn.atomMatch(mydep)
                pkg_repo = dbconn.repository_id()
            else:
                pkg_id, pkg_repo = entropy_client.atom_match(mydep,
                    match_repo = match_repo)

            match = (pkg_id, pkg_repo)
            if match in matchcache:
                # neeeext !
                try:
                    mydep = mybuffer.pop()
                except ValueError:
                    break # stack empty
                continue
            matchcache.add(match)

            if atoms:
                result.add(mydep)

            if pkg_id != -1:
                if not atoms:
                    result.add(match)
                if single_repo_mode:
                    pkg_dbconn = dbconn
                else:
                    pkg_dbconn = entropy_client.open_repository(pkg_repo)
                owndeps = pkg_dbconn.retrieveDependencies(pkg_id,
                    exclude_deptypes = excluded_dep_types)
                for owndep in owndeps:
                    mybuffer.push(owndep)

            depcache.add(mydep)
            try:
                mydep = mybuffer.pop()
            except ValueError:
                break # stack empty

        return result

    def __analyze_package_edb(self, pkg_path):
        """
        Check if the physical Entropy package file contains
        a valid Entropy embedded database.

        @param pkg_path: path to physical entropy package file
        @type pkg_path: string
        @return: package validity
        @rtype: bool
        """
        from entropy.db import EntropyRepository
        from entropy.db.exceptions import Error

        fd, tmp_path = None, None
        dbc = None
        try:
            fd, tmp_path = const_mkstemp(
                prefix="entropy.qa.__analyze_package_edb")
            dump_rc = entropy.tools.dump_entropy_metadata(pkg_path, tmp_path)
            if not dump_rc:
                return False # error!

            valid = True
            try:
                dbc = EntropyRepository(
                    readOnly = False,
                    dbFile = tmp_path,
                    name = "qa_testing",
                    xcache = False,
                    indexing = False,
                    skipChecks = False
                )
                etp_repo_meta = {
                    'output_interface': self,
                }
                repo_plug = QAEntropyRepositoryPlugin(self,
                    metadata = etp_repo_meta)
                dbc.add_plugin(repo_plug)

            except Error:
                valid = False

            if valid:
                try:
                    dbc.validate()
                    dbc.integrity_check()
                except SystemDatabaseError:
                    valid = False

            if valid:
                try:
                    for idpackage in dbc.listAllPackageIds():
                        _tpath = None
                        for _path, ftype in dbc.retrieveContentIter(
                            idpackage):
                            _tpath = _path
                except Error:
                    valid = False

        finally:
            if dbc is not None:
                dbc.close()
            if fd is not None:
                os.close(fd)
            if tmp_path is not None:
                os.remove(tmp_path)

        return valid

    def entropy_package_checks(self, package_path):
        """
        Main method for the execution of QA tests on physical Entropy
        package files.

        @param package_path: path to physical Entropy package file path
        @type package_path: string
        @return: True, if all checks passed
        @rtype: bool
        @raise EntropyPackageException: raised by the QA testing function
        """
        # built-in ones
        qa_methods = [self.__analyze_package_edb]

        # plugged ones
        for plug_id, plug_inst in self.get_plugins().items():
            qa_methods += plug_inst.get_tests()

        # let's play!
        for method in qa_methods:
            qa_rc = method(package_path)
            if not qa_rc:
                return False
        return True


class ErrorReportInterface:

    """

    Interface used by Entropy Client to remotely send errors via HTTP POST.
    Some anonymous info about the running system are collected and sent over,
    once the user gives the acknowledgement for this operation.
    User should be asked for valid credentials, such as name, surname and email.
    This has two advantages: block stupid and lazy people and make possible
    for Entropy developers to contact him/her back.
    Moreover, the same applies for a simple description. To improve the
    ability to debug an issue, it is also asked the user to describe his/her
    action prior to the error.

    Sample code:

        >>> from entropy.qa import ErrorReportInterface
        >>> error = ErrorReportInterface('http://url_for_http_post')
        >>> error.prepare('traceback_text', 'John Foo', 'john@foo.com',
                report_data = 'extra traceback info',
                description = 'I was installing foo!')
        >>> error.submit()

    """

    def __init__(self, post_url):
        """
        ErrorReportInterface constructor.

        @param post_url: HTTP post url where to submit data
        @type post_url: string
        """
        from entropy.misc import MultipartPostHandler
        if const_is_python3():
            import urllib.request as urlmod
        else:
            import urllib2 as urlmod

        self.url = post_url
        self.opener = urlmod.build_opener(MultipartPostHandler)
        self.generated = False
        self.params = {}

        self._settings = SystemSettings()
        proxy_settings = self._settings['system']['proxy']
        mydict = {}
        if proxy_settings['ftp']:
            mydict['ftp'] = proxy_settings['ftp']
        if proxy_settings['http']:
            mydict['http'] = proxy_settings['http']
        if mydict:
            mydict['username'] = proxy_settings['username']
            mydict['password'] = proxy_settings['password']
            entropy.tools.add_proxy_opener(urlmod, mydict)
        else:
            # unset
            urlmod._opener = None

    def prepare(self, tb_text, name, email, report_data = "", description = ""):

        """
        This method must be called prior to submit(). It is used to prepare
        and collect system information before the submission.
        It is intentionally split from submit() to allow easy reimplementation.

        @param tb_text: Python traceback text to send
        @type tb_text: string
        @param name: submitter name
        @type name: string
        @param email: submitter email address
        @type email: string
        @keyword report_data: extra information
        @type report_data: string
        @keyword description: submitter action description
        @type description: string
        @return: None
        @rtype: None
        """
        from entropy.tools import getstatusoutput
        from entropy.client.interfaces.client import ClientSystemSettingsPlugin
        enc = etpConst['conf_encoding']

        self.params['arch'] = etpConst['currentarch']
        self.params['stacktrace'] = tb_text
        self.params['name'] = name
        self.params['email'] = email
        self.params['version'] = etpConst['entropyversion']
        self.params['errordata'] = report_data
        self.params['description'] = description
        self.params['arguments'] = ' '.join(sys.argv)
        self.params['uid'] = etpConst['uid']
        self.params['system_version'] = "N/A"
        self.params['repositories.conf'] = "---NA---"
        self.params['client.conf'] = "---NA---"

        self.params['processes'] = getstatusoutput('ps auxf')[1]
        self.params['lsof'] = getstatusoutput('lsof -p %s' % (os.getpid(),))[1]
        self.params['lspci'] = getstatusoutput('/usr/sbin/lspci')[1]
        self.params['dmesg'] = getstatusoutput('dmesg')[1]
        self.params['locale'] = getstatusoutput('locale -v')[1]

        # configuration files paths
        config_files = self._settings.get_setting_files_data()
        client_conf = ClientSystemSettingsPlugin.client_conf_path()

        try:
            with codecs.open(etpConst['systemreleasefile'], "r",
                             encoding=enc) as f_rel:
                self.params['system_version'] = f_rel.readline().strip()
        except IOError:
            pass

        try:
            with codecs.open(config_files['repositories'], "r",
                             encoding=enc) as rc_f:
                self.params['repositories.conf'] = rc_f.read()
        except IOError:
            pass

        try:
            with codecs.open(client_conf, "r", encoding=enc) as rc_f:
                self.params['client.conf'] = rc_f.read()
        except IOError:
            pass

        self.generated = True

    # params is a dict, key(HTTP post item name): value
    def submit(self):
        """
        Submit collected data remotely via HTTP POST.

        @raise PermissionDenied: when prepare() hasn't been called.
        @return: None
        @rtype: None
        """
        if self.generated:
            result = self.opener.open(self.url, self.params).read()
            if result.strip() == "1":
                return True
            return False
        else:
            mytxt = _("Not prepared yet")
            raise PermissionDenied("PermissionDenied: %s" % (mytxt,))
