summaryrefslogtreecommitdiff
path: root/gio
diff options
context:
space:
mode:
authorAntoine Jacoutot <ajacoutot@gnome.org>2012-11-14 12:57:42 +0100
committerAntoine Jacoutot <ajacoutot@gnome.org>2012-11-15 08:13:30 +0100
commita335fd1de8fc2ab4b26c5fe6055014ea15043fb9 (patch)
treeeb247ebe4cce15db39b4eb22f61d38d422d8e2fb /gio
parent2054ccad95cfdcf5eccd2ef6847c12039c9678e8 (diff)
downloadglib-a335fd1de8fc2ab4b26c5fe6055014ea15043fb9.tar.gz
glib-a335fd1de8fc2ab4b26c5fe6055014ea15043fb9.tar.bz2
glib-a335fd1de8fc2ab4b26c5fe6055014ea15043fb9.zip
GFileMonitor: Add kqueue(3) support to GIO
Written by Dmitry Matveev as part of GSoC 2011: http://netbsd-soc.sourceforge.net/projects/kqueue4gio/ This brings native file monitoring support on systems supporting kqueue(3) (all BSDs) and remove the need to rely on the unmaintained gamin software. The backend adds GKqueueDirectoryMonitor and GKqueueFileMonitor. Some parts rewritten by myself (to prevent needing a configuration file). Helpful inputs from Colin Walters and Simon McVittie. https://bugzilla.gnome.org/show_bug.cgi?id=679793
Diffstat (limited to 'gio')
-rw-r--r--gio/Makefile.am6
-rw-r--r--gio/giomodule.c6
-rw-r--r--gio/kqueue/Makefile.am34
-rw-r--r--gio/kqueue/dep-list.c521
-rw-r--r--gio/kqueue/dep-list.h69
-rw-r--r--gio/kqueue/gkqueuedirectorymonitor.c205
-rw-r--r--gio/kqueue/gkqueuedirectorymonitor.h49
-rw-r--r--gio/kqueue/gkqueuefilemonitor.c209
-rw-r--r--gio/kqueue/gkqueuefilemonitor.h51
-rw-r--r--gio/kqueue/kqueue-exclusions.c60
-rw-r--r--gio/kqueue/kqueue-exclusions.h28
-rw-r--r--gio/kqueue/kqueue-helper.c644
-rw-r--r--gio/kqueue/kqueue-helper.h37
-rw-r--r--gio/kqueue/kqueue-missing.c157
-rw-r--r--gio/kqueue/kqueue-missing.h32
-rw-r--r--gio/kqueue/kqueue-sub.c79
-rw-r--r--gio/kqueue/kqueue-sub.h50
-rw-r--r--gio/kqueue/kqueue-thread.c310
-rw-r--r--gio/kqueue/kqueue-thread.h45
-rw-r--r--gio/kqueue/kqueue-utils.c242
-rw-r--r--gio/kqueue/kqueue-utils.h57
21 files changed, 2891 insertions, 0 deletions
diff --git a/gio/Makefile.am b/gio/Makefile.am
index 9732ab7c1..89def1bdf 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -222,6 +222,12 @@ platform_libadd += inotify/libinotify.la
platform_deps += inotify/libinotify.la
endif
+if HAVE_KQUEUE
+SUBDIRS += kqueue
+platform_libadd += kqueue/libkqueue.la
+platform_deps += kqueue/libkqueue.la
+endif
+
if HAVE_FEN
AM_CPPFLAGS += -DHAVE_FEN
SUBDIRS += fen
diff --git a/gio/giomodule.c b/gio/giomodule.c
index 37a9e70a9..e8ca3e470 100644
--- a/gio/giomodule.c
+++ b/gio/giomodule.c
@@ -762,6 +762,8 @@ extern GType _g_fen_directory_monitor_get_type (void);
extern GType _g_fen_file_monitor_get_type (void);
extern GType _g_inotify_directory_monitor_get_type (void);
extern GType _g_inotify_file_monitor_get_type (void);
+extern GType _g_kqueue_directory_monitor_get_type (void);
+extern GType _g_kqueue_file_monitor_get_type (void);
extern GType _g_unix_volume_monitor_get_type (void);
extern GType _g_local_vfs_get_type (void);
@@ -915,6 +917,10 @@ _g_io_modules_ensure_loaded (void)
g_type_ensure (_g_inotify_directory_monitor_get_type ());
g_type_ensure (_g_inotify_file_monitor_get_type ());
#endif
+#if defined(HAVE_KQUEUE)
+ g_type_ensure (_g_kqueue_directory_monitor_get_type ());
+ g_type_ensure (_g_kqueue_file_monitor_get_type ());
+#endif
#if defined(HAVE_FEN)
g_type_ensure (_g_fen_directory_monitor_get_type ());
g_type_ensure (_g_fen_file_monitor_get_type ());
diff --git a/gio/kqueue/Makefile.am b/gio/kqueue/Makefile.am
new file mode 100644
index 000000000..40fd1fee0
--- /dev/null
+++ b/gio/kqueue/Makefile.am
@@ -0,0 +1,34 @@
+include $(top_srcdir)/Makefile.decl
+
+NULL =
+
+noinst_LTLIBRARIES = libkqueue.la
+
+libkqueue_la_SOURCES = \
+ gkqueuefilemonitor.c \
+ gkqueuefilemonitor.h \
+ gkqueuedirectorymonitor.c \
+ gkqueuedirectorymonitor.h \
+ kqueue-helper.c \
+ kqueue-helper.h \
+ kqueue-thread.c \
+ kqueue-thread.h \
+ kqueue-sub.c \
+ kqueue-sub.h \
+ kqueue-missing.c \
+ kqueue-missing.h \
+ kqueue-utils.c \
+ kqueue-utils.h \
+ kqueue-exclusions.c \
+ kqueue-exclusions.h \
+ dep-list.c \
+ dep-list.h \
+ $(NULL)
+
+libkqueue_la_CFLAGS = \
+ -DG_LOG_DOMAIN=\"GLib-GIO\" \
+ $(gio_INCLUDES) \
+ $(GLIB_DEBUG_FLAGS) \
+ -DGIO_MODULE_DIR=\"$(GIO_MODULE_DIR)\" \
+ -DGIO_COMPILATION \
+ -DG_DISABLE_DEPRECATED
diff --git a/gio/kqueue/dep-list.c b/gio/kqueue/dep-list.c
new file mode 100644
index 000000000..092036fae
--- /dev/null
+++ b/gio/kqueue/dep-list.c
@@ -0,0 +1,521 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#include <glib.h>
+
+#include <stdlib.h> /* calloc */
+#include <stdio.h> /* printf */
+#include <dirent.h> /* opendir, readdir, closedir */
+#include <string.h> /* strcmp */
+#include <assert.h>
+
+#include "dep-list.h"
+
+static gboolean kdl_debug_enabled = FALSE;
+#define perror_msg if (kdl_debug_enabled) g_warning
+
+
+/**
+ * Print a list to stdout.
+ *
+ * @param[in] dl A pointer to a list.
+ **/
+void
+dl_print (const dep_list *dl)
+{
+ while (dl != NULL) {
+ printf ("%lld:%s ", (long long int) dl->inode, dl->path);
+ dl = dl->next;
+ }
+ printf ("\n");
+}
+
+/**
+ * Create a new list item.
+ *
+ * Create a new list item and initialize its fields.
+ *
+ * @param[in] path A name of a file (the string is not copied!).
+ * @param[in] inode A file's inode number.
+ * @return A pointer to a new item or NULL in the case of error.
+ **/
+dep_list* dl_create (char *path, ino_t inode)
+{
+ dep_list *dl = calloc (1, sizeof (dep_list));
+ if (dl == NULL) {
+ perror_msg ("Failed to create a new dep-list item");
+ return NULL;
+ }
+
+ dl->path = path;
+ dl->inode = inode;
+ return dl;
+}
+
+/**
+ * Create a shallow copy of a list.
+ *
+ * A shallow copy is a copy of a structure, but not the copy of the
+ * contents. All data pointers (`path' in our case) of a list and its
+ * shallow copy will point to the same memory.
+ *
+ * @param[in] dl A pointer to list to make a copy. May be NULL.
+ * @return A shallow copy of the list.
+ **/
+dep_list*
+dl_shallow_copy (const dep_list *dl)
+{
+ if (dl == NULL) {
+ return NULL;
+ }
+
+ dep_list *head = calloc (1, sizeof (dep_list));
+ if (head == NULL) {
+ perror_msg ("Failed to allocate head during shallow copy");
+ return NULL;
+ }
+
+ dep_list *cp = head;
+ const dep_list *it = dl;
+
+ while (it != NULL) {
+ cp->path = it->path;
+ cp->inode = it->inode;
+ if (it->next) {
+ cp->next = calloc (1, sizeof (dep_list));
+ if (cp->next == NULL) {
+ perror_msg ("Failed to allocate a new element during shallow copy");
+ dl_shallow_free (head);
+ return NULL;
+ }
+ cp = cp->next;
+ }
+ it = it->next;
+ }
+
+ return head;
+}
+
+/**
+ * Free the memory allocated for shallow copy.
+ *
+ * This function will free the memory used by a list structure, but
+ * the list data will remain in the heap.
+ *
+ * @param[in] dl A pointer to a list. May be NULL.
+ **/
+void
+dl_shallow_free (dep_list *dl)
+{
+ while (dl != NULL) {
+ dep_list *ptr = dl;
+ dl = dl->next;
+ free (ptr);
+ }
+}
+
+/**
+ * Free the memory allocated for a list.
+ *
+ * This function will free all the memory used by a list: both
+ * list structure and the list data.
+ *
+ * @param[in] dl A pointer to a list. May be NULL.
+ **/
+void
+dl_free (dep_list *dl)
+{
+ while (dl != NULL) {
+ dep_list *ptr = dl;
+ dl = dl->next;
+
+ free (ptr->path);
+ free (ptr);
+ }
+}
+
+/**
+ * Create a directory listing and return it as a list.
+ *
+ * @param[in] path A path to a directory.
+ * @return A pointer to a list. May return NULL, check errno in this case.
+ **/
+dep_list*
+dl_listing (const char *path)
+{
+ assert (path != NULL);
+
+ dep_list *head = NULL;
+ dep_list *prev = NULL;
+ DIR *dir = opendir (path);
+ if (dir != NULL) {
+ struct dirent *ent;
+
+ while ((ent = readdir (dir)) != NULL) {
+ if (!strcmp (ent->d_name, ".") || !strcmp (ent->d_name, "..")) {
+ continue;
+ }
+
+ if (head == NULL) {
+ head = calloc (1, sizeof (dep_list));
+ if (head == NULL) {
+ perror_msg ("Failed to allocate head during listing");
+ goto error;
+ }
+ }
+
+ dep_list *iter = (prev == NULL) ? head : calloc (1, sizeof (dep_list));
+ if (iter == NULL) {
+ perror_msg ("Failed to allocate a new element during listing");
+ goto error;
+ }
+
+ iter->path = strdup (ent->d_name);
+ if (iter->path == NULL) {
+ perror_msg ("Failed to copy a string during listing");
+ goto error;
+ }
+
+ iter->inode = ent->d_ino;
+ iter->next = NULL;
+ if (prev) {
+ prev->next = iter;
+ }
+ prev = iter;
+ }
+
+ closedir (dir);
+ }
+ return head;
+
+error:
+ if (dir != NULL) {
+ closedir (dir);
+ }
+ dl_free (head);
+ return NULL;
+}
+
+/**
+ * Perform a diff on lists.
+ *
+ * This function performs something like a set intersection. The same items
+ * will be removed from the both lists. Items are comapred by a filename.
+ *
+ * @param[in,out] before A pointer to a pointer to a list. Will contain items
+ * which were not found in the `after' list.
+ * @param[in,out] after A pointer to a pointer to a list. Will containt items
+ * which were not found in the `before' list.
+ **/
+void
+dl_diff (dep_list **before, dep_list **after)
+{
+ assert (before != NULL);
+ assert (after != NULL);
+
+ if (*before == NULL || *after == NULL) {
+ return;
+ }
+
+ dep_list *before_iter = *before;
+ dep_list *before_prev = NULL;
+
+ while (before_iter != NULL) {
+ dep_list *after_iter = *after;
+ dep_list *after_prev = NULL;
+
+ int matched = 0;
+ while (after_iter != NULL) {
+ if (strcmp (before_iter->path, after_iter->path) == 0) {
+ matched = 1;
+ /* removing the entry from the both lists */
+ if (before_prev) {
+ before_prev->next = before_iter->next;
+ } else {
+ *before = before_iter->next;
+ }
+
+ if (after_prev) {
+ after_prev->next = after_iter->next;
+ } else {
+ *after = after_iter->next;
+ }
+ free (after_iter);
+ break;
+ }
+ after_prev = after_iter;
+ after_iter = after_iter->next;
+ }
+
+ dep_list *oldptr = before_iter;
+ before_iter = before_iter->next;
+ if (matched == 0) {
+ before_prev = oldptr;
+ } else {
+ free (oldptr);
+ }
+ }
+}
+
+
+/**
+ * Traverses two lists. Compares items with a supplied expression
+ * and performs the passed code on a match. Removes the matched entries
+ * from the both lists.
+ **/
+#define EXCLUDE_SIMILAR(removed_list, added_list, match_expr, matched_code) \
+ assert (removed_list != NULL); \
+ assert (added_list != NULL); \
+ \
+ dep_list *removed_list##_iter = *removed_list; \
+ dep_list *removed_list##_prev = NULL; \
+ \
+ int productive = 0; \
+ \
+ while (removed_list##_iter != NULL) { \
+ dep_list *added_list##_iter = *added_list; \
+ dep_list *added_list##_prev = NULL; \
+ \
+ int matched = 0; \
+ while (added_list##_iter != NULL) { \
+ if (match_expr) { \
+ matched = 1; \
+ ++productive; \
+ matched_code; \
+ \
+ if (removed_list##_prev) { \
+ removed_list##_prev->next = removed_list##_iter->next; \
+ } else { \
+ *removed_list = removed_list##_iter->next; \
+ } \
+ if (added_list##_prev) { \
+ added_list##_prev->next = added_list##_iter->next; \
+ } else { \
+ *added_list = added_list##_iter->next; \
+ } \
+ free (added_list##_iter); \
+ break; \
+ } \
+ added_list##_iter = added_list##_iter->next; \
+ } \
+ dep_list *oldptr = removed_list##_iter; \
+ removed_list##_iter = removed_list##_iter->next; \
+ if (matched == 0) { \
+ removed_list##_prev = oldptr; \
+ } else { \
+ free (oldptr); \
+ } \
+ } \
+ return (productive > 0);
+
+
+#define cb_invoke(cbs, name, udata, ...) \
+ do { \
+ if (cbs->name) { \
+ (cbs->name) (udata, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+/**
+ * Detect and notify about moves in the watched directory.
+ *
+ * A move is what happens when you rename a file in a directory, and
+ * a new name is unique, i.e. you didnt overwrite any existing files
+ * with this one.
+ *
+ * @param[in,out] removed A list of the removed files in the directory.
+ * @param[in,out] added A list of the added files of the directory.
+ * @param[in] cbs A pointer to #traverse_cbs, an user-defined set of
+ * traverse callbacks.
+ * @param[in] udata A pointer to the user-defined data.
+ * @return 0 if no files were renamed, >0 otherwise.
+**/
+static int
+dl_detect_moves (dep_list **removed,
+ dep_list **added,
+ const traverse_cbs *cbs,
+ void *udata)
+{
+ assert (cbs != NULL);
+
+ EXCLUDE_SIMILAR
+ (removed, added,
+ (removed_iter->inode == added_iter->inode),
+ {
+ cb_invoke (cbs, moved, udata,
+ removed_iter->path, removed_iter->inode,
+ added_iter->path, added_iter->inode);
+ });
+}
+
+/**
+ * Detect and notify about replacements in the watched directory.
+ *
+ * Consider you are watching a directory foo with the folloing files
+ * insinde:
+ *
+ * foo/bar
+ * foo/baz
+ *
+ * A replacement in a watched directory is what happens when you invoke
+ *
+ * mv /foo/bar /foo/bar
+ *
+ * i.e. when you replace a file in a watched directory with another file
+ * from the same directory.
+ *
+ * @param[in,out] removed A list of the removed files in the directory.
+ * @param[in,out] current A list with the current contents of the directory.
+ * @param[in] cbs A pointer to #traverse_cbs, an user-defined set of
+ * traverse callbacks.
+ * @param[in] udata A pointer to the user-defined data.
+ * @return 0 if no files were renamed, >0 otherwise.
+ **/
+static int
+dl_detect_replacements (dep_list **removed,
+ dep_list **current,
+ const traverse_cbs *cbs,
+ void *udata)
+{
+ assert (cbs != NULL);
+
+ EXCLUDE_SIMILAR
+ (removed, current,
+ (removed_iter->inode == current_iter->inode),
+ {
+ cb_invoke (cbs, replaced, udata,
+ removed_iter->path, removed_iter->inode,
+ current_iter->path, current_iter->inode);
+ });
+}
+
+/**
+ * Detect and notify about overwrites in the watched directory.
+ *
+ * Consider you are watching a directory foo with a file inside:
+ *
+ * foo/bar
+ *
+ * And you also have a directory tmp with a file 1:
+ *
+ * tmp/1
+ *
+ * You do not watching directory tmp.
+ *
+ * An overwrite in a watched directory is what happens when you invoke
+ *
+ * mv /tmp/1 /foo/bar
+ *
+ * i.e. when you overwrite a file in a watched directory with another file
+ * from the another directory.
+ *
+ * @param[in,out] previous A list with the previous contents of the directory.
+ * @param[in,out] current A list with the current contents of the directory.
+ * @param[in] cbs A pointer to #traverse_cbs, an user-defined set of
+ * traverse callbacks.
+ * @param[in] udata A pointer to the user-defined data.
+ * @return 0 if no files were renamed, >0 otherwise.
+ **/
+static int
+dl_detect_overwrites (dep_list **previous,
+ dep_list **current,
+ const traverse_cbs *cbs,
+ void *udata)
+{
+ assert (cbs != NULL);
+
+ EXCLUDE_SIMILAR
+ (previous, current,
+ (strcmp (previous_iter->path, current_iter->path) == 0
+ && previous_iter->inode != current_iter->inode),
+ {
+ cb_invoke (cbs, overwritten, udata, current_iter->path, current_iter->inode);
+ });
+}
+
+
+/**
+ * Traverse a list and invoke a callback for each item.
+ *
+ * @param[in] list A #dep_list.
+ * @param[in] cb A #single_entry_cb callback function.
+ * @param[in] udata A pointer to the user-defined data.
+ **/
+static void
+dl_emit_single_cb_on (dep_list *list,
+ single_entry_cb cb,
+ void *udata)
+{
+ while (cb && list != NULL) {
+ (cb) (udata, list->path, list->inode);
+ list = list->next;
+ }
+}
+
+
+/**
+ * Recognize all the changes in the directory, invoke the appropriate callbacks.
+ *
+ * This is the core function of directory diffing submodule.
+ *
+ * @param[in] before The previous contents of the directory.
+ * @param[in] after The current contents of the directory.
+ * @param[in] cbs A pointer to user callbacks (#traverse_callbacks).
+ * @param[in] udata A pointer to user data.
+ **/
+void
+dl_calculate (dep_list *before,
+ dep_list *after,
+ const traverse_cbs *cbs,
+ void *udata)
+{
+ assert (cbs != NULL);
+
+ int need_update = 0;
+
+ dep_list *was = dl_shallow_copy (before);
+ dep_list *pre = dl_shallow_copy (before);
+ dep_list *now = dl_shallow_copy (after);
+ dep_list *lst = dl_shallow_copy (after);
+
+ dl_diff (&was, &now);
+
+ need_update += dl_detect_moves (&was, &now, cbs, udata);
+ need_update += dl_detect_replacements (&was, &lst, cbs, udata);
+ dl_detect_overwrites (&pre, &lst, cbs, udata);
+
+ if (need_update) {
+ cb_invoke (cbs, names_updated, udata);
+ }
+
+ dl_emit_single_cb_on (was, cbs->removed, udata);
+ dl_emit_single_cb_on (now, cbs->added, udata);
+
+ cb_invoke (cbs, many_added, udata, now);
+ cb_invoke (cbs, many_removed, udata, was);
+
+ dl_shallow_free (lst);
+ dl_shallow_free (now);
+ dl_shallow_free (pre);
+ dl_shallow_free (was);
+}
+
diff --git a/gio/kqueue/dep-list.h b/gio/kqueue/dep-list.h
new file mode 100644
index 000000000..b12802dce
--- /dev/null
+++ b/gio/kqueue/dep-list.h
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#ifndef __DEP_LIST_H__
+#define __DEP_LIST_H__
+
+#include <sys/types.h> /* ino_t */
+
+typedef struct dep_list {
+ struct dep_list *next;
+
+ char *path;
+ ino_t inode;
+} dep_list;
+
+typedef void (* no_entry_cb) (void *udata);
+typedef void (* single_entry_cb) (void *udata, const char *path, ino_t inode);
+typedef void (* dual_entry_cb) (void *udata,
+ const char *from_path, ino_t from_inode,
+ const char *to_path, ino_t to_inode);
+typedef void (* list_cb) (void *udata, const dep_list *list);
+
+
+typedef struct traverse_cbs {
+ single_entry_cb added;
+ single_entry_cb removed;
+ dual_entry_cb replaced;
+ single_entry_cb overwritten;
+ dual_entry_cb moved;
+ list_cb many_added;
+ list_cb many_removed;
+ no_entry_cb names_updated;
+} traverse_cbs;
+
+dep_list* dl_create (char *path, ino_t inode);
+void dl_print (const dep_list *dl);
+dep_list* dl_shallow_copy (const dep_list *dl);
+void dl_shallow_free (dep_list *dl);
+void dl_free (dep_list *dl);
+dep_list* dl_listing (const char *path);
+void dl_diff (dep_list **before, dep_list **after);
+
+void
+dl_calculate (dep_list *before,
+ dep_list *after,
+ const traverse_cbs *cbs,
+ void *udata);
+
+
+#endif /* __DEP_LIST_H__ */
diff --git a/gio/kqueue/gkqueuedirectorymonitor.c b/gio/kqueue/gkqueuedirectorymonitor.c
new file mode 100644
index 000000000..ee0cf3a80
--- /dev/null
+++ b/gio/kqueue/gkqueuedirectorymonitor.c
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#include "config.h"
+
+#include "gkqueuedirectorymonitor.h"
+#include "kqueue-helper.h"
+#include "kqueue-exclusions.h"
+#include <gio/gpollfilemonitor.h>
+#include <gio/gfile.h>
+#include <gio/giomodule.h>
+
+
+struct _GKqueueDirectoryMonitor
+{
+ GLocalDirectoryMonitor parent_instance;
+ kqueue_sub *sub;
+
+ GFileMonitor *fallback;
+ GFile *fbfile;
+
+ gboolean pair_moves;
+};
+
+static gboolean g_kqueue_directory_monitor_cancel (GFileMonitor *monitor);
+
+#define g_kqueue_directory_monitor_get_type _g_kqueue_directory_monitor_get_type
+G_DEFINE_TYPE_WITH_CODE (GKqueueDirectoryMonitor, g_kqueue_directory_monitor, G_TYPE_LOCAL_DIRECTORY_MONITOR,
+ g_io_extension_point_implement (G_LOCAL_DIRECTORY_MONITOR_EXTENSION_POINT_NAME,
+ g_define_type_id,
+ "kqueue",
+ 20))
+
+
+static void
+_fallback_callback (GFileMonitor *unused,
+ GFile *first,
+ GFile *second,
+ GFileMonitorEvent event,
+ gpointer udata)
+{
+ GKqueueDirectoryMonitor *kq_mon = G_KQUEUE_DIRECTORY_MONITOR (udata);
+ GFileMonitor *mon = G_FILE_MONITOR (kq_mon);
+ g_assert (kq_mon != NULL);
+ g_assert (mon != NULL);
+ (void) unused;
+
+ if (event == G_FILE_MONITOR_EVENT_CHANGED)
+ {
+ _kh_dir_diff (kq_mon->sub, mon);
+ }
+ else
+ g_file_monitor_emit_event (mon, first, second, event);
+}
+
+
+static void
+g_kqueue_directory_monitor_finalize (GObject *object)
+{
+ GKqueueDirectoryMonitor *kqueue_monitor = G_KQUEUE_DIRECTORY_MONITOR (object);
+
+ if (kqueue_monitor->sub)
+ {
+ _kh_cancel_sub (kqueue_monitor->sub);
+ _kh_sub_free (kqueue_monitor->sub);
+ kqueue_monitor->sub = NULL;
+ }
+
+ if (kqueue_monitor->fallback)
+ g_object_unref (kqueue_monitor->fallback);
+
+ if (kqueue_monitor->fbfile)
+ g_object_unref (kqueue_monitor->fbfile);
+
+ if (G_OBJECT_CLASS (g_kqueue_directory_monitor_parent_class)->finalize)
+ (*G_OBJECT_CLASS (g_kqueue_directory_monitor_parent_class)->finalize) (object);
+}
+
+static GObject*
+g_kqueue_directory_monitor_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GObject *obj;
+ GKqueueDirectoryMonitorClass *klass;
+ GObjectClass *parent_class;
+ GKqueueDirectoryMonitor *kqueue_monitor;
+ kqueue_sub *sub = NULL;
+ gboolean ret_kh_startup;
+ const gchar *path = NULL;
+
+ klass = G_KQUEUE_DIRECTORY_MONITOR_CLASS (g_type_class_peek (G_TYPE_KQUEUE_DIRECTORY_MONITOR));
+ parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
+ obj = parent_class->constructor (type,
+ n_construct_properties,
+ construct_properties);
+
+ kqueue_monitor = G_KQUEUE_DIRECTORY_MONITOR (obj);
+
+ ret_kh_startup = _kh_startup ();
+ g_assert (ret_kh_startup);
+
+ kqueue_monitor->pair_moves = (G_LOCAL_DIRECTORY_MONITOR (obj)->flags & G_FILE_MONITOR_SEND_MOVED)
+ ? TRUE : FALSE;
+
+ kqueue_monitor->sub = NULL;
+ kqueue_monitor->fallback = NULL;
+ kqueue_monitor->fbfile = NULL;
+
+ path = G_LOCAL_DIRECTORY_MONITOR (obj)->dirname;
+
+ /* For a directory monitor, create a subscription object anyway.
+ * It will be used for directory diff calculation routines. */
+
+ sub = _kh_sub_new (path,
+ kqueue_monitor->pair_moves,
+ kqueue_monitor);
+
+ /* FIXME: what to do about errors here? we can't return NULL or another
+ * kind of error and an assertion is probably too hard (same issue as in
+ * the inotify backend) */
+ g_assert (sub != NULL);
+ kqueue_monitor->sub = sub;
+
+ if (!_ke_is_excluded (path))
+ _kh_add_sub (sub);
+ else
+ {
+ GFile *file = g_file_new_for_path (path);
+ kqueue_monitor->fbfile = file;
+ kqueue_monitor->fallback = _g_poll_file_monitor_new (file);
+ g_signal_connect (kqueue_monitor->fallback,
+ "changed",
+ G_CALLBACK (_fallback_callback),
+ kqueue_monitor);
+ }
+
+ return obj;
+}
+
+static gboolean
+g_kqueue_directory_monitor_is_supported (void)
+{
+ return _kh_startup ();
+}
+
+static void
+g_kqueue_directory_monitor_class_init (GKqueueDirectoryMonitorClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GFileMonitorClass *directory_monitor_class = G_FILE_MONITOR_CLASS (klass);
+ GLocalDirectoryMonitorClass *local_directory_monitor_class = G_LOCAL_DIRECTORY_MONITOR_CLASS (klass);
+
+ gobject_class->finalize = g_kqueue_directory_monitor_finalize;
+ gobject_class->constructor = g_kqueue_directory_monitor_constructor;
+ directory_monitor_class->cancel = g_kqueue_directory_monitor_cancel;
+
+ local_directory_monitor_class->mount_notify = TRUE; /* TODO: ??? */
+ local_directory_monitor_class->is_supported = g_kqueue_directory_monitor_is_supported;
+}
+
+static void
+g_kqueue_directory_monitor_init (GKqueueDirectoryMonitor *monitor)
+{
+}
+
+static gboolean
+g_kqueue_directory_monitor_cancel (GFileMonitor *monitor)
+{
+ GKqueueDirectoryMonitor *kqueue_monitor = G_KQUEUE_DIRECTORY_MONITOR (monitor);
+
+ if (kqueue_monitor->sub)
+ {
+ _kh_cancel_sub (kqueue_monitor->sub);
+ _kh_sub_free (kqueue_monitor->sub);
+ kqueue_monitor->sub = NULL;
+ }
+ else if (kqueue_monitor->fallback)
+ g_file_monitor_cancel (kqueue_monitor->fallback);
+
+
+ if (G_FILE_MONITOR_CLASS (g_kqueue_directory_monitor_parent_class)->cancel)
+ (*G_FILE_MONITOR_CLASS (g_kqueue_directory_monitor_parent_class)->cancel) (monitor);
+
+ return TRUE;
+}
diff --git a/gio/kqueue/gkqueuedirectorymonitor.h b/gio/kqueue/gkqueuedirectorymonitor.h
new file mode 100644
index 000000000..7bd6a6461
--- /dev/null
+++ b/gio/kqueue/gkqueuedirectorymonitor.h
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#ifndef __G_KQUEUE_DIRECTORY_MONITOR_H__
+#define __G_KQUEUE_DIRECTORY_MONITOR_H__
+
+#include <glib-object.h>
+#include <gio/glocaldirectorymonitor.h>
+#include <gio/giomodule.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_KQUEUE_DIRECTORY_MONITOR (_g_kqueue_directory_monitor_get_type ())
+#define G_KQUEUE_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_KQUEUE_DIRECTORY_MONITOR, GKqueueDirectoryMonitor))
+#define G_KQUEUE_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), G_TYPE_KQUEUE_DIRECTORY_MONITOR, GKqueueDirectoryMonitorClass))
+#define G_IS_KQUEUE_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_KQUEUE_DIRECTORY_MONITOR))
+#define G_IS_KQUEUE_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_KQUEUE_DIRECTORY_MONITOR))
+
+typedef struct _GKqueueDirectoryMonitor GKqueueDirectoryMonitor;
+typedef struct _GKqueueDirectoryMonitorClass GKqueueDirectoryMonitorClass;
+
+struct _GKqueueDirectoryMonitorClass {
+ GLocalDirectoryMonitorClass parent_class;
+};
+
+GType _g_kqueue_directory_monitor_get_type (void);
+
+G_END_DECLS
+
+#endif /* __G_KQUEUE_DIRECTORY_MONITOR_H__ */
diff --git a/gio/kqueue/gkqueuefilemonitor.c b/gio/kqueue/gkqueuefilemonitor.c
new file mode 100644
index 000000000..d2d51a973
--- /dev/null
+++ b/gio/kqueue/gkqueuefilemonitor.c
@@ -0,0 +1,209 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#include "config.h"
+
+#include "gkqueuefilemonitor.h"
+#include "kqueue-helper.h"
+#include "kqueue-exclusions.h"
+#include <gio/gpollfilemonitor.h>
+#include <gio/gfile.h>
+#include <gio/giomodule.h>
+
+
+struct _GKqueueFileMonitor
+{
+ GLocalFileMonitor parent_instance;
+
+ kqueue_sub *sub;
+
+ GFileMonitor *fallback;
+ GFile *fbfile;
+
+ gboolean pair_moves;
+};
+
+static gboolean g_kqueue_file_monitor_cancel (GFileMonitor* monitor);
+
+#define g_kqueue_file_monitor_get_type _g_kqueue_file_monitor_get_type
+G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL_FILE_MONITOR,
+ g_io_extension_point_implement (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
+ g_define_type_id,
+ "kqueue",
+ 20))
+
+
+static void
+_fallback_callback (GFileMonitor *unused,
+ GFile *first,
+ GFile *second,
+ GFileMonitorEvent event,
+ gpointer udata)
+{
+ GKqueueFileMonitor *kq_mon = G_KQUEUE_FILE_MONITOR (udata);
+ GFileMonitor *mon = G_FILE_MONITOR (kq_mon);
+ g_assert (kq_mon != NULL);
+ g_assert (mon != NULL);
+ (void) unused;
+
+ if (event == G_FILE_MONITOR_EVENT_CHANGED)
+ {
+ _kh_dir_diff (kq_mon->sub, mon);
+ }
+ else
+ g_file_monitor_emit_event (mon, first, second, event);
+}
+
+
+static void
+g_kqueue_file_monitor_finalize (GObject *object)
+{
+ GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (object);
+
+ if (kqueue_monitor->sub)
+ {
+ _kh_cancel_sub (kqueue_monitor->sub);
+ _kh_sub_free (kqueue_monitor->sub);
+ kqueue_monitor->sub = NULL;
+ }
+
+ if (kqueue_monitor->fallback)
+ g_object_unref (kqueue_monitor->fallback);
+
+ if (kqueue_monitor->fbfile)
+ g_object_unref (kqueue_monitor->fbfile);
+
+ if (G_OBJECT_CLASS (g_kqueue_file_monitor_parent_class)->finalize)
+ (*G_OBJECT_CLASS (g_kqueue_file_monitor_parent_class)->finalize) (object);
+}
+
+static GObject*
+g_kqueue_file_monitor_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GObject *obj;
+ GKqueueFileMonitorClass *klass;
+ GObjectClass *parent_class;
+ GKqueueFileMonitor *kqueue_monitor;
+ kqueue_sub *sub = NULL;
+ gboolean ret_kh_startup = FALSE;
+ const gchar *path = NULL;
+
+ klass = G_KQUEUE_FILE_MONITOR_CLASS (g_type_class_peek (G_TYPE_KQUEUE_FILE_MONITOR));
+ parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
+ obj = parent_class->constructor (type,
+ n_construct_properties,
+ construct_properties);
+
+ kqueue_monitor = G_KQUEUE_FILE_MONITOR (obj);
+
+ ret_kh_startup = _kh_startup ();
+ g_assert (ret_kh_startup);
+
+ kqueue_monitor->pair_moves = G_LOCAL_FILE_MONITOR (obj)->flags & G_FILE_MONITOR_SEND_MOVED
+ ? TRUE : FALSE;
+
+ kqueue_monitor->sub = NULL;
+ kqueue_monitor->fallback = NULL;
+ kqueue_monitor->fbfile = NULL;
+
+ path = G_LOCAL_FILE_MONITOR (obj)->filename;
+
+ /* For a directory monitor, create a subscription object anyway.
+ * It will be used for directory diff calculation routines.
+ * Wait, directory diff in a GKqueueFileMonitor?
+ * Yes, it is. When a file monitor is started on an non-existent
+ * file, GIO uses a GKqueueFileMonitor object for that. If a directory
+ * will be created under that path, GKqueueFileMonitor will have to
+ * handle the directory notifications. */
+
+ sub = _kh_sub_new (path,
+ kqueue_monitor->pair_moves,
+ kqueue_monitor);
+
+ /* FIXME: what to do about errors here? we can't return NULL or another
+ * kind of error and an assertion is probably too hard (same issue as in
+ * the inotify backend) */
+ g_assert (sub != NULL);
+ kqueue_monitor->sub = sub;
+
+ if (!_ke_is_excluded (path))
+ _kh_add_sub (sub);
+ else
+ {
+ GFile *file = g_file_new_for_path (path);
+ kqueue_monitor->fbfile = file;
+ kqueue_monitor->fallback = _g_poll_file_monitor_new (file);
+ g_signal_connect (kqueue_monitor->fallback,
+ "changed",
+ G_CALLBACK (_fallback_callback),
+ kqueue_monitor);
+ }
+
+ return obj;
+}
+
+static gboolean
+g_kqueue_file_monitor_is_supported (void)
+{
+ return _kh_startup ();
+}
+
+static void
+g_kqueue_file_monitor_class_init (GKqueueFileMonitorClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass);
+ GLocalFileMonitorClass *local_file_monitor_class = G_LOCAL_FILE_MONITOR_CLASS (klass);
+
+ gobject_class->finalize = g_kqueue_file_monitor_finalize;
+ gobject_class->constructor = g_kqueue_file_monitor_constructor;
+ file_monitor_class->cancel = g_kqueue_file_monitor_cancel;
+
+ local_file_monitor_class->is_supported = g_kqueue_file_monitor_is_supported;
+}
+
+static void
+g_kqueue_file_monitor_init (GKqueueFileMonitor *monitor)
+{
+}
+
+static gboolean
+g_kqueue_file_monitor_cancel (GFileMonitor *monitor)
+{
+ GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (monitor);
+
+ if (kqueue_monitor->sub)
+ {
+ _kh_cancel_sub (kqueue_monitor->sub);
+ _kh_sub_free (kqueue_monitor->sub);
+ kqueue_monitor->sub = NULL;
+ }
+ else if (kqueue_monitor->fallback)
+ g_file_monitor_cancel (kqueue_monitor->fallback);
+
+ if (G_FILE_MONITOR_CLASS (g_kqueue_file_monitor_parent_class)->cancel)
+ (*G_FILE_MONITOR_CLASS (g_kqueue_file_monitor_parent_class)->cancel) (monitor);
+
+ return TRUE;
+}
diff --git a/gio/kqueue/gkqueuefilemonitor.h b/gio/kqueue/gkqueuefilemonitor.h
new file mode 100644
index 000000000..3c47377a9
--- /dev/null
+++ b/gio/kqueue/gkqueuefilemonitor.h
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#ifndef __G_KQUEUE_FILE_MONITOR_H__
+#define __G_KQUEUE_FILE_MONITOR_H__
+
+#include <glib-object.h>
+#include <string.h>
+#include <gio/gfilemonitor.h>
+#include <gio/glocalfilemonitor.h>
+#include <gio/giomodule.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_KQUEUE_FILE_MONITOR (_g_kqueue_file_monitor_get_type ())
+#define G_KQUEUE_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_KQUEUE_FILE_MONITOR, GKqueueFileMonitor))
+#define G_KQUEUE_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), G_TYPE_KQUEUE_FILE_MONITOR, GKqueueFileMonitorClass))
+#define G_IS_KQUEUE_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_KQUEUE_FILE_MONITOR))
+#define G_IS_KQUEUE_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_KQUEUE_FILE_MONITOR))
+
+typedef struct _GKqueueFileMonitor GKqueueFileMonitor;
+typedef struct _GKqueueFileMonitorClass GKqueueFileMonitorClass;
+
+struct _GKqueueFileMonitorClass {
+ GLocalFileMonitorClass parent_class;
+};
+
+GType _g_kqueue_file_monitor_get_type (void);
+
+G_END_DECLS
+
+#endif /* __G_KQUEUE_FILE_MONITOR_H__ */
diff --git a/gio/kqueue/kqueue-exclusions.c b/gio/kqueue/kqueue-exclusions.c
new file mode 100644
index 000000000..413391659
--- /dev/null
+++ b/gio/kqueue/kqueue-exclusions.c
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ Copyright (c) 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+ Copyright (c) 2012 Antoine Jacoutot <ajacoutot@openbsd.org>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#include <glib.h>
+#include <gio/gio.h>
+#include "kqueue-exclusions.h"
+
+static gboolean ke_debug_enabled = FALSE;
+#define KE_W if (ke_debug_enabled) g_warning
+
+/**
+ * _ke_is_excluded:
+ * @full_path - a path to file to check.
+ *
+ * Returns: TRUE if the file should be excluded from the kqueue-powered
+ * monitoring, FALSE otherwise.
+ **/
+gboolean
+_ke_is_excluded (const char *full_path)
+{
+ GFile *f = NULL;
+ GMount *mount = NULL;
+
+ f = g_file_new_for_path (full_path);
+
+ if (f != NULL) {
+ mount = g_file_find_enclosing_mount (f, NULL, NULL);
+ g_object_unref (f);
+ }
+
+ if ((mount != NULL && (g_mount_can_unmount (mount))) || g_str_has_prefix (full_path, "/mnt/"))
+ {
+ KE_W ("Excluding %s from kernel notification, falling back to poll", full_path);
+ if (mount)
+ g_object_unref (mount);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
diff --git a/gio/kqueue/kqueue-exclusions.h b/gio/kqueue/kqueue-exclusions.h
new file mode 100644
index 000000000..f1dad0e7e
--- /dev/null
+++ b/gio/kqueue/kqueue-exclusions.h
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ Copyright (c) 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#ifndef __KQUEUE_EXCLUSIONS_H
+#define __KQUEUE_EXCLUSIONS_H
+
+gboolean _ke_is_excluded (const char *full_path);
+
+#endif /* __KQUEUE_EXCLUDES_H */
diff --git a/gio/kqueue/kqueue-helper.c b/gio/kqueue/kqueue-helper.c
new file mode 100644
index 000000000..ba5a1406b
--- /dev/null
+++ b/gio/kqueue/kqueue-helper.c
@@ -0,0 +1,644 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#include "config.h"
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <gio/glocalfile.h>
+#include <gio/gfile.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include "kqueue-helper.h"
+#include "kqueue-utils.h"
+#include "kqueue-thread.h"
+#include "kqueue-missing.h"
+#include "kqueue-exclusions.h"
+
+#include "gkqueuedirectorymonitor.h"
+
+static gboolean kh_debug_enabled = FALSE;
+#define KH_W if (kh_debug_enabled) g_warning
+
+G_GNUC_INTERNAL G_LOCK_DEFINE (kqueue_lock);
+
+static GHashTable *subs_hash_table = NULL;
+G_GNUC_INTERNAL G_LOCK_DEFINE (hash_lock);
+
+static int kqueue_descriptor = -1;
+static int kqueue_socket_pair[] = {-1, -1};
+static pthread_t kqueue_thread;
+
+
+void _kh_file_appeared_cb (kqueue_sub *sub);
+
+/**
+ * accessor function for kqueue_descriptor
+ **/
+int
+get_kqueue_descriptor()
+{
+ return kqueue_descriptor;
+}
+
+/**
+ * convert_kqueue_events_to_gio:
+ * @flags: a set of kqueue filter flags
+ * @done: a pointer to #gboolean indicating that the
+ * conversion has been done (out)
+ *
+ * Translates kqueue filter flags into GIO event flags.
+ *
+ * Returns: a #GFileMonitorEvent
+ **/
+static GFileMonitorEvent
+convert_kqueue_events_to_gio (uint32_t flags, gboolean *done)
+{
+ g_assert (done != NULL);
+ *done = FALSE;
+
+ /* TODO: The following notifications should be emulated, if possible:
+ * - G_FILE_MONITOR_EVENT_PRE_UNMOUNT
+ */
+ if (flags & NOTE_DELETE)
+ {
+ *done = TRUE;
+ return G_FILE_MONITOR_EVENT_DELETED;
+ }
+ if (flags & NOTE_ATTRIB)
+ {
+ *done = TRUE;
+ return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
+ }
+ if (flags & (NOTE_WRITE | NOTE_EXTEND))
+ {
+ *done = TRUE;
+ return G_FILE_MONITOR_EVENT_CHANGED;
+ }
+ if (flags & NOTE_RENAME)
+ {
+ *done = TRUE;
+ return G_FILE_MONITOR_EVENT_MOVED;
+ }
+ if (flags & NOTE_REVOKE)
+ {
+ *done = TRUE;
+ return G_FILE_MONITOR_EVENT_UNMOUNTED;
+ }
+
+ /* done is FALSE */
+ return 0;
+}
+
+typedef struct {
+ kqueue_sub *sub;
+ GFileMonitor *monitor;
+} handle_ctx;
+
+/**
+ * handle_created:
+ * @udata: a pointer to user data (#handle_context).
+ * @path: file name of a new file.
+ * @inode: inode number of a new file.
+ *
+ * A callback function for the directory diff calculation routine,
+ * produces G_FILE_MONITOR_EVENT_CREATED event for a created file.
+ **/
+static void
+handle_created (void *udata, const char *path, ino_t inode)
+{
+ handle_ctx *ctx = NULL;
+ GFile *file = NULL;
+ gchar *fpath = NULL;
+
+ (void) inode;
+ ctx = (handle_ctx *) udata;
+ g_assert (udata != NULL);
+ g_assert (ctx->sub != NULL);
+ g_assert (ctx->monitor != NULL);
+
+ fpath = _ku_path_concat (ctx->sub->filename, path);
+ if (fpath == NULL)
+ {
+ KH_W ("Failed to allocate a string for a new event");
+ return;
+ }
+
+ file = g_file_new_for_path (fpath);
+ g_file_monitor_emit_event (ctx->monitor,
+ file,
+ NULL,
+ G_FILE_MONITOR_EVENT_CREATED);
+ g_free (fpath);
+ g_object_unref (file);
+}
+
+/**
+ * handle_deleted:
+ * @udata: a pointer to user data (#handle_context).
+ * @path: file name of the removed file.
+ * @inode: inode number of the removed file.
+ *
+ * A callback function for the directory diff calculation routine,
+ * produces G_FILE_MONITOR_EVENT_DELETED event for a deleted file.
+ **/
+static void
+handle_deleted (void *udata, const char *path, ino_t inode)
+{
+ handle_ctx *ctx = NULL;
+ GFile *file = NULL;
+ gchar *fpath = NULL;
+
+ (void) inode;
+ ctx = (handle_ctx *) udata;
+ g_assert (udata != NULL);
+ g_assert (ctx->sub != NULL);
+ g_assert (ctx->monitor != NULL);
+
+ fpath = _ku_path_concat (ctx->sub->filename, path);
+ if (fpath == NULL)
+ {
+ KH_W ("Failed to allocate a string for a new event");
+ return;
+ }
+
+ file = g_file_new_for_path (fpath);
+ g_file_monitor_emit_event (ctx->monitor,
+ file,
+ NULL,
+ G_FILE_MONITOR_EVENT_DELETED);
+ g_free (fpath);
+ g_object_unref (file);
+}
+
+/**
+ * handle_moved:
+ * @udata: a pointer to user data (#handle_context).
+ * @from_path: file name of the source file.
+ * @from_inode: inode number of the source file.
+ * @to_path: file name of the replaced file.
+ * @to_inode: inode number of the replaced file.
+ *
+ * A callback function for the directory diff calculation routine,
+ * produces G_FILE_MONITOR_EVENT_MOVED event on a move.
+ **/
+static void
+handle_moved (void *udata,
+ const char *from_path,
+ ino_t from_inode,
+ const char *to_path,
+ ino_t to_inode)
+{
+ handle_ctx *ctx = NULL;
+ GFile *file = NULL;
+ GFile *other = NULL;
+ gchar *path = NULL;
+ gchar *npath = NULL;
+
+ (void) from_inode;
+ (void) to_inode;
+
+ ctx = (handle_ctx *) udata;
+ g_assert (udata != NULL);
+ g_assert (ctx->sub != NULL);
+ g_assert (ctx->monitor != NULL);
+
+
+ path = _ku_path_concat (ctx->sub->filename, from_path);
+ npath = _ku_path_concat (ctx->sub->filename, to_path);
+ if (path == NULL || npath == NULL)
+ {
+ KH_W ("Failed to allocate strings for event");
+ return;
+ }
+
+ file = g_file_new_for_path (path);
+ other = g_file_new_for_path (npath);
+
+ if (ctx->sub->pair_moves)
+ {
+ g_file_monitor_emit_event (ctx->monitor,
+ file,
+ other,
+ G_FILE_MONITOR_EVENT_MOVED);
+ }
+ else
+ {
+ g_file_monitor_emit_event (ctx->monitor,
+ file,
+ NULL,
+ G_FILE_MONITOR_EVENT_DELETED);
+ g_file_monitor_emit_event (ctx->monitor,
+ other,
+ NULL,
+ G_FILE_MONITOR_EVENT_CREATED);
+ }
+
+ g_free (path);
+ g_free (npath);
+
+ g_object_unref (file);
+ g_object_unref (other);
+}
+
+
+/**
+ * handle_overwritten:
+ * @data: a pointer to user data (#handle_context).
+ * @path: file name of the overwritten file.
+ * @node: inode number of the overwritten file.
+ *
+ * A callback function for the directory diff calculation routine,
+ * produces G_FILE_MONITOR_EVENT_DELETED/CREATED event pair when
+ * an overwrite occurs in the directory (see dep-list for details).
+ **/
+static void
+handle_overwritten (void *udata, const char *path, ino_t inode)
+{
+ handle_ctx *ctx = NULL;
+ GFile *file = NULL;
+ gchar *fpath = NULL;
+
+ (void) inode;
+ ctx = (handle_ctx *) udata;
+ g_assert (udata != NULL);
+ g_assert (ctx->sub != NULL);
+ g_assert (ctx->monitor != NULL);
+
+ fpath = _ku_path_concat (ctx->sub->filename, path);
+ if (fpath == NULL)
+ {
+ KH_W ("Failed to allocate a string for a new event");
+ return;
+ }
+
+ file = g_file_new_for_path (fpath);
+ g_file_monitor_emit_event (ctx->monitor,
+ file,
+ NULL,
+ G_FILE_MONITOR_EVENT_DELETED);
+ g_file_monitor_emit_event (ctx->monitor,
+ file,
+ NULL,
+ G_FILE_MONITOR_EVENT_CREATED);
+
+ g_free (fpath);
+ g_object_unref (file);
+}
+
+static const traverse_cbs cbs = {
+ handle_created,
+ handle_deleted,
+ handle_moved,
+ handle_overwritten,
+ handle_moved,
+ NULL, /* many added */
+ NULL, /* many removed */
+ NULL, /* names updated */
+};
+
+
+void
+_kh_dir_diff (kqueue_sub *sub, GFileMonitor *monitor)
+{
+ dep_list *was;
+ handle_ctx ctx;
+
+ g_assert (sub != NULL);
+ g_assert (monitor != NULL);
+
+ memset (&ctx, 0, sizeof (handle_ctx));
+ ctx.sub = sub;
+ ctx.monitor = monitor;
+
+ was = sub->deps;
+ sub->deps = dl_listing (sub->filename);
+
+ dl_calculate (was, sub->deps, &cbs, &ctx);
+
+ dl_free (was);
+}
+
+
+/**
+ * process_kqueue_notifications:
+ * @gioc: unused.
+ * @cond: unused.
+ * @data: unused.
+ *
+ * Processes notifications, coming from the kqueue thread.
+ *
+ * Reads notifications from the command file descriptor, emits the
+ * "changed" event on the appropriate monitor.
+ *
+ * A typical GIO Channel callback function.
+ *
+ * Returns: %TRUE
+ **/
+static gboolean
+process_kqueue_notifications (GIOChannel *gioc,
+ GIOCondition cond,
+ gpointer data)
+{
+ struct kqueue_notification n;
+ kqueue_sub *sub = NULL;
+ GFileMonitor *monitor = NULL;
+ GFileMonitorEvent mask = 0;
+
+ g_assert (kqueue_socket_pair[0] != -1);
+ if (!_ku_read (kqueue_socket_pair[0], &n, sizeof (struct kqueue_notification)))
+ {
+ KH_W ("Failed to read a kqueue notification, error %d", errno);
+ return TRUE;
+ }
+
+ G_LOCK (hash_lock);
+ sub = (kqueue_sub *) g_hash_table_lookup (subs_hash_table, GINT_TO_POINTER (n.fd));
+ G_UNLOCK (hash_lock);
+
+ if (sub == NULL)
+ {
+ KH_W ("Got a notification for a deleted or non-existing subscription %d",
+ n.fd);
+ return TRUE;
+ }
+
+ monitor = G_FILE_MONITOR (sub->user_data);
+ g_assert (monitor != NULL);
+
+ if (n.flags & (NOTE_DELETE | NOTE_REVOKE))
+ {
+ if (sub->deps)
+ {
+ dl_free (sub->deps);
+ sub->deps = NULL;
+ }
+ _km_add_missing (sub);
+
+ if (!(n.flags & NOTE_REVOKE))
+ {
+ /* Note that NOTE_REVOKE is issued by the kqueue thread
+ * on EV_ERROR kevent. In this case, a file descriptor is
+ * already closed from the kqueue thread, no need to close
+ * it manually */
+ _kh_cancel_sub (sub);
+ }
+ }
+
+ if (sub->is_dir && n.flags & (NOTE_WRITE | NOTE_EXTEND))
+ {
+ _kh_dir_diff (sub, monitor);
+ n.flags &= ~(NOTE_WRITE | NOTE_EXTEND);
+ }
+
+ if (n.flags)
+ {
+ gboolean done = FALSE;
+ mask = convert_kqueue_events_to_gio (n.flags, &done);
+ if (done == TRUE)
+ {
+ GFile *file = g_file_new_for_path (sub->filename);
+ g_file_monitor_emit_event (monitor, file, NULL, mask);
+ g_object_unref (file);
+ }
+ }
+
+ return TRUE;
+}
+
+
+/**
+ * _kh_startup_impl:
+ * @unused: unused
+ *
+ * Kqueue backend startup code. Should be called only once.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ **/
+static gpointer
+_kh_startup_impl (gpointer unused)
+{
+ GIOChannel *channel = NULL;
+ gboolean result = FALSE;
+
+ kqueue_descriptor = kqueue ();
+ result = (kqueue_descriptor != -1);
+ if (!result)
+ {
+ KH_W ("Failed to initialize kqueue\n!");
+ return GINT_TO_POINTER (FALSE);
+ }
+
+ result = socketpair (AF_UNIX, SOCK_STREAM, 0, kqueue_socket_pair);
+ if (result != 0)
+ {
+ KH_W ("Failed to create socket pair\n!");
+ return GINT_TO_POINTER (FALSE) ;
+ }
+
+ result = pthread_create (&kqueue_thread,
+ NULL,
+ _kqueue_thread_func,
+ &kqueue_socket_pair[1]);
+ if (result != 0)
+ {
+ KH_W ("Failed to run kqueue thread\n!");
+ return GINT_TO_POINTER (FALSE);
+ }
+
+ _km_init (_kh_file_appeared_cb);
+
+ channel = g_io_channel_unix_new (kqueue_socket_pair[0]);
+ g_io_add_watch (channel, G_IO_IN, process_kqueue_notifications, NULL);
+
+ subs_hash_table = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ KH_W ("started gio kqueue backend\n");
+ return GINT_TO_POINTER (TRUE);
+}
+
+
+/**
+ * _kh_startup:
+ * Kqueue backend initialization.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ **/
+gboolean
+_kh_startup (void)
+{
+ static GOnce init_once = G_ONCE_INIT;
+ g_once (&init_once, _kh_startup_impl, NULL);
+ return GPOINTER_TO_INT (init_once.retval);
+}
+
+
+/**
+ * _kh_start_watching:
+ * @sub: a #kqueue_sub
+ *
+ * Starts watching on a subscription.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ **/
+gboolean
+_kh_start_watching (kqueue_sub *sub)
+{
+ g_assert (kqueue_socket_pair[0] != -1);
+ g_assert (sub != NULL);
+ g_assert (sub->filename != NULL);
+
+ /* kqueue requires a file descriptor to monitor. Sad but true */
+ sub->fd = open (sub->filename, O_RDONLY);
+
+ if (sub->fd == -1)
+ {
+ KH_W ("failed to open file %s (error %d)", sub->filename, errno);
+ return FALSE;
+ }
+
+ _ku_file_information (sub->fd, &sub->is_dir, NULL);
+ if (sub->is_dir)
+ {
+ /* I know, it is very bad to make such decisions in this way and here.
+ * We already do have an user_data at the #kqueue_sub, and it may point to
+ * GKqueueFileMonitor or GKqueueDirectoryMonitor. For a directory case,
+ * we need to scan in contents for the further diffs. Ideally this process
+ * should be delegated to the GKqueueDirectoryMonitor, but for now I will
+ * do it in a dirty way right here. */
+ if (sub->deps)
+ dl_free (sub->deps);
+
+ sub->deps = dl_listing (sub->filename);
+ }
+
+ G_LOCK (hash_lock);
+ g_hash_table_insert (subs_hash_table, GINT_TO_POINTER (sub->fd), sub);
+ G_UNLOCK (hash_lock);
+
+ _kqueue_thread_push_fd (sub->fd);
+
+ /* Bump the kqueue thread. It will pick up a new sub entry to monitor */
+ if (!_ku_write (kqueue_socket_pair[0], "A", 1))
+ KH_W ("Failed to bump the kqueue thread (add fd, error %d)", errno);
+ return TRUE;
+}
+
+
+/**
+ * _kh_add_sub:
+ * @sub: a #kqueue_sub
+ *
+ * Adds a subscription for monitoring.
+ *
+ * This funciton tries to start watching a subscription with
+ * _kh_start_watching(). On failure, i.e. when a file does not exist yet,
+ * the subscription will be added to a list of missing files to continue
+ * watching when the file will appear.
+ *
+ * Returns: %TRUE
+ **/
+gboolean
+_kh_add_sub (kqueue_sub *sub)
+{
+ g_assert (sub != NULL);
+
+ if (!_kh_start_watching (sub))
+ _km_add_missing (sub);
+
+ return TRUE;
+}
+
+
+/**
+ * _kh_cancel_sub:
+ * @sub a #kqueue_sub
+ *
+ * Stops monitoring on a subscription.
+ *
+ * Returns: %TRUE
+ **/
+gboolean
+_kh_cancel_sub (kqueue_sub *sub)
+{
+ gboolean missing = FALSE;
+ g_assert (kqueue_socket_pair[0] != -1);
+ g_assert (sub != NULL);
+
+ G_LOCK (hash_lock);
+ missing = !g_hash_table_remove (subs_hash_table, GINT_TO_POINTER (sub->fd));
+ G_UNLOCK (hash_lock);
+
+ if (missing)
+ {
+ /* If there were no fd for this subscription, file is still
+ * missing. */
+ KH_W ("Removing subscription from missing");
+ _km_remove (sub);
+ }
+ else
+ {
+ /* fd will be closed in the kqueue thread */
+ _kqueue_thread_remove_fd (sub->fd);
+
+ /* Bump the kqueue thread. It will pick up a new sub entry to remove*/
+ if (!_ku_write (kqueue_socket_pair[0], "R", 1))
+ KH_W ("Failed to bump the kqueue thread (remove fd, error %d)", errno);
+ }
+
+ return TRUE;
+}
+
+
+/**
+ * _kh_file_appeared_cb:
+ * @sub: a #kqueue_sub
+ *
+ * A callback function for kqueue-missing subsystem.
+ *
+ * Signals that a missing file has finally appeared in the filesystem.
+ * Emits %G_FILE_MONITOR_EVENT_CREATED.
+ **/
+void
+_kh_file_appeared_cb (kqueue_sub *sub)
+{
+ GFile* child;
+
+ g_assert (sub != NULL);
+ g_assert (sub->filename);
+
+ if (!g_file_test (sub->filename, G_FILE_TEST_EXISTS))
+ return;
+
+ child = g_file_new_for_path (sub->filename);
+
+ g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
+ child,
+ NULL,
+ G_FILE_MONITOR_EVENT_CREATED);
+
+ g_object_unref (child);
+}
diff --git a/gio/kqueue/kqueue-helper.h b/gio/kqueue/kqueue-helper.h
new file mode 100644
index 000000000..fc33a8dd1
--- /dev/null
+++ b/gio/kqueue/kqueue-helper.h
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#ifndef __KQUEUE_HELPER_H
+#define __KQUEUE_HELPER_H
+
+#include "kqueue-sub.h"
+#include <gio/gfilemonitor.h>
+
+gboolean _kh_startup (void);
+gboolean _kh_add_sub (kqueue_sub *sub);
+gboolean _kh_cancel_sub (kqueue_sub *sub);
+
+gboolean _kh_start_watching (kqueue_sub *sub);
+
+void _kh_dir_diff (kqueue_sub *sub, GFileMonitor *monitor);
+
+#endif /* __KQUEUE_HELPER_H */
diff --git a/gio/kqueue/kqueue-missing.c b/gio/kqueue/kqueue-missing.c
new file mode 100644
index 000000000..797f22728
--- /dev/null
+++ b/gio/kqueue/kqueue-missing.c
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#include <glib.h>
+
+#include "kqueue-helper.h"
+#include "kqueue-sub.h"
+#include "kqueue-missing.h"
+
+
+#define SCAN_MISSING_TIME 4 /* 1/4 Hz */
+
+static gboolean km_scan_missing (gpointer user_data);
+
+static gboolean km_debug_enabled = FALSE;
+#define KM_W if (km_debug_enabled) g_warning
+
+static GSList *missing_subs_list = NULL;
+G_GNUC_INTERNAL G_LOCK_DEFINE (missing_lock);
+
+static volatile gboolean scan_missing_running = FALSE;
+static on_create_cb file_appeared_callback;
+
+
+/**
+ * _km_init:
+ * @cb: a callback function. It will be called when a watched file
+ * will appear.
+ *
+ * Initialize the kqueue-missing module (optional).
+ **/
+void
+_km_init (on_create_cb cb)
+{
+ file_appeared_callback = cb;
+}
+
+
+/**
+ * _km_add_missing:
+ * @sub: a #kqueue_sub
+ *
+ * Adds a subscription to the missing files list.
+ **/
+void
+_km_add_missing (kqueue_sub *sub)
+{
+ G_LOCK (missing_lock);
+ if (g_slist_find (missing_subs_list, sub))
+ {
+ KM_W ("asked to add %s to missing list but it's already on the list!\n", sub->filename);
+ return;
+ }
+
+ KM_W ("adding %s to missing list\n", sub->filename);
+ missing_subs_list = g_slist_prepend (missing_subs_list, sub);
+ G_UNLOCK (missing_lock);
+
+ if (!scan_missing_running)
+ {
+ scan_missing_running = TRUE;
+ g_timeout_add_seconds (SCAN_MISSING_TIME, km_scan_missing, NULL);
+ }
+}
+
+
+/**
+ * km_scan_missing:
+ * @user_data: unused
+ *
+ * The core missing files watching routine.
+ *
+ * Traverses through a list of missing files, tries to start watching each with
+ * kqueue, removes the appropriate entry and invokes a user callback if the file
+ * has appeared.
+ *
+ * Returns: %FALSE if no missing files left, %TRUE otherwise.
+ **/
+static gboolean
+km_scan_missing (gpointer user_data)
+{
+ GSList *head;
+ GSList *not_missing = NULL;
+ gboolean retval = FALSE;
+
+ G_LOCK (missing_lock);
+
+ if (missing_subs_list)
+ KM_W ("we have a job");
+
+ for (head = missing_subs_list; head; head = head->next)
+ {
+ kqueue_sub *sub = (kqueue_sub *) head->data;
+ g_assert (sub != NULL);
+ g_assert (sub->filename != NULL);
+
+ if (_kh_start_watching (sub))
+ {
+ KM_W ("file %s now exists, starting watching", sub->filename);
+ if (file_appeared_callback)
+ file_appeared_callback (sub);
+ not_missing = g_slist_prepend (not_missing, head);
+ }
+ }
+
+ for (head = not_missing; head; head = head->next)
+ {
+ GSList *link = (GSList *) head->data;
+ missing_subs_list = g_slist_remove_link (missing_subs_list, link);
+ }
+ g_slist_free (not_missing);
+
+ if (missing_subs_list == NULL)
+ {
+ scan_missing_running = FALSE;
+ retval = FALSE;
+ }
+ else
+ retval = TRUE;
+
+ G_UNLOCK (missing_lock);
+ return retval;
+}
+
+
+/**
+ * _km_remove:
+ * @sub: a #kqueue_sub
+ *
+ * Removes a subscription from a list of missing files.
+ **/
+void
+_km_remove (kqueue_sub *sub)
+{
+ G_LOCK (missing_lock);
+ missing_subs_list = g_slist_remove (missing_subs_list, sub);
+ G_UNLOCK (missing_lock);
+}
diff --git a/gio/kqueue/kqueue-missing.h b/gio/kqueue/kqueue-missing.h
new file mode 100644
index 000000000..704a6f300
--- /dev/null
+++ b/gio/kqueue/kqueue-missing.h
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#ifndef __G_KQUEUE_MISSING_H
+#define __G_KQUEUE_MISSING_H
+
+typedef void (*on_create_cb) (kqueue_sub *);
+
+void _km_init (on_create_cb cb);
+void _km_add_missing (kqueue_sub *sub);
+void _km_remove (kqueue_sub *sub);
+
+#endif /* __G_KQUEUE_MISSING_H */
diff --git a/gio/kqueue/kqueue-sub.c b/gio/kqueue/kqueue-sub.c
new file mode 100644
index 000000000..8b864ba90
--- /dev/null
+++ b/gio/kqueue/kqueue-sub.c
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#include <glib.h>
+
+#include "kqueue-sub.h"
+
+static gboolean ks_debug_enabled = FALSE;
+#define KS_W if (ks_debug_enabled) g_warning
+
+/**
+ * _kh_sub_new:
+ * @filename: a file path to monitor (will be copied)
+ * @pair_moves: pair moves flag. Refer to #GFileMonitorFlags documentation.
+ * @user_data: user-supplied poiner.
+ *
+ * Creates a new subscription object.
+ *
+ * Returns: a pointer to a created subscription object.
+ **/
+kqueue_sub*
+_kh_sub_new (const gchar *filename,
+ gboolean pair_moves,
+ gpointer user_data)
+{
+ kqueue_sub *sub = g_slice_new (kqueue_sub);
+ g_assert (sub != NULL);
+
+ sub->filename = g_strdup (filename);
+ sub->pair_moves = pair_moves;
+ sub->user_data = user_data;
+ sub->fd = -1;
+ sub->deps = NULL;
+ /* I think that having such flag in the subscription is not good */
+ sub->is_dir = 0;
+
+ KS_W ("new subscription for %s being setup\n", sub->filename);
+
+ return sub;
+}
+
+
+/**
+ * _kh_sub_free:
+ * @sub: a #kqueue_sub
+ *
+ * Frees a subscription object and all its associated memory.
+ **/
+void
+_kh_sub_free (kqueue_sub *sub)
+{
+ if (sub->deps)
+ {
+ dl_free (sub->deps);
+ sub->deps = NULL;
+ }
+
+ g_free (sub->filename);
+ g_slice_free (kqueue_sub, sub);
+}
diff --git a/gio/kqueue/kqueue-sub.h b/gio/kqueue/kqueue-sub.h
new file mode 100644
index 000000000..215c49142
--- /dev/null
+++ b/gio/kqueue/kqueue-sub.h
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#ifndef __KQUEUE_SUB_H
+#define __KQUEUE_SUB_H
+
+#include "dep-list.h"
+
+/**
+ * kqueue_sub:
+ * @filename: a name of the file to monitor
+ * @user_data: the pointer to user data
+ * @pair_moves: unused (currently not implemented)
+ * @fd: the associated file descriptor (used by kqueue)
+ *
+ * Represents a subscription on a file or directory.
+ */
+typedef struct
+{
+ gchar* filename;
+ gpointer user_data;
+ gboolean pair_moves;
+ int fd;
+ dep_list* deps;
+ int is_dir;
+} kqueue_sub;
+
+kqueue_sub* _kh_sub_new (const gchar* filename, gboolean pair_moves, gpointer user_data);
+void _kh_sub_free (kqueue_sub* sub);
+
+#endif /* __KQUEUE_SUB_H */
diff --git a/gio/kqueue/kqueue-thread.c b/gio/kqueue/kqueue-thread.c
new file mode 100644
index 000000000..4b492e021
--- /dev/null
+++ b/gio/kqueue/kqueue-thread.c
@@ -0,0 +1,310 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#include "config.h"
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <errno.h>
+#include <glib.h>
+
+#include "kqueue-thread.h"
+#include "kqueue-sub.h"
+#include "kqueue-utils.h"
+
+static gboolean kt_debug_enabled = FALSE;
+#define KT_W if (kt_debug_enabled) g_warning
+
+static GQueue pick_up_fds_queue = G_QUEUE_INIT;
+G_GNUC_INTERNAL G_LOCK_DEFINE (pick_up_lock);
+
+static GSList *remove_fds_list = NULL;
+G_GNUC_INTERNAL G_LOCK_DEFINE (remove_lock);
+
+/* GIO does not have analogues for NOTE_LINK and(?) NOTE_REVOKE, so
+ * we do not ask kqueue() to watch for these events for now. */
+const uint32_t KQUEUE_VNODE_FLAGS =
+ NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME;
+
+extern int get_kqueue_descriptor(void);
+
+/**
+ * _kqueue_thread_collect_fds:
+ * @events: a #kevents - the list of events to monitor. Will be extended
+ * with new items.
+ *
+ * Picks up new file descriptors for monitoring from a global queue.
+ *
+ * To add new items to the list, use _kqueue_thread_push_fd().
+ **/
+static void
+_kqueue_thread_collect_fds (kevents *events)
+{
+ g_assert (events != NULL);
+ gint length = 0;
+
+ G_LOCK (pick_up_lock);
+ if ((length = g_queue_get_length (&pick_up_fds_queue)) != 0)
+ {
+ gpointer fdp = NULL;
+ kevents_extend_sz (events, length);
+
+ while ((fdp = g_queue_pop_head (&pick_up_fds_queue)) != NULL)
+ {
+ struct kevent *pevent = &events->memory[events->kq_size++];
+ EV_SET (pevent,
+ GPOINTER_TO_INT (fdp),
+ EVFILT_VNODE,
+ EV_ADD | EV_ENABLE | EV_ONESHOT,
+ KQUEUE_VNODE_FLAGS,
+ 0,
+ 0);
+ }
+ }
+ G_UNLOCK (pick_up_lock);
+}
+
+
+/**
+ * _kqueue_thread_cleanup_fds:
+ * @events: a #kevents -- list of events to monitor. Cancelled
+ * subscriptions will be removed from it, and its size
+ * probably will be reduced.
+ *
+ * Removes file descriptors from monitoring.
+ *
+ * This function will pick up file descriptors from a global list
+ * to cancel monitoring on them. The list will be freed then.
+ *
+ * To add new items to the list, use _kqueue_thread_remove_fd().
+ **/
+static void
+_kqueue_thread_cleanup_fds (kevents *events)
+{
+ g_assert (events != NULL);
+
+ G_LOCK (remove_lock);
+ if (remove_fds_list)
+ {
+ size_t oldsize = events->kq_size;
+ int i, j;
+
+ for (i = 1, j = 1; i < oldsize; i++)
+ {
+ int fd = events->memory[i].ident;
+ GSList *elem = g_slist_find (remove_fds_list, GINT_TO_POINTER (fd));
+ if (elem == NULL)
+ {
+ if (i != j)
+ events->memory[j] = events->memory[i];
+ ++j;
+ }
+ else if (close (fd) == -1)
+ KT_W ("Failed to close fd %d, error %d", fd, errno);
+ }
+
+ KT_W ("FD Clean up complete, kq_size now %d\n", j);
+ events->kq_size = j;
+ kevents_reduce (events);
+ g_slist_free (remove_fds_list);
+ remove_fds_list = NULL;
+ }
+ G_UNLOCK (remove_lock);
+}
+
+
+/**
+ * _kqueue_thread_drop_fd:
+ * @events: a #kevents -- list of events to monitor. Cancelled
+ * subscriptions will be removed from it, and its size
+ * probably will be reduced.
+ *
+ * Removes a concrete file descriptor from monitoring.
+ **/
+static void
+_kqueue_thread_drop_fd (kevents *events, int fd)
+{
+ g_assert (events != NULL);
+
+ int i;
+ for (i = 1; i < events->kq_size; i++)
+ {
+ if (events->memory[i].ident == fd)
+ {
+ if (close (fd) == -1)
+ KT_W ("Failed to close fd %d, error %d", fd, errno);
+
+ events->memory[i] = events->memory[--events->kq_size];
+ return;
+ }
+ }
+}
+
+/**
+ * _kqueue_thread_func:
+ * @arg: a pointer to int -- control file descriptor.
+ *
+ * The thread communicates with the outside world through a so-called
+ * command file descriptor. The thread reads control commands from it
+ * and writes the notifications into it.
+ *
+ * Control commands are single-byte characters:
+ * <itemizedlist>
+ * <listitem>
+ * 'A' - pick up new file descriptors to monitor
+ * </listitem>
+ * <listitem>
+ * 'R' - remove some descriptors from monitoring.
+ * </listitem>
+ * </itemizedlist>
+ *
+ * For details, see _kqueue_thread_collect_fds() and
+ * _kqueue_thread_cleanup_fds().
+ *
+ * Notifications, that thread writes into the command file descriptor,
+ * are represented with #kqueue_notification objects.
+ *
+ * Returns: %NULL
+ **/
+void*
+_kqueue_thread_func (void *arg)
+{
+ int fd, kqueue_descriptor;
+ kevents waiting;
+
+ g_assert (arg != NULL);
+ kevents_init_sz (&waiting, 1);
+
+ fd = *(int *) arg;
+
+ kqueue_descriptor = get_kqueue_descriptor();
+ if (kqueue_descriptor == -1)
+ {
+ KT_W ("fatal: kqueue is not initialized!\n");
+ return NULL;
+ }
+
+ EV_SET (&waiting.memory[0],
+ fd,
+ EVFILT_READ,
+ EV_ADD | EV_ENABLE | EV_ONESHOT,
+ NOTE_LOWAT,
+ 1,
+ 0);
+ waiting.kq_size = 1;
+
+ for (;;)
+ {
+ /* TODO: Provide more items in the `eventlist' to kqueue(2).
+ * Currently the backend takes notifications from the kernel one
+ * by one, i.e. there will be a lot of system calls and context
+ * switches when the application will monitor a lot of files with
+ * high filesystem activity on each. */
+
+ struct kevent received;
+ KT_W ("Watching for %zi items", waiting.kq_size);
+ int ret = kevent (kqueue_descriptor, waiting.memory, waiting.kq_size, &received, 1, NULL);
+ int kevent_errno = errno;
+ KT_W ("Awoken.");
+
+ if (ret == -1)
+ {
+ KT_W ("kevent failed: %d", kevent_errno);
+ if (kevent_errno == EINTR)
+ continue;
+ else
+ return NULL;
+ }
+
+ if (received.ident == fd)
+ {
+ char c;
+ if (!_ku_read (fd, &c, 1))
+ {
+ KT_W ("Failed to read command, error %d", errno);
+ continue;
+ }
+ if (c == 'A')
+ _kqueue_thread_collect_fds (&waiting);
+ else if (c == 'R')
+ _kqueue_thread_cleanup_fds (&waiting);
+ }
+ else
+ {
+ struct kqueue_notification kn;
+ kn.fd = received.ident;
+
+ if (received.flags & EV_ERROR)
+ {
+ kn.flags = NOTE_REVOKE;
+ _kqueue_thread_drop_fd (&waiting, received.ident);
+ }
+ else
+ kn.flags = (received.fflags & ~NOTE_REVOKE);
+
+ if (!_ku_write (fd, &kn, sizeof (struct kqueue_notification)))
+ KT_W ("Failed to write a kqueue notification, error %d", errno);
+ }
+ }
+ kevents_free (&waiting);
+ return NULL;
+}
+
+
+/**
+ * _kqueue_thread_push_fd:
+ * @fd: a file descriptor
+ *
+ * Puts a new file descriptor into the pick up list for monitroing.
+ *
+ * The kqueue thread will not start monitoring on it immediately, it
+ * should be bumped via its command file descriptor manually.
+ * See kqueue_thread() and _kqueue_thread_collect_fds() for details.
+ **/
+void
+_kqueue_thread_push_fd (int fd)
+{
+ G_LOCK (pick_up_lock);
+ g_queue_push_tail (&pick_up_fds_queue, GINT_TO_POINTER (fd));
+ G_UNLOCK (pick_up_lock);
+}
+
+
+/**
+ * _kqueue_thread_remove_fd:
+ * @fd: a file descriptor
+ *
+ * Puts a new file descriptor into the remove list to cancel monitoring
+ * on it.
+ *
+ * The kqueue thread will not stop monitoring on it immediately, it
+ * should be bumped via its command file descriptor manually.
+ * See kqueue_thread() and _kqueue_thread_collect_fds() for details.
+ **/
+void
+_kqueue_thread_remove_fd (int fd)
+{
+ G_LOCK (remove_lock);
+ remove_fds_list = g_slist_prepend (remove_fds_list, GINT_TO_POINTER (fd));
+ G_UNLOCK (remove_lock);
+}
diff --git a/gio/kqueue/kqueue-thread.h b/gio/kqueue/kqueue-thread.h
new file mode 100644
index 000000000..0e46a0d69
--- /dev/null
+++ b/gio/kqueue/kqueue-thread.h
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#ifndef __KQUEUE_THREAD_H
+#define __KQUEUE_THREAD_H
+
+/**
+ * kqueue_notification:
+ * @fd: file descriptor, on which an activity has occured.
+ * @flags: kqueue event flags, see man kevent(2).
+ *
+ * Represents an event occured on a file descriptor. Used for marshalling from
+ * kqueue thread to its subscribers.
+ */
+struct kqueue_notification {
+ /*< public >*/
+ int fd;
+ uint32_t flags;
+};
+
+
+void* _kqueue_thread_func (void *arg);
+void _kqueue_thread_push_fd (int fd);
+void _kqueue_thread_remove_fd (int fd);
+
+#endif /* __KQUEUE_SUB_H */
diff --git a/gio/kqueue/kqueue-utils.c b/gio/kqueue/kqueue-utils.c
new file mode 100644
index 000000000..00b5c262b
--- /dev/null
+++ b/gio/kqueue/kqueue-utils.c
@@ -0,0 +1,242 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <string.h>
+#include <glib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include "kqueue-utils.h"
+
+static gboolean ku_debug_enabled = FALSE;
+#define KU_W if (ku_debug_enabled) g_warning
+
+
+
+#define KEVENTS_EXTEND_COUNT 10
+
+
+/**
+ * kevents_init_sz:
+ * @kv: a #kevents
+ * @n_initial: the initial preallocated memory size. If it is less than
+ * %KEVENTS_EXTEND_COUNT, this value will be used instead.
+ *
+ * Initializes a #kevents object.
+ **/
+void
+kevents_init_sz (kevents *kv, gsize n_initial)
+{
+ g_assert (kv != NULL);
+
+ memset (kv, 0, sizeof (kevents));
+
+ if (n_initial < KEVENTS_EXTEND_COUNT)
+ n_initial = KEVENTS_EXTEND_COUNT;
+
+ kv->memory = g_new0 (struct kevent, n_initial);
+ kv->kq_allocated = n_initial;
+}
+
+
+/**
+ * kevents_extend_sz:
+ * @kv: a #kevents
+ * @n_new: the number of new objects to be added
+ *
+ * Extends the allocated memory, if needed.
+ **/
+void
+kevents_extend_sz (kevents *kv, gsize n_new)
+{
+ g_assert (kv != NULL);
+
+ if (kv->kq_size + n_new <= kv->kq_allocated)
+ return;
+
+ kv->kq_allocated += (n_new + KEVENTS_EXTEND_COUNT);
+ kv->memory = g_renew (struct kevent, kv->memory, kv->kq_allocated);
+}
+
+
+/**
+ * kevents_reduce:
+ * @kv: a #kevents
+ *
+ * Reduces the allocated heap size, if needed.
+ *
+ * If the allocated heap size is >= 3*used
+ * and 2*used >= %KEVENTS_EXTEND_COUNT, reduce it to 2*used.
+ **/
+void
+kevents_reduce (kevents *kv)
+{
+ g_assert (kv != NULL);
+ gsize candidate_sz;
+
+ if (kv->kq_size == 0 || kv->kq_allocated == 0 || kv->memory == NULL)
+ return;
+
+ candidate_sz = 2 * kv->kq_size;
+
+ if (((double) kv->kq_allocated / kv->kq_size) >= 3 &&
+ candidate_sz >= KEVENTS_EXTEND_COUNT)
+ {
+ kv->kq_allocated = candidate_sz;
+ kv->memory = g_renew (struct kevent, kv->memory, kv->kq_allocated);
+ }
+}
+
+
+/**
+ * kevents_free:
+ * @kv: a #kevents
+ *
+ * Resets the kevents object and frees all the associated memory.
+ **/
+void
+kevents_free (kevents *kv)
+{
+ g_assert (kv != NULL);
+
+ g_free (kv->memory);
+ memset (kv, 0, sizeof (kevents));
+}
+
+
+#define SAFE_GENERIC_OP(fcn, fd, data, size) \
+ while (size > 0) \
+ { \
+ gsize retval = fcn (fd, data, size); \
+ if (retval == -1) \
+ { \
+ if (errno == EINTR) \
+ continue; \
+ else \
+ return FALSE; \
+ } \
+ size -= retval; \
+ data += retval; \
+ } \
+ return TRUE;
+
+
+/**
+ * _ku_read:
+ * @fd: a file descriptor
+ * @data: the destination buffer
+ * @size: how many bytes to read
+ *
+ * A ready-to-EINTR version of read().
+ *
+ * This function expects to work with a blocking socket.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise
+ **/
+gboolean
+_ku_read (int fd, gpointer data, gsize size)
+{
+ SAFE_GENERIC_OP (read, fd, data, size);
+}
+
+
+/**
+ * _ku_write:
+ * @fd: a file descriptor
+ * @data: the buffer to write
+ * @size: how many bytes to write
+ *
+ * A ready-to-EINTR version of write().
+ *
+ * This function expects to work with a blocking socket.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise
+ **/
+gboolean
+_ku_write (int fd, gconstpointer data, gsize size)
+{
+ SAFE_GENERIC_OP (write, fd, data, size);
+}
+
+
+/**
+ * Get some file information by its file descriptor.
+ *
+ * @param[in] fd A file descriptor.
+ * @param[out] is_dir A flag indicating directory.
+ * @param[out] inode A file's inode number.
+ **/
+void
+_ku_file_information (int fd, int *is_dir, ino_t *inode)
+{
+ g_assert (fd != -1);
+
+ struct stat st;
+ memset (&st, 0, sizeof (struct stat));
+
+ if (fstat (fd, &st) == -1)
+ {
+ KU_W ("fstat failed, assuming it is just a file");
+ is_dir = NULL;
+ return;
+ }
+
+ if (is_dir != NULL)
+ *is_dir = ((st.st_mode & S_IFDIR) == S_IFDIR) ? 1 : 0;
+
+ if (inode != NULL)
+ *inode = st.st_ino;
+}
+
+/**
+ * Create a file path using its name and a path to its directory.
+ *
+ * @param[in] dir A path to a file directory. May end with a '/'.
+ * @param[in] file File name.
+ * @return A concatenated path. Should be freed with free().
+ **/
+gchar*
+_ku_path_concat (const gchar *dir, const gchar *file)
+{
+ int dir_len = strlen (dir);
+ int file_len = strlen (file);
+
+ char *path = g_malloc (dir_len + file_len + 2);
+ if (path == NULL)
+ {
+ KU_W ("Failed to allocate memory path for concatenation");
+ return NULL;
+ }
+
+ strcpy (path, dir);
+
+ if (dir[dir_len - 1] != '/') {
+ ++dir_len;
+ path[dir_len - 1] = '/';
+ }
+
+ strcpy (path + dir_len, file);
+ return path;
+}
+
diff --git a/gio/kqueue/kqueue-utils.h b/gio/kqueue/kqueue-utils.h
new file mode 100644
index 000000000..2c4f2c31e
--- /dev/null
+++ b/gio/kqueue/kqueue-utils.h
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*******************************************************************************/
+
+#ifndef __KQUEUE_UTILS_H
+#define __KQUEUE_UTILS_H
+
+#include <sys/types.h> /* ino_t */
+
+/**
+ * kqueue_notification:
+ * @memory: a pointer to the allocated memory
+ * @kq_size: the number of used items
+ * @kq_allocated: the number of allocated items
+ *
+ * Represents a pool of (struct kevent) objects.
+ */
+typedef struct {
+ struct kevent *memory;
+ gsize kq_size;
+ gsize kq_allocated;
+} kevents;
+
+void kevents_init_sz (kevents *kv, gsize n_initial);
+void kevents_extend_sz (kevents *kv, gsize n_new);
+void kevents_reduce (kevents *kv);
+void kevents_free (kevents *kv);
+
+
+gboolean _ku_read (int fd, gpointer data, gsize size);
+gboolean _ku_write (int fd, gconstpointer data, gsize size);
+
+void _ku_file_information (int fd, int *is_dir, ino_t *inode);
+
+gchar* _ku_path_concat (const gchar *dir, const gchar *file);
+
+
+
+#endif /* __KQUEUE_UTILS_H */