Signed-off-by: Petr Kubat <pkubat(a)redhat.com>
---
src/client-python/debuginfo.py | 174 +++++++++++++++++++++++++++++++++--------
1 file changed, 140 insertions(+), 34 deletions(-)
diff --git a/src/client-python/debuginfo.py b/src/client-python/debuginfo.py
index d1d78a6..fae03cd 100644
--- a/src/client-python/debuginfo.py
+++ b/src/client-python/debuginfo.py
@@ -1,30 +1,32 @@
-from subprocess import Popen, PIPE
-from yum import _, YumBase
-from yum.callbacks import DownloadBaseCallback
-from yum.Errors import YumBaseError
-from reportclient import *
-from reportclient import _
+"""
+ This module provides classes and functions used to download and manage
+ debuginfos.
+"""
+
import sys
import os
import time
import errno
import shutil
+from subprocess import Popen
-old_stdout = -1
-def mute_stdout():
- if verbose < 2:
- global old_stdout
- old_stdout = sys.stdout
- sys.stdout = open("/dev/null", "w")
-
-def unmute_stdout():
- if verbose < 2:
- if old_stdout != -1:
- sys.stdout = old_stdout
- else:
- print "ERR: unmute called without mute?"
+from yum import _, YumBase
+from yum.callbacks import DownloadBaseCallback
+from yum.Errors import YumBaseError
+
+from reportclient import (_, log1, log2, RETURN_OK, RETURN_FAILURE,
+ RETURN_CANCEL_BY_USER, verbose, ask_yes_no,
+ error_msg)
def ensure_abrt_uid(fn):
+ """
+ Ensures that the function is called using abrt's uid and gid
+
+ Returns:
+ Either an unchanged function object or a wrapper function object for
+ the function.
+ """
+
import pwd
current_uid = os.getuid()
current_gid = os.getgid()
@@ -35,6 +37,16 @@ def ensure_abrt_uid(fn):
return fn
def wrapped(*args, **kwargs):
+ """
+ Wrapper function around the called function.
+
+ Sets up uid and gid to match abrt's and after the function finishes
+ rolls its uid and gid back.
+
+ Returns:
+ Return value of the wrapped function.
+ """
+
# switch to abrt
os.setegid(abrt.pw_gid)
os.seteuid(abrt.pw_uid)
@@ -52,6 +64,21 @@ def ensure_abrt_uid(fn):
# files is not used...
@ensure_abrt_uid
def unpack_rpm(package_file_name, files, tmp_dir, destdir, keeprpm, exact_files=False):
+ """
+ Unpacks a single rpm located in tmp_dir into destdir.
+
+ Arguments:
+ package_file_name - name of the rpm file
+ files - files to extract from the rpm
+ tmp_dir - temporary directory where the rpm file is located
+ destdir - destination directory for the rpm package extraction
+ keeprpm - check if the user wants to delete rpms from the tmp directory
+ exact_files - extract only specified files
+
+ Returns:
+ RETURN_FAILURE in case of a serious problem
+ """
+
package_full_path = tmp_dir + "/" + package_file_name
log1("Extracting %s to %s", package_full_path, destdir)
log2("%s", files)
@@ -105,16 +132,19 @@ def unpack_rpm(package_file_name, files, tmp_dir, destdir, keeprpm,
exact_files=
print _("Can't extract files from
'{0}'").format(unpacked_cpio_path)
return RETURN_FAILURE
-def clean_up():
- if tmpdir:
- try:
- shutil.rmtree(tmpdir)
- except OSError, ex:
- if ex.errno != errno.ENOENT:
- error_msg(_("Can't remove '{0}':
{1}").format(tmpdir, ex))
-
class MyDownloadCallback(DownloadBaseCallback):
+ """
+ This class serves as a download progress handler for yum's progress bar.
+ """
+
def __init__(self, total_pkgs):
+ """
+ Sets up instance variables
+
+ Arguments:
+ total_pkgs - number of packages to download
+ """
+
self.total_pkgs = total_pkgs
self.downloaded_pkgs = 0
self.last_pct = 0
@@ -122,6 +152,16 @@ class MyDownloadCallback(DownloadBaseCallback):
DownloadBaseCallback.__init__(self)
def updateProgress(self, name, frac, fread, ftime):
+ """
+ A method used to update the progress
+
+ Arguments:
+ name - filename
+ frac - progress fracment (0 -> 1)
+ fread - formated string containing BytesRead
+ ftime - formated string containing remaining or elapsed time
+ """
+
pct = int(frac * 100)
if pct == self.last_pct:
log2("percentage is the same, not updating progress")
@@ -159,6 +199,10 @@ class MyDownloadCallback(DownloadBaseCallback):
sys.stdout.flush()
def downloadErrorCallback(callBackObj):
+ """
+ A callback function for mirror errors.
+ """
+
print _("Problem '{0!s}' occured while downloading from mirror:
'{1!s}'. Trying next one").format(
callBackObj.exception, callBackObj.mirror)
# explanation of the return value can be found here:
@@ -166,15 +210,18 @@ def downloadErrorCallback(callBackObj):
return {'fail':0}
class DebugInfoDownload(YumBase):
+ """
+ This class is used to manage download of debuginfos.
+ """
+
def __init__(self, cache, tmp, keep_rpms=False, noninteractive=True):
+ self.old_stdout = -1
self.cachedir = cache
self.tmpdir = tmp
- global tmpdir
- tmpdir = tmp
self.keeprpms = keep_rpms
self.noninteractive = noninteractive
YumBase.__init__(self)
- mute_stdout()
+ self.mute_stdout()
#self.conf.cache = os.geteuid() != 0
# Setup yum (Ts, RPM db, Repo & Sack)
# doConfigSetup() takes some time, let user know what we are doing
@@ -185,11 +232,44 @@ class DebugInfoDownload(YumBase):
# yum.Errors.YumBaseError: Error: rpmdb open failed
self.doConfigSetup()
except YumBaseError, ex:
- unmute_stdout()
+ self.unmute_stdout()
print _("Error initializing yum (YumBase.doConfigSetup):
'{0!s}'").format(ex)
#return 1 - can't do this in constructor
exit(1)
- unmute_stdout()
+ self.unmute_stdout()
+
+ def mute_stdout(self):
+ """
+ Links sys.stdout with /dev/null and saves the old stdout
+ """
+
+ if verbose < 2:
+ self.old_stdout = sys.stdout
+ sys.stdout = open("/dev/null", "w")
+
+ def unmute_stdout(self):
+ """
+ Replaces sys.stdout by stdout saved using mute
+ """
+
+ if verbose < 2:
+ if self.old_stdout != -1:
+ sys.stdout = self.old_stdout
+ else:
+ print "ERR: unmute called without mute?"
+
+ def cleanup_tmp_dir(self):
+ """
+ Removes the temporary directory.
+ """
+
+ if self.tmpdir:
+ try:
+ shutil.rmtree(self.tmpdir)
+ except OSError, ex:
+ if ex.errno != errno.ENOENT:
+ error_msg(_("Can't remove '{0}': {1}").format(
+ self.tmpdir, ex))
@ensure_abrt_uid
def setup_tmp_dirs(self):
@@ -210,7 +290,19 @@ class DebugInfoDownload(YumBase):
# return value will be used as exitcode. So 0 = ok, !0 - error
def download(self, files, download_exact_files=False):
- """ @files - """
+ """
+ Downloads rpms into a temporary directory
+
+ Arguments:
+ package_files_dict - a dict containing {pkg: file list} entries
+ total_pkgs - total number of packages to download
+ download_exact_files - extract only specified files
+
+ Returns:
+ RETURN_OK if all goes well.
+ RETURN_FAILURE in case it cannot set up either of the directories.
+ """
+
installed_size = 0
total_pkgs = 0
todownload_size = 0
@@ -394,7 +486,7 @@ class DebugInfoDownload(YumBase):
if unpack_result == RETURN_FAILURE:
# recursively delete the temp dir on failure
print _("Unpacking failed, aborting download...")
- clean_up()
+ self.cleanup_tmp_dir()
return RETURN_FAILURE
downloaded_pkgs += 1
@@ -413,15 +505,29 @@ class DebugInfoDownload(YumBase):
def build_ids_to_path(pfx, build_ids):
"""
+ Transforms build ids into a path.
+
build_id1=${build_id:0:2}
build_id2=${build_id:2}
file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug"
"""
+
return ["%s/usr/lib/debug/.build-id/%s/%s.debug" % (pfx, b_id[:2],
b_id[2:]) for b_id in build_ids]
# beware this finds only missing libraries, but not the executable itself ..
def filter_installed_debuginfos(build_ids, cache_dirs):
+ """
+ Checks for installed debuginfos.
+
+ Arguments:
+ build_ids - string containing build ids
+ cache_dirs - list of cache directories
+
+ Returns:
+ List of missing debuginfo files.
+ """
+
files = build_ids_to_path("", build_ids)
missing = []
--
1.7.11.7