Commit 40a84b3d authored by Philip Withnall's avatar Philip Withnall

Merge branch 'wip/lantw/freebsd-kqueue-complex' into 'master'

FreeBSD kqueue file monitor fixes: the complex parts

See merge request GNOME/glib!77
parents dd51b05e e714e1e9
......@@ -33,6 +33,7 @@
#include <string.h>
#include <glib-object.h>
#include <glib/gfileutils.h>
#include <gio/gfilemonitor.h>
#include <gio/glocalfilemonitor.h>
#include <gio/giomodule.h>
......@@ -52,19 +53,44 @@ static int kq_queue = -1;
#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;
typedef struct
/* 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. */
typedef struct _GKqueueFileMonitor
{
GLocalFileMonitor parent_instance;
kqueue_sub *sub;
kqueue_sub *sub_dir;
kqueue_sub *sub_file;
#ifndef O_EVTONLY
GFileMonitor *fallback;
GFile *fbfile;
#endif
} GKqueueFileMonitor;
#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,
......@@ -78,12 +104,23 @@ G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL
#define O_KQFLAG O_EVTONLY
#endif
#define NOTE_ALL (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND|NOTE_ATTRIB|NOTE_RENAME)
static inline unsigned int
note_all (void)
{
unsigned int notes = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME;
#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 (const gchar *, GLocalFileMonitor *, GFileMonitorSource *);
static kqueue_sub *_kqsub_new (gchar *, gchar *, GKqueueFileMonitor *, GFileMonitorSource *);
static void _kqsub_free (kqueue_sub *);
static gboolean _kqsub_cancel (kqueue_sub *);
......@@ -138,11 +175,18 @@ g_kqueue_file_monitor_finalize (GObject *object)
{
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (object);
if (kqueue_monitor->sub)
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);
_kqsub_free (kqueue_monitor->sub);
kqueue_monitor->sub = NULL;
_kqsub_cancel (kqueue_monitor->sub_file);
_kqsub_free (kqueue_monitor->sub_file);
kqueue_monitor->sub_file = NULL;
}
#ifndef O_EVTONLY
......@@ -165,17 +209,51 @@ g_kqueue_file_monitor_start (GLocalFileMonitor *local_monitor,
GFileMonitorSource *source)
{
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (local_monitor);
kqueue_sub *sub;
const gchar *path;
path = filename;
if (path == NULL)
path = dirname;
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))
if (_ke_is_excluded (path_dir))
{
GFile *file = g_file_new_for_path (path);
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",
......@@ -191,13 +269,30 @@ g_kqueue_file_monitor_start (GLocalFileMonitor *local_monitor,
* 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 = _kqsub_new (path, local_monitor, source);
if (sub == NULL)
return;
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 = sub;
if (!_kqsub_start_watching (sub))
_km_add_missing (sub);
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
......@@ -230,59 +325,127 @@ g_kqueue_file_monitor_callback (gint fd, GIOCondition condition, gpointer user_d
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)
{
GFileMonitorEvent mask = 0;
if (ev.filter != EVFILT_VNODE || ev.udata == NULL)
continue;
sub = ev.udata;
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 (ev.fflags & (NOTE_DELETE | NOTE_REVOKE))
{
_kqsub_cancel (sub);
_km_add_missing (sub);
}
if (sub->is_dir && ev.fflags & (NOTE_WRITE | NOTE_EXTEND))
{
_kh_dir_diff (sub);
/* 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 emiting '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)
{
mask = G_FILE_MONITOR_EVENT_DELETED;
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);
}
}
else if (ev.fflags & NOTE_ATTRIB)
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)
{
mask = G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
g_file_monitor_source_handle_event (source,
G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
sub->basename, NULL, NULL, now);
}
else if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND))
#ifdef NOTE_TRUNCATE
if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND | NOTE_TRUNCATE))
#else
if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND))
#endif
{
mask = G_FILE_MONITOR_EVENT_CHANGED;
g_file_monitor_source_handle_event (source,
G_FILE_MONITOR_EVENT_CHANGED,
sub->basename, NULL, NULL, now);
}
else if (ev.fflags & NOTE_RENAME)
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. */
mask = G_FILE_MONITOR_EVENT_DELETED;
g_file_monitor_source_handle_event (source,
G_FILE_MONITOR_EVENT_DELETED,
sub->basename, NULL, NULL, now);
}
else if (ev.fflags & NOTE_REVOKE)
#ifdef NOTE_CLOSE_WRITE
if (ev.fflags & NOTE_CLOSE_WRITE)
{
mask = G_FILE_MONITOR_EVENT_UNMOUNTED;
g_file_monitor_source_handle_event (source,
G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
sub->basename, NULL, NULL, now);
}
#endif
if (mask)
g_file_monitor_source_handle_event (source, mask, NULL, NULL, NULL, now);
/* 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;
}
......@@ -320,14 +483,28 @@ g_kqueue_file_monitor_cancel (GFileMonitor *monitor)
{
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (monitor);
if (kqueue_monitor->sub)
/* 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);
_kqsub_free (kqueue_monitor->sub);
kqueue_monitor->sub = NULL;
_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
else if (kqueue_monitor->fallback)
if (kqueue_monitor->fallback)
{
g_signal_handlers_disconnect_by_func (kqueue_monitor->fallback, _fallback_callback, kqueue_monitor);
g_file_monitor_cancel (kqueue_monitor->fallback);
......@@ -341,12 +518,13 @@ g_kqueue_file_monitor_cancel (GFileMonitor *monitor)
}
static kqueue_sub *
_kqsub_new (const gchar *filename, GLocalFileMonitor *mon, GFileMonitorSource *source)
_kqsub_new (gchar *filename, gchar *basename, GKqueueFileMonitor *mon, GFileMonitorSource *source)
{
kqueue_sub *sub;
sub = g_slice_new (kqueue_sub);
sub->filename = g_strdup (filename);
sub->filename = filename;
sub->basename = basename;
sub->mon = mon;
g_source_ref ((GSource *) source);
sub->source = source;
......@@ -365,19 +543,23 @@ _kqsub_free (kqueue_sub *sub)
g_source_unref ((GSource *) sub->source);
g_free (sub->filename);
g_free (sub->basename);
g_slice_free (kqueue_sub, sub);
}
static gboolean
_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);
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));
......@@ -425,7 +607,7 @@ _kqsub_start_watching (kqueue_sub *sub)
sub->deps = dl_listing (sub->filename);
}
EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_ALL, 0, sub);
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));
......
......@@ -25,6 +25,7 @@
#include <sys/event.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <gio/glocalfile.h>
#include <gio/glocalfilemonitor.h>
#include <gio/gfile.h>
......@@ -38,6 +39,7 @@
typedef struct {
kqueue_sub *sub;
GFileMonitorSource *source;
gboolean handle_deleted;
} handle_ctx;
/**
......@@ -53,6 +55,9 @@ static void
handle_created (void *udata, const char *path, ino_t inode)
{
handle_ctx *ctx = NULL;
gint64 now;
gchar *fullname;
struct stat st;
(void) inode;
ctx = (handle_ctx *) udata;
......@@ -60,8 +65,16 @@ handle_created (void *udata, const char *path, ino_t inode)
g_assert (ctx->sub != NULL);
g_assert (ctx->source != NULL);
now = g_get_monotonic_time ();
g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_CREATED, path,
NULL, NULL, g_get_monotonic_time ());
NULL, NULL, now);
/* Copied from ih_event_callback to report 'CHANGES_DONE_HINT' earlier. */
fullname = g_build_filename (ctx->sub->filename, path, NULL);
if (stat (fullname, &st) != 0 || !S_ISREG (st.st_mode) || st.st_nlink != 1)
g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, path,
NULL, NULL, now);
g_free (fullname);
}
/**
......@@ -84,6 +97,9 @@ handle_deleted (void *udata, const char *path, ino_t inode)
g_assert (ctx->sub != NULL);
g_assert (ctx->source != NULL);
if (!ctx->handle_deleted)
return;
g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_DELETED, path,
NULL, NULL, g_get_monotonic_time ());
}
......@@ -161,7 +177,7 @@ static const traverse_cbs cbs = {
void
_kh_dir_diff (kqueue_sub *sub)
_kh_dir_diff (kqueue_sub *sub, gboolean handle_deleted)
{
dep_list *was;
handle_ctx ctx;
......@@ -169,6 +185,7 @@ _kh_dir_diff (kqueue_sub *sub)
memset (&ctx, 0, sizeof (handle_ctx));
ctx.sub = sub;
ctx.source = sub->source;
ctx.handle_deleted = handle_deleted;
was = sub->deps;
sub->deps = dl_listing (sub->filename);
......
......@@ -28,26 +28,33 @@
#include "dep-list.h"
typedef struct _GKqueueFileMonitor GKqueueFileMonitor;
/**
* kqueue_sub:
* @mon: a pointer to the GKqueueFileMonitor which holds this subscription
* @filename: a name of the file to monitor
* @fd: the associated file descriptor (used by kqueue)
*
* Represents a subscription on a file or directory.
* Represents a subscription on a file or directory. To check whether a
* subscription is active, check the fd field. If fd is not -1, it is an
* active subscription which can emit events from kqueue.
*/
typedef struct
{
GLocalFileMonitor *mon;
GKqueueFileMonitor *mon;
GFileMonitorSource *source;
gchar* filename;
gchar* basename;
int fd;
dep_list* deps;
int is_dir;
} kqueue_sub;
gboolean _kqsub_start_watching (kqueue_sub *sub);
void _kh_dir_diff (kqueue_sub *sub);
void _kh_dir_diff (kqueue_sub *sub, gboolean handle_deleted);
void _km_add_missing (kqueue_sub *sub);
gboolean _km_scan_missing (kqueue_sub *check_this_sub_only);
void _km_remove (kqueue_sub *sub);
#endif /* __KQUEUE_HELPER_H */
......@@ -21,16 +21,13 @@
*******************************************************************************/
#include <glib.h>
#include "glib-private.h"
#include "kqueue-helper.h"
#define SCAN_MISSING_TIME 4 /* 1/4 Hz */
void _kh_file_appeared_cb (kqueue_sub *sub);
static gboolean km_scan_missing (gpointer user_data);
static gboolean km_debug_enabled = FALSE;
#define KM_W if (km_debug_enabled) g_warning
......@@ -40,6 +37,12 @@ G_LOCK_DEFINE_STATIC (missing_lock);
static volatile gboolean scan_missing_running = FALSE;
static gboolean
_km_scan_missing_cb (gpointer user_data)
{
return _km_scan_missing (NULL);
}
/**
* _km_add_missing:
* @sub: a #kqueue_sub
......@@ -63,8 +66,12 @@ _km_add_missing (kqueue_sub *sub)
if (!scan_missing_running)
{
GSource *source;
scan_missing_running = TRUE;
g_timeout_add_seconds (SCAN_MISSING_TIME, km_scan_missing, NULL);
source = g_timeout_source_new_seconds (SCAN_MISSING_TIME);
g_source_set_callback (source, _km_scan_missing_cb, NULL, NULL);
g_source_attach (source, GLIB_PRIVATE_CALL (g_get_worker_context) ());
g_source_unref (source);
}
}
......@@ -77,10 +84,10 @@ _km_add_missing (kqueue_sub *sub)
* Signals that a missing file has finally appeared in the filesystem.
* Emits %G_FILE_MONITOR_EVENT_CREATED.
**/
void
static void
_kh_file_appeared_cb (kqueue_sub *sub)
{
GFile *child;
gint64 now = g_get_monotonic_time ();
g_assert (sub != NULL);
g_assert (sub->filename);
......@@ -88,18 +95,14 @@ _kh_file_appeared_cb (kqueue_sub *sub)
if (!g_file_test (sub->filename, G_FILE_TEST_EXISTS))
return;
child = g_file_new_for_path (sub->filename);
g_file_monitor_emit_event (G_FILE_MONITOR (sub->mon),
child,
NULL,
G_FILE_MONITOR_EVENT_CREATED);
g_object_unref (child);
g_file_monitor_source_handle_event (sub->source, G_FILE_MONITOR_EVENT_CREATED,
sub->basename, NULL, NULL, now);
g_file_monitor_source_handle_event (sub->source, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
sub->basename, NULL, NULL, now);
}
/**
* km_scan_missing:
* _km_scan_missing:
* @user_data: unused
*
* The core missing files watching routine.
......@@ -110,8 +113,8 @@ _kh_file_appeared_cb (kqueue_sub *sub)
*
* Returns: %FALSE if no missing files left, %TRUE otherwise.
**/
static gboolean
km_scan_missing (gpointer user_data)
gboolean
_km_scan_missing (kqueue_sub *check_this_sub_only)
{
GSList *head;
GSList *not_missing = NULL;
......@@ -128,10 +131,14 @@ km_scan_missing (gpointer user_data)
g_assert (sub != NULL);
g_assert (sub->filename != NULL);
if (check_this_sub_only != NULL && sub != check_this_sub_only)
continue;
if (_kqsub_start_watching (sub))
{
KM_W ("file %s now exists, starting watching", sub->filename);
_kh_file_appeared_cb (sub);
if (check_this_sub_only == NULL)
_kh_file_appeared_cb (sub);
not_missing = g_slist_prepend (not_missing, head);
}
}
......
......@@ -9,12 +9,25 @@
* the tests, e.g. the length of timeouts
*/
typedef enum {
NONE = 0,
INOTIFY = (1 << 1),
KQUEUE = (1 << 2)
} Environment;
typedef struct
{
gint event_type;
gchar *file;
gchar *other_file;
gint step;
/* Since different file monitor implementation has different capabilities,
* we cannot expect all implementations to report all kind of events without
* any loss. This 'optional' field is a bit mask used to mark events which
* may be lost under specific platforms.
*/
Environment optional;
} RecordedEvent;
static void
......@@ -68,41 +81,158 @@ output_events (GList *list)
/* a placeholder for temp file names we don't want to compare */
static const gchar DONT_CARE[] = "";
static void
check_expected_event (gint i,
RecordedEvent *e1,
RecordedEvent *e2)
static Environment
get_environment (GFileMonitor *monitor)
{
g_assert_cmpint (e1->step, ==, e2->step);
if (e1->step < 0)
return;
g_assert_cmpint (e1->event_type, ==, e2->event_type);
if (e1->file != DONT_CARE)
g_assert_cmpstr (e1->file, ==, e2->file);
if (e1->other_file != DONT_CARE)
g_assert_cmpstr (e1->other_file, ==, e2->other_file);
if (g_str_equal (G_OBJECT_TYPE_NAME (monitor), "GInotifyFileMonitor"))
return INOTIFY;
if (g_str_equal (G_OBJECT_TYPE_NAME (monitor), "GKqueueFileMonitor"))
return KQUEUE;
return NONE;
}
static void
check_expected_events (RecordedEvent *expected,
gsize n_expected,
GList *recorded)
GList *recorded,
Environment env)
{
gint i;
gint i, li;
GList *l;
g_assert_cmpint (n_expected, ==, g_list_length (recorded));
for (i = 0, l = recorded; i < n_expected; i++, l = l->next)
for (i = 0, li = 0, l = recorded; i < n_expected && l != NULL;)
{
RecordedEvent *e1 = &expected[i];
RecordedEvent *e2 = (RecordedEvent *)l->data;
RecordedEvent *e2 = l->data;
gboolean mismatch = TRUE;
gboolean l_extra_step = FALSE;
check_expected_event (i, e1, e2);
do
{
gboolean ignore_other_file = FALSE;
if (e1->step != e2->step)
break;
/* Kqueue isn't good at detecting file renaming, so
* G_FILE_MONITOR_WATCH_MOVES is mostly useless there. */
if (e1->event_type != e2->event_type && env & KQUEUE)
{
/* It is possible for kqueue file monitor to emit 'RENAMED' event,
* but most of the time it is reported as a 'DELETED' event and
* a 'CREATED' event. */
if (e1->event_type == G_FILE_MONITOR_EVENT_RENAMED)
{
RecordedEvent *e2_next;
if (l->next == NULL)
break;
e2_next = l->next->data;
if (e2->event_type != G_FILE_MONITOR_EVENT_DELETED)
break;