#!/usr/bin/python3

# Copyright (c) 2006 - 2013 Canonical Ltd.
# Authors: Alex Chiang <alex.chiang@canonical.com>
#          Kyle Nitzsche <kyle.nitzsche@canonical.com>
#          Martin Pitt <martin.pitt@ubuntu.com>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.

"""Use the coredump in a crash report to regenerate the stack traces. This is
helpful to get a trace with debug symbols."""

# TODO: Address following pylint complaints
# pylint: disable=invalid-name

import argparse
import gettext
import os
import shutil
import subprocess
import sys
from gettext import gettext as _

import apport
import apport.fileutils
import apport.sandboxutils

#
# functions
#


def parse_options():
    """Parse command line options and return options."""

    description = _("See man page for details.")

    parser = argparse.ArgumentParser(description=description)

    parser.add_argument(
        "-l",
        "--log",
        metavar="LOGFILE",
        default="valgrind.log",
        help=_("specify the log file name produced by valgrind"),
    )
    parser.add_argument(
        "--sandbox-dir",
        metavar="SDIR",
        help=_(
            "reuse a previously created sandbox dir (SDIR) or, if it does "
            "not exist, create it"
        ),
    )
    parser.add_argument(
        "--no-sandbox",
        action="store_true",
        help=_(
            "do  not  create  or reuse a sandbox directory for additional "
            "debug symbols but rely only on installed debug symbols."
        ),
    )
    parser.add_argument(
        "-C",
        "--cache",
        metavar="DIR",
        help=_(
            "reuse a previously created cache dir (CDIR) or, if it does "
            "not exist, create it"
        ),
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help=_(
            "report download/install progress when installing packages into sandbox"
        ),
    )
    parser.add_argument(
        "exe",
        metavar="EXECUTABLE",
        help=_(
            "the executable that is run under valgrind's memcheck tool "
            "for memory leak detection"
        ),
    )
    parser.add_argument(
        "-p",
        "--extra-package",
        metavar="PKG",
        action="append",
        default=[],
        help=_(
            "Install an extra package into the sandbox (can be specified "
            "multiple times)"
        ),
    )
    opts = parser.parse_args()

    return opts


def _exit_on_interrupt():
    sys.exit(1)


#
# main
#


options = parse_options()

try:
    apport.memdbg("start")
    apport.memdbg(f"Executable: {options.exe}")
    apport.memdbg(f"Command arguments: {str(options)}")

    gettext.textdomain("apport")

    # get and verify path to executable
    exepath = shutil.which(options.exe)
    exepath = os.path.abspath(exepath)
    if not exepath:
        sys.stderr.write(_("Error: %s is not an executable. Stopping.") % options.exe)
        sys.stderr.write("\n")
        sys.exit(1)
except (KeyboardInterrupt, SystemExit):
    sys.stderr.write("\nInterrupted during initialization\n")
    _exit_on_interrupt()

try:
    if not options.no_sandbox:
        # create report unless in no-sandbox mode
        report = apport.Report()

        report["ExecutablePath"] = exepath
        report.add_os_info()
        report.add_package_info()

        apport.memdbg("\nCreated report")
except (KeyboardInterrupt, SystemExit):
    sys.stderr.write("\nInterrupted during report creation\n")
    _exit_on_interrupt()


apport.memdbg("About to handle sandbox")

cache = None

try:
    # make the sandbox unless not wanted
    if not options.no_sandbox:
        sandbox, cache, outdated_msg = apport.sandboxutils.make_sandbox(
            report,
            "system",
            options.cache,
            options.sandbox_dir,
            options.extra_package,
            options.verbose,
        )

except (KeyboardInterrupt, SystemExit):
    sys.stderr.write("\nInterrupted while creating sandbox\n")
    _exit_on_interrupt()

apport.memdbg("About to get path to sandbox")

debugrootdir = None

try:
    if not options.no_sandbox:
        # get path to sandbox
        if sandbox:
            # sandbox is only defined when an auto created dir in tmp is in use
            debugrootdir = os.path.abspath(sandbox)
        elif options.sandbox_dir:
            # this is used when --sandbox-dir is passed as arg
            debugrootdir = os.path.abspath(options.sandbox_dir)

        # display sandbox and cache dirs, if any
        if debugrootdir:
            print("Sandbox directory:", debugrootdir)
        if cache:
            print("Cache directory:", cache)

    # prep to run valgrind
    argv = ["valgrind"]
    argv += ["-v", "--tool=memcheck", "--leak-check=full", "--num-callers=40"]
    argv += [f"--log-file={options.log}"]
    argv += ["--track-origins=yes"]
    if not options.no_sandbox:
        argv += [f"--extra-debuginfo-path={debugrootdir}/usr/lib/debug/"]
    argv += [exepath]

    apport.memdbg("before calling valgrind")
except (KeyboardInterrupt, SystemExit):
    sys.stderr.write("\nInterrupted while preparing to create sandbox\n")
    _exit_on_interrupt()

valgrind_env = {k: v for k, v in os.environ.items() if k != "DEBUGINFOD_URLS"}

try:
    subprocess.call(argv, env=valgrind_env)
except (KeyboardInterrupt, SystemExit):
    sys.stderr.write("\nInterrupted while running valgrind\n")
    _exit_on_interrupt()

apport.memdbg("information collection done")
