|
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)
|