1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
3 /* inotify-path.c - GVFS Monitor based on inotify.
5 Copyright (C) 2006 John McCutchan
6 Copyright (C) 2009 Codethink Limited
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
13 This 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 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with this library; if not, see <http://www.gnu.org/licenses/>.
22 John McCutchan <john@johnmccutchan.com>
23 Ryan Lortie <desrt@desrt.ca>
28 /* Don't put conflicting kernel types in the global namespace: */
29 #define __KERNEL_STRICT_NAMES
31 #include <sys/inotify.h>
34 #include "inotify-kernel.h"
35 #include "inotify-path.h"
36 #include "inotify-missing.h"
38 #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)
40 #define IP_INOTIFY_FILE_MASK (IN_MODIFY|IN_ATTRIB|IN_CLOSE_WRITE)
42 /* Older libcs don't have this */
47 typedef struct ip_watched_file_s {
55 typedef struct ip_watched_dir_s {
57 /* TODO: We need to maintain a tree of watched directories
58 * so that we can deliver move/delete events to sub folders.
59 * Or the application could do it...
61 struct ip_watched_dir_s* parent;
64 /* basename -> ip_watched_file_t
65 * Maps basename to a ip_watched_file_t if the file is currently
66 * being directly watched for changes (ie: 'hardlinks' mode).
68 GHashTable *files_hash;
73 /* List of inotify subscriptions */
77 static gboolean ip_debug_enabled = FALSE;
78 #define IP_W if (ip_debug_enabled) g_warning
80 /* path -> ip_watched_dir */
81 static GHashTable * path_dir_hash = NULL;
82 /* inotify_sub * -> ip_watched_dir *
84 * Each subscription is attached to a watched directory or it is on
87 static GHashTable * sub_dir_hash = NULL;
88 /* This hash holds GLists of ip_watched_dir_t *'s
89 * We need to hold a list because symbolic links can share
92 static GHashTable * wd_dir_hash = NULL;
93 /* This hash holds GLists of ip_watched_file_t *'s
94 * We need to hold a list because links can share
97 static GHashTable * wd_file_hash = NULL;
99 static ip_watched_dir_t *ip_watched_dir_new (const char *path,
101 static void ip_watched_dir_free (ip_watched_dir_t *dir);
102 static gboolean ip_event_callback (ik_event_t *event);
105 static gboolean (*event_callback)(ik_event_t *event, inotify_sub *sub, gboolean file_event);
108 _ip_startup (gboolean (*cb)(ik_event_t *event, inotify_sub *sub, gboolean file_event))
110 static gboolean initialized = FALSE;
111 static gboolean result = FALSE;
113 if (initialized == TRUE)
117 result = _ik_startup (ip_event_callback);
122 path_dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
123 sub_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
124 wd_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
125 wd_file_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
132 ip_map_path_dir (const char *path,
133 ip_watched_dir_t *dir)
135 g_assert (path && dir);
136 g_hash_table_insert (path_dir_hash, dir->path, dir);
140 ip_map_sub_dir (inotify_sub *sub,
141 ip_watched_dir_t *dir)
143 /* Associate subscription and directory */
144 g_assert (dir && sub);
145 g_hash_table_insert (sub_dir_hash, sub, dir);
146 dir->subs = g_list_prepend (dir->subs, sub);
150 ip_map_wd_dir (gint32 wd,
151 ip_watched_dir_t *dir)
155 g_assert (wd >= 0 && dir);
156 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
157 dir_list = g_list_prepend (dir_list, dir);
158 g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
162 ip_map_wd_file (gint32 wd,
163 ip_watched_file_t *file)
167 g_assert (wd >= 0 && file);
168 file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
169 file_list = g_list_prepend (file_list, file);
170 g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
174 ip_unmap_wd_file (gint32 wd,
175 ip_watched_file_t *file)
177 GList *file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
182 g_assert (wd >= 0 && file);
183 file_list = g_list_remove (file_list, file);
184 if (file_list == NULL)
185 g_hash_table_remove (wd_file_hash, GINT_TO_POINTER (wd));
187 g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
191 static ip_watched_file_t *
192 ip_watched_file_new (const gchar *dirname,
193 const gchar *filename)
195 ip_watched_file_t *file;
197 file = g_new0 (ip_watched_file_t, 1);
198 file->path = g_strjoin ("/", dirname, filename, NULL);
199 file->filename = g_strdup (filename);
206 ip_watched_file_free (ip_watched_file_t *file)
208 g_assert (file->subs == NULL);
209 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,
444 gboolean interesting = FALSE;
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) &&
467 (!event->pair || !event->pair->name || strcmp (sub->filename, event->pair->name)))
470 /* If the subscription has a filename
471 * but this event doesn't, we don't
472 * deliver this event.
474 if (sub->filename && !event->name)
477 /* If we're also watching the file directly
478 * don't report events that will also be
479 * reported on the file itself.
483 event->mask &= ~IP_INOTIFY_FILE_MASK;
488 /* FIXME: We might need to synthesize
489 * DELETE/UNMOUNT events when
490 * the filename doesn't match
493 interesting |= event_callback (event, sub, FALSE);
497 ip_watched_file_t *file;
499 file = g_hash_table_lookup (dir->files_hash, sub->filename);
503 if (event->mask & (IN_MOVED_FROM | IN_DELETE))
504 ip_watched_file_stop (file);
506 if (event->mask & (IN_MOVED_TO | IN_CREATE))
507 ip_watched_file_start (file);
513 for (l = file_list; l; l = l->next)
515 ip_watched_file_t *file = l->data;
518 for (subl = file->subs; subl; subl = subl->next)
520 inotify_sub *sub = subl->data;
522 interesting |= event_callback (event, sub, TRUE);
530 ip_event_callback (ik_event_t *event)
532 gboolean interesting = FALSE;
533 GList* dir_list = NULL;
534 GList *file_list = NULL;
536 /* We can ignore the IGNORED events. Likewise, if the event queue overflowed,
537 * there is not much we can do to recover. */
538 if (event->mask & (IN_IGNORED | IN_Q_OVERFLOW))
540 _ik_event_free (event);
544 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd));
545 file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->wd));
547 if (event->mask & IP_INOTIFY_DIR_MASK)
548 interesting |= ip_event_dispatch (dir_list, file_list, event);
550 /* Only deliver paired events if the wds are separate */
551 if (event->pair && event->pair->wd != event->wd)
553 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
554 file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->pair->wd));
556 if (event->pair->mask & IP_INOTIFY_DIR_MASK)
557 interesting |= ip_event_dispatch (dir_list, file_list, event->pair);
560 /* We have to manage the missing list
561 * when we get an event that means the
562 * file has been deleted/moved/unmounted.
564 if (event->mask & IN_DELETE_SELF ||
565 event->mask & IN_MOVE_SELF ||
566 event->mask & IN_UNMOUNT)
568 /* Add all subscriptions to missing list */
569 g_list_foreach (dir_list, ip_wd_delete, NULL);
570 /* Unmap all directories attached to this wd */
571 ip_unmap_wd (event->wd);
574 _ik_event_free (event);
580 _ip_get_path_for_wd (gint32 wd)
583 ip_watched_dir_t *dir;
586 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
589 dir = dir_list->data;