glib/gio/inotify/inotify-kernel.c
Ryan Lortie fd40b5942d inotify: fix move event matching accounting
The hash table stores the list of unmatched IN_MOVE_FROM events, but we
were removing entries from it when popping IN_MOVE_TO events.

Fix that up to correct a crash in nautilus due to the assertion failure
below.

https://bugzilla.gnome.org/show_bug.cgi?id=746749
2015-03-26 14:56:25 -04:00

453 lines
12 KiB
C

/*
Copyright (C) 2005 John McCutchan
Copyright © 2015 Canonical Limited
The Gnome Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The Gnome Library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the Gnome Library; see the file COPYING.LIB. If not,
see <http://www.gnu.org/licenses/>.
Authors:
Ryan Lortie <desrt@desrt.ca>
John McCutchan <john@johnmccutchan.com>
*/
#include "config.h"
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <glib.h>
#include "inotify-kernel.h"
#include <sys/inotify.h>
#include <glib/glib-unix.h>
#include "glib-private.h"
/* From inotify(7) */
#define MAX_EVENT_SIZE (sizeof(struct inotify_event) + NAME_MAX + 1)
/* Amount of time to sleep on receipt of uninteresting events */
#define BOREDOM_SLEEP_TIME (100 * G_TIME_SPAN_MILLISECOND)
/* Define limits on the maximum amount of time and maximum amount of
* interceding events between FROM/TO that can be merged.
*/
#define MOVE_PAIR_DELAY (10 * G_TIME_SPAN_MILLISECOND)
#define MOVE_PAIR_DISTANCE (100)
/* We use the lock from inotify-helper.c
*
* We only have to take it on our read callback.
*
* The rest of locking is taken care of in inotify-helper.c
*/
G_LOCK_EXTERN (inotify_lock);
static ik_event_t *
ik_event_new (struct inotify_event *kevent,
gint64 now)
{
ik_event_t *event = g_new0 (ik_event_t, 1);
event->wd = kevent->wd;
event->mask = kevent->mask;
event->cookie = kevent->cookie;
event->len = kevent->len;
event->timestamp = now;
if (event->len)
event->name = g_strdup (kevent->name);
else
event->name = NULL;
return event;
}
void
_ik_event_free (ik_event_t *event)
{
if (event->pair)
{
event->pair->pair = NULL;
_ik_event_free (event->pair);
}
g_free (event->name);
g_free (event);
}
typedef struct
{
GSource source;
GQueue queue;
gpointer fd_tag;
gint fd;
GHashTable *unmatched_moves;
gboolean is_bored;
} InotifyKernelSource;
static InotifyKernelSource *inotify_source;
static gint64
ik_source_get_dispatch_time (InotifyKernelSource *iks)
{
ik_event_t *head;
head = g_queue_peek_head (&iks->queue);
/* nothing in the queue: not ready */
if (!head)
return -1;
/* if it's not an unpaired move, it is ready now */
if (~head->mask & IN_MOVED_FROM || head->pair)
return 0;
/* if the queue is too long then it's ready now */
if (iks->queue.length > MOVE_PAIR_DISTANCE)
return 0;
/* otherwise, it's ready after the delay */
return head->timestamp + MOVE_PAIR_DELAY;
}
static gboolean
ik_source_can_dispatch_now (InotifyKernelSource *iks,
gint64 now)
{
gint64 dispatch_time;
dispatch_time = ik_source_get_dispatch_time (iks);
return 0 <= dispatch_time && dispatch_time <= now;
}
static gsize
ik_source_read_some_events (InotifyKernelSource *iks,
gchar *buffer,
gsize buffer_len)
{
gssize result;
again:
result = read (iks->fd, buffer, buffer_len);
if (result < 0)
{
if (errno == EINTR)
goto again;
if (errno == EAGAIN)
return 0;
g_error ("inotify read(): %s", g_strerror (errno));
}
else if (result == 0)
g_error ("inotify unexpectedly hit eof");
return result;
}
static gchar *
ik_source_read_all_the_events (InotifyKernelSource *iks,
gchar *buffer,
gsize buffer_len,
gsize *length_out)
{
gsize n_read;
n_read = ik_source_read_some_events (iks, buffer, buffer_len);
/* Check if we might have gotten another event if we had passed in a
* bigger buffer...
*/
if (n_read + MAX_EVENT_SIZE > buffer_len)
{
gchar *new_buffer;
guint n_readable;
gint result;
/* figure out how many more bytes there are to read */
result = ioctl (iks->fd, FIONREAD, &n_readable);
if (result != 0)
g_error ("inotify ioctl(FIONREAD): %s", g_strerror (errno));
if (n_readable != 0)
{
/* there is in fact more data. allocate a new buffer, copy
* the existing data, and then append the remaining.
*/
new_buffer = g_malloc (n_read + n_readable);
memcpy (new_buffer, buffer, n_read);
n_read += ik_source_read_some_events (iks, new_buffer + n_read, n_readable);
buffer = new_buffer;
/* There may be new events in the buffer that were added after
* the FIONREAD was performed, but we can't risk getting into
* a loop. We'll get them next time.
*/
}
}
*length_out = n_read;
return buffer;
}
static gboolean
ik_source_dispatch (GSource *source,
GSourceFunc func,
gpointer user_data)
{
InotifyKernelSource *iks = (InotifyKernelSource *) source;
gboolean (*user_callback) (ik_event_t *event) = (void *) func;
gboolean interesting = FALSE;
gint64 now;
now = g_source_get_time (source);
if (iks->is_bored || g_source_query_unix_fd (source, iks->fd_tag))
{
gchar stack_buffer[4096];
gsize buffer_len;
gchar *buffer;
gsize offset;
/* We want to read all of the available events.
*
* We need to do it in a finite number of steps so that we don't
* get caught in a loop of read() with another process
* continuously adding events each time we drain them.
*
* In the normal case we will have only a few events in the queue,
* so start out by reading into a small stack-allocated buffer.
* Even though we're on a fresh stack frame, there is no need to
* pointlessly blow up with the size of the worker thread stack
* with a huge buffer here.
*
* If the result is large enough to cause us to suspect that
* another event may be pending then we allocate a buffer on the
* heap that can hold all of the events and read (once!) into that
* buffer.
*/
buffer = ik_source_read_all_the_events (iks, stack_buffer, sizeof stack_buffer, &buffer_len);
offset = 0;
while (offset < buffer_len)
{
struct inotify_event *kevent = (struct inotify_event *) (buffer + offset);
ik_event_t *event;
event = ik_event_new (kevent, now);
offset += sizeof (struct inotify_event) + event->len;
if (event->mask & IN_MOVED_TO)
{
ik_event_t *pair;
pair = g_hash_table_lookup (iks->unmatched_moves, GUINT_TO_POINTER (event->cookie));
if (pair != NULL)
{
g_assert (!pair->pair);
g_hash_table_remove (iks->unmatched_moves, GUINT_TO_POINTER (event->cookie));
event->is_second_in_pair = TRUE;
event->pair = pair;
pair->pair = event;
continue;
}
interesting = TRUE;
}
else if (event->mask & IN_MOVED_FROM)
{
gboolean new;
new = g_hash_table_insert (iks->unmatched_moves, GUINT_TO_POINTER (event->cookie), event);
if G_UNLIKELY (!new)
g_warning ("inotify: got IN_MOVED_FROM event with already-pending cookie %#x", event->cookie);
interesting = TRUE;
}
g_queue_push_tail (&iks->queue, event);
}
if (buffer_len == 0)
{
/* We can end up reading nothing if we arrived here due to a
* boredom timer but the stream of events stopped meanwhile.
*
* In that case, we need to switch back to polling the file
* descriptor in the usual way.
*/
g_assert (iks->is_bored);
interesting = TRUE;
}
if (buffer != stack_buffer)
g_free (buffer);
}
while (ik_source_can_dispatch_now (iks, now))
{
ik_event_t *event;
/* callback will free the event */
event = g_queue_pop_head (&iks->queue);
if (event->mask & IN_MOVED_FROM && !event->pair)
g_hash_table_remove (iks->unmatched_moves, GUINT_TO_POINTER (event->cookie));
G_LOCK (inotify_lock);
interesting |= (* user_callback) (event);
G_UNLOCK (inotify_lock);
}
/* The queue gets blocked iff we have unmatched moves */
g_assert ((iks->queue.length > 0) == (g_hash_table_size (iks->unmatched_moves) > 0));
/* Here's where we decide what will wake us up next.
*
* If the last event was interesting then we will wake up on the fd or
* when the timeout is reached on an unpaired move (if any).
*
* If the last event was uninteresting then we will wake up after the
* shorter of the boredom sleep or any timeout for a unpaired move.
*/
if (interesting)
{
if (iks->is_bored)
{
g_source_modify_unix_fd (source, iks->fd_tag, G_IO_IN);
iks->is_bored = FALSE;
}
g_source_set_ready_time (source, ik_source_get_dispatch_time (iks));
}
else
{
guint64 dispatch_time = ik_source_get_dispatch_time (iks);
guint64 boredom_time = now + BOREDOM_SLEEP_TIME;
if (!iks->is_bored)
{
g_source_modify_unix_fd (source, iks->fd_tag, 0);
iks->is_bored = TRUE;
}
g_source_set_ready_time (source, MIN (dispatch_time, boredom_time));
}
return TRUE;
}
static InotifyKernelSource *
ik_source_new (gboolean (* callback) (ik_event_t *event))
{
static GSourceFuncs source_funcs = {
NULL, NULL,
ik_source_dispatch
/* should have a finalize, but it will never happen */
};
InotifyKernelSource *iks;
GSource *source;
source = g_source_new (&source_funcs, sizeof (InotifyKernelSource));
iks = (InotifyKernelSource *) source;
g_source_set_name (source, "inotify kernel source");
iks->unmatched_moves = g_hash_table_new (NULL, NULL);
iks->fd = inotify_init1 (IN_CLOEXEC);
if (iks->fd < 0)
iks->fd = inotify_init ();
if (iks->fd >= 0)
{
GError *error = NULL;
g_unix_set_fd_nonblocking (iks->fd, TRUE, &error);
g_assert_no_error (error);
iks->fd_tag = g_source_add_unix_fd (source, iks->fd, G_IO_IN);
}
g_source_set_callback (source, (GSourceFunc) callback, NULL, NULL);
g_source_attach (source, GLIB_PRIVATE_CALL (g_get_worker_context) ());
return iks;
}
gboolean
_ik_startup (gboolean (*cb)(ik_event_t *event))
{
if (g_once_init_enter (&inotify_source))
g_once_init_leave (&inotify_source, ik_source_new (cb));
return inotify_source->fd >= 0;
}
gint32
_ik_watch (const char *path,
guint32 mask,
int *err)
{
gint32 wd = -1;
g_assert (path != NULL);
g_assert (inotify_source && inotify_source->fd >= 0);
wd = inotify_add_watch (inotify_source->fd, path, mask);
if (wd < 0)
{
int e = errno;
/* FIXME: debug msg failed to add watch */
if (err)
*err = e;
return wd;
}
g_assert (wd >= 0);
return wd;
}
int
_ik_ignore (const char *path,
gint32 wd)
{
g_assert (wd >= 0);
g_assert (inotify_source && inotify_source->fd >= 0);
if (inotify_rm_watch (inotify_source->fd, wd) < 0)
{
/* int e = errno; */
/* failed to rm watch */
return -1;
}
return 0;
}