| 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 (cb: ip_event_callback); |
| 118 | |
| 119 | if (!result) |
| 120 | return FALSE; |
| 121 | |
| 122 | path_dir_hash = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal); |
| 123 | sub_dir_hash = g_hash_table_new (hash_func: g_direct_hash, key_equal_func: g_direct_equal); |
| 124 | wd_dir_hash = g_hash_table_new (hash_func: g_direct_hash, key_equal_func: g_direct_equal); |
| 125 | wd_file_hash = g_hash_table_new (hash_func: g_direct_hash, key_equal_func: 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 (hash_table: path_dir_hash, key: dir->path, value: 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 (hash_table: sub_dir_hash, key: sub, value: dir); |
| 146 | dir->subs = g_list_prepend (list: dir->subs, data: 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 (hash_table: wd_dir_hash, GINT_TO_POINTER (wd)); |
| 157 | dir_list = g_list_prepend (list: dir_list, data: dir); |
| 158 | g_hash_table_replace (hash_table: wd_dir_hash, GINT_TO_POINTER (dir->wd), value: 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 (hash_table: wd_file_hash, GINT_TO_POINTER (wd)); |
| 169 | file_list = g_list_prepend (list: file_list, data: file); |
| 170 | g_hash_table_replace (hash_table: wd_file_hash, GINT_TO_POINTER (wd), value: 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 (hash_table: 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 (list: file_list, data: file); |
| 184 | if (file_list == NULL) |
| 185 | g_hash_table_remove (hash_table: wd_file_hash, GINT_TO_POINTER (wd)); |
| 186 | else |
| 187 | g_hash_table_replace (hash_table: wd_file_hash, GINT_TO_POINTER (wd), value: 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 (separator: "/" , dirname, filename, NULL); |
| 199 | file->filename = g_strdup (str: 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 (mem: file->filename); |
| 210 | g_free (mem: file->path); |
| 211 | g_free (mem: 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 (list: file->subs, data: 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 (path: file->path, |
| 229 | IP_INOTIFY_FILE_MASK, |
| 230 | err: &err); |
| 231 | |
| 232 | if (file->wd >= 0) |
| 233 | ip_map_wd_file (wd: 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 (path: file->path, wd: file->wd); |
| 243 | ip_unmap_wd_file (wd: 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 (hash_table: path_dir_hash, key: sub->dirname); |
| 261 | |
| 262 | if (dir == NULL) |
| 263 | { |
| 264 | IP_W ("Trying to add inotify watch " ); |
| 265 | wd = _ik_watch (path: sub->dirname, IP_INOTIFY_DIR_MASK|IN_ONLYDIR, err: &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 (path: sub->dirname, wd); |
| 278 | ip_map_wd_dir (wd, dir); |
| 279 | ip_map_path_dir (path: 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 (hash_table: dir->files_hash, key: sub->filename); |
| 290 | |
| 291 | if (file == NULL) |
| 292 | { |
| 293 | file = ip_watched_file_new (dirname: sub->dirname, filename: sub->filename); |
| 294 | g_hash_table_insert (hash_table: dir->files_hash, key: file->filename, value: 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 (hash_table: path_dir_hash, key: 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 (hash_table: 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 (list: dir_list, data: dir); |
| 325 | if (dir_list == NULL) |
| 326 | g_hash_table_remove (hash_table: wd_dir_hash, GINT_TO_POINTER (dir->wd)); |
| 327 | else |
| 328 | g_hash_table_replace (hash_table: wd_dir_hash, GINT_TO_POINTER (dir->wd), value: dir_list); |
| 329 | } |
| 330 | |
| 331 | static void |
| 332 | ip_unmap_wd (gint32 wd) |
| 333 | { |
| 334 | GList *dir_list = g_hash_table_lookup (hash_table: wd_dir_hash, GINT_TO_POINTER (wd)); |
| 335 | if (!dir_list) |
| 336 | return; |
| 337 | g_assert (wd >= 0); |
| 338 | g_hash_table_remove (hash_table: wd_dir_hash, GINT_TO_POINTER (wd)); |
| 339 | g_list_free (list: 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 (hash_table: sub_dir_hash, key: sub); |
| 348 | dir->subs = g_list_remove (list: dir->subs, data: sub); |
| 349 | |
| 350 | if (sub->hardlinks) |
| 351 | { |
| 352 | ip_watched_file_t *file; |
| 353 | |
| 354 | file = g_hash_table_lookup (hash_table: dir->files_hash, key: sub->filename); |
| 355 | file->subs = g_list_remove (list: file->subs, data: sub); |
| 356 | |
| 357 | if (file->subs == NULL) |
| 358 | { |
| 359 | g_hash_table_remove (hash_table: dir->files_hash, key: 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 (sub: 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 (hash_table: sub_dir_hash, key: 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 (path: dir->path, wd: dir->wd); |
| 388 | ip_unmap_wd_dir (wd: dir->wd, dir); |
| 389 | ip_unmap_path_dir (path: 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 (str: path); |
| 404 | dir->files_hash = g_hash_table_new (hash_func: g_str_hash, key_equal_func: 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 (mem: dir->path); |
| 416 | g_hash_table_unref (hash_table: dir->files_hash); |
| 417 | g_free (mem: 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 (path: 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 (s1: sub->filename, s2: event->name) && |
| 467 | (!event->pair || !event->pair->name || strcmp (s1: sub->filename, s2: 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 (hash_table: dir->files_hash, key: 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 (hash_table: wd_dir_hash, GINT_TO_POINTER (event->wd)); |
| 545 | file_list = g_hash_table_lookup (hash_table: 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 (hash_table: wd_dir_hash, GINT_TO_POINTER (event->pair->wd)); |
| 554 | file_list = g_hash_table_lookup (hash_table: 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: 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 (list: dir_list, func: ip_wd_delete, NULL); |
| 570 | /* Unmap all directories attached to this wd */ |
| 571 | ip_unmap_wd (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 (hash_table: 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 | } |
| 596 | |