[ABRT PATCH 2/5] introduce abrt-upload-watch

Jakub Filak jfilak at redhat.com
Wed Aug 21 10:22:25 UTC 2013


Related to #657

Signed-off-by: Jakub Filak <jfilak at redhat.com>
---
 doc/Makefile.am                |   1 +
 doc/abrt-upload-watch.txt      |  51 ++++++
 po/POTFILES.in                 |   1 +
 src/daemon/Makefile.am         |  25 ++-
 src/daemon/abrt-inotify.c      | 175 +++++++++++++++++++
 src/daemon/abrt-inotify.h      |  40 +++++
 src/daemon/abrt-upload-watch.c | 378 +++++++++++++++++++++++++++++++++++++++++
 src/daemon/abrtd.c             | 175 ++-----------------
 8 files changed, 680 insertions(+), 166 deletions(-)
 create mode 100644 doc/abrt-upload-watch.txt
 create mode 100644 src/daemon/abrt-inotify.c
 create mode 100644 src/daemon/abrt-inotify.h
 create mode 100644 src/daemon/abrt-upload-watch.c

diff --git a/doc/Makefile.am b/doc/Makefile.am
index 872d210..e0c50ff 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -33,6 +33,7 @@ MAN1_TXT += abrt-install-ccpp-hook.txt
 MAN1_TXT += abrt-action-analyze-vmcore.txt
 MAN1_TXT += abrt-action-analyze-ccpp-local.txt
 MAN1_TXT += abrt-watch-log.txt
+MAN1_TXT += abrt-upload-watch.txt
 MAN1_TXT += system-config-abrt.txt
 if BUILD_BODHI
 MAN1_TXT += abrt-bodhi.txt
diff --git a/doc/abrt-upload-watch.txt b/doc/abrt-upload-watch.txt
new file mode 100644
index 0000000..1486777
--- /dev/null
+++ b/doc/abrt-upload-watch.txt
@@ -0,0 +1,51 @@
+abrt-upload-watch(1)
+==================
+
+NAME
+----
+abrt-upload-watch - Watch upload directory and unpacks incoming archives into DumpLocation
+
+SYNOPSIS
+--------
+'abrt-upload-watch' [-vs] [-w NUM_WORKERS] [-c CACHE_SIZE_MIB] [UPLOAD_DIRECTORY]
+
+OPTIONS
+-------
+-v, --verbose::
+   Be more verbose. Can be given multiple times.
+
+-s::
+   Log to syslog
+
+-d::
+   Daemonize
+
+-w NUM_WORKERS::
+   Number of concurrent workers. Default is 10
+
+-c CACHE_SIZE_MIB::
+   Maximal cache size in MiB. Default is 4
+
+UPLOAD_DIRECTORY::
+   Watched directory. Default is a value of WatchCrashdumpArchiveDir option from abrt.conf
+
+FILES
+-----
+Uses these three configuration options from file '/etc/abrt/abrt.conf':
+
+WatchCrashdumpArchiveDir::
+   Default upload directory
+
+DumpLocation::
+   Place where uploaded archives are unpacked
+
+DeleteUploaded::
+   Specifies if uploaded archives are deleted after unpacking
+
+SEE ALSO
+--------
+abrt.conf(5)
+
+AUTHORS
+-------
+* ABRT team
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 5e14bc8..8d22e35 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -11,6 +11,7 @@ src/daemon/abrt-server.c
 src/dbus/abrt-dbus.c
 src/daemon/abrtd.c
 src/daemon/abrt-handle-event.c
+src/daemon/abrt-upload-watch.c
 src/lib/abrt_conf.c
 src/lib/hooklib.c
 src/lib/problem_api.c
diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am
index 33eec63..9369d26 100644
--- a/src/daemon/Makefile.am
+++ b/src/daemon/Makefile.am
@@ -11,14 +11,17 @@ bin_PROGRAMS = \
 
 sbin_PROGRAMS = \
     abrtd \
-    abrt-server
+    abrt-server \
+    abrt-upload-watch
 
 libexec_PROGRAMS = abrt-handle-event
 
 # This is a daemon, building with full relro and PIE
 # for increased security.
 abrtd_SOURCES = \
-    abrtd.c
+    abrtd.c \
+    abrt-inotify.c \
+    abrt-inotify.h
 abrtd_CPPFLAGS = \
     -I$(srcdir)/../include \
     -I$(srcdir)/../lib \
@@ -49,6 +52,24 @@ abrt_server_LDADD = \
     ../lib/libabrt.la \
     $(LIBREPORT_LIBS)
 
+abrt_upload_watch_SOURCES = \
+    abrt-upload-watch.c \
+    abrt-inotify.c \
+    abrt-inotify.h
+abrt_upload_watch_CPPFLAGS = \
+    -I$(srcdir)/../include \
+    -I$(srcdir)/../lib \
+    -DDEFAULT_DUMP_DIR_MODE=$(DEFAULT_DUMP_DIR_MODE) \
+    -DLIBEXEC_DIR=\"$(libexecdir)\" \
+    $(GLIB_CFLAGS) \
+    $(GIO_CFLAGS) \
+    $(LIBREPORT_CFLAGS) \
+    -D_GNU_SOURCE
+abrt_upload_watch_LDADD = \
+    ../lib/libabrt.la \
+    $(LIBREPORT_LIBS)
+
+
 abrt_handle_event_SOURCES = \
     abrt-handle-event.c
 abrt_handle_event_CPPFLAGS = \
diff --git a/src/daemon/abrt-inotify.c b/src/daemon/abrt-inotify.c
new file mode 100644
index 0000000..ffc5fd0
--- /dev/null
+++ b/src/daemon/abrt-inotify.c
@@ -0,0 +1,175 @@
+/*
+    Copyright (C) 2013  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.
+*/
+
+#include "abrt-inotify.h"
+#include "abrt_glib.h"
+#include "libabrt.h"
+
+#include <stdio.h>
+#include <sys/ioctl.h> /* ioctl(FIONREAD) */
+
+struct abrt_inotify_watch
+{
+    abrt_inotify_watch_handler handler;
+    void *user_data;
+    int inotify_fd;
+    int inotify_wd;
+    GIOChannel *channel_inotify;
+    guint channel_inotify_source_id;
+};
+
+/* Inotify handler */
+
+static gboolean handle_inotify_cb(GIOChannel *gio, GIOCondition condition, gpointer user_data)
+{
+    /* Default size: 128 simultaneous actions (about 1/2 meg) */
+#define INOTIFY_BUF_SIZE ((sizeof(struct inotify_event) + FILENAME_MAX)*128)
+    /* Determine how much to read (it usually is much smaller) */
+    /* NB: this variable _must_ be int-sized, ioctl expects that! */
+    int inotify_bytes = INOTIFY_BUF_SIZE;
+    if (ioctl(g_io_channel_unix_get_fd(gio), FIONREAD, &inotify_bytes) != 0
+    /*|| inotify_bytes < sizeof(struct inotify_event)
+         ^^^^^^^^^^^^^^^^^^^ - WRONG: legitimate 0 was seen when flooded with inotify events
+    */
+     || inotify_bytes > INOTIFY_BUF_SIZE
+    ) {
+        inotify_bytes = INOTIFY_BUF_SIZE;
+    }
+    VERB3 log("FIONREAD:%d", inotify_bytes);
+
+    if (inotify_bytes == 0)
+        return TRUE; /* "please don't remove this event" */
+
+    /* We may race: more inotify events may happen after ioctl(FIONREAD).
+     * To be more efficient, allocate a bit more space to eat those events too.
+     * This also would help against a bug we once had where
+     * g_io_channel_read_chars() was buffering reads
+     * and we were going out of sync wrt struct inotify_event's layout.
+     */
+    inotify_bytes += 2 * (sizeof(struct inotify_event) + FILENAME_MAX);
+    char *buf = xmalloc(inotify_bytes);
+    errno = 0;
+    gsize len;
+    GError *gerror = NULL;
+    /* Note: we ensured elsewhere that this read is non-blocking, making it ok
+     * for buffer len (inotify_bytes) to be larger than actual available byte count.
+     */
+    GIOStatus err = g_io_channel_read_chars(gio, buf, inotify_bytes, &len, &gerror);
+    if (err != G_IO_STATUS_NORMAL)
+    {
+        perror_msg("Error reading inotify fd: %s", gerror ? gerror->message : "unknown");
+        free(buf);
+        if (gerror)
+            g_error_free(gerror);
+        return FALSE; /* "remove this event" (huh??) */
+    }
+
+    struct abrt_inotify_watch *aic = (struct abrt_inotify_watch *)user_data;
+    /* Reconstruct each event */
+    gsize i = 0;
+    for (;;)
+    {
+        if (i >= len)
+        {
+            /* This would catch one of our former bugs. Let's be paranoid */
+            if (i > len)
+                error_msg("warning: ran off struct inotify (this should never happen): %u > %u", (int)i, (int)len);
+            break;
+        }
+        struct inotify_event *event = (struct inotify_event *) &buf[i];
+        i += sizeof(*event) + event->len;
+
+        aic->handler(aic, event, aic->user_data);
+    }
+    free(buf);
+    return TRUE;
+}
+
+struct abrt_inotify_watch *
+abrt_inotify_watch_init(const char *path, int inotify_flags, abrt_inotify_watch_handler handler, void *user_data)
+{
+    struct abrt_inotify_watch *aiw = malloc(sizeof(*aiw));
+    aiw->handler = handler;
+    aiw->user_data = user_data;
+
+    VERB1 log("Initializing inotify");
+    errno = 0;
+    aiw->inotify_fd = inotify_init();
+    if (aiw->inotify_fd == -1)
+        perror_msg_and_die("inotify_init failed");
+    close_on_exec_on(aiw->inotify_fd);
+
+    aiw->inotify_wd = inotify_add_watch(aiw->inotify_fd, path, inotify_flags);
+    if (aiw->inotify_wd < 0)
+        perror_msg_and_die("inotify_add_watch failed on '%s'", path);
+
+    VERB1 log("Adding inotify watch to glib main loop");
+    /* Without nonblocking mode, users observed abrtd blocking
+     * on inotify read forever. Must set fd to non-blocking:
+     */
+    ndelay_on(aiw->inotify_fd);
+    aiw->channel_inotify = abrt_gio_channel_unix_new(aiw->inotify_fd);
+
+    /*
+     * glib's read buffering must be disabled, or else
+     * FIONREAD-reported "available data" sizes and sizes of reads
+     * can become inconsistent, and worse, buffering can split
+     * struct inotify's (very bad!).
+     */
+    g_io_channel_set_buffered(aiw->channel_inotify, false);
+
+    errno = 0;
+    aiw->channel_inotify_source_id = g_io_add_watch(aiw->channel_inotify,
+            G_IO_IN | G_IO_PRI | G_IO_HUP,
+            handle_inotify_cb,
+            aiw);
+    if (!aiw->channel_inotify_source_id)
+        perror_msg_and_die("g_io_add_watch failed");
+
+    return aiw;
+}
+
+void
+abrt_inotify_watch_reset(struct abrt_inotify_watch *watch, const char *path, int inotify_flags)
+{
+    inotify_rm_watch(watch->inotify_fd, watch->inotify_wd);
+    watch->inotify_wd = inotify_add_watch(watch->inotify_fd, path, inotify_flags);
+    if (watch->inotify_wd < 0)
+        perror_msg_and_die("inotify_add_watch failed on '%s'", path);
+}
+
+void
+abrt_inotify_watch_destroy(struct abrt_inotify_watch *watch)
+{
+    if (!watch)
+        return;
+
+    inotify_rm_watch(watch->inotify_fd, watch->inotify_wd);
+    g_source_remove(watch->channel_inotify_source_id);
+
+    GError *error = NULL;
+    g_io_channel_shutdown(watch->channel_inotify, FALSE, &error);
+    if (error)
+    {
+        VERB1 log("Can't shutdown inotify gio channel: '%s'", error ? error->message : "");
+        g_error_free(error);
+    }
+
+    g_io_channel_unref(watch->channel_inotify);
+    free(watch);
+}
diff --git a/src/daemon/abrt-inotify.h b/src/daemon/abrt-inotify.h
new file mode 100644
index 0000000..7674fc0
--- /dev/null
+++ b/src/daemon/abrt-inotify.h
@@ -0,0 +1,40 @@
+/*
+    Copyright (C) 2013  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.
+*/
+
+#ifndef _ABRT_INOTIFY_H_
+#define _ABRT_INOTIFY_H_
+
+#include <sys/inotify.h>
+
+struct abrt_inotify_watch;
+
+typedef void (* abrt_inotify_watch_handler)(
+        struct abrt_inotify_watch *watch,
+        struct inotify_event *event,
+        void  *user_data);
+
+struct abrt_inotify_watch *
+abrt_inotify_watch_init(const char *path, int inotify_flags, abrt_inotify_watch_handler handler, void *user_data);
+
+void
+abrt_inotify_watch_destroy(struct abrt_inotify_watch *watch);
+
+void
+abrt_inotify_watch_reset(struct abrt_inotify_watch *watch, const char *path, int inotify_flags);
+
+#endif /*_ABRT_INOTIFY_H_*/
diff --git a/src/daemon/abrt-upload-watch.c b/src/daemon/abrt-upload-watch.c
new file mode 100644
index 0000000..400a456
--- /dev/null
+++ b/src/daemon/abrt-upload-watch.c
@@ -0,0 +1,378 @@
+/*
+    Copyright (C) 2013  ABRT Team
+    Copyright (C) 2013  Red Hat, 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.
+ */
+#include "abrt-inotify.h"
+#include "abrt_glib.h"
+#include "libabrt.h"
+
+#include <syslog.h>
+
+#define STRINGIZE_DETAIL(str) #str
+#define STRINGIZE(str) STRINGIZE_DETAIL(str)
+
+#define DEFAULT_COUNT_OF_WORKERS 10
+#define DEFAULT_CACHE_MIB_SIZE 4
+#define APPROX_BYTES_PER_ARCHIVE_NAME FILENAME_MAX
+
+static int g_signal_pipe[2];
+
+struct queue
+{
+    unsigned capacity;
+    GQueue q;
+};
+
+static int
+queue_push(struct queue *queue, char *value)
+{
+    if (g_queue_get_length(&queue->q) >= queue->capacity)
+        return 0;
+
+    g_queue_push_head(&queue->q, value);
+
+    return 1;
+}
+
+static char *
+queue_pop(struct queue *queue)
+{
+    if (g_queue_is_empty(&queue->q))
+        return NULL;
+
+    return (char *)g_queue_pop_tail(&queue->q);
+}
+
+struct process
+{
+    GMainLoop *main_loop;
+    const char *upload_directory;
+    unsigned children;
+    unsigned max_children;
+    struct queue queue;
+};
+
+static void
+process_quit(struct process *proc)
+{
+    g_main_loop_quit(proc->main_loop);
+}
+
+static void
+run_abrt_handle_upload(struct process *proc, const char *name)
+{
+    VERB2 log("Processing file '%s' in directory '%s'", name, proc->upload_directory);
+
+    ++proc->children;
+    VERB3 log("Running workers: %d", proc->children);
+
+    fflush(NULL); /* paranoia */
+    pid_t pid = fork();
+    if (pid < 0)
+    {
+        --proc->children;
+        perror_msg("fork");
+        return;
+    }
+
+    if (pid == 0)
+    {
+        /* child */
+        xchdir(proc->upload_directory);
+        if (g_settings_delete_uploaded)
+            execlp("abrt-handle-upload", "abrt-handle-upload", "-d",
+                           g_settings_dump_location, proc->upload_directory, name, (char*)NULL);
+        else
+            execlp("abrt-handle-upload", "abrt-handle-upload",
+                           g_settings_dump_location, proc->upload_directory, name, (char*)NULL);
+        error_msg_and_die("Can't execute '%s'", "abrt-handle-upload");
+    }
+}
+
+static void
+handle_new_path(struct process *proc, char *name)
+{
+    log("Detected creation of file '%s' in upload directory '%s'", name, proc->upload_directory);
+
+    if (proc->children < proc->max_children)
+    {
+        run_abrt_handle_upload(proc, name);
+        free(name);
+        return;
+    }
+
+    VERB3 log("Pushing '%s' to deferred queue", name);
+    if (!queue_push(&proc->queue, name))
+    {
+        error_msg(_("No free workers and full buffer. Omitting archive '%s'"), name);
+        free(name);
+        return;
+    }
+}
+
+static void
+decrement_child_count(struct process *proc)
+{
+    --proc->children;
+
+    char *name = queue_pop(&proc->queue);
+    if (!name)
+    {
+        VERB3 log("Deferred queue is empty. Running workers: %d", proc->children);
+        return;
+    }
+
+    run_abrt_handle_upload(proc, name);
+    free(name);
+}
+
+static void
+handle_signal(int signo)
+{
+    int save_errno = errno;
+    uint8_t sig_caught = signo;
+    if (write(g_signal_pipe[1], &sig_caught, 1))
+        /* we ignore result, if () shuts up stupid compiler */;
+    errno = save_errno;
+}
+
+static gboolean
+handle_sigchld_pipe(GIOChannel *gio, GIOCondition condition, gpointer user_data)
+{
+    struct process *proc = (struct process *)user_data;
+    uint8_t signals[DEFAULT_COUNT_OF_WORKERS];
+    gsize len = 0;
+
+    for (;;)
+    {
+        GError *error = NULL;
+        GIOStatus stat = g_io_channel_read_chars(gio, (void *)signals, sizeof(signals), &len, NULL);
+        if (stat == G_IO_STATUS_ERROR)
+        {
+            error_msg_and_die(_("Can't read from gio channel: '%s'"), error ? error->message : "");
+        }
+        if (stat == G_IO_STATUS_AGAIN)
+        {   /* We got all buffered data, but fd is still open. Done for now */
+            return TRUE; /* "glib, please don't remove this event (yet)" */
+        }
+        if (stat == G_IO_STATUS_EOF)
+            break;
+
+        /* G_IO_STATUS_NORMAL */
+        for (unsigned signo = 0; signo < len; ++signo)
+        {
+            /* we did receive a signal */
+            VERB3 log("Got signal %d through signal pipe", signals[signo]);
+            if (signals[signo]!= SIGCHLD)
+            {
+                process_quit(proc);
+                return FALSE; /* remove this event */
+            }
+            else
+            {
+                while (safe_waitpid(-1, NULL, WNOHANG) > 0)
+                {
+                    decrement_child_count(proc);
+                }
+            }
+        }
+    }
+
+    return TRUE; /* "please don't remove this event" */
+}
+
+static void
+handle_inotify_cb(struct abrt_inotify_watch *watch, struct inotify_event *event, void *user_data)
+{
+    /* Was the (presumable newly created) file closed in upload dir,
+     * or a file moved to upload dir? */
+    if (event->name && !(event->mask & IN_ISDIR) && (event->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)))
+    {
+        const char *ext = strrchr(event->name, '.');
+        if (ext && strcmp(ext + 1, "working") == 0)
+            return;
+
+        handle_new_path((struct process *)user_data, xstrdup(event->name));
+    }
+}
+
+static void
+daemonize()
+{
+    /* forking to background */
+    fflush(NULL); /* paranoia */
+    pid_t pid = fork();
+    if (pid < 0)
+        perror_msg_and_die("fork");
+    if (pid > 0)
+        exit(0);
+
+    /* Child (daemon) continues */
+    if (setsid() < 0)
+        perror_msg_and_die("setsid");
+
+    /* Change the current working directory */
+    if ((chdir("/")) < 0)
+        perror_msg_and_die("chdir(/)");
+
+    /* Close out the standard file descriptors */
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+}
+
+int
+main(int argc, char **argv)
+{
+    /* I18n */
+    setlocale(LC_ALL, "");
+#if ENABLE_NLS
+    bindtextdomain(PACKAGE, LOCALEDIR);
+    textdomain(PACKAGE);
+#endif
+
+    abrt_init(argv);
+    /* Can't keep these strings/structs static: _() doesn't support that */
+    const char *program_usage_string = _(
+        "& [-vs] [-w NUM] [-c MiB] [UPLOAD_DIRECTORY]\n"
+        "\n"
+        "\nWatches UPLOAD_DIRECTORY and unpacks incoming archives into DumpLocation"
+        "\nspecified in abrt.conf"
+        "\n"
+        "\nIf UPLOAD_DIRECTORY is not provided, uses a value of"
+        "\nWatchCrashdumpArchiveDir option from abrt.conf"
+    );
+    enum {
+        OPT_v = 1 << 0,
+        OPT_s = 1 << 1,
+        OPT_d = 1 << 2,
+        OPT_w = 1 << 3,
+        OPT_c = 1 << 4,
+    };
+
+    int concurrent_workers = DEFAULT_COUNT_OF_WORKERS;
+    int cache_size_mib = DEFAULT_CACHE_MIB_SIZE;
+
+    /* Keep enum above and order of options below in sync! */
+    struct options program_options[] = {
+        OPT__VERBOSE(&g_verbose),
+        OPT_BOOL('s', NULL, NULL              , _("Log to syslog")),
+        OPT_BOOL('d', NULL, NULL              , _("Daemize")),
+        OPT_INTEGER('w', NULL, &concurrent_workers, _("Number of concurrent workers. Default is "STRINGIZE(DEFAULT_COUNT_OF_WORKERS))),
+        OPT_INTEGER('c', NULL, &cache_size_mib, _("Maximal cache size in MiB. Default is "STRINGIZE(DEFAULT_CACHE_MIB_SIZE))),
+        OPT_END()
+    };
+    unsigned opts = parse_opts(argc, argv, program_options, program_usage_string);
+
+    if (concurrent_workers <= 0)
+        perror_msg_and_die("Invalid number of workers: %d", concurrent_workers);
+
+    if (cache_size_mib <= 0)
+        perror_msg_and_die("Invalid cache size in MiB: %d", cache_size_mib);
+
+    if (cache_size_mib > UINT_MAX / (1024 * 1024 / FILENAME_MAX))
+        perror_msg_and_die("Too big cache size. Maximum is : %u MiB", UINT_MAX / (1024 * 1024 / FILENAME_MAX));
+
+    struct process proc = {0};
+    proc.max_children = concurrent_workers;
+    /* By default it is about 1024 entries */
+    g_queue_init(&proc.queue.q);
+    proc.queue.capacity = cache_size_mib * (1024 * 1024 / FILENAME_MAX);
+    VERB3 log("Max queue size %u", proc.queue.capacity);
+
+    argv += optind;
+    if (argv[0])
+    {
+        proc.upload_directory = argv[0];
+
+        if (argv[1])
+            show_usage_and_die(program_usage_string, program_options);
+    }
+
+    /* Initialization */
+    VERB2 log("Loading settings");
+    if (load_abrt_conf() != 0)
+        return 1;
+
+    if (!proc.upload_directory)
+        proc.upload_directory = g_settings_sWatchCrashdumpArchiveDir;
+
+    if (!proc.upload_directory)
+        error_msg_and_die("Neither UPLOAD_DIRECTORY nor WatchCrashdumpArchiveDir was specified");
+
+    if (opts & OPT_d)
+        daemonize();
+
+    msg_prefix = g_progname;
+    if ((opts & OPT_d) || (opts & OPT_s) || getenv("ABRT_SYSLOG"))
+    {
+        openlog(msg_prefix, 0, LOG_DAEMON);
+        logmode = LOGMODE_SYSLOG;
+    }
+
+    VERB2 log("Creating glib main loop");
+    proc.main_loop = g_main_loop_new(NULL, FALSE);
+
+    VERB1 log("Setting up a file monitor for '%s'", proc.upload_directory);
+    /* Never returns NULL; it will die if an error occurs */
+    struct abrt_inotify_watch *aiw = abrt_inotify_watch_init(proc.upload_directory,
+            IN_CLOSE_WRITE | IN_MOVED_TO,
+            handle_inotify_cb, &proc);
+
+    VERB1 log("Setting up a signal handler");
+    /* Set up signal pipe */
+    xpipe(g_signal_pipe);
+    close_on_exec_on(g_signal_pipe[0]);
+    close_on_exec_on(g_signal_pipe[1]);
+    ndelay_on(g_signal_pipe[0]);
+    ndelay_on(g_signal_pipe[1]);
+    signal(SIGTERM, handle_signal);
+    signal(SIGINT, handle_signal);
+    signal(SIGCHLD, handle_signal);
+    GIOChannel *channel_signal = abrt_gio_channel_unix_new(g_signal_pipe[0]);
+    guint channel_signal_source_id = g_io_add_watch(channel_signal,
+                G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                handle_sigchld_pipe,
+                &proc);
+
+    VERB2 log("Starting glib main loop");
+
+    g_main_loop_run(proc.main_loop);
+
+    VERB2 log("Glib main loop finished");
+
+    g_source_remove(channel_signal_source_id);
+
+    GError *error = NULL;
+    g_io_channel_shutdown(channel_signal, FALSE, &error);
+    if (error)
+    {
+        VERB1 log("Can't shutdown gio channel: '%s'", error ? error->message : "");
+        g_error_free(error);
+    }
+
+    g_io_channel_unref(channel_signal);
+
+    abrt_inotify_watch_destroy(aiw);
+
+    if (proc.main_loop)
+        g_main_loop_unref(proc.main_loop);
+
+    free_abrt_conf_data();
+
+    return 0;
+}
diff --git a/src/daemon/abrtd.c b/src/daemon/abrtd.c
index 124421a..8196d39 100644
--- a/src/daemon/abrtd.c
+++ b/src/daemon/abrtd.c
@@ -21,10 +21,9 @@
 #endif
 #include <sys/un.h>
 #include <syslog.h>
-#include <sys/inotify.h>
-#include <sys/ioctl.h> /* ioctl(FIONREAD) */
 
 #include "abrt_glib.h"
+#include "abrt-inotify.h"
 #include "libabrt.h"
 
 
@@ -53,7 +52,6 @@
 static volatile sig_atomic_t s_sig_caught;
 static int s_signal_pipe[2];
 static int s_signal_pipe_write = -1;
-static int s_upload_watch = -1;
 static unsigned s_timeout;
 static bool s_exiting;
 
@@ -62,7 +60,6 @@ static guint channel_id_socket = 0;
 static int child_count = 0;
 
 /* Helpers */
-
 static guint add_watch_or_die(GIOChannel *channel, unsigned condition, GIOFunc func)
 {
     errno = 0;
@@ -193,124 +190,23 @@ static void sanitize_dump_dir_rights(void)
 
 /* Inotify handler */
 
-static gboolean handle_inotify_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr_unused)
+static void handle_inotify_cb(struct abrt_inotify_watch *watch, struct inotify_event *event, gpointer ptr_unused)
 {
-    /* Default size: 128 simultaneous actions (about 1/2 meg) */
-#define INOTIFY_BUF_SIZE ((sizeof(struct inotify_event) + FILENAME_MAX)*128)
-    /* Determine how much to read (it usually is much smaller) */
-    /* NB: this variable _must_ be int-sized, ioctl expects that! */
-    int inotify_bytes = INOTIFY_BUF_SIZE;
-    if (ioctl(g_io_channel_unix_get_fd(gio), FIONREAD, &inotify_bytes) != 0
-    /*|| inotify_bytes < sizeof(struct inotify_event)
-         ^^^^^^^^^^^^^^^^^^^ - WRONG: legitimate 0 was seen when flooded with inotify events
-    */
-     || inotify_bytes > INOTIFY_BUF_SIZE
-    ) {
-        inotify_bytes = INOTIFY_BUF_SIZE;
-    }
-    VERB3 log("FIONREAD:%d", inotify_bytes);
-
-    if (inotify_bytes == 0)
-        return TRUE; /* "please don't remove this event" */
-
-    /* We may race: more inotify events may happen after ioctl(FIONREAD).
-     * To be more efficient, allocate a bit more space to eat those events too.
-     * This also would help against a bug we once had where
-     * g_io_channel_read_chars() was buffering reads
-     * and we were going out of sync wrt struct inotify_event's layout.
-     */
-    inotify_bytes += 2 * (sizeof(struct inotify_event) + FILENAME_MAX);
-    char *buf = xmalloc(inotify_bytes);
-    errno = 0;
-    gsize len;
-    GError *gerror = NULL;
-    /* Note: we ensured elsewhere that this read is non-blocking, making it ok
-     * for buffer len (inotify_bytes) to be larger than actual available byte count.
-     */
-    GIOStatus err = g_io_channel_read_chars(gio, buf, inotify_bytes, &len, &gerror);
-    if (err != G_IO_STATUS_NORMAL)
-    {
-        perror_msg("Error reading inotify fd: %s", gerror ? gerror->message : "unknown");
-        free(buf);
-        if (gerror)
-            g_error_free(gerror);
-        return FALSE; /* "remove this event" (huh??) */
-    }
-
-    /* Reconstruct each event */
-    gsize i = 0;
-    for (;;)
-    {
-        if (i >= len)
-        {
-            /* This would catch one of our former bugs. Let's be paranoid */
-            if (i > len)
-                error_msg("warning: ran off struct inotify (this should never happen): %u > %u", (int)i, (int)len);
-            break;
-        }
-
-        struct inotify_event *event = (struct inotify_event *) &buf[i];
+/* We no longer need a name of event */
+#if 0
         const char *name = NULL;
         if (event->len)
             name = event->name;
+#endif
         //log("i:%d len:%d event->mask:%x IN_ISDIR:%x IN_CLOSE_WRITE:%x event->len:%d",
         //    i, len, event->mask, IN_ISDIR, IN_CLOSE_WRITE, event->len);
-        i += sizeof(*event) + event->len;
-
-        if (event->wd == s_upload_watch)
-        {
-            /* Was the (presumable newly created) file closed in upload dir,
-             * or a file moved to upload dir? */
-            if (!(event->mask & IN_ISDIR)
-             && event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO)
-             && name
-            ) {
-                const char *ext = strrchr(name, '.');
-                if (ext && strcmp(ext + 1, "working") == 0)
-                    continue;
-
-                const char *dir = g_settings_sWatchCrashdumpArchiveDir;
-                log("Detected creation of file '%s' in upload directory '%s'", name, dir);
-
-                fflush(NULL); /* paranoia */
-                pid_t pid = fork();
-                if (pid < 0)
-                    perror_msg("fork");
-                if (pid == 0)
-                {
-                    /* child */
-                    xchdir(dir);
-                    if (g_settings_delete_uploaded)
-                        execlp("abrt-handle-upload", "abrt-handle-upload", "-d",
-                               g_settings_dump_location, dir, name, (char*)NULL);
-                    else
-                        execlp("abrt-handle-upload", "abrt-handle-upload",
-                               g_settings_dump_location, dir, name, (char*)NULL);
-                    error_msg_and_die("Can't execute '%s'", "abrt-handle-upload");
-                }
-
-                if (pid > 0)
-                    increment_child_count();
-            }
-            continue;
-        }
 
         if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF)
         {
-            /* HACK: we expect that we watch deletion only of 'g_settings_dump_location'
-             * but this handler is used for 'g_settings_sWatchCrashdumpArchiveDir' too
-             */
             log("Recreating deleted dump location '%s'", g_settings_dump_location);
 
-            int inotify_fd = g_io_channel_unix_get_fd(gio);
-            inotify_rm_watch(inotify_fd, event->wd);
             sanitize_dump_dir_rights();
-            if (inotify_add_watch(inotify_fd, g_settings_dump_location, IN_DUMP_LOCATION_FLAGS) < 0)
-            {
-                perror_msg_and_die("inotify_add_watch failed on recreated '%s'", g_settings_dump_location);
-            }
-
-            continue;
+            abrt_inotify_watch_reset(watch, g_settings_dump_location, IN_DUMP_LOCATION_FLAGS);
         }
 /* We no longer watch for subdirectory creations */
 #if 0
@@ -330,10 +226,6 @@ static gboolean handle_inotify_cb(GIOChannel *gio, GIOCondition condition, gpoin
         log("Directory '%s' creation detected", name);
         ...
 #endif
-    } /* while */
-
-    free(buf);
-    return TRUE; /* "please don't remove this event" */
 }
 
 
@@ -644,11 +536,10 @@ int main(int argc, char** argv)
         signal(SIGALRM, handle_signal);
 
     GMainLoop* pMainloop = NULL;
-    GIOChannel* channel_inotify = NULL;
-    guint channel_id_inotify_event = 0;
     GIOChannel* channel_signal = NULL;
     guint channel_id_signal_event = 0;
     bool pidfile_created = false;
+    struct abrt_inotify_watch *aiw = NULL;
 
     /* Initialization */
     VERB1 log("Loading settings");
@@ -700,53 +591,11 @@ int main(int argc, char** argv)
     VERB1 log("Creating glib main loop");
     pMainloop = g_main_loop_new(NULL, FALSE);
 
-    VERB1 log("Initializing inotify");
-    errno = 0;
-    int inotify_fd = inotify_init();
-    if (inotify_fd == -1)
-        perror_msg_and_die("inotify_init failed");
-    close_on_exec_on(inotify_fd);
-
     /* Watching 'g_settings_dump_location' for delete self
      * because hooks expects that the dump location exists if abrtd is running
      */
-    if (inotify_add_watch(inotify_fd, g_settings_dump_location, IN_DUMP_LOCATION_FLAGS) < 0)
-    {
-        perror_msg("inotify_add_watch failed on '%s'", g_settings_dump_location);
-        goto init_error;
-    }
-    /* ...and upload dir */
-    if (g_settings_sWatchCrashdumpArchiveDir)
-    {
-        if (strcmp(g_settings_sWatchCrashdumpArchiveDir, g_settings_dump_location) == 0)
-        {
-            error_msg("%s and %s can't be the same", "DumpLocation", "WatchCrashdumpArchiveDir");
-            goto init_error;
-        }
-        s_upload_watch = inotify_add_watch(inotify_fd, g_settings_sWatchCrashdumpArchiveDir, IN_CLOSE_WRITE|IN_MOVED_TO);
-        if (s_upload_watch < 0)
-        {
-            perror_msg("inotify_add_watch failed on '%s'", g_settings_sWatchCrashdumpArchiveDir);
-            goto init_error;
-        }
-    }
-
-    VERB1 log("Adding inotify watch to glib main loop");
-    /* Without nonblocking mode, users observed abrtd blocking
-     * on inotify read forever. Must set fd to non-blocking:
-     */
-    ndelay_on(inotify_fd);
-    channel_inotify = abrt_gio_channel_unix_new(inotify_fd);
-    /*
-     * glib's read buffering must be disabled, or else
-     * FIONREAD-reported "available data" sizes and sizes of reads
-     * can become inconsistent, and worse, buffering can split
-     * struct inotify's (very bad!).
-     */
-    g_io_channel_set_buffered(channel_inotify, false);
-    channel_id_inotify_event = add_watch_or_die(channel_inotify,
-                        G_IO_IN | G_IO_PRI | G_IO_HUP,
-                        handle_inotify_cb);
+    aiw = abrt_inotify_watch_init(g_settings_dump_location,
+            IN_DUMP_LOCATION_FLAGS, handle_inotify_cb, /*user data*/NULL);
 
     /* Add an event source which waits for INT/TERM signal */
     VERB1 log("Adding signal pipe watch to glib main loop");
@@ -792,10 +641,8 @@ int main(int argc, char** argv)
         g_source_remove(channel_id_signal_event);
     if (channel_signal)
         g_io_channel_unref(channel_signal);
-    if (channel_id_inotify_event > 0)
-        g_source_remove(channel_id_inotify_event);
-    if (channel_inotify)
-        g_io_channel_unref(channel_inotify);
+
+    abrt_inotify_watch_destroy(aiw);
 
     if (pMainloop)
         g_main_loop_unref(pMainloop);
-- 
1.8.3.1



More information about the Crash-catcher mailing list