/*
    Copyright (C) 2009  Jiri Moskovcak (jmoskovc@redhat.com)
    Copyright (C) 2009  RedHat inc.

    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.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#if HAVE_LOCALE_H
# include <locale.h>
#endif
#include <sys/un.h>
#include <glib-unix.h>
#include <glib/gstdio.h>

#include "abrt_glib.h"
#include "abrt-inotify.h"
#include "libabrt.h"
#include "problem_api.h"


/* I want to use -Werror, but gcc-4.4 throws a curveball:
 * "warning: ignoring return value of 'ftruncate', declared with attribute warn_unused_result"
 * and (void) cast is not enough to shut it up! Oh God...
 */
#define IGNORE_RESULT(func_call) do { if (func_call) /* nothing */; } while (0)


#define VAR_RUN_PIDFILE   VAR_RUN"/abrt/abrtd.pid"

#define SOCKET_FILE       VAR_RUN"/abrt/abrt.socket"
#define SOCKET_PERMISSION 0666
/* Maximum number of simultaneously opened client connections. */
#define MAX_CLIENT_COUNT  10

#define IN_DUMP_LOCATION_FLAGS (IN_DELETE_SELF | IN_MOVE_SELF)

#define ABRTD_DBUS_NAME ABRT_DBUS_NAME".daemon"

/* Daemon initializes, then sits in glib main loop, waiting for events.
 * Events can be:
 * - inotify: something new appeared under /var/tmp/abrt or /var/spool/abrt-upload
 * - signal: we got SIGTERM, SIGINT, SIGALRM or SIGCHLD
 * - new socket connection
 */
static volatile sig_atomic_t s_sig_caught;
static int s_signal_pipe[2];
static int s_signal_pipe_write = -1;
static unsigned s_timeout;
static int s_timeout_src;
static GMainLoop *s_main_loop;

GList *s_processes;
GList *s_dir_queue;

static GIOChannel *channel_socket = NULL;
static guint channel_id_socket = 0;

struct abrt_server_proc
{
    pid_t pid;
    int fdout;
    char *dirname;
    GIOChannel *channel;
    guint watch_id;
    enum {
        AS_UKNOWN,
        AS_POST_CREATE,
    } type;
};

/* Returns 0 if proc's pid equals the the given pid */
static gint abrt_server_compare_pid(struct abrt_server_proc *proc, pid_t *pid)
{
    return proc->pid != *pid;
}

/* Returns 0 if proc's fdout equals the the given fdout */
static gint abrt_server_compare_fdout(struct abrt_server_proc *proc, int *fdout)
{
    return proc->fdout != *fdout;
}

/* Returns 0 if proc's dirname equals the the given dirname */
static gint abrt_server_compare_dirname(struct abrt_server_proc *proc, const char *dirname)
{
    return g_strcmp0(proc->dirname, dirname);
}

/* Helpers */
static guint add_watch_or_die(GIOChannel *channel, unsigned condition, GIOFunc func)
{
    errno = 0;
    guint r = g_io_add_watch(channel, (GIOCondition)condition, func, NULL);
    if (!r)
        perror_msg_and_die("g_io_add_watch failed");
    return r;
}

static void stop_abrt_server(struct abrt_server_proc *proc)
{
    kill(proc->pid, SIGINT);
}

static void dispose_abrt_server(struct abrt_server_proc *proc)
{
    free(proc->dirname);

    if (proc->watch_id > 0)
        g_source_remove(proc->watch_id);

    if (proc->channel != NULL)
        g_io_channel_unref(proc->channel);
}

static void notify_next_post_create_process(struct abrt_server_proc *finished)
{
    if (finished != NULL)
        s_dir_queue = g_list_remove(s_dir_queue, finished);

    while (s_dir_queue != NULL)
    {
        struct abrt_server_proc *n = (struct abrt_server_proc *)s_dir_queue->data;
        if (n->type == AS_POST_CREATE)
            break;

        if (kill(n->pid, SIGUSR1) >= 0)
        {
            n->type = AS_POST_CREATE;
            break;
        }

        /* This could happen only if the notified process disappeared - crashed?
         */
        perror_msg("Failed to send SIGUSR1 to %d", n->pid);
        log_warning("Directory '%s' will not be processed", n->dirname);

        /* Remove the problematic process from the post-crate directory queue
         * and go to try to notify another process.
         */
        s_dir_queue = g_list_delete_link(s_dir_queue, s_dir_queue);
    }
}

/* Queueing the process will also lead to cleaning up the dump location.
 */
static void queue_post_create_process(struct abrt_server_proc *proc)
{
    abrt_load_abrt_conf();
    struct abrt_server_proc *running = s_dir_queue == NULL ? NULL
                                                           : (struct abrt_server_proc *)s_dir_queue->data;
    if (abrt_g_settings_nMaxCrashReportsSize == 0)
        goto consider_processing;

    const char *full_path_ignored = running != NULL ? running->dirname
                                                    : proc->dirname;
    const char *ignored = strrchr(full_path_ignored, '/');
    if (NULL == ignored)
        /* Paranoia, this should not happen. */
        ignored = full_path_ignored;
    else
        /* Move behind '/' */
        ++ignored;

    char *worst_dir = NULL;
    const double max_size = (double) abrt_g_settings_nMaxCrashReportsSize * (1024 * 1024);
    while (libreport_get_dirsize_find_largest_dir(abrt_g_settings_dump_location, &worst_dir, ignored, proc->dirname) >= max_size
           && worst_dir)
    {
        const char *kind = "old";

        GList *proc_of_deleted_item = NULL;
        if ((proc_of_deleted_item = g_list_find_custom(s_dir_queue, worst_dir, (GCompareFunc)abrt_server_compare_dirname)))
        {
            kind = "unprocessed";
            struct abrt_server_proc *removed_proc = (struct abrt_server_proc *)proc_of_deleted_item->data;
            s_dir_queue = g_list_delete_link(s_dir_queue, proc_of_deleted_item);
            stop_abrt_server(removed_proc);
        }

        log_warning("Size of '%s' >= %u MB (MaxCrashReportsSize), deleting %s directory '%s'",
                abrt_g_settings_dump_location, abrt_g_settings_nMaxCrashReportsSize,
                kind, worst_dir);

        g_autofree char *deleted = g_build_filename(abrt_g_settings_dump_location ? abrt_g_settings_dump_location : "", worst_dir, NULL);
        g_clear_pointer(&worst_dir, free);

        struct dump_dir *dd = dd_opendir(deleted, DD_FAIL_QUIETLY_ENOENT);
        if (dd != NULL)
            dd_delete(dd);
    }

consider_processing:
    /* If the process survived cleaning up the dump location, append it to the
     * post-create queue.
     */
    if (proc != NULL)
        s_dir_queue = g_list_append(s_dir_queue, proc);

    /* If there were no running post-crate process before we added the
     * currently handled process to the post-create queue, start processing of
     * the currently handled process.
     */
    if (running == NULL)
        notify_next_post_create_process(NULL/*finished*/);
}

static gboolean abrt_server_output_cb(GIOChannel *channel, GIOCondition condition, gpointer user_data)
{
    int fdout = g_io_channel_unix_get_fd(channel);
    GList *item = g_list_find_custom(s_processes, &fdout, (GCompareFunc)abrt_server_compare_fdout);
    if (item == NULL)
    {
        log_warning("Removing an input channel fd (%d) without a process assigned", fdout);
        return FALSE;
    }

    struct abrt_server_proc *proc = (struct abrt_server_proc *)item->data;

    if (condition & G_IO_HUP)
    {
        log_debug("abrt-server(%d) closed its pipe", proc->pid);
        proc->watch_id = 0;
        return FALSE;
    }

    for (;;)
    {
        g_autofree gchar *line = NULL;
        gsize len = 0;
        gsize pos = 0;
        GError *error = NULL;

        /* We use buffered channel so we do not need to read from the channel in a
         * loop */
        GIOStatus stat = g_io_channel_read_line(channel, &line, &len, &pos, &error);
        if (stat == G_IO_STATUS_ERROR)
            error_msg_and_die("Can't read from pipe of abrt-server(%d): '%s'", proc->pid, error ? error->message : "");
        if (stat == G_IO_STATUS_EOF)
        {
            log_debug("abrt-server(%d)'s output read till end", proc->pid);
            proc->watch_id = 0;
            return FALSE; /* Remove this event */
        }
        if (stat == G_IO_STATUS_AGAIN)
            break;

        /* G_IO_STATUS_NORMAL) */
        line[pos] = '\0';
        if (g_str_has_prefix(line, "NEW_PROBLEM_DETECTED: "))
        {
            if (proc->dirname != NULL)
            {
                log_warning("abrt-server(%d): already handling: %s", proc->pid, proc->dirname);
                free(proc->dirname);
                /* Because process can be only once in the dir queue */
                s_dir_queue = g_list_remove(s_dir_queue, proc);
            }

            proc->dirname = g_strdup(line + strlen("NEW_PROBLEM_DETECTED: "));
            log_notice("abrt-server(%d): handling new problem: %s", proc->pid, proc->dirname);
            queue_post_create_process(proc);
        }
        else
            log_warning("abrt-server(%d): not recognized message: '%s'", proc->pid, line);
    }

    return TRUE; /* Keep this event */
}

static void add_abrt_server_proc(const pid_t pid, int fdout)
{
    struct abrt_server_proc *proc = g_new(struct abrt_server_proc, 1);
    proc->pid = pid;
    proc->fdout = fdout;
    proc->dirname = NULL;
    proc->type = AS_UKNOWN;
    proc->channel = abrt_gio_channel_unix_new(proc->fdout);
    proc->watch_id = g_io_add_watch(proc->channel,
                                    G_IO_IN | G_IO_HUP,
                                    abrt_server_output_cb,
                                    proc);

    GError *error = NULL;
    g_io_channel_set_flags(proc->channel, G_IO_FLAG_NONBLOCK, &error);
    if (error != NULL)
        error_msg_and_die("g_io_channel_set_flags failed: '%s'", error->message);

    g_io_channel_set_buffered(proc->channel, TRUE);

    s_processes = g_list_append(s_processes, proc);
    if (g_list_length(s_processes) >= MAX_CLIENT_COUNT)
    {
        error_msg("Too many clients, refusing connections to '%s'", SOCKET_FILE);
        /* To avoid infinite loop caused by the descriptor in "ready" state,
         * the callback must be disabled.
         */
        g_source_remove(channel_id_socket);
        channel_id_socket = 0;
    }
}

static void start_idle_timeout(void)
{
    if (s_timeout == 0)
        return;

    s_timeout_src = g_timeout_add_seconds(s_timeout, (GSourceFunc)g_main_loop_quit, s_main_loop);
}

static void kill_idle_timeout(void)
{
    if (s_timeout == 0)
        return;

    if (s_timeout_src != 0)
        g_source_remove(s_timeout_src);

    s_timeout_src = 0;
}


static gboolean server_socket_cb(GIOChannel *source, GIOCondition condition, gpointer ptr_unused);

static void remove_abrt_server_proc(pid_t pid, int status)
{
    GList *item = g_list_find_custom(s_processes, &pid, (GCompareFunc)abrt_server_compare_pid);
    if (item == NULL)
        return;

    struct abrt_server_proc *proc = (struct abrt_server_proc *)item->data;
    item->data = NULL;
    s_processes = g_list_delete_link(s_processes, item);

    if (proc->type == AS_POST_CREATE)
        notify_next_post_create_process(proc);
    else
    {   /* Make sure out-of-order exited abrt-server post-create processes do
         * not stay in the post-create queue.
         */
        s_dir_queue = g_list_remove(s_dir_queue, proc);
    }

    dispose_abrt_server(proc);
    free(proc);

    if (g_list_length(s_processes) < MAX_CLIENT_COUNT && !channel_id_socket)
    {
        log_info("Accepting connections on '%s'", SOCKET_FILE);
        channel_id_socket = add_watch_or_die(channel_socket, G_IO_IN | G_IO_PRI | G_IO_HUP, server_socket_cb);
    }
}

/* Callback called by glib main loop when a client connects to ABRT's socket. */
static gboolean server_socket_cb(GIOChannel *source, GIOCondition condition, gpointer ptr_unused)
{
    kill_idle_timeout();
    abrt_load_abrt_conf();

    int socket = accept(g_io_channel_unix_get_fd(source), NULL, NULL);
    if (socket == -1)
    {
        perror_msg("accept");
        goto server_socket_finitio;
    }

    log_notice("New client connected");
    fflush(NULL); /* paranoia */

    int pipefd[2];
    g_unix_open_pipe(pipefd, 0, NULL);

    pid_t pid = fork();
    if (pid < 0)
    {
        perror_msg("fork");
        close(socket);
        close(pipefd[0]);
        close(pipefd[1]);
        goto server_socket_finitio;
    }
    if (pid == 0) /* child */
    {
        libreport_xdup2(socket, STDIN_FILENO);
        libreport_xdup2(socket, STDOUT_FILENO);
        close(socket);

        close(pipefd[0]);
        libreport_xmove_fd(pipefd[1], STDERR_FILENO);

        char *argv[3];  /* abrt-server [-s] NULL */
        char **pp = argv;
        *pp++ = (char*)"abrt-server";
        if (libreport_logmode & LOGMODE_JOURNAL)
            *pp++ = (char*)"-s";
        *pp = NULL;

        execvp(argv[0], argv);
        perror_msg_and_die("Can't execute '%s'", argv[0]);
    }

    /* parent */
    close(socket);
    close(pipefd[1]);
    add_abrt_server_proc(pid, pipefd[0]);

server_socket_finitio:
    start_idle_timeout();
    return TRUE;
}

/* Signal pipe handler */
static gboolean handle_signal_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr_unused)
{
    uint8_t signo;
    gsize len = 0;
    GError *error = NULL;
    GIOStatus stat = g_io_channel_read_chars(gio, (void*) &signo, 1, &len, &error);
    if (stat == G_IO_STATUS_ERROR)
    {
        /* An error occurred on the channel. Report it and quit. */
        error_msg_and_die("Can't read from gio channel: '%s'",
                error ? error->message : "");
    }
    if (stat == G_IO_STATUS_AGAIN)
    {
        /* No new data available. Continue as usual. */
        return G_SOURCE_CONTINUE;
    }
    if (stat == G_IO_STATUS_EOF)
    {
        /* The channel has reached its end. Remove this event. */
        return G_SOURCE_REMOVE;
    }
    if (len == 1)
    {
        /* we did receive a signal */
        log_debug("Got signal %d through signal pipe", signo);
        if (signo != SIGCHLD)
            g_main_loop_quit(s_main_loop);
        else
        {
            pid_t cpid;
            int status;
            while ((cpid = libreport_safe_waitpid(-1, &status, WNOHANG)) > 0)
            {
                if (WIFSIGNALED(status))
                    log_debug("abrt-server(%d) signaled with %d", cpid, WTERMSIG(status));
                else if (WIFEXITED(status))
                    log_debug("abrt-server(%d) exited with %d", cpid, WEXITSTATUS(status));
                else
                {
                    log_debug("abrt-server(%d) is being debugged", cpid);
                    continue;
                }

                remove_abrt_server_proc(cpid, status);
            }
        }
    }
    start_idle_timeout();
    return G_SOURCE_CONTINUE; /* "please don't remove this event" */
}

static void sanitize_dump_dir_rights(void)
{
    /* We can't allow everyone to create dumps: otherwise users can flood
     * us with thousands of bogus or malicious dumps */
    /* 07000 bits are setuid, setgit, and sticky, and they must be unset */
    /* 00777 bits are usual "rwxrwxrwx" access rights */
    abrt_ensure_writable_dir_group(abrt_g_settings_dump_location, DEFAULT_DUMP_LOCATION_MODE, "root", "abrt");
    /* temp dir */
    abrt_ensure_writable_dir(VAR_RUN"/abrt", 0755, "root");
}

/* Inotify handler */

static void handle_inotify_cb(struct abrt_inotify_watch *watch, struct inotify_event *event, gpointer ptr_unused)
{
    kill_idle_timeout();

    if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF)
    {
        log_warning("Recreating deleted dump location '%s'", abrt_g_settings_dump_location);

        abrt_load_abrt_conf();

        sanitize_dump_dir_rights();
        abrt_inotify_watch_reset(watch, abrt_g_settings_dump_location, IN_DUMP_LOCATION_FLAGS);
    }

    start_idle_timeout();
}

/* Initializes the dump socket, usually in /var/run directory
 * (the path depends on compile-time configuration).
 */
static void dumpsocket_init(void)
{
    unlink(SOCKET_FILE); /* not caring about the result */

    int socketfd = libreport_xsocket(AF_UNIX, SOCK_STREAM, 0);
    libreport_close_on_exec_on(socketfd);

    struct sockaddr_un local;
    memset(&local, 0, sizeof(local));
    local.sun_family = AF_UNIX;
    strcpy(local.sun_path, SOCKET_FILE);
    libreport_xbind(socketfd, (struct sockaddr*)&local, sizeof(local));
    libreport_xlisten(socketfd, MAX_CLIENT_COUNT);

    if (chmod(SOCKET_FILE, SOCKET_PERMISSION) != 0)
        perror_msg_and_die("chmod '%s'", SOCKET_FILE);

    channel_socket = abrt_gio_channel_unix_new(socketfd);
    g_io_channel_set_buffered(channel_socket, FALSE);

    channel_id_socket = add_watch_or_die(channel_socket, G_IO_IN | G_IO_PRI | G_IO_HUP, server_socket_cb);
}

/* Releases all resources used by dumpsocket. */
static void dumpsocket_shutdown(void)
{
    /* Set everything to pre-initialization state. */
    if (channel_socket)
    {
        /* Undo add_watch_or_die */
        g_source_remove(channel_id_socket);
        /* Undo g_io_channel_unix_new */
        g_io_channel_unref(channel_socket);
        channel_socket = NULL;
    }
}

static int create_pidfile(void)
{
    /* Note:
     * No O_EXCL: we would happily overwrite stale pidfile from previous boot.
     * No O_TRUNC: we must first try to lock the file, and if lock fails,
     * there is another live abrtd. O_TRUNCing the file in this case
     * would be wrong - it'll erase the pid to empty string!
     */
    int fd = open(VAR_RUN_PIDFILE, O_RDWR|O_CREAT, 0644);
    if (fd >= 0)
    {
        if (lockf(fd, F_TLOCK, 0) < 0)
        {
            perror_msg("Can't lock file '%s'", VAR_RUN_PIDFILE);
            /* should help with problems like rhbz#859724 */
            char pid_str[sizeof(long)*3 + 4];
            int r = libreport_full_read(fd, pid_str, sizeof(pid_str));
            close(fd);

            /* File can contain garbage. Be careful interpreting it as PID */
            if (r > 0)
            {
                pid_str[r] = '\0';
                errno = 0;
                long locking_pid = strtol(pid_str, NULL, 10);
                if (!errno && locking_pid > 0 && locking_pid <= INT_MAX)
                {
                    g_autofree char *cmdline = libreport_get_cmdline(locking_pid);
                    if (cmdline)
                    {
                        error_msg("Process %lu '%s' is holding the lock", locking_pid, cmdline);
                    }
                }
            }

            return -1;
        }
        libreport_close_on_exec_on(fd);
        /* write our pid to it */
        char buf[sizeof(long)*3 + 2];
        int len = sprintf(buf, "%lu\n", (long)getpid());
        IGNORE_RESULT(write(fd, buf, len));
        IGNORE_RESULT(ftruncate(fd, len));
        /* we leak opened+locked fd intentionally */
        return 0;
    }

    perror_msg("Can't open '%s'", VAR_RUN_PIDFILE);
    return -1;
}

static void handle_signal(int signo)
{
    int save_errno = errno;

    // Enable for debugging only, malloc/printf are unsafe in signal handlers
    //log_debug("Got signal %d", signo);

    uint8_t sig_caught;
    s_sig_caught = sig_caught = signo;
    /* Using local copy of s_sig_caught so that concurrent signal
     * won't change it under us */
    if (s_signal_pipe_write >= 0)
        IGNORE_RESULT(write(s_signal_pipe_write, &sig_caught, 1));

    errno = save_errno;
}


static void start_logging(void)
{
    /* Open stdin to /dev/null */
    libreport_xmove_fd(g_open("/dev/null", O_RDWR), STDIN_FILENO);
    /* We must not leave fds 0,1,2 closed.
     * Otherwise fprintf(stderr) dumps messages into random fds, etc. */
    libreport_xdup2(STDIN_FILENO, STDOUT_FILENO);
    libreport_xdup2(STDIN_FILENO, STDERR_FILENO);
    libreport_logmode = LOGMODE_JOURNAL;
    putenv((char*)"ABRT_SYSLOG=1");
}

/* The function expects that FILENAME_COUNT dump dir element is created by
 * abrtd after all post-create events are successfully done. Thus if
 * FILENAME_COUNT element doesn't exist abrtd can consider the dump directory
 * as unprocessed.
 *
 * Relying on content of dump directory has one problem. If a hook provides
 * FILENAME_COUNT abrtd will consider the dump directory as processed.
 */
static void mark_unprocessed_dump_dirs_not_reportable(const char *path)
{
    log_notice("Searching for unprocessed dump directories");

    DIR *dp = opendir(path);
    if (!dp)
    {
        perror_msg("Can't open directory '%s'", path);
        return;
    }

    struct dirent *dent;
    while ((dent = readdir(dp)) != NULL)
    {
        if (libreport_dot_or_dotdot(dent->d_name))
            continue; /* skip "." and ".." */

        g_autofree char *full_name = g_build_filename(path, dent->d_name, NULL);

        struct stat stat_buf;
        if (stat(full_name, &stat_buf) != 0)
        {
            perror_msg("Can't access path '%s'", full_name);
            continue;
        }

        if (S_ISDIR(stat_buf.st_mode) == 0)
            /* This is expected. The dump location contains some aux files */
            continue;

        struct dump_dir *dd = dd_opendir(full_name, /*flags*/0);
        if (dd)
        {
            if (!problem_dump_dir_is_complete(dd) && !dd_exist(dd, FILENAME_NOT_REPORTABLE))
            {
                log_warning("Marking '%s' not reportable (no '"FILENAME_COUNT"' item)", full_name);

                dd_save_text(dd, FILENAME_NOT_REPORTABLE, _("The problem data are "
                            "incomplete. This usually happens when a problem "
                            "is detected while computer is shutting down or "
                            "user is logging out. In order to provide "
                            "valuable problem reports, ABRT will not allow "
                            "you to submit this problem. If you have time and "
                            "want to help the developers in their effort to "
                            "sort out this problem, please contact them directly."));

            }
            dd_close(dd);
        }
    }
    closedir(dp);
}

static void on_bus_acquired(GDBusConnection *connection,
                 const gchar     *name,
                 gpointer         user_data)
{
    log_debug("Going to own bus '%s'", name);
}

static void on_name_acquired (GDBusConnection *connection,
                  const gchar     *name,
                  gpointer         user_data)
{
    log_debug("Acquired the name '%s' on the system bus", name);
}

static void on_name_lost(GDBusConnection *connection,
                      const gchar *name,
                      gpointer user_data)
{
    error_msg_and_die(_("The name '%s' has been lost, please check if other "
                        "service owning the name is not running.\n"), name);
}

int main(int argc, char** argv)
{
    /* I18n */
    setlocale(LC_ALL, "");
#if ENABLE_NLS
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

    abrt_init(argv);

    int parent_pid = getpid();

    const char *program_usage_string = _(
        "& [options]"
    );
    enum {
        OPT_v = 1 << 0,
        OPT_d = 1 << 1,
        OPT_s = 1 << 2,
// TODO: get rid of -t NUM, it is no longer useful since dbus is moved to a separate tool
        OPT_t = 1 << 3,
        OPT_p = 1 << 4,
    };
    /* Keep enum above and order of options below in sync! */
    struct options program_options[] = {
        OPT__VERBOSE(&libreport_g_verbose),
        OPT_BOOL(   'd', NULL, NULL      , _("Do not daemonize")),
        OPT_BOOL(   's', NULL, NULL      , _("Log to syslog even with -d")),
        OPT_INTEGER('t', NULL, &s_timeout, _("Exit after NUM seconds of inactivity")),
        OPT_BOOL(   'p', NULL, NULL      , _("Add program names to log")),
        OPT_END()
    };
    unsigned opts = libreport_parse_opts(argc, argv, program_options, program_usage_string);

    libreport_export_abrt_envvars(opts & OPT_p);

    unsetenv("ABRT_SYSLOG");
    libreport_msg_prefix = libreport_g_progname; /* for log_warning(), error_msg() and such */

    if (getuid() != 0)
        error_msg_and_die("Must be run as root");

    if (opts & OPT_s)
        start_logging();

    g_unix_open_pipe(s_signal_pipe, 0, NULL);
    libreport_close_on_exec_on(s_signal_pipe[0]);
    libreport_close_on_exec_on(s_signal_pipe[1]);
    libreport_ndelay_on(s_signal_pipe[0]); /* I/O should not block - */
    libreport_ndelay_on(s_signal_pipe[1]); /* especially writes! they happen in signal handler! */
    signal(SIGTERM, handle_signal);
    signal(SIGINT,  handle_signal);
    signal(SIGCHLD, handle_signal);

    GIOChannel* channel_signal = NULL;
    guint channel_id_signal_event = 0;
    bool pidfile_created = false;
    struct abrt_inotify_watch *aiw = NULL;
    int ret = 1;

    /* Initialization */
    log_notice("Loading settings");
    if (abrt_load_abrt_conf() != 0)
        goto init_error;

    /* Moved before daemonization because parent waits for signal from daemon
     * only for short period and time consumed by
     * mark_unprocessed_dump_dirs_not_reportable() is slightly unpredictable.
     */
    sanitize_dump_dir_rights();
    mark_unprocessed_dump_dirs_not_reportable(abrt_g_settings_dump_location);

    /* Daemonize unless -d */
    if (!(opts & OPT_d))
    {
        /* forking to background */
        fflush(NULL); /* paranoia */
        pid_t pid = fork();
        if (pid < 0)
        {
            perror_msg_and_die("fork");
        }
        if (pid > 0)
        {
            /* Parent */
            /* Wait for child to notify us via SIGTERM that it feels ok */
            int i = 20; /* 2 sec */
            while (s_sig_caught == 0 && --i)
            {
                usleep(100 * 1000);
            }
            if (s_sig_caught == SIGTERM)
            {
                exit(0);
            }
            if (s_sig_caught)
            {
                error_msg_and_die("Failed to start: got sig %d", s_sig_caught);
            }
            error_msg_and_die("Failed to start: timeout waiting for child");
        }
        /* Child (daemon) continues */
        if (setsid() < 0)
            perror_msg_and_die("setsid");
        if (libreport_g_verbose == 0 && libreport_logmode != LOGMODE_JOURNAL)
            start_logging();
    }

    log_notice("Creating glib main loop");
    s_main_loop = g_main_loop_new(NULL, FALSE);

    /* Watching 'abrt_g_settings_dump_location' for delete self
     * because hooks expects that the dump location exists if abrtd is running
     */
    aiw = abrt_inotify_watch_init(abrt_g_settings_dump_location,
            IN_DUMP_LOCATION_FLAGS, handle_inotify_cb, /*user data*/NULL);

    /* Add an event source which waits for INT/TERM signal */
    log_notice("Adding signal pipe watch to glib main loop");
    channel_signal = abrt_gio_channel_unix_new(s_signal_pipe[0]);
    channel_id_signal_event = add_watch_or_die(channel_signal,
                        G_IO_IN | G_IO_PRI | G_IO_HUP,
                        handle_signal_cb);

    guint name_id = 0;

    /* Mark the territory */
    log_notice("Creating pid file");
    if (create_pidfile() != 0)
        goto init_error;
    pidfile_created = true;

    /* Open socket to receive new problem data (from python etc). */
    dumpsocket_init();

    /* Inform parent that we initialized ok */
    if (!(opts & OPT_d))
    {
        log_notice("Signalling parent");
        kill(parent_pid, SIGTERM);
        if (libreport_logmode != LOGMODE_JOURNAL)
            start_logging();
    }

    /* Only now we want signal pipe to work */
    s_signal_pipe_write = s_signal_pipe[1];

    /* Own a name on D-Bus */
    name_id = g_bus_own_name(G_BUS_TYPE_SYSTEM,
                             ABRTD_DBUS_NAME,
                             G_BUS_NAME_OWNER_FLAGS_NONE,
                             on_bus_acquired,
                             on_name_acquired,
                             on_name_lost,
                             NULL, NULL);

    start_idle_timeout();

    /* Enter the event loop */
    log_debug("Init complete, entering main loop");
    g_main_loop_run(s_main_loop);

    ret = 0;
    /* Jump to exit */
    goto cleanup;


 init_error:
    /* Initialization error */
    error_msg("Error while initializing daemon");
    /* Inform parent that initialization failed */
    if (!(opts & OPT_d))
        kill(parent_pid, SIGINT);


 cleanup:
    if (name_id > 0)
        g_bus_unown_name (name_id);

    /* Error or INT/TERM. Clean up, in reverse order.
     * Take care to not undo things we did not do.
     */
    dumpsocket_shutdown();
    if (pidfile_created)
        unlink(VAR_RUN_PIDFILE);

    if (channel_id_signal_event > 0)
        g_source_remove(channel_id_signal_event);
    if (channel_signal)
        g_io_channel_unref(channel_signal);

    abrt_inotify_watch_destroy(aiw);

    if (s_main_loop)
        g_main_loop_unref(s_main_loop);

    abrt_free_abrt_conf_data();

    if (s_sig_caught && s_sig_caught != SIGCHLD)
    {
        /* We use TERM to stop abrtd, so not printing out error message. */
        if (s_sig_caught != SIGTERM)
        {
            error_msg("Got signal %d, exiting", s_sig_caught);
            signal(s_sig_caught, SIG_DFL);
            raise(s_sig_caught);
        }
    }

    /* Exiting */
    log_notice("Exiting");
    return ret;
}
