Coverage Report

Created: 2025-07-23 08:13

/src/pango/subprojects/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
   SPDX-License-Identifier: LGPL-2.1-or-later
9
10
   This library is free software; you can redistribute it and/or
11
   modify it under the terms of the GNU Lesser General Public
12
   License as published by the Free Software Foundation; either
13
   version 2.1 of the License, or (at your option) any later version.
14
15
   This library is distributed in the hope that it will be useful,
16
   but WITHOUT ANY WARRANTY; without even the implied warranty of
17
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
   Lesser General Public License for more details.
19
20
   You should have received a copy of the GNU Lesser General Public License
21
   along with this library; if not, see <http://www.gnu.org/licenses/>.
22
23
   Authors:
24
     John McCutchan <john@johnmccutchan.com>
25
                 Ryan Lortie <desrt@desrt.ca>
26
*/
27
28
#include "config.h"
29
30
/* Don't put conflicting kernel types in the global namespace: */
31
#define __KERNEL_STRICT_NAMES
32
33
#include <sys/inotify.h>
34
#include <string.h>
35
#include <glib.h>
36
#include "inotify-kernel.h"
37
#include "inotify-path.h"
38
#include "inotify-missing.h"
39
40
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)
41
42
0
#define IP_INOTIFY_FILE_MASK (IN_MODIFY|IN_ATTRIB|IN_CLOSE_WRITE)
43
44
/* Older libcs don't have this */
45
#ifndef IN_ONLYDIR
46
#define IN_ONLYDIR 0  
47
#endif
48
49
typedef struct ip_watched_file_s {
50
  gchar *filename;
51
  gchar *path;
52
  gint32 wd;
53
54
  GList *subs;
55
} ip_watched_file_t;
56
57
typedef struct ip_watched_dir_s {
58
  char *path;
59
  /* TODO: We need to maintain a tree of watched directories
60
   * so that we can deliver move/delete events to sub folders.
61
   * Or the application could do it...
62
   */
63
  struct ip_watched_dir_s* parent;
64
  GList*   children;
65
66
  /* basename -> ip_watched_file_t
67
   * Maps basename to a ip_watched_file_t if the file is currently
68
   * being directly watched for changes (ie: 'hardlinks' mode).
69
   */
70
  GHashTable *files_hash;
71
72
  /* Inotify state */
73
  gint32 wd;
74
  
75
  /* List of inotify subscriptions */
76
  GList *subs;
77
} ip_watched_dir_t;
78
79
static gboolean     ip_debug_enabled = FALSE;
80
0
#define IP_W if (ip_debug_enabled) g_warning
81
82
/* path -> ip_watched_dir */
83
static GHashTable * path_dir_hash = NULL;
84
/* inotify_sub * -> ip_watched_dir *
85
 *
86
 * Each subscription is attached to a watched directory or it is on
87
 * the missing list
88
 */
89
static GHashTable * sub_dir_hash = NULL;
90
/* This hash holds GLists of ip_watched_dir_t *'s
91
 * We need to hold a list because symbolic links can share
92
 * the same wd
93
 */
94
static GHashTable * wd_dir_hash = NULL;
95
/* This hash holds GLists of ip_watched_file_t *'s
96
 * We need to hold a list because links can share
97
 * the same wd
98
 */
99
static GHashTable * wd_file_hash = NULL;
100
101
static ip_watched_dir_t *ip_watched_dir_new  (const char       *path,
102
                int               wd);
103
static void              ip_watched_dir_free (ip_watched_dir_t *dir);
104
static gboolean          ip_event_callback   (ik_event_t       *event);
105
106
107
static gboolean (*event_callback)(ik_event_t *event, inotify_sub *sub, gboolean file_event);
108
109
gboolean
110
_ip_startup (gboolean (*cb)(ik_event_t *event, inotify_sub *sub, gboolean file_event))
111
0
{
112
0
  static gboolean initialized = FALSE;
113
0
  static gboolean result = FALSE;
114
  
115
0
  if (initialized == TRUE)
116
0
    return result;
117
118
0
  event_callback = cb;
119
0
  result = _ik_startup (ip_event_callback);
120
121
0
  if (!result)
122
0
    return FALSE;
123
124
0
  path_dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
125
0
  sub_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
126
0
  wd_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
127
0
  wd_file_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
128
  
129
0
  initialized = TRUE;
130
0
  return TRUE;
131
0
}
132
133
static void
134
ip_map_path_dir (const char       *path, 
135
                 ip_watched_dir_t *dir)
136
0
{
137
0
  g_assert (path && dir);
138
0
  g_hash_table_insert (path_dir_hash, dir->path, dir);
139
0
}
140
141
static void
142
ip_map_sub_dir (inotify_sub      *sub, 
143
                ip_watched_dir_t *dir)
144
0
{
145
  /* Associate subscription and directory */
146
0
  g_assert (dir && sub);
147
0
  g_hash_table_insert (sub_dir_hash, sub, dir);
148
0
  dir->subs = g_list_prepend (dir->subs, sub);
149
0
}
150
151
static void
152
ip_map_wd_dir (gint32            wd, 
153
               ip_watched_dir_t *dir)
154
0
{
155
0
  GList *dir_list;
156
  
157
0
  g_assert (wd >= 0 && dir);
158
0
  dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
159
0
  dir_list = g_list_prepend (dir_list, dir);
160
0
  g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
161
0
}
162
163
static void
164
ip_map_wd_file (gint32             wd,
165
                ip_watched_file_t *file)
166
0
{
167
0
  GList *file_list;
168
169
0
  g_assert (wd >= 0 && file);
170
0
  file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
171
0
  file_list = g_list_prepend (file_list, file);
172
0
  g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
173
0
}
174
175
static void
176
ip_unmap_wd_file (gint32             wd,
177
                  ip_watched_file_t *file)
178
0
{
179
0
  GList *file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
180
181
0
  if (!file_list)
182
0
    return;
183
184
0
  g_assert (wd >= 0 && file);
185
0
  file_list = g_list_remove (file_list, file);
186
0
  if (file_list == NULL)
187
0
    g_hash_table_remove (wd_file_hash, GINT_TO_POINTER (wd));
188
0
  else
189
0
    g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
190
0
}
191
192
193
static ip_watched_file_t *
194
ip_watched_file_new (const gchar *dirname,
195
                     const gchar *filename)
196
0
{
197
0
  ip_watched_file_t *file;
198
199
0
  file = g_new0 (ip_watched_file_t, 1);
200
0
  file->path = g_strjoin ("/", dirname, filename, NULL);
201
0
  file->filename = g_strdup (filename);
202
0
  file->wd = -1;
203
204
0
  return file;
205
0
}
206
207
static void
208
ip_watched_file_free (ip_watched_file_t *file)
209
0
{
210
0
  g_assert (file->subs == NULL);
211
0
  g_free (file->filename);
212
0
  g_free (file->path);
213
0
  g_free (file);
214
0
}
215
216
static void
217
ip_watched_file_add_sub (ip_watched_file_t *file,
218
                         inotify_sub       *sub)
219
0
{
220
0
  file->subs = g_list_prepend (file->subs, sub);
221
0
}
222
223
static void
224
ip_watched_file_start (ip_watched_file_t *file)
225
0
{
226
0
  if (file->wd < 0)
227
0
    {
228
0
      gint err;
229
230
0
      file->wd = _ik_watch (file->path,
231
0
                            IP_INOTIFY_FILE_MASK,
232
0
                            &err);
233
234
0
      if (file->wd >= 0)
235
0
        ip_map_wd_file (file->wd, file);
236
0
    }
237
0
}
238
239
static void
240
ip_watched_file_stop (ip_watched_file_t *file)
241
0
{
242
0
  if (file->wd >= 0)
243
0
    {
244
0
      _ik_ignore (file->path, file->wd);
245
0
      ip_unmap_wd_file (file->wd, file);
246
0
      file->wd = -1;
247
0
    }
248
0
}
249
250
gboolean
251
_ip_start_watching (inotify_sub *sub)
252
0
{
253
0
  gint32 wd;
254
0
  int err;
255
0
  ip_watched_dir_t *dir;
256
  
257
0
  g_assert (sub);
258
0
  g_assert (!sub->cancelled);
259
0
  g_assert (sub->dirname);
260
  
261
0
  IP_W ("Starting to watch %s\n", sub->dirname);
262
0
  dir = g_hash_table_lookup (path_dir_hash, sub->dirname);
263
264
0
  if (dir == NULL)
265
0
    {
266
0
      IP_W ("Trying to add inotify watch ");
267
0
      wd = _ik_watch (sub->dirname, IP_INOTIFY_DIR_MASK|IN_ONLYDIR, &err);
268
0
      if (wd < 0)
269
0
        {
270
0
          IP_W ("Failed\n");
271
0
          return FALSE;
272
0
        }
273
0
      else
274
0
        {
275
          /* Create new watched directory and associate it with the
276
           * wd hash and path hash
277
           */
278
0
          IP_W ("Success\n");
279
0
          dir = ip_watched_dir_new (sub->dirname, wd);
280
0
          ip_map_wd_dir (wd, dir);
281
0
          ip_map_path_dir (sub->dirname, dir);
282
0
        }
283
0
    }
284
0
  else
285
0
    IP_W ("Already watching\n");
286
287
0
  if (sub->hardlinks)
288
0
    {
289
0
      ip_watched_file_t *file;
290
291
0
      file = g_hash_table_lookup (dir->files_hash, sub->filename);
292
293
0
      if (file == NULL)
294
0
        {
295
0
          file = ip_watched_file_new (sub->dirname, sub->filename);
296
0
          g_hash_table_insert (dir->files_hash, file->filename, file);
297
0
        }
298
299
0
      ip_watched_file_add_sub (file, sub);
300
0
      ip_watched_file_start (file);
301
0
    }
302
303
0
  ip_map_sub_dir (sub, dir);
304
  
305
0
  return TRUE;
306
0
}
307
308
static void
309
ip_unmap_path_dir (const char       *path, 
310
                   ip_watched_dir_t *dir)
311
0
{
312
0
  g_assert (path && dir);
313
0
  g_hash_table_remove (path_dir_hash, dir->path);
314
0
}
315
316
static void
317
ip_unmap_wd_dir (gint32            wd, 
318
                 ip_watched_dir_t *dir)
319
0
{
320
0
  GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
321
  
322
0
  if (!dir_list)
323
0
    return;
324
  
325
0
  g_assert (wd >= 0 && dir);
326
0
  dir_list = g_list_remove (dir_list, dir);
327
0
  if (dir_list == NULL) 
328
0
    g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (dir->wd));
329
0
  else
330
0
    g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
331
0
}
332
333
static void
334
ip_unmap_wd (gint32 wd)
335
0
{
336
0
  GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
337
0
  if (!dir_list)
338
0
    return;
339
0
  g_assert (wd >= 0);
340
0
  g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (wd));
341
0
  g_list_free (dir_list);
342
0
}
343
344
static void
345
ip_unmap_sub_dir (inotify_sub      *sub,
346
                  ip_watched_dir_t *dir)
347
0
{
348
0
  g_assert (sub && dir);
349
0
  g_hash_table_remove (sub_dir_hash, sub);
350
0
  dir->subs = g_list_remove (dir->subs, sub);
351
352
0
  if (sub->hardlinks)
353
0
    {
354
0
      ip_watched_file_t *file;
355
356
0
      file = g_hash_table_lookup (dir->files_hash, sub->filename);
357
0
      file->subs = g_list_remove (file->subs, sub);
358
359
0
      if (file->subs == NULL)
360
0
        {
361
0
          g_hash_table_remove (dir->files_hash, sub->filename);
362
0
          ip_watched_file_stop (file);
363
0
          ip_watched_file_free (file);
364
0
        }
365
0
    }
366
0
 }
367
368
static void
369
ip_unmap_all_subs (ip_watched_dir_t *dir)
370
0
{
371
0
  while (dir->subs != NULL)
372
0
    ip_unmap_sub_dir (dir->subs->data, dir);
373
0
}
374
375
gboolean
376
_ip_stop_watching (inotify_sub *sub)
377
0
{
378
0
  ip_watched_dir_t *dir = NULL;
379
  
380
0
  dir = g_hash_table_lookup (sub_dir_hash, sub);
381
0
  if (!dir) 
382
0
    return TRUE;
383
  
384
0
  ip_unmap_sub_dir (sub, dir);
385
  
386
  /* No one is subscribing to this directory any more */
387
0
  if (dir->subs == NULL)
388
0
    {
389
0
      _ik_ignore (dir->path, dir->wd);
390
0
      ip_unmap_wd_dir (dir->wd, dir);
391
0
      ip_unmap_path_dir (dir->path, dir);
392
0
      ip_watched_dir_free (dir);
393
0
    }
394
  
395
0
  return TRUE;
396
0
}
397
398
399
static ip_watched_dir_t *
400
ip_watched_dir_new (const char *path, 
401
                    gint32      wd)
402
0
{
403
0
  ip_watched_dir_t *dir = g_new0 (ip_watched_dir_t, 1);
404
  
405
0
  dir->path = g_strdup (path);
406
0
  dir->files_hash = g_hash_table_new (g_str_hash, g_str_equal);
407
0
  dir->wd = wd;
408
  
409
0
  return dir;
410
0
}
411
412
static void
413
ip_watched_dir_free (ip_watched_dir_t *dir)
414
0
{
415
0
  g_assert_cmpint (g_hash_table_size (dir->files_hash), ==, 0);
416
0
  g_assert (dir->subs == NULL);
417
0
  g_free (dir->path);
418
0
  g_hash_table_unref (dir->files_hash);
419
0
  g_free (dir);
420
0
}
421
422
static void
423
ip_wd_delete (gpointer data, 
424
              gpointer user_data)
425
0
{
426
0
  ip_watched_dir_t *dir = data;
427
0
  GList *l = NULL;
428
  
429
0
  for (l = dir->subs; l; l = l->next)
430
0
    {
431
0
      inotify_sub *sub = l->data;
432
      /* Add subscription to missing list */
433
0
      _im_add (sub);
434
0
    }
435
0
  ip_unmap_all_subs (dir);
436
  /* Unassociate the path and the directory */
437
0
  ip_unmap_path_dir (dir->path, dir);
438
0
  ip_watched_dir_free (dir);
439
0
}
440
441
static gboolean
442
ip_event_dispatch (GList      *dir_list, 
443
                   GList      *file_list,
444
                   ik_event_t *event)
445
0
{
446
0
  gboolean interesting = FALSE;
447
448
0
  GList *l;
449
  
450
0
  if (!event)
451
0
    return FALSE;
452
453
0
  for (l = dir_list; l; l = l->next)
454
0
    {
455
0
      GList *subl;
456
0
      ip_watched_dir_t *dir = l->data;
457
      
458
0
      for (subl = dir->subs; subl; subl = subl->next)
459
0
  {
460
0
    inotify_sub *sub = subl->data;
461
    
462
    /* If the subscription and the event
463
     * contain a filename and they don't
464
     * match, we don't deliver this event.
465
     */
466
0
    if (sub->filename &&
467
0
        event->name &&
468
0
        strcmp (sub->filename, event->name) &&
469
0
              (!event->pair || !event->pair->name || strcmp (sub->filename, event->pair->name)))
470
0
      continue;
471
    
472
    /* If the subscription has a filename
473
     * but this event doesn't, we don't
474
     * deliver this event.
475
     */
476
0
    if (sub->filename && !event->name)
477
0
      continue;
478
    
479
    /* If we're also watching the file directly
480
     * don't report events that will also be
481
     * reported on the file itself.
482
     */
483
0
    if (sub->hardlinks)
484
0
      {
485
0
        event->mask &= ~IP_INOTIFY_FILE_MASK;
486
0
        if (!event->mask)
487
0
    continue;
488
0
      }
489
    
490
    /* FIXME: We might need to synthesize
491
     * DELETE/UNMOUNT events when
492
     * the filename doesn't match
493
     */
494
    
495
0
    interesting |= event_callback (event, sub, FALSE);
496
497
0
          if (sub->hardlinks)
498
0
            {
499
0
              ip_watched_file_t *file;
500
501
0
              file = g_hash_table_lookup (dir->files_hash, sub->filename);
502
503
0
              if (file != NULL)
504
0
                {
505
0
                  if (event->mask & (IN_MOVED_FROM | IN_DELETE))
506
0
                    ip_watched_file_stop (file);
507
508
0
                  if (event->mask & (IN_MOVED_TO | IN_CREATE))
509
0
                    ip_watched_file_start (file);
510
0
                }
511
0
            }
512
0
        }
513
0
    }
514
515
0
  for (l = file_list; l; l = l->next)
516
0
    {
517
0
      ip_watched_file_t *file = l->data;
518
0
      GList *subl;
519
520
0
      for (subl = file->subs; subl; subl = subl->next)
521
0
        {
522
0
    inotify_sub *sub = subl->data;
523
524
0
    interesting |= event_callback (event, sub, TRUE);
525
0
        }
526
0
    }
527
528
0
  return interesting;
529
0
}
530
531
static gboolean
532
ip_event_callback (ik_event_t *event)
533
0
{
534
0
  gboolean interesting = FALSE;
535
0
  GList* dir_list = NULL;
536
0
  GList *file_list = NULL;
537
538
  /* We can ignore the IGNORED events. Likewise, if the event queue overflowed,
539
   * there is not much we can do to recover. */
540
0
  if (event->mask & (IN_IGNORED | IN_Q_OVERFLOW))
541
0
    {
542
0
      _ik_event_free (event);
543
0
      return TRUE;
544
0
    }
545
546
0
  dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd));
547
0
  file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->wd));
548
549
0
  if (event->mask & IP_INOTIFY_DIR_MASK)
550
0
    interesting |= ip_event_dispatch (dir_list, file_list, event);
551
552
  /* Only deliver paired events if the wds are separate */
553
0
  if (event->pair && event->pair->wd != event->wd)
554
0
    {
555
0
      dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
556
0
      file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->pair->wd));
557
558
0
      if (event->pair->mask & IP_INOTIFY_DIR_MASK)
559
0
        interesting |= ip_event_dispatch (dir_list, file_list, event->pair);
560
0
    }
561
562
  /* We have to manage the missing list
563
   * when we get an event that means the
564
   * file has been deleted/moved/unmounted.
565
   */
566
0
  if (event->mask & IN_DELETE_SELF ||
567
0
      event->mask & IN_MOVE_SELF ||
568
0
      event->mask & IN_UNMOUNT)
569
0
    {
570
      /* Add all subscriptions to missing list */
571
0
      g_list_foreach (dir_list, ip_wd_delete, NULL);
572
      /* Unmap all directories attached to this wd */
573
0
      ip_unmap_wd (event->wd);
574
0
    }
575
  
576
0
  _ik_event_free (event);
577
578
0
  return interesting;
579
0
}
580
581
const char *
582
_ip_get_path_for_wd (gint32 wd)
583
0
{
584
0
  GList *dir_list;
585
0
  ip_watched_dir_t *dir;
586
587
0
  g_assert (wd >= 0);
588
0
  dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
589
0
  if (dir_list)
590
0
    {
591
0
      dir = dir_list->data;
592
0
      if (dir)
593
0
  return dir->path;
594
0
    }
595
596
0
  return NULL;
597
0
}