Version 2: * used xmalloc, xchdir * removed APPROX_BYTES_PER_ARCHIVE_NAME * the standard descriptors moved to /dev/null * still using queue as we should decide which use cases we want to support * still using struct process because I want to avoid global variables (well, I know that the structure is overloaded but I'm still happy with this solution because it is less confusing for me)
git am -s -c *.mbox -->8-->8-->8-->8-->8-->8-->8-->8-->8-->8-->8-->8-->8-->8-->8-->8-- Related to #657
Signed-off-by: Jakub Filak jfilak@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 | 376 +++++++++++++++++++++++++++++++++++++++++ src/daemon/abrtd.c | 175 ++----------------- 8 files changed, 678 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..54ab080 --- /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 = xmalloc(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->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..88d3033 --- /dev/null +++ b/src/daemon/abrt-upload-watch.c @@ -0,0 +1,376 @@ +/* + 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 + +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_signal_pipe_cb(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 */ + xchdir("/"); + + /* Reopen the standard file descriptors to "/dev/null" */ + xmove_fd(xopen("/dev/null", O_RDWR), STDIN_FILENO); + xdup2(STDIN_FILENO, STDOUT_FILENO); + xdup2(STDIN_FILENO, 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_signal_pipe_cb, + &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);