gtestutils: Fix a typo in a documentation comment
[glib.git] / gio / inotify / inotify-path.c
blobb7e5135c6f31ad51eb4fa565928e9f95f33dae15
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
3 /* inotify-path.c - GVFS Monitor based on inotify.
5 Copyright (C) 2006 John McCutchan
6 Copyright (C) 2009 Codethink Limited
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
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 Library General Public License for more details.
18 You should have received a copy of the GNU Library General Public License
19 along with this library; if not, see <http://www.gnu.org/licenses/>.
21 Authors:
22 John McCutchan <john@johnmccutchan.com>
23 Ryan Lortie <desrt@desrt.ca>
26 #include "config.h"
28 /* Don't put conflicting kernel types in the global namespace: */
29 #define __KERNEL_STRICT_NAMES
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"
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)
40 #define IP_INOTIFY_FILE_MASK (IN_MODIFY|IN_ATTRIB|IN_CLOSE_WRITE)
42 /* Older libcs don't have this */
43 #ifndef IN_ONLYDIR
44 #define IN_ONLYDIR 0
45 #endif
47 typedef struct ip_watched_file_s {
48 gchar *filename;
49 gchar *path;
50 gint32 wd;
52 GList *subs;
53 } ip_watched_file_t;
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...
61 struct ip_watched_dir_s* parent;
62 GList* children;
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).
68 GHashTable *files_hash;
70 /* Inotify state */
71 gint32 wd;
73 /* List of inotify subscriptions */
74 GList *subs;
75 } ip_watched_dir_t;
77 static gboolean ip_debug_enabled = FALSE;
78 #define IP_W if (ip_debug_enabled) g_warning
80 /* path -> ip_watched_dir */
81 static GHashTable * path_dir_hash = NULL;
82 /* inotify_sub * -> ip_watched_dir *
84 * Each subscription is attached to a watched directory or it is on
85 * the missing list
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
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
97 static GHashTable * wd_file_hash = NULL;
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);
105 static gboolean (*event_callback)(ik_event_t *event, inotify_sub *sub, gboolean file_event);
107 gboolean
108 _ip_startup (gboolean (*cb)(ik_event_t *event, inotify_sub *sub, gboolean file_event))
110 static gboolean initialized = FALSE;
111 static gboolean result = FALSE;
113 if (initialized == TRUE)
114 return result;
116 event_callback = cb;
117 result = _ik_startup (ip_event_callback);
119 if (!result)
120 return FALSE;
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);
127 initialized = TRUE;
128 return TRUE;
131 static void
132 ip_map_path_dir (const char *path,
133 ip_watched_dir_t *dir)
135 g_assert (path && dir);
136 g_hash_table_insert (path_dir_hash, dir->path, dir);
139 static void
140 ip_map_sub_dir (inotify_sub *sub,
141 ip_watched_dir_t *dir)
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);
149 static void
150 ip_map_wd_dir (gint32 wd,
151 ip_watched_dir_t *dir)
153 GList *dir_list;
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);
161 static void
162 ip_map_wd_file (gint32 wd,
163 ip_watched_file_t *file)
165 GList *file_list;
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);
173 static void
174 ip_unmap_wd_file (gint32 wd,
175 ip_watched_file_t *file)
177 GList *file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
179 if (!file_list)
180 return;
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);
191 static ip_watched_file_t *
192 ip_watched_file_new (const gchar *dirname,
193 const gchar *filename)
195 ip_watched_file_t *file;
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;
202 return file;
205 static void
206 ip_watched_file_free (ip_watched_file_t *file)
208 g_assert (file->subs == NULL);
209 g_free (file->filename);
210 g_free (file->path);
213 static void
214 ip_watched_file_add_sub (ip_watched_file_t *file,
215 inotify_sub *sub)
217 file->subs = g_list_prepend (file->subs, sub);
220 static void
221 ip_watched_file_start (ip_watched_file_t *file)
223 if (file->wd < 0)
225 gint err;
227 file->wd = _ik_watch (file->path,
228 IP_INOTIFY_FILE_MASK,
229 &err);
231 if (file->wd >= 0)
232 ip_map_wd_file (file->wd, file);
236 static void
237 ip_watched_file_stop (ip_watched_file_t *file)
239 if (file->wd >= 0)
241 _ik_ignore (file->path, file->wd);
242 ip_unmap_wd_file (file->wd, file);
243 file->wd = -1;
247 gboolean
248 _ip_start_watching (inotify_sub *sub)
250 gint32 wd;
251 int err;
252 ip_watched_dir_t *dir;
254 g_assert (sub);
255 g_assert (!sub->cancelled);
256 g_assert (sub->dirname);
258 IP_W ("Starting to watch %s\n", sub->dirname);
259 dir = g_hash_table_lookup (path_dir_hash, sub->dirname);
261 if (dir == NULL)
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)
267 IP_W ("Failed\n");
268 return FALSE;
270 else
272 /* Create new watched directory and associate it with the
273 * wd hash and path hash
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);
281 else
282 IP_W ("Already watching\n");
284 if (sub->hardlinks)
286 ip_watched_file_t *file;
288 file = g_hash_table_lookup (dir->files_hash, sub->filename);
290 if (file == NULL)
292 file = ip_watched_file_new (sub->dirname, sub->filename);
293 g_hash_table_insert (dir->files_hash, file->filename, file);
296 ip_watched_file_add_sub (file, sub);
297 ip_watched_file_start (file);
300 ip_map_sub_dir (sub, dir);
302 return TRUE;
305 static void
306 ip_unmap_path_dir (const char *path,
307 ip_watched_dir_t *dir)
309 g_assert (path && dir);
310 g_hash_table_remove (path_dir_hash, dir->path);
313 static void
314 ip_unmap_wd_dir (gint32 wd,
315 ip_watched_dir_t *dir)
317 GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
319 if (!dir_list)
320 return;
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);
330 static void
331 ip_unmap_wd (gint32 wd)
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);
341 static void
342 ip_unmap_sub_dir (inotify_sub *sub,
343 ip_watched_dir_t *dir)
345 g_assert (sub && dir);
346 g_hash_table_remove (sub_dir_hash, sub);
347 dir->subs = g_list_remove (dir->subs, sub);
349 if (sub->hardlinks)
351 ip_watched_file_t *file;
353 file = g_hash_table_lookup (dir->files_hash, sub->filename);
354 file->subs = g_list_remove (file->subs, sub);
356 if (file->subs == NULL)
358 g_hash_table_remove (dir->files_hash, sub->filename);
359 ip_watched_file_stop (file);
360 ip_watched_file_free (file);
365 static void
366 ip_unmap_all_subs (ip_watched_dir_t *dir)
368 while (dir->subs != NULL)
369 ip_unmap_sub_dir (dir->subs->data, dir);
372 gboolean
373 _ip_stop_watching (inotify_sub *sub)
375 ip_watched_dir_t *dir = NULL;
377 dir = g_hash_table_lookup (sub_dir_hash, sub);
378 if (!dir)
379 return TRUE;
381 ip_unmap_sub_dir (sub, dir);
383 /* No one is subscribing to this directory any more */
384 if (dir->subs == NULL)
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);
392 return TRUE;
396 static ip_watched_dir_t *
397 ip_watched_dir_new (const char *path,
398 gint32 wd)
400 ip_watched_dir_t *dir = g_new0 (ip_watched_dir_t, 1);
402 dir->path = g_strdup (path);
403 dir->files_hash = g_hash_table_new (g_str_hash, g_str_equal);
404 dir->wd = wd;
406 return dir;
409 static void
410 ip_watched_dir_free (ip_watched_dir_t *dir)
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);
419 static void
420 ip_wd_delete (gpointer data,
421 gpointer user_data)
423 ip_watched_dir_t *dir = data;
424 GList *l = NULL;
426 for (l = dir->subs; l; l = l->next)
428 inotify_sub *sub = l->data;
429 /* Add subscription to missing list */
430 _im_add (sub);
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);
438 static gboolean
439 ip_event_dispatch (GList *dir_list,
440 GList *file_list,
441 ik_event_t *event)
443 gboolean interesting = FALSE;
445 GList *l;
447 if (!event)
448 return FALSE;
450 for (l = dir_list; l; l = l->next)
452 GList *subl;
453 ip_watched_dir_t *dir = l->data;
455 for (subl = dir->subs; subl; subl = subl->next)
457 inotify_sub *sub = subl->data;
459 /* If the subscription and the event
460 * contain a filename and they don't
461 * match, we don't deliver this event.
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;
469 /* If the subscription has a filename
470 * but this event doesn't, we don't
471 * deliver this event.
473 if (sub->filename && !event->name)
474 continue;
476 /* If we're also watching the file directly
477 * don't report events that will also be
478 * reported on the file itself.
480 if (sub->hardlinks)
482 event->mask &= ~IP_INOTIFY_FILE_MASK;
483 if (!event->mask)
484 continue;
487 /* FIXME: We might need to synthesize
488 * DELETE/UNMOUNT events when
489 * the filename doesn't match
492 interesting |= event_callback (event, sub, FALSE);
494 if (sub->hardlinks)
496 ip_watched_file_t *file;
498 file = g_hash_table_lookup (dir->files_hash, sub->filename);
500 if (file != NULL)
502 if (event->mask & (IN_MOVED_FROM | IN_DELETE))
503 ip_watched_file_stop (file);
505 if (event->mask & (IN_MOVED_TO | IN_CREATE))
506 ip_watched_file_start (file);
512 for (l = file_list; l; l = l->next)
514 ip_watched_file_t *file = l->data;
515 GList *subl;
517 for (subl = file->subs; subl; subl = subl->next)
519 inotify_sub *sub = subl->data;
521 interesting |= event_callback (event, sub, TRUE);
525 return interesting;
528 static gboolean
529 ip_event_callback (ik_event_t *event)
531 gboolean interesting = FALSE;
532 GList* dir_list = NULL;
533 GList *file_list = NULL;
535 /* We can ignore the IGNORED events */
536 if (event->mask & IN_IGNORED)
538 _ik_event_free (event);
539 return TRUE;
542 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd));
543 file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->wd));
545 if (event->mask & IP_INOTIFY_DIR_MASK)
546 interesting |= ip_event_dispatch (dir_list, file_list, event);
548 /* Only deliver paired events if the wds are separate */
549 if (event->pair && event->pair->wd != event->wd)
551 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
552 file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->pair->wd));
554 if (event->pair->mask & IP_INOTIFY_DIR_MASK)
555 interesting |= ip_event_dispatch (dir_list, file_list, event->pair);
558 /* We have to manage the missing list
559 * when we get an event that means the
560 * file has been deleted/moved/unmounted.
562 if (event->mask & IN_DELETE_SELF ||
563 event->mask & IN_MOVE_SELF ||
564 event->mask & IN_UNMOUNT)
566 /* Add all subscriptions to missing list */
567 g_list_foreach (dir_list, ip_wd_delete, NULL);
568 /* Unmap all directories attached to this wd */
569 ip_unmap_wd (event->wd);
572 _ik_event_free (event);
574 return interesting;
577 const char *
578 _ip_get_path_for_wd (gint32 wd)
580 GList *dir_list;
581 ip_watched_dir_t *dir;
583 g_assert (wd >= 0);
584 dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
585 if (dir_list)
587 dir = dir_list->data;
588 if (dir)
589 return dir->path;
592 return NULL;