[kdbus] sync with kdbus (commit: b078c757a3f9) 15/02/20
[platform/upstream/glib.git] / gio / inotify / inotify-path.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
2
3 /* inotify-path.c - GVFS Directory Monitor based on inotify.
4
5    Copyright (C) 2006 John McCutchan
6    Copyright (C) 2009 Codethink Limited
7
8    The Gnome Library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Library General Public License as
10    published by the Free Software Foundation; either version 2 of the
11    License, or (at your option) any later version.
12
13    The Gnome 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.
17
18    You should have received a copy of the GNU Library General Public
19    License along with the Gnome Library; see the file COPYING.LIB.  If not,
20    see <http://www.gnu.org/licenses/>.
21
22    Authors:
23                  John McCutchan <john@johnmccutchan.com>
24                  Ryan Lortie <desrt@desrt.ca>
25 */
26
27 #include "config.h"
28
29 /* Don't put conflicting kernel types in the global namespace: */
30 #define __KERNEL_STRICT_NAMES
31
32 #include <sys/inotify.h>
33 #include <string.h>
34 #include <glib.h>
35 #include "inotify-kernel.h"
36 #include "inotify-path.h"
37 #include "inotify-missing.h"
38
39 #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
41 #define IP_INOTIFY_FILE_MASK (IN_MODIFY|IN_ATTRIB|IN_CLOSE_WRITE)
42
43 /* Older libcs don't have this */
44 #ifndef IN_ONLYDIR
45 #define IN_ONLYDIR 0  
46 #endif
47
48 typedef struct ip_watched_file_s {
49   gchar *filename;
50   gchar *path;
51   gint32 wd;
52
53   GList *subs;
54 } ip_watched_file_t;
55
56 typedef struct ip_watched_dir_s {
57   char *path;
58   /* TODO: We need to maintain a tree of watched directories
59    * so that we can deliver move/delete events to sub folders.
60    * Or the application could do it...
61    */
62   struct ip_watched_dir_s* parent;
63   GList*         children;
64
65   /* basename -> ip_watched_file_t
66    * Maps basename to a ip_watched_file_t if the file is currently
67    * being directly watched for changes (ie: 'hardlinks' mode).
68    */
69   GHashTable *files_hash;
70
71   /* Inotify state */
72   gint32 wd;
73   
74   /* List of inotify subscriptions */
75   GList *subs;
76 } ip_watched_dir_t;
77
78 static gboolean     ip_debug_enabled = FALSE;
79 #define IP_W if (ip_debug_enabled) g_warning
80
81 /* path -> ip_watched_dir */
82 static GHashTable * path_dir_hash = NULL;
83 /* inotify_sub * -> ip_watched_dir *
84  *
85  * Each subscription is attached to a watched directory or it is on
86  * the missing list
87  */
88 static GHashTable * sub_dir_hash = NULL;
89 /* This hash holds GLists of ip_watched_dir_t *'s
90  * We need to hold a list because symbolic links can share
91  * the same wd
92  */
93 static GHashTable * wd_dir_hash = NULL;
94 /* This hash holds GLists of ip_watched_file_t *'s
95  * We need to hold a list because links can share
96  * the same wd
97  */
98 static GHashTable * wd_file_hash = NULL;
99
100 static ip_watched_dir_t *ip_watched_dir_new  (const char       *path,
101                                               int               wd);
102 static void              ip_watched_dir_free (ip_watched_dir_t *dir);
103 static void              ip_event_callback   (ik_event_t       *event);
104
105
106 static void (*event_callback)(ik_event_t *event, inotify_sub *sub, gboolean file_event);
107
108 gboolean
109 _ip_startup (void (*cb)(ik_event_t *event, inotify_sub *sub, gboolean file_event))
110 {
111   static gboolean initialized = FALSE;
112   static gboolean result = FALSE;
113   
114   if (initialized == TRUE)
115     return result;
116
117   event_callback = cb;
118   result = _ik_startup (ip_event_callback);
119
120   if (!result)
121     return FALSE;
122
123   path_dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
124   sub_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
125   wd_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
126   wd_file_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
127   
128   initialized = TRUE;
129   return TRUE;
130 }
131
132 static void
133 ip_map_path_dir (const char       *path, 
134                  ip_watched_dir_t *dir)
135 {
136   g_assert (path && dir);
137   g_hash_table_insert (path_dir_hash, dir->path, dir);
138 }
139
140 static void
141 ip_map_sub_dir (inotify_sub      *sub, 
142                 ip_watched_dir_t *dir)
143 {
144   /* Associate subscription and directory */
145   g_assert (dir && sub);
146   g_hash_table_insert (sub_dir_hash, sub, dir);
147   dir->subs = g_list_prepend (dir->subs, sub);
148 }
149
150 static void
151 ip_map_wd_dir (gint32            wd, 
152                ip_watched_dir_t *dir)
153 {
154   GList *dir_list;
155   
156   g_assert (wd >= 0 && dir);
157   dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
158   dir_list = g_list_prepend (dir_list, dir);
159   g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
160 }
161
162 static void
163 ip_map_wd_file (gint32             wd,
164                 ip_watched_file_t *file)
165 {
166   GList *file_list;
167
168   g_assert (wd >= 0 && file);
169   file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
170   file_list = g_list_prepend (file_list, file);
171   g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
172 }
173
174 static void
175 ip_unmap_wd_file (gint32             wd,
176                   ip_watched_file_t *file)
177 {
178   GList *file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
179
180   if (!file_list)
181     return;
182
183   g_assert (wd >= 0 && file);
184   file_list = g_list_remove (file_list, file);
185   if (file_list == NULL)
186     g_hash_table_remove (wd_file_hash, GINT_TO_POINTER (wd));
187   else
188     g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
189 }
190
191
192 static ip_watched_file_t *
193 ip_watched_file_new (const gchar *dirname,
194                      const gchar *filename)
195 {
196   ip_watched_file_t *file;
197
198   file = g_new0 (ip_watched_file_t, 1);
199   file->path = g_strjoin ("/", dirname, filename, NULL);
200   file->filename = g_strdup (filename);
201   file->wd = -1;
202
203   return file;
204 }
205
206 static void
207 ip_watched_file_free (ip_watched_file_t *file)
208 {
209   g_assert (file->subs == NULL);
210   g_free (file->filename);
211   g_free (file->path);
212 }
213
214 static void
215 ip_watched_file_add_sub (ip_watched_file_t *file,
216                          inotify_sub       *sub)
217 {
218   file->subs = g_list_prepend (file->subs, sub);
219 }
220
221 static void
222 ip_watched_file_start (ip_watched_file_t *file)
223 {
224   if (file->wd < 0)
225     {
226       gint err;
227
228       file->wd = _ik_watch (file->path,
229                             IP_INOTIFY_FILE_MASK,
230                             &err);
231
232       if (file->wd >= 0)
233         ip_map_wd_file (file->wd, file);
234     }
235 }
236
237 static void
238 ip_watched_file_stop (ip_watched_file_t *file)
239 {
240   if (file->wd >= 0)
241     {
242       _ik_ignore (file->path, file->wd);
243       ip_unmap_wd_file (file->wd, file);
244       file->wd = -1;
245     }
246 }
247
248 gboolean
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 (path_dir_hash, sub->dirname);
261
262   if (dir == NULL)
263     {
264       IP_W ("Trying to add inotify watch ");
265       wd = _ik_watch (sub->dirname, IP_INOTIFY_DIR_MASK|IN_ONLYDIR, &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 (sub->dirname, wd);
278           ip_map_wd_dir (wd, dir);
279           ip_map_path_dir (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 (dir->files_hash, sub->filename);
290
291       if (file == NULL)
292         {
293           file = ip_watched_file_new (sub->dirname, sub->filename);
294           g_hash_table_insert (dir->files_hash, file->filename, 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
306 static void
307 ip_unmap_path_dir (const char       *path, 
308                    ip_watched_dir_t *dir)
309 {
310   g_assert (path && dir);
311   g_hash_table_remove (path_dir_hash, dir->path);
312 }
313
314 static void
315 ip_unmap_wd_dir (gint32            wd, 
316                  ip_watched_dir_t *dir)
317 {
318   GList *dir_list = g_hash_table_lookup (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 (dir_list, dir);
325   if (dir_list == NULL) 
326     g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (dir->wd));
327   else
328     g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
329 }
330
331 static void
332 ip_unmap_wd (gint32 wd)
333 {
334   GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
335   if (!dir_list)
336     return;
337   g_assert (wd >= 0);
338   g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (wd));
339   g_list_free (dir_list);
340 }
341
342 static void
343 ip_unmap_sub_dir (inotify_sub      *sub,
344                   ip_watched_dir_t *dir)
345 {
346   g_assert (sub && dir);
347   g_hash_table_remove (sub_dir_hash, sub);
348   dir->subs = g_list_remove (dir->subs, sub);
349
350   if (sub->hardlinks)
351     {
352       ip_watched_file_t *file;
353
354       file = g_hash_table_lookup (dir->files_hash, sub->filename);
355       file->subs = g_list_remove (file->subs, sub);
356
357       if (file->subs == NULL)
358         {
359           g_hash_table_remove (dir->files_hash, sub->filename);
360           ip_watched_file_stop (file);
361           ip_watched_file_free (file);
362         }
363     }
364  }
365
366 static void
367 ip_unmap_all_subs (ip_watched_dir_t *dir)
368 {
369   while (dir->subs != NULL)
370     ip_unmap_sub_dir (dir->subs->data, dir);
371 }
372
373 gboolean
374 _ip_stop_watching (inotify_sub *sub)
375 {
376   ip_watched_dir_t *dir = NULL;
377   
378   dir = g_hash_table_lookup (sub_dir_hash, 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 (dir->path, dir->wd);
388       ip_unmap_wd_dir (dir->wd, dir);
389       ip_unmap_path_dir (dir->path, dir);
390       ip_watched_dir_free (dir);
391     }
392   
393   return TRUE;
394 }
395
396
397 static ip_watched_dir_t *
398 ip_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 (path);
404   dir->files_hash = g_hash_table_new (g_str_hash, g_str_equal);
405   dir->wd = wd;
406   
407   return dir;
408 }
409
410 static void
411 ip_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 (dir->path);
416   g_hash_table_unref (dir->files_hash);
417   g_free (dir);
418 }
419
420 static void
421 ip_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 (dir->path, dir);
436   ip_watched_dir_free (dir);
437 }
438
439 static void
440 ip_event_dispatch (GList      *dir_list, 
441                    GList      *pair_dir_list, 
442                    GList      *file_list,
443                    GList      *pair_file_list,
444                    ik_event_t *event)
445 {
446   GList *l;
447   
448   if (!event)
449     return;
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 (sub->filename, event->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           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           event_callback (event, sub, TRUE);
522         }
523     }
524   
525   if (!event->pair)
526     return;
527   
528   for (l = pair_dir_list; l; l = l->next)
529     {
530       GList *subl;
531       ip_watched_dir_t *dir = l->data;
532       
533       for (subl = dir->subs; subl; subl = subl->next)
534         {
535           inotify_sub *sub = subl->data;
536           
537           /* If the subscription and the event
538            * contain a filename and they don't
539            * match, we don't deliver this event.
540            */
541           if (sub->filename &&
542               event->pair->name &&
543               strcmp (sub->filename, event->pair->name))
544             continue;
545           
546           /* If the subscription has a filename
547            * but this event doesn't, we don't
548            * deliver this event.
549            */
550           if (sub->filename && !event->pair->name)
551             continue;
552           
553           /* If we're also watching the file directly
554            * don't report events that will also be
555            * reported on the file itself.
556            */
557           if (sub->hardlinks)
558             {
559               event->mask &= ~IP_INOTIFY_FILE_MASK;
560               if (!event->mask)
561                 continue;
562             }
563           
564           /* FIXME: We might need to synthesize
565            * DELETE/UNMOUNT events when
566            * the filename doesn't match
567            */
568           
569           event_callback (event->pair, sub, FALSE);
570
571           if (sub->hardlinks)
572             {
573               ip_watched_file_t *file;
574
575               file = g_hash_table_lookup (dir->files_hash, sub->filename);
576
577               if (file != NULL)
578                 {
579                   if (event->pair->mask & (IN_MOVED_FROM | IN_DELETE))
580                     ip_watched_file_stop (file);
581
582                   if (event->pair->mask & (IN_MOVED_TO | IN_CREATE))
583                     ip_watched_file_start (file);
584                 }
585             }
586         }
587     }
588
589   for (l = pair_file_list; l; l = l->next)
590     {
591       ip_watched_file_t *file = l->data;
592       GList *subl;
593
594       for (subl = file->subs; subl; subl = subl->next)
595         {
596           inotify_sub *sub = subl->data;
597
598           event_callback (event->pair, sub, TRUE);
599         }
600     }
601 }
602
603 static void
604 ip_event_callback (ik_event_t *event)
605 {
606   GList* dir_list = NULL;
607   GList* pair_dir_list = NULL;
608   GList *file_list = NULL;
609   GList *pair_file_list = NULL;
610
611   /* We can ignore the IGNORED events */
612   if (event->mask & IN_IGNORED)
613     {
614       _ik_event_free (event);
615       return;
616     }
617
618   dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd));
619   file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->wd));
620
621   if (event->pair)
622     {
623       pair_dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
624       pair_file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->pair->wd));
625     }
626
627   if (event->mask & IP_INOTIFY_DIR_MASK)
628     ip_event_dispatch (dir_list, pair_dir_list, file_list, pair_file_list, event);
629   
630   /* We have to manage the missing list
631    * when we get an event that means the
632    * file has been deleted/moved/unmounted.
633    */
634   if (event->mask & IN_DELETE_SELF ||
635       event->mask & IN_MOVE_SELF ||
636       event->mask & IN_UNMOUNT)
637     {
638       /* Add all subscriptions to missing list */
639       g_list_foreach (dir_list, ip_wd_delete, NULL);
640       /* Unmap all directories attached to this wd */
641       ip_unmap_wd (event->wd);
642     }
643   
644   _ik_event_free (event);
645 }
646
647 const char *
648 _ip_get_path_for_wd (gint32 wd)
649 {
650   GList *dir_list;
651   ip_watched_dir_t *dir;
652
653   g_assert (wd >= 0);
654   dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
655   if (dir_list)
656     {
657       dir = dir_list->data;
658       if (dir)
659         return dir->path;
660     }
661
662   return NULL;
663 }