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 Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 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 Library General Public License for more details.
18 You should have received a copy of the GNU Library 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 */
536 if (event
->mask
& IN_IGNORED
)
538 _ik_event_free (event
);
542 dir_list
= g_hash_table_lookup (wd_dir_hash
, GINT_TO_POINTER (event
->wd
));
543 file_list
= g_hash_table_lookup (wd_file_hash
, GINT_TO_POINTER (event
->wd
));
545 if (event
->mask
& IP_INOTIFY_DIR_MASK
)
546 interesting
|= ip_event_dispatch (dir_list
, file_list
, event
);
548 /* Only deliver paired events if the wds are separate */
549 if (event
->pair
&& event
->pair
->wd
!= event
->wd
)
551 dir_list
= g_hash_table_lookup (wd_dir_hash
, GINT_TO_POINTER (event
->pair
->wd
));
552 file_list
= g_hash_table_lookup (wd_file_hash
, GINT_TO_POINTER (event
->pair
->wd
));
554 if (event
->pair
->mask
& IP_INOTIFY_DIR_MASK
)
555 interesting
|= ip_event_dispatch (dir_list
, file_list
, event
->pair
);
558 /* We have to manage the missing list
559 * when we get an event that means the
560 * file has been deleted/moved/unmounted.
562 if (event
->mask
& IN_DELETE_SELF
||
563 event
->mask
& IN_MOVE_SELF
||
564 event
->mask
& IN_UNMOUNT
)
566 /* Add all subscriptions to missing list */
567 g_list_foreach (dir_list
, ip_wd_delete
, NULL
);
568 /* Unmap all directories attached to this wd */
569 ip_unmap_wd (event
->wd
);
572 _ik_event_free (event
);
578 _ip_get_path_for_wd (gint32 wd
)
581 ip_watched_dir_t
*dir
;
584 dir_list
= g_hash_table_lookup (wd_dir_hash
, GINT_TO_POINTER (wd
));
587 dir
= dir_list
->data
;