mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-10 03:16:17 +01:00
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:
commit
40a84b3d1c
@ -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 there’s apparently no way to get the new name of the
|
/* 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
|
* 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));
|
||||||
|
@ -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);
|
||||||
|
@ -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 */
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user