1/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
2
3/* inotify-path.c - GVFS Monitor based on inotify.
4
5 Copyright (C) 2006 John McCutchan
6 Copyright (C) 2009 Codethink Limited
7
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.
12
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.
17
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/>.
20
21 Authors:
22 John McCutchan <john@johnmccutchan.com>
23 Ryan Lortie <desrt@desrt.ca>
24*/
25
26#include "config.h"
27
28/* Don't put conflicting kernel types in the global namespace: */
29#define __KERNEL_STRICT_NAMES
30
31#include <sys/inotify.h>
32#include <string.h>
33#include <glib.h>
34#include "inotify-kernel.h"
35#include "inotify-path.h"
36#include "inotify-missing.h"
37
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)
39
40#define IP_INOTIFY_FILE_MASK (IN_MODIFY|IN_ATTRIB|IN_CLOSE_WRITE)
41
42/* Older libcs don't have this */
43#ifndef IN_ONLYDIR
44#define IN_ONLYDIR 0
45#endif
46
47typedef struct ip_watched_file_s {
48 gchar *filename;
49 gchar *path;
50 gint32 wd;
51
52 GList *subs;
53} ip_watched_file_t;
54
55typedef struct ip_watched_dir_s {
56 char *path;
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...
60 */
61 struct ip_watched_dir_s* parent;
62 GList* children;
63
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).
67 */
68 GHashTable *files_hash;
69
70 /* Inotify state */
71 gint32 wd;
72
73 /* List of inotify subscriptions */
74 GList *subs;
75} ip_watched_dir_t;
76
77static gboolean ip_debug_enabled = FALSE;
78#define IP_W if (ip_debug_enabled) g_warning
79
80/* path -> ip_watched_dir */
81static GHashTable * path_dir_hash = NULL;
82/* inotify_sub * -> ip_watched_dir *
83 *
84 * Each subscription is attached to a watched directory or it is on
85 * the missing list
86 */
87static 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
90 * the same wd
91 */
92static 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
95 * the same wd
96 */
97static GHashTable * wd_file_hash = NULL;
98
99static ip_watched_dir_t *ip_watched_dir_new (const char *path,
100 int wd);
101static void ip_watched_dir_free (ip_watched_dir_t *dir);
102static gboolean ip_event_callback (ik_event_t *event);
103
104
105static gboolean (*event_callback)(ik_event_t *event, inotify_sub *sub, gboolean file_event);
106
107gboolean
108_ip_startup (gboolean (*cb)(ik_event_t *event, inotify_sub *sub, gboolean file_event))
109{
110 static gboolean initialized = FALSE;
111 static gboolean result = FALSE;
112
113 if (initialized == TRUE)
114 return result;
115
116 event_callback = cb;
117 result = _ik_startup (cb: ip_event_callback);
118
119 if (!result)
120 return FALSE;
121
122 path_dir_hash = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal);
123 sub_dir_hash = g_hash_table_new (hash_func: g_direct_hash, key_equal_func: g_direct_equal);
124 wd_dir_hash = g_hash_table_new (hash_func: g_direct_hash, key_equal_func: g_direct_equal);
125 wd_file_hash = g_hash_table_new (hash_func: g_direct_hash, key_equal_func: g_direct_equal);
126
127 initialized = TRUE;
128 return TRUE;
129}
130
131static void
132ip_map_path_dir (const char *path,
133 ip_watched_dir_t *dir)
134{
135 g_assert (path && dir);
136 g_hash_table_insert (hash_table: path_dir_hash, key: dir->path, value: dir);
137}
138
139static void
140ip_map_sub_dir (inotify_sub *sub,
141 ip_watched_dir_t *dir)
142{
143 /* Associate subscription and directory */
144 g_assert (dir && sub);
145 g_hash_table_insert (hash_table: sub_dir_hash, key: sub, value: dir);
146 dir->subs = g_list_prepend (list: dir->subs, data: sub);
147}
148
149static void
150ip_map_wd_dir (gint32 wd,
151 ip_watched_dir_t *dir)
152{
153 GList *dir_list;
154
155 g_assert (wd >= 0 && dir);
156 dir_list = g_hash_table_lookup (hash_table: wd_dir_hash, GINT_TO_POINTER (wd));
157 dir_list = g_list_prepend (list: dir_list, data: dir);
158 g_hash_table_replace (hash_table: wd_dir_hash, GINT_TO_POINTER (dir->wd), value: dir_list);
159}
160
161static void
162ip_map_wd_file (gint32 wd,
163 ip_watched_file_t *file)
164{
165 GList *file_list;
166
167 g_assert (wd >= 0 && file);
168 file_list = g_hash_table_lookup (hash_table: wd_file_hash, GINT_TO_POINTER (wd));
169 file_list = g_list_prepend (list: file_list, data: file);
170 g_hash_table_replace (hash_table: wd_file_hash, GINT_TO_POINTER (wd), value: file_list);
171}
172
173static void
174ip_unmap_wd_file (gint32 wd,
175 ip_watched_file_t *file)
176{
177 GList *file_list = g_hash_table_lookup (hash_table: wd_file_hash, GINT_TO_POINTER (wd));
178
179 if (!file_list)
180 return;
181
182 g_assert (wd >= 0 && file);
183 file_list = g_list_remove (list: file_list, data: file);
184 if (file_list == NULL)
185 g_hash_table_remove (hash_table: wd_file_hash, GINT_TO_POINTER (wd));
186 else
187 g_hash_table_replace (hash_table: wd_file_hash, GINT_TO_POINTER (wd), value: file_list);
188}
189
190
191static ip_watched_file_t *
192ip_watched_file_new (const gchar *dirname,
193 const gchar *filename)
194{
195 ip_watched_file_t *file;
196
197 file = g_new0 (ip_watched_file_t, 1);
198 file->path = g_strjoin (separator: "/", dirname, filename, NULL);
199 file->filename = g_strdup (str: filename);
200 file->wd = -1;
201
202 return file;
203}
204
205static void
206ip_watched_file_free (ip_watched_file_t *file)
207{
208 g_assert (file->subs == NULL);
209 g_free (mem: file->filename);
210 g_free (mem: file->path);
211 g_free (mem: file);
212}
213
214static void
215ip_watched_file_add_sub (ip_watched_file_t *file,
216 inotify_sub *sub)
217{
218 file->subs = g_list_prepend (list: file->subs, data: sub);
219}
220
221static void
222ip_watched_file_start (ip_watched_file_t *file)
223{
224 if (file->wd < 0)
225 {
226 gint err;
227
228 file->wd = _ik_watch (path: file->path,
229 IP_INOTIFY_FILE_MASK,
230 err: &err);
231
232 if (file->wd >= 0)
233 ip_map_wd_file (wd: file->wd, file);
234 }
235}
236
237static void
238ip_watched_file_stop (ip_watched_file_t *file)
239{
240 if (file->wd >= 0)
241 {
242 _ik_ignore (path: file->path, wd: file->wd);
243 ip_unmap_wd_file (wd: file->wd, file);
244 file->wd = -1;
245 }
246}
247
248gboolean
249_ip_start_watching (inotify_sub *sub)
250{
251 gint32 wd;
252 int err;
253 ip_watched_dir_t *dir;
254
255 g_assert (sub);
256 g_assert (!sub->cancelled);
257 g_assert (sub->dirname);
258
259 IP_W ("Starting to watch %s\n", sub->dirname);
260 dir = g_hash_table_lookup (hash_table: path_dir_hash, key: sub->dirname);
261
262 if (dir == NULL)
263 {
264 IP_W ("Trying to add inotify watch ");
265 wd = _ik_watch (path: sub->dirname, IP_INOTIFY_DIR_MASK|IN_ONLYDIR, err: &err);
266 if (wd < 0)
267 {
268 IP_W ("Failed\n");
269 return FALSE;
270 }
271 else
272 {
273 /* Create new watched directory and associate it with the
274 * wd hash and path hash
275 */
276 IP_W ("Success\n");
277 dir = ip_watched_dir_new (path: sub->dirname, wd);
278 ip_map_wd_dir (wd, dir);
279 ip_map_path_dir (path: sub->dirname, dir);
280 }
281 }
282 else
283 IP_W ("Already watching\n");
284
285 if (sub->hardlinks)
286 {
287 ip_watched_file_t *file;
288
289 file = g_hash_table_lookup (hash_table: dir->files_hash, key: sub->filename);
290
291 if (file == NULL)
292 {
293 file = ip_watched_file_new (dirname: sub->dirname, filename: sub->filename);
294 g_hash_table_insert (hash_table: dir->files_hash, key: file->filename, value: file);
295 }
296
297 ip_watched_file_add_sub (file, sub);
298 ip_watched_file_start (file);
299 }
300
301 ip_map_sub_dir (sub, dir);
302
303 return TRUE;
304}
305
306static void
307ip_unmap_path_dir (const char *path,
308 ip_watched_dir_t *dir)
309{
310 g_assert (path && dir);
311 g_hash_table_remove (hash_table: path_dir_hash, key: dir->path);
312}
313
314static void
315ip_unmap_wd_dir (gint32 wd,
316 ip_watched_dir_t *dir)
317{
318 GList *dir_list = g_hash_table_lookup (hash_table: wd_dir_hash, GINT_TO_POINTER (wd));
319
320 if (!dir_list)
321 return;
322
323 g_assert (wd >= 0 && dir);
324 dir_list = g_list_remove (list: dir_list, data: dir);
325 if (dir_list == NULL)
326 g_hash_table_remove (hash_table: wd_dir_hash, GINT_TO_POINTER (dir->wd));
327 else
328 g_hash_table_replace (hash_table: wd_dir_hash, GINT_TO_POINTER (dir->wd), value: dir_list);
329}
330
331static void
332ip_unmap_wd (gint32 wd)
333{
334 GList *dir_list = g_hash_table_lookup (hash_table: wd_dir_hash, GINT_TO_POINTER (wd));
335 if (!dir_list)
336 return;
337 g_assert (wd >= 0);
338 g_hash_table_remove (hash_table: wd_dir_hash, GINT_TO_POINTER (wd));
339 g_list_free (list: dir_list);
340}
341
342static void
343ip_unmap_sub_dir (inotify_sub *sub,
344 ip_watched_dir_t *dir)
345{
346 g_assert (sub && dir);
347 g_hash_table_remove (hash_table: sub_dir_hash, key: sub);
348 dir->subs = g_list_remove (list: dir->subs, data: sub);
349
350 if (sub->hardlinks)
351 {
352 ip_watched_file_t *file;
353
354 file = g_hash_table_lookup (hash_table: dir->files_hash, key: sub->filename);
355 file->subs = g_list_remove (list: file->subs, data: sub);
356
357 if (file->subs == NULL)
358 {
359 g_hash_table_remove (hash_table: dir->files_hash, key: sub->filename);
360 ip_watched_file_stop (file);
361 ip_watched_file_free (file);
362 }
363 }
364 }
365
366static void
367ip_unmap_all_subs (ip_watched_dir_t *dir)
368{
369 while (dir->subs != NULL)
370 ip_unmap_sub_dir (sub: dir->subs->data, dir);
371}
372
373gboolean
374_ip_stop_watching (inotify_sub *sub)
375{
376 ip_watched_dir_t *dir = NULL;
377
378 dir = g_hash_table_lookup (hash_table: sub_dir_hash, key: sub);
379 if (!dir)
380 return TRUE;
381
382 ip_unmap_sub_dir (sub, dir);
383
384 /* No one is subscribing to this directory any more */
385 if (dir->subs == NULL)
386 {
387 _ik_ignore (path: dir->path, wd: dir->wd);
388 ip_unmap_wd_dir (wd: dir->wd, dir);
389 ip_unmap_path_dir (path: dir->path, dir);
390 ip_watched_dir_free (dir);
391 }
392
393 return TRUE;
394}
395
396
397static ip_watched_dir_t *
398ip_watched_dir_new (const char *path,
399 gint32 wd)
400{
401 ip_watched_dir_t *dir = g_new0 (ip_watched_dir_t, 1);
402
403 dir->path = g_strdup (str: path);
404 dir->files_hash = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal);
405 dir->wd = wd;
406
407 return dir;
408}
409
410static void
411ip_watched_dir_free (ip_watched_dir_t *dir)
412{
413 g_assert_cmpint (g_hash_table_size (dir->files_hash), ==, 0);
414 g_assert (dir->subs == NULL);
415 g_free (mem: dir->path);
416 g_hash_table_unref (hash_table: dir->files_hash);
417 g_free (mem: dir);
418}
419
420static void
421ip_wd_delete (gpointer data,
422 gpointer user_data)
423{
424 ip_watched_dir_t *dir = data;
425 GList *l = NULL;
426
427 for (l = dir->subs; l; l = l->next)
428 {
429 inotify_sub *sub = l->data;
430 /* Add subscription to missing list */
431 _im_add (sub);
432 }
433 ip_unmap_all_subs (dir);
434 /* Unassociate the path and the directory */
435 ip_unmap_path_dir (path: dir->path, dir);
436 ip_watched_dir_free (dir);
437}
438
439static gboolean
440ip_event_dispatch (GList *dir_list,
441 GList *file_list,
442 ik_event_t *event)
443{
444 gboolean interesting = FALSE;
445
446 GList *l;
447
448 if (!event)
449 return FALSE;
450
451 for (l = dir_list; l; l = l->next)
452 {
453 GList *subl;
454 ip_watched_dir_t *dir = l->data;
455
456 for (subl = dir->subs; subl; subl = subl->next)
457 {
458 inotify_sub *sub = subl->data;
459
460 /* If the subscription and the event
461 * contain a filename and they don't
462 * match, we don't deliver this event.
463 */
464 if (sub->filename &&
465 event->name &&
466 strcmp (s1: sub->filename, s2: event->name) &&
467 (!event->pair || !event->pair->name || strcmp (s1: sub->filename, s2: event->pair->name)))
468 continue;
469
470 /* If the subscription has a filename
471 * but this event doesn't, we don't
472 * deliver this event.
473 */
474 if (sub->filename && !event->name)
475 continue;
476
477 /* If we're also watching the file directly
478 * don't report events that will also be
479 * reported on the file itself.
480 */
481 if (sub->hardlinks)
482 {
483 event->mask &= ~IP_INOTIFY_FILE_MASK;
484 if (!event->mask)
485 continue;
486 }
487
488 /* FIXME: We might need to synthesize
489 * DELETE/UNMOUNT events when
490 * the filename doesn't match
491 */
492
493 interesting |= event_callback (event, sub, FALSE);
494
495 if (sub->hardlinks)
496 {
497 ip_watched_file_t *file;
498
499 file = g_hash_table_lookup (hash_table: dir->files_hash, key: sub->filename);
500
501 if (file != NULL)
502 {
503 if (event->mask & (IN_MOVED_FROM | IN_DELETE))
504 ip_watched_file_stop (file);
505
506 if (event->mask & (IN_MOVED_TO | IN_CREATE))
507 ip_watched_file_start (file);
508 }
509 }
510 }
511 }
512
513 for (l = file_list; l; l = l->next)
514 {
515 ip_watched_file_t *file = l->data;
516 GList *subl;
517
518 for (subl = file->subs; subl; subl = subl->next)
519 {
520 inotify_sub *sub = subl->data;
521
522 interesting |= event_callback (event, sub, TRUE);
523 }
524 }
525
526 return interesting;
527}
528
529static gboolean
530ip_event_callback (ik_event_t *event)
531{
532 gboolean interesting = FALSE;
533 GList* dir_list = NULL;
534 GList *file_list = NULL;
535
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))
539 {
540 _ik_event_free (event);
541 return TRUE;
542 }
543
544 dir_list = g_hash_table_lookup (hash_table: wd_dir_hash, GINT_TO_POINTER (event->wd));
545 file_list = g_hash_table_lookup (hash_table: wd_file_hash, GINT_TO_POINTER (event->wd));
546
547 if (event->mask & IP_INOTIFY_DIR_MASK)
548 interesting |= ip_event_dispatch (dir_list, file_list, event);
549
550 /* Only deliver paired events if the wds are separate */
551 if (event->pair && event->pair->wd != event->wd)
552 {
553 dir_list = g_hash_table_lookup (hash_table: wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
554 file_list = g_hash_table_lookup (hash_table: wd_file_hash, GINT_TO_POINTER (event->pair->wd));
555
556 if (event->pair->mask & IP_INOTIFY_DIR_MASK)
557 interesting |= ip_event_dispatch (dir_list, file_list, event: event->pair);
558 }
559
560 /* We have to manage the missing list
561 * when we get an event that means the
562 * file has been deleted/moved/unmounted.
563 */
564 if (event->mask & IN_DELETE_SELF ||
565 event->mask & IN_MOVE_SELF ||
566 event->mask & IN_UNMOUNT)
567 {
568 /* Add all subscriptions to missing list */
569 g_list_foreach (list: dir_list, func: ip_wd_delete, NULL);
570 /* Unmap all directories attached to this wd */
571 ip_unmap_wd (wd: event->wd);
572 }
573
574 _ik_event_free (event);
575
576 return interesting;
577}
578
579const char *
580_ip_get_path_for_wd (gint32 wd)
581{
582 GList *dir_list;
583 ip_watched_dir_t *dir;
584
585 g_assert (wd >= 0);
586 dir_list = g_hash_table_lookup (hash_table: wd_dir_hash, GINT_TO_POINTER (wd));
587 if (dir_list)
588 {
589 dir = dir_list->data;
590 if (dir)
591 return dir->path;
592 }
593
594 return NULL;
595}
596

source code of gtk/subprojects/glib/gio/inotify/inotify-path.c