Blame src/lib/report.c

Packit 4f15d5
/*
Packit 4f15d5
    Copyright (C) 2011  ABRT Team
Packit 4f15d5
    Copyright (C) 2011  RedHat inc.
Packit 4f15d5
Packit 4f15d5
    This program is free software; you can redistribute it and/or modify
Packit 4f15d5
    it under the terms of the GNU General Public License as published by
Packit 4f15d5
    the Free Software Foundation; either version 2 of the License, or
Packit 4f15d5
    (at your option) any later version.
Packit 4f15d5
Packit 4f15d5
    This program is distributed in the hope that it will be useful,
Packit 4f15d5
    but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 4f15d5
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 4f15d5
    GNU General Public License for more details.
Packit 4f15d5
Packit 4f15d5
    You should have received a copy of the GNU General Public License along
Packit 4f15d5
    with this program; if not, write to the Free Software Foundation, Inc.,
Packit 4f15d5
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Packit 4f15d5
*/
Packit 4f15d5
#include "report.h"
Packit 4f15d5
#include "internal_libreport.h"
Packit 4f15d5
Packit 4f15d5
int report_problem_in_dir(const char *dirname, int flags)
Packit 4f15d5
{
Packit 4f15d5
    /* Prepare it before fork, to avoid thread-unsafe setenv there */
Packit 4f15d5
    char *prgname = (char*) g_get_prgname();
Packit 4f15d5
    if (prgname)
Packit 4f15d5
        prgname = xasprintf("LIBREPORT_PRGNAME=%s", prgname);
Packit 4f15d5
Packit 4f15d5
    if (flags & LIBREPORT_IGNORE_NOT_REPORTABLE)
Packit 4f15d5
    {
Packit 4f15d5
        load_global_configuration();
Packit 4f15d5
        set_global_stop_on_not_reportable(false, 0);
Packit 4f15d5
    }
Packit 4f15d5
Packit 4f15d5
    fflush(NULL);
Packit 4f15d5
Packit 4f15d5
    pid_t pid = fork();
Packit 4f15d5
    if (pid < 0) /* error */
Packit 4f15d5
    {
Packit 4f15d5
        perror_msg("fork");
Packit 4f15d5
        return -1;
Packit 4f15d5
    }
Packit 4f15d5
Packit 4f15d5
    if (pid == 0) /* child */
Packit 4f15d5
    {
Packit 4f15d5
        const char *event_name;
Packit 4f15d5
        const char *path, *path1, *path2;
Packit 4f15d5
        char *args[7], **pp;
Packit 4f15d5
Packit 4f15d5
        /* Graphical tool */
Packit 4f15d5
        event_name = "report-gui";
Packit 4f15d5
        path1 = BIN_DIR"/report-gtk";
Packit 4f15d5
        path2 = "report-gtk";
Packit 4f15d5
        pp = args;
Packit 4f15d5
        *pp++ = (char *)"report-gtk";
Packit 4f15d5
        if (flags & LIBREPORT_DEL_DIR)
Packit 4f15d5
            *pp++ = (char *)"--delete";
Packit 4f15d5
        *pp++ = (char *)"--";
Packit 4f15d5
        *pp++ = (char *)dirname;
Packit 4f15d5
        *pp = NULL;
Packit 4f15d5
Packit 4f15d5
        if (prgname)
Packit 4f15d5
            putenv(prgname);
Packit 4f15d5
Packit 4f15d5
        if (flags & LIBREPORT_RUN_NEWT)
Packit 4f15d5
        {
Packit 4f15d5
            /* we want to run newt first */
Packit 4f15d5
            event_name = "report-cli";
Packit 4f15d5
            path1 = BIN_DIR"/report-newt";
Packit 4f15d5
            path2 = "report-newt";
Packit 4f15d5
            pp = args;
Packit 4f15d5
            *pp++ = (char *)"report-newt";
Packit 4f15d5
            if (flags & LIBREPORT_DEL_DIR)
Packit 4f15d5
                *pp++ = (char *)"--delete";
Packit 4f15d5
            *pp++ = (char *)"--";
Packit 4f15d5
            *pp++ = (char *)dirname;
Packit 4f15d5
            *pp = NULL;
Packit 4f15d5
        }
Packit 4f15d5
        else if (!getenv("DISPLAY") || (flags & LIBREPORT_RUN_CLI))
Packit 4f15d5
        {
Packit 4f15d5
            /* GUI won't work, use command line tool instead */
Packit 4f15d5
            event_name = "report-cli";
Packit 4f15d5
            path1 = BIN_DIR"/report-cli";
Packit 4f15d5
            path2 = "report-cli";
Packit 4f15d5
            pp = args;
Packit 4f15d5
            *pp++ = (char *)"report-cli";
Packit 4f15d5
            if (flags & LIBREPORT_DEL_DIR)
Packit 4f15d5
                *pp++ = (char *)"--delete";
Packit 4f15d5
            *pp++ = (char *)"-e";
Packit 4f15d5
            *pp++ = (char *)"report-cli";
Packit 4f15d5
            *pp++ = (char *)"--";
Packit 4f15d5
            *pp++ = (char *)dirname;
Packit 4f15d5
            *pp = NULL;
Packit 4f15d5
        }
Packit 4f15d5
Packit 4f15d5
        /* Some callers set SIGCHLD to SIG_IGN.
Packit 4f15d5
         * However, reporting spawns child processes.
Packit 4f15d5
         * Suppressing child death notification terribly confuses some of them.
Packit 4f15d5
         * Just in case, undo it.
Packit 4f15d5
         * Note that we do it in the child, so the parent is never affected.
Packit 4f15d5
         */
Packit 4f15d5
        signal(SIGCHLD, SIG_DFL);
Packit 4f15d5
Packit 4f15d5
        if (!(flags & (LIBREPORT_WAIT | LIBREPORT_GETPID)))
Packit 4f15d5
        {
Packit 4f15d5
            /* Caller doesn't want to wait for completion (!LIBREPORT_WAIT),
Packit 4f15d5
             * and doesn't want to have pid returned (!LIBREPORT_GETPID).
Packit 4f15d5
             * Create a grandchild, and then exit.
Packit 4f15d5
             * This reparents grandchild to init, and makes waitpid
Packit 4f15d5
             * in parent detect our exit and return almost immediately.
Packit 4f15d5
             */
Packit 4f15d5
            pid_t pid = fork();
Packit 4f15d5
            if (pid < 0) /* error */
Packit 4f15d5
                perror_msg_and_die("fork");
Packit 4f15d5
            if (pid != 0) /* not grandchild */
Packit 4f15d5
            {
Packit 4f15d5
                /* And now we exit: */
Packit 4f15d5
                _exit(0);
Packit 4f15d5
            }
Packit 4f15d5
            /* There's an alternative approach to achieve this,
Packit 4f15d5
             * instead of using --delete.
Packit 4f15d5
             * We can create yet another intermediate process which
Packit 4f15d5
             * waits for reporting child to finish, and then
Packit 4f15d5
             * removes temporary dump dir.
Packit 4f15d5
             * Pros: deletion becomes more robust.
Packit 4f15d5
             * Even if child crashes, dir will be deleted.
Packit 4f15d5
             * Cons: having another process would use some resources,
Packit 4f15d5
             * and we'll need to at least close all open file descriptors,
Packit 4f15d5
             * and reopen stdio to /dev/null. We also might keep
Packit 4f15d5
             * a lot of libraries loaded:
Packit 4f15d5
             * who knows what parent process links against.
Packit 4f15d5
             * (can be worked around by exec'ing a "wait & delete" helper)
Packit 4f15d5
             */
Packit 4f15d5
        }
Packit 4f15d5
Packit 4f15d5
        struct run_event_state *run_state = new_run_event_state();
Packit 4f15d5
        int r = run_event_on_dir_name(run_state, dirname, event_name);
Packit 4f15d5
        int no_such_event = (r == 0 && run_state->children_count == 0);
Packit 4f15d5
        free_run_event_state(run_state);
Packit 4f15d5
        if (!no_such_event)
Packit 4f15d5
        {
Packit 4f15d5
            if (flags & LIBREPORT_DEL_DIR)
Packit 4f15d5
            {
Packit 4f15d5
                struct dump_dir *dd = dd_opendir(dirname, 0);
Packit 4f15d5
                if (dd)
Packit 4f15d5
                    dd_delete(dd);
Packit 4f15d5
            }
Packit 4f15d5
            _exit(r);
Packit 4f15d5
        }
Packit 4f15d5
        /* No "report-cli/gui" event found, do it old-style */
Packit 4f15d5
Packit 4f15d5
        path = path1;
Packit 4f15d5
        log_info("Executing: %s", path);
Packit 4f15d5
        execv(path, args);
Packit 4f15d5
        /* Did not find the desired executable in the installation directory.
Packit 4f15d5
         * Trying to find it in PATH.
Packit 4f15d5
         */
Packit 4f15d5
        path = path2;
Packit 4f15d5
        execvp(path, args);
Packit 4f15d5
        perror_msg_and_die("Can't execute %s", path);
Packit 4f15d5
    }
Packit 4f15d5
Packit 4f15d5
    /* parent */
Packit 4f15d5
    free(prgname);
Packit 4f15d5
Packit 4f15d5
    if (!(flags & LIBREPORT_WAIT) && (flags & LIBREPORT_GETPID))
Packit 4f15d5
        return pid;
Packit 4f15d5
Packit 4f15d5
    /* we are here either if LIBREPORT_WAIT (caller wants exitcode)
Packit 4f15d5
     * or !LIBREPORT_GETPID (caller doesn't want to have a child).
Packit 4f15d5
     * In both cases, we need to wait for child:
Packit 4f15d5
     */
Packit 4f15d5
    int status;
Packit 4f15d5
    pid = safe_waitpid(pid, &status, 0);
Packit 4f15d5
    if (pid <= 0)
Packit 4f15d5
    {
Packit 4f15d5
        perror_msg("waitpid");
Packit 4f15d5
        return -1;
Packit 4f15d5
    }
Packit 4f15d5
Packit 4f15d5
    if (WIFEXITED(status))
Packit 4f15d5
    {
Packit 4f15d5
        log_info("reporting finished with exitcode %d", WEXITSTATUS(status));
Packit 4f15d5
        return WEXITSTATUS(status);
Packit 4f15d5
    }
Packit 4f15d5
    /* child died from a signal: WIFSIGNALED(status) should be true */
Packit 4f15d5
    log_info("reporting killed by signal %d", WTERMSIG(status));
Packit 4f15d5
    return WTERMSIG(status) + 128;
Packit 4f15d5
}
Packit 4f15d5
Packit 4f15d5
int report_problem_in_memory(problem_data_t *pd, int flags)
Packit 4f15d5
{
Packit 4f15d5
    int result = 0;
Packit 4f15d5
    struct dump_dir *dd = create_dump_dir_from_problem_data(pd, LARGE_DATA_TMP_DIR);
Packit 4f15d5
    if (!dd)
Packit 4f15d5
        return -1;
Packit 4f15d5
    char *dir_name = xstrdup(dd->dd_dirname);
Packit 4f15d5
    dd_close(dd);
Packit 4f15d5
    log_info("Temp problem dir: '%s'", dir_name);
Packit 4f15d5
Packit 4f15d5
    if (!(flags & LIBREPORT_WAIT))
Packit 4f15d5
        flags |= LIBREPORT_DEL_DIR;
Packit 4f15d5
    result = report_problem_in_dir(dir_name, flags);
Packit 4f15d5
Packit 4f15d5
    /* If we waited for reporter to finish, we should clean up the tmp dir
Packit 4f15d5
     * (if we didn't, cleaning up will be done by reporting child process later).
Packit 4f15d5
     * We can also reload the problem data if requested.
Packit 4f15d5
     */
Packit 4f15d5
    if (flags & LIBREPORT_WAIT)
Packit 4f15d5
    {
Packit 4f15d5
        if (flags & LIBREPORT_RELOAD_DATA)
Packit 4f15d5
            g_hash_table_remove_all(pd);
Packit 4f15d5
        dd = dd_opendir(dir_name, 0);
Packit 4f15d5
        if (dd)
Packit 4f15d5
        {
Packit 4f15d5
            if (flags & LIBREPORT_RELOAD_DATA)
Packit 4f15d5
                problem_data_load_from_dump_dir(pd, dd, NULL);
Packit 4f15d5
            dd_delete(dd);
Packit 4f15d5
        }
Packit 4f15d5
    }
Packit 4f15d5
Packit 4f15d5
    free(dir_name);
Packit 4f15d5
    return result;
Packit 4f15d5
}
Packit 4f15d5
Packit 4f15d5
int report_problem(problem_data_t *pd)
Packit 4f15d5
{
Packit 4f15d5
    return report_problem_in_memory(pd, LIBREPORT_NOWAIT);
Packit 4f15d5
}