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
47 typedef struct ip_watched_file_s {
48 gchar *filename;
49 gchar *path;
50 gint32 wd;
51
52 GList *subs;
53 } ip_watched_file_t;
54
55 typedef 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
77 static gboolean ip_debug_enabled = FALSE;
78 #define IP_W if (ip_debug_enabled) g_warning
79
80 /* path -> ip_watched_dir */
81 static 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 */
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
90 * the same wd
91 */
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
95 * the same wd
96 */
97 static GHashTable * wd_file_hash = NULL;
98
99 static ip_watched_dir_t *ip_watched_dir_new (const char *path,
100 int wd);
101 static void ip_watched_dir_free (ip_watched_dir_t *dir);
102 static gboolean ip_event_callback (ik_event_t *event);
103
104
105 static gboolean (*event_callback)(ik_event_t *event, inotify_sub *sub, gboolean file_event);
106
107 gboolean
_ip_startup(gboolean (* cb)(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))
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 (ip_event_callback);
118
119 if (!result)
120 return FALSE;
121
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);
126
127 initialized = TRUE;
128 return TRUE;
129 }
130
131 static void
ip_map_path_dir(const char * path,ip_watched_dir_t * dir)132 ip_map_path_dir (const char *path,
133 ip_watched_dir_t *dir)
134 {
135 g_assert (path && dir);
136 g_hash_table_insert (path_dir_hash, dir->path, dir);
137 }
138
139 static void
ip_map_sub_dir(inotify_sub * sub,ip_watched_dir_t * dir)140 ip_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 (sub_dir_hash, sub, dir);
146 dir->subs = g_list_prepend (dir->subs, sub);
147 }
148
149 static void
ip_map_wd_dir(gint32 wd,ip_watched_dir_t * dir)150 ip_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 (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);
159 }
160
161 static void
ip_map_wd_file(gint32 wd,ip_watched_file_t * file)162 ip_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 (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);
171 }
172
173 static void
ip_unmap_wd_file(gint32 wd,ip_watched_file_t * file)174 ip_unmap_wd_file (gint32 wd,
175 ip_watched_file_t *file)
176 {
177 GList *file_list = g_hash_table_lookup (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 (file_list, file);
184 if (file_list == NULL)
185 g_hash_table_remove (wd_file_hash, GINT_TO_POINTER (wd));
186 else
187 g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
188 }
189
190
191 static ip_watched_file_t *
ip_watched_file_new(const gchar * dirname,const gchar * filename)192 ip_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 ("/", dirname, filename, NULL);
199 file->filename = g_strdup (filename);
200 file->wd = -1;
201
202 return file;
203 }
204
205 static void
ip_watched_file_free(ip_watched_file_t * file)206 ip_watched_file_free (ip_watched_file_t *file)
207 {
208 g_assert (file->subs == NULL);
209 g_free (file->filename);
210 g_free (file->path);
211 }
212
213 static void
ip_watched_file_add_sub(ip_watched_file_t * file,inotify_sub * sub)214 ip_watched_file_add_sub (ip_watched_file_t *file,
215 inotify_sub *sub)
216 {
217 file->subs = g_list_prepend (file->subs, sub);
218 }
219
220 static void
ip_watched_file_start(ip_watched_file_t * file)221 ip_watched_file_start (ip_watched_file_t *file)
222 {
223 if (file->wd < 0)
224 {
225 gint err;
226
227 file->wd = _ik_watch (file->path,
228 IP_INOTIFY_FILE_MASK,
229 &err);
230
231 if (file->wd >= 0)
232 ip_map_wd_file (file->wd, file);
233 }
234 }
235
236 static void
ip_watched_file_stop(ip_watched_file_t * file)237 ip_watched_file_stop (ip_watched_file_t *file)
238 {
239 if (file->wd >= 0)
240 {
241 _ik_ignore (file->path, file->wd);
242 ip_unmap_wd_file (file->wd, file);
243 file->wd = -1;
244 }
245 }
246
247 gboolean
_ip_start_watching(inotify_sub * sub)248 _ip_start_watching (inotify_sub *sub)
249 {
250 gint32 wd;
251 int err;
252 ip_watched_dir_t *dir;
253
254 g_assert (sub);
255 g_assert (!sub->cancelled);
256 g_assert (sub->dirname);
257
258 IP_W ("Starting to watch %s\n", sub->dirname);
259 dir = g_hash_table_lookup (path_dir_hash, sub->dirname);
260
261 if (dir == NULL)
262 {
263 IP_W ("Trying to add inotify watch ");
264 wd = _ik_watch (sub->dirname, IP_INOTIFY_DIR_MASK|IN_ONLYDIR, &err);
265 if (wd < 0)
266 {
267 IP_W ("Failed\n");
268 return FALSE;
269 }
270 else
271 {
272 /* Create new watched directory and associate it with the
273 * wd hash and path hash
274 */
275 IP_W ("Success\n");
276 dir = ip_watched_dir_new (sub->dirname, wd);
277 ip_map_wd_dir (wd, dir);
278 ip_map_path_dir (sub->dirname, dir);
279 }
280 }
281 else
282 IP_W ("Already watching\n");
283
284 if (sub->hardlinks)
285 {
286 ip_watched_file_t *file;
287
288 file = g_hash_table_lookup (dir->files_hash, sub->filename);
289
290 if (file == NULL)
291 {
292 file = ip_watched_file_new (sub->dirname, sub->filename);
293 g_hash_table_insert (dir->files_hash, file->filename, file);
294 }
295
296 ip_watched_file_add_sub (file, sub);
297 ip_watched_file_start (file);
298 }
299
300 ip_map_sub_dir (sub, dir);
301
302 return TRUE;
303 }
304
305 static void
ip_unmap_path_dir(const char * path,ip_watched_dir_t * dir)306 ip_unmap_path_dir (const char *path,
307 ip_watched_dir_t *dir)
308 {
309 g_assert (path && dir);
310 g_hash_table_remove (path_dir_hash, dir->path);
311 }
312
313 static void
ip_unmap_wd_dir(gint32 wd,ip_watched_dir_t * dir)314 ip_unmap_wd_dir (gint32 wd,
315 ip_watched_dir_t *dir)
316 {
317 GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
318
319 if (!dir_list)
320 return;
321
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));
326 else
327 g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
328 }
329
330 static void
ip_unmap_wd(gint32 wd)331 ip_unmap_wd (gint32 wd)
332 {
333 GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
334 if (!dir_list)
335 return;
336 g_assert (wd >= 0);
337 g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (wd));
338 g_list_free (dir_list);
339 }
340
341 static void
ip_unmap_sub_dir(inotify_sub * sub,ip_watched_dir_t * dir)342 ip_unmap_sub_dir (inotify_sub *sub,
343 ip_watched_dir_t *dir)
344 {
345 g_assert (sub && dir);
346 g_hash_table_remove (sub_dir_hash, sub);
347 dir->subs = g_list_remove (dir->subs, sub);
348
349 if (sub->hardlinks)
350 {
351 ip_watched_file_t *file;
352
353 file = g_hash_table_lookup (dir->files_hash, sub->filename);
354 file->subs = g_list_remove (file->subs, sub);
355
356 if (file->subs == NULL)
357 {
358 g_hash_table_remove (dir->files_hash, sub->filename);
359 ip_watched_file_stop (file);
360 ip_watched_file_free (file);
361 }
362 }
363 }
364
365 static void
ip_unmap_all_subs(ip_watched_dir_t * dir)366 ip_unmap_all_subs (ip_watched_dir_t *dir)
367 {
368 while (dir->subs != NULL)
369 ip_unmap_sub_dir (dir->subs->data, dir);
370 }
371
372 gboolean
_ip_stop_watching(inotify_sub * sub)373 _ip_stop_watching (inotify_sub *sub)
374 {
375 ip_watched_dir_t *dir = NULL;
376
377 dir = g_hash_table_lookup (sub_dir_hash, sub);
378 if (!dir)
379 return TRUE;
380
381 ip_unmap_sub_dir (sub, dir);
382
383 /* No one is subscribing to this directory any more */
384 if (dir->subs == NULL)
385 {
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);
390 }
391
392 return TRUE;
393 }
394
395
396 static ip_watched_dir_t *
ip_watched_dir_new(const char * path,gint32 wd)397 ip_watched_dir_new (const char *path,
398 gint32 wd)
399 {
400 ip_watched_dir_t *dir = g_new0 (ip_watched_dir_t, 1);
401
402 dir->path = g_strdup (path);
403 dir->files_hash = g_hash_table_new (g_str_hash, g_str_equal);
404 dir->wd = wd;
405
406 return dir;
407 }
408
409 static void
ip_watched_dir_free(ip_watched_dir_t * dir)410 ip_watched_dir_free (ip_watched_dir_t *dir)
411 {
412 g_assert_cmpint (g_hash_table_size (dir->files_hash), ==, 0);
413 g_assert (dir->subs == NULL);
414 g_free (dir->path);
415 g_hash_table_unref (dir->files_hash);
416 g_free (dir);
417 }
418
419 static void
ip_wd_delete(gpointer data,gpointer user_data)420 ip_wd_delete (gpointer data,
421 gpointer user_data)
422 {
423 ip_watched_dir_t *dir = data;
424 GList *l = NULL;
425
426 for (l = dir->subs; l; l = l->next)
427 {
428 inotify_sub *sub = l->data;
429 /* Add subscription to missing list */
430 _im_add (sub);
431 }
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);
436 }
437
438 static gboolean
ip_event_dispatch(GList * dir_list,GList * file_list,ik_event_t * event)439 ip_event_dispatch (GList *dir_list,
440 GList *file_list,
441 ik_event_t *event)
442 {
443 gboolean interesting = FALSE;
444
445 GList *l;
446
447 if (!event)
448 return FALSE;
449
450 for (l = dir_list; l; l = l->next)
451 {
452 GList *subl;
453 ip_watched_dir_t *dir = l->data;
454
455 for (subl = dir->subs; subl; subl = subl->next)
456 {
457 inotify_sub *sub = subl->data;
458
459 /* If the subscription and the event
460 * contain a filename and they don't
461 * match, we don't deliver this event.
462 */
463 if (sub->filename &&
464 event->name &&
465 strcmp (sub->filename, event->name) &&
466 (!event->pair || !event->pair->name || strcmp (sub->filename, event->pair->name)))
467 continue;
468
469 /* If the subscription has a filename
470 * but this event doesn't, we don't
471 * deliver this event.
472 */
473 if (sub->filename && !event->name)
474 continue;
475
476 /* If we're also watching the file directly
477 * don't report events that will also be
478 * reported on the file itself.
479 */
480 if (sub->hardlinks)
481 {
482 event->mask &= ~IP_INOTIFY_FILE_MASK;
483 if (!event->mask)
484 continue;
485 }
486
487 /* FIXME: We might need to synthesize
488 * DELETE/UNMOUNT events when
489 * the filename doesn't match
490 */
491
492 interesting |= event_callback (event, sub, FALSE);
493
494 if (sub->hardlinks)
495 {
496 ip_watched_file_t *file;
497
498 file = g_hash_table_lookup (dir->files_hash, sub->filename);
499
500 if (file != NULL)
501 {
502 if (event->mask & (IN_MOVED_FROM | IN_DELETE))
503 ip_watched_file_stop (file);
504
505 if (event->mask & (IN_MOVED_TO | IN_CREATE))
506 ip_watched_file_start (file);
507 }
508 }
509 }
510 }
511
512 for (l = file_list; l; l = l->next)
513 {
514 ip_watched_file_t *file = l->data;
515 GList *subl;
516
517 for (subl = file->subs; subl; subl = subl->next)
518 {
519 inotify_sub *sub = subl->data;
520
521 interesting |= event_callback (event, sub, TRUE);
522 }
523 }
524
525 return interesting;
526 }
527
528 static gboolean
ip_event_callback(ik_event_t * event)529 ip_event_callback (ik_event_t *event)
530 {
531 gboolean interesting = FALSE;
532 GList* dir_list = NULL;
533 GList *file_list = NULL;
534
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))
538 {
539 _ik_event_free (event);
540 return TRUE;
541 }
542
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));
545
546 if (event->mask & IP_INOTIFY_DIR_MASK)
547 interesting |= ip_event_dispatch (dir_list, file_list, event);
548
549 /* Only deliver paired events if the wds are separate */
550 if (event->pair && event->pair->wd != event->wd)
551 {
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));
554
555 if (event->pair->mask & IP_INOTIFY_DIR_MASK)
556 interesting |= ip_event_dispatch (dir_list, file_list, event->pair);
557 }
558
559 /* We have to manage the missing list
560 * when we get an event that means the
561 * file has been deleted/moved/unmounted.
562 */
563 if (event->mask & IN_DELETE_SELF ||
564 event->mask & IN_MOVE_SELF ||
565 event->mask & IN_UNMOUNT)
566 {
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);
571 }
572
573 _ik_event_free (event);
574
575 return interesting;
576 }
577
578 const char *
_ip_get_path_for_wd(gint32 wd)579 _ip_get_path_for_wd (gint32 wd)
580 {
581 GList *dir_list;
582 ip_watched_dir_t *dir;
583
584 g_assert (wd >= 0);
585 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
586 if (dir_list)
587 {
588 dir = dir_list->data;
589 if (dir)
590 return dir->path;
591 }
592
593 return NULL;
594 }
595