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
);
214 ip_watched_file_add_sub (ip_watched_file_t
*file
,
217 file
->subs
= g_list_prepend (file
->subs
, sub
);
221 ip_watched_file_start (ip_watched_file_t
*file
)
227 file
->wd
= _ik_watch (file
->path
,
228 IP_INOTIFY_FILE_MASK
,
232 ip_map_wd_file (file
->wd
, file
);
237 ip_watched_file_stop (ip_watched_file_t
*file
)
241 _ik_ignore (file
->path
, file
->wd
);
242 ip_unmap_wd_file (file
->wd
, file
);
248 _ip_start_watching (inotify_sub
*sub
)
252 ip_watched_dir_t
*dir
;
255 g_assert (!sub
->cancelled
);
256 g_assert (sub
->dirname
);
258 IP_W ("Starting to watch %s\n", sub
->dirname
);
259 dir
= g_hash_table_lookup (path_dir_hash
, sub
->dirname
);
263 IP_W ("Trying to add inotify watch ");
264 wd
= _ik_watch (sub
->dirname
, IP_INOTIFY_DIR_MASK
|IN_ONLYDIR
, &err
);
272 /* Create new watched directory and associate it with the
273 * wd hash and path hash
276 dir
= ip_watched_dir_new (sub
->dirname
, wd
);
277 ip_map_wd_dir (wd
, dir
);
278 ip_map_path_dir (sub
->dirname
, dir
);
282 IP_W ("Already watching\n");
286 ip_watched_file_t
*file
;
288 file
= g_hash_table_lookup (dir
->files_hash
, sub
->filename
);
292 file
= ip_watched_file_new (sub
->dirname
, sub
->filename
);
293 g_hash_table_insert (dir
->files_hash
, file
->filename
, file
);
296 ip_watched_file_add_sub (file
, sub
);
297 ip_watched_file_start (file
);
300 ip_map_sub_dir (sub
, dir
);
306 ip_unmap_path_dir (const char *path
,
307 ip_watched_dir_t
*dir
)
309 g_assert (path
&& dir
);
310 g_hash_table_remove (path_dir_hash
, dir
->path
);
314 ip_unmap_wd_dir (gint32 wd
,
315 ip_watched_dir_t
*dir
)
317 GList
*dir_list
= g_hash_table_lookup (wd_dir_hash
, GINT_TO_POINTER (wd
));
322 g_assert (wd
>= 0 && dir
);
323 dir_list
= g_list_remove (dir_list
, dir
);
324 if (dir_list
== NULL
)
325 g_hash_table_remove (wd_dir_hash
, GINT_TO_POINTER (dir
->wd
));
327 g_hash_table_replace (wd_dir_hash
, GINT_TO_POINTER (dir
->wd
), dir_list
);
331 ip_unmap_wd (gint32 wd
)
333 GList
*dir_list
= g_hash_table_lookup (wd_dir_hash
, GINT_TO_POINTER (wd
));
337 g_hash_table_remove (wd_dir_hash
, GINT_TO_POINTER (wd
));
338 g_list_free (dir_list
);
342 ip_unmap_sub_dir (inotify_sub
*sub
,
343 ip_watched_dir_t
*dir
)
345 g_assert (sub
&& dir
);
346 g_hash_table_remove (sub_dir_hash
, sub
);
347 dir
->subs
= g_list_remove (dir
->subs
, sub
);
351 ip_watched_file_t
*file
;
353 file
= g_hash_table_lookup (dir
->files_hash
, sub
->filename
);
354 file
->subs
= g_list_remove (file
->subs
, sub
);
356 if (file
->subs
== NULL
)
358 g_hash_table_remove (dir
->files_hash
, sub
->filename
);
359 ip_watched_file_stop (file
);
360 ip_watched_file_free (file
);
366 ip_unmap_all_subs (ip_watched_dir_t
*dir
)
368 while (dir
->subs
!= NULL
)
369 ip_unmap_sub_dir (dir
->subs
->data
, dir
);
373 _ip_stop_watching (inotify_sub
*sub
)
375 ip_watched_dir_t
*dir
= NULL
;
377 dir
= g_hash_table_lookup (sub_dir_hash
, sub
);
381 ip_unmap_sub_dir (sub
, dir
);
383 /* No one is subscribing to this directory any more */
384 if (dir
->subs
== NULL
)
386 _ik_ignore (dir
->path
, dir
->wd
);
387 ip_unmap_wd_dir (dir
->wd
, dir
);
388 ip_unmap_path_dir (dir
->path
, dir
);
389 ip_watched_dir_free (dir
);
396 static ip_watched_dir_t
*
397 ip_watched_dir_new (const char *path
,
400 ip_watched_dir_t
*dir
= g_new0 (ip_watched_dir_t
, 1);
402 dir
->path
= g_strdup (path
);
403 dir
->files_hash
= g_hash_table_new (g_str_hash
, g_str_equal
);
410 ip_watched_dir_free (ip_watched_dir_t
*dir
)
412 g_assert_cmpint (g_hash_table_size (dir
->files_hash
), ==, 0);
413 g_assert (dir
->subs
== NULL
);
415 g_hash_table_unref (dir
->files_hash
);
420 ip_wd_delete (gpointer data
,
423 ip_watched_dir_t
*dir
= data
;
426 for (l
= dir
->subs
; l
; l
= l
->next
)
428 inotify_sub
*sub
= l
->data
;
429 /* Add subscription to missing list */
432 ip_unmap_all_subs (dir
);
433 /* Unassociate the path and the directory */
434 ip_unmap_path_dir (dir
->path
, dir
);
435 ip_watched_dir_free (dir
);
439 ip_event_dispatch (GList
*dir_list
,
443 gboolean interesting
= FALSE
;
450 for (l
= dir_list
; l
; l
= l
->next
)
453 ip_watched_dir_t
*dir
= l
->data
;
455 for (subl
= dir
->subs
; subl
; subl
= subl
->next
)
457 inotify_sub
*sub
= subl
->data
;
459 /* If the subscription and the event
460 * contain a filename and they don't
461 * match, we don't deliver this event.
465 strcmp (sub
->filename
, event
->name
) &&
466 (!event
->pair
|| !event
->pair
->name
|| strcmp (sub
->filename
, event
->pair
->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 interesting
|= 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 interesting
|= event_callback (event
, sub
, TRUE
);
529 ip_event_callback (ik_event_t
*event
)
531 gboolean interesting
= FALSE
;
532 GList
* dir_list
= NULL
;
533 GList
*file_list
= NULL
;
535 /* We can ignore the IGNORED events. Likewise, if the event queue overflowed,
536 * there is not much we can do to recover. */
537 if (event
->mask
& (IN_IGNORED
| IN_Q_OVERFLOW
))
539 _ik_event_free (event
);
543 dir_list
= g_hash_table_lookup (wd_dir_hash
, GINT_TO_POINTER (event
->wd
));
544 file_list
= g_hash_table_lookup (wd_file_hash
, GINT_TO_POINTER (event
->wd
));
546 if (event
->mask
& IP_INOTIFY_DIR_MASK
)
547 interesting
|= ip_event_dispatch (dir_list
, file_list
, event
);
549 /* Only deliver paired events if the wds are separate */
550 if (event
->pair
&& event
->pair
->wd
!= event
->wd
)
552 dir_list
= g_hash_table_lookup (wd_dir_hash
, GINT_TO_POINTER (event
->pair
->wd
));
553 file_list
= g_hash_table_lookup (wd_file_hash
, GINT_TO_POINTER (event
->pair
->wd
));
555 if (event
->pair
->mask
& IP_INOTIFY_DIR_MASK
)
556 interesting
|= ip_event_dispatch (dir_list
, file_list
, event
->pair
);
559 /* We have to manage the missing list
560 * when we get an event that means the
561 * file has been deleted/moved/unmounted.
563 if (event
->mask
& IN_DELETE_SELF
||
564 event
->mask
& IN_MOVE_SELF
||
565 event
->mask
& IN_UNMOUNT
)
567 /* Add all subscriptions to missing list */
568 g_list_foreach (dir_list
, ip_wd_delete
, NULL
);
569 /* Unmap all directories attached to this wd */
570 ip_unmap_wd (event
->wd
);
573 _ik_event_free (event
);
579 _ip_get_path_for_wd (gint32 wd
)
582 ip_watched_dir_t
*dir
;
585 dir_list
= g_hash_table_lookup (wd_dir_hash
, GINT_TO_POINTER (wd
));
588 dir
= dir_list
->data
;