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
7   The Gnome Library is free software; you can redistribute it and/or
8   modify it under the terms of the GNU Library General Public License as
9   published by the Free Software Foundation; either version 2 of the
10   License, or (at your option) any later version.
11
12   The Gnome Library is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   Library General Public License for more details.
16
17   You should have received a copy of the GNU Library General Public
18   License along with the Gnome Library; see the file COPYING.LIB.  If not,
19   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20   Boston, MA 02111-1307, USA.
21
22   Authors:
23		 John McCutchan <john@johnmccutchan.com>
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_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/* Older libcs don't have this */
41#ifndef IN_ONLYDIR
42#define IN_ONLYDIR 0
43#endif
44
45typedef struct ip_watched_dir_s {
46  char *path;
47  /* TODO: We need to maintain a tree of watched directories
48   * so that we can deliver move/delete events to sub folders.
49   * Or the application could do it...
50   */
51  struct ip_watched_dir_s* parent;
52  GList*	 children;
53
54  /* Inotify state */
55  gint32 wd;
56
57  /* List of inotify subscriptions */
58  GList *subs;
59} ip_watched_dir_t;
60
61static gboolean     ip_debug_enabled = FALSE;
62#define IP_W if (ip_debug_enabled) g_warning
63
64/* path -> ip_watched_dir */
65static GHashTable * path_dir_hash = NULL;
66/* inotify_sub * -> ip_watched_dir *
67 *
68 * Each subscription is attached to a watched directory or it is on
69 * the missing list
70 */
71static GHashTable * sub_dir_hash = NULL;
72/* This hash holds GLists of ip_watched_dir_t *'s
73 * We need to hold a list because symbolic links can share
74 * the same wd
75 */
76static GHashTable * wd_dir_hash = NULL;
77
78static ip_watched_dir_t *ip_watched_dir_new  (const char       *path,
79					      int               wd);
80static void              ip_watched_dir_free (ip_watched_dir_t *dir);
81static void              ip_event_callback   (ik_event_t       *event);
82
83
84static void (*event_callback)(ik_event_t *event, inotify_sub *sub);
85
86gboolean
87_ip_startup (void (*cb)(ik_event_t *event, inotify_sub *sub))
88{
89  static gboolean initialized = FALSE;
90  static gboolean result = FALSE;
91
92  if (initialized == TRUE)
93    return result;
94
95  event_callback = cb;
96  result = _ik_startup (ip_event_callback);
97
98  if (!result)
99    return FALSE;
100
101  path_dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
102  sub_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
103  wd_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
104
105  initialized = TRUE;
106  return TRUE;
107}
108
109static void
110ip_map_path_dir (const char       *path,
111                 ip_watched_dir_t *dir)
112{
113  g_assert (path && dir);
114  g_hash_table_insert (path_dir_hash, dir->path, dir);
115}
116
117static void
118ip_map_sub_dir (inotify_sub      *sub,
119                ip_watched_dir_t *dir)
120{
121  /* Associate subscription and directory */
122  g_assert (dir && sub);
123  g_hash_table_insert (sub_dir_hash, sub, dir);
124  dir->subs = g_list_prepend (dir->subs, sub);
125}
126
127static void
128ip_map_wd_dir (gint32            wd,
129               ip_watched_dir_t *dir)
130{
131  GList *dir_list;
132
133  g_assert (wd >= 0 && dir);
134  dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
135  dir_list = g_list_prepend (dir_list, dir);
136  g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
137}
138
139gboolean
140_ip_start_watching (inotify_sub *sub)
141{
142  gint32 wd;
143  int err;
144  ip_watched_dir_t *dir;
145
146  g_assert (sub);
147  g_assert (!sub->cancelled);
148  g_assert (sub->dirname);
149
150  IP_W ("Starting to watch %s\n", sub->dirname);
151  dir = g_hash_table_lookup (path_dir_hash, sub->dirname);
152  if (dir)
153    {
154      IP_W ("Already watching\n");
155      goto out;
156    }
157
158  IP_W ("Trying to add inotify watch ");
159  wd = _ik_watch (sub->dirname, IP_INOTIFY_MASK|IN_ONLYDIR, &err);
160  if (wd < 0)
161    {
162      IP_W ("Failed\n");
163      return FALSE;
164    }
165  else
166    {
167      /* Create new watched directory and associate it with the
168       * wd hash and path hash
169       */
170      IP_W ("Success\n");
171      dir = ip_watched_dir_new (sub->dirname, wd);
172      ip_map_wd_dir (wd, dir);
173      ip_map_path_dir (sub->dirname, dir);
174    }
175
176 out:
177  ip_map_sub_dir (sub, dir);
178
179  return TRUE;
180}
181
182static void
183ip_unmap_path_dir (const char       *path,
184                   ip_watched_dir_t *dir)
185{
186  g_assert (path && dir);
187  g_hash_table_remove (path_dir_hash, dir->path);
188}
189
190static void
191ip_unmap_wd_dir (gint32            wd,
192                 ip_watched_dir_t *dir)
193{
194  GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
195
196  if (!dir_list)
197    return;
198
199  g_assert (wd >= 0 && dir);
200  dir_list = g_list_remove (dir_list, dir);
201  if (dir_list == NULL)
202    g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (dir->wd));
203  else
204    g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
205}
206
207static void
208ip_unmap_wd (gint32 wd)
209{
210  GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
211  if (!dir_list)
212    return;
213  g_assert (wd >= 0);
214  g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (wd));
215  g_list_free (dir_list);
216}
217
218static void
219ip_unmap_sub_dir (inotify_sub       *sub,
220                  ip_watched_dir_t *dir)
221{
222  g_assert (sub && dir);
223  g_hash_table_remove (sub_dir_hash, sub);
224  dir->subs = g_list_remove (dir->subs, sub);
225}
226
227static void
228ip_unmap_all_subs (ip_watched_dir_t *dir)
229{
230  GList *l = NULL;
231
232  for (l = dir->subs; l; l = l->next)
233    {
234      inotify_sub *sub = l->data;
235      g_hash_table_remove (sub_dir_hash, sub);
236    }
237  g_list_free (dir->subs);
238  dir->subs = NULL;
239}
240
241gboolean
242_ip_stop_watching (inotify_sub *sub)
243{
244  ip_watched_dir_t *dir = NULL;
245
246  dir = g_hash_table_lookup (sub_dir_hash, sub);
247  if (!dir)
248    return TRUE;
249
250  ip_unmap_sub_dir (sub, dir);
251
252  /* No one is subscribing to this directory any more */
253  if (dir->subs == NULL)
254    {
255      _ik_ignore (dir->path, dir->wd);
256      ip_unmap_wd_dir (dir->wd, dir);
257      ip_unmap_path_dir (dir->path, dir);
258      ip_watched_dir_free (dir);
259    }
260
261  return TRUE;
262}
263
264
265static ip_watched_dir_t *
266ip_watched_dir_new (const char *path,
267                    gint32      wd)
268{
269  ip_watched_dir_t *dir = g_new0 (ip_watched_dir_t, 1);
270
271  dir->path = g_strdup (path);
272  dir->wd = wd;
273
274  return dir;
275}
276
277static void
278ip_watched_dir_free (ip_watched_dir_t *dir)
279{
280  g_assert (dir->subs == NULL);
281  g_free (dir->path);
282  g_free (dir);
283}
284
285static void
286ip_wd_delete (gpointer data,
287              gpointer user_data)
288{
289  ip_watched_dir_t *dir = data;
290  GList *l = NULL;
291
292  for (l = dir->subs; l; l = l->next)
293    {
294      inotify_sub *sub = l->data;
295      /* Add subscription to missing list */
296      _im_add (sub);
297    }
298  ip_unmap_all_subs (dir);
299  /* Unassociate the path and the directory */
300  ip_unmap_path_dir (dir->path, dir);
301  ip_watched_dir_free (dir);
302}
303
304static void
305ip_event_dispatch (GList      *dir_list,
306                   GList      *pair_dir_list,
307                   ik_event_t *event)
308{
309  GList *dirl;
310
311  if (!event)
312    return;
313
314  for (dirl = dir_list; dirl; dirl = dirl->next)
315    {
316      GList *subl;
317      ip_watched_dir_t *dir = dirl->data;
318
319      for (subl = dir->subs; subl; subl = subl->next)
320	{
321	  inotify_sub *sub = subl->data;
322
323	  /* If the subscription and the event
324	   * contain a filename and they don't
325	   * match, we don't deliver this event.
326	   */
327	  if (sub->filename &&
328	      event->name &&
329	      strcmp (sub->filename, event->name))
330	    continue;
331
332	  /* If the subscription has a filename
333	   * but this event doesn't, we don't
334	   * deliever this event.
335	   */
336	  if (sub->filename && !event->name)
337	    continue;
338
339	  /* FIXME: We might need to synthesize
340	   * DELETE/UNMOUNT events when
341	   * the filename doesn't match
342	   */
343
344	  event_callback (event, sub);
345	}
346    }
347
348  if (!event->pair)
349    return;
350
351  for (dirl = pair_dir_list; dirl; dirl = dirl->next)
352    {
353      GList *subl;
354      ip_watched_dir_t *dir = dirl->data;
355
356      for (subl = dir->subs; subl; subl = subl->next)
357	{
358	  inotify_sub *sub = subl->data;
359
360	  /* If the subscription and the event
361	   * contain a filename and they don't
362	   * match, we don't deliver this event.
363	   */
364	  if (sub->filename &&
365	      event->pair->name &&
366	      strcmp (sub->filename, event->pair->name))
367	    continue;
368
369	  /* If the subscription has a filename
370	   * but this event doesn't, we don't
371	   * deliever this event.
372	   */
373	  if (sub->filename && !event->pair->name)
374	    continue;
375
376	  /* FIXME: We might need to synthesize
377	   * DELETE/UNMOUNT events when
378	   * the filename doesn't match
379	   */
380
381	  event_callback (event->pair, sub);
382	}
383    }
384}
385
386static void
387ip_event_callback (ik_event_t *event)
388{
389  GList* dir_list = NULL;
390  GList* pair_dir_list = NULL;
391
392  dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd));
393
394  /* We can ignore the IGNORED events */
395  if (event->mask & IN_IGNORED)
396    {
397      _ik_event_free (event);
398      return;
399    }
400
401  if (event->pair)
402    pair_dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
403
404  if (event->mask & IP_INOTIFY_MASK)
405    ip_event_dispatch (dir_list, pair_dir_list, event);
406
407  /* We have to manage the missing list
408   * when we get an event that means the
409   * file has been deleted/moved/unmounted.
410   */
411  if (event->mask & IN_DELETE_SELF ||
412      event->mask & IN_MOVE_SELF ||
413      event->mask & IN_UNMOUNT)
414    {
415      /* Add all subscriptions to missing list */
416      g_list_foreach (dir_list, ip_wd_delete, NULL);
417      /* Unmap all directories attached to this wd */
418      ip_unmap_wd (event->wd);
419    }
420
421  _ik_event_free (event);
422}
423