summaryrefslogtreecommitdiffstats
path: root/src/plugins/abrt-action-install-debuginfo
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/abrt-action-install-debuginfo')
-rwxr-xr-xsrc/plugins/abrt-action-install-debuginfo418
1 files changed, 418 insertions, 0 deletions
diff --git a/src/plugins/abrt-action-install-debuginfo b/src/plugins/abrt-action-install-debuginfo
new file mode 100755
index 00000000..c1b8fdb9
--- /dev/null
+++ b/src/plugins/abrt-action-install-debuginfo
@@ -0,0 +1,418 @@
+#!/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.
+# Additionally, abrt daemon handles "MISSING:xxxx" messages specially:
+# it is used to inform about missing debuginfos.
+#
+# Exitcodes:
+# 0 - all debuginfos are installed
+# 1 - not all debuginfos are installed
+# 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
+
+ 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
+
+test x"$missing_build_ids" != x"" && exit 1
+echo "All needed debuginfos are present"
+exit 0