| /* | |
| Copyright (C) 2005 John McCutchan | |
| Copyright © 2015 Canonical Limited | |
| SPDX-License-Identifier: LGPL-2.1-or-later | |
| This library is free software; you can redistribute it and/or | |
| modify it under the terms of the GNU Lesser General Public | |
| License as published by the Free Software Foundation; either | |
| version 2.1 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 | |
| Lesser General Public License for more details. | |
| You should have received a copy of the GNU Lesser General Public License | |
| along with this library; 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> | |
| #ifdef HAVE_SYS_FILIO_H | |
| #include <sys/filio.h> | |
| #endif | |
| #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; | |
| int errsv; | |
| again: | |
| result = read (iks->fd, buffer, buffer_len); | |
| errsv = errno; | |
| if (result < 0) | |
| { | |
| if (errsv == EINTR) | |
| goto again; | |
| if (errsv == EAGAIN) | |
| return 0; | |
| g_error ("inotify read(): %s", g_strerror (errsv)); | |
| } | |
| 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; | |
| int errsv; | |
| /* figure out how many more bytes there are to read */ | |
| result = ioctl (iks->fd, FIONREAD, &n_readable); | |
| errsv = errno; | |
| if (result != 0) | |
| g_error ("inotify ioctl(FIONREAD): %s", g_strerror (errsv)); | |
| 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 an 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, | |
| NULL, NULL, NULL | |
| }; | |
| InotifyKernelSource *iks; | |
| GSource *source; | |
| gboolean should_set_nonblock = FALSE; | |
| source = g_source_new (&source_funcs, sizeof (InotifyKernelSource)); | |
| iks = (InotifyKernelSource *) source; | |
| g_source_set_static_name (source, "inotify kernel source"); | |
| iks->unmatched_moves = g_hash_table_new (NULL, NULL); | |
| iks->fd = inotify_init1 (IN_CLOEXEC | IN_NONBLOCK); | |
| if (iks->fd < 0) | |
| { | |
| should_set_nonblock = TRUE; | |
| iks->fd = inotify_init (); | |
| } | |
| if (iks->fd >= 0) | |
| { | |
| GError *error = NULL; | |
| if (should_set_nonblock) | |
| { | |
| 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_pointer (&inotify_source)) | |
| g_once_init_leave_pointer (&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; | |
| } |