/* Copyright (C) 2005 John McCutchan Copyright © 2015 Canonical Limited This 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. This 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 this library; if not, see . Authors: Ryan Lortie John McCutchan */ #include "config.h" #include #include #include #include #include #include #include "inotify-kernel.h" #include #include #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; }