summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--abrt.spec3
-rw-r--r--po/POTFILES.in1
-rw-r--r--src/daemon/abrt_event.conf4
-rw-r--r--src/plugins/Makefile.am6
-rwxr-xr-xsrc/plugins/abrt-action-install-debuginfo420
-rwxr-xr-xsrc/plugins/abrt-action-install-debuginfo.py472
6 files changed, 480 insertions, 426 deletions
diff --git a/abrt.spec b/abrt.spec
index b946cb30..154faa47 100644
--- a/abrt.spec
+++ b/abrt.spec
@@ -96,7 +96,6 @@ GTK+ wizard for convenient bug reporting.
Summary: %{name}'s C/C++ addon
Group: System Environment/Libraries
Requires: elfutils
-Requires: yum-utils
Requires: %{name} = %{version}-%{release}
%description addon-ccpp
@@ -378,7 +377,7 @@ fi
%{_libdir}/%{name}/libCCpp.so*
%{_libexecdir}/abrt-hook-ccpp
%{_libexecdir}/abrt-action-analyze-c
-%{_libexecdir}/abrt-action-install-debuginfo
+%{_libexecdir}/abrt-action-install-debuginfo.py*
%{_libexecdir}/abrt-action-generate-backtrace
%files addon-kerneloops
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ae6412f7..666d3c69 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -41,6 +41,7 @@ src/plugins/abrt-action-analyze-oops.c
src/plugins/abrt-action-analyze-python.c
src/plugins/abrt-action-bugzilla.cpp
src/plugins/abrt-action-generate-backtrace.c
+src/plugins/abrt-action-install-debuginfo.py
src/plugins/abrt-action-kerneloops.cpp
src/plugins/abrt-action-mailx.cpp
src/plugins/abrt-action-print.cpp
diff --git a/src/daemon/abrt_event.conf b/src/daemon/abrt_event.conf
index 938a1de6..45c9017a 100644
--- a/src/daemon/abrt_event.conf
+++ b/src/daemon/abrt_event.conf
@@ -46,12 +46,12 @@ EVENT=post-create analyzer=Kerneloops abrt-action-analyze-oops
# in the third argument (its format is CACHEDIR[:DEBUGINFODIR...]).
# For example, you can specify a network-mounted shared store
# of all debuginfos this way.
-EVENT=analyze analyzer=CCpp backtrace= abrt-action-install-debuginfo "$DUMP_DIR/coredump" "/var/run/abrt/$$-$RANDOM" /var/cache/abrt-di
+EVENT=analyze analyzer=CCpp backtrace= abrt-action-install-debuginfo.py "--core=$DUMP_DIR/coredump" "--tmpdir=/var/run/abrt/$$-$RANDOM" --cache=/var/cache/abrt-di
EVENT=analyze analyzer=CCpp backtrace= abrt-action-generate-backtrace
# Same as "analyze", but executed when user requests "refresh" in GUI
#EVENT=reanalyze analyzer=CCpp trim-debuginfo-cache /var/cache/abrt-di 4096m
-EVENT=reanalyze analyzer=CCpp abrt-action-install-debuginfo "$DUMP_DIR/coredump" "/var/run/abrt/$$-$RANDOM" /var/cache/abrt-di
+EVENT=reanalyze analyzer=CCpp abrt-action-install-debuginfo.py "--core=$DUMP_DIR/coredump" "--tmpdir=/var/run/abrt/$$-$RANDOM" --cache=/var/cache/abrt-di
EVENT=reanalyze analyzer=CCpp abrt-action-generate-backtrace
EVENT=report analyzer=Kerneloops abrt-action-kerneloops
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index 57b99e85..a7790e71 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -3,7 +3,7 @@ UTILS_PATH=$(srcdir)/../lib
AM_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH)
pluginslibdir=$(PLUGINS_LIB_DIR)
libexec_SCRIPTS = \
- abrt-action-install-debuginfo
+ abrt-action-install-debuginfo.py
pluginslib_LTLIBRARIES = \
libCCpp.la \
@@ -37,7 +37,9 @@ man_MANS = \
abrt-Upload.7 \
abrt-plugins.7
-EXTRA_DIST = $(man_MANS) abrt-action-install-debuginfo
+PYTHON_FILES = abrt-action-install-debuginfo.py
+
+EXTRA_DIST = $(man_MANS) $(PYTHON_FILES)
$(DESTDIR)/$(DEBUG_INFO_DIR):
$(mkdir_p) '$@'
diff --git a/src/plugins/abrt-action-install-debuginfo b/src/plugins/abrt-action-install-debuginfo
deleted file mode 100755
index 8af9345c..00000000
--- a/src/plugins/abrt-action-install-debuginfo
+++ /dev/null
@@ -1,420 +0,0 @@
-#!/bin/sh
-# Called by abrtd before producing a backtrace.
-# The task of this script is to install debuginfos.
-#
-# Just using [pk-]debuginfo-install does not work well.
-# - they can't install more than one version of debuginfo
-# for a package
-# - their output is unsuitable for scripting
-# - debuginfo-install aborts if yum lock is busy
-# - pk-debuginfo-install was observed to hang
-#
-# Usage: abrt-action-install-debuginfo CORE TEMPDIR [CACHEDIR[:DEBUGINFODIR1:DEBUGINFODIR2...]]
-# If CACHEDIR is specified, debuginfos should be installed there.
-# If not, debuginfos should be installed into TEMPDIR.
-#
-# Currently, we are called with CACHEDIR set to "/var/cache/abrt-di",
-# but in the future it may be omitted or set to something else.
-# Script must be ready for those cases too. Consider, for example,
-# corner cases of "" and "/".
-#
-# Output goes to GUI as debuginfo install log. The script should be careful
-# to give useful, but not overly cluttered info to stdout.
-#
-# Exitcodes:
-# 0 - all debuginfos are installed
-# 0 - not all debuginfos are installed
-# (was 1, but this scares event processing)
-# 2+ - serious problem
-#
-# Algorithm:
-# - Create TEMPDIR
-# - Extract build-ids from coredump
-# - For every build-id, check /usr/lib/debug/.build-id/XX/XXXX.debug
-# and CACHEDIR/usr/lib/debug/.build-id/XX/XXXX.debug
-# - If they all exist, exit 0
-# - Using "yum provides /usr/lib/debug/.build-id/XX/XXXX.debug",
-# figure out which debuginfo packages are needed
-# - Download them using "yumdownloader PACKAGE..."
-# - Unpack them with rpm2cpio | cpio to TEMPDIR
-# - If CACHEDIR is specified, copy usr/lib/debug/.build-id/XX/XXXX.debug
-# to CACHEDIR/usr/lib/debug/.build-id/XX/XXXX.debug and delete TEMPDIR
-# - Report which XX/XXXX.debug are still missing.
-#
-# For better debuggability, eu_unstrip.OUT, yum_provides.OUT etc files
-# are saved in TEMPDIR, and TEMPDIR is not deleted if we exit with exitcode 2
-# ("serious problem").
-
-
-debug=false
-# Useful if you need to see saved rpms, command outputs etc
-keep_tmp=false
-
-
-# Handle options
-if test x"$1" = x"--"; then
- shift
-else
- if test x"$1" = x"-v"; then
- debug=true
- shift
- fi
- if test $# -lt 2 || test x"$1" = x"--help"; then
- echo "Usage:"
- echo
- echo "abrt-action-install-debuginfo [-v] CORE TEMPDIR [CACHEDIR[:DEBUGINFODIR...]]"
- echo
- echo "TEMPDIR must be a name of a new temporary directory. It must not exist."
- echo "If CACHEDIR is specified, debuginfos are installed in CACHEDIR,"
- echo "and TEMPDIR is deleted on exit."
- echo "Otherwise, debuginfos are installed into TEMPDIR, which is not deleted."
- echo
- echo "Options:"
- echo " -v Verbose (for debugging)"
- echo
- exit
- fi
-fi
-
-
-# Parse params
-core="$1"
-tempdir="$2"
-debuginfodirs="${3//:/ }"
-cachedir="${3%%:*}"
-
-
-# stderr may be used for status messages too
-exec 2>&1
-
-
-error_msg_and_die() {
- echo "$*"
- exit 2
-}
-
-count_words() {
- echo $#
-}
-
-print_missing_build_ids() {
- local build_id
- local build_id1
- local build_id2
- local file
- local d
- for build_id in $build_ids; do
- build_id1=${build_id:0:2}
- build_id2=${build_id:2}
- file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug"
- test -f "/$file" && continue
- # On 2nd pass, we may already have some debuginfos in tempdir
- test -f "$tempdir/$file" && continue
- # Check cachedir if we have one
- for d in $debuginfodirs; do
- test -f "$d/$file" && continue 2
- done
- echo -n "$build_id "
- done
-}
-
-# Note: it is run in `backticks`, use >&2 for error messages
-print_missing_debuginfos() {
- local build_id
- local build_id1
- local build_id2
- local file
- local d
- for build_id in $build_ids; do
- build_id1=${build_id:0:2}
- build_id2=${build_id:2}
- file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug"
- test -f "/$file" && continue
- # On 2nd pass, we may already have some debuginfos in tempdir
- test -f "$tempdir/$file" && continue
- # Check cachedir if we have one
- if test x"$cachedir" != x""; then
- for d in $debuginfodirs; do
- test -f "$d/$file" && continue 2
- done
- fi
- echo -n "/$file "
- done
-}
-
-cleanup_and_report_missing() {
-# Which debuginfo files are still missing, including those we just unpacked?
- missing_build_ids=`print_missing_build_ids`
- $debug && echo "missing_build_ids:$missing_build_ids" >&2
-
- # If cachedir is specified, tempdir is just a staging area. Delete it
- if test x"$cachedir" != x""; then
- $keep_tmp && echo "NOT removing $tempdir (keep_tmp debugging is on)" >&2
- $keep_tmp || { $debug && echo "Removing $tempdir" >&2; rm -rf "$tempdir"; }
- fi
-
- # Disabled. We no longer need this, gdb output flags missing debuginfos anyway
- #for missing in $missing_build_ids; do
- # echo "MISSING:$missing" >&2
- #done
-
- test x"$missing_build_ids" != x"" && echo "`count_words $missing_build_ids` debuginfos can't be found" >&2
-}
-
-# $1: iteration (1,2...)
-# Note: it is run in `backticks`, use >&2 for error messages
-print_package_names() {
- # We'll run something like:
- # yum --enablerepo=*debuginfo* --quiet provides \
- # /usr/lib/debug/.build-id/bb/11528d59940983f495e9cb099cafb0cb206051.debug \
- # /usr/lib/debug/.build-id/c5/b84c0ad3676509dc30bfa7d42191574dac5b06.debug ...
- local yumopts=""
- if test x"$1" = x"1"; then
- yumopts="-C"
- echo "`count_words $missing_debuginfo_files` missing debuginfos, getting package list from cache" >&2
- else
- echo "`count_words $missing_debuginfo_files` missing debuginfos, getting package list from repositories" >&2
- fi
- # --showduplicates: do not just show the latest package
- # (tried to use -R2 to abort on stuck yum lock but -R is not about that)
- local cmd="yum $yumopts $yum_repo_opts --showduplicates --quiet provides $missing_debuginfo_files"
- echo "$cmd" >"yum_provides.$1.OUT"
- $debug && echo "Running: $cmd" >&2
- # eval is needed to strip away ''s in $yum_repo_opts; cant remove them and just use
- # unquoted $cmd, that would perform globbing on '*'
- local yum_provides_OUT="`eval $cmd 2>&1`"
- local err=$?
- printf "%s\nyum exitcode:%s\n" "$yum_provides_OUT" $err >>"yum_provides.$1.OUT"
- test $err = 0 || error_msg_and_die "yum provides... exited with $err:
-`head yum_provides.$1.OUT`" >&2
-
- # The output is pretty machine-unfriendly:
- # glibc-debuginfo-2.10.90-24.x86_64 : Debug information for package glibc
- # Repo : rawhide-debuginfo
- # Matched from:
- # Filename : /usr/lib/debug/.build-id/5b/c784c8d63f87dbdeb747a773940956a18ecd2f.debug
- #
- # 1:dbus-debuginfo-1.2.12-2.fc11.x86_64 : Debug information for package dbus
- # Repo : updates-debuginfo
- # Matched from:
- # Filename : /usr/lib/debug/.build-id/bc/da7d09eb6c9ee380dae0ed3d591d4311decc31.debug
- # Need to massage it a lot.
- # There can be duplicates (one package may provide many debuginfos).
- printf "%s\n" "$yum_provides_OUT" \
- | grep -- -debuginfo- \
- | sed 's/^[0-9]*://' \
- | sed -e 's/ .*//' -e 's/:.*//' \
- | sort | uniq | xargs
-}
-
-abort_if_low_on_disk_space() {
- local mb
- # free_blocks * block_size / (1024*1024), careful to not overflow:
- mb=$((`stat -f -c "%a / 8192 * %S / 128" "$tempdir"`))
- if test $mb -lt $1; then
- $debug && echo "Removing $tempdir" >&2
- rm -rf "$tempdir"
- error_msg_and_die "Less than $1 Mb of free space in $tempdir: $mb Mb"
- fi
- if test x"$cachedir" != x"" && test -d "$cachedir"; then
- mb=$((`stat -f -c "%a / 8192 * %S / 128" "$cachedir"`))
- if test $mb -lt $1; then
- $debug && echo "Removing $tempdir" >&2
- rm -rf "$tempdir"
- error_msg_and_die "Less than $1 Mb of free space in $cachedir: $mb Mb"
- fi
- fi
-}
-
-download_packages() {
- local pkg
- local err
- local file
- local build_id
- local build_id1
- local build_id2
- local d
-
- ## Download with one command (too silent):
- ## Redirecting, since progress bar stuff only messes up our output
- ##yumdownloader --enablerepo=*debuginfo* --quiet $packages >yumdownloader.OUT 2>&1
- ##err=$?
- ##echo "exitcode:$err" >>yumdownloader.OUT
- ##test $err = 0 || error_msg_and_die ...
- >yumdownloader.OUT
- i=1
- for pkg in $packages; do
- echo "Download $i/$num_packages: $pkg"
- echo "Download $i/$num_packages: $pkg" >>yumdownloader.OUT
- cmd="yumdownloader $yum_repo_opts --quiet $pkg"
- $debug && echo "Running: $cmd" >&2
- # eval is needed to strip away ''s in $yum_repo_opts
- eval $cmd >>yumdownloader.OUT 2>&1 &
- # using EXIT handler and this, make sure we kill yumdownloader if we exit:
- CHILD_PID=$!
- wait
- err=$?
- CHILD_PID=""
- echo "exitcode:$err" >>yumdownloader.OUT
- echo >>yumdownloader.OUT
- test $err = 0 || echo "Download of $pkg failed!"
- abort_if_low_on_disk_space 256
-
- # Process and delete the *.rpm file just downloaded
- # We do it right after download: some users have smallish disks...
- for file in *.rpm; do
- # Happens if no .rpm's were downloaded (yumdownloader problem)
- # In this case, $f is the literal "*.rpm" string
- test -f "$file" || { echo "No rpm file downloaded"; continue; }
- echo "Unpacking: $file"
- echo "Processing: $file" >>unpack.OUT
- rpm2cpio <"$file" >"unpacked.cpio" 2>>unpack.OUT || error_msg_and_die "Can't convert '$file' to cpio"
- $keep_tmp || rm "$file"
- abort_if_low_on_disk_space 256
- cpio -id <"unpacked.cpio" >>unpack.OUT 2>&1 || error_msg_and_die "Can't unpack '$file' cpio archive"
- rm "unpacked.cpio"
- abort_if_low_on_disk_space 256
- # Copy debuginfo files to cachedir
- if test x"$cachedir" != x"" && test -d "$cachedir"; then
- # For every needed debuginfo, check whether we have it
- for build_id in $build_ids; do
- build_id1=${build_id:0:2}
- build_id2=${build_id:2}
- file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug"
- # Do not copy it if it can be found in any of $debuginfodirs
- test -f "/$file" && continue
- if test x"$cachedir" != x""; then
- for d in $debuginfodirs; do
- test -f "$d/$file" && continue 2
- done
- fi
- if test -f "$file"; then
- # File is one of those we just installed, cache it
- mkdir -p "$cachedir/usr/lib/debug/.build-id/$build_id1"
- # Note: this does not preserve symlinks. This is intentional
- $debug && echo Copying "$file" to "$cachedir/$file" >&2
- echo "Caching debuginfo: $file"
- cp --remove-destination "$file" "$cachedir/$file" || error_msg_and_die "Can't copy $file (disk full?)"
- continue
- fi
- done
- fi
- # Delete remaining files unpacked from .cpio
- # which we didn't need after all
- rm -r etc bin sbin usr var opt 2>/dev/null
- done
- : $((i++))
- done
-}
-
-
-# Sanity checking
-test -f "$core" || error_msg_and_die "not a file: '$core'"
-# cachedir is optional
-test x"$cachedir" = x"" || test -d "$cachedir" || error_msg_and_die "bad cachedir '$cachedir'"
-# tempdir must not exist
-test -e "$tempdir" && error_msg_and_die "tempdir exists: '$tempdir'"
-
-# Intentionally not using -p: we want to abort if tempdir exists
-mkdir -- "$tempdir" || exit 2
-cd "$tempdir" || exit 2
-
-
-abort_if_low_on_disk_space 1024
-
-
-# A hook to stop yumdownloader, in case we are terminated by kill -TERM etc.
-CHILD_PID=""
-trap 'test x"$CHILD_PID" != x"" && kill -- "$CHILD_PID"' EXIT
-
-
-$debug && echo "Downloading rpms to $tempdir"
-
-
-echo "Getting list of build IDs"
-# Observed errors:
-# eu-unstrip: /var/spool/abrt/ccpp-1256301004-2754/coredump: Callback returned failure
-eu_unstrip_OUT=`eu-unstrip "--core=$core" -n 2>eu_unstrip.ERR`
-err=$?
-printf "%s\neu-unstrip exitcode:%s\n" "$eu_unstrip_OUT" $err >eu_unstrip.OUT
-test $err = 0 || error_msg_and_die "eu-unstrip exited with $err:
-`cat eu_unstrip.ERR`
-`head eu_unstrip.OUT`"
-
-# eu-unstrip output example:
-# 0x400000+0x209000 23c77451cf6adff77fc1f5ee2a01d75de6511dda@0x40024c - - [exe]
-# or
-# 0x400000+0x20d000 233aa1a57e9ffda65f53efdaf5e5058657a39993@0x40024c /usr/libexec/im-settings-daemon /usr/lib/debug/usr/libexec/im-settings-daemon.debug [exe]
-# 0x7fff5cdff000+0x1000 0d3eb4326fd7489fcf9b598269f1edc420e2c560@0x7fff5cdff2f8 . - linux-vdso.so.1
-# 0x3d15600000+0x208000 20196628d1bc062279622615cc9955554e5bb227@0x3d156001a0 /usr/lib64/libnotify.so.1.1.3 /usr/lib/debug/usr/lib64/libnotify.so.1.1.3.debug libnotify.so.1
-# 0x7fd8ae931000+0x62d000 dd49f44f958b5a11a1635523b2f09cb2e45c1734@0x7fd8ae9311a0 /usr/lib64/libgtk-x11-2.0.so.0.1600.6 /usr/lib/debug/usr/lib64/libgtk-x11-2.0.so.0.1600.6.debug
-#
-# Get space-separated list of all build-ids
-# There can be duplicates (observed in real world)
-build_ids=`printf "%s\n" "$eu_unstrip_OUT" \
-| while read junk1 build_id binary_file di_file lib_name junk2; do
- build_id=${build_id%%@*}
-
- # This filters out linux-vdso.so, among others
- test x"$lib_name" != x"[exe]" && test x"${binary_file:0:1}" != x"/" && continue
- # Sanitize build_id: must be longer than 2 chars
- test ${#build_id} -le 2 && continue
- # Sanitize build_id: must have only hex digits
- test x"${build_id//[0-9a-f]/}" != x"" && continue
-
- echo "$build_id"
-done | sort | uniq | xargs`
-$debug && echo "build_ids:$build_ids"
-
-
-# Prepare list of repos to use.
-# When we look for debuginfo we need only -debuginfo* repos, we can disable the rest
-# and thus make it faster.
-yum_repo_opts="'--disablerepo=*'"
-#// Disabled. Too often, debuginfo repos have names which do not conform to "foo-debuginfo" scheme,
-#// and users get bad backtraces.
-#// # (Without -C, yum for some reason wants to talk to repos! If one is down, it becomes S..L..O..W)
-#// for enabled_repo in `LANG=C yum -C repolist all | grep 'enabled:' | cut -f1 -d' ' | grep -v -- '-debuginfo'`; do
-#// yum_repo_opts="$yum_repo_opts '--enablerepo=${enabled_repo}-debuginfo*'"
-#// done
-yum_repo_opts="$yum_repo_opts '--enablerepo=*-debug*'"
-
-
-# We try to not run yum without -C unless absolutely necessary.
-# Therefore we loop. yum is run by print_package_names function,
-# on first iteration it is run with -C, on second - without,
-# which usually causes yum to download updated filelists,
-# which in turn takes several minutes and annoys users.
-iter=0
-while test $((++iter)) -le 2; do
- # Analyze $build_ids and check which debuginfos are present
- missing_debuginfo_files=`print_missing_debuginfos`
- # Did print_missing_debuginfos fail?
- test $? = 0 || exit 2
- $debug && echo "missing_debuginfo_files:$missing_debuginfo_files"
-
- test x"$missing_debuginfo_files" = x"" && break
-
- # Map $missing_debuginfo_files to package names.
- # yum is run here.
- packages=`print_package_names $iter`
- # Did print_package_names fail?
- test $? = 0 || exit 2
- $debug && echo "packages ($iter):$packages"
-
- # yum may return "" here if it found no packages (say, if coredump
- # is from a new, unreleased package fresh from koji).
- test x"$packages" = x"" && continue
-
- num_packages=`count_words $packages`
- echo "Downloading $num_packages packages"
- download_packages
-done
-
-cleanup_and_report_missing
-
-# Was exiting with 1, but this stops event processing.
-test x"$missing_build_ids" != x"" && exit 0
-
-echo "All needed debuginfos are present"
-exit 0
diff --git a/src/plugins/abrt-action-install-debuginfo.py b/src/plugins/abrt-action-install-debuginfo.py
new file mode 100755
index 00000000..b16cc3e9
--- /dev/null
+++ b/src/plugins/abrt-action-install-debuginfo.py
@@ -0,0 +1,472 @@
+#! /usr/bin/python -u
+# -*- coding: utf-8 -*-
+
+# WARNING: python -u means unbuffered I/O without it the messages are
+# passed to the parent asynchronously which looks bad in clients..
+from subprocess import Popen, PIPE
+import sys
+import os
+import getopt
+import shutil
+from yum import _, YumBase
+from yum.callbacks import DownloadBaseCallback
+
+# everything was ok
+RETURN_OK = 0
+# serious problem, should be logged somewhere
+RETURN_FAILURE = 2
+
+
+GETTEXT_PROGNAME = "abrt"
+import locale
+import gettext
+
+_ = lambda x: gettext.lgettext(x)
+
+def init_gettext():
+ try:
+ locale.setlocale(locale.LC_ALL, "")
+ except locale.Error:
+ import os
+ os.environ['LC_ALL'] = 'C'
+ locale.setlocale(locale.LC_ALL, "")
+ gettext.bind_textdomain_codeset(GETTEXT_PROGNAME, locale.nl_langinfo(locale.CODESET))
+ gettext.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale')
+ gettext.textdomain(GETTEXT_PROGNAME)
+
+
+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?"
+
+def ask_yes_no(prompt, retries=4):
+ while True:
+ response = raw_input(prompt)
+ if response in ('y', 'Y'):
+ return True
+ if response in ('n', 'N', ''):
+ return False
+ retries = retries - 1
+ if retries < 0:
+ break
+ return False
+
+# TODO: unpack just required debuginfo and not entire rpm?
+# ..that can lead to: foo.c No such file and directory
+# files is not used...
+def unpack_rpm(package_nevra, files, tmp_dir, destdir, keeprpm):
+ package_file_suffix = ".rpm"
+ package_full_path = tmp_dir + "/" + package_nevra + package_file_suffix
+ log1("Extracting %s to %s" % (package_full_path, destdir))
+ log2(files)
+ print (_("Extracting cpio from %s") % (package_full_path))
+ unpacked_cpio_path = tmp_dir + "/unpacked.cpio"
+ try:
+ unpacked_cpio = open(unpacked_cpio_path, 'wb')
+ except IOError, ex:
+ print (_("Can't write to:"), (unpacked_cpio_path,ex))
+ return RETURN_FAILURE
+ rpm2cpio = Popen(["rpm2cpio", package_full_path],
+ stdout=unpacked_cpio, bufsize=-1)
+ retcode = rpm2cpio.wait()
+
+ if retcode == 0:
+ log1("cpio written OK")
+ if not keeprpm:
+ log1("keeprpms = False, removing %s" % package_full_path)
+ print _("Removing the temporary rpm file")
+ os.unlink(package_full_path)
+ else:
+ unpacked_cpio.close()
+ print (_("Can't extract package: %s") % package_full_path)
+ return RETURN_FAILURE
+
+ # close the file
+ unpacked_cpio.close()
+ # and open it for reading
+ unpacked_cpio = open(unpacked_cpio_path, 'rb')
+
+ print (_("Caching files from %s made from %s") %
+ (unpacked_cpio_path, package_full_path))
+ cpio = Popen(["cpio","-i", "-d", "--quiet"],
+ stdin=unpacked_cpio, cwd=destdir, bufsize=-1)
+ retcode = cpio.wait()
+
+ if retcode == 0:
+ log1("files extracted OK")
+ print _("Removing the temporary cpio file")
+ os.unlink(unpacked_cpio_path)
+ else:
+ print (_("Can't extract files from: %s") % unpacked_cpio_path)
+ return RETURN_FAILURE
+
+class MyDownloadCallback(DownloadBaseCallback):
+ def __init__(self, total_pkgs):
+ self.total_pkgs = total_pkgs
+ self.downloaded_pkgs = 0
+ self.last_pct = 0
+ DownloadBaseCallback.__init__(self)
+
+ def updateProgress(self, name, frac, fread, ftime):
+ pct = int( frac*100 )
+ if pct == self.last_pct:
+ log2("percentage is the same, not updating progress")
+ return
+
+ self.last_pct = pct
+ # if run from terminal we can have a fancy output
+ if sys.stdout.isatty():
+ sys.stdout.write("\033[sDownloading (%i of %i) %.30s : %.3s %%\033[u"
+ % (self.downloaded_pkgs + 1, self.total_pkgs,
+ name, pct)
+ )
+ if pct == 100:
+ print _("Downloading (%i of %i) %.30s : %.3s %%"
+ % (self.downloaded_pkgs + 1, self.total_pkgs,
+ name, pct)
+ )
+ # but we want machine friendly output when spawned from abrt-server
+ else:
+ print (_("Downloading (%i of %i) %.30s : %.3s %%")
+ % (self.downloaded_pkgs + 1, self.total_pkgs, name, pct)
+ )
+
+ sys.stdout.flush()
+
+class DebugInfoDownload(YumBase):
+ """abrt-debuginfo-install --core=CORE tmpdir cachedir"""
+ def __init__(self, cache, tmp, keep_rpms=False):
+ self.cachedir = cache
+ self.tmpdir = tmp
+ self.keeprpms = keep_rpms
+ YumBase.__init__(self)
+ mute_stdout()
+ #self.conf.cache = os.geteuid() != 0
+ # Setup yum (Ts, RPM db, Repo & Sack)
+ self.doConfigSetup()
+ unmute_stdout()
+
+ def download(self, files):
+ """ @files - """
+ installed_size = 0
+ total_pkgs = 0
+ todownload_size = 0
+ downloaded_pkgs = 0
+ # nothing to download?
+ if not files:
+ return
+
+ print _("Searching the missing debuginfo packages")
+ # this suppress yum messages about setting up repositories
+ mute_stdout()
+
+ # make yumdownloader work as non root user.
+ if not self.setCacheDir():
+ self.logger.error("Error: Could not make cachedir, exiting")
+ sys.exit(50)
+
+ # disable all not needed
+ for repo in self.repos.listEnabled():
+ repo.close()
+ self.repos.disableRepo(repo.id)
+ # enable -debuginfo repos
+ for repo in self.repos.findRepos(pattern="*debuginfo*"):
+ #print repo
+ repo.enable()
+ rid = self.repos.enableRepo(repo.id)
+ log1("enabled repo %s" % rid)
+ setattr(repo, "skip_if_unavailable", True)
+ self.repos.doSetup()
+ # This is somewhat "magic", it unpacks the metadata making it usable.
+ self.repos.populateSack(mdtype='metadata', cacheonly=1)
+ self.repos.populateSack(mdtype='filelists', cacheonly=1)
+
+ # re-enable the output to stdout
+ unmute_stdout()
+
+ not_found = []
+ package_files_dict = {}
+ for debuginfo_path in files:
+ log2("yum whatprovides %s" %debuginfo_path)
+ pkg = self.pkgSack.searchFiles(debuginfo_path)
+ # sometimes one file is provided by more rpms, we can use either of
+ # them, so let's use the first match
+ if pkg:
+ if pkg[0] in package_files_dict.keys():
+ package_files_dict[pkg[0]].append(debuginfo_path)
+ else:
+ package_files_dict[pkg[0]] = [debuginfo_path]
+ todownload_size += float(pkg[0].size)
+ installed_size += float(pkg[0].installedsize)
+ total_pkgs += 1
+
+ log2("found pkg for %s" % debuginfo_path)
+ else:
+ log2("not found pkg for %s" % debuginfo_path)
+ not_found.append(debuginfo_path)
+
+ # connect our progress update callback
+ dnlcb = MyDownloadCallback(total_pkgs)
+ self.repos.setProgressBar( dnlcb )
+
+ log1("%i files in %i packages" % (len(files), total_pkgs))
+
+ print (_("To download: (%.2f) M / Installed size: %.2f M" %
+ (
+ todownload_size / (1024**2),
+ installed_size / (1024**2))
+ )
+ )
+ #print _("%i debug infos not found" % len(not_found))
+
+ log1("packages: %i\nTo download: %f \nUnpacked size: %f" %
+ (total_pkgs,
+ todownload_size / (1024**2),
+ installed_size / (1024**2)))
+
+ # ask only if we have terminal, because for now we don't have a way
+ # how to pass the question to gui and the response back
+ if noninteractive == False and sys.stdout.isatty():
+ if not ask_yes_no(_("Is this ok? [y/N] ")):
+ return RETURN_OK
+
+ for pkg, files in package_files_dict.iteritems():
+ dnlcb.downloaded_pkgs = downloaded_pkgs
+ repo.cache = 0
+ remote = pkg.returnSimple('relativepath')
+ local = os.path.basename(remote)
+ if not os.path.exists(self.tmpdir):
+ os.makedirs(self.tmpdir)
+ if not os.path.exists(self.cachedir):
+ os.makedirs(self.cachedir)
+ local = os.path.join(self.tmpdir, local)
+ pkg.localpath = local # Hack: to set the localpath we want
+ ret = self.downloadPkgs(pkglist=[pkg])
+ # downloadPkgs return an non empty list on succes
+ if ret:
+ print (_("Downloading package %s failed") % pkg)
+ else:
+ # normalize the name
+ # just str(pkg) doesn't work because it can have epoch
+ pkg_nvra = (pkg.name +"-"+ pkg.version +"-"+
+ pkg.release +"."+ pkg.arch)
+
+ unpack_result = unpack_rpm(pkg_nvra, files, self.tmpdir,
+ self.cachedir, keeprpms)
+ if unpack_result == RETURN_FAILURE:
+ # recursively delete the temp dir on failure
+ print _("Unpacking failed, aborting download...")
+ clean_up()
+ return RETURN_FAILURE
+
+ downloaded_pkgs += 1
+
+ if not self.keeprpms:
+ print (_("All downloaded packages have been extracted, removing %s")
+ % self.tmpdir)
+ try:
+ os.rmdir(self.tmpdir)
+ except OSError:
+ print _("Can't remove %s, probably contains an error log")
+
+verbose = 0
+def log1(message):
+ """ prints log message if verbosity > 0 """
+ if verbose > 0:
+ print "LOG1:", message
+
+def log2(message):
+ """ prints log message if verbosity > 1 """
+ if verbose > 1:
+ print "LOG2:", message
+
+#eu_unstrip_OUT=`eu-unstrip "--core=$core" -n 2>eu_unstrip.ERR`
+def extract_info_from_core(corefile):
+ """
+ Extracts builds with filenames,
+ Returns a list of tuples (build_id, filename)
+ """
+ #OFFSET = 0
+ BUILD_ID = 1
+ LIBRARY = 2
+ #SEP = 3
+ EXECUTABLE = 4
+
+ print (_("Analyzing corefile: %(corefile_path)s") %
+ {"corefile_path":corefile})
+ eu_unstrip_OUT = Popen(["eu-unstrip","--core=%s" % corefile, "-n"], stdout=PIPE, bufsize=-1).communicate()[0]
+ # parse eu_unstrip_OUT and return the list of build_ids
+
+ # eu_unstrip_OUT = ("0x7f42362ca000+0x204000 c4d35d993598a6242f7525d024b5ec3becf5b447@0x7f42362ca1a0 /usr/lib64/libcanberra-gtk.so.0 - libcanberra-gtk.so.0\n"
+ # "0x3afa400000+0x210000 607308f916c13c3ad9ee503008d31fa671ba73ce@0x3afa4001a0 /usr/lib64/libcanberra.so.0 - libcanberra.so.0\n"
+ # "0x3afa400000+0x210000 607308f916c13c3ad9ee503008d31fa671ba73ce@0x3afa4001a0 /usr/lib64/libcanberra.so.0 - libcanberra.so.0\n"
+ # "0x3bc7000000+0x208000 3be016bb723e85779a23e111a8ab1a520b209422@0x3bc70001a0 /usr/lib64/libvorbisfile.so.3 - libvorbisfile.so.3\n"
+ # "0x7f423609e000+0x22c000 87f9c7d9844f364c73aa2566d6cfc9c5fa36d35d@0x7f423609e1a0 /usr/lib64/libvorbis.so.0 - libvorbis.so.0\n"
+ # "0x7f4235e99000+0x205000 b5bc98c125a11b571cf4f2746268a6d3cfa95b68@0x7f4235e991a0 /usr/lib64/libogg.so.0 - libogg.so.0\n"
+ # "0x7f4235c8b000+0x20e000 f1ff6c8ee30dba27e90ef0c5b013df2833da2889@0x7f4235c8b1a0 /usr/lib64/libtdb.so.1 - libtdb.so.1\n"
+ # "0x3bc3000000+0x209000 8ef56f789fd914e8d0678eb0cdfda1bfebb00b40@0x3bc30001a0 /usr/lib64/libltdl.so.7 - libltdl.so.7\n"
+ # "0x7f4231b64000+0x22b000 3ca5b83798349f78b362b1ea51c8a4bc8114b8b1@0x7f4231b641a0 /usr/lib64/gio/modules/libgvfsdbus.so - libgvfsdbus.so\n"
+ # "0x7f423192a000+0x218000 ad024a01ad132737a8cfc7c95beb7c77733a652d@0x7f423192a1a0 /usr/lib64/libgvfscommon.so.0 - libgvfscommon.so.0\n"
+ # "0x7f423192a000+0x218000 ad024a01ad132737a8cfc7c95beb7c77733a652d@0x7f423192a1a0 /usr/lib64/libgvfscommon.so.0 - libgvfscommon.so.0\n"
+ # "0x3bb8e00000+0x20e000 d240ac5755184a95c783bb98a2d05530e0cf958a@0x3bb8e001a0 /lib64/libudev.so.0 - libudev.so.0")
+ #
+ #print eu_unstrip_OUT
+ # we failed to get build ids from the core -> die
+ if not eu_unstrip_OUT:
+ log1("can't get build ids from the core")
+ return RETURN_FAILURE
+
+ lines = eu_unstrip_OUT.split('\n')
+ # using set ensures the unique values
+ build_ids = set()
+ libraries = set()
+ build_ids = set()
+
+ for line in lines:
+ b_ids_line = line.split()
+ if len(b_ids_line) > 2:
+ # [exe] -> the executable itself
+ # linux-vdso.so.1 -> Virtual Dynamic Shared Object
+ if b_ids_line[EXECUTABLE] not in ["linux-vdso.so.1"]:
+ build_id = b_ids_line[BUILD_ID].split('@')[0]
+ build_ids.add(build_id)
+ library = b_ids_line[LIBRARY]
+ libraries.add(library)
+ build_ids.add(build_id)
+ else:
+ log2("skipping line %s" % line)
+ log1("Found %i build_ids" % len(build_ids))
+ log1("Found %i libs" % len(libraries))
+ return build_ids
+
+def build_ids_to_path(build_ids):
+ """
+ build_id1=${build_id:0:2}
+ build_id2=${build_id:2}
+ file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug"
+ """
+ return ["/usr/lib/debug/.build-id/%s/%s.debug" % (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_dir):
+ # 1st pass -> search in /usr/lib
+ missing_di = []
+ files = build_ids_to_path(build_ids)
+ for debuginfo_path in files:
+ cache_debuginfo_path = cache_dir + debuginfo_path
+ log2("checking path: %s" % debuginfo_path)
+ if os.path.exists(debuginfo_path):
+ log2("found: %s" % debuginfo_path)
+ continue
+ if os.path.exists(cache_debuginfo_path):
+ log2("found: %s" % cache_debuginfo_path)
+ continue
+ log2("not found: %s" % (cache_debuginfo_path))
+ missing_di.append(debuginfo_path)
+ return missing_di
+
+tmpdir = None
+def clean_up():
+ try:
+ shutil.rmtree(tmpdir)
+ except OSError, ex:
+ print (_("Can't remove %(tmpdir_path)s: %(reason)s")
+ % {"tmpdir_path":tmpdir, "reason": ex }
+ )
+
+def sigterm_handler(signum, frame):
+ clean_up()
+ exit(RETURN_OK)
+
+def sigint_handler(signum, frame):
+ clean_up()
+ print "\n", _("Exiting on user Command")
+ exit(RETURN_OK)
+
+import signal
+if __name__ == "__main__":
+ # abrt-server can send SIGTERM to abort the download
+ signal.signal(signal.SIGTERM, sigterm_handler)
+ # ctrl-c
+ signal.signal(signal.SIGINT, sigint_handler)
+ core = None
+ cachedir = None
+ tmpdir = None
+ keeprpms = False
+ result = RETURN_OK
+ noninteractive = False
+
+ # localization
+ init_gettext()
+
+ help_text = _("Usage: %s --core=<COREFILE> "
+ "--tmpdir=<TMPDIR> "
+ "--cachedir=<CACHEDIR>") % sys.argv[0]
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "vyhc:", ["help", "core=",
+ "cache=", "tmpdir=",
+ "keeprpms"])
+ except getopt.GetoptError, err:
+ print str(err) # prints something like "option -a not recognized"
+ sys.exit(RETURN_FAILURE)
+
+ for opt, arg in opts:
+ if opt == "-v":
+ verbose += 1
+ elif opt == "-y":
+ noninteractive = True
+ elif opt in ("--core","-c"):
+ core = arg
+ elif opt in ("--cache"):
+ cachedir = arg
+ elif opt in ("--tmpdir"):
+ tmpdir = arg
+ elif opt in ("--keeprpms"):
+ keeprpms = True
+ elif opt in ("-h", "--help"):
+ print help_text
+ sys.exit()
+
+ if not core:
+ print _("You have to specify the path to coredump.")
+ print help_text
+ exit(RETURN_FAILURE)
+ if not cachedir:
+ print _("You have to specify the path to cachedir.")
+ print help_text
+ exit(RETURN_FAILURE)
+ if not tmpdir:
+ print _("You have to specify the path to tmpdir.")
+ print help_text
+ exit(RETURN_FAILURE)
+
+ b_ids = extract_info_from_core(core)
+ if b_ids == RETURN_FAILURE:
+ exit(RETURN_FAILURE)
+ missing = filter_installed_debuginfos(b_ids, cachedir)
+ if missing:
+ log2(missing)
+ downloader = DebugInfoDownload(cache=cachedir, tmp=tmpdir)
+ result = downloader.download(missing)
+ else:
+ print _("All debuginfo seems to be available")
+ exit(RETURN_OK)
+
+ missing = filter_installed_debuginfos(b_ids, cachedir)
+ for bid in missing:
+ log1("MISSING:%s" % bid)
+
+ print _("Complete!")
+ exit(result)
+