14
14
* You should have received a copy of the GNU Lesser General Public
15
15
* License along with this library; if not, write to the Free
16
16
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
* Handle removal of subscriptions when we get IGNORE event
19
* The dnotify/poll hybrid backend produces more events
26
#include <sys/ioctl.h>
31
#ifdef HAVE_LINUX_INOTIFY_H
19
#include "server_config.h"
21
/* Just include the local header to stop all the pain */
22
#include "local_inotify.h"
24
#ifdef HAVE_SYS_INOTIFY_H
25
/* We don't actually include the libc header, because there has been
26
* problems with libc versions that was built without inotify support.
27
* Instead we use the local version.
29
#include "local_inotify.h"
30
#elif defined (HAVE_LINUX_INOTIFY_H)
32
31
#include <linux/inotify.h>
34
#include "local_inotify.h"
34
#include "inotify-sub.h"
35
#include "inotify-helper.h"
36
#include "inotify-diag.h"
37
#ifdef GAMIN_DEBUG_API
38
#include "gam_debugging.h"
36
40
#include "gam_error.h"
41
#include "gam_event.h"
42
#include "gam_server.h"
43
#include "gam_subscription.h"
37
44
#include "gam_inotify.h"
39
#include "gam_event.h"
40
#include "gam_server.h"
41
#include "gam_event.h"
50
static GHashTable *path_hash = NULL;
51
static GHashTable *wd_hash = NULL;
53
static GList *new_subs = NULL;
54
G_LOCK_DEFINE_STATIC(new_subs);
55
static GList *removed_subs = NULL;
56
G_LOCK_DEFINE_STATIC(removed_subs);
58
G_LOCK_DEFINE_STATIC(inotify);
59
static GIOChannel *inotify_read_ioc = NULL;
61
static gboolean have_consume_idler = FALSE;
64
int fd = -1; // the device fd
67
gam_inotify_data_new(const char *path, int wd)
71
data = g_new0(INotifyData, 1);
72
data->path = g_strdup(path);
81
gam_inotify_data_free(INotifyData * data)
88
gam_inotify_add_rm_handler(const char *path, GamSubscription *sub, gboolean added)
91
struct inotify_watch_request iwr;
100
subs = g_list_append(subs, sub);
102
if ((data = g_hash_table_lookup(path_hash, path)) != NULL) {
104
data->subs = g_list_prepend(data->subs, sub);
106
gam_debug(DEBUG_INFO, "inotify updated refcount\n");
107
gam_server_emit_event (path, GAMIN_EVENT_EXISTS, subs);
108
gam_server_emit_event (path, GAMIN_EVENT_ENDEXISTS, subs);
112
iwr.dirname = g_strdup(path);
113
iwr.mask = 0xffffffff; // all events
115
wd = ioctl(fd, INOTIFY_WATCH,&iwr);
123
data = gam_inotify_data_new(path, wd);
124
data->subs = g_list_prepend(data->subs, sub);
125
g_hash_table_insert(wd_hash, GINT_TO_POINTER(data->wd), data);
126
g_hash_table_insert(path_hash, data->path, data);
128
gam_debug(DEBUG_INFO, "activated INotify for %s\n", path);
130
gam_server_emit_event (path, GAMIN_EVENT_EXISTS, subs);
131
gam_server_emit_event (path, GAMIN_EVENT_ENDEXISTS, subs);
133
data = g_hash_table_lookup(path_hash, path);
140
if (g_list_find (data->subs, sub)) {
141
data->subs = g_list_remove_all (data->subs, sub);
144
gam_debug(DEBUG_INFO, "inotify decremeneted refcount\n");
146
if (data->refcount == 0) {
147
r = ioctl (fd, INOTIFY_IGNORE, &data->wd);
149
gam_debug (DEBUG_INFO, "INOTIFY_IGNORE failed for %s\n", data->path);
151
gam_debug(DEBUG_INFO, "deactivated INotify for %s\n",
153
g_hash_table_remove(path_hash, data->path);
154
g_hash_table_remove(wd_hash, GINT_TO_POINTER(data->wd));
155
gam_inotify_data_free(data);
163
static GaminEventType inotify_event_to_gamin_event (int mask)
46
/* Transforms a inotify event to a gamin event. */
48
ih_mask_to_EventType (guint32 mask)
54
return GAMIN_EVENT_CHANGED;
57
return GAMIN_EVENT_CHANGED;
63
return GAMIN_EVENT_DELETED;
67
return GAMIN_EVENT_CREATED;
72
case IN_CLOSE_NOWRITE:
83
gam_inotify_send_initial_events (const char *pathname, GamSubscription *sub, gboolean is_dir, gboolean was_missing)
85
GaminEventType gevent;
88
gevent = GAMIN_EVENT_CREATED;
90
if (g_file_test (pathname, G_FILE_TEST_EXISTS))
91
gevent = GAMIN_EVENT_EXISTS;
93
gevent = GAMIN_EVENT_DELETED;
96
gam_server_emit_one_event (pathname, is_dir ? 1 : 0, gevent, sub, 1);
169
return GAMIN_EVENT_CHANGED;
172
case IN_CREATE_SUBDIR:
174
return GAMIN_EVENT_CREATED;
177
case IN_DELETE_SUBDIR:
179
return GAMIN_EVENT_DELETED;
182
return GAMIN_EVENT_UNKNOWN;
185
static void gam_inotify_emit_event (INotifyData *data, struct inotify_event *event)
187
GaminEventType gevent;
193
gevent = inotify_event_to_gamin_event (event->mask);
194
// we got some event that GAMIN doesn't understand
195
if (gevent == GAMIN_EVENT_UNKNOWN) {
196
gam_debug(DEBUG_INFO, "inotify_emit_event got unknown event %x\n", event->mask);
200
if (event->filename[0] != '\0') {
201
int pathlen = strlen(data->path);
202
gam_debug(DEBUG_INFO, "Got filename with event\n");
203
if (data->path[pathlen-1] == '/') {
204
event_path = g_strconcat (data->path, event->filename, NULL);
102
dir = g_dir_open (pathname, 0, &err);
105
const char *filename;
107
while ((filename = g_dir_read_name (dir)))
109
gchar *fullname = g_strdup_printf ("%s/%s", pathname, filename);
110
gboolean file_is_dir = FALSE;
112
memset(&fsb, 0, sizeof (struct stat));
113
lstat(fullname, &fsb);
114
file_is_dir = (fsb.st_mode & S_IFDIR) != 0 ? TRUE : FALSE;
115
gam_server_emit_one_event (fullname, file_is_dir ? 1 : 0, gevent, sub, 1);
206
event_path = g_strconcat (data->path, "/", event->filename, NULL);
209
gam_debug(DEBUG_INFO, "Got no filename with event\n");
210
event_path = g_strdup (data->path);
213
gam_debug(DEBUG_INFO, "gam_inotify_emit_event() %s\n", event_path);
215
gam_server_emit_event (event_path, gevent, data->subs);
219
gam_inotify_read_handler(gpointer user_data)
221
struct inotify_event event;
224
gam_debug(DEBUG_INFO, "gam_inotify_read_handler()\n");
227
if (g_io_channel_read_chars(inotify_read_ioc, (char *)&event, sizeof(struct inotify_event), NULL, NULL) != G_IO_STATUS_NORMAL) {
229
gam_debug(DEBUG_INFO, "gam_inotify_read_handler failed\n");
233
/* When we get an ignore event, we
234
* remove all the subscriptions for this wd
236
if (event.mask == IN_IGNORED) {
238
data = g_hash_table_lookup (wd_hash, GINT_TO_POINTER(event.wd));
247
for (l = l; l; l = l->next) {
248
GamSubscription *sub = l->data;
249
gam_inotify_remove_subscription (sub);
255
data = g_hash_table_lookup (wd_hash, GINT_TO_POINTER(event.wd));
258
gam_debug(DEBUG_INFO, "Could not find WD %d in hash\n", event.wd);
263
gam_inotify_emit_event (data, &event);
265
gam_debug(DEBUG_INFO, "gam_inotify event for %s (%x) %s\n", data->path, event.mask, event.filename);
267
gam_debug(DEBUG_INFO, "gam_inotify_read_handler() done\n");
275
gam_inotify_consume_subscriptions_real(gpointer data)
121
GAM_DEBUG (DEBUG_INFO, "unable to open directory %s: %s\n", pathname, err->message);
129
gam_server_emit_one_event (pathname, is_dir ? 1 : 0, GAMIN_EVENT_ENDEXISTS, sub, 1);
135
gam_inotify_event_callback (const char *fullpath, guint32 mask, void *subdata)
137
GamSubscription *sub = (GamSubscription *)subdata;
138
GaminEventType gevent;
140
gevent = ih_mask_to_EventType (mask);
142
gam_server_emit_one_event (fullpath, gam_subscription_is_dir (sub), gevent, sub, 1);
146
gam_inotify_found_callback (const char *fullpath, void *subdata)
148
GamSubscription *sub = (GamSubscription *)subdata;
150
gam_inotify_send_initial_events (gam_subscription_get_path (sub), sub, gam_subscription_is_dir (sub), TRUE);
155
gam_inotify_init (void)
157
gam_server_install_kernel_hooks (GAMIN_K_INOTIFY2,
158
gam_inotify_add_subscription,
159
gam_inotify_remove_subscription,
160
gam_inotify_remove_all_for,
285
for (l = subs; l; l = l->next) {
286
GamSubscription *sub = l->data;
287
gam_debug(DEBUG_INFO, "called gam_inotify_add_handler()\n");
288
gam_inotify_add_rm_handler (gam_subscription_get_path (sub), sub, TRUE);
295
G_LOCK(removed_subs);
299
G_UNLOCK(removed_subs);
301
for (l = subs; l; l = l->next) {
302
GamSubscription *sub = l->data;
303
gam_debug(DEBUG_INFO, "called gam_inotify_rm_handler()\n");
304
gam_inotify_add_rm_handler (gam_subscription_get_path (sub), sub, FALSE);
307
G_UNLOCK(removed_subs);
310
gam_debug(DEBUG_INFO, "gam_inotify_consume_subscriptions()\n");
312
have_consume_idler = FALSE;
317
gam_inotify_consume_subscriptions(void)
321
if (have_consume_idler)
324
have_consume_idler = TRUE;
326
source = g_idle_source_new ();
327
g_source_set_callback (source, gam_inotify_consume_subscriptions_real, NULL, NULL);
329
g_source_attach (source, NULL);
333
* @defgroup INotify INotify Backend
335
* @brief INotify backend API
337
* Since version 2.6.X, Linux kernels have included the Linux Inode
338
* Notification system (inotify). This backend uses inotify to know when
339
* files are changed/created/deleted.
346
* Initializes the inotify system. This must be called before
347
* any other functions in this module.
349
* @returns TRUE if initialization succeeded, FALSE otherwise
352
gam_inotify_init(void)
356
fd = open("/dev/inotify", O_RDONLY);
359
gam_debug(DEBUG_INFO, "Could not open /dev/inotify\n");
363
inotify_read_ioc = g_io_channel_unix_new(fd);
365
/* For binary data */
366
g_io_channel_set_encoding (inotify_read_ioc, NULL, NULL);
368
g_io_channel_set_flags(inotify_read_ioc, G_IO_FLAG_NONBLOCK, NULL);
370
source = g_io_create_watch(inotify_read_ioc,
371
G_IO_IN | G_IO_HUP | G_IO_ERR);
372
g_source_set_callback(source, gam_inotify_read_handler, NULL, NULL);
374
g_source_attach(source, NULL);
376
path_hash = g_hash_table_new(g_str_hash, g_str_equal);
377
wd_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
379
gam_debug(DEBUG_INFO, "inotify initialized\n");
381
int i = 0; // INOTIFY_DEBUG_INODE|INOTIFY_DEBUG_ERRORS|INOTIFY_DEBUG_EVENTS;
382
ioctl(fd, INOTIFY_SETDEBUG, &i);
384
gam_backend_add_subscription = gam_inotify_add_subscription;
385
gam_backend_remove_subscription = gam_inotify_remove_subscription;
386
gam_backend_remove_all_for = gam_inotify_remove_all_for;
392
* Adds a subscription to be monitored.
394
* @param sub a #GamSubscription to be polled
395
* @returns TRUE if adding the subscription succeeded, FALSE otherwise
398
gam_inotify_add_subscription(GamSubscription * sub)
163
return ih_startup (gam_inotify_event_callback,
164
gam_inotify_found_callback);
168
gam_inotify_add_subscription (GamSubscription *sub)
170
ih_sub_t *isub = NULL;
400
172
gam_listener_add_subscription(gam_subscription_get_listener(sub), sub);
403
new_subs = g_list_prepend(new_subs, sub);
406
gam_debug(DEBUG_INFO, "inotify_add_sub\n");
408
gam_inotify_consume_subscriptions();
413
* Removes a subscription which was being monitored.
415
* @param sub a #GamSubscription to remove
416
* @returns TRUE if removing the subscription succeeded, FALSE otherwise
419
gam_inotify_remove_subscription(GamSubscription * sub)
422
if (g_list_find(new_subs, sub)) {
423
gam_debug(DEBUG_INFO, "removed sub found on new_subs\n");
424
new_subs = g_list_remove_all (new_subs, sub);
430
gam_subscription_cancel (sub);
431
gam_listener_remove_subscription(gam_subscription_get_listener(sub), sub);
433
G_LOCK(removed_subs);
434
removed_subs = g_list_prepend (removed_subs, sub);
435
G_UNLOCK(removed_subs);
437
gam_debug(DEBUG_INFO, "inotify_remove_sub\n");
438
gam_inotify_consume_subscriptions();
444
* Stop monitoring all subscriptions for a given listener.
446
* @param listener a #GamListener
447
* @returns TRUE if removing the subscriptions succeeded, FALSE otherwise
450
gam_inotify_remove_all_for(GamListener * listener)
452
GList *subs, *l = NULL;
454
subs = gam_listener_get_subscriptions (listener);
456
for (l = subs; l; l = l->next) {
457
GamSubscription *sub = l->data;
459
g_assert (sub != NULL);
461
gam_inotify_remove_subscription (sub);
467
gam_inotify_consume_subscriptions();
174
isub = ih_sub_new (gam_subscription_get_path (sub), gam_subscription_is_dir (sub), 0, sub);
176
if (!ih_sub_add (isub))
182
gam_inotify_send_initial_events (gam_subscription_get_path (sub), sub, gam_subscription_is_dir (sub), FALSE);
188
gam_inotify_remove_sub_pred (ih_sub_t *sub, void *callerdata)
190
return sub->usersubdata == callerdata;
194
gam_inotify_remove_subscription (GamSubscription *sub)
196
ih_sub_foreach_free (sub, gam_inotify_remove_sub_pred);
202
gam_inotify_remove_listener_pred (ih_sub_t *sub, void *callerdata)
204
GamSubscription *gsub = (GamSubscription *)sub->usersubdata;
206
return gam_subscription_get_listener (gsub) == callerdata;
210
gam_inotify_remove_all_for (GamListener *listener)
212
ih_sub_foreach_free (listener, gam_inotify_remove_listener_pred);
218
gam_inotify_debug (void)
224
gam_inotify_is_running (void)
226
return ih_running ();