spec: Remove kdbus-extension packages for _with_da_profile
[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 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
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
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
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
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
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
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 *
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
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   g_free (file);
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 gboolean
440 ip_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 (sub->filename, event->name) &&
467               (!event->pair || !event->pair->name || strcmp (sub->filename, 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 (dir->files_hash, 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
529 static gboolean
530 ip_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 (wd_dir_hash, GINT_TO_POINTER (event->wd));
545   file_list = g_hash_table_lookup (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 (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
554       file_list = g_hash_table_lookup (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->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 (dir_list, ip_wd_delete, NULL);
570       /* Unmap all directories attached to this wd */
571       ip_unmap_wd (event->wd);
572     }
573   
574   _ik_event_free (event);
575
576   return interesting;
577 }
578
579 const 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 (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 }