#!/usr/bin/python3 -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.
PROGNAME = "abrt-action-install-debuginfo"
import sys
import os
import errno
import getopt
import reportclient
from subprocess import Popen, PIPE
from reportclient import verbose, log_warning, log1, log2, set_verbosity, error_msg_and_die, error_msg
import time
from reportclient.debuginfo import DebugInfoDownload, filter_installed_debuginfos, build_ids_to_path, clean_up
import problem
# everything was ok
RETURN_OK = 0
# serious problem, should be logged somewhere
RETURN_FAILURE = 2
# path to tmp directory has to be global because of clean_up()
TMPDIR = None
GETTEXT_PROGNAME = "abrt"
import locale
import gettext
_ = lambda x: gettext.gettext(x)
def init_gettext():
try:
locale.setlocale(locale.LC_ALL, "")
except locale.Error:
os.environ['LC_ALL'] = 'C'
locale.setlocale(locale.LC_ALL, "")
# Defeat "AttributeError: 'module' object has no attribute 'nl_langinfo'"
try:
gettext.bind_textdomain_codeset(GETTEXT_PROGNAME, locale.nl_langinfo(locale.CODESET))
except AttributeError:
pass
gettext.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale')
gettext.textdomain(GETTEXT_PROGNAME)
def sigterm_handler(signum, frame):
clean_up(TMPDIR, silent=True)
exit(RETURN_OK)
def sigint_handler(signum, frame):
clean_up(TMPDIR)
print("\n{0}".format(_("Exiting on user command")))
sys.stdout.flush()
# ??! without "sys.", I am getting segv!
sys.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)
fbuild_ids = "build_ids"
cachedirs = []
size_mb = 4096
keeprpms = False
noninteractive = False
b_ids = []
exact_fls = False
missing = None
repo_pattern = "*debug*"
pkgmgr = None
releasever = None
# localization
init_gettext()
ABRT_VERBOSE = os.getenv("ABRT_VERBOSE")
if (ABRT_VERBOSE):
try:
verbose = int(ABRT_VERBOSE)
except:
pass
PROGNAME = os.path.basename(sys.argv[0])
# ____________________________________________________________________________________ 7
# ______01234567890123456789012345678901234567890123456789012345678901234567890123456789
help_text = _(
"Usage: %s [-vy] [--ids=BUILD_IDS_FILE] [--pkgmgr=(yum|dnf)]\n"
" [--tmpdir=TMPDIR] [--cache=CACHEDIR[:DEBUGINFODIR1:DEBUGINFODIR2...]] [--size_mb=SIZE]\n"
" [-e, --exact=PATH[:PATH]...]\n"
" [--releasever=RELEASEVER]\n"
"\n"
"Installs debuginfos for all build-ids listed in BUILD_IDS_FILE\n"
"to CACHEDIR, using TMPDIR as temporary staging area.\n"
"Old files in CACHEDIR are deleted until it is smaller than SIZE.\n"
"\n"
"Reads configuration from /etc/abrt/plugins/CCpp.conf\n"
"\n"
" -v Be verbose\n"
" -y Noninteractive, assume 'Yes' to all questions\n"
" --ids Default: build_ids\n"
" --tmpdir Default: @LARGE_DATA_TMP_DIR@/abrt-tmp-debuginfo-RANDOM_SUFFIX\n"
" --cache Colon separated list of directories. The first one is used for\n"
" saving installed debuginfos.\n"
" Default: /var/cache/abrt-dir\n"
" --size_mb Default: 4096\n"
" --pkgmgr Default: PackageManager from CCpp.conf or 'dnf'\n"
" -e,--exact Download only specified files\n"
" --repo Pattern to use when searching for repos.\n"
" Default: *debug*\n"
" --releasever RELEASEVER\n"
" Pass this OS version to package managers.\n"
# --keeprpms is not documented yet because it's a NOP so far
) % PROGNAME
try:
opts, args = getopt.getopt(sys.argv[1:], "vyhe",
["help", "ids=", "cache=", "size_mb=", "tmpdir=", "keeprpms",
"exact=", "repo=", "pkgmgr=", "releasever="])
except getopt.GetoptError as err:
print(err) # prints something like "option -a not recognized"
exit(RETURN_FAILURE)
for opt, arg in opts:
if opt in ("-h", "--help"):
print(help_text)
exit(0)
elif opt == "-v":
verbose += 1
elif opt == "-y":
noninteractive = True
elif opt == "--ids":
fbuild_ids = arg
elif opt == "--cache":
cachedirs = arg.split(':')
elif opt == "--size_mb":
try:
size_mb = int(arg)
except:
pass
elif opt == "--tmpdir":
TMPDIR = arg
elif opt == "--keeprpms":
keeprpms = True
# --exact takes precedence over --ids
elif opt in ("-e", "--exact"):
missing = arg.split(':')
exact_fls = True
elif opt == "--repo":
repo_pattern = arg
elif opt == "--pkgmgr":
pkgmgr = arg
elif opt == "--releasever":
releasever = arg
set_verbosity(verbose)
if not cachedirs:
cachedirs = ["/var/cache/abrt-di"]
try:
conf = problem.load_plugin_conf_file("CCpp.conf")
except OSError as ex:
print(ex)
else:
cachedirs = conf.get("DebuginfoLocation", cachedirs[0]).split(":")
if not TMPDIR:
# security people prefer temp subdirs in app's private dir, like /var/run/abrt
# and we switched to /tmp but Fedora feature tmp-on-tmpfs appeared, hence we must
# not use /tmp for potential big data anymore
TMPDIR = "@LARGE_DATA_TMP_DIR@/abrt-tmp-debuginfo-%s.%u" % (time.strftime("%Y-%m-%d-%H:%M:%S"), os.getpid())
if missing == None:
fin = sys.stdin
if fbuild_ids != "-":
try:
fin = open(fbuild_ids, "r")
except IOError as ex:
error_msg_and_die(_("Can't open {0}: {1}").format(fbuild_ids, ex))
for line in fin.readlines():
b_ids.append(line.strip('\n'))
if not b_ids:
exit(RETURN_FAILURE)
# Delete oldest/biggest files from cachedir.
# (Note that we need to do it before we check for missing debuginfos)
#
# We can do it as a separate step in report_event.conf, but this
# would require setuid'ing abrt-action-trim-files to abrt:abrt.
# Since we (via abrt-action-install-debuginfo-to-abrt-cache)
# are already running setuid,
# it makes sense to NOT setuid abrt-action-trim-files too,
# but instead run it as our child:
sys.stdout.flush()
try:
pid = os.fork()
if pid == 0:
argv = ["abrt-action-trim-files", "-f", "%um:%s" % (size_mb, cachedirs[0]), "--"]
argv.extend(build_ids_to_path(cachedirs[0], b_ids))
log2("abrt-action-trim-files %s", argv);
os.execvp("abrt-action-trim-files", argv);
error_msg_and_die("Can't execute '%s'", "abrt-action-trim-files");
if pid > 0:
os.waitpid(pid, 0);
except Exception as e:
error_msg("Can't execute abrt-action-trim-files: %s", e);
missing = filter_installed_debuginfos(b_ids, cachedirs)
exact_file_missing = False
result = RETURN_OK
if missing:
log2("%s", missing)
if len(b_ids) > 0:
print(_("Coredump references {0} debuginfo files, {1} of them are not installed").format(len(b_ids), len(missing)))
else:
# Only --exact FILE[:FILE2]... was specified
print(_("{0} of debuginfo files are not installed").format(len(missing)))
if pkgmgr is None:
try:
conf = problem.load_plugin_conf_file("CCpp.conf")
except OSError as ex:
sys.stderr.write("{0}".format(str(ex)))
sys.exit(RETURN_FAILURE)
pkgmgr = conf.get("PackageManager", "dnf").lower()
download_class = None
if pkgmgr == "dnf":
from reportclient.dnfdebuginfo import DNFDebugInfoDownload
download_class = DNFDebugInfoDownload
elif pkgmgr == "yum":
from reportclient.yumdebuginfo import YumDebugInfoDownload
download_class = YumDebugInfoDownload
else:
sys.stderr.write(_("Invalid configuration of CCpp addon, unsupported Package manager: '%s'") % (pkgmgr))
sys.exit(RETURN_FAILURE)
# TODO: should we pass keep_rpms=keeprpms to DebugInfoDownload here??
try:
downloader = download_class(cache=cachedirs[0], tmp=TMPDIR,
noninteractive=noninteractive,
repo_pattern=repo_pattern,
releasever=releasever)
result = downloader.download(missing, download_exact_files=exact_fls)
# make sure that all downloaded directories are writeable by abrt group
for root, dirs, files in os.walk(cachedirs[0]):
for walked_dir in dirs:
os.chmod(os.path.join(root, walked_dir), 0o775)
except OSError as ex:
if ex.errno == errno.EPIPE:
clean_up(TMPDIR, silent=True)
exit(RETURN_FAILURE)
error_msg_and_die("Can't download debuginfos: %s", ex)
if exact_fls:
for bid in missing:
if not os.path.isfile(bid):
print(_("Missing requested file: {0}").format(bid))
exact_file_missing = True
missing = filter_installed_debuginfos(b_ids, cachedirs)
for bid in missing:
print(_("Missing debuginfo file: {0}").format(bid))
if not missing and not exact_file_missing:
print(_("All debuginfo files are available"))
if exact_file_missing:
result = RETURN_FAILURE
exit(result)