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

FreeBSD kqueue file monitor fixes: the complex parts

See merge request GNOME/glib!77
This commit is contained in:
Philip Withnall 2018-06-19 11:19:43 +00:00
commit 40a84b3d1c
5 changed files with 546 additions and 176 deletions

View File

@ -33,6 +33,7 @@
#include <string.h> #include <string.h>
#include <glib-object.h> #include <glib-object.h>
#include <glib/gfileutils.h>
#include <gio/gfilemonitor.h> #include <gio/gfilemonitor.h>
#include <gio/glocalfilemonitor.h> #include <gio/glocalfilemonitor.h>
#include <gio/giomodule.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), \ #define G_KQUEUE_FILE_MONITOR(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
G_TYPE_KQUEUE_FILE_MONITOR, GKqueueFileMonitor)) 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 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; GLocalFileMonitor parent_instance;
kqueue_sub *sub; kqueue_sub *sub_dir;
kqueue_sub *sub_file;
#ifndef O_EVTONLY #ifndef O_EVTONLY
GFileMonitor *fallback; GFileMonitor *fallback;
GFile *fbfile; GFile *fbfile;
#endif #endif
} GKqueueFileMonitor; } GKqueueFileMonitor;
#ifdef __clang__
#pragma clang diagnostic pop
#endif
GType g_kqueue_file_monitor_get_type (void); GType g_kqueue_file_monitor_get_type (void);
G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL_FILE_MONITOR, 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_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 #define O_KQFLAG O_EVTONLY
#endif #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_cancel (GFileMonitor* monitor);
static gboolean g_kqueue_file_monitor_is_supported (void); 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 void _kqsub_free (kqueue_sub *);
static gboolean _kqsub_cancel (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); GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (object);
if (kqueue_monitor->sub) if (kqueue_monitor->sub_dir)
{ {
_kqsub_cancel (kqueue_monitor->sub); _kqsub_cancel (kqueue_monitor->sub_dir);
_kqsub_free (kqueue_monitor->sub); _kqsub_free (kqueue_monitor->sub_dir);
kqueue_monitor->sub = NULL; 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 #ifndef O_EVTONLY
@ -165,17 +209,51 @@ g_kqueue_file_monitor_start (GLocalFileMonitor *local_monitor,
GFileMonitorSource *source) GFileMonitorSource *source)
{ {
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (local_monitor); GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (local_monitor);
kqueue_sub *sub; kqueue_sub *sub_dir = NULL, *sub_file = NULL;
const gchar *path; gchar *path_dir, *path_file, *file_basename;
path = filename; /* There are three possible cases here:
if (path == NULL) *
path = dirname; * 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 #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->fbfile = file;
kqueue_monitor->fallback = _g_poll_file_monitor_new (file); kqueue_monitor->fallback = _g_poll_file_monitor_new (file);
g_signal_connect (kqueue_monitor->fallback, "changed", 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 * file, GIO uses a GKqueueFileMonitor object for that. If a directory
* will be created under that path, GKqueueFileMonitor will have to * will be created under that path, GKqueueFileMonitor will have to
* handle the directory notifications. */ * handle the directory notifications. */
sub = _kqsub_new (path, local_monitor, source); sub_dir = _kqsub_new (g_steal_pointer (&path_dir), NULL,
if (sub == NULL) kqueue_monitor, source);
return; if (!_kqsub_start_watching (sub_dir))
_km_add_missing (sub_dir);
kqueue_monitor->sub = sub; /* Unlike GInotifyFileMonitor, which always uses a directory monitor
if (!_kqsub_start_watching (sub)) * regardless of the type of the file being monitored, kqueue doesn't
_km_add_missing (sub); * 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 static void
@ -230,59 +325,127 @@ g_kqueue_file_monitor_callback (gint fd, GIOCondition condition, gpointer user_d
struct timespec ts; struct timespec ts;
memset (&ts, 0, sizeof(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) while (kevent(fd, NULL, 0, &ev, 1, &ts) > 0)
{ {
GFileMonitorEvent mask = 0;
if (ev.filter != EVFILT_VNODE || ev.udata == NULL) if (ev.filter != EVFILT_VNODE || ev.udata == NULL)
continue; continue;
sub = ev.udata; sub = ev.udata;
source = sub->source; 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) if (ev.flags & EV_ERROR)
ev.fflags = NOTE_REVOKE; 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)) 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); 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) 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 theres apparently no way to get the new name of the /* Since theres apparently no way to get the new name of the
* file out of kqueue(), all we can do is say that this one has * file out of kqueue(), all we can do is say that this one has
* been deleted. */ * 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) /* Handle the case when a file is created again shortly after it was
g_file_monitor_source_handle_event (source, mask, NULL, NULL, NULL, now); * 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; return TRUE;
} }
@ -320,14 +483,28 @@ g_kqueue_file_monitor_cancel (GFileMonitor *monitor)
{ {
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (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_cancel (kqueue_monitor->sub_dir);
_kqsub_free (kqueue_monitor->sub); _kqsub_free (kqueue_monitor->sub_dir);
kqueue_monitor->sub = NULL; 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 #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_signal_handlers_disconnect_by_func (kqueue_monitor->fallback, _fallback_callback, kqueue_monitor);
g_file_monitor_cancel (kqueue_monitor->fallback); g_file_monitor_cancel (kqueue_monitor->fallback);
@ -341,12 +518,13 @@ g_kqueue_file_monitor_cancel (GFileMonitor *monitor)
} }
static kqueue_sub * 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; kqueue_sub *sub;
sub = g_slice_new (kqueue_sub); sub = g_slice_new (kqueue_sub);
sub->filename = g_strdup (filename); sub->filename = filename;
sub->basename = basename;
sub->mon = mon; sub->mon = mon;
g_source_ref ((GSource *) source); g_source_ref ((GSource *) source);
sub->source = source; sub->source = source;
@ -365,19 +543,23 @@ _kqsub_free (kqueue_sub *sub)
g_source_unref ((GSource *) sub->source); g_source_unref ((GSource *) sub->source);
g_free (sub->filename); g_free (sub->filename);
g_free (sub->basename);
g_slice_free (kqueue_sub, sub); g_slice_free (kqueue_sub, sub);
} }
static gboolean static gboolean
_kqsub_cancel (kqueue_sub *sub) _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; struct kevent ev;
/* Remove the event and close the file descriptor to automatically /* Remove the event and close the file descriptor to automatically
* delete pending events. */ * delete pending events. */
if (sub->fd != -1) 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) if (kevent (kq_queue, &ev, 1, NULL, 0, NULL) == -1)
{ {
g_warning ("Unable to remove event for %s: %s", sub->filename, g_strerror (errno)); 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); 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) if (kevent (kq_queue, &ev, 1, NULL, 0, NULL) == -1)
{ {
g_warning ("Unable to add event for %s: %s", sub->filename, g_strerror (errno)); g_warning ("Unable to add event for %s: %s", sub->filename, g_strerror (errno));

View File

@ -25,6 +25,7 @@
#include <sys/event.h> #include <sys/event.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h>
#include <gio/glocalfile.h> #include <gio/glocalfile.h>
#include <gio/glocalfilemonitor.h> #include <gio/glocalfilemonitor.h>
#include <gio/gfile.h> #include <gio/gfile.h>
@ -38,6 +39,7 @@
typedef struct { typedef struct {
kqueue_sub *sub; kqueue_sub *sub;
GFileMonitorSource *source; GFileMonitorSource *source;
gboolean handle_deleted;
} handle_ctx; } handle_ctx;
/** /**
@ -53,6 +55,9 @@ static void
handle_created (void *udata, const char *path, ino_t inode) handle_created (void *udata, const char *path, ino_t inode)
{ {
handle_ctx *ctx = NULL; handle_ctx *ctx = NULL;
gint64 now;
gchar *fullname;
struct stat st;
(void) inode; (void) inode;
ctx = (handle_ctx *) udata; 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->sub != NULL);
g_assert (ctx->source != 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, 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->sub != NULL);
g_assert (ctx->source != 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, g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_DELETED, path,
NULL, NULL, g_get_monotonic_time ()); NULL, NULL, g_get_monotonic_time ());
} }
@ -161,7 +177,7 @@ static const traverse_cbs cbs = {
void void
_kh_dir_diff (kqueue_sub *sub) _kh_dir_diff (kqueue_sub *sub, gboolean handle_deleted)
{ {
dep_list *was; dep_list *was;
handle_ctx ctx; handle_ctx ctx;
@ -169,6 +185,7 @@ _kh_dir_diff (kqueue_sub *sub)
memset (&ctx, 0, sizeof (handle_ctx)); memset (&ctx, 0, sizeof (handle_ctx));
ctx.sub = sub; ctx.sub = sub;
ctx.source = sub->source; ctx.source = sub->source;
ctx.handle_deleted = handle_deleted;
was = sub->deps; was = sub->deps;
sub->deps = dl_listing (sub->filename); sub->deps = dl_listing (sub->filename);

View File

@ -28,26 +28,33 @@
#include "dep-list.h" #include "dep-list.h"
typedef struct _GKqueueFileMonitor GKqueueFileMonitor;
/** /**
* kqueue_sub: * kqueue_sub:
* @mon: a pointer to the GKqueueFileMonitor which holds this subscription
* @filename: a name of the file to monitor * @filename: a name of the file to monitor
* @fd: the associated file descriptor (used by kqueue) * @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 typedef struct
{ {
GLocalFileMonitor *mon; GKqueueFileMonitor *mon;
GFileMonitorSource *source; GFileMonitorSource *source;
gchar* filename; gchar* filename;
gchar* basename;
int fd; int fd;
dep_list* deps; dep_list* deps;
int is_dir; int is_dir;
} kqueue_sub; } kqueue_sub;
gboolean _kqsub_start_watching (kqueue_sub *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); void _km_add_missing (kqueue_sub *sub);
gboolean _km_scan_missing (kqueue_sub *check_this_sub_only);
void _km_remove (kqueue_sub *sub); void _km_remove (kqueue_sub *sub);
#endif /* __KQUEUE_HELPER_H */ #endif /* __KQUEUE_HELPER_H */

View File

@ -21,16 +21,13 @@
*******************************************************************************/ *******************************************************************************/
#include <glib.h> #include <glib.h>
#include "glib-private.h"
#include "kqueue-helper.h" #include "kqueue-helper.h"
#define SCAN_MISSING_TIME 4 /* 1/4 Hz */ #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; static gboolean km_debug_enabled = FALSE;
#define KM_W if (km_debug_enabled) g_warning #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 volatile gboolean scan_missing_running = FALSE;
static gboolean
_km_scan_missing_cb (gpointer user_data)
{
return _km_scan_missing (NULL);
}
/** /**
* _km_add_missing: * _km_add_missing:
* @sub: a #kqueue_sub * @sub: a #kqueue_sub
@ -63,8 +66,12 @@ _km_add_missing (kqueue_sub *sub)
if (!scan_missing_running) if (!scan_missing_running)
{ {
GSource *source;
scan_missing_running = TRUE; 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. * Signals that a missing file has finally appeared in the filesystem.
* Emits %G_FILE_MONITOR_EVENT_CREATED. * Emits %G_FILE_MONITOR_EVENT_CREATED.
**/ **/
void static void
_kh_file_appeared_cb (kqueue_sub *sub) _kh_file_appeared_cb (kqueue_sub *sub)
{ {
GFile *child; gint64 now = g_get_monotonic_time ();
g_assert (sub != NULL); g_assert (sub != NULL);
g_assert (sub->filename); 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)) if (!g_file_test (sub->filename, G_FILE_TEST_EXISTS))
return; return;
child = g_file_new_for_path (sub->filename); g_file_monitor_source_handle_event (sub->source, G_FILE_MONITOR_EVENT_CREATED,
sub->basename, NULL, NULL, now);
g_file_monitor_emit_event (G_FILE_MONITOR (sub->mon), g_file_monitor_source_handle_event (sub->source, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
child, sub->basename, NULL, NULL, now);
NULL,
G_FILE_MONITOR_EVENT_CREATED);
g_object_unref (child);
} }
/** /**
* km_scan_missing: * _km_scan_missing:
* @user_data: unused * @user_data: unused
* *
* The core missing files watching routine. * 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. * Returns: %FALSE if no missing files left, %TRUE otherwise.
**/ **/
static gboolean gboolean
km_scan_missing (gpointer user_data) _km_scan_missing (kqueue_sub *check_this_sub_only)
{ {
GSList *head; GSList *head;
GSList *not_missing = NULL; GSList *not_missing = NULL;
@ -128,10 +131,14 @@ km_scan_missing (gpointer user_data)
g_assert (sub != NULL); g_assert (sub != NULL);
g_assert (sub->filename != NULL); g_assert (sub->filename != NULL);
if (check_this_sub_only != NULL && sub != check_this_sub_only)
continue;
if (_kqsub_start_watching (sub)) if (_kqsub_start_watching (sub))
{ {
KM_W ("file %s now exists, starting watching", sub->filename); 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); not_missing = g_slist_prepend (not_missing, head);
} }
} }

View File

@ -9,12 +9,25 @@
* the tests, e.g. the length of timeouts * the tests, e.g. the length of timeouts
*/ */
typedef enum {
NONE = 0,
INOTIFY = (1 << 1),
KQUEUE = (1 << 2)
} Environment;
typedef struct typedef struct
{ {
gint event_type; gint event_type;
gchar *file; gchar *file;
gchar *other_file; gchar *other_file;
gint step; 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; } RecordedEvent;
static void static void
@ -68,41 +81,158 @@ output_events (GList *list)
/* a placeholder for temp file names we don't want to compare */ /* a placeholder for temp file names we don't want to compare */
static const gchar DONT_CARE[] = ""; static const gchar DONT_CARE[] = "";
static void static Environment
check_expected_event (gint i, get_environment (GFileMonitor *monitor)
RecordedEvent *e1,
RecordedEvent *e2)
{ {
g_assert_cmpint (e1->step, ==, e2->step); if (g_str_equal (G_OBJECT_TYPE_NAME (monitor), "GInotifyFileMonitor"))
if (e1->step < 0) return INOTIFY;
return; if (g_str_equal (G_OBJECT_TYPE_NAME (monitor), "GKqueueFileMonitor"))
return KQUEUE;
g_assert_cmpint (e1->event_type, ==, e2->event_type); return NONE;
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);
} }
static void static void
check_expected_events (RecordedEvent *expected, check_expected_events (RecordedEvent *expected,
gsize n_expected, gsize n_expected,
GList *recorded) GList *recorded,
Environment env)
{ {
gint i; gint i, li;
GList *l; GList *l;
g_assert_cmpint (n_expected, ==, g_list_length (recorded)); for (i = 0, li = 0, l = recorded; i < n_expected && l != NULL;)
for (i = 0, l = recorded; i < n_expected; i++, l = l->next)
{ {
RecordedEvent *e1 = &expected[i]; 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;
if (e2_next->event_type != G_FILE_MONITOR_EVENT_CREATED)
break;
if (e1->step != e2_next->step)
break;
if (e1->file != DONT_CARE &&
(g_strcmp0 (e1->file, e2->file) != 0 ||
e2->other_file != NULL))
break;
if (e1->other_file != DONT_CARE &&
(g_strcmp0 (e1->other_file, e2_next->file) != 0 ||
e2_next->other_file != NULL))
break;
l_extra_step = TRUE;
mismatch = FALSE;
break;
}
/* Kqueue won't report 'MOVED_IN' and 'MOVED_OUT' events. We set
* 'ignore_other_file' here to let the following code know that
* 'other_file' may not match. */
else if (e1->event_type == G_FILE_MONITOR_EVENT_MOVED_IN)
{
if (e2->event_type != G_FILE_MONITOR_EVENT_CREATED)
break;
ignore_other_file = TRUE;
}
else if (e1->event_type == G_FILE_MONITOR_EVENT_MOVED_OUT)
{
if (e2->event_type != G_FILE_MONITOR_EVENT_DELETED)
break;
ignore_other_file = TRUE;
}
else
break;
}
if (e1->file != DONT_CARE &&
g_strcmp0 (e1->file, e2->file) != 0)
break;
if (e1->other_file != DONT_CARE && !ignore_other_file &&
g_strcmp0 (e1->other_file, e2->other_file) != 0)
break;
mismatch = FALSE;
}
while (0);
if (mismatch)
{
/* Sometimes the emission of 'CHANGES_DONE_HINT' may be late because
* it depends on the ability of file monitor implementation to report
* 'CHANGES_DONE_HINT' itself. If the file monitor implementation
* doesn't report 'CHANGES_DONE_HINT' itself, it may be emitted by
* GLocalFileMonitor after a few seconds, which causes the event to
* mix with results from different steps. Since 'CHANGES_DONE_HINT'
* is just a hint, we don't require it to be reliable and we simply
* ignore unexpected 'CHANGES_DONE_HINT' events here. */
if (e1->event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT &&
e2->event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
{
g_test_message ("Event CHANGES_DONE_HINT ignored at "
"expected index %d, recorded index %d", i, li);
li++, l = l->next;
continue;
}
/* If an event is marked as optional in the current environment and
* the event doesn't match, it means the expected event has lost. */
else if (env & e1->optional)
{
g_test_message ("Event %d at expected index %d skipped because "
"it is marked as optional", e1->event_type, i);
i++;
continue;
}
/* Run above checks under g_assert_* again to provide more useful
* error messages. */
else
{
g_assert_cmpint (e1->step, ==, e2->step);
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);
g_assert_not_reached ();
}
}
i++, li++, l = l->next;
if (l_extra_step)
li++, l = l->next;
} }
g_assert_cmpint (i, ==, n_expected);
g_assert_cmpint (li, ==, g_list_length (recorded));
} }
static void static void
@ -180,15 +310,15 @@ atomic_replace_step (gpointer user_data)
/* this is the output we expect from the above steps */ /* this is the output we expect from the above steps */
static RecordedEvent atomic_replace_output[] = { static RecordedEvent atomic_replace_output[] = {
{ -1, NULL, NULL, 0 }, { -1, NULL, NULL, 0, NONE },
{ G_FILE_MONITOR_EVENT_CREATED, "atomic_replace_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_CREATED, "atomic_replace_file", NULL, -1, NONE },
{ G_FILE_MONITOR_EVENT_CHANGED, "atomic_replace_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGED, "atomic_replace_file", NULL, -1, KQUEUE },
{ G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "atomic_replace_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "atomic_replace_file", NULL, -1, KQUEUE },
{ -1, NULL, NULL, 1 }, { -1, NULL, NULL, 1, NONE },
{ G_FILE_MONITOR_EVENT_RENAMED, (gchar*)DONT_CARE, "atomic_replace_file", -1 }, { G_FILE_MONITOR_EVENT_RENAMED, (gchar*)DONT_CARE, "atomic_replace_file", -1, NONE },
{ -1, NULL, NULL, 2 }, { -1, NULL, NULL, 2, NONE },
{ G_FILE_MONITOR_EVENT_DELETED, "atomic_replace_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_DELETED, "atomic_replace_file", NULL, -1, NONE },
{ -1, NULL, NULL, 3 } { -1, NULL, NULL, 3, NONE }
}; };
static void static void
@ -216,7 +346,10 @@ test_atomic_replace (void)
g_main_loop_run (data.loop); g_main_loop_run (data.loop);
/*output_events (data.events);*/ /*output_events (data.events);*/
check_expected_events (atomic_replace_output, G_N_ELEMENTS (atomic_replace_output), data.events); check_expected_events (atomic_replace_output,
G_N_ELEMENTS (atomic_replace_output),
data.events,
get_environment (data.monitor));
g_list_free_full (data.events, (GDestroyNotify)free_recorded_event); g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
g_main_loop_unref (data.loop); g_main_loop_unref (data.loop);
@ -277,18 +410,18 @@ change_step (gpointer user_data)
/* this is the output we expect from the above steps */ /* this is the output we expect from the above steps */
static RecordedEvent change_output[] = { static RecordedEvent change_output[] = {
{ -1, NULL, NULL, 0 }, { -1, NULL, NULL, 0, NONE },
{ G_FILE_MONITOR_EVENT_CREATED, "change_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_CREATED, "change_file", NULL, -1, NONE },
{ G_FILE_MONITOR_EVENT_CHANGED, "change_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGED, "change_file", NULL, -1, KQUEUE },
{ G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "change_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "change_file", NULL, -1, KQUEUE },
{ -1, NULL, NULL, 1 }, { -1, NULL, NULL, 1, NONE },
{ G_FILE_MONITOR_EVENT_CHANGED, "change_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGED, "change_file", NULL, -1, NONE },
{ G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "change_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "change_file", NULL, -1, NONE },
{ -1, NULL, NULL, 2 }, { -1, NULL, NULL, 2, NONE },
{ G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED, "change_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED, "change_file", NULL, -1, NONE },
{ -1, NULL, NULL, 3 }, { -1, NULL, NULL, 3, NONE },
{ G_FILE_MONITOR_EVENT_DELETED, "change_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_DELETED, "change_file", NULL, -1, NONE },
{ -1, NULL, NULL, 4 } { -1, NULL, NULL, 4, NONE }
}; };
static void static void
@ -316,7 +449,10 @@ test_file_changes (void)
g_main_loop_run (data.loop); g_main_loop_run (data.loop);
/*output_events (data.events);*/ /*output_events (data.events);*/
check_expected_events (change_output, G_N_ELEMENTS (change_output), data.events); check_expected_events (change_output,
G_N_ELEMENTS (change_output),
data.events,
get_environment (data.monitor));
g_list_free_full (data.events, (GDestroyNotify)free_recorded_event); g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
g_main_loop_unref (data.loop); g_main_loop_unref (data.loop);
@ -391,16 +527,16 @@ dir_step (gpointer user_data)
/* this is the output we expect from the above steps */ /* this is the output we expect from the above steps */
static RecordedEvent dir_output[] = { static RecordedEvent dir_output[] = {
{ -1, NULL, NULL, 1 }, { -1, NULL, NULL, 1, NONE },
{ -1, NULL, NULL, 2 }, { -1, NULL, NULL, 2, NONE },
{ G_FILE_MONITOR_EVENT_MOVED_IN, "dir_test_file", NULL, -1 }, { G_FILE_MONITOR_EVENT_MOVED_IN, "dir_test_file", NULL, -1, NONE },
{ -1, NULL, NULL, 3 }, { -1, NULL, NULL, 3, NONE },
{ G_FILE_MONITOR_EVENT_RENAMED, "dir_test_file", "dir_test_file2", -1 }, { G_FILE_MONITOR_EVENT_RENAMED, "dir_test_file", "dir_test_file2", -1, NONE },
{ -1, NULL, NULL, 4 }, { -1, NULL, NULL, 4, NONE },
{ G_FILE_MONITOR_EVENT_MOVED_OUT, "dir_test_file2", NULL, -1 }, { G_FILE_MONITOR_EVENT_MOVED_OUT, "dir_test_file2", NULL, -1, NONE },
{ -1, NULL, NULL, 5 }, { -1, NULL, NULL, 5, NONE },
{ G_FILE_MONITOR_EVENT_DELETED, "dir_monitor_test", NULL, -1 }, { G_FILE_MONITOR_EVENT_DELETED, "dir_monitor_test", NULL, -1, NONE },
{ -1, NULL, NULL, 6 } { -1, NULL, NULL, 6, NONE }
}; };
static void static void
@ -429,7 +565,10 @@ test_dir_monitor (void)
g_main_loop_run (data.loop); g_main_loop_run (data.loop);
/*output_events (data.events);*/ /*output_events (data.events);*/
check_expected_events (dir_output, G_N_ELEMENTS (dir_output), data.events); check_expected_events (dir_output,
G_N_ELEMENTS (dir_output),
data.events,
get_environment (data.monitor));
g_list_free_full (data.events, (GDestroyNotify)free_recorded_event); g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
g_main_loop_unref (data.loop); g_main_loop_unref (data.loop);
@ -482,17 +621,17 @@ nodir_step (gpointer user_data)
} }
static RecordedEvent nodir_output[] = { static RecordedEvent nodir_output[] = {
{ -1, NULL, NULL, 0 }, { -1, NULL, NULL, 0, NONE },
{ G_FILE_MONITOR_EVENT_CREATED, "nosuchfile", NULL, -1 }, { G_FILE_MONITOR_EVENT_CREATED, "nosuchfile", NULL, -1, KQUEUE },
{ G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "nosuchfile", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "nosuchfile", NULL, -1, KQUEUE },
{ -1, NULL, NULL, 1 }, { -1, NULL, NULL, 1, NONE },
{ G_FILE_MONITOR_EVENT_CREATED, "nosuchfile", NULL, -1 }, { G_FILE_MONITOR_EVENT_CREATED, "nosuchfile", NULL, -1, NONE },
{ G_FILE_MONITOR_EVENT_CHANGED, "nosuchfile", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGED, "nosuchfile", NULL, -1, KQUEUE },
{ G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "nosuchfile", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "nosuchfile", NULL, -1, KQUEUE },
{ -1, NULL, NULL, 2 }, { -1, NULL, NULL, 2, NONE },
{ G_FILE_MONITOR_EVENT_DELETED, "nosuchfile", NULL, -1 }, { G_FILE_MONITOR_EVENT_DELETED, "nosuchfile", NULL, -1, NONE },
{ -1, NULL, NULL, 3 }, { -1, NULL, NULL, 3, NONE },
{ -1, NULL, NULL, 4 } { -1, NULL, NULL, 4, NONE }
}; };
static void static void
@ -521,7 +660,10 @@ test_dir_non_existent (void)
g_main_loop_run (data.loop); g_main_loop_run (data.loop);
/*output_events (data.events);*/ /*output_events (data.events);*/
check_expected_events (nodir_output, G_N_ELEMENTS (nodir_output), data.events); check_expected_events (nodir_output,
G_N_ELEMENTS (nodir_output),
data.events,
get_environment (data.monitor));
g_list_free_full (data.events, (GDestroyNotify)free_recorded_event); g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
g_main_loop_unref (data.loop); g_main_loop_unref (data.loop);
@ -578,26 +720,26 @@ cross_dir_step (gpointer user_data)
} }
static RecordedEvent cross_dir_a_output[] = { static RecordedEvent cross_dir_a_output[] = {
{ -1, NULL, NULL, 0 }, { -1, NULL, NULL, 0, NONE },
{ -1, NULL, NULL, 1 }, { -1, NULL, NULL, 1, NONE },
{ G_FILE_MONITOR_EVENT_CREATED, "a", NULL, -1 }, { G_FILE_MONITOR_EVENT_CREATED, "a", NULL, -1, NONE },
{ G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "a", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "a", NULL, -1, KQUEUE },
{ -1, NULL, NULL, 2 }, { -1, NULL, NULL, 2, NONE },
{ G_FILE_MONITOR_EVENT_DELETED, "a", NULL, -1 }, { G_FILE_MONITOR_EVENT_DELETED, "a", NULL, -1, NONE },
{ G_FILE_MONITOR_EVENT_DELETED, "cross_dir_a", NULL, -1 }, { G_FILE_MONITOR_EVENT_DELETED, "cross_dir_a", NULL, -1, NONE },
{ -1, NULL, NULL, 3 }, { -1, NULL, NULL, 3, NONE },
}; };
static RecordedEvent cross_dir_b_output[] = { static RecordedEvent cross_dir_b_output[] = {
{ -1, NULL, NULL, 0 }, { -1, NULL, NULL, 0, NONE },
{ G_FILE_MONITOR_EVENT_CREATED, "a", NULL, -1 }, { G_FILE_MONITOR_EVENT_CREATED, "a", NULL, -1, NONE },
{ G_FILE_MONITOR_EVENT_CHANGED, "a", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGED, "a", NULL, -1, KQUEUE },
{ G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "a", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "a", NULL, -1, KQUEUE },
{ -1, NULL, NULL, 1 }, { -1, NULL, NULL, 1, NONE },
{ G_FILE_MONITOR_EVENT_MOVED_OUT, "a", "a", -1 }, { G_FILE_MONITOR_EVENT_MOVED_OUT, "a", "a", -1, NONE },
{ -1, NULL, NULL, 2 }, { -1, NULL, NULL, 2, NONE },
{ G_FILE_MONITOR_EVENT_DELETED, "cross_dir_b", NULL, -1 }, { G_FILE_MONITOR_EVENT_DELETED, "cross_dir_b", NULL, -1, NONE },
{ -1, NULL, NULL, 3 }, { -1, NULL, NULL, 3, NONE },
}; };
static void static void
test_cross_dir_moves (void) test_cross_dir_moves (void)
@ -644,8 +786,14 @@ test_cross_dir_moves (void)
output_events (data[1].events); output_events (data[1].events);
#endif #endif
check_expected_events (cross_dir_a_output, G_N_ELEMENTS (cross_dir_a_output), data[0].events); check_expected_events (cross_dir_a_output,
check_expected_events (cross_dir_b_output, G_N_ELEMENTS (cross_dir_b_output), data[1].events); G_N_ELEMENTS (cross_dir_a_output),
data[0].events,
get_environment (data[0].monitor));
check_expected_events (cross_dir_b_output,
G_N_ELEMENTS (cross_dir_b_output),
data[1].events,
get_environment (data[1].monitor));
g_list_free_full (data[0].events, (GDestroyNotify)free_recorded_event); g_list_free_full (data[0].events, (GDestroyNotify)free_recorded_event);
g_main_loop_unref (data[0].loop); g_main_loop_unref (data[0].loop);
@ -742,19 +890,26 @@ file_hard_links_step (gpointer user_data)
} }
static RecordedEvent file_hard_links_output[] = { static RecordedEvent file_hard_links_output[] = {
{ -1, NULL, NULL, 0 }, { -1, NULL, NULL, 0, NONE },
{ G_FILE_MONITOR_EVENT_CHANGED, "testfilemonitor.db", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGED, "testfilemonitor.db", NULL, -1, NONE },
{ G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "testfilemonitor.db", NULL, -1 }, { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "testfilemonitor.db", NULL, -1, NONE },
{ -1, NULL, NULL, 1 }, { -1, NULL, NULL, 1, NONE },
{ G_FILE_MONITOR_EVENT_RENAMED, NULL /* .goutputstream-XXXXXX */, "testfilemonitor.db", -1 }, { G_FILE_MONITOR_EVENT_RENAMED, (gchar*)DONT_CARE /* .goutputstream-XXXXXX */, "testfilemonitor.db", -1, NONE },
{ -1, NULL, NULL, 2 }, { -1, NULL, NULL, 2, NONE },
{ -1, NULL, NULL, 3 }, { -1, NULL, NULL, 3, NONE },
/* FIXME: There should be a EVENT_CHANGED and EVENT_CHANGES_DONE_HINT here /* Kqueue is based on file descriptors. You can get events from all hard
* from the modification of the hard link. */ * links by just monitoring one open file descriptor, and it is not possible
{ -1, NULL, NULL, 4 }, * to know whether it is done on the file name we use to open the file. Since
{ G_FILE_MONITOR_EVENT_DELETED, "testfilemonitor.db", NULL, -1 }, * the hard link count of 'testfilemonitor.db' is 2, it is expected to see
{ -1, NULL, NULL, 5 }, * two 'DELETED' events reported here. You have to call 'unlink' twice on
{ -1, NULL, NULL, 6 }, * different file names to remove 'testfilemonitor.db' from the file system,
* and each 'unlink' call generates a 'DELETED' event. */
{ G_FILE_MONITOR_EVENT_CHANGED, "testfilemonitor.db", NULL, -1, INOTIFY },
{ -1, NULL, NULL, 4, NONE },
{ G_FILE_MONITOR_EVENT_DELETED, "testfilemonitor.db", NULL, -1, NONE },
{ -1, NULL, NULL, 5, NONE },
{ G_FILE_MONITOR_EVENT_DELETED, "testfilemonitor.db", NULL, -1, INOTIFY },
{ -1, NULL, NULL, 6, NONE },
}; };
static void static void
@ -800,7 +955,9 @@ test_file_hard_links (void)
/* output_events (data.events); */ /* output_events (data.events); */
check_expected_events (file_hard_links_output, check_expected_events (file_hard_links_output,
G_N_ELEMENTS (file_hard_links_output), data.events); G_N_ELEMENTS (file_hard_links_output),
data.events,
get_environment (data.monitor));
g_list_free_full (data.events, (GDestroyNotify) free_recorded_event); g_list_free_full (data.events, (GDestroyNotify) free_recorded_event);
g_main_loop_unref (data.loop); g_main_loop_unref (data.loop);