AM_CONDITIONAL(HAVE_INOTIFY, [test "$inotify_support" = "yes"])
+dnl ****************************
+dnl ** Check for kqueue (GIO) **
+dnl ****************************
+kqueue_support=no
+AC_CHECK_HEADERS([sys/event.h],
+[
+ AC_CHECK_FUNCS(kqueue kevent, [kqueue_support=yes])
+])
+
+AM_CONDITIONAL(HAVE_KQUEUE, [test "$kqueue_support" = "yes"])
+
dnl *********************************
dnl ** Check for Solaris FEN (GIO) **
dnl *********************************
gio/gdbus-2.0/codegen/config.py
gio/xdgmime/Makefile
gio/inotify/Makefile
+gio/kqueue/Makefile
gio/fen/Makefile
gio/fam/Makefile
gio/win32/Makefile
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
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);
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 ());
--- /dev/null
+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
--- /dev/null
+/*******************************************************************************
+ 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);
+}
+
--- /dev/null
+/*******************************************************************************
+ 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__ */
--- /dev/null
+/*******************************************************************************
+ 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;
+}
--- /dev/null
+/*******************************************************************************
+ 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__ */
--- /dev/null
+/*******************************************************************************
+ 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;
+}
--- /dev/null
+/*******************************************************************************
+ 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__ */
--- /dev/null
+/*******************************************************************************
+ 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;
+}
--- /dev/null
+/*******************************************************************************
+ 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 */
--- /dev/null
+/*******************************************************************************
+ 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);
+}
--- /dev/null
+/*******************************************************************************
+ 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 */
--- /dev/null
+/*******************************************************************************
+ 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);
+}
--- /dev/null
+/*******************************************************************************
+ 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 */
--- /dev/null
+/*******************************************************************************
+ 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);
+}
--- /dev/null
+/*******************************************************************************
+ 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 */
--- /dev/null
+/*******************************************************************************
+ 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);
+}
--- /dev/null
+/*******************************************************************************
+ 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 */
--- /dev/null
+/*******************************************************************************
+ 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;
+}
+
--- /dev/null
+/*******************************************************************************
+ 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 */