2012-11-14 12:57:42 +01:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
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"
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#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>
|
|
|
|
|
|
2023-02-19 16:22:22 +01:00
|
|
|
|
#ifndef O_CLOEXEC
|
|
|
|
|
#define O_CLOEXEC 0
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#include <glib-object.h>
|
2018-06-03 13:35:48 +02:00
|
|
|
|
#include <glib/gfileutils.h>
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#include <gio/gfilemonitor.h>
|
|
|
|
|
#include <gio/glocalfilemonitor.h>
|
|
|
|
|
#include <gio/giomodule.h>
|
2012-11-14 12:57:42 +01:00
|
|
|
|
#include <gio/gpollfilemonitor.h>
|
|
|
|
|
#include <gio/gfile.h>
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#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))
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
/* 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
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
typedef GLocalFileMonitorClass GKqueueFileMonitorClass;
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
/* 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. */
|
2018-08-31 16:52:29 +02:00
|
|
|
|
struct _GKqueueFileMonitor
|
2012-11-14 12:57:42 +01:00
|
|
|
|
{
|
|
|
|
|
GLocalFileMonitor parent_instance;
|
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
kqueue_sub *sub_dir;
|
|
|
|
|
kqueue_sub *sub_file;
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#ifndef O_EVTONLY
|
2012-11-14 12:57:42 +01:00
|
|
|
|
GFileMonitor *fallback;
|
|
|
|
|
GFile *fbfile;
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#endif
|
2018-08-31 16:52:29 +02:00
|
|
|
|
};
|
2018-02-20 17:57:00 +01:00
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
#ifdef __clang__
|
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
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
|
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
static inline unsigned int
|
|
|
|
|
note_all (void)
|
|
|
|
|
{
|
2019-11-11 18:59:31 +01:00
|
|
|
|
unsigned int notes = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE;
|
2018-06-03 13:35:48 +02:00
|
|
|
|
#ifdef NOTE_TRUNCATE
|
|
|
|
|
notes |= NOTE_TRUNCATE;
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef NOTE_CLOSE_WRITE
|
|
|
|
|
notes |= NOTE_CLOSE_WRITE;
|
|
|
|
|
#endif
|
|
|
|
|
return notes;
|
|
|
|
|
}
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
|
|
|
|
static gboolean g_kqueue_file_monitor_cancel (GFileMonitor* monitor);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
static gboolean g_kqueue_file_monitor_is_supported (void);
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
static kqueue_sub *_kqsub_new (gchar *, gchar *, GKqueueFileMonitor *, GFileMonitorSource *);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
static void _kqsub_free (kqueue_sub *);
|
2019-11-17 15:39:07 +01:00
|
|
|
|
static void _kqsub_cancel (kqueue_sub *);
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#ifndef O_EVTONLY
|
2012-11-14 12:57:42 +01:00
|
|
|
|
static void
|
|
|
|
|
_fallback_callback (GFileMonitor *unused,
|
|
|
|
|
GFile *first,
|
|
|
|
|
GFile *second,
|
|
|
|
|
GFileMonitorEvent event,
|
|
|
|
|
gpointer udata)
|
|
|
|
|
{
|
2015-01-17 00:51:34 +01:00
|
|
|
|
GKqueueFileMonitor *kq_mon = G_KQUEUE_FILE_MONITOR (udata);
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
g_file_monitor_emit_event (G_FILE_MONITOR (kq_mon), first, second, event);
|
2012-11-14 12:57:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
/*
|
|
|
|
|
* _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.
|
|
|
|
|
**/
|
2018-05-24 17:37:01 +02:00
|
|
|
|
static gboolean
|
2018-02-20 17:57:00 +01:00
|
|
|
|
_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);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-03 06:15:36 +02:00
|
|
|
|
if (mount != NULL && (g_str_has_prefix (full_path, "/media/") || g_str_has_prefix (full_path, "/run/media/")))
|
2018-02-20 17:57:00 +01:00
|
|
|
|
{
|
|
|
|
|
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 */
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_kqueue_file_monitor_finalize (GObject *object)
|
|
|
|
|
{
|
|
|
|
|
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (object);
|
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
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)
|
2012-11-14 12:57:42 +01:00
|
|
|
|
{
|
2018-06-03 13:35:48 +02:00
|
|
|
|
_kqsub_cancel (kqueue_monitor->sub_file);
|
|
|
|
|
_kqsub_free (kqueue_monitor->sub_file);
|
|
|
|
|
kqueue_monitor->sub_file = NULL;
|
2012-11-14 12:57:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#ifndef O_EVTONLY
|
2012-11-14 12:57:42 +01:00
|
|
|
|
if (kqueue_monitor->fallback)
|
|
|
|
|
g_object_unref (kqueue_monitor->fallback);
|
|
|
|
|
|
|
|
|
|
if (kqueue_monitor->fbfile)
|
|
|
|
|
g_object_unref (kqueue_monitor->fbfile);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#endif
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
|
|
|
|
if (G_OBJECT_CLASS (g_kqueue_file_monitor_parent_class)->finalize)
|
|
|
|
|
(*G_OBJECT_CLASS (g_kqueue_file_monitor_parent_class)->finalize) (object);
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-17 00:51:34 +01:00
|
|
|
|
static void
|
|
|
|
|
g_kqueue_file_monitor_start (GLocalFileMonitor *local_monitor,
|
|
|
|
|
const gchar *dirname,
|
|
|
|
|
const gchar *basename,
|
|
|
|
|
const gchar *filename,
|
|
|
|
|
GFileMonitorSource *source)
|
2012-11-14 12:57:42 +01:00
|
|
|
|
{
|
2015-01-17 00:51:34 +01:00
|
|
|
|
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (local_monitor);
|
2018-06-03 13:35:48 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#ifndef O_EVTONLY
|
2018-06-03 13:35:48 +02:00
|
|
|
|
if (_ke_is_excluded (path_dir))
|
2018-02-20 17:57:00 +01:00
|
|
|
|
{
|
2018-06-03 13:35:48 +02:00
|
|
|
|
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);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
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
|
|
|
|
|
|
2012-11-14 12:57:42 +01:00
|
|
|
|
/* For a directory monitor, create a subscription object anyway.
|
|
|
|
|
* It will be used for directory diff calculation routines.
|
|
|
|
|
* Wait, directory diff in a GKqueueFileMonitor?
|
2019-08-23 23:45:16 +02:00
|
|
|
|
* Yes, it is. When a file monitor is started on a non-existent
|
2012-11-14 12:57:42 +01:00
|
|
|
|
* 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. */
|
2018-06-03 13:35:48 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
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);
|
2012-11-14 12:57:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2015-01-17 00:51:34 +01:00
|
|
|
|
local_file_monitor_class->start = g_kqueue_file_monitor_start;
|
|
|
|
|
local_file_monitor_class->mount_notify = TRUE; /* TODO: ??? */
|
2012-11-14 12:57:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_kqueue_file_monitor_init (GKqueueFileMonitor *monitor)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
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));
|
2018-06-03 13:35:48 +02:00
|
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
while (kevent(fd, NULL, 0, &ev, 1, &ts) > 0)
|
|
|
|
|
{
|
|
|
|
|
if (ev.filter != EVFILT_VNODE || ev.udata == NULL)
|
|
|
|
|
continue;
|
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
sub = ev.udata;
|
2018-02-20 17:57:00 +01:00
|
|
|
|
source = sub->source;
|
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
if (ev.flags & EV_ERROR)
|
|
|
|
|
ev.fflags = NOTE_REVOKE;
|
|
|
|
|
|
|
|
|
|
if (sub->is_dir && ev.fflags & (NOTE_WRITE | NOTE_EXTEND))
|
|
|
|
|
{
|
2018-06-03 13:35:48 +02:00
|
|
|
|
/* 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'
|
2020-06-12 15:02:30 +02:00
|
|
|
|
* argument to prevent _km_scan_missing from emitting 'CREATED'
|
2018-06-03 13:35:48 +02:00
|
|
|
|
* 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
|
2018-02-20 17:57:00 +01:00
|
|
|
|
ev.fflags &= ~(NOTE_WRITE | NOTE_EXTEND);
|
2018-06-03 13:35:48 +02:00
|
|
|
|
#endif
|
2018-02-20 17:57:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
/* 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'. */
|
2018-02-20 17:57:00 +01:00
|
|
|
|
if (ev.fflags & NOTE_DELETE)
|
|
|
|
|
{
|
2018-06-03 13:35:48 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
2018-02-20 17:57:00 +01:00
|
|
|
|
}
|
2018-06-03 13:35:48 +02:00
|
|
|
|
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)
|
2018-02-20 17:57:00 +01:00
|
|
|
|
{
|
2018-06-03 13:35:48 +02:00
|
|
|
|
g_file_monitor_source_handle_event (source,
|
|
|
|
|
G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
|
|
|
|
|
sub->basename, NULL, NULL, now);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
}
|
2018-06-03 13:35:48 +02:00
|
|
|
|
#ifdef NOTE_TRUNCATE
|
|
|
|
|
if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND | NOTE_TRUNCATE))
|
|
|
|
|
#else
|
|
|
|
|
if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND))
|
|
|
|
|
#endif
|
2018-02-20 17:57:00 +01:00
|
|
|
|
{
|
2018-06-03 13:35:48 +02:00
|
|
|
|
g_file_monitor_source_handle_event (source,
|
|
|
|
|
G_FILE_MONITOR_EVENT_CHANGED,
|
|
|
|
|
sub->basename, NULL, NULL, now);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
}
|
2018-06-03 13:35:48 +02:00
|
|
|
|
if (ev.fflags & NOTE_RENAME)
|
2018-02-20 17:57:00 +01:00
|
|
|
|
{
|
|
|
|
|
/* 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. */
|
2018-06-03 13:35:48 +02:00
|
|
|
|
g_file_monitor_source_handle_event (source,
|
|
|
|
|
G_FILE_MONITOR_EVENT_DELETED,
|
|
|
|
|
sub->basename, NULL, NULL, now);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
}
|
2018-06-03 13:35:48 +02:00
|
|
|
|
#ifdef NOTE_CLOSE_WRITE
|
|
|
|
|
if (ev.fflags & NOTE_CLOSE_WRITE)
|
2018-02-20 17:57:00 +01:00
|
|
|
|
{
|
2018-06-03 13:35:48 +02:00
|
|
|
|
g_file_monitor_source_handle_event (source,
|
|
|
|
|
G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
|
|
|
|
|
sub->basename, NULL, NULL, now);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
}
|
2018-06-03 13:35:48 +02:00
|
|
|
|
#endif
|
2018-02-20 17:57:00 +01:00
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
/* 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);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
G_UNLOCK (kq_lock);
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-14 12:57:42 +01:00
|
|
|
|
static gboolean
|
|
|
|
|
g_kqueue_file_monitor_cancel (GFileMonitor *monitor)
|
|
|
|
|
{
|
|
|
|
|
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (monitor);
|
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
/* 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)
|
2012-11-14 12:57:42 +01:00
|
|
|
|
{
|
2018-06-03 13:35:48 +02:00
|
|
|
|
_kqsub_cancel (kqueue_monitor->sub_dir);
|
|
|
|
|
_kqsub_free (kqueue_monitor->sub_dir);
|
|
|
|
|
kqueue_monitor->sub_dir = NULL;
|
2012-11-14 12:57:42 +01:00
|
|
|
|
}
|
2018-06-03 13:35:48 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#ifndef O_EVTONLY
|
2018-06-03 13:35:48 +02:00
|
|
|
|
if (kqueue_monitor->fallback)
|
2015-03-26 21:35:06 +01:00
|
|
|
|
{
|
|
|
|
|
g_signal_handlers_disconnect_by_func (kqueue_monitor->fallback, _fallback_callback, kqueue_monitor);
|
|
|
|
|
g_file_monitor_cancel (kqueue_monitor->fallback);
|
|
|
|
|
}
|
2018-02-20 17:57:00 +01:00
|
|
|
|
#endif
|
2012-11-14 12:57:42 +01:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2018-02-20 17:57:00 +01:00
|
|
|
|
|
|
|
|
|
static kqueue_sub *
|
2018-06-03 13:35:48 +02:00
|
|
|
|
_kqsub_new (gchar *filename, gchar *basename, GKqueueFileMonitor *mon, GFileMonitorSource *source)
|
2018-02-20 17:57:00 +01:00
|
|
|
|
{
|
|
|
|
|
kqueue_sub *sub;
|
|
|
|
|
|
|
|
|
|
sub = g_slice_new (kqueue_sub);
|
2018-06-03 13:35:48 +02:00
|
|
|
|
sub->filename = filename;
|
|
|
|
|
sub->basename = basename;
|
2018-02-20 17:57:00 +01:00
|
|
|
|
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);
|
2018-06-03 13:35:48 +02:00
|
|
|
|
g_free (sub->basename);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
g_slice_free (kqueue_sub, sub);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-17 15:39:07 +01:00
|
|
|
|
static void
|
2018-02-20 17:57:00 +01:00
|
|
|
|
_kqsub_cancel (kqueue_sub *sub)
|
|
|
|
|
{
|
2018-06-03 13:35:48 +02:00
|
|
|
|
/* 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. */
|
|
|
|
|
|
2018-02-20 17:57:00 +01:00
|
|
|
|
struct kevent ev;
|
|
|
|
|
|
2018-04-11 17:58:07 +02:00
|
|
|
|
/* Remove the event and close the file descriptor to automatically
|
|
|
|
|
* delete pending events. */
|
|
|
|
|
if (sub->fd != -1)
|
2018-02-20 17:57:00 +01:00
|
|
|
|
{
|
2018-06-03 13:35:48 +02:00
|
|
|
|
EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_DELETE, note_all (), 0, sub);
|
2018-04-11 17:58:07 +02:00
|
|
|
|
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;
|
2018-02-20 17:57:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_km_remove (sub);
|
|
|
|
|
|
2018-04-11 17:58:07 +02:00
|
|
|
|
if (sub->deps)
|
2018-02-20 17:57:00 +01:00
|
|
|
|
{
|
2018-04-11 17:58:07 +02:00
|
|
|
|
dl_free (sub->deps);
|
|
|
|
|
sub->deps = NULL;
|
2018-02-20 17:57:00 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gboolean
|
|
|
|
|
_kqsub_start_watching (kqueue_sub *sub)
|
|
|
|
|
{
|
|
|
|
|
struct stat st;
|
|
|
|
|
struct kevent ev;
|
|
|
|
|
|
2023-02-19 16:22:22 +01:00
|
|
|
|
sub->fd = open (sub->filename, O_KQFLAG | O_CLOEXEC);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-03 13:35:48 +02:00
|
|
|
|
EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, note_all (), 0, sub);
|
2018-02-20 17:57:00 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|