1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
3 /* inotify-path.c - GVFS Directory Monitor based on inotify.
5 Copyright (C) 2006 John McCutchan
6 Copyright (C) 2009 Codethink Limited
8 The Gnome Library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
13 The Gnome Library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
18 You should have received a copy of the GNU Library General Public
19 License along with the Gnome Library; see the file COPYING.LIB. If not,
20 see <http://www.gnu.org/licenses/>.
23 John McCutchan <john@johnmccutchan.com>
24 Ryan Lortie <desrt@desrt.ca>
29 /* Don't put conflicting kernel types in the global namespace: */
30 #define __KERNEL_STRICT_NAMES
32 #include <sys/inotify.h>
35 #include "inotify-kernel.h"
36 #include "inotify-path.h"
37 #include "inotify-missing.h"
39 #define IP_INOTIFY_DIR_MASK (IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_DELETE|IN_CREATE|IN_DELETE_SELF|IN_UNMOUNT|IN_MOVE_SELF|IN_CLOSE_WRITE)
41 #define IP_INOTIFY_FILE_MASK (IN_MODIFY|IN_ATTRIB|IN_CLOSE_WRITE)
43 /* Older libcs don't have this */
48 typedef struct ip_watched_file_s {
56 typedef struct ip_watched_dir_s {
58 /* TODO: We need to maintain a tree of watched directories
59 * so that we can deliver move/delete events to sub folders.
60 * Or the application could do it...
62 struct ip_watched_dir_s* parent;
65 /* basename -> ip_watched_file_t
66 * Maps basename to a ip_watched_file_t if the file is currently
67 * being directly watched for changes (ie: 'hardlinks' mode).
69 GHashTable *files_hash;
74 /* List of inotify subscriptions */
78 static gboolean ip_debug_enabled = FALSE;
79 #define IP_W if (ip_debug_enabled) g_warning
81 /* path -> ip_watched_dir */
82 static GHashTable * path_dir_hash = NULL;
83 /* inotify_sub * -> ip_watched_dir *
85 * Each subscription is attached to a watched directory or it is on
88 static GHashTable * sub_dir_hash = NULL;
89 /* This hash holds GLists of ip_watched_dir_t *'s
90 * We need to hold a list because symbolic links can share
93 static GHashTable * wd_dir_hash = NULL;
94 /* This hash holds GLists of ip_watched_file_t *'s
95 * We need to hold a list because links can share
98 static GHashTable * wd_file_hash = NULL;
100 static ip_watched_dir_t *ip_watched_dir_new (const char *path,
102 static void ip_watched_dir_free (ip_watched_dir_t *dir);
103 static void ip_event_callback (ik_event_t *event);
106 static void (*event_callback)(ik_event_t *event, inotify_sub *sub, gboolean file_event);
109 _ip_startup (void (*cb)(ik_event_t *event, inotify_sub *sub, gboolean file_event))
111 static gboolean initialized = FALSE;
112 static gboolean result = FALSE;
114 if (initialized == TRUE)
118 result = _ik_startup (ip_event_callback);
123 path_dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
124 sub_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
125 wd_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
126 wd_file_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
133 ip_map_path_dir (const char *path,
134 ip_watched_dir_t *dir)
136 g_assert (path && dir);
137 g_hash_table_insert (path_dir_hash, dir->path, dir);
141 ip_map_sub_dir (inotify_sub *sub,
142 ip_watched_dir_t *dir)
144 /* Associate subscription and directory */
145 g_assert (dir && sub);
146 g_hash_table_insert (sub_dir_hash, sub, dir);
147 dir->subs = g_list_prepend (dir->subs, sub);
151 ip_map_wd_dir (gint32 wd,
152 ip_watched_dir_t *dir)
156 g_assert (wd >= 0 && dir);
157 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
158 dir_list = g_list_prepend (dir_list, dir);
159 g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
163 ip_map_wd_file (gint32 wd,
164 ip_watched_file_t *file)
168 g_assert (wd >= 0 && file);
169 file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
170 file_list = g_list_prepend (file_list, file);
171 g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
175 ip_unmap_wd_file (gint32 wd,
176 ip_watched_file_t *file)
178 GList *file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
183 g_assert (wd >= 0 && file);
184 file_list = g_list_remove (file_list, file);
185 if (file_list == NULL)
186 g_hash_table_remove (wd_file_hash, GINT_TO_POINTER (wd));
188 g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
192 static ip_watched_file_t *
193 ip_watched_file_new (const gchar *dirname,
194 const gchar *filename)
196 ip_watched_file_t *file;
198 file = g_new0 (ip_watched_file_t, 1);
199 file->path = g_strjoin ("/", dirname, filename, NULL);
200 file->filename = g_strdup (filename);
207 ip_watched_file_free (ip_watched_file_t *file)
209 g_assert (file->subs == NULL);
210 g_free (file->filename);
215 ip_watched_file_add_sub (ip_watched_file_t *file,
218 file->subs = g_list_prepend (file->subs, sub);
222 ip_watched_file_start (ip_watched_file_t *file)
228 file->wd = _ik_watch (file->path,
229 IP_INOTIFY_FILE_MASK,
233 ip_map_wd_file (file->wd, file);
238 ip_watched_file_stop (ip_watched_file_t *file)
242 _ik_ignore (file->path, file->wd);
243 ip_unmap_wd_file (file->wd, file);
249 _ip_start_watching (inotify_sub *sub)
253 ip_watched_dir_t *dir;
256 g_assert (!sub->cancelled);
257 g_assert (sub->dirname);
259 IP_W ("Starting to watch %s\n", sub->dirname);
260 dir = g_hash_table_lookup (path_dir_hash, sub->dirname);
264 IP_W ("Trying to add inotify watch ");
265 wd = _ik_watch (sub->dirname, IP_INOTIFY_DIR_MASK|IN_ONLYDIR, &err);
273 /* Create new watched directory and associate it with the
274 * wd hash and path hash
277 dir = ip_watched_dir_new (sub->dirname, wd);
278 ip_map_wd_dir (wd, dir);
279 ip_map_path_dir (sub->dirname, dir);
283 IP_W ("Already watching\n");
287 ip_watched_file_t *file;
289 file = g_hash_table_lookup (dir->files_hash, sub->filename);
293 file = ip_watched_file_new (sub->dirname, sub->filename);
294 g_hash_table_insert (dir->files_hash, file->filename, file);
297 ip_watched_file_add_sub (file, sub);
298 ip_watched_file_start (file);
301 ip_map_sub_dir (sub, dir);
307 ip_unmap_path_dir (const char *path,
308 ip_watched_dir_t *dir)
310 g_assert (path && dir);
311 g_hash_table_remove (path_dir_hash, dir->path);
315 ip_unmap_wd_dir (gint32 wd,
316 ip_watched_dir_t *dir)
318 GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
323 g_assert (wd >= 0 && dir);
324 dir_list = g_list_remove (dir_list, dir);
325 if (dir_list == NULL)
326 g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (dir->wd));
328 g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
332 ip_unmap_wd (gint32 wd)
334 GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
338 g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (wd));
339 g_list_free (dir_list);
343 ip_unmap_sub_dir (inotify_sub *sub,
344 ip_watched_dir_t *dir)
346 g_assert (sub && dir);
347 g_hash_table_remove (sub_dir_hash, sub);
348 dir->subs = g_list_remove (dir->subs, sub);
352 ip_watched_file_t *file;
354 file = g_hash_table_lookup (dir->files_hash, sub->filename);
355 file->subs = g_list_remove (file->subs, sub);
357 if (file->subs == NULL)
359 g_hash_table_remove (dir->files_hash, sub->filename);
360 ip_watched_file_stop (file);
361 ip_watched_file_free (file);
367 ip_unmap_all_subs (ip_watched_dir_t *dir)
369 while (dir->subs != NULL)
370 ip_unmap_sub_dir (dir->subs->data, dir);
374 _ip_stop_watching (inotify_sub *sub)
376 ip_watched_dir_t *dir = NULL;
378 dir = g_hash_table_lookup (sub_dir_hash, sub);
382 ip_unmap_sub_dir (sub, dir);
384 /* No one is subscribing to this directory any more */
385 if (dir->subs == NULL)
387 _ik_ignore (dir->path, dir->wd);
388 ip_unmap_wd_dir (dir->wd, dir);
389 ip_unmap_path_dir (dir->path, dir);
390 ip_watched_dir_free (dir);
397 static ip_watched_dir_t *
398 ip_watched_dir_new (const char *path,
401 ip_watched_dir_t *dir = g_new0 (ip_watched_dir_t, 1);
403 dir->path = g_strdup (path);
404 dir->files_hash = g_hash_table_new (g_str_hash, g_str_equal);
411 ip_watched_dir_free (ip_watched_dir_t *dir)
413 g_assert_cmpint (g_hash_table_size (dir->files_hash), ==, 0);
414 g_assert (dir->subs == NULL);
416 g_hash_table_unref (dir->files_hash);
421 ip_wd_delete (gpointer data,
424 ip_watched_dir_t *dir = data;
427 for (l = dir->subs; l; l = l->next)
429 inotify_sub *sub = l->data;
430 /* Add subscription to missing list */
433 ip_unmap_all_subs (dir);
434 /* Unassociate the path and the directory */
435 ip_unmap_path_dir (dir->path, dir);
436 ip_watched_dir_free (dir);
440 ip_event_dispatch (GList *dir_list,
441 GList *pair_dir_list,
443 GList *pair_file_list,
451 for (l = dir_list; l; l = l->next)
454 ip_watched_dir_t *dir = l->data;
456 for (subl = dir->subs; subl; subl = subl->next)
458 inotify_sub *sub = subl->data;
460 /* If the subscription and the event
461 * contain a filename and they don't
462 * match, we don't deliver this event.
466 strcmp (sub->filename, event->name))
469 /* If the subscription has a filename
470 * but this event doesn't, we don't
471 * deliver this event.
473 if (sub->filename && !event->name)
476 /* If we're also watching the file directly
477 * don't report events that will also be
478 * reported on the file itself.
482 event->mask &= ~IP_INOTIFY_FILE_MASK;
487 /* FIXME: We might need to synthesize
488 * DELETE/UNMOUNT events when
489 * the filename doesn't match
492 event_callback (event, sub, FALSE);
496 ip_watched_file_t *file;
498 file = g_hash_table_lookup (dir->files_hash, sub->filename);
502 if (event->mask & (IN_MOVED_FROM | IN_DELETE))
503 ip_watched_file_stop (file);
505 if (event->mask & (IN_MOVED_TO | IN_CREATE))
506 ip_watched_file_start (file);
512 for (l = file_list; l; l = l->next)
514 ip_watched_file_t *file = l->data;
517 for (subl = file->subs; subl; subl = subl->next)
519 inotify_sub *sub = subl->data;
521 event_callback (event, sub, TRUE);
528 for (l = pair_dir_list; l; l = l->next)
531 ip_watched_dir_t *dir = l->data;
533 for (subl = dir->subs; subl; subl = subl->next)
535 inotify_sub *sub = subl->data;
537 /* If the subscription and the event
538 * contain a filename and they don't
539 * match, we don't deliver this event.
543 strcmp (sub->filename, event->pair->name))
546 /* If the subscription has a filename
547 * but this event doesn't, we don't
548 * deliver this event.
550 if (sub->filename && !event->pair->name)
553 /* If we're also watching the file directly
554 * don't report events that will also be
555 * reported on the file itself.
559 event->mask &= ~IP_INOTIFY_FILE_MASK;
564 /* FIXME: We might need to synthesize
565 * DELETE/UNMOUNT events when
566 * the filename doesn't match
569 event_callback (event->pair, sub, FALSE);
573 ip_watched_file_t *file;
575 file = g_hash_table_lookup (dir->files_hash, sub->filename);
579 if (event->pair->mask & (IN_MOVED_FROM | IN_DELETE))
580 ip_watched_file_stop (file);
582 if (event->pair->mask & (IN_MOVED_TO | IN_CREATE))
583 ip_watched_file_start (file);
589 for (l = pair_file_list; l; l = l->next)
591 ip_watched_file_t *file = l->data;
594 for (subl = file->subs; subl; subl = subl->next)
596 inotify_sub *sub = subl->data;
598 event_callback (event->pair, sub, TRUE);
604 ip_event_callback (ik_event_t *event)
606 GList* dir_list = NULL;
607 GList* pair_dir_list = NULL;
608 GList *file_list = NULL;
609 GList *pair_file_list = NULL;
611 /* We can ignore the IGNORED events */
612 if (event->mask & IN_IGNORED)
614 _ik_event_free (event);
618 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd));
619 file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->wd));
623 pair_dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
624 pair_file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->pair->wd));
627 if (event->mask & IP_INOTIFY_DIR_MASK)
628 ip_event_dispatch (dir_list, pair_dir_list, file_list, pair_file_list, event);
630 /* We have to manage the missing list
631 * when we get an event that means the
632 * file has been deleted/moved/unmounted.
634 if (event->mask & IN_DELETE_SELF ||
635 event->mask & IN_MOVE_SELF ||
636 event->mask & IN_UNMOUNT)
638 /* Add all subscriptions to missing list */
639 g_list_foreach (dir_list, ip_wd_delete, NULL);
640 /* Unmap all directories attached to this wd */
641 ip_unmap_wd (event->wd);
644 _ik_event_free (event);
648 _ip_get_path_for_wd (gint32 wd)
651 ip_watched_dir_t *dir;
654 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
657 dir = dir_list->data;