• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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