|
|
/* | /* |
|
* gam_kqueue.c - a kqueue(2) Gamin backend |
|
* |
|
* Notes: |
|
* |
|
* * http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?coll=0650&db=bks&fname=/SGI_Developer/books/IIDsktp_IG/sgi_html/ch08.html |
|
* states that FAM does not follow monitored symbolic links: we |
|
* do the same (note that regarding |
|
* http://oss.sgi.com/bugzilla/show_bug.cgi?id=405, we do what |
|
* FAM should do: we do not call g_dir_open() if the file is a |
|
* symbolic link). |
|
* |
|
* * kqueue cannot monitor files residing on anything but a UFS |
|
* file system. If kqueue cannot monitor a file, this backend |
|
* will poll it periodically. |
|
* |
|
* * Monitoring a file with kqueue prevents the file system it |
|
* resides on from being unmounted, because kqueue can only |
|
* monitor open files. |
|
* |
|
* * The creation of missing monitored files is detected by |
|
* performing a lstat() every second. Although using kqueue to |
|
* monitor the parent directory is technically feasible, it |
|
* would introduce a lot of complexity in the code. |
|
* |
|
* * In light of the previous points, it appears that: |
|
* |
|
* - kqueue needs to be augmented with a filename-based |
|
* monitoring facility; |
|
* |
|
* - kqueue needs to be moved out the UFS code. |
|
* |
* Copyright (C) 2005 Joe Marcus Clarke <marcus@FreeBSD.org> | * Copyright (C) 2005 Joe Marcus Clarke <marcus@FreeBSD.org> |
|
* Copyright (C) 2005 Jean-Yves Lefort <jylefort@FreeBSD.org> |
* | * |
* This library is free software; you can redistribute it and/or | * This library is free software; you can redistribute it and/or |
* modify it under the terms of the GNU Lesser General Public | * modify it under the terms of the GNU Lesser General Public |
|
|
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
*/ | */ |
| |
|
#include "config.h" |
#include "server_config.h" |
#include <string.h> |
|
#include <fcntl.h> |
|
#include <unistd.h> |
#include <sys/types.h> | #include <sys/types.h> |
|
#include <sys/sysctl.h> |
|
#include <sys/stat.h> |
#include <sys/event.h> | #include <sys/event.h> |
#include <sys/time.h> | #include <sys/time.h> |
#include <fcntl.h> |
#include <errno.h> |
#include <sys/ioctl.h> |
|
#include <signal.h> |
|
#include <unistd.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <glib.h> |
|
#include "gam_error.h" | #include "gam_error.h" |
#include "gam_kqueue.h" | #include "gam_kqueue.h" |
#include "gam_tree.h" |
|
#include "gam_event.h" | #include "gam_event.h" |
#include "gam_server.h" | #include "gam_server.h" |
#include "gam_event.h" |
|
| |
typedef struct { |
/*** tunable constants, modify to tweak the backend aggressivity *************/ |
char *path; |
|
int fd; |
|
int refcount; |
|
gboolean isdir; |
|
GList *subs; |
|
GSList *dirlist; |
|
} KQueueData; |
|
| |
static GHashTable *dir_path_hash = NULL; |
/* |
static GHashTable *file_path_hash = NULL; |
* The backend will use at most n file descriptors, where n is the |
static GHashTable *fd_hash = NULL; |
* minimum value between (kern.maxfiles * CFG_GLOBAL_FILE_RESERVE_RATIO) |
|
* and (kern.maxfilesperproc - CFG_SELF_FILE_RESERVE). |
|
*/ |
|
#define CFG_GLOBAL_FILE_RESERVE_RATIO 0.7 |
|
#define CFG_SELF_FILE_RESERVE 200 |
| |
static GSList *exist_list = NULL; |
/* |
|
* If a SubMonitor or FileMonitor is not supported by kqueue and has |
|
* to be polled, the backend will re-attempt to enable kqueue |
|
* notification every n polls. |
|
*/ |
|
#define CFG_UNSUPPORTED_SMON_KQUEUE_RETRY_FREQUENCY 10 |
|
#define CFG_UNSUPPORTED_FMON_KQUEUE_RETRY_FREQUENCY 10 |
| |
static GList *new_subs = NULL; |
/* |
G_LOCK_DEFINE_STATIC(new_subs); |
* The various poll intervals, in milliseconds. The default interval |
static GList *removed_subs = NULL; |
* for each poller is based on the poller's expected usage. |
G_LOCK_DEFINE_STATIC(removed_subs); |
*/ |
|
#define CFG_MISSING_SMON_POLL_INTERVAL 1000 |
|
#define CFG_UNSUPPORTED_SMON_POLL_INTERVAL 3000 |
|
#define CFG_UNSUPPORTED_FMON_POLL_INTERVAL 3000 |
|
|
|
/*** end of tunable constants ************************************************/ |
|
|
|
#define VN_NOTE_CHANGED (NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK) |
|
#define VN_NOTE_DELETED (NOTE_DELETE | NOTE_REVOKE) |
|
#define VN_NOTE_ALL (VN_NOTE_CHANGED | VN_NOTE_DELETED | NOTE_RENAME) |
| |
G_LOCK_DEFINE_STATIC(kqueue); |
/* |
|
* A barebone stat structure, only containing the fields we need. |
|
*/ |
|
typedef struct |
|
{ |
|
gboolean exists; |
|
ino_t ino; |
|
mode_t mode; |
|
uid_t uid; |
|
gid_t gid; |
|
time_t mtime; |
|
time_t ctime; |
|
off_t size; |
|
} MiniStat; |
|
|
|
typedef void (*HashTableAddFunc) (GHashTable *table, |
|
gpointer item, |
|
gpointer user_data); |
|
typedef void (*HashTablePostAddFunc) (GHashTable *table, |
|
gpointer user_data); |
|
typedef void (*HashTableRemoveFunc) (GHashTable *table, |
|
gpointer item, |
|
gpointer user_data); |
|
typedef void (*HashTablePostRemoveFunc) (GHashTable *table, |
|
gpointer user_data); |
| |
static gboolean have_consume_idler = FALSE; |
typedef struct |
|
{ |
|
HashTableAddFunc add; |
|
HashTablePostAddFunc post_add; |
|
HashTableRemoveFunc remove; |
|
HashTablePostRemoveFunc post_remove; |
|
} HashTableMethods; |
|
|
|
/* |
|
* A hash table which can be modified while iterating over it. |
|
*/ |
|
typedef struct |
|
{ |
|
GHashTable *main; |
|
gboolean iterating; |
|
GSList *pending_additions; |
|
GSList *pending_removals; |
|
HashTableMethods *methods; |
|
gpointer user_data; |
|
} HashTable; |
|
|
|
/* the base monitor class */ |
|
typedef struct _Monitor Monitor; |
|
struct _Monitor |
|
{ |
|
void (*handle_kevent) (Monitor *monitor, struct kevent *event); |
|
char *pathname; |
|
int fd; /* for kqueue */ |
|
MiniStat sb; /* for poll */ |
|
unsigned int poll_count; |
|
}; |
|
#define MONITOR(ptr) ((Monitor *) ptr) |
| |
int kq = -1; |
typedef enum |
|
{ |
|
MONITOR_ISDIR = 1 << 0, |
|
MONITOR_ISNOTDIR = 1 << 1 |
|
} MonitorFlags; |
|
#define MONITOR_FLAGS_SHIFT 2 |
| |
static KQueueData * |
/* monitor for Gamin subscriptions */ |
gam_kqueue_data_new(const char *path, int fd) |
typedef struct |
{ | { |
KQueueData *data; |
Monitor base; |
|
GList *subs; |
|
HashTable *fmons; /* FileMonitor objects */ |
|
HashTable *unsupported_fmons; /* subset of fmons to poll */ |
|
gboolean isdir; /* is a directory subscription? */ |
|
} SubMonitor; |
|
#define SUB_MONITOR(ptr) ((SubMonitor *) ptr) |
| |
data = g_new0(KQueueData, 1); |
typedef enum |
data->path = g_strdup(path); |
{ |
data->fd = fd; |
SUB_MONITOR_WAS_MISSING = 1 << MONITOR_FLAGS_SHIFT |
data->refcount = 1; |
} SubMonitorFlags; |
data->isdir = FALSE; |
|
data->subs = NULL; |
|
data->dirlist = NULL; |
|
| |
return data; |
/* monitor for files within directory subscriptions */ |
} |
typedef struct |
|
{ |
|
Monitor base; |
|
SubMonitor *smon; /* the SubMonitor this fmon belongs to */ |
|
char *filename; /* pointer into base.pathname */ |
|
} FileMonitor; |
|
#define FILE_MONITOR(ptr) ((FileMonitor *) ptr) |
|
|
|
typedef enum |
|
{ |
|
FILE_MONITOR_POSSIBLY_RECREATED = 1 << MONITOR_FLAGS_SHIFT |
|
} FileMonitorFlags; |
| |
static GSList * |
typedef void (*PollerFunc) (SubMonitor *smon); |
gam_kqueue_lsdir(const char *path) |
typedef struct |
{ | { |
GDir *dir; |
PollerFunc func; |
GSList *lst = NULL; |
unsigned int interval; |
const gchar *entry; |
unsigned int timeout_id; |
|
HashTable *smons; /* SubMonitor objects */ |
|
} Poller; |
|
|
|
static int kq = -1; |
|
|
|
static unsigned int open_files = 0; |
|
static unsigned int max_open_files = 0; |
|
|
|
static GHashTable *dir_hash = NULL; |
|
static GHashTable *file_hash = NULL; |
|
|
|
static Poller missing_smon_poller; |
|
static Poller unsupported_smon_poller; |
|
static Poller unsupported_fmon_poller; |
|
|
|
static void gam_kqueue_hash_table_default_add_cb (GHashTable *table, |
|
gpointer item, |
|
gpointer user_data); |
|
static void gam_kqueue_hash_table_default_remove_cb (GHashTable *table, |
|
gpointer item, |
|
gpointer user_data); |
|
|
|
static void gam_kqueue_poller_post_add_cb (GHashTable *table, Poller *poller); |
|
static void gam_kqueue_poller_post_remove_cb (GHashTable *table, Poller *poller); |
|
|
|
static HashTableMethods poller_hash_table_methods = |
|
{ |
|
gam_kqueue_hash_table_default_add_cb, |
|
(HashTablePostAddFunc) gam_kqueue_poller_post_add_cb, |
|
gam_kqueue_hash_table_default_remove_cb, |
|
(HashTablePostRemoveFunc) gam_kqueue_poller_post_remove_cb |
|
}; |
|
|
|
static void gam_kqueue_sub_monitor_add_fmon_cb (GHashTable *table, |
|
FileMonitor *fmon, |
|
SubMonitor *smon); |
|
static void gam_kqueue_sub_monitor_remove_fmon_cb (GHashTable *table, |
|
FileMonitor *fmon, |
|
SubMonitor *smon); |
|
|
|
static HashTableMethods sub_monitor_fmons_hash_table_methods = |
|
{ |
|
(HashTableAddFunc) gam_kqueue_sub_monitor_add_fmon_cb, |
|
(HashTablePostAddFunc) NULL, |
|
(HashTableRemoveFunc) gam_kqueue_sub_monitor_remove_fmon_cb, |
|
(HashTablePostRemoveFunc) NULL |
|
}; |
|
|
|
static void gam_kqueue_sub_monitor_post_add_unsupported_fmon_cb (GHashTable *table, |
|
SubMonitor *smon); |
|
static void gam_kqueue_sub_monitor_post_remove_unsupported_fmon_cb (GHashTable *table, |
|
SubMonitor *smon); |
|
|
|
static HashTableMethods sub_monitor_unsupported_fmons_hash_table_methods = |
|
{ |
|
gam_kqueue_hash_table_default_add_cb, |
|
(HashTablePostAddFunc) gam_kqueue_sub_monitor_post_add_unsupported_fmon_cb, |
|
gam_kqueue_hash_table_default_remove_cb, |
|
(HashTablePostRemoveFunc) gam_kqueue_sub_monitor_post_remove_unsupported_fmon_cb |
|
}; |
|
|
|
static void gam_kqueue_sub_monitor_emit_event (SubMonitor *smon, |
|
GaminEventType event, |
|
SubMonitorFlags flags); |
|
static void gam_kqueue_sub_monitor_handle_kevent (Monitor *mon, |
|
struct kevent *event); |
|
|
|
static FileMonitor *gam_kqueue_file_monitor_new (SubMonitor *smon, |
|
const char *filename, |
|
FileMonitorFlags *flags); |
|
static void gam_kqueue_file_monitor_free (FileMonitor *fmon); |
|
static void gam_kqueue_file_monitor_emit_event (FileMonitor *fmon, |
|
GaminEventType event, |
|
FileMonitorFlags flags); |
|
static void gam_kqueue_file_monitor_handle_kevent (Monitor *mon, struct kevent *event); |
| |
if (!path) |
/*** helpers *****************************************************************/ |
return NULL; |
|
| |
dir = g_dir_open(path, 0, NULL); |
static void |
if (!dir) |
gam_kqueue_mini_lstat (const char *pathname, MiniStat *mini_sb) |
return NULL; |
{ |
|
struct stat sb; |
|
|
|
if (lstat(pathname, &sb) < 0) |
|
memset(mini_sb, 0, sizeof(*mini_sb)); |
|
else |
|
{ |
|
mini_sb->exists = TRUE; |
|
mini_sb->ino = sb.st_ino; |
|
mini_sb->mode = sb.st_mode; |
|
mini_sb->uid = sb.st_uid; |
|
mini_sb->gid = sb.st_gid; |
|
mini_sb->mtime = sb.st_mtime; |
|
mini_sb->ctime = sb.st_ctime; |
|
mini_sb->size = sb.st_size; |
|
} |
|
} |
|
|
|
static gboolean |
|
gam_kqueue_differs (const MiniStat *sb1, const MiniStat *sb2) |
|
{ |
|
return sb1->mtime != sb2->mtime |
|
|| sb1->ctime != sb2->ctime |
|
|| sb1->size != sb2->size |
|
|| sb1->mode != sb2->mode |
|
|| sb1->uid != sb2->uid |
|
|| sb1->gid != sb2->gid |
|
|| sb1->ino != sb2->ino; |
|
} |
|
|
|
static gboolean |
|
gam_kqueue_isdir (const char *pathname, MonitorFlags flags) |
|
{ |
|
if ((flags & MONITOR_ISDIR) != 0) |
|
return TRUE; |
|
else if ((flags & MONITOR_ISNOTDIR) != 0) |
|
return FALSE; |
|
else |
|
{ |
|
struct stat sb; |
|
return lstat(pathname, &sb) >= 0 && (sb.st_mode & S_IFDIR) != 0; |
|
} |
|
} |
| |
entry = g_dir_read_name(dir); |
static gboolean |
|
gam_kqueue_get_uint_sysctl (const char *name, unsigned int *value) |
|
{ |
|
unsigned int value_len = sizeof(*value); |
| |
while (entry) { |
if (sysctlbyname(name, value, &value_len, NULL, 0) < 0) |
lst = g_slist_prepend(lst, g_strdup(entry)); |
{ |
entry = g_dir_read_name(dir); |
gam_error(DEBUG_INFO, "unable to retrieve %s: %s\n", name, g_strerror(errno)); |
|
return FALSE; |
} | } |
|
else |
|
return TRUE; |
|
} |
| |
g_dir_close(dir); |
/*** HashTable ***************************************************************/ |
| |
return lst; |
static HashTable * |
|
gam_kqueue_hash_table_new (GHashTable *main, |
|
HashTableMethods *methods, |
|
gpointer user_data) |
|
{ |
|
HashTable *table; |
|
|
|
table = g_new0(HashTable, 1); |
|
table->main = main; |
|
table->methods = methods; |
|
table->user_data = user_data; |
|
|
|
return table; |
} | } |
| |
static void | static void |
gam_kqueue_cmplst(GSList *lst1, GSList *lst2, GSList **added, GSList **deleted) |
gam_kqueue_hash_table_default_add_cb (GHashTable *table, |
|
gpointer item, |
|
gpointer user_data) |
{ | { |
int found; |
g_hash_table_insert(table, item, GINT_TO_POINTER(TRUE)); |
GSList *l; |
} |
| |
if (!lst1 && !lst2) |
static void |
return; |
gam_kqueue_hash_table_default_remove_cb (GHashTable *table, |
|
gpointer item, |
|
gpointer user_data) |
|
{ |
|
g_hash_table_remove(table, item); |
|
} |
| |
if (!lst1) { |
static void |
*added = g_slist_copy(lst2); |
gam_kqueue_hash_table_add (HashTable *table, gpointer item) |
return; |
{ |
|
if (table->iterating) |
|
table->pending_additions = g_slist_prepend(table->pending_additions, item); |
|
else |
|
{ |
|
table->methods->add(table->main, item, table->user_data); |
|
if (table->methods->post_add) |
|
table->methods->post_add(table->main, table->user_data); |
} | } |
|
} |
| |
if (!lst2) { |
static void |
*deleted = g_slist_copy(lst1); |
gam_kqueue_hash_table_remove (HashTable *table, gpointer item) |
return; |
{ |
|
if (table->iterating) |
|
table->pending_removals = g_slist_prepend(table->pending_removals, item); |
|
else |
|
{ |
|
table->methods->remove(table->main, item, table->user_data); |
|
if (table->methods->post_remove) |
|
table->methods->post_remove(table->main, table->user_data); |
} | } |
|
} |
| |
for (l = lst1; l; l = l->next) { |
static void |
found = 0; |
gam_kqueue_hash_table_foreach (HashTable *table, |
if (g_slist_find_custom(lst2, l->data, (GCompareFunc)strcmp)) { |
GHFunc func, |
found = 1; |
gpointer user_data) |
} |
{ |
if (found == 0) { |
g_assert(table->iterating == FALSE); |
*deleted = g_slist_prepend(*deleted, l->data); |
|
} |
table->iterating = TRUE; |
|
g_hash_table_foreach(table->main, func, user_data); |
|
table->iterating = FALSE; |
|
|
|
if (table->pending_additions) |
|
{ |
|
GSList *l; |
|
|
|
for (l = table->pending_additions; l != NULL; l = l->next) |
|
table->methods->add(table->main, l->data, table->user_data); |
|
|
|
g_slist_free(table->pending_additions); |
|
table->pending_additions = NULL; |
|
|
|
if (table->methods->post_add) |
|
table->methods->post_add(table->main, table->user_data); |
} | } |
| |
for (l = lst2; l; l = l->next) { |
if (table->pending_removals) |
found = 0; |
{ |
if (g_slist_find_custom(lst1, l->data, (GCompareFunc)strcmp)) { |
GSList *l; |
found = 1; |
|
} |
for (l = table->pending_removals; l != NULL; l = l->next) |
if (found == 0) { |
table->methods->remove(table->main, l->data, table->user_data); |
*added = g_slist_prepend(*added, l->data); |
|
} |
g_slist_free(table->pending_removals); |
|
table->pending_removals = NULL; |
|
|
|
if (table->methods->post_remove) |
|
table->methods->post_remove(table->main, table->user_data); |
} | } |
} | } |
| |
static void | static void |
gam_kqueue_data_free(KQueueData * data) |
gam_kqueue_hash_table_destroy (HashTable *table) |
{ | { |
g_free(data->path); |
g_assert(table->iterating == FALSE); |
if (data->dirlist) { |
|
g_slist_foreach(data->dirlist, (GFunc)g_free, NULL); |
g_hash_table_destroy(table->main); |
g_slist_free(data->dirlist); |
g_free(table); |
} |
} |
if (data->subs) { |
|
g_list_free(data->subs); |
/*** Poller ******************************************************************/ |
|
|
|
static void |
|
gam_kqueue_poller_init (Poller *poller, PollerFunc func, unsigned int interval) |
|
{ |
|
poller->func = func; |
|
poller->interval = interval; |
|
poller->timeout_id = 0; |
|
poller->smons = gam_kqueue_hash_table_new(g_hash_table_new(g_direct_hash, g_direct_equal), |
|
&poller_hash_table_methods, |
|
poller); |
|
} |
|
|
|
static void |
|
gam_kqueue_poller_foreach_cb (SubMonitor *smon, |
|
gpointer unused, |
|
Poller *poller) |
|
{ |
|
poller->func(smon); |
|
} |
|
|
|
static gboolean |
|
gam_kqueue_poller_timeout_cb (Poller *poller) |
|
{ |
|
gam_kqueue_hash_table_foreach(poller->smons, (GHFunc) gam_kqueue_poller_foreach_cb, poller); |
|
|
|
return TRUE; /* keep source */ |
|
} |
|
|
|
static void |
|
gam_kqueue_poller_post_add_cb (GHashTable *table, Poller *poller) |
|
{ |
|
if (! poller->timeout_id) |
|
poller->timeout_id = g_timeout_add(poller->interval, (GSourceFunc) gam_kqueue_poller_timeout_cb, poller); |
|
} |
|
|
|
static void |
|
gam_kqueue_poller_post_remove_cb (GHashTable *table, Poller *poller) |
|
{ |
|
if (g_hash_table_size(table) == 0 && poller->timeout_id) |
|
{ |
|
g_source_remove(poller->timeout_id); |
|
poller->timeout_id = 0; |
} | } |
g_free(data); |
|
} | } |
| |
static void | static void |
gam_kqueue_add_rm_handler(const char *path, GamSubscription *sub, gboolean added, gboolean was_missing) |
gam_kqueue_poller_add_sub_monitor (Poller *poller, SubMonitor *smon) |
|
{ |
|
gam_kqueue_hash_table_add(poller->smons, smon); |
|
} |
|
|
|
static void |
|
gam_kqueue_poller_remove_sub_monitor (Poller *poller, SubMonitor *smon) |
{ | { |
KQueueData *data; |
gam_kqueue_hash_table_remove(poller->smons, smon); |
struct kevent ev[1]; |
} |
int isdir = 0; |
|
int fd; |
|
| |
G_LOCK(kqueue); |
/*** Monitor *****************************************************************/ |
| |
isdir = g_file_test(path, G_FILE_TEST_IS_DIR); |
static gboolean |
if (gam_subscription_is_dir(sub)) { |
gam_kqueue_monitor_enable_kqueue (Monitor *mon) |
data = g_hash_table_lookup(dir_path_hash, path); |
{ |
|
struct kevent ev[1]; |
|
|
|
if (open_files == max_open_files) |
|
{ |
|
GAM_DEBUG(DEBUG_INFO, "cannot open %s (max_open_files limit reached), falling back to poll\n", mon->pathname); |
|
return FALSE; |
} | } |
else { |
|
data = g_hash_table_lookup(file_path_hash, path); |
mon->fd = open(mon->pathname, O_RDONLY | O_NOFOLLOW); |
|
if (mon->fd < 0) |
|
{ |
|
GAM_DEBUG(DEBUG_INFO, "cannot open %s (%s), falling back to poll\n", mon->pathname, g_strerror(errno)); |
|
return FALSE; |
} | } |
| |
if (added) { |
EV_SET(ev, mon->fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, VN_NOTE_ALL, 0, mon); |
GList *subs; |
if (kevent(kq, ev, G_N_ELEMENTS(ev), NULL, 0, NULL) < 0) |
|
{ |
|
GAM_DEBUG(DEBUG_INFO, "cannot enable kqueue notification for %s (%s), falling back to poll\n", mon->pathname, g_strerror(errno)); |
| |
subs = NULL; |
close(mon->fd); |
subs = g_list_append(subs, sub); |
mon->fd = -1; |
| |
if (data != NULL) { |
return FALSE; |
data->refcount++; |
} |
data->subs = g_list_prepend(data->subs, sub); |
|
G_UNLOCK(kqueue); |
|
GAM_DEBUG(DEBUG_INFO, "kqueue updated refcount\n"); |
|
if (!was_missing) { |
|
gam_server_emit_event (path, isdir, GAMIN_EVENT_EXISTS, subs, 1); |
|
gam_server_emit_event (path, isdir, GAMIN_EVENT_ENDEXISTS, subs, 1); |
|
} |
|
else { |
|
gam_server_emit_event (path, isdir, GAMIN_EVENT_CREATED, subs, 1); |
|
} |
|
return; |
|
} |
|
| |
if (!g_file_test(path, G_FILE_TEST_EXISTS)) { |
open_files++; |
data = gam_kqueue_data_new(path, -1); |
return TRUE; |
data->subs = g_list_prepend(data->subs, sub); |
} |
exist_list = g_slist_append(exist_list, data); |
|
gam_server_emit_event (path, isdir, GAMIN_EVENT_DELETED, subs, 1); |
static void |
gam_server_emit_event (path, isdir, GAMIN_EVENT_ENDEXISTS, subs, 1); |
gam_kqueue_monitor_set_unsupported (Monitor *mon) |
G_UNLOCK(kqueue); |
{ |
return; |
if (mon->fd >= 0) |
} |
{ |
|
close(mon->fd); |
|
mon->fd = -1; |
| |
fd = open(path, O_RDONLY); |
open_files--; |
|
} |
| |
if (fd < 0) { |
gam_kqueue_mini_lstat(mon->pathname, &mon->sb); |
G_UNLOCK(kqueue); |
} |
return; |
|
} |
|
| |
EV_SET(ev, fd, EVFILT_VNODE, |
static void |
EV_ADD | EV_ENABLE | EV_CLEAR, VN_NOTE_ALL, 0, 0); |
gam_kqueue_monitor_free (Monitor *mon) |
kevent(kq, ev, 1, NULL, 0, NULL); |
{ |
|
g_free(mon->pathname); |
|
if (mon->fd >= 0) |
|
{ |
|
close(mon->fd); |
|
open_files--; |
|
} |
|
g_free(mon); |
|
} |
| |
data = gam_kqueue_data_new(path, fd); |
/*** SubMonitor **************************************************************/ |
data->subs = g_list_prepend(data->subs, sub); |
|
| |
if (!was_missing) { |
static void |
gam_server_emit_event (path, isdir, GAMIN_EVENT_EXISTS, subs, 1); |
gam_kqueue_sub_monitor_init_fmons (SubMonitor *smon) |
} |
{ |
else { |
smon->fmons = gam_kqueue_hash_table_new(g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) gam_kqueue_file_monitor_free), |
gam_server_emit_event (path, isdir, GAMIN_EVENT_CREATED, subs, 1); |
&sub_monitor_fmons_hash_table_methods, |
} |
smon); |
if (gam_subscription_is_dir(sub) && isdir) { |
smon->unsupported_fmons = gam_kqueue_hash_table_new(g_hash_table_new(g_direct_hash, g_direct_equal), |
GSList *l; |
&sub_monitor_unsupported_fmons_hash_table_methods, |
|
smon); |
|
} |
| |
data->isdir = TRUE; |
static SubMonitor * |
data->dirlist = gam_kqueue_lsdir(path); |
gam_kqueue_sub_monitor_new (GamSubscription *sub) |
|
{ |
|
SubMonitor *smon; |
|
Monitor *mon; |
| |
for (l = data->dirlist; l; l = l->next) { |
smon = g_new0(SubMonitor, 1); |
char *tmpentry; |
|
| |
tmpentry = g_build_filename(path, l->data, NULL); |
mon = MONITOR(smon); |
if (!was_missing) { |
mon->handle_kevent = gam_kqueue_sub_monitor_handle_kevent; |
gam_server_emit_event (tmpentry, |
mon->pathname = g_strdup(gam_subscription_get_path(sub)); |
g_file_test(tmpentry, G_FILE_TEST_IS_DIR), |
mon->fd = -1; |
GAMIN_EVENT_EXISTS, subs, 1); |
|
} |
|
g_free(tmpentry); |
|
} |
|
} |
|
| |
if (!was_missing) { |
smon->isdir = gam_subscription_is_dir(sub); |
gam_server_emit_event (path, isdir, GAMIN_EVENT_ENDEXISTS, subs, 1); |
gam_kqueue_sub_monitor_init_fmons(smon); |
} |
|
| |
g_hash_table_insert(fd_hash, GINT_TO_POINTER(data->fd), data); |
return smon; |
if (data->isdir) { |
} |
g_hash_table_insert(dir_path_hash, data->path, data); |
|
} |
|
else { |
|
g_hash_table_insert(file_path_hash, data->path, data); |
|
} |
|
| |
if (subs) |
static void |
g_list_free(subs); |
gam_kqueue_sub_monitor_clear_fmons (SubMonitor *smon) |
|
{ |
|
if (g_hash_table_size(smon->unsupported_fmons->main) > 0) |
|
gam_kqueue_poller_remove_sub_monitor(&unsupported_fmon_poller, smon); |
| |
GAM_DEBUG(DEBUG_INFO, "added kqueue watch for %s\n", path); |
gam_kqueue_hash_table_destroy(smon->unsupported_fmons); |
} else { |
gam_kqueue_hash_table_destroy(smon->fmons); |
|
} |
| |
if (!data) { |
static void |
G_UNLOCK(kqueue); |
gam_kqueue_sub_monitor_free (SubMonitor *smon) |
return; |
{ |
} |
gam_kqueue_poller_remove_sub_monitor(&missing_smon_poller, smon); |
|
gam_kqueue_poller_remove_sub_monitor(&unsupported_smon_poller, smon); |
|
/* unsupported_dirs_poller is handled by _clear_fmons() below */ |
|
|
|
gam_kqueue_sub_monitor_clear_fmons(smon); |
|
gam_kqueue_monitor_free(MONITOR(smon)); |
|
} |
| |
if (g_list_find (data->subs, sub)) { |
static void |
data->subs = g_list_remove_all (data->subs, sub); |
gam_kqueue_sub_monitor_add_fmon_cb (GHashTable *table, |
} |
FileMonitor *fmon, |
data->refcount--; |
SubMonitor *smon) |
GAM_DEBUG(DEBUG_INFO, "kqueue decremeneted refcount for %s\n", path); |
{ |
|
g_hash_table_replace(table, fmon->filename, fmon); |
|
} |
| |
if (data->refcount == 0) { |
static void |
GList *l; |
gam_kqueue_sub_monitor_remove_fmon_cb (GHashTable *table, |
|
FileMonitor *fmon, |
|
SubMonitor *smon) |
|
{ |
|
g_hash_table_remove(table, fmon->filename); |
|
} |
| |
close(data->fd); |
static void |
l = data->subs; |
gam_kqueue_sub_monitor_post_add_unsupported_fmon_cb (GHashTable *table, |
for (l = l; l; l = l->next) { |
SubMonitor *smon) |
GamSubscription *sub = l->data; |
{ |
gam_kqueue_remove_subscription (sub); |
gam_kqueue_poller_add_sub_monitor(&unsupported_fmon_poller, smon); |
} |
|
GAM_DEBUG(DEBUG_INFO, "removed kqueue watch for %s\n", data->path); |
|
if (data->isdir) { |
|
g_hash_table_remove(dir_path_hash, data->path); |
|
} |
|
else { |
|
g_hash_table_remove(file_path_hash, data->path); |
|
} |
|
g_hash_table_remove(fd_hash, GINT_TO_POINTER(data->fd)); |
|
gam_kqueue_data_free(data); |
|
} |
|
} |
|
G_UNLOCK(kqueue); |
|
} | } |
| |
static GaminEventType kqueue_event_to_gamin_event (int mask) |
static void |
|
gam_kqueue_sub_monitor_post_remove_unsupported_fmon_cb (GHashTable *table, |
|
SubMonitor *smon) |
{ | { |
if ((mask & VN_NOTE_CHANGED) != 0) |
if (g_hash_table_size(table) == 0) |
return GAMIN_EVENT_CHANGED; |
gam_kqueue_poller_remove_sub_monitor(&unsupported_fmon_poller, smon); |
else if ((mask & NOTE_DELETE) != 0) |
|
return GAMIN_EVENT_DELETED; |
|
else if ((mask & NOTE_REVOKE) != 0) |
|
return GAMIN_EVENT_ENDEXISTS; |
|
else if ((mask & NOTE_RENAME) != 0) |
|
return GAMIN_EVENT_MOVED; |
|
else |
|
return GAMIN_EVENT_UNKNOWN; |
|
} | } |
| |
static void gam_kqueue_emit_event (KQueueData *data, struct kevent *event) |
static void |
|
gam_kqueue_sub_monitor_set_missing (SubMonitor *smon) |
{ | { |
GaminEventType gevent; |
Monitor *mon = MONITOR(smon); |
int isdir = 0; |
|
char *event_path; |
|
| |
if (!data||!event) |
if (mon->fd >= 0) |
return; |
{ |
|
close(mon->fd); |
|
mon->fd = -1; |
| |
gevent = kqueue_event_to_gamin_event (event->fflags); |
open_files--; |
|
} |
| |
if (gevent == GAMIN_EVENT_UNKNOWN) { |
/* |
return; |
* A removed directory will normally not contain files, but we must |
} |
* not assume it (we might receive events out of order, etc). We |
|
* therefore check if files are remaining, and if yes, clear them. |
|
*/ |
|
if (g_hash_table_size(smon->fmons->main) > 0) |
|
{ |
|
gam_kqueue_sub_monitor_clear_fmons(smon); |
|
gam_kqueue_sub_monitor_init_fmons(smon); |
|
} |
| |
isdir = g_file_test(data->path, G_FILE_TEST_IS_DIR); |
gam_kqueue_poller_remove_sub_monitor(&unsupported_smon_poller, smon); |
|
gam_kqueue_poller_add_sub_monitor(&missing_smon_poller, smon); |
|
} |
| |
if (gevent == GAMIN_EVENT_CHANGED && data->isdir) { |
static void |
GSList *dirlist = NULL, *added = NULL, *deleted = NULL; |
gam_kqueue_sub_monitor_set_unsupported (SubMonitor *smon) |
GSList *l; |
{ |
|
Monitor *mon = MONITOR(smon); |
dirlist = gam_kqueue_lsdir(data->path); |
|
gam_kqueue_cmplst(data->dirlist, dirlist, &added, &deleted); |
|
if (added || deleted) { |
|
for (l = deleted; l; l = l->next) { |
|
data->dirlist = g_slist_remove(data->dirlist, l->data); |
|
event_path = g_build_filename(data->path, l->data, NULL); |
|
g_free(l->data); |
|
isdir = g_file_test(event_path, G_FILE_TEST_IS_DIR); |
|
|
|
GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_DELETED) , event_path); |
|
|
|
gam_server_emit_event (event_path, isdir, |
|
GAMIN_EVENT_DELETED, data->subs, 1); |
|
g_free(event_path); |
|
} |
|
| |
for (l = added; l; l = l->next) { |
gam_kqueue_monitor_set_unsupported(mon); |
dirlist = g_slist_remove(dirlist, l->data); |
gam_kqueue_poller_add_sub_monitor(&unsupported_smon_poller, smon); |
data->dirlist = g_slist_prepend(data->dirlist, |
} |
g_strdup(l->data)); |
|
event_path = g_build_filename(data->path, l->data, NULL); |
|
g_free(l->data); |
|
isdir = g_file_test(event_path, G_FILE_TEST_IS_DIR); |
|
|
|
GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_CREATED) , event_path); |
|
|
|
gam_server_emit_event (event_path, isdir, |
|
GAMIN_EVENT_CREATED, data->subs, 1); |
|
g_free(event_path); |
|
} |
|
| |
if (added) |
static void |
g_slist_free(added); |
gam_kqueue_sub_monitor_enable_notification (SubMonitor *smon, |
if (deleted) |
SubMonitorFlags flags) |
g_slist_free(deleted); |
{ |
} |
Monitor *mon = MONITOR(smon); |
|
gboolean exists; |
|
|
|
/* we first send CREATED or EXISTS/DELETED+ENDEXISTS events */ |
| |
if (dirlist) { |
if ((flags & SUB_MONITOR_WAS_MISSING) != 0) |
g_slist_foreach(dirlist, (GFunc)g_free, NULL); |
exists = TRUE; |
g_slist_free(dirlist); |
else |
|
{ |
|
struct stat sb; |
|
|
|
exists = lstat(mon->pathname, &sb) >= 0; |
|
flags |= (exists && (sb.st_mode & S_IFDIR) != 0) ? MONITOR_ISDIR : MONITOR_ISNOTDIR; |
|
} |
|
|
|
if (exists) |
|
{ |
|
GaminEventType gevent; |
|
|
|
gevent = (flags & SUB_MONITOR_WAS_MISSING) != 0 ? GAMIN_EVENT_CREATED : GAMIN_EVENT_EXISTS; |
|
gam_kqueue_sub_monitor_emit_event(smon, gevent, flags); |
|
|
|
if (smon->isdir && (flags & MONITOR_ISDIR) != 0) |
|
{ |
|
GDir *dir; |
|
GError *err = NULL; |
|
|
|
dir = g_dir_open(mon->pathname, 0, &err); |
|
if (dir) |
|
{ |
|
const char *filename; |
|
|
|
while ((filename = g_dir_read_name(dir))) |
|
{ |
|
FileMonitor *fmon; |
|
FileMonitorFlags fmon_flags; |
|
|
|
fmon = gam_kqueue_file_monitor_new(smon, filename, &fmon_flags); |
|
gam_kqueue_file_monitor_emit_event(fmon, gevent, fmon_flags); |
|
} |
|
|
|
g_dir_close(dir); |
|
} |
|
else |
|
{ |
|
GAM_DEBUG(DEBUG_INFO, "unable to open directory %s: %s\n", mon->pathname, err->message); |
|
g_error_free(err); |
} | } |
return; |
|
} |
|
else { |
|
event_path = g_strdup (data->path); |
|
} | } |
| |
isdir = g_file_test(event_path, G_FILE_TEST_IS_DIR); |
if ((flags & SUB_MONITOR_WAS_MISSING) == 0) |
|
gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_ENDEXISTS, flags); |
|
} |
|
else |
|
{ |
|
gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_DELETED, flags); |
|
gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_ENDEXISTS, flags); |
| |
GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(gevent) , event_path); |
return; |
|
} |
|
|
|
/* then we enable kqueue notification, falling back to poll if necessary */ |
| |
gam_server_emit_event (event_path, isdir, gevent, data->subs, 1); |
if (! gam_kqueue_monitor_enable_kqueue(mon)) |
|
gam_kqueue_sub_monitor_set_unsupported(smon); |
|
} |
| |
g_free (event_path); |
static void |
|
gam_kqueue_sub_monitor_handle_directory_change_removal_cb (const char *filename, |
|
FileMonitor *fmon, |
|
GHashTable *filenames) |
|
{ |
|
if (! g_hash_table_lookup(filenames, filename)) |
|
gam_kqueue_file_monitor_emit_event(fmon, GAMIN_EVENT_DELETED, MONITOR_ISNOTDIR); |
} | } |
| |
static gboolean |
static void |
gam_kqueue_exist_check (gpointer user_data) |
gam_kqueue_sub_monitor_handle_directory_change (SubMonitor *smon, |
|
gboolean isdir) |
{ | { |
GSList *l, *tmplst; |
Monitor *mon = MONITOR(smon); |
KQueueData *data; |
GHashTable *filenames; |
|
|
|
filenames = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
|
|
|
if (isdir) /* do not follow symbolic links */ |
|
{ |
|
GDir *dir; |
|
GError *err = NULL; |
|
|
|
dir = g_dir_open(mon->pathname, 0, &err); |
|
if (dir) |
|
{ |
|
const char *filename; |
|
|
|
while ((filename = g_dir_read_name(dir))) |
|
{ |
|
g_hash_table_insert(filenames, g_strdup(filename), GINT_TO_POINTER(TRUE)); |
|
|
|
/* handle file creation */ |
|
if (! g_hash_table_lookup(smon->fmons->main, filename)) |
|
{ |
|
FileMonitor *fmon; |
|
FileMonitorFlags fmon_flags; |
|
|
|
fmon = gam_kqueue_file_monitor_new(smon, filename, &fmon_flags); |
|
gam_kqueue_file_monitor_emit_event(fmon, GAMIN_EVENT_CREATED, fmon_flags); |
|
} |
|
} |
|
|
|
g_dir_close(dir); |
|
} |
|
else |
|
{ |
|
GAM_DEBUG(DEBUG_INFO, "unable to open directory %s: %s\n", mon->pathname, err->message); |
|
g_error_free(err); |
|
} |
|
} |
| |
tmplst = g_slist_copy(exist_list); |
/* |
|
* Handle deleted files (they are also handled at the FileMonitor |
|
* level, but we must use whichever event comes first). |
|
*/ |
|
gam_kqueue_hash_table_foreach(smon->fmons, (GHFunc) gam_kqueue_sub_monitor_handle_directory_change_removal_cb, filenames); |
|
g_hash_table_destroy(filenames); |
|
} |
| |
for (l = tmplst; l; l = l->next) { |
static void |
data = l->data; |
gam_kqueue_sub_monitor_emit_event (SubMonitor *smon, |
|
GaminEventType event, |
|
SubMonitorFlags flags) |
|
{ |
|
Monitor *mon = MONITOR(smon); |
|
gboolean isdir; |
| |
if (g_file_test(data->path, G_FILE_TEST_EXISTS)) { |
isdir = gam_kqueue_isdir(mon->pathname, flags); |
/* The subs list is guaranteed to have only one entry. */ |
|
GamSubscription *sub = data->subs->data; |
|
| |
exist_list = g_slist_remove(exist_list, data); |
switch (event) |
gam_kqueue_add_rm_handler(data->path, sub, TRUE, TRUE); |
{ |
gam_kqueue_data_free(data); |
case GAMIN_EVENT_CHANGED: |
|
if (smon->isdir) |
|
{ |
|
gam_kqueue_sub_monitor_handle_directory_change(smon, isdir); |
|
return; |
} | } |
|
break; |
|
|
|
case GAMIN_EVENT_DELETED: |
|
case GAMIN_EVENT_MOVED: |
|
gam_kqueue_sub_monitor_set_missing(smon); |
|
break; |
} | } |
| |
if (tmplst) |
gam_server_emit_event(mon->pathname, isdir, event, smon->subs, 1); |
g_slist_free(tmplst); |
} |
| |
return TRUE; |
static void |
|
gam_kqueue_sub_monitor_handle_kevent (Monitor *mon, struct kevent *event) |
|
{ |
|
SubMonitor *smon = SUB_MONITOR(mon); |
|
|
|
if ((event->flags & EV_ERROR) != 0) |
|
{ |
|
/* kqueue failed, fallback to poll */ |
|
GAM_DEBUG(DEBUG_INFO, "kqueue failed for %s, falling back to poll\n", mon->pathname); |
|
gam_kqueue_sub_monitor_set_unsupported(smon); |
|
return; |
|
} |
|
|
|
/* |
|
* kevent aggregates events, so we must handle a fflags with |
|
* multiple event bits set. |
|
*/ |
|
if ((event->fflags & VN_NOTE_CHANGED) != 0) |
|
gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_CHANGED, 0); |
|
|
|
/* emitting the following events will add the smon to the missing poller */ |
|
if ((event->fflags & VN_NOTE_DELETED) != 0) |
|
gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_DELETED, MONITOR_ISNOTDIR); |
|
else if ((event->fflags & NOTE_RENAME) != 0) |
|
gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_MOVED, MONITOR_ISNOTDIR); |
} | } |
| |
static gboolean |
/*** FileMonitor *************************************************************/ |
gam_kqueue_event_handler (gpointer user_data) |
|
|
static void |
|
gam_kqueue_file_monitor_set_unsupported (FileMonitor *fmon) |
{ | { |
KQueueData *data; |
Monitor *mon = MONITOR(fmon); |
struct kevent ev[1]; |
|
struct timespec timeout = { 0, 0 }; |
gam_kqueue_monitor_set_unsupported(mon); |
int fd, i, nevents; |
gam_kqueue_hash_table_add(fmon->smon->unsupported_fmons, fmon); |
|
} |
G_LOCK(kqueue); |
|
|
static FileMonitor * |
GAM_DEBUG(DEBUG_INFO, "gam_kqueue_event_handler()\n"); |
gam_kqueue_file_monitor_new (SubMonitor *smon, |
|
const char *filename, |
nevents = kevent(kq, NULL, 0, ev, 1, &timeout); |
FileMonitorFlags *flags) |
if (nevents == -1) |
{ |
return FALSE; |
FileMonitor *fmon; |
for (i = 0; i < nevents; i++) { |
Monitor *mon; |
fd = ev[i].ident; |
|
|
|
data = g_hash_table_lookup (fd_hash, GINT_TO_POINTER(fd)); |
|
if (!data) { |
|
GAM_DEBUG(DEBUG_INFO, "kqueue can't find fd %d\n", fd); |
|
GAM_DEBUG(DEBUG_INFO, "weird things have happened to kqueue.\n"); |
|
} else { |
|
gam_kqueue_emit_event (data, &ev[i]); |
|
} |
|
| |
|
fmon = g_new0(FileMonitor, 1); |
|
|
|
mon = MONITOR(fmon); |
|
mon->handle_kevent = gam_kqueue_file_monitor_handle_kevent; |
|
mon->pathname = g_build_filename(MONITOR(smon)->pathname, filename, NULL); |
|
mon->fd = -1; |
|
|
|
fmon->smon = smon; |
|
fmon->filename = strrchr(mon->pathname, G_DIR_SEPARATOR); |
|
fmon->filename = fmon->filename ? fmon->filename + 1 : mon->pathname; |
|
|
|
gam_kqueue_hash_table_add(fmon->smon->fmons, fmon); |
|
|
|
if (gam_kqueue_monitor_enable_kqueue(mon)) |
|
*flags = 0; |
|
else |
|
{ |
|
gam_kqueue_file_monitor_set_unsupported(fmon); |
|
*flags = (mon->sb.mode & S_IFDIR) != 0 ? MONITOR_ISDIR : MONITOR_ISNOTDIR; |
} | } |
| |
G_UNLOCK(kqueue); |
return fmon; |
|
} |
| |
return TRUE; |
static void |
|
gam_kqueue_file_monitor_free (FileMonitor *fmon) |
|
{ |
|
gam_kqueue_monitor_free(MONITOR(fmon)); |
} | } |
| |
static gboolean |
static void |
gam_kqueue_consume_subscriptions_real(gpointer data) |
gam_kqueue_file_monitor_emit_event (FileMonitor *fmon, |
|
GaminEventType event, |
|
FileMonitorFlags flags) |
{ | { |
GList *subs, *l; |
Monitor *mon = MONITOR(fmon); |
|
struct stat sb; |
|
gboolean isdir; |
|
gboolean stat_done; |
|
gboolean stat_succeeded; |
|
|
|
if ((flags & MONITOR_ISDIR) == 0 && (flags & MONITOR_ISNOTDIR) == 0) |
|
{ |
|
stat_done = TRUE; |
|
stat_succeeded = lstat(mon->pathname, &sb) >= 0; |
|
isdir = stat_succeeded && (sb.st_mode & S_IFDIR) != 0; |
|
} |
|
else |
|
{ |
|
stat_done = FALSE; |
|
isdir = (flags & MONITOR_ISDIR) != 0; |
|
} |
|
|
|
gam_server_emit_event(mon->pathname, isdir, event, fmon->smon->subs, 1); |
| |
G_LOCK(new_subs); |
switch (event) |
if (new_subs) { |
{ |
subs = new_subs; |
case GAMIN_EVENT_DELETED: |
new_subs = NULL; |
case GAMIN_EVENT_MOVED: |
G_UNLOCK(new_subs); |
if (mon->fd < 0) |
|
gam_kqueue_hash_table_remove(fmon->smon->unsupported_fmons, fmon); |
for (l = subs; l; l = l->next) { |
|
GamSubscription *sub = l->data; |
if ((flags & FILE_MONITOR_POSSIBLY_RECREATED) != 0) |
GAM_DEBUG(DEBUG_INFO, "called gam_kqueue_add_handler()\n"); |
{ |
gam_kqueue_add_rm_handler (gam_subscription_get_path (sub), sub, TRUE, FALSE); |
if (! stat_done) |
} |
stat_succeeded = lstat(mon->pathname, &sb) >= 0; |
|
|
|
if (stat_succeeded) |
|
{ |
|
FileMonitor *new_fmon; |
|
FileMonitorFlags new_fmon_flags; |
|
|
|
/* |
|
* The file exists again. It means that kqueue has |
|
* aggregated a removal+creation into a single event. We |
|
* must therefore create a new fmon and emit a |
|
* GAMIN_EVENT_CREATED event, because |
|
* gam_kqueue_sub_monitor_handle_directory_change() did |
|
* not detect the removal+creation. |
|
*/ |
| |
} else { |
new_fmon = gam_kqueue_file_monitor_new(fmon->smon, fmon->filename, &new_fmon_flags); |
G_UNLOCK(new_subs); |
gam_kqueue_file_monitor_emit_event(new_fmon, GAMIN_EVENT_CREATED, new_fmon_flags); |
} |
|
| |
G_LOCK(removed_subs); |
break; /* do not remove the fmon we've just created */ |
if (removed_subs) { |
} |
subs = removed_subs; |
|
removed_subs = NULL; |
|
G_UNLOCK(removed_subs); |
|
|
|
for (l = subs; l; l = l->next) { |
|
GamSubscription *sub = l->data; |
|
GAM_DEBUG(DEBUG_INFO, "called gam_kqueue_rm_handler()\n"); |
|
gam_kqueue_add_rm_handler (gam_subscription_get_path (sub), sub, FALSE, FALSE); |
|
} |
|
} else { |
|
G_UNLOCK(removed_subs); |
|
} | } |
|
|
|
gam_kqueue_hash_table_remove(fmon->smon->fmons, fmon); |
|
break; |
|
} |
|
} |
|
|
|
static void |
|
gam_kqueue_file_monitor_handle_kevent (Monitor *mon, struct kevent *event) |
|
{ |
|
FileMonitor *fmon = FILE_MONITOR(mon); |
|
|
|
if ((event->flags & EV_ERROR) != 0) |
|
{ |
|
/* kqueue failed, fallback to poll */ |
|
GAM_DEBUG(DEBUG_INFO, "kqueue failed for %s, falling back to poll\n", mon->pathname); |
|
gam_kqueue_file_monitor_set_unsupported(fmon); |
|
return; |
|
} |
|
|
|
/* |
|
* kevent aggregates events, so we must handle a fflags with |
|
* multiple event bits set. |
|
*/ |
|
|
|
if ((event->fflags & VN_NOTE_CHANGED) != 0) |
|
gam_kqueue_file_monitor_emit_event(fmon, GAMIN_EVENT_CHANGED, 0); |
|
|
|
/* emitting the following events will free the fmon */ |
|
if ((event->fflags & VN_NOTE_DELETED) != 0) |
|
gam_kqueue_file_monitor_emit_event(fmon, GAMIN_EVENT_DELETED, MONITOR_ISNOTDIR | FILE_MONITOR_POSSIBLY_RECREATED); |
|
else if ((event->fflags & NOTE_RENAME) != 0) |
|
gam_kqueue_file_monitor_emit_event(fmon, GAMIN_EVENT_MOVED, MONITOR_ISNOTDIR | FILE_MONITOR_POSSIBLY_RECREATED); |
|
} |
|
|
|
/*** kevent/poll callbacks ***************************************************/ |
|
|
|
static gboolean |
|
gam_kqueue_kevent_cb (GIOChannel *source, |
|
GIOCondition condition, |
|
gpointer user_data) |
|
{ |
|
int nevents; |
|
struct kevent ev[1]; |
|
struct timespec timeout = { 0, 0 }; |
|
int i; |
|
|
|
nevents = kevent(kq, NULL, 0, ev, G_N_ELEMENTS(ev), &timeout); |
|
if (nevents < 0) |
|
{ |
|
GAM_DEBUG(DEBUG_INFO, "kevent() failure: %s\n", g_strerror(errno)); |
|
return TRUE; /* keep source */ |
|
} |
|
|
|
for (i = 0; i < nevents; i++) |
|
MONITOR(ev[i].udata)->handle_kevent(ev[i].udata, &ev[i]); |
|
|
|
return TRUE; /* keep source */ |
|
} |
| |
GAM_DEBUG(DEBUG_INFO, "gam_kqueue_consume_subscriptions()\n"); |
static void |
|
gam_kqueue_missing_smon_poll (SubMonitor *smon) |
|
{ |
|
struct stat sb; |
| |
have_consume_idler = FALSE; |
if (lstat(MONITOR(smon)->pathname, &sb) >= 0) |
return FALSE; |
{ |
|
gam_kqueue_poller_remove_sub_monitor(&missing_smon_poller, smon); |
|
gam_kqueue_sub_monitor_enable_notification(smon, SUB_MONITOR_WAS_MISSING | ((sb.st_mode & S_IFDIR) != 0 ? MONITOR_ISDIR : MONITOR_ISNOTDIR)); |
|
} |
} | } |
| |
static void | static void |
gam_kqueue_consume_subscriptions(void) |
gam_kqueue_unsupported_smon_poll (SubMonitor *smon) |
{ | { |
GSource *source; |
Monitor *mon = MONITOR(smon); |
|
MiniStat sb; |
|
GaminEventType event; |
|
|
|
if (++mon->poll_count == CFG_UNSUPPORTED_SMON_KQUEUE_RETRY_FREQUENCY) |
|
{ |
|
mon->poll_count = 0; |
|
if (gam_kqueue_monitor_enable_kqueue(mon)) |
|
gam_kqueue_poller_remove_sub_monitor(&missing_smon_poller, smon); |
|
} |
|
|
|
gam_kqueue_mini_lstat(mon->pathname, &sb); |
|
|
|
if (! sb.exists && mon->sb.exists) |
|
event = GAMIN_EVENT_DELETED; |
|
else if (gam_kqueue_differs(&sb, &mon->sb)) |
|
event = GAMIN_EVENT_CHANGED; |
|
else |
|
return; |
|
|
|
memcpy(&mon->sb, &sb, sizeof(sb)); |
|
gam_kqueue_sub_monitor_emit_event(smon, event, (sb.mode & S_IFDIR) != 0 ? MONITOR_ISDIR : MONITOR_ISNOTDIR); |
|
} |
| |
if (have_consume_idler) |
static void |
return; |
gam_kqueue_unsupported_fmon_poll_foreach_cb (FileMonitor *fmon, |
|
gpointer unused, |
|
gpointer user_data) |
|
{ |
|
Monitor *mon = MONITOR(fmon); |
|
MiniStat sb; |
|
GaminEventType event; |
|
|
|
if (++mon->poll_count == CFG_UNSUPPORTED_FMON_KQUEUE_RETRY_FREQUENCY) |
|
{ |
|
mon->poll_count = 0; |
|
if (gam_kqueue_monitor_enable_kqueue(mon)) |
|
gam_kqueue_hash_table_remove(fmon->smon->unsupported_fmons, fmon); |
|
} |
| |
have_consume_idler = TRUE; |
gam_kqueue_mini_lstat(mon->pathname, &sb); |
| |
source = g_idle_source_new (); |
if (! sb.exists && mon->sb.exists) |
g_source_set_callback (source, gam_kqueue_consume_subscriptions_real, NULL, NULL); |
event = GAMIN_EVENT_DELETED; |
|
else if (gam_kqueue_differs(&sb, &mon->sb)) |
|
event = GAMIN_EVENT_CHANGED; |
|
else |
|
return; |
| |
g_source_attach (source, NULL); |
memcpy(&mon->sb, &sb, sizeof(sb)); |
|
gam_kqueue_file_monitor_emit_event(fmon, event, (sb.mode & S_IFDIR) != 0 ? MONITOR_ISDIR : MONITOR_ISNOTDIR); |
} | } |
| |
/** |
static void |
* @defgroup kqueue kqueue backend |
gam_kqueue_unsupported_fmon_poll (SubMonitor *smon) |
* @ingroup Backends |
{ |
* @brief kqueue backend API |
gam_kqueue_hash_table_foreach(smon->unsupported_fmons, (GHFunc) gam_kqueue_unsupported_fmon_poll_foreach_cb, NULL); |
* |
} |
* Since 4.1, FreeBSD kernels have included the kernel event notification |
|
* machanism (kqueue). This backend uses kqueue to know when |
|
* files are changed/created/deleted. |
|
* |
|
* @{ |
|
*/ |
|
| |
|
/*** Gamin backend implementation ********************************************/ |
| |
/** | /** |
* Initializes the kqueue system. This must be called before | * Initializes the kqueue system. This must be called before |
|
|
* @returns TRUE if initialization succeeded, FALSE otherwise | * @returns TRUE if initialization succeeded, FALSE otherwise |
*/ | */ |
gboolean | gboolean |
gam_kqueue_init(void) |
gam_kqueue_init (void) |
{ | { |
kq = kqueue(); |
GIOChannel *channel; |
if (kq == -1) { |
unsigned int maxfiles; |
GAM_DEBUG(DEBUG_INFO, "Could not initialize a new kqueue\n"); |
unsigned int maxfilesperproc; |
return FALSE; |
|
|
kq = kqueue(); |
|
if (kq < 0) |
|
{ |
|
gam_error(DEBUG_INFO, "kqueue initialization failure: %s\n", g_strerror(errno)); |
|
return FALSE; |
} | } |
| |
g_timeout_add(1000, gam_kqueue_exist_check, NULL); |
if (! gam_kqueue_get_uint_sysctl("kern.maxfiles", &maxfiles)) |
g_timeout_add(1000, gam_kqueue_event_handler, NULL); |
return FALSE; |
|
if (! gam_kqueue_get_uint_sysctl("kern.maxfilesperproc", &maxfilesperproc)) |
dir_path_hash = g_hash_table_new(g_str_hash, g_str_equal); |
return FALSE; |
file_path_hash = g_hash_table_new(g_str_hash, g_str_equal); |
|
fd_hash = g_hash_table_new(g_direct_hash, g_direct_equal); |
/* |
|
* We make sure to: |
GAM_DEBUG(DEBUG_INFO, "kqueue initialized\n"); |
* - never paralyze the system (CFG_GLOBAL_FILE_RESERVE_RATIO) |
|
* - never paralyze our own process (CFG_SELF_FILE_RESERVE) |
gam_backend_add_subscription = gam_kqueue_add_subscription; |
*/ |
gam_backend_remove_subscription = gam_kqueue_remove_subscription; |
|
gam_backend_remove_all_for = gam_kqueue_remove_all_for; |
maxfiles *= CFG_GLOBAL_FILE_RESERVE_RATIO; |
|
maxfilesperproc = maxfilesperproc > CFG_SELF_FILE_RESERVE |
|
? maxfilesperproc - CFG_SELF_FILE_RESERVE |
|
: 0; |
|
|
|
max_open_files = MIN(maxfiles, maxfilesperproc); |
|
|
|
dir_hash = g_hash_table_new(g_str_hash, g_str_equal); |
|
file_hash = g_hash_table_new(g_str_hash, g_str_equal); |
|
|
|
gam_kqueue_poller_init(&missing_smon_poller, |
|
gam_kqueue_missing_smon_poll, |
|
CFG_MISSING_SMON_POLL_INTERVAL); |
|
gam_kqueue_poller_init(&unsupported_smon_poller, |
|
gam_kqueue_unsupported_smon_poll, |
|
CFG_UNSUPPORTED_SMON_POLL_INTERVAL); |
|
gam_kqueue_poller_init(&unsupported_fmon_poller, |
|
gam_kqueue_unsupported_fmon_poll, |
|
CFG_UNSUPPORTED_FMON_POLL_INTERVAL); |
|
|
|
channel = g_io_channel_unix_new(kq); |
|
g_io_add_watch(channel, G_IO_IN, gam_kqueue_kevent_cb, NULL); |
|
|
|
gam_backend_add_subscription = gam_kqueue_add_subscription; |
|
gam_backend_remove_subscription = gam_kqueue_remove_subscription; |
|
gam_backend_remove_all_for = gam_kqueue_remove_all_for; |
| |
return TRUE; |
return TRUE; |
} | } |
| |
/** | /** |
|
|
* @returns TRUE if adding the subscription succeeded, FALSE otherwise | * @returns TRUE if adding the subscription succeeded, FALSE otherwise |
*/ | */ |
gboolean | gboolean |
gam_kqueue_add_subscription(GamSubscription * sub) |
gam_kqueue_add_subscription (GamSubscription *sub) |
{ | { |
gam_listener_add_subscription(gam_subscription_get_listener(sub), sub); |
const char *path; |
|
GHashTable *hash; |
G_LOCK(new_subs); |
SubMonitor *smon; |
new_subs = g_list_prepend(new_subs, sub); |
|
G_UNLOCK(new_subs); |
gam_listener_add_subscription(gam_subscription_get_listener(sub), sub); |
|
|
|
path = gam_subscription_get_path(sub); |
|
hash = gam_subscription_is_dir(sub) ? dir_hash : file_hash; |
|
smon = g_hash_table_lookup(hash, path); |
|
|
|
if (smon) |
|
{ |
|
smon->subs = g_list_append(smon->subs, sub); |
|
return TRUE; |
|
} |
|
|
|
smon = gam_kqueue_sub_monitor_new(sub); |
|
smon->subs = g_list_append(smon->subs, sub); |
| |
GAM_DEBUG(DEBUG_INFO, "kqueue_add_sub\n"); |
g_hash_table_insert(hash, MONITOR(smon)->pathname, smon); |
|
gam_kqueue_sub_monitor_enable_notification(smon, 0); |
| |
gam_kqueue_consume_subscriptions(); |
return TRUE; |
return TRUE; |
|
} | } |
| |
/** | /** |
|
|
* @returns TRUE if removing the subscription succeeded, FALSE otherwise | * @returns TRUE if removing the subscription succeeded, FALSE otherwise |
*/ | */ |
gboolean | gboolean |
gam_kqueue_remove_subscription(GamSubscription * sub) |
gam_kqueue_remove_subscription (GamSubscription *sub) |
{ | { |
G_LOCK(new_subs); |
GHashTable *hash; |
if (g_list_find(new_subs, sub)) { |
SubMonitor *smon; |
GAM_DEBUG(DEBUG_INFO, "removed sub found on new_subs\n"); |
|
new_subs = g_list_remove_all (new_subs, sub); |
|
G_UNLOCK(new_subs); |
|
return TRUE; |
|
} |
|
G_UNLOCK(new_subs); |
|
| |
gam_subscription_cancel (sub); |
hash = gam_subscription_is_dir(sub) ? dir_hash : file_hash; |
gam_listener_remove_subscription(gam_subscription_get_listener(sub), sub); |
smon = g_hash_table_lookup(hash, gam_subscription_get_path(sub)); |
| |
G_LOCK(removed_subs); |
if (! smon) |
removed_subs = g_list_prepend (removed_subs, sub); |
return FALSE; |
G_UNLOCK(removed_subs); |
|
| |
GAM_DEBUG(DEBUG_INFO, "kqueue_remove_sub\n"); |
smon->subs = g_list_remove_all(smon->subs, sub); |
gam_kqueue_consume_subscriptions(); |
if (! smon->subs) |
|
{ |
|
g_hash_table_remove(hash, MONITOR(smon)->pathname); |
|
gam_kqueue_sub_monitor_free(smon); |
|
} |
| |
return TRUE; |
gam_subscription_cancel(sub); |
|
gam_listener_remove_subscription(gam_subscription_get_listener(sub), sub); |
|
|
|
return TRUE; |
} | } |
| |
/** | /** |
|
|
* @returns TRUE if removing the subscriptions succeeded, FALSE otherwise | * @returns TRUE if removing the subscriptions succeeded, FALSE otherwise |
*/ | */ |
gboolean | gboolean |
gam_kqueue_remove_all_for(GamListener * listener) |
gam_kqueue_remove_all_for (GamListener *listener) |
{ | { |
GList *subs, *l = NULL; |
GList *subs; |
|
GList *l; |
subs = gam_listener_get_subscriptions (listener); |
gboolean success = TRUE; |
|
|
for (l = subs; l; l = l->next) { |
subs = gam_listener_get_subscriptions(listener); |
GamSubscription *sub = l->data; |
|
|
for (l = subs; l != NULL; l = l->next) |
g_assert (sub != NULL); |
if (! gam_kqueue_remove_subscription(l->data)) |
|
success = FALSE; |
gam_kqueue_remove_subscription (sub); |
|
|
g_list_free(subs); |
} |
|
|
return success; |
if (subs) { |
|
g_list_free (subs); |
|
gam_kqueue_consume_subscriptions(); |
|
return TRUE; |
|
} else { |
|
return FALSE; |
|
} |
|
} | } |
|
|
/** @} */ |
|