Coverage Report

Created: 2025-06-13 06:55

/src/glib/gio/inotify/inotify-path.c
Line
Count
Source (jump to first uncovered line)
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
0
#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
0
#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
0
#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
0
{
110
0
  static gboolean initialized = FALSE;
111
0
  static gboolean result = FALSE;
112
  
113
0
  if (initialized == TRUE)
114
0
    return result;
115
116
0
  event_callback = cb;
117
0
  result = _ik_startup (ip_event_callback);
118
119
0
  if (!result)
120
0
    return FALSE;
121
122
0
  path_dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
123
0
  sub_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
124
0
  wd_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
125
0
  wd_file_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
126
  
127
0
  initialized = TRUE;
128
0
  return TRUE;
129
0
}
130
131
static void
132
ip_map_path_dir (const char       *path, 
133
                 ip_watched_dir_t *dir)
134
0
{
135
0
  g_assert (path && dir);
136
0
  g_hash_table_insert (path_dir_hash, dir->path, dir);
137
0
}
138
139
static void
140
ip_map_sub_dir (inotify_sub      *sub, 
141
                ip_watched_dir_t *dir)
142
0
{
143
  /* Associate subscription and directory */
144
0
  g_assert (dir && sub);
145
0
  g_hash_table_insert (sub_dir_hash, sub, dir);
146
0
  dir->subs = g_list_prepend (dir->subs, sub);
147
0
}
148
149
static void
150
ip_map_wd_dir (gint32            wd, 
151
               ip_watched_dir_t *dir)
152
0
{
153
0
  GList *dir_list;
154
  
155
0
  g_assert (wd >= 0 && dir);
156
0
  dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
157
0
  dir_list = g_list_prepend (dir_list, dir);
158
0
  g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
159
0
}
160
161
static void
162
ip_map_wd_file (gint32             wd,
163
                ip_watched_file_t *file)
164
0
{
165
0
  GList *file_list;
166
167
0
  g_assert (wd >= 0 && file);
168
0
  file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
169
0
  file_list = g_list_prepend (file_list, file);
170
0
  g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
171
0
}
172
173
static void
174
ip_unmap_wd_file (gint32             wd,
175
                  ip_watched_file_t *file)
176
0
{
177
0
  GList *file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
178
179
0
  if (!file_list)
180
0
    return;
181
182
0
  g_assert (wd >= 0 && file);
183
0
  file_list = g_list_remove (file_list, file);
184
0
  if (file_list == NULL)
185
0
    g_hash_table_remove (wd_file_hash, GINT_TO_POINTER (wd));
186
0
  else
187
0
    g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
188
0
}
189
190
191
static ip_watched_file_t *
192
ip_watched_file_new (const gchar *dirname,
193
                     const gchar *filename)
194
0
{
195
0
  ip_watched_file_t *file;
196
197
0
  file = g_new0 (ip_watched_file_t, 1);
198
0
  file->path = g_strjoin ("/", dirname, filename, NULL);
199
0
  file->filename = g_strdup (filename);
200
0
  file->wd = -1;
201
202
0
  return file;
203
0
}
204
205
static void
206
ip_watched_file_free (ip_watched_file_t *file)
207
0
{
208
0
  g_assert (file->subs == NULL);
209
0
  g_free (file->filename);
210
0
  g_free (file->path);
211
0
  g_free (file);
212
0
}
213
214
static void
215
ip_watched_file_add_sub (ip_watched_file_t *file,
216
                         inotify_sub       *sub)
217
0
{
218
0
  file->subs = g_list_prepend (file->subs, sub);
219
0
}
220
221
static void
222
ip_watched_file_start (ip_watched_file_t *file)
223
0
{
224
0
  if (file->wd < 0)
225
0
    {
226
0
      gint err;
227
228
0
      file->wd = _ik_watch (file->path,
229
0
                            IP_INOTIFY_FILE_MASK,
230
0
                            &err);
231
232
0
      if (file->wd >= 0)
233
0
        ip_map_wd_file (file->wd, file);
234
0
    }
235
0
}
236
237
static void
238
ip_watched_file_stop (ip_watched_file_t *file)
239
0
{
240
0
  if (file->wd >= 0)
241
0
    {
242
0
      _ik_ignore (file->path, file->wd);
243
0
      ip_unmap_wd_file (file->wd, file);
244
0
      file->wd = -1;
245
0
    }
246
0
}
247
248
gboolean
249
_ip_start_watching (inotify_sub *sub)
250
0
{
251
0
  gint32 wd;
252
0
  int err;
253
0
  ip_watched_dir_t *dir;
254
  
255
0
  g_assert (sub);
256
0
  g_assert (!sub->cancelled);
257
0
  g_assert (sub->dirname);
258
  
259
0
  IP_W ("Starting to watch %s\n", sub->dirname);
260
0
  dir = g_hash_table_lookup (path_dir_hash, sub->dirname);
261
262
0
  if (dir == NULL)
263
0
    {
264
0
      IP_W ("Trying to add inotify watch ");
265
0
      wd = _ik_watch (sub->dirname, IP_INOTIFY_DIR_MASK|IN_ONLYDIR, &err);
266
0
      if (wd < 0)
267
0
        {
268
0
          IP_W ("Failed\n");
269
0
          return FALSE;
270
0
        }
271
0
      else
272
0
        {
273
          /* Create new watched directory and associate it with the
274
           * wd hash and path hash
275
           */
276
0
          IP_W ("Success\n");
277
0
          dir = ip_watched_dir_new (sub->dirname, wd);
278
0
          ip_map_wd_dir (wd, dir);
279
0
          ip_map_path_dir (sub->dirname, dir);
280
0
        }
281
0
    }
282
0
  else
283
0
    IP_W ("Already watching\n");
284
285
0
  if (sub->hardlinks)
286
0
    {
287
0
      ip_watched_file_t *file;
288
289
0
      file = g_hash_table_lookup (dir->files_hash, sub->filename);
290
291
0
      if (file == NULL)
292
0
        {
293
0
          file = ip_watched_file_new (sub->dirname, sub->filename);
294
0
          g_hash_table_insert (dir->files_hash, file->filename, file);
295
0
        }
296
297
0
      ip_watched_file_add_sub (file, sub);
298
0
      ip_watched_file_start (file);
299
0
    }
300
301
0
  ip_map_sub_dir (sub, dir);
302
  
303
0
  return TRUE;
304
0
}
305
306
static void
307
ip_unmap_path_dir (const char       *path, 
308
                   ip_watched_dir_t *dir)
309
0
{
310
0
  g_assert (path && dir);
311
0
  g_hash_table_remove (path_dir_hash, dir->path);
312
0
}
313
314
static void
315
ip_unmap_wd_dir (gint32            wd, 
316
                 ip_watched_dir_t *dir)
317
0
{
318
0
  GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
319
  
320
0
  if (!dir_list)
321
0
    return;
322
  
323
0
  g_assert (wd >= 0 && dir);
324
0
  dir_list = g_list_remove (dir_list, dir);
325
0
  if (dir_list == NULL) 
326
0
    g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (dir->wd));
327
0
  else
328
0
    g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
329
0
}
330
331
static void
332
ip_unmap_wd (gint32 wd)
333
0
{
334
0
  GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
335
0
  if (!dir_list)
336
0
    return;
337
0
  g_assert (wd >= 0);
338
0
  g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (wd));
339
0
  g_list_free (dir_list);
340
0
}
341
342
static void
343
ip_unmap_sub_dir (inotify_sub      *sub,
344
                  ip_watched_dir_t *dir)
345
0
{
346
0
  g_assert (sub && dir);
347
0
  g_hash_table_remove (sub_dir_hash, sub);
348
0
  dir->subs = g_list_remove (dir->subs, sub);
349
350
0
  if (sub->hardlinks)
351
0
    {
352
0
      ip_watched_file_t *file;
353
354
0
      file = g_hash_table_lookup (dir->files_hash, sub->filename);
355
0
      file->subs = g_list_remove (file->subs, sub);
356
357
0
      if (file->subs == NULL)
358
0
        {
359
0
          g_hash_table_remove (dir->files_hash, sub->filename);
360
0
          ip_watched_file_stop (file);
361
0
          ip_watched_file_free (file);
362
0
        }
363
0
    }
364
0
 }
365
366
static void
367
ip_unmap_all_subs (ip_watched_dir_t *dir)
368
0
{
369
0
  while (dir->subs != NULL)
370
0
    ip_unmap_sub_dir (dir->subs->data, dir);
371
0
}
372
373
gboolean
374
_ip_stop_watching (inotify_sub *sub)
375
0
{
376
0
  ip_watched_dir_t *dir = NULL;
377
  
378
0
  dir = g_hash_table_lookup (sub_dir_hash, sub);
379
0
  if (!dir) 
380
0
    return TRUE;
381
  
382
0
  ip_unmap_sub_dir (sub, dir);
383
  
384
  /* No one is subscribing to this directory any more */
385
0
  if (dir->subs == NULL)
386
0
    {
387
0
      _ik_ignore (dir->path, dir->wd);
388
0
      ip_unmap_wd_dir (dir->wd, dir);
389
0
      ip_unmap_path_dir (dir->path, dir);
390
0
      ip_watched_dir_free (dir);
391
0
    }
392
  
393
0
  return TRUE;
394
0
}
395
396
397
static ip_watched_dir_t *
398
ip_watched_dir_new (const char *path, 
399
                    gint32      wd)
400
0
{
401
0
  ip_watched_dir_t *dir = g_new0 (ip_watched_dir_t, 1);
402
  
403
0
  dir->path = g_strdup (path);
404
0
  dir->files_hash = g_hash_table_new (g_str_hash, g_str_equal);
405
0
  dir->wd = wd;
406
  
407
0
  return dir;
408
0
}
409
410
static void
411
ip_watched_dir_free (ip_watched_dir_t *dir)
412
0
{
413
0
  g_assert_cmpint (g_hash_table_size (dir->files_hash), ==, 0);
414
0
  g_assert (dir->subs == NULL);
415
0
  g_free (dir->path);
416
0
  g_hash_table_unref (dir->files_hash);
417
0
  g_free (dir);
418
0
}
419
420
static void
421
ip_wd_delete (gpointer data, 
422
              gpointer user_data)
423
0
{
424
0
  ip_watched_dir_t *dir = data;
425
0
  GList *l = NULL;
426
  
427
0
  for (l = dir->subs; l; l = l->next)
428
0
    {
429
0
      inotify_sub *sub = l->data;
430
      /* Add subscription to missing list */
431
0
      _im_add (sub);
432
0
    }
433
0
  ip_unmap_all_subs (dir);
434
  /* Unassociate the path and the directory */
435
0
  ip_unmap_path_dir (dir->path, dir);
436
0
  ip_watched_dir_free (dir);
437
0
}
438
439
static gboolean
440
ip_event_dispatch (GList      *dir_list, 
441
                   GList      *file_list,
442
                   ik_event_t *event)
443
0
{
444
0
  gboolean interesting = FALSE;
445
446
0
  GList *l;
447
  
448
0
  if (!event)
449
0
    return FALSE;
450
451
0
  for (l = dir_list; l; l = l->next)
452
0
    {
453
0
      GList *subl;
454
0
      ip_watched_dir_t *dir = l->data;
455
      
456
0
      for (subl = dir->subs; subl; subl = subl->next)
457
0
  {
458
0
    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
0
    if (sub->filename &&
465
0
        event->name &&
466
0
        strcmp (sub->filename, event->name) &&
467
0
              (!event->pair || !event->pair->name || strcmp (sub->filename, event->pair->name)))
468
0
      continue;
469
    
470
    /* If the subscription has a filename
471
     * but this event doesn't, we don't
472
     * deliver this event.
473
     */
474
0
    if (sub->filename && !event->name)
475
0
      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
0
    if (sub->hardlinks)
482
0
      {
483
0
        event->mask &= ~IP_INOTIFY_FILE_MASK;
484
0
        if (!event->mask)
485
0
    continue;
486
0
      }
487
    
488
    /* FIXME: We might need to synthesize
489
     * DELETE/UNMOUNT events when
490
     * the filename doesn't match
491
     */
492
    
493
0
    interesting |= event_callback (event, sub, FALSE);
494
495
0
          if (sub->hardlinks)
496
0
            {
497
0
              ip_watched_file_t *file;
498
499
0
              file = g_hash_table_lookup (dir->files_hash, sub->filename);
500
501
0
              if (file != NULL)
502
0
                {
503
0
                  if (event->mask & (IN_MOVED_FROM | IN_DELETE))
504
0
                    ip_watched_file_stop (file);
505
506
0
                  if (event->mask & (IN_MOVED_TO | IN_CREATE))
507
0
                    ip_watched_file_start (file);
508
0
                }
509
0
            }
510
0
        }
511
0
    }
512
513
0
  for (l = file_list; l; l = l->next)
514
0
    {
515
0
      ip_watched_file_t *file = l->data;
516
0
      GList *subl;
517
518
0
      for (subl = file->subs; subl; subl = subl->next)
519
0
        {
520
0
    inotify_sub *sub = subl->data;
521
522
0
    interesting |= event_callback (event, sub, TRUE);
523
0
        }
524
0
    }
525
526
0
  return interesting;
527
0
}
528
529
static gboolean
530
ip_event_callback (ik_event_t *event)
531
0
{
532
0
  gboolean interesting = FALSE;
533
0
  GList* dir_list = NULL;
534
0
  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
0
  if (event->mask & (IN_IGNORED | IN_Q_OVERFLOW))
539
0
    {
540
0
      _ik_event_free (event);
541
0
      return TRUE;
542
0
    }
543
544
0
  dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd));
545
0
  file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->wd));
546
547
0
  if (event->mask & IP_INOTIFY_DIR_MASK)
548
0
    interesting |= ip_event_dispatch (dir_list, file_list, event);
549
550
  /* Only deliver paired events if the wds are separate */
551
0
  if (event->pair && event->pair->wd != event->wd)
552
0
    {
553
0
      dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
554
0
      file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->pair->wd));
555
556
0
      if (event->pair->mask & IP_INOTIFY_DIR_MASK)
557
0
        interesting |= ip_event_dispatch (dir_list, file_list, event->pair);
558
0
    }
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
0
  if (event->mask & IN_DELETE_SELF ||
565
0
      event->mask & IN_MOVE_SELF ||
566
0
      event->mask & IN_UNMOUNT)
567
0
    {
568
      /* Add all subscriptions to missing list */
569
0
      g_list_foreach (dir_list, ip_wd_delete, NULL);
570
      /* Unmap all directories attached to this wd */
571
0
      ip_unmap_wd (event->wd);
572
0
    }
573
  
574
0
  _ik_event_free (event);
575
576
0
  return interesting;
577
0
}
578
579
const char *
580
_ip_get_path_for_wd (gint32 wd)
581
0
{
582
0
  GList *dir_list;
583
0
  ip_watched_dir_t *dir;
584
585
0
  g_assert (wd >= 0);
586
0
  dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
587
0
  if (dir_list)
588
0
    {
589
0
      dir = dir_list->data;
590
0
      if (dir)
591
0
  return dir->path;
592
0
    }
593
594
0
  return NULL;
595
0
}