mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-11-03 17:48:56 +01:00 
			
		
		
		
	The remaining call sites are either Windows-only, between fork () and exec () or in xdgmime copylib. Hope I haven't missed any site.
		
			
				
	
	
		
			622 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			622 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*******************************************************************************
 | 
						||
  Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
 | 
						||
 | 
						||
  Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
						||
  of this software and associated documentation files (the "Software"), to deal
 | 
						||
  in the Software without restriction, including without limitation the rights
 | 
						||
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
						||
  copies of the Software, and to permit persons to whom the Software is
 | 
						||
  furnished to do so, subject to the following conditions:
 | 
						||
 | 
						||
  The above copyright notice and this permission notice shall be included in
 | 
						||
  all copies or substantial portions of the Software.
 | 
						||
 | 
						||
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
						||
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
						||
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
						||
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
						||
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
						||
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
						||
  THE SOFTWARE.
 | 
						||
*******************************************************************************/
 | 
						||
 | 
						||
#include "config.h"
 | 
						||
 | 
						||
#include <sys/types.h>
 | 
						||
#include <sys/event.h>
 | 
						||
#include <sys/time.h>
 | 
						||
#include <sys/socket.h>
 | 
						||
#include <sys/stat.h>
 | 
						||
 | 
						||
#include <errno.h>
 | 
						||
#include <fcntl.h>
 | 
						||
#include <string.h>
 | 
						||
 | 
						||
#ifndef O_CLOEXEC
 | 
						||
#define O_CLOEXEC 0
 | 
						||
#endif
 | 
						||
 | 
						||
#include <glib-object.h>
 | 
						||
#include <glib/gfileutils.h>
 | 
						||
#include <gio/gfilemonitor.h>
 | 
						||
#include <gio/glocalfilemonitor.h>
 | 
						||
#include <gio/giomodule.h>
 | 
						||
#include <gio/gpollfilemonitor.h>
 | 
						||
#include <gio/gfile.h>
 | 
						||
#include <glib-unix.h>
 | 
						||
#include "glib-private.h"
 | 
						||
 | 
						||
#include "kqueue-helper.h"
 | 
						||
#include "dep-list.h"
 | 
						||
 | 
						||
G_LOCK_DEFINE_STATIC (kq_lock);
 | 
						||
static GSource       *kq_source;
 | 
						||
static int	      kq_queue = -1;
 | 
						||
 | 
						||
#define G_TYPE_KQUEUE_FILE_MONITOR	(g_kqueue_file_monitor_get_type ())
 | 
						||
#define G_KQUEUE_FILE_MONITOR(inst)	(G_TYPE_CHECK_INSTANCE_CAST ((inst), \
 | 
						||
					G_TYPE_KQUEUE_FILE_MONITOR, GKqueueFileMonitor))
 | 
						||
 | 
						||
/* C11 allows type redefinition, but GLib is configured to use C89, which causes
 | 
						||
 * clang to show warnings when we use a C11 feature. Since the C89 requirement
 | 
						||
 * is mostly used to support MSVC, we simply ignore the warning here because
 | 
						||
 * this file is never going to be useful on Windows. */
 | 
						||
#ifdef __clang__
 | 
						||
#pragma clang diagnostic push
 | 
						||
#pragma clang diagnostic ignored "-Wtypedef-redefinition"
 | 
						||
#endif
 | 
						||
 | 
						||
typedef GLocalFileMonitorClass GKqueueFileMonitorClass;
 | 
						||
 | 
						||
/* When the file we are monitoring is a directory, sub_dir is subscribed to the
 | 
						||
 * directory itself and sub_file is NULL.
 | 
						||
 *
 | 
						||
 * When the file we are monitoring is a regular file, sub_dir is subscribed to
 | 
						||
 * the directory containing the file and sub_file is subscribed to the file
 | 
						||
 * being monitored. We have to monitor both because it is possible that the
 | 
						||
 * file chosen for monitoring doesn't exist when the file monitor is started.
 | 
						||
 * We monitor on its parent in order to get notification when it is created.
 | 
						||
 *
 | 
						||
 * To distinguish between a directory monitor and a regular file monitor, check
 | 
						||
 * whether sub_file is NULL. */
 | 
						||
struct _GKqueueFileMonitor
 | 
						||
{
 | 
						||
  GLocalFileMonitor parent_instance;
 | 
						||
 | 
						||
  kqueue_sub *sub_dir;
 | 
						||
  kqueue_sub *sub_file;
 | 
						||
#ifndef O_EVTONLY
 | 
						||
  GFileMonitor *fallback;
 | 
						||
  GFile *fbfile;
 | 
						||
#endif
 | 
						||
};
 | 
						||
 | 
						||
#ifdef __clang__
 | 
						||
#pragma clang diagnostic pop
 | 
						||
#endif
 | 
						||
 | 
						||
GType g_kqueue_file_monitor_get_type (void);
 | 
						||
G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL_FILE_MONITOR,
 | 
						||
	g_io_extension_point_implement (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
 | 
						||
		g_define_type_id,
 | 
						||
                "kqueue",
 | 
						||
		20))
 | 
						||
 | 
						||
#ifndef O_EVTONLY
 | 
						||
#define O_KQFLAG O_RDONLY
 | 
						||
#else
 | 
						||
#define O_KQFLAG O_EVTONLY
 | 
						||
#endif
 | 
						||
 | 
						||
static inline unsigned int
 | 
						||
note_all (void)
 | 
						||
{
 | 
						||
  unsigned int notes = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE;
 | 
						||
#ifdef NOTE_TRUNCATE
 | 
						||
  notes |= NOTE_TRUNCATE;
 | 
						||
#endif
 | 
						||
#ifdef NOTE_CLOSE_WRITE
 | 
						||
  notes |= NOTE_CLOSE_WRITE;
 | 
						||
#endif
 | 
						||
  return notes;
 | 
						||
}
 | 
						||
 | 
						||
static gboolean g_kqueue_file_monitor_cancel (GFileMonitor* monitor);
 | 
						||
static gboolean g_kqueue_file_monitor_is_supported (void);
 | 
						||
 | 
						||
static kqueue_sub	*_kqsub_new (gchar *, gchar *, GKqueueFileMonitor *, GFileMonitorSource *);
 | 
						||
static void		 _kqsub_free (kqueue_sub *);
 | 
						||
static void		 _kqsub_cancel (kqueue_sub *);
 | 
						||
 | 
						||
 | 
						||
#ifndef O_EVTONLY
 | 
						||
static void
 | 
						||
_fallback_callback (GFileMonitor      *unused,
 | 
						||
                    GFile             *first,
 | 
						||
                    GFile             *second,
 | 
						||
                    GFileMonitorEvent  event,
 | 
						||
                    gpointer           udata)
 | 
						||
{
 | 
						||
  GKqueueFileMonitor *kq_mon = G_KQUEUE_FILE_MONITOR (udata);
 | 
						||
 | 
						||
  g_file_monitor_emit_event (G_FILE_MONITOR (kq_mon), first, second, event);
 | 
						||
}
 | 
						||
 | 
						||
/*
 | 
						||
 * _ke_is_excluded:
 | 
						||
 * @full_path - a path to file to check.
 | 
						||
 *
 | 
						||
 * Returns: TRUE if the file should be excluded from the kqueue-powered
 | 
						||
 *      monitoring, FALSE otherwise.
 | 
						||
 **/
 | 
						||
static gboolean
 | 
						||
_ke_is_excluded (const char *full_path)
 | 
						||
{
 | 
						||
  GFile *f = NULL;
 | 
						||
  GMount *mount = NULL;
 | 
						||
 | 
						||
  f = g_file_new_for_path (full_path);
 | 
						||
 | 
						||
  if (f != NULL) {
 | 
						||
    mount = g_file_find_enclosing_mount (f, NULL, NULL);
 | 
						||
    g_object_unref (f);
 | 
						||
  }
 | 
						||
 | 
						||
  if (mount != NULL && (g_str_has_prefix (full_path, "/media/") || g_str_has_prefix (full_path, "/run/media/")))
 | 
						||
  {
 | 
						||
    g_warning ("Excluding %s from kernel notification, falling back to poll", full_path);
 | 
						||
    if (mount)
 | 
						||
      g_object_unref (mount);
 | 
						||
    return TRUE;
 | 
						||
  }
 | 
						||
 | 
						||
  return FALSE;
 | 
						||
}
 | 
						||
#endif /* !O_EVTONLY */
 | 
						||
 | 
						||
static void
 | 
						||
g_kqueue_file_monitor_finalize (GObject *object)
 | 
						||
{
 | 
						||
  GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (object);
 | 
						||
 | 
						||
  if (kqueue_monitor->sub_dir)
 | 
						||
    {
 | 
						||
      _kqsub_cancel (kqueue_monitor->sub_dir);
 | 
						||
      _kqsub_free (kqueue_monitor->sub_dir);
 | 
						||
      kqueue_monitor->sub_dir = NULL;
 | 
						||
    }
 | 
						||
 | 
						||
  if (kqueue_monitor->sub_file)
 | 
						||
    {
 | 
						||
      _kqsub_cancel (kqueue_monitor->sub_file);
 | 
						||
      _kqsub_free (kqueue_monitor->sub_file);
 | 
						||
      kqueue_monitor->sub_file = NULL;
 | 
						||
    }
 | 
						||
 | 
						||
#ifndef O_EVTONLY
 | 
						||
  if (kqueue_monitor->fallback)
 | 
						||
    g_object_unref (kqueue_monitor->fallback);
 | 
						||
 | 
						||
  if (kqueue_monitor->fbfile)
 | 
						||
    g_object_unref (kqueue_monitor->fbfile);
 | 
						||
#endif
 | 
						||
 | 
						||
  if (G_OBJECT_CLASS (g_kqueue_file_monitor_parent_class)->finalize)
 | 
						||
    (*G_OBJECT_CLASS (g_kqueue_file_monitor_parent_class)->finalize) (object);
 | 
						||
}
 | 
						||
 | 
						||
static void
 | 
						||
g_kqueue_file_monitor_start (GLocalFileMonitor *local_monitor,
 | 
						||
                             const gchar *dirname,
 | 
						||
                             const gchar *basename,
 | 
						||
                             const gchar *filename,
 | 
						||
                             GFileMonitorSource *source)
 | 
						||
{
 | 
						||
  GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (local_monitor);
 | 
						||
  kqueue_sub *sub_dir = NULL, *sub_file = NULL;
 | 
						||
  gchar *path_dir, *path_file, *file_basename;
 | 
						||
 | 
						||
  /* There are three possible cases here:
 | 
						||
   *
 | 
						||
   *  1. Directory: dirname != NULL, basename == NULL, filename == NULL
 | 
						||
   *  2. Regular file: dirname != NULL, basename != NULL, filename == NULL
 | 
						||
   *  3. Hard links: dirname == NULL, basename == NULL, filename != NULL
 | 
						||
   *
 | 
						||
   *  Note that we don't distinguish between case 2 and 3. Kqueue monitors
 | 
						||
   *  files based on file descriptors, so we always receive events come from
 | 
						||
   *  hard links.
 | 
						||
   */
 | 
						||
  if (filename != NULL)
 | 
						||
    {
 | 
						||
      path_dir = g_path_get_dirname (filename);
 | 
						||
      path_file = g_strdup (filename);
 | 
						||
      file_basename = g_path_get_basename (filename);
 | 
						||
    }
 | 
						||
  else
 | 
						||
    {
 | 
						||
      path_dir = g_strdup (dirname);
 | 
						||
      if (basename != NULL)
 | 
						||
        {
 | 
						||
          path_file = g_build_filename (dirname, basename, NULL);
 | 
						||
          file_basename = g_strdup (basename);
 | 
						||
        }
 | 
						||
      else
 | 
						||
        {
 | 
						||
          path_file = NULL;
 | 
						||
          file_basename = NULL;
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
#ifndef O_EVTONLY
 | 
						||
  if (_ke_is_excluded (path_dir))
 | 
						||
    {
 | 
						||
      GFile *file;
 | 
						||
      if (path_file != NULL)
 | 
						||
        file = g_file_new_for_path (path_file);
 | 
						||
      else
 | 
						||
        file = g_file_new_for_path (path_dir);
 | 
						||
      g_free (path_dir);
 | 
						||
      g_free (path_file);
 | 
						||
      g_free (file_basename);
 | 
						||
      kqueue_monitor->fbfile = file;
 | 
						||
      kqueue_monitor->fallback = _g_poll_file_monitor_new (file);
 | 
						||
      g_signal_connect (kqueue_monitor->fallback, "changed",
 | 
						||
			G_CALLBACK (_fallback_callback), kqueue_monitor);
 | 
						||
      return;
 | 
						||
    }
 | 
						||
#endif
 | 
						||
 | 
						||
  /* For a directory monitor, create a subscription object anyway.
 | 
						||
   * It will be used for directory diff calculation routines. 
 | 
						||
   * Wait, directory diff in a GKqueueFileMonitor?
 | 
						||
   * Yes, it is. When a file monitor is started on a non-existent
 | 
						||
   * file, GIO uses a GKqueueFileMonitor object for that. If a directory
 | 
						||
   * will be created under that path, GKqueueFileMonitor will have to
 | 
						||
   * handle the directory notifications. */
 | 
						||
  sub_dir = _kqsub_new (g_steal_pointer (&path_dir), NULL,
 | 
						||
                        kqueue_monitor, source);
 | 
						||
  if (!_kqsub_start_watching (sub_dir))
 | 
						||
    _km_add_missing (sub_dir);
 | 
						||
 | 
						||
  /* Unlike GInotifyFileMonitor, which always uses a directory monitor
 | 
						||
   * regardless of the type of the file being monitored, kqueue doesn't
 | 
						||
   * give us events generated by files under it when we are monitoring
 | 
						||
   * a directory. We have to monitor the file itself to know changes which
 | 
						||
   * was made to the file itself. */
 | 
						||
  if (path_file != NULL)
 | 
						||
    {
 | 
						||
      sub_file = _kqsub_new (g_steal_pointer (&path_file),
 | 
						||
                             g_steal_pointer (&file_basename),
 | 
						||
                             kqueue_monitor, source);
 | 
						||
      if (!_kqsub_start_watching (sub_file))
 | 
						||
        _km_add_missing (sub_file);
 | 
						||
    }
 | 
						||
 | 
						||
  kqueue_monitor->sub_dir = sub_dir;
 | 
						||
  kqueue_monitor->sub_file = sub_file;
 | 
						||
  g_clear_pointer (&path_dir, g_free);
 | 
						||
  g_clear_pointer (&path_file, g_free);
 | 
						||
  g_clear_pointer (&file_basename, g_free);
 | 
						||
}
 | 
						||
 | 
						||
static void
 | 
						||
g_kqueue_file_monitor_class_init (GKqueueFileMonitorClass *klass)
 | 
						||
{
 | 
						||
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 | 
						||
  GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass);
 | 
						||
  GLocalFileMonitorClass *local_file_monitor_class = G_LOCAL_FILE_MONITOR_CLASS (klass);
 | 
						||
 | 
						||
  gobject_class->finalize = g_kqueue_file_monitor_finalize;
 | 
						||
  file_monitor_class->cancel = g_kqueue_file_monitor_cancel;
 | 
						||
 | 
						||
  local_file_monitor_class->is_supported = g_kqueue_file_monitor_is_supported;
 | 
						||
  local_file_monitor_class->start = g_kqueue_file_monitor_start;
 | 
						||
  local_file_monitor_class->mount_notify = TRUE; /* TODO: ??? */
 | 
						||
}
 | 
						||
 | 
						||
static void
 | 
						||
g_kqueue_file_monitor_init (GKqueueFileMonitor *monitor)
 | 
						||
{
 | 
						||
}
 | 
						||
 | 
						||
static gboolean
 | 
						||
g_kqueue_file_monitor_callback (gint fd, GIOCondition condition, gpointer user_data)
 | 
						||
{
 | 
						||
  gint64 now = g_source_get_time (kq_source);
 | 
						||
  kqueue_sub *sub;
 | 
						||
  GFileMonitorSource *source;
 | 
						||
  struct kevent ev;
 | 
						||
  struct timespec ts;
 | 
						||
 | 
						||
  memset (&ts, 0, sizeof(ts));
 | 
						||
 | 
						||
  /* We must hold the global lock before accessing any kqueue_sub because it is
 | 
						||
   * possible for other threads to call g_kqueue_file_monitor_cancel, which may
 | 
						||
   * free the kqueue_sub struct we are accessing. */
 | 
						||
  G_LOCK (kq_lock);
 | 
						||
 | 
						||
  while (kevent(fd, NULL, 0, &ev, 1, &ts) > 0)
 | 
						||
    {
 | 
						||
        if (ev.filter != EVFILT_VNODE || ev.udata == NULL)
 | 
						||
          continue;
 | 
						||
 | 
						||
        sub = ev.udata;
 | 
						||
        source = sub->source;
 | 
						||
 | 
						||
        /* When we are monitoring a regular file which already exists, ignore
 | 
						||
         * events generated by its parent directory. This has to be the first
 | 
						||
         * check to prevent the following code to emit useless events */
 | 
						||
        if (sub->is_dir && sub->mon->sub_file != NULL && sub->mon->sub_file->fd != -1)
 | 
						||
          continue;
 | 
						||
 | 
						||
        if (ev.flags & EV_ERROR)
 | 
						||
          ev.fflags = NOTE_REVOKE;
 | 
						||
 | 
						||
        if (sub->is_dir && ev.fflags & (NOTE_WRITE | NOTE_EXTEND))
 | 
						||
          {
 | 
						||
            /* If we are monitoring on a non-existent regular file, trigger the
 | 
						||
             * rescan of missing files immediately so we don't have to wait for
 | 
						||
             * 4 seconds for discovering missing files. We pass the sub_file
 | 
						||
             * corresponding to the GKqueueFileMonitor to 'check_this_sub_only'
 | 
						||
             * argument to prevent _km_scan_missing from emitting 'CREATED'
 | 
						||
             * events because _kh_dir_diff will do it for us. */
 | 
						||
            if (sub->mon->sub_file != NULL && sub->mon->sub_file->fd == -1)
 | 
						||
              _km_scan_missing (sub->mon->sub_file);
 | 
						||
 | 
						||
            /* If we are monitoring a regular file, don't emit 'DELETED' events
 | 
						||
             * from the directory monitor because it will be emitted from the
 | 
						||
             * file itself when a NOTE_DELETE is reported on sub_file. */
 | 
						||
            _kh_dir_diff (sub, sub->mon->sub_file == NULL);
 | 
						||
 | 
						||
#ifdef NOTE_TRUNCATE
 | 
						||
            ev.fflags &= ~(NOTE_WRITE | NOTE_EXTEND | NOTE_TRUNCATE);
 | 
						||
#else
 | 
						||
            ev.fflags &= ~(NOTE_WRITE | NOTE_EXTEND);
 | 
						||
#endif
 | 
						||
          }
 | 
						||
 | 
						||
        /* Here starts the long section of mapping kqueue events to
 | 
						||
         * GFileMonitorEvent. Since kqueue can return multiple events in a
 | 
						||
         * single kevent struct, we must use 'if' instead of 'else if'. */
 | 
						||
        if (ev.fflags & NOTE_DELETE)
 | 
						||
          {
 | 
						||
            struct stat st;
 | 
						||
            if (fstat (sub->fd, &st) < 0)
 | 
						||
              st.st_nlink = 0;
 | 
						||
 | 
						||
            g_file_monitor_source_handle_event (source,
 | 
						||
                                                G_FILE_MONITOR_EVENT_DELETED,
 | 
						||
                                                sub->basename, NULL, NULL, now);
 | 
						||
 | 
						||
            /* If the last reference to the file was removed, delete the
 | 
						||
             * subscription from kqueue and add it to the missing list.
 | 
						||
             * If you are monitoring a file which has hard link count higher
 | 
						||
             * than 1, it is possible for the same file to emit 'DELETED'
 | 
						||
             * events multiple times. */
 | 
						||
            if (st.st_nlink == 0)
 | 
						||
              {
 | 
						||
                _kqsub_cancel (sub);
 | 
						||
                _km_add_missing (sub);
 | 
						||
              }
 | 
						||
          }
 | 
						||
         if (ev.fflags & NOTE_REVOKE)
 | 
						||
           {
 | 
						||
             g_file_monitor_source_handle_event (source,
 | 
						||
                                                 G_FILE_MONITOR_EVENT_UNMOUNTED,
 | 
						||
                                                 sub->basename, NULL, NULL, now);
 | 
						||
             _kqsub_cancel (sub);
 | 
						||
             _km_add_missing (sub);
 | 
						||
           }
 | 
						||
        if (ev.fflags & NOTE_ATTRIB)
 | 
						||
          {
 | 
						||
            g_file_monitor_source_handle_event (source,
 | 
						||
                                                G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
 | 
						||
                                                sub->basename, NULL, NULL, now);
 | 
						||
          }
 | 
						||
#ifdef NOTE_TRUNCATE
 | 
						||
        if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND | NOTE_TRUNCATE))
 | 
						||
#else
 | 
						||
        if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND))
 | 
						||
#endif
 | 
						||
          {
 | 
						||
            g_file_monitor_source_handle_event (source,
 | 
						||
                                                G_FILE_MONITOR_EVENT_CHANGED,
 | 
						||
                                                sub->basename, NULL, NULL, now);
 | 
						||
          }
 | 
						||
        if (ev.fflags & NOTE_RENAME)
 | 
						||
          {
 | 
						||
            /* Since there’s apparently no way to get the new name of the
 | 
						||
             * file out of kqueue(), all we can do is say that this one has
 | 
						||
             * been deleted. */
 | 
						||
            g_file_monitor_source_handle_event (source,
 | 
						||
                                                G_FILE_MONITOR_EVENT_DELETED,
 | 
						||
                                                sub->basename, NULL, NULL, now);
 | 
						||
          }
 | 
						||
#ifdef NOTE_CLOSE_WRITE
 | 
						||
        if (ev.fflags & NOTE_CLOSE_WRITE)
 | 
						||
          {
 | 
						||
            g_file_monitor_source_handle_event (source,
 | 
						||
                                                G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
 | 
						||
                                                sub->basename, NULL, NULL, now);
 | 
						||
          }
 | 
						||
#endif
 | 
						||
 | 
						||
        /* Handle the case when a file is created again shortly after it was
 | 
						||
         * deleted. It has to be the last check because 'DELETED' must happen
 | 
						||
         * before 'CREATED'. */
 | 
						||
        if (ev.fflags & (NOTE_DELETE | NOTE_REVOKE))
 | 
						||
          _km_scan_missing (NULL);
 | 
						||
    }
 | 
						||
 | 
						||
  G_UNLOCK (kq_lock);
 | 
						||
 | 
						||
  return TRUE;
 | 
						||
}
 | 
						||
 | 
						||
static gboolean
 | 
						||
g_kqueue_file_monitor_is_supported (void)
 | 
						||
{
 | 
						||
  int errsv;
 | 
						||
 | 
						||
  G_LOCK (kq_lock);
 | 
						||
 | 
						||
  if (kq_queue == -1)
 | 
						||
    {
 | 
						||
      kq_queue = kqueue ();
 | 
						||
      errsv = errno;
 | 
						||
 | 
						||
      if (kq_queue == -1)
 | 
						||
        {
 | 
						||
          g_warning ("Unable to create a kqueue: %s", g_strerror (errsv));
 | 
						||
          G_UNLOCK (kq_lock);
 | 
						||
          return FALSE;
 | 
						||
        }
 | 
						||
 | 
						||
      kq_source = g_unix_fd_source_new (kq_queue, G_IO_IN);
 | 
						||
      g_source_set_callback (kq_source, (GSourceFunc) g_kqueue_file_monitor_callback, NULL, NULL);
 | 
						||
      g_source_attach (kq_source, GLIB_PRIVATE_CALL (g_get_worker_context) ());
 | 
						||
    }
 | 
						||
 | 
						||
  G_UNLOCK (kq_lock);
 | 
						||
 | 
						||
  return TRUE;
 | 
						||
}
 | 
						||
 | 
						||
static gboolean
 | 
						||
g_kqueue_file_monitor_cancel (GFileMonitor *monitor)
 | 
						||
{
 | 
						||
  GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (monitor);
 | 
						||
 | 
						||
  /* We must hold the global lock before calling _kqsub_cancel. However, we
 | 
						||
   * cannot call G_LOCK in _kqsub_cancel because it is also used by
 | 
						||
   * g_kqueue_file_monitor_callback, which already holds the lock itself. */
 | 
						||
  G_LOCK (kq_lock);
 | 
						||
 | 
						||
  if (kqueue_monitor->sub_dir)
 | 
						||
    {
 | 
						||
      _kqsub_cancel (kqueue_monitor->sub_dir);
 | 
						||
      _kqsub_free (kqueue_monitor->sub_dir);
 | 
						||
      kqueue_monitor->sub_dir = NULL;
 | 
						||
    }
 | 
						||
  if (kqueue_monitor->sub_file)
 | 
						||
    {
 | 
						||
      _kqsub_cancel (kqueue_monitor->sub_file);
 | 
						||
      _kqsub_free (kqueue_monitor->sub_file);
 | 
						||
      kqueue_monitor->sub_file = NULL;
 | 
						||
    }
 | 
						||
 | 
						||
  G_UNLOCK (kq_lock);
 | 
						||
 | 
						||
#ifndef O_EVTONLY
 | 
						||
  if (kqueue_monitor->fallback)
 | 
						||
    {
 | 
						||
      g_signal_handlers_disconnect_by_func (kqueue_monitor->fallback, _fallback_callback, kqueue_monitor);
 | 
						||
      g_file_monitor_cancel (kqueue_monitor->fallback);
 | 
						||
    }
 | 
						||
#endif
 | 
						||
 | 
						||
  if (G_FILE_MONITOR_CLASS (g_kqueue_file_monitor_parent_class)->cancel)
 | 
						||
    (*G_FILE_MONITOR_CLASS (g_kqueue_file_monitor_parent_class)->cancel) (monitor);
 | 
						||
 | 
						||
  return TRUE;
 | 
						||
}
 | 
						||
 | 
						||
static kqueue_sub *
 | 
						||
_kqsub_new (gchar *filename, gchar *basename, GKqueueFileMonitor *mon, GFileMonitorSource *source)
 | 
						||
{
 | 
						||
  kqueue_sub *sub;
 | 
						||
 | 
						||
  sub = g_slice_new (kqueue_sub);
 | 
						||
  sub->filename = filename;
 | 
						||
  sub->basename = basename;
 | 
						||
  sub->mon = mon;
 | 
						||
  g_source_ref ((GSource *) source);
 | 
						||
  sub->source = source;
 | 
						||
  sub->fd = -1;
 | 
						||
  sub->deps = NULL;
 | 
						||
  sub->is_dir = 0;
 | 
						||
 | 
						||
  return sub;
 | 
						||
}
 | 
						||
 | 
						||
static void
 | 
						||
_kqsub_free (kqueue_sub *sub)
 | 
						||
{
 | 
						||
  g_assert (sub->deps == NULL);
 | 
						||
  g_assert (sub->fd == -1);
 | 
						||
 | 
						||
  g_source_unref ((GSource *) sub->source);
 | 
						||
  g_free (sub->filename);
 | 
						||
  g_free (sub->basename);
 | 
						||
  g_slice_free (kqueue_sub, sub);
 | 
						||
}
 | 
						||
 | 
						||
static void
 | 
						||
_kqsub_cancel (kqueue_sub *sub)
 | 
						||
{
 | 
						||
  /* WARNING: Before calling this function, you must hold a lock on kq_lock
 | 
						||
   * or you will cause use-after-free in g_kqueue_file_monitor_callback. */
 | 
						||
 | 
						||
  struct kevent ev;
 | 
						||
 | 
						||
  /* Remove the event and close the file descriptor to automatically
 | 
						||
   * delete pending events. */
 | 
						||
  if (sub->fd != -1)
 | 
						||
    {
 | 
						||
      EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_DELETE, note_all (), 0, sub);
 | 
						||
      if (kevent (kq_queue, &ev, 1, NULL, 0, NULL) == -1)
 | 
						||
        {
 | 
						||
          g_warning ("Unable to remove event for %s: %s", sub->filename, g_strerror (errno));
 | 
						||
        }
 | 
						||
      close (sub->fd);
 | 
						||
      sub->fd = -1;
 | 
						||
    }
 | 
						||
 | 
						||
  _km_remove (sub);
 | 
						||
 | 
						||
  if (sub->deps)
 | 
						||
    {
 | 
						||
      dl_free (sub->deps);
 | 
						||
      sub->deps = NULL;
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
gboolean
 | 
						||
_kqsub_start_watching (kqueue_sub *sub)
 | 
						||
{
 | 
						||
  struct stat st;
 | 
						||
  struct kevent ev;
 | 
						||
 | 
						||
  sub->fd = open (sub->filename, O_KQFLAG | O_CLOEXEC);
 | 
						||
  if (sub->fd == -1)
 | 
						||
      return FALSE;
 | 
						||
 | 
						||
  if (fstat (sub->fd, &st) == -1)
 | 
						||
    {
 | 
						||
      g_warning ("fstat failed for %s: %s", sub->filename, g_strerror (errno));
 | 
						||
      close (sub->fd);
 | 
						||
      sub->fd = -1;
 | 
						||
      return FALSE;
 | 
						||
    }
 | 
						||
 | 
						||
  sub->is_dir = (st.st_mode & S_IFDIR) ? 1 : 0;
 | 
						||
  if (sub->is_dir)
 | 
						||
    {
 | 
						||
      if (sub->deps)
 | 
						||
        dl_free (sub->deps);
 | 
						||
 | 
						||
      sub->deps = dl_listing (sub->filename);
 | 
						||
    }
 | 
						||
 | 
						||
  EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, note_all (), 0, sub);
 | 
						||
  if (kevent (kq_queue, &ev, 1, NULL, 0, NULL) == -1)
 | 
						||
    {
 | 
						||
      g_warning ("Unable to add event for %s: %s", sub->filename, g_strerror (errno));
 | 
						||
      close (sub->fd);
 | 
						||
      sub->fd = -1;
 | 
						||
      return FALSE;
 | 
						||
    }
 | 
						||
 | 
						||
  return TRUE;
 | 
						||
}
 |