Blame src/hooks/abrt_harvest_vmcore.py.in

Packit 8ea169
#!/usr/bin/python3
Packit 8ea169
"""
Packit 8ea169
 This script is meant to be run once at system startup after abrtd is up
Packit 8ea169
 and running. It moves all vmcore directories in kdump's dump directory
Packit 8ea169
 (which are presumably created by kdump) to abrtd spool directory.
Packit 8ea169
Packit 8ea169
 The goal is to let abrtd notice and process them as new problem data dirs.
Packit 8ea169
"""
Packit 8ea169
Packit 8ea169
import os
Packit 8ea169
import sys
Packit 8ea169
import shutil
Packit 8ea169
import time
Packit 8ea169
import hashlib
Packit 8ea169
import augeas
Packit 8ea169
from argparse import ArgumentParser
Packit 8ea169
from subprocess import Popen, PIPE
Packit 8ea169
Packit 8ea169
import problem
Packit 8ea169
import report
Packit 8ea169
Packit 8ea169
Packit 8ea169
def errx(message, code=1):
Packit 8ea169
    sys.stderr.write(message)
Packit 8ea169
    sys.stderr.write("\n")
Packit 8ea169
    sys.stderr.flush()
Packit 8ea169
    sys.exit(code)
Packit 8ea169
Packit 8ea169
def get_augeas(module, file_path):
Packit 8ea169
    """
Packit 8ea169
    A function for efficient configuration of Augeas.
Packit 8ea169
    Augeas modules are placed in /usr/share/augeas/lenses/dist
Packit 8ea169
    """
Packit 8ea169
Packit 8ea169
    aug_obj = augeas.Augeas(flags=augeas.Augeas.NO_MODL_AUTOLOAD)
Packit 8ea169
    aug_obj.set("/augeas/load/{0}/lens".format(module), "{0}.lns".format(module))
Packit 8ea169
    aug_obj.set("/augeas/load/{0}/incl".format(module), file_path)
Packit 8ea169
    aug_obj.load()
Packit 8ea169
    return aug_obj
Packit 8ea169
Packit 8ea169
def get_mount_point(part_id):
Packit 8ea169
    """
Packit 8ea169
    A function used to look up a mount point of the provided identifier
Packit 8ea169
    using 'findmnt' system utility.
Packit 8ea169
Packit 8ea169
    part_id - device node, label or uuid
Packit 8ea169
    """
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        proc = Popen(["@FINDMNT@", "--noheadings", "--first-only", "--raw",
Packit 8ea169
                     "--evaluate", "--output", "TARGET", part_id],
Packit 8ea169
                     stdout=PIPE, stderr=PIPE)
Packit 8ea169
        out, err = proc.communicate()
Packit 8ea169
        if err:
Packit 8ea169
            errx("Error finding mountpoint of '{0}': {1}"
Packit 8ea169
                 .format(part_id, err.decode()))
Packit 8ea169
Packit 8ea169
        result = out.decode().strip()
Packit 8ea169
        if proc.returncode != 0 or not result:
Packit 8ea169
            errx("Cannot find mountpoint of '{0}'".format(part_id))
Packit 8ea169
Packit 8ea169
        return result
Packit 8ea169
    except OSError as ex:
Packit 8ea169
        errx("Cannot run 'findmnt': {1}".format(str(ex)))
Packit 8ea169
Packit 8ea169
def parse_kdump(kdump_conf_path="/etc/kdump.conf"):
Packit 8ea169
    """
Packit 8ea169
    This function parses /etc/kdump.conf to get a path to kdump's
Packit 8ea169
    dump directory.
Packit 8ea169
    """
Packit 8ea169
    # default
Packit 8ea169
    dump_path = '/var/crash'
Packit 8ea169
Packit 8ea169
    # filesystem types that can be used by kdump for dumping
Packit 8ea169
    fs_types = ['ext4', 'ext3', 'ext2', 'minix', 'btrfs', 'xfs']
Packit 8ea169
Packit 8ea169
    if not os.access(kdump_conf_path, os.R_OK):
Packit 8ea169
        sys.stderr.write("%s not readable, using "
Packit 8ea169
                         "default path '%s'\n" % (kdump_conf_path, dump_path))
Packit 8ea169
        return dump_path
Packit 8ea169
Packit 8ea169
    aug_obj = get_augeas("Kdump", kdump_conf_path)
Packit 8ea169
    # check for path variable in kdump.conf
Packit 8ea169
    kdump_path = aug_obj.get("/files%s/path" % kdump_conf_path)
Packit 8ea169
    if kdump_path:
Packit 8ea169
        dump_path = kdump_path
Packit 8ea169
Packit 8ea169
    # default
Packit 8ea169
    partition = None
Packit 8ea169
    # first uncommented fs_type partition instruction
Packit 8ea169
    for fs_type in fs_types:
Packit 8ea169
        result = aug_obj.get("/files%s/%s" % (kdump_conf_path, fs_type))
Packit 8ea169
        if result:
Packit 8ea169
            partition = result
Packit 8ea169
            break
Packit 8ea169
Packit 8ea169
    if partition:
Packit 8ea169
        if os.path.isabs(dump_path):
Packit 8ea169
            # path is absolute, change it to relative
Packit 8ea169
            dump_path = dump_path.lstrip("/")
Packit 8ea169
        mount_point = get_mount_point(partition)
Packit 8ea169
        path = os.path.join(mount_point, dump_path)
Packit 8ea169
    else:
Packit 8ea169
        path = dump_path
Packit 8ea169
Packit 8ea169
    # full path to the dump directory
Packit 8ea169
    return path
Packit 8ea169
Packit 8ea169
Packit 8ea169
def create_abrtd_info(dest, uuid):
Packit 8ea169
    """
Packit 8ea169
    A simple function to write important information for the abrt daemon into
Packit 8ea169
    the vmcore directory to let abrtd know what kind of problem it is.
Packit 8ea169
Packit 8ea169
    dest - path to the vmcore directory
Packit 8ea169
    uuid - unique indentifier of the vmcore
Packit 8ea169
    """
Packit 8ea169
Packit 8ea169
    dd = report.dd_create(dest, 0)
Packit 8ea169
    if dd is None:
Packit 8ea169
        return None
Packit 8ea169
Packit 8ea169
    dd.create_basic_files(0)
Packit 8ea169
    dd.save_text('analyzer', 'abrt-vmcore')
Packit 8ea169
    dd.save_text('type', 'vmcore')
Packit 8ea169
    dd.save_text('component', 'kernel')
Packit 8ea169
    dd.save_text('uuid', uuid)
Packit 8ea169
    return dd
Packit 8ea169
Packit 8ea169
Packit 8ea169
def delete_and_close(dd):
Packit 8ea169
    """
Packit 8ea169
    Deletes the given dump directory and closes it.
Packit 8ea169
Packit 8ea169
    dd - dump directory object
Packit 8ea169
    """
Packit 8ea169
    # Save the directory name as the directory object could be destroyed during
Packit 8ea169
    # delete().
Packit 8ea169
    dd_dirname = dd.name
Packit 8ea169
    if not dd.delete() == 0:
Packit 8ea169
        sys.stderr.write("Unable to delete '%s'\n" % (dd_dirname))
Packit 8ea169
        return
Packit 8ea169
Packit 8ea169
    dd.close()
Packit 8ea169
Packit 8ea169
Packit 8ea169
def harvest_vmcore(crash_dir):
Packit 8ea169
    """
Packit 8ea169
    This function moves vmcore directories from kdump's dump dir
Packit 8ea169
    to abrt's dump dir and notifies abrt.
Packit 8ea169
Packit 8ea169
    The script also creates additional files used to tell abrt what kind of
Packit 8ea169
    problem it is and creates an uuid from the vmcore using a sha1 hash
Packit 8ea169
    function.
Packit 8ea169
    """
Packit 8ea169
Packit 8ea169
    if not os.access(crash_dir, os.R_OK):
Packit 8ea169
        sys.stderr.write("Dump directory '%s' not accessible. "
Packit 8ea169
                         "Exiting.\n" % crash_dir)
Packit 8ea169
        sys.exit(1)
Packit 8ea169
Packit 8ea169
    # Wait for abrtd to start. Give it at least 1 second to initialize.
Packit 8ea169
    for i in range(10):
Packit 8ea169
        if i is 9:
Packit 8ea169
            sys.exit(1)
Packit 8ea169
        elif os.system('pidof abrtd >/dev/null'):
Packit 8ea169
            time.sleep(1)
Packit 8ea169
        else:
Packit 8ea169
            break
Packit 8ea169
Packit 8ea169
    # Check abrt config files for copy/move settings and
Packit 8ea169
    try:
Packit 8ea169
        conf = problem.load_plugin_conf_file("vmcore.conf")
Packit 8ea169
    except OSError as ex:
Packit 8ea169
        sys.stderr.write(str(ex))
Packit 8ea169
        sys.exit(1)
Packit 8ea169
    else:
Packit 8ea169
        copyvmcore = conf.get("CopyVMcore", "no")
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        conf = problem.load_conf_file("abrt.conf")
Packit 8ea169
    except OSError as ex:
Packit 8ea169
        sys.stderr.write(str(ex))
Packit 8ea169
        sys.exit(1)
Packit 8ea169
    else:
Packit 8ea169
        abrtdumpdir = conf.get("DumpLocation", "@DEFAULT_DUMP_LOCATION@")
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        filelist = os.listdir(crash_dir)
Packit 8ea169
    except OSError:
Packit 8ea169
        sys.stderr.write("Dump directory '%s' not accessible. "
Packit 8ea169
                         "Exiting.\n" % crash_dir)
Packit 8ea169
        sys.exit(1)
Packit 8ea169
Packit 8ea169
    # Go through all directories in core dump directory
Packit 8ea169
    for cfile in filelist:
Packit 8ea169
        f_full = os.path.join(crash_dir, cfile)
Packit 8ea169
        if not os.path.isdir(f_full):
Packit 8ea169
            continue
Packit 8ea169
Packit 8ea169
        try:
Packit 8ea169
            vmcoredirfilelist = os.listdir(f_full)
Packit 8ea169
        except OSError as ex:
Packit 8ea169
            sys.stderr.write("VMCore dir '%s' not accessible.\n" % f_full)
Packit 8ea169
            continue
Packit 8ea169
        else:
Packit 8ea169
             if all(("vmcore" != ff
Packit 8ea169
                     for ff in vmcoredirfilelist
Packit 8ea169
                        if os.path.isfile(os.path.join(f_full, ff)))):
Packit 8ea169
                sys.stderr.write(
Packit 8ea169
                    "VMCore dir '%s' doesn't contain 'vmcore' file.\n" % f_full)
Packit 8ea169
                continue
Packit 8ea169
Packit 8ea169
        # We use .new suffix - we must make sure abrtd doesn't try
Packit 8ea169
        # to process partially-copied directory.
Packit 8ea169
        destdir = os.path.join(abrtdumpdir, ('vmcore-' + cfile))
Packit 8ea169
        destdirnew = destdir + '.new'
Packit 8ea169
        # Did we already copy it last time we booted?
Packit 8ea169
        if os.path.isdir(destdir):
Packit 8ea169
            continue
Packit 8ea169
        if os.path.isdir(destdirnew):
Packit 8ea169
            continue
Packit 8ea169
Packit 8ea169
        # TODO: need to generate *real* UUID,
Packit 8ea169
        # one which has a real chance of catching dups!
Packit 8ea169
        # This one generates different hashes even for similar cores:
Packit 8ea169
        hashobj = hashlib.sha1()
Packit 8ea169
        # Iterate over the file a line at a time in order to not load the whole
Packit 8ea169
        # vmcore file
Packit 8ea169
        with open(os.path.join(f_full, 'vmcore'), 'rb') as corefile:
Packit 8ea169
            while True:
Packit 8ea169
                chunk = corefile.read(8192)
Packit 8ea169
                if not chunk:
Packit 8ea169
                    break
Packit 8ea169
                hashobj.update(chunk)
Packit 8ea169
Packit 8ea169
        dd = create_abrtd_info(destdirnew, hashobj.hexdigest())
Packit 8ea169
        if dd is None:
Packit 8ea169
            sys.stderr.write("Unable to create problem directory info")
Packit 8ea169
            continue
Packit 8ea169
Packit 8ea169
        # Copy/move vmcore directory to abrt spool dir.
Packit 8ea169
        for name in os.listdir(f_full):
Packit 8ea169
            full_name = os.path.join(f_full, name)
Packit 8ea169
Packit 8ea169
            # Skip sub-directories, abrt ignores them in its processing anyway
Packit 8ea169
            if not os.path.isfile(full_name):
Packit 8ea169
                continue
Packit 8ea169
Packit 8ea169
            try:
Packit 8ea169
                if not dd.copy_file(name, full_name) == 0:
Packit 8ea169
                    raise OSError
Packit 8ea169
            except (OSError, shutil.Error):
Packit 8ea169
                sys.stderr.write("Unable to copy '%s' to '%s'. Skipping\n"
Packit 8ea169
                                 % (full_name, destdirnew))
Packit 8ea169
                delete_and_close(dd)
Packit 8ea169
                continue
Packit 8ea169
Packit 8ea169
        # Get rid of the .new suffix
Packit 8ea169
        if not dd.rename(destdir) == 0:
Packit 8ea169
            sys.stderr.write("Unable to rename '%s' to '%s'. Skipping\n" % (destdirnew, destdir))
Packit 8ea169
            delete_and_close(dd)
Packit 8ea169
            continue
Packit 8ea169
Packit 8ea169
        dd.close()
Packit 8ea169
Packit 8ea169
        if copyvmcore == 'no':
Packit 8ea169
            try:
Packit 8ea169
                shutil.rmtree(f_full)
Packit 8ea169
            except OSError:
Packit 8ea169
                sys.stderr.write("Unable to delete '%s'. Ignoring\n" % f_full)
Packit 8ea169
Packit 8ea169
        problem.notify_new_path(destdir)
Packit 8ea169
Packit 8ea169
Packit 8ea169
if __name__ == '__main__':
Packit 8ea169
    parser = ArgumentParser()
Packit 8ea169
    parser.add_argument("--kdump", default="/etc/kdump.conf",
Packit 8ea169
                        help="path to kdump.conf")
Packit 8ea169
    args = parser.parse_args()
Packit 8ea169
Packit 8ea169
    crash_dir = parse_kdump(args.kdump)
Packit 8ea169
Packit 8ea169
    harvest_vmcore(crash_dir)