From 8d9f6184d56b59a35295f9df2aad8b94e723b6b8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 23 Sep 2010 16:52:19 +0200 Subject: rename abrt-debuginfo-install to abrt-action-install-debuginfo Signed-off-by: Denys Vlasenko --- abrt.spec | 2 +- doc/implementation | 6 +- lib/plugins/CCpp.cpp | 10 +- src/daemon/Makefile.am | 6 +- src/daemon/abrt-action-install-debuginfo | 418 +++++++++++++++++++++++++++++++ src/daemon/abrt-debuginfo-install | 418 ------------------------------- 6 files changed, 431 insertions(+), 429 deletions(-) create mode 100755 src/daemon/abrt-action-install-debuginfo delete mode 100755 src/daemon/abrt-debuginfo-install diff --git a/abrt.spec b/abrt.spec index 28e02eb3..1b04dda6 100644 --- a/abrt.spec +++ b/abrt.spec @@ -351,7 +351,7 @@ fi %{_sbindir}/abrt-server %{_sbindir}/abrt-action-generate-backtrace %{_sbindir}/abrt-action-save-package-data -%{_bindir}/%{name}-debuginfo-install +%{_bindir}/abrt-action-install-debuginfo %{_bindir}/%{name}-handle-upload %{_bindir}/%{name}-backtrace %config(noreplace) %{_sysconfdir}/%{name}/%{name}.conf diff --git a/doc/implementation b/doc/implementation index 72f12c72..e749a47d 100644 --- a/doc/implementation +++ b/doc/implementation @@ -72,11 +72,11 @@ getReport(UUID) method or automatically if admin sets it in /etc/abrt/abrt.conf if it does then: b) daemon tries to install the debuginfo issuing this command: -execlp("abrt-debuginfo-install", "abrt-debuginfo-install", coredump, tempdir, debuginfo_dirs, NULL); +execlp("abrt-action-install-debuginfo", "abrt-action-install-debuginfo", coredump, tempdir, debuginfo_dirs, NULL); -abrt-debuginfo-install is a shell script using elfutils to get build-ids from coredump and the use +abrt-action-install-debuginfo is a shell script using elfutils to get build-ids from coredump and the use "yum provides" and "yumdownloader" to determine and download the missing debuginfo packages -* script source code: http://git.fedorahosted.org/git/abrt.git?p=abrt.git;a=blob_plain;f=src/Daemon/abrt-debuginfo-install +* script source code: http://git.fedorahosted.org/git/abrt.git?p=abrt.git;a=blob_plain;f=src/Daemon/abrt-action-install-debuginfo c) run gdb and get backtrace from coredump: see http://git.fedorahosted.org/git/abrt.git?p=abrt.git;a=blob_plain;f=lib/Plugins/CCpp.cpp line 260 - gdb is run with the same privileges as the crashed app (setregid, setreuid) diff --git a/lib/plugins/CCpp.cpp b/lib/plugins/CCpp.cpp index 041c85b8..a83a2b23 100644 --- a/lib/plugins/CCpp.cpp +++ b/lib/plugins/CCpp.cpp @@ -259,11 +259,11 @@ static char *install_debug_infos(const char *pDebugDumpDir, const char *debuginf /* SELinux guys are not happy with /tmp, using /var/run/abrt */ char *tempdir = xasprintf(LOCALSTATEDIR"/run/abrt/tmp-%lu-%lu", (long)getpid(), (long)time(NULL)); /* log() goes to stderr/syslog, it's ok to use it here */ - VERB1 log("Executing: %s %s %s %s", "abrt-debuginfo-install", coredump, tempdir, debuginfo_dirs); + VERB1 log("Executing: %s %s %s %s", "abrt-action-install-debuginfo", coredump, tempdir, debuginfo_dirs); /* We want parent to see errors in the same stream */ xdup2(STDOUT_FILENO, STDERR_FILENO); - execlp("abrt-debuginfo-install", "abrt-debuginfo-install", coredump, tempdir, debuginfo_dirs, NULL); - perror_msg("Can't execute '%s'", "abrt-debuginfo-install"); + execlp("abrt-action-install-debuginfo", "abrt-action-install-debuginfo", coredump, tempdir, debuginfo_dirs, NULL); + perror_msg("Can't execute '%s'", "abrt-action-install-debuginfo"); /* Serious error (1 means "some debuginfos not found") */ exit(2); } @@ -317,11 +317,11 @@ static char *install_debug_infos(const char *pDebugDumpDir, const char *debuginf if (WIFEXITED(status)) { if (WEXITSTATUS(status) > 1) - error_msg("%s exited with %u", "abrt-debuginfo-install", (int)WEXITSTATUS(status)); + error_msg("%s exited with %u", "abrt-action-install-debuginfo", (int)WEXITSTATUS(status)); } else { - error_msg("%s killed by signal %u", "abrt-debuginfo-install", (int)WTERMSIG(status)); + error_msg("%s killed by signal %u", "abrt-action-install-debuginfo", (int)WTERMSIG(status)); } return strbuf_free_nobuf(buf_build_ids); diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am index c03463bf..88da7a02 100644 --- a/src/daemon/Makefile.am +++ b/src/daemon/Makefile.am @@ -1,4 +1,6 @@ -bin_SCRIPTS = abrt-debuginfo-install abrt-handle-upload +bin_SCRIPTS = \ + abrt-action-install-debuginfo \ + abrt-handle-upload sbin_PROGRAMS = abrtd \ abrt-server \ @@ -111,7 +113,7 @@ dist_comredhatabrtservice_DATA = com.redhat.abrt.service man_MANS = abrtd.8 abrt.conf.5 -EXTRA_DIST = $(man_MANS) abrt-debuginfo-install abrt-handle-upload +EXTRA_DIST = $(man_MANS) abrt-action-install-debuginfo abrt-handle-upload if HAVE_SYSTEMD dist_systemdsystemunit_DATA = \ diff --git a/src/daemon/abrt-action-install-debuginfo b/src/daemon/abrt-action-install-debuginfo new file mode 100755 index 00000000..c1b8fdb9 --- /dev/null +++ b/src/daemon/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 diff --git a/src/daemon/abrt-debuginfo-install b/src/daemon/abrt-debuginfo-install deleted file mode 100755 index 3a236b59..00000000 --- a/src/daemon/abrt-debuginfo-install +++ /dev/null @@ -1,418 +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-debuginfo-install 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-debuginfo-install [-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 -- cgit