#!/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)