mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-11-04 01:58:54 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			567 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			567 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
   Copyright (C) 2005 John McCutchan
 | 
						|
   Copyright © 2015 Canonical Limited
 | 
						|
   Copyright © 2024 Future Crew LLC
 | 
						|
 | 
						|
   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>
 | 
						|
     Gleb Popov <arrowd@FreeBSD.org>
 | 
						|
*/
 | 
						|
 | 
						|
#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_UIO_H
 | 
						|
#include <sys/uio.h>
 | 
						|
#endif
 | 
						|
#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;  /* (element-type ik_event_t) */
 | 
						|
  gpointer    fd_tag;
 | 
						|
  gint        fd;
 | 
						|
 | 
						|
  GHashTable *unmatched_moves;  /* (element-type guint ik_event_t) */
 | 
						|
  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))
 | 
						|
    {
 | 
						|
#if defined(FILE_MONITOR_BACKEND_INOTIFY)
 | 
						|
      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;
 | 
						|
 | 
						|
              if (g_hash_table_steal_extended (iks->unmatched_moves, GUINT_TO_POINTER (event->cookie), NULL, (gpointer*)&pair))
 | 
						|
                {
 | 
						|
                  g_assert (!pair->pair);
 | 
						|
 | 
						|
                  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);
 | 
						|
#elif defined(FILE_MONITOR_BACKEND_LIBINOTIFY_KQUEUE)
 | 
						|
      struct iovec *received[5];
 | 
						|
      int num_events = libinotify_direct_readv (iks->fd, received, G_N_ELEMENTS(received), /* no_block=*/ 1);
 | 
						|
 | 
						|
      if (num_events < 0)
 | 
						|
        {
 | 
						|
          int errsv = errno;
 | 
						|
          g_warning ("Failed to read inotify events: %s", g_strerror (errsv));
 | 
						|
          /* fall through and skip the next few blocks */
 | 
						|
        }
 | 
						|
 | 
						|
      for (int i = 0; i < num_events; i++)
 | 
						|
        {
 | 
						|
          struct iovec *cur_event = received[i];
 | 
						|
          while (cur_event->iov_base)
 | 
						|
            {
 | 
						|
              struct inotify_event *kevent = (struct inotify_event *) cur_event->iov_base;
 | 
						|
 | 
						|
              ik_event_t *event;
 | 
						|
 | 
						|
              event = ik_event_new (kevent, now);
 | 
						|
 | 
						|
              if (event->mask & IN_MOVED_TO)
 | 
						|
                {
 | 
						|
                  ik_event_t *pair;
 | 
						|
 | 
						|
                  if (g_hash_table_steal_extended (iks->unmatched_moves, GUINT_TO_POINTER (event->cookie), NULL, (gpointer*)&pair))
 | 
						|
                    {
 | 
						|
                      g_assert (!pair->pair);
 | 
						|
 | 
						|
                      event->is_second_in_pair = TRUE;
 | 
						|
                      event->pair = pair;
 | 
						|
                      pair->pair = event;
 | 
						|
 | 
						|
                      cur_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);
 | 
						|
 | 
						|
              cur_event++;
 | 
						|
            }
 | 
						|
          libinotify_free_iovec (received[i]);
 | 
						|
        }
 | 
						|
 | 
						|
      if (num_events == 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;
 | 
						|
        }
 | 
						|
#endif
 | 
						|
    }
 | 
						|
 | 
						|
  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 void
 | 
						|
ik_source_finalize (GSource *source)
 | 
						|
{
 | 
						|
  InotifyKernelSource *iks;
 | 
						|
 | 
						|
  iks = (InotifyKernelSource *) source;
 | 
						|
 | 
						|
#if defined(FILE_MONITOR_BACKEND_INOTIFY)
 | 
						|
  close (iks->fd);
 | 
						|
#elif defined(FILE_MONITOR_BACKEND_LIBINOTIFY_KQUEUE)
 | 
						|
  libinotify_direct_close (iks->fd);
 | 
						|
#endif
 | 
						|
 | 
						|
  iks->fd = -1;
 | 
						|
}
 | 
						|
 | 
						|
static InotifyKernelSource *
 | 
						|
ik_source_new (gboolean (* callback) (ik_event_t *event))
 | 
						|
{
 | 
						|
  static GSourceFuncs source_funcs = {
 | 
						|
    NULL, NULL,
 | 
						|
    ik_source_dispatch,
 | 
						|
    ik_source_finalize,
 | 
						|
    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);
 | 
						|
#if defined(FILE_MONITOR_BACKEND_INOTIFY)
 | 
						|
  iks->fd = inotify_init1 (IN_CLOEXEC | IN_NONBLOCK);
 | 
						|
#elif defined(FILE_MONITOR_BACKEND_LIBINOTIFY_KQUEUE)
 | 
						|
  iks->fd = inotify_init1 (IN_CLOEXEC | IN_NONBLOCK | IN_DIRECT);
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef FILE_MONITOR_BACKEND_INOTIFY
 | 
						|
  if (iks->fd < 0)
 | 
						|
    {
 | 
						|
      should_set_nonblock = TRUE;
 | 
						|
      iks->fd = inotify_init ();
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
  if (iks->fd >= 0)
 | 
						|
    {
 | 
						|
      GError *error = NULL;
 | 
						|
 | 
						|
#ifdef FILE_MONITOR_BACKEND_INOTIFY
 | 
						|
      if (should_set_nonblock)
 | 
						|
        {
 | 
						|
          g_unix_set_fd_nonblocking (iks->fd, TRUE, &error);
 | 
						|
          g_assert_no_error (error);
 | 
						|
        }
 | 
						|
#endif
 | 
						|
 | 
						|
      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;
 | 
						|
}
 |